docs: publish legacy feature catalogue
Build and publish 3D Architecture Wizzard documentation / build (pull_request) Successful in 22s
Build and publish 3D Architecture Wizzard documentation / build (pull_request) Successful in 22s
This commit is contained in:
+276
-42
@@ -1,32 +1,98 @@
|
||||
import {readFileSync, readdirSync, mkdirSync, writeFileSync, existsSync} from 'node:fs';
|
||||
import {dirname, join} from 'node:path';
|
||||
import {dirname, join, relative} from 'node:path';
|
||||
import process from 'node:process';
|
||||
|
||||
const root = process.cwd();
|
||||
const sourceDir = join(root, 'data/scope/epics');
|
||||
const epicSourceDir = join(root, 'data/scope/epics');
|
||||
const featureSourceDir = join(root, 'data/scope/features');
|
||||
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)));
|
||||
const numericId = (id) => Number(id.split('-').at(-1));
|
||||
const readRecords = (directory, recordPattern) => readdirSync(directory)
|
||||
.filter((name) => recordPattern.test(name))
|
||||
.map((name) => JSON.parse(readFileSync(join(directory, name), 'utf8')))
|
||||
.sort((left, right) => numericId(left.id) - numericId(right.id));
|
||||
|
||||
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}`);
|
||||
const epics = readRecords(epicSourceDir, /^3darch-ep-\d+\.yml$/);
|
||||
const features = readRecords(featureSourceDir, /^3darch-ft-\d+\.yml$/);
|
||||
if (epics.length !== 10) throw new Error(`Expected 10 Epic records, found ${epics.length}`);
|
||||
if (features.length !== 32) throw new Error(`Expected 32 Feature records, found ${features.length}`);
|
||||
|
||||
const epicIds = new Set();
|
||||
const featureIds = new Set();
|
||||
for (const [index, epic] of epics.entries()) {
|
||||
const expectedId = `3DARCH-EP-${index + 1}`;
|
||||
if (epic.id !== expectedId) throw new Error(`Expected ${expectedId}, found ${epic.id}`);
|
||||
if (epicIds.has(epic.id)) throw new Error(`Duplicate Epic ID: ${epic.id}`);
|
||||
epicIds.add(epic.id);
|
||||
if (epic.type !== 'Epic' || epic.status_source !== 'DERIVED') throw new Error(`Invalid governed Epic 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}`);
|
||||
if (!epic.catalogue_decision?.evidence_url || !epic.last_transition?.evidence_url) throw new Error(`Missing Epic decision evidence: ${epic.id}`);
|
||||
if (!Array.isArray(epic.legacy_evidence) || epic.legacy_evidence.length === 0) throw new Error(`Missing Epic legacy evidence: ${epic.id}`);
|
||||
}
|
||||
for (const [index, feature] of features.entries()) {
|
||||
const expectedId = `3DARCH-FT-${index + 1}`;
|
||||
if (feature.id !== expectedId) throw new Error(`Expected ${expectedId}, found ${feature.id}`);
|
||||
if (featureIds.has(feature.id)) throw new Error(`Duplicate Feature ID: ${feature.id}`);
|
||||
featureIds.add(feature.id);
|
||||
if (feature.type !== 'Feature' || feature.status !== 'IN_BACKLOG' || feature.status_source !== 'EXACT') throw new Error(`Invalid governed Feature state: ${feature.id}`);
|
||||
if (!epicIds.has(feature.epic_id)) throw new Error(`Dangling Epic relationship: ${feature.id} -> ${feature.epic_id}`);
|
||||
if (feature.approval?.approved_for_solution !== false) throw new Error(`Unexpected solution approval: ${feature.id}`);
|
||||
for (const field of ['description', 'user_problem', 'expected_value', 'scope', 'owner']) if (!feature[field]) throw new Error(`Missing ${field}: ${feature.id}`);
|
||||
for (const field of ['exclusions', 'acceptance_outcomes', 'risks', 'legacy_evidence']) if (!Array.isArray(feature[field]) || feature[field].length === 0) throw new Error(`Missing ${field}: ${feature.id}`);
|
||||
if (feature.acceptance_outcomes.length < 3) throw new Error(`Too few acceptance outcomes: ${feature.id}`);
|
||||
if (!Array.isArray(feature.dependencies) || !Array.isArray(feature.required_tasks)) throw new Error(`Invalid relationships: ${feature.id}`);
|
||||
if (!feature.catalogue_decision?.evidence_url || feature.catalogue_decision.decision !== 'Approved for documentation') throw new Error(`Missing Feature documentation decision: ${feature.id}`);
|
||||
if (!feature.last_transition?.evidence_url || feature.last_transition.event !== 'Catalogue registration' || feature.last_transition.to !== feature.status || !feature.last_transition.authority || !feature.last_transition.reason) throw new Error(`Missing distinct Feature lifecycle evidence: ${feature.id}`);
|
||||
}
|
||||
for (const feature of features) {
|
||||
for (const dependency of feature.dependencies) {
|
||||
if (!featureIds.has(dependency)) throw new Error(`Dangling Feature dependency: ${feature.id} -> ${dependency}`);
|
||||
if (dependency === feature.id) throw new Error(`Self dependency: ${feature.id}`);
|
||||
}
|
||||
}
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const featureById = new Map(features.map((feature) => [feature.id, feature]));
|
||||
const visit = (id) => {
|
||||
if (visiting.has(id)) throw new Error(`Feature dependency cycle at ${id}`);
|
||||
if (visited.has(id)) return;
|
||||
visiting.add(id);
|
||||
for (const dependency of featureById.get(id).dependencies) visit(dependency);
|
||||
visiting.delete(id);
|
||||
visited.add(id);
|
||||
};
|
||||
for (const feature of features) visit(feature.id);
|
||||
|
||||
const featuresByEpic = new Map(epics.map((epic) => [epic.id, features.filter((feature) => feature.epic_id === epic.id)]));
|
||||
for (const epic of epics) {
|
||||
const childIds = featuresByEpic.get(epic.id).map((feature) => feature.id);
|
||||
if (JSON.stringify(epic.required_features) !== JSON.stringify(childIds)) throw new Error(`One-sided or unsorted Feature relationship: ${epic.id}`);
|
||||
const statuses = featuresByEpic.get(epic.id).map((feature) => feature.status);
|
||||
const derived = statuses.every((status) => status === 'DONE') ? 'DONE'
|
||||
: statuses.every((status) => ['TO_BE_RELEASED', 'DONE'].includes(status)) ? 'TO_BE_RELEASED'
|
||||
: statuses.some((status) => ['IN_DELIVERY', 'TO_BE_RELEASED', 'DONE'].includes(status)) ? 'IN_DELIVERY'
|
||||
: statuses.some((status) => status === 'IN_DESIGN') ? 'IN_DESIGN' : 'IN_BACKLOG';
|
||||
if (epic.status !== derived) throw new Error(`Derived status mismatch: ${epic.id} is ${epic.status}, expected ${derived}`);
|
||||
}
|
||||
|
||||
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 sourceList = (record) => record.sources.map((source) => `- [${source.label}](${source.url})`).join('\n');
|
||||
const evidenceList = (record) => record.legacy_evidence.map((source) => `- [\`${source.path}\`](${source.url})`).join('\n');
|
||||
const bulletList = (items) => items.map((item) => `- ${item}`).join('\n');
|
||||
const featureRoute = (feature) => `/scope/epics/${feature.epic_id.toLowerCase()}/features/${feature.id.toLowerCase()}/`;
|
||||
const epicRoute = (epic) => `/scope/epics/${epic.id.toLowerCase()}/`;
|
||||
const relativeFeatureLink = (fromFeature, targetFeature) => {
|
||||
const fromDir = join('scope/epics', fromFeature.epic_id.toLowerCase(), 'features');
|
||||
const target = join('scope/epics', targetFeature.epic_id.toLowerCase(), 'features', `${targetFeature.id.toLowerCase()}.md`);
|
||||
const result = relative(fromDir, target).replaceAll('\\', '/');
|
||||
return result.startsWith('.') ? result : `./${result}`;
|
||||
};
|
||||
const featureLinkList = (epic, prefix) => featuresByEpic.get(epic.id)
|
||||
.map((feature) => `- [${feature.id} — ${feature.title}](${prefix}${feature.id.toLowerCase()}.md)`)
|
||||
.join('\n');
|
||||
|
||||
const epicPage = (epic) => `---
|
||||
title: "${epic.id} — ${epic.title}"
|
||||
description: "Use this approved Epic boundary to assess related Features and outcomes."
|
||||
description: "Review the governed outcome and child Features for ${epic.id}."
|
||||
---
|
||||
|
||||
# ${epic.title}
|
||||
@@ -49,11 +115,13 @@ ${epic.problem}
|
||||
|
||||
## Constraints
|
||||
|
||||
${epic.constraints.map((item) => `- ${item}`).join('\n')}
|
||||
${bulletList(epic.constraints)}
|
||||
|
||||
## 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.
|
||||
${featureLinkList(epic, `./${epic.id.toLowerCase()}/features/`)}
|
||||
|
||||
These Features are approved for documentation only. Each remains outside Architecture solution work until a separate human **Approved for Solution** decision is recorded.
|
||||
|
||||
## Original source
|
||||
|
||||
@@ -61,66 +129,232 @@ ${sourceList(epic)}
|
||||
|
||||
### Legacy implementation evidence
|
||||
|
||||
The approved catalogue was mined from legacy revision \`${epic.legacy_revision}\`.
|
||||
The 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}."
|
||||
description: "Review the governed Features beneath ${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.
|
||||
These Features decompose the [${epic.id} boundary](../${epic.id.toLowerCase()}.md) into non-overlapping user and operator outcomes grounded in the frozen legacy revision.
|
||||
|
||||
${featureLinkList(epic, './features/')}
|
||||
|
||||
Documentation approval does not grant **Approved for Solution** or implementation approval.
|
||||
`;
|
||||
|
||||
const epicLinks = records.map((epic) => `- [${epic.id} — ${epic.title}](./${epic.id.toLowerCase()}.md)`).join('\n');
|
||||
const featurePage = (feature) => {
|
||||
const epic = epics.find((item) => item.id === feature.epic_id);
|
||||
const dependencyLines = feature.dependencies.length
|
||||
? feature.dependencies.map((id) => {
|
||||
const target = featureById.get(id);
|
||||
return `- [${target.id} — ${target.title}](${relativeFeatureLink(feature, target)})`;
|
||||
}).join('\n')
|
||||
: '- None recorded.';
|
||||
return `---
|
||||
title: "${feature.id} — ${feature.title}"
|
||||
description: "${feature.description.replaceAll('"', '\\"')}"
|
||||
---
|
||||
|
||||
# ${feature.title}
|
||||
|
||||
| Feature | Parent Epic | Delivery status | Status source | Owner | Approved for Solution |
|
||||
|---|---|---|---|---|---|
|
||||
| \`${feature.id}\` | [${epic.id} — ${epic.title}](../../${epic.id.toLowerCase()}.md) | \`${feature.status}\` | \`${feature.status_source}\` | ${feature.owner} | No |
|
||||
|
||||
## Description
|
||||
|
||||
${feature.description}
|
||||
|
||||
## User or operator problem
|
||||
|
||||
${feature.user_problem}
|
||||
|
||||
## Expected value
|
||||
|
||||
${feature.expected_value}
|
||||
|
||||
## Scope
|
||||
|
||||
${feature.scope}
|
||||
|
||||
### Exclusions
|
||||
|
||||
${bulletList(feature.exclusions)}
|
||||
|
||||
## Acceptance outcomes
|
||||
|
||||
${feature.acceptance_outcomes.map((item) => `- [ ] ${item}`).join('\n')}
|
||||
|
||||
## Dependencies
|
||||
|
||||
${dependencyLines}
|
||||
|
||||
## Risks
|
||||
|
||||
${bulletList(feature.risks)}
|
||||
|
||||
## Human approval
|
||||
|
||||
Linas approved this Feature for documentation on ${feature.catalogue_decision.decided_at}. It is **not Approved for Solution** and has no implementation approval.
|
||||
|
||||
- [Documentation decision](${feature.catalogue_decision.evidence_url})
|
||||
|
||||
## Delivery status evidence
|
||||
|
||||
The Feature was separately registered at \`${feature.last_transition.to}\` as an accepted catalogue boundary. This records backlog retention, not design or delivery progress.
|
||||
|
||||
- Event: ${feature.last_transition.event}
|
||||
- Actor and authority: ${feature.last_transition.actor} — ${feature.last_transition.authority}
|
||||
- Reason: ${feature.last_transition.reason}
|
||||
- [Status evidence](${feature.last_transition.evidence_url})
|
||||
|
||||
## Architecture traceability
|
||||
|
||||
- Requirements: none derived.
|
||||
- Readiness: ${feature.architecture_traceability.readiness}.
|
||||
|
||||
Architecture solution work must not begin until a separate human **Approved for Solution** decision is recorded.
|
||||
|
||||
## Tasks
|
||||
|
||||
[Review the Tasks group](./${feature.id.toLowerCase()}/tasks.md). No canonical implementation Tasks have been defined.
|
||||
|
||||
## Original source
|
||||
|
||||
${sourceList(feature)}
|
||||
|
||||
### Legacy implementation evidence
|
||||
|
||||
The Feature boundary was mined from legacy revision \`${feature.legacy_revision}\`.
|
||||
|
||||
${evidenceList(feature)}
|
||||
`;
|
||||
};
|
||||
|
||||
const tasksPage = (feature) => `---
|
||||
title: "${feature.id} Tasks"
|
||||
description: "Review canonical implementation Tasks governed beneath ${feature.id}."
|
||||
---
|
||||
|
||||
# Review ${feature.id} Tasks
|
||||
|
||||
No canonical implementation Tasks have been defined for [${feature.id}](../${feature.id.toLowerCase()}.md). Architecture must first receive a separate human **Approved for Solution** decision, derive requirements and readiness evidence, and then register any resulting Tasks through Scope governance.
|
||||
`;
|
||||
|
||||
const epicLinks = epics.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."
|
||||
description: "Review the approved experience-led Epic catalogue and its documented Features."
|
||||
---
|
||||
|
||||
# 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.
|
||||
These ten Epics define the documented product boundary mined from materially implemented legacy behavior. Their 32 child Features provide the approved documentation-level decomposition without granting solution or implementation approval.
|
||||
|
||||
${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 roadmapRows = epics.map((epic) => `| Epic | [${epic.id}](./epics/${epic.id.toLowerCase()}.md) | — | ${epic.title} | \`${epic.status}\` | Current documented catalogue; no delivery forecast | ${epic.owner} | [2026-09-02 evidence](${epic.catalogue_decision.evidence_url}) |`).concat(
|
||||
features.map((feature) => `| Feature | [${feature.id}](./epics/${feature.epic_id.toLowerCase()}/features/${feature.id.toLowerCase()}.md) | [${feature.epic_id}](./epics/${feature.epic_id.toLowerCase()}.md) | ${feature.title} | \`${feature.status}\` | Current documented catalogue; no delivery forecast | ${feature.owner} | [2026-09-02 evidence](${feature.catalogue_decision.evidence_url}) |`),
|
||||
).join('\n');
|
||||
const roadmap = `---
|
||||
title: "Roadmap"
|
||||
description: "Distinguish the approved Epic baseline from future sequencing and delivery forecasts."
|
||||
description: "Distinguish the documented Epic and Feature 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.
|
||||
The current roadmap records the documented Epic and Feature catalogue without inventing dates or delivery commitments. All Epics and Features remain in \`IN_BACKLOG\`; Feature dependencies are recorded on each canonical Feature page, while sequencing and horizon decisions require separate evidence.
|
||||
|
||||
| Epic | Outcome area | Delivery status | Horizon | Owner | Last evidenced update |
|
||||
|---|---|---|---|---|---|
|
||||
| Type | Record | Parent | 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 linkFor = (id) => {
|
||||
if (epicIds.has(id)) {
|
||||
const epic = epics.find((item) => item.id === id);
|
||||
return {id, title: epic.title, recordUrl: epicRoute(epic)};
|
||||
}
|
||||
const feature = featureById.get(id);
|
||||
return {id, title: feature.title, recordUrl: featureRoute(feature)};
|
||||
};
|
||||
const issues = [
|
||||
...epics.map((epic) => ({
|
||||
id: epic.id, type: epic.type, title: epic.title, parent: null, status: epic.status,
|
||||
statusSource: epic.status_source, blocked: epic.blocked, blockedReason: epic.blocked_reason, owner: epic.owner,
|
||||
approval: epic.approval.approved_for_solution ? 'Approved for Solution' : 'Not approved for Solution',
|
||||
requiredChildren: epic.required_features.map(linkFor), dependencies: epic.dependencies.map(linkFor),
|
||||
releaseEvidence: epic.release_evidence ?? [], evidenceUrl: epic.last_transition.evidence_url, recordUrl: epicRoute(epic),
|
||||
})),
|
||||
...features.map((feature) => ({
|
||||
id: feature.id, type: feature.type, title: feature.title, parent: linkFor(feature.epic_id), status: feature.status,
|
||||
statusSource: feature.status_source, blocked: feature.blocked, blockedReason: feature.blocked_reason, owner: feature.owner,
|
||||
approval: feature.approval.approved_for_solution ? 'Approved for Solution' : 'Not approved for Solution',
|
||||
requiredChildren: feature.required_tasks.map(linkFor), dependencies: feature.dependencies.map(linkFor),
|
||||
releaseEvidence: feature.release_evidence, evidenceUrl: feature.last_transition.evidence_url, recordUrl: featureRoute(feature),
|
||||
})),
|
||||
];
|
||||
const ts = `// Generated by scripts/generate-scope.mjs from canonical Scope records. Do not edit.\nexport type ScopeIssueLink = {id:string; title:string; recordUrl:string};\nexport type ScopeIssue = {id:string; type:string; title:string; parent:ScopeIssueLink|null; status:string; statusSource:string; blocked:boolean; blockedReason:string|null; owner:string; approval:string; requiredChildren:ScopeIssueLink[]; dependencies:ScopeIssueLink[]; releaseEvidence:unknown[]; evidenceUrl:string; recordUrl:string};\nexport const scopeIssues: ScopeIssue[] = ${JSON.stringify(issues, null, 2)};\n`;
|
||||
|
||||
const sidebarItems = epics.map((epic) => ` {
|
||||
type: 'category' as const,
|
||||
label: '${epic.id}',
|
||||
link: {type: 'doc' as const, id: 'epics/${epic.id.toLowerCase()}'},
|
||||
items: [{
|
||||
type: 'category' as const,
|
||||
label: 'Features',
|
||||
link: {type: 'doc' as const, id: 'epics/${epic.id.toLowerCase()}/features'},
|
||||
items: [
|
||||
${featuresByEpic.get(epic.id).map((feature) => ` {
|
||||
type: 'category' as const,
|
||||
label: '${feature.id}',
|
||||
link: {type: 'doc' as const, id: 'epics/${epic.id.toLowerCase()}/features/${feature.id.toLowerCase()}'},
|
||||
items: [{type: 'category' as const, label: 'Tasks', link: {type: 'doc' as const, id: 'epics/${epic.id.toLowerCase()}/features/${feature.id.toLowerCase()}/tasks'}, items: []}],
|
||||
}`).join(',\n')}
|
||||
],
|
||||
}],
|
||||
}`).join(',\n');
|
||||
const sidebar = `import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
// Generated by scripts/generate-scope.mjs from canonical Scope records. Do not edit.
|
||||
const sidebars: SidebarsConfig = {
|
||||
scopeSidebar: [
|
||||
'overview',
|
||||
'roadmap',
|
||||
{type: 'category', label: 'Ideas', link: {type: 'doc', id: 'ideas'}, items: []},
|
||||
{type: 'category', label: 'Questions', link: {type: 'doc', id: 'questions'}, items: []},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Epics',
|
||||
link: {type: 'doc', id: 'epics/index'},
|
||||
collapsed: false,
|
||||
items: [
|
||||
${sidebarItems}
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
export default sidebars;
|
||||
`;
|
||||
|
||||
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)]),
|
||||
[join(root, 'sidebarsScope.ts'), sidebar],
|
||||
...epics.map((epic) => [join(root, `scope/epics/${epic.id.toLowerCase()}.md`), epicPage(epic)]),
|
||||
...epics.map((epic) => [join(root, `scope/epics/${epic.id.toLowerCase()}/features.md`), featuresPage(epic)]),
|
||||
...features.map((feature) => [join(root, `scope/epics/${feature.epic_id.toLowerCase()}/features/${feature.id.toLowerCase()}.md`), featurePage(feature)]),
|
||||
...features.map((feature) => [join(root, `scope/epics/${feature.epic_id.toLowerCase()}/features/${feature.id.toLowerCase()}/tasks.md`), tasksPage(feature)]),
|
||||
]);
|
||||
|
||||
let stale = false;
|
||||
@@ -136,4 +370,4 @@ for (const [path, content] of expected) {
|
||||
}
|
||||
}
|
||||
if (stale) process.exit(1);
|
||||
console.log(`${check ? 'Verified' : 'Generated'} ${expected.size} Scope artifacts from ${records.length} canonical Epics.`);
|
||||
console.log(`${check ? 'Verified' : 'Generated'} ${expected.size} Scope artifacts from ${epics.length} canonical Epics and ${features.length} canonical Features.`);
|
||||
|
||||
Reference in New Issue
Block a user