rcf-lite 0.0.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/CHANGELOG.md +288 -0
  2. package/LICENSE +202 -0
  3. package/README.md +92 -4
  4. package/bin/rcf-verify.js +122 -0
  5. package/bin/rcf.js +174 -0
  6. package/bin/view-supervisor-child.mjs +14 -0
  7. package/fixtures/canary-manifest.json +103 -0
  8. package/guidance/README.md +25 -0
  9. package/guidance/build-cycle-playbook.md +492 -0
  10. package/guidance/build-cycle.md +40 -0
  11. package/guidance/document-model.md +45 -0
  12. package/guidance/elicitation-playbook.md +442 -0
  13. package/guidance/harness-template.md +246 -0
  14. package/guidance/managed/README.md +63 -0
  15. package/guidance/managed/agent-instructions-block.hash +1 -0
  16. package/guidance/managed/agent-instructions-block.md +220 -0
  17. package/guidance/managed/legacy-fragment-hashes.json +22 -0
  18. package/guidance/manifest.json +21 -0
  19. package/guidance/overview.md +48 -0
  20. package/guidance/persona-programme.md +44 -0
  21. package/package.json +69 -8
  22. package/rcf/adrs/adr-001.json +25 -0
  23. package/rcf/adrs/adr-002.json +25 -0
  24. package/rcf/adrs/adr-003.json +25 -0
  25. package/rcf/adrs/adr-004.json +20 -0
  26. package/rcf/adrs/adr-005.json +20 -0
  27. package/rcf/adrs/adr-006.json +25 -0
  28. package/rcf/adrs/adr-007.json +25 -0
  29. package/rcf/adrs/adr-008.json +25 -0
  30. package/rcf/adrs/adr-009.json +25 -0
  31. package/rcf/build-sequence.json +11 -0
  32. package/rcf/code-nodes/cn-001.json +14 -0
  33. package/rcf/code-nodes/cn-002.json +14 -0
  34. package/rcf/code-nodes/cn-003.json +15 -0
  35. package/rcf/code-nodes/cn-004.json +17 -0
  36. package/rcf/code-nodes/cn-005.json +19 -0
  37. package/rcf/code-nodes/cn-006.json +17 -0
  38. package/rcf/code-nodes/cn-007.json +16 -0
  39. package/rcf/code-nodes/cn-008.json +19 -0
  40. package/rcf/code-nodes/cn-009.json +16 -0
  41. package/rcf/code-nodes/cn-010.json +18 -0
  42. package/rcf/code-nodes/cn-011.json +18 -0
  43. package/rcf/code-nodes/cn-012.json +18 -0
  44. package/rcf/code-nodes/cn-013.json +19 -0
  45. package/rcf/code-nodes/cn-014.json +16 -0
  46. package/rcf/code-nodes/cn-015.json +16 -0
  47. package/rcf/code-nodes/cn-016.json +16 -0
  48. package/rcf/code-nodes/cn-017.json +16 -0
  49. package/rcf/code-nodes/cn-018.json +15 -0
  50. package/rcf/code-nodes/cn-019.json +16 -0
  51. package/rcf/code-nodes/cn-020.json +16 -0
  52. package/rcf/code-nodes/cn-021.json +16 -0
  53. package/rcf/code-nodes/cn-022.json +16 -0
  54. package/rcf/code-nodes/cn-023.json +16 -0
  55. package/rcf/code-nodes/cn-024.json +16 -0
  56. package/rcf/code-nodes/cn-025.json +16 -0
  57. package/rcf/code-nodes/cn-026.json +16 -0
  58. package/rcf/code-nodes/cn-027.json +16 -0
  59. package/rcf/code-nodes/cn-028.json +16 -0
  60. package/rcf/code-nodes/cn-029.json +19 -0
  61. package/rcf/code-nodes/cn-030.json +14 -0
  62. package/rcf/code-nodes/cn-031.json +14 -0
  63. package/rcf/code-nodes/cn-032.json +14 -0
  64. package/rcf/code-nodes/cn-033.json +14 -0
  65. package/rcf/code-nodes/cn-034.json +14 -0
  66. package/rcf/code-nodes/cn-035.json +14 -0
  67. package/rcf/code-nodes/cn-036.json +14 -0
  68. package/rcf/code-nodes/cn-037.json +14 -0
  69. package/rcf/code-nodes/cn-038.json +14 -0
  70. package/rcf/code-nodes/cn-039.json +14 -0
  71. package/rcf/code-nodes/cn-040.json +14 -0
  72. package/rcf/code-nodes/cn-041.json +14 -0
  73. package/rcf/code-nodes/cn-042.json +14 -0
  74. package/rcf/code-nodes/cn-043.json +14 -0
  75. package/rcf/code-nodes/cn-044.json +14 -0
  76. package/rcf/code-nodes/cn-045.json +14 -0
  77. package/rcf/code-nodes/cn-046.json +14 -0
  78. package/rcf/code-nodes/cn-047.json +14 -0
  79. package/rcf/code-nodes/cn-048.json +14 -0
  80. package/rcf/code-nodes/cn-049.json +14 -0
  81. package/rcf/code-nodes/cn-050.json +14 -0
  82. package/rcf/code-nodes/cn-051.json +14 -0
  83. package/rcf/code-nodes/cn-052.json +14 -0
  84. package/rcf/code-nodes/cn-053.json +14 -0
  85. package/rcf/code-nodes/cn-054.json +14 -0
  86. package/rcf/code-nodes/cn-055.json +14 -0
  87. package/rcf/code-nodes/cn-056.json +14 -0
  88. package/rcf/code-nodes/cn-057.json +14 -0
  89. package/rcf/fbs/fbs-001.json +49 -0
  90. package/rcf/fbs/fbs-002.json +42 -0
  91. package/rcf/fbs/fbs-003.json +37 -0
  92. package/rcf/fbs/fbs-004.json +39 -0
  93. package/rcf/fbs/fbs-005.json +38 -0
  94. package/rcf/fbs/fbs-006.json +48 -0
  95. package/rcf/fbs/fbs-007.json +39 -0
  96. package/rcf/fbs/fbs-008.json +40 -0
  97. package/rcf/fbs/fbs-009.json +36 -0
  98. package/rcf/fbs/fbs-010.json +41 -0
  99. package/rcf/fbs/fbs-011.json +36 -0
  100. package/rcf/fbs/fbs-012.json +46 -0
  101. package/rcf/fbs/fbs-013.json +42 -0
  102. package/rcf/fbs/fbs-014.json +49 -0
  103. package/rcf/fbs/fbs-015.json +32 -0
  104. package/rcf/manifest.json +17 -0
  105. package/rcf/prd.json +47 -0
  106. package/rcf/requirements/req-001.json +19 -0
  107. package/rcf/requirements/req-002.json +19 -0
  108. package/rcf/requirements/req-003.json +19 -0
  109. package/rcf/requirements/req-004.json +19 -0
  110. package/rcf/requirements/req-005.json +19 -0
  111. package/rcf/requirements/req-006.json +19 -0
  112. package/rcf/requirements/req-007.json +19 -0
  113. package/rcf/requirements/req-008.json +19 -0
  114. package/rcf/requirements/req-009.json +19 -0
  115. package/rcf/tacs/tac-001.json +45 -0
  116. package/rcf/tacs/tac-002.json +109 -0
  117. package/rcf/tacs/tac-003.json +40 -0
  118. package/rcf/tacs/tac-004.json +51 -0
  119. package/rcf/tacs/tac-005.json +52 -0
  120. package/rcf/tacs/tac-006.json +104 -0
  121. package/rcf/tacs/tac-007.json +38 -0
  122. package/rcf/tacs/tac-008.json +51 -0
  123. package/rcf/tad.json +51 -0
  124. package/rcf/test-suites/PENDING.md +23 -0
  125. package/rcf/test-suites/ts-001.json +38 -0
  126. package/rcf/test-suites/ts-002.json +38 -0
  127. package/rcf/test-suites/ts-003.json +43 -0
  128. package/rcf/test-suites/ts-004.json +44 -0
  129. package/rcf/test-suites/ts-005.json +30 -0
  130. package/rcf/test-suites/ts-006.json +36 -0
  131. package/rcf/test-suites/ts-007.json +43 -0
  132. package/rcf/test-suites/ts-008.json +37 -0
  133. package/rcf/test-suites/ts-009.json +38 -0
  134. package/rcf/test-suites/ts-010.json +38 -0
  135. package/rcf/test-suites/ts-011.json +44 -0
  136. package/rcf/test-suites/ts-012.json +36 -0
  137. package/rcf/test-suites/ts-013.json +38 -0
  138. package/rcf/test-suites/ts-014.json +38 -0
  139. package/rcf/test-suites/ts-015.json +38 -0
  140. package/rcf/test-suites/ts-016.json +37 -0
  141. package/rcf/test-suites/ts-017.json +38 -0
  142. package/rcf/test-suites/ts-018.json +38 -0
  143. package/rcf/test-suites/ts-019.json +37 -0
  144. package/rcf/test-suites/ts-020.json +46 -0
  145. package/rcf/test-suites/ts-021.json +46 -0
  146. package/rcf/test-suites/ts-022.json +46 -0
  147. package/rcf/test-suites/ts-023.json +46 -0
  148. package/rcf/test-suites/ts-024.json +46 -0
  149. package/rcf/test-suites/ts-025.json +52 -0
  150. package/rcf/user-stories/us-101.json +40 -0
  151. package/rcf/user-stories/us-102.json +40 -0
  152. package/rcf/user-stories/us-201.json +40 -0
  153. package/rcf/user-stories/us-202.json +40 -0
  154. package/rcf/user-stories/us-203.json +32 -0
  155. package/rcf/user-stories/us-301.json +40 -0
  156. package/rcf/user-stories/us-302.json +40 -0
  157. package/rcf/user-stories/us-303.json +40 -0
  158. package/rcf/user-stories/us-304.json +40 -0
  159. package/rcf/user-stories/us-401.json +40 -0
  160. package/rcf/user-stories/us-402.json +40 -0
  161. package/rcf/user-stories/us-403.json +40 -0
  162. package/rcf/user-stories/us-501.json +40 -0
  163. package/rcf/user-stories/us-502.json +40 -0
  164. package/rcf/user-stories/us-503.json +40 -0
  165. package/rcf/user-stories/us-601.json +40 -0
  166. package/rcf/user-stories/us-602.json +40 -0
  167. package/rcf/user-stories/us-701.json +40 -0
  168. package/rcf/user-stories/us-702.json +40 -0
  169. package/rcf/user-stories/us-801.json +49 -0
  170. package/rcf/user-stories/us-802.json +49 -0
  171. package/rcf/user-stories/us-803.json +49 -0
  172. package/rcf/user-stories/us-804.json +49 -0
  173. package/rcf/user-stories/us-805.json +49 -0
  174. package/rcf/user-stories/us-901.json +40 -0
  175. package/src/.gitkeep +0 -0
  176. package/src/browser-verify/auth-smoke.js +109 -0
  177. package/src/browser-verify/index.js +29 -0
  178. package/src/browser-verify/invariants.js +336 -0
  179. package/src/browser-verify/manifest-writer.js +189 -0
  180. package/src/browser-verify/runner.js +171 -0
  181. package/src/build/bundle.js +198 -0
  182. package/src/build/formatters/json.js +18 -0
  183. package/src/build/formatters/markdown.js +406 -0
  184. package/src/build/index.js +14 -0
  185. package/src/build/mark.js +177 -0
  186. package/src/build/queue.js +285 -0
  187. package/src/cli/browser-verify.js +231 -0
  188. package/src/cli/build.js +584 -0
  189. package/src/cli/coverage.js +219 -0
  190. package/src/cli/create.js +355 -0
  191. package/src/cli/delete.js +127 -0
  192. package/src/cli/design.js +353 -0
  193. package/src/cli/doctor.js +548 -0
  194. package/src/cli/fbs.js +171 -0
  195. package/src/cli/finalise.js +377 -0
  196. package/src/cli/guidance.js +206 -0
  197. package/src/cli/help.js +156 -0
  198. package/src/cli/impact.js +119 -0
  199. package/src/cli/init.js +282 -0
  200. package/src/cli/intake.js +153 -0
  201. package/src/cli/link.js +128 -0
  202. package/src/cli/mcp.js +160 -0
  203. package/src/cli/preflight.js +220 -0
  204. package/src/cli/read.js +162 -0
  205. package/src/cli/req-baseline.js +269 -0
  206. package/src/cli/req-classify.js +135 -0
  207. package/src/cli/review.js +295 -0
  208. package/src/cli/test-suite.js +216 -0
  209. package/src/cli/trace.js +172 -0
  210. package/src/cli/ui-baseline.js +292 -0
  211. package/src/cli/ui-classify.js +108 -0
  212. package/src/cli/update.js +197 -0
  213. package/src/cli/validate.js +168 -0
  214. package/src/cli/view.js +452 -0
  215. package/src/core/baseline-catalog/data/auth.json +42 -0
  216. package/src/core/baseline-catalog/data/http-api.json +42 -0
  217. package/src/core/baseline-catalog/data/notifications.json +33 -0
  218. package/src/core/baseline-catalog/data/persistence.json +33 -0
  219. package/src/core/baseline-catalog/data/web-ui.json +60 -0
  220. package/src/core/baseline-catalog/index.js +121 -0
  221. package/src/core/errors/index.js +167 -0
  222. package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
  223. package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
  224. package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
  225. package/src/core/isolation/index.js +60 -0
  226. package/src/core/mcp/framing.js +103 -0
  227. package/src/core/mcp/index.js +8 -0
  228. package/src/core/mcp/server.js +228 -0
  229. package/src/core/patterns/register-canary.js +209 -0
  230. package/src/core/patterns/req-shapes.js +158 -0
  231. package/src/core/patterns/services.js +358 -0
  232. package/src/core/patterns/ui-shapes.js +166 -0
  233. package/src/core/store/cn-resolve.js +134 -0
  234. package/src/core/store/derive-deps.js +93 -0
  235. package/src/core/store/ids.js +78 -0
  236. package/src/core/store/index.js +20 -0
  237. package/src/core/store/init.js +255 -0
  238. package/src/core/store/loader.js +211 -0
  239. package/src/core/store/tp-resolve.js +176 -0
  240. package/src/core/store/validator.js +191 -0
  241. package/src/core/store/walker.js +898 -0
  242. package/src/core/store/writer.js +1849 -0
  243. package/src/design/index.js +11 -0
  244. package/src/design/writer.js +271 -0
  245. package/src/finalise/detect.js +129 -0
  246. package/src/finalise/index.js +17 -0
  247. package/src/finalise/ingest.js +119 -0
  248. package/src/finalise/install.js +119 -0
  249. package/src/finalise/ship-without-verified.js +131 -0
  250. package/src/finalise/spawn.js +84 -0
  251. package/src/intake/fidelity.js +105 -0
  252. package/src/intake/index.js +6 -0
  253. package/src/intake/manifest-writer.js +100 -0
  254. package/src/intake/orchestrator.js +138 -0
  255. package/src/intake/validate.js +80 -0
  256. package/src/mcp/map-errors.js +131 -0
  257. package/src/mcp/prompts.js +49 -0
  258. package/src/mcp/resources.js +244 -0
  259. package/src/mcp/tools.js +1204 -0
  260. package/src/preflight/design-shapes.js +185 -0
  261. package/src/preflight/index.js +90 -0
  262. package/src/preflight/manifest-writer.js +264 -0
  263. package/src/preflight/scanner.js +206 -0
  264. package/src/preflight/secrets.js +134 -0
  265. package/src/preflight/session.js +246 -0
  266. package/src/query/attestation.js +285 -0
  267. package/src/query/coverage.js +308 -0
  268. package/src/query/formatters/json.js +21 -0
  269. package/src/query/formatters/mermaid.js +209 -0
  270. package/src/query/formatters/table.js +203 -0
  271. package/src/query/impact.js +173 -0
  272. package/src/query/index.js +9 -0
  273. package/src/query/trace.js +345 -0
  274. package/src/register-canary/fixture-loader.js +87 -0
  275. package/src/register-canary/index.js +10 -0
  276. package/src/register-canary/record-writer.js +132 -0
  277. package/src/register-canary/runner.js +156 -0
  278. package/src/req-baseline/gate.js +86 -0
  279. package/src/req-baseline/index.js +27 -0
  280. package/src/req-baseline/open-candidates.js +143 -0
  281. package/src/req-baseline/opt-out.js +195 -0
  282. package/src/req-baseline/sweep.js +230 -0
  283. package/src/req-detection/classifier.js +181 -0
  284. package/src/req-detection/index.js +9 -0
  285. package/src/req-detection/persist.js +55 -0
  286. package/src/review/index.js +325 -0
  287. package/src/review/mutation.js +117 -0
  288. package/src/review/ui-baseline-drift.js +138 -0
  289. package/src/server/index.js +178 -0
  290. package/src/server/routes.js +110 -0
  291. package/src/server/sse.js +118 -0
  292. package/src/setup/agent-setup.js +362 -0
  293. package/src/setup/identity-seed.js +104 -0
  294. package/src/setup/knowledge-seed.js +123 -0
  295. package/src/setup/managed-block.js +193 -0
  296. package/src/setup/managed-gitignore.js +166 -0
  297. package/src/setup/managed-markers.js +49 -0
  298. package/src/ui-baseline/defaults.js +119 -0
  299. package/src/ui-baseline/index.js +25 -0
  300. package/src/ui-baseline/manifest-writer.js +282 -0
  301. package/src/ui-baseline/session.js +178 -0
  302. package/src/ui-detection/classifier.js +192 -0
  303. package/src/verify/chain/index.js +190 -0
  304. package/src/verify/cli/cleanup.js +61 -0
  305. package/src/verify/cli/help.js +56 -0
  306. package/src/verify/cli/mcp.js +98 -0
  307. package/src/verify/cli/provision.js +71 -0
  308. package/src/verify/cli/report.js +71 -0
  309. package/src/verify/cli/run.js +155 -0
  310. package/src/verify/engine/brief.js +87 -0
  311. package/src/verify/engine/index.js +177 -0
  312. package/src/verify/engine/launcher.js +307 -0
  313. package/src/verify/mcp/tools.js +107 -0
  314. package/src/verify/profile/index.js +146 -0
  315. package/src/verify/provision/index.js +256 -0
  316. package/src/verify/report/index.js +139 -0
  317. package/src/verify/report/renderer.js +118 -0
  318. package/src/verify/verdict/index.js +246 -0
  319. package/src/view/doc-renderers/adr.js +44 -0
  320. package/src/view/doc-renderers/build-sequence.js +40 -0
  321. package/src/view/doc-renderers/fbs.js +128 -0
  322. package/src/view/doc-renderers/helpers.js +159 -0
  323. package/src/view/doc-renderers/index.js +12 -0
  324. package/src/view/doc-renderers/prd.js +45 -0
  325. package/src/view/doc-renderers/req.js +43 -0
  326. package/src/view/doc-renderers/tac.js +38 -0
  327. package/src/view/doc-renderers/tad.js +74 -0
  328. package/src/view/doc-renderers/test-suite.js +45 -0
  329. package/src/view/doc-renderers/user-story.js +63 -0
  330. package/src/view/html-page.js +462 -0
  331. package/src/view/index.js +63 -0
  332. package/src/view/live-client.js +338 -0
  333. package/src/view/mermaid-diagram.js +178 -0
  334. package/src/view/style.css +735 -0
  335. package/src/view/tree-model.js +152 -0
  336. package/src/view/vendored/mermaid.min.js +2607 -0
  337. package/src/view-supervisor/index.js +26 -0
  338. package/src/view-supervisor/logs.js +32 -0
  339. package/src/view-supervisor/manifest-writer.js +178 -0
  340. package/src/view-supervisor/persist-until.js +85 -0
  341. package/src/view-supervisor/supervisor.js +276 -0
  342. package/src/watch/index.js +152 -0
@@ -0,0 +1,282 @@
1
+ // `rcf init` subcommand handler. Interactive by default when stdin +
2
+ // stdout are TTYs and --non-interactive is not set. Init is a bootstrap,
3
+ // not an elicitation session: it asks only for the project name and
4
+ // seeds a fully placeholder document tree - the same tree the
5
+ // non-interactive path produces - which the agent fills in during
6
+ // elicitation. It deliberately does NOT ask the user to name a first
7
+ // requirement / user story / problem statement up front (operator review
8
+ // 2026-07-16, comment 2: a product owner does not know those yet, and
9
+ // asking makes them freeze or type junk). Zero deps; prompt via
10
+ // node:readline/promises.
11
+ //
12
+ // Theme 1 (E2E matrix 2026-07-06-003): init is the full pre-session
13
+ // bootstrap. After scaffolding the tree it also (1) writes/merges the
14
+ // project-root .mcp.json rcf server entry and (2) writes the guidance
15
+ // fragment into CLAUDE.md / AGENTS.md inside rcf marker comments -
16
+ // the project is wired BEFORE the agent session starts. Re-running
17
+ // init on an existing tree leaves the tree alone and refreshes the
18
+ // wiring (idempotent). --no-agent-setup skips the wiring and prints
19
+ // the manual instructions instead.
20
+
21
+ import { parseArgs } from 'node:util';
22
+ import { createInterface } from 'node:readline/promises';
23
+
24
+ import { readFile, writeFile } from 'node:fs/promises';
25
+ import { join } from 'node:path';
26
+
27
+ import { initProject } from '#core/store/init.js';
28
+ import {
29
+ loadManagedBlock,
30
+ manualSetupInstructions,
31
+ writeAgentInstructions,
32
+ writeMcpConfig,
33
+ } from '../setup/agent-setup.js';
34
+ import { writeKnowledgeSeed } from '../setup/knowledge-seed.js';
35
+ import { writeIdentityTemplate } from '../setup/identity-seed.js';
36
+ import {
37
+ composeGitignoreBlock,
38
+ computeGitignoreBlockHash,
39
+ extractGitignoreBlock,
40
+ GITIGNORE_MARKER_BEGIN,
41
+ GITIGNORE_MARKER_END,
42
+ } from '../setup/managed-gitignore.js';
43
+ import { hashInnerContent } from '../setup/managed-block.js';
44
+
45
+ const OPTION_SPEC = {
46
+ 'project-name': { type: 'string' },
47
+ 'non-interactive': { type: 'boolean' },
48
+ 'no-agent-setup': { type: 'boolean' },
49
+ quiet: { type: 'boolean' },
50
+ help: { type: 'boolean' },
51
+ };
52
+
53
+ export const HELP = `Usage: rcf init [options]
54
+
55
+ Scaffolds the rcf/ tree, registers the MCP server in the project-root
56
+ .mcp.json and writes the agent-instructions fragment into both CLAUDE.md
57
+ and AGENTS.md (an existing instructions file is refreshed in place) -
58
+ the full pre-session bootstrap. Interactive runs prompt only for the
59
+ project name; everything else is seeded as placeholders for the agent
60
+ to elicit. Re-running on an existing project leaves the tree alone and
61
+ refreshes the wiring.
62
+
63
+ Options:
64
+ --project-name <name> Project name (required for --non-interactive)
65
+ --non-interactive Skip prompts; use seed values (default when
66
+ not on a TTY or when piped)
67
+ --no-agent-setup Scaffold the tree only; print the manual
68
+ harness-wiring instructions instead
69
+ --quiet Suppress non-error stdout
70
+ --help Print this help
71
+ `;
72
+
73
+ /**
74
+ * @param {string[]} argv - argv slice after `init`
75
+ * @param {object} [deps]
76
+ * @returns {Promise<number>}
77
+ */
78
+ export async function main(argv, deps = {}) {
79
+ const stdout = deps.stdout ?? process.stdout;
80
+ const stderr = deps.stderr ?? process.stderr;
81
+ const stdin = deps.stdin ?? process.stdin;
82
+
83
+ let parsed;
84
+ try {
85
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
86
+ } catch (err) {
87
+ stderr.write(`[error] usage ${err.message}\n`);
88
+ stderr.write(HELP);
89
+ return 2;
90
+ }
91
+ const flags = parsed.values;
92
+ if (flags.help) {
93
+ stdout.write(HELP);
94
+ return 0;
95
+ }
96
+ const cwd = deps.cwd ?? process.cwd();
97
+ const agentSetup = !flags['no-agent-setup'];
98
+ const forceNonInteractive = Boolean(flags['non-interactive']);
99
+ const isTty = Boolean(stdout.isTTY && stdin.isTTY);
100
+ const interactive = !forceNonInteractive && isTty;
101
+
102
+ let projectName = flags['project-name'];
103
+ let seed = null;
104
+
105
+ if (interactive) {
106
+ // Init asks only for the project name. Everything else in the tree
107
+ // is seeded as a placeholder for the agent to elicit - see the file
108
+ // header. `seed` stays null so interactive and non-interactive
109
+ // produce the identical placeholder tree.
110
+ const rl = createInterface({ input: stdin, output: stdout });
111
+ try {
112
+ projectName = projectName ?? (await rl.question('Project name: ')).trim();
113
+ if (!projectName) projectName = 'New RCF Project';
114
+ } finally {
115
+ rl.close();
116
+ }
117
+ } else {
118
+ if (!projectName) {
119
+ stderr.write('[error] usage --project-name is required in non-interactive mode\n');
120
+ stderr.write(HELP);
121
+ return 2;
122
+ }
123
+ }
124
+
125
+ const result = await initProject({ projectRoot: cwd, projectName, seed });
126
+ const treeExists = Boolean(result && 'kind' in result && result.kind === 'usage'
127
+ && /already exists/.test(result.message));
128
+ if (result && 'kind' in result && result.kind === 'usage' && !treeExists) {
129
+ stderr.write(`[error] usage ${result.message}\n`);
130
+ return 2;
131
+ }
132
+ if (result && 'kind' in result && !treeExists) {
133
+ stderr.write(`[error] ${result.kind} ${result.message}\n`);
134
+ return 1;
135
+ }
136
+ if (treeExists && !agentSetup) {
137
+ // Nothing to do at all: tree present, wiring explicitly skipped.
138
+ stderr.write(`[error] usage ${result.message}\n`);
139
+ return 2;
140
+ }
141
+
142
+ // --no-agent-setup: scaffold the tree only, print the manual wiring
143
+ // steps. (A tree-already-present + opt-out combination has already
144
+ // been refused above, so this branch is always a fresh scaffold.)
145
+ if (!agentSetup) {
146
+ if (!flags.quiet) {
147
+ stdout.write('Set up the RCF document chain under rcf/.\n');
148
+ stdout.write(`${manualSetupInstructions()}\n`);
149
+ }
150
+ return 0;
151
+ }
152
+
153
+ // Step 1: .mcp.json (merge; never clobber other servers / unknown keys).
154
+ const mcpResult = await writeMcpConfig({ projectRoot: cwd });
155
+ if (mcpResult && 'kind' in mcpResult && 'message' in mcpResult) {
156
+ stderr.write(`[error] ${mcpResult.kind} ${mcpResult.message}\n`);
157
+ return 2;
158
+ }
159
+
160
+ // Step 2: agent-instructions managed block inside rcf markers (idempotent).
161
+ // Source is the 0.6.0 canonical asset, not the harness-template fenced
162
+ // fragment (which is now regenerated from the same canonical text at
163
+ // package build time via scripts/gen-managed-artefacts.mjs).
164
+ const fragment = await loadManagedBlock();
165
+ if (typeof fragment !== 'string') {
166
+ stderr.write(`[error] ${fragment.kind} ${fragment.message}\n`);
167
+ return 1;
168
+ }
169
+ const instrResult = await writeAgentInstructions({ projectRoot: cwd, fragment });
170
+
171
+ // Step 3: knowledge space (§3, AC-2.1..AC-2.3). Idempotent per file.
172
+ const knowledgeResult = await writeKnowledgeSeed({ projectRoot: cwd });
173
+ // Step 4: identity template (§5, AC-4.1..AC-4.2). Idempotent.
174
+ const identityResult = await writeIdentityTemplate({ projectRoot: cwd });
175
+ // Step 5: managed .gitignore block (§4, AC-3.1..AC-3.2). Written or
176
+ // refreshed in place; operator content outside preserved byte-for-byte.
177
+ const gitignoreResult = await ensureManagedGitignore({ projectRoot: cwd });
178
+
179
+ // High-level completion summary: what was set up and what to do next -
180
+ // not a developer file list (operator review 2026-07-16, comment 3a).
181
+ if (!flags.quiet) {
182
+ let mcpDesc;
183
+ if (mcpResult.action === 'kept') mcpDesc = 'already registered in .mcp.json (kept)';
184
+ else if (mcpResult.action === 'merged') mcpDesc = 'registered in .mcp.json (merged with your existing servers)';
185
+ else mcpDesc = 'registered in .mcp.json';
186
+
187
+ const instrFiles = instrResult.writes.map((w) => w.file).join(' and ');
188
+ const actions = new Set(instrResult.writes.map((w) => w.action));
189
+ let instrVerb;
190
+ if (actions.size === 1 && actions.has('created')) instrVerb = 'written to';
191
+ else if (actions.has('replaced')) instrVerb = 'refreshed in';
192
+ else instrVerb = 'updated in';
193
+
194
+ if (treeExists) {
195
+ stdout.write('RCF project already set up here - document chain left untouched, agent wiring refreshed.\n');
196
+ } else {
197
+ stdout.write('RCF project created.\n');
198
+ stdout.write(' Document chain scaffolded under rcf/ - PRD, requirement, story, acceptance criterion, architecture and build-sequence placeholders for your agent to fill in.\n');
199
+ }
200
+ stdout.write(` MCP server ${mcpDesc}.\n`);
201
+ stdout.write(` Agent instructions ${instrVerb} ${instrFiles}.\n`);
202
+ stdout.write(` Knowledge space ${knowledgeVerbAndTarget(knowledgeResult)}.\n`);
203
+ stdout.write(` Operator profile ${identityVerbAndTarget(identityResult)}.\n`);
204
+ stdout.write(` Gitignore ${gitignoreVerbAndTarget(gitignoreResult)}.\n`);
205
+ stdout.write('\nNext: start your agent session in this directory and tell it what you want to build. '
206
+ + 'It elicits the requirements and drives the build from there - you do not fill in the document chain by hand.\n');
207
+ }
208
+ return 0;
209
+ }
210
+
211
+ function knowledgeVerbAndTarget(result) {
212
+ const created = result.writes.filter((w) => w.action === 'created').length;
213
+ if (created === 0) return 'left as-is at rcf/knowledge/ (all managed files already present)';
214
+ if (created === result.writes.length) return 'seeded at rcf/knowledge/ (write what you learn; grep before asking)';
215
+ return `refreshed at rcf/knowledge/ (${created} new managed files)`;
216
+ }
217
+
218
+ function identityVerbAndTarget(result) {
219
+ if (result.action === 'kept') return 'left as-is at rcf/.identity/profile.md (already present)';
220
+ return 'template at rcf/.identity/profile.md (gitignored by default; fill in what is useful)';
221
+ }
222
+
223
+ function gitignoreVerbAndTarget(result) {
224
+ if (result.action === 'noop') return 'managed block already up to date in .gitignore';
225
+ if (result.action === 'created') return 'managed block written to .gitignore (rcf/.identity/ ignored)';
226
+ if (result.action === 'appended') return 'managed block appended to existing .gitignore (rcf/.identity/ ignored)';
227
+ return 'managed block refreshed in .gitignore (rcf/.identity/ ignored)';
228
+ }
229
+
230
+ /**
231
+ * Ensure the project-root `.gitignore` carries the current managed
232
+ * block. Writes/refreshes in place; operator entries outside markers
233
+ * preserved byte-for-byte. Idempotent when block is already current.
234
+ *
235
+ * @param {object} args
236
+ * @param {string} args.projectRoot
237
+ * @returns {Promise<{ action: 'created' | 'appended' | 'replaced' | 'noop' }>}
238
+ */
239
+ async function ensureManagedGitignore({ projectRoot }) {
240
+ const path = join(projectRoot, '.gitignore');
241
+ let existing;
242
+ try {
243
+ existing = await readFile(path, 'utf8');
244
+ } catch (err) {
245
+ if (/** @type {NodeJS.ErrnoException} */ (err).code !== 'ENOENT') throw err;
246
+ await writeFile(path, composeGitignoreBlock(), 'utf8');
247
+ return { action: 'created' };
248
+ }
249
+ const located = extractGitignoreBlock(existing);
250
+ const composed = composeGitignoreBlock();
251
+ if (!located) {
252
+ // No managed block yet - append with a blank-line separator.
253
+ const beginCount = countLocal(existing, GITIGNORE_MARKER_BEGIN);
254
+ const endCount = countLocal(existing, GITIGNORE_MARKER_END);
255
+ if (beginCount !== 0 || endCount !== 0) {
256
+ // Structurally malformed - leave alone; doctor will surface the
257
+ // drift and the operator repairs by hand.
258
+ return { action: 'noop' };
259
+ }
260
+ const sep = existing.length === 0 ? '' : (existing.endsWith('\n') ? '\n' : '\n\n');
261
+ await writeFile(path, `${existing}${sep}${composed}`, 'utf8');
262
+ return { action: 'appended' };
263
+ }
264
+ const innerHash = hashInnerContent(located.innerText);
265
+ const expected = computeGitignoreBlockHash();
266
+ if (innerHash === expected) return { action: 'noop' };
267
+ const next = existing.slice(0, located.beginIndex) + composed + existing.slice(located.endIndex);
268
+ await writeFile(path, next, 'utf8');
269
+ return { action: 'replaced' };
270
+ }
271
+
272
+ function countLocal(haystack, needle) {
273
+ if (!needle) return 0;
274
+ let n = 0;
275
+ let i = 0;
276
+ while (true) {
277
+ const at = haystack.indexOf(needle, i);
278
+ if (at < 0) return n;
279
+ n += 1;
280
+ i = at + needle.length;
281
+ }
282
+ }
@@ -0,0 +1,153 @@
1
+ // `rcf intake` subcommand handler
2
+ // (elicitation-and-playbook-hardening-0.7.0-spec §6).
3
+ //
4
+ // Variable-fidelity intake stage. Classifies the supplied artefacts by
5
+ // fidelity (none | napkin | briefLight | briefStrong | prd | prdPlusTad
6
+ // per spec §3.7), applies elicitation-integrity discipline (§6.4 phase 2
7
+ // scans: impliedButNotStated, contradiction, missingLoadBearingConstraint),
8
+ // records validation findings, and writes an intakeClassification record
9
+ // to the manifest. Elicitation of gaps handed off to the standard
10
+ // playbook §3-§7 (this verb closes at phase 2).
11
+
12
+ import { readFile } from 'node:fs/promises';
13
+ import { parseArgs } from 'node:util';
14
+
15
+ import { walkTree } from '#core/store';
16
+
17
+ import { findProjectRoot } from '../view/index.js';
18
+ import {
19
+ runIntakePhases,
20
+ writeIntakeRecord,
21
+ } from '../intake/index.js';
22
+
23
+ const OPTION_SPEC = {
24
+ artefact: { type: 'string', multiple: true },
25
+ kind: { type: 'string' },
26
+ input: { type: 'string' },
27
+ 'dry-run': { type: 'boolean' },
28
+ json: { type: 'boolean' },
29
+ quiet: { type: 'boolean' },
30
+ help: { type: 'boolean' },
31
+ };
32
+
33
+ export const HELP = `Usage: rcf intake [--artefact <path>[,path]] [--kind <kind>] [--dry-run]
34
+ rcf intake --input <config.json> [--dry-run]
35
+
36
+ Variable-fidelity intake: read what the operator supplied, classify its
37
+ fidelity, validate it against elicitation integrity, and record an
38
+ intakeClassification block on the manifest. Runs BEFORE the standard
39
+ elicitation playbook when the operator has any starting material.
40
+
41
+ Options:
42
+ --artefact <path>[,path] One or more artefact file paths (repeatable
43
+ or comma-separated)
44
+ --kind <kind> Hint the artefact kind: napkin |
45
+ productBrief | prd | prdPlusTad | other
46
+ --input <config.json> Non-interactive mode: pre-filled artefacts
47
+ + findings + acknowledgements
48
+ --dry-run Print the plan without writing the record
49
+ --json Emit the intakeClassification block as JSON
50
+ --quiet Suppress non-error confirmations
51
+ --help Print this help
52
+
53
+ Exit codes:
54
+ 0 success
55
+ 2 usage error (missing --artefact/--input, unreadable file)
56
+ 3 validation failure on the written manifest
57
+ `;
58
+
59
+ /**
60
+ * @param {string[]} argv
61
+ * @param {object} [deps]
62
+ * @returns {Promise<number>}
63
+ */
64
+ export async function main(argv, deps = {}) {
65
+ const stdout = deps.stdout ?? process.stdout;
66
+ const stderr = deps.stderr ?? process.stderr;
67
+ const cwd = deps.cwd ?? process.cwd();
68
+
69
+ let parsed;
70
+ try {
71
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
72
+ } catch (err) {
73
+ stderr.write(`[error] usage ${err.message}\n`);
74
+ stderr.write(HELP);
75
+ return 2;
76
+ }
77
+ const flags = parsed.values;
78
+ if (flags.help) { stdout.write(HELP); return 0; }
79
+
80
+ const projectRoot = await findProjectRoot(cwd);
81
+ if (!projectRoot) {
82
+ 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');
83
+ return 2;
84
+ }
85
+
86
+ let input = null;
87
+ if (flags.input) {
88
+ try {
89
+ input = JSON.parse(await readFile(flags.input, 'utf8'));
90
+ } catch (err) {
91
+ stderr.write(`[error] usage intake: cannot read --input file: ${err.message}\n`);
92
+ return 2;
93
+ }
94
+ }
95
+
96
+ const artefactPaths = [];
97
+ if (input?.artefacts) {
98
+ for (const a of input.artefacts) {
99
+ if (typeof a?.path === 'string' && a.path.length > 0) artefactPaths.push(a.path);
100
+ }
101
+ }
102
+ for (const raw of flags.artefact ?? []) {
103
+ for (const p of raw.split(',').map((s) => s.trim()).filter(Boolean)) artefactPaths.push(p);
104
+ }
105
+ if (artefactPaths.length === 0 && !input?.fidelity) {
106
+ stderr.write('[error] usage intake: at least one --artefact <path> is required (or an --input config with artefacts or a bare fidelity)\n');
107
+ return 2;
108
+ }
109
+
110
+ const walkResult = await walkTree({ projectRoot });
111
+ if (walkResult.errors.length > 0) {
112
+ stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
113
+ }
114
+
115
+ const outcome = await runIntakePhases({
116
+ projectRoot,
117
+ artefactPaths,
118
+ kindHint: flags.kind ?? null,
119
+ input,
120
+ });
121
+ if (outcome && outcome.kind && typeof outcome.message === 'string') {
122
+ stderr.write(`[error] ${outcome.kind} ${outcome.message}\n`);
123
+ return outcome.kind === 'usage' ? 2 : 3;
124
+ }
125
+
126
+ if (flags['dry-run']) {
127
+ if (flags.json) {
128
+ stdout.write(`${JSON.stringify(outcome.record, null, 2)}\n`);
129
+ } else {
130
+ stdout.write(`[dry-run] would write intakeClassification ${outcome.record.id} (fidelity=${outcome.record.fidelity}, findings=${outcome.record.validationFindings.length})\n`);
131
+ }
132
+ return 0;
133
+ }
134
+
135
+ const result = await writeIntakeRecord({
136
+ projectRoot,
137
+ tree: walkResult.tree,
138
+ record: outcome.record,
139
+ });
140
+ if (result && result.kind && typeof result.message === 'string') {
141
+ stderr.write(`[error] ${result.kind} ${result.message}\n`);
142
+ return 3;
143
+ }
144
+
145
+ if (flags.json) {
146
+ stdout.write(`${JSON.stringify(outcome.record, null, 2)}\n`);
147
+ return 0;
148
+ }
149
+ if (!flags.quiet) {
150
+ stdout.write(`intake: ${outcome.record.id} fidelity=${outcome.record.fidelity} findings=${outcome.record.validationFindings.length}\n`);
151
+ }
152
+ return 0;
153
+ }
@@ -0,0 +1,128 @@
1
+ // `rcf link` / `rcf unlink` verb (Phase 4 §D19). Idempotent append/remove
2
+ // of TAC ids on a US's `tacIds[]`. Depends on `@stravica-ai/rcf-schemas`
3
+ // having `US.tacIds[]` defined (0.2.1+).
4
+
5
+ import { parseArgs } from 'node:util';
6
+
7
+ import { isRcfError } from '#core/errors';
8
+ import { updateDocument, walkTree } from '#core/store';
9
+ import { findProjectRoot } from '../view/index.js';
10
+
11
+ const OPTION_SPEC = {
12
+ tac: { type: 'string', multiple: true },
13
+ 'dry-run': { type: 'boolean' },
14
+ quiet: { type: 'boolean' },
15
+ help: { type: 'boolean' },
16
+ };
17
+
18
+ export const HELP = `Usage: rcf link <us-id> --tac <tac-id> [options]
19
+
20
+ Options:
21
+ --tac <tac-id> TAC id to link (repeatable to link multiple
22
+ TACs in one invocation)
23
+ --dry-run Print the intended write without executing
24
+ --quiet Suppress non-error stdout
25
+ --help Print this help
26
+ `;
27
+
28
+ export const UNLINK_HELP = `Usage: rcf unlink <us-id> --tac <tac-id> [options]
29
+
30
+ Options:
31
+ --tac <tac-id> TAC id to unlink (repeatable)
32
+ --dry-run Print the intended write without executing
33
+ --quiet Suppress non-error stdout
34
+ --help Print this help
35
+ `;
36
+
37
+ /**
38
+ * @param {string[]} argv - argv slice after `link`
39
+ * @param {object} deps
40
+ * @param {boolean} deps.remove - false for link, true for unlink
41
+ * @returns {Promise<number>}
42
+ */
43
+ export async function main(argv, deps = {}) {
44
+ const stdout = deps.stdout ?? process.stdout;
45
+ const stderr = deps.stderr ?? process.stderr;
46
+ const cwd = deps.cwd ?? process.cwd();
47
+ const removing = Boolean(deps.remove);
48
+
49
+ let parsed;
50
+ try {
51
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
52
+ } catch (err) {
53
+ stderr.write(`[error] usage ${err.message}\n`);
54
+ stderr.write(help(removing));
55
+ return 2;
56
+ }
57
+ const flags = parsed.values;
58
+ const positionals = parsed.positionals;
59
+ if (flags.help) { stdout.write(help(removing)); return 0; }
60
+ if (positionals.length !== 1) {
61
+ stderr.write(`[error] usage ${removing ? 'unlink' : 'link'}: expected exactly one <us-id>\n`);
62
+ return 2;
63
+ }
64
+ const usId = positionals[0];
65
+ const tacIds = flags.tac ?? [];
66
+ if (tacIds.length === 0) {
67
+ stderr.write(`[error] usage ${removing ? 'unlink' : 'link'}: at least one --tac is required\n`);
68
+ return 2;
69
+ }
70
+
71
+ const projectRoot = await findProjectRoot(cwd);
72
+ if (!projectRoot) {
73
+ 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');
74
+ return 2;
75
+ }
76
+ const walkResult = await walkTree({ projectRoot });
77
+ // B5: pre-existing tree breakage no longer blocks write verbs - the
78
+ // write is gated on the POST-write tree state inside the writer.
79
+ if (walkResult.errors.length > 0) {
80
+ stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
81
+ }
82
+ const us = walkResult.tree.byId.get(usId);
83
+ if (!us || walkResult.tree.kindById.get(usId) !== 'userStory') {
84
+ stderr.write(`[error] usage ${removing ? 'unlink' : 'link'}: ${usId} is not an existing US\n`);
85
+ return 2;
86
+ }
87
+ for (const tacId of tacIds) {
88
+ if (walkResult.tree.kindById.get(tacId) !== 'tac') {
89
+ stderr.write(`[error] brokenReference ${removing ? 'unlink' : 'link'}: ${tacId} is not an existing TAC\n`);
90
+ return 3;
91
+ }
92
+ }
93
+ const current = new Set(us.tacIds ?? []);
94
+ const target = new Set(current);
95
+ for (const tacId of tacIds) {
96
+ if (removing) target.delete(tacId); else target.add(tacId);
97
+ }
98
+ const next = [...target].sort();
99
+ const changed = next.length !== current.size || next.some((id, i) => id !== [...current].sort()[i]);
100
+ if (!changed) {
101
+ if (!flags.quiet) stdout.write(`${usId} tacIds already at target state (idempotent no-op).\n`);
102
+ return 0;
103
+ }
104
+ const result = await updateDocument({
105
+ projectRoot,
106
+ tree: walkResult.tree,
107
+ id: usId,
108
+ patch: next.length === 0 ? { tacIds: [] } : { tacIds: next },
109
+ sets: [],
110
+ options: { dryRun: Boolean(flags['dry-run']) },
111
+ walkErrors: walkResult.errors,
112
+ });
113
+ if (isRcfError(result)) {
114
+ stderr.write(`[error] ${result.kind} ${result.message}\n`);
115
+ if (result.kind === 'validation' || result.kind === 'brokenReference') return 3;
116
+ return 2;
117
+ }
118
+ if (flags['dry-run']) {
119
+ if (!flags.quiet) stdout.write(`[dry-run] would ${removing ? 'unlink' : 'link'} ${usId} tacIds=${JSON.stringify(next)}\n`);
120
+ return 0;
121
+ }
122
+ if (!flags.quiet) stdout.write(`${usId} tacIds updated (${next.length} entries).\n`);
123
+ return 0;
124
+ }
125
+
126
+ function help(removing) {
127
+ return removing ? UNLINK_HELP : HELP;
128
+ }