@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,282 @@
1
+ /**
2
+ * Acceptance Test Generator
3
+ *
4
+ * Parses spec.md acceptance criteria (AC-N lines) and generates acceptance.test.ts
5
+ * via LLM call to the agent adapter.
6
+ */
7
+
8
+ import type { AgentAdapter } from "../agents/types";
9
+ import { getLogger } from "../logger";
10
+ import type { AcceptanceCriterion, AcceptanceTestResult, GenerateAcceptanceTestsOptions } from "./types";
11
+
12
+ /**
13
+ * Parse acceptance criteria from spec.md content.
14
+ *
15
+ * Extracts lines matching "AC-N: description" or "- AC-N: description" patterns.
16
+ *
17
+ * @param specContent - Full spec.md markdown content
18
+ * @returns Array of extracted acceptance criteria
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const spec = `
23
+ * ## Acceptance Criteria
24
+ * - AC-1: System should handle empty input
25
+ * - AC-2: set(key, value, ttl) expires after ttl milliseconds
26
+ * `;
27
+ * const criteria = parseAcceptanceCriteria(spec);
28
+ * // Returns: [
29
+ * // { id: "AC-1", text: "System should handle empty input", lineNumber: 3 },
30
+ * // { id: "AC-2", text: "set(key, value, ttl) expires after ttl", lineNumber: 4 },
31
+ * // ]
32
+ * ```
33
+ */
34
+ export function parseAcceptanceCriteria(specContent: string): AcceptanceCriterion[] {
35
+ const criteria: AcceptanceCriterion[] = [];
36
+ const lines = specContent.split("\n");
37
+
38
+ for (let i = 0; i < lines.length; i++) {
39
+ const line = lines[i];
40
+ const lineNumber = i + 1;
41
+
42
+ // Match patterns:
43
+ // - AC-1: description
44
+ // - [ ] AC-1: description
45
+ // AC-1: description
46
+ const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
47
+
48
+ if (acMatch) {
49
+ const id = acMatch[1].toUpperCase(); // Normalize to uppercase
50
+ const text = acMatch[2].trim();
51
+
52
+ criteria.push({
53
+ id,
54
+ text,
55
+ lineNumber,
56
+ });
57
+ }
58
+ }
59
+
60
+ return criteria;
61
+ }
62
+
63
+ /**
64
+ * Build LLM prompt for generating acceptance tests.
65
+ *
66
+ * Combines acceptance criteria, codebase context, and test generation instructions.
67
+ *
68
+ * @param criteria - Extracted acceptance criteria
69
+ * @param featureName - Feature name for context
70
+ * @param codebaseContext - File tree, dependencies, test patterns
71
+ * @returns Formatted prompt string
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const prompt = buildAcceptanceTestPrompt(
76
+ * [{ id: "AC-1", text: "handles empty input", lineNumber: 5 }],
77
+ * "url-shortener",
78
+ * "File tree:\nsrc/\n index.ts\n"
79
+ * );
80
+ * ```
81
+ */
82
+ export function buildAcceptanceTestPrompt(
83
+ criteria: AcceptanceCriterion[],
84
+ featureName: string,
85
+ codebaseContext: string,
86
+ ): string {
87
+ const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join("\n");
88
+
89
+ return `You are a test engineer. Generate acceptance tests for the "${featureName}" feature based on the acceptance criteria below.
90
+
91
+ CODEBASE CONTEXT:
92
+ ${codebaseContext}
93
+
94
+ ACCEPTANCE CRITERIA:
95
+ ${criteriaList}
96
+
97
+ Generate a complete acceptance.test.ts file using bun:test framework. Follow these rules:
98
+
99
+ 1. **One test per AC**: Each acceptance criterion maps to exactly one test
100
+ 2. **Test observable behavior only**: No implementation details, only user-facing behavior
101
+ 3. **Independent tests**: No shared state between tests
102
+ 4. **Integration-level**: Tests should be runnable without mocking (use real implementations)
103
+ 5. **Clear test names**: Use format "AC-N: <description>" for test names
104
+ 6. **Async where needed**: Use async/await for operations that may be asynchronous
105
+
106
+ Use this structure:
107
+
108
+ \`\`\`typescript
109
+ import { describe, test, expect } from "bun:test";
110
+
111
+ describe("${featureName} - Acceptance Tests", () => {
112
+ test("AC-1: <description>", async () => {
113
+ // Test implementation
114
+ });
115
+
116
+ test("AC-2: <description>", async () => {
117
+ // Test implementation
118
+ });
119
+ });
120
+ \`\`\`
121
+
122
+ **Important**:
123
+ - Import the feature code being tested
124
+ - Set up any necessary test fixtures
125
+ - Use expect() assertions to verify behavior
126
+ - Clean up resources if needed (close connections, delete temp files)
127
+
128
+ Respond with ONLY the TypeScript test code (no markdown code fences, no explanation).`;
129
+ }
130
+
131
+ /**
132
+ * Generate acceptance tests from spec.md acceptance criteria.
133
+ *
134
+ * Parses AC lines from spec, builds LLM prompt, calls agent adapter,
135
+ * and returns generated test code. Falls back to skeleton tests if LLM fails.
136
+ *
137
+ * @param adapter - Agent adapter to use for test generation
138
+ * @param options - Generation options with spec content, context, and model
139
+ * @returns Generated test code and processed criteria
140
+ * @throws Error if AC parsing fails or agent call fails critically
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const adapter = new ClaudeCodeAdapter();
145
+ * const result = await generateAcceptanceTests(adapter, {
146
+ * specContent: await Bun.file("spec.md").text(),
147
+ * featureName: "url-shortener",
148
+ * workdir: "/project",
149
+ * codebaseContext: "File tree:\nsrc/\n",
150
+ * modelTier: "balanced",
151
+ * modelDef: { provider: "anthropic", model: "claude-sonnet-4-5" },
152
+ * });
153
+ *
154
+ * await Bun.write("acceptance.test.ts", result.testCode);
155
+ * ```
156
+ */
157
+ export async function generateAcceptanceTests(
158
+ adapter: AgentAdapter,
159
+ options: GenerateAcceptanceTestsOptions,
160
+ ): Promise<AcceptanceTestResult> {
161
+ // Parse acceptance criteria from spec
162
+ const logger = getLogger();
163
+ const criteria = parseAcceptanceCriteria(options.specContent);
164
+
165
+ if (criteria.length === 0) {
166
+ // No AC found — generate empty skeleton
167
+ logger.warn("acceptance", "⚠ No acceptance criteria found in spec.md");
168
+ return {
169
+ testCode: generateSkeletonTests(options.featureName, []),
170
+ criteria: [],
171
+ };
172
+ }
173
+
174
+ logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
175
+
176
+ // Build prompt
177
+ const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
178
+
179
+ try {
180
+ // Call agent to generate tests (using decompose as pattern)
181
+ const cmd = [adapter.binary, "--model", options.modelDef.model, "--dangerously-skip-permissions", "-p", prompt];
182
+
183
+ const proc = Bun.spawn(cmd, {
184
+ cwd: options.workdir,
185
+ stdout: "pipe",
186
+ stderr: "pipe",
187
+ env: {
188
+ ...process.env,
189
+ ...(options.modelDef.env || {}),
190
+ },
191
+ });
192
+
193
+ const exitCode = await proc.exited;
194
+ const stdout = await new Response(proc.stdout).text();
195
+ const stderr = await new Response(proc.stderr).text();
196
+
197
+ if (exitCode !== 0) {
198
+ logger.warn("acceptance", "⚠ Agent test generation failed", { stderr });
199
+ // Fall back to skeleton
200
+ return {
201
+ testCode: generateSkeletonTests(options.featureName, criteria),
202
+ criteria,
203
+ };
204
+ }
205
+
206
+ // Extract test code from output
207
+ const testCode = extractTestCode(stdout);
208
+
209
+ return {
210
+ testCode,
211
+ criteria,
212
+ };
213
+ } catch (error) {
214
+ logger.warn("acceptance", "⚠ Agent test generation error", { error: (error as Error).message });
215
+ // Fall back to skeleton
216
+ return {
217
+ testCode: generateSkeletonTests(options.featureName, criteria),
218
+ criteria,
219
+ };
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Extract TypeScript test code from agent output.
225
+ *
226
+ * Handles markdown code fences and extracts clean test code.
227
+ *
228
+ * @param output - Agent stdout
229
+ * @returns Extracted test code
230
+ */
231
+ function extractTestCode(output: string): string {
232
+ // Try to extract from markdown code fence
233
+ const fenceMatch = output.match(/```(?:typescript|ts)?\s*([\s\S]*?)\s*```/);
234
+ if (fenceMatch) {
235
+ return fenceMatch[1].trim();
236
+ }
237
+
238
+ // If no fence, try to find import statement and take everything from there
239
+ const importMatch = output.match(/import\s+{[\s\S]+/);
240
+ if (importMatch) {
241
+ return importMatch[0].trim();
242
+ }
243
+
244
+ // Fall back to full output
245
+ return output.trim();
246
+ }
247
+
248
+ /**
249
+ * Generate skeleton acceptance tests with TODO placeholders.
250
+ *
251
+ * Used as fallback when LLM test generation fails.
252
+ *
253
+ * @param featureName - Feature name
254
+ * @param criteria - Acceptance criteria to generate skeletons for
255
+ * @returns TypeScript test code with TODO placeholders
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * const skeleton = generateSkeletonTests("auth", [
260
+ * { id: "AC-1", text: "login succeeds", lineNumber: 5 },
261
+ * ]);
262
+ * // Generates test with TODO comment
263
+ * ```
264
+ */
265
+ export function generateSkeletonTests(featureName: string, criteria: AcceptanceCriterion[]): string {
266
+ const tests = criteria
267
+ .map((ac) => {
268
+ return ` test("${ac.id}: ${ac.text}", async () => {
269
+ // TODO: Implement acceptance test for ${ac.id}
270
+ // ${ac.text}
271
+ expect(true).toBe(false); // Replace with actual test
272
+ });`;
273
+ })
274
+ .join("\n\n");
275
+
276
+ return `import { describe, test, expect } from "bun:test";
277
+
278
+ describe("${featureName} - Acceptance Tests", () => {
279
+ ${tests || " // No acceptance criteria found"}
280
+ });
281
+ `;
282
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Acceptance Test Generation Module
3
+ *
4
+ * Barrel exports for acceptance test generation functionality.
5
+ */
6
+
7
+ export type {
8
+ AcceptanceCriterion,
9
+ GenerateAcceptanceTestsOptions,
10
+ AcceptanceTestResult,
11
+ } from "./types";
12
+
13
+ export {
14
+ parseAcceptanceCriteria,
15
+ buildAcceptanceTestPrompt,
16
+ generateAcceptanceTests,
17
+ generateSkeletonTests,
18
+ } from "./generator";
19
+
20
+ export type {
21
+ FixStory,
22
+ GenerateFixStoriesOptions,
23
+ } from "./fix-generator";
24
+
25
+ export {
26
+ generateFixStories,
27
+ findRelatedStories,
28
+ parseACTextFromSpec,
29
+ convertFixStoryToUserStory,
30
+ } from "./fix-generator";
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Acceptance Test Generation Types
3
+ *
4
+ * Types for generating acceptance tests from spec.md acceptance criteria.
5
+ */
6
+
7
+ import type { ModelDef, ModelTier } from "../config/schema";
8
+
9
+ /**
10
+ * A single acceptance criterion extracted from spec.md.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const ac: AcceptanceCriterion = {
15
+ * id: "AC-2",
16
+ * text: "set(key, value, ttl) expires after ttl milliseconds",
17
+ * lineNumber: 42,
18
+ * };
19
+ * ```
20
+ */
21
+ export interface AcceptanceCriterion {
22
+ /** AC identifier (e.g., "AC-1", "AC-2") */
23
+ id: string;
24
+ /** Full criterion text */
25
+ text: string;
26
+ /** Line number in spec.md for reference */
27
+ lineNumber: number;
28
+ }
29
+
30
+ /**
31
+ * Options for generating acceptance tests.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const options: GenerateAcceptanceTestsOptions = {
36
+ * specContent: "# Feature\n\n## Acceptance Criteria\n- AC-1: ...",
37
+ * featureName: "url-shortener",
38
+ * workdir: "/home/user/project",
39
+ * codebaseContext: "File tree:\nsrc/\n index.ts\n",
40
+ * modelTier: "balanced",
41
+ * modelDef: { provider: "anthropic", model: "claude-sonnet-4-5" },
42
+ * };
43
+ * ```
44
+ */
45
+ export interface GenerateAcceptanceTestsOptions {
46
+ /** Full spec.md content */
47
+ specContent: string;
48
+ /** Feature name for context */
49
+ featureName: string;
50
+ /** Working directory for context scanning */
51
+ workdir: string;
52
+ /** Codebase context (file tree, dependencies, test patterns) */
53
+ codebaseContext: string;
54
+ /** Model tier to use for test generation */
55
+ modelTier: ModelTier;
56
+ /** Resolved model definition */
57
+ modelDef: ModelDef;
58
+ }
59
+
60
+ /**
61
+ * Result from acceptance test generation.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const result: AcceptanceTestResult = {
66
+ * testCode: 'import { describe, test, expect } from "bun:test";\n\n...',
67
+ * criteria: [
68
+ * { id: "AC-1", text: "TTL expires", lineNumber: 12 },
69
+ * { id: "AC-2", text: "set(key, value, ttl) expires after ttl", lineNumber: 13 },
70
+ * ],
71
+ * };
72
+ * ```
73
+ */
74
+ export interface AcceptanceTestResult {
75
+ /** Generated test code */
76
+ testCode: string;
77
+ /** Acceptance criteria that were processed */
78
+ criteria: AcceptanceCriterion[];
79
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Claude Code Decompose Logic
3
+ *
4
+ * Extracted from claude.ts: decompose(), buildDecomposePrompt(),
5
+ * parseDecomposeOutput(), validateComplexity()
6
+ */
7
+
8
+ import type { DecomposeOptions, DecomposeResult, DecomposedStory } from "./types";
9
+
10
+ /**
11
+ * Build the decompose prompt combining spec content and codebase context.
12
+ */
13
+ export function buildDecomposePrompt(options: DecomposeOptions): string {
14
+ return `You are a requirements analyst. Break down the following feature specification into user stories and classify each story's complexity.
15
+
16
+ CODEBASE CONTEXT:
17
+ ${options.codebaseContext}
18
+
19
+ FEATURE SPECIFICATION:
20
+ ${options.specContent}
21
+
22
+ Decompose this spec into user stories. For each story, provide:
23
+ 1. id: Story ID (e.g., "US-001")
24
+ 2. title: Concise story title
25
+ 3. description: What needs to be implemented
26
+ 4. acceptanceCriteria: Array of testable criteria
27
+ 5. tags: Array of routing tags (e.g., ["security", "api"])
28
+ 6. dependencies: Array of story IDs this depends on (e.g., ["US-001"])
29
+ 7. complexity: "simple" | "medium" | "complex" | "expert"
30
+ 8. contextFiles: Array of file paths to inject into agent prompt before execution
31
+ 9. reasoning: Why this complexity level
32
+ 10. estimatedLOC: Estimated lines of code to change
33
+ 11. risks: Array of implementation risks
34
+ 12. testStrategy: "three-session-tdd" | "test-after"
35
+
36
+ testStrategy rules:
37
+ - "three-session-tdd": ONLY for complex/expert tasks that are security-critical (auth, encryption, tokens, credentials) or define public API contracts consumers depend on
38
+ - "test-after": for all other tasks including simple/medium complexity
39
+ - A "simple" complexity task should almost never be "three-session-tdd"
40
+
41
+ Complexity classification rules:
42
+ - simple: 1-3 files, <100 LOC, straightforward implementation, existing patterns
43
+ - medium: 3-6 files, 100-300 LOC, moderate logic, some new patterns
44
+ - complex: 6+ files, 300-800 LOC, architectural changes, cross-cutting concerns
45
+ - expert: Security/crypto/real-time/distributed systems, >800 LOC, new infrastructure
46
+
47
+ Grouping Guidelines:
48
+ - Combine small, related tasks (e.g., multiple utility functions, interfaces) into a single "simple" or "medium" story.
49
+ - Do NOT create separate stories for every single file or function unless complex.
50
+ - Aim for coherent units of value (e.g., "Implement User Authentication" vs "Create User Interface", "Create Login Service").
51
+ - Maximum recommended stories: 10-15 per feature. Group aggressively if list grows too long.
52
+
53
+ Consider:
54
+ 1. Does infrastructure exist? (e.g., "add caching" when no cache layer exists = complex)
55
+ 2. How many files will be touched?
56
+ 3. Are there cross-cutting concerns (auth, validation, error handling)?
57
+ 4. Does it require new dependencies or architectural decisions?
58
+
59
+ Respond with ONLY a JSON array (no markdown code fences):
60
+ [{
61
+ "id": "US-001",
62
+ "title": "Story title",
63
+ "description": "Story description",
64
+ "acceptanceCriteria": ["Criterion 1", "Criterion 2"],
65
+ "tags": ["tag1"],
66
+ "dependencies": [],
67
+ "complexity": "medium",
68
+ "contextFiles": ["src/path/to/file.ts"],
69
+ "reasoning": "Why this complexity level",
70
+ "estimatedLOC": 150,
71
+ "risks": ["Risk 1"],
72
+ "testStrategy": "test-after"
73
+ }]`;
74
+ }
75
+
76
+ /**
77
+ * Parse decompose output from agent stdout.
78
+ *
79
+ * Extracts JSON array from output, handles markdown code fences,
80
+ * and validates structure.
81
+ */
82
+ export function parseDecomposeOutput(output: string): DecomposedStory[] {
83
+ // Extract JSON from output (handles markdown code fences)
84
+ const jsonMatch = output.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/);
85
+ let jsonText = jsonMatch ? jsonMatch[1] : output;
86
+
87
+ // Try to find JSON array directly if no code fence
88
+ if (!jsonMatch) {
89
+ const arrayMatch = output.match(/\[[\s\S]*\]/);
90
+ if (arrayMatch) {
91
+ jsonText = arrayMatch[0];
92
+ }
93
+ }
94
+
95
+ // Parse JSON
96
+ let parsed: unknown;
97
+ try {
98
+ parsed = JSON.parse(jsonText.trim());
99
+ } catch (error) {
100
+ throw new Error(
101
+ `Failed to parse decompose output as JSON: ${(error as Error).message}\n\nOutput:\n${output.slice(0, 500)}`,
102
+ );
103
+ }
104
+
105
+ // Validate structure
106
+ if (!Array.isArray(parsed)) {
107
+ throw new Error("Decompose output is not an array");
108
+ }
109
+
110
+ // Map to DecomposedStory[] with validation
111
+ const stories: DecomposedStory[] = parsed.map((item: unknown, index: number) => {
112
+ // Type guard: ensure item is an object
113
+ if (typeof item !== "object" || item === null) {
114
+ throw new Error(`Story at index ${index} is not an object`);
115
+ }
116
+ const record = item as Record<string, unknown>;
117
+ if (!record.id || typeof record.id !== "string") {
118
+ throw new Error(`Story at index ${index} missing valid 'id' field`);
119
+ }
120
+ if (!record.title || typeof record.title !== "string") {
121
+ throw new Error(`Story ${record.id} missing valid 'title' field`);
122
+ }
123
+
124
+ return {
125
+ id: record.id,
126
+ title: record.title,
127
+ description: String(record.description || record.title),
128
+ acceptanceCriteria: Array.isArray(record.acceptanceCriteria)
129
+ ? record.acceptanceCriteria
130
+ : ["Implementation complete"],
131
+ tags: Array.isArray(record.tags) ? record.tags : [],
132
+ dependencies: Array.isArray(record.dependencies) ? record.dependencies : [],
133
+ complexity: validateComplexity(record.complexity),
134
+ // contextFiles: prefer the new field; fall back to legacy relevantFiles from older LLM responses
135
+ contextFiles: Array.isArray(record.contextFiles)
136
+ ? record.contextFiles
137
+ : Array.isArray(record.relevantFiles)
138
+ ? record.relevantFiles
139
+ : [],
140
+ relevantFiles: Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
141
+ reasoning: String(record.reasoning || "No reasoning provided"),
142
+ estimatedLOC: Number(record.estimatedLOC) || 0,
143
+ risks: Array.isArray(record.risks) ? record.risks : [],
144
+ testStrategy:
145
+ record.testStrategy === "three-session-tdd"
146
+ ? "three-session-tdd"
147
+ : record.testStrategy === "test-after"
148
+ ? "test-after"
149
+ : undefined,
150
+ };
151
+ });
152
+
153
+ if (stories.length === 0) {
154
+ throw new Error("Decompose returned empty story array");
155
+ }
156
+
157
+ return stories;
158
+ }
159
+
160
+ /**
161
+ * Validate complexity value from decompose output.
162
+ */
163
+ export function validateComplexity(value: unknown): "simple" | "medium" | "complex" | "expert" {
164
+ if (value === "simple" || value === "medium" || value === "complex" || value === "expert") {
165
+ return value;
166
+ }
167
+ // Default to medium if invalid
168
+ return "medium";
169
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Claude Code Plan Logic
3
+ *
4
+ * Extracted from claude.ts: plan(), buildPlanCommand()
5
+ */
6
+
7
+ import type { PidRegistry } from "../execution/pid-registry";
8
+ import { getLogger } from "../logger";
9
+ import type { AgentRunOptions } from "./types";
10
+ import type { PlanOptions, PlanResult } from "./types-extended";
11
+
12
+ /**
13
+ * Build the CLI command for plan mode.
14
+ */
15
+ export function buildPlanCommand(binary: string, options: PlanOptions): string[] {
16
+ const cmd = [binary, "--permission-mode", "plan"];
17
+
18
+ // Add model if specified
19
+ if (options.modelDef) {
20
+ cmd.push("--model", options.modelDef.model);
21
+ }
22
+
23
+ // Add dangerously-skip-permissions for automation
24
+ cmd.push("--dangerously-skip-permissions");
25
+
26
+ // Add prompt with codebase context and input file if available
27
+ let fullPrompt = options.prompt;
28
+ if (options.codebaseContext) {
29
+ fullPrompt = `${options.codebaseContext}\n\n${options.prompt}`;
30
+ }
31
+
32
+ // For non-interactive mode, include input file content in the prompt
33
+ if (options.inputFile) {
34
+ try {
35
+ const inputContent = require("node:fs").readFileSync(
36
+ require("node:path").resolve(options.workdir, options.inputFile),
37
+ "utf-8",
38
+ );
39
+ fullPrompt = `${fullPrompt}\n\n## Input Requirements\n\n${inputContent}`;
40
+ } catch (error) {
41
+ throw new Error(`Failed to read input file ${options.inputFile}: ${(error as Error).message}`);
42
+ }
43
+ }
44
+
45
+ if (!options.interactive) {
46
+ cmd.push("-p", fullPrompt);
47
+ } else {
48
+ // Interactive mode: pass prompt as initial message, agent will ask follow-ups
49
+ cmd.push("-p", fullPrompt);
50
+ }
51
+
52
+ return cmd;
53
+ }
54
+
55
+ /**
56
+ * Run Claude Code in plan mode to generate a feature specification.
57
+ */
58
+ export async function runPlan(
59
+ binary: string,
60
+ options: PlanOptions,
61
+ pidRegistry: PidRegistry,
62
+ buildAllowedEnv: (options: AgentRunOptions) => Record<string, string | undefined>,
63
+ ): Promise<PlanResult> {
64
+ const cmd = buildPlanCommand(binary, options);
65
+
66
+ const envOptions: AgentRunOptions = {
67
+ workdir: options.workdir,
68
+ modelDef: options.modelDef || { provider: "anthropic", model: "claude-sonnet-4-5", env: {} },
69
+ prompt: "",
70
+ modelTier: "balanced",
71
+ timeoutSeconds: 600,
72
+ };
73
+
74
+ if (options.interactive) {
75
+ // Interactive mode: inherit stdio
76
+ const proc = Bun.spawn(cmd, {
77
+ cwd: options.workdir,
78
+ stdin: "inherit",
79
+ stdout: "inherit",
80
+ stderr: "inherit",
81
+ env: buildAllowedEnv(envOptions),
82
+ });
83
+
84
+ // Register PID
85
+ await pidRegistry.register(proc.pid);
86
+
87
+ const exitCode = await proc.exited;
88
+
89
+ // Unregister PID after exit
90
+ await pidRegistry.unregister(proc.pid);
91
+
92
+ if (exitCode !== 0) {
93
+ throw new Error(`Plan mode failed with exit code ${exitCode}`);
94
+ }
95
+ return { specContent: "", conversationLog: "" };
96
+ }
97
+
98
+ // Non-interactive: redirect stdout to temp file via Bun.file()
99
+ const { join } = require("node:path");
100
+ const { mkdtempSync, readFileSync, rmSync } = require("node:fs");
101
+ const { tmpdir } = require("node:os");
102
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-plan-"));
103
+ const outFile = join(tempDir, "stdout.txt");
104
+ const errFile = join(tempDir, "stderr.txt");
105
+
106
+ try {
107
+ const proc = Bun.spawn(cmd, {
108
+ cwd: options.workdir,
109
+ stdin: "ignore",
110
+ stdout: Bun.file(outFile),
111
+ stderr: Bun.file(errFile),
112
+ env: buildAllowedEnv(envOptions),
113
+ });
114
+
115
+ // Register PID
116
+ await pidRegistry.register(proc.pid);
117
+
118
+ const exitCode = await proc.exited;
119
+
120
+ // Unregister PID after exit
121
+ await pidRegistry.unregister(proc.pid);
122
+
123
+ const specContent = readFileSync(outFile, "utf-8");
124
+ const conversationLog = readFileSync(errFile, "utf-8");
125
+
126
+ if (exitCode !== 0) {
127
+ throw new Error(`Plan mode failed with exit code ${exitCode}: ${conversationLog || "unknown error"}`);
128
+ }
129
+
130
+ return { specContent, conversationLog };
131
+ } finally {
132
+ try {
133
+ rmSync(tempDir, { recursive: true });
134
+ } catch (error) {
135
+ const logger = getLogger();
136
+ logger?.debug("agent", "Failed to clean up temp directory", { error, tempDir });
137
+ }
138
+ }
139
+ }