@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,215 @@
1
+ /**
2
+ * Tests for LLM Classifier
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import type { CodebaseScan } from "../../src/analyze";
7
+ import { classifyStories } from "../../src/analyze/classifier";
8
+ import { DEFAULT_CONFIG } from "../../src/config";
9
+ import type { UserStory } from "../../src/prd";
10
+
11
+ describe("classifyStories", () => {
12
+ const mockScan: CodebaseScan = {
13
+ fileTree: "src/\n├── index.ts\n└── utils/\n └── helper.ts",
14
+ dependencies: { zod: "^4.0.0" },
15
+ devDependencies: { typescript: "^5.0.0" },
16
+ testPatterns: ["Test framework: bun:test", "Test directory: test/"],
17
+ };
18
+
19
+ const mockStories: UserStory[] = [
20
+ {
21
+ id: "US-001",
22
+ title: "Add input validation",
23
+ description: "Validate user inputs with Zod schemas",
24
+ acceptanceCriteria: ["Schema defined", "Validation works"],
25
+ tags: [],
26
+ dependencies: [],
27
+ status: "pending",
28
+ passes: false,
29
+ escalations: [],
30
+ attempts: 0,
31
+ },
32
+ ];
33
+
34
+ test("falls back to keyword matching when LLM disabled", async () => {
35
+ const config = {
36
+ ...DEFAULT_CONFIG,
37
+ analyze: {
38
+ llmEnhanced: false,
39
+ classifierModel: "fast" as const,
40
+ fallbackToKeywords: true,
41
+ maxCodebaseSummaryTokens: 5000,
42
+ },
43
+ };
44
+
45
+ const result = await classifyStories(mockStories, mockScan, config);
46
+
47
+ expect(result.method).toBe("keyword-fallback");
48
+ expect(result.fallbackReason).toBe("LLM-enhanced analysis disabled in config");
49
+ expect(result.classifications).toHaveLength(1);
50
+ expect(result.classifications[0].storyId).toBe("US-001");
51
+ });
52
+
53
+ test("falls back to keyword matching when ANTHROPIC_API_KEY missing", async () => {
54
+ const config = {
55
+ ...DEFAULT_CONFIG,
56
+ analyze: {
57
+ llmEnhanced: true,
58
+ classifierModel: "fast" as const,
59
+ fallbackToKeywords: true,
60
+ maxCodebaseSummaryTokens: 5000,
61
+ },
62
+ };
63
+
64
+ // Temporarily unset API key
65
+ const originalKey = process.env.ANTHROPIC_API_KEY;
66
+ delete process.env.ANTHROPIC_API_KEY;
67
+
68
+ try {
69
+ const result = await classifyStories(mockStories, mockScan, config);
70
+
71
+ expect(result.method).toBe("keyword-fallback");
72
+ expect(result.fallbackReason).toContain("ANTHROPIC_API_KEY");
73
+ expect(result.classifications).toHaveLength(1);
74
+ } finally {
75
+ // Restore API key
76
+ if (originalKey) {
77
+ process.env.ANTHROPIC_API_KEY = originalKey;
78
+ }
79
+ }
80
+ });
81
+
82
+ test("keyword fallback classification includes all required fields", async () => {
83
+ const config = {
84
+ ...DEFAULT_CONFIG,
85
+ analyze: {
86
+ llmEnhanced: false,
87
+ classifierModel: "fast" as const,
88
+ fallbackToKeywords: true,
89
+ maxCodebaseSummaryTokens: 5000,
90
+ },
91
+ };
92
+
93
+ const result = await classifyStories(mockStories, mockScan, config);
94
+
95
+ const classification = result.classifications[0];
96
+ expect(classification.storyId).toBe("US-001");
97
+ expect(classification.complexity).toMatch(/simple|medium|complex|expert/);
98
+ expect(classification.contextFiles).toEqual([]);
99
+ expect(classification.reasoning).toContain("Keyword-based classification");
100
+ expect(typeof classification.estimatedLOC).toBe("number");
101
+ expect(classification.estimatedLOC).toBeGreaterThan(0);
102
+ expect(classification.risks).toEqual([]);
103
+ });
104
+
105
+ test("classifies simple stories correctly in keyword mode", async () => {
106
+ const simpleStory: UserStory = {
107
+ id: "US-002",
108
+ title: "Update button color",
109
+ description: "Change primary button to blue",
110
+ acceptanceCriteria: ["Button is blue"],
111
+ tags: [],
112
+ dependencies: [],
113
+ status: "pending",
114
+ passes: false,
115
+ escalations: [],
116
+ attempts: 0,
117
+ };
118
+
119
+ const config = {
120
+ ...DEFAULT_CONFIG,
121
+ analyze: {
122
+ llmEnhanced: false,
123
+ classifierModel: "fast" as const,
124
+ fallbackToKeywords: true,
125
+ maxCodebaseSummaryTokens: 5000,
126
+ },
127
+ };
128
+
129
+ const result = await classifyStories([simpleStory], mockScan, config);
130
+
131
+ expect(result.classifications[0].complexity).toBe("simple");
132
+ expect(result.classifications[0].estimatedLOC).toBe(50); // Simple = 50 LOC
133
+ });
134
+
135
+ test("classifies complex stories correctly in keyword mode", async () => {
136
+ const complexStory: UserStory = {
137
+ id: "US-003",
138
+ title: "Add JWT authentication",
139
+ description: "Implement secure JWT authentication with refresh tokens",
140
+ acceptanceCriteria: [
141
+ "Token generation",
142
+ "Token validation",
143
+ "Refresh logic",
144
+ "Expiry handling",
145
+ "Secure storage",
146
+ ],
147
+ tags: ["security", "auth"],
148
+ dependencies: [],
149
+ status: "pending",
150
+ passes: false,
151
+ escalations: [],
152
+ attempts: 0,
153
+ };
154
+
155
+ const config = {
156
+ ...DEFAULT_CONFIG,
157
+ analyze: {
158
+ llmEnhanced: false,
159
+ classifierModel: "fast" as const,
160
+ fallbackToKeywords: true,
161
+ maxCodebaseSummaryTokens: 5000,
162
+ },
163
+ };
164
+
165
+ const result = await classifyStories([complexStory], mockScan, config);
166
+
167
+ expect(result.classifications[0].complexity).toBe("complex");
168
+ expect(result.classifications[0].estimatedLOC).toBe(400); // Complex = 400 LOC
169
+ });
170
+
171
+ test("processes multiple stories", async () => {
172
+ const stories: UserStory[] = [
173
+ {
174
+ id: "US-001",
175
+ title: "Story 1",
176
+ description: "First story",
177
+ acceptanceCriteria: ["AC1"],
178
+ tags: [],
179
+ dependencies: [],
180
+ status: "pending",
181
+ passes: false,
182
+ escalations: [],
183
+ attempts: 0,
184
+ },
185
+ {
186
+ id: "US-002",
187
+ title: "Story 2",
188
+ description: "Second story",
189
+ acceptanceCriteria: ["AC2"],
190
+ tags: [],
191
+ dependencies: [],
192
+ status: "pending",
193
+ passes: false,
194
+ escalations: [],
195
+ attempts: 0,
196
+ },
197
+ ];
198
+
199
+ const config = {
200
+ ...DEFAULT_CONFIG,
201
+ analyze: {
202
+ llmEnhanced: false,
203
+ classifierModel: "fast" as const,
204
+ fallbackToKeywords: true,
205
+ maxCodebaseSummaryTokens: 5000,
206
+ },
207
+ };
208
+
209
+ const result = await classifyStories(stories, mockScan, config);
210
+
211
+ expect(result.classifications).toHaveLength(2);
212
+ expect(result.classifications[0].storyId).toBe("US-001");
213
+ expect(result.classifications[1].storyId).toBe("US-002");
214
+ });
215
+ });
@@ -0,0 +1,224 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { analyzeFeature } from "../../src/cli/analyze";
3
+ import type { NaxConfig } from "../../src/config";
4
+ import { DEFAULT_CONFIG } from "../../src/config/schema";
5
+
6
+ describe("analyzeFeature", () => {
7
+ test("parses spec.md into user stories (LLM disabled, keyword fallback)", async () => {
8
+ const tmpDir = `/tmp/nax-analyze-${Date.now()}`;
9
+ await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
10
+
11
+ await Bun.write(
12
+ `${tmpDir}/spec.md`,
13
+ `# Feature: Auth System
14
+
15
+ ## US-001: Add login endpoint
16
+
17
+ ### Description
18
+ Create a POST /auth/login endpoint that accepts email and password.
19
+
20
+ ### Acceptance Criteria
21
+ - [ ] Endpoint returns JWT token on success
22
+ - [ ] Returns 401 on invalid credentials
23
+ - [ ] Rate limited to 5 attempts per minute
24
+
25
+ Tags: security, auth
26
+ Dependencies: US-000
27
+
28
+ ## US-002: Add logout endpoint
29
+
30
+ ### Description
31
+ Create a POST /auth/logout endpoint.
32
+
33
+ ### Acceptance Criteria
34
+ - [ ] Invalidates the current token
35
+ - [ ] Returns 200 on success
36
+
37
+ Dependencies: US-001
38
+ `,
39
+ );
40
+
41
+ // Disable LLM for keyword-based classification
42
+ const config: NaxConfig = {
43
+ ...DEFAULT_CONFIG,
44
+ analyze: {
45
+ ...DEFAULT_CONFIG.analyze,
46
+ llmEnhanced: false,
47
+ },
48
+ };
49
+
50
+ const prd = await analyzeFeature({
51
+ featureDir: tmpDir,
52
+ featureName: "auth",
53
+ branchName: "feat/auth",
54
+ config,
55
+ });
56
+
57
+ expect(prd.feature).toBe("auth");
58
+ expect(prd.branchName).toBe("feat/auth");
59
+ expect(prd.userStories).toHaveLength(2);
60
+
61
+ const s1 = prd.userStories[0];
62
+ expect(s1.id).toBe("US-001");
63
+ expect(s1.title).toBe("Add login endpoint");
64
+ expect(s1.acceptanceCriteria).toHaveLength(3);
65
+ expect(s1.tags).toContain("security");
66
+ expect(s1.dependencies).toContain("US-000");
67
+
68
+ const s2 = prd.userStories[1];
69
+ expect(s2.id).toBe("US-002");
70
+ expect(s2.title).toBe("Add logout endpoint");
71
+ expect(s2.acceptanceCriteria).toHaveLength(2);
72
+ expect(s2.dependencies).toContain("US-001");
73
+
74
+ await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
75
+ });
76
+
77
+ test("throws when spec.md is missing", async () => {
78
+ const tmpDir = `/tmp/nax-analyze-empty-${Date.now()}`;
79
+ await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
80
+
81
+ await expect(
82
+ analyzeFeature({
83
+ featureDir: tmpDir,
84
+ featureName: "test",
85
+ branchName: "feat/test",
86
+ }),
87
+ ).rejects.toThrow("spec.md not found");
88
+
89
+ await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
90
+ });
91
+
92
+ test("warns but does not throw when story count exceeds maxStoriesPerFeature limit", async () => {
93
+ const tmpDir = `/tmp/nax-analyze-limit-${Date.now()}`;
94
+ await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
95
+
96
+ // Generate spec.md with 6 stories
97
+ const stories = Array.from(
98
+ { length: 6 },
99
+ (_, i) => `
100
+ ## US-${String(i + 1).padStart(3, "0")}: Story ${i + 1}
101
+
102
+ ### Description
103
+ Description for story ${i + 1}
104
+
105
+ ### Acceptance Criteria
106
+ - [ ] Criterion 1
107
+ `,
108
+ ).join("\n");
109
+
110
+ await Bun.write(`${tmpDir}/spec.md`, `# Feature\n${stories}`);
111
+
112
+ // Create config with limit of 5 stories (LLM disabled)
113
+ const config: NaxConfig = {
114
+ ...DEFAULT_CONFIG,
115
+ execution: {
116
+ ...DEFAULT_CONFIG.execution,
117
+ maxStoriesPerFeature: 5,
118
+ },
119
+ analyze: {
120
+ ...DEFAULT_CONFIG.analyze,
121
+ llmEnhanced: false,
122
+ },
123
+ };
124
+
125
+ // Should warn but still succeed (no longer throws)
126
+ const prd = await analyzeFeature({
127
+ featureDir: tmpDir,
128
+ featureName: "test",
129
+ branchName: "feat/test",
130
+ config,
131
+ });
132
+
133
+ expect(prd.userStories.length).toBe(6);
134
+
135
+ await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
136
+ });
137
+
138
+ test("allows story count at maxStoriesPerFeature limit", async () => {
139
+ const tmpDir = `/tmp/nax-analyze-ok-${Date.now()}`;
140
+ await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
141
+
142
+ // Generate spec.md with exactly 5 stories
143
+ const stories = Array.from(
144
+ { length: 5 },
145
+ (_, i) => `
146
+ ## US-${String(i + 1).padStart(3, "0")}: Story ${i + 1}
147
+
148
+ ### Description
149
+ Description for story ${i + 1}
150
+
151
+ ### Acceptance Criteria
152
+ - [ ] Criterion 1
153
+ `,
154
+ ).join("\n");
155
+
156
+ await Bun.write(`${tmpDir}/spec.md`, `# Feature\n${stories}`);
157
+
158
+ // Create config with limit of 5 stories (LLM disabled)
159
+ const config: NaxConfig = {
160
+ ...DEFAULT_CONFIG,
161
+ execution: {
162
+ ...DEFAULT_CONFIG.execution,
163
+ maxStoriesPerFeature: 5,
164
+ },
165
+ analyze: {
166
+ ...DEFAULT_CONFIG.analyze,
167
+ llmEnhanced: false,
168
+ },
169
+ };
170
+
171
+ // Should NOT throw because 5 === 5
172
+ const prd = await analyzeFeature({
173
+ featureDir: tmpDir,
174
+ featureName: "test",
175
+ branchName: "feat/test",
176
+ config,
177
+ });
178
+ expect(prd.userStories).toHaveLength(5);
179
+
180
+ await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
181
+ });
182
+
183
+ test("reads spec from explicit --from path", async () => {
184
+ const tmpDir = `/tmp/nax-analyze-from-${Date.now()}`;
185
+ await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
186
+
187
+ const customSpecPath = `${tmpDir}/custom-spec.md`;
188
+ await Bun.write(
189
+ customSpecPath,
190
+ `# Custom Spec
191
+
192
+ ## US-001: Custom story
193
+
194
+ ### Description
195
+ A custom story from explicit path
196
+
197
+ ### Acceptance Criteria
198
+ - [ ] Works with --from flag
199
+ `,
200
+ );
201
+
202
+ const config: NaxConfig = {
203
+ ...DEFAULT_CONFIG,
204
+ analyze: {
205
+ ...DEFAULT_CONFIG.analyze,
206
+ llmEnhanced: false,
207
+ },
208
+ };
209
+
210
+ const prd = await analyzeFeature({
211
+ featureDir: tmpDir,
212
+ featureName: "custom",
213
+ branchName: "feat/custom",
214
+ config,
215
+ specPath: customSpecPath,
216
+ });
217
+
218
+ expect(prd.userStories).toHaveLength(1);
219
+ expect(prd.userStories[0].id).toBe("US-001");
220
+ expect(prd.userStories[0].title).toBe("Custom story");
221
+
222
+ await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
223
+ });
224
+ });
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Tests for context auto-detection (BUG-006)
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
+ import fs from "node:fs/promises";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { autoDetectContextFiles, extractKeywords } from "../../src/context/auto-detect";
10
+
11
+ describe("Context Auto-Detection", () => {
12
+ describe("extractKeywords", () => {
13
+ test("should extract keywords from simple title", () => {
14
+ const keywords = extractKeywords("BUG-006: Context auto-detection");
15
+ expect(keywords).toContain("006");
16
+ expect(keywords).toContain("context");
17
+ expect(keywords).toContain("auto");
18
+ expect(keywords).toContain("detection");
19
+ });
20
+
21
+ test("should remove common stop words", () => {
22
+ const keywords = extractKeywords("Add the new feature for user authentication");
23
+ expect(keywords).not.toContain("the");
24
+ expect(keywords).not.toContain("for");
25
+ expect(keywords).not.toContain("add");
26
+ expect(keywords).toContain("new");
27
+ expect(keywords).toContain("feature");
28
+ expect(keywords).toContain("user");
29
+ expect(keywords).toContain("authentication");
30
+ });
31
+
32
+ test("should handle punctuation and special chars", () => {
33
+ const keywords = extractKeywords("Fix: API endpoint (v2) - rate-limiting");
34
+ expect(keywords).toContain("api");
35
+ expect(keywords).toContain("endpoint");
36
+ expect(keywords).toContain("rate");
37
+ expect(keywords).toContain("limiting");
38
+ });
39
+
40
+ test("should deduplicate keywords", () => {
41
+ const keywords = extractKeywords("Context context CONTEXT");
42
+ expect(keywords.filter((k) => k === "context")).toHaveLength(1);
43
+ });
44
+
45
+ test("should filter short words (< 3 chars)", () => {
46
+ const keywords = extractKeywords("Fix a bug in UI");
47
+ expect(keywords).not.toContain("in");
48
+ expect(keywords).not.toContain("ui"); // length 2
49
+ });
50
+
51
+ test("should return empty array for title with only stop words", () => {
52
+ const keywords = extractKeywords("The and or to");
53
+ expect(keywords).toHaveLength(0);
54
+ });
55
+
56
+ test("should lowercase all keywords", () => {
57
+ const keywords = extractKeywords("UPPERCASE lowercase MixedCase");
58
+ expect(keywords).toContain("uppercase");
59
+ expect(keywords).toContain("lowercase");
60
+ expect(keywords).toContain("mixedcase");
61
+ expect(keywords).not.toContain("UPPERCASE");
62
+ expect(keywords).not.toContain("MixedCase");
63
+ });
64
+ });
65
+
66
+ describe("autoDetectContextFiles", () => {
67
+ let tempDir: string;
68
+
69
+ beforeEach(async () => {
70
+ // Create temp git repo
71
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-autodetect-test-"));
72
+ await Bun.spawn(["git", "init"], { cwd: tempDir }).exited;
73
+ await Bun.spawn(["git", "config", "user.email", "test@test.com"], { cwd: tempDir }).exited;
74
+ await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: tempDir }).exited;
75
+ });
76
+
77
+ afterEach(async () => {
78
+ // Clean up
79
+ await fs.rm(tempDir, { recursive: true, force: true });
80
+ });
81
+
82
+ test("should detect files matching keywords", async () => {
83
+ // Create test files
84
+ await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
85
+ await fs.writeFile(path.join(tempDir, "src/context-builder.ts"), "export function buildContext() {}");
86
+ await fs.writeFile(path.join(tempDir, "src/auto-detect.ts"), "export function autoDetectContextFiles() {}");
87
+ await fs.writeFile(path.join(tempDir, "src/unrelated.ts"), "export function foo() {}");
88
+
89
+ // Commit files so git grep can find them
90
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
91
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
92
+
93
+ const files = await autoDetectContextFiles({
94
+ workdir: tempDir,
95
+ storyTitle: "BUG-006: Context auto-detection",
96
+ maxFiles: 5,
97
+ });
98
+
99
+ expect(files).toContain("src/context-builder.ts");
100
+ expect(files).toContain("src/auto-detect.ts");
101
+ expect(files).not.toContain("src/unrelated.ts");
102
+ });
103
+
104
+ test("should exclude test files", async () => {
105
+ await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
106
+ await fs.mkdir(path.join(tempDir, "test"), { recursive: true });
107
+ await fs.writeFile(path.join(tempDir, "src/context.ts"), "export function buildContext() {}");
108
+ await fs.writeFile(path.join(tempDir, "test/context.test.ts"), "test('context', () => {})");
109
+
110
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
111
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
112
+
113
+ const files = await autoDetectContextFiles({
114
+ workdir: tempDir,
115
+ storyTitle: "Context builder",
116
+ maxFiles: 5,
117
+ });
118
+
119
+ expect(files).toContain("src/context.ts");
120
+ expect(files).not.toContain("test/context.test.ts");
121
+ });
122
+
123
+ test("should exclude index files", async () => {
124
+ await fs.mkdir(path.join(tempDir, "src/context"), { recursive: true });
125
+ await fs.writeFile(path.join(tempDir, "src/context/index.ts"), "export * from './builder'");
126
+ await fs.writeFile(path.join(tempDir, "src/context/builder.ts"), "export function buildContext() {}");
127
+
128
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
129
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
130
+
131
+ const files = await autoDetectContextFiles({
132
+ workdir: tempDir,
133
+ storyTitle: "Context builder",
134
+ maxFiles: 5,
135
+ });
136
+
137
+ expect(files).toContain("src/context/builder.ts");
138
+ expect(files).not.toContain("src/context/index.ts");
139
+ });
140
+
141
+ test("should respect maxFiles limit", async () => {
142
+ await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
143
+ for (let i = 1; i <= 10; i++) {
144
+ await fs.writeFile(path.join(tempDir, `src/file${i}.ts`), `// Contains keyword: routing`);
145
+ }
146
+
147
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
148
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
149
+
150
+ const files = await autoDetectContextFiles({
151
+ workdir: tempDir,
152
+ storyTitle: "Routing system",
153
+ maxFiles: 3,
154
+ });
155
+
156
+ expect(files.length).toBeLessThanOrEqual(3);
157
+ });
158
+
159
+ test("should return empty array when no matches", async () => {
160
+ await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
161
+ await fs.writeFile(path.join(tempDir, "src/unrelated.ts"), "export function foo() {}");
162
+
163
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
164
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
165
+
166
+ const files = await autoDetectContextFiles({
167
+ workdir: tempDir,
168
+ storyTitle: "NonExistentKeyword XYZ123",
169
+ maxFiles: 5,
170
+ });
171
+
172
+ expect(files).toHaveLength(0);
173
+ });
174
+
175
+ test("should return empty array when no keywords extracted", async () => {
176
+ await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
177
+ await fs.writeFile(path.join(tempDir, "src/file.ts"), "export function test() {}");
178
+
179
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
180
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
181
+
182
+ const files = await autoDetectContextFiles({
183
+ workdir: tempDir,
184
+ storyTitle: "the and or", // All stop words
185
+ maxFiles: 5,
186
+ });
187
+
188
+ expect(files).toHaveLength(0);
189
+ });
190
+
191
+ test("should handle non-git directory gracefully", async () => {
192
+ // Create non-git temp dir
193
+ const nonGitDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-nongit-"));
194
+ await fs.mkdir(path.join(nonGitDir, "src"), { recursive: true });
195
+ await fs.writeFile(path.join(nonGitDir, "src/file.ts"), "export function test() {}");
196
+
197
+ const files = await autoDetectContextFiles({
198
+ workdir: nonGitDir,
199
+ storyTitle: "Test story",
200
+ maxFiles: 5,
201
+ });
202
+
203
+ expect(files).toHaveLength(0);
204
+
205
+ // Clean up
206
+ await fs.rm(nonGitDir, { recursive: true, force: true });
207
+ });
208
+
209
+ test("should sort files by relevance score", async () => {
210
+ await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
211
+ // File with 2 keyword matches
212
+ await fs.writeFile(path.join(tempDir, "src/context-builder.ts"), "context builder code");
213
+ // File with 1 keyword match
214
+ await fs.writeFile(path.join(tempDir, "src/context-helper.ts"), "context helper code");
215
+ // File with 1 keyword match
216
+ await fs.writeFile(path.join(tempDir, "src/builder-utils.ts"), "builder utils code");
217
+
218
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
219
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
220
+
221
+ const files = await autoDetectContextFiles({
222
+ workdir: tempDir,
223
+ storyTitle: "Context builder system",
224
+ maxFiles: 5,
225
+ });
226
+
227
+ // File with most keyword matches should be first
228
+ expect(files[0]).toBe("src/context-builder.ts");
229
+ });
230
+
231
+ test("should exclude generated files", async () => {
232
+ await fs.mkdir(path.join(tempDir, "src/__generated__"), { recursive: true });
233
+ await fs.writeFile(path.join(tempDir, "src/__generated__/schema.ts"), "// generated schema");
234
+ await fs.writeFile(path.join(tempDir, "src/schema.ts"), "export const schema = {}");
235
+
236
+ await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
237
+ await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
238
+
239
+ const files = await autoDetectContextFiles({
240
+ workdir: tempDir,
241
+ storyTitle: "Schema validation",
242
+ maxFiles: 5,
243
+ });
244
+
245
+ expect(files).toContain("src/schema.ts");
246
+ expect(files).not.toContain("src/__generated__/schema.ts");
247
+ });
248
+ });
249
+ });