@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,125 @@
1
+ # Fix Plan: Configurable LLM Routing Mode
2
+
3
+ **Date:** 2026-02-21
4
+ **Branch:** `feat/v0.9-routing-mode`
5
+ **Issue:** #2
6
+ **Base:** `master` (`b459e9f`)
7
+
8
+ ## Context
9
+
10
+ Current `batchMode: boolean` is a binary on/off. Issue #2 replaces it with
11
+ `mode: "one-shot" | "per-story" | "hybrid"` for fine-grained control.
12
+
13
+ **Mode semantics:**
14
+ - `one-shot`: batch-route ALL pending stories once at run start. If a story
15
+ is missing from cache at execution time, use keyword fallback (no new LLM call).
16
+ Minimises Claude sessions spawned (1 total). Eliminates hook noise.
17
+ - `per-story`: route each story individually just before execution.
18
+ Current behaviour when `batchMode: false`. Max LLM calls = N stories.
19
+ - `hybrid` (DEFAULT): batch-route upfront like one-shot, but on story
20
+ retry/failure, re-route that story individually. Best quality + cost balance.
21
+
22
+ **Problem solved:** With LLM routing, Run H spawned 9+ separate Claude sessions
23
+ for routing (one per story) causing hook noise and extra cost. One-shot or hybrid
24
+ reduces this to 1 batch call.
25
+
26
+ ## Phase 1: Config Schema
27
+
28
+ ### Fix 1.1: Replace batchMode with mode enum
29
+ **File:** `src/config/schema.ts`
30
+ **Change:**
31
+ - In `LlmRoutingConfig` interface: remove `batchMode?: boolean`, add `mode?: "one-shot" | "per-story" | "hybrid"`
32
+ - In Zod schema: replace `batchMode: z.boolean().optional()` with
33
+ `mode: z.enum(["one-shot", "per-story", "hybrid"]).optional()`
34
+ - Default value: `"hybrid"` (applied in defaults/config resolver)
35
+
36
+ ### Fix 1.2: Update config defaults
37
+ **File:** `src/config/defaults.ts` (or wherever defaults are set)
38
+ **Change:** Set `routing.llm.mode` default to `"hybrid"`.
39
+
40
+ ### Fix 1.3: Backward compat shim
41
+ **File:** `src/config/resolver.ts` (or schema.ts transform)
42
+ **Change:** If old `batchMode: true` is present, map to `mode: "one-shot"`.
43
+ If `batchMode: false`, map to `mode: "per-story"`. Log deprecation warning.
44
+
45
+ **Commit:** `feat(config): replace routing.llm.batchMode with routing.llm.mode enum`
46
+
47
+ ## Phase 2: LLM Strategy — One-Shot Strict Cache
48
+
49
+ ### Fix 2.1: Add one-shot cache-miss behaviour
50
+ **File:** `src/routing/strategies/llm.ts`
51
+ **Change:** In `llmStrategy.route()` (the per-story routing call), check if mode
52
+ is `one-shot`. If so and the story is NOT in cache → return keyword fallback
53
+ result immediately without making a new LLM call.
54
+
55
+ ```typescript
56
+ // In llmStrategy.route()
57
+ if (config.routing.llm?.mode === "one-shot" && cachedDecisions.has(story.id)) {
58
+ return cachedDecisions.get(story.id)!;
59
+ }
60
+ if (config.routing.llm?.mode === "one-shot") {
61
+ // Cache miss in one-shot mode — fall back to keyword, no new LLM call
62
+ return keywordStrategy.route(context);
63
+ }
64
+ ```
65
+
66
+ ### Fix 2.2: Export mode helper
67
+ **File:** `src/routing/strategies/llm.ts`
68
+ **Change:** Add `getCacheSize(): number` export for test verification.
69
+
70
+ **Commit:** `feat(routing): one-shot mode skips per-story LLM calls on cache miss`
71
+
72
+ ## Phase 3: Runner — Wire Mode to Batch Trigger
73
+
74
+ ### Fix 3.1: Update tryLlmBatchRoute guard
75
+ **File:** `src/execution/runner.ts`
76
+ **Change:** Replace the `batchMode` guard with mode check:
77
+ ```typescript
78
+ // OLD:
79
+ if (config.routing.strategy !== "llm" || !config.routing.llm?.batchMode ...) return;
80
+ // NEW:
81
+ const mode = config.routing.llm?.mode ?? "hybrid";
82
+ if (config.routing.strategy !== "llm" || mode === "per-story" ...) return;
83
+ ```
84
+
85
+ ### Fix 3.2: Hybrid re-route on failure
86
+ **File:** `src/execution/runner.ts`
87
+ **Change:** In story retry logic, when mode is `hybrid` and story failed:
88
+ - Call `llmRouteBatch([story], ...)` to re-route just that story
89
+ - This invalidates and refreshes its cache entry before the next attempt
90
+ Look for where stories are retried (error handling after agent run) and inject the re-route call.
91
+
92
+ ### Fix 3.3: Log mode at run start
93
+ **File:** `src/execution/runner.ts`
94
+ **Change:** In the run-start logging block, include `routingMode` in the log entry.
95
+
96
+ **Commit:** `feat(runner): wire routing mode to batch trigger and hybrid re-route`
97
+
98
+ ## Phase 4: Tests
99
+
100
+ ### Fix 4.1: Config schema tests
101
+ **Change:** Test `mode` enum is accepted (`one-shot`, `per-story`, `hybrid`).
102
+ Test backward compat: `batchMode: true` → `mode: "one-shot"`.
103
+ Test default is `"hybrid"`.
104
+
105
+ ### Fix 4.2: LLM strategy tests
106
+ **Change:** Test one-shot mode: after `routeBatch()`, a cache-miss story returns
107
+ keyword fallback without making another LLM call.
108
+ Test per-story mode: each story triggers individual LLM call.
109
+ Test hybrid: upfront batch + per-story call on cache miss.
110
+
111
+ ### Fix 4.3: Runner integration tests (if any)
112
+ **Change:** Verify `tryLlmBatchRoute` is called on `one-shot`/`hybrid` but not `per-story`.
113
+
114
+ **Commit:** `test: add tests for routing mode config and one-shot/hybrid behaviour`
115
+
116
+ ## Test Strategy
117
+ - Mode: test-after
118
+ - Run `bun test` after each phase
119
+ - Backward compat: existing configs with `batchMode: true` must still work
120
+
121
+ ## Commits
122
+ 1. `feat(config): replace routing.llm.batchMode with routing.llm.mode enum`
123
+ 2. `feat(routing): one-shot mode skips per-story LLM calls on cache miss`
124
+ 3. `feat(runner): wire routing mode to batch trigger and hybrid re-route`
125
+ 4. `test: add tests for routing mode config and one-shot/hybrid behaviour`
@@ -0,0 +1,379 @@
1
+ # v0.9 Implementation Review
2
+
3
+ **Date:** 2026-02-21
4
+ **Reviewer:** code-reviewer agent (Opus 4.6)
5
+ **Branches:** `feat/v0.9-relevantfiles-split` (Issue #1), `feat/v0.9-routing-mode` (Issue #2)
6
+ **Test Status:** 710 pass, 2 skip, 6 fail (out of 718). All failures are pre-existing.
7
+
8
+ ---
9
+
10
+ ## Overall Grade: B+ (84/100)
11
+
12
+ | Category | Score | Max | Notes |
13
+ |--------------------|-------|-----|--------------------------------------------------|
14
+ | Security | 18 | 20 | No injection risks. Minor: mutation of raw config object in compat shim. |
15
+ | Reliability | 16 | 20 | Global config compat shim missing. Edge case with empty array fallback well-handled. |
16
+ | API Design | 18 | 20 | Clean resolver pattern. `LlmRoutingMode` enum well-designed. Minor JSDoc gap in `classifier.ts` bridge. |
17
+ | Code Quality | 16 | 20 | Good test coverage. `runner.ts` at 921 lines (exceeds 800 guideline). Mutation in `applyCachedRouting`. |
18
+ | Best Practices | 16 | 20 | Bun-native APIs used throughout. Immutability violated in 2 places. LLM prompt not updated in classifier. |
19
+
20
+ ---
21
+
22
+ ## Findings
23
+
24
+ ### CRITICAL
25
+
26
+ None.
27
+
28
+ ### HIGH
29
+
30
+ #### [HIGH-1] `applyBatchModeCompat` not applied to global config
31
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/config/loader.ts:99-102`
32
+ **Branch:** `feat/v0.9-routing-mode`
33
+
34
+ The backward compatibility shim `applyBatchModeCompat()` is called on `projConf` (line 111) but NOT on `globalConf` (line 100-101). If a user has `routing.llm.batchMode: true` in their global config (`~/.nax/config.json`) but no project config, the shim never runs.
35
+
36
+ After `deepMerge` with defaults (which now set `mode: "hybrid"`), the `"mode" in llm` guard in `applyBatchModeCompat` would prevent mapping even if the shim were called later. But the root issue is that global-only users with `batchMode` will silently get `mode: "hybrid"` from defaults instead of the expected `mode: "one-shot"`.
37
+
38
+ **Impact:** Users with global-only config using deprecated `batchMode: true` will get unexpected routing behavior. Silent behavior change with no deprecation warning.
39
+
40
+ **Fix:**
41
+ ```typescript
42
+ // In loadConfig(), after loading global config:
43
+ const globalConf = await loadJsonFile<Record<string, unknown>>(globalConfigPath());
44
+ if (globalConf) {
45
+ applyBatchModeCompat(globalConf); // <-- Add this line
46
+ rawConfig = deepMerge(rawConfig, globalConf);
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ #### [HIGH-2] `applyBatchModeCompat` mutates its argument
53
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/config/loader.ts:76-91`
54
+ **Branch:** `feat/v0.9-routing-mode`
55
+
56
+ The function mutates the `conf` object in-place (`llm.mode = ...`). While this works because the caller does not reuse the raw config, it violates the project's immutability guideline (from CLAUDE.md: "Immutable patterns -- avoid mutation"). The `deepMerge` function correctly uses spread operators; the compat shim should follow the same pattern.
57
+
58
+ **Impact:** Low immediate risk (caller does not reuse), but sets a bad precedent and makes the code harder to reason about. Could cause subtle bugs if `loadConfig` is refactored to reuse partial configs.
59
+
60
+ **Fix:**
61
+ Return a new object instead of mutating, or document the intentional mutation:
62
+ ```typescript
63
+ function applyBatchModeCompat(conf: Record<string, unknown>): Record<string, unknown> {
64
+ const routing = conf.routing as Record<string, unknown> | undefined;
65
+ const llm = routing?.llm as Record<string, unknown> | undefined;
66
+ if (llm && "batchMode" in llm && !("mode" in llm)) {
67
+ const batchMode = llm.batchMode;
68
+ if (typeof batchMode === "boolean") {
69
+ return {
70
+ ...conf,
71
+ routing: {
72
+ ...routing,
73
+ llm: {
74
+ ...llm,
75
+ mode: batchMode ? "one-shot" : "per-story",
76
+ },
77
+ },
78
+ };
79
+ }
80
+ }
81
+ return conf;
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ #### [HIGH-3] `applyCachedRouting` mutates the routing object
88
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/execution/runner.ts:77-86`
89
+ **Branch:** `feat/v0.9-routing-mode` (pre-existing, but worsened by new code paths)
90
+
91
+ The function directly mutates `routing.complexity`, `routing.modelTier`, and `routing.testStrategy`. This is called from the main loop and the routing object is reused as `pipelineContext.routing`. If any downstream code assumes the routing object is the original from `routeTask()`, it will get incorrect values.
92
+
93
+ **Impact:** The mutation is currently "safe" because the routing object is scoped to a single iteration, but it makes the code fragile. The new hybrid re-route call at line 340 uses the `story` object (not the routing), so no immediate bug, but the pattern is error-prone.
94
+
95
+ **Fix:**
96
+ ```typescript
97
+ function applyCachedRouting(
98
+ routing: ReturnType<typeof routeTask>,
99
+ story: UserStory,
100
+ config: NaxConfig
101
+ ): ReturnType<typeof routeTask> {
102
+ if (!story.routing) return routing;
103
+ return {
104
+ ...routing,
105
+ ...(story.routing.complexity && {
106
+ complexity: story.routing.complexity,
107
+ modelTier: config.autoMode.complexityRouting[story.routing.complexity] ?? "balanced",
108
+ }),
109
+ ...(story.routing.testStrategy && {
110
+ testStrategy: story.routing.testStrategy,
111
+ }),
112
+ };
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ### MEDIUM
119
+
120
+ #### [MED-1] `runner.ts` exceeds 800-line file size limit (921 lines)
121
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/execution/runner.ts`
122
+ **Branch:** `feat/v0.9-routing-mode` (pre-existing, worsened by +12 lines)
123
+
124
+ The file was already at 909 lines before this change. The new routing mode code adds 12 more lines (two `tryLlmBatchRoute` calls and `routingMode` variable). The CLAUDE.md guidelines state "200-400 lines typical, 800 max per file." This file is now 15% over limit.
125
+
126
+ **Impact:** Reduced readability and maintainability. The acceptance retry loop (lines 669-853) alone is ~184 lines and could be extracted.
127
+
128
+ **Fix:** Extract the acceptance retry loop into `src/execution/acceptance-runner.ts`. This is already identified as STYLE-1 in the project backlog.
129
+
130
+ ---
131
+
132
+ #### [MED-2] LLM prompt in classifier still uses `relevantFiles` instead of `contextFiles`
133
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/analyze/classifier.ts:217`
134
+ **Branch:** `feat/v0.9-relevantfiles-split`
135
+
136
+ The LLM prompt template still instructs the model to output `"relevantFiles"` in its JSON response. The parser correctly maps `rawItem.relevantFiles` to `contextFiles`, creating an implicit bridge. However:
137
+ 1. This bridge is undocumented -- a future developer may update the prompt without updating the parser or vice versa.
138
+ 2. The raw JSON type at line 21 still declares `relevantFiles: unknown`, not `contextFiles`.
139
+
140
+ **Impact:** Confusion for future maintainers. If someone updates the prompt to use `contextFiles`, the parser will break (it reads `rawItem.relevantFiles`).
141
+
142
+ **Fix:** Either:
143
+ - (a) Update the prompt to output `contextFiles` and update the parser accordingly.
144
+ - (b) Add a code comment documenting the intentional bridge: `// BRIDGE: LLM prompt outputs "relevantFiles", mapped to contextFiles here. Update both together.`
145
+
146
+ ---
147
+
148
+ #### [MED-3] LLM prompt in `claude.ts` agent updated but no backward compat for existing LLM outputs
149
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/agents/claude.ts:538-541`
150
+ **Branch:** `feat/v0.9-relevantfiles-split`
151
+
152
+ The `decompose()` method now reads `item.contextFiles` from the LLM response. If an older cached LLM response or a different LLM version outputs `relevantFiles`, the parser will set `contextFiles: []` (empty array from the `Array.isArray` fallback). The `getContextFiles()` resolver would then return `[]` instead of falling back to `relevantFiles`.
153
+
154
+ **Impact:** Context injection silently breaks for any LLM response that uses the old field name. The probability is low since decompose is called interactively, but if responses are cached or replayed, context will be lost.
155
+
156
+ **Fix:** Parse both fields with fallback:
157
+ ```typescript
158
+ contextFiles: Array.isArray(item.contextFiles)
159
+ ? item.contextFiles
160
+ : Array.isArray(item.relevantFiles)
161
+ ? item.relevantFiles
162
+ : [],
163
+ ```
164
+
165
+ ---
166
+
167
+ #### [MED-4] Module-level mutable cache (`cachedDecisions`) with no eviction policy
168
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/routing/strategies/llm.ts:16`
169
+ **Branch:** `feat/v0.9-routing-mode` (pre-existing, now more relevant due to one-shot mode reliance on cache)
170
+
171
+ The `cachedDecisions` Map grows without bound during a run. For one-shot mode, the cache is the primary data store -- if it is cleared unexpectedly (e.g., module reload in watch mode), all subsequent stories fall back to keyword routing.
172
+
173
+ **Impact:** Memory accumulation for very large PRDs. More importantly, one-shot mode's correctness depends on cache integrity.
174
+
175
+ **Fix:** Add a `maxCacheSize` guard or LRU eviction. Document the cache's role in one-shot mode.
176
+
177
+ ---
178
+
179
+ #### [MED-5] `batchMode: false` + `mode: "one-shot"` conflict not validated
180
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/config/schema.ts:458-465`
181
+ **Branch:** `feat/v0.9-routing-mode`
182
+
183
+ A user can set both `batchMode: false` and `mode: "one-shot"` in their config. The compat shim only maps `batchMode` when `mode` is absent (guard: `!("mode" in llm)`), so when both are set, `mode` wins. But there is no validation warning for the contradictory combination. This can confuse users during migration.
184
+
185
+ **Impact:** User confusion. `batchMode: false` semantically means "don't batch" but `mode: "one-shot"` means "batch everything once." No error or warning.
186
+
187
+ **Fix:** Add a Zod `.refine()` to warn when both fields are set with contradictory values:
188
+ ```typescript
189
+ const LlmRoutingConfigSchema = z.object({
190
+ // ...existing fields
191
+ }).refine((data) => {
192
+ if (data.batchMode !== undefined && data.mode !== undefined) {
193
+ // Both set - check for contradiction
194
+ const implied = data.batchMode ? "one-shot" : "per-story";
195
+ if (implied !== data.mode) {
196
+ // Could log warning here
197
+ }
198
+ }
199
+ return true;
200
+ });
201
+ ```
202
+
203
+ ---
204
+
205
+ #### [MED-6] Duplicate log lines in iteration start
206
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/execution/runner.ts:385-403`
207
+ **Branch:** `feat/v0.9-routing-mode` (pre-existing)
208
+
209
+ Lines 385-392 log `"Starting iteration N"` with event name `"execution"`, and lines 395-403 log the same message with event name `"iteration.start"`. Both contain overlapping data. This was pre-existing but is worth noting as it inflates structured log output.
210
+
211
+ **Impact:** Log noise. JSONL log files contain two near-identical entries per iteration.
212
+
213
+ ---
214
+
215
+ ### LOW
216
+
217
+ #### [LOW-1] `getCacheSize()` exposed for testing only
218
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/routing/strategies/llm.ts:24-26`
219
+ **Branch:** `feat/v0.9-routing-mode`
220
+
221
+ The function is exported solely for testing (`/** Get the current cache size (for testing) */`). While harmless, it widens the public API surface for a test utility. Consider using Bun's test mocking or accessing the cache through the module's test exports pattern instead.
222
+
223
+ **Impact:** Negligible. Minor API surface bloat.
224
+
225
+ ---
226
+
227
+ #### [LOW-2] `fix-generator.ts` uses `contextFiles: []` instead of leaving field undefined
228
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/acceptance/fix-generator.ts:361`
229
+ **Branch:** `feat/v0.9-relevantfiles-split`
230
+
231
+ Setting `contextFiles: []` explicitly means `getContextFiles()` returns `[]` even if a parent process later sets `relevantFiles` on the story. This is technically correct (fix stories should not inherit context from parent stories), but the behavior depends on `[]` being truthy for `??` -- which is correct in JavaScript but could confuse developers unfamiliar with nullish coalescing semantics.
232
+
233
+ **Impact:** Negligible. Behavior is correct.
234
+
235
+ ---
236
+
237
+ #### [LOW-3] Missing JSDoc on `LlmRoutingMode` type
238
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/config/schema.ts:233-234`
239
+ **Branch:** `feat/v0.9-routing-mode`
240
+
241
+ The type has a one-line comment (`/** LLM routing mode */`) but no documentation of what each enum value means. The detailed documentation is on the `mode` field in `LlmRoutingConfig`, but the type itself should also document its values for consumers who import it directly.
242
+
243
+ **Impact:** Minor developer experience issue.
244
+
245
+ ---
246
+
247
+ #### [LOW-4] Test file backs up and restores global config
248
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/test/config-loader.test.ts:30-39`
249
+ **Branch:** `feat/v0.9-routing-mode`
250
+
251
+ The test manipulates the real global config at `~/.nax/config.json` using rename+restore. While the cleanup is in `afterEach`, a crash between backup and restore could leave the user's config in a broken state. Consider using a mock or environment variable to redirect the config path during tests.
252
+
253
+ **Impact:** Test safety. If test runner crashes, user's global config is a backup file.
254
+
255
+ ---
256
+
257
+ #### [LOW-5] Two empty lines between `deepMerge` and `applyBatchModeCompat`
258
+ **File:** `/Users/subrinaai/Desktop/workspace/subrina-coder/projects/nax/repos/nax/src/config/loader.ts:73-74`
259
+ **Branch:** `feat/v0.9-routing-mode`
260
+
261
+ Minor formatting inconsistency -- double blank line where the rest of the file uses single blank lines between functions.
262
+
263
+ **Impact:** Cosmetic.
264
+
265
+ ---
266
+
267
+ ## Backward Compatibility Assessment
268
+
269
+ ### Branch 1: `feat/v0.9-relevantfiles-split`
270
+
271
+ | Scenario | Behavior | Correct? |
272
+ |----------|----------|----------|
273
+ | `contextFiles` set, `relevantFiles` unset | Uses `contextFiles` for context | Yes |
274
+ | `contextFiles` unset, `relevantFiles` set | Falls back to `relevantFiles` for context | Yes |
275
+ | Both `contextFiles` and `relevantFiles` set | Prefers `contextFiles` (nullish coalescing) | Yes |
276
+ | Neither set | Returns empty array | Yes |
277
+ | `expectedFiles` set | Uses for asset verification | Yes |
278
+ | `expectedFiles` unset, `relevantFiles` set | Returns empty array (no fallback) | Yes, by design |
279
+ | `expectedFiles: []` | Returns empty array (skips verification) | Yes |
280
+ | LLM outputs `relevantFiles` in classify response | Mapped to `contextFiles` via bridge | Yes, but undocumented (MED-2) |
281
+ | LLM outputs `relevantFiles` in decompose response | **Silently drops data** | No (MED-3) |
282
+
283
+ ### Branch 2: `feat/v0.9-routing-mode`
284
+
285
+ | Scenario | Behavior | Correct? |
286
+ |----------|----------|----------|
287
+ | `mode: "one-shot"` | Batch upfront, keyword fallback on miss | Yes |
288
+ | `mode: "per-story"` | Individual LLM call per story | Yes |
289
+ | `mode: "hybrid"` | Batch upfront + re-route on escalation | Yes |
290
+ | `mode` unset | Defaults to `"hybrid"` | Yes |
291
+ | `batchMode: true`, `mode` unset (project config) | Maps to `mode: "one-shot"` | Yes |
292
+ | `batchMode: false`, `mode` unset (project config) | Maps to `mode: "per-story"` | Yes |
293
+ | `batchMode: true`, `mode: "per-story"` | `mode` wins (explicit takes precedence) | Yes |
294
+ | `batchMode: true`, `mode` unset (global config only) | **Silently ignored, gets "hybrid"** | No (HIGH-1) |
295
+ | `batchMode: "yes"` (invalid type) | Zod validation rejects | Yes |
296
+ | `mode: "ultra-batch"` (invalid value) | Zod validation rejects | Yes |
297
+
298
+ ---
299
+
300
+ ## Test Coverage Assessment
301
+
302
+ ### Branch 1: `feat/v0.9-relevantfiles-split`
303
+
304
+ | Test File | Tests Added | Coverage |
305
+ |-----------|-------------|----------|
306
+ | `test/prd-resolvers.test.ts` | 12 tests | Excellent: covers all fallback/precedence combinations |
307
+ | `test/verification-asset-check.test.ts` | 9 tests | Excellent: covers opt-in semantics, legacy compat, dogfood scenarios |
308
+ | `test/context-verification-integration.test.ts` | 6 tests | Good: end-to-end with temp files |
309
+ | `test/context-integration.test.ts` | 3 tests added | Good: contextFiles, relevantFiles fallback, no-files |
310
+ | `test/context.test.ts` | 3 tests added, 6 updated | Good: preference, fallback, limits |
311
+ | `test/analyze-classifier.test.ts` | 1 updated | Adequate |
312
+
313
+ **Missing coverage:**
314
+ - No test for the `classifier.ts` bridge pattern where LLM outputs `relevantFiles` and it maps to `contextFiles`
315
+ - No test for `claude.ts` decompose response with old `relevantFiles` field name
316
+
317
+ ### Branch 2: `feat/v0.9-routing-mode`
318
+
319
+ | Test File | Tests Added | Coverage |
320
+ |-----------|-------------|----------|
321
+ | `test/config.test.ts` | 6 tests | Good: all three modes + invalid + deprecated |
322
+ | `test/config-loader.test.ts` | 4 tests | Good: compat shim, precedence, invalid type, defaults |
323
+ | `test/routing/llm-strategy.test.ts` | 5 tests | Good: one-shot miss, one-shot hit, per-story, hybrid, default |
324
+
325
+ **Missing coverage:**
326
+ - No test for `batchMode` in global config (HIGH-1)
327
+ - No test for hybrid re-route in `runner.ts` (lines 339-341, 591-593) -- these are integration paths
328
+ - No test for `batchMode: false` mapping to `mode: "per-story"` in isolation (the loader test only covers `batchMode: true`)
329
+
330
+ ---
331
+
332
+ ## Logic Correctness
333
+
334
+ ### `getContextFiles()` and `getExpectedFiles()` (Branch 1)
335
+
336
+ The semantic split is correct and well-reasoned:
337
+ - **contextFiles** = input to the agent (pre-execution context injection). Falls back to `relevantFiles` because existing PRDs should continue to provide context.
338
+ - **expectedFiles** = output verification gate (post-execution asset check). Does NOT fall back because the old `relevantFiles` field was LLM-generated and unreliable for asset verification (documented in dogfood Run F findings).
339
+
340
+ The `??` (nullish coalescing) operator is the correct choice here. An empty array `[]` is an explicit "no files" signal and should not trigger fallback. This is tested in `test/prd-resolvers.test.ts`.
341
+
342
+ ### Routing Mode Logic (Branch 2)
343
+
344
+ The three-mode design is correct:
345
+
346
+ 1. **one-shot**: `tryLlmBatchRoute` runs at startup (line 208). On individual `route()`, cache hit returns cached decision; cache miss falls back to `keywordStrategy.route()` without an LLM call (line 380-386). This is cost-optimal for stable PRDs.
347
+
348
+ 2. **per-story**: `tryLlmBatchRoute` returns early when `mode === "per-story"` (line 60). Each `route()` call triggers an individual LLM call (lines 388-409). This is quality-optimal for dynamic PRDs.
349
+
350
+ 3. **hybrid**: `tryLlmBatchRoute` runs at startup (same as one-shot). On cache miss during `route()`, an individual LLM call is made (same as per-story). On escalation, `tryLlmBatchRoute` is called again for the escalated story (lines 339-341, 591-593). This combines upfront batching with retry precision.
351
+
352
+ The `mode ?? "hybrid"` default is applied consistently in all three locations: `tryLlmBatchRoute` (line 59), `llmStrategy.route` (line 364), and `run()` (line 144).
353
+
354
+ ---
355
+
356
+ ## Priority Fix Order
357
+
358
+ 1. **HIGH-1**: Apply `applyBatchModeCompat` to global config -- prevents silent behavior regression for global-config-only users.
359
+ 2. **MED-3**: Add fallback for `relevantFiles` in `claude.ts` decompose parser -- prevents data loss from older LLM responses.
360
+ 3. **HIGH-2**: Refactor `applyBatchModeCompat` to return new object -- aligns with project immutability guidelines.
361
+ 4. **HIGH-3**: Refactor `applyCachedRouting` to return new object -- same rationale.
362
+ 5. **MED-2**: Document or fix the classifier bridge pattern -- prevents future maintainer confusion.
363
+ 6. **MED-5**: Add validation for contradictory `batchMode` + `mode` combinations.
364
+ 7. **MED-1**: Extract acceptance retry loop from `runner.ts` -- file size reduction.
365
+ 8. **LOW-4**: Mock global config path in tests instead of manipulating real file.
366
+
367
+ ---
368
+
369
+ ## Verdict
370
+
371
+ **Approve with conditions (HIGH issues must be fixed before merge)**
372
+
373
+ The v0.9 changes are well-structured with clear semantics, comprehensive test coverage, and proper backward compatibility design. The resolver function pattern (`getContextFiles`, `getExpectedFiles`) is clean and the three-mode routing enum is well-thought-out.
374
+
375
+ However, three HIGH-priority issues need resolution before merge:
376
+ 1. Global config compat shim gap (HIGH-1) is a user-facing regression risk
377
+ 2. Two mutation patterns (HIGH-2, HIGH-3) violate project guidelines
378
+
379
+ Once these are fixed, the changes are ready for v0.9.0 release.