Dogfooding `plan` + `implement` against a throwaway config surfaced one root
defect with many arms: the command templates treat consecutive fenced blocks as
one shell. They are not. Every ```bash fence runs as its own Bash call in its own
process, so a variable set in one block is empty in the next, and `$$` is a
different PID (measured: 21710 vs 22109).
The planner agent confirmed the sharpest arm at runtime, reporting that
`Mode: $RAW_FLAG` "arrived literally unsubstituted" — `--raw` was documented in
three command files while being functionally dead. A machine sweep found the same
root in 20 places across 9 files, well past the two the written fasit predicted:
- `$RAW_FLAG` read from non-shell agent prompts (analyze, plan, implement)
- `$TMPFILE` read across blocks (tokens, manifest, whats-active,
plugin-health) — each command could not read the file it had just written
- `$GLOBAL_FLAG` across blocks (fix)
- `$TODAY` never assigned in any block (campaign), passing
`--reference-date ""` to a write CLI in six places
- three `$$` temp paths handed to the Read tool (fix), which expands neither
All now follow the hardened drift.md pattern: a fixed literal path, or a
re-derivation inside each block that needs it.
Also fixed, all confirmed against ground truth rather than inferred:
- `implement` printed a rollback ID it never captured (the timestamp lived only
inside a command substitution) — the one message a user reads after a bad run
- `plan` reported "No analysis results found" for valid sessions, because Read
was pointed at a glob it cannot expand; now uses Glob and verifies the
analysis report exists before spawning the agent
- five phase commands wrote state.yaml with two of four required fields; since
the agent writes all four, a follow-up write silently deleted the rest
- `implement` promised rollback deletes created files; rollback deliberately
leaves them (M-BUG-26 still open) — the doc, not the engine, was wrong
- `implement` claimed a score delta with no pre-change measurement
- `verifier-agent` was told to write a report it has no tool to write
- dead `Task` tool name in always-loaded rule context; planner-agent template
demonstrated the inline file content its own line 110 forbids
The sweeps land as tests/commands/command-shell-state-shape.test.mjs, verified
red before the fix and proven able to fail by reintroducing the defect. Two
existing tests asserted the old bash-block mechanism rather than the intent and
were updated. Suite 1449/0; frozen v5.0.0 snapshots and all scanner code
untouched.
Not fixed, deliberately: neither command scope-gates its actions to the audit
target. The generated plan included an edit to a real file under ~/.claude,
outside the throwaway target, because the skill/agent scanners are machine-wide.
That is a design change, not a side fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195udHgCcFegzm7ecKku2Yc
DEL B chunk `interview` (+ discover/status/cleanup/help). Fasit written before
the run predicted 8 defects and refuted 4 candidates; all 8 confirmed, all 4
refutations held, and three predictions turned out too narrow.
- M-BUG-36: `drift --list` reached the command as 0 bytes. drift-cli accepted
--output-file but list mode ignored it, and the listing goes to stderr, which
the command discards per ux-rules rule 2. Fixing the caller alone would not
have helped.
- M-BUG-37: feature-gap's "Create backup" step ran fix-cli without --apply.
Dry-run is the default, so no backup existed (backupId: null) while the
command went on to edit config believing it could roll back.
- M-BUG-38: fix-cli told users to recover with scanners/rollback-cli.mjs, which
does not exist. Dead reference in the one message read after a bad fix.
- M-BUG-21 fourth arm: five templates carried literal [--global]/[--full-machine]
inside executable bash blocks. A bracketed placeholder does not start with a
dash, so every scanner's arg loop takes it as the scan target.
- interview and analyze never said which session they act on; interview could
rewind a finished session; cleanup interpolated an unvalidated id into rm -rf
(an empty id deletes every session); status advertised a `resume` command that
does not exist and documented an `all` argument it never parsed.
TDD: 9 red tests first, including a machine sweep for dead /config-audit
references and for bracketed flags in bash blocks. Suite 1432 -> 1441/0.
Frozen v5.0.0 snapshots untouched; --raw/--json contracts unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGvA1uUQn2hPBPMaCKK6x3
Dogfooding `/config-audit plugin-health` against a fasit registered before the
run: 11 of 12 predictions confirmed, 1 refuted with evidence, 0 deviations.
The command's default path could not produce the report it documents.
M-BUG-21 (third arm): the argument loop ended in
`else if (!args[i].startsWith('-')) targetPath = args[i]` with no unknown-flag
branch, so `--output-file /tmp/x.json` was dropped and its value became the scan
target. Worse than in drift-cli: a non-existent path discovers no plugins, so the
scanner answered "No plugins found" (info) with exit 0 — a reassuring answer, not
an error. Unknown options and a value-less `--output-file` now exit 3.
M-BUG-33: the scanner had no `--output-file` and its default-mode report goes to
stderr, which `commands/plugin-health.md` discards with `2>/dev/null` before
telling the agent to read stdout. Zero bytes captured.
M-BUG-34: per-plugin rows and the grade formula never left `scan()` — the only
grade code, `formatPluginHealthReport`, had no caller — and cross-plugin findings
were flattened behind a `category` they share with per-plugin findings. The
mandated table and Cross-Plugin section were unbuildable, so the command had to
fabricate them. `scanDetailed()` now returns them; `scan()`'s frozen v5.0.0
envelope is unchanged by construction.
M-BUG-35: `.claude-plugin/marketplace.json` was flagged as an unknown file. It is
the documented catalog location, and `"source": "./"` makes the repo root its own
plugin, so one `.claude-plugin/` legitimately holds both.
Also: `commands/posture.md` ran both optional scanners in default mode under
`2>/dev/null` and read stdout — the same class as feature-gap.md:133 in the fix
chunk. A CLI-side flag fix does not close its callers.
Tests 1420 -> 1432, red first. Frozen v5.0.0 snapshots untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XhhZ8zg1amR7YCAPqiZWdt
implement.md spawns implementer agents in parallel batches, all appending to
the same implementation-log.md. Dogfooding showed agents satisfying 'Append
result to:' with a full-file Write — the last writer clobbered 4 of 6 entries.
Pin the mechanism in both contracts: append with Bash >> heredoc, never the
Write/Edit tool on the shared log. Shape tests pin the instruction in both
files (empirically verified: agents given the >> instruction appended safely).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Claude Code subagent harness instructs spawned agents NOT to write
report/summary/findings/analysis .md files — the parent reads the final
text message. Verified live: analyzer-agent skipped Write entirely and
returned the report inline, so analysis-report.md never landed on disk
and the plan/interview/status phases would find nothing to read.
New contract (orchestrator-writes pattern): analyzer-agent returns the
complete report as its final message; the analyze command saves it
verbatim to the session directory before presenting the summary.
Same class exists in plan/feature-gap/optimize/scanner agent pairs —
deliberately left for their own dogfood chunks (plan is judged as-is
first per the pipeline sequence).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTontYwY5JGS4nL2AuiASy
Wave 5 Step 15. Threads --raw plumbing through all seven action
command templates and adds a shape test covering structural plumbing
plus help.md's plain-language vocabulary.
- commands/fix.md: --raw flag parsed; fix-plan rendering groups by
userActionLanguage; humanized title/description/recommendation are
rendered verbatim from the cross-referenced scan envelope.
- commands/rollback.md: terminology pass — "manifest" → "list of
changes" in user-facing copy; the file name manifest.yaml is kept
as the machine contract; --raw threaded through.
- commands/plan.md: --raw forwarded to the planner-agent's prompt;
agent now instructed to group actions by userImpactCategory and
lead with userActionLanguage; bash block added for flag parsing.
- commands/implement.md: --raw forwarded to the implementer-agent's
prompt; progress-log lines now reference the humanized titles
already present in the action plan.
- commands/cleanup.md: --raw accepted as no-op (cleanup is
file-management only, no findings prose); bash block added.
- commands/help.md: full plain-language pass — "PreToolUse" and
"frontmatter" jargon removed from user-facing copy; new
vocabulary table surfaces the humanized userImpactCategory and
userActionLanguage labels ("Configuration mistake", "Conflict",
"Wasted tokens", "Missed opportunity", "Dead config" / "Fix this
now", "Fix soon", "Fix when convenient", "Optional cleanup",
"FYI"); --raw documented as global pass-through flag.
- commands/interview.md: --raw accepted as no-op; "unused hooks"
question phrased as "unused automation that runs at specific
events" in user-facing copy.
tests/commands/action-commands-shape.test.mjs (new, +6 tests, 780 → 786):
- structural: bash block + Read tool + --raw/$ARGUMENTS plumbing
across all 7 files
- help.md vocabulary: ≥3 userImpactCategory labels and ≥3
userActionLanguage phrases present
- help.md jargon: no bare "PreToolUse" or "frontmatter" in copy
Wave 5 Step 14. Threads the humanizer vocabulary through the remaining
six audit/analysis command templates and adds a shape test that locks
the structure plus a pair of anchor must-contains.
- commands/drift.md: --raw pass-through; new/resolved/changed-finding
rendering instructions reference userActionLanguage and
relevanceContext rather than raw severity.
- commands/plugin-health.md: --raw pass-through; finding rendering
groups by userImpactCategory and leads with userActionLanguage.
- commands/config-audit.md (router): replaces the 25-line A/B/C/D/F
prose ladder with a humanized stderr-scorecard reference + three
userActionLanguage-grouped "What you can do next" branches; --raw
threaded through both scan-orchestrator and posture invocations.
- commands/discover.md: --raw pass-through; finding-summary rendering
groups by userImpactCategory.
- commands/analyze.md: --raw pass-through; analyzer-agent prompt now
instructs grouping by userImpactCategory and leading with
userActionLanguage; humanized title/description/recommendation
strings rendered verbatim, no paraphrasing.
- commands/status.md: phase-label humanization table — current_phase
machine field name preserved, user-facing labels translated
("Looking at your config files", "Working out what to recommend",
"Asking what you'd like to focus on", "Putting together your action
plan", "Making the changes", "Double-checking everything worked");
--raw preserves verbatim machine field values.
tests/commands/group-b-shape.test.mjs (new, +8 tests, 772 → 780):
- structural: bash block + Read tool + --raw/$ARGUMENTS plumbing
across all 6 files
- findings-renderers: humanized field reference + no grade-prose
- anchor must-contains per plan: config-audit.md ⊇
userImpactCategory|userActionLanguage; drift.md ⊇ --raw|humanized
- status.md: current_phase preserved + ≥3 humanized phase labels
Wave 5 Step 13. Threads the humanizer vocabulary through five audit/
analysis command templates and adds a shape test that locks the
structure in place.
- commands/posture.md, tokens.md, feature-gap.md (findings-renderers):
reference userImpactCategory/userActionLanguage/relevanceContext;
remove hardcoded A/B/C/D/F-to-prose tables (humanizer owns the
grade-context vocabulary now via the stderr scorecard headline).
- commands/manifest.md, whats-active.md (inventory CLIs): add --raw
pass-through for CLI-surface consistency. --raw is a no-op in these
CLIs, but the flag is threaded through so users get uniform behaviour.
- All five files: --raw flag parsed from $ARGUMENTS and passed verbatim
to the underlying scanner CLI when present.
tests/commands/group-a-shape.test.mjs (new, +5 tests, 767 → 772):
- structural: every file has a bash invocation block, Read tool
reference, and --raw/$ARGUMENTS plumbing
- findings-renderers only: at least one humanized field referenced;
no hardcoded "[grade] grade is..." prose tables