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,159 @@
1
+ // Small helpers shared across the per-document renderers. Vanilla template
2
+ // strings; no template engine.
3
+
4
+ /**
5
+ * HTML-escape a string for safe injection between tags.
6
+ *
7
+ * @param {unknown} value
8
+ * @returns {string}
9
+ */
10
+ export function escapeHtml(value) {
11
+ if (value === null || value === undefined) return '';
12
+ const s = String(value);
13
+ return s
14
+ .replace(/&/g, '&')
15
+ .replace(/</g, '&lt;')
16
+ .replace(/>/g, '&gt;')
17
+ .replace(/"/g, '&quot;')
18
+ .replace(/'/g, '&#39;');
19
+ }
20
+
21
+ /**
22
+ * Return the raw document id as an anchor id. Phase 3.2 unified the anchor
23
+ * convention around the display id (e.g. "REQ-002") so Mermaid click targets
24
+ * and internal doc links resolve to the same DOM node.
25
+ *
26
+ * @param {string} id
27
+ * @returns {string}
28
+ */
29
+ export function anchorIdFor(id) {
30
+ return String(id);
31
+ }
32
+
33
+ /**
34
+ * Render an `<a>` link to the section of another document.
35
+ *
36
+ * @param {string} id
37
+ * @param {string} [label]
38
+ * @returns {string}
39
+ */
40
+ export function docLink(id, label) {
41
+ return `<a href="#${anchorIdFor(id)}">${escapeHtml(label ?? id)}</a>`;
42
+ }
43
+
44
+ /**
45
+ * Render a list of ids as comma-separated doc links.
46
+ *
47
+ * @param {string[] | undefined} ids
48
+ * @returns {string}
49
+ */
50
+ export function docLinkList(ids) {
51
+ if (!Array.isArray(ids) || ids.length === 0) return '<em>none</em>';
52
+ return ids.map((id) => docLink(id)).join(', ');
53
+ }
54
+
55
+ /**
56
+ * Render a paragraph if value is present.
57
+ *
58
+ * @param {string} label
59
+ * @param {unknown} value
60
+ * @returns {string}
61
+ */
62
+ export function fieldPara(label, value) {
63
+ if (value === undefined || value === null || value === '') return '';
64
+ return `<p><strong>${escapeHtml(label)}:</strong> ${escapeHtml(value)}</p>`;
65
+ }
66
+
67
+ /**
68
+ * Render an unordered list if items are present.
69
+ *
70
+ * @param {string} label
71
+ * @param {string[] | undefined} items
72
+ * @returns {string}
73
+ */
74
+ export function fieldList(label, items) {
75
+ if (!Array.isArray(items) || items.length === 0) return '';
76
+ const li = items.map((s) => `<li>${escapeHtml(s)}</li>`).join('');
77
+ return `<section class="field-list"><h4>${escapeHtml(label)}</h4><ul>${li}</ul></section>`;
78
+ }
79
+
80
+ /**
81
+ * Render the "Show raw JSON" disclosure block for a document.
82
+ *
83
+ * Phase 3.8 D13b: the raw-JSON disclosure now carries a stable
84
+ * `data-doc-id="{parentDocId}::raw"` attribute so the live-client can
85
+ * persist its open state across SSE swaps and page reloads. The main
86
+ * doc-details already get a `data-doc-id` via `detailsWrap`; this closes
87
+ * the last state-persistence gap.
88
+ *
89
+ * @param {string|undefined} raw
90
+ * @param {object} doc
91
+ * @param {string} parentDocId - the enclosing doc's display id (e.g. "REQ-002")
92
+ * @returns {string}
93
+ */
94
+ export function rawJsonDisclosure(raw, doc, parentDocId) {
95
+ const json = raw ?? JSON.stringify(doc, null, 2);
96
+ const rawId = `${parentDocId ?? 'doc'}::raw`;
97
+ return `<details class="raw-json" data-doc-id="${escapeHtml(rawId)}"><summary>Show raw JSON</summary><pre>${escapeHtml(json)}</pre></details>`;
98
+ }
99
+
100
+ /**
101
+ * Render a broken-document banner above the rest of a document section.
102
+ *
103
+ * @param {import('#core/errors').RcfError[]} errors
104
+ * @returns {string}
105
+ */
106
+ export function brokenBanner(errors) {
107
+ if (!errors || errors.length === 0) return '';
108
+ const items = errors
109
+ .map((e) => `<li><code>${escapeHtml(e.kind)}</code> ${escapeHtml(e.message)}</li>`)
110
+ .join('');
111
+ return `
112
+ <aside class="broken" role="alert">
113
+ <p><strong>Broken document</strong> - schema validation or reference failure.</p>
114
+ <ul>${items}</ul>
115
+ </aside>`.trim();
116
+ }
117
+
118
+ /**
119
+ * Render a broken-reference placeholder when a referenced id has no file.
120
+ *
121
+ * @param {string} id
122
+ * @returns {string}
123
+ */
124
+ export function brokenReferenceSection(id) {
125
+ const anchor = anchorIdFor(id);
126
+ return `
127
+ <article id="${anchor}" class="doc broken-doc">
128
+ <h3>${escapeHtml(id)} - broken reference</h3>
129
+ <aside class="broken" role="alert">
130
+ <p>Referenced by a parent document but no file was found at the expected path.</p>
131
+ </aside>
132
+ </article>`.trim();
133
+ }
134
+
135
+ /**
136
+ * Wrap a doc's rendered body in a `<details data-doc-id>` so it can be
137
+ * drilled-in from the tab tree. Closed by default. The `data-doc-id`
138
+ * attribute is what the hash-routing script targets.
139
+ *
140
+ * @param {object} args
141
+ * @param {string} args.id - display doc id (e.g. "REQ-001")
142
+ * @param {string} args.summary - short label shown in the summary line
143
+ * @param {string} args.className - class applied to the details wrapper
144
+ * @param {string} args.body - the rendered body HTML
145
+ * @param {string} [args.status] - optional doc status, rendered as a pill
146
+ * @returns {string}
147
+ */
148
+ export function detailsWrap({
149
+ id, summary, className, body, status,
150
+ }) {
151
+ const statusPill = status
152
+ ? `<span class="status ${escapeHtml(status)}">${escapeHtml(status)}</span>`
153
+ : '';
154
+ return `
155
+ <details class="doc-details ${className}" data-doc-id="${escapeHtml(id)}">
156
+ <summary><span class="summary-label">${escapeHtml(summary)}</span>${statusPill}</summary>
157
+ ${body}
158
+ </details>`.trim();
159
+ }
@@ -0,0 +1,12 @@
1
+ // Doc renderer index. Re-exports each per-type renderer so html-page.js can
2
+ // import them through a single module.
3
+
4
+ export { renderPrd } from './prd.js';
5
+ export { renderReq } from './req.js';
6
+ export { renderUserStory } from './user-story.js';
7
+ export { renderTad } from './tad.js';
8
+ export { renderTac } from './tac.js';
9
+ export { renderAdr } from './adr.js';
10
+ export { renderBuildSequence } from './build-sequence.js';
11
+ export { renderFbs } from './fbs.js';
12
+ export { renderTestSuite } from './test-suite.js';
@@ -0,0 +1,45 @@
1
+ // PRD renderer. Curated fields per spec D11. Post-3.7 (D15) the child REQ
2
+ // list is not read from `prd.requirementIds` (removed in 0.2.0) but from
3
+ // the computed `childrenByParent` map on the tree model.
4
+
5
+ import {
6
+ anchorIdFor,
7
+ brokenBanner,
8
+ docLinkList,
9
+ escapeHtml,
10
+ fieldList,
11
+ fieldPara,
12
+ rawJsonDisclosure,
13
+ } from './helpers.js';
14
+
15
+ /**
16
+ * @param {object} prd
17
+ * @param {object} ctx
18
+ * @param {string|undefined} ctx.raw
19
+ * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
+ * @param {string[]} [ctx.requirementIds] - computed REQ children for this PRD
21
+ * @returns {string}
22
+ */
23
+ export function renderPrd(prd, ctx) {
24
+ if (!prd) return '';
25
+ const anchor = anchorIdFor(prd.prdId ?? 'PRD');
26
+ const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
+ const requirementIds = ctx.requirementIds ?? [];
28
+ const sections = [
29
+ fieldPara('Executive summary', prd.executiveSummary),
30
+ fieldPara('Problem statement', prd.problemStatement),
31
+ fieldList('Target users', prd.targetUsers),
32
+ fieldList('In scope', prd.inScope),
33
+ fieldList('Out of scope', prd.outOfScope),
34
+ fieldList('Objectives', prd.objectives),
35
+ fieldList('Constraints', prd.constraints),
36
+ `<section class="field-list"><h4>Requirements</h4><p>${docLinkList(requirementIds)}</p></section>`,
37
+ ].filter(Boolean).join('\n');
38
+ return `
39
+ <article id="${anchor}" class="doc doc-prd">
40
+ <h3>${escapeHtml(prd.prdId ?? 'PRD')} - ${escapeHtml(prd.productName ?? '')}</h3>
41
+ ${broken}
42
+ ${sections}
43
+ ${rawJsonDisclosure(ctx.raw, prd, prd.prdId)}
44
+ </article>`.trim();
45
+ }
@@ -0,0 +1,43 @@
1
+ // REQ renderer. Carries an optional per-REQ Mermaid subdiagram block when
2
+ // the caller passes one (the html-page module decides whether to include it).
3
+ // Child user stories are NOT rendered as inline links because the Phase 3.2
4
+ // layout nests them as `<details>` under the REQ's own `<details>` wrapper.
5
+
6
+ import {
7
+ anchorIdFor,
8
+ brokenBanner,
9
+ escapeHtml,
10
+ fieldList,
11
+ fieldPara,
12
+ rawJsonDisclosure,
13
+ } from './helpers.js';
14
+
15
+ /**
16
+ * @param {object} req
17
+ * @param {object} ctx
18
+ * @param {string|undefined} ctx.raw
19
+ * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
+ * @param {string|undefined} [ctx.subdiagram]
21
+ * @returns {string}
22
+ */
23
+ export function renderReq(req, ctx) {
24
+ if (!req) return '';
25
+ const anchor = anchorIdFor(req.reqId ?? 'REQ');
26
+ const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
+ const subdiagram = ctx.subdiagram
28
+ ? `<section class="subdiagram"><h4>Slice diagram</h4><pre class="mermaid">${escapeHtml(ctx.subdiagram)}</pre></section>`
29
+ : '';
30
+ return `
31
+ <article id="${anchor}" class="doc doc-req">
32
+ <h3>${escapeHtml(req.reqId ?? 'REQ')} - ${escapeHtml(req.title ?? '')}</h3>
33
+ ${broken}
34
+ ${fieldPara('Description', req.description)}
35
+ ${fieldPara('Category', req.category)}
36
+ ${fieldPara('Domain', req.domain)}
37
+ ${fieldPara('Priority', req.priority)}
38
+ ${fieldPara('Rationale', req.rationale)}
39
+ ${fieldList('Tags', req.tags)}
40
+ ${subdiagram}
41
+ ${rawJsonDisclosure(ctx.raw, req, req.reqId)}
42
+ </article>`.trim();
43
+ }
@@ -0,0 +1,38 @@
1
+ // TAC renderer.
2
+
3
+ import {
4
+ anchorIdFor,
5
+ brokenBanner,
6
+ escapeHtml,
7
+ fieldList,
8
+ fieldPara,
9
+ rawJsonDisclosure,
10
+ } from './helpers.js';
11
+
12
+ /**
13
+ * @param {object} tac
14
+ * @param {object} ctx
15
+ * @param {string|undefined} ctx.raw
16
+ * @param {import('#core/errors').RcfError[]} [ctx.errors]
17
+ * @returns {string}
18
+ */
19
+ export function renderTac(tac, ctx) {
20
+ if (!tac) return '';
21
+ const anchor = anchorIdFor(tac.tacId ?? 'TAC');
22
+ const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
23
+ const interfaces = (tac.interfaces ?? []).map((i) => `<li><strong>${escapeHtml(i.name ?? '')}</strong> (${escapeHtml(i.kind ?? '')}) - ${escapeHtml(i.description ?? '')}</li>`).join('');
24
+ const deps = (tac.dependencies ?? []).map((d) => `<li><strong>${escapeHtml(d.name ?? '')}</strong> (${escapeHtml(d.kind ?? '')}) - ${escapeHtml(d.description ?? '')}</li>`).join('');
25
+ return `
26
+ <article id="${anchor}" class="doc doc-tac">
27
+ <h3>${escapeHtml(tac.tacId ?? 'TAC')} - ${escapeHtml(tac.name ?? '')}</h3>
28
+ ${broken}
29
+ ${fieldPara('Purpose', tac.purpose)}
30
+ ${fieldList('Responsibilities', tac.responsibilities)}
31
+ ${fieldPara('Internal structure', tac.internalStructure)}
32
+ ${interfaces ? `<section class="field-list"><h4>Interfaces</h4><ul>${interfaces}</ul></section>` : ''}
33
+ ${deps ? `<section class="field-list"><h4>Dependencies</h4><ul>${deps}</ul></section>` : ''}
34
+ ${fieldPara('Trade-offs', tac.tradeoffs)}
35
+ ${fieldPara('Notes', tac.notes)}
36
+ ${rawJsonDisclosure(ctx.raw, tac, tac.tacId)}
37
+ </article>`.trim();
38
+ }
@@ -0,0 +1,74 @@
1
+ // TAD renderer. Curated fields per spec D11; iterates the architecturePrinciples[]
2
+ // array rather than hard-coding a count. Post-3.7 (D15) the child TAC / ADR
3
+ // lists are not read from removed parent fields (`componentIds`,
4
+ // `architecturalDecisionIds`) but from computed lists supplied via ctx.
5
+
6
+ import {
7
+ anchorIdFor,
8
+ brokenBanner,
9
+ docLinkList,
10
+ escapeHtml,
11
+ fieldList,
12
+ fieldPara,
13
+ rawJsonDisclosure,
14
+ } from './helpers.js';
15
+
16
+ /**
17
+ * @param {object} tad
18
+ * @param {object} ctx
19
+ * @param {string|undefined} ctx.raw
20
+ * @param {import('#core/errors').RcfError[]} [ctx.errors]
21
+ * @param {string[]} [ctx.componentIds] - computed TAC children
22
+ * @param {string[]} [ctx.architecturalDecisionIds] - computed ADR children
23
+ * @returns {string}
24
+ */
25
+ export function renderTad(tad, ctx) {
26
+ if (!tad) return '';
27
+ const anchor = anchorIdFor(tad.tadId ?? 'TAD');
28
+ const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
29
+ const overview = tad.systemOverview ?? {};
30
+ const componentIds = ctx.componentIds ?? [];
31
+ const architecturalDecisionIds = ctx.architecturalDecisionIds ?? [];
32
+ const principles = (tad.architecturePrinciples ?? []).map((p) => `
33
+ <li>
34
+ <strong>${escapeHtml(p.name)}.</strong> ${escapeHtml(p.description)}
35
+ <br/><em>Rationale:</em> ${escapeHtml(p.rationale)}
36
+ </li>`.trim()).join('\n');
37
+ const optionalSections = renderOptionalSections(tad);
38
+ return `
39
+ <article id="${anchor}" class="doc doc-tad">
40
+ <h3>${escapeHtml(tad.tadId ?? 'TAD')} - Technical Architecture Document</h3>
41
+ ${broken}
42
+ ${fieldPara('Executive summary', overview.executiveSummary)}
43
+ ${fieldPara('System purpose', overview.systemPurpose)}
44
+ ${fieldPara('Architectural approach', overview.architecturalApproach)}
45
+ ${fieldList('Key capabilities', overview.keyCapabilities)}
46
+ ${principles ? `<section class="field-list"><h4>Architecture principles</h4><ul>${principles}</ul></section>` : ''}
47
+ ${optionalSections}
48
+ <section class="field-list"><h4>Components (TACs)</h4><p>${docLinkList(componentIds)}</p></section>
49
+ <section class="field-list"><h4>Architectural decisions (ADRs)</h4><p>${docLinkList(architecturalDecisionIds)}</p></section>
50
+ ${rawJsonDisclosure(ctx.raw, tad, tad.tadId)}
51
+ </article>`.trim();
52
+ }
53
+
54
+ function renderOptionalSections(tad) {
55
+ const out = [];
56
+ const named = {
57
+ dataArchitecture: 'Data architecture',
58
+ integrationArchitecture: 'Integration architecture',
59
+ securityArchitecture: 'Security architecture',
60
+ deploymentArchitecture: 'Deployment architecture',
61
+ operationalConcerns: 'Operational concerns',
62
+ };
63
+ for (const [key, label] of Object.entries(named)) {
64
+ const section = tad[key];
65
+ if (!section || typeof section !== 'object') continue;
66
+ const rows = Object.entries(section).map(([k, v]) => {
67
+ if (typeof v === 'string') return `<dt>${escapeHtml(k)}</dt><dd>${escapeHtml(v)}</dd>`;
68
+ if (Array.isArray(v)) return `<dt>${escapeHtml(k)}</dt><dd>${v.map((x) => `<code>${escapeHtml(typeof x === 'object' ? JSON.stringify(x) : x)}</code>`).join(', ')}</dd>`;
69
+ return `<dt>${escapeHtml(k)}</dt><dd><pre>${escapeHtml(JSON.stringify(v, null, 2))}</pre></dd>`;
70
+ }).join('\n');
71
+ out.push(`<section class="field-list"><h4>${escapeHtml(label)}</h4><dl>${rows}</dl></section>`);
72
+ }
73
+ return out.join('\n');
74
+ }
@@ -0,0 +1,45 @@
1
+ // Test suite renderer. Post-3.7 the TS id field is `id` (matches the
2
+ // 0.2.0 test-suite schema); inline test cases carry `id` + `acId` +
3
+ // `description` + `status` (+ optional `testPointer`).
4
+
5
+ import {
6
+ anchorIdFor,
7
+ brokenBanner,
8
+ docLink,
9
+ docLinkList,
10
+ escapeHtml,
11
+ fieldPara,
12
+ rawJsonDisclosure,
13
+ } from './helpers.js';
14
+
15
+ /**
16
+ * @param {object} ts
17
+ * @param {object} ctx
18
+ * @param {string|undefined} ctx.raw
19
+ * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
+ * @returns {string}
21
+ */
22
+ export function renderTestSuite(ts, ctx) {
23
+ if (!ts) return '';
24
+ const tsId = ts.id ?? 'TS';
25
+ const anchor = anchorIdFor(tsId);
26
+ const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
+ const tcs = (ts.testCases ?? []).map((tc) => `
28
+ <div class="tc" id="${escapeHtml(tc.id ?? '')}">
29
+ <p><strong>${escapeHtml(tc.id ?? 'TC')}</strong> - covers ${tc.acId ? docLink(tc.acId) : 'AC'} <span class="status ${escapeHtml(tc.status ?? '')}">${escapeHtml(tc.status ?? 'pending')}</span></p>
30
+ ${tc.description ? `<p>${escapeHtml(tc.description)}</p>` : ''}
31
+ ${tc.testPointer ? `<p><em>Test pointer:</em> <code>${escapeHtml(tc.testPointer)}</code></p>` : ''}
32
+ </div>`.trim()).join('\n');
33
+ return `
34
+ <article id="${anchor}" class="doc doc-ts">
35
+ <h3>${escapeHtml(tsId)} - ${escapeHtml(ts.title ?? 'test suite')}</h3>
36
+ ${broken}
37
+ ${ts.usId ? `<p><strong>Verifies user story:</strong> ${docLink(ts.usId)}</p>` : ''}
38
+ ${fieldPara('Purpose', ts.purpose)}
39
+ ${fieldPara('Test level', ts.testLevel)}
40
+ ${ts.acIds?.length ? `<section class="field-list"><h4>Acceptance criteria verified</h4><p>${docLinkList(ts.acIds)}</p></section>` : ''}
41
+ ${fieldPara('Status', ts.status)}
42
+ <section class="field-list"><h4>Test cases</h4>${tcs || '<em>no test cases</em>'}</section>
43
+ ${rawJsonDisclosure(ctx.raw, ts, ts.id)}
44
+ </article>`.trim();
45
+ }
@@ -0,0 +1,63 @@
1
+ // User story renderer. ACs are rendered inline as list items so an AC can
2
+ // be a top-level scroll anchor (per Phase 3.2 D4) without wrapping each in
3
+ // its own `<section>`. Each AC line links back to the FBS or FBSs that
4
+ // deliver it via `fbsByAcId` (D8).
5
+
6
+ import {
7
+ anchorIdFor,
8
+ brokenBanner,
9
+ docLink,
10
+ escapeHtml,
11
+ fieldPara,
12
+ rawJsonDisclosure,
13
+ } from './helpers.js';
14
+
15
+ /**
16
+ * @param {object} us
17
+ * @param {object} ctx
18
+ * @param {string|undefined} ctx.raw
19
+ * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
+ * @param {Map<string, object[]>} [ctx.fbsByAcId]
21
+ * @returns {string}
22
+ */
23
+ export function renderUserStory(us, ctx) {
24
+ if (!us) return '';
25
+ const anchor = anchorIdFor(us.usId ?? 'US');
26
+ const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
+ const acItems = (us.acceptanceCriteria ?? []).map((ac) => renderAcItem(ac, ctx)).join('\n');
28
+ return `
29
+ <article id="${anchor}" class="doc doc-us">
30
+ <h3>${escapeHtml(us.usId ?? 'US')} - ${escapeHtml(us.title ?? '')}</h3>
31
+ ${broken}
32
+ <p><strong>As a:</strong> ${escapeHtml(us.asA ?? '')}</p>
33
+ <p><strong>I want:</strong> ${escapeHtml(us.iWant ?? '')}</p>
34
+ <p><strong>So that:</strong> ${escapeHtml(us.soThat ?? '')}</p>
35
+ ${fieldPara('Description', us.description)}
36
+ <section class="field-list"><h4>Requirement</h4><p>${docLink(us.reqId)}</p></section>
37
+ <section class="acceptance-criteria">
38
+ <h4>Acceptance criteria</h4>
39
+ <ul class="ac-list">
40
+ ${acItems}
41
+ </ul>
42
+ </section>
43
+ ${rawJsonDisclosure(ctx.raw, us, us.usId)}
44
+ </article>`.trim();
45
+ }
46
+
47
+ function renderAcItem(ac, ctx) {
48
+ const acId = ac.id ?? 'AC';
49
+ const acAnchor = anchorIdFor(acId);
50
+ const fbsList = ctx.fbsByAcId?.get(acId) ?? [];
51
+ const coveredBy = fbsList.length === 0
52
+ ? '<em>not yet delivered by any FBS</em>'
53
+ : `Covered by ${fbsList.map((f) => docLink(f.fbsId)).join(', ')}`;
54
+ const parts = [];
55
+ parts.push(`<strong>${escapeHtml(acId)}</strong> - ${escapeHtml(ac.description ?? '')}`);
56
+ const gwt = [];
57
+ if (ac.given) gwt.push(`<em>Given</em> ${escapeHtml(ac.given)}`);
58
+ if (ac.when) gwt.push(`<em>When</em> ${escapeHtml(ac.when)}`);
59
+ if (ac.then) gwt.push(`<em>Then</em> ${escapeHtml(ac.then)}`);
60
+ if (gwt.length > 0) parts.push(`<div class="ac-gwt">${gwt.map((p) => `<p>${p}</p>`).join('')}</div>`);
61
+ parts.push(`<p class="ac-meta"><strong>Testable:</strong> ${escapeHtml(String(ac.testable ?? false))} - ${coveredBy}</p>`);
62
+ return `<li id="${acAnchor}" class="ac-item">${parts.join('\n')}</li>`;
63
+ }