@nathapp/nax 0.20.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/.claude/settings.json +15 -0
- package/.mcp.json +8 -0
- package/docs/20260304-review-nax.md +492 -0
- package/docs/ROADMAP.md +65 -18
- package/docs/adr/ADR-005-implementation-plan.md +655 -0
- package/docs/adr/ADR-005-pipeline-re-architecture.md +464 -0
- package/docs/specs/bug-039-orphan-processes.md +131 -0
- package/docs/specs/bug-040-review-rectification.md +82 -0
- package/docs/specs/bug-041-cross-story-test-isolation.md +88 -0
- package/docs/specs/bug-042-verifier-failure-capture.md +117 -0
- package/docs/specs/feat-010-smart-runner-git-history.md +96 -0
- package/docs/specs/feat-011-file-context-strategy.md +73 -0
- package/docs/specs/feat-012-tdd-writer-tier.md +79 -0
- package/docs/specs/feat-013-test-after-review.md +89 -0
- package/docs/specs/feat-014-heartbeat-observability.md +127 -0
- package/memory/topic/feat-010-baseref.md +28 -0
- package/memory/topic/feat-013-test-after-deprecation.md +22 -0
- package/nax/config.json +7 -4
- package/nax/features/bug-039-medium/prd.json +45 -0
- package/package.json +2 -2
- package/src/agents/claude.ts +109 -15
- package/src/config/types.ts +11 -0
- package/src/context/builder.ts +9 -1
- 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 -315
- 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 +19 -3
- 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 +12 -1
- package/src/review/orchestrator.ts +105 -0
- package/src/review/runner.ts +39 -4
- package/src/routing/router.ts +3 -3
- package/src/routing/strategies/keyword.ts +5 -2
- package/src/routing/strategies/llm.ts +27 -1
- package/src/tdd/prompts.ts +1 -1
- package/src/utils/git.ts +49 -25
- package/src/verification/executor.ts +8 -2
- 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/smart-runner.ts +6 -10
- 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} +10 -404
- 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 +107 -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 +7 -3
- 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 +2 -1
- package/test/unit/prd-auto-default.test.ts +3 -2
- 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 +2 -1
- package/test/unit/routing/strategies/llm.test.ts +251 -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
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iteration Runner (ADR-005, Phase 4)
|
|
3
|
+
*
|
|
4
|
+
* Runs a single story through the pipeline.
|
|
5
|
+
* Extracted from sequential-executor.ts to slim it below 120 lines.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getSafeLogger } from "../logger";
|
|
9
|
+
import type { StoryMetrics } from "../metrics";
|
|
10
|
+
import { runPipeline } from "../pipeline/runner";
|
|
11
|
+
import type { PipelineRunResult } from "../pipeline/runner";
|
|
12
|
+
import { defaultPipeline } from "../pipeline/stages";
|
|
13
|
+
import type { PipelineContext } from "../pipeline/types";
|
|
14
|
+
import type { PRD } from "../prd/types";
|
|
15
|
+
import { captureGitRef } from "../utils/git";
|
|
16
|
+
import { handleDryRun } from "./dry-run";
|
|
17
|
+
import type { SequentialExecutionContext } from "./executor-types";
|
|
18
|
+
import { handlePipelineFailure, handlePipelineSuccess } from "./pipeline-result-handler";
|
|
19
|
+
import type { StorySelection } from "./story-selector";
|
|
20
|
+
|
|
21
|
+
export interface IterationResult {
|
|
22
|
+
prd: PRD;
|
|
23
|
+
storiesCompletedDelta: number;
|
|
24
|
+
costDelta: number;
|
|
25
|
+
prdDirty: boolean;
|
|
26
|
+
finalAction?: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runIteration(
|
|
31
|
+
ctx: SequentialExecutionContext,
|
|
32
|
+
prd: PRD,
|
|
33
|
+
selection: StorySelection,
|
|
34
|
+
iterations: number,
|
|
35
|
+
totalCost: number,
|
|
36
|
+
allStoryMetrics: StoryMetrics[],
|
|
37
|
+
): Promise<IterationResult> {
|
|
38
|
+
const logger = getSafeLogger();
|
|
39
|
+
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
40
|
+
|
|
41
|
+
if (ctx.dryRun) {
|
|
42
|
+
const dryRunResult = await handleDryRun({
|
|
43
|
+
prd,
|
|
44
|
+
prdPath: ctx.prdPath,
|
|
45
|
+
storiesToExecute,
|
|
46
|
+
routing,
|
|
47
|
+
statusWriter: ctx.statusWriter,
|
|
48
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
49
|
+
runId: ctx.runId,
|
|
50
|
+
totalCost,
|
|
51
|
+
iterations,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
prd,
|
|
55
|
+
storiesCompletedDelta: dryRunResult.storiesCompletedDelta,
|
|
56
|
+
costDelta: 0,
|
|
57
|
+
prdDirty: dryRunResult.prdDirty,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
62
|
+
const pipelineContext: PipelineContext = {
|
|
63
|
+
config: ctx.config,
|
|
64
|
+
prd,
|
|
65
|
+
story,
|
|
66
|
+
stories: storiesToExecute,
|
|
67
|
+
routing,
|
|
68
|
+
workdir: ctx.workdir,
|
|
69
|
+
featureDir: ctx.featureDir,
|
|
70
|
+
hooks: ctx.hooks,
|
|
71
|
+
plugins: ctx.pluginRegistry,
|
|
72
|
+
storyStartTime: new Date().toISOString(),
|
|
73
|
+
storyGitRef: storyGitRef ?? undefined,
|
|
74
|
+
interaction: ctx.interactionChain ?? undefined,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
ctx.statusWriter.setPrd(prd);
|
|
78
|
+
ctx.statusWriter.setCurrentStory({
|
|
79
|
+
storyId: story.id,
|
|
80
|
+
title: story.title,
|
|
81
|
+
complexity: routing.complexity,
|
|
82
|
+
tddStrategy: routing.testStrategy,
|
|
83
|
+
model: routing.modelTier,
|
|
84
|
+
attempt: (story.attempts ?? 0) + 1,
|
|
85
|
+
phase: "routing",
|
|
86
|
+
});
|
|
87
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
88
|
+
|
|
89
|
+
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
90
|
+
const currentPrd = pipelineResult.context.prd;
|
|
91
|
+
|
|
92
|
+
const handlerCtx = {
|
|
93
|
+
config: ctx.config,
|
|
94
|
+
prd: currentPrd,
|
|
95
|
+
prdPath: ctx.prdPath,
|
|
96
|
+
workdir: ctx.workdir,
|
|
97
|
+
featureDir: ctx.featureDir,
|
|
98
|
+
hooks: ctx.hooks,
|
|
99
|
+
feature: ctx.feature,
|
|
100
|
+
totalCost,
|
|
101
|
+
startTime: ctx.startTime,
|
|
102
|
+
runId: ctx.runId,
|
|
103
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
104
|
+
story,
|
|
105
|
+
storiesToExecute,
|
|
106
|
+
routing: pipelineResult.context.routing ?? routing,
|
|
107
|
+
isBatchExecution,
|
|
108
|
+
allStoryMetrics,
|
|
109
|
+
storyGitRef,
|
|
110
|
+
interactionChain: ctx.interactionChain,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (pipelineResult.success) {
|
|
114
|
+
const r = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
115
|
+
return {
|
|
116
|
+
prd: r.prd,
|
|
117
|
+
storiesCompletedDelta: r.storiesCompletedDelta,
|
|
118
|
+
costDelta: r.costDelta,
|
|
119
|
+
prdDirty: r.prdDirty,
|
|
120
|
+
finalAction: pipelineResult.finalAction,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
124
|
+
return {
|
|
125
|
+
prd: r.prd,
|
|
126
|
+
storiesCompletedDelta: 0,
|
|
127
|
+
costDelta: 0,
|
|
128
|
+
prdDirty: r.prdDirty,
|
|
129
|
+
finalAction: pipelineResult.finalAction,
|
|
130
|
+
reason: pipelineResult.reason,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { runAcceptanceLoop, type AcceptanceLoopContext, type AcceptanceLoopResult } from "./acceptance-loop";
|
|
6
|
-
export { emitStoryComplete, type StoryCompleteEvent } from "./story-hooks";
|
|
7
6
|
export { outputRunHeader, outputRunFooter, type RunHeaderOptions, type RunFooterOptions } from "./headless-formatter";
|
|
8
7
|
export { handleParallelCompletion, type ParallelCompletionOptions } from "./parallel-lifecycle";
|
|
9
8
|
export { handleRunCompletion, type RunCompletionOptions, type RunCompletionResult } from "./run-completion";
|
|
@@ -14,16 +14,16 @@ import type { PRD, UserStory } from "../../prd";
|
|
|
14
14
|
import { countStories } from "../../prd";
|
|
15
15
|
import { hasCommitsForStory } from "../../utils/git";
|
|
16
16
|
import { parseBunTestOutput } from "../../verification";
|
|
17
|
+
import { runRectificationLoop } from "../../verification/rectification-loop";
|
|
18
|
+
import { fullSuite } from "../../verification/runners";
|
|
17
19
|
import { reverseMapTestToSource } from "../../verification/smart-runner";
|
|
18
|
-
import { runRectificationLoop } from "../post-verify-rectification";
|
|
19
|
-
import { runVerification } from "../verification";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Injectable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
|
|
23
23
|
* @internal - test use only.
|
|
24
24
|
*/
|
|
25
25
|
export const _regressionDeps = {
|
|
26
|
-
runVerification,
|
|
26
|
+
runVerification: fullSuite,
|
|
27
27
|
runRectificationLoop,
|
|
28
28
|
parseBunTestOutput,
|
|
29
29
|
reverseMapTestToSource,
|
|
@@ -133,7 +133,7 @@ export async function runDeferredRegression(options: DeferredRegressionOptions):
|
|
|
133
133
|
|
|
134
134
|
// Step 1: Run full test suite
|
|
135
135
|
const fullSuiteResult = await _regressionDeps.runVerification({
|
|
136
|
-
|
|
136
|
+
workdir: workdir,
|
|
137
137
|
command: testCommand,
|
|
138
138
|
timeoutSeconds,
|
|
139
139
|
forceExit: config.quality.forceExit,
|
|
@@ -268,7 +268,7 @@ export async function runDeferredRegression(options: DeferredRegressionOptions):
|
|
|
268
268
|
// Step 4: Re-run full suite to confirm
|
|
269
269
|
logger?.info("regression", "Re-running full suite after rectification");
|
|
270
270
|
const retryResult = await _regressionDeps.runVerification({
|
|
271
|
-
|
|
271
|
+
workdir: workdir,
|
|
272
272
|
command: testCommand,
|
|
273
273
|
timeoutSeconds,
|
|
274
274
|
forceExit: config.quality.forceExit,
|
|
@@ -1,31 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pipeline Result Handlers
|
|
2
|
+
* Pipeline Result Handlers (ADR-005, Phase 4)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Handles pipeline success, failure outcomes after story execution.
|
|
5
|
+
* Dry-run handling: see execution/dry-run.ts
|
|
6
|
+
* applyCachedRouting: removed (P4-001 — pipeline routing stage is sole source)
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type { NaxConfig } from "../config";
|
|
9
|
-
import {
|
|
10
|
+
import type { LoadedHooksConfig } from "../hooks";
|
|
10
11
|
import type { InteractionChain } from "../interaction/chain";
|
|
11
|
-
import { executeTrigger, isTriggerEnabled } from "../interaction/triggers";
|
|
12
12
|
import { getSafeLogger } from "../logger";
|
|
13
13
|
import type { StoryMetrics } from "../metrics";
|
|
14
|
+
import { pipelineEventBus } from "../pipeline/event-bus";
|
|
14
15
|
import type { PipelineRunResult } from "../pipeline/runner";
|
|
15
16
|
import type { PluginRegistry } from "../plugins";
|
|
16
17
|
import { countStories, markStoryFailed, markStoryPaused, savePRD } from "../prd";
|
|
17
18
|
import type { PRD, UserStory } from "../prd/types";
|
|
18
19
|
import type { routeTask } from "../routing";
|
|
19
20
|
import { handleTierEscalation } from "./escalation";
|
|
20
|
-
import { hookCtx } from "./helpers";
|
|
21
|
-
import { emitStoryComplete } from "./lifecycle/story-hooks";
|
|
22
|
-
import { runPostAgentVerification } from "./post-verify";
|
|
23
21
|
import { appendProgress } from "./progress";
|
|
24
|
-
import type { StatusWriter } from "./status-writer";
|
|
25
22
|
|
|
26
|
-
/** Context needed by pipeline result handlers */
|
|
27
23
|
export interface PipelineHandlerContext {
|
|
28
|
-
config:
|
|
24
|
+
config: NaxConfig;
|
|
29
25
|
prd: PRD;
|
|
30
26
|
prdPath: string;
|
|
31
27
|
workdir: string;
|
|
@@ -41,7 +37,6 @@ export interface PipelineHandlerContext {
|
|
|
41
37
|
routing: ReturnType<typeof routeTask>;
|
|
42
38
|
isBatchExecution: boolean;
|
|
43
39
|
allStoryMetrics: StoryMetrics[];
|
|
44
|
-
timeoutRetryCountMap: Map<string, number>;
|
|
45
40
|
storyGitRef: string | null | undefined;
|
|
46
41
|
interactionChain?: InteractionChain | null;
|
|
47
42
|
}
|
|
@@ -53,68 +48,40 @@ export interface PipelineSuccessResult {
|
|
|
53
48
|
prdDirty: boolean;
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
/**
|
|
57
|
-
* Handle a successful pipeline execution:
|
|
58
|
-
* - Post-agent verification
|
|
59
|
-
* - Emit story completion events
|
|
60
|
-
* - Log progress
|
|
61
|
-
*/
|
|
62
51
|
export async function handlePipelineSuccess(
|
|
63
52
|
ctx: PipelineHandlerContext,
|
|
64
53
|
pipelineResult: PipelineRunResult,
|
|
65
54
|
): Promise<PipelineSuccessResult> {
|
|
66
55
|
const logger = getSafeLogger();
|
|
67
56
|
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
68
|
-
|
|
57
|
+
const prd = ctx.prd;
|
|
69
58
|
|
|
70
|
-
// Collect story metrics
|
|
71
59
|
if (pipelineResult.context.storyMetrics) {
|
|
72
60
|
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
73
61
|
}
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
storiesToExecute: ctx.storiesToExecute,
|
|
84
|
-
allStoryMetrics: ctx.allStoryMetrics,
|
|
85
|
-
timeoutRetryCountMap: ctx.timeoutRetryCountMap,
|
|
86
|
-
});
|
|
87
|
-
const verificationPassed = verifyResult.passed;
|
|
88
|
-
prd = verifyResult.prd;
|
|
89
|
-
|
|
90
|
-
let storiesCompletedDelta = 0;
|
|
91
|
-
if (verificationPassed) {
|
|
92
|
-
storiesCompletedDelta = ctx.storiesToExecute.length;
|
|
93
|
-
|
|
94
|
-
const reporters = ctx.pluginRegistry.getReporters();
|
|
95
|
-
for (const completedStory of ctx.storiesToExecute) {
|
|
96
|
-
logger?.info("story.complete", "Story completed successfully", {
|
|
97
|
-
storyId: completedStory.id,
|
|
98
|
-
storyTitle: completedStory.title,
|
|
99
|
-
totalCost: ctx.totalCost + costDelta,
|
|
100
|
-
durationMs: Date.now() - ctx.startTime,
|
|
101
|
-
});
|
|
63
|
+
const storiesCompletedDelta = ctx.storiesToExecute.length;
|
|
64
|
+
for (const completedStory of ctx.storiesToExecute) {
|
|
65
|
+
logger?.info("story.complete", "Story completed successfully", {
|
|
66
|
+
storyId: completedStory.id,
|
|
67
|
+
storyTitle: completedStory.title,
|
|
68
|
+
totalCost: ctx.totalCost + costDelta,
|
|
69
|
+
durationMs: Date.now() - ctx.startTime,
|
|
70
|
+
});
|
|
102
71
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
72
|
+
pipelineEventBus.emit({
|
|
73
|
+
type: "story:completed",
|
|
74
|
+
storyId: completedStory.id,
|
|
75
|
+
story: completedStory,
|
|
76
|
+
passed: true,
|
|
77
|
+
durationMs: Date.now() - ctx.startTime,
|
|
78
|
+
cost: costDelta,
|
|
79
|
+
modelTier: ctx.routing.modelTier,
|
|
80
|
+
testStrategy: ctx.routing.testStrategy,
|
|
81
|
+
});
|
|
113
82
|
}
|
|
114
83
|
|
|
115
|
-
// Display progress
|
|
116
84
|
const updatedCounts = countStories(prd);
|
|
117
|
-
const elapsedMs = Date.now() - ctx.startTime;
|
|
118
85
|
logger?.info("progress", "Progress update", {
|
|
119
86
|
totalStories: updatedCounts.total,
|
|
120
87
|
passedStories: updatedCounts.passed,
|
|
@@ -122,7 +89,7 @@ export async function handlePipelineSuccess(
|
|
|
122
89
|
pendingStories: updatedCounts.pending,
|
|
123
90
|
totalCost: ctx.totalCost + costDelta,
|
|
124
91
|
costLimit: ctx.config.execution.costLimit,
|
|
125
|
-
elapsedMs,
|
|
92
|
+
elapsedMs: Date.now() - ctx.startTime,
|
|
126
93
|
});
|
|
127
94
|
|
|
128
95
|
return { storiesCompletedDelta, costDelta, prd, prdDirty: true };
|
|
@@ -133,16 +100,11 @@ export interface PipelineFailureResult {
|
|
|
133
100
|
prdDirty: boolean;
|
|
134
101
|
}
|
|
135
102
|
|
|
136
|
-
/**
|
|
137
|
-
* Handle a failed pipeline execution based on finalAction:
|
|
138
|
-
* pause, skip, fail, or escalate.
|
|
139
|
-
*/
|
|
140
103
|
export async function handlePipelineFailure(
|
|
141
104
|
ctx: PipelineHandlerContext,
|
|
142
105
|
pipelineResult: PipelineRunResult,
|
|
143
106
|
): Promise<PipelineFailureResult> {
|
|
144
107
|
const logger = getSafeLogger();
|
|
145
|
-
const reporters = ctx.pluginRegistry.getReporters();
|
|
146
108
|
let prd = ctx.prd;
|
|
147
109
|
let prdDirty = false;
|
|
148
110
|
|
|
@@ -151,114 +113,49 @@ export async function handlePipelineFailure(
|
|
|
151
113
|
markStoryPaused(prd, ctx.story.id);
|
|
152
114
|
await savePRD(prd, ctx.prdPath);
|
|
153
115
|
prdDirty = true;
|
|
154
|
-
|
|
155
|
-
|
|
116
|
+
logger?.warn("pipeline", "Story paused", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
117
|
+
pipelineEventBus.emit({
|
|
118
|
+
type: "story:paused",
|
|
156
119
|
storyId: ctx.story.id,
|
|
157
|
-
reason: pipelineResult.reason,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await fireHook(
|
|
161
|
-
ctx.hooks,
|
|
162
|
-
"on-pause",
|
|
163
|
-
hookCtx(ctx.feature, {
|
|
164
|
-
storyId: ctx.story.id,
|
|
165
|
-
reason: pipelineResult.reason || "Pipeline paused",
|
|
166
|
-
cost: ctx.totalCost,
|
|
167
|
-
}),
|
|
168
|
-
ctx.workdir,
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
await emitStoryComplete(reporters, {
|
|
172
|
-
runId: ctx.runId,
|
|
173
|
-
storyId: ctx.story.id,
|
|
174
|
-
status: "paused",
|
|
175
|
-
durationMs: Date.now() - ctx.startTime,
|
|
176
|
-
cost: pipelineResult.context.agentResult?.estimatedCost || 0,
|
|
177
|
-
tier: ctx.routing.modelTier,
|
|
178
|
-
testStrategy: ctx.routing.testStrategy,
|
|
120
|
+
reason: pipelineResult.reason || "Pipeline paused",
|
|
121
|
+
cost: ctx.totalCost,
|
|
179
122
|
});
|
|
180
123
|
break;
|
|
181
124
|
|
|
182
125
|
case "skip":
|
|
183
|
-
logger?.warn("pipeline", "Story skipped", {
|
|
184
|
-
storyId: ctx.story.id,
|
|
185
|
-
reason: pipelineResult.reason,
|
|
186
|
-
});
|
|
126
|
+
logger?.warn("pipeline", "Story skipped", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
187
127
|
prdDirty = true;
|
|
188
|
-
|
|
189
|
-
await emitStoryComplete(reporters, {
|
|
190
|
-
runId: ctx.runId,
|
|
191
|
-
storyId: ctx.story.id,
|
|
192
|
-
status: "skipped",
|
|
193
|
-
durationMs: Date.now() - ctx.startTime,
|
|
194
|
-
cost: 0,
|
|
195
|
-
tier: ctx.routing.modelTier,
|
|
196
|
-
testStrategy: ctx.routing.testStrategy,
|
|
197
|
-
});
|
|
198
128
|
break;
|
|
199
129
|
|
|
200
130
|
case "fail":
|
|
201
131
|
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory);
|
|
202
132
|
await savePRD(prd, ctx.prdPath);
|
|
203
133
|
prdDirty = true;
|
|
204
|
-
|
|
205
|
-
logger?.error("pipeline", "Story failed", {
|
|
206
|
-
storyId: ctx.story.id,
|
|
207
|
-
reason: pipelineResult.reason,
|
|
208
|
-
});
|
|
134
|
+
logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
209
135
|
|
|
210
136
|
if (ctx.featureDir) {
|
|
211
137
|
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} — ${pipelineResult.reason}`);
|
|
212
138
|
}
|
|
213
139
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
ctx.interactionChain &&
|
|
217
|
-
ctx.story.attempts !== undefined &&
|
|
218
|
-
ctx.story.attempts >= ctx.config.execution.rectification.maxRetries &&
|
|
219
|
-
isTriggerEnabled("human-review", ctx.config)
|
|
220
|
-
) {
|
|
221
|
-
try {
|
|
222
|
-
await executeTrigger(
|
|
223
|
-
"human-review",
|
|
224
|
-
{
|
|
225
|
-
featureName: ctx.feature,
|
|
226
|
-
storyId: ctx.story.id,
|
|
227
|
-
iteration: ctx.story.attempts,
|
|
228
|
-
reason: pipelineResult.reason || "Max retries exceeded",
|
|
229
|
-
},
|
|
230
|
-
ctx.config,
|
|
231
|
-
ctx.interactionChain,
|
|
232
|
-
);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
logger?.warn("pipeline", "human-review trigger failed", {
|
|
235
|
-
storyId: ctx.story.id,
|
|
236
|
-
error: String(err),
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
await fireHook(
|
|
242
|
-
ctx.hooks,
|
|
243
|
-
"on-story-fail",
|
|
244
|
-
hookCtx(ctx.feature, {
|
|
245
|
-
storyId: ctx.story.id,
|
|
246
|
-
status: "failed",
|
|
247
|
-
reason: pipelineResult.reason || "Pipeline failed",
|
|
248
|
-
cost: ctx.totalCost,
|
|
249
|
-
}),
|
|
250
|
-
ctx.workdir,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
await emitStoryComplete(reporters, {
|
|
254
|
-
runId: ctx.runId,
|
|
140
|
+
pipelineEventBus.emit({
|
|
141
|
+
type: "story:failed",
|
|
255
142
|
storyId: ctx.story.id,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
143
|
+
story: ctx.story,
|
|
144
|
+
reason: pipelineResult.reason || "Pipeline failed",
|
|
145
|
+
countsTowardEscalation: true,
|
|
146
|
+
feature: ctx.feature,
|
|
147
|
+
attempts: ctx.story.attempts,
|
|
261
148
|
});
|
|
149
|
+
|
|
150
|
+
if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxRetries) {
|
|
151
|
+
pipelineEventBus.emit({
|
|
152
|
+
type: "human-review:requested",
|
|
153
|
+
storyId: ctx.story.id,
|
|
154
|
+
reason: pipelineResult.reason || "Max retries exceeded",
|
|
155
|
+
feature: ctx.feature,
|
|
156
|
+
attempts: ctx.story.attempts,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
262
159
|
break;
|
|
263
160
|
|
|
264
161
|
case "escalate": {
|
|
@@ -277,7 +174,6 @@ export async function handlePipelineFailure(
|
|
|
277
174
|
totalCost: ctx.totalCost,
|
|
278
175
|
workdir: ctx.workdir,
|
|
279
176
|
});
|
|
280
|
-
|
|
281
177
|
prd = escalationResult.prd;
|
|
282
178
|
prdDirty = escalationResult.prdDirty;
|
|
283
179
|
break;
|
|
@@ -286,102 +182,3 @@ export async function handlePipelineFailure(
|
|
|
286
182
|
|
|
287
183
|
return { prd, prdDirty };
|
|
288
184
|
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Apply cached routing overrides from story.routing to a fresh routing decision.
|
|
292
|
-
*/
|
|
293
|
-
export function applyCachedRouting(
|
|
294
|
-
routing: ReturnType<typeof routeTask>,
|
|
295
|
-
story: UserStory,
|
|
296
|
-
config: NaxConfig,
|
|
297
|
-
): ReturnType<typeof routeTask> {
|
|
298
|
-
if (!story.routing) return routing;
|
|
299
|
-
const overrides: Partial<ReturnType<typeof routeTask>> = {};
|
|
300
|
-
if (story.routing.complexity) {
|
|
301
|
-
overrides.complexity = story.routing.complexity;
|
|
302
|
-
}
|
|
303
|
-
// BUG-013 fix: Use story.routing.modelTier directly if present (set by escalation).
|
|
304
|
-
// Only derive from complexity if modelTier is not explicitly set.
|
|
305
|
-
if (story.routing.modelTier) {
|
|
306
|
-
overrides.modelTier = story.routing.modelTier as ReturnType<typeof routeTask>["modelTier"];
|
|
307
|
-
} else if (story.routing.complexity) {
|
|
308
|
-
const tierFromComplexity = config.autoMode.complexityRouting[story.routing.complexity] ?? "balanced";
|
|
309
|
-
overrides.modelTier = tierFromComplexity as ReturnType<typeof routeTask>["modelTier"];
|
|
310
|
-
}
|
|
311
|
-
if (story.routing.testStrategy) {
|
|
312
|
-
overrides.testStrategy = story.routing.testStrategy;
|
|
313
|
-
}
|
|
314
|
-
return { ...routing, ...overrides };
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/** Context for dry-run iteration handling */
|
|
318
|
-
export interface DryRunContext {
|
|
319
|
-
prd: PRD;
|
|
320
|
-
prdPath: string;
|
|
321
|
-
storiesToExecute: UserStory[];
|
|
322
|
-
routing: ReturnType<typeof routeTask>;
|
|
323
|
-
statusWriter: StatusWriter;
|
|
324
|
-
pluginRegistry: PluginRegistry;
|
|
325
|
-
runId: string;
|
|
326
|
-
totalCost: number;
|
|
327
|
-
iterations: number;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export interface DryRunResult {
|
|
331
|
-
storiesCompletedDelta: number;
|
|
332
|
-
prdDirty: boolean;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/** Handle dry-run iteration: log what would happen, mark stories passed. */
|
|
336
|
-
export async function handleDryRun(ctx: DryRunContext): Promise<DryRunResult> {
|
|
337
|
-
const logger = getSafeLogger();
|
|
338
|
-
|
|
339
|
-
ctx.statusWriter.setPrd(ctx.prd);
|
|
340
|
-
ctx.statusWriter.setCurrentStory({
|
|
341
|
-
storyId: ctx.storiesToExecute[0].id,
|
|
342
|
-
title: ctx.storiesToExecute[0].title,
|
|
343
|
-
complexity: ctx.routing.complexity,
|
|
344
|
-
tddStrategy: ctx.routing.testStrategy,
|
|
345
|
-
model: ctx.routing.modelTier,
|
|
346
|
-
attempt: (ctx.storiesToExecute[0].attempts ?? 0) + 1,
|
|
347
|
-
phase: "routing",
|
|
348
|
-
});
|
|
349
|
-
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
350
|
-
|
|
351
|
-
for (const s of ctx.storiesToExecute) {
|
|
352
|
-
logger?.info("execution", "[DRY RUN] Would execute agent here", {
|
|
353
|
-
storyId: s.id,
|
|
354
|
-
storyTitle: s.title,
|
|
355
|
-
modelTier: ctx.routing.modelTier,
|
|
356
|
-
complexity: ctx.routing.complexity,
|
|
357
|
-
testStrategy: ctx.routing.testStrategy,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Mark stories as passed so the loop progresses
|
|
362
|
-
for (const s of ctx.storiesToExecute) {
|
|
363
|
-
const { markStoryPassed } = await import("../prd");
|
|
364
|
-
markStoryPassed(ctx.prd, s.id);
|
|
365
|
-
}
|
|
366
|
-
await savePRD(ctx.prd, ctx.prdPath);
|
|
367
|
-
|
|
368
|
-
// Emit onStoryComplete events for dry-run
|
|
369
|
-
const reporters = ctx.pluginRegistry.getReporters();
|
|
370
|
-
for (const s of ctx.storiesToExecute) {
|
|
371
|
-
await emitStoryComplete(reporters, {
|
|
372
|
-
runId: ctx.runId,
|
|
373
|
-
storyId: s.id,
|
|
374
|
-
status: "completed",
|
|
375
|
-
durationMs: 0,
|
|
376
|
-
cost: 0,
|
|
377
|
-
tier: ctx.routing.modelTier,
|
|
378
|
-
testStrategy: ctx.routing.testStrategy,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
ctx.statusWriter.setPrd(ctx.prd);
|
|
383
|
-
ctx.statusWriter.setCurrentStory(null);
|
|
384
|
-
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
385
|
-
|
|
386
|
-
return { storiesCompletedDelta: ctx.storiesToExecute.length, prdDirty: true };
|
|
387
|
-
}
|