@nathapp/nax 0.21.0 → 0.22.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/.mcp.json +8 -0
- package/docs/ROADMAP.md +20 -5
- package/docs/adr/ADR-005-implementation-plan.md +655 -0
- package/docs/adr/ADR-005-pipeline-re-architecture.md +464 -0
- package/package.json +1 -1
- package/src/agents/claude.ts +44 -9
- package/src/config/types.ts +11 -0
- package/src/execution/dry-run.ts +81 -0
- package/src/execution/escalation/tier-outcome.ts +29 -44
- package/src/execution/executor-types.ts +65 -0
- package/src/execution/index.ts +0 -17
- package/src/execution/iteration-runner.ts +132 -0
- package/src/execution/lifecycle/index.ts +0 -1
- package/src/execution/lifecycle/run-regression.ts +5 -5
- package/src/execution/pipeline-result-handler.ts +51 -254
- package/src/execution/sequential-executor.ts +72 -316
- package/src/execution/story-selector.ts +75 -0
- package/src/pipeline/event-bus.ts +276 -0
- package/src/pipeline/runner.ts +51 -77
- package/src/pipeline/stages/autofix.ts +133 -0
- package/src/pipeline/stages/completion.ts +22 -30
- package/src/pipeline/stages/index.ts +30 -13
- package/src/pipeline/stages/rectify.ts +93 -0
- package/src/pipeline/stages/regression.ts +88 -0
- package/src/pipeline/stages/review.ts +19 -153
- package/src/pipeline/stages/verify.ts +18 -2
- package/src/pipeline/subscribers/hooks.ts +133 -0
- package/src/pipeline/subscribers/interaction.ts +68 -0
- package/src/pipeline/subscribers/reporters.ts +174 -0
- package/src/pipeline/types.ts +10 -1
- package/src/review/orchestrator.ts +105 -0
- package/src/tdd/prompts.ts +1 -1
- package/src/verification/index.ts +1 -1
- package/src/verification/orchestrator-types.ts +145 -0
- package/src/verification/orchestrator.ts +76 -0
- package/src/{execution/post-verify-rectification.ts → verification/rectification-loop.ts} +13 -20
- package/src/verification/{gate.ts → runners.ts} +17 -105
- package/src/verification/strategies/acceptance.ts +133 -0
- package/src/verification/strategies/regression.ts +90 -0
- package/src/verification/strategies/scoped.ts +123 -0
- package/test/COVERAGE-GAPS.md +333 -0
- package/test/{acceptance → e2e}/cm-003-default-view.test.ts +1 -0
- package/test/{integration/e2e.test.ts → e2e/plan-analyze-run.test.ts} +1 -0
- package/test/integration/{agent-validation.test.ts → cli/agent-validation.test.ts} +3 -3
- package/test/integration/{cli-config-default-edge-cases.test.ts → cli/cli-config-default-edge-cases.test.ts} +6 -5
- package/test/integration/{cli-config-default-view.test.ts → cli/cli-config-default-view.test.ts} +8 -7
- package/test/integration/{cli-config-diff.test.ts → cli/cli-config-diff.test.ts} +3 -2
- package/test/integration/{cli-config.test.ts → cli/cli-config.test.ts} +3 -2
- package/test/integration/{cli-diagnose.test.ts → cli/cli-diagnose.test.ts} +5 -4
- package/test/integration/{cli-logs.test.ts → cli/cli-logs.test.ts} +12 -3
- package/test/integration/{cli-plugins.test.ts → cli/cli-plugins.test.ts} +4 -3
- package/test/integration/{cli-precheck.test.ts → cli/cli-precheck.test.ts} +4 -3
- package/test/integration/{cli-run-headless.test.ts → cli/cli-run-headless.test.ts} +3 -2
- package/test/integration/{cli.test.ts → cli/cli.test.ts} +2 -1
- package/test/integration/{precheck-integration.test.ts → cli/precheck-integration.test.ts} +10 -9
- package/test/integration/{precheck-orchestrator.test.ts → cli/precheck-orchestrator.test.ts} +4 -3
- package/test/integration/{precheck.test.ts → cli/precheck.test.ts} +5 -4
- package/test/integration/{config-loader.test.ts → config/config-loader.test.ts} +2 -1
- package/test/integration/{config.test.ts → config/config.test.ts} +2 -2
- package/test/integration/config/merger.test.ts +1 -0
- package/test/integration/config/paths.test.ts +1 -0
- package/test/integration/{security-loader.test.ts → config/security-loader.test.ts} +2 -2
- package/test/integration/{context-integration.test.ts → context/context-integration.test.ts} +7 -6
- package/test/integration/{path-security.test.ts → context/context-path-security.test.ts} +2 -2
- package/test/integration/{context-provider-injection.test.ts → context/context-provider-injection.test.ts} +7 -6
- package/test/integration/{context-verification-integration.test.ts → context/context-verification-integration.test.ts} +5 -4
- package/test/integration/{s5-greenfield-fallback.test.ts → context/s5-greenfield-fallback.test.ts} +4 -3
- package/test/integration/{isolation.test.ts → execution/execution-isolation.test.ts} +1 -1
- package/test/integration/{execution.test.ts → execution/execution.test.ts} +8 -8
- package/test/integration/{parallel.test.ts → execution/parallel.test.ts} +2 -1
- package/test/integration/{prd-pause.test.ts → execution/prd-pause.test.ts} +2 -2
- package/test/integration/{prd-resolvers.test.ts → execution/prd-resolvers.test.ts} +3 -2
- package/test/integration/{progress.test.ts → execution/progress.test.ts} +1 -1
- package/test/integration/execution/runner-batching.test.ts +682 -0
- package/test/integration/{runner-config-plugins.test.ts → execution/runner-config-plugins.test.ts} +3 -2
- package/test/integration/execution/runner-escalation.test.ts +561 -0
- package/test/integration/{runner-fixes.test.ts → execution/runner-fixes.test.ts} +4 -3
- package/test/integration/{runner-plugin-integration.test.ts → execution/runner-plugin-integration.test.ts} +6 -5
- package/test/integration/execution/runner-queue-and-attempts.test.ts +476 -0
- package/test/integration/{status-file-integration.test.ts → execution/status-file-integration.test.ts} +9 -8
- package/test/integration/{status-file.test.ts → execution/status-file.test.ts} +3 -2
- package/test/integration/{status-writer.test.ts → execution/status-writer.test.ts} +5 -4
- package/test/integration/{story-id-in-events.test.ts → execution/story-id-in-events.test.ts} +9 -8
- package/test/integration/{interaction-chain-pipeline.test.ts → interaction/interaction-chain-pipeline.test.ts} +26 -14
- package/test/integration/{hooks.test.ts → pipeline/hooks.test.ts} +4 -2
- package/test/integration/{pipeline-acceptance.test.ts → pipeline/pipeline-acceptance.test.ts} +7 -6
- package/test/integration/{pipeline-events.test.ts → pipeline/pipeline-events.test.ts} +7 -6
- package/test/integration/{pipeline.test.ts → pipeline/pipeline.test.ts} +9 -7
- package/test/integration/{reporter-lifecycle.test.ts → pipeline/reporter-lifecycle.test.ts} +9 -7
- package/test/integration/{verify-stage.test.ts → pipeline/verify-stage.test.ts} +7 -5
- package/test/integration/{analyze-integration.test.ts → plan/analyze-integration.test.ts} +3 -2
- package/test/integration/{analyze-scanner.test.ts → plan/analyze-scanner.test.ts} +8 -7
- package/test/integration/{logger.test.ts → plan/logger.test.ts} +1 -1
- package/test/integration/{plan.test.ts → plan/plan.test.ts} +3 -3
- package/test/integration/plugins/config-integration.test.ts +1 -0
- package/test/integration/plugins/config-resolution.test.ts +1 -0
- package/test/integration/plugins/loader.test.ts +1 -0
- package/test/integration/plugins/{registry.test.ts → plugins-registry.test.ts} +1 -0
- package/test/integration/plugins/validator.test.ts +1 -0
- package/test/integration/{review-config-commands.test.ts → review/review-config-commands.test.ts} +4 -3
- package/test/integration/{review-config-schema.test.ts → review/review-config-schema.test.ts} +3 -2
- package/test/integration/{review-plugin-integration.test.ts → review/review-plugin-integration.test.ts} +5 -4
- package/test/integration/{review.test.ts → review/review.test.ts} +3 -2
- package/test/integration/routing/plugin-routing-advanced.test.ts +461 -0
- package/test/integration/{plugin-routing.test.ts → routing/plugin-routing-core.test.ts} +9 -403
- package/test/integration/{routing-stage-bug-021.test.ts → routing/routing-stage-bug-021.test.ts} +8 -7
- package/test/integration/{routing-stage-greenfield.test.ts → routing/routing-stage-greenfield.test.ts} +7 -6
- package/test/integration/{tdd-cleanup.test.ts → tdd/tdd-cleanup.test.ts} +1 -1
- package/test/integration/tdd/tdd-orchestrator-core.test.ts +565 -0
- package/test/integration/tdd/tdd-orchestrator-failureCategory.test.ts +355 -0
- package/test/integration/tdd/tdd-orchestrator-fallback.test.ts +311 -0
- package/test/integration/tdd/tdd-orchestrator-lite.test.ts +289 -0
- package/test/integration/tdd/tdd-orchestrator-prompts.test.ts +260 -0
- package/test/integration/tdd/tdd-orchestrator-verdict.test.ts +536 -0
- package/test/integration/tmp/headless-test/test.jsonl +30 -0
- package/test/integration/{test-scanner.test.ts → verification/test-scanner.test.ts} +1 -1
- package/test/integration/{verification-asset-check.test.ts → verification/verification-asset-check.test.ts} +3 -2
- package/test/unit/acceptance.test.ts +1 -0
- package/test/unit/agent-stderr-capture.test.ts +1 -0
- package/test/unit/agents/claude.test.ts +1 -0
- package/test/unit/analyze-classifier.test.ts +1 -0
- package/test/unit/auto-detect.test.ts +1 -0
- package/test/unit/cli-status.test.ts +1 -0
- package/test/unit/commands/common.test.ts +1 -0
- package/test/unit/commands/logs.test.ts +1 -0
- package/test/unit/commands/unlock.test.ts +1 -0
- package/test/unit/config/defaults.test.ts +1 -0
- package/test/unit/config/regression-gate-schema.test.ts +1 -0
- package/test/unit/config/smart-runner-flag.test.ts +1 -0
- package/test/unit/constitution-generators.test.ts +1 -0
- package/test/unit/constitution.test.ts +1 -0
- package/test/unit/context/context-autodetect.test.ts +297 -0
- package/test/unit/context/context-build.test.ts +575 -0
- package/test/unit/context/context-coverage.test.ts +236 -0
- package/test/unit/context/context-error.test.ts +93 -0
- package/test/unit/context/context-estimate-tokens.test.ts +201 -0
- package/test/unit/context/context-format.test.ts +302 -0
- package/test/unit/context/context-isolation.test.ts +267 -0
- package/test/unit/context/context-sort.test.ts +93 -0
- package/test/unit/context/context-story.test.ts +108 -0
- package/test/{context → unit/context}/prior-failures.test.ts +5 -4
- package/test/unit/context.test.ts +1 -0
- package/test/unit/crash-recovery.test.ts +1 -0
- package/test/unit/escalation.test.ts +1 -0
- package/test/unit/execution/lifecycle/run-completion.test.ts +1 -0
- package/test/unit/execution/lifecycle/run-regression.test.ts +2 -0
- package/test/{execution → unit/execution}/pid-registry.test.ts +2 -1
- package/test/{execution → unit/execution}/structured-failure.test.ts +3 -2
- package/test/unit/execution-logging-stderr.test.ts +1 -0
- package/test/unit/execution-stage.test.ts +1 -0
- package/test/unit/fix-generator.test.ts +1 -0
- package/test/unit/greenfield.test.ts +1 -0
- package/test/unit/interaction/human-review-trigger.test.ts +1 -0
- package/test/unit/interaction-network-failures.test.ts +1 -0
- package/test/unit/interaction-plugins.test.ts +1 -0
- package/test/unit/logging/formatter.test.ts +1 -0
- package/test/unit/merge.test.ts +1 -0
- package/test/unit/pipeline/event-bus.test.ts +105 -0
- package/test/unit/pipeline/routing-partial-override.test.ts +1 -0
- package/test/unit/pipeline/runner-retry.test.ts +89 -0
- package/test/unit/pipeline/stages/autofix.test.ts +97 -0
- package/test/unit/pipeline/stages/rectify.test.ts +101 -0
- package/test/unit/pipeline/stages/regression-stage.test.ts +69 -0
- package/test/unit/pipeline/stages/verify.test.ts +1 -0
- package/test/unit/pipeline/subscribers/hooks.test.ts +45 -0
- package/test/unit/pipeline/subscribers/interaction.test.ts +31 -0
- package/test/unit/pipeline/subscribers/reporters.test.ts +90 -0
- package/test/unit/pipeline/verify-smart-runner.test.ts +1 -0
- package/test/unit/prd-auto-default.test.ts +1 -0
- package/test/unit/prd-failure-category.test.ts +1 -0
- package/test/unit/prd-get-next-story.test.ts +1 -0
- package/test/unit/precheck-checks.test.ts +1 -0
- package/test/unit/precheck-story-size-gate.test.ts +1 -0
- package/test/unit/precheck-types.test.ts +1 -0
- package/test/unit/prompts.test.ts +1 -0
- package/test/unit/rectification.test.ts +2 -1
- package/test/unit/registry.test.ts +1 -0
- package/test/unit/routing/routing-stability.test.ts +1 -0
- package/test/unit/routing/strategies/llm.test.ts +1 -0
- package/test/unit/routing-advanced.test.ts +313 -0
- package/test/unit/routing-core.test.ts +341 -0
- package/test/unit/routing-strategies.test.ts +442 -0
- package/test/unit/storyid-events.test.ts +1 -0
- package/test/{ui → unit/ui}/tui-controls.test.ts +8 -7
- package/test/{ui → unit/ui}/tui-cost-and-pty.test.ts +4 -3
- package/test/{ui → unit/ui}/tui-layout.test.ts +5 -4
- package/test/{ui → unit/ui}/tui-stories.test.ts +5 -4
- package/test/unit/{isolation.test.ts → unit-isolation.test.ts} +1 -0
- package/test/unit/{helpers.test.ts → utils-helpers.test.ts} +1 -0
- package/test/unit/verdict.test.ts +1 -0
- package/test/unit/verification/orchestrator-types.test.ts +54 -0
- package/test/unit/verification/orchestrator.test.ts +66 -0
- package/test/unit/verification/smart-runner-config.test.ts +1 -0
- package/test/unit/verification/smart-runner-discovery.test.ts +8 -7
- package/test/unit/verification/strategies/acceptance.test.ts +33 -0
- package/test/unit/verification/strategies/regression.test.ts +87 -0
- package/test/unit/verification/strategies/scoped.test.ts +100 -0
- package/test/unit/worktree-manager.test.ts +1 -0
- package/src/execution/lifecycle/story-hooks.ts +0 -38
- package/src/execution/post-verify.ts +0 -193
- package/src/execution/rectification.ts +0 -13
- package/src/execution/verification.ts +0 -72
- package/test/integration/rectification-flow.test.ts +0 -512
- package/test/integration/runner.test.ts +0 -1679
- package/test/integration/tdd-orchestrator.test.ts +0 -1762
- package/test/unit/execution/post-verify-regression.test.ts +0 -362
- package/test/unit/execution/post-verify.test.ts +0 -236
- package/test/unit/routing.test.ts +0 -1039
- /package/test/{integration → helpers}/helpers.test.ts +0 -0
- /package/test/integration/worktree/{merge.test.ts → worktree-merge.test.ts} +0 -0
|
@@ -1,77 +1,43 @@
|
|
|
1
|
-
/** Sequential Story Executor — main execution loop
|
|
1
|
+
/** Sequential Story Executor (ADR-005, Phase 4) — main execution loop. */
|
|
2
2
|
|
|
3
|
-
import type { NaxConfig } from "../config";
|
|
4
|
-
import { type LoadedHooksConfig, fireHook } from "../hooks";
|
|
5
|
-
import type { InteractionChain } from "../interaction/chain";
|
|
6
3
|
import { getSafeLogger } from "../logger";
|
|
7
4
|
import type { StoryMetrics } from "../metrics";
|
|
8
|
-
import
|
|
5
|
+
import { pipelineEventBus } from "../pipeline/event-bus";
|
|
9
6
|
import { runPipeline } from "../pipeline/runner";
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import type { StoryBatch } from "./batching";
|
|
7
|
+
import { postRunPipeline } from "../pipeline/stages";
|
|
8
|
+
import { wireHooks } from "../pipeline/subscribers/hooks";
|
|
9
|
+
import { wireInteraction } from "../pipeline/subscribers/interaction";
|
|
10
|
+
import { wireReporters } from "../pipeline/subscribers/reporters";
|
|
11
|
+
import type { PipelineContext } from "../pipeline/types";
|
|
12
|
+
import { generateHumanHaltSummary, isComplete, isStalled, loadPRD } from "../prd";
|
|
13
|
+
import type { PRD } from "../prd/types";
|
|
18
14
|
import { startHeartbeat, stopHeartbeat, writeExitSummary } from "./crash-recovery";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
applyCachedRouting,
|
|
23
|
-
handleDryRun,
|
|
24
|
-
handlePipelineFailure,
|
|
25
|
-
handlePipelineSuccess,
|
|
26
|
-
} from "./pipeline-result-handler";
|
|
27
|
-
import type { StatusWriter } from "./status-writer";
|
|
15
|
+
import type { SequentialExecutionContext, SequentialExecutionResult } from "./executor-types";
|
|
16
|
+
import { runIteration } from "./iteration-runner";
|
|
17
|
+
import { selectNextStories } from "./story-selector";
|
|
28
18
|
|
|
29
|
-
export
|
|
30
|
-
prdPath: string;
|
|
31
|
-
workdir: string;
|
|
32
|
-
config: NaxConfig;
|
|
33
|
-
hooks: LoadedHooksConfig;
|
|
34
|
-
feature: string;
|
|
35
|
-
featureDir?: string;
|
|
36
|
-
dryRun: boolean;
|
|
37
|
-
useBatch: boolean;
|
|
38
|
-
pluginRegistry: PluginRegistry;
|
|
39
|
-
eventEmitter?: PipelineEventEmitter;
|
|
40
|
-
statusWriter: StatusWriter;
|
|
41
|
-
logFilePath?: string;
|
|
42
|
-
runId: string;
|
|
43
|
-
startTime: number;
|
|
44
|
-
batchPlan: StoryBatch[];
|
|
45
|
-
interactionChain?: InteractionChain | null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface SequentialExecutionResult {
|
|
49
|
-
prd: PRD;
|
|
50
|
-
iterations: number;
|
|
51
|
-
storiesCompleted: number;
|
|
52
|
-
totalCost: number;
|
|
53
|
-
allStoryMetrics: StoryMetrics[];
|
|
54
|
-
timeoutRetryCountMap: Map<string, number>;
|
|
55
|
-
exitReason: "completed" | "cost-limit" | "max-iterations" | "stalled" | "no-stories";
|
|
56
|
-
}
|
|
19
|
+
export type { SequentialExecutionContext, SequentialExecutionResult } from "./executor-types";
|
|
57
20
|
|
|
58
|
-
/**
|
|
59
|
-
* Execute stories sequentially through the pipeline
|
|
60
|
-
*/
|
|
61
21
|
export async function executeSequential(
|
|
62
22
|
ctx: SequentialExecutionContext,
|
|
63
23
|
initialPrd: PRD,
|
|
64
24
|
): Promise<SequentialExecutionResult> {
|
|
65
25
|
const logger = getSafeLogger();
|
|
66
|
-
let prd =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
let [prd, prdDirty, iterations, storiesCompleted, totalCost, lastStoryId, currentBatchIndex] = [
|
|
27
|
+
initialPrd,
|
|
28
|
+
false,
|
|
29
|
+
0,
|
|
30
|
+
0,
|
|
31
|
+
0,
|
|
32
|
+
null as string | null,
|
|
33
|
+
0,
|
|
34
|
+
];
|
|
71
35
|
const allStoryMetrics: StoryMetrics[] = [];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
36
|
+
|
|
37
|
+
pipelineEventBus.clear();
|
|
38
|
+
wireHooks(pipelineEventBus, ctx.hooks, ctx.workdir, ctx.feature);
|
|
39
|
+
wireReporters(pipelineEventBus, ctx.pluginRegistry, ctx.runId, ctx.startTime);
|
|
40
|
+
wireInteraction(pipelineEventBus, ctx.interactionChain, ctx.config);
|
|
75
41
|
|
|
76
42
|
const buildResult = (exitReason: SequentialExecutionResult["exitReason"]): SequentialExecutionResult => ({
|
|
77
43
|
prd,
|
|
@@ -79,7 +45,6 @@ export async function executeSequential(
|
|
|
79
45
|
storiesCompleted,
|
|
80
46
|
totalCost,
|
|
81
47
|
allStoryMetrics,
|
|
82
|
-
timeoutRetryCountMap,
|
|
83
48
|
exitReason,
|
|
84
49
|
});
|
|
85
50
|
|
|
@@ -91,263 +56,65 @@ export async function executeSequential(
|
|
|
91
56
|
);
|
|
92
57
|
|
|
93
58
|
try {
|
|
94
|
-
// Main execution loop
|
|
95
59
|
while (iterations < ctx.config.execution.maxIterations) {
|
|
96
60
|
iterations++;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const memUsage = process.memoryUsage();
|
|
100
|
-
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
|
101
|
-
if (heapUsedMB > 1024) {
|
|
102
|
-
logger?.warn("execution", "High memory usage detected", {
|
|
103
|
-
heapUsedMB,
|
|
104
|
-
suggestion: "Consider pausing (echo PAUSE > .queue.txt) if this continues to grow",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Reload PRD only if dirty (modified since last load)
|
|
61
|
+
if (Math.round(process.memoryUsage().heapUsed / 1024 / 1024) > 1024)
|
|
62
|
+
logger?.warn("execution", "High memory usage detected");
|
|
109
63
|
if (prdDirty) {
|
|
110
64
|
prd = await loadPRD(ctx.prdPath);
|
|
111
65
|
prdDirty = false;
|
|
112
66
|
}
|
|
113
|
-
|
|
114
|
-
// Check completion
|
|
115
67
|
if (isComplete(prd)) {
|
|
116
|
-
|
|
117
|
-
|
|
68
|
+
pipelineEventBus.emit({
|
|
69
|
+
type: "run:completed",
|
|
70
|
+
totalStories: 0,
|
|
71
|
+
passedStories: 0,
|
|
72
|
+
failedStories: 0,
|
|
73
|
+
durationMs: Date.now() - ctx.startTime,
|
|
118
74
|
totalCost,
|
|
119
75
|
});
|
|
120
|
-
await fireHook(
|
|
121
|
-
ctx.hooks,
|
|
122
|
-
"on-complete",
|
|
123
|
-
hookCtx(ctx.feature, { status: "complete", cost: totalCost }),
|
|
124
|
-
ctx.workdir,
|
|
125
|
-
);
|
|
126
76
|
return buildResult("completed");
|
|
127
77
|
}
|
|
128
78
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
let routing: ReturnType<typeof routeTask>;
|
|
134
|
-
|
|
135
|
-
if (ctx.useBatch && currentBatchIndex < ctx.batchPlan.length) {
|
|
136
|
-
// Get next batch from precomputed plan
|
|
137
|
-
const batch = ctx.batchPlan[currentBatchIndex];
|
|
138
|
-
currentBatchIndex++;
|
|
139
|
-
|
|
140
|
-
// Filter out already-completed stories
|
|
141
|
-
storiesToExecute = batch.stories.filter(
|
|
142
|
-
(s) =>
|
|
143
|
-
!s.passes &&
|
|
144
|
-
s.status !== "passed" &&
|
|
145
|
-
s.status !== "skipped" &&
|
|
146
|
-
s.status !== "blocked" &&
|
|
147
|
-
s.status !== "failed" &&
|
|
148
|
-
s.status !== "paused",
|
|
149
|
-
);
|
|
150
|
-
isBatchExecution = batch.isBatch && storiesToExecute.length > 1;
|
|
151
|
-
|
|
152
|
-
if (storiesToExecute.length === 0) {
|
|
153
|
-
// All stories in this batch already completed, move to next batch
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Use first story as the primary story for routing/context
|
|
158
|
-
story = storiesToExecute[0];
|
|
159
|
-
routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, ctx.config);
|
|
160
|
-
routing = applyCachedRouting(routing, story, ctx.config);
|
|
161
|
-
} else {
|
|
162
|
-
// Fallback to single-story mode (when batching disabled or batch plan exhausted)
|
|
163
|
-
const nextStory = getNextStory(prd, lastStoryId, ctx.config.execution.rectification?.maxRetries ?? 2);
|
|
164
|
-
if (!nextStory) {
|
|
165
|
-
logger?.warn("execution", "No actionable stories (check dependencies)");
|
|
166
|
-
return buildResult("no-stories");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
story = nextStory;
|
|
170
|
-
lastStoryId = story.id;
|
|
171
|
-
storiesToExecute = [story];
|
|
172
|
-
isBatchExecution = false;
|
|
173
|
-
|
|
174
|
-
routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, ctx.config);
|
|
175
|
-
routing = applyCachedRouting(routing, story, ctx.config);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Pre-iteration tier escalation check
|
|
179
|
-
const tierCheckResult = await preIterationTierCheck(
|
|
180
|
-
story,
|
|
181
|
-
routing,
|
|
182
|
-
ctx.config,
|
|
183
|
-
prd,
|
|
184
|
-
ctx.prdPath,
|
|
185
|
-
ctx.featureDir,
|
|
186
|
-
ctx.hooks,
|
|
187
|
-
ctx.feature,
|
|
188
|
-
totalCost,
|
|
189
|
-
ctx.workdir,
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
if (tierCheckResult.shouldSkipIteration) {
|
|
193
|
-
prd = tierCheckResult.prd;
|
|
194
|
-
prdDirty = tierCheckResult.prdDirty;
|
|
79
|
+
const selected = selectNextStories(prd, ctx.config, ctx.batchPlan, currentBatchIndex, lastStoryId, ctx.useBatch);
|
|
80
|
+
if (!selected) return buildResult("no-stories");
|
|
81
|
+
if (!selected.selection) {
|
|
82
|
+
currentBatchIndex = selected.nextBatchIndex;
|
|
195
83
|
continue;
|
|
196
84
|
}
|
|
85
|
+
currentBatchIndex = selected.nextBatchIndex;
|
|
86
|
+
const { selection } = selected;
|
|
87
|
+
if (!ctx.useBatch) lastStoryId = selection.story.id;
|
|
197
88
|
|
|
198
|
-
// Check cost limit
|
|
199
89
|
if (totalCost >= ctx.config.execution.costLimit) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
90
|
+
pipelineEventBus.emit({
|
|
91
|
+
type: "run:paused",
|
|
92
|
+
reason: `Cost limit reached: $${totalCost.toFixed(2)}`,
|
|
93
|
+
storyId: selection.story.id,
|
|
94
|
+
cost: totalCost,
|
|
203
95
|
});
|
|
204
|
-
await fireHook(
|
|
205
|
-
ctx.hooks,
|
|
206
|
-
"on-pause",
|
|
207
|
-
hookCtx(ctx.feature, {
|
|
208
|
-
storyId: story.id,
|
|
209
|
-
reason: `Cost limit reached: $${totalCost.toFixed(2)}`,
|
|
210
|
-
cost: totalCost,
|
|
211
|
-
}),
|
|
212
|
-
ctx.workdir,
|
|
213
|
-
);
|
|
214
96
|
return buildResult("cost-limit");
|
|
215
97
|
}
|
|
216
98
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
storyId: story.id,
|
|
220
|
-
|
|
221
|
-
isBatch: isBatchExecution,
|
|
222
|
-
batchSize: isBatchExecution ? storiesToExecute.length : 1,
|
|
223
|
-
modelTier: routing.modelTier,
|
|
224
|
-
complexity: routing.complexity,
|
|
225
|
-
...(isBatchExecution && { batchStoryIds: storiesToExecute.map((s) => s.id) }),
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Fire story-start hook
|
|
229
|
-
await fireHook(
|
|
230
|
-
ctx.hooks,
|
|
231
|
-
"on-story-start",
|
|
232
|
-
hookCtx(ctx.feature, {
|
|
233
|
-
storyId: story.id,
|
|
234
|
-
model: routing.modelTier,
|
|
235
|
-
agent: ctx.config.autoMode.defaultAgent,
|
|
236
|
-
iteration: iterations,
|
|
237
|
-
}),
|
|
238
|
-
ctx.workdir,
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
if (ctx.dryRun) {
|
|
242
|
-
const dryRunResult = await handleDryRun({
|
|
243
|
-
prd,
|
|
244
|
-
prdPath: ctx.prdPath,
|
|
245
|
-
storiesToExecute,
|
|
246
|
-
routing,
|
|
247
|
-
statusWriter: ctx.statusWriter,
|
|
248
|
-
pluginRegistry: ctx.pluginRegistry,
|
|
249
|
-
runId: ctx.runId,
|
|
250
|
-
totalCost,
|
|
251
|
-
iterations,
|
|
252
|
-
});
|
|
253
|
-
storiesCompleted += dryRunResult.storiesCompletedDelta;
|
|
254
|
-
prdDirty = dryRunResult.prdDirty;
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Capture git ref for scoped verification
|
|
259
|
-
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
260
|
-
|
|
261
|
-
// Build pipeline context
|
|
262
|
-
const storyStartTime = new Date().toISOString();
|
|
263
|
-
const pipelineContext: PipelineContext = {
|
|
264
|
-
config: ctx.config,
|
|
265
|
-
prd,
|
|
266
|
-
story,
|
|
267
|
-
stories: storiesToExecute,
|
|
268
|
-
routing: routing as RoutingResult,
|
|
99
|
+
pipelineEventBus.emit({
|
|
100
|
+
type: "story:started",
|
|
101
|
+
storyId: selection.story.id,
|
|
102
|
+
story: selection.story,
|
|
269
103
|
workdir: ctx.workdir,
|
|
270
|
-
|
|
271
|
-
hooks: ctx.hooks,
|
|
272
|
-
plugins: ctx.pluginRegistry,
|
|
273
|
-
storyStartTime,
|
|
274
|
-
storyGitRef: storyGitRef ?? undefined, // FEAT-010: per-attempt baseRef for precise smart-runner diff
|
|
275
|
-
interaction: ctx.interactionChain ?? undefined,
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
// Log agent start
|
|
279
|
-
logger?.info("agent.start", "Starting agent execution", {
|
|
280
|
-
storyId: story.id,
|
|
104
|
+
modelTier: selection.routing.modelTier,
|
|
281
105
|
agent: ctx.config.autoMode.defaultAgent,
|
|
282
|
-
|
|
283
|
-
testStrategy: routing.testStrategy,
|
|
284
|
-
isBatch: isBatchExecution,
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Update status before execution
|
|
288
|
-
ctx.statusWriter.setPrd(prd);
|
|
289
|
-
ctx.statusWriter.setCurrentStory({
|
|
290
|
-
storyId: story.id,
|
|
291
|
-
title: story.title,
|
|
292
|
-
complexity: routing.complexity,
|
|
293
|
-
tddStrategy: routing.testStrategy,
|
|
294
|
-
model: routing.modelTier,
|
|
295
|
-
attempt: (story.attempts ?? 0) + 1,
|
|
296
|
-
phase: "routing",
|
|
297
|
-
});
|
|
298
|
-
await ctx.statusWriter.update(totalCost, iterations);
|
|
299
|
-
|
|
300
|
-
// Run pipeline
|
|
301
|
-
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
302
|
-
|
|
303
|
-
// Log agent complete
|
|
304
|
-
logger?.info("agent.complete", "Agent execution completed", {
|
|
305
|
-
storyId: story.id,
|
|
306
|
-
success: pipelineResult.success,
|
|
307
|
-
finalAction: pipelineResult.finalAction,
|
|
308
|
-
estimatedCost: pipelineResult.context.agentResult?.estimatedCost,
|
|
106
|
+
iteration: iterations,
|
|
309
107
|
});
|
|
310
108
|
|
|
311
|
-
|
|
312
|
-
prd =
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
prdPath: ctx.prdPath,
|
|
319
|
-
workdir: ctx.workdir,
|
|
320
|
-
featureDir: ctx.featureDir,
|
|
321
|
-
hooks: ctx.hooks,
|
|
322
|
-
feature: ctx.feature,
|
|
323
|
-
totalCost,
|
|
324
|
-
startTime: ctx.startTime,
|
|
325
|
-
runId: ctx.runId,
|
|
326
|
-
pluginRegistry: ctx.pluginRegistry,
|
|
327
|
-
story,
|
|
328
|
-
storiesToExecute,
|
|
329
|
-
routing,
|
|
330
|
-
isBatchExecution,
|
|
331
|
-
allStoryMetrics,
|
|
332
|
-
timeoutRetryCountMap,
|
|
333
|
-
storyGitRef,
|
|
334
|
-
interactionChain: ctx.interactionChain,
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
if (pipelineResult.success) {
|
|
338
|
-
const successResult = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
339
|
-
totalCost += successResult.costDelta;
|
|
340
|
-
storiesCompleted += successResult.storiesCompletedDelta;
|
|
341
|
-
prd = successResult.prd;
|
|
342
|
-
prdDirty = successResult.prdDirty;
|
|
343
|
-
} else {
|
|
344
|
-
const failResult = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
345
|
-
prd = failResult.prd;
|
|
346
|
-
prdDirty = failResult.prdDirty;
|
|
347
|
-
}
|
|
109
|
+
const iter = await runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics);
|
|
110
|
+
[prd, storiesCompleted, totalCost, prdDirty] = [
|
|
111
|
+
iter.prd,
|
|
112
|
+
storiesCompleted + iter.storiesCompletedDelta,
|
|
113
|
+
totalCost + iter.costDelta,
|
|
114
|
+
iter.prdDirty,
|
|
115
|
+
];
|
|
348
116
|
|
|
349
|
-
|
|
350
|
-
if (prdDirty) {
|
|
117
|
+
if (iter.prdDirty) {
|
|
351
118
|
prd = await loadPRD(ctx.prdPath);
|
|
352
119
|
prdDirty = false;
|
|
353
120
|
}
|
|
@@ -355,35 +122,24 @@ export async function executeSequential(
|
|
|
355
122
|
ctx.statusWriter.setCurrentStory(null);
|
|
356
123
|
await ctx.statusWriter.update(totalCost, iterations);
|
|
357
124
|
|
|
358
|
-
// Stall detection
|
|
359
125
|
if (isStalled(prd)) {
|
|
360
|
-
|
|
361
|
-
logger?.error("execution", "Execution stalled", {
|
|
362
|
-
reason: "All remaining stories blocked or dependent on blocked stories",
|
|
363
|
-
summary,
|
|
364
|
-
});
|
|
365
|
-
await fireHook(
|
|
366
|
-
ctx.hooks,
|
|
367
|
-
"on-pause",
|
|
368
|
-
hookCtx(ctx.feature, {
|
|
369
|
-
reason: "All remaining stories blocked or dependent on blocked stories",
|
|
370
|
-
cost: totalCost,
|
|
371
|
-
}),
|
|
372
|
-
ctx.workdir,
|
|
373
|
-
);
|
|
126
|
+
pipelineEventBus.emit({ type: "run:paused", reason: "All remaining stories blocked", cost: totalCost });
|
|
374
127
|
return buildResult("stalled");
|
|
375
128
|
}
|
|
376
|
-
|
|
377
|
-
// Delay between iterations
|
|
378
|
-
if (ctx.config.execution.iterationDelayMs > 0) {
|
|
379
|
-
await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
380
|
-
}
|
|
129
|
+
if (ctx.config.execution.iterationDelayMs > 0) await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
381
130
|
}
|
|
382
131
|
|
|
132
|
+
// Post-run pipeline (acceptance tests)
|
|
133
|
+
logger?.info("execution", "Running post-run pipeline (acceptance tests)");
|
|
134
|
+
await runPipeline(
|
|
135
|
+
postRunPipeline,
|
|
136
|
+
{ config: ctx.config, prd, workdir: ctx.workdir, story: prd.userStories[0] } as unknown as PipelineContext,
|
|
137
|
+
ctx.eventEmitter,
|
|
138
|
+
);
|
|
139
|
+
|
|
383
140
|
return buildResult("max-iterations");
|
|
384
141
|
} finally {
|
|
385
|
-
// Stop heartbeat and write exit summary
|
|
386
142
|
stopHeartbeat();
|
|
387
|
-
|
|
143
|
+
writeExitSummary(ctx.logFilePath, totalCost, iterations, storiesCompleted, Date.now() - ctx.startTime);
|
|
388
144
|
}
|
|
389
145
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story Selector (ADR-005, Phase 4)
|
|
3
|
+
*
|
|
4
|
+
* Extracted from sequential-executor.ts: batch/single-story selection logic.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NaxConfig } from "../config";
|
|
8
|
+
import type { RoutingResult } from "../pipeline/types";
|
|
9
|
+
import { getNextStory } from "../prd";
|
|
10
|
+
import type { PRD, UserStory } from "../prd/types";
|
|
11
|
+
import type { StoryBatch } from "./batching";
|
|
12
|
+
import { buildPreviewRouting } from "./executor-types";
|
|
13
|
+
|
|
14
|
+
export interface StorySelection {
|
|
15
|
+
story: UserStory;
|
|
16
|
+
storiesToExecute: UserStory[];
|
|
17
|
+
routing: RoutingResult;
|
|
18
|
+
isBatchExecution: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Select the next story (or batch) to execute.
|
|
23
|
+
* Returns null when there are no more stories to run.
|
|
24
|
+
*/
|
|
25
|
+
export function selectNextStories(
|
|
26
|
+
prd: PRD,
|
|
27
|
+
config: NaxConfig,
|
|
28
|
+
batchPlan: StoryBatch[],
|
|
29
|
+
currentBatchIndex: number,
|
|
30
|
+
lastStoryId: string | null,
|
|
31
|
+
useBatch: boolean,
|
|
32
|
+
): { selection: StorySelection; nextBatchIndex: number } | null {
|
|
33
|
+
if (useBatch && currentBatchIndex < batchPlan.length) {
|
|
34
|
+
const batch = batchPlan[currentBatchIndex];
|
|
35
|
+
const storiesToExecute = batch.stories.filter(
|
|
36
|
+
(s) =>
|
|
37
|
+
!s.passes &&
|
|
38
|
+
s.status !== "passed" &&
|
|
39
|
+
s.status !== "skipped" &&
|
|
40
|
+
s.status !== "blocked" &&
|
|
41
|
+
s.status !== "failed" &&
|
|
42
|
+
s.status !== "paused",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (storiesToExecute.length === 0) {
|
|
46
|
+
// Batch exhausted (all already done) — advance index, caller retries
|
|
47
|
+
return { selection: null as unknown as StorySelection, nextBatchIndex: currentBatchIndex + 1 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const story = storiesToExecute[0];
|
|
51
|
+
return {
|
|
52
|
+
selection: {
|
|
53
|
+
story,
|
|
54
|
+
storiesToExecute,
|
|
55
|
+
routing: buildPreviewRouting(story, config),
|
|
56
|
+
isBatchExecution: batch.isBatch && storiesToExecute.length > 1,
|
|
57
|
+
},
|
|
58
|
+
nextBatchIndex: currentBatchIndex + 1,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Single-story fallback
|
|
63
|
+
const story = getNextStory(prd, lastStoryId, config.execution.rectification?.maxRetries ?? 2);
|
|
64
|
+
if (!story) return null;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
selection: {
|
|
68
|
+
story,
|
|
69
|
+
storiesToExecute: [story],
|
|
70
|
+
routing: buildPreviewRouting(story, config),
|
|
71
|
+
isBatchExecution: false,
|
|
72
|
+
},
|
|
73
|
+
nextBatchIndex: currentBatchIndex,
|
|
74
|
+
};
|
|
75
|
+
}
|