Files
corp-v1-3darch-documentation/scripts/generate-scope.mjs
jarvis-at-skic 9b85ba4322
Build and publish 3D Architecture Wizzard documentation / build (pull_request) Failing after 21s
fix: link empty Feature groups
2026-09-02 09:45:36 +00:00

140 lines
6.3 KiB
JavaScript

import {readFileSync, readdirSync, mkdirSync, writeFileSync, existsSync} from 'node:fs';
import {dirname, join} from 'node:path';
import process from 'node:process';
const root = process.cwd();
const sourceDir = join(root, 'data/scope/epics');
const check = process.argv.includes('--check');
const records = readdirSync(sourceDir)
.filter((name) => name.endsWith('.yml'))
.map((name) => JSON.parse(readFileSync(join(sourceDir, name), 'utf8')))
.sort((left, right) => Number(left.id.split('-').at(-1)) - Number(right.id.split('-').at(-1)));
if (records.length !== 10) throw new Error(`Expected 10 Epic records, found ${records.length}`);
const ids = new Set();
for (const epic of records) {
if (!/^3DARCH-EP-([1-9]|10)$/.test(epic.id)) throw new Error(`Invalid Epic ID: ${epic.id}`);
if (ids.has(epic.id)) throw new Error(`Duplicate Epic ID: ${epic.id}`);
ids.add(epic.id);
if (epic.type !== 'Epic' || epic.status !== 'IN_BACKLOG' || epic.status_source !== 'EXACT') throw new Error(`Invalid governed state: ${epic.id}`);
if (epic.approval?.approved_for_solution !== false) throw new Error(`Unexpected solution approval: ${epic.id}`);
if (!epic.catalogue_decision?.evidence_url || !epic.last_transition?.evidence_url) throw new Error(`Missing decision evidence: ${epic.id}`);
if (!Array.isArray(epic.legacy_evidence) || epic.legacy_evidence.length === 0) throw new Error(`Missing legacy evidence: ${epic.id}`);
}
const sourceList = (epic) => epic.sources.map((source) => `- [${source.label}](${source.url})`).join('\n');
const evidenceList = (epic) => epic.legacy_evidence.map((source) => `- [\`${source.path}\`](${source.url})`).join('\n');
const epicPage = (epic) => `---
title: "${epic.id} — ${epic.title}"
description: "Use this approved Epic boundary to assess related Features and outcomes."
---
# ${epic.title}
| Epic | Delivery status | Status source | Owner | Approved for Solution |
|---|---|---|---|---|
| \`${epic.id}\` | \`${epic.status}\` | \`${epic.status_source}\` | ${epic.owner} | No |
## Goal
${epic.goal}
## Benefit
${epic.value}
## Problem or opportunity
${epic.problem}
## Constraints
${epic.constraints.map((item) => `- ${item}`).join('\n')}
## Features
No child Features have been defined or approved. Feature discovery must preserve this Epic boundary and requires a separate human **Approved for Solution** decision before Architecture solution work begins.
## Original source
${sourceList(epic)}
### Legacy implementation evidence
The approved catalogue was mined from legacy revision \`${epic.legacy_revision}\`.
${evidenceList(epic)}
`;
const featuresPage = (epic) => `---
title: "${epic.id} Features"
description: "Review Features governed by ${epic.id}."
---
# Review ${epic.id} Features
No child Features have been defined or approved for this Epic. Feature discovery must preserve the [${epic.id} boundary](../${epic.id.toLowerCase()}.md) and requires a separate human **Approved for Solution** decision before Architecture solution work begins.
`;
const epicLinks = records.map((epic) => `- [${epic.id} — ${epic.title}](./${epic.id.toLowerCase()}.md)`).join('\n');
const epicIndex = `---
title: "Epics"
description: "Review the approved experience-led Epic catalogue mined from the legacy platform."
---
# Review the approved Epic catalogue
These ten Epics define the documented product boundary mined from materially implemented legacy behavior. \`3DARCH-EP-1\` owns the engineering foundation; every remaining Epic describes a user experience or usage outcome.
${epicLinks}
Planned-only capabilities—including VR entry, whiteboards, extra Archwiz domains, exports, and undo—remain outside this catalogue until separately evidenced and approved.
`;
const roadmapRows = records.map((epic) => `| [${epic.id}](./epics/${epic.id.toLowerCase()}.md) | ${epic.title} | \`${epic.status}\` | Current approved catalogue; no delivery forecast | ${epic.owner} | [2026-09-02 decision](${epic.catalogue_decision.evidence_url}) |`).join('\n');
const roadmap = `---
title: "Roadmap"
description: "Distinguish the approved Epic baseline from future sequencing and delivery forecasts."
---
# Review the product roadmap baseline
The current roadmap records the approved Epic catalogue without inventing sequence, dates, Feature scope, or delivery commitments. All Epics remain in \`IN_BACKLOG\`; future horizon and dependency decisions require separate evidence and human approval.
| Epic | Outcome area | Delivery status | Horizon | Owner | Last evidenced update |
|---|---|---|---|---|---|
${roadmapRows}
`;
const ts = `// Generated by scripts/generate-scope.mjs from data/scope/epics/*.yml. Do not edit.\nexport type ScopeIssue = {id:string; type:string; title:string; parent:string; status:string; statusSource:string; blocked:boolean; owner:string; approval:string; requiredChildren:string; dependencies:string; evidenceUrl:string; recordUrl:string};\nexport const scopeIssues: ScopeIssue[] = ${JSON.stringify(records.map((epic) => ({
id: epic.id, type: epic.type, title: epic.title, parent: '—', status: epic.status,
statusSource: epic.status_source, blocked: epic.blocked, owner: epic.owner,
approval: epic.approval.approved_for_solution ? 'Approved for Solution' : 'Not approved for Solution',
requiredChildren: epic.required_features.length ? epic.required_features.join(', ') : 'None defined',
dependencies: epic.dependencies.length ? epic.dependencies.join(', ') : 'None recorded',
evidenceUrl: epic.last_transition.evidence_url, recordUrl: `/scope/epics/${epic.id.toLowerCase()}/`,
})), null, 2)};\n`;
const expected = new Map([
[join(root, 'scope/epics/index.md'), epicIndex],
[join(root, 'scope/roadmap.md'), roadmap],
[join(root, 'src/data/scopeIssues.ts'), ts],
...records.map((epic) => [join(root, `scope/epics/${epic.id.toLowerCase()}.md`), epicPage(epic)]),
...records.map((epic) => [join(root, `scope/epics/${epic.id.toLowerCase()}/features.md`), featuresPage(epic)]),
]);
let stale = false;
for (const [path, content] of expected) {
if (check) {
if (!existsSync(path) || readFileSync(path, 'utf8') !== content) {
console.error(`Stale generated artifact: ${path}`);
stale = true;
}
} else {
mkdirSync(dirname(path), {recursive: true});
writeFileSync(path, content);
}
}
if (stale) process.exit(1);
console.log(`${check ? 'Verified' : 'Generated'} ${expected.size} Scope artifacts from ${records.length} canonical Epics.`);