rcf-lite 0.0.1 → 0.8.0
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 +344 -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 +71 -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/admissibility/enforce.js +142 -0
- package/src/admissibility/index.js +8 -0
- package/src/admissibility/markers.js +104 -0
- package/src/admissibility/scope-lint.js +163 -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 +358 -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 +168 -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 +221 -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 +214 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +944 -0
- package/src/core/store/writer.js +1879 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +151 -0
- package/src/finalise/index.js +31 -0
- package/src/finalise/ingest.js +160 -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 +1212 -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 +13 -0
- package/src/query/refuse-on-admissibility.js +73 -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/ruleset/index.js +140 -0
- package/src/ruleset/ruleset.json +146 -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 +221 -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 +313 -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,219 @@
|
|
|
1
|
+
// `rcf coverage` subcommand handler. Reports structural coverage over
|
|
2
|
+
// the REQ chain (PRD -> REQ -> US -> AC -> TS -> TC). Phase 5 §D2 / §D10.
|
|
3
|
+
//
|
|
4
|
+
// Shallow-any default (any AC covered by any TC = REQ covered);
|
|
5
|
+
// --strict opts into per-AC-strict. --strict on a tree with uncovered
|
|
6
|
+
// ACs exits 4 (CI-gate friendly). Otherwise coverage always exits 0 -
|
|
7
|
+
// the gap count is data, not a refusal.
|
|
8
|
+
//
|
|
9
|
+
// Phase-boundary reminder (§D2, §1.4): this verb is a MECHANICAL /
|
|
10
|
+
// DETERMINISTIC structural check. It does NOT answer "does the AC
|
|
11
|
+
// set adequately capture the REQ's intent?" - that non-deterministic
|
|
12
|
+
// question belongs to a later prompting + MCP resources phase (7+).
|
|
13
|
+
|
|
14
|
+
import { parseArgs } from 'node:util';
|
|
15
|
+
|
|
16
|
+
import { formatErrors } from '#core/errors';
|
|
17
|
+
import { resolveTestPointers, walkTree } from '#core/store';
|
|
18
|
+
import { findProjectRoot } from '../view/index.js';
|
|
19
|
+
import {
|
|
20
|
+
classifyCoverageScope,
|
|
21
|
+
computeCoverage,
|
|
22
|
+
formatJson,
|
|
23
|
+
formatMermaid,
|
|
24
|
+
formatTable,
|
|
25
|
+
} from '../query/index.js';
|
|
26
|
+
import {
|
|
27
|
+
findAttestationDrift,
|
|
28
|
+
findAttestationMissing,
|
|
29
|
+
findProvenanceMissing,
|
|
30
|
+
findServicesWithEmptyAffectedFbsIds,
|
|
31
|
+
} from '../query/attestation.js';
|
|
32
|
+
|
|
33
|
+
const OPTION_SPEC = {
|
|
34
|
+
strict: { type: 'boolean' },
|
|
35
|
+
format: { type: 'string' },
|
|
36
|
+
help: { type: 'boolean' },
|
|
37
|
+
// Phase 10 (X2 CodeNode bridge, D11): layer the code axis onto coverage.
|
|
38
|
+
'with-code': { type: 'boolean' },
|
|
39
|
+
// 0.7.0 verification-integrity: opt-in extra gate on --strict that
|
|
40
|
+
// refuses any TS still `draft` after Stage 4 (spec §7.2).
|
|
41
|
+
'require-approved': { type: 'boolean' },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const HELP = `Usage: rcf coverage [scope-id] [options]
|
|
45
|
+
|
|
46
|
+
Report structural coverage over the REQ chain (PRD -> REQ -> US -> AC
|
|
47
|
+
-> TS -> TC). A TC counts as covering its AC only when its testPointer
|
|
48
|
+
(filePath::testName) resolves to a real test in the working tree; a TC
|
|
49
|
+
whose pointer does not resolve is reported as covered-unresolved, never
|
|
50
|
+
counted as coverage. Default is shallow-any (any AC covered by any
|
|
51
|
+
resolving TC = REQ covered); --strict flips to per-AC-strict (every AC
|
|
52
|
+
has resolving TC coverage).
|
|
53
|
+
|
|
54
|
+
This is a mechanical / deterministic structural check. It does NOT
|
|
55
|
+
answer 'does the AC set adequately capture the REQ's intent?' - that
|
|
56
|
+
non-deterministic question is out of scope for Phase 5 (belongs to a
|
|
57
|
+
later prompting + MCP resources phase).
|
|
58
|
+
|
|
59
|
+
Positional:
|
|
60
|
+
scope-id Optional PRD / REQ / US id to scope
|
|
61
|
+
coverage to a subtree. Below-AC ids
|
|
62
|
+
(AC / TS / TC / FBS / TAC / ADR / BS /
|
|
63
|
+
TAD) are refused with exit 2.
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
--strict Per-AC-strict mode; exits 4 on any gap. Also
|
|
67
|
+
runs the attestation × profile matrix over
|
|
68
|
+
every AC that binds a dependsOnServices
|
|
69
|
+
entry (verification-integrity 0.7.0 §5.2):
|
|
70
|
+
attestation drift, missing provenance, and
|
|
71
|
+
missing FBS-level attestation all exit 4.
|
|
72
|
+
--require-approved Extra --strict gate: refuse any TS still at
|
|
73
|
+
authoringStatus 'draft' after Stage 4
|
|
74
|
+
(verification-integrity 0.7.0 §7.2). Off
|
|
75
|
+
by default; opt-in via CI.
|
|
76
|
+
--with-code Layer the code axis onto every AC: one of
|
|
77
|
+
implemented-and-covered / implemented-uncovered
|
|
78
|
+
/ unimplemented, plus a tree-wide list of
|
|
79
|
+
CN-orphaned code nodes. INFORMATIONAL ONLY -
|
|
80
|
+
never blocks or affects the exit code (D11;
|
|
81
|
+
the mark-complete gate is where CN
|
|
82
|
+
completeness is enforced).
|
|
83
|
+
--format <format> table (default) | json | mermaid
|
|
84
|
+
--help Print this help
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const VALID_FORMATS = new Set(['table', 'json', 'mermaid']);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {string[]} argv - argv slice after `coverage`
|
|
91
|
+
* @param {object} [deps]
|
|
92
|
+
* @returns {Promise<number>}
|
|
93
|
+
*/
|
|
94
|
+
export async function main(argv, deps = {}) {
|
|
95
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
96
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
97
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
98
|
+
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
104
|
+
stderr.write(HELP);
|
|
105
|
+
return 2;
|
|
106
|
+
}
|
|
107
|
+
const flags = parsed.values;
|
|
108
|
+
const positionals = parsed.positionals;
|
|
109
|
+
if (flags.help) { stdout.write(HELP); return 0; }
|
|
110
|
+
|
|
111
|
+
const format = flags.format ?? 'table';
|
|
112
|
+
if (!VALID_FORMATS.has(format)) {
|
|
113
|
+
stderr.write(`[error] usage coverage: unknown --format ${format} (expected table | json | mermaid)\n`);
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
if (positionals.length > 1) {
|
|
117
|
+
stderr.write('[error] usage coverage: multiple positional ids are not supported\n');
|
|
118
|
+
return 2;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
122
|
+
if (!projectRoot) {
|
|
123
|
+
stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `npx rcf init` to create and wire a project.\n');
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
const { tree, errors } = await walkTree({ projectRoot });
|
|
127
|
+
if (errors.length > 0) {
|
|
128
|
+
stderr.write(`${formatErrors(errors, { verbose: false, strict: false })}\n`);
|
|
129
|
+
return 3;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let scopeId = null;
|
|
133
|
+
if (positionals.length === 1) {
|
|
134
|
+
scopeId = positionals[0];
|
|
135
|
+
// Reject wildcards / globs (spec §D13).
|
|
136
|
+
if (scopeId.includes('*') || scopeId.includes('?')) {
|
|
137
|
+
stderr.write('[error] usage coverage: wildcard / glob positional not supported\n');
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
const classification = classifyCoverageScope(tree, scopeId);
|
|
141
|
+
if (classification === 'below-ac') {
|
|
142
|
+
stderr.write(
|
|
143
|
+
`[error] usage coverage: scope-id ${scopeId} is below the AC layer or off the REQ chain; ` +
|
|
144
|
+
'coverage scope must be a PRD / REQ / US id\n',
|
|
145
|
+
);
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
if (classification === 'not-found' || classification === 'unknown-kind') {
|
|
149
|
+
stderr.write(`[error] usage coverage: id ${scopeId} not found\n`);
|
|
150
|
+
return 2;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// w-2026-07-28-005: resolve every TC's testPointer against the working
|
|
155
|
+
// tree first - "covered" means a pointer that resolves to a real test,
|
|
156
|
+
// and a TC that fails resolution surfaces as covered-unresolved.
|
|
157
|
+
const testPointers = await resolveTestPointers({ projectRoot, tree });
|
|
158
|
+
const result = computeCoverage(tree, {
|
|
159
|
+
strict: Boolean(flags.strict), scopeId, withCode: Boolean(flags['with-code']), testPointers,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
let output;
|
|
163
|
+
if (format === 'json') output = formatJson(result, 'coverage');
|
|
164
|
+
else if (format === 'mermaid') output = formatMermaid(result, 'coverage');
|
|
165
|
+
else output = formatTable(result, 'coverage');
|
|
166
|
+
stdout.write(output);
|
|
167
|
+
|
|
168
|
+
// --strict on any gap = exit 4 (CI-gate friendly). Otherwise 0.
|
|
169
|
+
if (flags.strict && !result.ok) return 4;
|
|
170
|
+
|
|
171
|
+
// 0.7.0 verification-integrity extension: --strict also runs the
|
|
172
|
+
// attestation × profile matrix (spec §5.2). Three refusal classes:
|
|
173
|
+
// (1) attestation missing, (2) provenance missing, (3) attestation
|
|
174
|
+
// drift. All are additive — they never turn a passing coverage into
|
|
175
|
+
// a passing --strict run; they only add exit-4 refusals on new
|
|
176
|
+
// failure modes that the matrix now polices.
|
|
177
|
+
if (flags.strict) {
|
|
178
|
+
// Review N-3 (non-blocking): surface preFlightConfig services with
|
|
179
|
+
// empty affectedFbsIds so the operator knows findAttestationMissing
|
|
180
|
+
// is skipping them on purpose (honest-but-invisible without this
|
|
181
|
+
// line). Warn-only, additive, never turns a passing strict run
|
|
182
|
+
// into a failing one.
|
|
183
|
+
const emptyAffected = findServicesWithEmptyAffectedFbsIds(tree);
|
|
184
|
+
for (const s of emptyAffected) {
|
|
185
|
+
stderr.write(`[warn] coverage --strict: preFlightConfig service '${s.serviceId}' (${s.preFlightConfigId}) has empty affectedFbsIds; the attestation-missing detector cannot cross-check it until the back-reference is populated.\n`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const missing = findAttestationMissing(tree);
|
|
189
|
+
const provMissing = findProvenanceMissing(tree);
|
|
190
|
+
const drift = findAttestationDrift(tree);
|
|
191
|
+
const refusals = [];
|
|
192
|
+
if (missing.length > 0) {
|
|
193
|
+
refusals.push('Attestation missing (FBSes listed in preflight affectedFbsIds but with no dependsOnServices entry):');
|
|
194
|
+
for (const m of missing) refusals.push(` - ${m.fbsId}: run \`rcf fbs ${m.fbsId} depends-on --service ${m.serviceId} --mode ${m.attestationMode} --acs <acIds>\``);
|
|
195
|
+
}
|
|
196
|
+
if (provMissing.length > 0) {
|
|
197
|
+
refusals.push('Runtime provenance missing (TC covers an AC that binds a service):');
|
|
198
|
+
for (const p of provMissing) refusals.push(` - ${p.tsId}/${p.tcId} on ${p.acId}: run \`rcf test-suite ${p.tsId} provenance --tc ${p.tcId} --profile <mock|stub|fixture|live>\``);
|
|
199
|
+
}
|
|
200
|
+
const drifts = drift.filter((d) => d.verdict === 'refuse');
|
|
201
|
+
if (drifts.length > 0) {
|
|
202
|
+
refusals.push('Attestation drift (§3.5 matrix refusal):');
|
|
203
|
+
for (const d of drifts) refusals.push(` - ${d.tsId}/${d.tcId} on ${d.acId} (service ${d.serviceId}): ${d.reason}`);
|
|
204
|
+
}
|
|
205
|
+
if (refusals.length > 0) {
|
|
206
|
+
stderr.write(`[error] coverage --strict: refused - the verification-integrity matrix caught the following:\n${refusals.join('\n')}\n`);
|
|
207
|
+
return 4;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (flags['require-approved']) {
|
|
211
|
+
const draftTsIds = (tree.testSuites ?? []).filter((ts) => ts.status !== 'approved').map((ts) => ts.id);
|
|
212
|
+
if (draftTsIds.length > 0) {
|
|
213
|
+
stderr.write(`[error] coverage --strict --require-approved: refused - ${draftTsIds.length} test suite(s) still not approved: ${draftTsIds.join(', ')}\n`);
|
|
214
|
+
return 4;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// `rcf create <kind>` subcommand handler. Delegates to writer.js for
|
|
2
|
+
// the actual persistence; this file handles CLI parsing + defaults +
|
|
3
|
+
// pre-run tree walk. Phase 4 §D6 (revised).
|
|
4
|
+
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
|
|
8
|
+
import { writeUnexpectedFailure } from '#core/errors';
|
|
9
|
+
import { createDocument, deriveSlug, splitCnPath, walkTree } from '#core/store';
|
|
10
|
+
import { deriveFileDeps, mapDerivedDepsToCnIds } from '#core/store/derive-deps.js';
|
|
11
|
+
import { findProjectRoot } from '../view/index.js';
|
|
12
|
+
// Track C+D §4.4: run the REQ-shape classifier on newly-created REQs
|
|
13
|
+
// so downstream tooling (rcf req-baseline, rcf preflight, the Stage-1
|
|
14
|
+
// gate) can act without a manual `rcf req-classify` first.
|
|
15
|
+
import { classifyAndPersistReq } from '../req-detection/index.js';
|
|
16
|
+
// Track C+D §5.3 moment 4: surface open baseline candidates on a
|
|
17
|
+
// newly-created US so the operator sees the sweep queue immediately.
|
|
18
|
+
import { openCandidatesForUs } from '../req-baseline/open-candidates.js';
|
|
19
|
+
|
|
20
|
+
const OPTION_SPEC = {
|
|
21
|
+
parent: { type: 'string' },
|
|
22
|
+
id: { type: 'string' },
|
|
23
|
+
title: { type: 'string' },
|
|
24
|
+
description: { type: 'string' },
|
|
25
|
+
acs: { type: 'string' },
|
|
26
|
+
ac: { type: 'string' },
|
|
27
|
+
purpose: { type: 'string' },
|
|
28
|
+
'test-level': { type: 'string' },
|
|
29
|
+
slug: { type: 'string' },
|
|
30
|
+
'test-pointer': { type: 'string' },
|
|
31
|
+
'build-order': { type: 'string' },
|
|
32
|
+
'from-file': { type: 'string' },
|
|
33
|
+
'dry-run': { type: 'boolean' },
|
|
34
|
+
quiet: { type: 'boolean' },
|
|
35
|
+
help: { type: 'boolean' },
|
|
36
|
+
// Phase 10 (X2 CodeNode bridge): `rcf create cn` flags.
|
|
37
|
+
path: { type: 'string' },
|
|
38
|
+
deps: { type: 'string' },
|
|
39
|
+
'derive-deps': { type: 'boolean' },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const HELP = `Usage: rcf create <kind> [options]
|
|
43
|
+
|
|
44
|
+
Kinds: req | us | ac | tac | adr | fbs | ts | tc | cn
|
|
45
|
+
|
|
46
|
+
Parent by kind (--parent takes the id of the DIRECT parent listed here,
|
|
47
|
+
not any higher ancestor):
|
|
48
|
+
req -> PRD id us -> REQ id ac -> US id
|
|
49
|
+
tac -> TAD id adr -> TAD id fbs -> BS id
|
|
50
|
+
ts -> US id tc -> TS id
|
|
51
|
+
cn -> no --parent; a Code Node's identity is its --path
|
|
52
|
+
|
|
53
|
+
Note ts -> US id. A test suite hangs off the user story, not off the FBS
|
|
54
|
+
that scheduled the work.
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--parent <id> Required for every kind except cn (post-3.7
|
|
58
|
+
every non-root child carries a mandatory
|
|
59
|
+
parentId-style field). See the parent table
|
|
60
|
+
above for which id each kind expects.
|
|
61
|
+
--id <id> Override auto-assigned id (refuses on
|
|
62
|
+
collision)
|
|
63
|
+
--title <string> Required for req / us / tac / adr / fbs / ts
|
|
64
|
+
(ac / tc use --description)
|
|
65
|
+
--description <string> Body description; required for ac / tc
|
|
66
|
+
--acs <id>[,<id>...] Required for fbs and ts (one or more AC ids)
|
|
67
|
+
--ac <id> Required for tc (single AC id per test case)
|
|
68
|
+
--purpose <string> Required for ts
|
|
69
|
+
--test-level <level> Required for ts; one of
|
|
70
|
+
unit / integration / e2e / contract / manual
|
|
71
|
+
--slug <slug> Optional for tc; derived from description if
|
|
72
|
+
absent
|
|
73
|
+
--test-pointer <path> Required for tc; format filePath::testName.
|
|
74
|
+
Coverage counts a TC only when this pointer
|
|
75
|
+
resolves to a real test in the working tree
|
|
76
|
+
--build-order <int> Optional for fbs; default = max+1 within its BS
|
|
77
|
+
--from-file <path> Read body fields from a JSON file
|
|
78
|
+
(merged with CLI fields; CLI wins on conflict)
|
|
79
|
+
--dry-run Print intended writes without executing
|
|
80
|
+
--quiet Suppress non-error stdout
|
|
81
|
+
--help Print this help
|
|
82
|
+
|
|
83
|
+
Code Node (cn) options:
|
|
84
|
+
--path <path> Repo-relative source path, optionally
|
|
85
|
+
#symbol-suffixed (required)
|
|
86
|
+
--acs <ids> Comma-separated AC ids this node implements
|
|
87
|
+
(may be empty - an orphan CN is legitimate)
|
|
88
|
+
--deps <ids> Comma-separated CN ids this node depends on
|
|
89
|
+
--derive-deps Assist --deps with dependency-cruiser file-level
|
|
90
|
+
analysis (dev-time only; never a runtime dep -
|
|
91
|
+
errors helpfully when the tool is not resolvable)
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const VALID_KINDS = new Set(['req', 'us', 'ac', 'tac', 'adr', 'fbs', 'ts', 'tc', 'cn']);
|
|
95
|
+
// Root-singleton kinds: created by `rcf init`, not by `rcf create`. When
|
|
96
|
+
// a user reaches for `rcf create prd|tad|bs|manifest`, we return a clearer
|
|
97
|
+
// message that points them at `rcf init` (BUG-010).
|
|
98
|
+
const SINGLETON_KINDS = new Set(['prd', 'tad', 'bs', 'manifest']);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string[]} argv - argv slice after `create`
|
|
102
|
+
* @param {object} [deps]
|
|
103
|
+
* @returns {Promise<number>}
|
|
104
|
+
*/
|
|
105
|
+
export async function main(argv, deps = {}) {
|
|
106
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
107
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
108
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
109
|
+
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
115
|
+
stderr.write(HELP);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
const flags = parsed.values;
|
|
119
|
+
const positionals = parsed.positionals;
|
|
120
|
+
if (flags.help) { stdout.write(HELP); return 0; }
|
|
121
|
+
// BUG-009 fix: split the two distinct usage errors so the operator can
|
|
122
|
+
// tell "kind missing" apart from "kind unknown". BUG-010 fix: singleton
|
|
123
|
+
// kinds (prd / tad / bs / manifest) get a clarifying "use rcf init"
|
|
124
|
+
// hint rather than the generic "unknown kind" line.
|
|
125
|
+
if (positionals.length === 0) {
|
|
126
|
+
stderr.write('[error] usage create: <kind> is required (one of req|us|ac|tac|adr|fbs|ts|tc)\n');
|
|
127
|
+
stderr.write(HELP);
|
|
128
|
+
return 2;
|
|
129
|
+
}
|
|
130
|
+
if (positionals.length > 1) {
|
|
131
|
+
stderr.write(`[error] usage create: expected exactly one <kind>, got ${positionals.length}\n`);
|
|
132
|
+
stderr.write(HELP);
|
|
133
|
+
return 2;
|
|
134
|
+
}
|
|
135
|
+
const rawKind = positionals[0];
|
|
136
|
+
if (SINGLETON_KINDS.has(rawKind)) {
|
|
137
|
+
stderr.write(`[error] usage create: ${rawKind} is a root singleton — use \`rcf init\` to create it\n`);
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
if (!VALID_KINDS.has(rawKind)) {
|
|
141
|
+
stderr.write(
|
|
142
|
+
`[error] usage create: unknown kind: ${rawKind} (expected one of req|us|ac|tac|adr|fbs|ts|tc)\n`,
|
|
143
|
+
);
|
|
144
|
+
stderr.write(HELP);
|
|
145
|
+
return 2;
|
|
146
|
+
}
|
|
147
|
+
const kind = rawKind;
|
|
148
|
+
|
|
149
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
150
|
+
if (!projectRoot) {
|
|
151
|
+
stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `npx rcf init` to create and wire a project.\n');
|
|
152
|
+
return 2;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const walkResult = await walkTree({ projectRoot });
|
|
156
|
+
// B5: pre-existing tree breakage no longer blocks write verbs - the
|
|
157
|
+
// write is gated on the POST-write tree state inside the writer, so
|
|
158
|
+
// repairing a broken tree is possible while net-new breakage is still
|
|
159
|
+
// refused.
|
|
160
|
+
if (walkResult.errors.length > 0) {
|
|
161
|
+
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let fileBody = null;
|
|
165
|
+
if (flags['from-file']) {
|
|
166
|
+
try {
|
|
167
|
+
const raw = await readFile(flags['from-file'], 'utf8');
|
|
168
|
+
fileBody = JSON.parse(raw);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
stderr.write(`[error] usage create: cannot read --from-file: ${err.message}\n`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const body = { ...(fileBody ?? {}) };
|
|
176
|
+
// CLI wins on conflict.
|
|
177
|
+
if (flags.title !== undefined) body.title = flags.title;
|
|
178
|
+
if (flags.description !== undefined) body.description = flags.description;
|
|
179
|
+
if (flags.purpose !== undefined) body.purpose = flags.purpose;
|
|
180
|
+
if (flags['test-level'] !== undefined) body.testLevel = flags['test-level'];
|
|
181
|
+
// Phase 10: `cn`'s AC cross-link field is `implementsAcIds`, not `acIds`
|
|
182
|
+
// (fbs/ts share `acIds`) - --acs maps to whichever the kind expects.
|
|
183
|
+
if (flags.acs !== undefined) {
|
|
184
|
+
const ids = flags.acs.split(',').map((s) => s.trim()).filter(Boolean);
|
|
185
|
+
if (kind === 'cn') body.implementsAcIds = ids;
|
|
186
|
+
else body.acIds = ids;
|
|
187
|
+
}
|
|
188
|
+
if (kind === 'cn') {
|
|
189
|
+
if (flags.path !== undefined) body.path = flags.path;
|
|
190
|
+
if (flags.deps !== undefined) body.dependencies = flags.deps.split(',').map((s) => s.trim()).filter(Boolean);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const options = {
|
|
194
|
+
id: flags.id,
|
|
195
|
+
parentId: flags.parent,
|
|
196
|
+
dryRun: Boolean(flags['dry-run']),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Per-kind mandatory-title / mandatory-description checks.
|
|
200
|
+
if (kind === 'ac' || kind === 'tc') {
|
|
201
|
+
if (!body.description) {
|
|
202
|
+
stderr.write(`[error] usage create ${kind}: --description is required\n`);
|
|
203
|
+
return 2;
|
|
204
|
+
}
|
|
205
|
+
} else if (kind === 'cn') {
|
|
206
|
+
if (!body.path) {
|
|
207
|
+
stderr.write('[error] usage create cn: --path is required\n');
|
|
208
|
+
return 2;
|
|
209
|
+
}
|
|
210
|
+
// Phase 10 D5: --derive-deps assist. Optional, dev-time only, never a
|
|
211
|
+
// runtime dependency - errors helpfully (exit 2) when the tool cannot
|
|
212
|
+
// be resolved rather than silently degrading or reaching for the
|
|
213
|
+
// network to install it.
|
|
214
|
+
if (flags['derive-deps']) {
|
|
215
|
+
const { file } = splitCnPath(body.path);
|
|
216
|
+
const derived = await deriveFileDeps({ projectRoot, filePath: file });
|
|
217
|
+
if (!derived.ok) {
|
|
218
|
+
stderr.write(`[error] usage create cn: --derive-deps: ${derived.message}\n`);
|
|
219
|
+
return 2;
|
|
220
|
+
}
|
|
221
|
+
const { cnIds, unmatched } = mapDerivedDepsToCnIds(walkResult.tree, derived.deps);
|
|
222
|
+
const existing = Array.isArray(body.dependencies) ? body.dependencies : [];
|
|
223
|
+
body.dependencies = [...new Set([...existing, ...cnIds])].sort();
|
|
224
|
+
if (unmatched.length > 0 && !flags.quiet) {
|
|
225
|
+
stdout.write(`[info] --derive-deps: ${unmatched.length} file-level import(s) have no matching CN yet, skipped: ${unmatched.join(', ')}\n`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else if (!body.title) {
|
|
229
|
+
stderr.write(`[error] usage create ${kind}: --title is required\n`);
|
|
230
|
+
return 2;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (kind === 'ts') {
|
|
234
|
+
if (!body.purpose) { stderr.write('[error] usage create ts: --purpose is required\n'); return 2; }
|
|
235
|
+
if (!body.testLevel) { stderr.write('[error] usage create ts: --test-level is required\n'); return 2; }
|
|
236
|
+
if (!Array.isArray(body.acIds) || body.acIds.length === 0) {
|
|
237
|
+
stderr.write('[error] usage create ts: --acs is required (one or more AC ids)\n');
|
|
238
|
+
return 2;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (kind === 'fbs') {
|
|
242
|
+
if (!Array.isArray(body.acIds) || body.acIds.length === 0) {
|
|
243
|
+
stderr.write('[error] usage create fbs: --acs is required (one or more AC ids)\n');
|
|
244
|
+
return 2;
|
|
245
|
+
}
|
|
246
|
+
if (flags['build-order'] !== undefined) {
|
|
247
|
+
const n = Number(flags['build-order']);
|
|
248
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
249
|
+
stderr.write(`[error] usage create fbs: --build-order expects a positive integer, got ${flags['build-order']}\n`);
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
options.buildOrder = n;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (kind === 'tc') {
|
|
256
|
+
if (!flags.ac) { stderr.write('[error] usage create tc: --ac is required\n'); return 2; }
|
|
257
|
+
// w-2026-07-28-005: a TC without a pointer is a coverage claim with
|
|
258
|
+
// nothing behind it; refuse at the usage layer with the fix in hand.
|
|
259
|
+
if (!flags['test-pointer']) {
|
|
260
|
+
stderr.write('[error] usage create tc: --test-pointer is required (format filePath::testName; coverage counts a TC only when its pointer resolves to a real test)\n');
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
body.acId = flags.ac;
|
|
264
|
+
// 0.8.0 slug-train (w-2026-07-28-012 landmine 4): deriveSlug returns ''
|
|
265
|
+
// on empty derivation; TC keeps its historical 'tc' fallback locally
|
|
266
|
+
// rather than letting deriveSlug bake it in.
|
|
267
|
+
options.slug = flags.slug ?? (deriveSlug(body.description) || 'tc');
|
|
268
|
+
options.testPointer = flags['test-pointer'];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const result = await createDocument({
|
|
272
|
+
projectRoot, tree: walkResult.tree, kind, body, options, walkErrors: walkResult.errors,
|
|
273
|
+
});
|
|
274
|
+
if (isRcfError(result)) {
|
|
275
|
+
return handleWriterError(result, stderr);
|
|
276
|
+
}
|
|
277
|
+
if (options.dryRun) {
|
|
278
|
+
if (!flags.quiet) stdout.write(`[dry-run] would create ${result.id} at ${result.filePath}\n`);
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
if (!flags.quiet) {
|
|
282
|
+
stdout.write(`${result.id} created at ${result.filePath}\n`);
|
|
283
|
+
}
|
|
284
|
+
// Track C+D §5.3 moment 4: on a newly-created US under a
|
|
285
|
+
// shape-classified REQ, surface any baseline keys that will be OPEN
|
|
286
|
+
// until the operator resolves them. The Stage-1 gate refuses build
|
|
287
|
+
// on the same set; the operator sees the queue here rather than at
|
|
288
|
+
// the refusal.
|
|
289
|
+
if (kind === 'us') {
|
|
290
|
+
try {
|
|
291
|
+
const postWalk = await walkTree({ projectRoot });
|
|
292
|
+
const usDoc = postWalk.tree.byId.get(result.id);
|
|
293
|
+
if (usDoc) {
|
|
294
|
+
const open = openCandidatesForUs(postWalk.tree, usDoc);
|
|
295
|
+
if (open.length > 0 && !flags.quiet) {
|
|
296
|
+
const keys = open.map((c) => c.baselineKey).join(', ');
|
|
297
|
+
stdout.write(`${result.id} has ${open.length} open baseline candidate${open.length === 1 ? '' : 's'}: ${keys}\n`);
|
|
298
|
+
stdout.write(` Resolve: rcf req-baseline sweep --req ${usDoc.reqId}\n`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch (err) {
|
|
302
|
+
stderr.write(`[warn] baseline-scan (post-create-us): ${err.message}\n`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Track C+D §4.4: fire the classifier on newly-created REQs. Best-
|
|
307
|
+
// effort: a classification error surfaces on stderr but never fails
|
|
308
|
+
// the create (the REQ file was already written; classification is
|
|
309
|
+
// provenance layered on top).
|
|
310
|
+
if (kind === 'req') {
|
|
311
|
+
try {
|
|
312
|
+
const postWalk = await walkTree({ projectRoot });
|
|
313
|
+
const classifyOutcome = await classifyAndPersistReq({
|
|
314
|
+
projectRoot,
|
|
315
|
+
tree: postWalk.tree,
|
|
316
|
+
reqId: result.id,
|
|
317
|
+
});
|
|
318
|
+
if (classifyOutcome && classifyOutcome.ok === false) {
|
|
319
|
+
stderr.write(`[warn] ${classifyOutcome.message}\n`);
|
|
320
|
+
} else if (!flags.quiet && classifyOutcome?.block?.shapes?.length > 0) {
|
|
321
|
+
const shapes = classifyOutcome.block.shapes.join(', ');
|
|
322
|
+
stdout.write(`${result.id} shapeClassification: [${shapes}] (${classifyOutcome.block.reason})\n`);
|
|
323
|
+
}
|
|
324
|
+
} catch (err) {
|
|
325
|
+
stderr.write(`[warn] req-classify (post-create): ${err.message}\n`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const ERROR_KINDS = new Set([
|
|
332
|
+
'validation',
|
|
333
|
+
'missingFile',
|
|
334
|
+
'brokenReference',
|
|
335
|
+
'parseFailure',
|
|
336
|
+
'ioFailure',
|
|
337
|
+
'usage',
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
function isRcfError(value) {
|
|
341
|
+
return Boolean(value) && typeof value === 'object' && typeof value.kind === 'string'
|
|
342
|
+
&& ERROR_KINDS.has(value.kind) && typeof value.message === 'string';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function handleWriterError(err, stderr) {
|
|
346
|
+
const kind = err.kind;
|
|
347
|
+
// BUG-007 fix: spec §D15 mandates exit-1 emit
|
|
348
|
+
// `[rcf] unexpected failure: <msg>\n<stack>` — even under --quiet.
|
|
349
|
+
if (kind === 'ioFailure') {
|
|
350
|
+
writeUnexpectedFailure(err, stderr);
|
|
351
|
+
return 1;
|
|
352
|
+
}
|
|
353
|
+
stderr.write(`[error] ${kind} ${err.message}\n`);
|
|
354
|
+
if (kind === 'usage') return 2;
|
|
355
|
+
if (kind === 'validation' || kind === 'brokenReference') return 3;
|
|
356
|
+
if (kind === 'missingFile' || kind === 'parseFailure') return 2;
|
|
357
|
+
return 1;
|
|
358
|
+
}
|