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.
Files changed (342) hide show
  1. package/CHANGELOG.md +288 -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 +69 -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/browser-verify/auth-smoke.js +109 -0
  177. package/src/browser-verify/index.js +29 -0
  178. package/src/browser-verify/invariants.js +336 -0
  179. package/src/browser-verify/manifest-writer.js +189 -0
  180. package/src/browser-verify/runner.js +171 -0
  181. package/src/build/bundle.js +198 -0
  182. package/src/build/formatters/json.js +18 -0
  183. package/src/build/formatters/markdown.js +406 -0
  184. package/src/build/index.js +14 -0
  185. package/src/build/mark.js +177 -0
  186. package/src/build/queue.js +285 -0
  187. package/src/cli/browser-verify.js +231 -0
  188. package/src/cli/build.js +584 -0
  189. package/src/cli/coverage.js +219 -0
  190. package/src/cli/create.js +355 -0
  191. package/src/cli/delete.js +127 -0
  192. package/src/cli/design.js +353 -0
  193. package/src/cli/doctor.js +548 -0
  194. package/src/cli/fbs.js +171 -0
  195. package/src/cli/finalise.js +377 -0
  196. package/src/cli/guidance.js +206 -0
  197. package/src/cli/help.js +156 -0
  198. package/src/cli/impact.js +119 -0
  199. package/src/cli/init.js +282 -0
  200. package/src/cli/intake.js +153 -0
  201. package/src/cli/link.js +128 -0
  202. package/src/cli/mcp.js +160 -0
  203. package/src/cli/preflight.js +220 -0
  204. package/src/cli/read.js +162 -0
  205. package/src/cli/req-baseline.js +269 -0
  206. package/src/cli/req-classify.js +135 -0
  207. package/src/cli/review.js +295 -0
  208. package/src/cli/test-suite.js +216 -0
  209. package/src/cli/trace.js +172 -0
  210. package/src/cli/ui-baseline.js +292 -0
  211. package/src/cli/ui-classify.js +108 -0
  212. package/src/cli/update.js +197 -0
  213. package/src/cli/validate.js +168 -0
  214. package/src/cli/view.js +452 -0
  215. package/src/core/baseline-catalog/data/auth.json +42 -0
  216. package/src/core/baseline-catalog/data/http-api.json +42 -0
  217. package/src/core/baseline-catalog/data/notifications.json +33 -0
  218. package/src/core/baseline-catalog/data/persistence.json +33 -0
  219. package/src/core/baseline-catalog/data/web-ui.json +60 -0
  220. package/src/core/baseline-catalog/index.js +121 -0
  221. package/src/core/errors/index.js +167 -0
  222. package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
  223. package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
  224. package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
  225. package/src/core/isolation/index.js +60 -0
  226. package/src/core/mcp/framing.js +103 -0
  227. package/src/core/mcp/index.js +8 -0
  228. package/src/core/mcp/server.js +228 -0
  229. package/src/core/patterns/register-canary.js +209 -0
  230. package/src/core/patterns/req-shapes.js +158 -0
  231. package/src/core/patterns/services.js +358 -0
  232. package/src/core/patterns/ui-shapes.js +166 -0
  233. package/src/core/store/cn-resolve.js +134 -0
  234. package/src/core/store/derive-deps.js +93 -0
  235. package/src/core/store/ids.js +78 -0
  236. package/src/core/store/index.js +20 -0
  237. package/src/core/store/init.js +255 -0
  238. package/src/core/store/loader.js +211 -0
  239. package/src/core/store/tp-resolve.js +176 -0
  240. package/src/core/store/validator.js +191 -0
  241. package/src/core/store/walker.js +898 -0
  242. package/src/core/store/writer.js +1849 -0
  243. package/src/design/index.js +11 -0
  244. package/src/design/writer.js +271 -0
  245. package/src/finalise/detect.js +129 -0
  246. package/src/finalise/index.js +17 -0
  247. package/src/finalise/ingest.js +119 -0
  248. package/src/finalise/install.js +119 -0
  249. package/src/finalise/ship-without-verified.js +131 -0
  250. package/src/finalise/spawn.js +84 -0
  251. package/src/intake/fidelity.js +105 -0
  252. package/src/intake/index.js +6 -0
  253. package/src/intake/manifest-writer.js +100 -0
  254. package/src/intake/orchestrator.js +138 -0
  255. package/src/intake/validate.js +80 -0
  256. package/src/mcp/map-errors.js +131 -0
  257. package/src/mcp/prompts.js +49 -0
  258. package/src/mcp/resources.js +244 -0
  259. package/src/mcp/tools.js +1204 -0
  260. package/src/preflight/design-shapes.js +185 -0
  261. package/src/preflight/index.js +90 -0
  262. package/src/preflight/manifest-writer.js +264 -0
  263. package/src/preflight/scanner.js +206 -0
  264. package/src/preflight/secrets.js +134 -0
  265. package/src/preflight/session.js +246 -0
  266. package/src/query/attestation.js +285 -0
  267. package/src/query/coverage.js +308 -0
  268. package/src/query/formatters/json.js +21 -0
  269. package/src/query/formatters/mermaid.js +209 -0
  270. package/src/query/formatters/table.js +203 -0
  271. package/src/query/impact.js +173 -0
  272. package/src/query/index.js +9 -0
  273. package/src/query/trace.js +345 -0
  274. package/src/register-canary/fixture-loader.js +87 -0
  275. package/src/register-canary/index.js +10 -0
  276. package/src/register-canary/record-writer.js +132 -0
  277. package/src/register-canary/runner.js +156 -0
  278. package/src/req-baseline/gate.js +86 -0
  279. package/src/req-baseline/index.js +27 -0
  280. package/src/req-baseline/open-candidates.js +143 -0
  281. package/src/req-baseline/opt-out.js +195 -0
  282. package/src/req-baseline/sweep.js +230 -0
  283. package/src/req-detection/classifier.js +181 -0
  284. package/src/req-detection/index.js +9 -0
  285. package/src/req-detection/persist.js +55 -0
  286. package/src/review/index.js +325 -0
  287. package/src/review/mutation.js +117 -0
  288. package/src/review/ui-baseline-drift.js +138 -0
  289. package/src/server/index.js +178 -0
  290. package/src/server/routes.js +110 -0
  291. package/src/server/sse.js +118 -0
  292. package/src/setup/agent-setup.js +362 -0
  293. package/src/setup/identity-seed.js +104 -0
  294. package/src/setup/knowledge-seed.js +123 -0
  295. package/src/setup/managed-block.js +193 -0
  296. package/src/setup/managed-gitignore.js +166 -0
  297. package/src/setup/managed-markers.js +49 -0
  298. package/src/ui-baseline/defaults.js +119 -0
  299. package/src/ui-baseline/index.js +25 -0
  300. package/src/ui-baseline/manifest-writer.js +282 -0
  301. package/src/ui-baseline/session.js +178 -0
  302. package/src/ui-detection/classifier.js +192 -0
  303. package/src/verify/chain/index.js +190 -0
  304. package/src/verify/cli/cleanup.js +61 -0
  305. package/src/verify/cli/help.js +56 -0
  306. package/src/verify/cli/mcp.js +98 -0
  307. package/src/verify/cli/provision.js +71 -0
  308. package/src/verify/cli/report.js +71 -0
  309. package/src/verify/cli/run.js +155 -0
  310. package/src/verify/engine/brief.js +87 -0
  311. package/src/verify/engine/index.js +177 -0
  312. package/src/verify/engine/launcher.js +307 -0
  313. package/src/verify/mcp/tools.js +107 -0
  314. package/src/verify/profile/index.js +146 -0
  315. package/src/verify/provision/index.js +256 -0
  316. package/src/verify/report/index.js +139 -0
  317. package/src/verify/report/renderer.js +118 -0
  318. package/src/verify/verdict/index.js +246 -0
  319. package/src/view/doc-renderers/adr.js +44 -0
  320. package/src/view/doc-renderers/build-sequence.js +40 -0
  321. package/src/view/doc-renderers/fbs.js +128 -0
  322. package/src/view/doc-renderers/helpers.js +159 -0
  323. package/src/view/doc-renderers/index.js +12 -0
  324. package/src/view/doc-renderers/prd.js +45 -0
  325. package/src/view/doc-renderers/req.js +43 -0
  326. package/src/view/doc-renderers/tac.js +38 -0
  327. package/src/view/doc-renderers/tad.js +74 -0
  328. package/src/view/doc-renderers/test-suite.js +45 -0
  329. package/src/view/doc-renderers/user-story.js +63 -0
  330. package/src/view/html-page.js +462 -0
  331. package/src/view/index.js +63 -0
  332. package/src/view/live-client.js +338 -0
  333. package/src/view/mermaid-diagram.js +178 -0
  334. package/src/view/style.css +735 -0
  335. package/src/view/tree-model.js +152 -0
  336. package/src/view/vendored/mermaid.min.js +2607 -0
  337. package/src/view-supervisor/index.js +26 -0
  338. package/src/view-supervisor/logs.js +32 -0
  339. package/src/view-supervisor/manifest-writer.js +178 -0
  340. package/src/view-supervisor/persist-until.js +85 -0
  341. package/src/view-supervisor/supervisor.js +276 -0
  342. package/src/watch/index.js +152 -0
package/src/cli/mcp.js ADDED
@@ -0,0 +1,160 @@
1
+ // `rcf mcp` subcommand handler (Phase 7 §D1 / §D13). Resolves the
2
+ // project root ONCE at startup, wires process stdin / stdout / stderr
3
+ // into the MCP server core, and serves until stdin EOF or a signal.
4
+ //
5
+ // Stream discipline (D2): stdout carries MCP messages and NOTHING
6
+ // else. All logging goes to stderr; default stderr output is one
7
+ // startup line plus unexpected-failure blocks, with per-request lines
8
+ // behind --verbose. No root found: one stderr line and exit 2 BEFORE
9
+ // any stdout traffic - a misconfigured client sees a dead subprocess
10
+ // and a clear stderr message.
11
+
12
+ import { readFile } from 'node:fs/promises';
13
+ import { dirname, resolve as resolvePath } from 'node:path';
14
+ import { parseArgs } from 'node:util';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ import { findProjectRoot } from '../view/index.js';
18
+ import { createMcpServer, serveStreams } from '#core/mcp-shell';
19
+ import { createToolRegistry } from '../mcp/tools.js';
20
+ import { createResourceRegistry } from '../mcp/resources.js';
21
+ import { createPromptRegistry } from '../mcp/prompts.js';
22
+
23
+ const here = dirname(fileURLToPath(import.meta.url));
24
+
25
+ const OPTION_SPEC = {
26
+ 'project-root': { type: 'string' },
27
+ verbose: { type: 'boolean' },
28
+ help: { type: 'boolean' },
29
+ };
30
+
31
+ export const HELP = `Usage: rcf mcp [options]
32
+
33
+ Serve the project over the Model Context Protocol (local stdio). An
34
+ MCP-capable agent harness launches 'rcf mcp' as a subprocess in a
35
+ project directory; stdout carries protocol messages only, logging
36
+ goes to stderr.
37
+
38
+ Serves eleven tools (rcf_validate, rcf_coverage, rcf_trace,
39
+ rcf_impact, rcf_read, rcf_create, rcf_update, rcf_delete, rcf_link,
40
+ rcf_unlink, rcf_build), resources (rcf://tree, rcf://doc/<id>,
41
+ rcf://docs/<slug>) and the agent-guidance prompts.
42
+
43
+ The project root is resolved once at startup and fixed for the
44
+ process lifetime; multi-project harnesses run multiple server
45
+ entries. The server exits when the client closes stdin.
46
+
47
+ Options:
48
+ --project-root <path> Resolve the project root from <path>
49
+ instead of the working directory
50
+ --verbose Per-request logging on stderr
51
+ --help Print this help
52
+ `;
53
+
54
+ const INSTRUCTIONS = 'Filesystem-backed RCF (Requirements Confidence Framework) project. '
55
+ + 'Start with rcf_validate to check tree health, and the rcf://tree resource to orient. '
56
+ + 'Coverage is structural, not semantic: rcf_coverage reports whether chains reach test '
57
+ + 'cases, not whether acceptance criteria adequately capture intent. Write tools edit '
58
+ + 'the git-tracked rcf/ tree directly.';
59
+
60
+ /**
61
+ * @param {string[]} argv - argv slice after `mcp`
62
+ * @param {object} [deps]
63
+ * @returns {Promise<number>}
64
+ */
65
+ export async function main(argv, deps = {}) {
66
+ const stdout = deps.stdout ?? process.stdout;
67
+ const stderr = deps.stderr ?? process.stderr;
68
+ const stdin = deps.stdin ?? process.stdin;
69
+ const cwd = deps.cwd ?? process.cwd();
70
+ const onSignal = deps.onSignal ?? ((sig, fn) => process.on(sig, fn));
71
+
72
+ let parsed;
73
+ try {
74
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
75
+ } catch (err) {
76
+ stderr.write(`[error] usage ${err.message}\n`);
77
+ stderr.write(HELP);
78
+ return 2;
79
+ }
80
+ const flags = parsed.values;
81
+ if (flags.help) {
82
+ stdout.write(HELP);
83
+ return 0;
84
+ }
85
+ if (parsed.positionals.length > 0) {
86
+ stderr.write('[error] usage mcp: takes no positional arguments\n');
87
+ stderr.write(HELP);
88
+ return 2;
89
+ }
90
+
91
+ // D13: resolve the root once, before any protocol traffic.
92
+ const start = flags['project-root'] ? resolvePath(cwd, flags['project-root']) : cwd;
93
+ const projectRoot = await findProjectRoot(start);
94
+ if (!projectRoot) {
95
+ // B3 (E2E matrix 2026-07-06-003): an MCP client surfaces this failure
96
+ // as "zero tools" with no visible error, so the stderr line must tell
97
+ // the operator exactly what to do next. Theme 1 funnel: incomplete
98
+ // setup always routes to `npx rcf init` + a session restart.
99
+ stderr.write(`[error] usage no project root found (no rcf/manifest.json in ${start} or any ancestor). `
100
+ + 'The MCP server needs an existing rcf/ tree - run `npx rcf init` in the project first '
101
+ + '(it wires the tree, .mcp.json and the agent instructions), then restart your agent '
102
+ + 'session; or pass --project-root <path>. See docs/install.md, section 7.\n');
103
+ return 2;
104
+ }
105
+
106
+ const verbose = Boolean(flags.verbose);
107
+ const log = {
108
+ info: verbose ? (line) => stderr.write(`[rcf mcp] ${line}\n`) : () => {},
109
+ error: (line) => stderr.write(`${line}\n`),
110
+ };
111
+
112
+ const tools = createToolRegistry({ projectRoot, log });
113
+ const resources = createResourceRegistry({ projectRoot });
114
+ const prompts = createPromptRegistry();
115
+
116
+ const server = createMcpServer({
117
+ serverInfo: { name: 'rcf-build-lite', version: await readPackageVersion() },
118
+ instructions: INSTRUCTIONS,
119
+ capabilities: { tools: {}, resources: {}, prompts: {} },
120
+ handlers: {
121
+ 'tools/list': async () => ({ tools: tools.definitions }),
122
+ 'tools/call': async (params) => {
123
+ log.info(`tools/call ${params.name}`);
124
+ return await tools.call(params.name, params.arguments);
125
+ },
126
+ 'resources/list': async () => await resources.list(),
127
+ 'resources/read': async (params) => {
128
+ log.info(`resources/read ${params.uri}`);
129
+ return await resources.read(params.uri);
130
+ },
131
+ 'prompts/list': async () => await prompts.list(),
132
+ 'prompts/get': async (params) => {
133
+ log.info(`prompts/get ${params?.name}`);
134
+ return await prompts.get(params);
135
+ },
136
+ },
137
+ log,
138
+ });
139
+
140
+ stderr.write(`rcf mcp: serving ${projectRoot}\n`);
141
+
142
+ const { done, stop } = serveStreams(server, { input: stdin, output: stdout, log });
143
+
144
+ // D18: SIGINT / SIGTERM exit cleanly - nothing to drain, no ports,
145
+ // no watchers. EOF on stdin is the spec's termination path.
146
+ onSignal('SIGINT', () => { log.info('received SIGINT, shutting down'); stop(); });
147
+ onSignal('SIGTERM', () => { log.info('received SIGTERM, shutting down'); stop(); });
148
+
149
+ return await done;
150
+ }
151
+
152
+ async function readPackageVersion() {
153
+ try {
154
+ const pkgPath = resolvePath(here, '..', '..', 'package.json');
155
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
156
+ return pkg.version ?? '0.0.0';
157
+ } catch {
158
+ return '0.0.0';
159
+ }
160
+ }
@@ -0,0 +1,220 @@
1
+ // `rcf preflight` subcommand handler
2
+ // (verification-integrity-cluster-spec §4, ADDENDUM §A).
3
+ //
4
+ // Runs the pre-flight config elicitation session and writes a
5
+ // `preFlightConfig` record onto the manifest. Interactive mode by
6
+ // default when stdin + stdout are TTYs; non-interactive mode reads a
7
+ // pre-filled JSON file (--input) for CI / automation. The session
8
+ // composes:
9
+ // - one per-service ruling per candidate the scanner surfaced (§4.5),
10
+ // plus operator additions;
11
+ // - one design-shape answer per applicable REQ + catalogue question
12
+ // (ADDENDUM §A.1);
13
+ // - one baselineAcOptOuts entry per design-shape answer that triggers
14
+ // an opt-out (ADDENDUM §A.2).
15
+ //
16
+ // Credentials never enter the chain: the session records only env-var
17
+ // NAMES + presence booleans, via the gitignored
18
+ // `.rcf/preflight-secrets.local.json` side-file whose gitignore path
19
+ // rides the 0.6.0 aggregator seam (§4.6).
20
+
21
+ import { readFile } from 'node:fs/promises';
22
+ import { parseArgs } from 'node:util';
23
+ import { createInterface } from 'node:readline/promises';
24
+ import process from 'node:process';
25
+
26
+ import { walkTree } from '#core/store';
27
+ import { writeUnexpectedFailure, rcfError } from '#core/errors';
28
+
29
+ import { findProjectRoot } from '../view/index.js';
30
+ import {
31
+ composeDesignShapeOptOuts,
32
+ composePreflightRecord,
33
+ normaliseNonInteractiveInput,
34
+ runInteractiveSession,
35
+ scanForServiceCandidates,
36
+ writePreflightRecord,
37
+ } from '../preflight/index.js';
38
+
39
+ const OPTION_SPEC = {
40
+ prd: { type: 'string' },
41
+ 'from-tad': { type: 'string' },
42
+ input: { type: 'string' },
43
+ 'non-interactive': { type: 'boolean' },
44
+ 'dry-run': { type: 'boolean' },
45
+ quiet: { type: 'boolean' },
46
+ json: { type: 'boolean' },
47
+ help: { type: 'boolean' },
48
+ };
49
+
50
+ export const HELP = `Usage: rcf preflight [options]
51
+
52
+ Elicit the pre-flight configuration record: for every third-party
53
+ service the PRD or TAD names, force one of the five attestation modes,
54
+ and record the operator's ruling on the manifest. Also poses any
55
+ applicable design-shape questions (v1 catalogue: auth.htmlLoginPage).
56
+
57
+ Interactive by default when running on a TTY. Non-interactive mode
58
+ reads a JSON file matching the session-summary shape.
59
+
60
+ Options:
61
+ --prd <prd-id> PRD to scan (default: the project's PRD)
62
+ --from-tad <tad-id> Also scan this TAD (default: the project's TAD)
63
+ --input <path> Non-interactive: read pre-filled session
64
+ --non-interactive Force non-interactive mode (default when
65
+ not on a TTY or when piped)
66
+ --dry-run Print the composed record; do not write
67
+ --json Emit the composed record as JSON to stdout
68
+ --quiet Suppress non-error stdout
69
+ --help Print this help
70
+
71
+ Credentials NEVER enter the chain. The session prompts for env var
72
+ NAMES only; values are read from the shell at test / finalise time.
73
+ The name-metadata is written to .rcf/preflight-secrets.local.json,
74
+ which is gitignored via the managed block written by rcf init.
75
+
76
+ Exit codes:
77
+ 0 success
78
+ 1 IO / unexpected runtime failure
79
+ 2 usage error (bad flags, unresolvable PRD id)
80
+ 3 schema validation on the composed record
81
+ 4 operator cancelled the session at the confirm step
82
+ `;
83
+
84
+ /**
85
+ * @param {string[]} argv
86
+ * @param {object} [deps]
87
+ * @returns {Promise<number>}
88
+ */
89
+ export async function main(argv, deps = {}) {
90
+ const stdout = deps.stdout ?? process.stdout;
91
+ const stderr = deps.stderr ?? process.stderr;
92
+ const stdin = deps.stdin ?? process.stdin;
93
+ const cwd = deps.cwd ?? process.cwd();
94
+
95
+ let parsed;
96
+ try {
97
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
98
+ } catch (err) {
99
+ stderr.write(`[error] usage ${err.message}\n`);
100
+ stderr.write(HELP);
101
+ return 2;
102
+ }
103
+ const flags = parsed.values;
104
+ if (flags.help) { stdout.write(HELP); return 0; }
105
+ if (parsed.positionals.length > 0) {
106
+ stderr.write('[error] usage preflight: no positional arguments (use --prd / --from-tad)\n');
107
+ return 2;
108
+ }
109
+
110
+ const projectRoot = await findProjectRoot(cwd);
111
+ if (!projectRoot) {
112
+ stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `npx rcf init` first.\n');
113
+ return 2;
114
+ }
115
+
116
+ const walkResult = await walkTree({ projectRoot });
117
+ if (walkResult.errors.length > 0) {
118
+ stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding — run 'rcf validate' for details\n`);
119
+ }
120
+ const { tree } = walkResult;
121
+
122
+ const prdId = flags.prd ?? tree.prd?.prdId;
123
+ if (!prdId) {
124
+ stderr.write('[error] usage preflight: no --prd given and no PRD found on the tree\n');
125
+ return 2;
126
+ }
127
+ const tadId = flags['from-tad'] ?? tree.tad?.tadId ?? null;
128
+
129
+ const scan = scanForServiceCandidates({ tree, prdId, tadId });
130
+ if (scan.skippedDocIds.length > 0) {
131
+ stderr.write(`[warn] preflight: skipped ids not found on the tree: ${scan.skippedDocIds.join(', ')}\n`);
132
+ }
133
+
134
+ const isTty = Boolean(stdout.isTTY && stdin.isTTY);
135
+ const forceNonInteractive = Boolean(flags['non-interactive']) || Boolean(flags.input);
136
+ const interactive = !forceNonInteractive && isTty;
137
+
138
+ let sessionResult;
139
+ try {
140
+ if (interactive) {
141
+ const rl = createInterface({ input: stdin, output: stdout });
142
+ const prompt = (q) => rl.question(q);
143
+ const write = (line) => stdout.write(`${line}\n`);
144
+ try {
145
+ sessionResult = await runInteractiveSession({
146
+ projectRoot,
147
+ scan,
148
+ reqsForDesignShapes: tree.requirements ?? [],
149
+ prompt,
150
+ write,
151
+ env: deps.env ?? process.env,
152
+ });
153
+ } finally {
154
+ rl.close();
155
+ }
156
+ } else if (flags.input) {
157
+ const raw = await readFile(flags.input, 'utf8');
158
+ const parsedInput = JSON.parse(raw);
159
+ sessionResult = normaliseNonInteractiveInput(parsedInput);
160
+ } else {
161
+ stderr.write('[error] usage preflight: not on a TTY and no --input file given\n');
162
+ return 2;
163
+ }
164
+ } catch (err) {
165
+ if (err.message === 'preflight: session cancelled by operator') {
166
+ stderr.write('[error] preflight: session cancelled; no record written.\n');
167
+ return 4;
168
+ }
169
+ writeUnexpectedFailure(
170
+ rcfError({ kind: 'ioFailure', message: `preflight: ${err.message}`, stack: err.stack }),
171
+ stderr,
172
+ );
173
+ return 1;
174
+ }
175
+
176
+ const now = deps.now ? new Date(deps.now()) : new Date();
177
+ const record = composePreflightRecord({
178
+ manifest: tree.manifest,
179
+ prdId,
180
+ services: sessionResult.services,
181
+ designShapeAnswers: sessionResult.designShapeAnswers,
182
+ now,
183
+ });
184
+ const optOuts = composeDesignShapeOptOuts({
185
+ manifest: tree.manifest,
186
+ preflightRecord: record,
187
+ designShapeAnswers: sessionResult.designShapeAnswers,
188
+ now,
189
+ });
190
+
191
+ if (flags['dry-run']) {
192
+ if (flags.json) stdout.write(`${JSON.stringify({ record, optOuts }, null, 2)}\n`);
193
+ if (!flags.quiet) {
194
+ stdout.write(`[dry-run] preflight would write record ${record.id} (${record.servicesInScope.length} services, ${record.designShapeAnswers?.length ?? 0} design-shape answers, ${optOuts.length} opt-outs)\n`);
195
+ }
196
+ return 0;
197
+ }
198
+
199
+ const writeResult = await writePreflightRecord({
200
+ projectRoot, tree, record, optOuts, options: {},
201
+ });
202
+ if (writeResult && 'kind' in writeResult && 'message' in writeResult) {
203
+ if (writeResult.kind === 'ioFailure') { writeUnexpectedFailure(writeResult, stderr); return 1; }
204
+ stderr.write(`[error] ${writeResult.kind} ${writeResult.message}\n`);
205
+ if (writeResult.kind === 'usage') return 2;
206
+ if (writeResult.kind === 'validation' || writeResult.kind === 'brokenReference') return 3;
207
+ return 1;
208
+ }
209
+
210
+ if (flags.json) stdout.write(`${JSON.stringify({ record, optOuts }, null, 2)}\n`);
211
+ if (!flags.quiet) {
212
+ stdout.write(`preflight: wrote ${record.id} to rcf/manifest.json (${record.servicesInScope.length} services, ${record.designShapeAnswers?.length ?? 0} design-shape answers)`);
213
+ if (optOuts.length > 0) stdout.write(` and ${optOuts.length} baseline opt-out(s)`);
214
+ stdout.write('.\n');
215
+ if (writeResult.skippedUiBaseline && (record.designShapeAnswers ?? []).length > 0) {
216
+ stdout.write(' Note: uiBaseline defaults writes are fenced pending Track B (UI design gate). The opt-out ledger carries the linkedPreFlightConfigRef for Track B to consume when it lands.\n');
217
+ }
218
+ }
219
+ return 0;
220
+ }
@@ -0,0 +1,162 @@
1
+ // `rcf read <id>` subcommand handler. Prints the document body (or a
2
+ // dot-path field). Inline AC / TC ids resolve to the addressed inline
3
+ // entry inside the parent US / TS body. Phase 4 §D7.
4
+
5
+ import { parseArgs } from 'node:util';
6
+
7
+ import { walkTree } from '#core/store';
8
+ import { findProjectRoot } from '../view/index.js';
9
+
10
+ const OPTION_SPEC = {
11
+ field: { type: 'string' },
12
+ raw: { type: 'boolean' },
13
+ help: { type: 'boolean' },
14
+ };
15
+
16
+ export const HELP = `Usage: rcf read <id> [options]
17
+
18
+ Prints the document body to stdout and a schema-validity report to
19
+ stderr: a valid document is reported as valid; an invalid-but-present
20
+ document is still returned, together with its validation errors, so the
21
+ caller sees both the content and the problem. The exit code stays 0
22
+ either way - read is a retrieval verb, not a gate (that is rcf validate).
23
+
24
+ Options:
25
+ --field <dotPath> Print only the addressed field
26
+ --raw Emit unformatted (single-line) JSON
27
+ --help Print this help
28
+ `;
29
+
30
+ /**
31
+ * @param {string[]} argv - argv slice after `read`
32
+ * @param {object} [deps]
33
+ * @returns {Promise<number>}
34
+ */
35
+ export async function main(argv, deps = {}) {
36
+ const stdout = deps.stdout ?? process.stdout;
37
+ const stderr = deps.stderr ?? process.stderr;
38
+ const cwd = deps.cwd ?? process.cwd();
39
+
40
+ let parsed;
41
+ try {
42
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
43
+ } catch (err) {
44
+ stderr.write(`[error] usage ${err.message}\n`);
45
+ stderr.write(HELP);
46
+ return 2;
47
+ }
48
+ const flags = parsed.values;
49
+ const positionals = parsed.positionals;
50
+ if (flags.help) { stdout.write(HELP); return 0; }
51
+ if (positionals.length !== 1) {
52
+ stderr.write('[error] usage read: expected exactly one <id>\n');
53
+ stderr.write(HELP);
54
+ return 2;
55
+ }
56
+ const id = positionals[0];
57
+ const projectRoot = await findProjectRoot(cwd);
58
+ if (!projectRoot) {
59
+ 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');
60
+ return 2;
61
+ }
62
+ const { tree, errors } = await walkTree({ projectRoot });
63
+ const target = resolveTarget(tree, id);
64
+ if (!target) {
65
+ stderr.write(`[error] usage read: id ${id} not found\n`);
66
+ return 2;
67
+ }
68
+ let value = target.doc;
69
+ if (flags.field) {
70
+ value = extractField(target.doc, flags.field);
71
+ if (value === undefined) {
72
+ stderr.write(`[error] usage read: field ${flags.field} not present on ${id}\n`);
73
+ return 2;
74
+ }
75
+ }
76
+ const out = flags.raw ? JSON.stringify(value) : JSON.stringify(value, null, 2);
77
+ stdout.write(`${out}\n`);
78
+ // Validity report (AC-301-1 / AC-301-3): the body went to stdout; the
79
+ // schema verdict goes to stderr so piped output stays pure JSON. An
80
+ // invalid-but-present document is returned WITH its validation errors -
81
+ // content and problem together. Read never gates on validity (exit 0);
82
+ // rcf validate is the gate.
83
+ if (target.invalid) {
84
+ stderr.write(`[warn] read: ${target.containerId} fails schema validation; body returned together with the errors:\n`);
85
+ const validationErrors = (errors ?? []).filter(
86
+ (e) => e.kind === 'validation' && e.documentId === target.containerId,
87
+ );
88
+ for (const e of validationErrors) {
89
+ stderr.write(`[warn] validation ${target.containerId}: ${e.message}\n`);
90
+ }
91
+ } else {
92
+ stderr.write(`[ok] read: ${id} is valid against its schema\n`);
93
+ }
94
+ return 0;
95
+ }
96
+
97
+ /**
98
+ * Resolve a target id against the tree. Supports:
99
+ * - root docs (PRD, TAD, BS, MANIFEST)
100
+ * - child docs (REQ, US, TAC, ADR, FBS, TS)
101
+ * - inline AC (`AC-XXX-N`) -> returns the AC entry from parent US
102
+ * - inline TC (`TC-XXX-slug`) -> returns the TC entry from parent TS
103
+ */
104
+ function resolveTarget(tree, id) {
105
+ if (id === 'MANIFEST' && tree.manifest) {
106
+ return { doc: tree.manifest, containerId: 'MANIFEST', invalid: false };
107
+ }
108
+ const doc = tree.byId.get(id);
109
+ if (doc) return { doc, containerId: id, invalid: false };
110
+ // B5: schema-invalid docs stay addressable (tree.invalidDocs) - read
111
+ // returns the body together with the validation errors (AC-301-3).
112
+ const invalidEntry = tree.invalidDocs?.get(id);
113
+ if (invalidEntry) return { doc: invalidEntry.doc, containerId: id, invalid: true };
114
+ if (/^AC-\d+(-\d+)?$/.test(id)) {
115
+ const parentId = tree.parentByChild.get(id);
116
+ if (!parentId) return null;
117
+ const us = tree.byId.get(parentId);
118
+ if (!us) return null;
119
+ const entry = (us.acceptanceCriteria ?? []).find((ac) => ac.id === id);
120
+ return entry ? { doc: entry, containerId: parentId, invalid: false } : null;
121
+ }
122
+ if (/^TC-\d{3}-[a-z0-9-]+$/.test(id)) {
123
+ const parentId = tree.parentByChild.get(id);
124
+ if (!parentId) return null;
125
+ const ts = tree.byId.get(parentId);
126
+ if (!ts) return null;
127
+ const entry = (ts.testCases ?? []).find((tc) => tc.id === id);
128
+ return entry ? { doc: entry, containerId: parentId, invalid: false } : null;
129
+ }
130
+ return null;
131
+ }
132
+
133
+ function extractField(root, path) {
134
+ const parts = parseDotPath(path);
135
+ if (!parts) return undefined;
136
+ let cur = root;
137
+ for (const seg of parts) {
138
+ if (cur === undefined || cur === null) return undefined;
139
+ if (seg.kind === 'index') {
140
+ if (!Array.isArray(cur)) return undefined;
141
+ cur = cur[seg.value];
142
+ } else {
143
+ cur = cur[seg.value];
144
+ }
145
+ }
146
+ return cur;
147
+ }
148
+
149
+ function parseDotPath(path) {
150
+ if (typeof path !== 'string' || path.length === 0) return null;
151
+ const parts = [];
152
+ for (const seg of path.split('.')) {
153
+ const m = /^([^\[\]]+)((?:\[\d+\])*)$/.exec(seg);
154
+ if (!m) return null;
155
+ parts.push({ kind: 'prop', value: m[1] });
156
+ if (m[2]) {
157
+ const indices = m[2].match(/\d+/g) ?? [];
158
+ for (const n of indices) parts.push({ kind: 'index', value: Number(n) });
159
+ }
160
+ }
161
+ return parts;
162
+ }