@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,896 @@
1
+ /**
2
+ * End-to-End Integration Tests
3
+ *
4
+ * Tests the full nax workflow: plan → analyze → run
5
+ * Uses a MockAgentAdapter to avoid requiring real Claude Code installation
6
+ */
7
+
8
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { ALL_AGENTS } from "../../src/agents/registry";
12
+ import type {
13
+ AgentAdapter,
14
+ AgentCapabilities,
15
+ AgentResult,
16
+ AgentRunOptions,
17
+ DecomposeOptions,
18
+ DecomposeResult,
19
+ PlanOptions,
20
+ PlanResult,
21
+ } from "../../src/agents/types";
22
+ import { analyzeFeature } from "../../src/cli/analyze";
23
+ import { planCommand } from "../../src/cli/plan";
24
+ import { DEFAULT_CONFIG } from "../../src/config";
25
+ import type { NaxConfig } from "../../src/config";
26
+ import { run } from "../../src/execution/runner";
27
+ import { initLogger, resetLogger } from "../../src/logger";
28
+ import { loadPRD } from "../../src/prd";
29
+
30
+ /**
31
+ * Mock Agent Adapter for testing
32
+ *
33
+ * Implements the AgentAdapter interface but doesn't spawn real processes.
34
+ * Returns realistic, controllable results for testing scenarios.
35
+ */
36
+ class MockAgentAdapter implements AgentAdapter {
37
+ readonly name = "mock";
38
+ readonly displayName = "Mock Agent";
39
+ readonly binary = "mock-agent";
40
+
41
+ readonly capabilities: AgentCapabilities = {
42
+ supportedTiers: ["fast", "balanced", "powerful"],
43
+ maxContextTokens: 200_000,
44
+ features: new Set(["tdd", "review", "refactor", "batch"]),
45
+ };
46
+
47
+ // Control behavior via flags
48
+ public shouldFailRun = false;
49
+ public shouldRateLimit = false;
50
+ public shouldFailReview = false;
51
+ public callCount = 0;
52
+ public runCalls: AgentRunOptions[] = [];
53
+ public planCalls: PlanOptions[] = [];
54
+ public decomposeCalls: DecomposeOptions[] = [];
55
+
56
+ // Hard iteration cap to prevent infinite retry loops in tests
57
+ // Set to 5 to allow for story batching and escalation scenarios
58
+ public maxAttempts = 5;
59
+ private attemptCountMap = new Map<string, number>();
60
+
61
+ async isInstalled(): Promise<boolean> {
62
+ return true;
63
+ }
64
+
65
+ buildCommand(options: AgentRunOptions): string[] {
66
+ return [this.binary, "--prompt", options.prompt];
67
+ }
68
+
69
+ async run(_options: AgentRunOptions): Promise<AgentResult> {
70
+ this.callCount++;
71
+ this.runCalls.push(_options);
72
+
73
+ // Track attempts per unique prompt to prevent infinite loops
74
+ const promptKey = _options.prompt.slice(0, 100);
75
+ const currentAttempts = (this.attemptCountMap.get(promptKey) || 0) + 1;
76
+ this.attemptCountMap.set(promptKey, currentAttempts);
77
+
78
+ // Hard cap: fail after maxAttempts to prevent infinite retry loops
79
+ if (currentAttempts > this.maxAttempts) {
80
+ return {
81
+ success: false,
82
+ exitCode: 1,
83
+ output: `Mock agent: max attempts (${this.maxAttempts}) exceeded for this prompt`,
84
+ rateLimited: false,
85
+ durationMs: 100,
86
+ estimatedCost: 0.01,
87
+ };
88
+ }
89
+
90
+ // Simulate execution time
91
+ await Bun.sleep(10);
92
+
93
+ // Rate limit scenario
94
+ if (this.shouldRateLimit && this.callCount === 1) {
95
+ return {
96
+ success: false,
97
+ exitCode: 1,
98
+ output: "Rate limit exceeded. Too many requests.",
99
+ rateLimited: true,
100
+ durationMs: 100,
101
+ estimatedCost: 0.0,
102
+ };
103
+ }
104
+
105
+ // Failure scenario
106
+ if (this.shouldFailRun) {
107
+ return {
108
+ success: false,
109
+ exitCode: 1,
110
+ output: "Agent execution failed: mock error",
111
+ rateLimited: false,
112
+ durationMs: 500,
113
+ estimatedCost: 0.01,
114
+ };
115
+ }
116
+
117
+ // Success scenario
118
+ return {
119
+ success: true,
120
+ exitCode: 0,
121
+ output: `Mock agent completed task: ${_options.prompt.slice(0, 50)}...\n\nToken usage: 1500 input, 800 output`,
122
+ rateLimited: false,
123
+ durationMs: 2000,
124
+ estimatedCost: 0.015,
125
+ };
126
+ }
127
+
128
+ async plan(_options: PlanOptions): Promise<PlanResult> {
129
+ this.planCalls.push(_options);
130
+
131
+ // Simulate planning time
132
+ await Bun.sleep(10);
133
+
134
+ const specContent = `# Feature: URL Shortener
135
+
136
+ ## Problem
137
+ We need a URL shortening service to make long URLs more shareable.
138
+
139
+ ## Requirements
140
+ - REQ-1: Accept long URLs and generate short codes
141
+ - REQ-2: Redirect short codes to original URLs
142
+ - REQ-3: Track click analytics
143
+ - REQ-4: Support custom short codes (optional)
144
+
145
+ ## Acceptance Criteria
146
+ - AC-1: Short codes are unique and collision-free
147
+ - AC-2: Redirects work with 301 status
148
+ - AC-3: Click counts are tracked accurately
149
+ - AC-4: API returns JSON responses
150
+
151
+ ## Technical Notes
152
+ - Use base62 encoding for short codes
153
+ - Store mappings in database (consider Redis for caching)
154
+ - Log all redirects for analytics
155
+ - Validate URLs before shortening
156
+
157
+ ## Out of Scope
158
+ - User accounts and authentication (MVP only)
159
+ - Custom domains
160
+ - Link expiration
161
+ `;
162
+
163
+ return {
164
+ specContent,
165
+ conversationLog: "Mock planning session",
166
+ };
167
+ }
168
+
169
+ async decompose(_options: DecomposeOptions): Promise<DecomposeResult> {
170
+ this.decomposeCalls.push(_options);
171
+
172
+ // Simulate decompose time
173
+ await Bun.sleep(10);
174
+
175
+ // Parse the spec content to determine what stories to generate
176
+ // For URL shortener spec, return realistic stories
177
+ const stories = [
178
+ {
179
+ id: "US-001",
180
+ title: "Implement short code generation",
181
+ description: "Create algorithm to generate unique base62 short codes from URLs",
182
+ acceptanceCriteria: [
183
+ "Short codes are 6-8 characters",
184
+ "Codes use base62 charset (a-zA-Z0-9)",
185
+ "Collision detection works",
186
+ "Codes are URL-safe",
187
+ ],
188
+ tags: ["core", "algorithm"],
189
+ dependencies: [],
190
+ complexity: "medium" as const,
191
+ relevantFiles: ["src/shortener/generator.ts", "src/utils/base62.ts"],
192
+ reasoning: "Requires algorithmic implementation with collision handling. 2-3 files, ~150 LOC.",
193
+ estimatedLOC: 150,
194
+ risks: ["Collision probability under high load"],
195
+ },
196
+ {
197
+ id: "US-002",
198
+ title: "Add database storage for URL mappings",
199
+ description: "Store short code → long URL mappings in database with timestamps",
200
+ acceptanceCriteria: [
201
+ "Database schema defined",
202
+ "CRUD operations work",
203
+ "Queries are indexed",
204
+ "Timestamps recorded",
205
+ ],
206
+ tags: ["database", "storage"],
207
+ dependencies: [],
208
+ complexity: "medium" as const,
209
+ relevantFiles: ["src/db/schema.ts", "src/db/repository.ts"],
210
+ reasoning: "Standard CRUD with indexing. 2 files, ~120 LOC.",
211
+ estimatedLOC: 120,
212
+ risks: ["Database performance at scale"],
213
+ },
214
+ {
215
+ id: "US-003",
216
+ title: "Create redirect handler",
217
+ description: "Route that redirects /:code to the original URL with 301 status",
218
+ acceptanceCriteria: [
219
+ "GET /:code returns 301 redirect",
220
+ "404 for invalid codes",
221
+ "Click count incremented",
222
+ "Response headers correct",
223
+ ],
224
+ tags: ["api", "core"],
225
+ dependencies: ["US-001", "US-002"],
226
+ complexity: "simple" as const,
227
+ relevantFiles: ["src/api/redirect.ts"],
228
+ reasoning: "Simple handler with lookup and redirect. 1 file, ~50 LOC.",
229
+ estimatedLOC: 50,
230
+ risks: [],
231
+ },
232
+ {
233
+ id: "US-004",
234
+ title: "Build URL shortening handler",
235
+ description: "POST /api/shorten route that accepts URL and returns short code",
236
+ acceptanceCriteria: [
237
+ "POST /api/shorten accepts JSON",
238
+ "URL validation works",
239
+ "Returns short code in response",
240
+ "Error handling for invalid URLs",
241
+ ],
242
+ tags: ["api", "core"],
243
+ dependencies: ["US-001", "US-002"],
244
+ complexity: "simple" as const,
245
+ relevantFiles: ["src/api/shorten.ts"],
246
+ reasoning: "Standard POST handler. 1 file, ~60 LOC.",
247
+ estimatedLOC: 60,
248
+ risks: [],
249
+ },
250
+ {
251
+ id: "US-005",
252
+ title: "Implement click analytics tracking",
253
+ description: "Track clicks on each short URL with timestamps and IP addresses",
254
+ acceptanceCriteria: [
255
+ "Clicks logged with timestamp",
256
+ "IP address recorded (anonymized)",
257
+ "Analytics queryable by code",
258
+ "Performance doesn't block redirects",
259
+ ],
260
+ tags: ["analytics", "database"],
261
+ dependencies: ["US-003"],
262
+ complexity: "medium" as const,
263
+ relevantFiles: ["src/analytics/tracker.ts", "src/db/analytics-schema.ts"],
264
+ reasoning: "Async logging with privacy concerns. 2 files, ~100 LOC.",
265
+ estimatedLOC: 100,
266
+ risks: ["Privacy compliance (GDPR)", "Performance under high traffic"],
267
+ },
268
+ ];
269
+
270
+ return { stories };
271
+ }
272
+
273
+ reset() {
274
+ this.shouldFailRun = false;
275
+ this.shouldRateLimit = false;
276
+ this.shouldFailReview = false;
277
+ this.callCount = 0;
278
+ this.runCalls = [];
279
+ this.planCalls = [];
280
+ this.decomposeCalls = [];
281
+ this.attemptCountMap.clear();
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Register mock agent in the registry for testing
287
+ *
288
+ * Modifies the ALL_AGENTS array to include the mock agent
289
+ */
290
+ function registerMockAgent(adapter: MockAgentAdapter): () => void {
291
+ // Add mock agent to registry
292
+ ALL_AGENTS.push(adapter);
293
+
294
+ // Return cleanup function that removes it
295
+ return () => {
296
+ const index = ALL_AGENTS.findIndex((a) => a.name === "mock");
297
+ if (index >= 0) {
298
+ ALL_AGENTS.splice(index, 1);
299
+ }
300
+ };
301
+ }
302
+
303
+ describe("E2E: plan → analyze → run workflow", () => {
304
+ let testDir: string;
305
+ let mockAgent: MockAgentAdapter;
306
+ let cleanup: () => void;
307
+
308
+ beforeAll(() => {
309
+ // Create mock agent and register once for the whole suite
310
+ mockAgent = new MockAgentAdapter();
311
+ cleanup = registerMockAgent(mockAgent);
312
+ });
313
+
314
+ afterAll(() => {
315
+ // Unregister mock agent once after all tests complete
316
+ cleanup();
317
+ });
318
+
319
+ beforeEach(() => {
320
+ // Initialize logger
321
+ initLogger({ level: "error", useChalk: false });
322
+
323
+ // Reset mock agent state between tests
324
+ mockAgent.reset();
325
+
326
+ // Create temp directory
327
+ testDir = `/tmp/nax-e2e-test-${Date.now()}`;
328
+ mkdirSync(testDir, { recursive: true });
329
+
330
+ // Set up minimal project structure
331
+ setupTestProject(testDir);
332
+ });
333
+
334
+ afterEach(() => {
335
+ // Clean up temp directory
336
+ if (existsSync(testDir)) {
337
+ rmSync(testDir, { recursive: true, force: true });
338
+ }
339
+ // Reset logger
340
+ resetLogger();
341
+ });
342
+
343
+ test("full workflow: init → plan → analyze → run", { timeout: 120000 }, async () => {
344
+ const ngentDir = join(testDir, "nax");
345
+ const featureDir = join(ngentDir, "features/url-shortener");
346
+ mkdirSync(featureDir, { recursive: true });
347
+
348
+ // Step 1: Initialize (create config, constitution, hooks)
349
+ await initializeNgent(ngentDir);
350
+ expect(existsSync(join(ngentDir, "config.json"))).toBe(true);
351
+ expect(existsSync(join(ngentDir, "constitution.md"))).toBe(true);
352
+ expect(existsSync(join(ngentDir, "hooks.json"))).toBe(true);
353
+
354
+ // Step 2: Plan (manually create spec.md since mock agent doesn't spawn real process)
355
+ const config = createTestConfig();
356
+ const specPath = join(featureDir, "spec.md");
357
+ const spec = await mockAgent.plan({
358
+ prompt: "Build a URL shortener with analytics",
359
+ workdir: testDir,
360
+ interactive: false,
361
+ });
362
+ await Bun.write(specPath, spec.specContent);
363
+
364
+ expect(existsSync(specPath)).toBe(true);
365
+ const specContent = await Bun.file(specPath).text();
366
+ expect(specContent).toContain("# Feature: URL Shortener");
367
+ expect(specContent).toContain("## Requirements");
368
+ expect(mockAgent.planCalls).toHaveLength(1);
369
+
370
+ // Step 3: Analyze (decompose spec into prd.json)
371
+ const prd = await analyzeFeature({
372
+ featureDir,
373
+ featureName: "url-shortener",
374
+ branchName: "feat/url-shortener",
375
+ config,
376
+ });
377
+
378
+ expect(prd.userStories).toHaveLength(5);
379
+ expect(prd.userStories[0].id).toBe("US-001");
380
+ expect(prd.userStories[0].routing?.complexity).toBe("medium");
381
+ expect(prd.userStories[2].dependencies).toContain("US-001");
382
+ expect(mockAgent.decomposeCalls).toHaveLength(1);
383
+
384
+ // Save PRD
385
+ const prdPath = join(featureDir, "prd.json");
386
+ await Bun.write(prdPath, JSON.stringify(prd, null, 2));
387
+
388
+ // Step 4: Run (execute stories via pipeline)
389
+ const runResult = await run({
390
+ prdPath,
391
+ workdir: testDir,
392
+ config: {
393
+ ...config,
394
+ execution: {
395
+ ...config.execution,
396
+ maxIterations: 10, // Enough for 5 stories
397
+ },
398
+ },
399
+ hooks: { hooks: {} },
400
+ feature: "url-shortener",
401
+ featureDir,
402
+ dryRun: false,
403
+ useBatch: true, // Enable batching
404
+ skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
405
+ });
406
+
407
+ expect(runResult.success).toBe(true);
408
+ expect(runResult.storiesCompleted).toBe(5);
409
+ expect(mockAgent.runCalls.length).toBeGreaterThan(0);
410
+
411
+ // Verify PRD was updated
412
+ const finalPRD = await loadPRD(prdPath);
413
+ expect(finalPRD.userStories.every((s) => s.status === "passed")).toBe(true);
414
+ });
415
+
416
+ test("pipeline stages execute in correct order", { timeout: 15000 }, async () => {
417
+ const ngentDir = join(testDir, "nax");
418
+ const featureDir = join(ngentDir, "features/simple-task");
419
+ mkdirSync(featureDir, { recursive: true });
420
+
421
+ await initializeNgent(ngentDir);
422
+
423
+ // Create minimal PRD with one simple story
424
+ const prd = {
425
+ project: "test",
426
+ feature: "simple-task",
427
+ branchName: "feat/simple-task",
428
+ createdAt: new Date().toISOString(),
429
+ updatedAt: new Date().toISOString(),
430
+ userStories: [
431
+ {
432
+ id: "US-001",
433
+ title: "Add console log",
434
+ description: "Add a console.log statement to index.ts",
435
+ acceptanceCriteria: ["Log statement added"],
436
+ tags: [],
437
+ dependencies: [],
438
+ status: "pending" as const,
439
+ passes: false,
440
+ escalations: [],
441
+ attempts: 0,
442
+ routing: {
443
+ complexity: "simple" as const,
444
+ modelTier: "fast" as const,
445
+ testStrategy: "test-after" as const,
446
+ reasoning: "Trivial change",
447
+ estimatedLOC: 1,
448
+ risks: [],
449
+ },
450
+ },
451
+ ],
452
+ };
453
+
454
+ const prdPath = join(featureDir, "prd.json");
455
+ await Bun.write(prdPath, JSON.stringify(prd, null, 2));
456
+
457
+ const config = createTestConfig();
458
+
459
+ // Run execution
460
+ await run({
461
+ prdPath,
462
+ workdir: testDir,
463
+ config,
464
+ hooks: {
465
+ hooks: {
466
+ "on-story-start": {
467
+ command: "echo story-start",
468
+ enabled: true,
469
+ timeout: 60000,
470
+ },
471
+ "on-story-complete": {
472
+ command: "echo story-complete",
473
+ enabled: true,
474
+ timeout: 60000,
475
+ },
476
+ },
477
+ },
478
+ feature: "simple-task",
479
+ featureDir,
480
+ dryRun: false,
481
+ skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
482
+ });
483
+
484
+ // Verify agent was called (execution stage ran)
485
+ expect(mockAgent.runCalls.length).toBeGreaterThan(0);
486
+
487
+ // Verify story completed (completion stage ran)
488
+ const finalPRD = await loadPRD(prdPath);
489
+ expect(finalPRD.userStories[0].status).toBe("passed");
490
+ });
491
+
492
+ test("agent failure triggers escalation", { timeout: 60000 }, async () => {
493
+ const ngentDir = join(testDir, "nax");
494
+ const featureDir = join(ngentDir, "features/fail-task");
495
+ mkdirSync(featureDir, { recursive: true });
496
+
497
+ await initializeNgent(ngentDir);
498
+
499
+ const prd = {
500
+ project: "test",
501
+ feature: "fail-task",
502
+ branchName: "feat/fail-task",
503
+ createdAt: new Date().toISOString(),
504
+ updatedAt: new Date().toISOString(),
505
+ userStories: [
506
+ {
507
+ id: "US-001",
508
+ title: "Task that will fail",
509
+ description: "This task will fail on first attempt",
510
+ acceptanceCriteria: ["Task complete"],
511
+ tags: [],
512
+ dependencies: [],
513
+ status: "pending" as const,
514
+ passes: false,
515
+ escalations: [],
516
+ attempts: 0,
517
+ routing: {
518
+ complexity: "simple" as const,
519
+ modelTier: "fast" as const,
520
+ testStrategy: "test-after" as const,
521
+ reasoning: "Simple task",
522
+ estimatedLOC: 10,
523
+ risks: [],
524
+ },
525
+ },
526
+ ],
527
+ };
528
+
529
+ const prdPath = join(featureDir, "prd.json");
530
+ await Bun.write(prdPath, JSON.stringify(prd, null, 2));
531
+
532
+ // Make first call fail, subsequent calls succeed
533
+ let failCount = 0;
534
+ const originalRun = mockAgent.run.bind(mockAgent);
535
+ mockAgent.run = async (opts: AgentRunOptions): Promise<AgentResult> => {
536
+ failCount++;
537
+ if (failCount === 1) {
538
+ // First call fails
539
+ return {
540
+ success: false,
541
+ exitCode: 1,
542
+ output: "Tests failed",
543
+ rateLimited: false,
544
+ durationMs: 100,
545
+ estimatedCost: 0.01,
546
+ };
547
+ }
548
+ // Subsequent calls succeed
549
+ return originalRun(opts);
550
+ };
551
+
552
+ const config = createTestConfig();
553
+
554
+ await run({
555
+ prdPath,
556
+ workdir: testDir,
557
+ config,
558
+ hooks: { hooks: {} },
559
+ feature: "fail-task",
560
+ featureDir,
561
+ dryRun: false,
562
+ skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
563
+ });
564
+
565
+ // Verify story completed (escalation auto-handled by system)
566
+ const finalPRD = await loadPRD(prdPath);
567
+
568
+ // The story should complete after escalation kicks in
569
+ expect(finalPRD.userStories[0].status).toBe("passed");
570
+ });
571
+
572
+ test("rate limit triggers retry with backoff", { timeout: 60000 }, async () => {
573
+ const ngentDir = join(testDir, "nax");
574
+ const featureDir = join(ngentDir, "features/rate-limit-task");
575
+ mkdirSync(featureDir, { recursive: true });
576
+
577
+ await initializeNgent(ngentDir);
578
+
579
+ const prd = {
580
+ project: "test",
581
+ feature: "rate-limit-task",
582
+ branchName: "feat/rate-limit",
583
+ createdAt: new Date().toISOString(),
584
+ updatedAt: new Date().toISOString(),
585
+ userStories: [
586
+ {
587
+ id: "US-001",
588
+ title: "Task with rate limit",
589
+ description: "This task will hit rate limit once",
590
+ acceptanceCriteria: ["Task complete"],
591
+ tags: [],
592
+ dependencies: [],
593
+ status: "pending" as const,
594
+ passes: false,
595
+ escalations: [],
596
+ attempts: 0,
597
+ routing: {
598
+ complexity: "simple" as const,
599
+ modelTier: "fast" as const,
600
+ testStrategy: "test-after" as const,
601
+ reasoning: "Simple task",
602
+ estimatedLOC: 10,
603
+ risks: [],
604
+ },
605
+ },
606
+ ],
607
+ };
608
+
609
+ const prdPath = join(featureDir, "prd.json");
610
+ await Bun.write(prdPath, JSON.stringify(prd, null, 2));
611
+
612
+ // Set rate limit on first call
613
+ mockAgent.shouldRateLimit = true;
614
+
615
+ const config = createTestConfig();
616
+
617
+ const runResult = await run({
618
+ prdPath,
619
+ workdir: testDir,
620
+ config,
621
+ hooks: { hooks: {} },
622
+ feature: "rate-limit-task",
623
+ featureDir,
624
+ dryRun: false,
625
+ skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
626
+ });
627
+
628
+ expect(runResult.success).toBe(true);
629
+
630
+ const finalPRD = await loadPRD(prdPath);
631
+ expect(finalPRD.userStories[0].status).toBe("passed");
632
+ });
633
+
634
+ test.skip("review phase failure marks story as failed (skipped - review disabled in tests)", async () => {
635
+ // This test is skipped because review is disabled in test config to avoid mocking
636
+ // typecheck/lint/test commands. In a real scenario with review enabled, this would
637
+ // test that review failures are properly handled.
638
+ });
639
+
640
+ test("story batching groups simple stories", { timeout: 15000 }, async () => {
641
+ const ngentDir = join(testDir, "nax");
642
+ const featureDir = join(ngentDir, "features/batch-test");
643
+ mkdirSync(featureDir, { recursive: true });
644
+
645
+ await initializeNgent(ngentDir);
646
+
647
+ const prd = {
648
+ project: "test",
649
+ feature: "batch-test",
650
+ branchName: "feat/batch",
651
+ createdAt: new Date().toISOString(),
652
+ updatedAt: new Date().toISOString(),
653
+ userStories: [
654
+ {
655
+ id: "US-001",
656
+ title: "Add log statement 1",
657
+ description: "Add console.log to file1.ts",
658
+ acceptanceCriteria: ["Log added"],
659
+ tags: [],
660
+ dependencies: [],
661
+ status: "pending" as const,
662
+ passes: false,
663
+ escalations: [],
664
+ attempts: 0,
665
+ routing: {
666
+ complexity: "simple" as const,
667
+ modelTier: "fast" as const,
668
+ testStrategy: "test-after" as const,
669
+ reasoning: "Trivial",
670
+ estimatedLOC: 1,
671
+ risks: [],
672
+ },
673
+ },
674
+ {
675
+ id: "US-002",
676
+ title: "Add log statement 2",
677
+ description: "Add console.log to file2.ts",
678
+ acceptanceCriteria: ["Log added"],
679
+ tags: [],
680
+ dependencies: [],
681
+ status: "pending" as const,
682
+ passes: false,
683
+ escalations: [],
684
+ attempts: 0,
685
+ routing: {
686
+ complexity: "simple" as const,
687
+ modelTier: "fast" as const,
688
+ testStrategy: "test-after" as const,
689
+ reasoning: "Trivial",
690
+ estimatedLOC: 1,
691
+ risks: [],
692
+ },
693
+ },
694
+ {
695
+ id: "US-003",
696
+ title: "Add log statement 3",
697
+ description: "Add console.log to file3.ts",
698
+ acceptanceCriteria: ["Log added"],
699
+ tags: [],
700
+ dependencies: [],
701
+ status: "pending" as const,
702
+ passes: false,
703
+ escalations: [],
704
+ attempts: 0,
705
+ routing: {
706
+ complexity: "simple" as const,
707
+ modelTier: "fast" as const,
708
+ testStrategy: "test-after" as const,
709
+ reasoning: "Trivial",
710
+ estimatedLOC: 1,
711
+ risks: [],
712
+ },
713
+ },
714
+ ],
715
+ };
716
+
717
+ const prdPath = join(featureDir, "prd.json");
718
+ await Bun.write(prdPath, JSON.stringify(prd, null, 2));
719
+
720
+ const config = createTestConfig();
721
+
722
+ const runResult = await run({
723
+ prdPath,
724
+ workdir: testDir,
725
+ config,
726
+ hooks: { hooks: {} },
727
+ feature: "batch-test",
728
+ featureDir,
729
+ dryRun: false,
730
+ useBatch: true,
731
+ skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
732
+ });
733
+
734
+ expect(runResult.success).toBe(true);
735
+ expect(runResult.storiesCompleted).toBe(3);
736
+
737
+ // With batching, should have fewer agent calls than stories
738
+ // (3 simple stories should be batched into 1 call)
739
+ expect(mockAgent.runCalls.length).toBeLessThan(3);
740
+
741
+ // Verify all stories completed
742
+ const finalPRD = await loadPRD(prdPath);
743
+ expect(finalPRD.userStories.every((s) => s.status === "passed")).toBe(true);
744
+ });
745
+ });
746
+
747
+ // ── Helper Functions ──────────────────────────────────
748
+
749
+ function setupTestProject(dir: string) {
750
+ // Create src/ directory
751
+ mkdirSync(join(dir, "src"), { recursive: true });
752
+ Bun.write(join(dir, "src/index.ts"), "export const greet = () => 'Hello';\n");
753
+
754
+ // Create test/ directory
755
+ mkdirSync(join(dir, "test"), { recursive: true });
756
+ Bun.write(
757
+ join(dir, "test/index.test.ts"),
758
+ "import { expect, test } from 'bun:test';\nimport { greet } from '../../src/index';\n\ntest('greet', () => {\n expect(greet()).toBe('Hello');\n});\n",
759
+ );
760
+
761
+ // Create package.json
762
+ Bun.write(
763
+ join(dir, "package.json"),
764
+ JSON.stringify(
765
+ {
766
+ name: "e2e-test-project",
767
+ version: "1.0.0",
768
+ dependencies: {
769
+ zod: "^4.0.0",
770
+ },
771
+ devDependencies: {
772
+ typescript: "^5.0.0",
773
+ "@types/bun": "^1.0.0",
774
+ },
775
+ },
776
+ null,
777
+ 2,
778
+ ),
779
+ );
780
+
781
+ // Create tsconfig.json
782
+ Bun.write(
783
+ join(dir, "tsconfig.json"),
784
+ JSON.stringify(
785
+ {
786
+ compilerOptions: {
787
+ target: "ES2022",
788
+ module: "ESNext",
789
+ moduleResolution: "bundler",
790
+ strict: true,
791
+ esModuleInterop: true,
792
+ skipLibCheck: true,
793
+ forceConsistentCasingInFileNames: true,
794
+ outDir: "./dist",
795
+ },
796
+ include: ["src/**/*"],
797
+ exclude: ["node_modules", "dist"],
798
+ },
799
+ null,
800
+ 2,
801
+ ),
802
+ );
803
+ }
804
+
805
+ async function initializeNgent(ngentDir: string) {
806
+ // Create directory structure
807
+ mkdirSync(join(ngentDir, "features"), { recursive: true });
808
+ mkdirSync(join(ngentDir, "hooks"), { recursive: true });
809
+
810
+ // Write config.json
811
+ await Bun.write(join(ngentDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
812
+
813
+ // Write hooks.json
814
+ await Bun.write(
815
+ join(ngentDir, "hooks.json"),
816
+ JSON.stringify(
817
+ {
818
+ hooks: {
819
+ "on-start": { command: "echo nax started", enabled: false },
820
+ "on-complete": { command: "echo nax complete", enabled: false },
821
+ },
822
+ },
823
+ null,
824
+ 2,
825
+ ),
826
+ );
827
+
828
+ // Write constitution.md
829
+ await Bun.write(
830
+ join(ngentDir, "constitution.md"),
831
+ `# Project Constitution
832
+
833
+ ## Coding Standards
834
+ - Write clear, maintainable code
835
+ - Follow project conventions
836
+
837
+ ## Testing Requirements
838
+ - All code must have tests
839
+ - Aim for 80%+ coverage
840
+
841
+ ## Architecture Rules
842
+ - Keep functions small and focused
843
+ - Avoid tight coupling
844
+ `,
845
+ );
846
+ }
847
+
848
+ function createTestConfig(): NaxConfig {
849
+ return {
850
+ ...DEFAULT_CONFIG,
851
+ autoMode: {
852
+ ...DEFAULT_CONFIG.autoMode,
853
+ defaultAgent: "mock", // Use our mock agent
854
+ escalation: {
855
+ ...DEFAULT_CONFIG.autoMode.escalation,
856
+ enabled: true,
857
+ tierOrder: [
858
+ { tier: "fast", attempts: 1 },
859
+ { tier: "balanced", attempts: 1 },
860
+ ],
861
+ },
862
+ },
863
+ analyze: {
864
+ ...DEFAULT_CONFIG.analyze,
865
+ llmEnhanced: true, // Enable LLM decompose
866
+ },
867
+ execution: {
868
+ ...DEFAULT_CONFIG.execution,
869
+ maxIterations: 15, // Reduced from 20 to fail faster in tests
870
+ maxStoriesPerFeature: 500,
871
+ regressionGate: {
872
+ ...DEFAULT_CONFIG.execution.regressionGate,
873
+ enabled: false, // Disable regression gate for E2E tests
874
+ },
875
+ rectification: {
876
+ ...DEFAULT_CONFIG.execution.rectification,
877
+ enabled: false, // Disable rectification for E2E tests
878
+ },
879
+ },
880
+ quality: {
881
+ ...DEFAULT_CONFIG.quality,
882
+ requireTypecheck: false,
883
+ requireLint: false,
884
+ requireTests: false,
885
+ commands: {}, // No quality commands for E2E tests
886
+ },
887
+ review: {
888
+ ...DEFAULT_CONFIG.review,
889
+ enabled: false, // Disable review for tests (would require mocking typecheck/lint/test)
890
+ },
891
+ acceptance: {
892
+ ...DEFAULT_CONFIG.acceptance,
893
+ enabled: false, // Disable acceptance for E2E tests (no real acceptance tests)
894
+ },
895
+ };
896
+ }