From 2f8a4ec629b75c7ecefd38a9abc4b1511bc9a891 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:52:25 +0100 Subject: [PATCH] Require a Status line on planning documents Any *_SPEC.md, *_PLAN.md, or *_CONTRACT.md under docs/ must now carry a Status: line (or a ## Status section) in its header, enforced by the pre-push lint. A spec, plan, or contract is a record of a decision, not demand; the 2026-09-01 Home wall case showed what happens when a stale handoff spec reads as a current signal. Subsystem contract markdown is governed separately and is not scanned. The commercial cancellation/reactivation e2e test plan gets the one missing line so the existing set passes. --- ...CANCELLATION_REACTIVATION_E2E_TEST_PLAN.md | 2 + frontend-modern/package.json | 3 +- .../scripts/planning-doc-status-audit.mjs | 61 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 frontend-modern/scripts/planning-doc-status-audit.mjs diff --git a/docs/release-control/v6/internal/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.md b/docs/release-control/v6/internal/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.md index 6fded6389..3659ddb44 100644 --- a/docs/release-control/v6/internal/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.md +++ b/docs/release-control/v6/internal/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.md @@ -1,5 +1,7 @@ # Pulse v6 Commercial Cancellation/Reactivation E2E Test Plan +Status: ACTIVE drill asset (companion for the passed release gate `commercial-cancellation-reactivation`; a test plan, not a demand signal). Last reviewed 2026-09-01. + Use this plan for the trust-critical commercial boundary between: 1. grandfathered v5 recurring subscription continuity while the subscription is still active diff --git a/frontend-modern/package.json b/frontend-modern/package.json index 8c57a319a..c5d9ca354 100644 --- a/frontend-modern/package.json +++ b/frontend-modern/package.json @@ -35,12 +35,13 @@ "test:coverage": "vitest run --coverage --coverage.provider=v8 --coverage.include=src/**/*.ts --coverage.include=src/**/*.tsx --coverage.exclude=src/index.tsx", "test:coverage:ai": "vitest run --coverage --coverage.provider=v8 --coverage.thresholds.100 --coverage.thresholds.perFile --coverage.include=src/components/AI/aiChatUtils.ts", "type-check": "tsc --noEmit", - "lint": "npm run lint:eslint && npm run lint:theme && npm run lint:copy && npm run lint:canonical-platforms && npm run lint:form-labels && npm run lint:external-domains", + "lint": "npm run lint:eslint && npm run lint:theme && npm run lint:copy && npm run lint:canonical-platforms && npm run lint:form-labels && npm run lint:external-domains && npm run lint:planning-docs", "lint:canonical-platforms": "node scripts/canonical-platform-audit.mjs", "lint:eslint": "eslint \"src/**/*.{ts,tsx}\"", "lint:copy": "node scripts/copy-style-audit.mjs", "lint:form-labels": "node scripts/form-label-audit.mjs", "lint:external-domains": "node scripts/external-domain-audit.mjs", + "lint:planning-docs": "node scripts/planning-doc-status-audit.mjs", "lint:theme": "node scripts/theme-audit.mjs", "lint:headers": "node scripts/header-audit.mjs", "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix", diff --git a/frontend-modern/scripts/planning-doc-status-audit.mjs b/frontend-modern/scripts/planning-doc-status-audit.mjs new file mode 100644 index 000000000..e3a24f2fd --- /dev/null +++ b/frontend-modern/scripts/planning-doc-status-audit.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +// Requires an explicit status marker on every planning document under docs/. +// +// Context: on 2026-09-01 the autonomous web-product lane found +// docs/release-control/v6/internal/HOME_STATUS_WALL_SPEC.md (a handoff spec +// written 2026-07-10) and shipped it as a new navigation tab with no demand +// ledger entry. A repository spec, plan, or contract is a record of a past +// decision, not demand; the maintainer prompts now say so, and the stale-spec +// triage of 2026-09-01 gave every existing planning doc an honest Status line. +// This audit keeps that true for new documents: any *_SPEC.md, *_PLAN.md, or +// *_CONTRACT.md under docs/ must carry a `Status:` line (or a `## Status` +// section) in its header, so nobody, human or agent, has to infer whether the +// document is live, implemented, superseded, or parked. +// +// Subsystem contract markdown lives under +// docs/release-control/v6/internal/subsystems/ and is governed separately, so +// it is not scanned even though the filenames do not match the suffixes above. +import fs from 'node:fs'; +import path from 'node:path'; + +const ROOT = process.cwd(); +const REPO_ROOT = path.resolve(ROOT, '..'); +const DOCS_ROOT = path.join(REPO_ROOT, 'docs'); +const SKIP_DIRS = new Set([path.join(DOCS_ROOT, 'release-control', 'v6', 'internal', 'subsystems')]); +const PLANNING_SUFFIX = /_(SPEC|PLAN|CONTRACT)\.md$/; +const HEADER_LINES = 40; +const STATUS_LINE = /^(Status:\s*\S|## Status\s*$)/; + +function walk(dir, out) { + if (SKIP_DIRS.has(dir)) return out; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full, out); + else if (entry.isFile() && PLANNING_SUFFIX.test(entry.name)) out.push(full); + } + return out; +} + +const failures = []; +for (const file of walk(DOCS_ROOT, []).sort()) { + const head = fs.readFileSync(file, 'utf8').split('\n').slice(0, HEADER_LINES); + if (!head.some((line) => STATUS_LINE.test(line))) { + failures.push(path.relative(REPO_ROOT, file)); + } +} + +if (failures.length > 0) { + console.error('planning-doc-status-audit: planning documents without a Status line:'); + for (const file of failures) console.error(` ${file}`); + console.error(''); + console.error( + `Add a "Status:" line within the first ${HEADER_LINES} lines. A spec, plan, or contract is a\n` + + 'record of a decision, not demand: say whether it is ACTIVE (with its\n' + + 'FEATURE_REQUESTS.md ledger entry), IMPLEMENTED (cite the code), SUPERSEDED (name the\n' + + 'replacement), or PARKED (not a current signal; reactivate only through a\n' + + 'FEATURE_REQUESTS.md ledger entry), and date the review.', + ); + process.exit(1); +} + +console.log('planning-doc-status-audit: ok');