feat(campaign): durable machine-wide campaign-ledger core (v5.7 Fase 2 Block 3a THIN)

Add the durable ledger that sits ABOVE individual config-audit sessions for a
machine-wide audit campaign: repo list + per-repo lifecycle (pending → audited →
planned → implemented) + a machine-wide roll-up by status and severity.

scanners/lib/campaign-ledger.mjs — same hybrid split as knowledge-refresh:
- PURE transforms (createLedger / addRepo / setRepoStatus / rollUp) with `now`
  injected (YYYY-MM-DD, never the clock) → deterministic + unit-testable.
- soft validateLedger (returns {valid,errors}, never throws) for loaded data;
  transforms throw on programmer error (invalid status, unknown path).
- thin IO shell (defaultLedgerPath / loadLedger→null-on-ENOENT / saveLedger).
- persists to ~/.claude/config-audit/campaign-ledger.json — OUTSIDE the plugin
  dir (next to sessions/) so it survives uninstall/reinstall/upgrade.
- schemaVersion stamped from the start → cheap Block 4 migration.

THIN scope (Block 3a, operator-approved): ledger + roll-up + persistence only —
no CLI/command/execution (Blocks 3b/3c/4). Internal plumbing, byte-stable until
consumed: no `export async function scan` + lives in lib/ → scanner count stays
15, no orchestrator wiring, SC-5 unchanged.

tests/lib/campaign-ledger.test.mjs — 28 tests (TDD, red→green). Full hermetic
suite 1091→1119 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-22 13:30:44 +02:00
commit f93830ce74
3 changed files with 468 additions and 0 deletions

View file

@ -419,6 +419,22 @@ Same hybrid split as Chunk 2b — a deterministic, byte-stable, unit-tested core
like `/config-audit optimize`. **No new agent** (web poll runs in the command's own context), **no new
orchestrated scanner**. suite 1068→1091.
### campaign-ledger — durable machine-wide campaign core (v5.7 Fase 2, Block 3a THIN)
`scanners/lib/campaign-ledger.mjs`: the durable ledger that sits ABOVE individual sessions for a
machine-wide audit campaign — repo list + per-repo lifecycle (`STATUSES` = pending→audited→planned
→implemented) + a machine-wide `rollUp` (counts by status + severity aggregated across repos). It
persists to a single JSON file **outside** the plugin dir (`~/.claude/config-audit/campaign-ledger
.json`, next to `sessions/`) so it survives uninstall/reinstall/upgrade. Same hybrid split as
knowledge-refresh: PURE transforms (`createLedger`/`addRepo`/`setRepoStatus`/`rollUp`) with `now`
**injected** (YYYY-MM-DD, never the clock) + soft `validateLedger` (returns `{valid,errors}`, never
throws) + a thin IO shell (`defaultLedgerPath`/`loadLedger`→null-on-ENOENT/`saveLedger`). Transforms
throw on programmer error (invalid status, unknown path); `schemaVersion` stamped from the start so a
Block 4 migration is cheap. **THIN**: ledger + roll-up + persistence only — NO execution, CLI, or
command surface (Blocks 3b/3c/4). **Internal plumbing, byte-stable until consumed**: no `export async
function scan` + lives in `lib/` → scanner count stays 15, no orchestrator wiring, SC-5 unchanged.
28 tests, suite 1091→1119.
## Gotchas
- Session directories accumulate — use `/config-audit cleanup` to manage