docs(voyage): S28 — locate plan_version in plan header line, not frontmatter

HANDOVER-CONTRACTS.md was the lone source that called plan_version a
frontmatter field. The plan template (templates/plan-template.md:21) emits it
ONLY in the prose "Generated by" header line; brief-generated plans carry no
frontmatter at all (the sole frontmatter block, source_findings, is
trekreview-only and additive). planning-orchestrator.md:228 ("metadata line
below the title"), the parser (lib/parsers/plan-schema.mjs, S26), and the
plan-schema.test.mjs comment all already say prose. The contract was the
outlier — the deferred doc-side closure the S26 commit named as scope to avoid
then ("a 3-file doc reconciliation").

Operator-chosen direction (Option A + A1): correct the contract to the
artifact, doc-only — NOT move the field to frontmatter (which would alter every
plan's shape and fight the trekreview-only frontmatter design). A1 keeps the
standardized "Frontmatter schema" heading, corrects the row, and notes the real
(optional, trekreview-only) source_findings frontmatter.

Three surgical spots in HANDOVER-CONTRACTS.md:
- versioning table: `plan_version` (frontmatter) → (plan header line)
- Handover 4 "Frontmatter schema": no-required-frontmatter note + Location
  column locating plan_version in the "Generated by" header line + parser ref
- Handover 5 progress.json: "Mirrors plan's frontmatter" → "Mirrors the plan
  header's plan_version"

TDD (red first): new doc-consistency pin asserts the contract no longer labels
plan_version a (frontmatter) field, drops "Mirrors plan's frontmatter", and
positively locates it in the plan header line. Red on the old wording (L15 +
L264), green after the edits.

Scope: only HANDOVER-CONTRACTS.md + the one pin. Template/orchestrator/parser/
examples untouched (already correct). Example-01's bare-line emission drift is
noted out-of-scope for a possible later item.

Tests: 729 (727 pass / 2 skip / 0 fail; live `node --test`, = 728 baseline +1).
plugin validate passes (1 accepted CLAUDE.md-at-root warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
This commit is contained in:
Kjell Tore Guttormsen 2026-06-19 22:26:02 +02:00
commit 8485ea1d36
2 changed files with 28 additions and 5 deletions

View file

@ -12,7 +12,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated:
|---|---|---| |---|---|---|
| `brief.md` | `brief_version` (frontmatter) | `2.2` | | `brief.md` | `brief_version` (frontmatter) | `2.2` |
| `research/*.md` | (implicit; tracked via `type: trekresearch-brief`) | unversioned | | `research/*.md` | (implicit; tracked via `type: trekresearch-brief`) | unversioned |
| `plan.md` | `plan_version` (frontmatter) | `1.7` | | `plan.md` | `plan_version` (plan header line) | `1.7` |
| `progress.json` | `schema_version` (top-level) | `"1"` | | `progress.json` | `schema_version` (top-level) | `"1"` |
| `review.md` | `review_version` (frontmatter) | `1.0` | | `review.md` | `review_version` (frontmatter) | `1.0` |
| `.session-state.local.json` | `schema_version` (top-level) | `1` (number) | | `.session-state.local.json` | `schema_version` (top-level) | `1` (number) |
@ -203,9 +203,18 @@ The validator (`lib/validators/architecture-discovery.mjs`) is intentionally min
**Frontmatter schema:** **Frontmatter schema:**
| Field | Type | Required | Allowed | `plan.md` has **no required frontmatter**. The only frontmatter block,
|---|---|---|---| `source_findings` (optional), appears for `type: trekreview` plans — see
| `plan_version` | string | yes | `"1.7"` (current) | Handover 6. Brief-generated plans carry no frontmatter at all.
| Field | Type | Required | Location | Allowed |
|---|---|---|---|---|
| `plan_version` | string | yes | plan header line, not frontmatter | `"1.7"` (current) |
`plan_version` is emitted in the plan's "Generated by" header metadata line
(`> Generated by … — `plan_version: 1.7``), parsed by `extractPlanVersion`
(`lib/parsers/plan-schema.mjs`) from either line-start or the backtick-wrapped
prose form (relaxed in S26 so the template's prose emission parses).
**Body invariants (strict, v1.7):** **Body invariants (strict, v1.7):**
@ -261,7 +270,7 @@ The strongest validation in the entire pipeline. Phase 5.5 (planning-orchestrato
| `schema_version` | string | yes | `"1"` (current) | | `schema_version` | string | yes | `"1"` (current) |
| `plan` | string | yes | Path to the plan being executed | | `plan` | string | yes | Path to the plan being executed |
| `plan_type` | string | optional | `plan \| session-spec` | | `plan_type` | string | optional | `plan \| session-spec` |
| `plan_version` | string | yes | Mirrors plan's frontmatter | | `plan_version` | string | yes | Mirrors the plan header's plan_version |
| `started_at` | ISO string | yes | | | `started_at` | ISO string | yes | |
| `updated_at` | ISO string | yes | Bumped on every write | | `updated_at` | ISO string | yes | Bumped on every write |
| `completed_at` | ISO string | optional | Set when status flips to completed | | `completed_at` | ISO string | optional | Set when status flips to completed |

View file

@ -143,6 +143,20 @@ test('templates/plan-template.md declares plan_version: 1.7', () => {
assert.match(tpl, /plan_version:\s*['"]?1\.7['"]?/); assert.match(tpl, /plan_version:\s*['"]?1\.7['"]?/);
}); });
test('HANDOVER-CONTRACTS.md locates plan_version in the plan header line, not frontmatter (S28)', () => {
// The plan template emits plan_version only in the prose "Generated by"
// header line; brief-generated plans carry no frontmatter at all (the sole
// frontmatter block, `source_findings`, is trekreview-only and additive).
// The contract must therefore not label plan_version a frontmatter field.
const t = read('docs/HANDOVER-CONTRACTS.md');
assert.doesNotMatch(t, /`plan_version`\s*\(frontmatter\)/,
'HANDOVER-CONTRACTS.md must not call plan_version a (frontmatter) field — it lives in the plan header line');
assert.doesNotMatch(t, /Mirrors plan's frontmatter/,
"progress.json's plan_version mirrors the plan header line, not frontmatter");
assert.match(t, /`plan_version`\s*\(plan header line\)/,
'HANDOVER-CONTRACTS.md versioning table must locate plan_version in the plan header line');
});
// S19 prune (audit §Top changes #8): removed a tautological prose-pin — // S19 prune (audit §Top changes #8): removed a tautological prose-pin —
// "commands/trekexecute.md still parses v1.7 plan schema" was an OR-chain // "commands/trekexecute.md still parses v1.7 plan schema" was an OR-chain
// (`includes('plan_version') || includes('Step N:') || includes('### Step ')`) // (`includes('plan_version') || includes('Step N:') || includes('### Step ')`)