From 5f26de2f0d83f0799cc818bdde8309ddcb735fdf Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sun, 10 May 2026 21:03:54 +0200 Subject: [PATCH] fix(voyage): inject plan_critic via Phase 9 readAndUpdate (906f155d) --- plugins/voyage/commands/trekplan.md | 41 +++++++++++++++++++ .../voyage/tests/lib/doc-consistency.test.mjs | 21 ++++++++++ 2 files changed, 62 insertions(+) diff --git a/plugins/voyage/commands/trekplan.md b/plugins/voyage/commands/trekplan.md index 0be812b..ed5d388 100644 --- a/plugins/voyage/commands/trekplan.md +++ b/plugins/voyage/commands/trekplan.md @@ -726,6 +726,47 @@ After both complete: - If only **minor** issues or clean: proceed without changes. Note the review result in the plan. +### Inject plan_critic verdict into plan frontmatter (post-dedup, post-revise) + +After the dedup pass completes and any blocker/major revisions are folded +in, atomically update the plan's frontmatter with the plan-critic verdict +so downstream surfaces (notably `playground/voyage-playground.html` +`buildArtifactKeyStat`) can read it without re-parsing the review JSON. + +Phase 8 (write plan) precedes Phase 9 (adversarial review), so the +verdict cannot be in Phase 8's frontmatter template — it must be +injected here, after the verdict exists. + +Read the verdict from `/tmp/plan-critic-out.json` and apply it via +`lib/util/markdown-write.mjs` `readAndUpdate`: + +```js +import { readFileSync } from 'node:fs'; +import { readAndUpdate } from `${CLAUDE_PLUGIN_ROOT}/lib/util/markdown-write.mjs`; + +const criticVerdict = JSON.parse(readFileSync('/tmp/plan-critic-out.json', 'utf-8')).verdict; +const result = readAndUpdate(planPath, ({ frontmatter, body }) => { + frontmatter.plan_critic = criticVerdict; + return { frontmatter, body }; +}); +if (!result.valid) { + // Non-fatal: log warning. plan.md already exists from Phase 8; + // missing plan_critic is degraded UX, not blocking. + console.warn('plan_critic injection failed:', result.errors); +} +``` + +Field semantics: +- `plan_critic` — string. One of `APPROVE`, `APPROVE_WITH_NOTES`, + `REVISE`, or `BLOCK`. Matches the rubric in `plan-critic` agent + output. Omitted when the inject failed (Phase 9 surfaces a warning + in that case but does not abort). + +Schema compatibility: `plan_critic` is **additive and optional**. The +plan validator (`lib/validators/plan-validator.mjs`) tolerates unknown +frontmatter keys, so this addition does NOT require a `plan_version` +bump. Plans written before this field was added validate identically. + ## Phase 10 — Present and refine Present a summary to the user: diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index be17cca..686805e 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -625,3 +625,24 @@ test('docs/annotation-quickstart.md exists with ≤7 numbered steps and example- `${path} must reference the canonical example fixture for hands-on verification`, ); }); + +test('commands/trekplan.md Phase 9 documents plan_critic injection via readAndUpdate (906f155d)', () => { + // Phase 9 (adversarial review) writes the plan-critic verdict back into + // plan.md frontmatter AFTER plan-review-dedup completes. The inject must + // happen post-Phase-8 (write) because Phase 8 precedes Phase 9 in the + // pipeline — the value cannot be in Phase 8's frontmatter template. + // Both the field name (plan_critic) and the inject mechanism + // (readAndUpdate from lib/util/markdown-write.mjs) must be documented + // so future maintainers can trace the contract. + const text = read('commands/trekplan.md'); + assert.match( + text, + /plan_critic/, + 'commands/trekplan.md must document plan_critic frontmatter field (906f155d)', + ); + assert.match( + text, + /readAndUpdate/, + 'commands/trekplan.md must reference readAndUpdate from lib/util/markdown-write.mjs (906f155d)', + ); +});