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,462 @@
1
+ // Assembles index.html from doc-renderer output and the per-REQ Mermaid
2
+ // subdiagrams. Phase 3.2 introduced the four-tab layout (Overview,
3
+ // Requirements, Architecture, Build sequence) plus nested `<details>`
4
+ // drill-down under Requirements. Phase 3.6 dropped the top-of-overview
5
+ // diagram - it was redundant with the PRD body's requirementIds list
6
+ // and unwieldy past ~15 REQs. Overview tab now renders the PRD body
7
+ // only. Client-side tabs + hash routing are wired by an inline script
8
+ // at the end of `<body>`; if JS is unavailable, every tabpanel is
9
+ // visible in DOM order (D12).
10
+ //
11
+ // Phase 3.8 wraps the swappable tree content in a stable
12
+ // `<div id="rcf-live-content">` (D13a) and always injects the live
13
+ // client script `<script src="/live-client.js" defer>` before `</body>`.
14
+ // The tab init routine is exposed as `window.rcfPage.init()` so the
15
+ // live client can re-invoke it after every SSE innerHTML swap.
16
+
17
+ import {
18
+ renderAdr,
19
+ renderBuildSequence,
20
+ renderFbs,
21
+ renderPrd,
22
+ renderReq,
23
+ renderTac,
24
+ renderTad,
25
+ renderTestSuite,
26
+ renderUserStory,
27
+ } from './doc-renderers/index.js';
28
+ import { detailsWrap, escapeHtml } from './doc-renderers/helpers.js';
29
+ import { allRequirementSubdiagrams } from './mermaid-diagram.js';
30
+
31
+ // Inline SVG favicon: the Stravica monogram (terracotta rounded-square with
32
+ // cream serif S), pinched from stravica.ai/assets/brand/logo-monogram.svg
33
+ // so the review surface shares brand identity with the marketing site.
34
+ // Delivered as a data URL so no separate file has to ship.
35
+ const FAVICON_HREF =
36
+ "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Crect width='512' height='512' rx='64' fill='%23c14a3a'/%3E%3Ctext x='256' y='370' font-family='Georgia,serif' font-size='360' font-weight='600' text-anchor='middle' fill='%23f7f5f0'%3ES%3C/text%3E%3C/svg%3E";
37
+
38
+ const LIVE_WRAPPER_OPEN = '<div id="rcf-live-content">';
39
+ const LIVE_WRAPPER_CLOSE = '</div>';
40
+
41
+ /**
42
+ * Render the complete index.html string. Phase 3.8: always includes the
43
+ * live-content wrapper and the live-client script tag.
44
+ *
45
+ * @param {import('./tree-model.js').BuiltTreeModel} model
46
+ * @returns {string}
47
+ */
48
+ export function renderPage(model) {
49
+ const projectName = model.manifest?.projectName ?? model.prd?.productName ?? 'RCF project';
50
+ const contentHtml = renderContent(model);
51
+ const script = inlineScript();
52
+
53
+ return `<!DOCTYPE html>
54
+ <html lang="en-GB">
55
+ <head>
56
+ <meta charset="utf-8">
57
+ <meta name="viewport" content="width=device-width, initial-scale=1">
58
+ <title>${escapeHtml(projectName)} - RCF review surface</title>
59
+ <link rel="icon" type="image/svg+xml" href="${FAVICON_HREF}">
60
+ <link rel="stylesheet" href="style.css">
61
+ <noscript>
62
+ <style>
63
+ /* Progressive enhancement per D12: no JS -> every tabpanel is
64
+ visible, stacked in DOM order. The tab bar becomes visual chrome
65
+ only (buttons do nothing) so we hide it. */
66
+ nav.tabs { display: none !important; }
67
+ section[role="tabpanel"][hidden] { display: block !important; }
68
+ </style>
69
+ </noscript>
70
+ </head>
71
+ <body>
72
+ <header>
73
+ <h1>${escapeHtml(projectName)}</h1>
74
+ <p class="subtitle">RCF review surface</p>
75
+ <nav class="tabs" role="tablist" aria-label="Document sections">
76
+ <button type="button" role="tab" data-tab="overview" aria-selected="true" aria-controls="tab-overview">Overview</button>
77
+ <button type="button" role="tab" data-tab="requirements" aria-selected="false" aria-controls="tab-requirements">Requirements</button>
78
+ <button type="button" role="tab" data-tab="architecture" aria-selected="false" aria-controls="tab-architecture">Architecture</button>
79
+ <button type="button" role="tab" data-tab="build" aria-selected="false" aria-controls="tab-build">Build sequence</button>
80
+ </nav>
81
+ </header>
82
+ <main>
83
+ ${LIVE_WRAPPER_OPEN}
84
+ ${contentHtml}
85
+ ${LIVE_WRAPPER_CLOSE}
86
+ </main>
87
+ <footer>
88
+ <p>Generated from the on-disk RCF tree at <code>rcf/</code>. Read-only; changes on disk stream to this tab automatically.</p>
89
+ <p>Learn more about the Requirements Confidence Framework at <a href="https://stravica.ai/rcf-methodology" target="_blank" rel="noopener">stravica.ai/rcf-methodology</a>.</p>
90
+ </footer>
91
+ <script src="mermaid.min.js"></script>
92
+ <script>${script}</script>
93
+ <script src="/live-client.js" defer></script>
94
+ </body>
95
+ </html>
96
+ `;
97
+ }
98
+
99
+ /**
100
+ * Render the innerHTML of the swappable `<div id="rcf-live-content">`
101
+ * container - i.e. everything inside `<main>`. This is the payload the
102
+ * SSE `tree-update` event carries; the live client replaces the
103
+ * wrapper's innerHTML with this string.
104
+ *
105
+ * @param {import('./tree-model.js').BuiltTreeModel} model
106
+ * @returns {string}
107
+ */
108
+ export function renderContent(model) {
109
+ const subdiagrams = allRequirementSubdiagrams(model);
110
+
111
+ const prdSection = model.prd
112
+ ? renderPrd(model.prd, {
113
+ raw: model.rawById.get(model.prd.prdId),
114
+ errors: model.errorsById.get(model.prd.prdId),
115
+ requirementIds: model.childrenByParent.get(model.prd.prdId) ?? [],
116
+ })
117
+ : '<p><em>No PRD on disk.</em></p>';
118
+
119
+ const requirementsPanel = renderRequirementsPanel(model, subdiagrams);
120
+ const architecturePanel = renderArchitecturePanel(model);
121
+ const buildPanel = renderBuildPanel(model);
122
+
123
+ const errorBanner = renderErrorBanner(model.errors ?? []);
124
+
125
+ return `${errorBanner}
126
+ <section id="tab-overview" role="tabpanel" aria-labelledby="tab-overview-button">
127
+ <h2 class="tab-heading">Overview</h2>
128
+ <div class="prd-body">
129
+ ${prdSection}
130
+ </div>
131
+ </section>
132
+ <section id="tab-requirements" role="tabpanel" hidden>
133
+ <h2 class="tab-heading">Requirements</h2>
134
+ ${requirementsPanel}
135
+ </section>
136
+ <section id="tab-architecture" role="tabpanel" hidden>
137
+ <h2 class="tab-heading">Architecture</h2>
138
+ ${architecturePanel}
139
+ </section>
140
+ <section id="tab-build" role="tabpanel" hidden>
141
+ <h2 class="tab-heading">Build sequence</h2>
142
+ ${buildPanel}
143
+ </section>`;
144
+ }
145
+
146
+ function renderRequirementsPanel(model, subdiagrams) {
147
+ if (model.requirements.length === 0 && model.userStories.length === 0) {
148
+ return '<p><em>No requirements on disk.</em></p>';
149
+ }
150
+ const reqBlocks = model.requirements.map((r) => {
151
+ const reqBody = renderReq(r, {
152
+ raw: model.rawById.get(r.reqId),
153
+ errors: model.errorsById.get(r.reqId),
154
+ subdiagram: subdiagrams.get(r.reqId),
155
+ });
156
+ const stories = model.storiesByReqId.get(r.reqId) ?? [];
157
+ const usBlocks = stories.map((u) => detailsWrap({
158
+ id: u.usId,
159
+ summary: `${u.usId} - ${u.title ?? ''}`,
160
+ className: 'doc-us-wrap',
161
+ status: u.status,
162
+ body: renderUserStory(u, {
163
+ raw: model.rawById.get(u.usId),
164
+ errors: model.errorsById.get(u.usId),
165
+ fbsByAcId: model.fbsByAcId,
166
+ }),
167
+ })).join('\n');
168
+ const storiesSection = usBlocks
169
+ ? `<section class="nested-details"><h4>User stories</h4>${usBlocks}</section>`
170
+ : '<p><em>No user stories under this requirement.</em></p>';
171
+ return detailsWrap({
172
+ id: r.reqId,
173
+ summary: `${r.reqId} - ${r.title ?? ''}`,
174
+ className: 'doc-req-wrap',
175
+ status: r.status,
176
+ body: `${reqBody}\n${storiesSection}`,
177
+ });
178
+ }).join('\n');
179
+
180
+ const orphanUs = model.userStories.filter((u) => !u.reqId || !model.requirements.some((r) => r.reqId === u.reqId));
181
+ const orphanBlock = orphanUs.length > 0
182
+ ? `<section class="orphan-us"><h3>Orphan user stories</h3>${orphanUs.map((u) => detailsWrap({
183
+ id: u.usId,
184
+ summary: `${u.usId} - ${u.title ?? ''}`,
185
+ className: 'doc-us-wrap',
186
+ status: u.status,
187
+ body: renderUserStory(u, {
188
+ raw: model.rawById.get(u.usId),
189
+ errors: model.errorsById.get(u.usId),
190
+ fbsByAcId: model.fbsByAcId,
191
+ }),
192
+ })).join('\n')}</section>`
193
+ : '';
194
+
195
+ return `${reqBlocks}\n${orphanBlock}`;
196
+ }
197
+
198
+ function renderArchitecturePanel(model) {
199
+ const tadChildren = model.tad ? (model.childrenByParent.get(model.tad.tadId) ?? []) : [];
200
+ const componentIds = tadChildren.filter((id) => id.startsWith('TAC-'));
201
+ const architecturalDecisionIds = tadChildren.filter((id) => id.startsWith('ADR-'));
202
+ const tadSection = model.tad
203
+ ? renderTad(model.tad, {
204
+ raw: model.rawById.get(model.tad.tadId),
205
+ errors: model.errorsById.get(model.tad.tadId),
206
+ componentIds,
207
+ architecturalDecisionIds,
208
+ })
209
+ : '<p><em>No TAD on disk.</em></p>';
210
+
211
+ const tacBlocks = model.tacs.map((t) => detailsWrap({
212
+ id: t.tacId,
213
+ summary: `${t.tacId} - ${t.name ?? ''}`,
214
+ className: 'doc-tac-wrap',
215
+ status: t.status,
216
+ body: renderTac(t, {
217
+ raw: model.rawById.get(t.tacId),
218
+ errors: model.errorsById.get(t.tacId),
219
+ }),
220
+ })).join('\n');
221
+
222
+ const adrBlocks = model.adrs.map((a) => detailsWrap({
223
+ id: a.adrId,
224
+ summary: `${a.adrId} - ${a.title ?? ''}`,
225
+ className: 'doc-adr-wrap',
226
+ status: a.status,
227
+ body: renderAdr(a, {
228
+ raw: model.rawById.get(a.adrId),
229
+ errors: model.errorsById.get(a.adrId),
230
+ }),
231
+ })).join('\n');
232
+
233
+ return `
234
+ ${tadSection}
235
+ <h3 class="group-heading">Components</h3>
236
+ ${tacBlocks || '<p><em>No TAC components on disk.</em></p>'}
237
+ <h3 class="group-heading">Architectural decisions</h3>
238
+ ${adrBlocks || '<p><em>No ADRs on disk.</em></p>'}
239
+ `;
240
+ }
241
+
242
+ function renderBuildPanel(model) {
243
+ const bsSlots = model.bs
244
+ ? [...model.fbsItems]
245
+ .filter((f) => f.bsId === model.bs.bsId)
246
+ .sort((a, b) => (a.buildOrder ?? 0) - (b.buildOrder ?? 0))
247
+ .map((f) => ({
248
+ fbsId: f.fbsId,
249
+ buildOrder: f.buildOrder,
250
+ executionStatus: f.executionStatus,
251
+ title: f.title,
252
+ }))
253
+ : [];
254
+ const bsSection = model.bs
255
+ ? renderBuildSequence(model.bs, {
256
+ raw: model.rawById.get(model.bs.bsId),
257
+ errors: model.errorsById.get(model.bs.bsId),
258
+ slots: bsSlots,
259
+ })
260
+ : '<p><em>No build sequence on disk.</em></p>';
261
+
262
+ const fbsBlocks = model.fbsItems.map((f) => detailsWrap({
263
+ id: f.fbsId,
264
+ summary: `${f.fbsId} - ${f.title ?? ''}`,
265
+ className: 'doc-fbs-wrap',
266
+ status: f.executionStatus,
267
+ body: renderFbs(f, {
268
+ raw: model.rawById.get(f.fbsId),
269
+ errors: model.errorsById.get(f.fbsId),
270
+ usByAcId: model.usByAcId,
271
+ }),
272
+ })).join('\n');
273
+
274
+ const tsBlocks = model.testSuites.map((ts) => detailsWrap({
275
+ id: ts.id,
276
+ summary: `${ts.id} - ${ts.title ?? 'test suite'}`,
277
+ className: 'doc-ts-wrap',
278
+ status: ts.status,
279
+ body: renderTestSuite(ts, {
280
+ raw: model.rawById.get(ts.id),
281
+ errors: model.errorsById.get(ts.id),
282
+ }),
283
+ })).join('\n');
284
+
285
+ const tsSection = tsBlocks
286
+ ? `<h3 class="group-heading">Test suites</h3>${tsBlocks}`
287
+ : '';
288
+
289
+ return `
290
+ ${bsSection}
291
+ <h3 class="group-heading">Functional Build Specifications</h3>
292
+ ${fbsBlocks || '<p><em>No FBS items on disk.</em></p>'}
293
+ ${tsSection}
294
+ `;
295
+ }
296
+
297
+ function renderErrorBanner(errors) {
298
+ if (!errors || errors.length === 0) return '';
299
+ const count = errors.length;
300
+ const noun = count === 1 ? 'error' : 'errors';
301
+ const items = errors.slice(0, 20).map((e) => {
302
+ const label = e.documentId ? `${e.documentId}` : e.filePath ?? '';
303
+ return `<li><strong>${escapeHtml(e.kind)}</strong> ${escapeHtml(label)} - ${escapeHtml(e.message)}</li>`;
304
+ }).join('');
305
+ const more = count > 20 ? `<p><em>... and ${count - 20} more</em></p>` : '';
306
+ return `<aside class="tree-errors" role="alert">
307
+ <h2>Tree has ${count} ${noun}</h2>
308
+ <ul>${items}</ul>
309
+ ${more}
310
+ </aside>`;
311
+ }
312
+
313
+ // Inline client-side script: initialises Mermaid, wires tab switching,
314
+ // resolves hash routes to a tab + opens any ancestor `<details>` on the
315
+ // target. Vanilla; no dependencies beyond the vendored Mermaid loaded above.
316
+ // Phase 3.8 note: this script is byte-identical to the Phase 3.6 shape
317
+ // (the layout-regression test guards it). Live-swap recovery lives
318
+ // entirely in `src/view/live-client.js`, which reads state from the DOM
319
+ // after each SSE innerHTML swap - it does not call into this IIFE.
320
+ function inlineScript() {
321
+ return `
322
+ (function () {
323
+ var TABS = ['overview', 'requirements', 'architecture', 'build'];
324
+
325
+ function initMermaid() {
326
+ if (typeof window.mermaid !== 'undefined') {
327
+ // startOnLoad: false. Mermaid can't lay out diagrams inside a
328
+ // display:none tabpanel (getBoundingClientRect is 0x0 -> NaN
329
+ // transforms). We run each tab's diagrams on activation instead;
330
+ // runMermaidIn is idempotent via the data-processed attribute.
331
+ window.mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' });
332
+ }
333
+ }
334
+
335
+ function runMermaidIn(container) {
336
+ if (!container || typeof window.mermaid === 'undefined') return;
337
+ var pending = container.querySelectorAll('.mermaid:not([data-processed="true"])');
338
+ if (pending.length === 0) return;
339
+ try {
340
+ window.mermaid.run({ nodes: Array.prototype.slice.call(pending) });
341
+ } catch (e) {
342
+ // Mermaid.run may throw on init errors; do not block the UI.
343
+ }
344
+ }
345
+
346
+ function tabButtons() {
347
+ return Array.prototype.slice.call(document.querySelectorAll('[role="tab"]'));
348
+ }
349
+
350
+ function panelFor(name) {
351
+ return document.getElementById('tab-' + name);
352
+ }
353
+
354
+ function activateTab(name) {
355
+ if (TABS.indexOf(name) === -1) return false;
356
+ tabButtons().forEach(function (btn) {
357
+ var isTarget = btn.getAttribute('data-tab') === name;
358
+ btn.setAttribute('aria-selected', isTarget ? 'true' : 'false');
359
+ });
360
+ TABS.forEach(function (t) {
361
+ var p = panelFor(t);
362
+ if (!p) return;
363
+ if (t === name) {
364
+ p.removeAttribute('hidden');
365
+ runMermaidIn(p);
366
+ } else {
367
+ p.setAttribute('hidden', '');
368
+ }
369
+ });
370
+ return true;
371
+ }
372
+
373
+ function tabForNode(node) {
374
+ var cur = node;
375
+ while (cur && cur !== document.body) {
376
+ if (cur.getAttribute && cur.getAttribute('role') === 'tabpanel') {
377
+ var id = cur.id || '';
378
+ if (id.indexOf('tab-') === 0) return id.slice(4);
379
+ }
380
+ cur = cur.parentNode;
381
+ }
382
+ return null;
383
+ }
384
+
385
+ function openAncestorDetails(node) {
386
+ var cur = node.parentNode;
387
+ while (cur && cur !== document.body) {
388
+ if (cur.tagName && cur.tagName.toLowerCase() === 'details') {
389
+ cur.open = true;
390
+ }
391
+ cur = cur.parentNode;
392
+ }
393
+ }
394
+
395
+ function findByDocId(id) {
396
+ if (!id) return null;
397
+ var byId = document.getElementById(id);
398
+ if (byId) return byId;
399
+ return document.querySelector('[data-doc-id="' + id.replace(/"/g, '\\\\"') + '"]');
400
+ }
401
+
402
+ function resolveHash(hash) {
403
+ if (!hash) {
404
+ activateTab('overview');
405
+ return;
406
+ }
407
+ var raw = hash.charAt(0) === '#' ? hash.slice(1) : hash;
408
+ if (raw.indexOf('tab=') === 0) {
409
+ activateTab(raw.slice(4));
410
+ return;
411
+ }
412
+ var target = findByDocId(raw);
413
+ if (!target) {
414
+ activateTab('overview');
415
+ return;
416
+ }
417
+ var tab = tabForNode(target);
418
+ if (tab) activateTab(tab);
419
+ openAncestorDetails(target);
420
+ if (target.tagName && target.tagName.toLowerCase() === 'details') {
421
+ target.open = true;
422
+ }
423
+ try {
424
+ target.scrollIntoView({ block: 'start' });
425
+ } catch (e) {
426
+ target.scrollIntoView();
427
+ }
428
+ }
429
+
430
+ function onTabClick(ev) {
431
+ var btn = ev.currentTarget;
432
+ var name = btn.getAttribute('data-tab');
433
+ if (!name) return;
434
+ activateTab(name);
435
+ if (window.history && typeof window.history.replaceState === 'function') {
436
+ window.history.replaceState(null, '', '#tab=' + name);
437
+ }
438
+ }
439
+
440
+ function wireTabs() {
441
+ tabButtons().forEach(function (btn) {
442
+ btn.addEventListener('click', onTabClick);
443
+ });
444
+ }
445
+
446
+ function onReady() {
447
+ initMermaid();
448
+ wireTabs();
449
+ resolveHash(window.location.hash);
450
+ window.addEventListener('hashchange', function () {
451
+ resolveHash(window.location.hash);
452
+ });
453
+ }
454
+
455
+ if (document.readyState === 'loading') {
456
+ document.addEventListener('DOMContentLoaded', onReady);
457
+ } else {
458
+ onReady();
459
+ }
460
+ })();
461
+ `.trim();
462
+ }
@@ -0,0 +1,63 @@
1
+ // View layer entry point. Locates the project root, walks the tree via the
2
+ // store, builds the render model and hands the server the pre-rendered
3
+ // HTML strings. Phase 3.8 removed the disk-write path (`.rcf-view/`
4
+ // convention retired wholesale); the view surface is now server-only.
5
+ // See specs/phase-3.8-live-view.md D9 for the CLI rewrite rationale.
6
+
7
+ import { dirname, join, resolve } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { stat } from 'node:fs/promises';
10
+
11
+ import { walkTree } from '#core/store';
12
+ import { renderContent, renderPage } from './html-page.js';
13
+ import { buildTreeModel } from './tree-model.js';
14
+
15
+ const here = dirname(fileURLToPath(import.meta.url));
16
+ export const VENDORED_MERMAID_PATH = resolve(here, 'vendored', 'mermaid.min.js');
17
+ export const STYLE_CSS_PATH = resolve(here, 'style.css');
18
+ export const LIVE_CLIENT_PATH = resolve(here, 'live-client.js');
19
+
20
+ /**
21
+ * Walk up from `start` looking for an ancestor directory containing a
22
+ * `rcf/manifest.json`. Returns the absolute directory path or null.
23
+ *
24
+ * @param {string} start - absolute directory to start from
25
+ * @returns {Promise<string | null>}
26
+ */
27
+ export async function findProjectRoot(start) {
28
+ let dir = resolve(start);
29
+ while (true) {
30
+ try {
31
+ const candidate = join(dir, 'rcf', 'manifest.json');
32
+ // eslint-disable-next-line no-await-in-loop
33
+ const s = await stat(candidate);
34
+ if (s.isFile()) return dir;
35
+ } catch (err) {
36
+ if (/** @type {NodeJS.ErrnoException} */ (err).code !== 'ENOENT') throw err;
37
+ }
38
+ const parent = dirname(dir);
39
+ if (parent === dir) return null;
40
+ dir = parent;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Walk the tree at `projectRoot`, build the render model and render both
46
+ * the full HTML page and the innerHTML of the swappable content wrapper.
47
+ *
48
+ * @param {object} args
49
+ * @param {string} args.projectRoot - absolute path to the project root
50
+ * @returns {Promise<{
51
+ * fullPageHtml: string,
52
+ * contentHtml: string,
53
+ * errors: import('#core/errors').RcfError[],
54
+ * tree: import('#core/store/walker.js').Tree,
55
+ * }>}
56
+ */
57
+ export async function renderModelToPage({ projectRoot }) {
58
+ const { tree, errors } = await walkTree({ projectRoot });
59
+ const model = buildTreeModel({ tree, errors });
60
+ const fullPageHtml = renderPage(model);
61
+ const contentHtml = renderContent(model);
62
+ return { fullPageHtml, contentHtml, errors, tree };
63
+ }