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,171 @@
1
+ // Browser-verify runner (ui-design-gate-0.7.0-spec §8.2).
2
+ //
3
+ // Two modes:
4
+ // - `operatorSession`: the operator loaded the app and cleared the
5
+ // pages themselves; the runner takes a minimal input (the routes
6
+ // they visited, the invariants they ticked) and writes the record.
7
+ // - `agentScreenshotCritique`: the runner drives a browser (injected)
8
+ // over every enumerated route × theme, captures a DOM string per
9
+ // capture, runs the versioned invariant set (§8.3), runs the auth
10
+ // smoke pack when the FBS binds an auth REQ, aggregates the record
11
+ // verdict per §8.5.
12
+ //
13
+ // Everything driver-shaped is injectable (browser driver, fetch,
14
+ // artefact writer) so unit tests do not need a live Playwright and CI
15
+ // can run this without a real browser. The default agent-mode driver
16
+ // is a stub that returns an empty capture and a `verdict: warn` record
17
+ // with a note pointing at "no driver wired"; production wiring lands
18
+ // via `deps.browserDriver` on the CLI handler.
19
+
20
+ import { runInvariantsForCapture, foldInvariantsForRecord, compareTopLevelStructure } from './invariants.js';
21
+ import { runAuthSmokeChecks, shouldRunAuthSmokeChecks } from './auth-smoke.js';
22
+ import { composeBrowserVerificationRecord } from './manifest-writer.js';
23
+
24
+ /**
25
+ * @typedef {object} BrowserCapture
26
+ * @property {string} routePath
27
+ * @property {'light'|'dark'} themeApplied
28
+ * @property {string} dom
29
+ * @property {string} screenshotPath
30
+ * @property {boolean} [authenticated]
31
+ */
32
+
33
+ /**
34
+ * @typedef {object} BrowserDriver
35
+ * @property {(args: { url: string, routes: object[], themes: ('light'|'dark')[], artefactDir: string }) => Promise<BrowserCapture[]>} capture
36
+ */
37
+
38
+ /**
39
+ * Default agent-mode driver stub. Emits no captures; a warning surfaces
40
+ * on the record notes. Callers wire a real Playwright driver via
41
+ * `deps.browserDriver` on the CLI.
42
+ *
43
+ * @type {BrowserDriver}
44
+ */
45
+ export const stubBrowserDriver = Object.freeze({
46
+ async capture() { return []; },
47
+ });
48
+
49
+ /**
50
+ * Run the agent-screenshot-critique mode against a FBS.
51
+ *
52
+ * @param {object} args
53
+ * @param {import('#core/store/walker.js').TreeModel} args.tree
54
+ * @param {object} args.fbs
55
+ * @param {string} args.runtimeUrl
56
+ * @param {'deployed'|'ci'|'local-dev'} args.runtimeProfile
57
+ * @param {BrowserDriver} args.browserDriver
58
+ * @param {(url: string, init?: object) => Promise<any>} args.fetch
59
+ * @param {string} args.artefactDir
60
+ * @param {string} [args.critiqueNotes] agent-critique rubric findings (spec §8.4)
61
+ * @param {Date} [args.now]
62
+ * @returns {Promise<object>} the composed browserVerification record
63
+ */
64
+ export async function runAgentScreenshotCritique({
65
+ tree, fbs, runtimeUrl, runtimeProfile, browserDriver, fetch, artefactDir, critiqueNotes, now = new Date(),
66
+ }) {
67
+ const routes = fbs?.designStage?.navModel?.routes ?? [];
68
+ const themes = readThemesFromBaseline(tree.manifest?.uiBaseline);
69
+ const captures = await browserDriver.capture({ url: runtimeUrl, routes, themes, artefactDir });
70
+
71
+ const perCapture = [];
72
+ const routesChecked = [];
73
+ for (const cap of captures) {
74
+ const results = runInvariantsForCapture({
75
+ routePath: cap.routePath,
76
+ themeApplied: cap.themeApplied,
77
+ fbs,
78
+ uiBaseline: tree.manifest?.uiBaseline,
79
+ dom: cap.dom,
80
+ authenticated: cap.authenticated,
81
+ });
82
+ perCapture.push({ routePath: cap.routePath, themeApplied: cap.themeApplied, results });
83
+ routesChecked.push({ path: cap.routePath, screenshotPath: cap.screenshotPath, themeApplied: cap.themeApplied });
84
+ }
85
+
86
+ // Structural cross-route compare for `sharedLayoutModule`.
87
+ const structuralResult = compareTopLevelStructure(
88
+ captures.filter((c) => c.themeApplied === 'light'),
89
+ tree.manifest?.uiBaseline,
90
+ );
91
+
92
+ let invariantChecks = foldInvariantsForRecord(perCapture);
93
+ // Replace / append the structural sharedLayoutModule result.
94
+ invariantChecks = invariantChecks.filter((c) => c.invariant !== 'sharedLayoutModule');
95
+ invariantChecks.push({
96
+ invariant: structuralResult.invariant,
97
+ verdict: structuralResult.verdict,
98
+ ...(structuralResult.detail ? { detail: structuralResult.detail } : {}),
99
+ severity: structuralResult.severity,
100
+ });
101
+
102
+ // If no captures came back at all, record a synthetic warn so the
103
+ // operator sees the "driver was not wired" state distinctly from a
104
+ // clean pass.
105
+ let notes = critiqueNotes;
106
+ if (captures.length === 0) {
107
+ invariantChecks.push({
108
+ invariant: 'agentDriverWired',
109
+ verdict: 'fail',
110
+ detail: 'browser driver returned zero captures; wire a Playwright driver via deps.browserDriver or run in --mode operatorSession',
111
+ severity: 'warn',
112
+ });
113
+ notes = notes ? `${notes}\n\nDriver: no captures produced.` : 'Driver: no captures produced.';
114
+ // Ensure schema minimums are met even without captures.
115
+ if (routesChecked.length === 0) {
116
+ routesChecked.push({ path: routes[0]?.path ?? '/', screenshotPath: `${artefactDir}/placeholder.png`, themeApplied: 'light' });
117
+ }
118
+ }
119
+
120
+ let authSmokeChecks;
121
+ if (shouldRunAuthSmokeChecks(fbs, tree.manifest?.uiBaseline)) {
122
+ authSmokeChecks = await runAuthSmokeChecks({ fetch, runtimeUrl });
123
+ }
124
+
125
+ return composeBrowserVerificationRecord({
126
+ manifest: tree.manifest,
127
+ fbsId: fbs.fbsId,
128
+ mode: 'agentScreenshotCritique',
129
+ runtimeProfile,
130
+ runtimeUrl,
131
+ routesChecked,
132
+ invariantChecks,
133
+ authSmokeChecks,
134
+ notes,
135
+ now,
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Compose the operator-session record. No captures; the operator's
141
+ * declared list of routes plus their invariant ticks are the evidence.
142
+ *
143
+ * @param {object} args
144
+ * @returns {object}
145
+ */
146
+ export function composeOperatorSessionRecord({
147
+ tree, fbs, runtimeUrl, runtimeProfile, declaredRoutes, invariantTicks, now = new Date(),
148
+ }) {
149
+ const routesChecked = declaredRoutes.length > 0
150
+ ? declaredRoutes.map((r) => ({ path: r.path, screenshotPath: `.rcf/artefacts/operator-session/${fbs.fbsId}${r.path.replace(/\W+/g, '_')}.png`, themeApplied: r.themeApplied ?? 'light' }))
151
+ : [{ path: fbs?.designStage?.navModel?.routes?.[0]?.path ?? '/', screenshotPath: `.rcf/artefacts/operator-session/${fbs.fbsId}-declared.png`, themeApplied: 'light' }];
152
+ const invariantChecks = invariantTicks && invariantTicks.length > 0
153
+ ? invariantTicks.map((t) => ({ invariant: t.invariant, verdict: t.verdict ?? 'pass', ...(t.detail ? { detail: t.detail } : {}), severity: t.severity ?? 'block' }))
154
+ : [{ invariant: 'operatorAcknowledged', verdict: 'pass', severity: 'block' }];
155
+ return composeBrowserVerificationRecord({
156
+ manifest: tree.manifest,
157
+ fbsId: fbs.fbsId,
158
+ mode: 'operatorSession',
159
+ runtimeProfile,
160
+ runtimeUrl,
161
+ routesChecked,
162
+ invariantChecks,
163
+ now,
164
+ });
165
+ }
166
+
167
+ function readThemesFromBaseline(baseline) {
168
+ const mode = baseline?.defaults?.themeMode;
169
+ if (mode === 'single-theme-declared') return ['light'];
170
+ return ['light', 'dark'];
171
+ }
@@ -0,0 +1,198 @@
1
+ // Spec-bundle assembly (Phase 6 §D3). Pure projection of one FBS item
2
+ // and its surroundings into the seven-section bundle shape: header,
3
+ // queue and dependency context, the work, acceptance criteria with
4
+ // US/REQ ancestry, architectural context, existing test surface, and
5
+ // the build-cycle runbook parameters. No I/O, no wall clock, no
6
+ // semantic judgement (§D13): the bundle reports what the tree says.
7
+ //
8
+ // Ordering rules (§D10): acIds and every contextRequirements list
9
+ // render in authored order; dependency and dependent lists sort by
10
+ // buildOrder then fbsId; US groups order by first appearance in the
11
+ // authored acIds.
12
+
13
+ import { byBuildOrder, computeQueue } from './queue.js';
14
+
15
+ /**
16
+ * Copy `fields` from `source` onto a new object, skipping fields not
17
+ * present. Preserves the given key order (JSON key order is
18
+ * construction order, §D10).
19
+ *
20
+ * @param {object} source
21
+ * @param {string[]} fields
22
+ * @returns {object}
23
+ */
24
+ function pick(source, fields) {
25
+ const out = {};
26
+ for (const f of fields) {
27
+ if (source?.[f] !== undefined) out[f] = source[f];
28
+ }
29
+ return out;
30
+ }
31
+
32
+ /**
33
+ * Assemble the bundle for one FBS item. The caller (handler) is
34
+ * responsible for id classification; this function returns null when
35
+ * the id does not resolve to an FBS document.
36
+ *
37
+ * @param {import('#core/store/walker.js').TreeModel} tree
38
+ * @param {object} opts
39
+ * @param {string} opts.fbsId
40
+ * @returns {object|null} BundleResult
41
+ */
42
+ export function assembleBundle(tree, { fbsId }) {
43
+ const fbs = tree.byId.get(fbsId);
44
+ if (!fbs || tree.kindById.get(fbsId) !== 'fbs') return null;
45
+
46
+ const queue = computeQueue(tree);
47
+ const position = queue.items.findIndex((i) => i.fbsId === fbsId) + 1;
48
+ const queueItem = queue.items[position - 1];
49
+ const fbsById = new Map((tree.fbsItems ?? []).map((f) => [f.fbsId, f]));
50
+
51
+ // Section 1 - header identity (order follows §D14 fbs shape, then
52
+ // the optional estimate fields and the header's "spec last touched").
53
+ const fbsBlock = pick(fbs, [
54
+ 'fbsId', 'title', 'buildOrder', 'executionStatus',
55
+ 'summary', 'approach', 'deliverables', 'notes',
56
+ 'estimatedSize', 'estimatedHours', 'riskLevel', 'domain', 'updatedAt',
57
+ ]);
58
+
59
+ // Section 2 - queue and dependency context.
60
+ const dependencies = (fbs.dependsOnFbsIds ?? [])
61
+ .map((depId) => fbsById.get(depId) ?? { fbsId: depId })
62
+ .sort(byBuildOrder)
63
+ .map((dep) => pick(dep, ['fbsId', 'title', 'executionStatus']));
64
+ const dependents = (tree.dependentsByFbsId.get(fbsId) ?? [])
65
+ .map((depId) => fbsById.get(depId) ?? { fbsId: depId })
66
+ .sort(byBuildOrder)
67
+ .map((dep) => dep.fbsId);
68
+ const blockedBy = queueItem ? queueItem.blockedBy : [];
69
+
70
+ // Section 4 - acceptance criteria with US/REQ ancestry. Groups order
71
+ // by first appearance in the authored acIds; ACs within a group keep
72
+ // the authored acIds order.
73
+ const usOrder = [];
74
+ const acsByUs = new Map();
75
+ for (const acId of fbs.acIds ?? []) {
76
+ const usId = tree.parentByChild.get(acId);
77
+ const us = usId ? tree.byId.get(usId) : null;
78
+ if (!us) continue; // Broken cross-links cannot occur post-walker (§D6).
79
+ if (!acsByUs.has(usId)) {
80
+ usOrder.push(usId);
81
+ acsByUs.set(usId, []);
82
+ }
83
+ const ac = (us.acceptanceCriteria ?? []).find((a) => a.id === acId);
84
+ if (ac) acsByUs.get(usId).push(ac);
85
+ }
86
+
87
+ const acceptanceCriteria = [];
88
+ const userStories = [];
89
+ const requirements = [];
90
+ const seenReqs = new Set();
91
+ for (const usId of usOrder) {
92
+ const us = tree.byId.get(usId);
93
+ userStories.push(pick(us, ['usId', 'title', 'asA', 'iWant', 'soThat', 'status']));
94
+ const req = tree.byId.get(us.reqId);
95
+ if (req && !seenReqs.has(req.reqId)) {
96
+ seenReqs.add(req.reqId);
97
+ requirements.push(pick(req, ['reqId', 'title', 'description', 'category', 'priority', 'rationale']));
98
+ }
99
+ for (const ac of acsByUs.get(usId)) {
100
+ acceptanceCriteria.push({
101
+ ...pick(ac, ['id', 'description', 'given', 'when', 'then', 'testable']),
102
+ usId,
103
+ reqId: us.reqId,
104
+ });
105
+ }
106
+ }
107
+
108
+ // Section 5 - architectural context (§D11). Omitted entirely when
109
+ // contextRequirements is absent.
110
+ let context;
111
+ const ctx = fbs.contextRequirements;
112
+ if (ctx) {
113
+ const tacs = (ctx.tacIds ?? [])
114
+ .map((id) => tree.byId.get(id))
115
+ .filter(Boolean)
116
+ .map((tac) => pick(tac, [
117
+ 'tacId', 'name', 'purpose', 'responsibilities', 'interfaces',
118
+ 'dependencies', 'tradeoffs', 'notes',
119
+ ]));
120
+ const adrs = (ctx.adrIds ?? [])
121
+ .map((id) => tree.byId.get(id))
122
+ .filter(Boolean)
123
+ .map((adr) => pick(adr, [
124
+ 'adrId', 'title', 'status', 'context', 'decision', 'consequences',
125
+ 'alternativesConsidered',
126
+ ]));
127
+ const unresolvedSections = [];
128
+ const resolveSections = (names, doc) => {
129
+ const out = {};
130
+ for (const name of names ?? []) {
131
+ if (doc && Object.prototype.hasOwnProperty.call(doc, name)) {
132
+ out[name] = doc[name];
133
+ } else {
134
+ unresolvedSections.push(name);
135
+ }
136
+ }
137
+ return out;
138
+ };
139
+ const tadSections = resolveSections(ctx.tadSections, tree.tad);
140
+ const prdSections = resolveSections(ctx.prdSections, tree.prd);
141
+ context = {
142
+ tacs,
143
+ adrs,
144
+ tadSections,
145
+ prdSections,
146
+ unresolvedSections,
147
+ passThrough: {
148
+ existingModules: ctx.existingModules ?? [],
149
+ schemas: ctx.schemas ?? [],
150
+ externalDocs: ctx.externalDocs ?? [],
151
+ other: ctx.other ?? [],
152
+ },
153
+ };
154
+ }
155
+
156
+ // Section 6 - existing test surface. Presence reporting off the
157
+ // walker maps, not a coverage verdict (§D7). The per-AC `covered`
158
+ // flag here is PRESENCE-based (at least one TC row via tcsByAcId);
159
+ // since w-2026-07-28-005, computeCoverage's own leaf test is stricter
160
+ // (the TC's testPointer must resolve to a real test). `rcf coverage`
161
+ // is the coverage surface; this section only lists what is on the
162
+ // tree, pointers included, so the builder can see what exists.
163
+ const tests = acceptanceCriteria.map((ac) => {
164
+ const suites = tree.tsByAcId.get(ac.id) ?? [];
165
+ const cases = (tree.tcsByAcId.get(ac.id) ?? []).map(({ tsId, tcId }) => {
166
+ const ts = tree.byId.get(tsId);
167
+ const tc = (ts?.testCases ?? []).find((c) => c.id === tcId) ?? {};
168
+ return { tcId, tsId, ...pick(tc, ['description', 'status', 'testPointer']) };
169
+ });
170
+ return { acId: ac.id, covered: cases.length > 0, suites, cases };
171
+ });
172
+
173
+ // Section 7 - completion contract (static, parameterised by fbsId).
174
+ const completionContract = {
175
+ markInProgress: `rcf build ${fbsId} --mark inProgress`,
176
+ markComplete: `rcf build ${fbsId} --mark complete`,
177
+ finalise: `rcf finalise ${fbsId} --url <deploy-url>`,
178
+ };
179
+
180
+ const result = {
181
+ fbs: fbsBlock,
182
+ queue: { position, total: queue.items.length },
183
+ bs: tree.bs
184
+ ? pick(tree.bs, ['bsId', 'title', 'buildPhilosophy', 'generationStrategy'])
185
+ : null,
186
+ prd: tree.prd ? pick(tree.prd, ['prdId', 'productName']) : null,
187
+ blockedBy,
188
+ dependencies,
189
+ dependents,
190
+ acceptanceCriteria,
191
+ userStories,
192
+ requirements,
193
+ ...(context ? { context } : {}),
194
+ tests,
195
+ completionContract,
196
+ };
197
+ return result;
198
+ }
@@ -0,0 +1,18 @@
1
+ // JSON formatter for the build verb (Phase 6 §D14). Envelope shapes
2
+ // are stable-by-convention, same contract discipline as Phase 5 D15:
3
+ // no in-place reshape without a new flag or subcommand. These
4
+ // envelopes are the Phase 7 MCP layer's anchor. camelCase throughout;
5
+ // serialisation via JSON.stringify with construction-ordered keys
6
+ // (§D10 determinism).
7
+
8
+ /**
9
+ * Format a build result as a JSON envelope.
10
+ *
11
+ * @param {object} result - QueueResult | BundleResult | next-mode empty envelope
12
+ * @param {('queue'|'bundle'|'next')} mode
13
+ * @returns {string}
14
+ */
15
+ export function formatJson(result, mode) {
16
+ const envelope = { ok: true, mode, ...result };
17
+ return `${JSON.stringify(envelope, null, 2)}\n`;
18
+ }