@nathapp/nax 0.38.0 → 0.38.2
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/dist/nax.js +3294 -2907
- package/package.json +2 -2
- package/src/agents/claude-complete.ts +72 -0
- package/src/agents/claude-execution.ts +189 -0
- package/src/agents/claude-interactive.ts +77 -0
- package/src/agents/claude-plan.ts +23 -8
- package/src/agents/claude.ts +64 -349
- package/src/analyze/classifier.ts +2 -1
- package/src/cli/config-descriptions.ts +206 -0
- package/src/cli/config-diff.ts +103 -0
- package/src/cli/config-display.ts +285 -0
- package/src/cli/config-get.ts +55 -0
- package/src/cli/config.ts +7 -618
- package/src/cli/plugins.ts +15 -4
- package/src/cli/prompts-export.ts +58 -0
- package/src/cli/prompts-init.ts +200 -0
- package/src/cli/prompts-main.ts +237 -0
- package/src/cli/prompts-tdd.ts +78 -0
- package/src/cli/prompts.ts +10 -541
- package/src/commands/logs-formatter.ts +201 -0
- package/src/commands/logs-reader.ts +171 -0
- package/src/commands/logs.ts +11 -362
- package/src/config/loader.ts +4 -15
- package/src/config/runtime-types.ts +451 -0
- package/src/config/schema-types.ts +53 -0
- package/src/config/schemas.ts +2 -0
- package/src/config/types.ts +49 -486
- package/src/context/auto-detect.ts +2 -1
- package/src/context/builder.ts +3 -2
- package/src/execution/crash-heartbeat.ts +77 -0
- package/src/execution/crash-recovery.ts +23 -365
- package/src/execution/crash-signals.ts +149 -0
- package/src/execution/crash-writer.ts +154 -0
- package/src/execution/lifecycle/run-setup.ts +7 -1
- package/src/execution/parallel-coordinator.ts +278 -0
- package/src/execution/parallel-executor-rectification-pass.ts +117 -0
- package/src/execution/parallel-executor-rectify.ts +135 -0
- package/src/execution/parallel-executor.ts +19 -211
- package/src/execution/parallel-worker.ts +148 -0
- package/src/execution/parallel.ts +5 -404
- package/src/execution/pid-registry.ts +3 -8
- package/src/execution/runner-completion.ts +160 -0
- package/src/execution/runner-execution.ts +221 -0
- package/src/execution/runner-setup.ts +82 -0
- package/src/execution/runner.ts +53 -202
- package/src/execution/timeout-handler.ts +100 -0
- package/src/hooks/runner.ts +11 -21
- package/src/metrics/tracker.ts +7 -30
- package/src/pipeline/runner.ts +2 -1
- package/src/pipeline/stages/completion.ts +0 -1
- package/src/pipeline/stages/context.ts +2 -1
- package/src/plugins/extensions.ts +225 -0
- package/src/plugins/loader.ts +40 -4
- package/src/plugins/types.ts +18 -221
- package/src/prd/index.ts +2 -1
- package/src/prd/validate.ts +41 -0
- package/src/precheck/checks-blockers.ts +15 -419
- package/src/precheck/checks-cli.ts +68 -0
- package/src/precheck/checks-config.ts +102 -0
- package/src/precheck/checks-git.ts +87 -0
- package/src/precheck/checks-system.ts +163 -0
- package/src/review/orchestrator.ts +19 -6
- package/src/review/runner.ts +17 -5
- package/src/routing/chain.ts +2 -1
- package/src/routing/loader.ts +2 -5
- package/src/tdd/orchestrator.ts +2 -1
- package/src/tdd/verdict-reader.ts +266 -0
- package/src/tdd/verdict.ts +6 -271
- package/src/utils/errors.ts +12 -0
- package/src/utils/git.ts +12 -5
- package/src/utils/json-file.ts +72 -0
- package/src/verification/executor.ts +2 -1
- package/src/verification/smart-runner.ts +23 -3
- package/src/worktree/manager.ts +9 -3
- package/src/worktree/merge.ts +3 -2
|
@@ -19,139 +19,14 @@ import type { StoryMetrics } from "../metrics";
|
|
|
19
19
|
import type { PipelineEventEmitter } from "../pipeline/events";
|
|
20
20
|
import type { PluginRegistry } from "../plugins/registry";
|
|
21
21
|
import type { PRD } from "../prd";
|
|
22
|
-
import { countStories, isComplete
|
|
22
|
+
import { countStories, isComplete } from "../prd";
|
|
23
|
+
import { errorMessage } from "../utils/errors";
|
|
23
24
|
import { getAllReadyStories, hookCtx } from "./helpers";
|
|
24
25
|
import { executeParallel } from "./parallel";
|
|
26
|
+
import { type ParallelStoryMetrics, runRectificationPass } from "./parallel-executor-rectification-pass";
|
|
27
|
+
import { type ConflictedStoryInfo, rectifyConflictedStory } from "./parallel-executor-rectify";
|
|
25
28
|
import type { StatusWriter } from "./status-writer";
|
|
26
29
|
|
|
27
|
-
/** StoryMetrics extended with execution-path source */
|
|
28
|
-
export type ParallelStoryMetrics = StoryMetrics & {
|
|
29
|
-
source: "parallel" | "sequential" | "rectification";
|
|
30
|
-
rectifiedFromConflict?: boolean;
|
|
31
|
-
originalCost?: number;
|
|
32
|
-
rectificationCost?: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** A story that conflicted during the initial parallel merge pass */
|
|
36
|
-
export interface ConflictedStoryInfo {
|
|
37
|
-
storyId: string;
|
|
38
|
-
conflictFiles: string[];
|
|
39
|
-
originalCost: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Result from attempting to rectify a single conflicted story */
|
|
43
|
-
export type RectificationResult =
|
|
44
|
-
| { success: true; storyId: string; cost: number }
|
|
45
|
-
| {
|
|
46
|
-
success: false;
|
|
47
|
-
storyId: string;
|
|
48
|
-
cost: number;
|
|
49
|
-
finalConflict: boolean;
|
|
50
|
-
pipelineFailure?: boolean;
|
|
51
|
-
conflictFiles?: string[];
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/** Options passed to rectifyConflictedStory */
|
|
55
|
-
export interface RectifyConflictedStoryOptions extends ConflictedStoryInfo {
|
|
56
|
-
workdir: string;
|
|
57
|
-
config: NaxConfig;
|
|
58
|
-
hooks: LoadedHooksConfig;
|
|
59
|
-
pluginRegistry: PluginRegistry;
|
|
60
|
-
prd: PRD;
|
|
61
|
-
eventEmitter?: PipelineEventEmitter;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Actual implementation of rectifyConflictedStory.
|
|
66
|
-
*
|
|
67
|
-
* Steps:
|
|
68
|
-
* 1. Remove the old worktree
|
|
69
|
-
* 2. Create a fresh worktree from current HEAD (post-merge state)
|
|
70
|
-
* 3. Re-run the full story pipeline
|
|
71
|
-
* 4. Attempt merge on the updated base
|
|
72
|
-
* 5. Return success/finalConflict
|
|
73
|
-
*/
|
|
74
|
-
async function rectifyConflictedStory(options: RectifyConflictedStoryOptions): Promise<RectificationResult> {
|
|
75
|
-
const { storyId, workdir, config, hooks, pluginRegistry, prd, eventEmitter } = options;
|
|
76
|
-
const logger = getSafeLogger();
|
|
77
|
-
|
|
78
|
-
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
const { WorktreeManager } = await import("../worktree/manager");
|
|
82
|
-
const { MergeEngine } = await import("../worktree/merge");
|
|
83
|
-
const { runPipeline } = await import("../pipeline/runner");
|
|
84
|
-
const { defaultPipeline } = await import("../pipeline/stages");
|
|
85
|
-
const { routeTask } = await import("../routing");
|
|
86
|
-
|
|
87
|
-
const worktreeManager = new WorktreeManager();
|
|
88
|
-
const mergeEngine = new MergeEngine(worktreeManager);
|
|
89
|
-
|
|
90
|
-
// Step 1: Remove old worktree
|
|
91
|
-
try {
|
|
92
|
-
await worktreeManager.remove(workdir, storyId);
|
|
93
|
-
} catch {
|
|
94
|
-
// Ignore — worktree may have already been removed
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Step 2: Create fresh worktree from current HEAD
|
|
98
|
-
await worktreeManager.create(workdir, storyId);
|
|
99
|
-
const worktreePath = path.join(workdir, ".nax-wt", storyId);
|
|
100
|
-
|
|
101
|
-
// Step 3: Re-run the story pipeline
|
|
102
|
-
const story = prd.userStories.find((s) => s.id === storyId);
|
|
103
|
-
if (!story) {
|
|
104
|
-
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config);
|
|
108
|
-
|
|
109
|
-
const pipelineContext = {
|
|
110
|
-
config,
|
|
111
|
-
prd,
|
|
112
|
-
story,
|
|
113
|
-
stories: [story],
|
|
114
|
-
workdir: worktreePath,
|
|
115
|
-
featureDir: undefined,
|
|
116
|
-
hooks,
|
|
117
|
-
plugins: pluginRegistry,
|
|
118
|
-
storyStartTime: new Date().toISOString(),
|
|
119
|
-
routing: routing as import("../pipeline/types").RoutingResult,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, eventEmitter);
|
|
123
|
-
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
124
|
-
|
|
125
|
-
if (!pipelineResult.success) {
|
|
126
|
-
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
127
|
-
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Step 4: Attempt merge on updated base
|
|
131
|
-
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
132
|
-
const mergeResult = mergeResults[0];
|
|
133
|
-
|
|
134
|
-
if (!mergeResult || !mergeResult.success) {
|
|
135
|
-
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
136
|
-
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
137
|
-
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
141
|
-
storyId,
|
|
142
|
-
originalCost: options.originalCost,
|
|
143
|
-
rectificationCost: cost,
|
|
144
|
-
});
|
|
145
|
-
return { success: true, storyId, cost };
|
|
146
|
-
} catch (error) {
|
|
147
|
-
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
148
|
-
storyId,
|
|
149
|
-
error: error instanceof Error ? error.message : String(error),
|
|
150
|
-
});
|
|
151
|
-
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
30
|
/**
|
|
156
31
|
* Injectable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
|
|
157
32
|
* @internal - test use only.
|
|
@@ -201,86 +76,6 @@ export interface ParallelExecutorResult {
|
|
|
201
76
|
rectificationStats: RectificationStats;
|
|
202
77
|
}
|
|
203
78
|
|
|
204
|
-
/**
|
|
205
|
-
* Run the rectification pass: sequentially re-run each conflicted story on
|
|
206
|
-
* the updated base (which already includes all clean merges from the first pass).
|
|
207
|
-
*/
|
|
208
|
-
async function runRectificationPass(
|
|
209
|
-
conflictedStories: ConflictedStoryInfo[],
|
|
210
|
-
options: ParallelExecutorOptions,
|
|
211
|
-
prd: PRD,
|
|
212
|
-
): Promise<{
|
|
213
|
-
rectifiedCount: number;
|
|
214
|
-
stillConflictingCount: number;
|
|
215
|
-
additionalCost: number;
|
|
216
|
-
updatedPrd: PRD;
|
|
217
|
-
rectificationMetrics: ParallelStoryMetrics[];
|
|
218
|
-
}> {
|
|
219
|
-
const logger = getSafeLogger();
|
|
220
|
-
const { workdir, config, hooks, pluginRegistry, eventEmitter } = options;
|
|
221
|
-
const rectificationMetrics: ParallelStoryMetrics[] = [];
|
|
222
|
-
let rectifiedCount = 0;
|
|
223
|
-
let stillConflictingCount = 0;
|
|
224
|
-
let additionalCost = 0;
|
|
225
|
-
|
|
226
|
-
logger?.info("parallel", "Starting merge conflict rectification", {
|
|
227
|
-
stories: conflictedStories.map((s) => s.storyId),
|
|
228
|
-
totalConflicts: conflictedStories.length,
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// Sequential — each story sees all previously rectified stories in the base
|
|
232
|
-
for (const conflictInfo of conflictedStories) {
|
|
233
|
-
const result = await _parallelExecutorDeps.rectifyConflictedStory({
|
|
234
|
-
...conflictInfo,
|
|
235
|
-
workdir,
|
|
236
|
-
config,
|
|
237
|
-
hooks,
|
|
238
|
-
pluginRegistry,
|
|
239
|
-
prd,
|
|
240
|
-
eventEmitter,
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
additionalCost += result.cost;
|
|
244
|
-
|
|
245
|
-
if (result.success) {
|
|
246
|
-
markStoryPassed(prd, result.storyId);
|
|
247
|
-
rectifiedCount++;
|
|
248
|
-
|
|
249
|
-
rectificationMetrics.push({
|
|
250
|
-
storyId: result.storyId,
|
|
251
|
-
complexity: "unknown",
|
|
252
|
-
modelTier: "parallel",
|
|
253
|
-
modelUsed: "parallel",
|
|
254
|
-
attempts: 1,
|
|
255
|
-
finalTier: "parallel",
|
|
256
|
-
success: true,
|
|
257
|
-
cost: result.cost,
|
|
258
|
-
durationMs: 0,
|
|
259
|
-
firstPassSuccess: false,
|
|
260
|
-
startedAt: new Date().toISOString(),
|
|
261
|
-
completedAt: new Date().toISOString(),
|
|
262
|
-
source: "rectification" as const,
|
|
263
|
-
rectifiedFromConflict: true,
|
|
264
|
-
originalCost: conflictInfo.originalCost,
|
|
265
|
-
rectificationCost: result.cost,
|
|
266
|
-
});
|
|
267
|
-
} else {
|
|
268
|
-
const isFinalConflict = result.finalConflict === true;
|
|
269
|
-
if (isFinalConflict) {
|
|
270
|
-
stillConflictingCount++;
|
|
271
|
-
}
|
|
272
|
-
// pipelineFailure — not counted as structural conflict, story remains failed
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
logger?.info("parallel", "Rectification complete", {
|
|
277
|
-
rectified: rectifiedCount,
|
|
278
|
-
stillConflicting: stillConflictingCount,
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
return { rectifiedCount, stillConflictingCount, additionalCost, updatedPrd: prd, rectificationMetrics };
|
|
282
|
-
}
|
|
283
|
-
|
|
284
79
|
/**
|
|
285
80
|
* Execute parallel stories if --parallel is set
|
|
286
81
|
*/
|
|
@@ -415,7 +210,7 @@ export async function runParallelExecution(
|
|
|
415
210
|
});
|
|
416
211
|
} catch (error) {
|
|
417
212
|
logger?.error("parallel", "Parallel execution failed", {
|
|
418
|
-
error:
|
|
213
|
+
error: errorMessage(error),
|
|
419
214
|
});
|
|
420
215
|
|
|
421
216
|
// Clear parallel status on error
|
|
@@ -430,7 +225,12 @@ export async function runParallelExecution(
|
|
|
430
225
|
let rectificationStats: RectificationStats = { rectified: 0, stillConflicting: 0 };
|
|
431
226
|
|
|
432
227
|
if (conflictedStories.length > 0) {
|
|
433
|
-
const rectResult = await runRectificationPass(
|
|
228
|
+
const rectResult = await runRectificationPass(
|
|
229
|
+
conflictedStories,
|
|
230
|
+
options,
|
|
231
|
+
prd,
|
|
232
|
+
_parallelExecutorDeps.rectifyConflictedStory,
|
|
233
|
+
);
|
|
434
234
|
prd = rectResult.updatedPrd;
|
|
435
235
|
storiesCompleted += rectResult.rectifiedCount;
|
|
436
236
|
totalCost += rectResult.additionalCost;
|
|
@@ -517,3 +317,11 @@ export async function runParallelExecution(
|
|
|
517
317
|
|
|
518
318
|
return { prd, totalCost, storiesCompleted, completed: false, storyMetrics: batchStoryMetrics, rectificationStats };
|
|
519
319
|
}
|
|
320
|
+
|
|
321
|
+
// Re-export types for backward compatibility
|
|
322
|
+
export type {
|
|
323
|
+
ConflictedStoryInfo,
|
|
324
|
+
RectificationResult,
|
|
325
|
+
RectifyConflictedStoryOptions,
|
|
326
|
+
} from "./parallel-executor-rectify";
|
|
327
|
+
export type { ParallelStoryMetrics } from "./parallel-executor-rectification-pass";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel worker — Story execution in worktrees
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import type { NaxConfig } from "../config";
|
|
7
|
+
import { getSafeLogger } from "../logger";
|
|
8
|
+
import type { PipelineEventEmitter } from "../pipeline/events";
|
|
9
|
+
import { runPipeline } from "../pipeline/runner";
|
|
10
|
+
import { defaultPipeline } from "../pipeline/stages";
|
|
11
|
+
import type { PipelineContext, RoutingResult } from "../pipeline/types";
|
|
12
|
+
import type { UserStory } from "../prd";
|
|
13
|
+
import { routeTask } from "../routing";
|
|
14
|
+
import { errorMessage } from "../utils/errors";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Execute a single story in its worktree
|
|
18
|
+
*/
|
|
19
|
+
export async function executeStoryInWorktree(
|
|
20
|
+
story: UserStory,
|
|
21
|
+
worktreePath: string,
|
|
22
|
+
context: Omit<PipelineContext, "story" | "stories" | "workdir" | "routing">,
|
|
23
|
+
routing: RoutingResult,
|
|
24
|
+
eventEmitter?: PipelineEventEmitter,
|
|
25
|
+
): Promise<{ success: boolean; cost: number; error?: string }> {
|
|
26
|
+
const logger = getSafeLogger();
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const pipelineContext: PipelineContext = {
|
|
30
|
+
...context,
|
|
31
|
+
story,
|
|
32
|
+
stories: [story],
|
|
33
|
+
workdir: worktreePath,
|
|
34
|
+
routing,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
logger?.debug("parallel", "Executing story in worktree", {
|
|
38
|
+
storyId: story.id,
|
|
39
|
+
worktreePath,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const result = await runPipeline(defaultPipeline, pipelineContext, eventEmitter);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
success: result.success,
|
|
46
|
+
cost: result.context.agentResult?.estimatedCost || 0,
|
|
47
|
+
error: result.success ? undefined : result.reason,
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
cost: 0,
|
|
53
|
+
error: errorMessage(error),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Batch execution result
|
|
60
|
+
*/
|
|
61
|
+
export interface ParallelBatchResult {
|
|
62
|
+
/** Stories that passed the TDD pipeline (pre-merge) */
|
|
63
|
+
pipelinePassed: UserStory[];
|
|
64
|
+
/** Stories that were actually merged to the base branch */
|
|
65
|
+
merged: UserStory[];
|
|
66
|
+
/** Stories that failed the pipeline */
|
|
67
|
+
failed: Array<{ story: UserStory; error: string }>;
|
|
68
|
+
/** Total cost accumulated */
|
|
69
|
+
totalCost: number;
|
|
70
|
+
/** Stories with merge conflicts (includes per-story original cost for rectification) */
|
|
71
|
+
mergeConflicts: Array<{ storyId: string; conflictFiles: string[]; originalCost: number }>;
|
|
72
|
+
/** Per-story execution costs for successful stories */
|
|
73
|
+
storyCosts: Map<string, number>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Execute a batch of independent stories in parallel (worktree setup must be done separately)
|
|
78
|
+
*/
|
|
79
|
+
export async function executeParallelBatch(
|
|
80
|
+
stories: UserStory[],
|
|
81
|
+
projectRoot: string,
|
|
82
|
+
config: NaxConfig,
|
|
83
|
+
context: Omit<PipelineContext, "story" | "stories" | "workdir" | "routing">,
|
|
84
|
+
worktreePaths: Map<string, string>,
|
|
85
|
+
maxConcurrency: number,
|
|
86
|
+
eventEmitter?: PipelineEventEmitter,
|
|
87
|
+
): Promise<ParallelBatchResult> {
|
|
88
|
+
const logger = getSafeLogger();
|
|
89
|
+
const results: ParallelBatchResult = {
|
|
90
|
+
pipelinePassed: [],
|
|
91
|
+
merged: [],
|
|
92
|
+
failed: [],
|
|
93
|
+
totalCost: 0,
|
|
94
|
+
mergeConflicts: [],
|
|
95
|
+
storyCosts: new Map(),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Execute stories in parallel with concurrency limit
|
|
99
|
+
const executing = new Set<Promise<void>>();
|
|
100
|
+
|
|
101
|
+
for (const story of stories) {
|
|
102
|
+
const worktreePath = worktreePaths.get(story.id);
|
|
103
|
+
if (!worktreePath) {
|
|
104
|
+
results.failed.push({
|
|
105
|
+
story,
|
|
106
|
+
error: "Worktree not created",
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config);
|
|
112
|
+
|
|
113
|
+
const executePromise = executeStoryInWorktree(story, worktreePath, context, routing as RoutingResult, eventEmitter)
|
|
114
|
+
.then((result) => {
|
|
115
|
+
results.totalCost += result.cost;
|
|
116
|
+
results.storyCosts.set(story.id, result.cost);
|
|
117
|
+
|
|
118
|
+
if (result.success) {
|
|
119
|
+
results.pipelinePassed.push(story);
|
|
120
|
+
logger?.info("parallel", "Story execution succeeded", {
|
|
121
|
+
storyId: story.id,
|
|
122
|
+
cost: result.cost,
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
results.failed.push({ story, error: result.error || "Unknown error" });
|
|
126
|
+
logger?.error("parallel", "Story execution failed", {
|
|
127
|
+
storyId: story.id,
|
|
128
|
+
error: result.error,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
.finally(() => {
|
|
133
|
+
executing.delete(executePromise);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
executing.add(executePromise);
|
|
137
|
+
|
|
138
|
+
// Wait if we've hit the concurrency limit
|
|
139
|
+
if (executing.size >= maxConcurrency) {
|
|
140
|
+
await Promise.race(executing);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Wait for all remaining executions
|
|
145
|
+
await Promise.all(executing);
|
|
146
|
+
|
|
147
|
+
return results;
|
|
148
|
+
}
|