mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
Reference in New Issue
Block a user