@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,722 @@
1
+ /**
2
+ * Review Stage Plugin Integration Tests
3
+ *
4
+ * Tests plugin reviewer integration in the review pipeline stage.
5
+ */
6
+
7
+ import { beforeEach, describe, expect, test } from "bun:test";
8
+ import { mkdtempSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { spawn } from "bun";
12
+ import { reviewStage } from "../../src/pipeline/stages/review";
13
+ import type { PipelineContext } from "../../src/pipeline/types";
14
+ import { PluginRegistry } from "../../src/plugins/registry";
15
+ import type { IReviewPlugin, NaxPlugin } from "../../src/plugins/types";
16
+
17
+ /**
18
+ * Create a mock pipeline context with minimal required fields
19
+ */
20
+ function createMockContext(workdir: string, plugins?: PluginRegistry): PipelineContext {
21
+ return {
22
+ config: {
23
+ review: {
24
+ enabled: true,
25
+ checks: [],
26
+ commands: {},
27
+ },
28
+ } as any,
29
+ prd: {} as any,
30
+ story: { id: "US-003" } as any,
31
+ stories: [],
32
+ routing: {} as any,
33
+ workdir,
34
+ hooks: {} as any,
35
+ plugins,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Initialize a git repo in the test directory
41
+ */
42
+ async function initGitRepo(workdir: string) {
43
+ await spawn({ cmd: ["git", "init"], cwd: workdir, stdout: "ignore", stderr: "ignore" }).exited;
44
+ await spawn({
45
+ cmd: ["git", "config", "user.email", "test@example.com"],
46
+ cwd: workdir,
47
+ stdout: "ignore",
48
+ stderr: "ignore",
49
+ }).exited;
50
+ await spawn({
51
+ cmd: ["git", "config", "user.name", "Test User"],
52
+ cwd: workdir,
53
+ stdout: "ignore",
54
+ stderr: "ignore",
55
+ }).exited;
56
+ await spawn({ cmd: ["git", "add", "-A"], cwd: workdir, stdout: "ignore", stderr: "ignore" }).exited;
57
+ await spawn({
58
+ cmd: ["git", "commit", "-m", "initial"],
59
+ cwd: workdir,
60
+ stdout: "ignore",
61
+ stderr: "ignore",
62
+ }).exited;
63
+ }
64
+
65
+ describe("Review Stage - Plugin Integration", () => {
66
+ describe("AC1: Plugin reviewers run after built-in checks pass", () => {
67
+ test("plugin reviewers execute when built-in checks pass", async () => {
68
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
69
+ await initGitRepo(tempDir);
70
+
71
+ let pluginCalled = false;
72
+ const mockReviewer: IReviewPlugin = {
73
+ name: "test-reviewer",
74
+ description: "Test reviewer",
75
+ async check() {
76
+ pluginCalled = true;
77
+ return { passed: true, output: "All good" };
78
+ },
79
+ };
80
+
81
+ const mockPlugin: NaxPlugin = {
82
+ name: "test-plugin",
83
+ version: "1.0.0",
84
+ provides: ["reviewer"],
85
+ extensions: { reviewer: mockReviewer },
86
+ };
87
+
88
+ const registry = new PluginRegistry([mockPlugin]);
89
+ const ctx = createMockContext(tempDir, registry);
90
+
91
+ const result = await reviewStage.execute(ctx);
92
+
93
+ expect(pluginCalled).toBe(true);
94
+ expect(result.action).toBe("continue");
95
+ });
96
+
97
+ test("plugin reviewers do not run if built-in checks fail", async () => {
98
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
99
+ await initGitRepo(tempDir);
100
+
101
+ let pluginCalled = false;
102
+ const mockReviewer: IReviewPlugin = {
103
+ name: "test-reviewer",
104
+ description: "Test reviewer",
105
+ async check() {
106
+ pluginCalled = true;
107
+ return { passed: true, output: "All good" };
108
+ },
109
+ };
110
+
111
+ const mockPlugin: NaxPlugin = {
112
+ name: "test-plugin",
113
+ version: "1.0.0",
114
+ provides: ["reviewer"],
115
+ extensions: { reviewer: mockReviewer },
116
+ };
117
+
118
+ const registry = new PluginRegistry([mockPlugin]);
119
+ const ctx = createMockContext(tempDir, registry);
120
+ // Configure a failing built-in check
121
+ ctx.config.review.checks = ["typecheck"];
122
+ ctx.config.review.commands = { typecheck: "sh -c 'exit 1'" };
123
+
124
+ const result = await reviewStage.execute(ctx);
125
+
126
+ expect(pluginCalled).toBe(false);
127
+ expect(result.action).toBe("fail");
128
+ expect(result.reason).toContain("Review failed");
129
+ });
130
+
131
+ test("no plugin reviewers registered - continues normally", async () => {
132
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
133
+ await initGitRepo(tempDir);
134
+
135
+ const registry = new PluginRegistry([]);
136
+ const ctx = createMockContext(tempDir, registry);
137
+
138
+ const result = await reviewStage.execute(ctx);
139
+
140
+ expect(result.action).toBe("continue");
141
+ });
142
+ });
143
+
144
+ describe("AC2: Each reviewer receives workdir and changed files", () => {
145
+ test("reviewer receives correct workdir", async () => {
146
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
147
+ await initGitRepo(tempDir);
148
+
149
+ let receivedWorkdir: string | undefined;
150
+ const mockReviewer: IReviewPlugin = {
151
+ name: "test-reviewer",
152
+ description: "Test reviewer",
153
+ async check(workdir) {
154
+ receivedWorkdir = workdir;
155
+ return { passed: true, output: "OK" };
156
+ },
157
+ };
158
+
159
+ const mockPlugin: NaxPlugin = {
160
+ name: "test-plugin",
161
+ version: "1.0.0",
162
+ provides: ["reviewer"],
163
+ extensions: { reviewer: mockReviewer },
164
+ };
165
+
166
+ const registry = new PluginRegistry([mockPlugin]);
167
+ const ctx = createMockContext(tempDir, registry);
168
+
169
+ await reviewStage.execute(ctx);
170
+
171
+ expect(receivedWorkdir).toBe(tempDir);
172
+ });
173
+
174
+ test("reviewer receives list of changed files", async () => {
175
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
176
+
177
+ // Create a file first
178
+ writeFileSync(join(tempDir, "test.ts"), "// initial");
179
+
180
+ await initGitRepo(tempDir);
181
+
182
+ // Now modify the file after git init
183
+ writeFileSync(join(tempDir, "test.ts"), "// modified");
184
+
185
+ let receivedFiles: string[] | undefined;
186
+ const mockReviewer: IReviewPlugin = {
187
+ name: "test-reviewer",
188
+ description: "Test reviewer",
189
+ async check(_workdir, changedFiles) {
190
+ receivedFiles = changedFiles;
191
+ return { passed: true, output: "OK" };
192
+ },
193
+ };
194
+
195
+ const mockPlugin: NaxPlugin = {
196
+ name: "test-plugin",
197
+ version: "1.0.0",
198
+ provides: ["reviewer"],
199
+ extensions: { reviewer: mockReviewer },
200
+ };
201
+
202
+ const registry = new PluginRegistry([mockPlugin]);
203
+ const ctx = createMockContext(tempDir, registry);
204
+
205
+ await reviewStage.execute(ctx);
206
+
207
+ expect(receivedFiles).toContain("test.ts");
208
+ });
209
+
210
+ test("reviewer receives empty array when no files changed", async () => {
211
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
212
+ await initGitRepo(tempDir);
213
+
214
+ let receivedFiles: string[] | undefined;
215
+ const mockReviewer: IReviewPlugin = {
216
+ name: "test-reviewer",
217
+ description: "Test reviewer",
218
+ async check(_workdir, changedFiles) {
219
+ receivedFiles = changedFiles;
220
+ return { passed: true, output: "OK" };
221
+ },
222
+ };
223
+
224
+ const mockPlugin: NaxPlugin = {
225
+ name: "test-plugin",
226
+ version: "1.0.0",
227
+ provides: ["reviewer"],
228
+ extensions: { reviewer: mockReviewer },
229
+ };
230
+
231
+ const registry = new PluginRegistry([mockPlugin]);
232
+ const ctx = createMockContext(tempDir, registry);
233
+
234
+ await reviewStage.execute(ctx);
235
+
236
+ expect(receivedFiles).toEqual([]);
237
+ });
238
+ });
239
+
240
+ describe("AC3: Reviewer failure triggers retry/escalation", () => {
241
+ test("failing reviewer returns fail action", async () => {
242
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
243
+ await initGitRepo(tempDir);
244
+
245
+ const mockReviewer: IReviewPlugin = {
246
+ name: "failing-reviewer",
247
+ description: "Failing reviewer",
248
+ async check() {
249
+ return { passed: false, output: "Security issues found" };
250
+ },
251
+ };
252
+
253
+ const mockPlugin: NaxPlugin = {
254
+ name: "test-plugin",
255
+ version: "1.0.0",
256
+ provides: ["reviewer"],
257
+ extensions: { reviewer: mockReviewer },
258
+ };
259
+
260
+ const registry = new PluginRegistry([mockPlugin]);
261
+ const ctx = createMockContext(tempDir, registry);
262
+
263
+ const result = await reviewStage.execute(ctx);
264
+
265
+ expect(result.action).toBe("fail");
266
+ expect(result.reason).toContain("failing-reviewer");
267
+ expect(result.reason).toContain("failed");
268
+ });
269
+
270
+ test("reviewer failure includes plugin name in reason", async () => {
271
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
272
+ await initGitRepo(tempDir);
273
+
274
+ const mockReviewer: IReviewPlugin = {
275
+ name: "security-scanner",
276
+ description: "Security scanner",
277
+ async check() {
278
+ return { passed: false, output: "Vulnerabilities detected" };
279
+ },
280
+ };
281
+
282
+ const mockPlugin: NaxPlugin = {
283
+ name: "test-plugin",
284
+ version: "1.0.0",
285
+ provides: ["reviewer"],
286
+ extensions: { reviewer: mockReviewer },
287
+ };
288
+
289
+ const registry = new PluginRegistry([mockPlugin]);
290
+ const ctx = createMockContext(tempDir, registry);
291
+
292
+ const result = await reviewStage.execute(ctx);
293
+
294
+ expect(result.action).toBe("fail");
295
+ expect(result.reason).toContain("security-scanner");
296
+ });
297
+ });
298
+
299
+ describe("AC4: Reviewer output included in story result", () => {
300
+ test("passing reviewer output is captured", async () => {
301
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
302
+ await initGitRepo(tempDir);
303
+
304
+ const mockReviewer: IReviewPlugin = {
305
+ name: "test-reviewer",
306
+ description: "Test reviewer",
307
+ async check() {
308
+ return { passed: true, output: "All checks passed successfully", exitCode: 0 };
309
+ },
310
+ };
311
+
312
+ const mockPlugin: NaxPlugin = {
313
+ name: "test-plugin",
314
+ version: "1.0.0",
315
+ provides: ["reviewer"],
316
+ extensions: { reviewer: mockReviewer },
317
+ };
318
+
319
+ const registry = new PluginRegistry([mockPlugin]);
320
+ const ctx = createMockContext(tempDir, registry);
321
+
322
+ await reviewStage.execute(ctx);
323
+
324
+ expect(ctx.reviewResult).toBeDefined();
325
+ expect(ctx.reviewResult?.pluginReviewers).toBeDefined();
326
+ expect(ctx.reviewResult?.pluginReviewers).toHaveLength(1);
327
+ expect(ctx.reviewResult?.pluginReviewers?.[0].name).toBe("test-reviewer");
328
+ expect(ctx.reviewResult?.pluginReviewers?.[0].passed).toBe(true);
329
+ expect(ctx.reviewResult?.pluginReviewers?.[0].output).toBe("All checks passed successfully");
330
+ expect(ctx.reviewResult?.pluginReviewers?.[0].exitCode).toBe(0);
331
+ });
332
+
333
+ test("failing reviewer output is captured", async () => {
334
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
335
+ await initGitRepo(tempDir);
336
+
337
+ const mockReviewer: IReviewPlugin = {
338
+ name: "security-scanner",
339
+ description: "Security scanner",
340
+ async check() {
341
+ return {
342
+ passed: false,
343
+ output: "Found 3 critical vulnerabilities",
344
+ exitCode: 1,
345
+ };
346
+ },
347
+ };
348
+
349
+ const mockPlugin: NaxPlugin = {
350
+ name: "test-plugin",
351
+ version: "1.0.0",
352
+ provides: ["reviewer"],
353
+ extensions: { reviewer: mockReviewer },
354
+ };
355
+
356
+ const registry = new PluginRegistry([mockPlugin]);
357
+ const ctx = createMockContext(tempDir, registry);
358
+
359
+ await reviewStage.execute(ctx);
360
+
361
+ expect(ctx.reviewResult?.pluginReviewers).toBeDefined();
362
+ expect(ctx.reviewResult?.pluginReviewers).toHaveLength(1);
363
+ expect(ctx.reviewResult?.pluginReviewers?.[0].name).toBe("security-scanner");
364
+ expect(ctx.reviewResult?.pluginReviewers?.[0].passed).toBe(false);
365
+ expect(ctx.reviewResult?.pluginReviewers?.[0].output).toBe("Found 3 critical vulnerabilities");
366
+ expect(ctx.reviewResult?.pluginReviewers?.[0].exitCode).toBe(1);
367
+ });
368
+ });
369
+
370
+ describe("AC5: Exceptions count as failures", () => {
371
+ test("reviewer throwing exception counts as failure", async () => {
372
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
373
+ await initGitRepo(tempDir);
374
+
375
+ const mockReviewer: IReviewPlugin = {
376
+ name: "buggy-reviewer",
377
+ description: "Buggy reviewer",
378
+ async check() {
379
+ throw new Error("Unexpected error in reviewer");
380
+ },
381
+ };
382
+
383
+ const mockPlugin: NaxPlugin = {
384
+ name: "test-plugin",
385
+ version: "1.0.0",
386
+ provides: ["reviewer"],
387
+ extensions: { reviewer: mockReviewer },
388
+ };
389
+
390
+ const registry = new PluginRegistry([mockPlugin]);
391
+ const ctx = createMockContext(tempDir, registry);
392
+
393
+ const result = await reviewStage.execute(ctx);
394
+
395
+ expect(result.action).toBe("fail");
396
+ expect(result.reason).toContain("buggy-reviewer");
397
+ expect(result.reason).toContain("threw error");
398
+ });
399
+
400
+ test("exception message captured in output", async () => {
401
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
402
+ await initGitRepo(tempDir);
403
+
404
+ const mockReviewer: IReviewPlugin = {
405
+ name: "failing-reviewer",
406
+ description: "Failing reviewer",
407
+ async check() {
408
+ throw new Error("Connection timeout");
409
+ },
410
+ };
411
+
412
+ const mockPlugin: NaxPlugin = {
413
+ name: "test-plugin",
414
+ version: "1.0.0",
415
+ provides: ["reviewer"],
416
+ extensions: { reviewer: mockReviewer },
417
+ };
418
+
419
+ const registry = new PluginRegistry([mockPlugin]);
420
+ const ctx = createMockContext(tempDir, registry);
421
+
422
+ await reviewStage.execute(ctx);
423
+
424
+ expect(ctx.reviewResult?.pluginReviewers).toBeDefined();
425
+ expect(ctx.reviewResult?.pluginReviewers).toHaveLength(1);
426
+ expect(ctx.reviewResult?.pluginReviewers?.[0].passed).toBe(false);
427
+ expect(ctx.reviewResult?.pluginReviewers?.[0].error).toBe("Connection timeout");
428
+ });
429
+
430
+ test("non-Error exception converted to string", async () => {
431
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
432
+ await initGitRepo(tempDir);
433
+
434
+ const mockReviewer: IReviewPlugin = {
435
+ name: "string-thrower",
436
+ description: "String thrower",
437
+ async check() {
438
+ throw "Something went wrong"; // eslint-disable-line
439
+ },
440
+ };
441
+
442
+ const mockPlugin: NaxPlugin = {
443
+ name: "test-plugin",
444
+ version: "1.0.0",
445
+ provides: ["reviewer"],
446
+ extensions: { reviewer: mockReviewer },
447
+ };
448
+
449
+ const registry = new PluginRegistry([mockPlugin]);
450
+ const ctx = createMockContext(tempDir, registry);
451
+
452
+ await reviewStage.execute(ctx);
453
+
454
+ expect(ctx.reviewResult?.pluginReviewers?.[0].error).toBe("Something went wrong");
455
+ });
456
+ });
457
+
458
+ describe("AC6: Multiple reviewers run sequentially with short-circuiting", () => {
459
+ test("multiple reviewers run in order when all pass", async () => {
460
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
461
+ await initGitRepo(tempDir);
462
+
463
+ const callOrder: string[] = [];
464
+
465
+ const reviewer1: IReviewPlugin = {
466
+ name: "reviewer-1",
467
+ description: "First reviewer",
468
+ async check() {
469
+ callOrder.push("reviewer-1");
470
+ return { passed: true, output: "OK" };
471
+ },
472
+ };
473
+
474
+ const reviewer2: IReviewPlugin = {
475
+ name: "reviewer-2",
476
+ description: "Second reviewer",
477
+ async check() {
478
+ callOrder.push("reviewer-2");
479
+ return { passed: true, output: "OK" };
480
+ },
481
+ };
482
+
483
+ const reviewer3: IReviewPlugin = {
484
+ name: "reviewer-3",
485
+ description: "Third reviewer",
486
+ async check() {
487
+ callOrder.push("reviewer-3");
488
+ return { passed: true, output: "OK" };
489
+ },
490
+ };
491
+
492
+ const plugin1: NaxPlugin = {
493
+ name: "plugin-1",
494
+ version: "1.0.0",
495
+ provides: ["reviewer"],
496
+ extensions: { reviewer: reviewer1 },
497
+ };
498
+
499
+ const plugin2: NaxPlugin = {
500
+ name: "plugin-2",
501
+ version: "1.0.0",
502
+ provides: ["reviewer"],
503
+ extensions: { reviewer: reviewer2 },
504
+ };
505
+
506
+ const plugin3: NaxPlugin = {
507
+ name: "plugin-3",
508
+ version: "1.0.0",
509
+ provides: ["reviewer"],
510
+ extensions: { reviewer: reviewer3 },
511
+ };
512
+
513
+ const registry = new PluginRegistry([plugin1, plugin2, plugin3]);
514
+ const ctx = createMockContext(tempDir, registry);
515
+
516
+ const result = await reviewStage.execute(ctx);
517
+
518
+ expect(result.action).toBe("continue");
519
+ expect(callOrder).toEqual(["reviewer-1", "reviewer-2", "reviewer-3"]);
520
+ expect(ctx.reviewResult?.pluginReviewers).toHaveLength(3);
521
+ });
522
+
523
+ test("first failure short-circuits remaining reviewers", async () => {
524
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
525
+ await initGitRepo(tempDir);
526
+
527
+ const callOrder: string[] = [];
528
+
529
+ const reviewer1: IReviewPlugin = {
530
+ name: "reviewer-1",
531
+ description: "First reviewer",
532
+ async check() {
533
+ callOrder.push("reviewer-1");
534
+ return { passed: true, output: "OK" };
535
+ },
536
+ };
537
+
538
+ const reviewer2: IReviewPlugin = {
539
+ name: "reviewer-2",
540
+ description: "Second reviewer (fails)",
541
+ async check() {
542
+ callOrder.push("reviewer-2");
543
+ return { passed: false, output: "Failed" };
544
+ },
545
+ };
546
+
547
+ const reviewer3: IReviewPlugin = {
548
+ name: "reviewer-3",
549
+ description: "Third reviewer (should not run)",
550
+ async check() {
551
+ callOrder.push("reviewer-3");
552
+ return { passed: true, output: "OK" };
553
+ },
554
+ };
555
+
556
+ const plugin1: NaxPlugin = {
557
+ name: "plugin-1",
558
+ version: "1.0.0",
559
+ provides: ["reviewer"],
560
+ extensions: { reviewer: reviewer1 },
561
+ };
562
+
563
+ const plugin2: NaxPlugin = {
564
+ name: "plugin-2",
565
+ version: "1.0.0",
566
+ provides: ["reviewer"],
567
+ extensions: { reviewer: reviewer2 },
568
+ };
569
+
570
+ const plugin3: NaxPlugin = {
571
+ name: "plugin-3",
572
+ version: "1.0.0",
573
+ provides: ["reviewer"],
574
+ extensions: { reviewer: reviewer3 },
575
+ };
576
+
577
+ const registry = new PluginRegistry([plugin1, plugin2, plugin3]);
578
+ const ctx = createMockContext(tempDir, registry);
579
+
580
+ const result = await reviewStage.execute(ctx);
581
+
582
+ expect(result.action).toBe("fail");
583
+ expect(callOrder).toEqual(["reviewer-1", "reviewer-2"]);
584
+ expect(callOrder).not.toContain("reviewer-3");
585
+ expect(ctx.reviewResult?.pluginReviewers).toHaveLength(2);
586
+ });
587
+
588
+ test("exception short-circuits remaining reviewers", async () => {
589
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
590
+ await initGitRepo(tempDir);
591
+
592
+ const callOrder: string[] = [];
593
+
594
+ const reviewer1: IReviewPlugin = {
595
+ name: "reviewer-1",
596
+ description: "First reviewer",
597
+ async check() {
598
+ callOrder.push("reviewer-1");
599
+ return { passed: true, output: "OK" };
600
+ },
601
+ };
602
+
603
+ const reviewer2: IReviewPlugin = {
604
+ name: "reviewer-2",
605
+ description: "Second reviewer (throws)",
606
+ async check() {
607
+ callOrder.push("reviewer-2");
608
+ throw new Error("Boom!");
609
+ },
610
+ };
611
+
612
+ const reviewer3: IReviewPlugin = {
613
+ name: "reviewer-3",
614
+ description: "Third reviewer (should not run)",
615
+ async check() {
616
+ callOrder.push("reviewer-3");
617
+ return { passed: true, output: "OK" };
618
+ },
619
+ };
620
+
621
+ const plugin1: NaxPlugin = {
622
+ name: "plugin-1",
623
+ version: "1.0.0",
624
+ provides: ["reviewer"],
625
+ extensions: { reviewer: reviewer1 },
626
+ };
627
+
628
+ const plugin2: NaxPlugin = {
629
+ name: "plugin-2",
630
+ version: "1.0.0",
631
+ provides: ["reviewer"],
632
+ extensions: { reviewer: reviewer2 },
633
+ };
634
+
635
+ const plugin3: NaxPlugin = {
636
+ name: "plugin-3",
637
+ version: "1.0.0",
638
+ provides: ["reviewer"],
639
+ extensions: { reviewer: reviewer3 },
640
+ };
641
+
642
+ const registry = new PluginRegistry([plugin1, plugin2, plugin3]);
643
+ const ctx = createMockContext(tempDir, registry);
644
+
645
+ const result = await reviewStage.execute(ctx);
646
+
647
+ expect(result.action).toBe("fail");
648
+ expect(callOrder).toEqual(["reviewer-1", "reviewer-2"]);
649
+ expect(callOrder).not.toContain("reviewer-3");
650
+ expect(ctx.reviewResult?.pluginReviewers).toHaveLength(2);
651
+ });
652
+ });
653
+
654
+ describe("Edge Cases", () => {
655
+ test("no plugins context - continues normally", async () => {
656
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
657
+ await initGitRepo(tempDir);
658
+
659
+ const ctx = createMockContext(tempDir, undefined);
660
+
661
+ const result = await reviewStage.execute(ctx);
662
+
663
+ expect(result.action).toBe("continue");
664
+ });
665
+
666
+ test("reviewer returns empty output", async () => {
667
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
668
+ await initGitRepo(tempDir);
669
+
670
+ const mockReviewer: IReviewPlugin = {
671
+ name: "silent-reviewer",
672
+ description: "Silent reviewer",
673
+ async check() {
674
+ return { passed: true, output: "" };
675
+ },
676
+ };
677
+
678
+ const mockPlugin: NaxPlugin = {
679
+ name: "test-plugin",
680
+ version: "1.0.0",
681
+ provides: ["reviewer"],
682
+ extensions: { reviewer: mockReviewer },
683
+ };
684
+
685
+ const registry = new PluginRegistry([mockPlugin]);
686
+ const ctx = createMockContext(tempDir, registry);
687
+
688
+ const result = await reviewStage.execute(ctx);
689
+
690
+ expect(result.action).toBe("continue");
691
+ expect(ctx.reviewResult?.pluginReviewers?.[0].output).toBe("");
692
+ });
693
+
694
+ test("reviewer without exitCode works", async () => {
695
+ const tempDir = mkdtempSync(join(tmpdir(), "nax-review-plugin-"));
696
+ await initGitRepo(tempDir);
697
+
698
+ const mockReviewer: IReviewPlugin = {
699
+ name: "test-reviewer",
700
+ description: "Test reviewer",
701
+ async check() {
702
+ return { passed: true, output: "OK" }; // No exitCode
703
+ },
704
+ };
705
+
706
+ const mockPlugin: NaxPlugin = {
707
+ name: "test-plugin",
708
+ version: "1.0.0",
709
+ provides: ["reviewer"],
710
+ extensions: { reviewer: mockReviewer },
711
+ };
712
+
713
+ const registry = new PluginRegistry([mockPlugin]);
714
+ const ctx = createMockContext(tempDir, registry);
715
+
716
+ const result = await reviewStage.execute(ctx);
717
+
718
+ expect(result.action).toBe("continue");
719
+ expect(ctx.reviewResult?.pluginReviewers?.[0].exitCode).toBeUndefined();
720
+ });
721
+ });
722
+ });