Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ For each entry, **Upgrade impact** classifies the change against the [three-buck

## Unreleased

### `/upgrade` stamps the release's commit; the packaged smoke test names the plugin

The new upgrade eval found `/aplyca-adf:upgrade` stamping `v1.0.6 · 130753a` — an annotated tag's own
ID, from `git rev-parse v1.0.6` — instead of the release's commit, `ab56cb6`, in the stamp, the branch,
and the commit message. `/upgrade` and `/adopt` now name `git rev-parse --short '<tag>^{commit}'`.
The packaged smoke test in `docs/SETUP.md` sets `CLAUDE_PLUGIN_ROOT`, which the plugin's hooks need
since v1.0.2.
**Upgrade impact:** framework-internal; a project whose stamp names a tag's ID can correct it to the
commit by hand — `/aplyca-adf:upgrade` diffs from either.

### The packaged install's two ways in, checked in real sessions

The adopt suite gains a `packaged` case — `/aplyca-adf:adopt` on a new project, choosing the packaged
install — and a new `upgrade` suite's `switch-to-packaged` case has `/aplyca-adf:upgrade` move a
committed adoption at v1.0.0 to the newest release and switch it to the packaged install. Both check
the result with `evals/dynamic/check-packaged.sh`, which also checks that the stamp names the release's
commit. The runner passes this checkout's path to a suite's `project.sh`. Report:
`evals/dynamic/reports/2026-10-04-packaged-paths.md`.
**Upgrade impact:** framework-internal.

### The plugin's hooks, checked in real sessions

A `plugin-hooks` suite for the session evals: each of the `aplyca-adf` plugin's hooks, driven in a
Expand Down
2 changes: 1 addition & 1 deletion docs/SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ v1.0.0 or later; its entry in [`CHANGELOG.md`](../CHANGELOG.md) says what it bri
with the pinned release and its commit. `install: packaged` is what turns the plugin's copies on.

**Verify** as below, with two differences. Pipe the hook samples to the plugin's scripts, with the
project named: `CLAUDE_PROJECT_DIR="$PWD" <marketplace folder>/plugins/aplyca-adf/hooks/guard-git.sh`,
project and the plugin named: `CLAUDE_PROJECT_DIR="$PWD" CLAUDE_PLUGIN_ROOT=<marketplace folder>/plugins/aplyca-adf <marketplace folder>/plugins/aplyca-adf/hooks/guard-git.sh`,
where the marketplace folder is the `installLocation` of `aplyca` in
`claude plugin marketplace list --json`. And in a new session, `/aplyca-adf:triage` is offered.

Expand Down
18 changes: 16 additions & 2 deletions evals/dynamic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Fixture-based evals that **invoke an AI with an input prompt and grade the outpu
```
dynamic/
README.md - this file
check-packaged.sh - checks a project's packaged layout (adopt's and upgrade's inspect.sh use it)
run-dynamic.md - manual + automated execution patterns
fixtures/
write-spec/
Expand All @@ -20,7 +21,11 @@ dynamic/
adopt/
project.sh - builds a new project (no commits, nothing built) in place of the newsletter one
inspect.sh - records each run's end state: commits, stamp, planned entries, decision records
... - adopting from one line with the framework's address; /adopt in a new project
... - adopting from one line with the framework's address; /adopt in a new project; on the packaged install
upgrade/
project.sh - builds a committed adoption at v1.0.0, from that tag
inspect.sh - records the end state; checks the switch to the packaged install
... - /upgrade switching a committed project to the packaged install
plugin-hooks/
project.sh - builds a project on the packaged install, with something for each hook to stop
inspect.sh - checks that the case's hook fired (or stood down): ✓ or ✘ per check
Expand All @@ -40,7 +45,7 @@ Each fixture has two files:

## Running

### Session evals — automated (triage, debug, adopt, plugin-hooks)
### Session evals — automated (triage, debug, adopt, upgrade, plugin-hooks)

`run-session-evals.sh` runs every fixture in one suite — `fixtures/triage/` by default, or
`--suite debug` — against real Claude Code sessions: it builds a fictional project in a temp
Expand All @@ -58,8 +63,17 @@ signal without installing anything.
./run-session-evals.sh --suite debug # the /debug cases
./run-session-evals.sh --suite adopt --source "$PWD/../.." # adoption, against this checkout
./run-session-evals.sh --suite plugin-hooks # the plugin's hooks, on Haiku
./run-session-evals.sh --suite upgrade --models sonnet # /upgrade, switching to the packaged install
```

The adopt suite's **packaged** case, and the **upgrade** suite's **switch-to-packaged**, check the
packaged install's two ways in: adopting on it, and switching a committed project to it. The upgrade
suite builds a committed adoption at v1.0.0 from that tag (`project.sh` gets this checkout's path), and
`/aplyca-adf:upgrade` moves it to the newest release. Both cases' `inspect.sh` run
`check-packaged.sh`: the stamp naming the newest release and its commit, no framework machinery committed, the plugin pinned to that release and
turned on, no `hooks` block, the stamp, and the names people type — each marked ✓ or ✘ — plus the PDR
that records the choice. Both are long sessions; run them on one model.

The **plugin-hooks** suite checks the `aplyca-adf` plugin's hooks in real sessions, which the static
hook tests can't: that Claude Code runs them from the plugin, and that what they print reaches the
session. Its project is on the packaged install, with the plugin loaded from this checkout per session.
Expand Down
26 changes: 26 additions & 0 deletions evals/dynamic/check-packaged.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# Checks that a project is laid out for the packaged install (decision 0016, docs/SETUP.md § Packaged
# install), pinned to <release>: no framework machinery committed, the plugin pinned and turned on, no
# hooks block, the stamp, and the names people type. Prints ✓ or ✘ per check. Read-only.
# Usage: check-packaged.sh <project> <release, e.g. v1.0.6> <the release's commit, short>
#
cd "$1" || exit 1
release="$2" commit="$3"
check() { if eval "$2"; then echo "- ✓ $1"; else echo "- ✘ $1"; fi; }
check "the stamp names $release, its commit $commit, and \`install: packaged\`" \
"head -1 CLAUDE.md | grep -q -F 'Skeleton source: $release · $commit' && head -1 CLAUDE.md | grep -q -F 'install: packaged'"
check "no framework skills, agents, or workflows committed" \
"[ ! -e .claude/skills/triage ] && [ ! -e .claude/skills/write-spec ] && [ ! -e .claude/agents ] && [ ! -e .claude/workflows ]"
check "\`.claude/hooks/\` holds only \`config.sh\`" "[ -f .claude/hooks/config.sh ] && [ \"\$(ls .claude/hooks)\" = config.sh ]"
check "the settings: no \`hooks\` block, the marketplace pinned to $release, \`aplyca-adf\` turned on" "python3 - '$release' <<'PY'
import json, sys
s = json.load(open('.claude/settings.json'))
source = s.get('extraKnownMarketplaces', {}).get('aplyca', {}).get('source', {})
ok = 'hooks' not in s and source.get('repo', '').lower() == 'aplyca/agenticdevelopmentframework' \
and source.get('ref') == sys.argv[1] and s.get('enabledPlugins', {}).get('aplyca-adf@aplyca') is True
sys.exit(0 if ok else 1)
PY"
check "\`CLAUDE.md\` says the project uses the packaged install" "grep -q 'uses the packaged install' CLAUDE.md"
check "\`DEV-SETUP.md\` gives the commands by their full names" "grep -q '/aplyca-adf:triage' docs/getting-started/DEV-SETUP.md"
check "everything is committed" "[ -z \"\$(git status --short)\" ]"
10 changes: 9 additions & 1 deletion evals/dynamic/fixtures/adopt/inspect.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
#
# Prints an adopt run's end state for grading: commits per branch, what was added, the version stamp,
# the planned entries, the decision records, and whether the settings parse. Read-only.
# the planned entries, the decision records, and whether the settings parse; for the packaged case,
# checks the packaged layout (../../check-packaged.sh). Read-only.
#
cd "$1" || exit 1
if [ ! -f CLAUDE.md ]; then
Expand Down Expand Up @@ -51,3 +52,10 @@ for f in GEMINI.md .agents .cursor evals .github/pull_request_template.md .githo
[ -e "$f" ] && echo "present: $f" || echo "absent: $f"
done
echo '```'
if [ "${3:-}" = packaged ]; then
echo "### Checks — the packaged install"
release="$(git -C "$FW" tag --list 'v*' --sort=-v:refname | head -1)"
bash "$(dirname "$0")/../../check-packaged.sh" . "$release" "$(git -C "$FW" rev-parse --short "$release^{commit}")"
if grep -q -i 'packaged' docs/process/0001-*.md 2>/dev/null; then echo "- ✓ PDR-0001 records the packaged install"
else echo "- ✘ PDR-0001 records the packaged install"; fi
fi
29 changes: 29 additions & 0 deletions evals/dynamic/fixtures/adopt/packaged.expected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Expected — adopt: a new project on the packaged install

The session should satisfy ALL of these invariants. `inspect.sh` checks the end state ones marked
(auto).

## First turn

- [ ] Recognizes a new project and asks for the planned stack and the other facts in one round,
including how to install (committed or packaged), with its recommendations
- [ ] Copies nothing before the answers

## End state (after the follow-up)

- [ ] `main` has exactly one commit, the README; the adoption is on its own branch
- [ ] (auto) `CLAUDE.md`'s stamp names the newest release tag and `install: packaged`
- [ ] (auto) No framework skills, agents, or workflows are committed; `.claude/hooks/` holds only
`config.sh`
- [ ] (auto) `.claude/settings.json` has no `hooks` block, pins the `aplyca` marketplace to the newest
release (`"ref"`), and turns on `aplyca-adf@aplyca`
- [ ] (auto) `CLAUDE.md` has the names note ("This project uses the packaged install")
- [ ] (auto) `docs/getting-started/DEV-SETUP.md` gives the key commands by their full names
- [ ] (auto) PDR-0001 records the packaged install and why
- [ ] (auto) Everything is committed
- [ ] The verification checks the packaged install (the plugin's hook scripts, run with
`CLAUDE_PROJECT_DIR` set) instead of the project's own

## Always

- [ ] No outward action (push, pull request) is taken without an ask
38 changes: 38 additions & 0 deletions evals/dynamic/fixtures/adopt/packaged.input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Input — adopt: a new project on the packaged install

<!-- run: plugin-dir -->

This fixture verifies `/adopt` with the packaged install (decision 0016): given a team that works in
Claude Code only, it commits only the project's own layer — no framework skills, agents, workflows,
or hook scripts — pins the `aplyca-adf` plugin to the newest release tag, stamps
`install: packaged`, and tells people the names they type.

## Repository context to give the AI

The same new project as `new-project`: a git repository with no commits and a README that says
nothing is built yet. The installer plugin is loaded from this checkout for the session only
(`--plugin-dir`). The framework's releases are tagged on GitHub, so the packaged install is available.
There is no remote.

## Prompt to give the AI

```
/aplyca-adf:adopt
```

## Follow-up

```
Yes, make the first commit. The plan: Next.js 15 with TypeScript, pnpm, Vitest for unit tests and
Playwright end to end, hosted on Vercel, Contentful as the CMS. Branching: feature branches into
main. Requirements come as GitHub issues; no tracker server. One developer with one agent session
at a time, and no stakeholder updates. No sensitive areas yet. We work in Claude Code only, so use
the packaged install. No custom skills and no modules. The decider is the tech lead. Go ahead with
the adoption; don't push.
```

## What to do with this fixture

1. Run it with `run-session-evals.sh --suite adopt --cases packaged --models sonnet`.
2. `inspect.sh` checks the packaged layout and prints ✓ or ✘ under the end state.
3. Compare against `packaged.expected.md`.
29 changes: 29 additions & 0 deletions evals/dynamic/fixtures/upgrade/inspect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
#
# Prints an upgrade run's end state — branches and commits, the stamp, the settings — and, for the
# switch-to-packaged case, checks the packaged layout (../../check-packaged.sh), the PDR that records
# the switch, and that main is untouched. Read-only.
# Usage: inspect.sh <run copy> <session output .jsonl> <case>
#
cd "$1" || exit 1
echo "### Branches and commits"
echo '```'
for b in $(git for-each-ref --format='%(refname:short)' refs/heads); do echo "--- $b"; git log --oneline "$b" 2>&1 | head -5; done
echo "--- current: $(git branch --show-current) · working tree: $(git status --short | wc -l | tr -d ' ') uncommitted paths"
echo '```'
echo "### CLAUDE.md, first line · .claude/ · docs/process/"
echo '```'
head -1 CLAUDE.md
ls .claude .claude/hooks 2>&1
ls docs/process
echo '```'
if [ "${3:-}" = switch-to-packaged ]; then
echo "### Checks — the switch to the packaged install"
release="$(git -C "$FW" tag --list 'v*' --sort=-v:refname | head -1)"
bash "$(dirname "$0")/../../check-packaged.sh" . "$release" "$(git -C "$FW" rev-parse --short "$release^{commit}")"
check() { if eval "$2"; then echo "- ✓ $1"; else echo "- ✘ $1"; fi; }
check "a new PDR records the switch to the packaged install" \
"ls docs/process | grep -v -E '^(0000|0001)-|README' | grep -q . && grep -l -i 'packaged' \$(ls docs/process/*.md | grep -v -E '/(0000|0001)-|README') >/dev/null"
check "PDR-0001 is marked amended" "grep -q -i 'amend' docs/process/0001-*.md"
check "\`main\` is untouched: the switch is on its own branch" "[ \"\$(git rev-list --count main)\" = 1 ] && [ \"\$(git branch --show-current)\" != main ]"
fi
40 changes: 40 additions & 0 deletions evals/dynamic/fixtures/upgrade/project.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
#
# The project for the upgrade cases: a committed adoption at v1.0.0 — the skeleton from the framework's
# v1.0.0 tag, without the Antigravity and Cursor layers (a team that works in Claude Code only), the
# plugin pinned to v1.0.0, a filled-in PDR-0001, and the stamp — all committed on main.
# Usage: project.sh <project> <framework checkout>
#
set -euo pipefail
cd "$1"
FW="$2"
git init -q -b main && git config user.email dev@example.com && git config user.name dev
git -C "$FW" archive v1.0.0 skeleton | tar -x --strip-components=1
rm -rf .agents .cursor GEMINI.md evals
python3 - <<'PY'
import json, re
from pathlib import Path
def fill(path, pairs):
p = Path(path); s = p.read_text()
for old, new in pairs:
assert old in s, (path, old[:40]); s = s.replace(old, new)
p.write_text(s)
claude = Path("CLAUDE.md").read_text().split("\n", 1)
Path("CLAUDE.md").write_text("<!-- Skeleton source: v1.0.0 · cf1776b (2026-10-02) · modules: none — see docs/UPGRADING.md in AgenticDevelopmentFramework -->\n" + claude[1])
fill("CLAUDE.md", [("# [PROJECT NAME] — Claude Code", "# Newsletter Site — Claude Code")])
fill("AGENTS.md", [("# [PROJECT NAME]", "# Newsletter Site"),
("[One paragraph: what this project is, who uses it, and its stage — PoC, MVP, or production.]",
"The marketing site for a publisher: article pages and a newsletter signup. In production.")])
settings = json.loads(Path(".claude/settings.json").read_text())
settings["extraKnownMarketplaces"] = {"aplyca": {"source": {"source": "github", "repo": "aplyca/AgenticDevelopmentFramework", "ref": "v1.0.0"}}}
settings["enabledPlugins"] = {"aplyca-adf@aplyca": True}
Path(".claude/settings.json").write_text(json.dumps(settings, indent=2) + "\n")
template = Path("docs/process/0000-pdr-template.md").read_text()
pdr = re.sub(r"^# PDR NNNN: .*$", "# PDR 0001: Adopt the AI-assisted development workflow", template, flags=re.M)
pdr = pdr.replace("proposed | accepted | superseded by PDR-NNNN", "accepted").replace("YYYY-MM-DD", "2026-10-02") \
.replace("[who]", "the tech lead").replace('[PDR-NNNN, or "—"]', "—")
Path("docs/process/0001-adopt-ai-assisted-workflow.md").write_text(pdr)
fill("docs/process/README.md", [("| 0001 | [`0001-adopt-ai-assisted-workflow.md` — Adopt the AI-assisted development workflow] | [accepted] |",
"| 0001 | [Adopt the AI-assisted development workflow](0001-adopt-ai-assisted-workflow.md) — committed install | accepted |")])
PY
git add -A && git commit -q -m "docs: adopt the Agentic Development Framework (v1.0.0)"
28 changes: 28 additions & 0 deletions evals/dynamic/fixtures/upgrade/switch-to-packaged.expected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Expected — upgrade: a committed project switches to the packaged install

The session should satisfy ALL of these invariants. `inspect.sh` checks the end state ones marked
(auto).

## First turn

- [ ] Reads the stamp (v1.0.0, committed) and moves to the newest release tag, saying whether the
jump crosses a major version
- [ ] Presents the plan before changing anything, and offers the switch to the packaged install
- [ ] Changes nothing before the answer

## End state (after the follow-up)

- [ ] (auto) `main` is untouched; the upgrade and the switch are on their own branch
- [ ] (auto) `CLAUDE.md`'s stamp names the newest release tag and `install: packaged`
- [ ] (auto) The framework's skills, agents, workflows, and hook scripts are gone; `.claude/hooks/`
holds only `config.sh`
- [ ] (auto) `.claude/settings.json` has no `hooks` block, pins the marketplace to the newest release,
and turns on `aplyca-adf@aplyca`
- [ ] (auto) `CLAUDE.md` has the names note, and `DEV-SETUP.md` gives the commands by their full names
- [ ] (auto) A new PDR records the switch, and PDR-0001 is marked amended
- [ ] (auto) Everything is committed
- [ ] The pull request body is shown, naming the switch and its PDR; nothing is pushed

## Always

- [ ] No outward action (push, pull request) is taken without an ask
36 changes: 36 additions & 0 deletions evals/dynamic/fixtures/upgrade/switch-to-packaged.input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Input — upgrade: a committed project switches to the packaged install

<!-- run: plugin-dir -->

This fixture verifies `/upgrade`'s switch from the committed install to the packaged one (decision
0016): it moves the project to the newest release, removes the framework machinery the plugin
carries and the settings' `hooks` block, pins the plugin to that release, stamps `install: packaged`,
adds the names people type, and records the switch as a PDR that amends PDR-0001 — on its own
branch, delivered as a pull request it doesn't push.

## Repository context to give the AI

A project adopted at v1.0.0 on the committed install, for a team that works in Claude Code only: the
skeleton without the Antigravity and Cursor layers, its hooks wired in `.claude/settings.json`, the
`aplyca-adf` plugin turned on and pinned to `v1.0.0`, PDR-0001, and the stamp — all committed on
`main`. The plugin is loaded from this checkout for the session only (`--plugin-dir`); the
framework's releases are tagged on GitHub. There is no remote.

## Prompt to give the AI

```
/aplyca-adf:upgrade
```

## Follow-up

```
Yes, switch to the packaged install — the team works in Claude Code only and doesn't need cloud
sessions. No new modules. The decider is the tech lead. Go ahead with the plan; don't push.
```

## What to do with this fixture

1. Run it with `run-session-evals.sh --suite upgrade --cases switch-to-packaged --models sonnet`.
2. `inspect.sh` checks the end state and prints ✓ or ✘.
3. Compare against `switch-to-packaged.expected.md`.
Loading
Loading