@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,230 @@
1
+ /**
2
+ * Test Output Parsing
3
+ *
4
+ * Unified test output parsing logic for Bun test framework.
5
+ * Extracted from execution/test-output-parser.ts and execution/verification.ts.
6
+ */
7
+
8
+ import type { TestFailure, TestOutputAnalysis, TestSummary } from "./types";
9
+
10
+ /**
11
+ * Parse Bun test output into structured failure objects.
12
+ *
13
+ * Example output format:
14
+ * ```
15
+ * bun test v1.0.0
16
+ *
17
+ * test/example.test.ts:
18
+ * ✓ passing test [0.5ms]
19
+ * ✗ failing test [1.2ms]
20
+ *
21
+ * (fail) describe block > nested block > test name [1.2ms]
22
+ * Error: Expected 1 to equal 2
23
+ * at /path/to/file.ts:10:15
24
+ * at Object.test (/path/to/file.ts:8:3)
25
+ * ```
26
+ */
27
+ export function parseBunTestOutput(output: string): TestSummary {
28
+ const lines = output.split("\n");
29
+ const failures: TestFailure[] = [];
30
+ let passed = 0;
31
+ let failed = 0;
32
+ let currentFile = "";
33
+ let i = 0;
34
+
35
+ while (i < lines.length) {
36
+ const line = lines[i];
37
+
38
+ // Extract file path from headers like "test/example.test.ts:"
39
+ if (line.trim().endsWith(".test.ts:") || line.trim().endsWith(".test.js:")) {
40
+ currentFile = line.trim().replace(/:$/, "");
41
+ i++;
42
+ continue;
43
+ }
44
+
45
+ // Count passed tests (✓ or ✔)
46
+ if (line.includes("✓") || line.includes("✔")) {
47
+ passed++;
48
+ i++;
49
+ continue;
50
+ }
51
+
52
+ // Count failed tests (✗ or ✘)
53
+ if (line.includes("✗") || line.includes("✘")) {
54
+ failed++;
55
+ i++;
56
+ continue;
57
+ }
58
+
59
+ // Parse failure line: "(fail) TestName > nested > name [duration]"
60
+ const failMatch = line.match(/^\(fail\)\s+(.+?)\s+\[[\d.]+m?s\]/);
61
+ if (failMatch) {
62
+ const testName = failMatch[1].trim();
63
+ i++;
64
+
65
+ // Extract error message and stack trace
66
+ let error = "";
67
+ const stackTrace: string[] = [];
68
+ let stackLineCount = 0;
69
+
70
+ // Read lines until we hit a blank line or another test result
71
+ while (i < lines.length && stackLineCount < 5) {
72
+ const nextLine = lines[i];
73
+
74
+ // Stop at blank line or next test result
75
+ if (!nextLine.trim() || nextLine.includes("(fail)") || nextLine.includes("✓") || nextLine.includes("✗")) {
76
+ break;
77
+ }
78
+
79
+ // First non-blank line is typically the error message
80
+ if (!error && nextLine.trim()) {
81
+ error = nextLine.trim();
82
+ i++;
83
+ continue;
84
+ }
85
+
86
+ // Subsequent lines starting with "at" are stack trace
87
+ if (nextLine.trim().startsWith("at ")) {
88
+ stackTrace.push(nextLine.trim());
89
+ stackLineCount++;
90
+ }
91
+ i++;
92
+ }
93
+
94
+ failures.push({
95
+ file: currentFile || "unknown",
96
+ testName,
97
+ error: error || "Unknown error",
98
+ stackTrace,
99
+ });
100
+ continue;
101
+ }
102
+
103
+ i++;
104
+ }
105
+
106
+ return { passed, failed, failures };
107
+ }
108
+
109
+ /**
110
+ * Format failure summary for agent feedback.
111
+ *
112
+ * Format:
113
+ * ```
114
+ * 1. file.test.ts > TestName > nested
115
+ * Error: message
116
+ * at file.ts:10:15
117
+ *
118
+ * 2. another.test.ts > OtherTest
119
+ * Error: another error
120
+ * at other.ts:20:10
121
+ * ```
122
+ */
123
+ export function formatFailureSummary(failures: TestFailure[], maxChars = 2000): string {
124
+ if (failures.length === 0) {
125
+ return "No test failures";
126
+ }
127
+
128
+ const lines: string[] = [];
129
+ let totalChars = 0;
130
+
131
+ for (let i = 0; i < failures.length; i++) {
132
+ const failure = failures[i];
133
+ const num = i + 1;
134
+
135
+ // Format: "1. file.test.ts > TestName"
136
+ const header = `${num}. ${failure.file} > ${failure.testName}`;
137
+ const errorLine = ` Error: ${failure.error}`;
138
+
139
+ // Add first stack trace line if available
140
+ const stackLine = failure.stackTrace.length > 0 ? ` ${failure.stackTrace[0]}` : "";
141
+
142
+ const blockLines = [header, errorLine];
143
+ if (stackLine) {
144
+ blockLines.push(stackLine);
145
+ }
146
+ blockLines.push(""); // blank line separator
147
+
148
+ const block = blockLines.join("\n");
149
+ const blockLength = block.length;
150
+
151
+ // Check if adding this block would exceed maxChars
152
+ if (totalChars + blockLength > maxChars && lines.length > 0) {
153
+ const remaining = failures.length - i;
154
+ lines.push(`\n... and ${remaining} more failure(s) (truncated)`);
155
+ break;
156
+ }
157
+
158
+ lines.push(...blockLines);
159
+ totalChars += blockLength;
160
+ }
161
+
162
+ return lines.join("\n").trim();
163
+ }
164
+
165
+ /**
166
+ * Parse test output to detect environmental failures.
167
+ *
168
+ * When exit code != 0 but all tests pass, classifies as ENVIRONMENTAL_FAILURE
169
+ * instead of TEST_FAILURE.
170
+ */
171
+ export function parseTestOutput(output: string, exitCode: number): TestOutputAnalysis {
172
+ // Regex patterns for different test frameworks
173
+ const patterns = [
174
+ /(\d+)\s+pass(?:ed)?(?:,\s+|\s+)(\d+)\s+fail/i, // "5 pass, 0 fail" or "5 passed 0 fail"
175
+ /Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i, // Jest format
176
+ /(\d+)\s+pass/i, // Bun format (just pass count)
177
+ ];
178
+
179
+ let passCount = 0;
180
+ let failCount = 0;
181
+
182
+ for (const pattern of patterns) {
183
+ // Match ALL occurrences — use the LAST one (final summary line)
184
+ const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
185
+ if (matches.length > 0) {
186
+ const lastMatch = matches[matches.length - 1];
187
+ passCount = Number.parseInt(lastMatch[1], 10);
188
+ // Some formats only show pass count
189
+ failCount = lastMatch[2] ? Number.parseInt(lastMatch[2], 10) : 0;
190
+ break;
191
+ }
192
+ }
193
+
194
+ // Check for explicit fail count if not found
195
+ if (failCount === 0) {
196
+ const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
197
+ if (failMatches.length > 0) {
198
+ failCount = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
199
+ }
200
+ }
201
+
202
+ const allTestsPassed = passCount > 0 && failCount === 0;
203
+ const isEnvironmentalFailure = allTestsPassed && exitCode !== 0;
204
+
205
+ const result: TestOutputAnalysis = {
206
+ allTestsPassed,
207
+ passCount,
208
+ failCount,
209
+ isEnvironmentalFailure,
210
+ };
211
+
212
+ if (isEnvironmentalFailure) {
213
+ result.error = `ENVIRONMENTAL_FAILURE: All ${passCount} tests passed but exit code was ${exitCode}. Check linter/typecheck/missing files.`;
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ /**
220
+ * Calculate early escalation threshold for environmental failures.
221
+ *
222
+ * Environmental failures should escalate faster: after ceil(tier.attempts / 2)
223
+ * instead of the full tier budget.
224
+ */
225
+ export function getEnvironmentalEscalationThreshold(tierAttempts: number, divisor = 2): number {
226
+ return Math.ceil(tierAttempts / divisor);
227
+ }
228
+
229
+ // Re-export types for consumers that import from this module
230
+ export type { TestFailure, TestSummary } from "./types";
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Rectification Logic
3
+ *
4
+ * Retry logic with backoff, max attempt tracking, and failure categorization.
5
+ * Extracted from execution/rectification.ts to eliminate duplication.
6
+ */
7
+
8
+ import type { RectificationConfig } from "../config";
9
+ import type { UserStory } from "../prd";
10
+ import { formatFailureSummary } from "./parser";
11
+ import type { RectificationState, TestFailure } from "./types";
12
+
13
+ /**
14
+ * Determine if rectification should retry based on state and config.
15
+ *
16
+ * Returns true if:
17
+ * - Current attempt < maxRetries
18
+ * - AND currentFailures > 0 (still have failures to fix)
19
+ * - AND NOT regressing (if abortOnIncreasingFailures is true)
20
+ *
21
+ * Returns false if:
22
+ * - Max retries reached
23
+ * - OR all tests passing (currentFailures = 0)
24
+ * - OR failures increased (regression spiral) AND abortOnIncreasingFailures = true
25
+ */
26
+ export function shouldRetryRectification(state: RectificationState, config: RectificationConfig): boolean {
27
+ // Stop if max retries reached
28
+ if (state.attempt >= config.maxRetries) {
29
+ return false;
30
+ }
31
+
32
+ // Stop if all tests passing
33
+ if (state.currentFailures === 0) {
34
+ return false;
35
+ }
36
+
37
+ // Abort if failures increased (regression spiral check)
38
+ if (config.abortOnIncreasingFailures && state.currentFailures > state.initialFailures) {
39
+ return false;
40
+ }
41
+
42
+ // Continue retrying
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * Create a rectification prompt with failure context.
48
+ *
49
+ * Includes:
50
+ * - Clear instructions about test regressions
51
+ * - Formatted failure summary
52
+ * - Specific test commands for failing files
53
+ */
54
+ export function createRectificationPrompt(
55
+ failures: TestFailure[],
56
+ story: UserStory,
57
+ config?: RectificationConfig,
58
+ ): string {
59
+ const maxChars = config?.maxFailureSummaryChars ?? 2000;
60
+ const failureSummary = formatFailureSummary(failures, maxChars);
61
+
62
+ // Extract unique failing test files
63
+ const failingFiles = Array.from(new Set(failures.map((f) => f.file)));
64
+ const testCommands = failingFiles.map((file) => ` bun test ${file}`).join("\n");
65
+
66
+ return `# Rectification Required
67
+
68
+ Your changes caused test regressions. Fix these without breaking existing logic.
69
+
70
+ ## Story Context
71
+
72
+ **Title:** ${story.title}
73
+
74
+ **Description:**
75
+ ${story.description}
76
+
77
+ **Acceptance Criteria:**
78
+ ${story.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}
79
+
80
+ ---
81
+
82
+ ## Test Failures
83
+
84
+ ${failureSummary}
85
+
86
+ ---
87
+
88
+ ## Instructions
89
+
90
+ 1. Review the failures above carefully.
91
+ 2. Identify the root cause of each failure.
92
+ 3. Fix the implementation WITHOUT loosening test assertions.
93
+ 4. Run the failing tests to verify your fixes:
94
+
95
+ ${testCommands}
96
+
97
+ 5. Ensure ALL tests pass before completing.
98
+
99
+ **IMPORTANT:**
100
+ - Do NOT modify test files unless there is a legitimate bug in the test itself.
101
+ - Do NOT loosen assertions to mask implementation bugs.
102
+ - Focus on fixing the source code to meet the test requirements.
103
+ - When running tests, run ONLY the failing test files shown above — NEVER run \`bun test\` without a file filter.
104
+ `;
105
+ }
106
+
107
+ // Re-export types for consumers that import from this module
108
+ export type { RectificationState } from "./types";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Unified Verification Types
3
+ *
4
+ * Shared type definitions for test execution, parsing, and verification.
5
+ */
6
+
7
+ /** Verification scope: what tests to run */
8
+ export type VerificationScope = "scoped" | "full" | "regression";
9
+
10
+ /** Verification status outcomes */
11
+ export type VerificationStatus =
12
+ | "SUCCESS"
13
+ | "TEST_FAILURE"
14
+ | "ENVIRONMENTAL_FAILURE"
15
+ | "ASSET_CHECK_FAILED"
16
+ | "TIMEOUT";
17
+
18
+ /** Test execution result (raw) */
19
+ export interface TestExecutionResult {
20
+ success: boolean;
21
+ timeout: boolean;
22
+ exitCode?: number;
23
+ output?: string;
24
+ error?: string;
25
+ killed?: boolean;
26
+ childProcessesKilled?: boolean;
27
+ countsTowardEscalation: boolean;
28
+ }
29
+
30
+ /** Test output analysis (parsed) */
31
+ export interface TestOutputAnalysis {
32
+ allTestsPassed: boolean;
33
+ passCount: number;
34
+ failCount: number;
35
+ isEnvironmentalFailure: boolean;
36
+ error?: string;
37
+ }
38
+
39
+ /** Asset verification result */
40
+ export interface AssetVerificationResult {
41
+ success: boolean;
42
+ missingFiles: string[];
43
+ error?: string;
44
+ }
45
+
46
+ /** Structured test failure information */
47
+ export interface TestFailure {
48
+ /** File path where the test failed */
49
+ file: string;
50
+ /** Full test name (including nested describe blocks) */
51
+ testName: string;
52
+ /** Error message */
53
+ error: string;
54
+ /** Stack trace lines (truncated to first 5 lines) */
55
+ stackTrace: string[];
56
+ }
57
+
58
+ /** Test run summary */
59
+ export interface TestSummary {
60
+ /** Number of tests that passed */
61
+ passed: number;
62
+ /** Number of tests that failed */
63
+ failed: number;
64
+ /** Structured failure details */
65
+ failures: TestFailure[];
66
+ }
67
+
68
+ /** Complete verification result */
69
+ export interface VerificationResult {
70
+ status: VerificationStatus;
71
+ success: boolean;
72
+ countsTowardEscalation: boolean;
73
+ output?: string;
74
+ error?: string;
75
+ missingFiles?: string[];
76
+ passCount?: number;
77
+ failCount?: number;
78
+ }
79
+
80
+ /** Rectification state tracking per story execution */
81
+ export interface RectificationState {
82
+ /** Current attempt number (0 = initial run, 1+ = retries) */
83
+ attempt: number;
84
+ /** Number of test failures on initial run */
85
+ initialFailures: number;
86
+ /** Number of test failures on current run */
87
+ currentFailures: number;
88
+ }
89
+
90
+ /** Verification gate options */
91
+ export interface VerificationGateOptions {
92
+ /** Working directory */
93
+ workdir: string;
94
+ /** Test command to execute */
95
+ command: string;
96
+ /** Timeout in seconds */
97
+ timeoutSeconds: number;
98
+ /** Expected files (for asset verification) */
99
+ expectedFiles?: string[];
100
+ /** Quality config for open handle / force exit behavior */
101
+ forceExit?: boolean;
102
+ detectOpenHandles?: boolean;
103
+ detectOpenHandlesRetries?: number;
104
+ /** How many times this story has timed out (tracks across retries) */
105
+ timeoutRetryCount?: number;
106
+ /** Process management config */
107
+ gracePeriodMs?: number;
108
+ drainTimeoutMs?: number;
109
+ shell?: string;
110
+ stripEnvVars?: string[];
111
+ /** Scoped test paths (for scoped verification) */
112
+ scopedTestPaths?: string[];
113
+ }
@@ -0,0 +1,65 @@
1
+ import type { UserStory } from "../prd/types";
2
+ import type { WorktreeManager } from "./manager";
3
+
4
+ export interface DispatchResult {
5
+ storyId: string;
6
+ success: boolean;
7
+ worktreePath: string;
8
+ error?: string;
9
+ }
10
+
11
+ export class ParallelDispatcher {
12
+ constructor(
13
+ private worktreeManager: WorktreeManager,
14
+ private runPipeline: (args: { workdir: string; story: UserStory }) => Promise<boolean>,
15
+ ) {}
16
+
17
+ async dispatch(projectRoot: string, stories: UserStory[], maxConcurrency: number): Promise<DispatchResult[]> {
18
+ const results: DispatchResult[] = [];
19
+ const independentBatches = this.getBatches(stories);
20
+
21
+ for (const batch of independentBatches) {
22
+ const batchPromises = batch.map(async (story) => {
23
+ const worktreePath = `${projectRoot}/.nax-wt/${story.id}`;
24
+ try {
25
+ await this.worktreeManager.create(projectRoot, story.id);
26
+ const success = await this.runPipeline({ workdir: worktreePath, story });
27
+ return { storyId: story.id, success, worktreePath };
28
+ } catch (err) {
29
+ return {
30
+ storyId: story.id,
31
+ success: false,
32
+ worktreePath,
33
+ error: err instanceof Error ? err.message : String(err),
34
+ };
35
+ }
36
+ });
37
+
38
+ const batchResults = await pLimit(maxConcurrency, batchPromises);
39
+ results.push(...batchResults);
40
+ }
41
+
42
+ return results;
43
+ }
44
+
45
+ private getBatches(stories: UserStory[]): UserStory[][] {
46
+ // TODO: Implement dependency-aware batching
47
+ return [stories];
48
+ }
49
+ }
50
+
51
+ // Helper for concurrency limiting (Simplified p-limit)
52
+ async function pLimit<T>(concurrency: number, promises: Promise<T>[]): Promise<T[]> {
53
+ const results: T[] = [];
54
+ const executing: Promise<void>[] = [];
55
+ for (const p of promises) {
56
+ const e = p.then((r) => {
57
+ results.push(r);
58
+ executing.splice(executing.indexOf(e), 1);
59
+ });
60
+ executing.push(e);
61
+ if (executing.length >= concurrency) await Promise.race(executing);
62
+ }
63
+ await Promise.all(executing);
64
+ return results;
65
+ }
@@ -0,0 +1,2 @@
1
+ export { WorktreeManager } from "./manager";
2
+ export type { WorktreeInfo } from "./types";
@@ -0,0 +1,187 @@
1
+ import { existsSync, symlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getSafeLogger } from "../logger";
4
+ import type { WorktreeInfo } from "./types";
5
+
6
+ export class WorktreeManager {
7
+ /**
8
+ * Creates a git worktree at .nax-wt/<storyId>/ with branch nax/<storyId>
9
+ * and symlinks node_modules and .env from project root
10
+ */
11
+ async create(projectRoot: string, storyId: string): Promise<void> {
12
+ const worktreePath = join(projectRoot, ".nax-wt", storyId);
13
+ const branchName = `nax/${storyId}`;
14
+
15
+ try {
16
+ // Create worktree with new branch
17
+ const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
18
+ cwd: projectRoot,
19
+ stdout: "pipe",
20
+ stderr: "pipe",
21
+ });
22
+
23
+ const exitCode = await proc.exited;
24
+ if (exitCode !== 0) {
25
+ const stderr = await new Response(proc.stderr).text();
26
+ throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
27
+ }
28
+ } catch (error) {
29
+ if (error instanceof Error) {
30
+ // Enhance error messages for common scenarios
31
+ if (error.message.includes("not a git repository")) {
32
+ throw new Error(`Not a git repository: ${projectRoot}`);
33
+ }
34
+ if (error.message.includes("already exists")) {
35
+ throw new Error(`Worktree for story ${storyId} already exists at ${worktreePath}`);
36
+ }
37
+ throw error;
38
+ }
39
+ throw new Error(`Failed to create worktree: ${String(error)}`);
40
+ }
41
+
42
+ // Symlink node_modules if it exists
43
+ const nodeModulesSource = join(projectRoot, "node_modules");
44
+ if (existsSync(nodeModulesSource)) {
45
+ const nodeModulesTarget = join(worktreePath, "node_modules");
46
+ try {
47
+ symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
48
+ } catch (error) {
49
+ // Clean up worktree if symlinking fails
50
+ await this.remove(projectRoot, storyId);
51
+ throw new Error(`Failed to symlink node_modules: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ }
54
+
55
+ // Symlink .env if it exists
56
+ const envSource = join(projectRoot, ".env");
57
+ if (existsSync(envSource)) {
58
+ const envTarget = join(worktreePath, ".env");
59
+ try {
60
+ symlinkSync(envSource, envTarget, "file");
61
+ } catch (error) {
62
+ // Clean up worktree if symlinking fails
63
+ await this.remove(projectRoot, storyId);
64
+ throw new Error(`Failed to symlink .env: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Removes worktree and deletes branch
71
+ */
72
+ async remove(projectRoot: string, storyId: string): Promise<void> {
73
+ const worktreePath = join(projectRoot, ".nax-wt", storyId);
74
+ const branchName = `nax/${storyId}`;
75
+
76
+ // Remove worktree
77
+ try {
78
+ const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
79
+ cwd: projectRoot,
80
+ stdout: "pipe",
81
+ stderr: "pipe",
82
+ });
83
+
84
+ const exitCode = await proc.exited;
85
+ if (exitCode !== 0) {
86
+ const stderr = await new Response(proc.stderr).text();
87
+ if (
88
+ stderr.includes("not found") ||
89
+ stderr.includes("does not exist") ||
90
+ stderr.includes("no such worktree") ||
91
+ stderr.includes("is not a working tree")
92
+ ) {
93
+ throw new Error(`Worktree not found: ${worktreePath}`);
94
+ }
95
+ throw new Error(`Failed to remove worktree: ${stderr || "unknown error"}`);
96
+ }
97
+ } catch (error) {
98
+ if (error instanceof Error) {
99
+ throw error;
100
+ }
101
+ throw new Error(`Failed to remove worktree: ${String(error)}`);
102
+ }
103
+
104
+ // Delete branch
105
+ try {
106
+ const proc = Bun.spawn(["git", "branch", "-D", branchName], {
107
+ cwd: projectRoot,
108
+ stdout: "pipe",
109
+ stderr: "pipe",
110
+ });
111
+
112
+ const exitCode = await proc.exited;
113
+ if (exitCode !== 0) {
114
+ const stderr = await new Response(proc.stderr).text();
115
+ // Don't fail if branch doesn't exist
116
+ if (!stderr.includes("not found")) {
117
+ const logger = getSafeLogger();
118
+ logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
119
+ }
120
+ }
121
+ } catch (error) {
122
+ // Log warning but don't fail - worktree is already removed
123
+ const logger = getSafeLogger();
124
+ logger?.warn("worktree", `Failed to delete branch ${branchName}`, {
125
+ error: error instanceof Error ? error.message : String(error),
126
+ });
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Returns active worktrees
132
+ */
133
+ async list(projectRoot: string): Promise<WorktreeInfo[]> {
134
+ try {
135
+ const proc = Bun.spawn(["git", "worktree", "list", "--porcelain"], {
136
+ cwd: projectRoot,
137
+ stdout: "pipe",
138
+ stderr: "pipe",
139
+ });
140
+
141
+ const exitCode = await proc.exited;
142
+ if (exitCode !== 0) {
143
+ const stderr = await new Response(proc.stderr).text();
144
+ throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
145
+ }
146
+
147
+ const stdout = await new Response(proc.stdout).text();
148
+ return this.parseWorktreeList(stdout);
149
+ } catch (error) {
150
+ if (error instanceof Error) {
151
+ throw error;
152
+ }
153
+ throw new Error(`Failed to list worktrees: ${String(error)}`);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Parses git worktree list --porcelain output
159
+ */
160
+ private parseWorktreeList(output: string): WorktreeInfo[] {
161
+ const worktrees: WorktreeInfo[] = [];
162
+ const lines = output.trim().split("\n");
163
+
164
+ let currentWorktree: Partial<WorktreeInfo> = {};
165
+
166
+ for (const line of lines) {
167
+ if (line.startsWith("worktree ")) {
168
+ currentWorktree.path = line.substring("worktree ".length);
169
+ } else if (line.startsWith("branch ")) {
170
+ currentWorktree.branch = line.substring("branch ".length).replace("refs/heads/", "");
171
+ } else if (line === "") {
172
+ // Empty line indicates end of worktree entry
173
+ if (currentWorktree.path && currentWorktree.branch) {
174
+ worktrees.push(currentWorktree as WorktreeInfo);
175
+ }
176
+ currentWorktree = {};
177
+ }
178
+ }
179
+
180
+ // Handle last entry if no trailing newline
181
+ if (currentWorktree.path && currentWorktree.branch) {
182
+ worktrees.push(currentWorktree as WorktreeInfo);
183
+ }
184
+
185
+ return worktrees;
186
+ }
187
+ }