feat(campaign): plan export + execution-by-reuse (v5.7 Fase 2 Block 4c)

Completes Block 4 (4b backlog + 4c export/execution). Asymmetric: plan
export is new testable code; execution is pure reuse of the existing
per-repo implement/rollback (no new execution machinery), per the plan's
"reuse existing backup/rollback".

Plan export ("planer følger arbeidsstedet"):
- scanners/lib/campaign-export.mjs (pure, now injected, 8 tests):
  planExportPath(repo,sessionId) -> <repo>/docs/config-audit-plan-<sessionId>.md
  (sessionId-keyed so same-day re-audits never collide);
  buildPlanExportDocument({...,now}) -> provenance header + verbatim plan.
- scanners/campaign-export-cli.mjs (-cli, read-only by default, 10 tests):
  --repo resolves the repo's linked session, reads its action-plan.md,
  assembles the doc, emits {exportable,problems,targetPath,document}. Two
  gates -> exit 1 advisory: no-session-linked / no-action-plan. Writes the
  file ONLY under opt-in --write (byte-faithful copy; the LLM never re-types
  a 200-line plan). --sessions-dir override for hermetic tests; exit 0/1/3.

Command: commands/campaign.md gains an `export <path>` mode (Step 6:
preview -> approve -> --write), then routes the user to the existing
/config-audit implement (backup + verify) + rollback + set-status
implemented. Nothing auto-written (Verifiseringsplikt).

Byte-stable: lib + -cli + command-doc only -> scanner count stays 15,
agents 7, commands 21 (export is a mode, not a new command), SC-5 +
backcompat suite untouched. suite 1150->1168. Block 4a (migrateLedger)
still deferred to the first breaking schema change.

Docs: CLAUDE.md section + badge 1150->1168/65->67 files; README badge +
campaign row + Testing prose (fixed stale 1055/59 -> true 1168/67).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-23 10:08:04 +02:00
commit 319e5541c9
7 changed files with 575 additions and 22 deletions

View file

@ -1,7 +1,7 @@
---
name: config-audit:campaign
description: Machine-wide audit campaign — track which repos are pending/audited/planned/implemented across sessions, with a machine-wide roll-up. Human-approved writes only.
argument-hint: "[init | add <path>... | add --discover <root> | set-status <path> <status>]"
argument-hint: "[init | add <path>... | set-status <path> <status> | export <path>]"
allowed-tools: Read, Write, Edit, Bash, Glob
model: opus
---
@ -21,19 +21,28 @@ and applied **only on explicit approval**, by invoking one deterministic write-C
The command never hand-edits the ledger JSON.
This is the **THIN** campaign surface (ledger + roll-up + status + a cross-repo prioritized
backlog to pick from). Execution is a later block — this command does not run audits or apply
fixes itself; it tracks where each repo stands and shows what to tackle next.
backlog to pick from + plan **export**). It does not run audits or apply fixes itself: it tracks
where each repo stands, shows what to tackle next, exports a planned repo's plan into that repo's
own `docs/`, and points at the **existing** per-repo `/config-audit implement` (backup + apply +
verify) and `/config-audit rollback` for execution — Block 4c reuses that machinery, it does not
reinvent it.
## Two CLIs back this command
## Three CLIs back this command
- **Read (report):** `scanners/campaign-cli.mjs` — loads + validates the ledger, emits the
repo list + roll-up. Never writes.
repo list + roll-up + backlog. Never writes.
- **Write (mutate):** `scanners/campaign-write-cli.mjs``init` / `add` / `set-status`, each a
thin wrapper over the invariant-enforcing lib transforms + save. Invoked **only** after the
user approves a specific action.
- **Export:** `scanners/campaign-export-cli.mjs``--repo <path>` resolves the repo's linked
session, reads its `action-plan.md`, and assembles a `docs/config-audit-plan-<sessionId>.md`.
Read-only (a preview) by default; it writes the file **only** under `--write`, which is
invoked **only** after the user approves. The CLI writes the file byte-faithfully — the plan is
never re-typed.
Both take `--ledger-file <path>` (defaults to the durable path) and `--output-file <path>`;
the write-CLI also takes `--reference-date <YYYY-MM-DD>` (the audit date stamp).
All take `--ledger-file <path>` (defaults to the durable path) and `--output-file <path>`; the
write-CLI + export-CLI also take `--reference-date <YYYY-MM-DD>` (the date stamp), and the
export-CLI takes `--sessions-dir <path>` (defaults to `~/.claude/config-audit/sessions`).
## Implementation
@ -46,6 +55,7 @@ From `$ARGUMENTS`, pick the mode:
- `add <path>...` → add one or more repo paths.
- `add --discover <root>` → find git repos under `<root>` and let the user pick which to add.
- `set-status <path> <status>` → transition a tracked repo (`status` ∈ pending/audited/planned/implemented).
- `export <path>` → export a planned repo's action plan into that repo's own `docs/`.
- `help` → show this surface and stop.
Set a shared date stamp for any write: `TODAY=$(date +%F)`.
@ -165,7 +175,48 @@ node ${CLAUDE_PLUGIN_ROOT}/scanners/campaign-write-cli.mjs set-status <path> <st
Exit **3** = invalid status, untracked repo, or no ledger → report the message plainly and do
not retry blindly. On success, read the result and re-show the updated roll-up + repo row.
### Step 6: Next steps
### Step 6 (mode `export`): Export a repo's plan to its own `docs/` — preview, approve, write
"Planer følger arbeidsstedet": a planned repo's action plan belongs in **that repo's** `docs/`,
not buried in a session dir. This step copies it there, byte-faithfully.
**Preview first (read-only — never writes).** The repo must be tracked and have a linked session
that carries an `action-plan.md` (i.e. `/config-audit plan` has run there). Run without `--write`:
```bash
node ${CLAUDE_PLUGIN_ROOT}/scanners/campaign-export-cli.mjs --repo "<path>" \
--reference-date "$TODAY" \
--output-file ~/.claude/config-audit/sessions/campaign-export.json 2>/dev/null; echo $?
```
Exit **0** = previewable, **1** = tracked but not exportable yet, **3** = error (untracked repo,
no/corrupt ledger). Read `~/.claude/config-audit/sessions/campaign-export.json` with the Read tool.
- **Exit 1 — read `problems`** and guide, then stop (nothing to export):
- `no-session-linked` → "`<name>` has no linked audit session. Link one with
`/config-audit campaign set-status <path> <status> --session <id>`, or audit + plan it first."
- `no-action-plan` → "`<name>`'s session has no plan yet. Run `/config-audit plan` in that repo
first, mark it `planned`, then export."
- **Exit 0 — show, then ask.** Tell the user the destination (`targetPath`) and a **short** preview
— the first ~12 lines of `document` only, never the whole file, never the raw JSON (UX rules).
Ask for explicit approval to write it.
**On approval, write it** (the CLI does the faithful copy — do NOT hand-write the file):
```bash
node ${CLAUDE_PLUGIN_ROOT}/scanners/campaign-export-cli.mjs --repo "<path>" --write \
--reference-date "$TODAY" \
--output-file ~/.claude/config-audit/sessions/campaign-export.json 2>/dev/null; echo $?
```
Confirm: "Plan exported to `<targetPath>`." Then hand off to the **existing** execution machinery
(Block 4c reuses it — this command does not run it for you):
> To **execute**: run `/config-audit implement` in `<path>` — it backs up every changed file,
> applies the plan, and verifies. To **undo**: `/config-audit rollback`. When done, record it:
> `/config-audit campaign set-status <path> implemented`.
### Step 7: Next steps
Tailor to where the campaign stands:
@ -174,20 +225,27 @@ Tailor to where the campaign stands:
- **Pending repos exist:** "Run `/config-audit` in a pending repo to audit it, then
`/config-audit campaign set-status <path> audited` to record the result here."
- **Audited but not planned:** "`/config-audit plan` in that repo, then mark it `planned`."
- **Planned repos exist:** "`/config-audit campaign export <path>` to drop the plan into that
repo's own `docs/`, then `/config-audit implement` there to execute it (backup + verify)."
- **Backlog has items:** point at the top backlog repo and the natural next verb for its status
(audit → plan → export → implement).
- Always: the campaign survives this session — re-run `/config-audit campaign` anytime to see
the machine-wide picture.
## Notes
- **Read-only report, human-approved writes.** `campaign-cli` never writes; every mutation goes
through `campaign-write-cli` and only after explicit approval, exactly mirroring how
`/config-audit knowledge-refresh` gates register writes.
- **Deterministic core, not byte-stable command.** The lib transforms + both CLIs are
- **Read-only report, human-approved writes.** `campaign-cli` never writes; every mutation —
ledger changes via `campaign-write-cli`, plan exports via `campaign-export-cli --write` — happens
only after explicit approval, exactly mirroring how `/config-audit knowledge-refresh` gates
register writes. The export-CLI's default (no `--write`) is a read-only preview.
- **Deterministic core, not byte-stable command.** The lib transforms + all three CLIs are
unit-tested and deterministic (`--reference-date` injected); this command's orchestration is
judgment-driven and deliberately **not** in the snapshot suite (like `/config-audit optimize`
and `knowledge-refresh`).
- The `-cli` suffix keeps both CLIs out of the scan-orchestrator, so the scanner count and the
byte-stable snapshot suite are unaffected.
- **THIN scope:** ledger + roll-up + status + a read-only cross-repo prioritized backlog. The
backlog is a derived pick-list (`buildBacklog`, severity-weighted); execution is a later block —
this command tracks state and shows what to tackle next, it does not run audits or apply fixes.
- The `-cli` suffix keeps all three CLIs out of the scan-orchestrator, so the scanner count and
the byte-stable snapshot suite are unaffected.
- **THIN scope:** ledger + roll-up + status + a cross-repo prioritized backlog + plan export.
Execution is **not** reinvented here — `export` drops a planned repo's plan into its own `docs/`
(a durable record), and the user runs the existing `/config-audit implement` (backup + apply +
verify) + `/config-audit rollback` to execute and undo. This command tracks state and routes the
work; it does not run audits or apply fixes itself.