@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,229 @@
1
+ /**
2
+ * CLI Integration Tests for `nax config` Default View
3
+ *
4
+ * Tests the full end-to-end flow of running `nax config` without flags
5
+ * via the CLI entry point (bin/nax.ts).
6
+ */
7
+
8
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
9
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+
13
+ describe("nax config (default view) - CLI integration", () => {
14
+ let tempDir: string;
15
+ let originalCwd: string;
16
+
17
+ beforeEach(() => {
18
+ // Create temp directory
19
+ tempDir = mkdtempSync(join(tmpdir(), "nax-config-cli-test-"));
20
+ originalCwd = process.cwd();
21
+ });
22
+
23
+ afterEach(() => {
24
+ // Cleanup
25
+ process.chdir(originalCwd);
26
+ rmSync(tempDir, { recursive: true, force: true });
27
+ });
28
+
29
+ test("prints effective merged config as JSON with header", async () => {
30
+ process.chdir(tempDir);
31
+
32
+ // Run `nax config` command
33
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
34
+ cwd: tempDir,
35
+ stdout: "pipe",
36
+ stderr: "pipe",
37
+ });
38
+
39
+ const output = await new Response(proc.stdout).text();
40
+ const exitCode = await proc.exited;
41
+
42
+ expect(exitCode).toBe(0);
43
+
44
+ // Should have header
45
+ expect(output).toContain("// nax Configuration");
46
+ expect(output).toContain("// Resolution order: defaults → global → project → CLI overrides");
47
+ expect(output).toContain("// Global config:");
48
+ expect(output).toContain("// Project config:");
49
+
50
+ // Should have valid JSON after header
51
+ const lines = output.split("\n");
52
+ const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
53
+ expect(jsonStartIndex).toBeGreaterThan(0);
54
+
55
+ const jsonOutput = lines.slice(jsonStartIndex).join("\n");
56
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
57
+
58
+ const parsed = JSON.parse(jsonOutput);
59
+ expect(parsed.version).toBe(1);
60
+ expect(parsed.models).toBeDefined();
61
+ expect(parsed.autoMode).toBeDefined();
62
+ expect(parsed.execution).toBeDefined();
63
+ });
64
+
65
+ test("shows global config path in header", async () => {
66
+ process.chdir(tempDir);
67
+
68
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
69
+ cwd: tempDir,
70
+ stdout: "pipe",
71
+ stderr: "pipe",
72
+ });
73
+
74
+ const output = await new Response(proc.stdout).text();
75
+ const exitCode = await proc.exited;
76
+
77
+ expect(exitCode).toBe(0);
78
+
79
+ // Should show global config path (may be "not found" or actual path)
80
+ expect(output).toContain("// Global config:");
81
+ });
82
+
83
+ test("shows (not found) for missing project config", async () => {
84
+ // Use a directory without nax/config.json
85
+ const isolatedDir = join(tempDir, "isolated");
86
+ mkdirSync(isolatedDir, { recursive: true });
87
+ process.chdir(isolatedDir);
88
+
89
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
90
+ cwd: isolatedDir,
91
+ stdout: "pipe",
92
+ stderr: "pipe",
93
+ });
94
+
95
+ const output = await new Response(proc.stdout).text();
96
+ const exitCode = await proc.exited;
97
+
98
+ expect(exitCode).toBe(0);
99
+
100
+ // Should show project config as not found
101
+ expect(output).toContain("// Project config: (not found)");
102
+ });
103
+
104
+ test("shows project config path when present", async () => {
105
+ // Create project config
106
+ const naxDir = join(tempDir, "nax");
107
+ mkdirSync(naxDir, { recursive: true });
108
+ writeFileSync(
109
+ join(naxDir, "config.json"),
110
+ JSON.stringify({
111
+ execution: {
112
+ maxIterations: 20,
113
+ },
114
+ }),
115
+ );
116
+
117
+ process.chdir(tempDir);
118
+
119
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
120
+ cwd: tempDir,
121
+ stdout: "pipe",
122
+ stderr: "pipe",
123
+ });
124
+
125
+ const output = await new Response(proc.stdout).text();
126
+ const exitCode = await proc.exited;
127
+
128
+ expect(exitCode).toBe(0);
129
+
130
+ // Should show project config path
131
+ expect(output).toContain("// Project config:");
132
+ expect(output).toContain("config.json");
133
+
134
+ // Should reflect merged config
135
+ const lines = output.split("\n");
136
+ const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
137
+ const jsonOutput = lines.slice(jsonStartIndex).join("\n");
138
+ const parsed = JSON.parse(jsonOutput);
139
+ expect(parsed.execution.maxIterations).toBe(20);
140
+ });
141
+
142
+ test("header precedes JSON output with blank line", async () => {
143
+ process.chdir(tempDir);
144
+
145
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
146
+ cwd: tempDir,
147
+ stdout: "pipe",
148
+ stderr: "pipe",
149
+ });
150
+
151
+ const output = await new Response(proc.stdout).text();
152
+ const exitCode = await proc.exited;
153
+
154
+ expect(exitCode).toBe(0);
155
+
156
+ const lines = output.split("\n");
157
+
158
+ // Find header and JSON start
159
+ const headerLineIndex = lines.findIndex((line) => line.includes("// nax Configuration"));
160
+ const jsonLineIndex = lines.findIndex((line) => line.startsWith("{"));
161
+
162
+ expect(headerLineIndex).toBeGreaterThanOrEqual(0);
163
+ expect(jsonLineIndex).toBeGreaterThan(headerLineIndex);
164
+
165
+ // Should have blank line between header and JSON
166
+ expect(lines[jsonLineIndex - 1]).toBe("");
167
+ });
168
+
169
+ test("JSON output is pretty-printed (indented)", async () => {
170
+ process.chdir(tempDir);
171
+
172
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
173
+ cwd: tempDir,
174
+ stdout: "pipe",
175
+ stderr: "pipe",
176
+ });
177
+
178
+ const output = await new Response(proc.stdout).text();
179
+ const exitCode = await proc.exited;
180
+
181
+ expect(exitCode).toBe(0);
182
+
183
+ // JSON should be pretty-printed with 2-space indentation
184
+ expect(output).toMatch(/"version": 1/);
185
+ expect(output).toMatch(/"models": \{/);
186
+ expect(output).toContain(" "); // Should have indentation
187
+ });
188
+
189
+ test("works when run from project subdirectory", async () => {
190
+ // Create project config
191
+ const naxDir = join(tempDir, "nax");
192
+ mkdirSync(naxDir, { recursive: true });
193
+ writeFileSync(
194
+ join(naxDir, "config.json"),
195
+ JSON.stringify({
196
+ execution: {
197
+ maxIterations: 30,
198
+ },
199
+ }),
200
+ );
201
+
202
+ // Create subdirectory and run from there
203
+ const subdir = join(tempDir, "src", "components");
204
+ mkdirSync(subdir, { recursive: true });
205
+ process.chdir(subdir);
206
+
207
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
208
+ cwd: subdir,
209
+ stdout: "pipe",
210
+ stderr: "pipe",
211
+ });
212
+
213
+ const output = await new Response(proc.stdout).text();
214
+ const exitCode = await proc.exited;
215
+
216
+ expect(exitCode).toBe(0);
217
+
218
+ // Should find project config by walking up
219
+ expect(output).toContain("// Project config:");
220
+ expect(output).toContain("config.json");
221
+
222
+ // Should reflect merged config
223
+ const lines = output.split("\n");
224
+ const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
225
+ const jsonOutput = lines.slice(jsonStartIndex).join("\n");
226
+ const parsed = JSON.parse(jsonOutput);
227
+ expect(parsed.execution.maxIterations).toBe(30);
228
+ });
229
+ });
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Config Command --diff Flag Integration Tests
3
+ *
4
+ * Tests for `nax config --diff` command that shows only fields where
5
+ * project config overrides the global config.
6
+ */
7
+
8
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
9
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { configCommand } from "../../src/cli/config";
13
+ import { loadConfig } from "../../src/config/loader";
14
+
15
+ describe("Config Command --diff", () => {
16
+ let tempDir: string;
17
+ let originalCwd: string;
18
+ let consoleOutput: string[];
19
+ let originalConsoleLog: typeof console.log;
20
+ let originalProcessExit: typeof process.exit;
21
+ let exitCode: number | undefined;
22
+
23
+ beforeEach(() => {
24
+ // Create temp directory
25
+ tempDir = mkdtempSync(join(tmpdir(), "nax-config-diff-test-"));
26
+ originalCwd = process.cwd();
27
+
28
+ // Capture console output
29
+ consoleOutput = [];
30
+ originalConsoleLog = console.log;
31
+ console.log = (...args: unknown[]) => {
32
+ consoleOutput.push(args.map((a) => String(a)).join(" "));
33
+ };
34
+
35
+ // Mock process.exit to capture exit code
36
+ exitCode = undefined;
37
+ originalProcessExit = process.exit;
38
+ process.exit = ((code?: number) => {
39
+ exitCode = code ?? 0;
40
+ throw new Error(`process.exit(${code ?? 0})`);
41
+ }) as typeof process.exit;
42
+ });
43
+
44
+ afterEach(() => {
45
+ // Restore console and process.exit
46
+ console.log = originalConsoleLog;
47
+ process.exit = originalProcessExit;
48
+
49
+ // Cleanup
50
+ process.chdir(originalCwd);
51
+ rmSync(tempDir, { recursive: true, force: true });
52
+ });
53
+
54
+ describe("No project config", () => {
55
+ test("shows 'No project config found' when no nax/config.json exists", async () => {
56
+ // Set up: no project config, just load defaults
57
+ process.chdir(tempDir);
58
+ const config = await loadConfig(tempDir);
59
+
60
+ // Run with --diff
61
+ await configCommand(config, { diff: true });
62
+
63
+ const output = consoleOutput.join("\n");
64
+ expect(output).toContain("No project config found — using global defaults");
65
+ });
66
+
67
+ test("shows 'No project config found' when nax/ dir exists but config.json doesn't", async () => {
68
+ // Create nax/ dir but no config.json
69
+ const naxDir = join(tempDir, "nax");
70
+ mkdirSync(naxDir, { recursive: true });
71
+
72
+ process.chdir(tempDir);
73
+ const config = await loadConfig(tempDir);
74
+
75
+ await configCommand(config, { diff: true });
76
+
77
+ const output = consoleOutput.join("\n");
78
+ expect(output).toContain("No project config found — using global defaults");
79
+ });
80
+ });
81
+
82
+ describe("Project config exists but identical to global", () => {
83
+ test("shows 'No differences' when project config matches global", async () => {
84
+ // Create empty project config (should merge to same as global)
85
+ const naxDir = join(tempDir, "nax");
86
+ mkdirSync(naxDir, { recursive: true });
87
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify({}));
88
+
89
+ process.chdir(tempDir);
90
+ const config = await loadConfig(tempDir);
91
+
92
+ await configCommand(config, { diff: true });
93
+
94
+ const output = consoleOutput.join("\n");
95
+ expect(output).toContain("No differences between project and global config");
96
+ });
97
+ });
98
+
99
+ describe("Project config overrides global", () => {
100
+ test("shows table with field path, project value, and global value", async () => {
101
+ // Create project config with a simple override
102
+ const naxDir = join(tempDir, "nax");
103
+ mkdirSync(naxDir, { recursive: true });
104
+ const projectConfig = {
105
+ execution: {
106
+ maxIterations: 25, // Different from any global value
107
+ },
108
+ };
109
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
110
+
111
+ process.chdir(tempDir);
112
+ const config = await loadConfig(tempDir);
113
+
114
+ await configCommand(config, { diff: true });
115
+
116
+ const output = consoleOutput.join("\n");
117
+
118
+ // Should show header
119
+ expect(output).toContain("# Config Differences (Project overrides Global)");
120
+
121
+ // Should show table separator
122
+ expect(output).toContain("─");
123
+
124
+ // Should show column headers
125
+ expect(output).toContain("Field");
126
+ expect(output).toContain("Project Value");
127
+ expect(output).toContain("Global Value");
128
+
129
+ // Should show the specific field
130
+ expect(output).toContain("execution.maxIterations");
131
+
132
+ // Should show project value (25)
133
+ expect(output).toContain("25");
134
+
135
+ // Global value will vary based on ~/.nax/config.json, just verify it's present
136
+ // The key assertion is that we show both values in a diff
137
+ const lines = output.split("\n");
138
+ const maxIterLine = lines.find((line) => line.includes("execution.maxIterations"));
139
+ expect(maxIterLine).toBeDefined();
140
+ });
141
+
142
+ test("shows multiple differences when multiple fields override", async () => {
143
+ const naxDir = join(tempDir, "nax");
144
+ mkdirSync(naxDir, { recursive: true });
145
+ const projectConfig = {
146
+ execution: {
147
+ maxIterations: 25,
148
+ costLimit: 10.0,
149
+ },
150
+ tdd: {
151
+ maxRetries: 5,
152
+ },
153
+ };
154
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
155
+
156
+ process.chdir(tempDir);
157
+ const config = await loadConfig(tempDir);
158
+
159
+ await configCommand(config, { diff: true });
160
+
161
+ const output = consoleOutput.join("\n");
162
+
163
+ // Should show all three differences
164
+ expect(output).toContain("execution.maxIterations");
165
+ expect(output).toContain("execution.costLimit");
166
+ expect(output).toContain("tdd.maxRetries");
167
+
168
+ // Check project values are present
169
+ expect(output).toContain("25"); // maxIterations project
170
+ expect(output).toContain("10"); // costLimit project
171
+ expect(output).toContain("5"); // tdd.maxRetries project
172
+
173
+ // Verify table structure exists
174
+ const lines = output.split("\n");
175
+ expect(lines.some((line) => line.includes("execution.maxIterations"))).toBe(true);
176
+ expect(lines.some((line) => line.includes("execution.costLimit"))).toBe(true);
177
+ expect(lines.some((line) => line.includes("tdd.maxRetries"))).toBe(true);
178
+ });
179
+
180
+ test("shows nested field paths correctly", async () => {
181
+ const naxDir = join(tempDir, "nax");
182
+ mkdirSync(naxDir, { recursive: true });
183
+ const projectConfig = {
184
+ routing: {
185
+ llm: {
186
+ timeoutMs: 30000, // Different from default (15000)
187
+ },
188
+ },
189
+ };
190
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
191
+
192
+ process.chdir(tempDir);
193
+ const config = await loadConfig(tempDir);
194
+
195
+ await configCommand(config, { diff: true });
196
+
197
+ const output = consoleOutput.join("\n");
198
+
199
+ // Should show nested path
200
+ expect(output).toContain("routing.llm.timeoutMs");
201
+ expect(output).toContain("30000");
202
+ expect(output).toContain("15000");
203
+ });
204
+
205
+ test("shows field descriptions when available", async () => {
206
+ const naxDir = join(tempDir, "nax");
207
+ mkdirSync(naxDir, { recursive: true });
208
+ const projectConfig = {
209
+ execution: {
210
+ maxIterations: 25,
211
+ },
212
+ };
213
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
214
+
215
+ process.chdir(tempDir);
216
+ const config = await loadConfig(tempDir);
217
+
218
+ await configCommand(config, { diff: true });
219
+
220
+ const output = consoleOutput.join("\n");
221
+
222
+ // Should show description for execution.maxIterations
223
+ // Description: "Max iterations per feature run (auto-calculated if not set)"
224
+ expect(output).toContain("Max iterations per feature run");
225
+ });
226
+
227
+ test("handles array differences correctly", async () => {
228
+ const naxDir = join(tempDir, "nax");
229
+ mkdirSync(naxDir, { recursive: true });
230
+ const projectConfig = {
231
+ quality: {
232
+ stripEnvVars: ["CUSTOM_VAR", "ANOTHER_VAR"], // Different from default
233
+ },
234
+ };
235
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
236
+
237
+ process.chdir(tempDir);
238
+ const config = await loadConfig(tempDir);
239
+
240
+ await configCommand(config, { diff: true });
241
+
242
+ const output = consoleOutput.join("\n");
243
+
244
+ // Should show the field
245
+ expect(output).toContain("quality.stripEnvVars");
246
+
247
+ // Arrays should be formatted compactly for table
248
+ expect(output).toContain("[..."); // Compact array notation
249
+ });
250
+
251
+ test("handles boolean differences correctly", async () => {
252
+ const naxDir = join(tempDir, "nax");
253
+ mkdirSync(naxDir, { recursive: true });
254
+ const projectConfig = {
255
+ quality: {
256
+ requireTests: false, // Different from default/global (true)
257
+ },
258
+ };
259
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
260
+
261
+ process.chdir(tempDir);
262
+ const config = await loadConfig(tempDir);
263
+
264
+ await configCommand(config, { diff: true });
265
+
266
+ const output = consoleOutput.join("\n");
267
+
268
+ expect(output).toContain("quality.requireTests");
269
+ expect(output).toContain("false");
270
+ expect(output).toContain("true");
271
+ });
272
+
273
+ test("handles string value differences correctly", async () => {
274
+ const naxDir = join(tempDir, "nax");
275
+ mkdirSync(naxDir, { recursive: true });
276
+ const projectConfig = {
277
+ routing: {
278
+ strategy: "manual", // Use "manual" to ensure it's different from any global
279
+ },
280
+ };
281
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
282
+
283
+ process.chdir(tempDir);
284
+ const config = await loadConfig(tempDir);
285
+
286
+ await configCommand(config, { diff: true });
287
+
288
+ const output = consoleOutput.join("\n");
289
+
290
+ expect(output).toContain("routing.strategy");
291
+ expect(output).toContain('"manual"'); // Project value
292
+ // Global value will vary, just verify the field is shown
293
+ });
294
+
295
+ test("truncates long string values in table", async () => {
296
+ const naxDir = join(tempDir, "nax");
297
+ mkdirSync(naxDir, { recursive: true });
298
+ const projectConfig = {
299
+ constitution: {
300
+ path: "very-long-constitution-filename-that-should-be-truncated.md",
301
+ },
302
+ };
303
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
304
+
305
+ process.chdir(tempDir);
306
+ const config = await loadConfig(tempDir);
307
+
308
+ await configCommand(config, { diff: true });
309
+
310
+ const output = consoleOutput.join("\n");
311
+
312
+ expect(output).toContain("constitution.path");
313
+ // Long strings should be truncated with "..."
314
+ expect(output).toMatch(/\.\.\./);
315
+ });
316
+
317
+ test("handles object differences correctly", async () => {
318
+ const naxDir = join(tempDir, "nax");
319
+ mkdirSync(naxDir, { recursive: true });
320
+ const projectConfig = {
321
+ models: {
322
+ fast: {
323
+ provider: "openai",
324
+ model: "gpt-4o-mini",
325
+ },
326
+ },
327
+ };
328
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
329
+
330
+ process.chdir(tempDir);
331
+ const config = await loadConfig(tempDir);
332
+
333
+ await configCommand(config, { diff: true });
334
+
335
+ const output = consoleOutput.join("\n");
336
+
337
+ // Should show nested differences (provider and model fields)
338
+ expect(output).toContain("models.fast.provider");
339
+ expect(output).toContain("models.fast.model");
340
+ });
341
+ });
342
+
343
+ describe("Mutual exclusivity with --explain", () => {
344
+ test("rejects when both --diff and --explain are provided", async () => {
345
+ process.chdir(tempDir);
346
+ const config = await loadConfig(tempDir);
347
+
348
+ // Attempt to use both flags
349
+ let didThrow = false;
350
+ try {
351
+ await configCommand(config, { diff: true, explain: true });
352
+ } catch (err) {
353
+ didThrow = true;
354
+ expect(String(err)).toContain("process.exit(1)");
355
+ }
356
+
357
+ expect(didThrow).toBe(true);
358
+ expect(exitCode).toBe(1);
359
+ });
360
+
361
+ test("shows error message when both flags provided", async () => {
362
+ // Capture console.error as well
363
+ const errorOutput: string[] = [];
364
+ const originalConsoleError = console.error;
365
+ console.error = (...args: unknown[]) => {
366
+ errorOutput.push(args.map((a) => String(a)).join(" "));
367
+ };
368
+
369
+ process.chdir(tempDir);
370
+ const config = await loadConfig(tempDir);
371
+
372
+ try {
373
+ await configCommand(config, { diff: true, explain: true });
374
+ } catch {
375
+ // Expected to throw
376
+ }
377
+
378
+ console.error = originalConsoleError;
379
+
380
+ const errorMsg = errorOutput.join("\n");
381
+ expect(errorMsg).toContain("--explain and --diff are mutually exclusive");
382
+ });
383
+ });
384
+
385
+ describe("Edge cases", () => {
386
+ test("handles null values in diff correctly", async () => {
387
+ const naxDir = join(tempDir, "nax");
388
+ mkdirSync(naxDir, { recursive: true });
389
+ const projectConfig = {
390
+ execution: {
391
+ lintCommand: null, // Explicitly disable
392
+ },
393
+ };
394
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
395
+
396
+ process.chdir(tempDir);
397
+ const config = await loadConfig(tempDir);
398
+
399
+ await configCommand(config, { diff: true });
400
+
401
+ const output = consoleOutput.join("\n");
402
+
403
+ // Should show the difference if global has undefined and project has null
404
+ // (depends on how deepDiffConfigs handles null vs undefined)
405
+ // This test verifies the function doesn't crash
406
+ expect(output).toBeDefined();
407
+ });
408
+
409
+ test("skips fields that are only in global (not overridden)", async () => {
410
+ const naxDir = join(tempDir, "nax");
411
+ mkdirSync(naxDir, { recursive: true });
412
+ const projectConfig = {
413
+ execution: {
414
+ maxIterations: 25, // Only override this one field
415
+ },
416
+ };
417
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
418
+
419
+ process.chdir(tempDir);
420
+ const config = await loadConfig(tempDir);
421
+
422
+ await configCommand(config, { diff: true });
423
+
424
+ const output = consoleOutput.join("\n");
425
+
426
+ // Should only show execution.maxIterations
427
+ expect(output).toContain("execution.maxIterations");
428
+
429
+ // Should NOT show other execution fields that weren't overridden
430
+ expect(output).not.toContain("execution.costLimit");
431
+ expect(output).not.toContain("execution.sessionTimeoutSeconds");
432
+ });
433
+
434
+ test("handles deeply nested config overrides", async () => {
435
+ const naxDir = join(tempDir, "nax");
436
+ mkdirSync(naxDir, { recursive: true });
437
+ const projectConfig = {
438
+ autoMode: {
439
+ escalation: {
440
+ tierOrder: [
441
+ { tier: "fast", attempts: 3 },
442
+ { tier: "balanced", attempts: 2 },
443
+ ],
444
+ },
445
+ },
446
+ };
447
+ writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
448
+
449
+ process.chdir(tempDir);
450
+ const config = await loadConfig(tempDir);
451
+
452
+ await configCommand(config, { diff: true });
453
+
454
+ const output = consoleOutput.join("\n");
455
+
456
+ // Should show the nested field
457
+ expect(output).toContain("autoMode.escalation.tierOrder");
458
+ });
459
+ });
460
+ });