@nathapp/nax 0.18.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 (459) hide show
  1. package/.gitlab-ci.yml +96 -0
  2. package/BRIEF.md +140 -0
  3. package/CHANGELOG.md +60 -0
  4. package/CLAUDE.md +159 -0
  5. package/README.md +373 -0
  6. package/US-007-IMPLEMENTATION.md +139 -0
  7. package/bin/nax.ts +930 -0
  8. package/biome.json +14 -0
  9. package/bun.lock +168 -0
  10. package/bunfig.toml +11 -0
  11. package/docs/20260216-fix-plan-context-review.md +56 -0
  12. package/docs/20260216-relentless-vs-ngent-comparison.md +208 -0
  13. package/docs/20260216-v02-plan.md +136 -0
  14. package/docs/20260216-v02-review.md +685 -0
  15. package/docs/20260217-dogfood-findings.md +56 -0
  16. package/docs/20260217-p2-plus-plan.md +117 -0
  17. package/docs/20260217-partial-fixes-plan.md +62 -0
  18. package/docs/20260217-plan-analyze-spec.md +117 -0
  19. package/docs/20260217-post-impl-review.md +1137 -0
  20. package/docs/20260217-quick-wins-plan.md +66 -0
  21. package/docs/20260217-split-runner-plan.md +75 -0
  22. package/docs/20260217-v03-impl-plan.md +80 -0
  23. package/docs/20260217-v03-post-impl-review.md +589 -0
  24. package/docs/20260217-v04-impl-plan.md +86 -0
  25. package/docs/20260217-v05-post-impl-review.md +850 -0
  26. package/docs/20260217-v06-post-impl-review.md +817 -0
  27. package/docs/20260218-adr003-port-plan.md +151 -0
  28. package/docs/20260218-review-adr003-verification.md +175 -0
  29. package/docs/20260219-fix-plan-bug16-19.md +79 -0
  30. package/docs/20260219-fix-plan-bug20-22.md +114 -0
  31. package/docs/20260219-plan-llm-routing.md +116 -0
  32. package/docs/20260219-review-bug20-22-fixes.md +135 -0
  33. package/docs/20260219-routing-baseline-keyword.md +63 -0
  34. package/docs/20260220-plan-structured-logging-p1.md +80 -0
  35. package/docs/20260220-plan-structured-logging-p2.md +37 -0
  36. package/docs/20260220-review-llm-routing.md +180 -0
  37. package/docs/20260220-review-post-fix-llm-routing.md +70 -0
  38. package/docs/20260221-fix-plan-relevantfiles-split.md +101 -0
  39. package/docs/20260221-fix-plan-routing-mode.md +125 -0
  40. package/docs/20260221-review-v0.9-implementation.md +379 -0
  41. package/docs/20260222-fix-plan-v091-routing-isolation.md +197 -0
  42. package/docs/20260223-fix-plan-prompt-audit.md +62 -0
  43. package/docs/20260224-nax-roadmap-phases.md +189 -0
  44. package/docs/20260225-phase2-llm-service-layer.md +401 -0
  45. package/docs/20260225-review-v0.10.1.md +187 -0
  46. package/docs/20260303-v010-implementation-plan.md +165 -0
  47. package/docs/CLAUDE.md.bak +191 -0
  48. package/docs/ROADMAP.md +165 -0
  49. package/docs/SPEC-rectification.md +0 -0
  50. package/docs/SPEC.md +324 -0
  51. package/docs/US-001-plugin-loading-verification.md +152 -0
  52. package/docs/architecture-analysis.md +1076 -0
  53. package/docs/bugs/BUG-21-escalation-null-attempts.md +48 -0
  54. package/docs/bugs-from-dogfood-run-c.md +243 -0
  55. package/docs/code-review-20260228.md +612 -0
  56. package/docs/code-review-v0.15.0.md +629 -0
  57. package/docs/hook-lifecycle-test-plan.md +149 -0
  58. package/docs/releases/v0.11.0-and-earlier.md +20 -0
  59. package/docs/releases/v0.12.0.md +15 -0
  60. package/docs/releases/v0.13.0.md +14 -0
  61. package/docs/releases/v0.14.0.md +20 -0
  62. package/docs/releases/v0.14.1.md +36 -0
  63. package/docs/releases/v0.14.2.md +51 -0
  64. package/docs/releases/v0.14.3.md +174 -0
  65. package/docs/releases/v0.14.4.md +94 -0
  66. package/docs/releases/v0.15.0.md +502 -0
  67. package/docs/releases/v0.15.1.md +170 -0
  68. package/docs/releases/v0.15.3.md +193 -0
  69. package/docs/specs/status-file-v0.10.1.md +812 -0
  70. package/docs/v0.10-global-config.md +206 -0
  71. package/docs/v0.10-plugin-system.md +415 -0
  72. package/docs/v0.10-prompt-optimizer.md +234 -0
  73. package/docs/v0.3-spec.md +244 -0
  74. package/docs/v0.4-spec.md +140 -0
  75. package/docs/v0.5-spec.md +237 -0
  76. package/docs/v0.6-spec.md +371 -0
  77. package/docs/v0.7-spec.md +177 -0
  78. package/docs/v0.8-llm-routing.md +206 -0
  79. package/docs/v0.8-structured-logging.md +132 -0
  80. package/docs/v0.9.3-prompt-audit.md +112 -0
  81. package/examples/plugins/console-reporter/index.test.ts +207 -0
  82. package/examples/plugins/console-reporter/index.ts +110 -0
  83. package/nax/config.json +147 -0
  84. package/nax/features/bugfix-v0171/prd.json +52 -0
  85. package/nax/features/config-management/prd.json +108 -0
  86. package/nax/features/config-management/progress.txt +5 -0
  87. package/nax/features/diagnose/acceptance.test.ts +412 -0
  88. package/nax/features/diagnose/prd.json +41 -0
  89. package/nax/features/orchestration-fixes/prd.json +89 -0
  90. package/nax/features/orchestration-fixes/progress.txt +1 -0
  91. package/nax/features/plugin-integration/US-007-VERIFICATION.md +259 -0
  92. package/nax/features/plugin-integration/prd.json +208 -0
  93. package/nax/features/plugin-integration/progress.txt +5 -0
  94. package/nax/features/precheck/prd.json +205 -0
  95. package/nax/features/precheck/progress.txt +15 -0
  96. package/nax/features/structured-logging/prd.json +199 -0
  97. package/nax/features/unlock/prd.json +36 -0
  98. package/package.json +47 -0
  99. package/src/acceptance/fix-generator.ts +348 -0
  100. package/src/acceptance/generator.ts +282 -0
  101. package/src/acceptance/index.ts +30 -0
  102. package/src/acceptance/types.ts +79 -0
  103. package/src/agents/claude-decompose.ts +169 -0
  104. package/src/agents/claude-plan.ts +139 -0
  105. package/src/agents/claude.ts +324 -0
  106. package/src/agents/cost.ts +268 -0
  107. package/src/agents/index.ts +13 -0
  108. package/src/agents/registry.ts +48 -0
  109. package/src/agents/types-extended.ts +133 -0
  110. package/src/agents/types.ts +113 -0
  111. package/src/agents/validation.ts +69 -0
  112. package/src/analyze/classifier.ts +305 -0
  113. package/src/analyze/index.ts +16 -0
  114. package/src/analyze/scanner.ts +175 -0
  115. package/src/analyze/types.ts +51 -0
  116. package/src/cli/accept.ts +108 -0
  117. package/src/cli/analyze-parser.ts +284 -0
  118. package/src/cli/analyze.ts +207 -0
  119. package/src/cli/config.ts +561 -0
  120. package/src/cli/constitution.ts +109 -0
  121. package/src/cli/diagnose-analysis.ts +159 -0
  122. package/src/cli/diagnose-formatter.ts +87 -0
  123. package/src/cli/diagnose.ts +203 -0
  124. package/src/cli/generate.ts +127 -0
  125. package/src/cli/index.ts +37 -0
  126. package/src/cli/init.ts +188 -0
  127. package/src/cli/interact.ts +295 -0
  128. package/src/cli/plan.ts +198 -0
  129. package/src/cli/plugins.ts +111 -0
  130. package/src/cli/prompts.ts +295 -0
  131. package/src/cli/runs.ts +174 -0
  132. package/src/cli/status-cost.ts +151 -0
  133. package/src/cli/status-features.ts +338 -0
  134. package/src/cli/status.ts +13 -0
  135. package/src/commands/common.ts +171 -0
  136. package/src/commands/diagnose.ts +17 -0
  137. package/src/commands/index.ts +8 -0
  138. package/src/commands/logs.ts +384 -0
  139. package/src/commands/precheck.ts +86 -0
  140. package/src/commands/unlock.ts +96 -0
  141. package/src/config/defaults.ts +160 -0
  142. package/src/config/index.ts +22 -0
  143. package/src/config/loader.ts +121 -0
  144. package/src/config/merger.ts +147 -0
  145. package/src/config/path-security.ts +121 -0
  146. package/src/config/paths.ts +27 -0
  147. package/src/config/schema.ts +56 -0
  148. package/src/config/schemas.ts +286 -0
  149. package/src/config/types.ts +423 -0
  150. package/src/config/validate.ts +103 -0
  151. package/src/constitution/generator.ts +191 -0
  152. package/src/constitution/generators/aider.ts +41 -0
  153. package/src/constitution/generators/claude.ts +35 -0
  154. package/src/constitution/generators/cursor.ts +36 -0
  155. package/src/constitution/generators/opencode.ts +38 -0
  156. package/src/constitution/generators/types.ts +33 -0
  157. package/src/constitution/generators/windsurf.ts +36 -0
  158. package/src/constitution/index.ts +10 -0
  159. package/src/constitution/loader.ts +133 -0
  160. package/src/constitution/types.ts +31 -0
  161. package/src/context/auto-detect.ts +227 -0
  162. package/src/context/builder.ts +246 -0
  163. package/src/context/elements.ts +83 -0
  164. package/src/context/formatter.ts +107 -0
  165. package/src/context/generator.ts +129 -0
  166. package/src/context/generators/aider.ts +34 -0
  167. package/src/context/generators/claude.ts +28 -0
  168. package/src/context/generators/cursor.ts +28 -0
  169. package/src/context/generators/opencode.ts +30 -0
  170. package/src/context/generators/windsurf.ts +28 -0
  171. package/src/context/greenfield.ts +114 -0
  172. package/src/context/index.ts +33 -0
  173. package/src/context/injector.ts +279 -0
  174. package/src/context/test-scanner.ts +370 -0
  175. package/src/context/types.ts +98 -0
  176. package/src/errors.ts +67 -0
  177. package/src/execution/batching.ts +157 -0
  178. package/src/execution/crash-recovery.ts +373 -0
  179. package/src/execution/escalation/escalation.ts +44 -0
  180. package/src/execution/escalation/index.ts +13 -0
  181. package/src/execution/escalation/tier-escalation.ts +295 -0
  182. package/src/execution/escalation/tier-outcome.ts +158 -0
  183. package/src/execution/helpers.ts +38 -0
  184. package/src/execution/index.ts +45 -0
  185. package/src/execution/lifecycle/acceptance-loop.ts +272 -0
  186. package/src/execution/lifecycle/headless-formatter.ts +85 -0
  187. package/src/execution/lifecycle/index.ts +12 -0
  188. package/src/execution/lifecycle/parallel-lifecycle.ts +101 -0
  189. package/src/execution/lifecycle/precheck-runner.ts +140 -0
  190. package/src/execution/lifecycle/run-cleanup.ts +81 -0
  191. package/src/execution/lifecycle/run-completion.ts +129 -0
  192. package/src/execution/lifecycle/run-initialization.ts +141 -0
  193. package/src/execution/lifecycle/run-lifecycle.ts +312 -0
  194. package/src/execution/lifecycle/run-setup.ts +204 -0
  195. package/src/execution/lifecycle/story-hooks.ts +38 -0
  196. package/src/execution/lifecycle/story-size-prompts.ts +123 -0
  197. package/src/execution/lock.ts +115 -0
  198. package/src/execution/parallel-executor.ts +216 -0
  199. package/src/execution/parallel.ts +400 -0
  200. package/src/execution/pid-registry.ts +280 -0
  201. package/src/execution/pipeline-result-handler.ts +388 -0
  202. package/src/execution/post-verify-rectification.ts +188 -0
  203. package/src/execution/post-verify.ts +274 -0
  204. package/src/execution/progress.ts +25 -0
  205. package/src/execution/prompts.ts +127 -0
  206. package/src/execution/queue-handler.ts +109 -0
  207. package/src/execution/rectification.ts +13 -0
  208. package/src/execution/runner.ts +377 -0
  209. package/src/execution/sequential-executor.ts +388 -0
  210. package/src/execution/status-file.ts +264 -0
  211. package/src/execution/status-writer.ts +139 -0
  212. package/src/execution/story-context.ts +229 -0
  213. package/src/execution/test-output-parser.ts +14 -0
  214. package/src/execution/verification.ts +72 -0
  215. package/src/hooks/index.ts +2 -0
  216. package/src/hooks/runner.ts +286 -0
  217. package/src/hooks/types.ts +67 -0
  218. package/src/interaction/chain.ts +154 -0
  219. package/src/interaction/index.ts +60 -0
  220. package/src/interaction/init.ts +83 -0
  221. package/src/interaction/plugins/auto.ts +217 -0
  222. package/src/interaction/plugins/cli.ts +300 -0
  223. package/src/interaction/plugins/telegram.ts +384 -0
  224. package/src/interaction/plugins/webhook.ts +258 -0
  225. package/src/interaction/state.ts +171 -0
  226. package/src/interaction/triggers.ts +229 -0
  227. package/src/interaction/types.ts +163 -0
  228. package/src/logger/formatters.ts +84 -0
  229. package/src/logger/index.ts +16 -0
  230. package/src/logger/logger.ts +298 -0
  231. package/src/logger/types.ts +48 -0
  232. package/src/logging/formatter.ts +355 -0
  233. package/src/logging/index.ts +22 -0
  234. package/src/logging/types.ts +93 -0
  235. package/src/metrics/aggregator.ts +190 -0
  236. package/src/metrics/index.ts +14 -0
  237. package/src/metrics/tracker.ts +200 -0
  238. package/src/metrics/types.ts +109 -0
  239. package/src/optimizer/index.ts +62 -0
  240. package/src/optimizer/noop.optimizer.ts +24 -0
  241. package/src/optimizer/rule-based.optimizer.ts +248 -0
  242. package/src/optimizer/types.ts +53 -0
  243. package/src/pipeline/events.ts +130 -0
  244. package/src/pipeline/index.ts +19 -0
  245. package/src/pipeline/runner.ts +161 -0
  246. package/src/pipeline/stages/acceptance.ts +197 -0
  247. package/src/pipeline/stages/completion.ts +99 -0
  248. package/src/pipeline/stages/constitution.ts +63 -0
  249. package/src/pipeline/stages/context.ts +117 -0
  250. package/src/pipeline/stages/execution.ts +194 -0
  251. package/src/pipeline/stages/index.ts +62 -0
  252. package/src/pipeline/stages/optimizer.ts +74 -0
  253. package/src/pipeline/stages/prompt.ts +57 -0
  254. package/src/pipeline/stages/queue-check.ts +103 -0
  255. package/src/pipeline/stages/review.ts +181 -0
  256. package/src/pipeline/stages/routing.ts +81 -0
  257. package/src/pipeline/stages/verify.ts +100 -0
  258. package/src/pipeline/types.ts +167 -0
  259. package/src/plugins/index.ts +31 -0
  260. package/src/plugins/loader.ts +287 -0
  261. package/src/plugins/registry.ts +168 -0
  262. package/src/plugins/types.ts +327 -0
  263. package/src/plugins/validator.ts +352 -0
  264. package/src/prd/index.ts +172 -0
  265. package/src/prd/types.ts +202 -0
  266. package/src/precheck/checks-blockers.ts +391 -0
  267. package/src/precheck/checks-warnings.ts +142 -0
  268. package/src/precheck/checks.ts +30 -0
  269. package/src/precheck/index.ts +247 -0
  270. package/src/precheck/story-size-gate.ts +144 -0
  271. package/src/precheck/types.ts +31 -0
  272. package/src/queue/index.ts +2 -0
  273. package/src/queue/manager.ts +254 -0
  274. package/src/queue/types.ts +54 -0
  275. package/src/review/index.ts +8 -0
  276. package/src/review/runner.ts +172 -0
  277. package/src/review/types.ts +66 -0
  278. package/src/routing/builder.ts +81 -0
  279. package/src/routing/chain.ts +74 -0
  280. package/src/routing/index.ts +16 -0
  281. package/src/routing/loader.ts +58 -0
  282. package/src/routing/router.ts +303 -0
  283. package/src/routing/strategies/adaptive.ts +215 -0
  284. package/src/routing/strategies/index.ts +8 -0
  285. package/src/routing/strategies/keyword.ts +163 -0
  286. package/src/routing/strategies/llm-prompts.ts +209 -0
  287. package/src/routing/strategies/llm.ts +235 -0
  288. package/src/routing/strategies/manual.ts +50 -0
  289. package/src/routing/strategy.ts +99 -0
  290. package/src/tdd/cleanup.ts +111 -0
  291. package/src/tdd/index.ts +23 -0
  292. package/src/tdd/isolation.ts +123 -0
  293. package/src/tdd/orchestrator.ts +383 -0
  294. package/src/tdd/prompts.ts +270 -0
  295. package/src/tdd/rectification-gate.ts +183 -0
  296. package/src/tdd/session-runner.ts +179 -0
  297. package/src/tdd/types.ts +81 -0
  298. package/src/tdd/verdict.ts +271 -0
  299. package/src/tui/App.tsx +265 -0
  300. package/src/tui/components/AgentPanel.tsx +75 -0
  301. package/src/tui/components/CostOverlay.tsx +118 -0
  302. package/src/tui/components/HelpOverlay.tsx +107 -0
  303. package/src/tui/components/StatusBar.tsx +63 -0
  304. package/src/tui/components/StoriesPanel.tsx +177 -0
  305. package/src/tui/hooks/useKeyboard.ts +142 -0
  306. package/src/tui/hooks/useLayout.ts +137 -0
  307. package/src/tui/hooks/usePipelineEvents.ts +183 -0
  308. package/src/tui/hooks/usePty.ts +194 -0
  309. package/src/tui/index.tsx +38 -0
  310. package/src/tui/types.ts +76 -0
  311. package/src/utils/git.ts +83 -0
  312. package/src/utils/queue-writer.ts +54 -0
  313. package/src/verification/executor.ts +235 -0
  314. package/src/verification/gate.ts +207 -0
  315. package/src/verification/index.ts +12 -0
  316. package/src/verification/parser.ts +230 -0
  317. package/src/verification/rectification.ts +108 -0
  318. package/src/verification/types.ts +113 -0
  319. package/src/worktree/dispatcher.ts +65 -0
  320. package/src/worktree/index.ts +2 -0
  321. package/src/worktree/manager.ts +187 -0
  322. package/src/worktree/merge.ts +301 -0
  323. package/src/worktree/types.ts +4 -0
  324. package/test/TEST_COVERAGE_US001.md +217 -0
  325. package/test/TEST_COVERAGE_US003.md +84 -0
  326. package/test/TEST_COVERAGE_US005.md +86 -0
  327. package/test/US-002-orchestrator.test.ts +246 -0
  328. package/test/acceptance/cm-003-default-view.test.ts +194 -0
  329. package/test/execution/pid-registry.test.ts +240 -0
  330. package/test/execution/post-verify.test.ts +224 -0
  331. package/test/helpers/timeout.ts +42 -0
  332. package/test/integration/US-002-TEST-SUMMARY.md +107 -0
  333. package/test/integration/US-003-TEST-SUMMARY.md +149 -0
  334. package/test/integration/US-004-TEST-SUMMARY.md +106 -0
  335. package/test/integration/US-005-TEST-SUMMARY.md +138 -0
  336. package/test/integration/US-007-TEST-SUMMARY.md +100 -0
  337. package/test/integration/agent-validation.test.ts +439 -0
  338. package/test/integration/analyze-integration.test.ts +261 -0
  339. package/test/integration/analyze-scanner.test.ts +131 -0
  340. package/test/integration/cli-config-default-edge-cases.test.ts +222 -0
  341. package/test/integration/cli-config-default-view.test.ts +229 -0
  342. package/test/integration/cli-config-diff.test.ts +460 -0
  343. package/test/integration/cli-config.test.ts +736 -0
  344. package/test/integration/cli-diagnose.test.ts +592 -0
  345. package/test/integration/cli-logs.test.ts +314 -0
  346. package/test/integration/cli-plugins.test.ts +678 -0
  347. package/test/integration/cli-precheck.test.ts +371 -0
  348. package/test/integration/cli-run-headless.test.ts +173 -0
  349. package/test/integration/cli.test.ts +75 -0
  350. package/test/integration/config/merger.test.ts +465 -0
  351. package/test/integration/config/paths.test.ts +51 -0
  352. package/test/integration/config-loader.test.ts +265 -0
  353. package/test/integration/config.test.ts +444 -0
  354. package/test/integration/context-integration.test.ts +702 -0
  355. package/test/integration/context-provider-injection.test.ts +506 -0
  356. package/test/integration/context-verification-integration.test.ts +295 -0
  357. package/test/integration/e2e.test.ts +896 -0
  358. package/test/integration/execution.test.ts +625 -0
  359. package/test/integration/helpers.test.ts +295 -0
  360. package/test/integration/hooks.test.ts +361 -0
  361. package/test/integration/interaction-chain-pipeline.test.ts +464 -0
  362. package/test/integration/isolation.test.ts +143 -0
  363. package/test/integration/logger.test.ts +461 -0
  364. package/test/integration/parallel.test.ts +250 -0
  365. package/test/integration/path-security.test.ts +173 -0
  366. package/test/integration/pipeline-acceptance.test.ts +302 -0
  367. package/test/integration/pipeline-events.test.ts +475 -0
  368. package/test/integration/pipeline.test.ts +658 -0
  369. package/test/integration/plan.test.ts +157 -0
  370. package/test/integration/plugin-routing.test.ts +921 -0
  371. package/test/integration/plugins/config-integration.test.ts +172 -0
  372. package/test/integration/plugins/config-resolution.test.ts +522 -0
  373. package/test/integration/plugins/loader.test.ts +641 -0
  374. package/test/integration/plugins/registry.test.ts +746 -0
  375. package/test/integration/plugins/validator.test.ts +563 -0
  376. package/test/integration/prd-pause.test.ts +205 -0
  377. package/test/integration/prd-resolvers.test.ts +185 -0
  378. package/test/integration/precheck-integration.test.ts +468 -0
  379. package/test/integration/precheck.test.ts +805 -0
  380. package/test/integration/progress.test.ts +34 -0
  381. package/test/integration/rectification-flow.test.ts +512 -0
  382. package/test/integration/reporter-lifecycle.test.ts +860 -0
  383. package/test/integration/review-config-commands.test.ts +319 -0
  384. package/test/integration/review-config-schema.test.ts +116 -0
  385. package/test/integration/review-plugin-integration.test.ts +722 -0
  386. package/test/integration/review.test.ts +149 -0
  387. package/test/integration/routing-stage-bug-021.test.ts +274 -0
  388. package/test/integration/routing-stage-greenfield.test.ts +286 -0
  389. package/test/integration/runner-config-plugins.test.ts +461 -0
  390. package/test/integration/runner-fixes.test.ts +399 -0
  391. package/test/integration/runner-plugin-integration.test.ts +543 -0
  392. package/test/integration/runner.test.ts +1679 -0
  393. package/test/integration/s5-greenfield-fallback.test.ts +297 -0
  394. package/test/integration/status-file-integration.test.ts +325 -0
  395. package/test/integration/status-file.test.ts +379 -0
  396. package/test/integration/status-writer.test.ts +345 -0
  397. package/test/integration/story-id-in-events.test.ts +273 -0
  398. package/test/integration/tdd-cleanup.test.ts +246 -0
  399. package/test/integration/tdd-orchestrator.test.ts +1762 -0
  400. package/test/integration/test-scanner.test.ts +403 -0
  401. package/test/integration/verification-asset-check.test.ts +142 -0
  402. package/test/integration/verify-stage.test.ts +275 -0
  403. package/test/integration/worktree/manager.test.ts +218 -0
  404. package/test/integration/worktree/merge.test.ts +341 -0
  405. package/test/manual/logging-formatter-demo.ts +158 -0
  406. package/test/ui/tui-agent-panel.test.tsx +99 -0
  407. package/test/ui/tui-controls.test.ts +334 -0
  408. package/test/ui/tui-cost-and-pty.test.ts +189 -0
  409. package/test/ui/tui-layout.test.ts +378 -0
  410. package/test/ui/tui-pty-integration.test.tsx +159 -0
  411. package/test/ui/tui-stories.test.ts +332 -0
  412. package/test/unit/acceptance.test.ts +186 -0
  413. package/test/unit/agent-stderr-capture.test.ts +146 -0
  414. package/test/unit/analyze-classifier.test.ts +215 -0
  415. package/test/unit/analyze.test.ts +224 -0
  416. package/test/unit/auto-detect.test.ts +249 -0
  417. package/test/unit/cli-status.test.ts +417 -0
  418. package/test/unit/commands/common.test.ts +320 -0
  419. package/test/unit/commands/logs.test.ts +416 -0
  420. package/test/unit/commands/unlock.test.ts +319 -0
  421. package/test/unit/constitution-generators.test.ts +160 -0
  422. package/test/unit/constitution.test.ts +209 -0
  423. package/test/unit/context.test.ts +1722 -0
  424. package/test/unit/cost.test.ts +231 -0
  425. package/test/unit/crash-recovery.test.ts +308 -0
  426. package/test/unit/escalation.test.ts +126 -0
  427. package/test/unit/execution-logging-stderr.test.ts +156 -0
  428. package/test/unit/execution-stage.test.ts +122 -0
  429. package/test/unit/fix-generator.test.ts +275 -0
  430. package/test/unit/formatters.test.ts +469 -0
  431. package/test/unit/greenfield.test.ts +179 -0
  432. package/test/unit/helpers.test.ts +317 -0
  433. package/test/unit/interaction/human-review-trigger.test.ts +164 -0
  434. package/test/unit/interaction-network-failures.test.ts +389 -0
  435. package/test/unit/interaction-plugins.test.ts +164 -0
  436. package/test/unit/isolation.test.ts +134 -0
  437. package/test/unit/logging/formatter.test.ts +455 -0
  438. package/test/unit/merge.test.ts +268 -0
  439. package/test/unit/metrics.test.ts +276 -0
  440. package/test/unit/optimizer/noop.optimizer.test.ts +125 -0
  441. package/test/unit/optimizer/rule-based.optimizer.test.ts +358 -0
  442. package/test/unit/prd-auto-default.test.ts +290 -0
  443. package/test/unit/prd-failure-category.test.ts +176 -0
  444. package/test/unit/prd-get-next-story.test.ts +186 -0
  445. package/test/unit/precheck-checks.test.ts +840 -0
  446. package/test/unit/precheck-story-size-gate.test.ts +287 -0
  447. package/test/unit/precheck-types.test.ts +142 -0
  448. package/test/unit/prompts.test.ts +475 -0
  449. package/test/unit/queue.test.ts +237 -0
  450. package/test/unit/rectification.test.ts +284 -0
  451. package/test/unit/registry.test.ts +287 -0
  452. package/test/unit/routing.test.ts +937 -0
  453. package/test/unit/run-lifecycle.test.ts +140 -0
  454. package/test/unit/storyid-events.test.ts +224 -0
  455. package/test/unit/tdd-verdict.test.ts +492 -0
  456. package/test/unit/test-output-parser.test.ts +377 -0
  457. package/test/unit/verdict.test.ts +324 -0
  458. package/test/unit/worktree-manager.test.ts +158 -0
  459. package/tsconfig.json +27 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Init Command
3
+ *
4
+ * Initializes nax configuration directories and files.
5
+ */
6
+
7
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { globalConfigDir, projectConfigDir } from "../config/paths";
10
+ import { DEFAULT_CONFIG } from "../config/schema";
11
+ import { getLogger } from "../logger";
12
+
13
+ /** Init command options */
14
+ export interface InitOptions {
15
+ /** Initialize global config (~/.nax) */
16
+ global?: boolean;
17
+ /** Project root (default: cwd) */
18
+ projectRoot?: string;
19
+ }
20
+
21
+ /**
22
+ * Gitignore entries added by nax init
23
+ */
24
+ const NAX_GITIGNORE_ENTRIES = [".nax-verifier-verdict.json"];
25
+
26
+ /**
27
+ * Add nax-specific entries to .gitignore if not already present.
28
+ *
29
+ * Appends a clearly marked nax section to the project .gitignore.
30
+ */
31
+ async function updateGitignore(projectRoot: string): Promise<void> {
32
+ const logger = getLogger();
33
+ const gitignorePath = join(projectRoot, ".gitignore");
34
+
35
+ let existing = "";
36
+ if (existsSync(gitignorePath)) {
37
+ existing = readFileSync(gitignorePath, "utf-8");
38
+ }
39
+
40
+ const missingEntries = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
41
+
42
+ if (missingEntries.length === 0) {
43
+ logger.info("init", ".gitignore already has nax entries", { path: gitignorePath });
44
+ return;
45
+ }
46
+
47
+ const naxSection = `\n# nax — generated files\n${missingEntries.join("\n")}\n`;
48
+ await Bun.write(gitignorePath, existing + naxSection);
49
+ logger.info("init", "Updated .gitignore with nax entries", {
50
+ path: gitignorePath,
51
+ added: missingEntries,
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Template for default constitution.md
57
+ */
58
+ const DEFAULT_CONSTITUTION = `# Project Constitution
59
+
60
+ ## Goals
61
+ - Deliver high-quality, maintainable code
62
+ - Follow project conventions and best practices
63
+ - Maintain comprehensive test coverage
64
+
65
+ ## Constraints
66
+ - Use Bun-native APIs only
67
+ - Follow functional style for pure logic
68
+ - Keep files focused and under 400 lines
69
+
70
+ ## Preferences
71
+ - Prefer immutability over mutation
72
+ - Write tests first (TDD approach)
73
+ - Clear, descriptive naming
74
+ `;
75
+
76
+ /**
77
+ * Template for minimal config.json (references defaults, only overrides)
78
+ */
79
+ const MINIMAL_PROJECT_CONFIG = {
80
+ version: 1,
81
+ // Add project-specific overrides here
82
+ };
83
+
84
+ const MINIMAL_GLOBAL_CONFIG = {
85
+ version: 1,
86
+ // Add global preferences here (e.g., model tiers, execution limits)
87
+ };
88
+
89
+ /**
90
+ * Initialize global nax config directory (~/.nax)
91
+ */
92
+ async function initGlobal(): Promise<void> {
93
+ const logger = getLogger();
94
+ const globalDir = globalConfigDir();
95
+
96
+ // Create ~/.nax if it doesn't exist
97
+ if (!existsSync(globalDir)) {
98
+ mkdirSync(globalDir, { recursive: true });
99
+ logger.info("init", "Created global config directory", { path: globalDir });
100
+ }
101
+
102
+ // Create ~/.nax/config.json if it doesn't exist
103
+ const configPath = join(globalDir, "config.json");
104
+ if (!existsSync(configPath)) {
105
+ await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}\n`);
106
+ logger.info("init", "Created global config", { path: configPath });
107
+ } else {
108
+ logger.info("init", "Global config already exists", { path: configPath });
109
+ }
110
+
111
+ // Create ~/.nax/constitution.md if it doesn't exist
112
+ const constitutionPath = join(globalDir, "constitution.md");
113
+ if (!existsSync(constitutionPath)) {
114
+ await Bun.write(constitutionPath, DEFAULT_CONSTITUTION);
115
+ logger.info("init", "Created global constitution", { path: constitutionPath });
116
+ } else {
117
+ logger.info("init", "Global constitution already exists", { path: constitutionPath });
118
+ }
119
+
120
+ // Create ~/.nax/hooks/ directory if it doesn't exist
121
+ const hooksDir = join(globalDir, "hooks");
122
+ if (!existsSync(hooksDir)) {
123
+ mkdirSync(hooksDir, { recursive: true });
124
+ logger.info("init", "Created global hooks directory", { path: hooksDir });
125
+ } else {
126
+ logger.info("init", "Global hooks directory already exists", { path: hooksDir });
127
+ }
128
+
129
+ logger.info("init", "Global config initialized successfully", { path: globalDir });
130
+ }
131
+
132
+ /**
133
+ * Initialize project nax directory (nax/)
134
+ */
135
+ async function initProject(projectRoot: string): Promise<void> {
136
+ const logger = getLogger();
137
+ const projectDir = projectConfigDir(projectRoot);
138
+
139
+ // Create nax/ directory if it doesn't exist
140
+ if (!existsSync(projectDir)) {
141
+ mkdirSync(projectDir, { recursive: true });
142
+ logger.info("init", "Created project config directory", { path: projectDir });
143
+ }
144
+
145
+ // Create nax/config.json if it doesn't exist
146
+ const configPath = join(projectDir, "config.json");
147
+ if (!existsSync(configPath)) {
148
+ await Bun.write(configPath, `${JSON.stringify(MINIMAL_PROJECT_CONFIG, null, 2)}\n`);
149
+ logger.info("init", "Created project config", { path: configPath });
150
+ } else {
151
+ logger.info("init", "Project config already exists", { path: configPath });
152
+ }
153
+
154
+ // Create nax/constitution.md if it doesn't exist
155
+ const constitutionPath = join(projectDir, "constitution.md");
156
+ if (!existsSync(constitutionPath)) {
157
+ await Bun.write(constitutionPath, DEFAULT_CONSTITUTION);
158
+ logger.info("init", "Created project constitution", { path: constitutionPath });
159
+ } else {
160
+ logger.info("init", "Project constitution already exists", { path: constitutionPath });
161
+ }
162
+
163
+ // Create nax/hooks/ directory if it doesn't exist
164
+ const hooksDir = join(projectDir, "hooks");
165
+ if (!existsSync(hooksDir)) {
166
+ mkdirSync(hooksDir, { recursive: true });
167
+ logger.info("init", "Created project hooks directory", { path: hooksDir });
168
+ } else {
169
+ logger.info("init", "Project hooks directory already exists", { path: hooksDir });
170
+ }
171
+
172
+ // Update .gitignore to include nax-specific entries
173
+ await updateGitignore(projectRoot);
174
+
175
+ logger.info("init", "Project config initialized successfully", { path: projectDir });
176
+ }
177
+
178
+ /**
179
+ * Run init command
180
+ */
181
+ export async function initCommand(options: InitOptions = {}): Promise<void> {
182
+ if (options.global) {
183
+ await initGlobal();
184
+ } else {
185
+ const projectRoot = options.projectRoot ?? process.cwd();
186
+ await initProject(projectRoot);
187
+ }
188
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Interact CLI Command (v0.15.0 US-006)
3
+ *
4
+ * Manage pending interactions from CLI:
5
+ * - nax interact list -f <feature>
6
+ * - nax interact respond <id> --action approve|reject|choose|input --value <val>
7
+ * - nax interact cancel <id>
8
+ */
9
+
10
+ import { existsSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import chalk from "chalk";
13
+ import { resolveProject } from "../commands/common";
14
+ import type { InteractionRequest, InteractionResponse } from "../interaction";
15
+ import {
16
+ deletePendingInteraction,
17
+ listPendingInteractions,
18
+ loadPendingInteraction,
19
+ savePendingInteraction,
20
+ } from "../interaction";
21
+
22
+ /**
23
+ * Options for interact list command
24
+ */
25
+ export interface InteractListOptions {
26
+ /** Feature name (required) */
27
+ feature: string;
28
+ /** Explicit project directory */
29
+ dir?: string;
30
+ /** JSON output mode */
31
+ json?: boolean;
32
+ }
33
+
34
+ /**
35
+ * Options for interact respond command
36
+ */
37
+ export interface InteractRespondOptions {
38
+ /** Feature name */
39
+ feature?: string;
40
+ /** Explicit project directory */
41
+ dir?: string;
42
+ /** Action to take */
43
+ action: "approve" | "reject" | "choose" | "input" | "skip" | "abort";
44
+ /** Value (for choose/input) */
45
+ value?: string;
46
+ /** JSON output mode */
47
+ json?: boolean;
48
+ }
49
+
50
+ /**
51
+ * Options for interact cancel command
52
+ */
53
+ export interface InteractCancelOptions {
54
+ /** Feature name */
55
+ feature?: string;
56
+ /** Explicit project directory */
57
+ dir?: string;
58
+ /** JSON output mode */
59
+ json?: boolean;
60
+ }
61
+
62
+ /**
63
+ * List pending interactions for a feature
64
+ */
65
+ export async function interactListCommand(options: InteractListOptions): Promise<void> {
66
+ const resolved = resolveProject({
67
+ dir: options.dir,
68
+ feature: options.feature,
69
+ });
70
+
71
+ if (!resolved.featureDir) {
72
+ throw new Error("Feature directory not resolved");
73
+ }
74
+
75
+ const interactionsDir = join(resolved.featureDir, "interactions");
76
+ if (!existsSync(interactionsDir)) {
77
+ if (options.json) {
78
+ console.log(JSON.stringify({ interactions: [] }));
79
+ } else {
80
+ console.log(chalk.dim("No pending interactions."));
81
+ }
82
+ return;
83
+ }
84
+
85
+ const ids = await listPendingInteractions(resolved.featureDir);
86
+
87
+ if (ids.length === 0) {
88
+ if (options.json) {
89
+ console.log(JSON.stringify({ interactions: [] }));
90
+ } else {
91
+ console.log(chalk.dim("No pending interactions."));
92
+ }
93
+ return;
94
+ }
95
+
96
+ // Load full requests
97
+ const requests: InteractionRequest[] = [];
98
+ for (const id of ids) {
99
+ const req = await loadPendingInteraction(id, resolved.featureDir);
100
+ if (req) {
101
+ requests.push(req);
102
+ }
103
+ }
104
+
105
+ if (options.json) {
106
+ console.log(JSON.stringify({ interactions: requests }, null, 2));
107
+ return;
108
+ }
109
+
110
+ // Display table
111
+ console.log(chalk.bold(`\n📬 Pending Interactions (${options.feature})\n`));
112
+
113
+ for (const req of requests) {
114
+ const safety = req.metadata?.safety ?? "unknown";
115
+ const safetyIcon = safety === "red" ? "🔴" : safety === "yellow" ? "🟡" : "🟢";
116
+ const timeRemaining = req.timeout ? Math.max(0, req.createdAt + req.timeout - Date.now()) : null;
117
+
118
+ console.log(`${safetyIcon} ${chalk.bold(req.id)}`);
119
+ console.log(chalk.dim(` Type: ${req.type}`));
120
+ console.log(chalk.dim(` Stage: ${req.stage}`));
121
+ console.log(chalk.dim(` Summary: ${req.summary}`));
122
+ if (req.storyId) {
123
+ console.log(chalk.dim(` Story: ${req.storyId}`));
124
+ }
125
+ console.log(chalk.dim(` Fallback: ${req.fallback}`));
126
+ if (timeRemaining !== null) {
127
+ const timeoutSec = Math.floor(timeRemaining / 1000);
128
+ console.log(chalk.dim(` Timeout: ${timeoutSec}s remaining`));
129
+ }
130
+ if (req.options && req.options.length > 0) {
131
+ console.log(chalk.dim(" Options:"));
132
+ for (const opt of req.options) {
133
+ console.log(chalk.dim(` [${opt.key}] ${opt.label}`));
134
+ }
135
+ }
136
+ console.log();
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Respond to a pending interaction
142
+ */
143
+ export async function interactRespondCommand(requestId: string, options: InteractRespondOptions): Promise<void> {
144
+ // Find the feature by searching all features for the request
145
+ let featureDir: string | null = null;
146
+ let request: InteractionRequest | null = null;
147
+
148
+ if (options.feature) {
149
+ const resolved = resolveProject({
150
+ dir: options.dir,
151
+ feature: options.feature,
152
+ });
153
+ featureDir = resolved.featureDir ?? null;
154
+ if (featureDir) {
155
+ request = await loadPendingInteraction(requestId, featureDir);
156
+ }
157
+ } else {
158
+ // Search all features
159
+ const resolved = resolveProject({ dir: options.dir });
160
+ const featuresDir = join(resolved.projectDir, "nax", "features");
161
+ if (existsSync(featuresDir)) {
162
+ const { readdirSync } = await import("node:fs");
163
+ const features = readdirSync(featuresDir, { withFileTypes: true })
164
+ .filter((e) => e.isDirectory())
165
+ .map((e) => e.name);
166
+
167
+ for (const feature of features) {
168
+ const dir = join(featuresDir, feature);
169
+ const req = await loadPendingInteraction(requestId, dir);
170
+ if (req) {
171
+ request = req;
172
+ featureDir = dir;
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ if (!request || !featureDir) {
180
+ throw new Error(`Interaction request not found: ${requestId}`);
181
+ }
182
+
183
+ // Validate action matches request type
184
+ if (options.action === "choose" && request.type !== "choose") {
185
+ throw new Error(`Action "choose" only valid for type "choose" (request is ${request.type})`);
186
+ }
187
+
188
+ if (options.action === "input" && request.type !== "input") {
189
+ throw new Error(`Action "input" only valid for type "input" (request is ${request.type})`);
190
+ }
191
+
192
+ if (options.action === "choose" && !options.value) {
193
+ throw new Error("--value required for action choose");
194
+ }
195
+
196
+ if (options.action === "input" && !options.value) {
197
+ throw new Error("--value required for action input");
198
+ }
199
+
200
+ // Create response
201
+ const response: InteractionResponse = {
202
+ requestId,
203
+ action: options.action,
204
+ value: options.value,
205
+ respondedBy: "cli",
206
+ respondedAt: Date.now(),
207
+ };
208
+
209
+ // Save response (write to responses/ directory)
210
+ const responsesDir = join(featureDir, "responses");
211
+ await Bun.write(join(responsesDir, ".gitkeep"), "");
212
+ const responseFile = join(responsesDir, `${requestId}.json`);
213
+ await Bun.write(responseFile, JSON.stringify(response, null, 2));
214
+
215
+ // Delete pending interaction
216
+ await deletePendingInteraction(requestId, featureDir);
217
+
218
+ if (options.json) {
219
+ console.log(JSON.stringify({ success: true, response }));
220
+ } else {
221
+ console.log(chalk.green(`✅ Response recorded: ${options.action}`));
222
+ if (options.value) {
223
+ console.log(chalk.dim(` Value: ${options.value}`));
224
+ }
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Cancel a pending interaction (applies fallback)
230
+ */
231
+ export async function interactCancelCommand(requestId: string, options: InteractCancelOptions): Promise<void> {
232
+ // Find the feature by searching all features for the request
233
+ let featureDir: string | null = null;
234
+ let request: InteractionRequest | null = null;
235
+
236
+ if (options.feature) {
237
+ const resolved = resolveProject({
238
+ dir: options.dir,
239
+ feature: options.feature,
240
+ });
241
+ featureDir = resolved.featureDir ?? null;
242
+ if (featureDir) {
243
+ request = await loadPendingInteraction(requestId, featureDir);
244
+ }
245
+ } else {
246
+ // Search all features
247
+ const resolved = resolveProject({ dir: options.dir });
248
+ const featuresDir = join(resolved.projectDir, "nax", "features");
249
+ if (existsSync(featuresDir)) {
250
+ const { readdirSync } = await import("node:fs");
251
+ const features = readdirSync(featuresDir, { withFileTypes: true })
252
+ .filter((e) => e.isDirectory())
253
+ .map((e) => e.name);
254
+
255
+ for (const feature of features) {
256
+ const dir = join(featuresDir, feature);
257
+ const req = await loadPendingInteraction(requestId, dir);
258
+ if (req) {
259
+ request = req;
260
+ featureDir = dir;
261
+ break;
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ if (!request || !featureDir) {
268
+ throw new Error(`Interaction request not found: ${requestId}`);
269
+ }
270
+
271
+ // Create response with fallback action
272
+ const fallbackAction = request.fallback === "continue" ? "approve" : request.fallback === "skip" ? "skip" : "abort";
273
+
274
+ const response: InteractionResponse = {
275
+ requestId,
276
+ action: fallbackAction,
277
+ respondedBy: "cli-cancel",
278
+ respondedAt: Date.now(),
279
+ };
280
+
281
+ // Save response
282
+ const responsesDir = join(featureDir, "responses");
283
+ await Bun.write(join(responsesDir, ".gitkeep"), "");
284
+ const responseFile = join(responsesDir, `${requestId}.json`);
285
+ await Bun.write(responseFile, JSON.stringify(response, null, 2));
286
+
287
+ // Delete pending interaction
288
+ await deletePendingInteraction(requestId, featureDir);
289
+
290
+ if (options.json) {
291
+ console.log(JSON.stringify({ success: true, response }));
292
+ } else {
293
+ console.log(chalk.yellow(`⏭ Interaction cancelled (fallback: ${request.fallback})`));
294
+ }
295
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Plan Command — Interactive planning via agent plan mode
3
+ *
4
+ * Spawns a coding agent in plan mode to gather requirements,
5
+ * ask clarifying questions, and generate a structured specification.
6
+ */
7
+
8
+ import { existsSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { ClaudeCodeAdapter } from "../agents/claude";
11
+ import type { PlanOptions } from "../agents/types";
12
+ import { scanCodebase } from "../analyze/scanner";
13
+ import type { NaxConfig } from "../config";
14
+ import { resolveModel } from "../config/schema";
15
+ import { getLogger } from "../logger";
16
+
17
+ /**
18
+ * Template for structured specification output.
19
+ *
20
+ * This template guides the agent to produce a consistent spec format
21
+ * that can be parsed by the analyze command.
22
+ */
23
+ const SPEC_TEMPLATE = `# Feature: [title]
24
+
25
+ ## Problem
26
+ Why this is needed.
27
+
28
+ ## Requirements
29
+ - REQ-1: ...
30
+ - REQ-2: ...
31
+
32
+ ## Acceptance Criteria
33
+ - AC-1: ...
34
+
35
+ ## Technical Notes
36
+ Architecture hints, constraints, dependencies.
37
+
38
+ ## Out of Scope
39
+ What this does NOT include.
40
+ `;
41
+
42
+ /**
43
+ * Run the plan command to generate a feature specification.
44
+ *
45
+ * @param prompt - The feature description or task
46
+ * @param workdir - Project root directory
47
+ * @param config - Ngent configuration
48
+ * @param options - Command options (interactive, from)
49
+ * @returns Path to the generated spec file
50
+ */
51
+ export async function planCommand(
52
+ prompt: string,
53
+ workdir: string,
54
+ config: NaxConfig,
55
+ options: {
56
+ interactive?: boolean;
57
+ from?: string;
58
+ } = {},
59
+ ): Promise<string> {
60
+ const interactive = options.interactive !== false; // Default to true
61
+ const ngentDir = join(workdir, "nax");
62
+ const outputPath = join(ngentDir, config.plan.outputPath);
63
+
64
+ // Ensure nax directory exists
65
+ if (!existsSync(ngentDir)) {
66
+ throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
67
+ }
68
+
69
+ // Scan codebase for context
70
+ const logger = getLogger();
71
+ logger.info("cli", "Scanning codebase...");
72
+ const scan = await scanCodebase(workdir);
73
+
74
+ // Build codebase context markdown
75
+ const codebaseContext = buildCodebaseContext(scan);
76
+
77
+ // Resolve model for planning
78
+ const modelTier = config.plan.model;
79
+ const modelEntry = config.models[modelTier];
80
+ const modelDef = resolveModel(modelEntry);
81
+
82
+ // Build full prompt with template
83
+ const fullPrompt = buildPlanPrompt(prompt, SPEC_TEMPLATE);
84
+
85
+ // Prepare plan options
86
+ const planOptions: PlanOptions = {
87
+ prompt: fullPrompt,
88
+ workdir,
89
+ interactive,
90
+ codebaseContext,
91
+ inputFile: options.from,
92
+ modelTier,
93
+ modelDef,
94
+ };
95
+
96
+ // Run agent in plan mode
97
+ const adapter = new ClaudeCodeAdapter();
98
+
99
+ logger.info("cli", interactive ? "Starting interactive planning session..." : `Reading from ${options.from}...`, {
100
+ interactive,
101
+ from: options.from,
102
+ });
103
+
104
+ const result = await adapter.plan(planOptions);
105
+
106
+ // Write spec to output file
107
+ if (interactive) {
108
+ // In interactive mode, the agent may have written directly
109
+ // But we also capture and write to ensure consistency
110
+ if (result.specContent) {
111
+ await Bun.write(outputPath, result.specContent);
112
+ } else {
113
+ // If agent wrote directly, verify it exists
114
+ if (!existsSync(outputPath)) {
115
+ throw new Error(`Interactive planning completed but spec not found at ${outputPath}`);
116
+ }
117
+ }
118
+ } else {
119
+ // In non-interactive mode, we have the spec in result
120
+ if (!result.specContent) {
121
+ throw new Error("Agent did not produce specification content");
122
+ }
123
+ await Bun.write(outputPath, result.specContent);
124
+ }
125
+
126
+ logger.info("cli", "✓ Specification written to output", { outputPath });
127
+
128
+ return outputPath;
129
+ }
130
+
131
+ /**
132
+ * Build codebase context markdown from scan results.
133
+ *
134
+ * @param scan - Codebase scan result
135
+ * @returns Formatted context string
136
+ */
137
+ function buildCodebaseContext(scan: {
138
+ fileTree: string;
139
+ dependencies: Record<string, string>;
140
+ devDependencies: Record<string, string>;
141
+ testPatterns: string[];
142
+ }): string {
143
+ const sections: string[] = [];
144
+
145
+ // File tree
146
+ sections.push("## Codebase Structure\n");
147
+ sections.push("```");
148
+ sections.push(scan.fileTree);
149
+ sections.push("```\n");
150
+
151
+ // Dependencies
152
+ const allDeps = { ...scan.dependencies, ...scan.devDependencies };
153
+ const depList = Object.entries(allDeps)
154
+ .map(([name, version]) => `- ${name}@${version}`)
155
+ .join("\n");
156
+
157
+ if (depList) {
158
+ sections.push("## Dependencies\n");
159
+ sections.push(depList);
160
+ sections.push("");
161
+ }
162
+
163
+ // Test patterns
164
+ if (scan.testPatterns.length > 0) {
165
+ sections.push("## Test Setup\n");
166
+ sections.push(scan.testPatterns.map((p) => `- ${p}`).join("\n"));
167
+ sections.push("");
168
+ }
169
+
170
+ return sections.join("\n");
171
+ }
172
+
173
+ /**
174
+ * Build the full planning prompt with template.
175
+ *
176
+ * @param userPrompt - User's task description
177
+ * @param template - Spec template
178
+ * @returns Full prompt with instructions
179
+ */
180
+ function buildPlanPrompt(userPrompt: string, template: string): string {
181
+ return `You are helping plan a new feature for this codebase.
182
+
183
+ Task: ${userPrompt}
184
+
185
+ Please gather requirements and produce a structured specification following this template:
186
+
187
+ ${template}
188
+
189
+ Ask clarifying questions as needed to ensure the spec is complete and unambiguous.
190
+ Focus on understanding:
191
+ - The problem being solved
192
+ - Specific requirements and constraints
193
+ - Acceptance criteria for success
194
+ - Technical approach and architecture
195
+ - What is explicitly out of scope
196
+
197
+ When done, output the complete specification in markdown format.`;
198
+ }