@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,377 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { type TestFailure, formatFailureSummary, parseBunTestOutput } from "../../src/execution/test-output-parser";
3
+
4
+ describe("parseBunTestOutput", () => {
5
+ test("parses passing output (0 failures)", () => {
6
+ const output = `
7
+ bun test v1.0.0
8
+
9
+ test/example.test.ts:
10
+ ✓ test 1 [0.5ms]
11
+ ✓ test 2 [0.3ms]
12
+ ✓ test 3 [0.7ms]
13
+
14
+ 3 tests passed [1.5ms]
15
+ `.trim();
16
+
17
+ const result = parseBunTestOutput(output);
18
+
19
+ expect(result.passed).toBe(3);
20
+ expect(result.failed).toBe(0);
21
+ expect(result.failures).toHaveLength(0);
22
+ });
23
+
24
+ test("parses mixed pass/fail output", () => {
25
+ const output = `
26
+ bun test v1.0.0
27
+
28
+ test/example.test.ts:
29
+ ✓ passing test [0.5ms]
30
+ ✗ failing test [1.2ms]
31
+
32
+ (fail) failing test [1.2ms]
33
+ Error: Expected 1 to equal 2
34
+ at /path/to/file.ts:10:15
35
+ at Object.test (/path/to/file.ts:8:3)
36
+
37
+ 1 passed, 1 failed [1.7ms]
38
+ `.trim();
39
+
40
+ const result = parseBunTestOutput(output);
41
+
42
+ expect(result.passed).toBe(1);
43
+ expect(result.failed).toBe(1);
44
+ expect(result.failures).toHaveLength(1);
45
+ expect(result.failures[0].file).toBe("test/example.test.ts");
46
+ expect(result.failures[0].testName).toBe("failing test");
47
+ expect(result.failures[0].error).toBe("Error: Expected 1 to equal 2");
48
+ expect(result.failures[0].stackTrace).toHaveLength(2);
49
+ });
50
+
51
+ test("extracts test names from nested describe blocks", () => {
52
+ const output = `
53
+ bun test v1.0.0
54
+
55
+ test/nested.test.ts:
56
+ ✓ outer test [0.2ms]
57
+ ✗ inner test [0.8ms]
58
+
59
+ (fail) describe block > nested block > inner test [0.8ms]
60
+ Error: Assertion failed
61
+ at /path/to/nested.ts:20:10
62
+
63
+ 1 passed, 1 failed [1.0ms]
64
+ `.trim();
65
+
66
+ const result = parseBunTestOutput(output);
67
+
68
+ expect(result.failures).toHaveLength(1);
69
+ expect(result.failures[0].testName).toBe("describe block > nested block > inner test");
70
+ expect(result.failures[0].file).toBe("test/nested.test.ts");
71
+ });
72
+
73
+ test("truncates stack trace to 5 lines", () => {
74
+ const output = `
75
+ bun test v1.0.0
76
+
77
+ test/stack.test.ts:
78
+ ✗ test with long stack [2.0ms]
79
+
80
+ (fail) test with long stack [2.0ms]
81
+ Error: Stack overflow
82
+ at line1 (/path/to/file.ts:1:1)
83
+ at line2 (/path/to/file.ts:2:2)
84
+ at line3 (/path/to/file.ts:3:3)
85
+ at line4 (/path/to/file.ts:4:4)
86
+ at line5 (/path/to/file.ts:5:5)
87
+ at line6 (/path/to/file.ts:6:6)
88
+ at line7 (/path/to/file.ts:7:7)
89
+ at line8 (/path/to/file.ts:8:8)
90
+
91
+ 0 passed, 1 failed [2.0ms]
92
+ `.trim();
93
+
94
+ const result = parseBunTestOutput(output);
95
+
96
+ expect(result.failures).toHaveLength(1);
97
+ expect(result.failures[0].stackTrace).toHaveLength(5);
98
+ expect(result.failures[0].stackTrace[0]).toBe("at line1 (/path/to/file.ts:1:1)");
99
+ expect(result.failures[0].stackTrace[4]).toBe("at line5 (/path/to/file.ts:5:5)");
100
+ });
101
+
102
+ test("handles empty/malformed input", () => {
103
+ const emptyResult = parseBunTestOutput("");
104
+ expect(emptyResult.passed).toBe(0);
105
+ expect(emptyResult.failed).toBe(0);
106
+ expect(emptyResult.failures).toHaveLength(0);
107
+
108
+ const malformedResult = parseBunTestOutput("random text\nno test output");
109
+ expect(malformedResult.passed).toBe(0);
110
+ expect(malformedResult.failed).toBe(0);
111
+ expect(malformedResult.failures).toHaveLength(0);
112
+ });
113
+
114
+ test("handles multiple test files", () => {
115
+ const output = `
116
+ bun test v1.0.0
117
+
118
+ test/file1.test.ts:
119
+ ✓ test 1 [0.5ms]
120
+ ✗ test 2 [1.2ms]
121
+
122
+ (fail) test 2 [1.2ms]
123
+ Error: File 1 error
124
+ at /path/to/file1.ts:10:15
125
+
126
+ test/file2.test.ts:
127
+ ✓ test 3 [0.3ms]
128
+ ✗ test 4 [0.8ms]
129
+
130
+ (fail) test 4 [0.8ms]
131
+ Error: File 2 error
132
+ at /path/to/file2.ts:20:25
133
+
134
+ 2 passed, 2 failed [2.8ms]
135
+ `.trim();
136
+
137
+ const result = parseBunTestOutput(output);
138
+
139
+ expect(result.passed).toBe(2);
140
+ expect(result.failed).toBe(2);
141
+ expect(result.failures).toHaveLength(2);
142
+ expect(result.failures[0].file).toBe("test/file1.test.ts");
143
+ expect(result.failures[0].error).toBe("Error: File 1 error");
144
+ expect(result.failures[1].file).toBe("test/file2.test.ts");
145
+ expect(result.failures[1].error).toBe("Error: File 2 error");
146
+ });
147
+
148
+ test("handles test files with .js extension", () => {
149
+ const output = `
150
+ bun test v1.0.0
151
+
152
+ test/example.test.js:
153
+ ✗ failing test [1.0ms]
154
+
155
+ (fail) failing test [1.0ms]
156
+ Error: JS test error
157
+ at /path/to/file.js:5:10
158
+
159
+ 0 passed, 1 failed [1.0ms]
160
+ `.trim();
161
+
162
+ const result = parseBunTestOutput(output);
163
+
164
+ expect(result.failures).toHaveLength(1);
165
+ expect(result.failures[0].file).toBe("test/example.test.js");
166
+ });
167
+
168
+ test("handles failures without file context", () => {
169
+ const output = `
170
+ bun test v1.0.0
171
+
172
+ ✗ orphan test [1.0ms]
173
+
174
+ (fail) orphan test [1.0ms]
175
+ Error: No file context
176
+ at /path/to/unknown.ts:1:1
177
+
178
+ 0 passed, 1 failed [1.0ms]
179
+ `.trim();
180
+
181
+ const result = parseBunTestOutput(output);
182
+
183
+ expect(result.failures).toHaveLength(1);
184
+ expect(result.failures[0].file).toBe("unknown");
185
+ });
186
+
187
+ test("handles failures with no error message", () => {
188
+ const output = `
189
+ bun test v1.0.0
190
+
191
+ test/minimal.test.ts:
192
+ ✗ minimal fail [0.5ms]
193
+
194
+ (fail) minimal fail [0.5ms]
195
+
196
+ 0 passed, 1 failed [0.5ms]
197
+ `.trim();
198
+
199
+ const result = parseBunTestOutput(output);
200
+
201
+ expect(result.failures).toHaveLength(1);
202
+ expect(result.failures[0].error).toBe("Unknown error");
203
+ expect(result.failures[0].stackTrace).toHaveLength(0);
204
+ });
205
+
206
+ test("handles alternative check marks (✔ and ✘)", () => {
207
+ const output = `
208
+ bun test v1.0.0
209
+
210
+ test/marks.test.ts:
211
+ ✔ pass with heavy check [0.2ms]
212
+ ✘ fail with heavy X [0.5ms]
213
+
214
+ (fail) fail with heavy X [0.5ms]
215
+ Error: Alternative marks error
216
+
217
+ 1 passed, 1 failed [0.7ms]
218
+ `.trim();
219
+
220
+ const result = parseBunTestOutput(output);
221
+
222
+ expect(result.passed).toBe(1);
223
+ expect(result.failed).toBe(1);
224
+ expect(result.failures).toHaveLength(1);
225
+ });
226
+ });
227
+
228
+ describe("formatFailureSummary", () => {
229
+ test("returns 'No test failures' for empty array", () => {
230
+ const result = formatFailureSummary([]);
231
+ expect(result).toBe("No test failures");
232
+ });
233
+
234
+ test("formats single failure", () => {
235
+ const failures: TestFailure[] = [
236
+ {
237
+ file: "test/example.test.ts",
238
+ testName: "failing test",
239
+ error: "Expected 1 to equal 2",
240
+ stackTrace: ["at /path/to/file.ts:10:15"],
241
+ },
242
+ ];
243
+
244
+ const result = formatFailureSummary(failures);
245
+
246
+ expect(result).toContain("1. test/example.test.ts > failing test");
247
+ expect(result).toContain("Error: Expected 1 to equal 2");
248
+ expect(result).toContain("at /path/to/file.ts:10:15");
249
+ });
250
+
251
+ test("formats multiple failures", () => {
252
+ const failures: TestFailure[] = [
253
+ {
254
+ file: "test/file1.test.ts",
255
+ testName: "test 1",
256
+ error: "Error 1",
257
+ stackTrace: ["at /path/file1.ts:5:10"],
258
+ },
259
+ {
260
+ file: "test/file2.test.ts",
261
+ testName: "test 2",
262
+ error: "Error 2",
263
+ stackTrace: ["at /path/file2.ts:15:20"],
264
+ },
265
+ ];
266
+
267
+ const result = formatFailureSummary(failures);
268
+
269
+ expect(result).toContain("1. test/file1.test.ts > test 1");
270
+ expect(result).toContain("Error: Error 1");
271
+ expect(result).toContain("2. test/file2.test.ts > test 2");
272
+ expect(result).toContain("Error: Error 2");
273
+ });
274
+
275
+ test("respects maxChars limit", () => {
276
+ const failures: TestFailure[] = Array.from({ length: 10 }, (_, i) => ({
277
+ file: `test/file${i}.test.ts`,
278
+ testName: `test ${i}`,
279
+ error: `Error message ${i}`,
280
+ stackTrace: [`at /path/file${i}.ts:${i}:${i}`],
281
+ }));
282
+
283
+ const result = formatFailureSummary(failures, 300);
284
+
285
+ expect(result.length).toBeLessThanOrEqual(350); // Some buffer for truncation message
286
+ expect(result).toContain("... and");
287
+ expect(result).toContain("more failure(s) (truncated)");
288
+ });
289
+
290
+ test("handles failure without stack trace", () => {
291
+ const failures: TestFailure[] = [
292
+ {
293
+ file: "test/nostack.test.ts",
294
+ testName: "no stack",
295
+ error: "Error without stack",
296
+ stackTrace: [],
297
+ },
298
+ ];
299
+
300
+ const result = formatFailureSummary(failures);
301
+
302
+ expect(result).toContain("1. test/nostack.test.ts > no stack");
303
+ expect(result).toContain("Error: Error without stack");
304
+ expect(result).not.toContain("at ");
305
+ });
306
+
307
+ test("handles nested test names", () => {
308
+ const failures: TestFailure[] = [
309
+ {
310
+ file: "test/nested.test.ts",
311
+ testName: "outer > middle > inner",
312
+ error: "Nested test error",
313
+ stackTrace: ["at /path/nested.ts:30:5"],
314
+ },
315
+ ];
316
+
317
+ const result = formatFailureSummary(failures);
318
+
319
+ expect(result).toContain("1. test/nested.test.ts > outer > middle > inner");
320
+ expect(result).toContain("Error: Nested test error");
321
+ });
322
+
323
+ test("includes only first stack trace line", () => {
324
+ const failures: TestFailure[] = [
325
+ {
326
+ file: "test/multi.test.ts",
327
+ testName: "multi stack",
328
+ error: "Error with multiple stack lines",
329
+ stackTrace: ["at /path/file.ts:10:5", "at /path/file.ts:20:10", "at /path/file.ts:30:15"],
330
+ },
331
+ ];
332
+
333
+ const result = formatFailureSummary(failures);
334
+
335
+ expect(result).toContain("at /path/file.ts:10:5");
336
+ expect(result).not.toContain("at /path/file.ts:20:10");
337
+ expect(result).not.toContain("at /path/file.ts:30:15");
338
+ });
339
+
340
+ test("separates failures with blank lines", () => {
341
+ const failures: TestFailure[] = [
342
+ {
343
+ file: "test/a.test.ts",
344
+ testName: "test a",
345
+ error: "Error A",
346
+ stackTrace: ["at a.ts:1:1"],
347
+ },
348
+ {
349
+ file: "test/b.test.ts",
350
+ testName: "test b",
351
+ error: "Error B",
352
+ stackTrace: ["at b.ts:2:2"],
353
+ },
354
+ ];
355
+
356
+ const result = formatFailureSummary(failures);
357
+
358
+ // Check that there's proper separation between failures
359
+ const lines = result.split("\n");
360
+ const blankLines = lines.filter((line) => line.trim() === "");
361
+ expect(blankLines.length).toBeGreaterThan(0);
362
+ });
363
+
364
+ test("uses default maxChars of 2000", () => {
365
+ const failures: TestFailure[] = Array.from({ length: 50 }, (_, i) => ({
366
+ file: `test/file${i}.test.ts`,
367
+ testName: `test ${i}`,
368
+ error: `Error message ${i}`,
369
+ stackTrace: [`at /path/file${i}.ts:${i}:${i}`],
370
+ }));
371
+
372
+ const result = formatFailureSummary(failures); // No maxChars argument
373
+
374
+ expect(result.length).toBeLessThanOrEqual(2100); // Some buffer
375
+ expect(result).toContain("(truncated)");
376
+ });
377
+ });
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Tests for src/tdd/verdict.ts
3
+ *
4
+ * Covers: readVerdict, cleanupVerdict, categorizeVerdict
5
+ */
6
+
7
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
8
+ import { existsSync } from "node:fs";
9
+ import { mkdir, rm, writeFile } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import path from "node:path";
12
+ import { categorizeVerdict, cleanupVerdict, readVerdict } from "../../src/tdd/verdict";
13
+ import type { VerifierVerdict } from "../../src/tdd/verdict";
14
+
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // Test fixtures
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+
19
+ /** Build a fully valid verdict with sensible defaults. */
20
+ function makeVerdict(overrides: Partial<VerifierVerdict> = {}): VerifierVerdict {
21
+ return {
22
+ version: 1,
23
+ approved: true,
24
+ tests: { allPassing: true, passCount: 10, failCount: 0 },
25
+ testModifications: { detected: false, files: [], legitimate: true, reasoning: "No modifications" },
26
+ acceptanceCriteria: {
27
+ allMet: true,
28
+ criteria: [{ criterion: "Feature works", met: true }],
29
+ },
30
+ quality: { rating: "good", issues: [] },
31
+ fixes: [],
32
+ reasoning: "All good.",
33
+ ...overrides,
34
+ };
35
+ }
36
+
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ // Setup / teardown
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+
41
+ let workdir: string;
42
+
43
+ beforeEach(async () => {
44
+ workdir = path.join(tmpdir(), `verdict-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
45
+ await mkdir(workdir, { recursive: true });
46
+ });
47
+
48
+ afterEach(async () => {
49
+ await rm(workdir, { recursive: true, force: true });
50
+ });
51
+
52
+ // ─────────────────────────────────────────────────────────────────────────────
53
+ // readVerdict
54
+ // ─────────────────────────────────────────────────────────────────────────────
55
+
56
+ describe("readVerdict", () => {
57
+ it("returns parsed verdict when file exists and is valid", async () => {
58
+ const verdict = makeVerdict();
59
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify(verdict), "utf-8");
60
+
61
+ const result = await readVerdict(workdir);
62
+ expect(result).not.toBeNull();
63
+ expect(result?.version).toBe(1);
64
+ expect(result?.approved).toBe(true);
65
+ expect(result?.tests.allPassing).toBe(true);
66
+ expect(result?.tests.passCount).toBe(10);
67
+ expect(result?.tests.failCount).toBe(0);
68
+ });
69
+
70
+ it("returns null when file does not exist (no throw)", async () => {
71
+ const result = await readVerdict(workdir);
72
+ expect(result).toBeNull();
73
+ });
74
+
75
+ it("returns null when JSON is malformed (no throw, logs warning)", async () => {
76
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), "{ this is not valid json !!!", "utf-8");
77
+
78
+ const result = await readVerdict(workdir);
79
+ expect(result).toBeNull();
80
+ });
81
+
82
+ it("returns null when required field 'version' is missing", async () => {
83
+ const bad = { approved: true, tests: { allPassing: true, passCount: 5, failCount: 0 } };
84
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify(bad), "utf-8");
85
+
86
+ const result = await readVerdict(workdir);
87
+ expect(result).toBeNull();
88
+ });
89
+
90
+ it("returns null when required field 'approved' is missing", async () => {
91
+ const bad = { version: 1, tests: { allPassing: true, passCount: 5, failCount: 0 } };
92
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify(bad), "utf-8");
93
+
94
+ const result = await readVerdict(workdir);
95
+ expect(result).toBeNull();
96
+ });
97
+
98
+ it("returns null when required field 'tests' is missing", async () => {
99
+ const bad = { version: 1, approved: true };
100
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify(bad), "utf-8");
101
+
102
+ const result = await readVerdict(workdir);
103
+ expect(result).toBeNull();
104
+ });
105
+
106
+ it("returns null when tests sub-fields are missing", async () => {
107
+ const bad = { version: 1, approved: true, tests: { allPassing: true } };
108
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify(bad), "utf-8");
109
+
110
+ const result = await readVerdict(workdir);
111
+ expect(result).toBeNull();
112
+ });
113
+
114
+ it("returns null when version is not 1", async () => {
115
+ const bad = { version: 2, approved: true, tests: { allPassing: true, passCount: 5, failCount: 0 } };
116
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify(bad), "utf-8");
117
+
118
+ const result = await readVerdict(workdir);
119
+ expect(result).toBeNull();
120
+ });
121
+
122
+ it("returns null for empty JSON object", async () => {
123
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify({}), "utf-8");
124
+
125
+ const result = await readVerdict(workdir);
126
+ expect(result).toBeNull();
127
+ });
128
+
129
+ it("returns null for JSON array (not an object)", async () => {
130
+ await writeFile(path.join(workdir, ".nax-verifier-verdict.json"), JSON.stringify([1, 2, 3]), "utf-8");
131
+
132
+ const result = await readVerdict(workdir);
133
+ expect(result).toBeNull();
134
+ });
135
+ });
136
+
137
+ // ─────────────────────────────────────────────────────────────────────────────
138
+ // cleanupVerdict
139
+ // ─────────────────────────────────────────────────────────────────────────────
140
+
141
+ describe("cleanupVerdict", () => {
142
+ it("deletes the verdict file when it exists", async () => {
143
+ const verdictPath = path.join(workdir, ".nax-verifier-verdict.json");
144
+ await writeFile(verdictPath, JSON.stringify(makeVerdict()), "utf-8");
145
+
146
+ expect(existsSync(verdictPath)).toBe(true);
147
+
148
+ await cleanupVerdict(workdir);
149
+
150
+ expect(existsSync(verdictPath)).toBe(false);
151
+ });
152
+
153
+ it("does not throw when verdict file does not exist", async () => {
154
+ // Should not throw
155
+ await expect(cleanupVerdict(workdir)).resolves.toBeUndefined();
156
+ });
157
+
158
+ it("does not throw when called twice (second call is a no-op)", async () => {
159
+ const verdictPath = path.join(workdir, ".nax-verifier-verdict.json");
160
+ await writeFile(verdictPath, JSON.stringify(makeVerdict()), "utf-8");
161
+
162
+ await cleanupVerdict(workdir);
163
+ // Second call — file already gone
164
+ await expect(cleanupVerdict(workdir)).resolves.toBeUndefined();
165
+ });
166
+ });
167
+
168
+ // ─────────────────────────────────────────────────────────────────────────────
169
+ // categorizeVerdict — with valid verdict
170
+ // ─────────────────────────────────────────────────────────────────────────────
171
+
172
+ describe("categorizeVerdict — with verdict", () => {
173
+ it("returns success when approved=true", () => {
174
+ const result = categorizeVerdict(makeVerdict({ approved: true }), false);
175
+ expect(result.success).toBe(true);
176
+ expect(result.failureCategory).toBeUndefined();
177
+ });
178
+
179
+ it("returns success when approved=true regardless of testsPass", () => {
180
+ const result = categorizeVerdict(makeVerdict({ approved: true }), false);
181
+ expect(result.success).toBe(true);
182
+ });
183
+
184
+ it("returns verifier-rejected when illegitimate test modifications detected", () => {
185
+ const verdict = makeVerdict({
186
+ approved: false,
187
+ testModifications: {
188
+ detected: true,
189
+ files: ["test/foo.test.ts"],
190
+ legitimate: false,
191
+ reasoning: "Assertions loosened to hide failures",
192
+ },
193
+ });
194
+
195
+ const result = categorizeVerdict(verdict, true);
196
+ expect(result.success).toBe(false);
197
+ expect(result.failureCategory).toBe("verifier-rejected");
198
+ expect(result.reviewReason).toContain("illegitimate test modifications");
199
+ expect(result.reviewReason).toContain("test/foo.test.ts");
200
+ });
201
+
202
+ it("returns tests-failing when tests are not all passing", () => {
203
+ const verdict = makeVerdict({
204
+ approved: false,
205
+ tests: { allPassing: false, passCount: 8, failCount: 3 },
206
+ testModifications: { detected: false, files: [], legitimate: true, reasoning: "" },
207
+ });
208
+
209
+ const result = categorizeVerdict(verdict, false);
210
+ expect(result.success).toBe(false);
211
+ expect(result.failureCategory).toBe("tests-failing");
212
+ expect(result.reviewReason).toContain("3 failure(s)");
213
+ });
214
+
215
+ it("returns verifier-rejected when acceptance criteria not met", () => {
216
+ const verdict = makeVerdict({
217
+ approved: false,
218
+ tests: { allPassing: true, passCount: 10, failCount: 0 },
219
+ testModifications: { detected: false, files: [], legitimate: true, reasoning: "" },
220
+ acceptanceCriteria: {
221
+ allMet: false,
222
+ criteria: [
223
+ { criterion: "Feature works", met: true },
224
+ { criterion: "Error handling", met: false },
225
+ ],
226
+ },
227
+ });
228
+
229
+ const result = categorizeVerdict(verdict, true);
230
+ expect(result.success).toBe(false);
231
+ expect(result.failureCategory).toBe("verifier-rejected");
232
+ expect(result.reviewReason).toContain("Acceptance criteria not met");
233
+ expect(result.reviewReason).toContain("Error handling");
234
+ });
235
+
236
+ it("returns verifier-rejected when quality rating is poor", () => {
237
+ const verdict = makeVerdict({
238
+ approved: false,
239
+ tests: { allPassing: true, passCount: 10, failCount: 0 },
240
+ testModifications: { detected: false, files: [], legitimate: true, reasoning: "" },
241
+ acceptanceCriteria: {
242
+ allMet: true,
243
+ criteria: [{ criterion: "Feature works", met: true }],
244
+ },
245
+ quality: { rating: "poor", issues: ["Security vulnerability", "Memory leak"] },
246
+ });
247
+
248
+ const result = categorizeVerdict(verdict, true);
249
+ expect(result.success).toBe(false);
250
+ expect(result.failureCategory).toBe("verifier-rejected");
251
+ expect(result.reviewReason).toContain("Poor code quality");
252
+ expect(result.reviewReason).toContain("Security vulnerability");
253
+ });
254
+
255
+ it("returns verifier-rejected for catch-all (approved=false, no matching reason)", () => {
256
+ const verdict = makeVerdict({
257
+ approved: false,
258
+ tests: { allPassing: true, passCount: 10, failCount: 0 },
259
+ testModifications: { detected: false, files: [], legitimate: true, reasoning: "" },
260
+ acceptanceCriteria: { allMet: true, criteria: [] },
261
+ quality: { rating: "acceptable", issues: [] },
262
+ reasoning: "Something else went wrong",
263
+ });
264
+
265
+ const result = categorizeVerdict(verdict, true);
266
+ expect(result.success).toBe(false);
267
+ expect(result.failureCategory).toBe("verifier-rejected");
268
+ expect(result.reviewReason).toContain("Something else went wrong");
269
+ });
270
+
271
+ it("legitimate test modifications do not trigger verifier-rejected", () => {
272
+ const verdict = makeVerdict({
273
+ approved: false,
274
+ tests: { allPassing: false, passCount: 5, failCount: 2 },
275
+ testModifications: {
276
+ detected: true,
277
+ files: ["test/bar.test.ts"],
278
+ legitimate: true,
279
+ reasoning: "Tests updated to fix incorrect expected values",
280
+ },
281
+ acceptanceCriteria: { allMet: true, criteria: [] },
282
+ quality: { rating: "good", issues: [] },
283
+ });
284
+
285
+ // Should fall through to tests-failing (not illegitimate mod path)
286
+ const result = categorizeVerdict(verdict, false);
287
+ expect(result.failureCategory).toBe("tests-failing");
288
+ });
289
+
290
+ it("not-detected test modifications do not trigger verifier-rejected for test mods", () => {
291
+ const verdict = makeVerdict({
292
+ approved: false,
293
+ tests: { allPassing: false, passCount: 3, failCount: 4 },
294
+ testModifications: {
295
+ detected: false,
296
+ files: [],
297
+ legitimate: true,
298
+ reasoning: "",
299
+ },
300
+ });
301
+
302
+ const result = categorizeVerdict(verdict, false);
303
+ expect(result.failureCategory).toBe("tests-failing");
304
+ });
305
+ });
306
+
307
+ // ─────────────────────────────────────────────────────────────────────────────
308
+ // categorizeVerdict — null verdict (fallback)
309
+ // ─────────────────────────────────────────────────────────────────────────────
310
+
311
+ describe("categorizeVerdict — null verdict (fallback)", () => {
312
+ it("returns success when null verdict + testsPass=true", () => {
313
+ const result = categorizeVerdict(null, true);
314
+ expect(result.success).toBe(true);
315
+ expect(result.failureCategory).toBeUndefined();
316
+ });
317
+
318
+ it("returns tests-failing when null verdict + testsPass=false", () => {
319
+ const result = categorizeVerdict(null, false);
320
+ expect(result.success).toBe(false);
321
+ expect(result.failureCategory).toBe("tests-failing");
322
+ expect(result.reviewReason).toContain("no verdict file");
323
+ });
324
+ });