/** * campaign-export — plan-export transforms (v5.7 Fase 2, Block 4c). * * The second half of Block 4 ("durable backlog + execution"). Block 4b built the cross-repo * prioritized backlog the user picks from; this exports a picked repo's per-repo action plan * into the TARGET repo's OWN `docs/` directory, so the plan gets a durable, human-readable * home where the work is done ("planer følger arbeidsstedet" — the operator's continuity rule * that plans live next to the workplace, in `docs/`). * * Design mirrors campaign-ledger: PURE, deterministic transforms — `now` is injected as a * YYYY-MM-DD string, never read from the clock here, so they are fully unit-testable. The IO * (loading the ledger, reading the session's action-plan.md, writing the exported file) lives * in the thin `campaign-export-cli` shell. The transforms throw on programmer error * (missing/blank required field), consistent with the ledger transforms. * * NOTE on execution: Block 4c deliberately adds NO new execution machinery. Execution reuses * the existing per-repo `/config-audit implement` (which backs up every changed file, applies * the plan from the session, and verifies) + `/config-audit rollback`. The exported `docs/` * copy is the repo's durable record of the plan, NOT the execution input — `implement` still * reads the canonical plan from the session directory. See docs/v5.7-optimization-lens-plan.md * §Fase 2 (Block 4). */ import { join } from 'node:path'; /** * The exported plan's destination inside the TARGET repo's own `docs/`. Keyed on the source * `sessionId` (timestamp-unique per audit) rather than the calendar date, so two audits of the * same repo on the same day produce distinct files (history is preserved, never silently * overwritten) and the filename ties the export back to the audit that produced it. * * @param {string} repoPath - absolute path to the target repo (the ledger stores it resolved) * @param {string} sessionId - the config-audit session that produced the plan * @returns {string} `/docs/config-audit-plan-.md` */ export function planExportPath(repoPath, sessionId) { if (typeof repoPath !== 'string' || repoPath.trim() === '') { throw new TypeError('repoPath is required'); } if (typeof sessionId !== 'string' || sessionId.trim() === '') { throw new TypeError('sessionId is required'); } return join(repoPath, 'docs', `config-audit-plan-${sessionId}.md`); } /** * Assemble the exported document: a provenance header (who/when/where this came from + how to * execute and undo it) followed by the verbatim session plan body. Pure — given the same inputs * it always produces the same bytes, so it is snapshot-testable. * * @param {{repoName:string, repoPath:string, sessionId:string, planMarkdown:string, now:string}} input * @returns {string} the full markdown to write into the repo's docs/ */ export function buildPlanExportDocument({ repoName, repoPath, sessionId, planMarkdown, now } = {}) { for (const [k, v] of Object.entries({ repoName, repoPath, sessionId, planMarkdown, now })) { if (typeof v !== 'string' || v.trim() === '') { throw new TypeError(`${k} is required`); } } const header = [ `# Config-Audit Action Plan — ${repoName}`, '', `> Exported from the config-audit machine-wide campaign on ${now}.`, `> **Repo:** \`${repoPath}\``, `> **Source session:** \`${sessionId}\``, '>', '> Generated by `/config-audit plan`. To **execute**: run `/config-audit implement` in this', '> repo — it backs up every changed file, applies the plan, then verifies the result. To', '> **undo**: `/config-audit rollback`. Record progress back in the campaign with', `> \`/config-audit campaign set-status ${repoPath} implemented\`.`, '', '---', '', ].join('\n'); return `${header}${planMarkdown.trimEnd()}\n`; }