@nathapp/nax 0.50.2 → 0.51.0

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 (352) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/nax.js +579 -373
  3. package/package.json +1 -3
  4. package/bin/nax.ts +0 -1195
  5. package/src/acceptance/fix-generator.ts +0 -322
  6. package/src/acceptance/generator.ts +0 -423
  7. package/src/acceptance/index.ts +0 -42
  8. package/src/acceptance/refinement.ts +0 -224
  9. package/src/acceptance/templates/cli.ts +0 -47
  10. package/src/acceptance/templates/component.ts +0 -78
  11. package/src/acceptance/templates/e2e.ts +0 -43
  12. package/src/acceptance/templates/index.ts +0 -21
  13. package/src/acceptance/templates/snapshot.ts +0 -50
  14. package/src/acceptance/templates/unit.ts +0 -48
  15. package/src/acceptance/types.ts +0 -135
  16. package/src/agents/acp/adapter.ts +0 -888
  17. package/src/agents/acp/cost.ts +0 -9
  18. package/src/agents/acp/index.ts +0 -7
  19. package/src/agents/acp/interaction-bridge.ts +0 -126
  20. package/src/agents/acp/parser.ts +0 -119
  21. package/src/agents/acp/spawn-client.ts +0 -373
  22. package/src/agents/acp/types.ts +0 -22
  23. package/src/agents/aider/adapter.ts +0 -135
  24. package/src/agents/claude/adapter.ts +0 -258
  25. package/src/agents/claude/complete.ts +0 -80
  26. package/src/agents/claude/cost.ts +0 -16
  27. package/src/agents/claude/execution.ts +0 -215
  28. package/src/agents/claude/index.ts +0 -3
  29. package/src/agents/claude/interactive.ts +0 -77
  30. package/src/agents/claude/plan.ts +0 -179
  31. package/src/agents/codex/adapter.ts +0 -153
  32. package/src/agents/cost/calculate.ts +0 -154
  33. package/src/agents/cost/index.ts +0 -10
  34. package/src/agents/cost/parse.ts +0 -97
  35. package/src/agents/cost/pricing.ts +0 -59
  36. package/src/agents/cost/types.ts +0 -45
  37. package/src/agents/gemini/adapter.ts +0 -177
  38. package/src/agents/index.ts +0 -18
  39. package/src/agents/opencode/adapter.ts +0 -106
  40. package/src/agents/registry.ts +0 -136
  41. package/src/agents/shared/decompose.ts +0 -154
  42. package/src/agents/shared/model-resolution.ts +0 -43
  43. package/src/agents/shared/types-extended.ts +0 -164
  44. package/src/agents/shared/validation.ts +0 -69
  45. package/src/agents/shared/version-detection.ts +0 -109
  46. package/src/agents/types.ts +0 -205
  47. package/src/analyze/classifier.ts +0 -282
  48. package/src/analyze/index.ts +0 -16
  49. package/src/analyze/scanner.ts +0 -171
  50. package/src/analyze/types.ts +0 -51
  51. package/src/cli/accept.ts +0 -108
  52. package/src/cli/agents.ts +0 -87
  53. package/src/cli/analyze-parser.ts +0 -291
  54. package/src/cli/analyze.ts +0 -352
  55. package/src/cli/config-descriptions.ts +0 -218
  56. package/src/cli/config-diff.ts +0 -103
  57. package/src/cli/config-display.ts +0 -285
  58. package/src/cli/config-get.ts +0 -55
  59. package/src/cli/config.ts +0 -14
  60. package/src/cli/constitution.ts +0 -17
  61. package/src/cli/diagnose-analysis.ts +0 -159
  62. package/src/cli/diagnose-formatter.ts +0 -87
  63. package/src/cli/diagnose.ts +0 -203
  64. package/src/cli/generate.ts +0 -250
  65. package/src/cli/index.ts +0 -42
  66. package/src/cli/init-context.ts +0 -405
  67. package/src/cli/init-detect.ts +0 -303
  68. package/src/cli/init.ts +0 -296
  69. package/src/cli/interact.ts +0 -295
  70. package/src/cli/plan.ts +0 -509
  71. package/src/cli/plugins.ts +0 -122
  72. package/src/cli/prompts-export.ts +0 -58
  73. package/src/cli/prompts-init.ts +0 -200
  74. package/src/cli/prompts-main.ts +0 -183
  75. package/src/cli/prompts-shared.ts +0 -70
  76. package/src/cli/prompts-tdd.ts +0 -88
  77. package/src/cli/prompts.ts +0 -17
  78. package/src/cli/runs.ts +0 -174
  79. package/src/cli/status-cost.ts +0 -151
  80. package/src/cli/status-features.ts +0 -405
  81. package/src/cli/status.ts +0 -13
  82. package/src/commands/common.ts +0 -171
  83. package/src/commands/diagnose.ts +0 -17
  84. package/src/commands/index.ts +0 -9
  85. package/src/commands/logs-formatter.ts +0 -201
  86. package/src/commands/logs-reader.ts +0 -171
  87. package/src/commands/logs.ts +0 -103
  88. package/src/commands/precheck.ts +0 -86
  89. package/src/commands/runs.ts +0 -220
  90. package/src/commands/unlock.ts +0 -96
  91. package/src/config/defaults.ts +0 -217
  92. package/src/config/index.ts +0 -22
  93. package/src/config/loader.ts +0 -143
  94. package/src/config/merge.ts +0 -106
  95. package/src/config/merger.ts +0 -147
  96. package/src/config/path-security.ts +0 -121
  97. package/src/config/paths.ts +0 -27
  98. package/src/config/permissions.ts +0 -63
  99. package/src/config/runtime-types.ts +0 -520
  100. package/src/config/schema-types.ts +0 -53
  101. package/src/config/schema.ts +0 -60
  102. package/src/config/schemas.ts +0 -425
  103. package/src/config/test-strategy.ts +0 -71
  104. package/src/config/types.ts +0 -57
  105. package/src/config/validate.ts +0 -103
  106. package/src/constitution/generator.ts +0 -158
  107. package/src/constitution/generators/aider.ts +0 -41
  108. package/src/constitution/generators/claude.ts +0 -35
  109. package/src/constitution/generators/cursor.ts +0 -36
  110. package/src/constitution/generators/opencode.ts +0 -38
  111. package/src/constitution/generators/types.ts +0 -33
  112. package/src/constitution/generators/windsurf.ts +0 -36
  113. package/src/constitution/index.ts +0 -11
  114. package/src/constitution/loader.ts +0 -121
  115. package/src/constitution/types.ts +0 -31
  116. package/src/context/auto-detect.ts +0 -228
  117. package/src/context/builder.ts +0 -299
  118. package/src/context/elements.ts +0 -122
  119. package/src/context/formatter.ts +0 -107
  120. package/src/context/generator.ts +0 -343
  121. package/src/context/generators/aider.ts +0 -34
  122. package/src/context/generators/claude.ts +0 -28
  123. package/src/context/generators/codex.ts +0 -28
  124. package/src/context/generators/cursor.ts +0 -28
  125. package/src/context/generators/gemini.ts +0 -28
  126. package/src/context/generators/opencode.ts +0 -30
  127. package/src/context/generators/windsurf.ts +0 -28
  128. package/src/context/greenfield.ts +0 -114
  129. package/src/context/index.ts +0 -34
  130. package/src/context/injector.ts +0 -279
  131. package/src/context/parent-context.ts +0 -39
  132. package/src/context/test-scanner.ts +0 -370
  133. package/src/context/types.ts +0 -98
  134. package/src/decompose/apply.ts +0 -50
  135. package/src/decompose/builder.ts +0 -181
  136. package/src/decompose/index.ts +0 -8
  137. package/src/decompose/sections/codebase.ts +0 -26
  138. package/src/decompose/sections/constraints.ts +0 -32
  139. package/src/decompose/sections/index.ts +0 -4
  140. package/src/decompose/sections/sibling-stories.ts +0 -25
  141. package/src/decompose/sections/target-story.ts +0 -31
  142. package/src/decompose/types.ts +0 -55
  143. package/src/decompose/validators/complexity.ts +0 -45
  144. package/src/decompose/validators/coverage.ts +0 -134
  145. package/src/decompose/validators/dependency.ts +0 -91
  146. package/src/decompose/validators/index.ts +0 -35
  147. package/src/decompose/validators/overlap.ts +0 -128
  148. package/src/errors.ts +0 -67
  149. package/src/execution/batching.ts +0 -157
  150. package/src/execution/crash-heartbeat.ts +0 -77
  151. package/src/execution/crash-recovery.ts +0 -79
  152. package/src/execution/crash-signals.ts +0 -165
  153. package/src/execution/crash-writer.ts +0 -154
  154. package/src/execution/deferred-review.ts +0 -105
  155. package/src/execution/dry-run.ts +0 -81
  156. package/src/execution/escalation/escalation.ts +0 -46
  157. package/src/execution/escalation/index.ts +0 -13
  158. package/src/execution/escalation/tier-escalation.ts +0 -346
  159. package/src/execution/escalation/tier-outcome.ts +0 -143
  160. package/src/execution/executor-types.ts +0 -73
  161. package/src/execution/helpers.ts +0 -38
  162. package/src/execution/index.ts +0 -27
  163. package/src/execution/iteration-runner.ts +0 -160
  164. package/src/execution/lifecycle/acceptance-loop.ts +0 -280
  165. package/src/execution/lifecycle/headless-formatter.ts +0 -83
  166. package/src/execution/lifecycle/index.ts +0 -11
  167. package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
  168. package/src/execution/lifecycle/precheck-runner.ts +0 -140
  169. package/src/execution/lifecycle/run-cleanup.ts +0 -81
  170. package/src/execution/lifecycle/run-completion.ts +0 -247
  171. package/src/execution/lifecycle/run-initialization.ts +0 -187
  172. package/src/execution/lifecycle/run-regression.ts +0 -305
  173. package/src/execution/lifecycle/run-setup.ts +0 -240
  174. package/src/execution/lifecycle/story-size-prompts.ts +0 -123
  175. package/src/execution/lock.ts +0 -129
  176. package/src/execution/parallel-coordinator.ts +0 -281
  177. package/src/execution/parallel-executor-rectification-pass.ts +0 -117
  178. package/src/execution/parallel-executor-rectify.ts +0 -136
  179. package/src/execution/parallel-executor.ts +0 -330
  180. package/src/execution/parallel-worker.ts +0 -149
  181. package/src/execution/parallel.ts +0 -13
  182. package/src/execution/pid-registry.ts +0 -275
  183. package/src/execution/pipeline-result-handler.ts +0 -221
  184. package/src/execution/progress.ts +0 -27
  185. package/src/execution/queue-handler.ts +0 -109
  186. package/src/execution/runner-completion.ts +0 -171
  187. package/src/execution/runner-execution.ts +0 -243
  188. package/src/execution/runner-setup.ts +0 -86
  189. package/src/execution/runner.ts +0 -265
  190. package/src/execution/sequential-executor.ts +0 -219
  191. package/src/execution/status-file.ts +0 -264
  192. package/src/execution/status-writer.ts +0 -181
  193. package/src/execution/story-context.ts +0 -266
  194. package/src/execution/story-selector.ts +0 -76
  195. package/src/execution/test-output-parser.ts +0 -14
  196. package/src/execution/timeout-handler.ts +0 -100
  197. package/src/hooks/index.ts +0 -2
  198. package/src/hooks/runner.ts +0 -280
  199. package/src/hooks/types.ts +0 -79
  200. package/src/interaction/chain.ts +0 -170
  201. package/src/interaction/index.ts +0 -61
  202. package/src/interaction/init.ts +0 -84
  203. package/src/interaction/plugins/auto.ts +0 -243
  204. package/src/interaction/plugins/cli.ts +0 -300
  205. package/src/interaction/plugins/telegram.ts +0 -384
  206. package/src/interaction/plugins/webhook.ts +0 -286
  207. package/src/interaction/state.ts +0 -171
  208. package/src/interaction/triggers.ts +0 -250
  209. package/src/interaction/types.ts +0 -170
  210. package/src/logger/formatters.ts +0 -84
  211. package/src/logger/index.ts +0 -16
  212. package/src/logger/logger.ts +0 -296
  213. package/src/logger/types.ts +0 -48
  214. package/src/logging/formatter.ts +0 -355
  215. package/src/logging/index.ts +0 -22
  216. package/src/logging/types.ts +0 -93
  217. package/src/metrics/aggregator.ts +0 -191
  218. package/src/metrics/index.ts +0 -14
  219. package/src/metrics/tracker.ts +0 -200
  220. package/src/metrics/types.ts +0 -115
  221. package/src/optimizer/index.ts +0 -63
  222. package/src/optimizer/noop.optimizer.ts +0 -24
  223. package/src/optimizer/rule-based.optimizer.ts +0 -248
  224. package/src/optimizer/types.ts +0 -53
  225. package/src/pipeline/event-bus.ts +0 -297
  226. package/src/pipeline/events.ts +0 -130
  227. package/src/pipeline/index.ts +0 -19
  228. package/src/pipeline/runner.ts +0 -149
  229. package/src/pipeline/stages/acceptance-setup.ts +0 -140
  230. package/src/pipeline/stages/acceptance.ts +0 -215
  231. package/src/pipeline/stages/autofix.ts +0 -262
  232. package/src/pipeline/stages/completion.ts +0 -110
  233. package/src/pipeline/stages/constitution.ts +0 -63
  234. package/src/pipeline/stages/context.ts +0 -122
  235. package/src/pipeline/stages/execution.ts +0 -359
  236. package/src/pipeline/stages/index.ts +0 -86
  237. package/src/pipeline/stages/optimizer.ts +0 -74
  238. package/src/pipeline/stages/prompt.ts +0 -79
  239. package/src/pipeline/stages/queue-check.ts +0 -103
  240. package/src/pipeline/stages/rectify.ts +0 -101
  241. package/src/pipeline/stages/regression.ts +0 -99
  242. package/src/pipeline/stages/review.ts +0 -94
  243. package/src/pipeline/stages/routing.ts +0 -276
  244. package/src/pipeline/stages/verify.ts +0 -286
  245. package/src/pipeline/subscribers/events-writer.ts +0 -135
  246. package/src/pipeline/subscribers/hooks.ts +0 -179
  247. package/src/pipeline/subscribers/interaction.ts +0 -103
  248. package/src/pipeline/subscribers/registry.ts +0 -73
  249. package/src/pipeline/subscribers/reporters.ts +0 -174
  250. package/src/pipeline/types.ts +0 -220
  251. package/src/plugins/extensions.ts +0 -225
  252. package/src/plugins/index.ts +0 -33
  253. package/src/plugins/loader.ts +0 -352
  254. package/src/plugins/plugin-logger.ts +0 -41
  255. package/src/plugins/registry.ts +0 -168
  256. package/src/plugins/types.ts +0 -206
  257. package/src/plugins/validator.ts +0 -352
  258. package/src/prd/index.ts +0 -220
  259. package/src/prd/schema.ts +0 -268
  260. package/src/prd/types.ts +0 -273
  261. package/src/prd/validate.ts +0 -41
  262. package/src/precheck/checks-agents.ts +0 -63
  263. package/src/precheck/checks-blockers.ts +0 -23
  264. package/src/precheck/checks-cli.ts +0 -68
  265. package/src/precheck/checks-config.ts +0 -102
  266. package/src/precheck/checks-git.ts +0 -117
  267. package/src/precheck/checks-system.ts +0 -101
  268. package/src/precheck/checks-warnings.ts +0 -221
  269. package/src/precheck/checks.ts +0 -36
  270. package/src/precheck/index.ts +0 -374
  271. package/src/precheck/story-size-gate.ts +0 -144
  272. package/src/precheck/types.ts +0 -31
  273. package/src/prompts/builder.ts +0 -166
  274. package/src/prompts/index.ts +0 -2
  275. package/src/prompts/loader.ts +0 -43
  276. package/src/prompts/sections/conventions.ts +0 -19
  277. package/src/prompts/sections/hermetic.ts +0 -41
  278. package/src/prompts/sections/index.ts +0 -12
  279. package/src/prompts/sections/isolation.ts +0 -70
  280. package/src/prompts/sections/role-task.ts +0 -182
  281. package/src/prompts/sections/story.ts +0 -55
  282. package/src/prompts/sections/verdict.ts +0 -70
  283. package/src/prompts/types.ts +0 -21
  284. package/src/queue/index.ts +0 -2
  285. package/src/queue/manager.ts +0 -254
  286. package/src/queue/types.ts +0 -54
  287. package/src/review/index.ts +0 -8
  288. package/src/review/orchestrator.ts +0 -154
  289. package/src/review/runner.ts +0 -303
  290. package/src/review/types.ts +0 -70
  291. package/src/routing/batch-route.ts +0 -35
  292. package/src/routing/builder.ts +0 -81
  293. package/src/routing/chain.ts +0 -75
  294. package/src/routing/content-hash.ts +0 -25
  295. package/src/routing/index.ts +0 -20
  296. package/src/routing/loader.ts +0 -62
  297. package/src/routing/router.ts +0 -305
  298. package/src/routing/strategies/adaptive.ts +0 -215
  299. package/src/routing/strategies/index.ts +0 -8
  300. package/src/routing/strategies/keyword.ts +0 -180
  301. package/src/routing/strategies/llm-prompts.ts +0 -224
  302. package/src/routing/strategies/llm.ts +0 -320
  303. package/src/routing/strategies/manual.ts +0 -50
  304. package/src/routing/strategy.ts +0 -102
  305. package/src/tdd/cleanup.ts +0 -120
  306. package/src/tdd/index.ts +0 -22
  307. package/src/tdd/isolation.ts +0 -117
  308. package/src/tdd/orchestrator.ts +0 -406
  309. package/src/tdd/prompts.ts +0 -40
  310. package/src/tdd/rectification-gate.ts +0 -274
  311. package/src/tdd/session-runner.ts +0 -263
  312. package/src/tdd/types.ts +0 -84
  313. package/src/tdd/verdict-reader.ts +0 -266
  314. package/src/tdd/verdict.ts +0 -152
  315. package/src/tui/App.tsx +0 -265
  316. package/src/tui/components/AgentPanel.tsx +0 -75
  317. package/src/tui/components/CostOverlay.tsx +0 -118
  318. package/src/tui/components/HelpOverlay.tsx +0 -107
  319. package/src/tui/components/StatusBar.tsx +0 -63
  320. package/src/tui/components/StoriesPanel.tsx +0 -177
  321. package/src/tui/hooks/useKeyboard.ts +0 -142
  322. package/src/tui/hooks/useLayout.ts +0 -137
  323. package/src/tui/hooks/usePipelineEvents.ts +0 -183
  324. package/src/tui/hooks/usePty.ts +0 -189
  325. package/src/tui/index.tsx +0 -38
  326. package/src/tui/types.ts +0 -76
  327. package/src/utils/errors.ts +0 -12
  328. package/src/utils/git.ts +0 -245
  329. package/src/utils/json-file.ts +0 -72
  330. package/src/utils/log-test-output.ts +0 -25
  331. package/src/utils/path-security.ts +0 -73
  332. package/src/utils/queue-writer.ts +0 -54
  333. package/src/verification/crash-detector.ts +0 -34
  334. package/src/verification/executor.ts +0 -250
  335. package/src/verification/index.ts +0 -12
  336. package/src/verification/orchestrator-types.ts +0 -154
  337. package/src/verification/orchestrator.ts +0 -76
  338. package/src/verification/parser.ts +0 -220
  339. package/src/verification/rectification-loop.ts +0 -172
  340. package/src/verification/rectification.ts +0 -108
  341. package/src/verification/runners.ts +0 -129
  342. package/src/verification/smart-runner.ts +0 -307
  343. package/src/verification/strategies/acceptance.ts +0 -136
  344. package/src/verification/strategies/regression.ts +0 -90
  345. package/src/verification/strategies/scoped.ts +0 -154
  346. package/src/verification/types.ts +0 -117
  347. package/src/version.ts +0 -40
  348. package/src/worktree/dispatcher.ts +0 -6
  349. package/src/worktree/index.ts +0 -2
  350. package/src/worktree/manager.ts +0 -193
  351. package/src/worktree/merge.ts +0 -302
  352. package/src/worktree/types.ts +0 -4
@@ -1,276 +0,0 @@
1
- /**
2
- * Routing Stage
3
- *
4
- * Classifies story complexity and determines model tier + test strategy.
5
- * Uses cached complexity/testStrategy/modelTier from story if contentHash matches.
6
- * modelTier: uses escalated tier if explicitly set (BUG-032), otherwise derives from config.
7
- *
8
- * RRP-003: contentHash staleness detection — if story.routing.contentHash is missing or
9
- * does not match the current story content, treats cached routing as a miss and re-classifies.
10
- *
11
- * SD-004: Oversized story detection — after routing, checks if story exceeds
12
- * config.decompose.maxAcceptanceCriteria with complex/expert complexity. Decomposes
13
- * based on trigger mode (auto / confirm / disabled).
14
- *
15
- * @returns
16
- * - `continue`: Routing determined, proceed to next stage
17
- * - `skip`: Story was decomposed into substories; runner should pick up first substory
18
- *
19
- * @example
20
- * ```ts
21
- * // Story has cached routing with matching contentHash
22
- * await routingStage.execute(ctx);
23
- * // ctx.routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "..." }
24
- * // modelTier is derived from current config.autoMode.complexityRouting
25
- * ```
26
- */
27
-
28
- import { join } from "node:path";
29
- import { getAgent } from "../../agents/registry";
30
- import type { NaxConfig } from "../../config";
31
- import { isGreenfieldStory } from "../../context/greenfield";
32
- import { applyDecomposition } from "../../decompose/apply";
33
- import { DecomposeBuilder } from "../../decompose/builder";
34
- import type { DecomposeConfig as BuilderDecomposeConfig, DecomposeResult } from "../../decompose/types";
35
- import { checkStoryOversized } from "../../interaction/triggers";
36
- import { getLogger } from "../../logger";
37
- import { savePRD } from "../../prd";
38
- import type { PRD, UserStory } from "../../prd";
39
- import { complexityToModelTier, computeStoryContentHash, routeStory } from "../../routing";
40
- import { clearCache, routeBatch } from "../../routing/strategies/llm";
41
- import type { PipelineContext, PipelineStage, RoutingResult, StageResult } from "../types";
42
-
43
- /**
44
- * Run story decomposition using DecomposeBuilder.
45
- * Used as the default implementation in _routingDeps.runDecompose.
46
- * In production, replace with an LLM-backed adapter.
47
- */
48
- async function runDecompose(
49
- story: UserStory,
50
- prd: PRD,
51
- config: NaxConfig,
52
- _workdir: string,
53
- agentGetFn?: import("../types").AgentGetFn,
54
- ): Promise<DecomposeResult> {
55
- const naxDecompose = config.decompose;
56
- const builderConfig: BuilderDecomposeConfig = {
57
- maxSubStories: naxDecompose?.maxSubstories ?? 5,
58
- maxComplexity: naxDecompose?.maxSubstoryComplexity ?? "medium",
59
- maxRetries: naxDecompose?.maxRetries ?? 2,
60
- };
61
-
62
- // Resolve the default agent adapter for LLM-backed decompose.
63
- // Falls back to agent.complete() with JSON mode — works with both CLI and ACP adapters.
64
- const agent = (agentGetFn ?? getAgent)(config.autoMode.defaultAgent);
65
- if (!agent) {
66
- throw new Error(`[decompose] Agent "${config.autoMode.defaultAgent}" not found — cannot decompose`);
67
- }
68
-
69
- // Resolve decompose model: config.decompose.model tier → actual model string
70
- const decomposeTier = naxDecompose?.model ?? "balanced";
71
- let decomposeModel: string | undefined;
72
- try {
73
- const { resolveModel } = await import("../../config/schema");
74
- const models = config.models as Record<string, unknown>;
75
- const entry = models[decomposeTier] ?? models.balanced;
76
- if (entry) decomposeModel = resolveModel(entry as Parameters<typeof resolveModel>[0]).model;
77
- } catch {
78
- // resolveModel can throw on malformed entries — fall through to let complete() handle it
79
- }
80
-
81
- const storySessionName = `nax-decompose-${story.id.toLowerCase()}`;
82
- const adapter = {
83
- async decompose(prompt: string): Promise<string> {
84
- return agent.complete(prompt, {
85
- model: decomposeModel,
86
- jsonMode: true,
87
- config,
88
- sessionName: storySessionName,
89
- });
90
- },
91
- };
92
-
93
- return DecomposeBuilder.for(story).prd(prd).config(builderConfig).decompose(adapter);
94
- }
95
-
96
- export const routingStage: PipelineStage = {
97
- name: "routing",
98
- enabled: () => true,
99
-
100
- async execute(ctx: PipelineContext): Promise<StageResult> {
101
- const logger = getLogger();
102
-
103
- // Resolve agent adapter for LLM routing (shared with execution)
104
- const agentName = ctx.config.execution?.agent ?? "claude";
105
- const adapter = (ctx.agentGetFn ?? _routingDeps.getAgent)(agentName);
106
-
107
- // Staleness detection (RRP-003):
108
- // - story.routing absent → cache miss (no prior routing)
109
- // - story.routing + no contentHash → legacy cache hit (manual / pre-RRP-003 routing, honor as-is)
110
- // - story.routing + contentHash matches → cache hit
111
- // - story.routing + contentHash mismatches → cache miss (stale, re-classify)
112
- const hasExistingRouting = ctx.story.routing !== undefined;
113
- const hasContentHash = ctx.story.routing?.contentHash !== undefined;
114
- let currentHash: string | undefined;
115
- let hashMatch = false;
116
- if (hasContentHash) {
117
- currentHash = _routingDeps.computeStoryContentHash(ctx.story);
118
- hashMatch = ctx.story.routing?.contentHash === currentHash;
119
- }
120
- const isCacheHit = hasExistingRouting && (!hasContentHash || hashMatch);
121
-
122
- let routing: { complexity: string; testStrategy: string; modelTier: string; reasoning?: string };
123
-
124
- if (isCacheHit) {
125
- // Cache hit: legacy routing (no contentHash) or matching contentHash — use cached values
126
- routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config, adapter }, ctx.workdir, ctx.plugins);
127
- // Override with cached values only when they are actually set
128
- if (ctx.story.routing?.complexity) routing.complexity = ctx.story.routing.complexity;
129
- // BUG-062: Only honor stored testStrategy for legacy/manual routing (no contentHash).
130
- // When contentHash exists, the LLM strategy layer already recomputes testStrategy
131
- // fresh via determineTestStrategy() — don't clobber it with the stale PRD value.
132
- if (!hasContentHash && ctx.story.routing?.testStrategy) routing.testStrategy = ctx.story.routing.testStrategy;
133
- // BUG-032: Use escalated modelTier if explicitly set (by handleTierEscalation),
134
- // otherwise derive from complexity + current config
135
- if (ctx.story.routing?.modelTier) {
136
- routing.modelTier = ctx.story.routing.modelTier;
137
- } else {
138
- routing.modelTier = _routingDeps.complexityToModelTier(
139
- routing.complexity as import("../../config").Complexity,
140
- ctx.config,
141
- );
142
- }
143
- } else {
144
- // Cache miss: no routing, or contentHash present but mismatched — fresh classification
145
- routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config, adapter }, ctx.workdir, ctx.plugins);
146
- // currentHash already computed if a mismatch was detected; compute now if starting fresh
147
- currentHash = currentHash ?? _routingDeps.computeStoryContentHash(ctx.story);
148
- ctx.story.routing = {
149
- ...(ctx.story.routing ?? {}),
150
- complexity: routing.complexity as import("../../config").Complexity,
151
- initialComplexity:
152
- ctx.story.routing?.initialComplexity ?? (routing.complexity as import("../../config").Complexity),
153
- testStrategy: routing.testStrategy as import("../../config").TestStrategy,
154
- reasoning: routing.reasoning ?? "",
155
- contentHash: currentHash,
156
- };
157
- if (ctx.prdPath) {
158
- await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
159
- }
160
- }
161
-
162
- // BUG-010: Greenfield detection — force test-after if no test files exist
163
- // MW-011: For monorepo stories, scan the story's package workdir (story.workdir), not the
164
- // repo root. Scanning the repo root would find tests in OTHER packages and incorrectly
165
- // classify the story as non-greenfield even when the target package has zero tests.
166
- const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
167
- if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
168
- const greenfieldScanDir = ctx.story.workdir ? join(ctx.workdir, ctx.story.workdir) : ctx.workdir;
169
- const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
170
- if (isGreenfield) {
171
- logger.info("routing", "Greenfield detected — forcing test-after strategy", {
172
- storyId: ctx.story.id,
173
- originalStrategy: routing.testStrategy,
174
- scanDir: greenfieldScanDir,
175
- });
176
- routing.testStrategy = "test-after";
177
- routing.reasoning = `${routing.reasoning} [GREENFIELD OVERRIDE: No test files exist, using test-after instead of TDD]`;
178
- }
179
- }
180
-
181
- // Set ctx.routing after all overrides are applied
182
- ctx.routing = routing as RoutingResult;
183
-
184
- const isBatch = ctx.stories.length > 1;
185
-
186
- logger.debug("routing", "Task classified", {
187
- complexity: ctx.routing.complexity,
188
- modelTier: ctx.routing.modelTier,
189
- testStrategy: ctx.routing.testStrategy,
190
- storyId: ctx.story.id,
191
- });
192
-
193
- if (!isBatch) {
194
- logger.debug("routing", ctx.routing.reasoning);
195
- }
196
-
197
- // SD-004: Oversized story detection and decomposition
198
- const decomposeConfig = ctx.config.decompose;
199
- if (decomposeConfig && ctx.story.status !== "decomposed") {
200
- const acCount = ctx.story.acceptanceCriteria.length;
201
- const complexity = ctx.routing.complexity;
202
- const isOversized =
203
- acCount > decomposeConfig.maxAcceptanceCriteria && (complexity === "complex" || complexity === "expert");
204
-
205
- if (isOversized) {
206
- if (decomposeConfig.trigger === "disabled") {
207
- logger.warn(
208
- "routing",
209
- `Story ${ctx.story.id} is oversized (${acCount} ACs) but decompose is disabled — continuing with original`,
210
- );
211
- } else if (decomposeConfig.trigger === "auto") {
212
- const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
213
- if (result.validation.valid) {
214
- _routingDeps.applyDecomposition(ctx.prd, result);
215
- if (ctx.prdPath) {
216
- await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
217
- }
218
- logger.info("routing", `Story ${ctx.story.id} decomposed into ${result.subStories.length} substories`);
219
- return {
220
- action: "decomposed",
221
- reason: `Decomposed into ${result.subStories.length} substories`,
222
- subStoryCount: result.subStories.length,
223
- };
224
- }
225
- logger.warn("routing", `Story ${ctx.story.id} decompose failed after retries — continuing with original`, {
226
- errors: result.validation.errors,
227
- });
228
- } else if (decomposeConfig.trigger === "confirm") {
229
- const action = await _routingDeps.checkStoryOversized(
230
- { featureName: ctx.prd.feature, storyId: ctx.story.id, criteriaCount: acCount },
231
- ctx.config,
232
- // biome-ignore lint/style/noNonNullAssertion: confirm mode is only reached when interaction chain is present in production; tests mock checkStoryOversized directly
233
- ctx.interaction!,
234
- );
235
- if (action === "decompose") {
236
- const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
237
- if (result.validation.valid) {
238
- _routingDeps.applyDecomposition(ctx.prd, result);
239
- if (ctx.prdPath) {
240
- await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
241
- }
242
- logger.info("routing", `Story ${ctx.story.id} decomposed into ${result.subStories.length} substories`);
243
- return {
244
- action: "decomposed",
245
- reason: `Decomposed into ${result.subStories.length} substories`,
246
- subStoryCount: result.subStories.length,
247
- };
248
- }
249
- logger.warn("routing", `Story ${ctx.story.id} decompose failed after retries — continuing with original`, {
250
- errors: result.validation.errors,
251
- });
252
- }
253
- }
254
- }
255
- }
256
-
257
- return { action: "continue" };
258
- },
259
- };
260
-
261
- /**
262
- * Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
263
- * Tests can override individual functions without poisoning the module registry.
264
- */
265
- export const _routingDeps = {
266
- routeStory,
267
- complexityToModelTier,
268
- isGreenfieldStory,
269
- clearCache,
270
- savePRD,
271
- computeStoryContentHash,
272
- applyDecomposition,
273
- runDecompose,
274
- checkStoryOversized,
275
- getAgent,
276
- };
@@ -1,286 +0,0 @@
1
- /**
2
- * Verify Stage
3
- *
4
- * Verifies the agent's work meets basic requirements by running tests.
5
- * This is a lightweight verification before the full review stage.
6
- *
7
- * @returns
8
- * - `continue`: Tests passed, OR TEST_FAILURE (ctx.verifyResult.success===false → rectifyStage handles it)
9
- * - `escalate`: TIMEOUT or RUNTIME_CRASH (structural — rectify can't fix these)
10
- */
11
-
12
- import { basename, join } from "node:path";
13
- import type { SmartTestRunnerConfig } from "../../config/types";
14
- import { getLogger } from "../../logger";
15
- import { logTestOutput } from "../../utils/log-test-output";
16
- import { detectRuntimeCrash } from "../../verification/crash-detector";
17
- import type { VerifyStatus } from "../../verification/orchestrator-types";
18
- import { regression } from "../../verification/runners";
19
- import { _smartRunnerDeps } from "../../verification/smart-runner";
20
- import { isMonorepoOrchestratorCommand } from "../../verification/strategies/scoped";
21
- import type { PipelineContext, PipelineStage, StageResult } from "../types";
22
-
23
- const DEFAULT_SMART_RUNNER_CONFIG: SmartTestRunnerConfig = {
24
- enabled: true,
25
- testFilePatterns: ["test/**/*.test.ts"],
26
- fallback: "import-grep",
27
- };
28
-
29
- /**
30
- * Coerces boolean or partial config into a full SmartTestRunnerConfig
31
- */
32
- function coerceSmartTestRunner(val: boolean | SmartTestRunnerConfig | undefined): SmartTestRunnerConfig {
33
- if (val === undefined || val === true) return DEFAULT_SMART_RUNNER_CONFIG;
34
- if (val === false) return { ...DEFAULT_SMART_RUNNER_CONFIG, enabled: false };
35
- return val;
36
- }
37
-
38
- /**
39
- * Build the scoped test command from discovered test files.
40
- * Uses the testScoped template (with {{files}} placeholder) if configured,
41
- * otherwise falls back to buildSmartTestCommand heuristic.
42
- */
43
- function buildScopedCommand(testFiles: string[], baseCommand: string, testScopedTemplate?: string): string {
44
- if (testScopedTemplate) {
45
- return testScopedTemplate.replace("{{files}}", testFiles.join(" "));
46
- }
47
- return _smartRunnerDeps.buildSmartTestCommand(testFiles, baseCommand);
48
- }
49
-
50
- /**
51
- * Read the npm package name from <dir>/package.json.
52
- * Returns null if not found or file has no name field.
53
- */
54
- async function readPackageName(dir: string): Promise<string | null> {
55
- try {
56
- const content = await Bun.file(join(dir, "package.json")).json();
57
- return typeof content.name === "string" ? content.name : null;
58
- } catch {
59
- return null;
60
- }
61
- }
62
-
63
- /**
64
- * Substitute {{package}} placeholder in a testScoped template.
65
- *
66
- * Reads the npm package name from <packageDir>/package.json.
67
- * Returns null when package.json is absent or has no name field — callers
68
- * should skip the template entirely in that case (non-JS/non-Node projects
69
- * have no package identity to inject, so don't fall back to a dir name guess).
70
- *
71
- * @param template - Template string (e.g. "bunx turbo test --filter={{package}}")
72
- * @param packageDir - Absolute path to the package directory
73
- * @returns Resolved template, or null if {{package}} cannot be resolved
74
- */
75
- async function resolvePackageTemplate(template: string, packageDir: string): Promise<string | null> {
76
- if (!template.includes("{{package}}")) return template;
77
- const name = await _verifyDeps.readPackageName(packageDir);
78
- if (name === null) {
79
- // No package.json or no name field — skip template, can't resolve {{package}}
80
- return null;
81
- }
82
- return template.replaceAll("{{package}}", name);
83
- }
84
-
85
- export const verifyStage: PipelineStage = {
86
- name: "verify",
87
- enabled: (ctx: PipelineContext) => !ctx.fullSuiteGatePassed,
88
- skipReason: () => "not needed (full-suite gate already passed)",
89
-
90
- async execute(ctx: PipelineContext): Promise<StageResult> {
91
- const logger = getLogger();
92
-
93
- // PKG-003: use centrally resolved effective config (set once per story in iteration-runner)
94
- // Falls back to ctx.config for contexts that predate PKG-003 (e.g., acceptance-loop)
95
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
96
-
97
- // Skip verification if tests are not required
98
- if (!effectiveConfig.quality.requireTests) {
99
- logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
100
- return { action: "continue" };
101
- }
102
-
103
- // Skip verification if no test command is configured
104
- const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test;
105
- const testScopedTemplate = effectiveConfig.quality.commands.testScoped;
106
- if (!testCommand) {
107
- logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
108
- return { action: "continue" };
109
- }
110
-
111
- logger.info("verify", "Running verification", { storyId: ctx.story.id });
112
-
113
- // MW-006: resolve effective workdir for test execution
114
- const effectiveWorkdir = ctx.story.workdir ? join(ctx.workdir, ctx.story.workdir) : ctx.workdir;
115
-
116
- // Determine effective test command (smart runner or full suite)
117
- let effectiveCommand = testCommand;
118
- let isFullSuite = true;
119
- const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
120
- const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
121
-
122
- // Resolve {{package}} in testScoped template for monorepo stories.
123
- // Returns null if package.json is absent (non-JS project) — falls through to smart-runner.
124
- let resolvedTestScopedTemplate: string | undefined = testScopedTemplate;
125
- if (testScopedTemplate && ctx.story.workdir) {
126
- const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
127
- resolvedTestScopedTemplate = resolved ?? undefined; // null → skip template
128
- }
129
-
130
- // Monorepo orchestrators (turbo, nx) handle change-aware scoping natively via their own
131
- // filter syntax. Skip nax's smart runner — appending file paths would produce invalid syntax.
132
- // Instead, use the testScoped template (with {{package}} resolved) to scope per-story.
133
- const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
134
-
135
- if (isMonorepoOrchestrator) {
136
- if (resolvedTestScopedTemplate && ctx.story.workdir) {
137
- // Use the resolved scoped template (e.g. "bunx turbo test --filter=@koda/cli")
138
- effectiveCommand = resolvedTestScopedTemplate;
139
- isFullSuite = false;
140
- logger.info("verify", "Monorepo orchestrator — using testScoped template", {
141
- storyId: ctx.story.id,
142
- command: effectiveCommand,
143
- });
144
- } else {
145
- logger.info("verify", "Monorepo orchestrator — running full suite (no package context)", {
146
- storyId: ctx.story.id,
147
- command: effectiveCommand,
148
- });
149
- }
150
- } else if (smartRunnerConfig.enabled) {
151
- // MW-006: pass packagePrefix so git diff is scoped to the package in monorepos
152
- const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(
153
- effectiveWorkdir,
154
- ctx.storyGitRef,
155
- ctx.story.workdir,
156
- );
157
-
158
- // Pass 1: path convention mapping
159
- const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, effectiveWorkdir);
160
- if (pass1Files.length > 0) {
161
- logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
162
- storyId: ctx.story.id,
163
- });
164
- effectiveCommand = buildScopedCommand(pass1Files, testCommand, resolvedTestScopedTemplate);
165
- isFullSuite = false;
166
- } else if (smartRunnerConfig.fallback === "import-grep") {
167
- // Pass 2: import-grep fallback
168
- const pass2Files = await _smartRunnerDeps.importGrepFallback(
169
- sourceFiles,
170
- effectiveWorkdir,
171
- smartRunnerConfig.testFilePatterns,
172
- );
173
- if (pass2Files.length > 0) {
174
- logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
175
- storyId: ctx.story.id,
176
- });
177
- effectiveCommand = buildScopedCommand(pass2Files, testCommand, resolvedTestScopedTemplate);
178
- isFullSuite = false;
179
- }
180
- }
181
- }
182
-
183
- // US-003: If we are falling back to the full suite AND mode is deferred, skip this stage
184
- // because the deferred regression gate will handle the full suite at run-end.
185
- if (isFullSuite && regressionMode === "deferred") {
186
- logger.info("verify", "[smart-runner] No mapped tests — deferring full suite to run-end (mode: deferred)", {
187
- storyId: ctx.story.id,
188
- });
189
- return { action: "continue" };
190
- }
191
-
192
- if (isFullSuite) {
193
- logger.info("verify", "[smart-runner] No mapped tests — falling back to full suite", {
194
- storyId: ctx.story.id,
195
- });
196
- }
197
-
198
- // BUG-044: Log the effective command for observability
199
- logger.info("verify", isFullSuite ? "Running full suite" : "Running scoped tests", {
200
- storyId: ctx.story.id,
201
- command: effectiveCommand,
202
- });
203
-
204
- // Use unified regression gate (includes 2s wait for agent process cleanup)
205
- const result = await _verifyDeps.regression({
206
- workdir: effectiveWorkdir,
207
- command: effectiveCommand,
208
- timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
209
- acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
210
- });
211
-
212
- // Store result on context for rectify stage
213
- ctx.verifyResult = {
214
- success: result.success,
215
- status: (result.status === "TIMEOUT"
216
- ? "TIMEOUT"
217
- : result.success
218
- ? "PASS"
219
- : detectRuntimeCrash(result.output)
220
- ? "RUNTIME_CRASH"
221
- : "TEST_FAILURE") as VerifyStatus,
222
- storyId: ctx.story.id,
223
- strategy: "scoped",
224
- passCount: result.passCount ?? 0,
225
- failCount: result.failCount ?? 0,
226
- totalCount: (result.passCount ?? 0) + (result.failCount ?? 0),
227
- failures: [],
228
- rawOutput: result.output,
229
- durationMs: 0,
230
- countsTowardEscalation: result.countsTowardEscalation,
231
- };
232
-
233
- // HARD FAILURE: Tests must pass for story to be marked complete
234
- if (!result.success) {
235
- // BUG-019: Distinguish timeout from actual test failures
236
- if (result.status === "TIMEOUT") {
237
- const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
238
- logger.error(
239
- "verify",
240
- `Test suite exceeded timeout (${timeout}s). This is NOT a test failure — consider increasing execution.verificationTimeoutSeconds or scoping tests.`,
241
- {
242
- exitCode: result.status,
243
- storyId: ctx.story.id,
244
- timeoutSeconds: timeout,
245
- },
246
- );
247
- } else {
248
- logger.error("verify", "Tests failed", {
249
- exitCode: result.status,
250
- storyId: ctx.story.id,
251
- });
252
- }
253
-
254
- // Log tail of output at debug level for context (ENH-001)
255
- // BUG-037: Use .slice(-20) to show failures, not prechecks
256
- if (result.status !== "TIMEOUT") {
257
- logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
258
- }
259
-
260
- // RUNTIME_CRASH and TIMEOUT are structural — escalate immediately (rectify can't fix them)
261
- if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
262
- return {
263
- action: "escalate",
264
- reason:
265
- result.status === "TIMEOUT"
266
- ? `Test suite TIMEOUT after ${effectiveConfig.execution.verificationTimeoutSeconds}s (not a code failure)`
267
- : `Tests failed with runtime crash (exit code ${result.status ?? "non-zero"})`,
268
- };
269
- }
270
-
271
- // TEST_FAILURE: ctx.verifyResult is set with success:false — rectifyStage handles it next
272
- return { action: "continue" };
273
- }
274
-
275
- logger.info("verify", "Tests passed", { storyId: ctx.story.id });
276
- return { action: "continue" };
277
- },
278
- };
279
-
280
- /**
281
- * Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
282
- */
283
- export const _verifyDeps = {
284
- regression,
285
- readPackageName,
286
- };