@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,860 @@
1
+ /**
2
+ * Test reporter lifecycle events
3
+ *
4
+ * Verifies that reporter plugins receive lifecycle events at the appropriate
5
+ * points in the runner loop (US-004).
6
+ */
7
+
8
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import * as fs from "node:fs/promises";
10
+ import * as os from "node:os";
11
+ import * as path from "node:path";
12
+ import { ALL_AGENTS } from "../../src/agents/registry";
13
+ import type {
14
+ AgentAdapter,
15
+ AgentCapabilities,
16
+ AgentResult,
17
+ AgentRunOptions,
18
+ DecomposeOptions,
19
+ DecomposeResult,
20
+ PlanOptions,
21
+ PlanResult,
22
+ } from "../../src/agents/types";
23
+ import type { NaxConfig } from "../../src/config";
24
+ import { run } from "../../src/execution/runner";
25
+ import { loadHooksConfig } from "../../src/hooks";
26
+ import type { IReporter, NaxPlugin, RunEndEvent, RunStartEvent, StoryCompleteEvent } from "../../src/plugins/types";
27
+ import { loadPRD, savePRD } from "../../src/prd";
28
+
29
+ // ============================================================================
30
+ // Mock agent (satisfies agent installation check in runner)
31
+ // ============================================================================
32
+ class MockAgentAdapter implements AgentAdapter {
33
+ readonly name = "mock";
34
+ readonly displayName = "Mock Agent";
35
+ readonly binary = "mock-agent";
36
+ readonly capabilities: AgentCapabilities = {
37
+ supportedTiers: ["fast", "balanced", "powerful"],
38
+ maxContextTokens: 200_000,
39
+ features: new Set(["tdd", "review", "refactor", "batch"]),
40
+ };
41
+ async isInstalled(): Promise<boolean> {
42
+ return true;
43
+ }
44
+ buildCommand(_o: AgentRunOptions): string[] {
45
+ return [this.binary];
46
+ }
47
+ async run(_o: AgentRunOptions): Promise<AgentResult> {
48
+ return { success: true, exitCode: 0, output: "", durationMs: 10, estimatedCost: 0 };
49
+ }
50
+ async plan(_o: PlanOptions): Promise<PlanResult> {
51
+ return { specContent: "# Feature\n", success: true };
52
+ }
53
+ async decompose(_o: DecomposeOptions): Promise<DecomposeResult> {
54
+ return { stories: [], success: true };
55
+ }
56
+ }
57
+
58
+ describe("Reporter Lifecycle Events (US-004)", () => {
59
+ let tmpDir: string;
60
+ let workdir: string;
61
+ let prdPath: string;
62
+ let pluginDir: string;
63
+ let config: NaxConfig;
64
+
65
+ // Track reporter calls
66
+ let onRunStartCalls: RunStartEvent[] = [];
67
+ let onStoryCompleteCalls: StoryCompleteEvent[] = [];
68
+ let onRunEndCalls: RunEndEvent[] = [];
69
+
70
+ beforeAll(() => {
71
+ // Register mock agent
72
+ ALL_AGENTS.push(new MockAgentAdapter());
73
+ });
74
+
75
+ afterAll(() => {
76
+ // Cleanup mock agent
77
+ const mockIndex = ALL_AGENTS.findIndex((a) => a.name === "mock");
78
+ if (mockIndex !== -1) {
79
+ ALL_AGENTS.splice(mockIndex, 1);
80
+ }
81
+ });
82
+
83
+ beforeEach(async () => {
84
+ // Create temp directory
85
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-reporter-test-"));
86
+ workdir = tmpDir;
87
+ prdPath = path.join(workdir, "nax", "features", "test-feature", "prd.json");
88
+ pluginDir = path.join(workdir, "nax", "plugins");
89
+
90
+ // Ensure directories exist
91
+ await fs.mkdir(path.dirname(prdPath), { recursive: true });
92
+ await fs.mkdir(pluginDir, { recursive: true });
93
+
94
+ // Initialize git repo
95
+ await Bun.spawn(["git", "init"], { cwd: workdir, stdout: "ignore" }).exited;
96
+ await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: workdir, stdout: "ignore" }).exited;
97
+ await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: workdir, stdout: "ignore" }).exited;
98
+ await fs.writeFile(path.join(workdir, "README.md"), "# Test");
99
+ await Bun.spawn(["git", "add", "."], { cwd: workdir, stdout: "ignore" }).exited;
100
+ await Bun.spawn(["git", "commit", "-m", "Initial commit"], { cwd: workdir, stdout: "ignore" }).exited;
101
+
102
+ // Reset tracking arrays
103
+ onRunStartCalls = [];
104
+ onStoryCompleteCalls = [];
105
+ onRunEndCalls = [];
106
+
107
+ // Create mock reporter plugin
108
+ const mockReporter: IReporter = {
109
+ name: "test-reporter",
110
+ async onRunStart(event: RunStartEvent) {
111
+ onRunStartCalls.push(event);
112
+ },
113
+ async onStoryComplete(event: StoryCompleteEvent) {
114
+ onStoryCompleteCalls.push(event);
115
+ },
116
+ async onRunEnd(event: RunEndEvent) {
117
+ onRunEndCalls.push(event);
118
+ },
119
+ };
120
+
121
+ const plugin: NaxPlugin = {
122
+ name: "test-reporter-plugin",
123
+ version: "1.0.0",
124
+ provides: ["reporter"],
125
+ extensions: {
126
+ reporter: mockReporter,
127
+ },
128
+ };
129
+
130
+ // Write plugin to disk
131
+ const pluginCode = `
132
+ const mockReporter = {
133
+ name: "test-reporter",
134
+ async onRunStart(event) {
135
+ const fs = require("node:fs/promises");
136
+ const path = require("node:path");
137
+ const file = path.join("${tmpDir}", "run-start.json");
138
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
139
+ },
140
+ async onStoryComplete(event) {
141
+ const fs = require("node:fs/promises");
142
+ const path = require("node:path");
143
+ const file = path.join("${tmpDir}", "story-complete-" + event.storyId + ".json");
144
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
145
+ },
146
+ async onRunEnd(event) {
147
+ const fs = require("node:fs/promises");
148
+ const path = require("node:path");
149
+ const file = path.join("${tmpDir}", "run-end.json");
150
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
151
+ },
152
+ };
153
+
154
+ export default {
155
+ name: "test-reporter-plugin",
156
+ version: "1.0.0",
157
+ provides: ["reporter"],
158
+ extensions: {
159
+ reporter: mockReporter,
160
+ },
161
+ };
162
+ `;
163
+
164
+ await fs.writeFile(path.join(pluginDir, "test-reporter.ts"), pluginCode);
165
+
166
+ // Create minimal config
167
+ config = {
168
+ agents: {
169
+ mock: { enabled: true },
170
+ },
171
+ routing: {
172
+ strategy: "complexity",
173
+ defaultTier: "fast",
174
+ defaultTestStrategy: "unit",
175
+ },
176
+ autoMode: {
177
+ defaultAgent: "mock",
178
+ complexityRouting: {
179
+ simple: "fast",
180
+ moderate: "balanced",
181
+ complex: "advanced",
182
+ },
183
+ escalation: {
184
+ enabled: false,
185
+ tierOrder: [],
186
+ },
187
+ },
188
+ execution: {
189
+ maxIterations: 20,
190
+ timeout: 1800000,
191
+ costLimit: 100,
192
+ iterationDelayMs: 0,
193
+ maxStoriesPerFeature: 100,
194
+ },
195
+ analyze: {
196
+ model: "balanced",
197
+ },
198
+ models: {
199
+ fast: {
200
+ model: "claude-3-5-haiku-20241022",
201
+ apiKeyEnvVar: "ANTHROPIC_API_KEY",
202
+ },
203
+ balanced: {
204
+ model: "claude-3-5-sonnet-20241022",
205
+ apiKeyEnvVar: "ANTHROPIC_API_KEY",
206
+ },
207
+ advanced: {
208
+ model: "claude-3-opus-20240229",
209
+ apiKeyEnvVar: "ANTHROPIC_API_KEY",
210
+ },
211
+ },
212
+ quality: {
213
+ commands: {},
214
+ },
215
+ acceptance: {
216
+ enabled: false,
217
+ maxRetries: 3,
218
+ },
219
+ plugins: [
220
+ {
221
+ module: path.join(pluginDir, "test-reporter.ts"),
222
+ },
223
+ ],
224
+ } as NaxConfig;
225
+ });
226
+
227
+ afterEach(async () => {
228
+ // Cleanup
229
+ try {
230
+ await fs.rm(tmpDir, { recursive: true, force: true });
231
+ } catch (error) {
232
+ // Ignore cleanup errors
233
+ }
234
+ });
235
+
236
+ test("AC1: onRunStart fires once at run start with runId, feature, totalStories, startTime", async () => {
237
+ // Create minimal PRD with 2 stories
238
+ const prd = {
239
+ feature: "test-feature",
240
+ userStories: [
241
+ {
242
+ id: "US-001",
243
+ title: "Story 1",
244
+ description: "Test story 1",
245
+ acceptanceCriteria: ["AC1: Should work"],
246
+ status: "pending" as const,
247
+ dependencies: [],
248
+ tags: [],
249
+ },
250
+ {
251
+ id: "US-002",
252
+ title: "Story 2",
253
+ description: "Test story 2",
254
+ acceptanceCriteria: ["AC1: Should work"],
255
+ status: "pending" as const,
256
+ dependencies: [],
257
+ tags: [],
258
+ },
259
+ ],
260
+ };
261
+
262
+ await savePRD(prd, prdPath);
263
+
264
+ const hooks = await loadHooksConfig(workdir);
265
+
266
+ // Run in dry-run mode
267
+ await run({
268
+ prdPath,
269
+ workdir,
270
+ config,
271
+ hooks,
272
+ feature: "test-feature",
273
+ dryRun: true,
274
+ useBatch: false,
275
+ skipPrecheck: true,
276
+ });
277
+
278
+ // Verify onRunStart was called
279
+ const runStartFile = path.join(tmpDir, "run-start.json");
280
+ const runStartExists = await Bun.file(runStartFile).exists();
281
+ expect(runStartExists).toBe(true);
282
+
283
+ const runStartData = JSON.parse(await Bun.file(runStartFile).text());
284
+
285
+ // Verify event structure
286
+ expect(runStartData).toHaveProperty("runId");
287
+ expect(runStartData.runId).toContain("run-");
288
+ expect(runStartData.feature).toBe("test-feature");
289
+ expect(runStartData.totalStories).toBe(2);
290
+ expect(runStartData).toHaveProperty("startTime");
291
+ expect(new Date(runStartData.startTime).toString()).not.toBe("Invalid Date");
292
+ });
293
+
294
+ test("AC2: onStoryComplete fires after each story with storyId, status, durationMs, cost, tier, testStrategy", async () => {
295
+ // Create minimal PRD with 1 story
296
+ const prd = {
297
+ feature: "test-feature",
298
+ userStories: [
299
+ {
300
+ id: "US-001",
301
+ title: "Story 1",
302
+ description: "Test story 1",
303
+ acceptanceCriteria: ["AC1: Should work"],
304
+ status: "pending" as const,
305
+ dependencies: [],
306
+ tags: [],
307
+ },
308
+ ],
309
+ };
310
+
311
+ await savePRD(prd, prdPath);
312
+
313
+ const hooks = await loadHooksConfig(workdir);
314
+
315
+ // Run in dry-run mode
316
+ await run({
317
+ prdPath,
318
+ workdir,
319
+ config,
320
+ hooks,
321
+ feature: "test-feature",
322
+ dryRun: true,
323
+ useBatch: false,
324
+ skipPrecheck: true,
325
+ });
326
+
327
+ // Verify onStoryComplete was called
328
+ const storyCompleteFile = path.join(tmpDir, "story-complete-US-001.json");
329
+ const storyCompleteExists = await Bun.file(storyCompleteFile).exists();
330
+ expect(storyCompleteExists).toBe(true);
331
+
332
+ const storyCompleteData = JSON.parse(await Bun.file(storyCompleteFile).text());
333
+
334
+ // Verify event structure
335
+ expect(storyCompleteData).toHaveProperty("runId");
336
+ expect(storyCompleteData.storyId).toBe("US-001");
337
+ expect(storyCompleteData.status).toBe("completed");
338
+ expect(typeof storyCompleteData.durationMs).toBe("number");
339
+ expect(storyCompleteData.durationMs).toBeGreaterThanOrEqual(0);
340
+ expect(typeof storyCompleteData.cost).toBe("number");
341
+ expect(storyCompleteData).toHaveProperty("tier");
342
+ expect(storyCompleteData).toHaveProperty("testStrategy");
343
+ });
344
+
345
+ test("AC3: onRunEnd fires once at run end with runId, totalDurationMs, totalCost, storySummary counts", async () => {
346
+ // Create minimal PRD with 2 stories
347
+ const prd = {
348
+ feature: "test-feature",
349
+ userStories: [
350
+ {
351
+ id: "US-001",
352
+ title: "Story 1",
353
+ description: "Test story 1",
354
+ acceptanceCriteria: ["AC1: Should work"],
355
+ status: "pending" as const,
356
+ dependencies: [],
357
+ tags: [],
358
+ },
359
+ {
360
+ id: "US-002",
361
+ title: "Story 2",
362
+ description: "Test story 2",
363
+ acceptanceCriteria: ["AC1: Should work"],
364
+ status: "pending" as const,
365
+ dependencies: [],
366
+ tags: [],
367
+ },
368
+ ],
369
+ };
370
+
371
+ await savePRD(prd, prdPath);
372
+
373
+ const hooks = await loadHooksConfig(workdir);
374
+
375
+ // Run in dry-run mode
376
+ await run({
377
+ prdPath,
378
+ workdir,
379
+ config,
380
+ hooks,
381
+ feature: "test-feature",
382
+ dryRun: true,
383
+ useBatch: false,
384
+ skipPrecheck: true,
385
+ });
386
+
387
+ // Verify onRunEnd was called
388
+ const runEndFile = path.join(tmpDir, "run-end.json");
389
+ const runEndExists = await Bun.file(runEndFile).exists();
390
+ expect(runEndExists).toBe(true);
391
+
392
+ const runEndData = JSON.parse(await Bun.file(runEndFile).text());
393
+
394
+ // Verify event structure
395
+ expect(runEndData).toHaveProperty("runId");
396
+ expect(typeof runEndData.totalDurationMs).toBe("number");
397
+ expect(runEndData.totalDurationMs).toBeGreaterThanOrEqual(0);
398
+ expect(typeof runEndData.totalCost).toBe("number");
399
+ expect(runEndData).toHaveProperty("storySummary");
400
+ expect(runEndData.storySummary).toHaveProperty("completed");
401
+ expect(runEndData.storySummary).toHaveProperty("failed");
402
+ expect(runEndData.storySummary).toHaveProperty("skipped");
403
+ expect(runEndData.storySummary).toHaveProperty("paused");
404
+ expect(runEndData.storySummary.completed).toBe(2); // Both stories completed in dry-run
405
+ });
406
+
407
+ test("AC4: Reporter errors are caught and logged but never block execution", async () => {
408
+ // Create a failing reporter
409
+ const failingPluginCode = `
410
+ const failingReporter = {
411
+ name: "failing-reporter",
412
+ async onRunStart(event) {
413
+ throw new Error("onRunStart intentional failure");
414
+ },
415
+ async onStoryComplete(event) {
416
+ throw new Error("onStoryComplete intentional failure");
417
+ },
418
+ async onRunEnd(event) {
419
+ throw new Error("onRunEnd intentional failure");
420
+ },
421
+ };
422
+
423
+ export default {
424
+ name: "failing-reporter-plugin",
425
+ version: "1.0.0",
426
+ provides: ["reporter"],
427
+ extensions: {
428
+ reporter: failingReporter,
429
+ },
430
+ };
431
+ `;
432
+
433
+ await fs.writeFile(path.join(pluginDir, "failing-reporter.ts"), failingPluginCode);
434
+
435
+ // Update config to use failing reporter
436
+ config.plugins = [
437
+ {
438
+ module: path.join(pluginDir, "failing-reporter.ts"),
439
+ },
440
+ ];
441
+
442
+ // Create minimal PRD
443
+ const prd = {
444
+ feature: "test-feature",
445
+ userStories: [
446
+ {
447
+ id: "US-001",
448
+ title: "Story 1",
449
+ description: "Test story 1",
450
+ acceptanceCriteria: ["AC1: Should work"],
451
+ status: "pending" as const,
452
+ dependencies: [],
453
+ tags: [],
454
+ },
455
+ ],
456
+ };
457
+
458
+ await savePRD(prd, prdPath);
459
+
460
+ const hooks = await loadHooksConfig(workdir);
461
+
462
+ // Run should not throw even though reporter fails
463
+ const result = await run({
464
+ prdPath,
465
+ workdir,
466
+ config,
467
+ hooks,
468
+ feature: "test-feature",
469
+ dryRun: true,
470
+ useBatch: false,
471
+ skipPrecheck: true,
472
+ });
473
+
474
+ // Verify run completed successfully despite reporter errors
475
+ expect(result.success).toBe(true);
476
+ expect(result.storiesCompleted).toBe(1);
477
+ });
478
+
479
+ test("AC5: Multiple reporters all receive events (not short-circuited on error)", async () => {
480
+ // Create two reporters
481
+ const reporter1Code = `
482
+ const reporter1 = {
483
+ name: "reporter-1",
484
+ async onRunStart(event) {
485
+ const fs = require("node:fs/promises");
486
+ const path = require("node:path");
487
+ const file = path.join("${tmpDir}", "reporter-1-run-start.json");
488
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
489
+ },
490
+ async onStoryComplete(event) {
491
+ const fs = require("node:fs/promises");
492
+ const path = require("node:path");
493
+ const file = path.join("${tmpDir}", "reporter-1-story-" + event.storyId + ".json");
494
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
495
+ },
496
+ async onRunEnd(event) {
497
+ const fs = require("node:fs/promises");
498
+ const path = require("node:path");
499
+ const file = path.join("${tmpDir}", "reporter-1-run-end.json");
500
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
501
+ },
502
+ };
503
+
504
+ export default {
505
+ name: "reporter-1-plugin",
506
+ version: "1.0.0",
507
+ provides: ["reporter"],
508
+ extensions: {
509
+ reporter: reporter1,
510
+ },
511
+ };
512
+ `;
513
+
514
+ const reporter2Code = `
515
+ const reporter2 = {
516
+ name: "reporter-2",
517
+ async onRunStart(event) {
518
+ const fs = require("node:fs/promises");
519
+ const path = require("node:path");
520
+ const file = path.join("${tmpDir}", "reporter-2-run-start.json");
521
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
522
+ },
523
+ async onStoryComplete(event) {
524
+ const fs = require("node:fs/promises");
525
+ const path = require("node:path");
526
+ const file = path.join("${tmpDir}", "reporter-2-story-" + event.storyId + ".json");
527
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
528
+ },
529
+ async onRunEnd(event) {
530
+ const fs = require("node:fs/promises");
531
+ const path = require("node:path");
532
+ const file = path.join("${tmpDir}", "reporter-2-run-end.json");
533
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
534
+ },
535
+ };
536
+
537
+ export default {
538
+ name: "reporter-2-plugin",
539
+ version: "1.0.0",
540
+ provides: ["reporter"],
541
+ extensions: {
542
+ reporter: reporter2,
543
+ },
544
+ };
545
+ `;
546
+
547
+ await fs.writeFile(path.join(pluginDir, "reporter-1.ts"), reporter1Code);
548
+ await fs.writeFile(path.join(pluginDir, "reporter-2.ts"), reporter2Code);
549
+
550
+ // Update config to use both reporters
551
+ config.plugins = [
552
+ {
553
+ module: path.join(pluginDir, "reporter-1.ts"),
554
+ },
555
+ {
556
+ module: path.join(pluginDir, "reporter-2.ts"),
557
+ },
558
+ ];
559
+
560
+ // Create minimal PRD
561
+ const prd = {
562
+ feature: "test-feature",
563
+ userStories: [
564
+ {
565
+ id: "US-001",
566
+ title: "Story 1",
567
+ description: "Test story 1",
568
+ acceptanceCriteria: ["AC1: Should work"],
569
+ status: "pending" as const,
570
+ dependencies: [],
571
+ tags: [],
572
+ },
573
+ ],
574
+ };
575
+
576
+ await savePRD(prd, prdPath);
577
+
578
+ const hooks = await loadHooksConfig(workdir);
579
+
580
+ await run({
581
+ prdPath,
582
+ workdir,
583
+ config,
584
+ hooks,
585
+ feature: "test-feature",
586
+ dryRun: true,
587
+ useBatch: false,
588
+ skipPrecheck: true,
589
+ });
590
+
591
+ // Verify both reporters received events
592
+ const reporter1RunStart = path.join(tmpDir, "reporter-1-run-start.json");
593
+ const reporter2RunStart = path.join(tmpDir, "reporter-2-run-start.json");
594
+ const reporter1Story = path.join(tmpDir, "reporter-1-story-US-001.json");
595
+ const reporter2Story = path.join(tmpDir, "reporter-2-story-US-001.json");
596
+ const reporter1RunEnd = path.join(tmpDir, "reporter-1-run-end.json");
597
+ const reporter2RunEnd = path.join(tmpDir, "reporter-2-run-end.json");
598
+
599
+ expect(await Bun.file(reporter1RunStart).exists()).toBe(true);
600
+ expect(await Bun.file(reporter2RunStart).exists()).toBe(true);
601
+ expect(await Bun.file(reporter1Story).exists()).toBe(true);
602
+ expect(await Bun.file(reporter2Story).exists()).toBe(true);
603
+ expect(await Bun.file(reporter1RunEnd).exists()).toBe(true);
604
+ expect(await Bun.file(reporter2RunEnd).exists()).toBe(true);
605
+ });
606
+
607
+ test("AC5 (edge case): Second reporter receives events even if first reporter fails", async () => {
608
+ // Create a failing reporter and a working reporter
609
+ const failingReporterCode = `
610
+ const failingReporter = {
611
+ name: "failing-reporter",
612
+ async onRunStart(event) {
613
+ throw new Error("onRunStart failure");
614
+ },
615
+ async onStoryComplete(event) {
616
+ throw new Error("onStoryComplete failure");
617
+ },
618
+ async onRunEnd(event) {
619
+ throw new Error("onRunEnd failure");
620
+ },
621
+ };
622
+
623
+ export default {
624
+ name: "failing-reporter-plugin",
625
+ version: "1.0.0",
626
+ provides: ["reporter"],
627
+ extensions: {
628
+ reporter: failingReporter,
629
+ },
630
+ };
631
+ `;
632
+
633
+ const workingReporterCode = `
634
+ const workingReporter = {
635
+ name: "working-reporter",
636
+ async onRunStart(event) {
637
+ const fs = require("node:fs/promises");
638
+ const path = require("node:path");
639
+ const file = path.join("${tmpDir}", "working-run-start.json");
640
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
641
+ },
642
+ async onStoryComplete(event) {
643
+ const fs = require("node:fs/promises");
644
+ const path = require("node:path");
645
+ const file = path.join("${tmpDir}", "working-story-" + event.storyId + ".json");
646
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
647
+ },
648
+ async onRunEnd(event) {
649
+ const fs = require("node:fs/promises");
650
+ const path = require("node:path");
651
+ const file = path.join("${tmpDir}", "working-run-end.json");
652
+ await fs.writeFile(file, JSON.stringify(event, null, 2));
653
+ },
654
+ };
655
+
656
+ export default {
657
+ name: "working-reporter-plugin",
658
+ version: "1.0.0",
659
+ provides: ["reporter"],
660
+ extensions: {
661
+ reporter: workingReporter,
662
+ },
663
+ };
664
+ `;
665
+
666
+ await fs.writeFile(path.join(pluginDir, "failing-reporter.ts"), failingReporterCode);
667
+ await fs.writeFile(path.join(pluginDir, "working-reporter.ts"), workingReporterCode);
668
+
669
+ // Update config to use both reporters (failing first)
670
+ config.plugins = [
671
+ {
672
+ module: path.join(pluginDir, "failing-reporter.ts"),
673
+ },
674
+ {
675
+ module: path.join(pluginDir, "working-reporter.ts"),
676
+ },
677
+ ];
678
+
679
+ // Create minimal PRD
680
+ const prd = {
681
+ feature: "test-feature",
682
+ userStories: [
683
+ {
684
+ id: "US-001",
685
+ title: "Story 1",
686
+ description: "Test story 1",
687
+ acceptanceCriteria: ["AC1: Should work"],
688
+ status: "pending" as const,
689
+ dependencies: [],
690
+ tags: [],
691
+ },
692
+ ],
693
+ };
694
+
695
+ await savePRD(prd, prdPath);
696
+
697
+ const hooks = await loadHooksConfig(workdir);
698
+
699
+ await run({
700
+ prdPath,
701
+ workdir,
702
+ config,
703
+ hooks,
704
+ feature: "test-feature",
705
+ dryRun: true,
706
+ useBatch: false,
707
+ skipPrecheck: true,
708
+ });
709
+
710
+ // Verify working reporter still received events despite first reporter failing
711
+ const workingRunStart = path.join(tmpDir, "working-run-start.json");
712
+ const workingStory = path.join(tmpDir, "working-story-US-001.json");
713
+ const workingRunEnd = path.join(tmpDir, "working-run-end.json");
714
+
715
+ expect(await Bun.file(workingRunStart).exists()).toBe(true);
716
+ expect(await Bun.file(workingStory).exists()).toBe(true);
717
+ expect(await Bun.file(workingRunEnd).exists()).toBe(true);
718
+ });
719
+
720
+ test("AC6: Events fire even when the run fails or is aborted (onRunEnd still fires)", async () => {
721
+ // Create PRD with a story that will be marked as failed
722
+ const prd = {
723
+ feature: "test-feature",
724
+ userStories: [
725
+ {
726
+ id: "US-001",
727
+ title: "Story 1",
728
+ description: "Test story 1",
729
+ acceptanceCriteria: ["AC1: Should work"],
730
+ status: "failed" as const,
731
+ dependencies: [],
732
+ },
733
+ ],
734
+ };
735
+
736
+ await savePRD(prd, prdPath);
737
+
738
+ const hooks = await loadHooksConfig(workdir);
739
+
740
+ // Run should complete even though story is already failed
741
+ await run({
742
+ prdPath,
743
+ workdir,
744
+ config,
745
+ hooks,
746
+ feature: "test-feature",
747
+ dryRun: false,
748
+ useBatch: false,
749
+ skipPrecheck: true,
750
+ });
751
+
752
+ // Verify onRunStart and onRunEnd were still called
753
+ const runStartFile = path.join(tmpDir, "run-start.json");
754
+ const runEndFile = path.join(tmpDir, "run-end.json");
755
+
756
+ expect(await Bun.file(runStartFile).exists()).toBe(true);
757
+ expect(await Bun.file(runEndFile).exists()).toBe(true);
758
+
759
+ const runEndData = JSON.parse(await Bun.file(runEndFile).text());
760
+ expect(runEndData.storySummary.failed).toBeGreaterThan(0);
761
+ });
762
+
763
+ test("onStoryComplete receives correct status for paused stories", async () => {
764
+ // Create PRD with a paused story
765
+ const prd = {
766
+ feature: "test-feature",
767
+ userStories: [
768
+ {
769
+ id: "US-001",
770
+ title: "Story 1",
771
+ description: "Test story 1",
772
+ acceptanceCriteria: ["AC1: Should work"],
773
+ status: "paused" as const,
774
+ dependencies: [],
775
+ },
776
+ ],
777
+ };
778
+
779
+ await savePRD(prd, prdPath);
780
+
781
+ const hooks = await loadHooksConfig(workdir);
782
+
783
+ // Run with paused story
784
+ await run({
785
+ prdPath,
786
+ workdir,
787
+ config,
788
+ hooks,
789
+ feature: "test-feature",
790
+ dryRun: false,
791
+ useBatch: false,
792
+ skipPrecheck: true,
793
+ });
794
+
795
+ // Note: paused stories are not picked up by getNextStory, so no onStoryComplete event fires
796
+ // This is expected behavior - paused stories don't get executed
797
+ });
798
+
799
+ test("onStoryComplete receives all required fields for different story outcomes", async () => {
800
+ // Create PRD with multiple stories
801
+ const prd = {
802
+ feature: "test-feature",
803
+ userStories: [
804
+ {
805
+ id: "US-001",
806
+ title: "Story 1",
807
+ description: "Test story 1",
808
+ acceptanceCriteria: ["AC1: Should work"],
809
+ status: "pending" as const,
810
+ dependencies: [],
811
+ tags: [],
812
+ },
813
+ {
814
+ id: "US-002",
815
+ title: "Story 2",
816
+ description: "Test story 2",
817
+ acceptanceCriteria: ["AC1: Should work"],
818
+ status: "pending" as const,
819
+ dependencies: [],
820
+ tags: [],
821
+ },
822
+ ],
823
+ };
824
+
825
+ await savePRD(prd, prdPath);
826
+
827
+ const hooks = await loadHooksConfig(workdir);
828
+
829
+ // Run in dry-run mode
830
+ await run({
831
+ prdPath,
832
+ workdir,
833
+ config,
834
+ hooks,
835
+ feature: "test-feature",
836
+ dryRun: true,
837
+ useBatch: false,
838
+ skipPrecheck: true,
839
+ });
840
+
841
+ // Verify both stories received onStoryComplete events
842
+ const story1File = path.join(tmpDir, "story-complete-US-001.json");
843
+ const story2File = path.join(tmpDir, "story-complete-US-002.json");
844
+
845
+ expect(await Bun.file(story1File).exists()).toBe(true);
846
+ expect(await Bun.file(story2File).exists()).toBe(true);
847
+
848
+ const story1Data = JSON.parse(await Bun.file(story1File).text());
849
+ const story2Data = JSON.parse(await Bun.file(story2File).text());
850
+
851
+ // Verify both events have the same runId
852
+ expect(story1Data.runId).toBe(story2Data.runId);
853
+
854
+ // Verify both events have required fields
855
+ expect(story1Data.storyId).toBe("US-001");
856
+ expect(story2Data.storyId).toBe("US-002");
857
+ expect(story1Data.status).toBe("completed");
858
+ expect(story2Data.status).toBe("completed");
859
+ });
860
+ });