@nathapp/nax 0.50.3 → 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.
- package/CHANGELOG.md +30 -0
- package/dist/nax.js +393 -197
- package/package.json +1 -3
- package/bin/nax.ts +0 -1195
- package/src/acceptance/fix-generator.ts +0 -322
- package/src/acceptance/generator.ts +0 -415
- package/src/acceptance/index.ts +0 -42
- package/src/acceptance/refinement.ts +0 -224
- package/src/acceptance/templates/cli.ts +0 -47
- package/src/acceptance/templates/component.ts +0 -78
- package/src/acceptance/templates/e2e.ts +0 -43
- package/src/acceptance/templates/index.ts +0 -21
- package/src/acceptance/templates/snapshot.ts +0 -50
- package/src/acceptance/templates/unit.ts +0 -48
- package/src/acceptance/types.ts +0 -138
- package/src/agents/acp/adapter.ts +0 -888
- package/src/agents/acp/cost.ts +0 -9
- package/src/agents/acp/index.ts +0 -7
- package/src/agents/acp/interaction-bridge.ts +0 -126
- package/src/agents/acp/parser.ts +0 -119
- package/src/agents/acp/spawn-client.ts +0 -373
- package/src/agents/acp/types.ts +0 -22
- package/src/agents/aider/adapter.ts +0 -135
- package/src/agents/claude/adapter.ts +0 -258
- package/src/agents/claude/complete.ts +0 -80
- package/src/agents/claude/cost.ts +0 -16
- package/src/agents/claude/execution.ts +0 -215
- package/src/agents/claude/index.ts +0 -3
- package/src/agents/claude/interactive.ts +0 -77
- package/src/agents/claude/plan.ts +0 -179
- package/src/agents/codex/adapter.ts +0 -153
- package/src/agents/cost/calculate.ts +0 -154
- package/src/agents/cost/index.ts +0 -10
- package/src/agents/cost/parse.ts +0 -97
- package/src/agents/cost/pricing.ts +0 -59
- package/src/agents/cost/types.ts +0 -45
- package/src/agents/gemini/adapter.ts +0 -177
- package/src/agents/index.ts +0 -18
- package/src/agents/opencode/adapter.ts +0 -106
- package/src/agents/registry.ts +0 -136
- package/src/agents/shared/decompose.ts +0 -154
- package/src/agents/shared/model-resolution.ts +0 -43
- package/src/agents/shared/types-extended.ts +0 -164
- package/src/agents/shared/validation.ts +0 -69
- package/src/agents/shared/version-detection.ts +0 -109
- package/src/agents/types.ts +0 -205
- package/src/analyze/classifier.ts +0 -282
- package/src/analyze/index.ts +0 -16
- package/src/analyze/scanner.ts +0 -171
- package/src/analyze/types.ts +0 -51
- package/src/cli/accept.ts +0 -108
- package/src/cli/agents.ts +0 -87
- package/src/cli/analyze-parser.ts +0 -291
- package/src/cli/analyze.ts +0 -352
- package/src/cli/config-descriptions.ts +0 -219
- package/src/cli/config-diff.ts +0 -103
- package/src/cli/config-display.ts +0 -285
- package/src/cli/config-get.ts +0 -55
- package/src/cli/config.ts +0 -14
- package/src/cli/constitution.ts +0 -17
- package/src/cli/diagnose-analysis.ts +0 -159
- package/src/cli/diagnose-formatter.ts +0 -87
- package/src/cli/diagnose.ts +0 -203
- package/src/cli/generate.ts +0 -250
- package/src/cli/index.ts +0 -42
- package/src/cli/init-context.ts +0 -405
- package/src/cli/init-detect.ts +0 -303
- package/src/cli/init.ts +0 -296
- package/src/cli/interact.ts +0 -295
- package/src/cli/plan.ts +0 -509
- package/src/cli/plugins.ts +0 -122
- package/src/cli/prompts-export.ts +0 -58
- package/src/cli/prompts-init.ts +0 -200
- package/src/cli/prompts-main.ts +0 -183
- package/src/cli/prompts-shared.ts +0 -70
- package/src/cli/prompts-tdd.ts +0 -88
- package/src/cli/prompts.ts +0 -17
- package/src/cli/runs.ts +0 -174
- package/src/cli/status-cost.ts +0 -151
- package/src/cli/status-features.ts +0 -405
- package/src/cli/status.ts +0 -13
- package/src/commands/common.ts +0 -171
- package/src/commands/diagnose.ts +0 -17
- package/src/commands/index.ts +0 -9
- package/src/commands/logs-formatter.ts +0 -201
- package/src/commands/logs-reader.ts +0 -171
- package/src/commands/logs.ts +0 -103
- package/src/commands/precheck.ts +0 -86
- package/src/commands/runs.ts +0 -220
- package/src/commands/unlock.ts +0 -96
- package/src/config/defaults.ts +0 -218
- package/src/config/index.ts +0 -22
- package/src/config/loader.ts +0 -143
- package/src/config/merge.ts +0 -106
- package/src/config/merger.ts +0 -147
- package/src/config/path-security.ts +0 -121
- package/src/config/paths.ts +0 -27
- package/src/config/permissions.ts +0 -63
- package/src/config/runtime-types.ts +0 -522
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -426
- package/src/config/test-strategy.ts +0 -71
- package/src/config/types.ts +0 -57
- package/src/config/validate.ts +0 -103
- package/src/constitution/generator.ts +0 -158
- package/src/constitution/generators/aider.ts +0 -41
- package/src/constitution/generators/claude.ts +0 -35
- package/src/constitution/generators/cursor.ts +0 -36
- package/src/constitution/generators/opencode.ts +0 -38
- package/src/constitution/generators/types.ts +0 -33
- package/src/constitution/generators/windsurf.ts +0 -36
- package/src/constitution/index.ts +0 -11
- package/src/constitution/loader.ts +0 -121
- package/src/constitution/types.ts +0 -31
- package/src/context/auto-detect.ts +0 -228
- package/src/context/builder.ts +0 -299
- package/src/context/elements.ts +0 -122
- package/src/context/formatter.ts +0 -107
- package/src/context/generator.ts +0 -343
- package/src/context/generators/aider.ts +0 -34
- package/src/context/generators/claude.ts +0 -28
- package/src/context/generators/codex.ts +0 -28
- package/src/context/generators/cursor.ts +0 -28
- package/src/context/generators/gemini.ts +0 -28
- package/src/context/generators/opencode.ts +0 -30
- package/src/context/generators/windsurf.ts +0 -28
- package/src/context/greenfield.ts +0 -114
- package/src/context/index.ts +0 -34
- package/src/context/injector.ts +0 -279
- package/src/context/parent-context.ts +0 -39
- package/src/context/test-scanner.ts +0 -370
- package/src/context/types.ts +0 -98
- package/src/decompose/apply.ts +0 -50
- package/src/decompose/builder.ts +0 -181
- package/src/decompose/index.ts +0 -8
- package/src/decompose/sections/codebase.ts +0 -26
- package/src/decompose/sections/constraints.ts +0 -32
- package/src/decompose/sections/index.ts +0 -4
- package/src/decompose/sections/sibling-stories.ts +0 -25
- package/src/decompose/sections/target-story.ts +0 -31
- package/src/decompose/types.ts +0 -55
- package/src/decompose/validators/complexity.ts +0 -45
- package/src/decompose/validators/coverage.ts +0 -134
- package/src/decompose/validators/dependency.ts +0 -91
- package/src/decompose/validators/index.ts +0 -35
- package/src/decompose/validators/overlap.ts +0 -128
- package/src/errors.ts +0 -67
- package/src/execution/batching.ts +0 -157
- package/src/execution/crash-heartbeat.ts +0 -77
- package/src/execution/crash-recovery.ts +0 -79
- package/src/execution/crash-signals.ts +0 -165
- package/src/execution/crash-writer.ts +0 -154
- package/src/execution/deferred-review.ts +0 -105
- package/src/execution/dry-run.ts +0 -81
- package/src/execution/escalation/escalation.ts +0 -46
- package/src/execution/escalation/index.ts +0 -13
- package/src/execution/escalation/tier-escalation.ts +0 -346
- package/src/execution/escalation/tier-outcome.ts +0 -143
- package/src/execution/executor-types.ts +0 -73
- package/src/execution/helpers.ts +0 -38
- package/src/execution/index.ts +0 -27
- package/src/execution/iteration-runner.ts +0 -160
- package/src/execution/lifecycle/acceptance-loop.ts +0 -309
- package/src/execution/lifecycle/headless-formatter.ts +0 -83
- package/src/execution/lifecycle/index.ts +0 -11
- package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
- package/src/execution/lifecycle/precheck-runner.ts +0 -140
- package/src/execution/lifecycle/run-cleanup.ts +0 -81
- package/src/execution/lifecycle/run-completion.ts +0 -247
- package/src/execution/lifecycle/run-initialization.ts +0 -187
- package/src/execution/lifecycle/run-regression.ts +0 -305
- package/src/execution/lifecycle/run-setup.ts +0 -240
- package/src/execution/lifecycle/story-size-prompts.ts +0 -123
- package/src/execution/lock.ts +0 -129
- package/src/execution/parallel-coordinator.ts +0 -281
- package/src/execution/parallel-executor-rectification-pass.ts +0 -117
- package/src/execution/parallel-executor-rectify.ts +0 -136
- package/src/execution/parallel-executor.ts +0 -330
- package/src/execution/parallel-worker.ts +0 -149
- package/src/execution/parallel.ts +0 -13
- package/src/execution/pid-registry.ts +0 -275
- package/src/execution/pipeline-result-handler.ts +0 -221
- package/src/execution/progress.ts +0 -27
- package/src/execution/queue-handler.ts +0 -109
- package/src/execution/runner-completion.ts +0 -171
- package/src/execution/runner-execution.ts +0 -243
- package/src/execution/runner-setup.ts +0 -86
- package/src/execution/runner.ts +0 -265
- package/src/execution/sequential-executor.ts +0 -219
- package/src/execution/status-file.ts +0 -264
- package/src/execution/status-writer.ts +0 -181
- package/src/execution/story-context.ts +0 -266
- package/src/execution/story-selector.ts +0 -76
- package/src/execution/test-output-parser.ts +0 -14
- package/src/execution/timeout-handler.ts +0 -100
- package/src/hooks/index.ts +0 -2
- package/src/hooks/runner.ts +0 -280
- package/src/hooks/types.ts +0 -79
- package/src/interaction/chain.ts +0 -170
- package/src/interaction/index.ts +0 -61
- package/src/interaction/init.ts +0 -84
- package/src/interaction/plugins/auto.ts +0 -243
- package/src/interaction/plugins/cli.ts +0 -300
- package/src/interaction/plugins/telegram.ts +0 -384
- package/src/interaction/plugins/webhook.ts +0 -286
- package/src/interaction/state.ts +0 -171
- package/src/interaction/triggers.ts +0 -250
- package/src/interaction/types.ts +0 -170
- package/src/logger/formatters.ts +0 -84
- package/src/logger/index.ts +0 -16
- package/src/logger/logger.ts +0 -296
- package/src/logger/types.ts +0 -48
- package/src/logging/formatter.ts +0 -355
- package/src/logging/index.ts +0 -22
- package/src/logging/types.ts +0 -93
- package/src/metrics/aggregator.ts +0 -191
- package/src/metrics/index.ts +0 -14
- package/src/metrics/tracker.ts +0 -200
- package/src/metrics/types.ts +0 -115
- package/src/optimizer/index.ts +0 -63
- package/src/optimizer/noop.optimizer.ts +0 -24
- package/src/optimizer/rule-based.optimizer.ts +0 -248
- package/src/optimizer/types.ts +0 -53
- package/src/pipeline/event-bus.ts +0 -297
- package/src/pipeline/events.ts +0 -130
- package/src/pipeline/index.ts +0 -19
- package/src/pipeline/runner.ts +0 -149
- package/src/pipeline/stages/acceptance-setup.ts +0 -144
- package/src/pipeline/stages/acceptance.ts +0 -215
- package/src/pipeline/stages/autofix.ts +0 -262
- package/src/pipeline/stages/completion.ts +0 -110
- package/src/pipeline/stages/constitution.ts +0 -63
- package/src/pipeline/stages/context.ts +0 -122
- package/src/pipeline/stages/execution.ts +0 -359
- package/src/pipeline/stages/index.ts +0 -86
- package/src/pipeline/stages/optimizer.ts +0 -74
- package/src/pipeline/stages/prompt.ts +0 -79
- package/src/pipeline/stages/queue-check.ts +0 -103
- package/src/pipeline/stages/rectify.ts +0 -101
- package/src/pipeline/stages/regression.ts +0 -99
- package/src/pipeline/stages/review.ts +0 -94
- package/src/pipeline/stages/routing.ts +0 -276
- package/src/pipeline/stages/verify.ts +0 -286
- package/src/pipeline/subscribers/events-writer.ts +0 -135
- package/src/pipeline/subscribers/hooks.ts +0 -179
- package/src/pipeline/subscribers/interaction.ts +0 -103
- package/src/pipeline/subscribers/registry.ts +0 -73
- package/src/pipeline/subscribers/reporters.ts +0 -174
- package/src/pipeline/types.ts +0 -220
- package/src/plugins/extensions.ts +0 -225
- package/src/plugins/index.ts +0 -33
- package/src/plugins/loader.ts +0 -352
- package/src/plugins/plugin-logger.ts +0 -41
- package/src/plugins/registry.ts +0 -168
- package/src/plugins/types.ts +0 -206
- package/src/plugins/validator.ts +0 -352
- package/src/prd/index.ts +0 -220
- package/src/prd/schema.ts +0 -268
- package/src/prd/types.ts +0 -273
- package/src/prd/validate.ts +0 -41
- package/src/precheck/checks-agents.ts +0 -63
- package/src/precheck/checks-blockers.ts +0 -23
- package/src/precheck/checks-cli.ts +0 -68
- package/src/precheck/checks-config.ts +0 -102
- package/src/precheck/checks-git.ts +0 -117
- package/src/precheck/checks-system.ts +0 -101
- package/src/precheck/checks-warnings.ts +0 -221
- package/src/precheck/checks.ts +0 -36
- package/src/precheck/index.ts +0 -374
- package/src/precheck/story-size-gate.ts +0 -144
- package/src/precheck/types.ts +0 -31
- package/src/prompts/builder.ts +0 -166
- package/src/prompts/index.ts +0 -2
- package/src/prompts/loader.ts +0 -43
- package/src/prompts/sections/conventions.ts +0 -19
- package/src/prompts/sections/hermetic.ts +0 -41
- package/src/prompts/sections/index.ts +0 -12
- package/src/prompts/sections/isolation.ts +0 -70
- package/src/prompts/sections/role-task.ts +0 -182
- package/src/prompts/sections/story.ts +0 -55
- package/src/prompts/sections/verdict.ts +0 -70
- package/src/prompts/types.ts +0 -21
- package/src/queue/index.ts +0 -2
- package/src/queue/manager.ts +0 -254
- package/src/queue/types.ts +0 -54
- package/src/review/index.ts +0 -8
- package/src/review/orchestrator.ts +0 -154
- package/src/review/runner.ts +0 -303
- package/src/review/types.ts +0 -70
- package/src/routing/batch-route.ts +0 -35
- package/src/routing/builder.ts +0 -81
- package/src/routing/chain.ts +0 -75
- package/src/routing/content-hash.ts +0 -25
- package/src/routing/index.ts +0 -20
- package/src/routing/loader.ts +0 -62
- package/src/routing/router.ts +0 -305
- package/src/routing/strategies/adaptive.ts +0 -215
- package/src/routing/strategies/index.ts +0 -8
- package/src/routing/strategies/keyword.ts +0 -180
- package/src/routing/strategies/llm-prompts.ts +0 -224
- package/src/routing/strategies/llm.ts +0 -320
- package/src/routing/strategies/manual.ts +0 -50
- package/src/routing/strategy.ts +0 -102
- package/src/tdd/cleanup.ts +0 -120
- package/src/tdd/index.ts +0 -22
- package/src/tdd/isolation.ts +0 -117
- package/src/tdd/orchestrator.ts +0 -406
- package/src/tdd/prompts.ts +0 -40
- package/src/tdd/rectification-gate.ts +0 -274
- package/src/tdd/session-runner.ts +0 -263
- package/src/tdd/types.ts +0 -84
- package/src/tdd/verdict-reader.ts +0 -266
- package/src/tdd/verdict.ts +0 -152
- package/src/tui/App.tsx +0 -265
- package/src/tui/components/AgentPanel.tsx +0 -75
- package/src/tui/components/CostOverlay.tsx +0 -118
- package/src/tui/components/HelpOverlay.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -63
- package/src/tui/components/StoriesPanel.tsx +0 -177
- package/src/tui/hooks/useKeyboard.ts +0 -142
- package/src/tui/hooks/useLayout.ts +0 -137
- package/src/tui/hooks/usePipelineEvents.ts +0 -183
- package/src/tui/hooks/usePty.ts +0 -189
- package/src/tui/index.tsx +0 -38
- package/src/tui/types.ts +0 -76
- package/src/utils/errors.ts +0 -12
- package/src/utils/git.ts +0 -245
- package/src/utils/json-file.ts +0 -72
- package/src/utils/log-test-output.ts +0 -25
- package/src/utils/path-security.ts +0 -73
- package/src/utils/queue-writer.ts +0 -54
- package/src/verification/crash-detector.ts +0 -34
- package/src/verification/executor.ts +0 -250
- package/src/verification/index.ts +0 -12
- package/src/verification/orchestrator-types.ts +0 -154
- package/src/verification/orchestrator.ts +0 -76
- package/src/verification/parser.ts +0 -220
- package/src/verification/rectification-loop.ts +0 -172
- package/src/verification/rectification.ts +0 -108
- package/src/verification/runners.ts +0 -129
- package/src/verification/smart-runner.ts +0 -307
- package/src/verification/strategies/acceptance.ts +0 -136
- package/src/verification/strategies/regression.ts +0 -90
- package/src/verification/strategies/scoped.ts +0 -154
- package/src/verification/types.ts +0 -117
- package/src/version.ts +0 -40
- package/src/worktree/dispatcher.ts +0 -6
- package/src/worktree/index.ts +0 -2
- package/src/worktree/manager.ts +0 -193
- package/src/worktree/merge.ts +0 -302
- package/src/worktree/types.ts +0 -4
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deferred Plugin Review (DR-003)
|
|
3
|
-
*
|
|
4
|
-
* Captures the run-start git ref and runs all plugin reviewers once after
|
|
5
|
-
* all stories complete, using the full diff from run-start to HEAD.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { spawn } from "bun";
|
|
9
|
-
import type { PluginRegistry } from "../plugins";
|
|
10
|
-
import type { ReviewConfig } from "../review/types";
|
|
11
|
-
|
|
12
|
-
/** Injectable deps for testing */
|
|
13
|
-
export const _deferredReviewDeps = { spawn };
|
|
14
|
-
|
|
15
|
-
export interface DeferredReviewResult {
|
|
16
|
-
runStartRef: string;
|
|
17
|
-
changedFiles: string[];
|
|
18
|
-
reviewerResults: Array<{
|
|
19
|
-
name: string;
|
|
20
|
-
passed: boolean;
|
|
21
|
-
output: string;
|
|
22
|
-
exitCode?: number;
|
|
23
|
-
error?: string;
|
|
24
|
-
}>;
|
|
25
|
-
anyFailed: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Capture the current HEAD git ref. Returns "" on failure. */
|
|
29
|
-
export async function captureRunStartRef(workdir: string): Promise<string> {
|
|
30
|
-
try {
|
|
31
|
-
const proc = _deferredReviewDeps.spawn({
|
|
32
|
-
cmd: ["git", "rev-parse", "HEAD"],
|
|
33
|
-
cwd: workdir,
|
|
34
|
-
stdout: "pipe",
|
|
35
|
-
stderr: "pipe",
|
|
36
|
-
});
|
|
37
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
38
|
-
return stdout.trim();
|
|
39
|
-
} catch {
|
|
40
|
-
return "";
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function getChangedFilesForDeferred(workdir: string, baseRef: string): Promise<string[]> {
|
|
45
|
-
try {
|
|
46
|
-
const proc = _deferredReviewDeps.spawn({
|
|
47
|
-
cmd: ["git", "diff", "--name-only", `${baseRef}...HEAD`],
|
|
48
|
-
cwd: workdir,
|
|
49
|
-
stdout: "pipe",
|
|
50
|
-
stderr: "pipe",
|
|
51
|
-
});
|
|
52
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
53
|
-
return stdout.trim().split("\n").filter(Boolean);
|
|
54
|
-
} catch {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Run all plugin reviewers once with the full diff since runStartRef. */
|
|
60
|
-
export async function runDeferredReview(
|
|
61
|
-
workdir: string,
|
|
62
|
-
reviewConfig: ReviewConfig,
|
|
63
|
-
plugins: PluginRegistry,
|
|
64
|
-
runStartRef: string,
|
|
65
|
-
): Promise<DeferredReviewResult | undefined> {
|
|
66
|
-
if (!reviewConfig || reviewConfig.pluginMode !== "deferred") {
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const reviewers = plugins.getReviewers();
|
|
71
|
-
if (reviewers.length === 0) {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const changedFiles = await getChangedFilesForDeferred(workdir, runStartRef);
|
|
76
|
-
|
|
77
|
-
const reviewerResults: DeferredReviewResult["reviewerResults"] = [];
|
|
78
|
-
let anyFailed = false;
|
|
79
|
-
|
|
80
|
-
for (const reviewer of reviewers) {
|
|
81
|
-
try {
|
|
82
|
-
const result = await reviewer.check(workdir, changedFiles);
|
|
83
|
-
reviewerResults.push({
|
|
84
|
-
name: reviewer.name,
|
|
85
|
-
passed: result.passed,
|
|
86
|
-
output: result.output,
|
|
87
|
-
exitCode: result.exitCode,
|
|
88
|
-
});
|
|
89
|
-
if (!result.passed) {
|
|
90
|
-
anyFailed = true;
|
|
91
|
-
}
|
|
92
|
-
} catch (error) {
|
|
93
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
94
|
-
reviewerResults.push({
|
|
95
|
-
name: reviewer.name,
|
|
96
|
-
passed: false,
|
|
97
|
-
output: "",
|
|
98
|
-
error: errorMsg,
|
|
99
|
-
});
|
|
100
|
-
anyFailed = true;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { runStartRef, changedFiles, reviewerResults, anyFailed };
|
|
105
|
-
}
|
package/src/execution/dry-run.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dry Run Handler (ADR-005, Phase 4)
|
|
3
|
-
*
|
|
4
|
-
* Extracted from pipeline-result-handler.ts to slim that file below 200 lines.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { getSafeLogger } from "../logger";
|
|
8
|
-
import { pipelineEventBus } from "../pipeline/event-bus";
|
|
9
|
-
import type { PluginRegistry } from "../plugins";
|
|
10
|
-
import { markStoryPassed, savePRD } from "../prd";
|
|
11
|
-
import type { PRD, UserStory } from "../prd/types";
|
|
12
|
-
import type { routeTask } from "../routing";
|
|
13
|
-
import type { StatusWriter } from "./status-writer";
|
|
14
|
-
|
|
15
|
-
export interface DryRunContext {
|
|
16
|
-
prd: PRD;
|
|
17
|
-
prdPath: string;
|
|
18
|
-
storiesToExecute: UserStory[];
|
|
19
|
-
routing: ReturnType<typeof routeTask>;
|
|
20
|
-
statusWriter: StatusWriter;
|
|
21
|
-
pluginRegistry: PluginRegistry;
|
|
22
|
-
runId: string;
|
|
23
|
-
totalCost: number;
|
|
24
|
-
iterations: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface DryRunResult {
|
|
28
|
-
storiesCompletedDelta: number;
|
|
29
|
-
prdDirty: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Handle dry-run iteration: log what would happen, mark stories passed. */
|
|
33
|
-
export async function handleDryRun(ctx: DryRunContext): Promise<DryRunResult> {
|
|
34
|
-
const logger = getSafeLogger();
|
|
35
|
-
|
|
36
|
-
ctx.statusWriter.setPrd(ctx.prd);
|
|
37
|
-
ctx.statusWriter.setCurrentStory({
|
|
38
|
-
storyId: ctx.storiesToExecute[0].id,
|
|
39
|
-
title: ctx.storiesToExecute[0].title,
|
|
40
|
-
complexity: ctx.routing.complexity,
|
|
41
|
-
tddStrategy: ctx.routing.testStrategy,
|
|
42
|
-
model: ctx.routing.modelTier,
|
|
43
|
-
attempt: (ctx.storiesToExecute[0].attempts ?? 0) + 1,
|
|
44
|
-
phase: "routing",
|
|
45
|
-
});
|
|
46
|
-
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
47
|
-
|
|
48
|
-
for (const s of ctx.storiesToExecute) {
|
|
49
|
-
logger?.info("execution", "[DRY RUN] Would execute agent here", {
|
|
50
|
-
storyId: s.id,
|
|
51
|
-
storyTitle: s.title,
|
|
52
|
-
modelTier: ctx.routing.modelTier,
|
|
53
|
-
complexity: ctx.routing.complexity,
|
|
54
|
-
testStrategy: ctx.routing.testStrategy,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (const s of ctx.storiesToExecute) {
|
|
59
|
-
markStoryPassed(ctx.prd, s.id);
|
|
60
|
-
}
|
|
61
|
-
await savePRD(ctx.prd, ctx.prdPath);
|
|
62
|
-
|
|
63
|
-
for (const s of ctx.storiesToExecute) {
|
|
64
|
-
pipelineEventBus.emit({
|
|
65
|
-
type: "story:completed",
|
|
66
|
-
storyId: s.id,
|
|
67
|
-
story: s,
|
|
68
|
-
passed: true,
|
|
69
|
-
runElapsedMs: 0,
|
|
70
|
-
cost: 0,
|
|
71
|
-
modelTier: ctx.routing.modelTier,
|
|
72
|
-
testStrategy: ctx.routing.testStrategy,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
ctx.statusWriter.setPrd(ctx.prd);
|
|
77
|
-
ctx.statusWriter.setCurrentStory(null);
|
|
78
|
-
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
79
|
-
|
|
80
|
-
return { storiesCompletedDelta: ctx.storiesToExecute.length, prdDirty: true };
|
|
81
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model Tier Escalation (ADR-003)
|
|
3
|
-
*
|
|
4
|
-
* Handles escalating model tiers through configurable tier chain
|
|
5
|
-
* with per-tier attempt budgets.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { TierConfig } from "../../config";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Escalate to the next tier in the configured order.
|
|
12
|
-
*
|
|
13
|
-
* @param currentTier - Current tier name
|
|
14
|
-
* @param tierOrder - Ordered tier config array from config (e.g., [{tier:"fast",attempts:5}, ...])
|
|
15
|
-
* @returns Next tier name, or null if at max tier
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* const tiers = [{tier:"fast",attempts:5}, {tier:"balanced",attempts:3}, {tier:"powerful",attempts:2}];
|
|
20
|
-
* escalateTier("fast", tiers); // => "balanced"
|
|
21
|
-
* escalateTier("powerful", tiers); // => null
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export function escalateTier(currentTier: string, tierOrder: TierConfig[]): string | null {
|
|
25
|
-
const getName = (t: TierConfig) => t.tier ?? (t as unknown as { name?: string }).name ?? null;
|
|
26
|
-
const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
|
|
27
|
-
if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return getName(tierOrder[currentIndex + 1]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get the tier config for a given tier name.
|
|
35
|
-
*/
|
|
36
|
-
export function getTierConfig(tierName: string, tierOrder: TierConfig[]): TierConfig | undefined {
|
|
37
|
-
const getName = (t: TierConfig) => t.tier ?? (t as unknown as { name?: string }).name ?? null;
|
|
38
|
-
return tierOrder.find((t) => getName(t) === tierName);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Calculate total max iterations from tier order (sum of all attempts).
|
|
43
|
-
*/
|
|
44
|
-
export function calculateMaxIterations(tierOrder: TierConfig[]): number {
|
|
45
|
-
return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
|
|
46
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Escalation module exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { escalateTier, getTierConfig, calculateMaxIterations } from "./escalation";
|
|
6
|
-
export {
|
|
7
|
-
resolveMaxAttemptsOutcome,
|
|
8
|
-
preIterationTierCheck,
|
|
9
|
-
handleTierEscalation,
|
|
10
|
-
type PreIterationCheckResult,
|
|
11
|
-
type EscalationHandlerContext,
|
|
12
|
-
type EscalationHandlerResult,
|
|
13
|
-
} from "./tier-escalation";
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tier Escalation Logic
|
|
3
|
-
*
|
|
4
|
-
* Handles model tier escalation when stories fail:
|
|
5
|
-
* - Pre-iteration tier budget checks
|
|
6
|
-
* - Tier escalation with attempt counter reset
|
|
7
|
-
* - Max attempts outcome resolution (pause vs fail)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { NaxConfig } from "../../config";
|
|
11
|
-
import { type LoadedHooksConfig, fireHook } from "../../hooks";
|
|
12
|
-
import { getSafeLogger } from "../../logger";
|
|
13
|
-
import type { PRD, StructuredFailure, UserStory } from "../../prd";
|
|
14
|
-
import { markStoryFailed, savePRD } from "../../prd";
|
|
15
|
-
import { tryLlmBatchRoute } from "../../routing/batch-route";
|
|
16
|
-
import { clearCacheForStory } from "../../routing/strategies/llm";
|
|
17
|
-
import type { FailureCategory } from "../../tdd/types";
|
|
18
|
-
import { calculateMaxIterations, escalateTier, getTierConfig } from "../escalation";
|
|
19
|
-
import { hookCtx } from "../helpers";
|
|
20
|
-
import { appendProgress } from "../progress";
|
|
21
|
-
import { handleMaxAttemptsReached, handleNoTierAvailable } from "./tier-outcome";
|
|
22
|
-
|
|
23
|
-
/** Build a StructuredFailure for tier escalation. */
|
|
24
|
-
function buildEscalationFailure(
|
|
25
|
-
story: UserStory,
|
|
26
|
-
currentTier: string,
|
|
27
|
-
reviewFindings?: import("../../plugins/types").ReviewFinding[],
|
|
28
|
-
cost?: number,
|
|
29
|
-
): StructuredFailure {
|
|
30
|
-
return {
|
|
31
|
-
attempt: (story.attempts ?? 0) + 1,
|
|
32
|
-
modelTier: currentTier,
|
|
33
|
-
stage: "escalation" as const,
|
|
34
|
-
summary: `Failed with tier ${currentTier}, escalating to next tier`,
|
|
35
|
-
reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
|
|
36
|
-
cost: cost ?? 0,
|
|
37
|
-
timestamp: new Date().toISOString(),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Determine the outcome when max attempts are reached for an escalation.
|
|
43
|
-
*
|
|
44
|
-
* Returns 'pause' if the failure category requires human review
|
|
45
|
-
* (isolation-violation or verifier-rejected). For all other categories
|
|
46
|
-
* (session-failure, tests-failing, or no category) returns 'fail'.
|
|
47
|
-
*
|
|
48
|
-
* Exported for unit-testing without running the full runner loop.
|
|
49
|
-
*/
|
|
50
|
-
export function resolveMaxAttemptsOutcome(failureCategory?: FailureCategory): "pause" | "fail" {
|
|
51
|
-
if (!failureCategory) {
|
|
52
|
-
return "fail";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
switch (failureCategory) {
|
|
56
|
-
case "isolation-violation":
|
|
57
|
-
case "verifier-rejected":
|
|
58
|
-
case "greenfield-no-tests":
|
|
59
|
-
return "pause";
|
|
60
|
-
case "runtime-crash":
|
|
61
|
-
return "pause";
|
|
62
|
-
case "session-failure":
|
|
63
|
-
case "tests-failing":
|
|
64
|
-
return "fail";
|
|
65
|
-
default:
|
|
66
|
-
// Exhaustive check: if a new FailureCategory is added, this will error
|
|
67
|
-
failureCategory satisfies never;
|
|
68
|
-
return "fail";
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface PreIterationCheckResult {
|
|
73
|
-
shouldSkipIteration: boolean;
|
|
74
|
-
prdDirty: boolean;
|
|
75
|
-
prd: PRD;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Pre-iteration tier escalation check (BUG-16 + BUG-17 fix)
|
|
80
|
-
*
|
|
81
|
-
* Check if story has exceeded current tier's attempt budget BEFORE spawning agent.
|
|
82
|
-
* If exceeded, escalate to next tier or mark as failed.
|
|
83
|
-
*/
|
|
84
|
-
export async function preIterationTierCheck(
|
|
85
|
-
story: UserStory,
|
|
86
|
-
routing: { modelTier: string },
|
|
87
|
-
config: NaxConfig,
|
|
88
|
-
prd: PRD,
|
|
89
|
-
prdPath: string,
|
|
90
|
-
featureDir: string | undefined,
|
|
91
|
-
hooks: LoadedHooksConfig,
|
|
92
|
-
feature: string,
|
|
93
|
-
totalCost: number,
|
|
94
|
-
workdir: string,
|
|
95
|
-
): Promise<PreIterationCheckResult> {
|
|
96
|
-
const logger = getSafeLogger();
|
|
97
|
-
const currentTier = story.routing?.modelTier ?? routing.modelTier;
|
|
98
|
-
const tierOrder = config.autoMode.escalation?.tierOrder || [];
|
|
99
|
-
const tierCfg = tierOrder.length > 0 ? getTierConfig(currentTier, tierOrder) : undefined;
|
|
100
|
-
|
|
101
|
-
if (!tierCfg || (story.attempts ?? 0) < tierCfg.attempts) {
|
|
102
|
-
// Story still has budget in current tier
|
|
103
|
-
return { shouldSkipIteration: false, prdDirty: false, prd };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Exceeded current tier budget — try to escalate
|
|
107
|
-
const nextTier = escalateTier(currentTier, tierOrder);
|
|
108
|
-
const routingMode = config.routing.llm?.mode ?? "hybrid";
|
|
109
|
-
|
|
110
|
-
if (nextTier && config.autoMode.escalation.enabled) {
|
|
111
|
-
logger?.warn("escalation", "Story exceeded tier budget, escalating", {
|
|
112
|
-
storyId: story.id,
|
|
113
|
-
attempts: story.attempts,
|
|
114
|
-
tierAttempts: tierCfg.attempts,
|
|
115
|
-
currentTier,
|
|
116
|
-
nextTier,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Update story routing in PRD and reset attempts for new tier
|
|
120
|
-
const updatedPrd = {
|
|
121
|
-
...prd,
|
|
122
|
-
userStories: prd.userStories.map((s) =>
|
|
123
|
-
s.id === story.id
|
|
124
|
-
? {
|
|
125
|
-
...s,
|
|
126
|
-
attempts: 0, // Reset attempts for new tier
|
|
127
|
-
routing: s.routing ? { ...s.routing, modelTier: nextTier } : { ...routing, modelTier: nextTier },
|
|
128
|
-
}
|
|
129
|
-
: s,
|
|
130
|
-
) as PRD["userStories"],
|
|
131
|
-
} as PRD;
|
|
132
|
-
await savePRD(updatedPrd, prdPath);
|
|
133
|
-
|
|
134
|
-
// Clear routing cache for story to avoid returning old cached decision
|
|
135
|
-
clearCacheForStory(story.id);
|
|
136
|
-
|
|
137
|
-
// Hybrid mode: re-route story after escalation
|
|
138
|
-
if (routingMode === "hybrid") {
|
|
139
|
-
await tryLlmBatchRoute(config, [story], "hybrid-re-route");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Skip to next iteration (will reload PRD and use new tier)
|
|
143
|
-
return { shouldSkipIteration: true, prdDirty: true, prd: updatedPrd };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// No next tier or escalation disabled — mark story as failed
|
|
147
|
-
logger?.error("execution", "Story failed - all tiers exhausted", {
|
|
148
|
-
storyId: story.id,
|
|
149
|
-
attempts: story.attempts,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const failedPrd = { ...prd };
|
|
153
|
-
markStoryFailed(failedPrd, story.id, undefined, undefined);
|
|
154
|
-
await savePRD(failedPrd, prdPath);
|
|
155
|
-
|
|
156
|
-
if (featureDir) {
|
|
157
|
-
await appendProgress(featureDir, story.id, "failed", `${story.title} — All tiers exhausted`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
await fireHook(
|
|
161
|
-
hooks,
|
|
162
|
-
"on-story-fail",
|
|
163
|
-
hookCtx(feature, {
|
|
164
|
-
storyId: story.id,
|
|
165
|
-
status: "failed",
|
|
166
|
-
reason: `All tiers exhausted (${story.attempts} attempts)`,
|
|
167
|
-
cost: totalCost,
|
|
168
|
-
}),
|
|
169
|
-
workdir,
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// Skip to next iteration (will pick next story)
|
|
173
|
-
return { shouldSkipIteration: true, prdDirty: true, prd: failedPrd };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export interface EscalationHandlerContext {
|
|
177
|
-
story: UserStory;
|
|
178
|
-
storiesToExecute: UserStory[];
|
|
179
|
-
isBatchExecution: boolean;
|
|
180
|
-
routing: { modelTier: string; testStrategy: string };
|
|
181
|
-
pipelineResult: {
|
|
182
|
-
reason?: string;
|
|
183
|
-
context: {
|
|
184
|
-
retryAsLite?: boolean;
|
|
185
|
-
tddFailureCategory?: FailureCategory;
|
|
186
|
-
reviewFindings?: import("../../plugins/types").ReviewFinding[];
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
config: NaxConfig;
|
|
190
|
-
prd: PRD;
|
|
191
|
-
prdPath: string;
|
|
192
|
-
featureDir?: string;
|
|
193
|
-
hooks: LoadedHooksConfig;
|
|
194
|
-
feature: string;
|
|
195
|
-
totalCost: number;
|
|
196
|
-
workdir: string;
|
|
197
|
-
/** Verify result from the pipeline verify stage — used to detect RUNTIME_CRASH (BUG-070) */
|
|
198
|
-
verifyResult?: { status: string; success: boolean };
|
|
199
|
-
/** Cost of the failed attempt being escalated (BUG-067: accumulated across escalations) */
|
|
200
|
-
attemptCost?: number;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export interface EscalationHandlerResult {
|
|
204
|
-
outcome: "escalated" | "paused" | "failed" | "retry-same";
|
|
205
|
-
prdDirty: boolean;
|
|
206
|
-
prd: PRD;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Determine if the pipeline should retry the same tier due to a transient runtime crash.
|
|
211
|
-
*
|
|
212
|
-
* Returns true when the verify result status is RUNTIME_CRASH — these are Bun
|
|
213
|
-
* runtime-level failures, not code quality issues, so escalating the model tier
|
|
214
|
-
* would not help. Instead the same tier should be retried.
|
|
215
|
-
*
|
|
216
|
-
* @param verifyResult - Verify result from the pipeline verify stage
|
|
217
|
-
*/
|
|
218
|
-
export function shouldRetrySameTier(verifyResult: { status: string; success: boolean } | undefined): boolean {
|
|
219
|
-
return verifyResult?.status === "RUNTIME_CRASH";
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
|
|
224
|
-
*/
|
|
225
|
-
export const _tierEscalationDeps = {
|
|
226
|
-
savePRD,
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Handle tier escalation after pipeline escalation action
|
|
231
|
-
*
|
|
232
|
-
* Escalates to next tier or marks story as paused/failed based on failure category.
|
|
233
|
-
*/
|
|
234
|
-
export async function handleTierEscalation(ctx: EscalationHandlerContext): Promise<EscalationHandlerResult> {
|
|
235
|
-
const logger = getSafeLogger();
|
|
236
|
-
|
|
237
|
-
// BUG-070: Runtime crashes are transient — retry same tier, do NOT escalate
|
|
238
|
-
if (shouldRetrySameTier(ctx.verifyResult)) {
|
|
239
|
-
logger?.warn("escalation", "Runtime crash detected — retrying same tier (transient, not a code issue)", {
|
|
240
|
-
storyId: ctx.story.id,
|
|
241
|
-
});
|
|
242
|
-
return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
|
|
246
|
-
const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
|
|
247
|
-
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
248
|
-
|
|
249
|
-
// Retrieve TDD-specific context flags set by executionStage
|
|
250
|
-
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
251
|
-
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
252
|
-
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
253
|
-
// S5: Auto-switch to test-after on greenfield-no-tests
|
|
254
|
-
const escalateRetryAsTestAfter = escalateFailureCategory === "greenfield-no-tests";
|
|
255
|
-
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
256
|
-
|
|
257
|
-
if (!nextTier || !ctx.config.autoMode.escalation.enabled) {
|
|
258
|
-
// No next tier or escalation disabled — pause or fail based on failure category
|
|
259
|
-
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const maxAttempts = calculateMaxIterations(ctx.config.autoMode.escalation.tierOrder);
|
|
263
|
-
const canEscalate = storiesToEscalate.every((s) => (s.attempts ?? 0) < maxAttempts);
|
|
264
|
-
|
|
265
|
-
if (!canEscalate) {
|
|
266
|
-
// Max attempts reached — pause or fail based on failure category
|
|
267
|
-
return await handleMaxAttemptsReached(ctx, escalateFailureCategory);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Can escalate — log and update stories
|
|
271
|
-
for (const s of storiesToEscalate) {
|
|
272
|
-
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
273
|
-
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after";
|
|
274
|
-
|
|
275
|
-
if (shouldSwitchToTestAfter) {
|
|
276
|
-
logger?.warn("escalation", "Switching strategy to test-after (greenfield-no-tests fallback)", {
|
|
277
|
-
storyId: s.id,
|
|
278
|
-
fromStrategy: currentTestStrategy,
|
|
279
|
-
toStrategy: "test-after",
|
|
280
|
-
});
|
|
281
|
-
} else {
|
|
282
|
-
logger?.warn("escalation", "Escalating story to next tier", {
|
|
283
|
-
storyId: s.id,
|
|
284
|
-
nextTier,
|
|
285
|
-
retryAsLite: escalateRetryAsLite,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const pipelineReason = ctx.pipelineResult.reason ? `: ${ctx.pipelineResult.reason}` : "";
|
|
291
|
-
const errorMessage = `Attempt ${ctx.story.attempts + 1} failed with model tier: ${ctx.routing.modelTier}${ctx.isBatchExecution ? " (in batch)" : ""}${pipelineReason}`;
|
|
292
|
-
|
|
293
|
-
const updatedPrd = {
|
|
294
|
-
...ctx.prd,
|
|
295
|
-
userStories: ctx.prd.userStories.map((s) => {
|
|
296
|
-
const shouldEscalate = storiesToEscalate.some((story) => story.id === s.id);
|
|
297
|
-
if (!shouldEscalate) return s;
|
|
298
|
-
|
|
299
|
-
// S5: Check if this is a one-time test-after switch
|
|
300
|
-
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
301
|
-
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after";
|
|
302
|
-
|
|
303
|
-
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
304
|
-
const updatedRouting = {
|
|
305
|
-
...baseRouting,
|
|
306
|
-
modelTier: shouldSwitchToTestAfter ? baseRouting.modelTier : nextTier,
|
|
307
|
-
...(escalateRetryAsLite ? { testStrategy: "three-session-tdd-lite" as const } : {}),
|
|
308
|
-
...(shouldSwitchToTestAfter ? { testStrategy: "test-after" as const } : {}),
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// BUG-011: Reset attempt counter on tier escalation
|
|
312
|
-
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
313
|
-
const isChangingTier = currentStoryTier !== nextTier;
|
|
314
|
-
const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
|
|
315
|
-
|
|
316
|
-
// Build escalation failure (BUG-067: include cost for accumulatedAttemptCost in metrics)
|
|
317
|
-
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
318
|
-
|
|
319
|
-
return {
|
|
320
|
-
...s,
|
|
321
|
-
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
322
|
-
routing: updatedRouting,
|
|
323
|
-
priorErrors: [...(s.priorErrors || []), errorMessage],
|
|
324
|
-
priorFailures: [...(s.priorFailures || []), escalationFailure],
|
|
325
|
-
} as UserStory;
|
|
326
|
-
}) as PRD["userStories"],
|
|
327
|
-
} as PRD;
|
|
328
|
-
|
|
329
|
-
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
330
|
-
|
|
331
|
-
// Clear routing cache for all escalated stories to avoid returning old cached decisions
|
|
332
|
-
for (const story of storiesToEscalate) {
|
|
333
|
-
clearCacheForStory(story.id);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Hybrid mode: re-route escalated stories
|
|
337
|
-
if (routingMode === "hybrid") {
|
|
338
|
-
await tryLlmBatchRoute(ctx.config, storiesToEscalate, "hybrid-re-route-pipeline");
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
outcome: "escalated",
|
|
343
|
-
prdDirty: true,
|
|
344
|
-
prd: updatedPrd,
|
|
345
|
-
};
|
|
346
|
-
}
|