The chain observed configuration across repos but presented every write it then proposed as though it landed where the session stands. STATE named two arms; measuring found five, and two of them are worse than the two already known: - implement — the approval prompt named NO path at all, only a count, so a plan editing ~/.claude/CLAUDE.md and one editing ./CLAUDE.md produced byte-identical prompts. - rollback — the file list rendered `.claude/settings.json`, a repo-relative FORM, while the restore writes to the absolute original. The other arms were silent; this one pointed the wrong way. - fix — paths were visible but unclassified, and --global mixed machine-wide and project rows into one unmarked table. The gate's strength comes from the target's scope class, never from the command asking: five command-owned policies would drift apart the way five copies of the lever table did. SCOPE_CLASSES is one source for class, gate, wording and predicate; templates render `disclosures[]` from the CLI instead of restating what a class means. Two orderings in that table are load-bearing, and both were measured: - plugin-managed before user-scope. Both ~/.claude/config-audit/ and the legacy ~/.config-audit/ are live, and every command writes session state there. The other order fires the gate on every write ever made and gets it switched off, which is worse than no gate. - user-scope before cross-repo. ~/.claude/.git EXISTS, so a plain .git-upward walk answers "another repo" for ~/.claude/CLAUDE.md and silently downgrades the strongest gate on the subtraction axis's primary target to disclosure. disclose is not require-ok: campaign export is cross-repo by design, so the gate there says so rather than refusing. Distinct from require-target-dir.mjs, which asks whether a scan ROOT is readable (exit 3) — a different invariant, left unmerged along with its four inline copies. Also structural, both found while building this: the hand-maintained GUARDED list in the unknown-flag sweep now derives its completeness from the directory (measured complete at 14 of 14 first, so nothing was hiding — but the 15th CLI would have been swept by nothing); and prose shape-guards use whitespace- tolerant patterns, after one went red against a command file that did say the right thing, line-wrapped. Gated: implement, fix, rollback, plan, campaign export. Suite 1596 -> 1625/0, frozen v5.0.0 and default-output baselines 0 changed files. No new GAP dimension, no lever, no finding code — utilization denominators untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013941cEohSD5Aw56FVAtBgZ
241 lines
8.9 KiB
Markdown
241 lines
8.9 KiB
Markdown
---
|
|
name: config-audit:implement
|
|
description: Phase 5 - Execute action plan with backups and verification
|
|
allowed-tools: Read, Write, Edit, Bash, Agent, AskUserQuestion
|
|
model: 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):
|
|
|
|
```bash
|
|
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 backup silently, and **print the backup ID** — Step 6 has to tell the user
|
|
how to roll back, and a timestamp that only ever existed inside a command
|
|
substitution cannot be quoted later. Shell state does not survive to the next
|
|
block, so capture the printed value and substitute it literally from here on:
|
|
|
|
```bash
|
|
BACKUP_ID=$(date +%Y%m%d_%H%M%S)
|
|
mkdir -p ~/.claude/config-audit/backups/"$BACKUP_ID"/files/ 2>/dev/null
|
|
echo "$BACKUP_ID"
|
|
```
|
|
|
|
Use the printed ID wherever `{backup-id}` appears below. Never invent or re-derive
|
|
it with a second `date` call — a run that straddles a second boundary would hand
|
|
the user a rollback ID that does not exist.
|
|
|
|
Copy each file to be modified. Generate `manifest.yaml` with checksums.
|
|
|
|
The manifest is what `/config-audit rollback` reads, so it MUST carry both lists:
|
|
|
|
```yaml
|
|
files: # pre-existing files this run will MODIFY
|
|
- backup: files/root/CLAUDE.md
|
|
original: /abs/path/CLAUDE.md
|
|
sha256: <sha256 of the pre-change content>
|
|
created: # files this run will CREATE (no backup can exist)
|
|
- /abs/path/.claude/rules/post-quality.md
|
|
```
|
|
|
|
Record every `create`-type action under `created:`. Rollback cannot restore a
|
|
file that never existed, but it must be able to tell the user which files it is
|
|
leaving behind — a half-restored target is only dangerous when it is silent.
|
|
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```markdown
|
|
### 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:
|
|
|
|
```bash
|
|
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. Read `manifest.yaml` from backup
|
|
2. Restore each file and verify checksums
|
|
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 |
|