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
@@ -0,0 +1,131 @@
1
+ // The D10 / D11 mapping: RcfError lists, writer refusals and unknown
2
+ // ids become MCP tool execution errors (`isError: true`) carrying the
3
+ // validate-shaped payload `{ok: false, errors: [{id, kind, rule,
4
+ // filePath, field, message}]}` - the same per-issue grammar `rcf
5
+ // validate --json` ships, so agents parse exactly one error shape
6
+ // across the whole surface (D11). Unexpected failures keep their
7
+ // stacks on stderr, never in model context (D10).
8
+
9
+ import { formatError, rcfError } from '#core/errors';
10
+
11
+ /**
12
+ * Map one RcfError to the validate --json issue shape (nullable
13
+ * id / rule / filePath / field). Mirrors src/cli/validate.js.
14
+ *
15
+ * @param {import('#core/errors').RcfError} e
16
+ * @returns {{id: string|null, kind: string, rule: string|null, filePath: string|null, field: string|null, message: string}}
17
+ */
18
+ export function issueFromRcfError(e) {
19
+ return {
20
+ id: e.documentId ?? null,
21
+ kind: e.kind,
22
+ rule: e.rule ?? null,
23
+ filePath: e.filePath ?? null,
24
+ field: e.field ?? null,
25
+ message: e.message,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Map a list of RcfErrors to the issues array.
31
+ *
32
+ * @param {import('#core/errors').RcfError[]} errors
33
+ * @returns {ReturnType<typeof issueFromRcfError>[]}
34
+ */
35
+ export function issuesFromErrors(errors) {
36
+ return errors.map(issueFromRcfError);
37
+ }
38
+
39
+ /**
40
+ * Build a tool execution error result from structured errors. The text
41
+ * block carries the human-readable line(s) via the existing
42
+ * formatError helper (D11); the structured payload is the validate
43
+ * issue shape.
44
+ *
45
+ * @param {import('#core/errors').RcfError[]} errors
46
+ * @returns {object} tools/call result with isError: true
47
+ */
48
+ export function errorResult(errors) {
49
+ const text = errors.map((e) => formatError(e)).join('\n');
50
+ return {
51
+ content: [{ type: 'text', text }],
52
+ structuredContent: { ok: false, errors: issuesFromErrors(errors) },
53
+ isError: true,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Single-cause usage error (unknown id, bad argument, refused scope) -
59
+ * a one-element errors array per D11.
60
+ *
61
+ * @param {string} message
62
+ * @param {object} [extras] - optional documentId / field / rule
63
+ * @returns {object}
64
+ */
65
+ export function usageErrorResult(message, extras = {}) {
66
+ return errorResult([rcfError({ kind: 'usage', message, ...extras })]);
67
+ }
68
+
69
+ /**
70
+ * D10 row: walker errors block a QUERY. The tree is broken; the agent's
71
+ * next move is fixing it, so it gets the full validate-shaped issue
72
+ * list. Write tools no longer route here (B5): they proceed and gate on
73
+ * the post-write tree state instead - that is how a broken tree gets
74
+ * fixed in-tool.
75
+ *
76
+ * @param {import('#core/errors').RcfError[]} errors
77
+ * @returns {object}
78
+ */
79
+ export function walkerBlockedResult(errors) {
80
+ return errorResult(errors);
81
+ }
82
+
83
+ /**
84
+ * D10 row: unexpected / IO failure. Message only into model context;
85
+ * the full stack goes to stderr via the injected logger - mirrors
86
+ * writeUnexpectedFailure without leaking stacks into the conversation.
87
+ *
88
+ * @param {import('#core/errors').RcfError} err
89
+ * @param {{error: (line: string) => void}} log
90
+ * @returns {object}
91
+ */
92
+ export function unexpectedFailureResult(err, log) {
93
+ log.error(`[rcf mcp] unexpected failure: ${err.message}\n${err.stack ?? ''}`);
94
+ const stripped = rcfError({
95
+ kind: 'ioFailure',
96
+ message: err.message,
97
+ ...(err.documentId !== undefined ? { documentId: err.documentId } : {}),
98
+ ...(err.filePath !== undefined ? { filePath: err.filePath } : {}),
99
+ });
100
+ return errorResult([stripped]);
101
+ }
102
+
103
+ /**
104
+ * Map a structured writer return (RcfError) to the right D10 row:
105
+ * - ioFailure -> unexpected-failure handling (stack to stderr)
106
+ * - usage + dependents / wouldOrphan rule -> refusal (exit-4 twin);
107
+ * message names the dependents and the cascade remedy
108
+ * - everything else -> plain execution error with the D11 payload
109
+ *
110
+ * @param {import('#core/errors').RcfError} err
111
+ * @param {{error: (line: string) => void}} log
112
+ * @returns {object}
113
+ */
114
+ export function writerErrorResult(err, log) {
115
+ if (err.kind === 'ioFailure') {
116
+ return unexpectedFailureResult(err, log);
117
+ }
118
+ if (err.kind === 'usage' && (err.rule === 'dependents' || err.rule === 'wouldOrphan')) {
119
+ // The writer message says "pass --cascade to opt in" - restate the
120
+ // remedy in this surface's vocabulary (D10: names the dependents
121
+ // and the cascade: true remedy).
122
+ const message = `${err.message.replace(/pass --cascade to opt in/, 'set cascade: true to delete dependents too')}`;
123
+ return errorResult([rcfError({
124
+ kind: 'usage',
125
+ message,
126
+ ...(err.documentId !== undefined ? { documentId: err.documentId } : {}),
127
+ rule: err.rule,
128
+ })]);
129
+ }
130
+ return errorResult([err]);
131
+ }
@@ -0,0 +1,49 @@
1
+ // MCP prompts (Phase 7 §D16-A). Serves the Phase 7.5 agent-guidance
2
+ // playbooks as static, argument-free prompts via prompts/list and
3
+ // prompts/get. Same plumbing posture as the D15-A resources: the pack
4
+ // owns content (guidance/manifest.json is the contract file, OQ-P7.5-1);
5
+ // this file reads the mapped markdown and serves bytes, byte-faithful.
6
+ // Semantic-adequacy assessment prompts stay Phase 7.x (spec §7).
7
+
8
+ import { readFile } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+
11
+ import { JsonRpcError, INVALID_PARAMS } from '#core/mcp-shell';
12
+ import { GUIDANCE_DIR, readGuidanceManifest } from './resources.js';
13
+
14
+ /**
15
+ * Create the prompt registry.
16
+ *
17
+ * @param {object} [opts]
18
+ * @param {string} [opts.guidanceDir]
19
+ * @returns {{list: () => Promise<object>, get: (params: object) => Promise<object>}}
20
+ */
21
+ export function createPromptRegistry({ guidanceDir = GUIDANCE_DIR } = {}) {
22
+ async function list() {
23
+ const manifest = await readGuidanceManifest(guidanceDir);
24
+ return {
25
+ prompts: manifest.prompts.map((p) => ({
26
+ name: p.name,
27
+ description: p.description,
28
+ })),
29
+ };
30
+ }
31
+
32
+ async function get(params) {
33
+ const name = params?.name;
34
+ const manifest = await readGuidanceManifest(guidanceDir);
35
+ const entry = manifest.prompts.find((p) => p.name === name);
36
+ if (!entry) {
37
+ throw new JsonRpcError(INVALID_PARAMS, `Unknown prompt: ${name}`);
38
+ }
39
+ const text = await readFile(join(guidanceDir, entry.file), 'utf8');
40
+ return {
41
+ description: entry.description,
42
+ messages: [
43
+ { role: 'user', content: { type: 'text', text } },
44
+ ],
45
+ };
46
+ }
47
+
48
+ return { list, get };
49
+ }
@@ -0,0 +1,244 @@
1
+ // MCP resources (Phase 7 §D15 / D15-A). Four URI forms over a fresh
2
+ // walk per call (D14):
3
+ //
4
+ // rcf://tree - project index: {id, kind, title, filePath} per doc
5
+ // rcf://doc/<id> - one resource per document id, incl. inline AC / TC
6
+ // rcf://docs/<slug> - static methodology docs from the Phase 7.5
7
+ // guidance pack, wired through guidance/manifest.json
8
+ // (the pack owns content; this file serves bytes)
9
+ // rcf://guidance/<slug> - manifest-declared guidance blocks that are not
10
+ // themselves markdown files. Currently one:
11
+ // `platform-invariants` serves the
12
+ // `manifest.platformInvariants[]` array as JSON
13
+ // (Track C+D §8.2 / §13.3 AC).
14
+ //
15
+ // No subscriptions, no listChanged, no resource templates (spec §7).
16
+ // resources/list returns everything in one page; a cursor param is
17
+ // accepted and ignored. Unknown URIs are the MCP resources protocol
18
+ // error -32002.
19
+
20
+ import { readFile } from 'node:fs/promises';
21
+ import { dirname, join, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+
24
+ import { JsonRpcError, RESOURCE_NOT_FOUND } from '#core/mcp-shell';
25
+ import { walkTree } from '#core/store';
26
+ import { pathForId } from '#core/store/loader.js';
27
+
28
+ const here = dirname(fileURLToPath(import.meta.url));
29
+ /** The guidance pack ships with the server install, not the served project. */
30
+ export const GUIDANCE_DIR = resolve(here, '..', '..', 'guidance');
31
+
32
+ const TREE_URI = 'rcf://tree';
33
+ const DOC_PREFIX = 'rcf://doc/';
34
+ const DOCS_PREFIX = 'rcf://docs/';
35
+ const GUIDANCE_PREFIX = 'rcf://guidance/';
36
+
37
+ // Track C+D §8.2 / §13.3 AC: the platform-invariants block gets its own
38
+ // MCP resource, distinct from `rcf://docs/*` (which serves markdown
39
+ // files). The list is derived from the shipping manifest.json; the URI
40
+ // slug is stable.
41
+ const PLATFORM_INVARIANTS_URI = `${GUIDANCE_PREFIX}platform-invariants`;
42
+ const PLATFORM_INVARIANTS_SLUG = 'platform-invariants';
43
+
44
+ /**
45
+ * Read and parse guidance/manifest.json - the Phase 7.5 contract file
46
+ * (OQ-P7.5-1). Read per call: cheap, and the manifest is the single
47
+ * source of truth for what is served (pack D2 deliberately excludes
48
+ * README.md and manifest.json itself).
49
+ *
50
+ * @param {string} guidanceDir
51
+ * @returns {Promise<{docs: Array<{slug: string, file: string, title: string}>, prompts: Array<object>}>}
52
+ */
53
+ export async function readGuidanceManifest(guidanceDir) {
54
+ const raw = await readFile(join(guidanceDir, 'manifest.json'), 'utf8');
55
+ return JSON.parse(raw);
56
+ }
57
+
58
+ /**
59
+ * Best-effort display title for a document. Kinds carry different
60
+ * naming fields; inline AC / TC entries carry only a description.
61
+ *
62
+ * @param {object} doc
63
+ * @returns {string | null}
64
+ */
65
+ function titleOf(doc) {
66
+ if (!doc || typeof doc !== 'object') return null;
67
+ return doc.title ?? doc.name ?? doc.productName ?? doc.description ?? null;
68
+ }
69
+
70
+ /**
71
+ * Relative file path for a document id; inline AC / TC ids resolve to
72
+ * their parent's file.
73
+ *
74
+ * @param {import('#core/store/walker.js').TreeModel} tree
75
+ * @param {string} id
76
+ * @returns {string | null}
77
+ */
78
+ function filePathFor(tree, id) {
79
+ const resolved = pathForId(id);
80
+ if (resolved) return `rcf/${resolved.relPath}`;
81
+ const parentId = tree.parentByChild.get(id);
82
+ if (!parentId) return null;
83
+ const parentResolved = pathForId(parentId);
84
+ return parentResolved ? `rcf/${parentResolved.relPath}` : null;
85
+ }
86
+
87
+ /**
88
+ * Enumerate every document id in the tree: standalone docs (byId) plus
89
+ * inline AC / TC ids (parentByChild keys not present in byId).
90
+ *
91
+ * @param {import('#core/store/walker.js').TreeModel} tree
92
+ * @returns {Array<{id: string, kind: string, doc: object | null}>}
93
+ */
94
+ function enumerateDocuments(tree) {
95
+ const out = [];
96
+ for (const [id, doc] of tree.byId.entries()) {
97
+ out.push({ id, kind: tree.kindById.get(id) ?? 'unknown', doc });
98
+ }
99
+ for (const id of tree.parentByChild.keys()) {
100
+ if (tree.byId.has(id)) continue;
101
+ const kind = id.startsWith('AC-') ? 'ac' : id.startsWith('TC-') ? 'tc' : 'unknown';
102
+ out.push({ id, kind, doc: resolveInlineDoc(tree, id) });
103
+ }
104
+ return out;
105
+ }
106
+
107
+ function resolveInlineDoc(tree, id) {
108
+ const parentId = tree.parentByChild.get(id);
109
+ if (!parentId) return null;
110
+ const parent = tree.byId.get(parentId);
111
+ if (!parent) return null;
112
+ const entries = id.startsWith('AC-') ? parent.acceptanceCriteria : parent.testCases;
113
+ return (entries ?? []).find((e) => e.id === id) ?? null;
114
+ }
115
+
116
+ /**
117
+ * Create the resource registry bound to one project root.
118
+ *
119
+ * @param {object} opts
120
+ * @param {string} opts.projectRoot
121
+ * @param {string} [opts.guidanceDir]
122
+ * @returns {{list: () => Promise<object>, read: (uri: string) => Promise<object>}}
123
+ */
124
+ export function createResourceRegistry({ projectRoot, guidanceDir = GUIDANCE_DIR }) {
125
+ async function buildTreeIndex() {
126
+ const { tree } = await walkTree({ projectRoot });
127
+ return {
128
+ tree,
129
+ index: {
130
+ project: tree.manifest?.projectName ?? null,
131
+ documents: enumerateDocuments(tree).map(({ id, kind, doc }) => ({
132
+ id,
133
+ kind,
134
+ title: titleOf(doc),
135
+ filePath: filePathFor(tree, id),
136
+ })),
137
+ },
138
+ };
139
+ }
140
+
141
+ async function list() {
142
+ // A broken tree does not block resources: the walker tolerates
143
+ // per-document failures and this surface serves what loaded -
144
+ // rcf_validate is the diagnosis channel (D18 posture).
145
+ const { tree, index } = await buildTreeIndex();
146
+ const manifest = await readGuidanceManifest(guidanceDir);
147
+ const resources = [
148
+ {
149
+ uri: TREE_URI,
150
+ name: 'tree',
151
+ title: index.project ? `${index.project} document index` : 'RCF document index',
152
+ description: 'Project document index: id, kind, title and file path per document, from a fresh tree walk.',
153
+ mimeType: 'application/json',
154
+ },
155
+ ...enumerateDocuments(tree).map(({ id, kind, doc }) => ({
156
+ uri: `${DOC_PREFIX}${id}`,
157
+ name: id,
158
+ ...(titleOf(doc) ? { title: `${id}: ${titleOf(doc)}` } : {}),
159
+ description: `RCF ${kind} document ${id}`,
160
+ mimeType: 'application/json',
161
+ })),
162
+ ...manifest.docs.map((d) => ({
163
+ uri: `${DOCS_PREFIX}${d.slug}`,
164
+ name: d.slug,
165
+ title: d.title,
166
+ description: `RCF methodology: ${d.title}. Canonical web reference: https://stravica.ai/rcf-methodology/`,
167
+ mimeType: 'text/markdown',
168
+ })),
169
+ ...(Array.isArray(manifest.platformInvariants) && manifest.platformInvariants.length > 0
170
+ ? [{
171
+ uri: PLATFORM_INVARIANTS_URI,
172
+ name: PLATFORM_INVARIANTS_SLUG,
173
+ title: 'RCF platform invariants',
174
+ description: 'Platform-wide invariants (currently: never-skip-RCF). JSON array of {id, title, text} records lifted verbatim from guidance/manifest.json.',
175
+ mimeType: 'application/json',
176
+ }]
177
+ : []),
178
+ ];
179
+ return { resources };
180
+ }
181
+
182
+ async function read(uri) {
183
+ if (typeof uri !== 'string' || uri.length === 0) {
184
+ throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
185
+ }
186
+ if (uri === TREE_URI) {
187
+ const { index } = await buildTreeIndex();
188
+ return {
189
+ contents: [{
190
+ uri,
191
+ mimeType: 'application/json',
192
+ text: JSON.stringify(index, null, 2),
193
+ }],
194
+ };
195
+ }
196
+ if (uri.startsWith(DOCS_PREFIX)) {
197
+ const slug = uri.slice(DOCS_PREFIX.length);
198
+ const manifest = await readGuidanceManifest(guidanceDir);
199
+ const entry = manifest.docs.find((d) => d.slug === slug);
200
+ if (!entry) {
201
+ throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
202
+ }
203
+ const text = await readFile(join(guidanceDir, entry.file), 'utf8');
204
+ return { contents: [{ uri, mimeType: 'text/markdown', text }] };
205
+ }
206
+ if (uri.startsWith(GUIDANCE_PREFIX)) {
207
+ const slug = uri.slice(GUIDANCE_PREFIX.length);
208
+ if (slug !== PLATFORM_INVARIANTS_SLUG) {
209
+ throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
210
+ }
211
+ const manifest = await readGuidanceManifest(guidanceDir);
212
+ const invariants = Array.isArray(manifest.platformInvariants) ? manifest.platformInvariants : [];
213
+ // Serves the manifest bytes verbatim - the never-skip-RCF locking
214
+ // test in test/guidance/ owns the byte-for-byte canonicalness of
215
+ // each entry's text; this handler is the transport, not a second
216
+ // source of truth.
217
+ return {
218
+ contents: [{
219
+ uri,
220
+ mimeType: 'application/json',
221
+ text: JSON.stringify(invariants, null, 2),
222
+ }],
223
+ };
224
+ }
225
+ if (uri.startsWith(DOC_PREFIX)) {
226
+ const id = uri.slice(DOC_PREFIX.length);
227
+ const { tree } = await walkTree({ projectRoot });
228
+ const doc = tree.byId.get(id) ?? resolveInlineDoc(tree, id);
229
+ if (!doc) {
230
+ throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
231
+ }
232
+ return {
233
+ contents: [{
234
+ uri,
235
+ mimeType: 'application/json',
236
+ text: JSON.stringify(doc, null, 2),
237
+ }],
238
+ };
239
+ }
240
+ throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
241
+ }
242
+
243
+ return { list, read };
244
+ }