diff --git a/docs/HANDOVER-CONTRACTS.md b/docs/HANDOVER-CONTRACTS.md index f15642d..89d01c9 100644 --- a/docs/HANDOVER-CONTRACTS.md +++ b/docs/HANDOVER-CONTRACTS.md @@ -12,7 +12,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated: |---|---|---| | `brief.md` | `brief_version` (frontmatter) | `2.2` | | `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"` | | `review.md` | `review_version` (frontmatter) | `1.0` | | `.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:** -| Field | Type | Required | Allowed | -|---|---|---|---| -| `plan_version` | string | yes | `"1.7"` (current) | +`plan.md` has **no required frontmatter**. The only frontmatter block, +`source_findings` (optional), appears for `type: trekreview` plans — see +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):** @@ -261,7 +270,7 @@ The strongest validation in the entire pipeline. Phase 5.5 (planning-orchestrato | `schema_version` | string | yes | `"1"` (current) | | `plan` | string | yes | Path to the plan being executed | | `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 | | | `updated_at` | ISO string | yes | Bumped on every write | | `completed_at` | ISO string | optional | Set when status flips to completed | diff --git a/tests/lib/doc-consistency.test.mjs b/tests/lib/doc-consistency.test.mjs index bbfd555..4e1fcff 100644 --- a/tests/lib/doc-consistency.test.mjs +++ b/tests/lib/doc-consistency.test.mjs @@ -143,6 +143,20 @@ test('templates/plan-template.md declares plan_version: 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 — // "commands/trekexecute.md still parses v1.7 plan schema" was an OR-chain // (`includes('plan_version') || includes('Step N:') || includes('### Step ')`)