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,288 @@
1
+ // `rcf define questions` subcommand handler (REQ-186; spec 2026-10-01
2
+ // §1.2, §6).
3
+ //
4
+ // Computes and prints the persona question set from readiness and the
5
+ // four ledgers. Pure read: writes NOTHING under rcf/.
6
+ //
7
+ // rcf define questions [--json] [--persona productOwner|engineer]
8
+ // [--stage <D|short>] [--limit <n>]
9
+ //
10
+ // Persona defaults from the profile register (viewer / register
11
+ // marker scan in rcf/.identity/profile.md); when the profile is
12
+ // absent or unstated, the default is productOwner. Unknown persona
13
+ // exits 2 with the usage line naming productOwner and engineer.
14
+ // The verdict exit code lives on `rcf define readiness`; this verb
15
+ // is a diagnostic that always exits 0.
16
+
17
+ import { parseArgs } from 'node:util';
18
+ import { readFile } from 'node:fs/promises';
19
+ import { join } from 'node:path';
20
+
21
+ import { resolveTestPointers, walkTree } from '#core/store';
22
+ import { checkCodeNodeResolution } from '#core/store';
23
+ import { findProjectRoot } from '../view/index.js';
24
+ import { loadFreezeRecord } from '../define/freeze-record.js';
25
+ import { loadAllLedgers } from '../define/ledgers.js';
26
+ import { STAGE_ALIASES } from '../query/gates.js';
27
+ import { computeReadiness } from '../query/readiness.js';
28
+ import { computeQuestions, PERSONAS } from '../query/questions.js';
29
+
30
+ const OPTION_SPEC = {
31
+ json: { type: 'boolean' },
32
+ persona: { type: 'string' },
33
+ stage: { type: 'string' },
34
+ limit: { type: 'string' },
35
+ help: { type: 'boolean' },
36
+ };
37
+
38
+ export const HELP = `Usage: rcf define questions [options]
39
+
40
+ Print the persona question set (REQ-186). Each failing product-owner
41
+ check in readiness becomes one plain question per failing item, each
42
+ carrying the exact write-back that answers it.
43
+
44
+ The compute is pure: it never creates or modifies a file under rcf/.
45
+ Answers land through the write-back the question carries; the loop
46
+ ends when 'rcf define readiness --level intent' reports ok.
47
+
48
+ Options:
49
+ --json Emit the full questions object as JSON.
50
+ --persona <who> One of productOwner | engineer. Defaults
51
+ to the register marker in
52
+ rcf/.identity/profile.md, else
53
+ productOwner.
54
+ --stage <stage> Narrow the output to one stage. Accepts
55
+ a stage id (D1..D8) or its short name
56
+ (brief | skeleton | shapes | stories |
57
+ crosscut | consistency | decisions |
58
+ freeze).
59
+ --limit <n> Cap the questions[] array to the first n
60
+ entries. 'remaining' still counts every
61
+ matching item.
62
+ --help Print this help.
63
+ `;
64
+
65
+ /**
66
+ * Read `rcf/.identity/profile.md` when it exists; return null on
67
+ * ENOENT or on any read error (the question compute treats absence
68
+ * as "productOwner default" rather than a runtime error).
69
+ *
70
+ * @param {string} projectRoot
71
+ * @returns {Promise<string|null>}
72
+ */
73
+ async function readProfileText(projectRoot) {
74
+ try {
75
+ return await readFile(join(projectRoot, 'rcf', '.identity', 'profile.md'), 'utf8');
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Pick the profile register from the text, returning one of
83
+ * `productOwner` | `engineer` | `unstated`.
84
+ */
85
+ function pickRegister(text) {
86
+ if (typeof text !== 'string' || text.length === 0) return 'unstated';
87
+ // The canonical register template (src/setup/identity-seed.js) names
88
+ // all three markers in its explanation ("- `productOwner`: ..."),
89
+ // then places the chosen value as a bare word on a line of its own
90
+ // ("engineer\n", "unstated\n", etc.). Ad-hoc profiles also use
91
+ // "Register: engineer" on one line. In both shapes the ACTIVE value
92
+ // sits AFTER the explanation, so pick the LAST marker occurrence
93
+ // in the text; a line matching nothing but a marker word takes
94
+ // precedence over an in-prose occurrence at the same position.
95
+ const markers = ['productOwner', 'engineer', 'unstated'];
96
+ // Pass 1: prefer a bare-line marker (canonical template shape).
97
+ const lines = text.split(/\r?\n/);
98
+ let bareLine = null;
99
+ for (const raw of lines) {
100
+ const trimmed = raw.trim();
101
+ if (markers.includes(trimmed)) bareLine = trimmed;
102
+ }
103
+ if (bareLine) return /** @type {any} */ (bareLine);
104
+ // Pass 2: fallback - last occurrence anywhere (handles
105
+ // "Register: engineer" and other operator-prose shapes).
106
+ let bestIdx = -1;
107
+ let best = null;
108
+ for (const m of markers) {
109
+ const idx = text.lastIndexOf(m);
110
+ if (idx > bestIdx) { bestIdx = idx; best = m; }
111
+ }
112
+ return /** @type {any} */ (best) ?? 'unstated';
113
+ }
114
+
115
+ /**
116
+ * @param {string[]} argv
117
+ * @param {object} [deps]
118
+ * @returns {Promise<number>}
119
+ */
120
+ export async function main(argv, deps = {}) {
121
+ const stdout = deps.stdout ?? process.stdout;
122
+ const stderr = deps.stderr ?? process.stderr;
123
+ const cwd = deps.cwd ?? process.cwd();
124
+
125
+ let parsed;
126
+ try {
127
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: false, strict: true });
128
+ } catch (err) {
129
+ stderr.write(`[error] usage ${err.message}\n`);
130
+ stderr.write(HELP);
131
+ return 2;
132
+ }
133
+ const flags = parsed.values;
134
+ if (flags.help) {
135
+ stdout.write(HELP);
136
+ return 0;
137
+ }
138
+
139
+ // Validate --persona before any filesystem work.
140
+ let persona = flags.persona;
141
+ if (persona !== undefined && !PERSONAS.includes(persona)) {
142
+ stderr.write(`[error] usage questions: unknown persona '${persona}' (expected one of ${PERSONAS.join(' | ')})\n`);
143
+ return 2;
144
+ }
145
+
146
+ // Validate --stage against the readiness aliases (same vocabulary).
147
+ let stageFilter = null;
148
+ if (typeof flags.stage === 'string') {
149
+ const canonical = STAGE_ALIASES[flags.stage];
150
+ if (!canonical) {
151
+ stderr.write(`[error] usage questions: unknown --stage '${flags.stage}' (expected D1..D8 or a short name)\n`);
152
+ return 2;
153
+ }
154
+ stageFilter = canonical;
155
+ }
156
+
157
+ let limit = null;
158
+ if (flags.limit !== undefined) {
159
+ const n = Number(flags.limit);
160
+ if (!Number.isInteger(n) || n < 0) {
161
+ stderr.write(`[error] usage questions: --limit expects a non-negative integer, got '${flags.limit}'\n`);
162
+ return 2;
163
+ }
164
+ limit = n;
165
+ }
166
+
167
+ const projectRoot = await findProjectRoot(cwd);
168
+ if (!projectRoot) {
169
+ stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `npx rcf init` to create and wire a project.\n');
170
+ return 2;
171
+ }
172
+
173
+ const { tree, errors } = await walkTree({ projectRoot });
174
+ const staleErrors = await checkCodeNodeResolution({ projectRoot, tree });
175
+ const validateErrors = [...errors, ...staleErrors];
176
+
177
+ const [freeze, ledgers, testPointers, profileText] = await Promise.all([
178
+ loadFreezeRecord({ projectRoot }),
179
+ loadAllLedgers({ projectRoot }),
180
+ resolveTestPointers({ projectRoot, tree }),
181
+ readProfileText(projectRoot),
182
+ ]);
183
+
184
+ if (persona === undefined) {
185
+ const register = pickRegister(profileText);
186
+ persona = register === 'engineer' ? 'engineer' : 'productOwner';
187
+ }
188
+
189
+ const readiness = computeReadiness(tree, {
190
+ freeze, ledgers, profile: undefined, profileText, testPointers, validateErrors,
191
+ });
192
+
193
+ const result = computeQuestions(readiness, { tree, ledgers, profileText, persona });
194
+
195
+ // Apply --stage and --limit AFTER compute so `remaining` counts
196
+ // every matching item (spec §6).
197
+ let filtered = result.questions;
198
+ if (stageFilter) filtered = filtered.filter((q) => q.stage === stageFilter);
199
+ const remaining = filtered.length;
200
+ if (limit !== null) filtered = filtered.slice(0, limit);
201
+
202
+ // Rebuild groups[] from the filtered list, preserving source-span keys.
203
+ const groupMap = new Map();
204
+ for (const q of filtered) {
205
+ const key = q.context?.statement?.source ?? q.context?.sourceSpan ?? 'tree';
206
+ const existing = groupMap.get(key) ?? { key, label: result.groups.find((g) => g.key === key)?.label ?? key, questionIds: [] };
207
+ existing.questionIds.push(q.id);
208
+ groupMap.set(key, existing);
209
+ }
210
+ const envelope = {
211
+ ...result,
212
+ questions: filtered,
213
+ groups: [...groupMap.values()],
214
+ remaining,
215
+ };
216
+
217
+ if (flags.json) {
218
+ stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
219
+ return 0;
220
+ }
221
+
222
+ renderText(stdout, envelope);
223
+ return 0;
224
+ }
225
+
226
+ /**
227
+ * Print the text summary (spec §1.5): one progress line, then each
228
+ * group with its asks in plain words. Ids, verbs and stage names are
229
+ * not shown unless the owner asks.
230
+ *
231
+ * @param {NodeJS.WritableStream} stdout
232
+ * @param {ReturnType<typeof computeQuestions>} result
233
+ */
234
+ function renderText(stdout, result) {
235
+ const persona = result.persona;
236
+ // Termination is `result.ok` alone (spec §1.4): --stage filtering
237
+ // can leave remaining=0 for a stage while the whole tree is still
238
+ // failing, which must not print the complete verdict.
239
+ if (result.ok) {
240
+ stdout.write(persona === 'productOwner'
241
+ ? 'Intent-complete: yes. The question set is empty.\n'
242
+ : 'Ready-to-build: no engineer blockers in this stage.\n');
243
+ return;
244
+ }
245
+
246
+ // Progress line: "Intent-complete: not yet; N questions left, ..."
247
+ const n = result.remaining;
248
+ const partition = partitionByGroupLabel(result);
249
+ const parts = Object.entries(partition)
250
+ .filter(([, c]) => c > 0)
251
+ .map(([label, c]) => `${c} about ${label}`);
252
+ const progressLine = persona === 'productOwner'
253
+ ? `Intent-complete: not yet; ${n} ${n === 1 ? 'question' : 'questions'} left${parts.length ? `, ${parts.join(', ')}` : ''}.`
254
+ : `Ready-to-build: not yet; ${n} ${n === 1 ? 'check' : 'checks'} blocking.`;
255
+ stdout.write(`${progressLine}\n`);
256
+
257
+ for (const group of result.groups) {
258
+ stdout.write(`\n(${group.label})\n`);
259
+ for (const qid of group.questionIds) {
260
+ const q = result.questions.find((x) => x.id === qid);
261
+ if (!q) continue;
262
+ stdout.write(` - ${q.ask}\n`);
263
+ }
264
+ }
265
+
266
+ if (persona === 'productOwner' && result.optional?.length) {
267
+ stdout.write('\nOpen decisions (optional, not blocking):\n');
268
+ for (const o of result.optional) {
269
+ stdout.write(` - ${o.ask}\n`);
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Count questions by group-label bucket for the progress line. For
276
+ * the PO register the two buckets are "your document" (brief:... items)
277
+ * and "requirements" (REQ-... items); anything else goes under "other".
278
+ */
279
+ function partitionByGroupLabel(result) {
280
+ const buckets = { 'your document': 0, 'requirements': 0, 'other': 0 };
281
+ for (const q of result.questions) {
282
+ const src = q.context?.statement?.source ?? q.context?.sourceSpan ?? 'tree';
283
+ if (typeof src === 'string' && src !== 'tree' && !src.startsWith('scan:')) buckets['your document']++;
284
+ else if (q.context?.reqId) buckets['requirements']++;
285
+ else buckets['other']++;
286
+ }
287
+ return buckets;
288
+ }