@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,303 @@
1
+ /**
2
+ * Task Router
3
+ *
4
+ * Routes stories using pluggable strategy system.
5
+ * Falls back to keyword-based classification for backward compatibility.
6
+ */
7
+
8
+ import type { Complexity, ModelTier, NaxConfig, TddStrategy, TestStrategy } from "../config";
9
+ import type { UserStory } from "../prd/types";
10
+ import { buildStrategyChain } from "./builder";
11
+ import type { RoutingContext } from "./strategy";
12
+
13
+ /** Routing decision for a story */
14
+ export interface RoutingDecision {
15
+ /** Classified complexity */
16
+ complexity: Complexity;
17
+ /** Model tier to use */
18
+ modelTier: ModelTier;
19
+ /** Test strategy to apply */
20
+ testStrategy: TestStrategy;
21
+ /** Reasoning for the classification */
22
+ reasoning: string;
23
+ }
24
+
25
+ /** Keywords that indicate higher complexity */
26
+ const COMPLEX_KEYWORDS = [
27
+ "refactor",
28
+ "redesign",
29
+ "architecture",
30
+ "migration",
31
+ "breaking change",
32
+ "public api",
33
+ "security",
34
+ "auth",
35
+ "encryption",
36
+ "permission",
37
+ "rbac",
38
+ "casl",
39
+ "jwt",
40
+ "grpc",
41
+ "microservice",
42
+ "event-driven",
43
+ "saga",
44
+ ];
45
+
46
+ const EXPERT_KEYWORDS = [
47
+ "cryptograph",
48
+ "zero-knowledge",
49
+ "distributed consensus",
50
+ "real-time",
51
+ "websocket",
52
+ "streaming",
53
+ "performance critical",
54
+ ];
55
+
56
+ const SECURITY_KEYWORDS = [
57
+ "auth",
58
+ "security",
59
+ "permission",
60
+ "jwt",
61
+ "oauth",
62
+ "token",
63
+ "encryption",
64
+ "secret",
65
+ "credential",
66
+ "password",
67
+ "rbac",
68
+ "casl",
69
+ ];
70
+
71
+ const PUBLIC_API_KEYWORDS = [
72
+ "public api",
73
+ "breaking change",
74
+ "external",
75
+ "consumer",
76
+ "sdk",
77
+ "npm publish",
78
+ "release",
79
+ "endpoint",
80
+ ];
81
+
82
+ /**
83
+ * Classify a story's complexity based on keywords and acceptance criteria count.
84
+ *
85
+ * Classification rules:
86
+ * - expert: matches expert keywords (cryptography, distributed consensus, real-time)
87
+ * - complex: matches complex keywords or >8 acceptance criteria
88
+ * - medium: >4 acceptance criteria
89
+ * - simple: default
90
+ *
91
+ * @param title - Story title
92
+ * @param description - Story description
93
+ * @param acceptanceCriteria - Array of acceptance criteria
94
+ * @param tags - Optional story tags
95
+ * @returns Classified complexity level
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * classifyComplexity(
100
+ * "Add JWT authentication",
101
+ * "Implement JWT auth with refresh tokens",
102
+ * ["Secure token storage", "Token refresh", "Expiry handling"],
103
+ * ["security", "auth"]
104
+ * );
105
+ * // "complex" (matches security keywords)
106
+ *
107
+ * classifyComplexity(
108
+ * "Fix typo in README",
109
+ * "Correct spelling mistake",
110
+ * ["Update README.md"],
111
+ * []
112
+ * );
113
+ * // "simple"
114
+ * ```
115
+ */
116
+ export function classifyComplexity(
117
+ title: string,
118
+ description: string,
119
+ acceptanceCriteria: string[],
120
+ tags: string[] = [],
121
+ ): Complexity {
122
+ const text = [title, description, ...(acceptanceCriteria ?? []), ...(tags ?? [])].join(" ").toLowerCase();
123
+
124
+ // Expert: matches expert keywords
125
+ if (EXPERT_KEYWORDS.some((kw) => text.includes(kw))) {
126
+ return "expert";
127
+ }
128
+
129
+ // Complex: matches complex keywords or has many criteria
130
+ if (COMPLEX_KEYWORDS.some((kw) => text.includes(kw)) || acceptanceCriteria.length > 8) {
131
+ return "complex";
132
+ }
133
+
134
+ // Medium: moderate criteria or some structural keywords
135
+ if (acceptanceCriteria.length > 4) {
136
+ return "medium";
137
+ }
138
+
139
+ return "simple";
140
+ }
141
+
142
+ /** Tags that indicate a lite-mode story (UI, layout, CLI, integration, polyglot) */
143
+ const LITE_TAGS = ["ui", "layout", "cli", "integration", "polyglot"];
144
+
145
+ /**
146
+ * Determine test strategy using decision tree logic.
147
+ *
148
+ * When tddStrategy is provided:
149
+ * - 'strict' → always three-session-tdd
150
+ * - 'lite' → always three-session-tdd-lite
151
+ * - 'off' → always test-after
152
+ * - 'auto' → existing heuristic logic, plus:
153
+ * if tags include ui/layout/cli/integration/polyglot → three-session-tdd-lite
154
+ * if security/public-api/complex/expert → three-session-tdd
155
+ * otherwise → test-after
156
+ *
157
+ * @param complexity - Pre-classified complexity level
158
+ * @param title - Story title
159
+ * @param description - Story description
160
+ * @param tags - Optional story tags
161
+ * @param tddStrategy - TDD strategy override from config (default: 'auto')
162
+ * @returns Test strategy
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * determineTestStrategy("complex", "Add OAuth", "Implement OAuth 2.0", ["security", "auth"], "strict");
167
+ * // "three-session-tdd"
168
+ *
169
+ * determineTestStrategy("simple", "Update button", "Change primary button", ["ui"], "auto");
170
+ * // "three-session-tdd-lite"
171
+ * ```
172
+ */
173
+
174
+ export function determineTestStrategy(
175
+ complexity: Complexity,
176
+ title: string,
177
+ description: string,
178
+ tags: string[] = [],
179
+ tddStrategy: TddStrategy = "auto",
180
+ ): TestStrategy {
181
+ // Explicit overrides — ignore all heuristics
182
+ if (tddStrategy === "strict") return "three-session-tdd";
183
+ if (tddStrategy === "lite") return "three-session-tdd-lite";
184
+ if (tddStrategy === "off") return "test-after";
185
+
186
+ // auto mode: apply heuristics
187
+ const text = [title, description, ...(tags ?? [])].join(" ").toLowerCase();
188
+
189
+ // Public API or security → three-session-tdd (strict)
190
+ const isSecurityCritical = SECURITY_KEYWORDS.some((kw) => text.includes(kw));
191
+ const isPublicApi = PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
192
+
193
+ if (isSecurityCritical || isPublicApi) {
194
+ return "three-session-tdd";
195
+ }
196
+
197
+ // Complex/expert heuristic
198
+ if (complexity === "complex" || complexity === "expert") {
199
+ const normalizedTags = (tags ?? []).map((t) => t.toLowerCase());
200
+ const hasLiteTag = LITE_TAGS.some((tag) => normalizedTags.includes(tag));
201
+ return hasLiteTag ? "three-session-tdd-lite" : "three-session-tdd";
202
+ }
203
+
204
+ // Simple/medium → test-after (default)
205
+ return "test-after";
206
+ }
207
+
208
+ /** Map complexity to model tier */
209
+ export function complexityToModelTier(complexity: Complexity, config: NaxConfig): ModelTier {
210
+ const mapping = config.autoMode.complexityRouting;
211
+ return (mapping[complexity] ?? "balanced") as ModelTier;
212
+ }
213
+
214
+ /**
215
+ * Route a story using the pluggable strategy system.
216
+ *
217
+ * This is the new main entry point for the routing system. It:
218
+ * 1. Builds the strategy chain based on config
219
+ * 2. Routes the story through the chain
220
+ * 3. Returns the first non-null decision
221
+ *
222
+ * @param story - User story to route
223
+ * @param context - Routing context (config, codebase, metrics)
224
+ * @param workdir - Working directory for resolving custom strategy paths
225
+ * @param plugins - Optional plugin registry for plugin-provided routers
226
+ * @returns Routing decision from the strategy chain
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * const decision = await routeStory(story, { config }, "/path/to/project", plugins);
231
+ * // {
232
+ * // complexity: "complex",
233
+ * // modelTier: "balanced",
234
+ * // testStrategy: "three-session-tdd",
235
+ * // reasoning: "three-session-tdd: security-critical, complexity:complex"
236
+ * // }
237
+ * ```
238
+ */
239
+ export async function routeStory(
240
+ story: UserStory,
241
+ context: RoutingContext,
242
+ workdir: string,
243
+ plugins?: import("../plugins/registry").PluginRegistry,
244
+ ): Promise<RoutingDecision> {
245
+ const chain = await buildStrategyChain(context.config, workdir, plugins);
246
+ return await chain.route(story, context);
247
+ }
248
+
249
+ /**
250
+ * Route a task through complexity classification, model tier selection, and test strategy.
251
+ *
252
+ * DEPRECATED: Use routeStory() instead. This function is kept for backward compatibility
253
+ * and uses only the keyword strategy.
254
+ *
255
+ * @param title - Story title
256
+ * @param description - Story description
257
+ * @param acceptanceCriteria - Array of acceptance criteria
258
+ * @param tags - Story tags
259
+ * @param config - nax configuration with complexity routing mappings
260
+ * @returns Routing decision with complexity, model tier, test strategy, and reasoning
261
+ *
262
+ * @deprecated Use routeStory() with a UserStory object instead
263
+ */
264
+ export function routeTask(
265
+ title: string,
266
+ description: string,
267
+ acceptanceCriteria: string[],
268
+ tags: string[],
269
+ config: NaxConfig,
270
+ ): RoutingDecision {
271
+ const complexity = classifyComplexity(title, description, acceptanceCriteria, tags);
272
+ const modelTier = complexityToModelTier(complexity, config);
273
+ const tddStrategy: TddStrategy = config.tdd?.strategy ?? "auto";
274
+ const testStrategy = determineTestStrategy(complexity, title, description, tags, tddStrategy);
275
+
276
+ const reasons: string[] = [];
277
+ const text = [title, description, ...(tags ?? [])].join(" ").toLowerCase();
278
+ const normalizedTags = (tags ?? []).map((t) => t.toLowerCase());
279
+ const hasLiteTag = LITE_TAGS.some((tag) => normalizedTags.includes(tag));
280
+
281
+ if (SECURITY_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("security-critical");
282
+ if (PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("public-api");
283
+
284
+ // Only add complexity reason if it's the primary reason for strict/lite TDD
285
+ if (complexity === "complex" || complexity === "expert") {
286
+ if (reasons.length === 0) {
287
+ reasons.push(`complexity:${complexity}`);
288
+ }
289
+ }
290
+
291
+ if (tddStrategy !== "auto") reasons.push(`strategy:${tddStrategy}`);
292
+ if (hasLiteTag && (complexity === "complex" || complexity === "expert")) {
293
+ reasons.push("lite-tags");
294
+ }
295
+
296
+ const prefix = testStrategy;
297
+ return {
298
+ complexity,
299
+ modelTier,
300
+ testStrategy,
301
+ reasoning: reasons.length > 0 ? `${prefix}: ${reasons.join(", ")}` : `test-after: simple task (${complexity})`,
302
+ };
303
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Adaptive Routing Strategy
3
+ *
4
+ * Uses historical metrics to optimize model tier selection based on cost-effectiveness.
5
+ * Routes to the cheapest tier that maintains acceptable success rates, accounting for
6
+ * escalation costs.
7
+ */
8
+
9
+ import type { Complexity, ModelTier } from "../../config";
10
+ import type { AggregateMetrics } from "../../metrics/types";
11
+ import type { UserStory } from "../../prd/types";
12
+ import type { RoutingContext, RoutingDecision, RoutingStrategy } from "../strategy";
13
+ import { keywordStrategy } from "./keyword";
14
+
15
+ /**
16
+ * Estimated costs per model tier (USD per story, approximate).
17
+ * These are rough estimates based on typical story complexity.
18
+ * Actual costs vary based on input/output tokens.
19
+ */
20
+ const ESTIMATED_TIER_COSTS: Record<ModelTier, number> = {
21
+ fast: 0.005, // ~$0.005 per simple story
22
+ balanced: 0.02, // ~$0.02 per medium story
23
+ powerful: 0.08, // ~$0.08 per complex story
24
+ };
25
+
26
+ /**
27
+ * Calculate effective cost for a model tier given historical metrics.
28
+ *
29
+ * effectiveCost = baseCost + (failRate × escalationCost)
30
+ *
31
+ * Where:
32
+ * - baseCost = cost of using this tier
33
+ * - failRate = probability of failure (requiring escalation)
34
+ * - escalationCost = cost of escalating to next tier
35
+ *
36
+ * @param tier - Model tier to evaluate
37
+ * @param complexity - Story complexity level
38
+ * @param metrics - Historical aggregate metrics
39
+ * @param tierOrder - Escalation tier order
40
+ * @returns Effective cost (USD)
41
+ */
42
+ function calculateEffectiveCost(
43
+ tier: ModelTier,
44
+ complexity: Complexity,
45
+ metrics: AggregateMetrics,
46
+ tierOrder: ModelTier[],
47
+ ): number {
48
+ const baseCost = ESTIMATED_TIER_COSTS[tier];
49
+
50
+ // Get historical pass rate for this tier on this complexity level
51
+ const complexityStats = metrics.complexityAccuracy[complexity];
52
+ if (!complexityStats || complexityStats.predicted < 1) {
53
+ // No data for this complexity level — assume base cost (no escalation)
54
+ return baseCost;
55
+ }
56
+
57
+ // Calculate fail rate (stories that needed escalation)
58
+ // mismatchRate = percentage of stories where initial tier != final tier
59
+ const failRate = complexityStats.mismatchRate;
60
+
61
+ // Find next tier in escalation chain
62
+ const currentIndex = tierOrder.indexOf(tier);
63
+ const nextTier = currentIndex < tierOrder.length - 1 ? tierOrder[currentIndex + 1] : null;
64
+
65
+ if (!nextTier) {
66
+ // Already at highest tier — no escalation possible
67
+ return baseCost;
68
+ }
69
+
70
+ // Escalation cost = cost of trying this tier + cost of next tier
71
+ const escalationCost = ESTIMATED_TIER_COSTS[nextTier];
72
+
73
+ return baseCost + failRate * escalationCost;
74
+ }
75
+
76
+ /**
77
+ * Find the most cost-effective tier for a given complexity level.
78
+ *
79
+ * Evaluates all tiers in the escalation chain and selects the one with
80
+ * the lowest effective cost (including escalation probability).
81
+ *
82
+ * @param complexity - Story complexity
83
+ * @param metrics - Historical metrics
84
+ * @param tierOrder - Escalation tier order
85
+ * @param costThreshold - Switch threshold (0-1)
86
+ * @returns Best tier and reasoning
87
+ */
88
+ function selectOptimalTier(
89
+ complexity: Complexity,
90
+ metrics: AggregateMetrics,
91
+ tierOrder: ModelTier[],
92
+ costThreshold: number,
93
+ ): { tier: ModelTier; reasoning: string } {
94
+ // Calculate effective cost for each tier
95
+ const costs = tierOrder.map((tier) => ({
96
+ tier,
97
+ effectiveCost: calculateEffectiveCost(tier, complexity, metrics, tierOrder),
98
+ }));
99
+
100
+ // Sort by effective cost (lowest first)
101
+ costs.sort((a, b) => a.effectiveCost - b.effectiveCost);
102
+
103
+ const optimal = costs[0];
104
+ const complexityStats = metrics.complexityAccuracy[complexity];
105
+
106
+ // If the cheapest tier's effective cost is within threshold of next tier, use it
107
+ const reasoning = complexityStats
108
+ ? `adaptive: ${complexity} → ${optimal.tier} (cost: $${optimal.effectiveCost.toFixed(4)}, ` +
109
+ `samples: ${complexityStats.predicted}, mismatch: ${(complexityStats.mismatchRate * 100).toFixed(1)}%)`
110
+ : `adaptive: ${complexity} → ${optimal.tier} (insufficient data, using base cost: $${optimal.effectiveCost.toFixed(4)})`;
111
+
112
+ return { tier: optimal.tier, reasoning };
113
+ }
114
+
115
+ /**
116
+ * Check if there's sufficient data for adaptive routing.
117
+ *
118
+ * @param complexity - Story complexity
119
+ * @param metrics - Historical metrics
120
+ * @param minSamples - Minimum samples required
121
+ * @returns True if sufficient data exists
122
+ */
123
+ function hasSufficientData(complexity: Complexity, metrics: AggregateMetrics, minSamples: number): boolean {
124
+ const complexityStats = metrics.complexityAccuracy[complexity];
125
+ return Boolean(complexityStats && complexityStats.predicted >= minSamples);
126
+ }
127
+
128
+ /**
129
+ * Adaptive routing strategy.
130
+ *
131
+ * Uses historical metrics to select the most cost-effective model tier.
132
+ * Falls back to configured strategy when insufficient data is available.
133
+ *
134
+ * Algorithm:
135
+ * 1. Check if sufficient historical data exists (>= minSamples)
136
+ * 2. If yes: Calculate effective cost for each tier (base + fail × escalation)
137
+ * 3. Select tier with lowest effective cost
138
+ * 4. If no: Delegate to fallback strategy
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const decision = adaptiveStrategy.route(story, context);
143
+ * // With sufficient data:
144
+ * // {
145
+ * // complexity: "medium",
146
+ * // modelTier: "fast",
147
+ * // reasoning: "adaptive: medium → fast (cost: $0.0078, samples: 23, mismatch: 12.5%)"
148
+ * // }
149
+ * //
150
+ * // Without sufficient data:
151
+ * // {
152
+ * // complexity: "medium",
153
+ * // modelTier: "balanced",
154
+ * // reasoning: "adaptive: insufficient data (7/10 samples) → fallback to llm"
155
+ * // }
156
+ * ```
157
+ */
158
+ export const adaptiveStrategy: RoutingStrategy = {
159
+ name: "adaptive",
160
+
161
+ async route(story: UserStory, context: RoutingContext): Promise<RoutingDecision | null> {
162
+ const { config, metrics } = context;
163
+
164
+ // Require metrics to be present - use keyword as ultimate fallback
165
+ if (!metrics) {
166
+ const fallbackStrategy = config.routing.adaptive?.fallbackStrategy || "llm";
167
+ const decision = await keywordStrategy.route(story, context); // keyword never returns null
168
+ if (!decision) return null;
169
+
170
+ return {
171
+ ...decision,
172
+ reasoning: `adaptive: no metrics available → fallback to ${fallbackStrategy}`,
173
+ };
174
+ }
175
+
176
+ // Get adaptive config
177
+ const adaptiveConfig = config.routing.adaptive || {
178
+ minSamples: 10,
179
+ costThreshold: 0.8,
180
+ fallbackStrategy: "llm" as const,
181
+ };
182
+
183
+ // First, classify complexity using fallback strategy
184
+ // (We need to know complexity before checking metrics)
185
+ // Always use keyword as the classification source since it never returns null
186
+ const fallbackDecision = await keywordStrategy.route(story, context);
187
+ if (!fallbackDecision) return null;
188
+
189
+ const complexity = fallbackDecision.complexity;
190
+
191
+ // Check if we have sufficient historical data for this complexity
192
+ if (!hasSufficientData(complexity, metrics, adaptiveConfig.minSamples)) {
193
+ const complexityStats = metrics.complexityAccuracy[complexity];
194
+ const sampleCount = complexityStats?.predicted || 0;
195
+
196
+ return {
197
+ ...fallbackDecision,
198
+ reasoning:
199
+ `adaptive: insufficient data (${sampleCount}/${adaptiveConfig.minSamples} samples) ` +
200
+ `→ fallback to ${adaptiveConfig.fallbackStrategy}`,
201
+ };
202
+ }
203
+
204
+ // We have sufficient data — calculate optimal tier
205
+ const tierOrder = config.autoMode.escalation.tierOrder.map((t) => t.tier);
206
+ const { tier, reasoning } = selectOptimalTier(complexity, metrics, tierOrder, adaptiveConfig.costThreshold);
207
+
208
+ return {
209
+ complexity,
210
+ modelTier: tier,
211
+ testStrategy: fallbackDecision.testStrategy, // Use fallback's test strategy decision
212
+ reasoning,
213
+ };
214
+ },
215
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Built-in Routing Strategies
3
+ */
4
+
5
+ export { keywordStrategy } from "./keyword";
6
+ export { llmStrategy } from "./llm";
7
+ export { manualStrategy } from "./manual";
8
+ export { adaptiveStrategy } from "./adaptive";
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Keyword-Based Routing Strategy
3
+ *
4
+ * Routes stories based on keyword matching and acceptance criteria count.
5
+ * This is the default fallback strategy — always returns a decision (never null).
6
+ */
7
+
8
+ import type { Complexity, ModelTier, TestStrategy } from "../../config";
9
+ import type { UserStory } from "../../prd/types";
10
+ import type { RoutingContext, RoutingDecision, RoutingStrategy } from "../strategy";
11
+
12
+ /** Keywords that indicate higher complexity */
13
+ const COMPLEX_KEYWORDS = [
14
+ "refactor",
15
+ "redesign",
16
+ "architecture",
17
+ "migration",
18
+ "breaking change",
19
+ "public api",
20
+ "security",
21
+ "auth",
22
+ "encryption",
23
+ "permission",
24
+ "rbac",
25
+ "casl",
26
+ "jwt",
27
+ "grpc",
28
+ "microservice",
29
+ "event-driven",
30
+ "saga",
31
+ ];
32
+
33
+ const EXPERT_KEYWORDS = [
34
+ "cryptograph",
35
+ "zero-knowledge",
36
+ "distributed consensus",
37
+ "real-time",
38
+ "websocket",
39
+ "streaming",
40
+ "performance critical",
41
+ ];
42
+
43
+ const SECURITY_KEYWORDS = [
44
+ "auth",
45
+ "security",
46
+ "permission",
47
+ "jwt",
48
+ "oauth",
49
+ "token",
50
+ "encryption",
51
+ "secret",
52
+ "credential",
53
+ "password",
54
+ "rbac",
55
+ "casl",
56
+ ];
57
+
58
+ const PUBLIC_API_KEYWORDS = [
59
+ "public api",
60
+ "breaking change",
61
+ "external",
62
+ "consumer",
63
+ "sdk",
64
+ "npm publish",
65
+ "release",
66
+ "endpoint",
67
+ ];
68
+
69
+ /**
70
+ * Classify complexity based on keywords and criteria count.
71
+ */
72
+ function classifyComplexity(
73
+ title: string,
74
+ description: string,
75
+ acceptanceCriteria: string[],
76
+ tags: string[] = [],
77
+ ): Complexity {
78
+ const text = [title, description, ...acceptanceCriteria, ...tags].join(" ").toLowerCase();
79
+
80
+ if (EXPERT_KEYWORDS.some((kw) => text.includes(kw))) {
81
+ return "expert";
82
+ }
83
+
84
+ if (COMPLEX_KEYWORDS.some((kw) => text.includes(kw)) || acceptanceCriteria.length > 8) {
85
+ return "complex";
86
+ }
87
+
88
+ if (acceptanceCriteria.length > 4) {
89
+ return "medium";
90
+ }
91
+
92
+ return "simple";
93
+ }
94
+
95
+ /**
96
+ * Determine test strategy using decision tree.
97
+ */
98
+ function determineTestStrategy(
99
+ complexity: Complexity,
100
+ title: string,
101
+ description: string,
102
+ tags: string[] = [],
103
+ ): TestStrategy {
104
+ const text = [title, description, ...tags].join(" ").toLowerCase();
105
+
106
+ const isSecurityCritical = SECURITY_KEYWORDS.some((kw) => text.includes(kw));
107
+ const isPublicApi = PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
108
+
109
+ if (isSecurityCritical || isPublicApi) {
110
+ return "three-session-tdd";
111
+ }
112
+
113
+ if (complexity === "complex" || complexity === "expert") {
114
+ return "three-session-tdd";
115
+ }
116
+
117
+ return "test-after";
118
+ }
119
+
120
+ /** Map complexity to model tier */
121
+ function complexityToModelTier(complexity: Complexity, context: RoutingContext): ModelTier {
122
+ const mapping = context.config.autoMode.complexityRouting;
123
+ return (mapping[complexity] ?? "balanced") as ModelTier;
124
+ }
125
+
126
+ /**
127
+ * Keyword-based routing strategy.
128
+ *
129
+ * This strategy:
130
+ * - Classifies complexity based on keywords and criteria count
131
+ * - Maps complexity to model tier via config
132
+ * - Applies test strategy decision tree
133
+ * - ALWAYS returns a decision (never null)
134
+ *
135
+ * Use as the final fallback strategy in a chain.
136
+ */
137
+ export const keywordStrategy: RoutingStrategy = {
138
+ name: "keyword",
139
+
140
+ route(story: UserStory, context: RoutingContext): RoutingDecision {
141
+ const { title, description, acceptanceCriteria, tags } = story;
142
+
143
+ const complexity = classifyComplexity(title, description, acceptanceCriteria, tags);
144
+ const modelTier = complexityToModelTier(complexity, context);
145
+ const testStrategy = determineTestStrategy(complexity, title, description, tags);
146
+
147
+ const reasons: string[] = [];
148
+ if (testStrategy === "three-session-tdd") {
149
+ const text = [title, description, ...tags].join(" ").toLowerCase();
150
+ if (SECURITY_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("security-critical");
151
+ if (PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("public-api");
152
+ if (complexity === "complex" || complexity === "expert") reasons.push(`complexity:${complexity}`);
153
+ }
154
+
155
+ return {
156
+ complexity,
157
+ modelTier,
158
+ testStrategy,
159
+ reasoning:
160
+ reasons.length > 0 ? `three-session-tdd: ${reasons.join(", ")}` : `test-after: simple task (${complexity})`,
161
+ };
162
+ },
163
+ };