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,121 @@
1
+ // Baseline-AC catalog - single source of truth for the five REQ-shape
2
+ // baseline sets defined in Track C+D §5.2 of the
3
+ // elicitation-and-playbook-hardening-0.7.0 spec.
4
+ //
5
+ // Consumed by `packages/rcf-lite/src/req-baseline/` (Track C+D injection
6
+ // mechanism, spec §5.3): the build package reads a shape's baseline set
7
+ // here, then writes each entry into a US as an AC with
8
+ // `provenance.authoredBy: baseline` and `provenance.baselineKey` set to
9
+ // the catalog entry's `baselineKey`.
10
+ //
11
+ // Data-side loading: each set is stored as a JSON file under `./data/`,
12
+ // with entries in canonical spec order. The canonical AC text is the
13
+ // primary load-bearing record; `given`, `when`, `then` are mechanical
14
+ // decompositions of the canonical sentence for build's convenience, and
15
+ // `notes` captures any trailing commentary the spec attached to the entry
16
+ // (kept verbatim; not part of the AC proper).
17
+ //
18
+ // The catalog is FROZEN at module load - consumers cannot mutate the
19
+ // shared source.
20
+
21
+ import webUiSet from './data/web-ui.json' with { type: 'json' };
22
+ import httpApiSet from './data/http-api.json' with { type: 'json' };
23
+ import authSet from './data/auth.json' with { type: 'json' };
24
+ import persistenceSet from './data/persistence.json' with { type: 'json' };
25
+ import notificationsSet from './data/notifications.json' with { type: 'json' };
26
+
27
+ /**
28
+ * @typedef {'webUi'|'httpApi'|'auth'|'persistence'|'notifications'} BaselineShape
29
+ */
30
+
31
+ /**
32
+ * @typedef {object} BaselineCatalogEntry
33
+ * @property {string} baselineKey e.g. `webUi.sharedNav`; unique across shapes
34
+ * @property {string} canonicalText the verbatim spec §5.2 sentence, load-bearing
35
+ * @property {string} given mechanical extract from canonicalText
36
+ * @property {string} when mechanical extract from canonicalText
37
+ * @property {string} then mechanical extract from canonicalText
38
+ * @property {string|null} notes verbatim trailing commentary from the spec, or null
39
+ * @property {boolean} testable always true; every catalog AC is testable
40
+ */
41
+
42
+ /**
43
+ * @typedef {object} BaselineCatalogSet
44
+ * @property {BaselineShape} sourceReqShape
45
+ * @property {string} specSource
46
+ * @property {BaselineCatalogEntry[]} entries
47
+ */
48
+
49
+ function freezeSet(set) {
50
+ return Object.freeze({
51
+ sourceReqShape: set.sourceReqShape,
52
+ specSource: set.specSource,
53
+ entries: Object.freeze(set.entries.map((entry) => Object.freeze({ ...entry }))),
54
+ });
55
+ }
56
+
57
+ /**
58
+ * The full catalog, one set per REQ shape.
59
+ *
60
+ * @type {Readonly<Record<BaselineShape, BaselineCatalogSet>>}
61
+ */
62
+ export const BASELINE_CATALOG_V1 = Object.freeze({
63
+ webUi: freezeSet(webUiSet),
64
+ httpApi: freezeSet(httpApiSet),
65
+ auth: freezeSet(authSet),
66
+ persistence: freezeSet(persistenceSet),
67
+ notifications: freezeSet(notificationsSet),
68
+ });
69
+
70
+ /**
71
+ * Canonical shape ordering (matches spec §4.2). Consumers that iterate the
72
+ * catalog use this so display order stays stable.
73
+ */
74
+ export const BASELINE_SHAPE_KEYS = Object.freeze(
75
+ ['webUi', 'httpApi', 'auth', 'persistence', 'notifications'],
76
+ );
77
+
78
+ /**
79
+ * Return the baseline set for a shape, or null if the shape carries no
80
+ * baseline entries. `none` is a legitimate REQ shape with no injections
81
+ * (spec §4.2); calling `getBaselineSet('none')` returns null.
82
+ *
83
+ * @param {string} shape
84
+ * @returns {BaselineCatalogSet|null}
85
+ */
86
+ export function getBaselineSet(shape) {
87
+ return BASELINE_CATALOG_V1[shape] ?? null;
88
+ }
89
+
90
+ /**
91
+ * Look up a single catalog entry by its `baselineKey`. Returns null if the
92
+ * key is unknown. Rarely used directly; consumers usually iterate a whole
93
+ * set. Kept exported so tests and audit tooling can spot-check a specific
94
+ * key without walking every set.
95
+ *
96
+ * @param {string} baselineKey
97
+ * @returns {BaselineCatalogEntry|null}
98
+ */
99
+ export function getBaselineEntry(baselineKey) {
100
+ for (const key of BASELINE_SHAPE_KEYS) {
101
+ const set = BASELINE_CATALOG_V1[key];
102
+ for (const entry of set.entries) {
103
+ if (entry.baselineKey === baselineKey) return entry;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Iterate every catalog entry across every shape, in canonical order.
111
+ * Useful for audit passes (e.g. lint every canonical AC text for banned
112
+ * patterns) and for tests.
113
+ *
114
+ * @returns {Generator<{ shape: BaselineShape, entry: BaselineCatalogEntry }, void, undefined>}
115
+ */
116
+ export function* iterateBaselineEntries() {
117
+ for (const shape of BASELINE_SHAPE_KEYS) {
118
+ const set = BASELINE_CATALOG_V1[shape];
119
+ for (const entry of set.entries) yield { shape, entry };
120
+ }
121
+ }
@@ -0,0 +1,167 @@
1
+ // Structured error type owned by TAC-007 and consumed by the store, the
2
+ // walker, the renderer and (later) every CLI / MCP surface.
3
+ //
4
+ // Errors are plain data, not thrown exceptions. Every adapter returns them
5
+ // so the rendering of "what is wrong" is uniform across surfaces. At Phase
6
+ // 3 the renderer surfaces them on stderr and, in the default mode, also
7
+ // renders them inline in the page so an owner can see what is broken.
8
+
9
+ /**
10
+ * @typedef {('validation'|'missingFile'|'brokenReference'|'parseFailure'|'ioFailure'|'usage'|'staleCode'|'missingCodeNodes'|'duplicateId')} ErrorKind
11
+ */
12
+
13
+ /**
14
+ * @typedef {object} RcfError
15
+ * @property {ErrorKind} kind
16
+ * @property {string} message
17
+ * @property {string} [documentId]
18
+ * @property {string} [filePath]
19
+ * @property {string} [field]
20
+ * @property {string} [rule]
21
+ * @property {string} [stack] - underlying Error.stack for exit-1 spec §D15
22
+ */
23
+
24
+ const VALID_KINDS = new Set([
25
+ 'validation',
26
+ 'missingFile',
27
+ 'brokenReference',
28
+ 'parseFailure',
29
+ 'ioFailure',
30
+ 'usage',
31
+ // Phase 10 (X2 CodeNode bridge): a Code Node's declared path/symbol no
32
+ // longer resolves against the working tree (file renamed/moved, or symbol
33
+ // renamed/removed). This is the X2 advantage over sidecar approaches:
34
+ // the breakage is mechanically detectable at `rcf validate` time.
35
+ 'staleCode',
36
+ // Phase 10 (X2 CodeNode bridge, D17, operator ruling): `rcf build --mark
37
+ // complete` refuses when any AC of the completed build spec carries no
38
+ // Code Node. Deterministic edge counting only (Phase 6 D13 holds).
39
+ 'missingCodeNodes',
40
+ // w-2026-07-28-017: two locations claim one id (after leading-zero
41
+ // normalisation). Distinct from `validation` (each document is
42
+ // individually schema-clean) and from `brokenReference` (nothing
43
+ // dangles; the graph is over-connected, not under-connected).
44
+ 'duplicateId',
45
+ ]);
46
+
47
+ /**
48
+ * Construct a structured error.
49
+ *
50
+ * @param {object} opts
51
+ * @param {ErrorKind} opts.kind
52
+ * @param {string} opts.message
53
+ * @param {string} [opts.documentId]
54
+ * @param {string} [opts.filePath]
55
+ * @param {string} [opts.field]
56
+ * @param {string} [opts.rule]
57
+ * @param {string} [opts.stack] - underlying Error.stack, retained on
58
+ * `ioFailure` so CLI handlers can emit the spec §D15 `[rcf]
59
+ * unexpected failure` block including the stack.
60
+ * @returns {RcfError}
61
+ */
62
+ export function rcfError({ kind, message, documentId, filePath, field, rule, stack }) {
63
+ if (!VALID_KINDS.has(kind)) {
64
+ throw new TypeError(`Unknown error kind: ${kind}`);
65
+ }
66
+ if (typeof message !== 'string' || message.length === 0) {
67
+ throw new TypeError('rcfError requires a non-empty message');
68
+ }
69
+ const out = { kind, message };
70
+ if (documentId !== undefined) out.documentId = documentId;
71
+ if (filePath !== undefined) out.filePath = filePath;
72
+ if (field !== undefined) out.field = field;
73
+ if (rule !== undefined) out.rule = rule;
74
+ if (typeof stack === 'string' && stack.length > 0) out.stack = stack;
75
+ return out;
76
+ }
77
+
78
+ /**
79
+ * Emit the spec §D15 `[rcf] unexpected failure` block to a stream.
80
+ * Used by CLI handlers when they receive a structured `ioFailure` —
81
+ * exit 1 is the "unexpected / IO" escape hatch and must always print
82
+ * `[rcf] unexpected failure: <message>\n<stack>` on stderr, per spec.
83
+ *
84
+ * @param {RcfError} err
85
+ * @param {NodeJS.WritableStream} stderr
86
+ */
87
+ export function writeUnexpectedFailure(err, stderr) {
88
+ const message = err.message ?? 'unexpected failure';
89
+ const stack = err.stack ?? '';
90
+ stderr.write(`[rcf] unexpected failure: ${message}\n${stack}\n`);
91
+ }
92
+
93
+ /**
94
+ * Type guard.
95
+ *
96
+ * @param {unknown} value
97
+ * @returns {boolean}
98
+ */
99
+ export function isRcfError(value) {
100
+ if (!value || typeof value !== 'object') return false;
101
+ const v = /** @type {{ kind?: unknown, message?: unknown }} */ (value);
102
+ return (
103
+ typeof v.kind === 'string' &&
104
+ VALID_KINDS.has(/** @type {ErrorKind} */ (v.kind)) &&
105
+ typeof v.message === 'string'
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Render one error as a single line for stderr. Verbose mode appends the
111
+ * field and rule when present.
112
+ *
113
+ * @param {RcfError} err
114
+ * @param {{ verbose?: boolean }} [opts]
115
+ * @returns {string}
116
+ */
117
+ export function formatError(err, opts = {}) {
118
+ const verbose = Boolean(opts.verbose);
119
+ const parts = [`[error] ${err.kind}`];
120
+ if (err.documentId) parts.push(`${err.documentId}:`);
121
+ else if (err.filePath) parts.push(`${err.filePath}:`);
122
+ else parts.push('');
123
+ parts.push(err.message);
124
+ let line = parts.filter(Boolean).join(' ');
125
+ if (verbose) {
126
+ const extras = [];
127
+ if (err.field) extras.push(`field=${err.field}`);
128
+ if (err.rule) extras.push(`rule=${err.rule}`);
129
+ if (err.filePath && err.documentId) extras.push(`path=${err.filePath}`);
130
+ if (extras.length > 0) line += ` (${extras.join(', ')})`;
131
+ }
132
+ return line;
133
+ }
134
+
135
+ /**
136
+ * Render a list of errors plus a summary line. Callers that refuse to
137
+ * produce output on errors (the view's --strict startup gate) pass
138
+ * `strict: true` and get "output not written" appended; every other
139
+ * caller gets a plain count. The summary never advertises flags - the
140
+ * verbs sharing this formatter have different (or no) strictness flags,
141
+ * so a flag hint here is wrong for most of them (validate has no
142
+ * --strict at all).
143
+ *
144
+ * When the caller renders only a subset of a larger error list (e.g.
145
+ * `validate --quiet` shows the first 3 of N), pass the true total via
146
+ * `opts.total` so the summary reports the tree-wide count. Without the
147
+ * override the summary reports the length of `errors` (BUG-004 fix).
148
+ *
149
+ * @param {RcfError[]} errors
150
+ * @param {{ verbose?: boolean, strict?: boolean, total?: number }} [opts]
151
+ * @returns {string}
152
+ */
153
+ export function formatErrors(errors, opts = {}) {
154
+ if (errors.length === 0) return '';
155
+ const verbose = Boolean(opts.verbose);
156
+ const strict = Boolean(opts.strict);
157
+ const lines = errors.map((e) => formatError(e, { verbose }));
158
+ const count = typeof opts.total === 'number' && Number.isFinite(opts.total)
159
+ ? opts.total
160
+ : errors.length;
161
+ const noun = count === 1 ? 'error' : 'errors';
162
+ const summary = strict
163
+ ? `[error] ${count} ${noun} found; output not written.`
164
+ : `[error] ${count} ${noun} found.`;
165
+ lines.push(summary);
166
+ return lines.join('\n');
167
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "canary-prompt-01",
3
+ "specSource": "elicitation-and-playbook-hardening-0.7.0-spec.md §7.3",
4
+ "operatorPrompt": "The app I want you to build is described in detail in this brief: /Users/thefoot/scratch/canary-brief.md\n\nYou are currently homed in a brand new cloned repo - uninitialised. You have my approval to manage the code pushes, branches, PRs and actions/workflows for this repository.\n\nRead the brief and then tell me whats next please?",
5
+ "supportingArtefacts": [
6
+ {
7
+ "path": "canary-brief.md",
8
+ "content": "# Uptime Monitor - product brief\n\n## Problem\n\nSmall teams running a handful of internal services need to know when one of them goes down. Existing paid tools are overkill for a fleet of ten to twenty checks and existing self-hosted options need weekly attention to stay running.\n\n## Users\n\n- The single operator who owns the fleet and its uptime.\n- The occasional teammate who wants to glance at status without a login they need to remember.\n\n## Must-haves (v1)\n\n1. HTTP monitor: given a URL and an expected status, poll on a schedule, record pass and fail with timestamps.\n2. Read-only dashboard: shows every monitor's current status and the last twenty-four hours of results.\n3. Signed-in admin surface: add, edit and remove monitors; runs on the same web app under an authenticated route.\n4. Outbound recovery emails via Resend when a monitor moves from failing back to passing.\n\n## Should-haves (v1)\n\n- Alert email on the first fail after a passing run.\n- CSV export of a monitor's last thirty days.\n\n## Non-functional constraints\n\n- Runs on a single Node process, less than one hundred megabytes resident.\n- Handles two hundred monitors on a one-minute poll without falling behind.\n\n## Out of scope for v1\n\n- Slack, SMS, or webhook notifications.\n- Multi-tenant sign-in; one operator per install.\n- Read-only dashboard authentication; the read view is anonymous by design."
9
+ }
10
+ ],
11
+ "grantedPermissions": [
12
+ "gitPush",
13
+ "githubRepoManagement",
14
+ "actionsWorkflowManagement"
15
+ ],
16
+ "wordCountBudget": 200,
17
+ "notes": "The 0.5.1 first-response scenario, replayed against the shipping build."
18
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "canary-prompt-02",
3
+ "specSource": "elicitation-and-playbook-hardening-0.7.0-spec.md §7.3",
4
+ "operatorPrompt": "mid-build check-in: we're on FBS-014, the recovery email dispatcher, and the integration test is failing with a 401 from Resend. I checked the key in the env, it's the sandbox one. Can you look and tell me what's going on, and what the plan is? I don't want to have to unwind anything if we don't have to.",
5
+ "supportingArtefacts": [
6
+ {
7
+ "path": "test-output.log",
8
+ "content": "PASS src/monitor.test.ts (7 tests)\nFAIL src/email-dispatcher.test.ts\n ✗ dispatches recovery email on transition-to-passing\n AssertionError: expected 200 but got 401\n at Object.<anonymous> (src/email-dispatcher.test.ts:41:5)\n body: { error: 'invalid_api_key' }\n"
9
+ },
10
+ {
11
+ "path": "fbs-014-summary.txt",
12
+ "content": "FBS-014: Recovery email dispatcher\n acIds: AC-1401-1, AC-1401-2\n dependsOnServices: [{ id: 'resend', attestationMode: 'sandboxed' }]\n status: inProgress"
13
+ }
14
+ ],
15
+ "grantedPermissions": [
16
+ "gitPush",
17
+ "githubRepoManagement"
18
+ ],
19
+ "wordCountBudget": 200,
20
+ "notes": "Mid-build bug-report scenario. Exercises the bypass-offer grep in the exact context that produced the cold-run wobble - an in-progress FBS whose test just failed, with the operator hedging against a rollback. The 0.5.1 register regression was the offer to skip RCF for a small fix. This fixture is the shape of prompt that regression showed up in."
21
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "canary-prompt-03",
3
+ "specSource": "elicitation-and-playbook-hardening-0.7.0-spec.md §7.3",
4
+ "operatorPrompt": "I've got a full PRD ready to go for a small internal tool - it's at /Users/thefoot/scratch/internal-search-prd.md. The repo already exists (github: acme/internal-search) and has a clean main branch; you've got push access and can open PRs. Please take a look at the PRD and get us started on the RCF chain from it.",
5
+ "supportingArtefacts": [
6
+ {
7
+ "path": "internal-search-prd.md",
8
+ "content": "# Internal Search - PRD\n\n## Product\n\nA staff-only search box across the org's internal wiki, ticket system, and shared drive. One query, three sources, one result list.\n\n## Users\n\n- Any staff member with a company sign-in.\n- No external users.\n\n## Requirements (initial draft)\n\n### REQ-001 - Federated search over three sources\n\n- Query fans out to the wiki API, the ticket API, and the drive API in parallel.\n- Each source returns up to twenty hits with title, snippet, source id, and permission-filtered visibility.\n- The result page shows hits interleaved by relevance and grouped by source.\n\n### REQ-002 - Company sign-in required\n\n- All routes are behind the corporate SSO provider (OpenID Connect).\n- The sign-in page is corporate-branded; sign-out returns to the corporate portal.\n- Session cookies expire after eight hours of inactivity.\n\n### REQ-003 - Zero-result telemetry\n\n- When a query returns zero hits across all sources, an event is written to the observability pipeline with the query text redacted to counts and length.\n- Weekly digest of zero-result queries goes to the search-owner via email.\n\n## Out of scope for v1\n\n- Public search or unauthenticated access.\n- Result personalisation.\n- Query autocomplete.\n\n## Non-functional constraints\n\n- P95 query latency under one second at ten queries per second.\n- Zero user data leaves the corporate boundary."
9
+ }
10
+ ],
11
+ "grantedPermissions": [
12
+ "gitPush",
13
+ "githubRepoManagement"
14
+ ],
15
+ "wordCountBudget": 200,
16
+ "notes": "Existing-repo, PRD-supplied scenario. Fidelity is briefStrong-to-prd; intake stage engages before the classifier runs. Tests that the register holds when the operator supplies structure at the front of the session rather than eliciting it from scratch."
17
+ }
@@ -0,0 +1,60 @@
1
+ // Verifier-agent isolation-env recipe (spec §7.3). Defined once in core so
2
+ // both the (future) build agent-launch and verify-lite's launcher use the
3
+ // identical environment. In v1 only verify consumes it, but core is its
4
+ // correct home — the recipe is a shared-suite invariant, not a verify detail.
5
+ //
6
+ // Why both flags (proven in the persona programme, run-05 clean sweep):
7
+ // auto-memory-off ALONE was insufficient — a server-side OAuth profile fetch
8
+ // re-populated operator identity into the fresh agent session. The
9
+ // non-essential-traffic flag closed that leak. Verify launches its verifier
10
+ // agent with BOTH set by default; a run's report stamps which were applied
11
+ // (report.run.verifierIsolation) so the isolation is provenance, not a claim.
12
+
13
+ /**
14
+ * Canonical isolation-env recipe. `autoMemory:false` mirrors the harness
15
+ * config toggle; the two env vars are the process-level enforcement.
16
+ * Frozen so no caller can mutate the shared recipe in place.
17
+ *
18
+ * @type {{ autoMemory: boolean, env: Readonly<Record<string, string>> }}
19
+ */
20
+ export const ISOLATION_RECIPE = Object.freeze({
21
+ autoMemory: false,
22
+ env: Object.freeze({
23
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
24
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
25
+ }),
26
+ });
27
+
28
+ /**
29
+ * Build a child-process env with the isolation recipe layered over a base
30
+ * env (defaults to the current process env). The recipe wins on conflict —
31
+ * the whole point is that a leaked parent value cannot re-enable memory or
32
+ * non-essential traffic in the verifier session.
33
+ *
34
+ * @param {Record<string, string|undefined>} [baseEnv] - env to layer onto (default process.env)
35
+ * @returns {Record<string, string|undefined>}
36
+ */
37
+ export function isolationEnv(baseEnv = process.env) {
38
+ return { ...baseEnv, ...ISOLATION_RECIPE.env };
39
+ }
40
+
41
+ /**
42
+ * The provenance stamp recorded in a run report's `verifierIsolation`
43
+ * block (spec §5.3). Reports whether each isolation guarantee was applied.
44
+ *
45
+ * POLARITY (important, matches spec §5.3): each field is the FEATURE STATE in
46
+ * the verifier session — i.e. whether that feature is ENABLED — NOT the state
47
+ * of the corresponding CLAUDE_CODE_DISABLE_* env var. The recipe DISABLES both
48
+ * features (env vars set to '1'), so the correct provenance is that both
49
+ * features are OFF: `autoMemory: false` (auto-memory disabled) and
50
+ * `nonEssentialTraffic: false` (non-essential traffic disabled). Both `false`
51
+ * is the fully-isolated state, exactly as the §5.3 schema example shows.
52
+ *
53
+ * @returns {{ autoMemory: boolean, nonEssentialTraffic: boolean }}
54
+ */
55
+ export function isolationProvenance() {
56
+ return {
57
+ autoMemory: ISOLATION_RECIPE.autoMemory,
58
+ nonEssentialTraffic: false,
59
+ };
60
+ }
@@ -0,0 +1,103 @@
1
+ // Newline-delimited JSON-RPC framing over injectable streams (Phase 7
2
+ // §D2). stdio transport rules per the pinned MCP revision 2025-11-25:
3
+ // messages are individual JSON-RPC objects, delimited by newlines,
4
+ // UTF-8, and MUST NOT contain embedded newlines. The read side buffers
5
+ // chunks, tolerates blank lines and surfaces parse failures to the
6
+ // caller (the server maps them to JSON-RPC -32700). The write side
7
+ // serialises to a single line and refuses embedded newlines.
8
+
9
+ import { StringDecoder } from 'node:string_decoder';
10
+
11
+ /**
12
+ * Attach a line-buffered JSON reader to a Readable stream.
13
+ *
14
+ * @param {NodeJS.ReadableStream} stream
15
+ * @param {object} handlers
16
+ * @param {(message: object) => void} handlers.onMessage - parsed JSON value per line
17
+ * @param {(rawLine: string, err: Error) => void} [handlers.onParseError]
18
+ * @param {() => void} [handlers.onEnd] - stream EOF
19
+ * @returns {{ detach: () => void }}
20
+ */
21
+ export function attachLineReader(stream, { onMessage, onParseError, onEnd }) {
22
+ let buffer = '';
23
+ // StringDecoder holds back partial multibyte sequences across chunk
24
+ // boundaries - a raw per-chunk toString would garble split UTF-8.
25
+ const decoder = new StringDecoder('utf8');
26
+
27
+ const handleChunk = (chunk) => {
28
+ buffer += typeof chunk === 'string' ? chunk : decoder.write(chunk);
29
+ let idx = buffer.indexOf('\n');
30
+ while (idx >= 0) {
31
+ const rawLine = buffer.slice(0, idx);
32
+ buffer = buffer.slice(idx + 1);
33
+ emitLine(rawLine);
34
+ idx = buffer.indexOf('\n');
35
+ }
36
+ };
37
+
38
+ const handleEnd = () => {
39
+ buffer += decoder.end();
40
+ // A trailing unterminated line is still a message (the peer may
41
+ // close without a final newline).
42
+ if (buffer.length > 0) {
43
+ const rawLine = buffer;
44
+ buffer = '';
45
+ emitLine(rawLine);
46
+ }
47
+ if (onEnd) onEnd();
48
+ };
49
+
50
+ const emitLine = (rawLine) => {
51
+ // Tolerate CRLF peers and blank keep-alive lines.
52
+ const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
53
+ if (line.trim().length === 0) return;
54
+ let message;
55
+ try {
56
+ message = JSON.parse(line);
57
+ } catch (err) {
58
+ if (onParseError) onParseError(line, /** @type {Error} */ (err));
59
+ return;
60
+ }
61
+ onMessage(message);
62
+ };
63
+
64
+ stream.on('data', handleChunk);
65
+ stream.on('end', handleEnd);
66
+
67
+ return {
68
+ detach: () => {
69
+ stream.off('data', handleChunk);
70
+ stream.off('end', handleEnd);
71
+ },
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Serialise one JSON-RPC message to a single line. Throws on embedded
77
+ * newlines: JSON.stringify escapes newline characters inside string
78
+ * values, so a raw newline in the serialised form can only mean a
79
+ * programming error upstream - refusing is the D2 discipline.
80
+ *
81
+ * @param {object} message
82
+ * @returns {string} single-line JSON, no trailing newline
83
+ */
84
+ export function serialiseMessage(message) {
85
+ const line = JSON.stringify(message);
86
+ if (typeof line !== 'string') {
87
+ throw new TypeError('serialiseMessage: message did not serialise to JSON');
88
+ }
89
+ if (line.includes('\n') || line.includes('\r')) {
90
+ throw new TypeError('serialiseMessage: refusing embedded newline in MCP message');
91
+ }
92
+ return line;
93
+ }
94
+
95
+ /**
96
+ * Write one message to a Writable stream, newline-terminated.
97
+ *
98
+ * @param {NodeJS.WritableStream} stream
99
+ * @param {object} message
100
+ */
101
+ export function writeMessage(stream, message) {
102
+ stream.write(`${serialiseMessage(message)}\n`);
103
+ }
@@ -0,0 +1,8 @@
1
+ // RCF-agnostic MCP protocol shell — the shared surface both build-lite and
2
+ // verify-lite build their domain MCP adapters on. This barrel re-exports the
3
+ // pure protocol lifecycle (server.js) and the newline-delimited JSON-RPC
4
+ // framing (framing.js). Neither module knows anything about RCF; domain
5
+ // behaviour arrives through the injected `handlers` map (see server.js).
6
+
7
+ export * from './server.js';
8
+ export * from './framing.js';