rcf-lite 0.28.2 → 0.29.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 (327) hide show
  1. package/CHANGELOG.md +317 -0
  2. package/bin/rcf.js +8 -0
  3. package/fixtures/canary-manifest.json +9 -9
  4. package/guidance/define-draft-shapes.md +89 -0
  5. package/guidance/define-intake.md +97 -0
  6. package/guidance/define-intent.md +107 -0
  7. package/guidance/discover-prototype.md +67 -0
  8. package/guidance/discovery-prototypes.md +69 -0
  9. package/guidance/harness-template.md +34 -0
  10. package/guidance/managed/agent-instructions-block.hash +1 -1
  11. package/guidance/managed/agent-instructions-block.md +34 -0
  12. package/guidance/manifest.json +9 -2
  13. package/package.json +1 -1
  14. package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
  15. package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
  16. package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
  17. package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
  18. package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
  19. package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
  20. package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
  21. package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
  22. package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
  23. package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
  24. package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
  25. package/rcf/adrs/adr-4134-index-json-route.json +35 -0
  26. package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
  27. package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
  28. package/rcf/code-nodes/cn-700.json +23 -0
  29. package/rcf/code-nodes/cn-701.json +14 -0
  30. package/rcf/code-nodes/cn-702.json +14 -0
  31. package/rcf/code-nodes/cn-703.json +16 -0
  32. package/rcf/code-nodes/cn-720.json +15 -0
  33. package/rcf/code-nodes/cn-721.json +15 -0
  34. package/rcf/code-nodes/cn-722.json +15 -0
  35. package/rcf/code-nodes/cn-723.json +18 -0
  36. package/rcf/code-nodes/cn-724.json +14 -0
  37. package/rcf/code-nodes/cn-725.json +37 -0
  38. package/rcf/code-nodes/cn-726.json +25 -0
  39. package/rcf/code-nodes/cn-727.json +23 -0
  40. package/rcf/code-nodes/cn-728.json +37 -0
  41. package/rcf/code-nodes/cn-729.json +26 -0
  42. package/rcf/code-nodes/cn-730.json +24 -0
  43. package/rcf/code-nodes/cn-731.json +20 -0
  44. package/rcf/code-nodes/cn-732.json +14 -0
  45. package/rcf/code-nodes/cn-733.json +14 -0
  46. package/rcf/code-nodes/cn-734.json +14 -0
  47. package/rcf/code-nodes/cn-735.json +20 -0
  48. package/rcf/code-nodes/cn-736.json +14 -0
  49. package/rcf/code-nodes/cn-737.json +14 -0
  50. package/rcf/code-nodes/cn-738.json +14 -0
  51. package/rcf/code-nodes/cn-739.json +18 -0
  52. package/rcf/code-nodes/cn-740.json +19 -0
  53. package/rcf/code-nodes/cn-741.json +16 -0
  54. package/rcf/code-nodes/cn-742.json +16 -0
  55. package/rcf/code-nodes/cn-743.json +15 -0
  56. package/rcf/code-nodes/cn-744.json +22 -0
  57. package/rcf/code-nodes/cn-745.json +16 -0
  58. package/rcf/code-nodes/cn-746.json +14 -0
  59. package/rcf/code-nodes/cn-747.json +18 -0
  60. package/rcf/code-nodes/cn-748.json +16 -0
  61. package/rcf/code-nodes/cn-749.json +16 -0
  62. package/rcf/code-nodes/cn-750.json +15 -0
  63. package/rcf/code-nodes/cn-751.json +18 -0
  64. package/rcf/code-nodes/cn-752.json +14 -0
  65. package/rcf/fbs/fbs-190.json +37 -0
  66. package/rcf/fbs/fbs-191.json +34 -0
  67. package/rcf/fbs/fbs-192.json +17 -0
  68. package/rcf/fbs/fbs-193.json +37 -0
  69. package/rcf/fbs/fbs-194.json +35 -0
  70. package/rcf/fbs/fbs-195.json +37 -0
  71. package/rcf/fbs/fbs-196.json +45 -0
  72. package/rcf/fbs/fbs-197.json +41 -0
  73. package/rcf/fbs/fbs-198.json +42 -0
  74. package/rcf/fbs/fbs-199.json +59 -0
  75. package/rcf/fbs/fbs-200.json +46 -0
  76. package/rcf/fbs/fbs-201.json +49 -0
  77. package/rcf/fbs/fbs-202.json +110 -0
  78. package/rcf/fbs/fbs-203.json +100 -0
  79. package/rcf/requirements/req-001.json +2 -1
  80. package/rcf/requirements/req-002.json +3 -2
  81. package/rcf/requirements/req-003.json +2 -1
  82. package/rcf/requirements/req-004.json +2 -1
  83. package/rcf/requirements/req-005.json +2 -1
  84. package/rcf/requirements/req-006.json +2 -1
  85. package/rcf/requirements/req-007.json +2 -1
  86. package/rcf/requirements/req-008.json +2 -1
  87. package/rcf/requirements/req-009.json +2 -1
  88. package/rcf/requirements/req-010.json +5 -2
  89. package/rcf/requirements/req-011.json +2 -1
  90. package/rcf/requirements/req-012.json +2 -1
  91. package/rcf/requirements/req-013.json +2 -1
  92. package/rcf/requirements/req-014.json +2 -1
  93. package/rcf/requirements/req-015.json +2 -1
  94. package/rcf/requirements/req-016.json +5 -2
  95. package/rcf/requirements/req-017.json +6 -2
  96. package/rcf/requirements/req-018.json +5 -2
  97. package/rcf/requirements/req-019.json +5 -2
  98. package/rcf/requirements/req-020.json +5 -2
  99. package/rcf/requirements/req-021.json +5 -2
  100. package/rcf/requirements/req-022.json +5 -2
  101. package/rcf/requirements/req-023.json +6 -4
  102. package/rcf/requirements/req-024.json +5 -2
  103. package/rcf/requirements/req-025.json +5 -2
  104. package/rcf/requirements/req-026.json +5 -2
  105. package/rcf/requirements/req-027.json +5 -2
  106. package/rcf/requirements/req-028.json +5 -2
  107. package/rcf/requirements/req-029.json +5 -2
  108. package/rcf/requirements/req-030.json +5 -2
  109. package/rcf/requirements/req-040.json +5 -2
  110. package/rcf/requirements/req-041.json +5 -2
  111. package/rcf/requirements/req-042.json +5 -2
  112. package/rcf/requirements/req-043.json +5 -2
  113. package/rcf/requirements/req-050.json +5 -2
  114. package/rcf/requirements/req-051.json +5 -2
  115. package/rcf/requirements/req-052.json +5 -2
  116. package/rcf/requirements/req-053.json +5 -2
  117. package/rcf/requirements/req-054.json +5 -2
  118. package/rcf/requirements/req-060.json +5 -2
  119. package/rcf/requirements/req-061.json +5 -2
  120. package/rcf/requirements/req-062.json +5 -2
  121. package/rcf/requirements/req-063.json +5 -2
  122. package/rcf/requirements/req-070.json +5 -2
  123. package/rcf/requirements/req-071.json +5 -2
  124. package/rcf/requirements/req-072.json +5 -2
  125. package/rcf/requirements/req-073.json +5 -2
  126. package/rcf/requirements/req-074.json +5 -2
  127. package/rcf/requirements/req-075.json +5 -2
  128. package/rcf/requirements/req-076.json +5 -2
  129. package/rcf/requirements/req-077.json +5 -2
  130. package/rcf/requirements/req-080.json +5 -2
  131. package/rcf/requirements/req-081.json +5 -2
  132. package/rcf/requirements/req-082.json +5 -2
  133. package/rcf/requirements/req-083.json +5 -2
  134. package/rcf/requirements/req-084.json +5 -2
  135. package/rcf/requirements/req-085.json +5 -2
  136. package/rcf/requirements/req-086.json +5 -2
  137. package/rcf/requirements/req-090.json +5 -2
  138. package/rcf/requirements/req-091.json +5 -2
  139. package/rcf/requirements/req-092.json +5 -2
  140. package/rcf/requirements/req-093.json +5 -2
  141. package/rcf/requirements/req-094.json +5 -2
  142. package/rcf/requirements/req-100.json +5 -2
  143. package/rcf/requirements/req-101.json +5 -2
  144. package/rcf/requirements/req-102.json +5 -2
  145. package/rcf/requirements/req-103.json +5 -2
  146. package/rcf/requirements/req-104.json +5 -2
  147. package/rcf/requirements/req-110.json +5 -2
  148. package/rcf/requirements/req-111.json +5 -2
  149. package/rcf/requirements/req-112.json +5 -2
  150. package/rcf/requirements/req-113.json +5 -2
  151. package/rcf/requirements/req-114.json +5 -2
  152. package/rcf/requirements/req-115.json +5 -2
  153. package/rcf/requirements/req-120.json +5 -2
  154. package/rcf/requirements/req-121.json +5 -2
  155. package/rcf/requirements/req-122.json +5 -2
  156. package/rcf/requirements/req-123.json +5 -2
  157. package/rcf/requirements/req-124.json +5 -2
  158. package/rcf/requirements/req-125.json +5 -2
  159. package/rcf/requirements/req-130.json +6 -2
  160. package/rcf/requirements/req-131.json +6 -2
  161. package/rcf/requirements/req-132.json +6 -2
  162. package/rcf/requirements/req-133.json +6 -2
  163. package/rcf/requirements/req-134.json +6 -2
  164. package/rcf/requirements/req-140.json +5 -2
  165. package/rcf/requirements/req-145.json +5 -2
  166. package/rcf/requirements/req-150.json +6 -2
  167. package/rcf/requirements/req-151.json +6 -2
  168. package/rcf/requirements/req-152.json +6 -2
  169. package/rcf/requirements/req-153.json +6 -2
  170. package/rcf/requirements/req-154.json +5 -2
  171. package/rcf/requirements/req-155.json +4 -1
  172. package/rcf/requirements/req-156.json +4 -1
  173. package/rcf/requirements/req-157.json +4 -1
  174. package/rcf/requirements/req-158.json +4 -1
  175. package/rcf/requirements/req-159.json +4 -1
  176. package/rcf/requirements/req-160.json +4 -1
  177. package/rcf/requirements/req-161.json +4 -1
  178. package/rcf/requirements/req-162.json +4 -1
  179. package/rcf/requirements/req-163.json +5 -2
  180. package/rcf/requirements/req-164.json +5 -2
  181. package/rcf/requirements/req-165.json +5 -2
  182. package/rcf/requirements/req-170.json +33 -0
  183. package/rcf/requirements/req-171.json +44 -0
  184. package/rcf/requirements/req-172.json +25 -0
  185. package/rcf/requirements/req-173.json +25 -0
  186. package/rcf/requirements/req-174.json +31 -0
  187. package/rcf/requirements/req-175.json +31 -0
  188. package/rcf/requirements/req-176.json +25 -0
  189. package/rcf/requirements/req-180.json +31 -0
  190. package/rcf/requirements/req-181.json +31 -0
  191. package/rcf/requirements/req-182.json +25 -0
  192. package/rcf/requirements/req-186.json +25 -0
  193. package/rcf/requirements/req-187.json +31 -0
  194. package/rcf/requirements/req-188.json +25 -0
  195. package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
  196. package/rcf/tacs/tac-4121-define-ledgers.json +35 -0
  197. package/rcf/tacs/tac-4122-define-gates.json +61 -0
  198. package/rcf/tacs/tac-4123-define-readiness.json +61 -0
  199. package/rcf/tacs/tac-4124-define-freeze.json +28 -0
  200. package/rcf/tacs/tac-4127-readiness-view.json +49 -0
  201. package/rcf/tacs/tac-4128-define-guidance.json +53 -0
  202. package/rcf/tacs/tac-4129-define-questions.json +45 -0
  203. package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
  204. package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
  205. package/rcf/tacs/tac-4132-id-lookup.json +42 -0
  206. package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
  207. package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
  208. package/rcf/test-suites/ts-016.json +3 -3
  209. package/rcf/test-suites/ts-204.json +30 -0
  210. package/rcf/test-suites/ts-205.json +22 -0
  211. package/rcf/test-suites/ts-206.json +22 -0
  212. package/rcf/test-suites/ts-207.json +22 -0
  213. package/rcf/test-suites/ts-208.json +22 -0
  214. package/rcf/test-suites/ts-209.json +22 -0
  215. package/rcf/test-suites/ts-210.json +22 -0
  216. package/rcf/test-suites/ts-211.json +22 -0
  217. package/rcf/test-suites/ts-212.json +22 -0
  218. package/rcf/test-suites/ts-213.json +50 -0
  219. package/rcf/test-suites/ts-214.json +74 -0
  220. package/rcf/test-suites/ts-215.json +211 -0
  221. package/rcf/test-suites/ts-216.json +94 -0
  222. package/rcf/test-suites/ts-217.json +62 -0
  223. package/rcf/test-suites/ts-218.json +78 -0
  224. package/rcf/test-suites/ts-219.json +70 -0
  225. package/rcf/test-suites/ts-220.json +78 -0
  226. package/rcf/test-suites/ts-221.json +78 -0
  227. package/rcf/test-suites/ts-222.json +100 -0
  228. package/rcf/test-suites/ts-223.json +94 -0
  229. package/rcf/test-suites/ts-224.json +70 -0
  230. package/rcf/test-suites/ts-225.json +78 -0
  231. package/rcf/test-suites/ts-226.json +158 -0
  232. package/rcf/test-suites/ts-227.json +38 -0
  233. package/rcf/test-suites/ts-228.json +86 -0
  234. package/rcf/test-suites/ts-229.json +83 -0
  235. package/rcf/user-stories/us-15601.json +4 -4
  236. package/rcf/user-stories/us-17001.json +27 -0
  237. package/rcf/user-stories/us-17002.json +22 -0
  238. package/rcf/user-stories/us-17003.json +22 -0
  239. package/rcf/user-stories/us-17004.json +22 -0
  240. package/rcf/user-stories/us-17005.json +22 -0
  241. package/rcf/user-stories/us-17006.json +21 -0
  242. package/rcf/user-stories/us-17007.json +21 -0
  243. package/rcf/user-stories/us-17008.json +22 -0
  244. package/rcf/user-stories/us-17101.json +22 -0
  245. package/rcf/user-stories/us-17102.json +22 -0
  246. package/rcf/user-stories/us-17201.json +38 -0
  247. package/rcf/user-stories/us-17301.json +38 -0
  248. package/rcf/user-stories/us-17302.json +50 -0
  249. package/rcf/user-stories/us-17401.json +70 -0
  250. package/rcf/user-stories/us-17402.json +55 -0
  251. package/rcf/user-stories/us-17403.json +60 -0
  252. package/rcf/user-stories/us-17501.json +48 -0
  253. package/rcf/user-stories/us-17502.json +60 -0
  254. package/rcf/user-stories/us-17503.json +65 -0
  255. package/rcf/user-stories/us-17601.json +58 -0
  256. package/rcf/user-stories/us-18001.json +55 -0
  257. package/rcf/user-stories/us-18002.json +35 -0
  258. package/rcf/user-stories/us-18101.json +35 -0
  259. package/rcf/user-stories/us-18202.json +40 -0
  260. package/rcf/user-stories/us-18601.json +55 -0
  261. package/rcf/user-stories/us-18602.json +50 -0
  262. package/rcf/user-stories/us-18603.json +45 -0
  263. package/rcf/user-stories/us-18701.json +50 -0
  264. package/rcf/user-stories/us-18702.json +50 -0
  265. package/rcf/user-stories/us-18801.json +45 -0
  266. package/rcf/user-stories/us-204.json +59 -0
  267. package/rcf/user-stories/us-205.json +64 -0
  268. package/rcf/user-stories/us-206.json +54 -0
  269. package/releases/releases.yaml +31 -1
  270. package/src/cli/feedback.js +126 -10
  271. package/src/cli/freeze.js +730 -0
  272. package/src/cli/help.js +23 -0
  273. package/src/cli/ledger.js +615 -0
  274. package/src/cli/questions.js +288 -0
  275. package/src/cli/readiness.js +498 -0
  276. package/src/cli/view.js +29 -0
  277. package/src/core/store/writer.js +86 -6
  278. package/src/define/freeze-record.js +247 -0
  279. package/src/define/ledgers.js +712 -0
  280. package/src/feedback/redact.js +105 -19
  281. package/src/intake/index.js +1 -1
  282. package/src/intake/orchestrator.js +117 -0
  283. package/src/mcp/tools.js +354 -0
  284. package/src/query/delta.js +282 -0
  285. package/src/query/gates.js +1107 -0
  286. package/src/query/index.js +50 -0
  287. package/src/query/questions.js +438 -0
  288. package/src/query/readiness.js +574 -0
  289. package/src/server/index.js +60 -1
  290. package/src/server/routes.js +133 -4
  291. package/src/setup/identity-seed.js +17 -0
  292. package/src/view/build-dag.js +563 -0
  293. package/src/view/components/badge.js +41 -0
  294. package/src/view/components/diff.js +72 -0
  295. package/src/view/components/doc-row.js +62 -0
  296. package/src/view/components/empty-state.js +35 -0
  297. package/src/view/components/entity-selector.js +137 -0
  298. package/src/view/components/filter-bar.js +97 -0
  299. package/src/view/components/findings-list.js +49 -0
  300. package/src/view/components/index.js +12 -0
  301. package/src/view/components/pill.js +46 -0
  302. package/src/view/components/sub-tab-strip.js +43 -0
  303. package/src/view/components/toast.js +25 -0
  304. package/src/view/components-fixture.js +265 -0
  305. package/src/view/doc-renderers/architecture.js +304 -0
  306. package/src/view/doc-renderers/build.js +160 -0
  307. package/src/view/doc-renderers/helpers.js +2 -2
  308. package/src/view/doc-renderers/index.js +2 -0
  309. package/src/view/doc-renderers/prd.js +35 -6
  310. package/src/view/doc-renderers/req.js +10 -2
  311. package/src/view/doc-renderers/user-story.js +10 -5
  312. package/src/view/html-page.js +717 -300
  313. package/src/view/index.js +109 -3
  314. package/src/view/live-client.js +61 -8
  315. package/src/view/needs-work.js +79 -0
  316. package/src/view/page-init.js +2193 -0
  317. package/src/view/product-map.js +751 -0
  318. package/src/view/readiness/phrasebook.js +115 -0
  319. package/src/view/readiness/po-layer.js +288 -0
  320. package/src/view/readiness/question-adapter.js +329 -0
  321. package/src/view/readiness/stage-legend.js +71 -0
  322. package/src/view/readiness.js +505 -0
  323. package/src/view/style.css +2279 -266
  324. package/src/view/test-host/embed-client.js +121 -0
  325. package/src/view/test-host/fixture.js +183 -0
  326. package/src/view/test-host/lints.js +220 -0
  327. package/src/view/view-index.js +323 -0
@@ -0,0 +1,160 @@
1
+ // Build-tab renderers (viewer UI refresh PR 5, w-2026-10-02-dave-010
2
+ // decisions 2, 3, 6, 12, 14; design doc section 5.6).
3
+ //
4
+ // This module ships the Build tab's own small pieces:
5
+ // - renderBuildStats(stats): the one-line totals card above the
6
+ // sub-tabstrip. Totals come from the pre-computed BuildStats
7
+ // payload the panel assembles from `computeQueue` (Build Queue is
8
+ // the authoritative source for "buildable now", decision 6 + the
9
+ // brief evidence row).
10
+ // - renderBuildPageHead(bs): the one-line page head carrying the BS
11
+ // id and its generation strategy.
12
+ // - renderSpecBody(fbs, model, buildable): the body shown inside an
13
+ // expanded Spec row - summary, approach, AC and dependency links,
14
+ // deliverables, context links, "Show in the DAG".
15
+ //
16
+ // The list row chrome (DocRow summary with order / AC / deps / size /
17
+ // buildable pill / status) is composed in html-page.js so the row
18
+ // renderer stays close to the FilterBar and the sub-tabstrip wiring.
19
+ //
20
+ // The Test suites section and the FBS slots block are dropped (design
21
+ // doc section 3, decision 6); suites stay reachable through AC links
22
+ // and the ID lookup once PR 7 ships.
23
+
24
+ import { escapeHtml, rawJsonDisclosure } from './helpers.js';
25
+
26
+ /**
27
+ * BuildStats payload shape consumed by renderBuildStats.
28
+ *
29
+ * @typedef {object} BuildStatsPayload
30
+ * @property {number} items
31
+ * @property {number} verified
32
+ * @property {number} complete
33
+ * @property {number} inProgress
34
+ * @property {number} notStarted
35
+ * @property {number} buildableNow actionable count from computeQueue
36
+ */
37
+
38
+ /**
39
+ * @param {BuildStatsPayload} stats
40
+ * @returns {string}
41
+ */
42
+ export function renderBuildStats(stats) {
43
+ const row = [
44
+ { n: stats.items ?? 0, l: 'build specs' },
45
+ { n: stats.verified ?? 0, l: 'verified' },
46
+ { n: stats.complete ?? 0, l: 'complete' },
47
+ { n: stats.inProgress ?? 0, l: 'in progress' },
48
+ { n: stats.notStarted ?? 0, l: 'not started' },
49
+ { n: stats.buildableNow ?? 0, l: 'buildable now' },
50
+ ];
51
+ const items = stats.items > 0 ? stats.items : 1;
52
+ const pct = (n) => Math.max(0, Math.min(100, Math.round((n / items) * 100)));
53
+ const verifiedPct = pct(stats.verified ?? 0);
54
+ const completePct = pct(stats.complete ?? 0);
55
+ const inProgressPct = pct(stats.inProgress ?? 0);
56
+ const statRow = row.map((s) => (
57
+ `<div class="rcf-build-stat"><span class="rcf-build-stat-n">${escapeHtml(String(s.n))}</span><span class="rcf-build-stat-l">${escapeHtml(s.l)}</span></div>`
58
+ )).join('');
59
+ return `<div class="rcf-build-stats card" role="group" aria-label="Build totals">
60
+ <div class="rcf-build-stats-row">${statRow}</div>
61
+ <div class="rcf-build-progress" aria-hidden="true">
62
+ <span class="rcf-build-progress-seg rcf-build-progress-seg--verified" style="width:${verifiedPct}%"></span>
63
+ <span class="rcf-build-progress-seg rcf-build-progress-seg--complete" style="width:${completePct}%"></span>
64
+ <span class="rcf-build-progress-seg rcf-build-progress-seg--inprogress" style="width:${inProgressPct}%"></span>
65
+ </div>
66
+ </div>`;
67
+ }
68
+
69
+ /**
70
+ * @param {object | null | undefined} bs - the Build Sequence doc from the tree
71
+ * @returns {string}
72
+ */
73
+ export function renderBuildPageHead(bs) {
74
+ if (!bs) return `<div class="rcf-build-head"></div>`;
75
+ const title = typeof bs.title === 'string' ? bs.title : '';
76
+ const strategy = typeof bs.generationStrategy === 'string' ? bs.generationStrategy : '';
77
+ const parts = [`<span class="rcf-build-head-id mono">${escapeHtml(bs.bsId ?? 'BS')}</span>`];
78
+ const detail = [title, strategy].filter((v) => typeof v === 'string' && v.length > 0).join(' - ');
79
+ if (detail) parts.push(`<span class="muted small">${escapeHtml(detail)}</span>`);
80
+ return `<div class="rcf-build-head">${parts.join('')}</div>`;
81
+ }
82
+
83
+ /**
84
+ * Spec row body: summary, approach, AC and dependency links, deliverables,
85
+ * context links, and the "Show in the DAG" pointer (DAG panel ships in
86
+ * PR 6; the hash target is #tab=build&sub=dag&entity=<id>).
87
+ *
88
+ * @param {object} fbs
89
+ * @param {import('../tree-model.js').BuiltTreeModel} model
90
+ * @param {string} [raw] raw JSON string for the <details class="raw-json"> disclosure
91
+ * @returns {string}
92
+ */
93
+ export function renderSpecBody(fbs, model, raw) {
94
+ const summary = typeof fbs.summary === 'string' && fbs.summary.length > 0
95
+ ? `<p class="rcf-spec-summary">${escapeHtml(fbs.summary)}</p>`
96
+ : '';
97
+ const approach = typeof fbs.approach === 'string' && fbs.approach.length > 0
98
+ ? `<section class="rcf-spec-block"><h4>Approach</h4><p>${escapeHtml(fbs.approach)}</p></section>`
99
+ : '';
100
+
101
+ const acIds = Array.isArray(fbs.acIds) ? fbs.acIds : [];
102
+ const acBlock = acIds.length > 0
103
+ ? `<section class="rcf-spec-block"><h4>Acceptance criteria (${acIds.length})</h4><p class="rcf-spec-link-row">${acIds.map((id) => renderAcLink(id)).join(', ')}</p></section>`
104
+ : '';
105
+
106
+ const depIds = Array.isArray(fbs.dependsOnFbsIds) ? fbs.dependsOnFbsIds : [];
107
+ const depBlock = depIds.length > 0
108
+ ? `<section class="rcf-spec-block"><h4>Depends on</h4><ul class="rcf-spec-deps">${depIds.map((id) => renderDepLine(id, model)).join('')}</ul></section>`
109
+ : '';
110
+
111
+ const deliverables = Array.isArray(fbs.deliverables) ? fbs.deliverables.filter((d) => typeof d === 'string' && d.length > 0) : [];
112
+ const deliverableBlock = deliverables.length > 0
113
+ ? `<section class="rcf-spec-block"><h4>Deliverables</h4><ul class="rcf-spec-deliverables">${deliverables.map((d) => `<li>${escapeHtml(d)}</li>`).join('')}</ul></section>`
114
+ : '';
115
+
116
+ const contextBlock = renderContextLinks(fbs.contextRequirements);
117
+
118
+ const dagLink = `<p class="rcf-spec-dag"><a href="#tab=build&amp;sub=dag&amp;entity=${escapeHtml(fbs.fbsId ?? '')}">Show in the DAG</a></p>`;
119
+
120
+ const rawBlock = rawJsonDisclosure(raw, fbs, fbs.fbsId);
121
+
122
+ return `${summary}${approach}${acBlock}${depBlock}${deliverableBlock}${contextBlock}${dagLink}${rawBlock}`;
123
+ }
124
+
125
+ function renderAcLink(id) {
126
+ const esc = escapeHtml(id);
127
+ return `<a class="mono" href="#tab=requirements&amp;entity=${esc}">${esc}</a>`;
128
+ }
129
+
130
+ function renderDepLine(id, model) {
131
+ const esc = escapeHtml(id);
132
+ const dep = model.byId?.get(id);
133
+ const title = typeof dep?.title === 'string' ? dep.title : '';
134
+ const titleFrag = title ? ` <span class="muted">${escapeHtml(title)}</span>` : '';
135
+ return `<li><a class="mono" href="#tab=build&amp;sub=specs&amp;entity=${esc}">${esc}</a>${titleFrag}</li>`;
136
+ }
137
+
138
+ function renderContextLinks(ctx) {
139
+ if (!ctx || typeof ctx !== 'object') return '';
140
+ const parts = [];
141
+ const tacIds = Array.isArray(ctx.tacIds) ? ctx.tacIds : [];
142
+ const adrIds = Array.isArray(ctx.adrIds) ? ctx.adrIds : [];
143
+ const tadSections = Array.isArray(ctx.tadSections) ? ctx.tadSections : [];
144
+ if (tacIds.length === 0 && adrIds.length === 0 && tadSections.length === 0) return '';
145
+ if (tacIds.length > 0) {
146
+ parts.push(`<p class="rcf-spec-link-row"><strong>TACs:</strong> ${tacIds.map((id) => archLink(id)).join(', ')}</p>`);
147
+ }
148
+ if (adrIds.length > 0) {
149
+ parts.push(`<p class="rcf-spec-link-row"><strong>ADRs:</strong> ${adrIds.map((id) => archLink(id)).join(', ')}</p>`);
150
+ }
151
+ if (tadSections.length > 0) {
152
+ parts.push(`<p class="rcf-spec-link-row"><strong>TAD sections:</strong> ${tadSections.map((k) => `<a class="mono" href="#tab=architecture&amp;open=${escapeHtml(k)}">${escapeHtml(k)}</a>`).join(', ')}</p>`);
153
+ }
154
+ return `<section class="rcf-spec-block"><h4>Context</h4>${parts.join('')}</section>`;
155
+ }
156
+
157
+ function archLink(id) {
158
+ const esc = escapeHtml(id);
159
+ return `<a class="mono" href="#tab=architecture&amp;entity=${esc}">${esc}</a>`;
160
+ }
@@ -128,9 +128,9 @@ export function fieldObjectTable(label, rows, columns) {
128
128
  * @param {string} parentDocId - the enclosing doc's display id (e.g. "REQ-002")
129
129
  * @returns {string}
130
130
  */
131
- export function rawJsonDisclosure(raw, doc, parentDocId) {
131
+ export function rawJsonDisclosure(raw, doc, parentDocId, idPrefix) {
132
132
  const json = raw ?? JSON.stringify(doc, null, 2);
133
- const rawId = `${parentDocId ?? 'doc'}::raw`;
133
+ const rawId = `${idPrefix ?? ''}${parentDocId ?? 'doc'}::raw`;
134
134
  return `<details class="raw-json" data-doc-id="${escapeHtml(rawId)}"><summary>Show raw JSON</summary><pre>${escapeHtml(json)}</pre></details>`;
135
135
  }
136
136
 
@@ -5,8 +5,10 @@ export { renderPrd } from './prd.js';
5
5
  export { renderReq } from './req.js';
6
6
  export { renderUserStory } from './user-story.js';
7
7
  export { renderTad } from './tad.js';
8
+ export { renderTadSections, shortenDocId } from './architecture.js';
8
9
  export { renderTac } from './tac.js';
9
10
  export { renderAdr } from './adr.js';
10
11
  export { renderBuildSequence } from './build-sequence.js';
11
12
  export { renderFbs } from './fbs.js';
12
13
  export { renderTestSuite } from './test-suite.js';
14
+ export { renderBuildStats, renderBuildPageHead, renderSpecBody } from './build.js';
@@ -18,6 +18,16 @@ import {
18
18
  * @param {string|undefined} ctx.raw
19
19
  * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
20
  * @param {string[]} [ctx.requirementIds] - computed REQ children for this PRD
21
+ * @param {boolean} [ctx.suppressRequirementList] - viewer UI refresh PR 3
22
+ * (decision 4): the PRD tab replaces the inline doc-link paragraph with
23
+ * an EntitySelector mounted outside this article, so renderPrd no
24
+ * longer emits the 109 inline requirement anchors when that flag is on.
25
+ * Non-tab callers (if any) still see the legacy paragraph by default.
26
+ * @param {boolean} [ctx.collapsibleLists] - viewer UI refresh PR 3 (design
27
+ * section 5): the PRD tab collapses Target users / In scope / Out of
28
+ * scope / Objectives / Constraints each with a count badge; Executive
29
+ * summary and Problem statement stay open. Non-tab callers (if any)
30
+ * still see the open sections by default.
21
31
  * @returns {string}
22
32
  */
23
33
  export function renderPrd(prd, ctx) {
@@ -25,15 +35,19 @@ export function renderPrd(prd, ctx) {
25
35
  const anchor = anchorIdFor(prd.prdId ?? 'PRD');
26
36
  const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
37
  const requirementIds = ctx.requirementIds ?? [];
38
+ const requirementSection = ctx.suppressRequirementList
39
+ ? ''
40
+ : `<section class="field-list"><h4>Requirements</h4><p>${docLinkList(requirementIds)}</p></section>`;
41
+ const renderList = ctx.collapsibleLists ? collapsibleFieldList : fieldList;
28
42
  const sections = [
29
43
  fieldPara('Executive summary', prd.executiveSummary),
30
44
  fieldPara('Problem statement', prd.problemStatement),
31
- fieldList('Target users', prd.targetUsers),
32
- fieldList('In scope', prd.inScope),
33
- fieldList('Out of scope', prd.outOfScope),
34
- fieldList('Objectives', prd.objectives),
35
- fieldList('Constraints', prd.constraints),
36
- `<section class="field-list"><h4>Requirements</h4><p>${docLinkList(requirementIds)}</p></section>`,
45
+ renderList('Target users', prd.targetUsers),
46
+ renderList('In scope', prd.inScope),
47
+ renderList('Out of scope', prd.outOfScope),
48
+ renderList('Objectives', prd.objectives),
49
+ renderList('Constraints', prd.constraints),
50
+ requirementSection,
37
51
  ].filter(Boolean).join('\n');
38
52
  return `
39
53
  <article id="${anchor}" class="doc doc-prd">
@@ -43,3 +57,18 @@ export function renderPrd(prd, ctx) {
43
57
  ${rawJsonDisclosure(ctx.raw, prd, prd.prdId)}
44
58
  </article>`.trim();
45
59
  }
60
+
61
+ /**
62
+ * Collapsible variant of `fieldList`: a <details> whose summary names
63
+ * the field and carries the count badge, body is the same `<ul>` list.
64
+ * The count badge uses the shared Badge class names so it looks the
65
+ * same as the Requirements-tab counts.
66
+ *
67
+ * @param {string} label
68
+ * @param {string[] | undefined} items
69
+ */
70
+ function collapsibleFieldList(label, items) {
71
+ if (!Array.isArray(items) || items.length === 0) return '';
72
+ const li = items.map((s) => `<li>${escapeHtml(s)}</li>`).join('');
73
+ return `<details class="rcf-prd-list field-list"><summary><strong>${escapeHtml(label)}</strong> <span class="rcf-badge rcf-badge--count"><span class="rcf-badge-value">${items.length}</span></span></summary><div class="rcf-prd-list-body"><ul>${li}</ul></div></details>`;
74
+ }
@@ -18,11 +18,19 @@ import {
18
18
  * @param {string|undefined} ctx.raw
19
19
  * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
20
  * @param {string|undefined} [ctx.subdiagram]
21
+ * @param {string|undefined} [ctx.idPrefix] - prefix prepended to every emitted
22
+ * id/anchor so the same REQ can be rendered under multiple Product Map
23
+ * buckets without duplicating ids. Empty when rendered from the
24
+ * Requirements tab so its anchors stay canonical.
25
+ * @param {boolean} [ctx.suppressRawJson] - when true, the raw-JSON disclosure
26
+ * is not emitted (Product Map compact cards; the disclosure stays on
27
+ * the Requirements tab). AC-17007-1 payload weight.
21
28
  * @returns {string}
22
29
  */
23
30
  export function renderReq(req, ctx) {
24
31
  if (!req) return '';
25
- const anchor = anchorIdFor(req.reqId ?? 'REQ');
32
+ const prefix = ctx.idPrefix ?? '';
33
+ const anchor = `${prefix}${anchorIdFor(req.reqId ?? 'REQ')}`;
26
34
  const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
35
  const subdiagram = ctx.subdiagram
28
36
  ? `<section class="subdiagram"><h4>Slice diagram</h4><pre class="mermaid">${escapeHtml(ctx.subdiagram)}</pre></section>`
@@ -38,6 +46,6 @@ export function renderReq(req, ctx) {
38
46
  ${fieldPara('Rationale', req.rationale)}
39
47
  ${fieldList('Tags', req.tags)}
40
48
  ${subdiagram}
41
- ${rawJsonDisclosure(ctx.raw, req, req.reqId)}
49
+ ${ctx.suppressRawJson ? '' : rawJsonDisclosure(ctx.raw, req, req.reqId, prefix)}
42
50
  </article>`.trim();
43
51
  }
@@ -18,13 +18,18 @@ import {
18
18
  * @param {string|undefined} ctx.raw
19
19
  * @param {import('#core/errors').RcfError[]} [ctx.errors]
20
20
  * @param {Map<string, object[]>} [ctx.fbsByAcId]
21
+ * @param {string|undefined} [ctx.idPrefix] - prefix prepended to every emitted
22
+ * id/anchor (article, AC li items, raw-JSON disclosure) so a US rendered
23
+ * under multiple Product Map buckets does not collide with the canonical
24
+ * Requirements-tab anchors. Empty from the Requirements tab.
21
25
  * @returns {string}
22
26
  */
23
27
  export function renderUserStory(us, ctx) {
24
28
  if (!us) return '';
25
- const anchor = anchorIdFor(us.usId ?? 'US');
29
+ const prefix = ctx.idPrefix ?? '';
30
+ const anchor = `${prefix}${anchorIdFor(us.usId ?? 'US')}`;
26
31
  const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
27
- const acItems = (us.acceptanceCriteria ?? []).map((ac) => renderAcItem(ac, ctx)).join('\n');
32
+ const acItems = (us.acceptanceCriteria ?? []).map((ac) => renderAcItem(ac, ctx, prefix)).join('\n');
28
33
  return `
29
34
  <article id="${anchor}" class="doc doc-us">
30
35
  <h3>${escapeHtml(us.usId ?? 'US')} - ${escapeHtml(us.title ?? '')}</h3>
@@ -40,13 +45,13 @@ export function renderUserStory(us, ctx) {
40
45
  ${acItems}
41
46
  </ul>
42
47
  </section>
43
- ${rawJsonDisclosure(ctx.raw, us, us.usId)}
48
+ ${ctx.suppressRawJson ? '' : rawJsonDisclosure(ctx.raw, us, us.usId, prefix)}
44
49
  </article>`.trim();
45
50
  }
46
51
 
47
- function renderAcItem(ac, ctx) {
52
+ function renderAcItem(ac, ctx, prefix) {
48
53
  const acId = ac.id ?? 'AC';
49
- const acAnchor = anchorIdFor(acId);
54
+ const acAnchor = `${prefix ?? ''}${anchorIdFor(acId)}`;
50
55
  const fbsList = ctx.fbsByAcId?.get(acId) ?? [];
51
56
  const coveredBy = fbsList.length === 0
52
57
  ? '<em>not yet delivered by any FBS</em>'