rcf-lite 0.0.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +288 -0
- package/LICENSE +202 -0
- package/README.md +92 -4
- package/bin/rcf-verify.js +122 -0
- package/bin/rcf.js +174 -0
- package/bin/view-supervisor-child.mjs +14 -0
- package/fixtures/canary-manifest.json +103 -0
- package/guidance/README.md +25 -0
- package/guidance/build-cycle-playbook.md +492 -0
- package/guidance/build-cycle.md +40 -0
- package/guidance/document-model.md +45 -0
- package/guidance/elicitation-playbook.md +442 -0
- package/guidance/harness-template.md +246 -0
- package/guidance/managed/README.md +63 -0
- package/guidance/managed/agent-instructions-block.hash +1 -0
- package/guidance/managed/agent-instructions-block.md +220 -0
- package/guidance/managed/legacy-fragment-hashes.json +22 -0
- package/guidance/manifest.json +21 -0
- package/guidance/overview.md +48 -0
- package/guidance/persona-programme.md +44 -0
- package/package.json +69 -8
- package/rcf/adrs/adr-001.json +25 -0
- package/rcf/adrs/adr-002.json +25 -0
- package/rcf/adrs/adr-003.json +25 -0
- package/rcf/adrs/adr-004.json +20 -0
- package/rcf/adrs/adr-005.json +20 -0
- package/rcf/adrs/adr-006.json +25 -0
- package/rcf/adrs/adr-007.json +25 -0
- package/rcf/adrs/adr-008.json +25 -0
- package/rcf/adrs/adr-009.json +25 -0
- package/rcf/build-sequence.json +11 -0
- package/rcf/code-nodes/cn-001.json +14 -0
- package/rcf/code-nodes/cn-002.json +14 -0
- package/rcf/code-nodes/cn-003.json +15 -0
- package/rcf/code-nodes/cn-004.json +17 -0
- package/rcf/code-nodes/cn-005.json +19 -0
- package/rcf/code-nodes/cn-006.json +17 -0
- package/rcf/code-nodes/cn-007.json +16 -0
- package/rcf/code-nodes/cn-008.json +19 -0
- package/rcf/code-nodes/cn-009.json +16 -0
- package/rcf/code-nodes/cn-010.json +18 -0
- package/rcf/code-nodes/cn-011.json +18 -0
- package/rcf/code-nodes/cn-012.json +18 -0
- package/rcf/code-nodes/cn-013.json +19 -0
- package/rcf/code-nodes/cn-014.json +16 -0
- package/rcf/code-nodes/cn-015.json +16 -0
- package/rcf/code-nodes/cn-016.json +16 -0
- package/rcf/code-nodes/cn-017.json +16 -0
- package/rcf/code-nodes/cn-018.json +15 -0
- package/rcf/code-nodes/cn-019.json +16 -0
- package/rcf/code-nodes/cn-020.json +16 -0
- package/rcf/code-nodes/cn-021.json +16 -0
- package/rcf/code-nodes/cn-022.json +16 -0
- package/rcf/code-nodes/cn-023.json +16 -0
- package/rcf/code-nodes/cn-024.json +16 -0
- package/rcf/code-nodes/cn-025.json +16 -0
- package/rcf/code-nodes/cn-026.json +16 -0
- package/rcf/code-nodes/cn-027.json +16 -0
- package/rcf/code-nodes/cn-028.json +16 -0
- package/rcf/code-nodes/cn-029.json +19 -0
- package/rcf/code-nodes/cn-030.json +14 -0
- package/rcf/code-nodes/cn-031.json +14 -0
- package/rcf/code-nodes/cn-032.json +14 -0
- package/rcf/code-nodes/cn-033.json +14 -0
- package/rcf/code-nodes/cn-034.json +14 -0
- package/rcf/code-nodes/cn-035.json +14 -0
- package/rcf/code-nodes/cn-036.json +14 -0
- package/rcf/code-nodes/cn-037.json +14 -0
- package/rcf/code-nodes/cn-038.json +14 -0
- package/rcf/code-nodes/cn-039.json +14 -0
- package/rcf/code-nodes/cn-040.json +14 -0
- package/rcf/code-nodes/cn-041.json +14 -0
- package/rcf/code-nodes/cn-042.json +14 -0
- package/rcf/code-nodes/cn-043.json +14 -0
- package/rcf/code-nodes/cn-044.json +14 -0
- package/rcf/code-nodes/cn-045.json +14 -0
- package/rcf/code-nodes/cn-046.json +14 -0
- package/rcf/code-nodes/cn-047.json +14 -0
- package/rcf/code-nodes/cn-048.json +14 -0
- package/rcf/code-nodes/cn-049.json +14 -0
- package/rcf/code-nodes/cn-050.json +14 -0
- package/rcf/code-nodes/cn-051.json +14 -0
- package/rcf/code-nodes/cn-052.json +14 -0
- package/rcf/code-nodes/cn-053.json +14 -0
- package/rcf/code-nodes/cn-054.json +14 -0
- package/rcf/code-nodes/cn-055.json +14 -0
- package/rcf/code-nodes/cn-056.json +14 -0
- package/rcf/code-nodes/cn-057.json +14 -0
- package/rcf/fbs/fbs-001.json +49 -0
- package/rcf/fbs/fbs-002.json +42 -0
- package/rcf/fbs/fbs-003.json +37 -0
- package/rcf/fbs/fbs-004.json +39 -0
- package/rcf/fbs/fbs-005.json +38 -0
- package/rcf/fbs/fbs-006.json +48 -0
- package/rcf/fbs/fbs-007.json +39 -0
- package/rcf/fbs/fbs-008.json +40 -0
- package/rcf/fbs/fbs-009.json +36 -0
- package/rcf/fbs/fbs-010.json +41 -0
- package/rcf/fbs/fbs-011.json +36 -0
- package/rcf/fbs/fbs-012.json +46 -0
- package/rcf/fbs/fbs-013.json +42 -0
- package/rcf/fbs/fbs-014.json +49 -0
- package/rcf/fbs/fbs-015.json +32 -0
- package/rcf/manifest.json +17 -0
- package/rcf/prd.json +47 -0
- package/rcf/requirements/req-001.json +19 -0
- package/rcf/requirements/req-002.json +19 -0
- package/rcf/requirements/req-003.json +19 -0
- package/rcf/requirements/req-004.json +19 -0
- package/rcf/requirements/req-005.json +19 -0
- package/rcf/requirements/req-006.json +19 -0
- package/rcf/requirements/req-007.json +19 -0
- package/rcf/requirements/req-008.json +19 -0
- package/rcf/requirements/req-009.json +19 -0
- package/rcf/tacs/tac-001.json +45 -0
- package/rcf/tacs/tac-002.json +109 -0
- package/rcf/tacs/tac-003.json +40 -0
- package/rcf/tacs/tac-004.json +51 -0
- package/rcf/tacs/tac-005.json +52 -0
- package/rcf/tacs/tac-006.json +104 -0
- package/rcf/tacs/tac-007.json +38 -0
- package/rcf/tacs/tac-008.json +51 -0
- package/rcf/tad.json +51 -0
- package/rcf/test-suites/PENDING.md +23 -0
- package/rcf/test-suites/ts-001.json +38 -0
- package/rcf/test-suites/ts-002.json +38 -0
- package/rcf/test-suites/ts-003.json +43 -0
- package/rcf/test-suites/ts-004.json +44 -0
- package/rcf/test-suites/ts-005.json +30 -0
- package/rcf/test-suites/ts-006.json +36 -0
- package/rcf/test-suites/ts-007.json +43 -0
- package/rcf/test-suites/ts-008.json +37 -0
- package/rcf/test-suites/ts-009.json +38 -0
- package/rcf/test-suites/ts-010.json +38 -0
- package/rcf/test-suites/ts-011.json +44 -0
- package/rcf/test-suites/ts-012.json +36 -0
- package/rcf/test-suites/ts-013.json +38 -0
- package/rcf/test-suites/ts-014.json +38 -0
- package/rcf/test-suites/ts-015.json +38 -0
- package/rcf/test-suites/ts-016.json +37 -0
- package/rcf/test-suites/ts-017.json +38 -0
- package/rcf/test-suites/ts-018.json +38 -0
- package/rcf/test-suites/ts-019.json +37 -0
- package/rcf/test-suites/ts-020.json +46 -0
- package/rcf/test-suites/ts-021.json +46 -0
- package/rcf/test-suites/ts-022.json +46 -0
- package/rcf/test-suites/ts-023.json +46 -0
- package/rcf/test-suites/ts-024.json +46 -0
- package/rcf/test-suites/ts-025.json +52 -0
- package/rcf/user-stories/us-101.json +40 -0
- package/rcf/user-stories/us-102.json +40 -0
- package/rcf/user-stories/us-201.json +40 -0
- package/rcf/user-stories/us-202.json +40 -0
- package/rcf/user-stories/us-203.json +32 -0
- package/rcf/user-stories/us-301.json +40 -0
- package/rcf/user-stories/us-302.json +40 -0
- package/rcf/user-stories/us-303.json +40 -0
- package/rcf/user-stories/us-304.json +40 -0
- package/rcf/user-stories/us-401.json +40 -0
- package/rcf/user-stories/us-402.json +40 -0
- package/rcf/user-stories/us-403.json +40 -0
- package/rcf/user-stories/us-501.json +40 -0
- package/rcf/user-stories/us-502.json +40 -0
- package/rcf/user-stories/us-503.json +40 -0
- package/rcf/user-stories/us-601.json +40 -0
- package/rcf/user-stories/us-602.json +40 -0
- package/rcf/user-stories/us-701.json +40 -0
- package/rcf/user-stories/us-702.json +40 -0
- package/rcf/user-stories/us-801.json +49 -0
- package/rcf/user-stories/us-802.json +49 -0
- package/rcf/user-stories/us-803.json +49 -0
- package/rcf/user-stories/us-804.json +49 -0
- package/rcf/user-stories/us-805.json +49 -0
- package/rcf/user-stories/us-901.json +40 -0
- package/src/.gitkeep +0 -0
- package/src/browser-verify/auth-smoke.js +109 -0
- package/src/browser-verify/index.js +29 -0
- package/src/browser-verify/invariants.js +336 -0
- package/src/browser-verify/manifest-writer.js +189 -0
- package/src/browser-verify/runner.js +171 -0
- package/src/build/bundle.js +198 -0
- package/src/build/formatters/json.js +18 -0
- package/src/build/formatters/markdown.js +406 -0
- package/src/build/index.js +14 -0
- package/src/build/mark.js +177 -0
- package/src/build/queue.js +285 -0
- package/src/cli/browser-verify.js +231 -0
- package/src/cli/build.js +584 -0
- package/src/cli/coverage.js +219 -0
- package/src/cli/create.js +355 -0
- package/src/cli/delete.js +127 -0
- package/src/cli/design.js +353 -0
- package/src/cli/doctor.js +548 -0
- package/src/cli/fbs.js +171 -0
- package/src/cli/finalise.js +377 -0
- package/src/cli/guidance.js +206 -0
- package/src/cli/help.js +156 -0
- package/src/cli/impact.js +119 -0
- package/src/cli/init.js +282 -0
- package/src/cli/intake.js +153 -0
- package/src/cli/link.js +128 -0
- package/src/cli/mcp.js +160 -0
- package/src/cli/preflight.js +220 -0
- package/src/cli/read.js +162 -0
- package/src/cli/req-baseline.js +269 -0
- package/src/cli/req-classify.js +135 -0
- package/src/cli/review.js +295 -0
- package/src/cli/test-suite.js +216 -0
- package/src/cli/trace.js +172 -0
- package/src/cli/ui-baseline.js +292 -0
- package/src/cli/ui-classify.js +108 -0
- package/src/cli/update.js +197 -0
- package/src/cli/validate.js +168 -0
- package/src/cli/view.js +452 -0
- package/src/core/baseline-catalog/data/auth.json +42 -0
- package/src/core/baseline-catalog/data/http-api.json +42 -0
- package/src/core/baseline-catalog/data/notifications.json +33 -0
- package/src/core/baseline-catalog/data/persistence.json +33 -0
- package/src/core/baseline-catalog/data/web-ui.json +60 -0
- package/src/core/baseline-catalog/index.js +121 -0
- package/src/core/errors/index.js +167 -0
- package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
- package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
- package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
- package/src/core/isolation/index.js +60 -0
- package/src/core/mcp/framing.js +103 -0
- package/src/core/mcp/index.js +8 -0
- package/src/core/mcp/server.js +228 -0
- package/src/core/patterns/register-canary.js +209 -0
- package/src/core/patterns/req-shapes.js +158 -0
- package/src/core/patterns/services.js +358 -0
- package/src/core/patterns/ui-shapes.js +166 -0
- package/src/core/store/cn-resolve.js +134 -0
- package/src/core/store/derive-deps.js +93 -0
- package/src/core/store/ids.js +78 -0
- package/src/core/store/index.js +20 -0
- package/src/core/store/init.js +255 -0
- package/src/core/store/loader.js +211 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +898 -0
- package/src/core/store/writer.js +1849 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +129 -0
- package/src/finalise/index.js +17 -0
- package/src/finalise/ingest.js +119 -0
- package/src/finalise/install.js +119 -0
- package/src/finalise/ship-without-verified.js +131 -0
- package/src/finalise/spawn.js +84 -0
- package/src/intake/fidelity.js +105 -0
- package/src/intake/index.js +6 -0
- package/src/intake/manifest-writer.js +100 -0
- package/src/intake/orchestrator.js +138 -0
- package/src/intake/validate.js +80 -0
- package/src/mcp/map-errors.js +131 -0
- package/src/mcp/prompts.js +49 -0
- package/src/mcp/resources.js +244 -0
- package/src/mcp/tools.js +1204 -0
- package/src/preflight/design-shapes.js +185 -0
- package/src/preflight/index.js +90 -0
- package/src/preflight/manifest-writer.js +264 -0
- package/src/preflight/scanner.js +206 -0
- package/src/preflight/secrets.js +134 -0
- package/src/preflight/session.js +246 -0
- package/src/query/attestation.js +285 -0
- package/src/query/coverage.js +308 -0
- package/src/query/formatters/json.js +21 -0
- package/src/query/formatters/mermaid.js +209 -0
- package/src/query/formatters/table.js +203 -0
- package/src/query/impact.js +173 -0
- package/src/query/index.js +9 -0
- package/src/query/trace.js +345 -0
- package/src/register-canary/fixture-loader.js +87 -0
- package/src/register-canary/index.js +10 -0
- package/src/register-canary/record-writer.js +132 -0
- package/src/register-canary/runner.js +156 -0
- package/src/req-baseline/gate.js +86 -0
- package/src/req-baseline/index.js +27 -0
- package/src/req-baseline/open-candidates.js +143 -0
- package/src/req-baseline/opt-out.js +195 -0
- package/src/req-baseline/sweep.js +230 -0
- package/src/req-detection/classifier.js +181 -0
- package/src/req-detection/index.js +9 -0
- package/src/req-detection/persist.js +55 -0
- package/src/review/index.js +325 -0
- package/src/review/mutation.js +117 -0
- package/src/review/ui-baseline-drift.js +138 -0
- package/src/server/index.js +178 -0
- package/src/server/routes.js +110 -0
- package/src/server/sse.js +118 -0
- package/src/setup/agent-setup.js +362 -0
- package/src/setup/identity-seed.js +104 -0
- package/src/setup/knowledge-seed.js +123 -0
- package/src/setup/managed-block.js +193 -0
- package/src/setup/managed-gitignore.js +166 -0
- package/src/setup/managed-markers.js +49 -0
- package/src/ui-baseline/defaults.js +119 -0
- package/src/ui-baseline/index.js +25 -0
- package/src/ui-baseline/manifest-writer.js +282 -0
- package/src/ui-baseline/session.js +178 -0
- package/src/ui-detection/classifier.js +192 -0
- package/src/verify/chain/index.js +190 -0
- package/src/verify/cli/cleanup.js +61 -0
- package/src/verify/cli/help.js +56 -0
- package/src/verify/cli/mcp.js +98 -0
- package/src/verify/cli/provision.js +71 -0
- package/src/verify/cli/report.js +71 -0
- package/src/verify/cli/run.js +155 -0
- package/src/verify/engine/brief.js +87 -0
- package/src/verify/engine/index.js +177 -0
- package/src/verify/engine/launcher.js +307 -0
- package/src/verify/mcp/tools.js +107 -0
- package/src/verify/profile/index.js +146 -0
- package/src/verify/provision/index.js +256 -0
- package/src/verify/report/index.js +139 -0
- package/src/verify/report/renderer.js +118 -0
- package/src/verify/verdict/index.js +246 -0
- package/src/view/doc-renderers/adr.js +44 -0
- package/src/view/doc-renderers/build-sequence.js +40 -0
- package/src/view/doc-renderers/fbs.js +128 -0
- package/src/view/doc-renderers/helpers.js +159 -0
- package/src/view/doc-renderers/index.js +12 -0
- package/src/view/doc-renderers/prd.js +45 -0
- package/src/view/doc-renderers/req.js +43 -0
- package/src/view/doc-renderers/tac.js +38 -0
- package/src/view/doc-renderers/tad.js +74 -0
- package/src/view/doc-renderers/test-suite.js +45 -0
- package/src/view/doc-renderers/user-story.js +63 -0
- package/src/view/html-page.js +462 -0
- package/src/view/index.js +63 -0
- package/src/view/live-client.js +338 -0
- package/src/view/mermaid-diagram.js +178 -0
- package/src/view/style.css +735 -0
- package/src/view/tree-model.js +152 -0
- package/src/view/vendored/mermaid.min.js +2607 -0
- package/src/view-supervisor/index.js +26 -0
- package/src/view-supervisor/logs.js +32 -0
- package/src/view-supervisor/manifest-writer.js +178 -0
- package/src/view-supervisor/persist-until.js +85 -0
- package/src/view-supervisor/supervisor.js +276 -0
- package/src/watch/index.js +152 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// Markdown emitter for the build verb (Phase 6 §D4). Two shapes: the
|
|
2
|
+
// seven-section spec-bundle document (§D3) and the queue-overview
|
|
3
|
+
// table. Zero-dep string assembly, byte-stable given the same result
|
|
4
|
+
// (§D10): no wall clock, no randomness, fixed ordering throughout.
|
|
5
|
+
// British English, ASCII hyphens only (§D17).
|
|
6
|
+
//
|
|
7
|
+
// The bundle's consumer is an implementing agent-harness reading a
|
|
8
|
+
// spec: headings and tables carry the structure without a parser, and
|
|
9
|
+
// the same artefact is human-reviewable as-is (OQ-P6-5).
|
|
10
|
+
|
|
11
|
+
const SECTION_NUMBERS = {
|
|
12
|
+
header: 1,
|
|
13
|
+
queueContext: 2,
|
|
14
|
+
work: 3,
|
|
15
|
+
acceptanceCriteria: 4,
|
|
16
|
+
architecturalContext: 5,
|
|
17
|
+
testSurface: 6,
|
|
18
|
+
runbook: 7,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format a build result as markdown.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} result - QueueResult | BundleResult | next-mode empty envelope
|
|
25
|
+
* @param {('queue'|'bundle'|'next')} mode
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function formatMarkdown(result, mode) {
|
|
29
|
+
if (mode === 'queue') return formatQueueTable(result);
|
|
30
|
+
if (mode === 'next' && !result.fbs) return formatNextEmpty(result);
|
|
31
|
+
return formatBundle(result);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Queue overview as a markdown table.
|
|
36
|
+
*
|
|
37
|
+
* @param {object} queue - QueueResult
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function formatQueueTable(queue) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
const heading = queue.bs
|
|
43
|
+
? `# Build queue: ${queue.bs.bsId} - ${queue.bs.title}`
|
|
44
|
+
: '# Build queue';
|
|
45
|
+
lines.push(heading, '');
|
|
46
|
+
if (queue.bs?.generationStrategy) {
|
|
47
|
+
lines.push(`Generation strategy: ${queue.bs.generationStrategy}`, '');
|
|
48
|
+
}
|
|
49
|
+
lines.push('| order | tier | id | title | status | state | blocked by |');
|
|
50
|
+
lines.push('|---|---|---|---|---|---|---|');
|
|
51
|
+
for (const item of queue.items) {
|
|
52
|
+
const state = item.cycle ? 'blocked (cycle)' : item.state;
|
|
53
|
+
const blockedBy = item.blockedBy.join(', ');
|
|
54
|
+
const tier = item.tier ?? '-';
|
|
55
|
+
lines.push(
|
|
56
|
+
`| ${item.buildOrder} | ${tier} | ${item.fbsId} | ${item.title} | ${item.executionStatus} | ${state} | ${blockedBy} |`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const t = queue.totals;
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push(
|
|
62
|
+
`Totals: items ${t.items} | notStarted ${t.notStarted} | inProgress ${t.inProgress} `
|
|
63
|
+
+ `| complete ${t.complete} | verified ${t.verified} | actionable ${t.actionable} `
|
|
64
|
+
+ `| blocked ${t.blocked}`,
|
|
65
|
+
);
|
|
66
|
+
// Parallel-safe tiers (AC-502-2): items sharing a tier have no
|
|
67
|
+
// dependency path between them and can be built concurrently.
|
|
68
|
+
// Cycle-tainted items have no tier and appear in no group.
|
|
69
|
+
const tiers = queue.tiers ?? [];
|
|
70
|
+
lines.push('');
|
|
71
|
+
if (tiers.length > 0) {
|
|
72
|
+
lines.push('Parallel-safe tiers (items in the same tier have no dependency between them and can build in parallel):');
|
|
73
|
+
for (const group of tiers) {
|
|
74
|
+
lines.push(`- tier ${group.tier}: ${group.fbsIds.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
lines.push('Parallel-safe tiers: none.');
|
|
78
|
+
}
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push(`Next actionable: ${queue.nextActionable ?? 'none'}`);
|
|
81
|
+
return `${lines.join('\n')}\n`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* `--next` with nothing actionable (§D2 / OQ-P6-2): an explicit
|
|
86
|
+
* envelope distinguishing "done" from "stuck", not an error.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} envelope
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function formatNextEmpty(envelope) {
|
|
92
|
+
const lines = ['# Build queue: nothing actionable', ''];
|
|
93
|
+
if (envelope.queueEmpty) {
|
|
94
|
+
lines.push('Queue complete: every item is complete or verified.');
|
|
95
|
+
} else {
|
|
96
|
+
const blocked = envelope.blocked.length > 0 ? envelope.blocked.join(', ') : 'none';
|
|
97
|
+
const inProgress = envelope.inProgress.length > 0 ? envelope.inProgress.join(', ') : 'none';
|
|
98
|
+
lines.push(
|
|
99
|
+
'Queue not complete but nothing is actionable.',
|
|
100
|
+
'',
|
|
101
|
+
`- Blocked: ${blocked}`,
|
|
102
|
+
`- In progress: ${inProgress}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return `${lines.join('\n')}\n`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The seven-section spec bundle (§D3). Section numbering is fixed;
|
|
110
|
+
* section 5 is omitted entirely when the FBS carries no
|
|
111
|
+
* contextRequirements (§D11) without renumbering the rest.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} bundle - BundleResult
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
function formatBundle(bundle) {
|
|
117
|
+
const lines = [];
|
|
118
|
+
const { fbs } = bundle;
|
|
119
|
+
lines.push(`# Spec bundle: ${fbs.fbsId} - ${fbs.title}`, '');
|
|
120
|
+
pushHeader(lines, bundle);
|
|
121
|
+
pushQueueContext(lines, bundle);
|
|
122
|
+
pushWork(lines, bundle);
|
|
123
|
+
pushAcceptanceCriteria(lines, bundle);
|
|
124
|
+
if (bundle.context) pushArchitecturalContext(lines, bundle);
|
|
125
|
+
pushTestSurface(lines, bundle);
|
|
126
|
+
pushRunbook(lines, bundle);
|
|
127
|
+
return `${lines.join('\n')}\n`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function pushHeader(lines, bundle) {
|
|
131
|
+
const { fbs, queue, bs, prd } = bundle;
|
|
132
|
+
lines.push(`## ${SECTION_NUMBERS.header}. Header`, '');
|
|
133
|
+
lines.push(`- Item: ${fbs.fbsId} - ${fbs.title}`);
|
|
134
|
+
lines.push(`- Queue: order ${fbs.buildOrder}, item ${queue.position} of ${queue.total}`);
|
|
135
|
+
lines.push(`- Execution status: ${fbs.executionStatus}`);
|
|
136
|
+
if (fbs.estimatedSize !== undefined) lines.push(`- Estimated size: ${fbs.estimatedSize}`);
|
|
137
|
+
if (fbs.estimatedHours !== undefined) lines.push(`- Estimated hours: ${fbs.estimatedHours}`);
|
|
138
|
+
if (fbs.riskLevel !== undefined) lines.push(`- Risk level: ${fbs.riskLevel}`);
|
|
139
|
+
if (fbs.domain !== undefined) lines.push(`- Domain: ${fbs.domain}`);
|
|
140
|
+
const chain = [];
|
|
141
|
+
if (bs) chain.push(bs.bsId);
|
|
142
|
+
if (prd) chain.push(prd.productName ? `${prd.prdId} (${prd.productName})` : prd.prdId);
|
|
143
|
+
if (chain.length > 0) lines.push(`- Parent chain: ${chain.join(' -> ')}`);
|
|
144
|
+
if (fbs.updatedAt !== undefined) lines.push(`- Spec last touched: ${fbs.updatedAt}`);
|
|
145
|
+
lines.push('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function pushQueueContext(lines, bundle) {
|
|
149
|
+
const { bs, blockedBy, dependencies, dependents } = bundle;
|
|
150
|
+
lines.push(`## ${SECTION_NUMBERS.queueContext}. Queue and dependency context`, '');
|
|
151
|
+
if (blockedBy.length > 0) {
|
|
152
|
+
const blocking = dependencies
|
|
153
|
+
.filter((d) => blockedBy.includes(d.fbsId))
|
|
154
|
+
.map((d) => `${d.fbsId} (${d.executionStatus ?? 'unknown'})`)
|
|
155
|
+
.join(', ');
|
|
156
|
+
lines.push(`> **BLOCKED**: unsatisfied dependencies - ${blocking}.`);
|
|
157
|
+
lines.push('> This bundle is a read-ahead. Do not start implementation until every');
|
|
158
|
+
lines.push('> dependency is complete or verified; `--strict` refuses this bundle.');
|
|
159
|
+
lines.push('');
|
|
160
|
+
}
|
|
161
|
+
if (bs) {
|
|
162
|
+
lines.push(`- Build sequence: ${bs.bsId} - ${bs.title}`);
|
|
163
|
+
if (bs.generationStrategy !== undefined) lines.push(`- Generation strategy: ${bs.generationStrategy}`);
|
|
164
|
+
if (bs.buildPhilosophy !== undefined) lines.push(`- Build philosophy: ${bs.buildPhilosophy}`);
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
if (dependencies.length > 0) {
|
|
168
|
+
lines.push('Dependencies:', '');
|
|
169
|
+
lines.push('| id | title | status |');
|
|
170
|
+
lines.push('|---|---|---|');
|
|
171
|
+
for (const dep of dependencies) {
|
|
172
|
+
lines.push(`| ${dep.fbsId} | ${dep.title ?? ''} | ${dep.executionStatus ?? ''} |`);
|
|
173
|
+
}
|
|
174
|
+
lines.push('');
|
|
175
|
+
} else {
|
|
176
|
+
lines.push('Dependencies: none.', '');
|
|
177
|
+
}
|
|
178
|
+
lines.push(
|
|
179
|
+
dependents.length > 0
|
|
180
|
+
? `Dependents waiting on this item: ${dependents.join(', ')}`
|
|
181
|
+
: 'Dependents waiting on this item: none.',
|
|
182
|
+
);
|
|
183
|
+
lines.push('');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pushWork(lines, bundle) {
|
|
187
|
+
const { fbs } = bundle;
|
|
188
|
+
lines.push(`## ${SECTION_NUMBERS.work}. The work`, '');
|
|
189
|
+
lines.push(fbs.summary, '');
|
|
190
|
+
if (fbs.approach !== undefined) {
|
|
191
|
+
lines.push('Approach:', '', fbs.approach, '');
|
|
192
|
+
}
|
|
193
|
+
if (fbs.deliverables !== undefined && fbs.deliverables.length > 0) {
|
|
194
|
+
lines.push('Deliverables:', '');
|
|
195
|
+
for (const d of fbs.deliverables) lines.push(`- ${d}`);
|
|
196
|
+
lines.push('');
|
|
197
|
+
}
|
|
198
|
+
if (fbs.notes !== undefined) {
|
|
199
|
+
lines.push('Notes:', '', fbs.notes, '');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function pushAcceptanceCriteria(lines, bundle) {
|
|
204
|
+
lines.push(`## ${SECTION_NUMBERS.acceptanceCriteria}. Acceptance criteria`, '');
|
|
205
|
+
for (const us of bundle.userStories) {
|
|
206
|
+
lines.push(`### ${us.usId}: ${us.title}${us.status !== undefined ? ` (status: ${us.status})` : ''}`, '');
|
|
207
|
+
if (us.asA !== undefined && us.iWant !== undefined && us.soThat !== undefined) {
|
|
208
|
+
lines.push(`As a ${us.asA}, I want ${us.iWant}, so that ${us.soThat}.`, '');
|
|
209
|
+
}
|
|
210
|
+
const acs = bundle.acceptanceCriteria.filter((ac) => ac.usId === us.usId);
|
|
211
|
+
const req = bundle.requirements.find((r) => r.reqId === acs[0]?.reqId);
|
|
212
|
+
if (req) {
|
|
213
|
+
const meta = [req.category, req.priority !== undefined ? `priority: ${req.priority}` : null]
|
|
214
|
+
.filter((v) => v !== null && v !== undefined)
|
|
215
|
+
.join(', ');
|
|
216
|
+
lines.push(`Parent requirement ${req.reqId}: ${req.title}${meta ? ` (${meta})` : ''}`, '');
|
|
217
|
+
if (req.description !== undefined) lines.push(`> ${req.description}`);
|
|
218
|
+
if (req.rationale !== undefined) {
|
|
219
|
+
if (req.description !== undefined) lines.push('>');
|
|
220
|
+
lines.push(`> Rationale: ${req.rationale}`);
|
|
221
|
+
}
|
|
222
|
+
if (req.description !== undefined || req.rationale !== undefined) lines.push('');
|
|
223
|
+
}
|
|
224
|
+
for (const ac of acs) {
|
|
225
|
+
lines.push(`#### ${ac.id}: ${ac.description}`, '');
|
|
226
|
+
if (ac.given !== undefined) lines.push(`- Given: ${ac.given}`);
|
|
227
|
+
if (ac.when !== undefined) lines.push(`- When: ${ac.when}`);
|
|
228
|
+
if (ac.then !== undefined) lines.push(`- Then: ${ac.then}`);
|
|
229
|
+
lines.push(`- Testable: ${ac.testable ? 'yes' : 'no'}`);
|
|
230
|
+
lines.push('');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Render a resolved TAD / PRD section value. String content renders as
|
|
237
|
+
* prose; structured content (the TAD's sections are objects / arrays)
|
|
238
|
+
* renders as a fenced JSON block - still byte-stable, still readable.
|
|
239
|
+
*/
|
|
240
|
+
function pushSectionValue(lines, value) {
|
|
241
|
+
if (typeof value === 'string') {
|
|
242
|
+
lines.push(value, '');
|
|
243
|
+
} else {
|
|
244
|
+
lines.push('```json');
|
|
245
|
+
lines.push(JSON.stringify(value, null, 2));
|
|
246
|
+
lines.push('```', '');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function pushArchitecturalContext(lines, bundle) {
|
|
251
|
+
const ctx = bundle.context;
|
|
252
|
+
lines.push(`## ${SECTION_NUMBERS.architecturalContext}. Architectural context`, '');
|
|
253
|
+
for (const tac of ctx.tacs) {
|
|
254
|
+
lines.push(`### TAC ${tac.tacId}: ${tac.name}`, '');
|
|
255
|
+
if (tac.purpose !== undefined) lines.push(`Purpose: ${tac.purpose}`, '');
|
|
256
|
+
if (tac.responsibilities !== undefined && tac.responsibilities.length > 0) {
|
|
257
|
+
lines.push('Responsibilities:', '');
|
|
258
|
+
for (const r of tac.responsibilities) lines.push(`- ${r}`);
|
|
259
|
+
lines.push('');
|
|
260
|
+
}
|
|
261
|
+
if (tac.interfaces !== undefined && tac.interfaces.length > 0) {
|
|
262
|
+
lines.push('Interfaces:', '');
|
|
263
|
+
for (const i of tac.interfaces) {
|
|
264
|
+
const kind = i.kind !== undefined ? ` (${i.kind})` : '';
|
|
265
|
+
const desc = i.description !== undefined ? `: ${i.description}` : '';
|
|
266
|
+
lines.push(`- ${i.name}${kind}${desc}`);
|
|
267
|
+
}
|
|
268
|
+
lines.push('');
|
|
269
|
+
}
|
|
270
|
+
if (tac.dependencies !== undefined && tac.dependencies.length > 0) {
|
|
271
|
+
lines.push('Dependencies:', '');
|
|
272
|
+
for (const d of tac.dependencies) {
|
|
273
|
+
const ref = d.kind === 'tac' && d.tacId !== undefined ? `${d.kind} ${d.tacId}` : d.kind;
|
|
274
|
+
const desc = d.description !== undefined ? `: ${d.description}` : '';
|
|
275
|
+
lines.push(`- ${d.name} (${ref})${desc}`);
|
|
276
|
+
}
|
|
277
|
+
lines.push('');
|
|
278
|
+
}
|
|
279
|
+
if (tac.tradeoffs !== undefined) lines.push(`Tradeoffs: ${tac.tradeoffs}`, '');
|
|
280
|
+
if (tac.notes !== undefined) lines.push(`Notes: ${tac.notes}`, '');
|
|
281
|
+
}
|
|
282
|
+
for (const adr of ctx.adrs) {
|
|
283
|
+
lines.push(`### ADR ${adr.adrId}: ${adr.title}${adr.status !== undefined ? ` (${adr.status})` : ''}`, '');
|
|
284
|
+
if (adr.context !== undefined) lines.push(`Context: ${adr.context}`, '');
|
|
285
|
+
if (adr.decision !== undefined) lines.push(`Decision: ${adr.decision}`, '');
|
|
286
|
+
if (adr.consequences !== undefined) lines.push(`Consequences: ${adr.consequences}`, '');
|
|
287
|
+
if (adr.alternativesConsidered !== undefined && adr.alternativesConsidered.length > 0) {
|
|
288
|
+
lines.push('Alternatives considered:', '');
|
|
289
|
+
for (const alt of adr.alternativesConsidered) {
|
|
290
|
+
const summary = alt.summary !== undefined ? `: ${alt.summary}` : '';
|
|
291
|
+
const reason = alt.reasonNotChosen !== undefined ? ` (not chosen: ${alt.reasonNotChosen})` : '';
|
|
292
|
+
lines.push(`- ${alt.name}${summary}${reason}`);
|
|
293
|
+
}
|
|
294
|
+
lines.push('');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
for (const [name, value] of Object.entries(ctx.tadSections)) {
|
|
298
|
+
lines.push(`### TAD section: ${name}`, '');
|
|
299
|
+
pushSectionValue(lines, value);
|
|
300
|
+
}
|
|
301
|
+
for (const [name, value] of Object.entries(ctx.prdSections)) {
|
|
302
|
+
lines.push(`### PRD section: ${name}`, '');
|
|
303
|
+
pushSectionValue(lines, value);
|
|
304
|
+
}
|
|
305
|
+
if (ctx.unresolvedSections.length > 0) {
|
|
306
|
+
lines.push(
|
|
307
|
+
`Warning - unresolved sections (names that did not match a top-level TAD / PRD property): ${ctx.unresolvedSections.join(', ')}`,
|
|
308
|
+
'',
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
const pt = ctx.passThrough;
|
|
312
|
+
const ptRows = [
|
|
313
|
+
['Existing modules', pt.existingModules],
|
|
314
|
+
['Schemas', pt.schemas],
|
|
315
|
+
['External docs', pt.externalDocs],
|
|
316
|
+
['Other', pt.other],
|
|
317
|
+
].filter(([, list]) => list.length > 0);
|
|
318
|
+
if (ptRows.length > 0) {
|
|
319
|
+
lines.push('Referenced material (pass-through, verbatim):', '');
|
|
320
|
+
for (const [label, list] of ptRows) {
|
|
321
|
+
lines.push(`- ${label}: ${list.join(', ')}`);
|
|
322
|
+
}
|
|
323
|
+
lines.push('');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function pushTestSurface(lines, bundle) {
|
|
328
|
+
lines.push(`## ${SECTION_NUMBERS.testSurface}. Existing test surface`, '');
|
|
329
|
+
lines.push('Presence reporting off the tree, not a coverage verdict (`rcf coverage` is the coverage surface).', '');
|
|
330
|
+
for (const entry of bundle.tests) {
|
|
331
|
+
if (!entry.covered && entry.suites.length === 0) {
|
|
332
|
+
lines.push(`- ${entry.acId}: no existing tests - test suite to be written for this AC`);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const suites = entry.suites.length > 0 ? entry.suites.join(', ') : 'none';
|
|
336
|
+
lines.push(`- ${entry.acId}: suites: ${suites}`);
|
|
337
|
+
for (const c of entry.cases) {
|
|
338
|
+
const pointer = c.testPointer !== undefined ? ` [${c.testPointer}]` : '';
|
|
339
|
+
const status = c.status !== undefined ? ` (${c.status})` : '';
|
|
340
|
+
lines.push(` - ${c.tcId}${status}: ${c.description ?? ''}${pointer}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
lines.push('');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Section 7 - the full RCF five-stage build-cycle runbook (§D3.7,
|
|
348
|
+
* amendment D3-A). Static text parameterised only by fbsId and the
|
|
349
|
+
* in-scope AC ids; the each-stage-commits discipline is part of the
|
|
350
|
+
* printed contract. The tool assembles and referees; the harness
|
|
351
|
+
* executes the cycle.
|
|
352
|
+
*/
|
|
353
|
+
function pushRunbook(lines, bundle) {
|
|
354
|
+
const fbsId = bundle.fbs.fbsId;
|
|
355
|
+
const acIds = bundle.acceptanceCriteria.map((ac) => ac.id).join(', ');
|
|
356
|
+
lines.push(`## ${SECTION_NUMBERS.runbook}. Build-cycle runbook`, '');
|
|
357
|
+
lines.push('This bundle is the work order for one pass of the RCF five-stage build');
|
|
358
|
+
lines.push('cycle: Define -> Build -> Review -> Test -> Finalise. The tool assembles');
|
|
359
|
+
lines.push('and referees; the harness executes. Every stage ends in a commit.', '');
|
|
360
|
+
// Deep-layer pointer (Phase 7.5 §D5.4, OQ-P7.5-3): the runbook is the
|
|
361
|
+
// terse just-in-time layer; the playbook is the deep layer.
|
|
362
|
+
lines.push('Deep guidance: rcf://docs/build-cycle and the rcf_execute_build_cycle prompt, or `rcf guidance build-cycle-playbook` on the CLI.', '');
|
|
363
|
+
|
|
364
|
+
lines.push('### Stage 1 - Define', '');
|
|
365
|
+
lines.push('Satisfied by this bundle: the FBS, acceptance criteria, ancestry and');
|
|
366
|
+
lines.push('architectural context above ARE the definition. Confirm your plan against');
|
|
367
|
+
lines.push(`every in-scope acceptance criterion (${acIds}) in section 4 before writing`);
|
|
368
|
+
lines.push('code, then mark pickup:', '');
|
|
369
|
+
lines.push(` rcf build ${fbsId} --mark inProgress`, '');
|
|
370
|
+
lines.push('Commit any plan artefacts the driving workflow requires.', '');
|
|
371
|
+
|
|
372
|
+
lines.push('### Stage 2 - Build', '');
|
|
373
|
+
lines.push('Implement to the acceptance criteria in section 4 using the architectural');
|
|
374
|
+
lines.push('context in section 5. The bundle is the spec: deviation is escalation to');
|
|
375
|
+
lines.push('the operator of the loop, not improvisation. As you implement, author or');
|
|
376
|
+
lines.push('update Code Nodes for the source you write:', '');
|
|
377
|
+
lines.push(' rcf create cn --path <file>[#symbol] --acs <ac-ids>', '');
|
|
378
|
+
lines.push('Do this now, not as an afterthought: comprehension of which symbols serve');
|
|
379
|
+
lines.push('which acceptance criteria is cheapest to capture while you are writing the');
|
|
380
|
+
lines.push('code, and Stage 5 refuses completion without it. Commit at stage end.', '');
|
|
381
|
+
|
|
382
|
+
lines.push('### Stage 3 - Review', '');
|
|
383
|
+
lines.push('Mechanical referee pass:', '');
|
|
384
|
+
lines.push(' rcf validate', '');
|
|
385
|
+
lines.push('must come back clean; then re-read the diff against every in-scope');
|
|
386
|
+
lines.push('acceptance criterion and document any deviations. Commit.', '');
|
|
387
|
+
|
|
388
|
+
lines.push('### Stage 4 - Test', '');
|
|
389
|
+
lines.push('Exercise every in-scope acceptance criterion: write or extend the TS / TC');
|
|
390
|
+
lines.push('documents (section 6 lists the existing surface and the flagged gaps) and');
|
|
391
|
+
lines.push('the tests they point to, until:', '');
|
|
392
|
+
lines.push(' rcf coverage --strict', '');
|
|
393
|
+
lines.push('covers the in-scope acceptance criteria. Commit.', '');
|
|
394
|
+
|
|
395
|
+
lines.push('### Stage 5 - Finalise', '');
|
|
396
|
+
lines.push('CI green; PR raised and merged per the driving workflow\'s convention.');
|
|
397
|
+
lines.push('After the merge:', '');
|
|
398
|
+
lines.push(` rcf build ${fbsId} --mark complete`, '');
|
|
399
|
+
lines.push('This refuses (exit 3, missingCodeNodes) if any in-scope acceptance');
|
|
400
|
+
lines.push('criterion still carries no Code Node - go back to Stage 2 and author it,');
|
|
401
|
+
lines.push('or, for a genuinely no-code spec (docs-only, config-only), declare:', '');
|
|
402
|
+
lines.push(` rcf build ${fbsId} --mark complete --no-code-nodes`, '');
|
|
403
|
+
lines.push('Then ship-gate the deployed app - an independent rcf-verify run that');
|
|
404
|
+
lines.push('passes with ship authority promotes complete -> verified:', '');
|
|
405
|
+
lines.push(` rcf finalise ${fbsId} --url <deploy-url>`);
|
|
406
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Public surface for the build module (Phase 6 §D8). Pure logic +
|
|
2
|
+
// formatters; the CLI handler lives at `src/cli/build.js`.
|
|
3
|
+
|
|
4
|
+
export { computeQueue, selectNext, byBuildOrder, LIFECYCLE } from './queue.js';
|
|
5
|
+
export { assembleBundle } from './bundle.js';
|
|
6
|
+
export {
|
|
7
|
+
checkCodeNodeGate,
|
|
8
|
+
checkContrastBeforePaletteGate,
|
|
9
|
+
checkDesignGate,
|
|
10
|
+
hasNoCodeNodesDeclaration,
|
|
11
|
+
planMark,
|
|
12
|
+
} from './mark.js';
|
|
13
|
+
export { formatMarkdown } from './formatters/markdown.js';
|
|
14
|
+
export { formatJson } from './formatters/json.js';
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Lifecycle marking (Phase 6 §D5). Pure transition planning for
|
|
2
|
+
// `rcf build <fbs-id> --mark <status>`: enum validation, forward-only
|
|
3
|
+
// transition-table validation, idempotent no-op detection. Returns a
|
|
4
|
+
// plan (or a structured error / refusal); the CLI handler executes the
|
|
5
|
+
// plan via the Phase 4 `updateDocument` write path. Phase 6 adds no
|
|
6
|
+
// new write primitive.
|
|
7
|
+
//
|
|
8
|
+
// The lifecycle is the schema enum, in order:
|
|
9
|
+
// notStarted -> inProgress -> complete -> verified
|
|
10
|
+
// Forward jumps are legal (notStarted -> complete for trivially-shipped
|
|
11
|
+
// items) but the mark ladder caps at `complete`: `--mark verified` is
|
|
12
|
+
// refused (exit 4) and points to the finalise gate (`rcf finalise`), which
|
|
13
|
+
// is the only path that writes `verified` after an independent verify run.
|
|
14
|
+
// Backward transitions are refused (exit 4) and the message names the
|
|
15
|
+
// deliberate-correction escape hatch (`rcf update`).
|
|
16
|
+
|
|
17
|
+
import { rcfError } from '#core/errors';
|
|
18
|
+
import { LIFECYCLE } from './queue.js';
|
|
19
|
+
|
|
20
|
+
// Phase 10 (X2 CodeNode bridge, D17, operator ruling 2026-07-10): the
|
|
21
|
+
// mark-complete CN gate. The no-code-nodes declaration is a dedicated
|
|
22
|
+
// `noCodeNodes: boolean` field on the FBS schema (rcf-schemas@0.3.1) - NOT
|
|
23
|
+
// a free-form convention string. An unvalidated magic value would silently
|
|
24
|
+
// fail on a typo, which is exactly the failure mode this feature exists to
|
|
25
|
+
// make visible (operator ruling 2026-07-10).
|
|
26
|
+
/**
|
|
27
|
+
* True when the FBS already carries the `--no-code-nodes` declaration.
|
|
28
|
+
* @param {object} fbs
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
export function hasNoCodeNodesDeclaration(fbs) {
|
|
32
|
+
return fbs?.noCodeNodes === true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} MarkPlan
|
|
37
|
+
* @property {string} fbsId
|
|
38
|
+
* @property {string} from
|
|
39
|
+
* @property {string} to
|
|
40
|
+
* @property {boolean} noOp - true when the item is already at `to`
|
|
41
|
+
* @property {boolean} [refused] - true on a backward transition
|
|
42
|
+
* @property {string} [message] - refusal message (refused plans only)
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Plan a lifecycle transition. Returns an RcfError (usage) for a bad
|
|
47
|
+
* status value or a non-FBS id, a refused plan for a backward
|
|
48
|
+
* transition, otherwise an executable plan.
|
|
49
|
+
*
|
|
50
|
+
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
51
|
+
* @param {object} opts
|
|
52
|
+
* @param {string} opts.fbsId
|
|
53
|
+
* @param {string} opts.status - target executionStatus
|
|
54
|
+
* @returns {MarkPlan | import('#core/errors').RcfError}
|
|
55
|
+
*/
|
|
56
|
+
export function planMark(tree, { fbsId, status }) {
|
|
57
|
+
if (!LIFECYCLE.includes(status)) {
|
|
58
|
+
return rcfError({
|
|
59
|
+
kind: 'usage',
|
|
60
|
+
message: `build: unknown --mark value '${status}' (expected ${LIFECYCLE.join(' | ')})`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const fbs = tree.byId.get(fbsId);
|
|
64
|
+
if (!fbs || tree.kindById.get(fbsId) !== 'fbs') {
|
|
65
|
+
return rcfError({
|
|
66
|
+
kind: 'usage',
|
|
67
|
+
message: `build: id ${fbsId} not found or not an FBS`,
|
|
68
|
+
documentId: fbsId,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const from = fbs.executionStatus;
|
|
72
|
+
// The mark ladder caps at `complete` (w-2026-07-22-004). `verified` means
|
|
73
|
+
// "independently verified against the running deploy" - it is written only by
|
|
74
|
+
// the finalise gate (`rcf finalise`), which spawns a fresh rcf-verify run.
|
|
75
|
+
// Letting `--mark verified` promote with no verify run would let the builder
|
|
76
|
+
// sidestep that gate with one flag, defeating the spec-9 independence
|
|
77
|
+
// guarantee. Refused as part of the mark-refusal family (exit 4); the message
|
|
78
|
+
// names the finalise gate and keeps `rcf update` as the explicit manual
|
|
79
|
+
// override (unchanged - a deliberate, logged operator decision).
|
|
80
|
+
if (status === 'verified') {
|
|
81
|
+
return {
|
|
82
|
+
fbsId,
|
|
83
|
+
from,
|
|
84
|
+
to: status,
|
|
85
|
+
noOp: false,
|
|
86
|
+
refused: true,
|
|
87
|
+
message: `build: refusing --mark verified on ${fbsId}; 'verified' is written only by the `
|
|
88
|
+
+ `independent verify gate, not by --mark. Promote it with: rcf finalise ${fbsId} --url <deploy-url>. `
|
|
89
|
+
+ `For a deliberate manual override (no verify run) use: rcf update ${fbsId} --set executionStatus=verified`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const fromIndex = LIFECYCLE.indexOf(from);
|
|
93
|
+
const toIndex = LIFECYCLE.indexOf(status);
|
|
94
|
+
if (toIndex === fromIndex) {
|
|
95
|
+
return { fbsId, from, to: status, noOp: true };
|
|
96
|
+
}
|
|
97
|
+
if (toIndex < fromIndex) {
|
|
98
|
+
return {
|
|
99
|
+
fbsId,
|
|
100
|
+
from,
|
|
101
|
+
to: status,
|
|
102
|
+
noOp: false,
|
|
103
|
+
refused: true,
|
|
104
|
+
message: `build: refusing backward transition ${from} -> ${status} on ${fbsId}; `
|
|
105
|
+
+ `for a deliberate correction use: rcf update ${fbsId} --set executionStatus=${status}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { fbsId, from, to: status, noOp: false };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The mark-complete CN gate (D17). Deterministic edge counting: an AC is
|
|
113
|
+
* "covered" when `tree.cnByAcId` carries at least one implementing CN.
|
|
114
|
+
* Called only when the target status is `complete`; the caller (cli/build)
|
|
115
|
+
* skips this entirely when the FBS already carries the no-code-nodes
|
|
116
|
+
* declaration or the invocation supplies `--no-code-nodes`.
|
|
117
|
+
*
|
|
118
|
+
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
119
|
+
* @param {object} fbs - the FBS document being marked complete
|
|
120
|
+
* @returns {{ ok: true } | { ok: false, missingAcIds: string[] }}
|
|
121
|
+
*/
|
|
122
|
+
export function checkCodeNodeGate(tree, fbs) {
|
|
123
|
+
const missingAcIds = (fbs.acIds ?? []).filter((acId) => (tree.cnByAcId?.get(acId) ?? []).length === 0);
|
|
124
|
+
if (missingAcIds.length === 0) return { ok: true };
|
|
125
|
+
return { ok: false, missingAcIds: [...missingAcIds].sort() };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Track B (ui-design-gate-0.7.0-spec §5.2, §5.5): the `--mark complete`
|
|
129
|
+
// design gate. Refuses when the FBS is uiBearing=true and
|
|
130
|
+
// designStageComplete is not true on the FBS record. The gate is the
|
|
131
|
+
// boolean primitive per §12 O-3 (Decided); the executionStatus enum is
|
|
132
|
+
// unchanged.
|
|
133
|
+
/**
|
|
134
|
+
* @param {object} fbs
|
|
135
|
+
* @returns {{ ok: true } | { ok: false, reason: 'designStageIncomplete' }}
|
|
136
|
+
*/
|
|
137
|
+
export function checkDesignGate(fbs) {
|
|
138
|
+
if (fbs?.uiBearing !== true) return { ok: true };
|
|
139
|
+
if (fbs?.designStageComplete === true) return { ok: true };
|
|
140
|
+
return { ok: false, reason: 'designStageIncomplete' };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Track B (ui-design-gate-0.7.0-spec §7 mandate 10). Refuses when the
|
|
144
|
+
// operator's own attestation says the wrong order was authored, and
|
|
145
|
+
// (optionally) when a git-history probe unambiguously contradicts the
|
|
146
|
+
// operator's attestation. The git probe is out of scope for the pure
|
|
147
|
+
// mark.js module; the caller (cli/build) supplies the probe result.
|
|
148
|
+
/**
|
|
149
|
+
* @param {object} fbs
|
|
150
|
+
* @param {object} [historyProbe] optional git-history corroboration result
|
|
151
|
+
* @param {boolean} [historyProbe.inconclusive] true when git says nothing
|
|
152
|
+
* @param {boolean} [historyProbe.tokensCreatedFirst] true when history says tokens preceded contrast test
|
|
153
|
+
* @returns {{ ok: true } | { ok: false, reason: string, message: string }}
|
|
154
|
+
*/
|
|
155
|
+
export function checkContrastBeforePaletteGate(fbs, historyProbe = { inconclusive: true }) {
|
|
156
|
+
if (fbs?.uiBearing !== true) return { ok: true };
|
|
157
|
+
const stage = fbs?.designStage;
|
|
158
|
+
if (!stage?.themeAndA11y) return { ok: true }; // gate deferred to design-mark-complete surface
|
|
159
|
+
const boolean = stage.themeAndA11y.contrastTestAuthoredBeforePalette;
|
|
160
|
+
if (boolean === false) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
reason: 'contrastTestAfterPalette',
|
|
164
|
+
message: `build --mark complete: refused - ${fbs.fbsId} designStage.themeAndA11y.contrastTestAuthoredBeforePalette is false. `
|
|
165
|
+
+ 'Author the contrast test before the palette, or record the reversal by ratifying the ordering explicitly.',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (boolean === true && historyProbe && historyProbe.inconclusive !== true && historyProbe.tokensCreatedFirst === true) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
reason: 'contrastGitHistoryConflict',
|
|
172
|
+
message: `build --mark complete: refused - ${fbs.fbsId} attests contrastTestAuthoredBeforePalette=true, but git history shows the tokens module (${stage.themeAndA11y.themeTokensModule}) was created before the contrast test (${stage.themeAndA11y.contrastTestPath}). `
|
|
173
|
+
+ 'Reconcile the attestation with the history: rewrite the test-authoring order in the log, or set contrastTestAuthoredBeforePalette=false and accept the ruling.',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { ok: true };
|
|
177
|
+
}
|