@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,204 @@
1
+ /**
2
+ * Run Setup — Initial Setup Logic
3
+ *
4
+ * Handles the initial setup phase before the main execution loop:
5
+ * - Status writer initialization
6
+ * - PID registry cleanup
7
+ * - Crash handler installation
8
+ * - Lock acquisition
9
+ * - Plugin loading
10
+ * - PRD loading
11
+ * - Precheck validation
12
+ * - Run initialization
13
+ */
14
+
15
+ import * as os from "node:os";
16
+ import path from "node:path";
17
+ import type { NaxConfig } from "../../config";
18
+ import { LockAcquisitionError } from "../../errors";
19
+ import type { LoadedHooksConfig } from "../../hooks";
20
+ import { fireHook } from "../../hooks";
21
+ import type { InteractionChain } from "../../interaction";
22
+ import { initInteractionChain } from "../../interaction";
23
+ import { getSafeLogger } from "../../logger";
24
+ import { loadPlugins } from "../../plugins/loader";
25
+ import type { PluginRegistry } from "../../plugins/registry";
26
+ import type { PRD } from "../../prd";
27
+ import { loadPRD } from "../../prd";
28
+ import { installCrashHandlers } from "../crash-recovery";
29
+ import { acquireLock, hookCtx } from "../helpers";
30
+ import { PidRegistry } from "../pid-registry";
31
+ import { StatusWriter } from "../status-writer";
32
+
33
+ export interface RunSetupOptions {
34
+ prdPath: string;
35
+ workdir: string;
36
+ config: NaxConfig;
37
+ hooks: LoadedHooksConfig;
38
+ feature: string;
39
+ dryRun: boolean;
40
+ statusFile?: string;
41
+ logFilePath?: string;
42
+ runId: string;
43
+ startedAt: string;
44
+ startTime: number;
45
+ skipPrecheck: boolean;
46
+ headless: boolean;
47
+ formatterMode: "quiet" | "normal" | "verbose" | "json";
48
+ getTotalCost: () => number;
49
+ getIterations: () => number;
50
+ // BUG-017: Additional getters for run.complete event on SIGTERM
51
+ getStoriesCompleted: () => number;
52
+ getTotalStories: () => number;
53
+ }
54
+
55
+ export interface RunSetupResult {
56
+ statusWriter: StatusWriter;
57
+ pidRegistry: PidRegistry;
58
+ cleanupCrashHandlers: () => void;
59
+ pluginRegistry: PluginRegistry;
60
+ prd: PRD;
61
+ storyCounts: {
62
+ total: number;
63
+ passed: number;
64
+ pending: number;
65
+ failed: number;
66
+ };
67
+ interactionChain: InteractionChain | null;
68
+ }
69
+
70
+ /**
71
+ * Execute initial setup phase
72
+ */
73
+ export async function setupRun(options: RunSetupOptions): Promise<RunSetupResult> {
74
+ const logger = getSafeLogger();
75
+ const {
76
+ prdPath,
77
+ workdir,
78
+ config,
79
+ hooks,
80
+ feature,
81
+ dryRun,
82
+ statusFile,
83
+ logFilePath,
84
+ runId,
85
+ startedAt,
86
+ startTime,
87
+ skipPrecheck,
88
+ headless,
89
+ formatterMode,
90
+ getTotalCost,
91
+ getIterations,
92
+ } = options;
93
+
94
+ // ── Status writer (encapsulates status file state and write logic) ───────
95
+ const statusWriter = new StatusWriter(statusFile, config, {
96
+ runId,
97
+ feature,
98
+ startedAt,
99
+ dryRun,
100
+ startTimeMs: startTime,
101
+ pid: process.pid,
102
+ });
103
+
104
+ // ── PID registry for orphan process cleanup (BUG-002) ───────
105
+ const pidRegistry = new PidRegistry(workdir);
106
+
107
+ // Cleanup stale PIDs from previous crashed runs
108
+ await pidRegistry.cleanupStale();
109
+
110
+ // Install crash handlers for signal recovery (US-007, BUG-1+MEM-1 fix: pass getters, cleanup in finally)
111
+ const cleanupCrashHandlers = installCrashHandlers({
112
+ statusWriter,
113
+ getTotalCost,
114
+ getIterations,
115
+ jsonlFilePath: logFilePath,
116
+ pidRegistry,
117
+ // BUG-017: Pass context for run.complete event on SIGTERM
118
+ runId: options.runId,
119
+ feature: options.feature,
120
+ getStartTime: () => options.startTime,
121
+ getTotalStories: options.getTotalStories,
122
+ getStoriesCompleted: options.getStoriesCompleted,
123
+ });
124
+
125
+ // Acquire lock to prevent concurrent execution
126
+ const lockAcquired = await acquireLock(workdir);
127
+ if (!lockAcquired) {
128
+ logger?.error("execution", "Another nax process is already running in this directory");
129
+ logger?.error("execution", "If you believe this is an error, remove nax.lock manually");
130
+ throw new LockAcquisitionError(workdir);
131
+ }
132
+
133
+ // Load plugins (before try block so it's accessible in finally)
134
+ const globalPluginsDir = path.join(os.homedir(), ".nax", "plugins");
135
+ const projectPluginsDir = path.join(workdir, "nax", "plugins");
136
+ const configPlugins = config.plugins || [];
137
+ const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir);
138
+
139
+ // Load PRD (before try block so it's accessible in finally for onRunEnd)
140
+ let prd = await loadPRD(prdPath);
141
+
142
+ // Initialize interaction chain (US-008) — do this BEFORE precheck so story size prompts can use it
143
+ const interactionChain = await initInteractionChain(config, headless);
144
+
145
+ // ── Prime StatusWriter with PRD so precheck-failed can be recorded ─────────
146
+ statusWriter.setPrd(prd);
147
+
148
+ // ── Run precheck validations (unless --skip-precheck) ──────────────────────
149
+ if (!skipPrecheck) {
150
+ const { runPrecheckValidation } = await import("./precheck-runner");
151
+ await runPrecheckValidation({
152
+ config,
153
+ prd,
154
+ workdir,
155
+ logFilePath,
156
+ statusWriter,
157
+ headless,
158
+ formatterMode,
159
+ interactionChain,
160
+ featureName: feature,
161
+ });
162
+ } else {
163
+ logger?.warn("precheck", "Precheck validations skipped (--skip-precheck)");
164
+ }
165
+
166
+ // Log plugins loaded
167
+ logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
168
+ plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides })),
169
+ });
170
+
171
+ // Log run start
172
+ const routingMode = config.routing.llm?.mode ?? "hybrid";
173
+ logger?.info("run.start", `Starting feature: ${feature}`, {
174
+ runId,
175
+ feature,
176
+ workdir,
177
+ dryRun,
178
+ routingMode,
179
+ });
180
+
181
+ // Fire on-start hook
182
+ await fireHook(hooks, "on-start", hookCtx(feature), workdir);
183
+
184
+ // Initialize run: check agent, reconcile state, validate limits
185
+ const { initializeRun } = await import("./run-initialization");
186
+ const initResult = await initializeRun({
187
+ config,
188
+ prdPath,
189
+ workdir,
190
+ dryRun,
191
+ });
192
+ prd = initResult.prd;
193
+ const counts = initResult.storyCounts;
194
+
195
+ return {
196
+ statusWriter,
197
+ pidRegistry,
198
+ cleanupCrashHandlers,
199
+ pluginRegistry,
200
+ prd,
201
+ storyCounts: counts,
202
+ interactionChain,
203
+ };
204
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Story Lifecycle Hooks
3
+ *
4
+ * Centralizes reporter notification boilerplate for story lifecycle events:
5
+ * - onStoryComplete (completed/paused/skipped/failed)
6
+ */
7
+
8
+ import { getSafeLogger } from "../../logger";
9
+ import type { IReporter } from "../../plugins/types";
10
+
11
+ export interface StoryCompleteEvent {
12
+ runId: string;
13
+ storyId: string;
14
+ status: "completed" | "paused" | "skipped" | "failed";
15
+ durationMs: number;
16
+ cost: number;
17
+ tier: string;
18
+ testStrategy: string;
19
+ }
20
+
21
+ /**
22
+ * Emit onStoryComplete event to all reporters
23
+ *
24
+ * Handles error recovery for individual reporter failures.
25
+ */
26
+ export async function emitStoryComplete(reporters: IReporter[], event: StoryCompleteEvent): Promise<void> {
27
+ const logger = getSafeLogger();
28
+
29
+ for (const reporter of reporters) {
30
+ if (reporter.onStoryComplete) {
31
+ try {
32
+ await reporter.onStoryComplete(event);
33
+ } catch (error) {
34
+ logger?.warn("plugins", `Reporter '${reporter.name}' onStoryComplete failed`, { error });
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Story Size Prompts (v0.16.0)
3
+ *
4
+ * Post-precheck interaction prompts for flagged stories.
5
+ * Asks user to confirm (Approve/Skip/Abort) for each large story.
6
+ * Integrates with interaction chain.
7
+ */
8
+
9
+ import type { InteractionChain } from "../../interaction/chain";
10
+ import type { InteractionResponse } from "../../interaction/types";
11
+ import { getSafeLogger } from "../../logger";
12
+ import type { PRD } from "../../prd/types";
13
+ import type { FlaggedStory } from "../../precheck/story-size-gate";
14
+
15
+ /** Prompt result for a single story */
16
+ export interface StoryPromptResult {
17
+ storyId: string;
18
+ action: "approve" | "skip" | "abort";
19
+ }
20
+
21
+ /** Summary of story size prompt results */
22
+ export interface StorySizePromptSummary {
23
+ approved: string[];
24
+ skipped: string[];
25
+ aborted: boolean;
26
+ }
27
+
28
+ /**
29
+ * Prompt user for each flagged story
30
+ *
31
+ * @param flaggedStories - Stories that exceeded size thresholds
32
+ * @param prd - PRD instance (mutated: skips stories if user selects Skip)
33
+ * @param chain - Interaction chain
34
+ * @param featureName - Feature name for context
35
+ * @returns Summary of user decisions
36
+ */
37
+ export async function promptForFlaggedStories(
38
+ flaggedStories: FlaggedStory[],
39
+ prd: PRD,
40
+ chain: InteractionChain,
41
+ featureName: string,
42
+ ): Promise<StorySizePromptSummary> {
43
+ const logger = getSafeLogger();
44
+ const summary: StorySizePromptSummary = {
45
+ approved: [],
46
+ skipped: [],
47
+ aborted: false,
48
+ };
49
+
50
+ for (const flagged of flaggedStories) {
51
+ logger?.info("precheck", `Story size gate: prompting for ${flagged.storyId}`, {
52
+ recommendation: flagged.recommendation,
53
+ });
54
+
55
+ const story = prd.userStories.find((s) => s.id === flagged.storyId);
56
+ if (!story) {
57
+ logger?.warn("precheck", `Story ${flagged.storyId} not found in PRD, skipping prompt`);
58
+ continue;
59
+ }
60
+
61
+ // Build detail message with signal breakdown
62
+ const signalDetails = [
63
+ `- Acceptance Criteria: ${flagged.signals.acCount.value} (threshold: ${flagged.signals.acCount.threshold}) ${flagged.signals.acCount.flagged ? "⚠" : "✓"}`,
64
+ `- Description Length: ${flagged.signals.descriptionLength.value} chars (threshold: ${flagged.signals.descriptionLength.threshold}) ${flagged.signals.descriptionLength.flagged ? "⚠" : "✓"}`,
65
+ `- Bullet Points: ${flagged.signals.bulletPoints.value} (threshold: ${flagged.signals.bulletPoints.threshold}) ${flagged.signals.bulletPoints.flagged ? "⚠" : "✓"}`,
66
+ ];
67
+
68
+ const detail = `${flagged.recommendation}\n\nSignal breakdown:\n${signalDetails.join("\n")}\n\nStory: ${story.title}`;
69
+
70
+ // Send interaction request
71
+ const requestId = `ix-${flagged.storyId}-size-gate`;
72
+ const response: InteractionResponse = await chain.prompt({
73
+ id: requestId,
74
+ type: "choose",
75
+ featureName,
76
+ storyId: flagged.storyId,
77
+ stage: "pre-flight",
78
+ summary: `Story ${flagged.storyId} exceeds size thresholds — proceed?`,
79
+ detail,
80
+ options: [
81
+ { key: "approve", label: "Approve", description: "Continue with this story despite size warnings" },
82
+ { key: "skip", label: "Skip", description: "Skip this story and continue with others" },
83
+ { key: "abort", label: "Abort", description: "Abort the entire run" },
84
+ ],
85
+ timeout: 600000, // 10 minutes
86
+ fallback: "escalate",
87
+ createdAt: Date.now(),
88
+ });
89
+
90
+ // Process response
91
+ switch (response.action) {
92
+ case "approve": {
93
+ summary.approved.push(flagged.storyId);
94
+ logger?.info("precheck", `User approved ${flagged.storyId} despite size warnings`);
95
+ break;
96
+ }
97
+ case "skip": {
98
+ summary.skipped.push(flagged.storyId);
99
+ story.status = "skipped";
100
+ logger?.info("precheck", `User skipped ${flagged.storyId} due to size warnings`);
101
+ break;
102
+ }
103
+ case "abort": {
104
+ summary.aborted = true;
105
+ logger?.warn("precheck", `User aborted run due to ${flagged.storyId} size warnings`);
106
+ throw new Error(`Run aborted by user: story ${flagged.storyId} exceeds size thresholds`);
107
+ }
108
+ default: {
109
+ logger?.warn("precheck", `Unknown action ${response.action} for ${flagged.storyId}, aborting`);
110
+ summary.aborted = true;
111
+ throw new Error(`Run aborted: unknown action ${response.action}`);
112
+ }
113
+ }
114
+ }
115
+
116
+ logger?.info("precheck", "Story size gate prompts complete", {
117
+ approved: summary.approved.length,
118
+ skipped: summary.skipped.length,
119
+ aborted: summary.aborted,
120
+ });
121
+
122
+ return summary;
123
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Lock File Management
3
+ *
4
+ * Extracted from helpers.ts: execution lock acquisition and release.
5
+ * Prevents concurrent runs in the same directory.
6
+ */
7
+
8
+ import path from "node:path";
9
+ import { getLogger } from "../logger";
10
+
11
+ /** Safely get logger instance, returns null if not initialized */
12
+ function getSafeLogger() {
13
+ try {
14
+ return getLogger();
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ /** Check if a process with given PID is still alive */
21
+ function isProcessAlive(pid: number): boolean {
22
+ try {
23
+ // kill(pid, 0) checks if process exists without actually sending a signal
24
+ process.kill(pid, 0);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Acquire execution lock to prevent concurrent runs in same directory.
33
+ * Creates nax.lock file with PID and timestamp.
34
+ * Returns true if lock acquired, false if another process holds it.
35
+ *
36
+ * Handles stale locks from crashed/OOM-killed processes:
37
+ * - Reads PID from existing lock file
38
+ * - Checks if process is still alive using kill(pid, 0)
39
+ * - Removes stale lock if process is dead
40
+ * - Re-acquires lock after removal
41
+ */
42
+ export async function acquireLock(workdir: string): Promise<boolean> {
43
+ const lockPath = path.join(workdir, "nax.lock");
44
+ const lockFile = Bun.file(lockPath);
45
+
46
+ try {
47
+ // BUG-2 fix: First check for stale lock before attempting atomic create
48
+ const exists = await lockFile.exists();
49
+ if (exists) {
50
+ // Read lock data
51
+ const lockContent = await lockFile.text();
52
+ const lockData = JSON.parse(lockContent);
53
+ const lockPid = lockData.pid;
54
+
55
+ // Check if the process is still alive
56
+ if (isProcessAlive(lockPid)) {
57
+ // Process is alive, lock is valid
58
+ return false;
59
+ }
60
+
61
+ // Process is dead, remove stale lock
62
+ const logger = getSafeLogger();
63
+ logger?.warn("execution", "Removing stale lock", {
64
+ pid: lockPid,
65
+ });
66
+ const fs = await import("node:fs/promises");
67
+ await fs.unlink(lockPath).catch(() => {});
68
+ }
69
+
70
+ // Create lock file atomically using exclusive create (O_CREAT | O_EXCL)
71
+ const lockData = {
72
+ pid: process.pid,
73
+ timestamp: Date.now(),
74
+ };
75
+ const fs = await import("node:fs");
76
+ const fd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o644);
77
+ fs.writeSync(fd, JSON.stringify(lockData));
78
+ fs.closeSync(fd);
79
+ return true;
80
+ } catch (error) {
81
+ // EEXIST means another process won the race
82
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") {
83
+ return false;
84
+ }
85
+ const logger = getSafeLogger();
86
+ logger?.warn("execution", "Failed to acquire lock", {
87
+ error: (error as Error).message,
88
+ });
89
+ return false;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Release execution lock by deleting nax.lock file.
95
+ *
96
+ * @param workdir - Working directory to unlock
97
+ */
98
+ export async function releaseLock(workdir: string): Promise<void> {
99
+ const lockPath = path.join(workdir, "nax.lock");
100
+ try {
101
+ const file = Bun.file(lockPath);
102
+ const exists = await file.exists();
103
+ if (exists) {
104
+ const proc = Bun.spawn(["rm", lockPath], { stdout: "pipe" });
105
+ await proc.exited;
106
+ // Wait a bit for filesystem to sync (prevents race in tests)
107
+ await Bun.sleep(10);
108
+ }
109
+ } catch (error) {
110
+ const logger = getSafeLogger();
111
+ logger?.warn("execution", "Failed to release lock", {
112
+ error: (error as Error).message,
113
+ });
114
+ }
115
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Parallel Execution Wrapper
3
+ *
4
+ * Handles the full parallel execution flow:
5
+ * - Status updates with parallel info
6
+ * - Execute parallel stories
7
+ * - Handle completion or continue to sequential
8
+ */
9
+
10
+ import * as os from "node:os";
11
+ import path from "node:path";
12
+ import type { NaxConfig } from "../config";
13
+ import type { LoadedHooksConfig } from "../hooks";
14
+ import { fireHook } from "../hooks";
15
+ import { getSafeLogger } from "../logger";
16
+ import type { StoryMetrics } from "../metrics";
17
+ import type { PipelineEventEmitter } from "../pipeline/events";
18
+ import type { PluginRegistry } from "../plugins/registry";
19
+ import type { PRD } from "../prd";
20
+ import { countStories, isComplete } from "../prd";
21
+ import { getAllReadyStories, hookCtx } from "./helpers";
22
+ import { executeParallel } from "./parallel";
23
+ import type { StatusWriter } from "./status-writer";
24
+
25
+ export interface ParallelExecutorOptions {
26
+ prdPath: string;
27
+ workdir: string;
28
+ config: NaxConfig;
29
+ hooks: LoadedHooksConfig;
30
+ feature: string;
31
+ featureDir?: string;
32
+ parallelCount: number;
33
+ eventEmitter?: PipelineEventEmitter;
34
+ statusWriter: StatusWriter;
35
+ runId: string;
36
+ startedAt: string;
37
+ startTime: number;
38
+ totalCost: number;
39
+ iterations: number;
40
+ storiesCompleted: number;
41
+ allStoryMetrics: StoryMetrics[];
42
+ pluginRegistry: PluginRegistry;
43
+ formatterMode: "quiet" | "normal" | "verbose" | "json";
44
+ headless: boolean;
45
+ }
46
+
47
+ export interface ParallelExecutorResult {
48
+ prd: PRD;
49
+ totalCost: number;
50
+ storiesCompleted: number;
51
+ completed: boolean;
52
+ durationMs?: number;
53
+ }
54
+
55
+ /**
56
+ * Execute parallel stories if --parallel is set
57
+ */
58
+ export async function runParallelExecution(
59
+ options: ParallelExecutorOptions,
60
+ initialPrd: PRD,
61
+ ): Promise<ParallelExecutorResult> {
62
+ const logger = getSafeLogger();
63
+ const {
64
+ prdPath,
65
+ workdir,
66
+ config,
67
+ hooks,
68
+ feature,
69
+ featureDir,
70
+ parallelCount,
71
+ eventEmitter,
72
+ statusWriter,
73
+ runId,
74
+ startedAt,
75
+ startTime,
76
+ pluginRegistry,
77
+ formatterMode,
78
+ headless,
79
+ } = options;
80
+
81
+ let { totalCost, iterations, storiesCompleted, allStoryMetrics } = options;
82
+ let prd = initialPrd;
83
+
84
+ const readyStories = getAllReadyStories(prd);
85
+ if (readyStories.length === 0) {
86
+ logger?.info("parallel", "No stories ready for parallel execution");
87
+ return { prd, totalCost, storiesCompleted, completed: false };
88
+ }
89
+
90
+ const maxConcurrency = parallelCount === 0 ? os.cpus().length : Math.max(1, parallelCount);
91
+
92
+ logger?.info("parallel", "Starting parallel execution mode", {
93
+ totalStories: readyStories.length,
94
+ maxConcurrency,
95
+ });
96
+
97
+ // Update status with parallel info
98
+ statusWriter.setPrd(prd);
99
+ await statusWriter.update(totalCost, iterations, {
100
+ parallel: {
101
+ enabled: true,
102
+ maxConcurrency,
103
+ activeStories: readyStories.map((s) => ({
104
+ storyId: s.id,
105
+ worktreePath: path.join(workdir, ".nax-wt", s.id),
106
+ })),
107
+ },
108
+ });
109
+
110
+ try {
111
+ const parallelResult = await executeParallel(
112
+ readyStories,
113
+ prdPath,
114
+ workdir,
115
+ config,
116
+ hooks,
117
+ pluginRegistry,
118
+ prd,
119
+ featureDir,
120
+ parallelCount,
121
+ eventEmitter,
122
+ );
123
+
124
+ prd = parallelResult.updatedPrd;
125
+ storiesCompleted += parallelResult.storiesCompleted;
126
+ totalCost += parallelResult.totalCost;
127
+
128
+ logger?.info("parallel", "Parallel execution complete", {
129
+ storiesCompleted: parallelResult.storiesCompleted,
130
+ totalCost: parallelResult.totalCost,
131
+ });
132
+
133
+ // Clear parallel status
134
+ statusWriter.setPrd(prd);
135
+ await statusWriter.update(totalCost, iterations, {
136
+ parallel: {
137
+ enabled: true,
138
+ maxConcurrency,
139
+ activeStories: [],
140
+ },
141
+ });
142
+ } catch (error) {
143
+ logger?.error("parallel", "Parallel execution failed", {
144
+ error: error instanceof Error ? error.message : String(error),
145
+ });
146
+
147
+ // Clear parallel status on error
148
+ await statusWriter.update(totalCost, iterations, {
149
+ parallel: undefined,
150
+ });
151
+
152
+ throw error;
153
+ }
154
+
155
+ // Check if all stories are complete after parallel execution
156
+ if (isComplete(prd)) {
157
+ logger?.info("execution", "All stories complete!", {
158
+ feature,
159
+ totalCost,
160
+ });
161
+ await fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: totalCost }), workdir);
162
+
163
+ // Skip to metrics and cleanup
164
+ const durationMs = Date.now() - startTime;
165
+ const runCompletedAt = new Date().toISOString();
166
+
167
+ const { handleParallelCompletion } = await import("./lifecycle/parallel-lifecycle");
168
+ await handleParallelCompletion({
169
+ runId,
170
+ feature,
171
+ startedAt,
172
+ completedAt: runCompletedAt,
173
+ prd,
174
+ allStoryMetrics,
175
+ totalCost,
176
+ storiesCompleted,
177
+ durationMs,
178
+ workdir,
179
+ pluginRegistry,
180
+ });
181
+
182
+ const finalCounts = countStories(prd);
183
+ statusWriter.setPrd(prd);
184
+ statusWriter.setCurrentStory(null);
185
+ statusWriter.setRunStatus("completed");
186
+ await statusWriter.update(totalCost, iterations);
187
+
188
+ // ── Output run footer in headless mode (parallel path) ──────────────
189
+ if (headless && formatterMode !== "json") {
190
+ const { outputRunFooter } = await import("./lifecycle/headless-formatter");
191
+ outputRunFooter({
192
+ finalCounts: {
193
+ total: finalCounts.total,
194
+ passed: finalCounts.passed,
195
+ failed: finalCounts.failed,
196
+ skipped: finalCounts.skipped,
197
+ },
198
+ durationMs,
199
+ totalCost,
200
+ startedAt,
201
+ completedAt: runCompletedAt,
202
+ formatterMode,
203
+ });
204
+ }
205
+
206
+ return {
207
+ prd,
208
+ totalCost,
209
+ storiesCompleted,
210
+ completed: true,
211
+ durationMs,
212
+ };
213
+ }
214
+
215
+ return { prd, totalCost, storiesCompleted, completed: false };
216
+ }