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,338 @@
1
+ // Live-view client. Loaded by the server-rendered page as
2
+ // `<script src="/live-client.js" defer>`. Handles the SSE `/events`
3
+ // stream, in-place `innerHTML` swaps into `#rcf-live-content`, and
4
+ // persists `<details>` open-set + main scrollTop to `localStorage`
5
+ // across every swap and every full reload.
6
+ //
7
+ // Vanilla classic script (no ESM syntax) so the base HTML script tag
8
+ // stays plain. The IIFE exposes its internals as `window.__rcfLiveClient`
9
+ // so `test/view/live-client.test.js` can drive them via `node:vm` without
10
+ // jsdom.
11
+ //
12
+ // Spec: projects/rcf-build-lite/specs/phase-3.8-live-view.md D12, D13a,
13
+ // D13b, D14.
14
+
15
+ (function () {
16
+ var STORAGE_NS = 'rcf-view:v1:';
17
+ var STORAGE_OPEN = STORAGE_NS + 'openDetails';
18
+ var STORAGE_SCROLL = STORAGE_NS + 'scrollTop';
19
+ var HEARTBEAT_STALE_MS = 45000;
20
+
21
+ // ---- state persistence ------------------------------------------------
22
+
23
+ function safeGetItem(storage, key) {
24
+ try { return storage.getItem(key); } catch (e) { return null; }
25
+ }
26
+
27
+ function safeSetItem(storage, key, value) {
28
+ try { storage.setItem(key, value); return true; } catch (e) { return false; }
29
+ }
30
+
31
+ function snapshotState(deps) {
32
+ var doc = deps.document;
33
+ var storage = deps.storage;
34
+ if (!doc || !storage) return;
35
+ try {
36
+ var open = [];
37
+ var nodes = doc.querySelectorAll('details[data-doc-id]');
38
+ for (var i = 0; i < nodes.length; i += 1) {
39
+ var el = nodes[i];
40
+ if (el.open) open.push(el.getAttribute('data-doc-id'));
41
+ }
42
+ safeSetItem(storage, STORAGE_OPEN, JSON.stringify(open));
43
+ } catch (e) { /* soft */ }
44
+ try {
45
+ var main = doc.querySelector('main');
46
+ var scrollTop = 0;
47
+ if (main && typeof main.scrollTop === 'number') scrollTop = main.scrollTop;
48
+ else if (deps.window && typeof deps.window.scrollY === 'number') scrollTop = deps.window.scrollY;
49
+ safeSetItem(storage, STORAGE_SCROLL, String(scrollTop));
50
+ } catch (e) { /* soft */ }
51
+ }
52
+
53
+ function loadOpenSet(storage) {
54
+ var raw = safeGetItem(storage, STORAGE_OPEN);
55
+ if (!raw) return [];
56
+ try {
57
+ var parsed = JSON.parse(raw);
58
+ if (Array.isArray(parsed)) return parsed.filter(function (id) { return typeof id === 'string'; });
59
+ } catch (e) { /* soft */ }
60
+ return [];
61
+ }
62
+
63
+ function cssEscape(id) {
64
+ return String(id).replace(/["\\]/g, function (c) { return '\\' + c; });
65
+ }
66
+
67
+ function restoreState(deps) {
68
+ var doc = deps.document;
69
+ var storage = deps.storage;
70
+ if (!doc || !storage) return { openedCount: 0, droppedIds: [], scrollTop: null };
71
+ var wanted = loadOpenSet(storage);
72
+ var opened = 0;
73
+ var dropped = [];
74
+ for (var i = 0; i < wanted.length; i += 1) {
75
+ var id = wanted[i];
76
+ var sel = 'details[data-doc-id="' + cssEscape(id) + '"]';
77
+ var el = null;
78
+ try { el = doc.querySelector(sel); } catch (e) { el = null; }
79
+ if (el) {
80
+ el.open = true;
81
+ opened += 1;
82
+ } else {
83
+ dropped.push(id);
84
+ }
85
+ }
86
+ if (dropped.length > 0) {
87
+ // Stale-key hygiene: rewrite the persisted set with only the ids
88
+ // that still exist. Silent - no console output for expected drift.
89
+ var kept = wanted.filter(function (id) { return dropped.indexOf(id) === -1; });
90
+ safeSetItem(storage, STORAGE_OPEN, JSON.stringify(kept));
91
+ }
92
+
93
+ var scrollTop = null;
94
+ var raw = safeGetItem(storage, STORAGE_SCROLL);
95
+ if (raw != null) {
96
+ var n = Number(raw);
97
+ if (isFinite(n)) {
98
+ scrollTop = n;
99
+ var main = doc.querySelector('main');
100
+ try {
101
+ if (main) main.scrollTop = n;
102
+ else if (deps.window && typeof deps.window.scrollTo === 'function') deps.window.scrollTo(0, n);
103
+ } catch (e) { /* soft */ }
104
+ }
105
+ }
106
+ return { openedCount: opened, droppedIds: dropped, scrollTop: scrollTop };
107
+ }
108
+
109
+ // ---- connection state machine -----------------------------------------
110
+
111
+ // Inputs: readyState of EventSource (CONNECTING=0, OPEN=1, CLOSED=2),
112
+ // ms since last heartbeat (any type of event resets this). Output is
113
+ // one of 'connected' | 'reconnecting' | 'disconnected'.
114
+ function classifyConnection(state) {
115
+ var rs = state.readyState;
116
+ var since = state.msSinceLastEvent;
117
+ if (rs === 2) return 'disconnected';
118
+ if (rs === 0) return 'reconnecting';
119
+ if (typeof since === 'number' && since > HEARTBEAT_STALE_MS) return 'reconnecting';
120
+ return 'connected';
121
+ }
122
+
123
+ // ---- version de-dup ---------------------------------------------------
124
+
125
+ function shouldApplyUpdate(currentVersion, nextVersion) {
126
+ if (typeof nextVersion !== 'number' || !isFinite(nextVersion)) return true;
127
+ if (currentVersion === null || currentVersion === undefined) return true;
128
+ return nextVersion > currentVersion;
129
+ }
130
+
131
+ // ---- browser boot ------------------------------------------------------
132
+
133
+ function bootBrowser(win) {
134
+ var doc = win.document;
135
+ var storage;
136
+ try { storage = win.localStorage; } catch (e) { storage = null; }
137
+ var deps = { document: doc, storage: storage || memoryStorage(), window: win };
138
+
139
+ // Inject the connection-status dot into the header. Kept out of the
140
+ // server-rendered HTML so the layout-regression baseline stays clean
141
+ // (only the wrapper + script tag + raw-json data-doc-id are the
142
+ // whitelisted deltas from Phase 3.6).
143
+ injectConnectionDot(doc);
144
+
145
+ // Restore prior state on initial load.
146
+ if (doc.readyState === 'loading') {
147
+ doc.addEventListener('DOMContentLoaded', function () { restoreState(deps); });
148
+ } else {
149
+ restoreState(deps);
150
+ }
151
+
152
+ var currentVersion = null;
153
+ var lastEventAt = Date.now();
154
+ var connState = 'reconnecting';
155
+
156
+ function setState(next) {
157
+ if (next === connState) return;
158
+ connState = next;
159
+ updateDot(doc, next);
160
+ }
161
+
162
+ // Snapshot on navigate-away / hide.
163
+ win.addEventListener('beforeunload', function () { snapshotState(deps); });
164
+ doc.addEventListener('visibilitychange', function () {
165
+ if (doc.visibilityState === 'hidden') snapshotState(deps);
166
+ });
167
+
168
+ var EventSourceCtor = win.EventSource;
169
+ if (typeof EventSourceCtor !== 'function') {
170
+ setState('disconnected');
171
+ return;
172
+ }
173
+ var source = new EventSourceCtor('/events');
174
+
175
+ source.onopen = function () {
176
+ lastEventAt = Date.now();
177
+ setState('connected');
178
+ };
179
+ source.onerror = function () {
180
+ // EventSource sets readyState internally. Native reconnect keeps
181
+ // trying while readyState is CONNECTING.
182
+ var rs = typeof source.readyState === 'number' ? source.readyState : 2;
183
+ setState(classifyConnection({ readyState: rs, msSinceLastEvent: Date.now() - lastEventAt }));
184
+ };
185
+
186
+ source.addEventListener('tree-update', function (ev) {
187
+ lastEventAt = Date.now();
188
+ var payload;
189
+ try { payload = JSON.parse(ev.data); } catch (e) { return; }
190
+ if (!shouldApplyUpdate(currentVersion, payload.version)) return;
191
+ snapshotState(deps);
192
+ var host = doc.querySelector('#rcf-live-content');
193
+ if (host && typeof payload.contentHtml === 'string') {
194
+ host.innerHTML = payload.contentHtml;
195
+ }
196
+ currentVersion = payload.version;
197
+ // Tab buttons live in the header (outside the swap wrapper) so
198
+ // their click handlers survive. But the swap serves every panel
199
+ // with the same `hidden` state Phase 3.6 renders (Overview
200
+ // visible, others hidden), so we resync visibility with the
201
+ // currently-selected tab and re-run Mermaid in it. Also re-run
202
+ // Mermaid inside the selected panel because .mermaid nodes in
203
+ // the payload have no `data-processed` yet.
204
+ resyncTabsAfterSwap(doc, win);
205
+ restoreState(deps);
206
+ setState('connected');
207
+ });
208
+
209
+ source.addEventListener('heartbeat', function () {
210
+ lastEventAt = Date.now();
211
+ setState('connected');
212
+ });
213
+
214
+ source.addEventListener('walker-error', function (ev) {
215
+ lastEventAt = Date.now();
216
+ // Errors also render inline via the next tree-update; nothing
217
+ // else to do client-side for now.
218
+ try {
219
+ var payload = JSON.parse(ev.data);
220
+ if (win.rcfPage) win.rcfPage._lastWalkerErrors = payload.errors;
221
+ } catch (e) { /* soft */ }
222
+ });
223
+
224
+ source.addEventListener('shutdown', function () {
225
+ setState('disconnected');
226
+ try { source.close(); } catch (e) { /* soft */ }
227
+ });
228
+
229
+ // Idle liveness check: if no event in the heartbeat-stale window,
230
+ // demote the dot to reconnecting even if the socket has not thrown
231
+ // an error yet.
232
+ var pollId = win.setInterval(function () {
233
+ var rs = typeof source.readyState === 'number' ? source.readyState : 1;
234
+ setState(classifyConnection({ readyState: rs, msSinceLastEvent: Date.now() - lastEventAt }));
235
+ }, 5000);
236
+ if (pollId && typeof pollId.unref === 'function') pollId.unref();
237
+ }
238
+
239
+ // After an SSE innerHTML swap, the tab bar in the header still shows
240
+ // the tab the user was on (its click handlers survived because the
241
+ // header was not replaced) but every freshly-inserted panel has the
242
+ // Overview-visible default from the server-rendered content. Bring
243
+ // the panels back in line with the aria-selected button and re-run
244
+ // Mermaid in the visible one.
245
+ function resyncTabsAfterSwap(doc, win) {
246
+ var TABS = ['overview', 'requirements', 'architecture', 'build'];
247
+ var selectedBtn = doc.querySelector('[role="tab"][aria-selected="true"]');
248
+ var selected = selectedBtn && selectedBtn.getAttribute ? selectedBtn.getAttribute('data-tab') : null;
249
+ if (!selected || TABS.indexOf(selected) === -1) selected = 'overview';
250
+ for (var i = 0; i < TABS.length; i += 1) {
251
+ var name = TABS[i];
252
+ var panel = doc.getElementById('tab-' + name);
253
+ if (!panel) continue;
254
+ if (name === selected) panel.removeAttribute('hidden');
255
+ else panel.setAttribute('hidden', '');
256
+ }
257
+ var visible = doc.getElementById('tab-' + selected);
258
+ try {
259
+ if (visible && win && win.mermaid && typeof win.mermaid.run === 'function') {
260
+ var pending = visible.querySelectorAll('.mermaid:not([data-processed="true"])');
261
+ if (pending.length > 0) {
262
+ win.mermaid.run({ nodes: Array.prototype.slice.call(pending) });
263
+ }
264
+ }
265
+ } catch (e) { /* soft */ }
266
+ }
267
+
268
+ function memoryStorage() {
269
+ var m = Object.create(null);
270
+ return {
271
+ getItem: function (k) { return Object.prototype.hasOwnProperty.call(m, k) ? m[k] : null; },
272
+ setItem: function (k, v) { m[k] = String(v); },
273
+ removeItem: function (k) { delete m[k]; },
274
+ };
275
+ }
276
+
277
+ function injectConnectionDot(doc) {
278
+ if (doc.getElementById('rcf-conn-dot')) return;
279
+ var style = doc.createElement('style');
280
+ style.textContent = [
281
+ '#rcf-conn-dot {',
282
+ ' position: fixed; top: 12px; right: 12px;',
283
+ ' width: 10px; height: 10px; border-radius: 50%;',
284
+ ' background: #999; opacity: 0.65; z-index: 9999;',
285
+ ' transition: background 120ms ease-in-out;',
286
+ '}',
287
+ '#rcf-conn-dot.connected { background: #4c9a2a; }',
288
+ '#rcf-conn-dot.reconnecting { background: #d68f00; }',
289
+ '#rcf-conn-dot.disconnected { background: #b03a2e; }',
290
+ ].join('\n');
291
+ doc.head.appendChild(style);
292
+ var dot = doc.createElement('span');
293
+ dot.id = 'rcf-conn-dot';
294
+ dot.className = 'reconnecting';
295
+ dot.setAttribute('role', 'status');
296
+ dot.setAttribute('aria-live', 'polite');
297
+ dot.title = 'Connecting to view server...';
298
+ doc.body.appendChild(dot);
299
+ }
300
+
301
+ function updateDot(doc, state) {
302
+ var dot = doc.getElementById('rcf-conn-dot');
303
+ if (!dot) return;
304
+ dot.className = state;
305
+ if (state === 'connected') dot.title = 'Connected - live updates on';
306
+ else if (state === 'reconnecting') dot.title = 'Reconnecting to view server...';
307
+ else dot.title = 'Disconnected from view server';
308
+ }
309
+
310
+ // ---- module exports (for node:vm tests) --------------------------------
311
+
312
+ var api = {
313
+ snapshotState: snapshotState,
314
+ restoreState: restoreState,
315
+ classifyConnection: classifyConnection,
316
+ shouldApplyUpdate: shouldApplyUpdate,
317
+ loadOpenSet: loadOpenSet,
318
+ memoryStorage: memoryStorage,
319
+ injectConnectionDot: injectConnectionDot,
320
+ updateDot: updateDot,
321
+ resyncTabsAfterSwap: resyncTabsAfterSwap,
322
+ STORAGE_OPEN: STORAGE_OPEN,
323
+ STORAGE_SCROLL: STORAGE_SCROLL,
324
+ HEARTBEAT_STALE_MS: HEARTBEAT_STALE_MS,
325
+ bootBrowser: bootBrowser,
326
+ };
327
+
328
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
329
+ window.__rcfLiveClient = api;
330
+ try { bootBrowser(window); } catch (e) {
331
+ // Never let a client-side boot fault break the page.
332
+ try { window.console && window.console.error && window.console.error('[rcf-live-client]', e); } catch (_e) { /* soft */ }
333
+ }
334
+ } else if (typeof globalThis !== 'undefined') {
335
+ // Non-browser context (e.g. node:vm test harness with no window/document).
336
+ globalThis.__rcfLiveClient = api;
337
+ }
338
+ })();
@@ -0,0 +1,178 @@
1
+ // Mermaid diagram emission. Phase 3.6 removed the top-of-Overview-tab
2
+ // diagram: at ~7 REQs it was already stacking vertically and the PRD
3
+ // body already lists the requirement links inline. Per-requirement
4
+ // subdiagrams (REQ -> US -> AC + FBS delivery) remain - they add
5
+ // actual structural information per REQ. Click bindings anchor into
6
+ // the hash-routed doc DOM (D5, D7).
7
+ //
8
+ // Node classes match the CSS palette in style.css. Broken nodes carry the
9
+ // `broken` class (dashed border, distinct colour). FBS-to-AC edges are
10
+ // dashed; FBS dependencies are dotted; FBS context (TAC / ADR) edges are
11
+ // dotted-thick.
12
+
13
+ const NODE_LABEL = {
14
+ prd: 'PRD',
15
+ req: 'REQ',
16
+ us: 'US',
17
+ ac: 'AC',
18
+ tad: 'TAD',
19
+ tac: 'TAC',
20
+ adr: 'ADR',
21
+ bs: 'BS',
22
+ fbs: 'FBS',
23
+ };
24
+
25
+ /**
26
+ * Mermaid is strict about id syntax: `-` is fine but parentheses, quotes
27
+ * and unicode chars are not. Document ids are already safe (e.g. "REQ-002");
28
+ * we just wrap labels in quotes.
29
+ *
30
+ * @param {string} id
31
+ * @returns {string}
32
+ */
33
+ function nodeId(id) {
34
+ return id;
35
+ }
36
+
37
+ function nodeLabel(id, model) {
38
+ const doc = model.byId.get(id);
39
+ const title = pickTitle(doc, id);
40
+ if (!title) return `"${id}"`;
41
+ const safe = title.replace(/"/g, "'").replace(/\n/g, ' ');
42
+ const trimmed = safe.length > 48 ? `${safe.slice(0, 45)}...` : safe;
43
+ return `"${id}<br/>${trimmed}"`;
44
+ }
45
+
46
+ function pickTitle(doc, id) {
47
+ if (!doc) return null;
48
+ if (typeof doc.title === 'string') return doc.title;
49
+ if (typeof doc.name === 'string') return doc.name;
50
+ if (typeof doc.productName === 'string') return doc.productName;
51
+ // Phase 10 (X2 CodeNode bridge): CN documents often carry no title, but
52
+ // `path` is the identity that matters visually - show it in preference to
53
+ // falling straight back to the bare id.
54
+ if (typeof doc.path === 'string') return doc.path;
55
+ if (typeof doc.description === 'string') return doc.description;
56
+ return id;
57
+ }
58
+
59
+ function classForId(id) {
60
+ if (id.startsWith('PRD-')) return 'prd';
61
+ if (id.startsWith('REQ-')) return 'req';
62
+ if (id.startsWith('US-')) return 'us';
63
+ if (id.startsWith('AC-')) return 'ac';
64
+ if (id.startsWith('TAD-')) return 'tad';
65
+ if (id.startsWith('TAC-')) return 'tac';
66
+ if (id.startsWith('ADR-')) return 'adr';
67
+ if (id.startsWith('BS-')) return 'bs';
68
+ if (id.startsWith('FBS-')) return 'fbs';
69
+ if (id.startsWith('TS-')) return 'ts';
70
+ if (id.startsWith('TC-')) return 'tc';
71
+ // Phase 10 (X2 CodeNode bridge, spec D12): CN cosmetic class.
72
+ if (id.startsWith('CN-')) return 'cn';
73
+ return 'unknown';
74
+ }
75
+
76
+ const CLASS_DEFS = `
77
+ classDef prd fill:#fde68a,stroke:#92400e,color:#1f2937;
78
+ classDef req fill:#bbf7d0,stroke:#065f46,color:#1f2937;
79
+ classDef us fill:#bae6fd,stroke:#0c4a6e,color:#1f2937;
80
+ classDef ac fill:#e0e7ff,stroke:#3730a3,color:#1f2937;
81
+ classDef tad fill:#fecaca,stroke:#991b1b,color:#1f2937;
82
+ classDef tac fill:#fed7aa,stroke:#9a3412,color:#1f2937;
83
+ classDef adr fill:#fbcfe8,stroke:#9d174d,color:#1f2937;
84
+ classDef bs fill:#ddd6fe,stroke:#5b21b6,color:#1f2937;
85
+ classDef fbs fill:#c7d2fe,stroke:#3730a3,color:#1f2937;
86
+ classDef ts fill:#99f6e4,stroke:#115e59,color:#1f2937;
87
+ classDef tc fill:#d9f99d,stroke:#3f6212,color:#1f2937;
88
+ classDef cn fill:#a5b4fc,stroke:#312e81,color:#1f2937;
89
+ classDef broken stroke:#dc2626,stroke-width:2px,stroke-dasharray:5 5,color:#7f1d1d;
90
+ `.trim();
91
+
92
+ function emitClick(id) {
93
+ return ` click ${nodeId(id)} "#${id}";`;
94
+ }
95
+
96
+ function emitClassAssignments(ids, model) {
97
+ const lines = [];
98
+ for (const id of ids) {
99
+ const cls = classForId(id);
100
+ if (cls !== 'unknown') {
101
+ lines.push(` class ${nodeId(id)} ${cls};`);
102
+ }
103
+ if (model.brokenIds.has(id)) {
104
+ lines.push(` class ${nodeId(id)} broken;`);
105
+ }
106
+ }
107
+ return lines;
108
+ }
109
+
110
+ /**
111
+ * Build a per-REQ subdiagram (`flowchart LR`).
112
+ *
113
+ * @param {import('./tree-model.js').BuiltTreeModel} model
114
+ * @param {object} req - the requirement document
115
+ * @returns {string}
116
+ */
117
+ export function requirementSubdiagram(model, req) {
118
+ if (!req?.reqId) return 'flowchart LR\n empty[No requirement provided]';
119
+ /** @type {string[]} */
120
+ const lines = ['flowchart LR'];
121
+ /** @type {Set<string>} */
122
+ const declared = new Set();
123
+ /** @type {Set<string>} */
124
+ const allIds = new Set();
125
+ const declare = (id) => {
126
+ if (declared.has(id)) return;
127
+ declared.add(id);
128
+ allIds.add(id);
129
+ lines.push(` ${nodeId(id)}[${nodeLabel(id, model)}]`);
130
+ };
131
+
132
+ declare(req.reqId);
133
+ const stories = model.storiesByReqId.get(req.reqId) ?? [];
134
+ for (const us of stories) {
135
+ declare(us.usId);
136
+ lines.push(` ${nodeId(req.reqId)} --> ${nodeId(us.usId)}`);
137
+ for (const ac of us.acceptanceCriteria ?? []) {
138
+ declare(ac.id);
139
+ lines.push(` ${nodeId(us.usId)} --> ${nodeId(ac.id)}`);
140
+ const fbsList = model.fbsByAcId.get(ac.id) ?? [];
141
+ for (const f of fbsList) {
142
+ declare(f.fbsId);
143
+ // Edge direction reversed so LR layout places FBS to the right of
144
+ // its AC (reads "AC ... delivered by ... FBS"). Same relationship
145
+ // as the old FBS->AC edge; just laid out more naturally.
146
+ lines.push(` ${nodeId(ac.id)} -.->|delivered by| ${nodeId(f.fbsId)}`);
147
+ }
148
+ // Phase 10 (X2 CodeNode bridge, spec D12): Code Nodes implementing
149
+ // this AC. Empty when no code-nodes/ dir exists, so a spec-only tree
150
+ // renders byte-identical to pre-Phase-10 output.
151
+ const cnList = model.cnByAcId?.get(ac.id) ?? [];
152
+ for (const cnId of cnList) {
153
+ declare(cnId);
154
+ lines.push(` ${nodeId(ac.id)} -.->|implemented by| ${nodeId(cnId)}`);
155
+ }
156
+ }
157
+ }
158
+
159
+ for (const id of allIds) lines.push(emitClick(id));
160
+ lines.push(...emitClassAssignments([...allIds], model));
161
+ lines.push(CLASS_DEFS);
162
+ return lines.join('\n');
163
+ }
164
+
165
+ /**
166
+ * Build every per-REQ subdiagram, keyed by reqId.
167
+ *
168
+ * @param {import('./tree-model.js').BuiltTreeModel} model
169
+ * @returns {Map<string, string>}
170
+ */
171
+ export function allRequirementSubdiagrams(model) {
172
+ const out = new Map();
173
+ for (const req of model.requirements) {
174
+ if (!req?.reqId) continue;
175
+ out.set(req.reqId, requirementSubdiagram(model, req));
176
+ }
177
+ return out;
178
+ }