--- name: config-audit:rollback description: Restore configuration from backup — list available backups or rollback a specific one argument-hint: "[backup-id]" allowed-tools: Read, Write, Glob, Grep, Bash, AskUserQuestion model: sonnet --- # Config-Audit: Rollback Restore configuration files from a previous backup. Without arguments, lists available backups. With a backup ID, restores files from that backup. Every step below runs `scanners/rollback-cli.mjs`, which drives the rollback engine: it verifies each file's checksum before AND after writing it, resolves backups made under the pre-v2.2.0 root, and reports the files a restore cannot undo. Never restore by copying files back by hand — a copy performs none of those checks, and the result cannot honestly be reported as verified. ## Arguments - `$ARGUMENTS` may contain a backup ID (format: `YYYYMMDD_HHMMSS`) - `--raw`: pass-through flag accepted for CLI surface consistency. Rollback is file restoration only (no scanner output, no findings prose), so `--raw` is a no-op here, but the flag is still parsed so users get uniform behaviour across the toolchain. ## Behavior ### List mode (no argument) ```bash RAW_FLAG="" if echo "$ARGUMENTS" | grep -q -- "--raw"; then RAW_FLAG="--raw"; fi node ${CLAUDE_PLUGIN_ROOT}/scanners/rollback-cli.mjs --list --output-file /tmp/config-audit-rollback-list.json 2>/dev/null; echo $? ``` Exit 0 = listed; 3 = the CLI could not do its job (show the stderr message). Read `/tmp/config-audit-rollback-list.json` and render one row per entry in `backups[]` — `{id}`, how many `{files}` it holds, and `{createdAt}`. An entry whose `{legacy}` is true was made under the pre-v2.2.0 backup root; say so, since its path differs from the one printed below. ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Available Backups ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 20260403_163045 — 3 files (settings.json, hooks.json, typescript.md) 2. 20260403_141230 — 1 file (CLAUDE.md) 3. 20260402_092015 — 5 files (full audit) Usage: /config-audit rollback 20260403_163045 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` If `{count}` is 0, say there are no backups yet and stop — do not offer a restore. ### Restore mode (with backup ID) 1. The `backups[]` entry for that ID (from the list payload above) carries the `files[]` this restore would write. Classify those `{originalPath}` values before showing them. A restore writes to the absolute path recorded at backup time, which may be machine-wide even when the backup was taken from a project — so the file list must be rendered as the absolute originals, never shortened to a repo-relative-looking form that implies the write stays local: ```bash node ${CLAUDE_PLUGIN_ROOT}/scanners/write-scope-cli.mjs --target "" --target "" --repo "$PWD" --output-file /tmp/config-audit-rollback-scope.json 2>/dev/null; echo $? ``` Exit 0 = classified; 3 = argument error (show the stderr message). Read `/tmp/config-audit-rollback-scope.json`, render each distinct string in `disclosures[]` verbatim, then ask for confirmation. When `requiresApproval` is false: ``` AskUserQuestion: question: "Restore 3 files from backup 20260403_163045?" options: - "Yes, restore" - "Cancel" ``` When `requiresApproval` is true, name the scope and put the safe option first: ``` AskUserQuestion: question: "This restores {K} of 3 files to locations outside this project. Restore all 3?" options: - "Cancel" - "Yes — restore, including outside this project" ``` 2. Run the restore. Classifying is not approving: add `--approve-scope` only after the user has answered yes to the question above, and only then. ```bash node ${CLAUDE_PLUGIN_ROOT}/scanners/rollback-cli.mjs --restore "" --repo "$PWD" --output-file /tmp/config-audit-rollback-restore.json 2>/dev/null; echo $? ``` Append ` --approve-scope` to that command when the user approved a restore that leaves this project. To preview without writing anything, append ` --dry-run` instead — a dry run reports what would happen and touches no file. | Exit | Meaning | |------|---------| | 0 | every file restored | | 1 | nothing was written — the restore needs approval it was not given | | 2 | at least one file failed; read `failed[]` before saying anything else | | 3 | the CLI could not run (bad ID, unreadable manifest) — show the stderr message | 3. Read `/tmp/config-audit-rollback-restore.json` and report what the run actually did. Render one line per entry in `restored[]` and `failed[]`, each showing its own `{status}` from the payload — never a fixed verification phrase, because the outcome differs per file and only the payload knows it: ``` Restored 2 of 3 files from backup 20260403_163045 - /abs/path/.claude/settings.json — {status} - /abs/path/hooks/hooks.json — {status} - /abs/path/.claude/rules/typescript.md — {status} ``` When `{requiresApproval}` is true and nothing was restored, the run was refused: list the `refused[]` paths, say plainly that no file was changed, and offer to re-run with approval. 4. **Report what rollback cannot undo.** A backup only holds files that already existed, so files the implement step CREATED survive the restore. When `createdNotRemoved` is non-empty, list those paths and say plainly that they remain: ``` Left in place — created by implement, no backup exists: - .claude/rules/post-quality.md - guidelines/posting-rhythm.md Remove them manually if you want the pre-implement state exactly. ``` Never finish a restore without this section when the list is non-empty; a silently half-restored target reads as a clean rollback. ### Delete mode If the user says "delete" after listing, confirm, then: ```bash node ${CLAUDE_PLUGIN_ROOT}/scanners/rollback-cli.mjs --delete "" --output-file /tmp/config-audit-rollback-delete.json 2>/dev/null; echo $? ``` Exit 0 = removed (`deleted` is true in the payload); 3 = no backup with that ID in either root — show the stderr message rather than reporting a deletion. ## Implementation `scanners/rollback-cli.mjs` is the only entry point. It reads `~/.claude/config-audit/backups` and falls back to the pre-v2.2.0 `~/.config-audit/backups`, so a backup made before the move still resolves; the list payload flags those with `legacy: true`, and both roots are echoed in `meta` so a report can name the one it used. The same CLI creates backups (`--create --target …`), which is how `/config-audit implement` records what it is about to change. Backup and restore therefore share one manifest format, owned by `scanners/lib/backup.mjs` — the format is never written or read by hand.