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
package/src/view/index.js CHANGED
@@ -6,16 +6,33 @@
6
6
 
7
7
  import { dirname, join, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
- import { stat } from 'node:fs/promises';
9
+ import { readFile, stat } from 'node:fs/promises';
10
10
 
11
- import { walkTree } from '#core/store';
11
+ import { resolveTestPointers, walkTree } from '#core/store';
12
+ import { computeReadiness } from '../query/readiness.js';
13
+ import { computeQuestions } from '../query/questions.js';
14
+ import { loadFreezeRecord } from '../define/freeze-record.js';
15
+ import { loadAllLedgers } from '../define/ledgers.js';
12
16
  import { renderContent, renderPage } from './html-page.js';
17
+ import { renderProductMapGrouping } from './product-map.js';
13
18
  import { buildTreeModel } from './tree-model.js';
19
+ import { serialiseViewIndex } from './view-index.js';
14
20
 
15
21
  const here = dirname(fileURLToPath(import.meta.url));
16
22
  export const VENDORED_MERMAID_PATH = resolve(here, 'vendored', 'mermaid.min.js');
17
23
  export const STYLE_CSS_PATH = resolve(here, 'style.css');
18
24
  export const LIVE_CLIENT_PATH = resolve(here, 'live-client.js');
25
+ // Viewer UI refresh PR 1 (Dex / wespa 2026-10-02 #263): the former
26
+ // inline <script> block from html-page.js now lives at this path and
27
+ // is served over the new /page-init.js route. Release notes name it so
28
+ // wespa's proxy allow-list is extended before any version pin bump.
29
+ export const PAGE_INIT_PATH = resolve(here, 'page-init.js');
30
+ // Viewer UI refresh PR 9 (TAC-4134, ADR-4136): wespa host fixture
31
+ // client script. Served over /test-host.js only when the server is
32
+ // started with testHost: true (CLI --test-host or RCF_VIEW_TEST_HOST=1).
33
+ // Off the default route surface so wespa's proxy allow-list is
34
+ // unaffected.
35
+ export const TEST_HOST_CLIENT_PATH = resolve(here, 'test-host', 'embed-client.js');
19
36
 
20
37
  /**
21
38
  * Walk up from `start` looking for an ancestor directory containing a
@@ -56,8 +73,97 @@ export async function findProjectRoot(start) {
56
73
  */
57
74
  export async function renderModelToPage({ projectRoot }) {
58
75
  const { tree, errors } = await walkTree({ projectRoot });
76
+
77
+ // Load the readiness inputs once per rewalk; the viewer is pure
78
+ // (never recomputes readiness). Failures load as `null` so the tab
79
+ // still renders when a tree is partial.
80
+ const [freezeRecord, ledgers, testPointers, profileText] = await Promise.all([
81
+ loadFreezeRecord({ projectRoot }).catch(() => null),
82
+ loadAllLedgers({ projectRoot }).catch(() => ({})),
83
+ resolveTestPointers({ projectRoot, tree }).catch(() => undefined),
84
+ readProfileText(projectRoot),
85
+ ]);
86
+
87
+ let readiness = null;
88
+ try {
89
+ readiness = computeReadiness(tree, {
90
+ freeze: freezeRecord,
91
+ ledgers,
92
+ profile: undefined,
93
+ profileText,
94
+ testPointers,
95
+ validateErrors: errors,
96
+ });
97
+ } catch (err) {
98
+ // A malformed tree should not block the viewer; the Readiness tab
99
+ // renders its "could not be computed" placeholder in that case.
100
+ readiness = null;
101
+ }
102
+
103
+ // Viewer UI refresh PR 8 (TAC-4133, ADR-4135, AC-18002-1): fold the
104
+ // DEFINE step 3 PR 2 `computeQuestions` output onto the readiness
105
+ // object before the view reads it, so the PO layer's question
106
+ // adapter feature-detects the real shape (`questions[]`, `groups[]`,
107
+ // `optional[]`, `engineer.blockers`) rather than falling back to the
108
+ // blockers path. Pure fold; `computeQuestions` is a composer over
109
+ // the same readiness object and writes nothing under rcf/.
110
+ if (readiness) {
111
+ try {
112
+ const q = computeQuestions(readiness, {
113
+ tree,
114
+ ledgers,
115
+ profileText,
116
+ persona: 'productOwner',
117
+ });
118
+ readiness.questions = q.questions;
119
+ readiness.questionGroups = q.groups;
120
+ readiness.questionOptional = q.optional;
121
+ readiness.questionEngineer = q.engineer ?? null;
122
+ } catch {
123
+ // Composition failure never blocks the viewer; the adapter falls
124
+ // back to the blockers path when `readiness.questions` is absent.
125
+ }
126
+ }
127
+
59
128
  const model = buildTreeModel({ tree, errors });
129
+ model.readiness = readiness;
130
+ model.profileText = profileText;
131
+ model.freezeRecord = freezeRecord;
60
132
  const fullPageHtml = renderPage(model);
61
133
  const contentHtml = renderContent(model);
62
- return { fullPageHtml, contentHtml, errors, tree };
134
+ // Pre-rendered partials for the /product-map/<group> endpoint
135
+ // (AC-17007-1). The full page ships only the shape grouping's REQ
136
+ // cards; the client fetches the other three on demand from these
137
+ // strings. Regenerated on every rewalk so the cache stays consistent
138
+ // with the tree on disk.
139
+ const pmPartials = {
140
+ shape: renderProductMapGrouping(model, 'shape'),
141
+ component: renderProductMapGrouping(model, 'component'),
142
+ trace: renderProductMapGrouping(model, 'trace'),
143
+ capability: renderProductMapGrouping(model, 'capability'),
144
+ blueprint: renderProductMapGrouping(model, 'blueprint'),
145
+ };
146
+ // Viewer UI refresh PR 7 (TAC-4132, ADR-4134): the ID lookup index
147
+ // is built on every rewalk from BuiltTreeModel and served from the
148
+ // new `./index.json` route (release-noted so wespa's proxy allow-
149
+ // list is extended before the version pin bump). One row per doc id
150
+ // plus one row per AC under each US.
151
+ const indexJson = serialiseViewIndex(model);
152
+ return { fullPageHtml, contentHtml, errors, tree, pmPartials, indexJson };
153
+ }
154
+
155
+ /**
156
+ * Read `rcf/.identity/profile.md` text or return null when the file
157
+ * is absent (the Readiness tab orders persona groups as PO-first in
158
+ * the absent case).
159
+ *
160
+ * @param {string} projectRoot
161
+ * @returns {Promise<string | null>}
162
+ */
163
+ async function readProfileText(projectRoot) {
164
+ try {
165
+ return await readFile(join(projectRoot, 'rcf', '.identity', 'profile.md'), 'utf8');
166
+ } catch (err) {
167
+ return null;
168
+ }
63
169
  }
@@ -185,7 +185,10 @@
185
185
  function fetchScope(slug, fetchImpl) {
186
186
  var f = typeof fetchImpl === 'function' ? fetchImpl : (typeof fetch === 'function' ? fetch : null);
187
187
  if (!f) return Promise.reject(new Error('no fetch available'));
188
- var url = '/scope.json?blueprint=' + encodeURIComponent(slug);
188
+ // Dex / wespa 2026-10-02 (#263, PR 1): asset references are relative
189
+ // so the viewer works under the proxy mount (/rcf-viewer/) without
190
+ // path rewrites.
191
+ var url = './scope.json?blueprint=' + encodeURIComponent(slug);
189
192
  return Promise.resolve(f(url)).then(function (resp) {
190
193
  if (!resp) throw new Error('scope: no response');
191
194
  var okFlag = resp.ok !== undefined ? resp.ok : (resp.status >= 200 && resp.status < 300);
@@ -410,7 +413,9 @@
410
413
  setState('disconnected');
411
414
  return;
412
415
  }
413
- var source = new EventSourceCtor('/events');
416
+ // Dex / wespa 2026-10-02 (#263, PR 1): relative so the stream
417
+ // works under the proxy mount without a rewrite rule.
418
+ var source = new EventSourceCtor('./events');
414
419
 
415
420
  source.onopen = function () {
416
421
  lastEventAt = Date.now();
@@ -434,6 +439,17 @@
434
439
  host.innerHTML = payload.contentHtml;
435
440
  }
436
441
  currentVersion = payload.version;
442
+ // The Product Map panel keeps a client-side partial cache
443
+ // (window.__rcfPmPartialCache is opaque to us). A tree-update
444
+ // invalidates every partial: the next activatePmGroup on a lazy
445
+ // grouping refetches instead of using the stale cache. See
446
+ // hydrateLazyGroup in the inline script.
447
+ win.__rcfPmDirty = true;
448
+ // Viewer UI refresh PR 7 (TAC-4132, ADR-4134): the ID lookup
449
+ // index is a snapshot of the tree. A tree-update invalidates
450
+ // whichever rows the client last fetched; wireLookup refetches
451
+ // on the next modal open instead of staying stale.
452
+ win.__rcfLookupDirty = true;
437
453
  // Tab buttons live in the header (outside the swap wrapper) so
438
454
  // their click handlers survive. But the swap serves every panel
439
455
  // with the same `hidden` state Phase 3.6 renders (Overview
@@ -442,6 +458,13 @@
442
458
  // Mermaid inside the selected panel because .mermaid nodes in
443
459
  // the payload have no `data-processed` yet.
444
460
  resyncTabsAfterSwap(doc, win);
461
+ // Re-invoke the page init so pm-group-btn / pm-status-select
462
+ // listeners (which lived inside the swapped wrapper) are attached
463
+ // to the fresh DOM. onReady is idempotent for the header-scoped
464
+ // bindings that survive the swap.
465
+ if (win.rcfPage && typeof win.rcfPage.init === 'function') {
466
+ try { win.rcfPage.init(); } catch (e) { /* soft */ }
467
+ }
445
468
  restoreState(deps);
446
469
  applyScope();
447
470
  setState('connected');
@@ -484,8 +507,15 @@
484
507
  // the panels back in line with the aria-selected button and re-run
485
508
  // Mermaid in the visible one.
486
509
  function resyncTabsAfterSwap(doc, win) {
487
- var TABS = ['overview', 'requirements', 'architecture', 'build'];
488
- var selectedBtn = doc.querySelector('[role="tab"][aria-selected="true"]');
510
+ // Phase 3.8 tabs; Phase 3.9 added product-map. Product Map MUST be
511
+ // in this list so an SSE swap while it is the active tab keeps the
512
+ // panel visible; without it, resolveActiveTab falls back to
513
+ // overview and the freshly-swapped pm listeners have already been
514
+ // rewired by the rcfPage.init() call below.
515
+ var TABS = ['overview', 'requirements', 'architecture', 'build', 'product-map'];
516
+ // Scope to the top-level nav.tabs to avoid matching pm-group-btn
517
+ // nodes (they also carry role="tab").
518
+ var selectedBtn = doc.querySelector('nav.tabs [role="tab"][aria-selected="true"]');
489
519
  var selected = selectedBtn && selectedBtn.getAttribute ? selectedBtn.getAttribute('data-tab') : null;
490
520
  if (!selected || TABS.indexOf(selected) === -1) selected = 'overview';
491
521
  for (var i = 0; i < TABS.length; i += 1) {
@@ -515,8 +545,25 @@
515
545
  };
516
546
  }
517
547
 
548
+ // Connection pill: the shell footer renders #rcf-conn-pill (viewer UI
549
+ // refresh PR 1 follow-up: pill moved from a floating top-right dot
550
+ // into the footer). If the pill is missing (older shell, embedded
551
+ // snippet, no-shell contexts), fall back to injecting a floating dot
552
+ // so the live-status signal is never silently dropped.
553
+ function pillLabelFor(state) {
554
+ if (state === 'connected') return 'Live updates on';
555
+ if (state === 'reconnecting') return 'Reconnecting…';
556
+ return 'Disconnected';
557
+ }
558
+
559
+ function pillTitleFor(state) {
560
+ if (state === 'connected') return 'Connected - live updates on';
561
+ if (state === 'reconnecting') return 'Reconnecting to view server...';
562
+ return 'Disconnected from view server';
563
+ }
564
+
518
565
  function injectConnectionDot(doc) {
519
- if (doc.getElementById('rcf-conn-dot')) return;
566
+ if (doc.getElementById('rcf-conn-pill') || doc.getElementById('rcf-conn-dot')) return;
520
567
  var style = doc.createElement('style');
521
568
  style.textContent = [
522
569
  '#rcf-conn-dot {',
@@ -540,12 +587,18 @@
540
587
  }
541
588
 
542
589
  function updateDot(doc, state) {
590
+ var pill = doc.getElementById('rcf-conn-pill');
591
+ if (pill) {
592
+ pill.className = 'live ' + state;
593
+ pill.title = pillTitleFor(state);
594
+ var label = pill.querySelector('.label');
595
+ if (label) label.textContent = pillLabelFor(state);
596
+ return;
597
+ }
543
598
  var dot = doc.getElementById('rcf-conn-dot');
544
599
  if (!dot) return;
545
600
  dot.className = state;
546
- if (state === 'connected') dot.title = 'Connected - live updates on';
547
- else if (state === 'reconnecting') dot.title = 'Reconnecting to view server...';
548
- else dot.title = 'Disconnected from view server';
601
+ dot.title = pillTitleFor(state);
549
602
  }
550
603
 
551
604
  // ---- module exports (for node:vm tests) --------------------------------
@@ -0,0 +1,79 @@
1
+ // Viewer UI refresh PR 3 (decision 3, decision 14): compute the set of
2
+ // REQ ids that still "need work" from the readiness result. The source
3
+ // is the readiness result the server already carries on `model.readiness`
4
+ // (built once per rewalk by `computeReadiness`), so we never re-run gates
5
+ // in the client and never recompute them from the tree.
6
+ //
7
+ // The design doc section 8 names the three PO-persona checks that call
8
+ // out specific REQ ids: `skeleton:reqIntent` (requirement needs a plain
9
+ // description), `stories:reqHasUs` (who uses this requirement, what do
10
+ // they do), and `skeleton:resolvedBy` (brief kinds that could become a
11
+ // REQ). The first two name REQ ids directly on the blocker's `ids`
12
+ // array. `skeleton:resolvedBy` names brief statement ids (SK-x), not
13
+ // REQ ids, so it is informational for the Requirements tab and only
14
+ // gets picked up when the suggestion names a REQ. Any REQ id that ends
15
+ // up in any of the above blocker lists is in the Needs-work set.
16
+ //
17
+ // Decision 14: Requirements is the engineer view at all times. The
18
+ // Needs-work chip uses engineer-plain language (no PO phrasebook text);
19
+ // the data behind it is still the PO blockers so a product-owner and
20
+ // an engineer looking at the same tree see the same "still needs work"
21
+ // requirements.
22
+
23
+ const NEEDS_WORK_CHECKS = new Set([
24
+ 'skeleton:reqIntent',
25
+ 'stories:reqHasUs',
26
+ 'skeleton:resolvedBy',
27
+ ]);
28
+
29
+ /**
30
+ * @param {import('../query/readiness.js').ReadinessResult | null | undefined} readiness
31
+ * @returns {Set<string>} REQ ids that still need work
32
+ */
33
+ export function computeReqNeedsWorkIds(readiness) {
34
+ /** @type {Set<string>} */
35
+ const out = new Set();
36
+ if (!readiness) return out;
37
+ const level = readiness.levels?.intentComplete;
38
+ if (!level || !Array.isArray(level.blockedBy)) return out;
39
+ for (const blocker of level.blockedBy) {
40
+ if (!blocker || !NEEDS_WORK_CHECKS.has(blocker.check)) continue;
41
+ const ids = Array.isArray(blocker.ids) ? blocker.ids : [];
42
+ for (const id of ids) {
43
+ if (typeof id === 'string' && id.startsWith('REQ-')) out.add(id);
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+
49
+ /**
50
+ * Return a short, engineer-plain reason for a REQ being in the needs-
51
+ * work set. Decision 14 keeps PO phrasebook text out of the Requirements
52
+ * tab, so we map the check name to a terse engineer-facing label rather
53
+ * than reproducing CHECK_QUESTION. Returns null when the REQ is not in
54
+ * any needs-work check.
55
+ *
56
+ * @param {import('../query/readiness.js').ReadinessResult | null | undefined} readiness
57
+ * @param {string} reqId
58
+ * @returns {string | null}
59
+ */
60
+ export function needsWorkReasonFor(readiness, reqId) {
61
+ if (!readiness) return null;
62
+ const level = readiness.levels?.intentComplete;
63
+ if (!level || !Array.isArray(level.blockedBy)) return null;
64
+ const reasons = [];
65
+ for (const blocker of level.blockedBy) {
66
+ if (!blocker || !NEEDS_WORK_CHECKS.has(blocker.check)) continue;
67
+ const ids = Array.isArray(blocker.ids) ? blocker.ids : [];
68
+ if (!ids.includes(reqId)) continue;
69
+ reasons.push(REASON_BY_CHECK[blocker.check] ?? blocker.check);
70
+ }
71
+ if (reasons.length === 0) return null;
72
+ return reasons.join('; ');
73
+ }
74
+
75
+ const REASON_BY_CHECK = {
76
+ 'skeleton:reqIntent': 'missing plain description or domain',
77
+ 'stories:reqHasUs': 'no user story yet',
78
+ 'skeleton:resolvedBy': 'resolved-by target still open',
79
+ };