@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,268 @@
1
+ /**
2
+ * Tests for src/worktree/merge.ts
3
+ *
4
+ * Covers: MergeEngine topological sort and merge logic
5
+ */
6
+
7
+ import { describe, expect, it } from "bun:test";
8
+ import { MergeEngine } from "../../src/worktree/merge";
9
+ import type { StoryDependencies } from "../../src/worktree/merge";
10
+
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ // Test fixtures
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+
15
+ const mockWorktreeManager = {
16
+ create: async () => {},
17
+ remove: async () => {},
18
+ list: async () => [],
19
+ } as any;
20
+
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+ // MergeEngine.topologicalSort
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+
25
+ describe("MergeEngine.topologicalSort", () => {
26
+ it("sorts stories with no dependencies", () => {
27
+ const engine = new MergeEngine(mockWorktreeManager);
28
+ const storyIds = ["US-001", "US-002", "US-003"];
29
+ const dependencies: StoryDependencies = {};
30
+
31
+ // @ts-expect-error - accessing private method for testing
32
+ const sorted = engine.topologicalSort(storyIds, dependencies);
33
+
34
+ expect(sorted.length).toBe(3);
35
+ expect(sorted).toContain("US-001");
36
+ expect(sorted).toContain("US-002");
37
+ expect(sorted).toContain("US-003");
38
+ });
39
+
40
+ it("sorts stories with simple linear dependencies", () => {
41
+ const engine = new MergeEngine(mockWorktreeManager);
42
+ const storyIds = ["US-001", "US-002", "US-003"];
43
+ const dependencies: StoryDependencies = {
44
+ "US-002": ["US-001"],
45
+ "US-003": ["US-002"],
46
+ };
47
+
48
+ // @ts-expect-error - accessing private method for testing
49
+ const sorted = engine.topologicalSort(storyIds, dependencies);
50
+
51
+ expect(sorted).toEqual(["US-001", "US-002", "US-003"]);
52
+ });
53
+
54
+ it("sorts stories with multiple dependencies", () => {
55
+ const engine = new MergeEngine(mockWorktreeManager);
56
+ const storyIds = ["US-001", "US-002", "US-003", "US-004"];
57
+ const dependencies: StoryDependencies = {
58
+ "US-003": ["US-001", "US-002"],
59
+ "US-004": ["US-002"],
60
+ };
61
+
62
+ // @ts-expect-error - accessing private method for testing
63
+ const sorted = engine.topologicalSort(storyIds, dependencies);
64
+
65
+ expect(sorted.length).toBe(4);
66
+
67
+ // US-001 and US-002 must come before US-003
68
+ const idx001 = sorted.indexOf("US-001");
69
+ const idx002 = sorted.indexOf("US-002");
70
+ const idx003 = sorted.indexOf("US-003");
71
+ expect(idx001).toBeLessThan(idx003);
72
+ expect(idx002).toBeLessThan(idx003);
73
+
74
+ // US-002 must come before US-004
75
+ const idx004 = sorted.indexOf("US-004");
76
+ expect(idx002).toBeLessThan(idx004);
77
+ });
78
+
79
+ it("handles diamond dependency pattern", () => {
80
+ const engine = new MergeEngine(mockWorktreeManager);
81
+ const storyIds = ["US-001", "US-002", "US-003", "US-004"];
82
+ const dependencies: StoryDependencies = {
83
+ "US-002": ["US-001"],
84
+ "US-003": ["US-001"],
85
+ "US-004": ["US-002", "US-003"],
86
+ };
87
+
88
+ // @ts-expect-error - accessing private method for testing
89
+ const sorted = engine.topologicalSort(storyIds, dependencies);
90
+
91
+ expect(sorted.length).toBe(4);
92
+
93
+ // US-001 must come first
94
+ expect(sorted[0]).toBe("US-001");
95
+
96
+ // US-002 and US-003 must come before US-004
97
+ const idx002 = sorted.indexOf("US-002");
98
+ const idx003 = sorted.indexOf("US-003");
99
+ const idx004 = sorted.indexOf("US-004");
100
+ expect(idx002).toBeLessThan(idx004);
101
+ expect(idx003).toBeLessThan(idx004);
102
+ });
103
+
104
+ it("throws on circular dependency", () => {
105
+ const engine = new MergeEngine(mockWorktreeManager);
106
+ const storyIds = ["US-001", "US-002", "US-003"];
107
+ const dependencies: StoryDependencies = {
108
+ "US-001": ["US-003"],
109
+ "US-002": ["US-001"],
110
+ "US-003": ["US-002"],
111
+ };
112
+
113
+ expect(() => {
114
+ // @ts-expect-error - accessing private method for testing
115
+ engine.topologicalSort(storyIds, dependencies);
116
+ }).toThrow("Circular dependency detected");
117
+ });
118
+
119
+ it("handles self-circular dependency", () => {
120
+ const engine = new MergeEngine(mockWorktreeManager);
121
+ const storyIds = ["US-001"];
122
+ const dependencies: StoryDependencies = {
123
+ "US-001": ["US-001"],
124
+ };
125
+
126
+ expect(() => {
127
+ // @ts-expect-error - accessing private method for testing
128
+ engine.topologicalSort(storyIds, dependencies);
129
+ }).toThrow("Circular dependency detected");
130
+ });
131
+
132
+ it("ignores dependencies not in storyIds list", () => {
133
+ const engine = new MergeEngine(mockWorktreeManager);
134
+ const storyIds = ["US-002", "US-003"];
135
+ const dependencies: StoryDependencies = {
136
+ "US-002": ["US-001"], // US-001 not in storyIds
137
+ "US-003": ["US-002"],
138
+ };
139
+
140
+ // @ts-expect-error - accessing private method for testing
141
+ const sorted = engine.topologicalSort(storyIds, dependencies);
142
+
143
+ // Should sort US-002 before US-003, ignoring missing US-001
144
+ expect(sorted).toEqual(["US-002", "US-003"]);
145
+ });
146
+
147
+ it("handles complex dependency graph", () => {
148
+ const engine = new MergeEngine(mockWorktreeManager);
149
+ const storyIds = ["US-001", "US-002", "US-003", "US-004", "US-005"];
150
+ const dependencies: StoryDependencies = {
151
+ "US-002": ["US-001"],
152
+ "US-003": ["US-001"],
153
+ "US-004": ["US-002", "US-003"],
154
+ "US-005": ["US-003"],
155
+ };
156
+
157
+ // @ts-expect-error - accessing private method for testing
158
+ const sorted = engine.topologicalSort(storyIds, dependencies);
159
+
160
+ expect(sorted.length).toBe(5);
161
+ expect(sorted[0]).toBe("US-001");
162
+
163
+ const idx002 = sorted.indexOf("US-002");
164
+ const idx003 = sorted.indexOf("US-003");
165
+ const idx004 = sorted.indexOf("US-004");
166
+ const idx005 = sorted.indexOf("US-005");
167
+
168
+ expect(idx002).toBeGreaterThan(0);
169
+ expect(idx003).toBeGreaterThan(0);
170
+ expect(idx002).toBeLessThan(idx004);
171
+ expect(idx003).toBeLessThan(idx004);
172
+ expect(idx003).toBeLessThan(idx005);
173
+ });
174
+
175
+ it("handles empty story list", () => {
176
+ const engine = new MergeEngine(mockWorktreeManager);
177
+ const storyIds: string[] = [];
178
+ const dependencies: StoryDependencies = {};
179
+
180
+ // @ts-expect-error - accessing private method for testing
181
+ const sorted = engine.topologicalSort(storyIds, dependencies);
182
+
183
+ expect(sorted.length).toBe(0);
184
+ });
185
+
186
+ it("handles single story", () => {
187
+ const engine = new MergeEngine(mockWorktreeManager);
188
+ const storyIds = ["US-001"];
189
+ const dependencies: StoryDependencies = {};
190
+
191
+ // @ts-expect-error - accessing private method for testing
192
+ const sorted = engine.topologicalSort(storyIds, dependencies);
193
+
194
+ expect(sorted).toEqual(["US-001"]);
195
+ });
196
+ });
197
+
198
+ // ─────────────────────────────────────────────────────────────────────────────
199
+ // MergeEngine.mergeAll
200
+ // ─────────────────────────────────────────────────────────────────────────────
201
+
202
+ describe("MergeEngine.mergeAll", () => {
203
+ it("skips stories with failed dependencies", async () => {
204
+ const mockManager = {
205
+ ...mockWorktreeManager,
206
+ remove: async () => {},
207
+ };
208
+
209
+ const engine = new MergeEngine(mockManager);
210
+
211
+ // Mock merge to fail for US-001
212
+ const originalMerge = engine.merge;
213
+ let callCount = 0;
214
+ engine.merge = async (_projectRoot: string, storyId: string) => {
215
+ callCount++;
216
+ if (storyId === "US-001") {
217
+ return { success: false, conflictFiles: ["file.ts"], retryCount: 0 };
218
+ }
219
+ return { success: true, retryCount: 0 };
220
+ };
221
+
222
+ const storyIds = ["US-001", "US-002"];
223
+ const dependencies: StoryDependencies = {
224
+ "US-002": ["US-001"],
225
+ };
226
+
227
+ const results = await engine.mergeAll("/tmp/project", storyIds, dependencies);
228
+
229
+ expect(results.length).toBe(2);
230
+ expect(results[0].success).toBe(false);
231
+ expect(results[1].success).toBe(false); // Skipped due to failed dependency
232
+ expect(callCount).toBe(1); // Only US-001 was attempted
233
+
234
+ // Restore original method
235
+ engine.merge = originalMerge;
236
+ });
237
+
238
+ it("continues with remaining stories after one fails", async () => {
239
+ const mockManager = {
240
+ ...mockWorktreeManager,
241
+ remove: async () => {},
242
+ };
243
+
244
+ const engine = new MergeEngine(mockManager);
245
+
246
+ // Mock merge to fail for US-002 only
247
+ const originalMerge = engine.merge;
248
+ engine.merge = async (_projectRoot: string, storyId: string) => {
249
+ if (storyId === "US-002") {
250
+ return { success: false, conflictFiles: ["file.ts"], retryCount: 0 };
251
+ }
252
+ return { success: true, retryCount: 0 };
253
+ };
254
+
255
+ const storyIds = ["US-001", "US-002", "US-003"];
256
+ const dependencies: StoryDependencies = {};
257
+
258
+ const results = await engine.mergeAll("/tmp/project", storyIds, dependencies);
259
+
260
+ expect(results.length).toBe(3);
261
+ expect(results[0].success).toBe(true); // US-001 succeeds
262
+ expect(results[1].success).toBe(false); // US-002 fails
263
+ expect(results[2].success).toBe(true); // US-003 succeeds
264
+
265
+ // Restore original method
266
+ engine.merge = originalMerge;
267
+ });
268
+ });
@@ -0,0 +1,276 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { type RunMetrics, type StoryMetrics, calculateAggregateMetrics, getLastRun } from "../../src/metrics";
3
+
4
+ describe("metrics/aggregator", () => {
5
+ describe("calculateAggregateMetrics", () => {
6
+ test("returns empty metrics for no runs", () => {
7
+ const aggregate = calculateAggregateMetrics([]);
8
+
9
+ expect(aggregate.totalRuns).toBe(0);
10
+ expect(aggregate.totalCost).toBe(0);
11
+ expect(aggregate.totalStories).toBe(0);
12
+ expect(aggregate.firstPassRate).toBe(0);
13
+ expect(aggregate.escalationRate).toBe(0);
14
+ expect(aggregate.avgCostPerStory).toBe(0);
15
+ expect(aggregate.avgCostPerFeature).toBe(0);
16
+ expect(Object.keys(aggregate.modelEfficiency)).toHaveLength(0);
17
+ expect(Object.keys(aggregate.complexityAccuracy)).toHaveLength(0);
18
+ });
19
+
20
+ test("calculates metrics for single run", () => {
21
+ const storyMetrics: StoryMetrics[] = [
22
+ {
23
+ storyId: "US-001",
24
+ complexity: "simple",
25
+ modelTier: "fast",
26
+ modelUsed: "claude-haiku-4.5",
27
+ attempts: 1,
28
+ finalTier: "fast",
29
+ success: true,
30
+ cost: 0.01,
31
+ durationMs: 30000,
32
+ firstPassSuccess: true,
33
+ startedAt: "2026-02-17T10:00:00.000Z",
34
+ completedAt: "2026-02-17T10:00:30.000Z",
35
+ },
36
+ {
37
+ storyId: "US-002",
38
+ complexity: "medium",
39
+ modelTier: "balanced",
40
+ modelUsed: "claude-sonnet-4.5",
41
+ attempts: 2,
42
+ finalTier: "powerful",
43
+ success: true,
44
+ cost: 0.05,
45
+ durationMs: 60000,
46
+ firstPassSuccess: false,
47
+ startedAt: "2026-02-17T10:01:00.000Z",
48
+ completedAt: "2026-02-17T10:02:00.000Z",
49
+ },
50
+ ];
51
+
52
+ const runMetrics: RunMetrics = {
53
+ runId: "run-test-1",
54
+ feature: "test-feature",
55
+ startedAt: "2026-02-17T10:00:00.000Z",
56
+ completedAt: "2026-02-17T10:02:00.000Z",
57
+ totalCost: 0.06,
58
+ totalStories: 2,
59
+ storiesCompleted: 2,
60
+ storiesFailed: 0,
61
+ totalDurationMs: 120000,
62
+ stories: storyMetrics,
63
+ };
64
+
65
+ const aggregate = calculateAggregateMetrics([runMetrics]);
66
+
67
+ expect(aggregate.totalRuns).toBe(1);
68
+ expect(aggregate.totalCost).toBe(0.06);
69
+ expect(aggregate.totalStories).toBe(2);
70
+ expect(aggregate.firstPassRate).toBe(0.5); // 1/2
71
+ expect(aggregate.escalationRate).toBe(0.5); // 1/2
72
+ expect(aggregate.avgCostPerStory).toBe(0.03);
73
+ expect(aggregate.avgCostPerFeature).toBe(0.06);
74
+ });
75
+
76
+ test("calculates model efficiency across multiple runs", () => {
77
+ const run1: RunMetrics = {
78
+ runId: "run-1",
79
+ feature: "feature-1",
80
+ startedAt: "2026-02-17T10:00:00.000Z",
81
+ completedAt: "2026-02-17T10:01:00.000Z",
82
+ totalCost: 0.02,
83
+ totalStories: 2,
84
+ storiesCompleted: 2,
85
+ storiesFailed: 0,
86
+ totalDurationMs: 60000,
87
+ stories: [
88
+ {
89
+ storyId: "US-001",
90
+ complexity: "simple",
91
+ modelTier: "fast",
92
+ modelUsed: "claude-haiku-4.5",
93
+ attempts: 1,
94
+ finalTier: "fast",
95
+ success: true,
96
+ cost: 0.01,
97
+ durationMs: 30000,
98
+ firstPassSuccess: true,
99
+ startedAt: "2026-02-17T10:00:00.000Z",
100
+ completedAt: "2026-02-17T10:00:30.000Z",
101
+ },
102
+ {
103
+ storyId: "US-002",
104
+ complexity: "simple",
105
+ modelTier: "fast",
106
+ modelUsed: "claude-haiku-4.5",
107
+ attempts: 1,
108
+ finalTier: "fast",
109
+ success: true,
110
+ cost: 0.01,
111
+ durationMs: 30000,
112
+ firstPassSuccess: true,
113
+ startedAt: "2026-02-17T10:00:30.000Z",
114
+ completedAt: "2026-02-17T10:01:00.000Z",
115
+ },
116
+ ],
117
+ };
118
+
119
+ const run2: RunMetrics = {
120
+ runId: "run-2",
121
+ feature: "feature-2",
122
+ startedAt: "2026-02-17T11:00:00.000Z",
123
+ completedAt: "2026-02-17T11:01:00.000Z",
124
+ totalCost: 0.05,
125
+ totalStories: 1,
126
+ storiesCompleted: 1,
127
+ storiesFailed: 0,
128
+ totalDurationMs: 60000,
129
+ stories: [
130
+ {
131
+ storyId: "US-003",
132
+ complexity: "complex",
133
+ modelTier: "powerful",
134
+ modelUsed: "claude-opus-4.6",
135
+ attempts: 1,
136
+ finalTier: "powerful",
137
+ success: true,
138
+ cost: 0.05,
139
+ durationMs: 60000,
140
+ firstPassSuccess: true,
141
+ startedAt: "2026-02-17T11:00:00.000Z",
142
+ completedAt: "2026-02-17T11:01:00.000Z",
143
+ },
144
+ ],
145
+ };
146
+
147
+ const aggregate = calculateAggregateMetrics([run1, run2]);
148
+
149
+ expect(aggregate.totalRuns).toBe(2);
150
+ expect(aggregate.totalStories).toBe(3);
151
+
152
+ // Check haiku model efficiency
153
+ expect(aggregate.modelEfficiency["claude-haiku-4.5"]).toBeDefined();
154
+ expect(aggregate.modelEfficiency["claude-haiku-4.5"].attempts).toBe(2);
155
+ expect(aggregate.modelEfficiency["claude-haiku-4.5"].successes).toBe(2);
156
+ expect(aggregate.modelEfficiency["claude-haiku-4.5"].passRate).toBe(1.0);
157
+ expect(aggregate.modelEfficiency["claude-haiku-4.5"].totalCost).toBe(0.02);
158
+
159
+ // Check opus model efficiency
160
+ expect(aggregate.modelEfficiency["claude-opus-4.6"]).toBeDefined();
161
+ expect(aggregate.modelEfficiency["claude-opus-4.6"].attempts).toBe(1);
162
+ expect(aggregate.modelEfficiency["claude-opus-4.6"].successes).toBe(1);
163
+ expect(aggregate.modelEfficiency["claude-opus-4.6"].passRate).toBe(1.0);
164
+ expect(aggregate.modelEfficiency["claude-opus-4.6"].totalCost).toBe(0.05);
165
+ });
166
+
167
+ test("calculates complexity accuracy with mismatches", () => {
168
+ const runMetrics: RunMetrics = {
169
+ runId: "run-test-1",
170
+ feature: "test-feature",
171
+ startedAt: "2026-02-17T10:00:00.000Z",
172
+ completedAt: "2026-02-17T10:05:00.000Z",
173
+ totalCost: 0.15,
174
+ totalStories: 3,
175
+ storiesCompleted: 3,
176
+ storiesFailed: 0,
177
+ totalDurationMs: 300000,
178
+ stories: [
179
+ {
180
+ storyId: "US-001",
181
+ complexity: "simple",
182
+ modelTier: "fast",
183
+ modelUsed: "claude-haiku-4.5",
184
+ attempts: 1,
185
+ finalTier: "fast",
186
+ success: true,
187
+ cost: 0.01,
188
+ durationMs: 30000,
189
+ firstPassSuccess: true,
190
+ startedAt: "2026-02-17T10:00:00.000Z",
191
+ completedAt: "2026-02-17T10:00:30.000Z",
192
+ },
193
+ {
194
+ storyId: "US-002",
195
+ complexity: "simple",
196
+ modelTier: "fast",
197
+ modelUsed: "claude-sonnet-4.5",
198
+ attempts: 2,
199
+ finalTier: "balanced",
200
+ success: true,
201
+ cost: 0.04,
202
+ durationMs: 120000,
203
+ firstPassSuccess: false,
204
+ startedAt: "2026-02-17T10:01:00.000Z",
205
+ completedAt: "2026-02-17T10:03:00.000Z",
206
+ },
207
+ {
208
+ storyId: "US-003",
209
+ complexity: "medium",
210
+ modelTier: "balanced",
211
+ modelUsed: "claude-sonnet-4.5",
212
+ attempts: 1,
213
+ finalTier: "balanced",
214
+ success: true,
215
+ cost: 0.03,
216
+ durationMs: 90000,
217
+ firstPassSuccess: true,
218
+ startedAt: "2026-02-17T10:03:00.000Z",
219
+ completedAt: "2026-02-17T10:04:30.000Z",
220
+ },
221
+ ],
222
+ };
223
+
224
+ const aggregate = calculateAggregateMetrics([runMetrics]);
225
+
226
+ // Check simple complexity accuracy
227
+ expect(aggregate.complexityAccuracy.simple).toBeDefined();
228
+ expect(aggregate.complexityAccuracy.simple.predicted).toBe(2);
229
+ expect(aggregate.complexityAccuracy.simple.mismatchRate).toBe(0.5); // 1 mismatch out of 2
230
+
231
+ // Check medium complexity accuracy
232
+ expect(aggregate.complexityAccuracy.medium).toBeDefined();
233
+ expect(aggregate.complexityAccuracy.medium.predicted).toBe(1);
234
+ expect(aggregate.complexityAccuracy.medium.mismatchRate).toBe(0); // no mismatch
235
+ });
236
+ });
237
+
238
+ describe("getLastRun", () => {
239
+ test("returns null for empty runs", () => {
240
+ expect(getLastRun([])).toBeNull();
241
+ });
242
+
243
+ test("returns last run from array", () => {
244
+ const runs: RunMetrics[] = [
245
+ {
246
+ runId: "run-1",
247
+ feature: "feature-1",
248
+ startedAt: "2026-02-17T10:00:00.000Z",
249
+ completedAt: "2026-02-17T10:01:00.000Z",
250
+ totalCost: 0.01,
251
+ totalStories: 1,
252
+ storiesCompleted: 1,
253
+ storiesFailed: 0,
254
+ totalDurationMs: 60000,
255
+ stories: [],
256
+ },
257
+ {
258
+ runId: "run-2",
259
+ feature: "feature-2",
260
+ startedAt: "2026-02-17T11:00:00.000Z",
261
+ completedAt: "2026-02-17T11:01:00.000Z",
262
+ totalCost: 0.02,
263
+ totalStories: 2,
264
+ storiesCompleted: 2,
265
+ storiesFailed: 0,
266
+ totalDurationMs: 120000,
267
+ stories: [],
268
+ },
269
+ ];
270
+
271
+ const lastRun = getLastRun(runs);
272
+ expect(lastRun).not.toBeNull();
273
+ expect(lastRun?.runId).toBe("run-2");
274
+ });
275
+ });
276
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { NaxConfig } from "../../../src/config/schema.js";
3
+ import type { PromptOptimizerInput, PromptOptimizerResult } from "../../../src/optimizer/types.js";
4
+
5
+ // NoopOptimizer implementation is imported here
6
+ // We're writing tests FIRST, so this will fail until we implement it
7
+ import { NoopOptimizer } from "../../../src/optimizer/noop.optimizer.js";
8
+
9
+ describe("NoopOptimizer", () => {
10
+ const mockConfig: NaxConfig = {
11
+ modelTier: "fast",
12
+ provider: "anthropic",
13
+ apiKeys: {},
14
+ optimizer: {
15
+ enabled: false,
16
+ strategy: "noop",
17
+ },
18
+ };
19
+
20
+ test("should have name 'noop'", () => {
21
+ const optimizer = new NoopOptimizer();
22
+ expect(optimizer.name).toBe("noop");
23
+ });
24
+
25
+ test("should return prompt unchanged", async () => {
26
+ const optimizer = new NoopOptimizer();
27
+ const input: PromptOptimizerInput = {
28
+ prompt: "This is a test prompt with some content.",
29
+ stories: [],
30
+ config: mockConfig,
31
+ };
32
+
33
+ const result = await optimizer.optimize(input);
34
+
35
+ expect(result.prompt).toBe(input.prompt);
36
+ });
37
+
38
+ test("should return zero savings", async () => {
39
+ const optimizer = new NoopOptimizer();
40
+ const input: PromptOptimizerInput = {
41
+ prompt: "Any prompt content here.",
42
+ stories: [],
43
+ config: mockConfig,
44
+ };
45
+
46
+ const result = await optimizer.optimize(input);
47
+
48
+ expect(result.savings).toBe(0);
49
+ });
50
+
51
+ test("should return equal original and optimized token counts", async () => {
52
+ const optimizer = new NoopOptimizer();
53
+ const input: PromptOptimizerInput = {
54
+ prompt: "A prompt with several words to estimate tokens.",
55
+ stories: [],
56
+ config: mockConfig,
57
+ };
58
+
59
+ const result = await optimizer.optimize(input);
60
+
61
+ expect(result.originalTokens).toBe(result.optimizedTokens);
62
+ expect(result.originalTokens).toBeGreaterThan(0);
63
+ });
64
+
65
+ test("should return empty applied rules", async () => {
66
+ const optimizer = new NoopOptimizer();
67
+ const input: PromptOptimizerInput = {
68
+ prompt: "Test prompt",
69
+ stories: [],
70
+ config: mockConfig,
71
+ };
72
+
73
+ const result = await optimizer.optimize(input);
74
+
75
+ expect(result.appliedRules).toEqual([]);
76
+ });
77
+
78
+ test("should handle empty prompt", async () => {
79
+ const optimizer = new NoopOptimizer();
80
+ const input: PromptOptimizerInput = {
81
+ prompt: "",
82
+ stories: [],
83
+ config: mockConfig,
84
+ };
85
+
86
+ const result = await optimizer.optimize(input);
87
+
88
+ expect(result.prompt).toBe("");
89
+ expect(result.originalTokens).toBe(0);
90
+ expect(result.optimizedTokens).toBe(0);
91
+ expect(result.savings).toBe(0);
92
+ });
93
+
94
+ test("should handle very long prompt", async () => {
95
+ const optimizer = new NoopOptimizer();
96
+ const longPrompt = "a".repeat(10000);
97
+ const input: PromptOptimizerInput = {
98
+ prompt: longPrompt,
99
+ stories: [],
100
+ config: mockConfig,
101
+ };
102
+
103
+ const result = await optimizer.optimize(input);
104
+
105
+ expect(result.prompt).toBe(longPrompt);
106
+ expect(result.originalTokens).toBe(result.optimizedTokens);
107
+ });
108
+
109
+ test("should preserve multiline prompts", async () => {
110
+ const optimizer = new NoopOptimizer();
111
+ const multilinePrompt = `Line 1
112
+ Line 2
113
+
114
+ Line 4 with blank line above`;
115
+ const input: PromptOptimizerInput = {
116
+ prompt: multilinePrompt,
117
+ stories: [],
118
+ config: mockConfig,
119
+ };
120
+
121
+ const result = await optimizer.optimize(input);
122
+
123
+ expect(result.prompt).toBe(multilinePrompt);
124
+ });
125
+ });