config-audit/commands/implement.md
Kjell Tore Guttormsen 44b222859e feat(scanners): the recovery path is code you can run, not prose you can read
R1+R2 as one chunk — both KRITISK rows of the Q3 severity table sit on the
restore path, and neither closes alone.

R1: rollback-engine.mjs verified every checksum before AND after each write,
resolved the legacy backup root and reported createdNotRemoved — and none of it
was reachable. Measured: 16 files under scanners/ carry a process.argv entry;
the engine was not one of them. commands/rollback.md drove the restore as model
prose: an ESM import block a template cannot execute, ad-hoc `cp` offered
underneath as the runnable path, and "(checksum verified)" pre-rendered three
times in the success output. `cp` establishes no checksum, so the verification
was a property of the template rather than of the run — on the one surface that
runs when the user is already in trouble.

R2: implement.md Step 3 hand-built its backup (mkdir, cp, a date-derived id, a
manifest typed out in the template) while parseManifest knew one frozen sample
of that format, pinned by a HAND-WRITTEN fixture instead of by the template's
own text. Rename a key and parseManifest returns zero files while rollback
reports success.

Fixing only R1 leaves the new CLI parsing a prose format; fixing only R2 leaves
a clean format with no runnable entry.

- scanners/rollback-cli.mjs — --list / --create / --restore / --delete over the
  existing engine, on the shared requireValidArgs gate. Exit 0 done, 1
  outstanding (gate refusal with nothing written, or a backup that covered fewer
  targets than given), 2 a file failed, 3 could not do the job. A gated restore
  is 1, not 3: "this write leaves your project" is a verdict about a write that
  WAS examined, and it rides in the payload where a command under 2>/dev/null
  can act on it.
- createBackup gains `created` (recorded, never copied — no backup can hold a
  file that does not exist) and `skipped`, so a backup covering fewer files than
  asked is no longer indistinguishable from a clean one.
- implement.md Step 3 and rollback.md now call the CLI. parseManifest's
  implement-format branch stays: nothing writes that shape now, but every backup
  made before this chunk is on disk in it.
- backup-restore-contract.test.mjs checks every field rollback.md renders
  against a payload produced by RUNNING the CLI. That is what replaced
  "(checksum verified)".

20 guards seen red against the original state before any production code, then
each against its own defect. Two holes that surfaced there were mine: the
implement assertion matched `--create` as a substring of `--created` and stayed
green when the call was removed; and mutating the argv gate showed
requireValidArgs sets exit 3 by itself, so a CLI can report that it could not
parse its arguments and still run the restore underneath — that case is now
asserted on the bytes.

Suite 1752 -> 1777, 0 fail. Frozen tests/snapshots/v5.0.0 untouched. Dogfooded
through the templates' own command lines against a sandboxed HOME, including the
machine-wide arm: refused with the file unchanged, then restored under
--approve-scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Logq8GGWKhtyDem63FTEnG
2026-08-18 21:28:15 +02:00

9.2 KiB

name description allowed-tools model
config-audit:implement Phase 5 - Execute action plan with backups and verification Read, Write, Edit, Bash, Agent, AskUserQuestion opus

Config-Audit: Implementation (Phase 5)

Execute the action plan with full backup, verification, and rollback support.

Prerequisites

  • Must have completed Phase 4 (plan)
  • Action plan at ~/.claude/config-audit/sessions/{session-id}/action-plan.md

Arguments

  • $ARGUMENTS may contain --raw to forward to the implementer-agent's instructions; in --raw mode the agent renders v5.0.0 verbatim severity prefiks instead of humanized userActionLanguage urgency phrasing.

Implementation

Step 1: Parse flags, load and verify

Check whether $ARGUMENTS contains --raw. Carry the answer yourself: the agent prompt in Step 4 is not a shell, so a variable assigned in a bash block cannot be referenced from it. Substitute {mode} literally with --raw or humanized.

Find the most recent session with a plan (use the Glob tool for ~/.claude/config-audit/sessions/*/state.yaml, then Read the newest match — Read does not expand *). If none: "No action plan found. Run /config-audit plan first."

Use the Read tool on the action plan and count actions.

Now classify where those actions actually write. A plan whose actions target ~/.claude/CLAUDE.md and a plan whose actions target ./CLAUDE.md are the same count of actions — presenting only the count made a machine-wide change look identical to a project-local one. Pass one --target per distinct file the plan touches (absolute paths, as written in the plan):

node ${CLAUDE_PLUGIN_ROOT}/scanners/write-scope-cli.mjs --target "<file-1>" --target "<file-2>" --repo "$PWD" --output-file /tmp/config-audit-implement-scope.json 2>/dev/null; echo $?

Exit 0 = classified; 3 = argument error (show the stderr message). Read /tmp/config-audit-implement-scope.json. Tell the user:

## Implementing Action Plan

Found {N} actions to execute across {M} files.
A backup will be created before any changes are made.

{For each target whose `gate` is not "silent", one line:}
- `{target}` — {scopeClass}

Step 2: Get user approval

Render each distinct string in disclosures[] verbatim before asking — they are already plain-language, and the payload carries them so this template never has to restate what a scope class means.

When requiresApproval is false, ask as before:

AskUserQuestion:
  question: "Ready to implement {N} actions? Backup created automatically — you can roll back with one command."
  options:
    - "Yes, proceed"
    - "Review plan first" (then show the plan file path)
    - "Cancel"

When requiresApproval is true, the question MUST name the scope, and the safe option MUST come first — a plan that edits machine-wide configuration affects every project the user opens, so the default must not be "proceed":

AskUserQuestion:
  question: "This plan changes configuration outside this project ({K} of {M} files). Proceed?"
  options:
    - "Review plan first" (then show the plan file path)
    - "Yes — change files outside this project too"
    - "Cancel"

Step 3: Create backup

Create the backup through the code that owns the format. Pass one --target per pre-existing file the plan will MODIFY, and one --created per file the plan will CREATE — a backup cannot hold a file that does not exist yet, so those are recorded rather than copied, and /config-audit rollback reads them back to tell the user which files it is leaving behind.

node ${CLAUDE_PLUGIN_ROOT}/scanners/rollback-cli.mjs --create --target "<file-1>" --target "<file-2>" --created "<new-file-1>" --repo "$PWD" --output-file /tmp/config-audit-implement-backup.json 2>/dev/null; echo $?
Exit Meaning
0 every target was backed up
1 at least one target was not there — read skipped[] before continuing
3 the CLI could not run (show the stderr message); do not edit anything

Read /tmp/config-audit-implement-backup.json. The payload's backupId is the ID to quote from here on — never re-derive it. Shell state does not survive to the next block, and a second date call that straddles a second boundary would hand the user a rollback ID that does not exist. Use it wherever {backup-id} appears below.

On exit 1, name the skipped[] paths before doing anything else: the plan is about to change files that have no backup behind them. If any skipped path is one the plan MODIFIES (rather than creates), stop and report — that action cannot be rolled back.

Tell the user: "Backup created. Implementing actions..."

Step 4: Execute actions

Group actions by dependencies. For each group, spawn implementer agents (batch of 3):

Agent(subagent_type: "config-audit:implementer-agent")
  model: sonnet
  prompt: |
    Execute action: {action-id}
    File: {file-path}, Type: {create|modify|delete}
    Mode: {mode} ("humanized" = humanized progress prose; "--raw" = v5.0.0 verbatim)
    Details: {changes}
    Verify backup exists, make change, validate syntax.
    When logging progress, use the humanized title/userActionLanguage
    fields from the action plan (the planner already rendered them) —
    do not re-derive severity prose. Append result to:
    ~/.claude/config-audit/sessions/{session-id}/implementation-log.md
    Append with Bash `>>` (heredoc) — NEVER the Write tool on this log;
    parallel agents share it and a full-file Write clobbers their entries.

Show progress between groups using the humanized titles already present in the action plan:

Action 1/N: {humanized title} — done
Action 2/N: {humanized title} — done
...

Step 5: Verify results

Spawn verifier agent:

Agent(subagent_type: "config-audit:verifier-agent")
  model: sonnet (note: using sonnet, not haiku)
  prompt: |
    Verify all changes from implementation:
    1. Modified files exist and are syntactically valid
    2. New files created correctly
    3. No new conflicts introduced
    Return your findings as your final message. Do NOT write them to a file —
    this agent is read-only by design (tools: Read, Glob, Grep) and has no
    write tool; instructing it to write a report is a contract it cannot keep.

Append the verifier's returned findings to the log yourself, with Bash >> (heredoc) — never the Write tool, for the same reason as Step 4:

cat >> ~/.claude/config-audit/sessions/{session-id}/implementation-log.md <<'EOF'
## Verification
{verifier findings}
EOF

If verifier finds issues: one retry with implementer agent. If still failing: report and suggest rollback.

Step 6: Present results

### Implementation Complete

**{succeeded} succeeded** | {failed} failed | {skipped} skipped

{If failed > 0:}
{failed} action(s) couldn't be completed — see log for details.

**Backup location:** `~/.claude/config-audit/backups/{backup-id}/`
**Rollback:** `/config-audit rollback {backup-id}`
**Full log:** `~/.claude/config-audit/sessions/{session-id}/implementation-log.md`

On reporting a score. Only quote a grade change if the pre-change grade was actually captured before Step 4 ran. Once the files are edited, only the new grade is measurable — a delta computed after the fact has no source and must not be invented. To offer one, measure first in Step 1 and again here:

node ${CLAUDE_PLUGIN_ROOT}/scanners/posture.mjs "<target-path>" --output-file /tmp/config-audit-implement-posture.json 2>/dev/null; echo $?

Then Read /tmp/config-audit-implement-posture.json. Both the --output-file and the 2>/dev/null are required by the output rules — a bare scanner call would put diagnostic output in front of the user. If no pre-change grade was captured, report the new grade alone and say nothing about a delta.

Step 7: Update state

Update state.yaml with all four fields .claude/rules/state-management.md requires:

  • current_phase: "implement"
  • completed_phases: append implement to the existing array (read it first; never replace it)
  • next_phase: null
  • updated_at: current timestamp

A full-file Write that names only two of the four silently deletes the other two.

Rollback

If the user requests rollback at any point:

  1. Run /config-audit rollback {backup-id} — it drives scanners/rollback-cli.mjs, which verifies each checksum before and after writing. Do not restore by hand: a copy performs neither check, and the result cannot be reported as verified.
  2. Read the restore payload and report the per-file status it returns
  3. Report — do not delete — the files this run created. Rollback restores from backup, and no backup can exist for a file that did not exist before. Those paths stay on disk; /config-audit rollback lists them under "Left in place" so the user can remove them deliberately. Promising deletion here would leave a half-restored config that reads as a clean rollback.
  4. Update state to rolled_back

Error Handling

Error What happens
Permission denied Skip action, log it, continue with others
File not found Skip action, log it, continue
Invalid syntax after edit Rollback that single file, log, continue
Critical failure Offer full rollback