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.
Files changed (349) hide show
  1. package/CHANGELOG.md +344 -0
  2. package/LICENSE +202 -0
  3. package/README.md +92 -4
  4. package/bin/rcf-verify.js +122 -0
  5. package/bin/rcf.js +174 -0
  6. package/bin/view-supervisor-child.mjs +14 -0
  7. package/fixtures/canary-manifest.json +103 -0
  8. package/guidance/README.md +25 -0
  9. package/guidance/build-cycle-playbook.md +492 -0
  10. package/guidance/build-cycle.md +40 -0
  11. package/guidance/document-model.md +45 -0
  12. package/guidance/elicitation-playbook.md +442 -0
  13. package/guidance/harness-template.md +246 -0
  14. package/guidance/managed/README.md +63 -0
  15. package/guidance/managed/agent-instructions-block.hash +1 -0
  16. package/guidance/managed/agent-instructions-block.md +220 -0
  17. package/guidance/managed/legacy-fragment-hashes.json +22 -0
  18. package/guidance/manifest.json +21 -0
  19. package/guidance/overview.md +48 -0
  20. package/guidance/persona-programme.md +44 -0
  21. package/package.json +71 -8
  22. package/rcf/adrs/adr-001.json +25 -0
  23. package/rcf/adrs/adr-002.json +25 -0
  24. package/rcf/adrs/adr-003.json +25 -0
  25. package/rcf/adrs/adr-004.json +20 -0
  26. package/rcf/adrs/adr-005.json +20 -0
  27. package/rcf/adrs/adr-006.json +25 -0
  28. package/rcf/adrs/adr-007.json +25 -0
  29. package/rcf/adrs/adr-008.json +25 -0
  30. package/rcf/adrs/adr-009.json +25 -0
  31. package/rcf/build-sequence.json +11 -0
  32. package/rcf/code-nodes/cn-001.json +14 -0
  33. package/rcf/code-nodes/cn-002.json +14 -0
  34. package/rcf/code-nodes/cn-003.json +15 -0
  35. package/rcf/code-nodes/cn-004.json +17 -0
  36. package/rcf/code-nodes/cn-005.json +19 -0
  37. package/rcf/code-nodes/cn-006.json +17 -0
  38. package/rcf/code-nodes/cn-007.json +16 -0
  39. package/rcf/code-nodes/cn-008.json +19 -0
  40. package/rcf/code-nodes/cn-009.json +16 -0
  41. package/rcf/code-nodes/cn-010.json +18 -0
  42. package/rcf/code-nodes/cn-011.json +18 -0
  43. package/rcf/code-nodes/cn-012.json +18 -0
  44. package/rcf/code-nodes/cn-013.json +19 -0
  45. package/rcf/code-nodes/cn-014.json +16 -0
  46. package/rcf/code-nodes/cn-015.json +16 -0
  47. package/rcf/code-nodes/cn-016.json +16 -0
  48. package/rcf/code-nodes/cn-017.json +16 -0
  49. package/rcf/code-nodes/cn-018.json +15 -0
  50. package/rcf/code-nodes/cn-019.json +16 -0
  51. package/rcf/code-nodes/cn-020.json +16 -0
  52. package/rcf/code-nodes/cn-021.json +16 -0
  53. package/rcf/code-nodes/cn-022.json +16 -0
  54. package/rcf/code-nodes/cn-023.json +16 -0
  55. package/rcf/code-nodes/cn-024.json +16 -0
  56. package/rcf/code-nodes/cn-025.json +16 -0
  57. package/rcf/code-nodes/cn-026.json +16 -0
  58. package/rcf/code-nodes/cn-027.json +16 -0
  59. package/rcf/code-nodes/cn-028.json +16 -0
  60. package/rcf/code-nodes/cn-029.json +19 -0
  61. package/rcf/code-nodes/cn-030.json +14 -0
  62. package/rcf/code-nodes/cn-031.json +14 -0
  63. package/rcf/code-nodes/cn-032.json +14 -0
  64. package/rcf/code-nodes/cn-033.json +14 -0
  65. package/rcf/code-nodes/cn-034.json +14 -0
  66. package/rcf/code-nodes/cn-035.json +14 -0
  67. package/rcf/code-nodes/cn-036.json +14 -0
  68. package/rcf/code-nodes/cn-037.json +14 -0
  69. package/rcf/code-nodes/cn-038.json +14 -0
  70. package/rcf/code-nodes/cn-039.json +14 -0
  71. package/rcf/code-nodes/cn-040.json +14 -0
  72. package/rcf/code-nodes/cn-041.json +14 -0
  73. package/rcf/code-nodes/cn-042.json +14 -0
  74. package/rcf/code-nodes/cn-043.json +14 -0
  75. package/rcf/code-nodes/cn-044.json +14 -0
  76. package/rcf/code-nodes/cn-045.json +14 -0
  77. package/rcf/code-nodes/cn-046.json +14 -0
  78. package/rcf/code-nodes/cn-047.json +14 -0
  79. package/rcf/code-nodes/cn-048.json +14 -0
  80. package/rcf/code-nodes/cn-049.json +14 -0
  81. package/rcf/code-nodes/cn-050.json +14 -0
  82. package/rcf/code-nodes/cn-051.json +14 -0
  83. package/rcf/code-nodes/cn-052.json +14 -0
  84. package/rcf/code-nodes/cn-053.json +14 -0
  85. package/rcf/code-nodes/cn-054.json +14 -0
  86. package/rcf/code-nodes/cn-055.json +14 -0
  87. package/rcf/code-nodes/cn-056.json +14 -0
  88. package/rcf/code-nodes/cn-057.json +14 -0
  89. package/rcf/fbs/fbs-001.json +49 -0
  90. package/rcf/fbs/fbs-002.json +42 -0
  91. package/rcf/fbs/fbs-003.json +37 -0
  92. package/rcf/fbs/fbs-004.json +39 -0
  93. package/rcf/fbs/fbs-005.json +38 -0
  94. package/rcf/fbs/fbs-006.json +48 -0
  95. package/rcf/fbs/fbs-007.json +39 -0
  96. package/rcf/fbs/fbs-008.json +40 -0
  97. package/rcf/fbs/fbs-009.json +36 -0
  98. package/rcf/fbs/fbs-010.json +41 -0
  99. package/rcf/fbs/fbs-011.json +36 -0
  100. package/rcf/fbs/fbs-012.json +46 -0
  101. package/rcf/fbs/fbs-013.json +42 -0
  102. package/rcf/fbs/fbs-014.json +49 -0
  103. package/rcf/fbs/fbs-015.json +32 -0
  104. package/rcf/manifest.json +17 -0
  105. package/rcf/prd.json +47 -0
  106. package/rcf/requirements/req-001.json +19 -0
  107. package/rcf/requirements/req-002.json +19 -0
  108. package/rcf/requirements/req-003.json +19 -0
  109. package/rcf/requirements/req-004.json +19 -0
  110. package/rcf/requirements/req-005.json +19 -0
  111. package/rcf/requirements/req-006.json +19 -0
  112. package/rcf/requirements/req-007.json +19 -0
  113. package/rcf/requirements/req-008.json +19 -0
  114. package/rcf/requirements/req-009.json +19 -0
  115. package/rcf/tacs/tac-001.json +45 -0
  116. package/rcf/tacs/tac-002.json +109 -0
  117. package/rcf/tacs/tac-003.json +40 -0
  118. package/rcf/tacs/tac-004.json +51 -0
  119. package/rcf/tacs/tac-005.json +52 -0
  120. package/rcf/tacs/tac-006.json +104 -0
  121. package/rcf/tacs/tac-007.json +38 -0
  122. package/rcf/tacs/tac-008.json +51 -0
  123. package/rcf/tad.json +51 -0
  124. package/rcf/test-suites/PENDING.md +23 -0
  125. package/rcf/test-suites/ts-001.json +38 -0
  126. package/rcf/test-suites/ts-002.json +38 -0
  127. package/rcf/test-suites/ts-003.json +43 -0
  128. package/rcf/test-suites/ts-004.json +44 -0
  129. package/rcf/test-suites/ts-005.json +30 -0
  130. package/rcf/test-suites/ts-006.json +36 -0
  131. package/rcf/test-suites/ts-007.json +43 -0
  132. package/rcf/test-suites/ts-008.json +37 -0
  133. package/rcf/test-suites/ts-009.json +38 -0
  134. package/rcf/test-suites/ts-010.json +38 -0
  135. package/rcf/test-suites/ts-011.json +44 -0
  136. package/rcf/test-suites/ts-012.json +36 -0
  137. package/rcf/test-suites/ts-013.json +38 -0
  138. package/rcf/test-suites/ts-014.json +38 -0
  139. package/rcf/test-suites/ts-015.json +38 -0
  140. package/rcf/test-suites/ts-016.json +37 -0
  141. package/rcf/test-suites/ts-017.json +38 -0
  142. package/rcf/test-suites/ts-018.json +38 -0
  143. package/rcf/test-suites/ts-019.json +37 -0
  144. package/rcf/test-suites/ts-020.json +46 -0
  145. package/rcf/test-suites/ts-021.json +46 -0
  146. package/rcf/test-suites/ts-022.json +46 -0
  147. package/rcf/test-suites/ts-023.json +46 -0
  148. package/rcf/test-suites/ts-024.json +46 -0
  149. package/rcf/test-suites/ts-025.json +52 -0
  150. package/rcf/user-stories/us-101.json +40 -0
  151. package/rcf/user-stories/us-102.json +40 -0
  152. package/rcf/user-stories/us-201.json +40 -0
  153. package/rcf/user-stories/us-202.json +40 -0
  154. package/rcf/user-stories/us-203.json +32 -0
  155. package/rcf/user-stories/us-301.json +40 -0
  156. package/rcf/user-stories/us-302.json +40 -0
  157. package/rcf/user-stories/us-303.json +40 -0
  158. package/rcf/user-stories/us-304.json +40 -0
  159. package/rcf/user-stories/us-401.json +40 -0
  160. package/rcf/user-stories/us-402.json +40 -0
  161. package/rcf/user-stories/us-403.json +40 -0
  162. package/rcf/user-stories/us-501.json +40 -0
  163. package/rcf/user-stories/us-502.json +40 -0
  164. package/rcf/user-stories/us-503.json +40 -0
  165. package/rcf/user-stories/us-601.json +40 -0
  166. package/rcf/user-stories/us-602.json +40 -0
  167. package/rcf/user-stories/us-701.json +40 -0
  168. package/rcf/user-stories/us-702.json +40 -0
  169. package/rcf/user-stories/us-801.json +49 -0
  170. package/rcf/user-stories/us-802.json +49 -0
  171. package/rcf/user-stories/us-803.json +49 -0
  172. package/rcf/user-stories/us-804.json +49 -0
  173. package/rcf/user-stories/us-805.json +49 -0
  174. package/rcf/user-stories/us-901.json +40 -0
  175. package/src/.gitkeep +0 -0
  176. package/src/admissibility/enforce.js +142 -0
  177. package/src/admissibility/index.js +8 -0
  178. package/src/admissibility/markers.js +104 -0
  179. package/src/admissibility/scope-lint.js +163 -0
  180. package/src/browser-verify/auth-smoke.js +109 -0
  181. package/src/browser-verify/index.js +29 -0
  182. package/src/browser-verify/invariants.js +336 -0
  183. package/src/browser-verify/manifest-writer.js +189 -0
  184. package/src/browser-verify/runner.js +171 -0
  185. package/src/build/bundle.js +198 -0
  186. package/src/build/formatters/json.js +18 -0
  187. package/src/build/formatters/markdown.js +406 -0
  188. package/src/build/index.js +14 -0
  189. package/src/build/mark.js +177 -0
  190. package/src/build/queue.js +285 -0
  191. package/src/cli/browser-verify.js +231 -0
  192. package/src/cli/build.js +584 -0
  193. package/src/cli/coverage.js +219 -0
  194. package/src/cli/create.js +358 -0
  195. package/src/cli/delete.js +127 -0
  196. package/src/cli/design.js +353 -0
  197. package/src/cli/doctor.js +548 -0
  198. package/src/cli/fbs.js +171 -0
  199. package/src/cli/finalise.js +377 -0
  200. package/src/cli/guidance.js +206 -0
  201. package/src/cli/help.js +156 -0
  202. package/src/cli/impact.js +119 -0
  203. package/src/cli/init.js +282 -0
  204. package/src/cli/intake.js +153 -0
  205. package/src/cli/link.js +128 -0
  206. package/src/cli/mcp.js +160 -0
  207. package/src/cli/preflight.js +220 -0
  208. package/src/cli/read.js +168 -0
  209. package/src/cli/req-baseline.js +269 -0
  210. package/src/cli/req-classify.js +135 -0
  211. package/src/cli/review.js +295 -0
  212. package/src/cli/test-suite.js +221 -0
  213. package/src/cli/trace.js +172 -0
  214. package/src/cli/ui-baseline.js +292 -0
  215. package/src/cli/ui-classify.js +108 -0
  216. package/src/cli/update.js +197 -0
  217. package/src/cli/validate.js +168 -0
  218. package/src/cli/view.js +452 -0
  219. package/src/core/baseline-catalog/data/auth.json +42 -0
  220. package/src/core/baseline-catalog/data/http-api.json +42 -0
  221. package/src/core/baseline-catalog/data/notifications.json +33 -0
  222. package/src/core/baseline-catalog/data/persistence.json +33 -0
  223. package/src/core/baseline-catalog/data/web-ui.json +60 -0
  224. package/src/core/baseline-catalog/index.js +121 -0
  225. package/src/core/errors/index.js +167 -0
  226. package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
  227. package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
  228. package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
  229. package/src/core/isolation/index.js +60 -0
  230. package/src/core/mcp/framing.js +103 -0
  231. package/src/core/mcp/index.js +8 -0
  232. package/src/core/mcp/server.js +228 -0
  233. package/src/core/patterns/register-canary.js +209 -0
  234. package/src/core/patterns/req-shapes.js +158 -0
  235. package/src/core/patterns/services.js +358 -0
  236. package/src/core/patterns/ui-shapes.js +166 -0
  237. package/src/core/store/cn-resolve.js +134 -0
  238. package/src/core/store/derive-deps.js +93 -0
  239. package/src/core/store/ids.js +78 -0
  240. package/src/core/store/index.js +20 -0
  241. package/src/core/store/init.js +255 -0
  242. package/src/core/store/loader.js +214 -0
  243. package/src/core/store/tp-resolve.js +176 -0
  244. package/src/core/store/validator.js +191 -0
  245. package/src/core/store/walker.js +944 -0
  246. package/src/core/store/writer.js +1879 -0
  247. package/src/design/index.js +11 -0
  248. package/src/design/writer.js +271 -0
  249. package/src/finalise/detect.js +151 -0
  250. package/src/finalise/index.js +31 -0
  251. package/src/finalise/ingest.js +160 -0
  252. package/src/finalise/install.js +119 -0
  253. package/src/finalise/ship-without-verified.js +131 -0
  254. package/src/finalise/spawn.js +84 -0
  255. package/src/intake/fidelity.js +105 -0
  256. package/src/intake/index.js +6 -0
  257. package/src/intake/manifest-writer.js +100 -0
  258. package/src/intake/orchestrator.js +138 -0
  259. package/src/intake/validate.js +80 -0
  260. package/src/mcp/map-errors.js +131 -0
  261. package/src/mcp/prompts.js +49 -0
  262. package/src/mcp/resources.js +244 -0
  263. package/src/mcp/tools.js +1212 -0
  264. package/src/preflight/design-shapes.js +185 -0
  265. package/src/preflight/index.js +90 -0
  266. package/src/preflight/manifest-writer.js +264 -0
  267. package/src/preflight/scanner.js +206 -0
  268. package/src/preflight/secrets.js +134 -0
  269. package/src/preflight/session.js +246 -0
  270. package/src/query/attestation.js +285 -0
  271. package/src/query/coverage.js +308 -0
  272. package/src/query/formatters/json.js +21 -0
  273. package/src/query/formatters/mermaid.js +209 -0
  274. package/src/query/formatters/table.js +203 -0
  275. package/src/query/impact.js +173 -0
  276. package/src/query/index.js +13 -0
  277. package/src/query/refuse-on-admissibility.js +73 -0
  278. package/src/query/trace.js +345 -0
  279. package/src/register-canary/fixture-loader.js +87 -0
  280. package/src/register-canary/index.js +10 -0
  281. package/src/register-canary/record-writer.js +132 -0
  282. package/src/register-canary/runner.js +156 -0
  283. package/src/req-baseline/gate.js +86 -0
  284. package/src/req-baseline/index.js +27 -0
  285. package/src/req-baseline/open-candidates.js +143 -0
  286. package/src/req-baseline/opt-out.js +195 -0
  287. package/src/req-baseline/sweep.js +230 -0
  288. package/src/req-detection/classifier.js +181 -0
  289. package/src/req-detection/index.js +9 -0
  290. package/src/req-detection/persist.js +55 -0
  291. package/src/review/index.js +325 -0
  292. package/src/review/mutation.js +117 -0
  293. package/src/review/ui-baseline-drift.js +138 -0
  294. package/src/ruleset/index.js +140 -0
  295. package/src/ruleset/ruleset.json +146 -0
  296. package/src/server/index.js +178 -0
  297. package/src/server/routes.js +110 -0
  298. package/src/server/sse.js +118 -0
  299. package/src/setup/agent-setup.js +362 -0
  300. package/src/setup/identity-seed.js +104 -0
  301. package/src/setup/knowledge-seed.js +123 -0
  302. package/src/setup/managed-block.js +193 -0
  303. package/src/setup/managed-gitignore.js +166 -0
  304. package/src/setup/managed-markers.js +49 -0
  305. package/src/ui-baseline/defaults.js +119 -0
  306. package/src/ui-baseline/index.js +25 -0
  307. package/src/ui-baseline/manifest-writer.js +282 -0
  308. package/src/ui-baseline/session.js +178 -0
  309. package/src/ui-detection/classifier.js +192 -0
  310. package/src/verify/chain/index.js +221 -0
  311. package/src/verify/cli/cleanup.js +61 -0
  312. package/src/verify/cli/help.js +56 -0
  313. package/src/verify/cli/mcp.js +98 -0
  314. package/src/verify/cli/provision.js +71 -0
  315. package/src/verify/cli/report.js +71 -0
  316. package/src/verify/cli/run.js +155 -0
  317. package/src/verify/engine/brief.js +87 -0
  318. package/src/verify/engine/index.js +177 -0
  319. package/src/verify/engine/launcher.js +307 -0
  320. package/src/verify/mcp/tools.js +107 -0
  321. package/src/verify/profile/index.js +146 -0
  322. package/src/verify/provision/index.js +256 -0
  323. package/src/verify/report/index.js +139 -0
  324. package/src/verify/report/renderer.js +118 -0
  325. package/src/verify/verdict/index.js +313 -0
  326. package/src/view/doc-renderers/adr.js +44 -0
  327. package/src/view/doc-renderers/build-sequence.js +40 -0
  328. package/src/view/doc-renderers/fbs.js +128 -0
  329. package/src/view/doc-renderers/helpers.js +159 -0
  330. package/src/view/doc-renderers/index.js +12 -0
  331. package/src/view/doc-renderers/prd.js +45 -0
  332. package/src/view/doc-renderers/req.js +43 -0
  333. package/src/view/doc-renderers/tac.js +38 -0
  334. package/src/view/doc-renderers/tad.js +74 -0
  335. package/src/view/doc-renderers/test-suite.js +45 -0
  336. package/src/view/doc-renderers/user-story.js +63 -0
  337. package/src/view/html-page.js +462 -0
  338. package/src/view/index.js +63 -0
  339. package/src/view/live-client.js +338 -0
  340. package/src/view/mermaid-diagram.js +178 -0
  341. package/src/view/style.css +735 -0
  342. package/src/view/tree-model.js +152 -0
  343. package/src/view/vendored/mermaid.min.js +2607 -0
  344. package/src/view-supervisor/index.js +26 -0
  345. package/src/view-supervisor/logs.js +32 -0
  346. package/src/view-supervisor/manifest-writer.js +178 -0
  347. package/src/view-supervisor/persist-until.js +85 -0
  348. package/src/view-supervisor/supervisor.js +276 -0
  349. 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
+ }