@nathapp/nax 0.36.1 → 0.37.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/README.md +19 -0
- package/bin/nax.ts +38 -4
- package/dist/nax.js +412 -71
- package/package.json +1 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/init.ts +7 -1
- package/src/cli/prompts.ts +62 -8
- package/src/config/schemas.ts +6 -2
- package/src/execution/dry-run.ts +1 -1
- package/src/execution/escalation/escalation.ts +5 -3
- package/src/execution/escalation/tier-escalation.ts +41 -4
- package/src/execution/iteration-runner.ts +5 -0
- package/src/execution/parallel-executor.ts +293 -9
- package/src/execution/parallel.ts +40 -21
- package/src/execution/pipeline-result-handler.ts +3 -2
- package/src/execution/runner.ts +13 -3
- package/src/metrics/tracker.ts +8 -4
- package/src/metrics/types.ts +2 -0
- package/src/pipeline/event-bus.ts +1 -1
- package/src/pipeline/stages/completion.ts +1 -1
- package/src/pipeline/stages/verify.ts +8 -1
- package/src/pipeline/subscribers/reporters.ts +3 -3
- package/src/pipeline/types.ts +4 -0
- package/src/plugins/types.ts +1 -1
- package/src/prd/types.ts +2 -0
- package/src/review/orchestrator.ts +24 -9
- package/src/tdd/types.ts +2 -1
- package/src/verification/crash-detector.ts +34 -0
- package/src/verification/orchestrator-types.ts +8 -1
package/dist/nax.js
CHANGED
|
@@ -18047,7 +18047,9 @@ var init_schemas3 = __esm(() => {
|
|
|
18047
18047
|
storySizeGate: StorySizeGateConfigSchema
|
|
18048
18048
|
});
|
|
18049
18049
|
PromptsConfigSchema = exports_external.object({
|
|
18050
|
-
overrides: exports_external.record(exports_external.
|
|
18050
|
+
overrides: exports_external.record(exports_external.string().refine((key) => ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
|
|
18051
|
+
message: "Role must be one of: test-writer, implementer, verifier, single-session, tdd-simple"
|
|
18052
|
+
}), exports_external.string().min(1, "Override path must be non-empty")).optional()
|
|
18051
18053
|
});
|
|
18052
18054
|
DecomposeConfigSchema = exports_external.object({
|
|
18053
18055
|
trigger: exports_external.enum(["auto", "confirm", "disabled"]).default("auto"),
|
|
@@ -20063,6 +20065,22 @@ function computeStoryContentHash(story) {
|
|
|
20063
20065
|
}
|
|
20064
20066
|
|
|
20065
20067
|
// src/routing/index.ts
|
|
20068
|
+
var exports_routing = {};
|
|
20069
|
+
__export(exports_routing, {
|
|
20070
|
+
tryLlmBatchRoute: () => tryLlmBatchRoute,
|
|
20071
|
+
routeTask: () => routeTask,
|
|
20072
|
+
routeStory: () => routeStory,
|
|
20073
|
+
manualStrategy: () => manualStrategy,
|
|
20074
|
+
loadCustomStrategy: () => loadCustomStrategy,
|
|
20075
|
+
llmStrategy: () => llmStrategy,
|
|
20076
|
+
keywordStrategy: () => keywordStrategy,
|
|
20077
|
+
determineTestStrategy: () => determineTestStrategy2,
|
|
20078
|
+
computeStoryContentHash: () => computeStoryContentHash,
|
|
20079
|
+
complexityToModelTier: () => complexityToModelTier2,
|
|
20080
|
+
classifyComplexity: () => classifyComplexity2,
|
|
20081
|
+
buildStrategyChain: () => buildStrategyChain,
|
|
20082
|
+
StrategyChain: () => StrategyChain
|
|
20083
|
+
});
|
|
20066
20084
|
var init_routing = __esm(() => {
|
|
20067
20085
|
init_router();
|
|
20068
20086
|
init_chain();
|
|
@@ -20657,7 +20675,7 @@ var package_default;
|
|
|
20657
20675
|
var init_package = __esm(() => {
|
|
20658
20676
|
package_default = {
|
|
20659
20677
|
name: "@nathapp/nax",
|
|
20660
|
-
version: "0.
|
|
20678
|
+
version: "0.37.0",
|
|
20661
20679
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
20662
20680
|
type: "module",
|
|
20663
20681
|
bin: {
|
|
@@ -20718,8 +20736,8 @@ var init_version = __esm(() => {
|
|
|
20718
20736
|
NAX_VERSION = package_default.version;
|
|
20719
20737
|
NAX_COMMIT = (() => {
|
|
20720
20738
|
try {
|
|
20721
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
20722
|
-
return "
|
|
20739
|
+
if (/^[0-9a-f]{6,10}$/.test("0a7a065"))
|
|
20740
|
+
return "0a7a065";
|
|
20723
20741
|
} catch {}
|
|
20724
20742
|
try {
|
|
20725
20743
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -20791,9 +20809,10 @@ function collectStoryMetrics(ctx, storyStartTime) {
|
|
|
20791
20809
|
const routing = ctx.routing;
|
|
20792
20810
|
const agentResult = ctx.agentResult;
|
|
20793
20811
|
const escalationCount = story.escalations?.length || 0;
|
|
20794
|
-
const
|
|
20812
|
+
const priorFailureCount = story.priorFailures?.length || 0;
|
|
20813
|
+
const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
|
|
20795
20814
|
const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
|
|
20796
|
-
const firstPassSuccess = agentResult?.success === true && escalationCount === 0;
|
|
20815
|
+
const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
|
|
20797
20816
|
const modelEntry = ctx.config.models[routing.modelTier];
|
|
20798
20817
|
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
20799
20818
|
const modelUsed = modelDef?.model || routing.modelTier;
|
|
@@ -20809,12 +20828,13 @@ function collectStoryMetrics(ctx, storyStartTime) {
|
|
|
20809
20828
|
attempts,
|
|
20810
20829
|
finalTier,
|
|
20811
20830
|
success: agentResult?.success || false,
|
|
20812
|
-
cost: agentResult?.estimatedCost || 0,
|
|
20831
|
+
cost: (ctx.accumulatedAttemptCost ?? 0) + (agentResult?.estimatedCost || 0),
|
|
20813
20832
|
durationMs: agentResult?.durationMs || 0,
|
|
20814
20833
|
firstPassSuccess,
|
|
20815
20834
|
startedAt: storyStartTime,
|
|
20816
20835
|
completedAt: new Date().toISOString(),
|
|
20817
|
-
fullSuiteGatePassed
|
|
20836
|
+
fullSuiteGatePassed,
|
|
20837
|
+
runtimeCrashes: ctx.storyRuntimeCrashes ?? 0
|
|
20818
20838
|
};
|
|
20819
20839
|
}
|
|
20820
20840
|
function collectBatchMetrics(ctx, storyStartTime) {
|
|
@@ -20844,7 +20864,8 @@ function collectBatchMetrics(ctx, storyStartTime) {
|
|
|
20844
20864
|
firstPassSuccess: true,
|
|
20845
20865
|
startedAt: storyStartTime,
|
|
20846
20866
|
completedAt: new Date().toISOString(),
|
|
20847
|
-
fullSuiteGatePassed: false
|
|
20867
|
+
fullSuiteGatePassed: false,
|
|
20868
|
+
runtimeCrashes: 0
|
|
20848
20869
|
};
|
|
20849
20870
|
});
|
|
20850
20871
|
}
|
|
@@ -22227,6 +22248,11 @@ var init_interaction = __esm(() => {
|
|
|
22227
22248
|
});
|
|
22228
22249
|
|
|
22229
22250
|
// src/pipeline/runner.ts
|
|
22251
|
+
var exports_runner = {};
|
|
22252
|
+
__export(exports_runner, {
|
|
22253
|
+
runPipeline: () => runPipeline,
|
|
22254
|
+
MAX_STAGE_RETRIES: () => MAX_STAGE_RETRIES
|
|
22255
|
+
});
|
|
22230
22256
|
async function runPipeline(stages, context, eventEmitter) {
|
|
22231
22257
|
const logger = getLogger();
|
|
22232
22258
|
const retryCountMap = new Map;
|
|
@@ -22657,18 +22683,24 @@ var init_runner2 = __esm(() => {
|
|
|
22657
22683
|
|
|
22658
22684
|
// src/review/orchestrator.ts
|
|
22659
22685
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
22660
|
-
async function getChangedFiles(workdir) {
|
|
22686
|
+
async function getChangedFiles(workdir, baseRef) {
|
|
22661
22687
|
try {
|
|
22662
|
-
const [
|
|
22663
|
-
|
|
22664
|
-
spawn2({ cmd: ["git",
|
|
22688
|
+
const diffArgs = ["diff", "--name-only"];
|
|
22689
|
+
const [stagedProc, unstagedProc, baseProc] = [
|
|
22690
|
+
spawn2({ cmd: ["git", ...diffArgs, "--cached"], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
|
|
22691
|
+
spawn2({ cmd: ["git", ...diffArgs], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
|
|
22692
|
+
baseRef ? spawn2({ cmd: ["git", ...diffArgs, `${baseRef}...HEAD`], cwd: workdir, stdout: "pipe", stderr: "pipe" }) : null
|
|
22665
22693
|
];
|
|
22666
|
-
await Promise.all([stagedProc.exited, unstagedProc.exited]);
|
|
22667
|
-
const staged =
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22671
|
-
|
|
22694
|
+
await Promise.all([stagedProc.exited, unstagedProc.exited, baseProc?.exited]);
|
|
22695
|
+
const [staged, unstaged, based] = await Promise.all([
|
|
22696
|
+
new Response(stagedProc.stdout).text().then((t) => t.trim().split(`
|
|
22697
|
+
`).filter(Boolean)),
|
|
22698
|
+
new Response(unstagedProc.stdout).text().then((t) => t.trim().split(`
|
|
22699
|
+
`).filter(Boolean)),
|
|
22700
|
+
baseProc ? new Response(baseProc.stdout).text().then((t) => t.trim().split(`
|
|
22701
|
+
`).filter(Boolean)) : Promise.resolve([])
|
|
22702
|
+
]);
|
|
22703
|
+
return Array.from(new Set([...staged, ...unstaged, ...based]));
|
|
22672
22704
|
} catch {
|
|
22673
22705
|
return [];
|
|
22674
22706
|
}
|
|
@@ -22684,7 +22716,8 @@ class ReviewOrchestrator {
|
|
|
22684
22716
|
if (plugins) {
|
|
22685
22717
|
const reviewers = plugins.getReviewers();
|
|
22686
22718
|
if (reviewers.length > 0) {
|
|
22687
|
-
const
|
|
22719
|
+
const baseRef = executionConfig?.storyGitRef;
|
|
22720
|
+
const changedFiles = await getChangedFiles(workdir, baseRef);
|
|
22688
22721
|
const pluginResults = [];
|
|
22689
22722
|
for (const reviewer of reviewers) {
|
|
22690
22723
|
logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
|
|
@@ -22946,7 +22979,7 @@ var init_completion = __esm(() => {
|
|
|
22946
22979
|
storyId: completedStory.id,
|
|
22947
22980
|
story: completedStory,
|
|
22948
22981
|
passed: true,
|
|
22949
|
-
|
|
22982
|
+
runElapsedMs: storyMetric?.durationMs ?? 0,
|
|
22950
22983
|
cost: costPerStory,
|
|
22951
22984
|
modelTier: ctx.routing?.modelTier,
|
|
22952
22985
|
testStrategy: ctx.routing?.testStrategy
|
|
@@ -27456,6 +27489,22 @@ var init_routing2 = __esm(() => {
|
|
|
27456
27489
|
};
|
|
27457
27490
|
});
|
|
27458
27491
|
|
|
27492
|
+
// src/verification/crash-detector.ts
|
|
27493
|
+
function detectRuntimeCrash(output) {
|
|
27494
|
+
if (!output)
|
|
27495
|
+
return false;
|
|
27496
|
+
return CRASH_PATTERNS.some((pattern) => output.includes(pattern));
|
|
27497
|
+
}
|
|
27498
|
+
var CRASH_PATTERNS;
|
|
27499
|
+
var init_crash_detector = __esm(() => {
|
|
27500
|
+
CRASH_PATTERNS = [
|
|
27501
|
+
"panic(main thread)",
|
|
27502
|
+
"Segmentation fault",
|
|
27503
|
+
"Bun has crashed",
|
|
27504
|
+
"oh no: Bun has crashed"
|
|
27505
|
+
];
|
|
27506
|
+
});
|
|
27507
|
+
|
|
27459
27508
|
// src/pipeline/stages/verify.ts
|
|
27460
27509
|
function coerceSmartTestRunner(val) {
|
|
27461
27510
|
if (val === undefined || val === true)
|
|
@@ -27473,6 +27522,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
27473
27522
|
var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
|
|
27474
27523
|
var init_verify = __esm(() => {
|
|
27475
27524
|
init_logger2();
|
|
27525
|
+
init_crash_detector();
|
|
27476
27526
|
init_runners();
|
|
27477
27527
|
init_smart_runner();
|
|
27478
27528
|
DEFAULT_SMART_RUNNER_CONFIG2 = {
|
|
@@ -27544,7 +27594,7 @@ var init_verify = __esm(() => {
|
|
|
27544
27594
|
});
|
|
27545
27595
|
ctx.verifyResult = {
|
|
27546
27596
|
success: result.success,
|
|
27547
|
-
status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : "TEST_FAILURE",
|
|
27597
|
+
status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : detectRuntimeCrash(result.output) ? "RUNTIME_CRASH" : "TEST_FAILURE",
|
|
27548
27598
|
storyId: ctx.story.id,
|
|
27549
27599
|
strategy: "scoped",
|
|
27550
27600
|
passCount: result.passCount ?? 0,
|
|
@@ -27595,6 +27645,25 @@ var init_verify = __esm(() => {
|
|
|
27595
27645
|
});
|
|
27596
27646
|
|
|
27597
27647
|
// src/pipeline/stages/index.ts
|
|
27648
|
+
var exports_stages = {};
|
|
27649
|
+
__export(exports_stages, {
|
|
27650
|
+
verifyStage: () => verifyStage,
|
|
27651
|
+
routingStage: () => routingStage,
|
|
27652
|
+
reviewStage: () => reviewStage,
|
|
27653
|
+
regressionStage: () => regressionStage,
|
|
27654
|
+
rectifyStage: () => rectifyStage,
|
|
27655
|
+
queueCheckStage: () => queueCheckStage,
|
|
27656
|
+
promptStage: () => promptStage,
|
|
27657
|
+
postRunPipeline: () => postRunPipeline,
|
|
27658
|
+
optimizerStage: () => optimizerStage,
|
|
27659
|
+
executionStage: () => executionStage,
|
|
27660
|
+
defaultPipeline: () => defaultPipeline,
|
|
27661
|
+
contextStage: () => contextStage,
|
|
27662
|
+
constitutionStage: () => constitutionStage,
|
|
27663
|
+
completionStage: () => completionStage,
|
|
27664
|
+
autofixStage: () => autofixStage,
|
|
27665
|
+
acceptanceStage: () => acceptanceStage
|
|
27666
|
+
});
|
|
27598
27667
|
var defaultPipeline, postRunPipeline;
|
|
27599
27668
|
var init_stages = __esm(() => {
|
|
27600
27669
|
init_acceptance2();
|
|
@@ -29244,11 +29313,12 @@ var init_crash_recovery = __esm(() => {
|
|
|
29244
29313
|
|
|
29245
29314
|
// src/execution/escalation/escalation.ts
|
|
29246
29315
|
function escalateTier(currentTier, tierOrder) {
|
|
29247
|
-
const
|
|
29316
|
+
const getName = (t) => t.tier ?? t.name ?? null;
|
|
29317
|
+
const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
|
|
29248
29318
|
if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
|
|
29249
29319
|
return null;
|
|
29250
29320
|
}
|
|
29251
|
-
return tierOrder[currentIndex + 1]
|
|
29321
|
+
return getName(tierOrder[currentIndex + 1]);
|
|
29252
29322
|
}
|
|
29253
29323
|
function calculateMaxIterations(tierOrder) {
|
|
29254
29324
|
return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
|
|
@@ -29345,13 +29415,14 @@ var init_tier_outcome = __esm(() => {
|
|
|
29345
29415
|
});
|
|
29346
29416
|
|
|
29347
29417
|
// src/execution/escalation/tier-escalation.ts
|
|
29348
|
-
function buildEscalationFailure(story, currentTier, reviewFindings) {
|
|
29418
|
+
function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
|
|
29349
29419
|
return {
|
|
29350
29420
|
attempt: (story.attempts ?? 0) + 1,
|
|
29351
29421
|
modelTier: currentTier,
|
|
29352
29422
|
stage: "escalation",
|
|
29353
29423
|
summary: `Failed with tier ${currentTier}, escalating to next tier`,
|
|
29354
29424
|
reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
|
|
29425
|
+
cost: cost ?? 0,
|
|
29355
29426
|
timestamp: new Date().toISOString()
|
|
29356
29427
|
};
|
|
29357
29428
|
}
|
|
@@ -29364,6 +29435,8 @@ function resolveMaxAttemptsOutcome(failureCategory) {
|
|
|
29364
29435
|
case "verifier-rejected":
|
|
29365
29436
|
case "greenfield-no-tests":
|
|
29366
29437
|
return "pause";
|
|
29438
|
+
case "runtime-crash":
|
|
29439
|
+
return "pause";
|
|
29367
29440
|
case "session-failure":
|
|
29368
29441
|
case "tests-failing":
|
|
29369
29442
|
return "fail";
|
|
@@ -29387,8 +29460,17 @@ async function tryLlmBatchRoute2(config2, stories, label = "routing") {
|
|
|
29387
29460
|
});
|
|
29388
29461
|
}
|
|
29389
29462
|
}
|
|
29463
|
+
function shouldRetrySameTier(verifyResult) {
|
|
29464
|
+
return verifyResult?.status === "RUNTIME_CRASH";
|
|
29465
|
+
}
|
|
29390
29466
|
async function handleTierEscalation(ctx) {
|
|
29391
29467
|
const logger = getSafeLogger();
|
|
29468
|
+
if (shouldRetrySameTier(ctx.verifyResult)) {
|
|
29469
|
+
logger?.warn("escalation", "Runtime crash detected \u2014 retrying same tier (transient, not a code issue)", {
|
|
29470
|
+
storyId: ctx.story.id
|
|
29471
|
+
});
|
|
29472
|
+
return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
|
|
29473
|
+
}
|
|
29392
29474
|
const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
|
|
29393
29475
|
const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
|
|
29394
29476
|
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
@@ -29442,7 +29524,7 @@ async function handleTierEscalation(ctx) {
|
|
|
29442
29524
|
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
29443
29525
|
const isChangingTier = currentStoryTier !== nextTier;
|
|
29444
29526
|
const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
|
|
29445
|
-
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings);
|
|
29527
|
+
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
29446
29528
|
return {
|
|
29447
29529
|
...s,
|
|
29448
29530
|
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
@@ -29452,7 +29534,7 @@ async function handleTierEscalation(ctx) {
|
|
|
29452
29534
|
};
|
|
29453
29535
|
})
|
|
29454
29536
|
};
|
|
29455
|
-
await savePRD(updatedPrd, ctx.prdPath);
|
|
29537
|
+
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
29456
29538
|
for (const story of storiesToEscalate) {
|
|
29457
29539
|
clearCacheForStory(story.id);
|
|
29458
29540
|
}
|
|
@@ -29465,6 +29547,7 @@ async function handleTierEscalation(ctx) {
|
|
|
29465
29547
|
prd: updatedPrd
|
|
29466
29548
|
};
|
|
29467
29549
|
}
|
|
29550
|
+
var _tierEscalationDeps;
|
|
29468
29551
|
var init_tier_escalation = __esm(() => {
|
|
29469
29552
|
init_hooks();
|
|
29470
29553
|
init_logger2();
|
|
@@ -29474,6 +29557,9 @@ var init_tier_escalation = __esm(() => {
|
|
|
29474
29557
|
init_helpers();
|
|
29475
29558
|
init_progress();
|
|
29476
29559
|
init_tier_outcome();
|
|
29560
|
+
_tierEscalationDeps = {
|
|
29561
|
+
savePRD
|
|
29562
|
+
};
|
|
29477
29563
|
});
|
|
29478
29564
|
|
|
29479
29565
|
// src/execution/escalation/index.ts
|
|
@@ -30075,6 +30161,10 @@ var init_headless_formatter = __esm(() => {
|
|
|
30075
30161
|
});
|
|
30076
30162
|
|
|
30077
30163
|
// src/worktree/manager.ts
|
|
30164
|
+
var exports_manager = {};
|
|
30165
|
+
__export(exports_manager, {
|
|
30166
|
+
WorktreeManager: () => WorktreeManager
|
|
30167
|
+
});
|
|
30078
30168
|
import { existsSync as existsSync26, symlinkSync } from "fs";
|
|
30079
30169
|
import { join as join32 } from "path";
|
|
30080
30170
|
|
|
@@ -30219,6 +30309,11 @@ var init_manager = __esm(() => {
|
|
|
30219
30309
|
});
|
|
30220
30310
|
|
|
30221
30311
|
// src/worktree/merge.ts
|
|
30312
|
+
var exports_merge = {};
|
|
30313
|
+
__export(exports_merge, {
|
|
30314
|
+
MergeEngine: () => MergeEngine
|
|
30315
|
+
});
|
|
30316
|
+
|
|
30222
30317
|
class MergeEngine {
|
|
30223
30318
|
worktreeManager;
|
|
30224
30319
|
constructor(worktreeManager) {
|
|
@@ -30497,10 +30592,12 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
|
|
|
30497
30592
|
const logger = getSafeLogger();
|
|
30498
30593
|
const worktreeManager = new WorktreeManager;
|
|
30499
30594
|
const results = {
|
|
30500
|
-
|
|
30501
|
-
|
|
30595
|
+
pipelinePassed: [],
|
|
30596
|
+
merged: [],
|
|
30597
|
+
failed: [],
|
|
30502
30598
|
totalCost: 0,
|
|
30503
|
-
|
|
30599
|
+
mergeConflicts: [],
|
|
30600
|
+
storyCosts: new Map
|
|
30504
30601
|
};
|
|
30505
30602
|
const worktreeSetup = [];
|
|
30506
30603
|
for (const story of stories) {
|
|
@@ -30513,7 +30610,7 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
|
|
|
30513
30610
|
worktreePath
|
|
30514
30611
|
});
|
|
30515
30612
|
} catch (error48) {
|
|
30516
|
-
results.
|
|
30613
|
+
results.failed.push({
|
|
30517
30614
|
story,
|
|
30518
30615
|
error: `Failed to create worktree: ${error48 instanceof Error ? error48.message : String(error48)}`
|
|
30519
30616
|
});
|
|
@@ -30528,14 +30625,15 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
|
|
|
30528
30625
|
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
30529
30626
|
const executePromise = executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter).then((result) => {
|
|
30530
30627
|
results.totalCost += result.cost;
|
|
30628
|
+
results.storyCosts.set(story.id, result.cost);
|
|
30531
30629
|
if (result.success) {
|
|
30532
|
-
results.
|
|
30630
|
+
results.pipelinePassed.push(story);
|
|
30533
30631
|
logger?.info("parallel", "Story execution succeeded", {
|
|
30534
30632
|
storyId: story.id,
|
|
30535
30633
|
cost: result.cost
|
|
30536
30634
|
});
|
|
30537
30635
|
} else {
|
|
30538
|
-
results.
|
|
30636
|
+
results.failed.push({ story, error: result.error || "Unknown error" });
|
|
30539
30637
|
logger?.error("parallel", "Story execution failed", {
|
|
30540
30638
|
storyId: story.id,
|
|
30541
30639
|
error: result.error
|
|
@@ -30575,6 +30673,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30575
30673
|
let storiesCompleted = 0;
|
|
30576
30674
|
let totalCost = 0;
|
|
30577
30675
|
const currentPrd = prd;
|
|
30676
|
+
const allMergeConflicts = [];
|
|
30578
30677
|
for (let batchIndex = 0;batchIndex < batches.length; batchIndex++) {
|
|
30579
30678
|
const batch = batches[batchIndex];
|
|
30580
30679
|
logger?.info("parallel", `Executing batch ${batchIndex + 1}/${batches.length}`, {
|
|
@@ -30591,8 +30690,8 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30591
30690
|
};
|
|
30592
30691
|
const batchResult = await executeParallelBatch(batch, projectRoot, config2, currentPrd, baseContext, maxConcurrency, eventEmitter);
|
|
30593
30692
|
totalCost += batchResult.totalCost;
|
|
30594
|
-
if (batchResult.
|
|
30595
|
-
const successfulIds = batchResult.
|
|
30693
|
+
if (batchResult.pipelinePassed.length > 0) {
|
|
30694
|
+
const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
|
|
30596
30695
|
const deps = buildDependencyMap(batch);
|
|
30597
30696
|
logger?.info("parallel", "Merging successful stories", {
|
|
30598
30697
|
storyIds: successfulIds
|
|
@@ -30602,15 +30701,19 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30602
30701
|
if (mergeResult.success) {
|
|
30603
30702
|
markStoryPassed(currentPrd, mergeResult.storyId);
|
|
30604
30703
|
storiesCompleted++;
|
|
30704
|
+
const mergedStory = batchResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
|
|
30705
|
+
if (mergedStory)
|
|
30706
|
+
batchResult.merged.push(mergedStory);
|
|
30605
30707
|
logger?.info("parallel", "Story merged successfully", {
|
|
30606
30708
|
storyId: mergeResult.storyId,
|
|
30607
30709
|
retryCount: mergeResult.retryCount
|
|
30608
30710
|
});
|
|
30609
30711
|
} else {
|
|
30610
30712
|
markStoryFailed(currentPrd, mergeResult.storyId);
|
|
30611
|
-
batchResult.
|
|
30713
|
+
batchResult.mergeConflicts.push({
|
|
30612
30714
|
storyId: mergeResult.storyId,
|
|
30613
|
-
conflictFiles: mergeResult.conflictFiles || []
|
|
30715
|
+
conflictFiles: mergeResult.conflictFiles || [],
|
|
30716
|
+
originalCost: batchResult.storyCosts.get(mergeResult.storyId) ?? 0
|
|
30614
30717
|
});
|
|
30615
30718
|
logger?.error("parallel", "Merge conflict", {
|
|
30616
30719
|
storyId: mergeResult.storyId,
|
|
@@ -30623,7 +30726,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30623
30726
|
}
|
|
30624
30727
|
}
|
|
30625
30728
|
}
|
|
30626
|
-
for (const { story, error: error48 } of batchResult.
|
|
30729
|
+
for (const { story, error: error48 } of batchResult.failed) {
|
|
30627
30730
|
markStoryFailed(currentPrd, story.id);
|
|
30628
30731
|
logger?.error("parallel", "Cleaning up failed story worktree", {
|
|
30629
30732
|
storyId: story.id,
|
|
@@ -30639,10 +30742,12 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30639
30742
|
}
|
|
30640
30743
|
}
|
|
30641
30744
|
await savePRD(currentPrd, prdPath);
|
|
30745
|
+
allMergeConflicts.push(...batchResult.mergeConflicts);
|
|
30642
30746
|
logger?.info("parallel", `Batch ${batchIndex + 1} complete`, {
|
|
30643
|
-
|
|
30644
|
-
|
|
30645
|
-
|
|
30747
|
+
pipelinePassed: batchResult.pipelinePassed.length,
|
|
30748
|
+
merged: batchResult.merged.length,
|
|
30749
|
+
failed: batchResult.failed.length,
|
|
30750
|
+
mergeConflicts: batchResult.mergeConflicts.length,
|
|
30646
30751
|
batchCost: batchResult.totalCost
|
|
30647
30752
|
});
|
|
30648
30753
|
}
|
|
@@ -30650,7 +30755,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30650
30755
|
storiesCompleted,
|
|
30651
30756
|
totalCost
|
|
30652
30757
|
});
|
|
30653
|
-
return { storiesCompleted, totalCost, updatedPrd: currentPrd };
|
|
30758
|
+
return { storiesCompleted, totalCost, updatedPrd: currentPrd, mergeConflicts: allMergeConflicts };
|
|
30654
30759
|
}
|
|
30655
30760
|
var init_parallel = __esm(() => {
|
|
30656
30761
|
init_logger2();
|
|
@@ -30742,6 +30847,123 @@ __export(exports_parallel_executor, {
|
|
|
30742
30847
|
});
|
|
30743
30848
|
import * as os5 from "os";
|
|
30744
30849
|
import path15 from "path";
|
|
30850
|
+
async function rectifyConflictedStory(options) {
|
|
30851
|
+
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter } = options;
|
|
30852
|
+
const logger = getSafeLogger();
|
|
30853
|
+
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
30854
|
+
try {
|
|
30855
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
30856
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
30857
|
+
const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
30858
|
+
const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
|
|
30859
|
+
const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
|
|
30860
|
+
const worktreeManager = new WorktreeManager2;
|
|
30861
|
+
const mergeEngine = new MergeEngine2(worktreeManager);
|
|
30862
|
+
try {
|
|
30863
|
+
await worktreeManager.remove(workdir, storyId);
|
|
30864
|
+
} catch {}
|
|
30865
|
+
await worktreeManager.create(workdir, storyId);
|
|
30866
|
+
const worktreePath = path15.join(workdir, ".nax-wt", storyId);
|
|
30867
|
+
const story = prd.userStories.find((s) => s.id === storyId);
|
|
30868
|
+
if (!story) {
|
|
30869
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
30870
|
+
}
|
|
30871
|
+
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
30872
|
+
const pipelineContext = {
|
|
30873
|
+
config: config2,
|
|
30874
|
+
prd,
|
|
30875
|
+
story,
|
|
30876
|
+
stories: [story],
|
|
30877
|
+
workdir: worktreePath,
|
|
30878
|
+
featureDir: undefined,
|
|
30879
|
+
hooks,
|
|
30880
|
+
plugins: pluginRegistry,
|
|
30881
|
+
storyStartTime: new Date().toISOString(),
|
|
30882
|
+
routing
|
|
30883
|
+
};
|
|
30884
|
+
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
30885
|
+
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
30886
|
+
if (!pipelineResult.success) {
|
|
30887
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
30888
|
+
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
30889
|
+
}
|
|
30890
|
+
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
30891
|
+
const mergeResult = mergeResults[0];
|
|
30892
|
+
if (!mergeResult || !mergeResult.success) {
|
|
30893
|
+
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
30894
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
30895
|
+
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
30896
|
+
}
|
|
30897
|
+
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
30898
|
+
storyId,
|
|
30899
|
+
originalCost: options.originalCost,
|
|
30900
|
+
rectificationCost: cost
|
|
30901
|
+
});
|
|
30902
|
+
return { success: true, storyId, cost };
|
|
30903
|
+
} catch (error48) {
|
|
30904
|
+
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
30905
|
+
storyId,
|
|
30906
|
+
error: error48 instanceof Error ? error48.message : String(error48)
|
|
30907
|
+
});
|
|
30908
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
30909
|
+
}
|
|
30910
|
+
}
|
|
30911
|
+
async function runRectificationPass(conflictedStories, options, prd) {
|
|
30912
|
+
const logger = getSafeLogger();
|
|
30913
|
+
const { workdir, config: config2, hooks, pluginRegistry, eventEmitter } = options;
|
|
30914
|
+
const rectificationMetrics = [];
|
|
30915
|
+
let rectifiedCount = 0;
|
|
30916
|
+
let stillConflictingCount = 0;
|
|
30917
|
+
let additionalCost = 0;
|
|
30918
|
+
logger?.info("parallel", "Starting merge conflict rectification", {
|
|
30919
|
+
stories: conflictedStories.map((s) => s.storyId),
|
|
30920
|
+
totalConflicts: conflictedStories.length
|
|
30921
|
+
});
|
|
30922
|
+
for (const conflictInfo of conflictedStories) {
|
|
30923
|
+
const result = await _parallelExecutorDeps.rectifyConflictedStory({
|
|
30924
|
+
...conflictInfo,
|
|
30925
|
+
workdir,
|
|
30926
|
+
config: config2,
|
|
30927
|
+
hooks,
|
|
30928
|
+
pluginRegistry,
|
|
30929
|
+
prd,
|
|
30930
|
+
eventEmitter
|
|
30931
|
+
});
|
|
30932
|
+
additionalCost += result.cost;
|
|
30933
|
+
if (result.success) {
|
|
30934
|
+
markStoryPassed(prd, result.storyId);
|
|
30935
|
+
rectifiedCount++;
|
|
30936
|
+
rectificationMetrics.push({
|
|
30937
|
+
storyId: result.storyId,
|
|
30938
|
+
complexity: "unknown",
|
|
30939
|
+
modelTier: "parallel",
|
|
30940
|
+
modelUsed: "parallel",
|
|
30941
|
+
attempts: 1,
|
|
30942
|
+
finalTier: "parallel",
|
|
30943
|
+
success: true,
|
|
30944
|
+
cost: result.cost,
|
|
30945
|
+
durationMs: 0,
|
|
30946
|
+
firstPassSuccess: false,
|
|
30947
|
+
startedAt: new Date().toISOString(),
|
|
30948
|
+
completedAt: new Date().toISOString(),
|
|
30949
|
+
source: "rectification",
|
|
30950
|
+
rectifiedFromConflict: true,
|
|
30951
|
+
originalCost: conflictInfo.originalCost,
|
|
30952
|
+
rectificationCost: result.cost
|
|
30953
|
+
});
|
|
30954
|
+
} else {
|
|
30955
|
+
const isFinalConflict = result.finalConflict === true;
|
|
30956
|
+
if (isFinalConflict) {
|
|
30957
|
+
stillConflictingCount++;
|
|
30958
|
+
}
|
|
30959
|
+
}
|
|
30960
|
+
}
|
|
30961
|
+
logger?.info("parallel", "Rectification complete", {
|
|
30962
|
+
rectified: rectifiedCount,
|
|
30963
|
+
stillConflicting: stillConflictingCount
|
|
30964
|
+
});
|
|
30965
|
+
return { rectifiedCount, stillConflictingCount, additionalCost, updatedPrd: prd, rectificationMetrics };
|
|
30966
|
+
}
|
|
30745
30967
|
async function runParallelExecution(options, initialPrd) {
|
|
30746
30968
|
const logger = getSafeLogger();
|
|
30747
30969
|
const {
|
|
@@ -30766,7 +30988,14 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30766
30988
|
const readyStories = getAllReadyStories(prd);
|
|
30767
30989
|
if (readyStories.length === 0) {
|
|
30768
30990
|
logger?.info("parallel", "No stories ready for parallel execution");
|
|
30769
|
-
return {
|
|
30991
|
+
return {
|
|
30992
|
+
prd,
|
|
30993
|
+
totalCost,
|
|
30994
|
+
storiesCompleted,
|
|
30995
|
+
completed: false,
|
|
30996
|
+
storyMetrics: [],
|
|
30997
|
+
rectificationStats: { rectified: 0, stillConflicting: 0 }
|
|
30998
|
+
};
|
|
30770
30999
|
}
|
|
30771
31000
|
const maxConcurrency = parallelCount === 0 ? os5.cpus().length : Math.max(1, parallelCount);
|
|
30772
31001
|
logger?.info("parallel", "Starting parallel execution mode", {
|
|
@@ -30784,15 +31013,45 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30784
31013
|
}))
|
|
30785
31014
|
}
|
|
30786
31015
|
});
|
|
31016
|
+
const initialPassedIds = new Set(initialPrd.userStories.filter((s) => s.status === "passed").map((s) => s.id));
|
|
31017
|
+
const batchStartedAt = new Date().toISOString();
|
|
31018
|
+
const batchStartMs = Date.now();
|
|
31019
|
+
const batchStoryMetrics = [];
|
|
31020
|
+
let conflictedStories = [];
|
|
30787
31021
|
try {
|
|
30788
|
-
const parallelResult = await executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
|
|
31022
|
+
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
|
|
31023
|
+
const batchDurationMs = Date.now() - batchStartMs;
|
|
31024
|
+
const batchCompletedAt = new Date().toISOString();
|
|
30789
31025
|
prd = parallelResult.updatedPrd;
|
|
30790
31026
|
storiesCompleted += parallelResult.storiesCompleted;
|
|
30791
31027
|
totalCost += parallelResult.totalCost;
|
|
30792
|
-
|
|
30793
|
-
|
|
30794
|
-
|
|
30795
|
-
|
|
31028
|
+
conflictedStories = parallelResult.mergeConflicts ?? [];
|
|
31029
|
+
const newlyPassedStories = prd.userStories.filter((s) => s.status === "passed" && !initialPassedIds.has(s.id));
|
|
31030
|
+
const costPerStory = newlyPassedStories.length > 0 ? parallelResult.totalCost / newlyPassedStories.length : 0;
|
|
31031
|
+
for (const story of newlyPassedStories) {
|
|
31032
|
+
batchStoryMetrics.push({
|
|
31033
|
+
storyId: story.id,
|
|
31034
|
+
complexity: "unknown",
|
|
31035
|
+
modelTier: "parallel",
|
|
31036
|
+
modelUsed: "parallel",
|
|
31037
|
+
attempts: 1,
|
|
31038
|
+
finalTier: "parallel",
|
|
31039
|
+
success: true,
|
|
31040
|
+
cost: costPerStory,
|
|
31041
|
+
durationMs: batchDurationMs,
|
|
31042
|
+
firstPassSuccess: true,
|
|
31043
|
+
startedAt: batchStartedAt,
|
|
31044
|
+
completedAt: batchCompletedAt,
|
|
31045
|
+
source: "parallel"
|
|
31046
|
+
});
|
|
31047
|
+
}
|
|
31048
|
+
allStoryMetrics.push(...batchStoryMetrics);
|
|
31049
|
+
for (const conflict of conflictedStories) {
|
|
31050
|
+
logger?.info("parallel", "Merge conflict detected - scheduling for rectification", {
|
|
31051
|
+
storyId: conflict.storyId,
|
|
31052
|
+
conflictFiles: conflict.conflictFiles
|
|
31053
|
+
});
|
|
31054
|
+
}
|
|
30796
31055
|
statusWriter.setPrd(prd);
|
|
30797
31056
|
await statusWriter.update(totalCost, iterations, {
|
|
30798
31057
|
parallel: {
|
|
@@ -30810,6 +31069,19 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30810
31069
|
});
|
|
30811
31070
|
throw error48;
|
|
30812
31071
|
}
|
|
31072
|
+
let rectificationStats = { rectified: 0, stillConflicting: 0 };
|
|
31073
|
+
if (conflictedStories.length > 0) {
|
|
31074
|
+
const rectResult = await runRectificationPass(conflictedStories, options, prd);
|
|
31075
|
+
prd = rectResult.updatedPrd;
|
|
31076
|
+
storiesCompleted += rectResult.rectifiedCount;
|
|
31077
|
+
totalCost += rectResult.additionalCost;
|
|
31078
|
+
rectificationStats = {
|
|
31079
|
+
rectified: rectResult.rectifiedCount,
|
|
31080
|
+
stillConflicting: rectResult.stillConflictingCount
|
|
31081
|
+
};
|
|
31082
|
+
batchStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
31083
|
+
allStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
31084
|
+
}
|
|
30813
31085
|
if (isComplete(prd)) {
|
|
30814
31086
|
logger?.info("execution", "All stories complete!", {
|
|
30815
31087
|
feature,
|
|
@@ -30859,10 +31131,12 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30859
31131
|
totalCost,
|
|
30860
31132
|
storiesCompleted,
|
|
30861
31133
|
completed: true,
|
|
30862
|
-
durationMs
|
|
31134
|
+
durationMs,
|
|
31135
|
+
storyMetrics: batchStoryMetrics,
|
|
31136
|
+
rectificationStats
|
|
30863
31137
|
};
|
|
30864
31138
|
}
|
|
30865
|
-
return { prd, totalCost, storiesCompleted, completed: false };
|
|
31139
|
+
return { prd, totalCost, storiesCompleted, completed: false, storyMetrics: batchStoryMetrics, rectificationStats };
|
|
30866
31140
|
}
|
|
30867
31141
|
var _parallelExecutorDeps;
|
|
30868
31142
|
var init_parallel_executor = __esm(() => {
|
|
@@ -30872,7 +31146,9 @@ var init_parallel_executor = __esm(() => {
|
|
|
30872
31146
|
init_helpers();
|
|
30873
31147
|
init_parallel();
|
|
30874
31148
|
_parallelExecutorDeps = {
|
|
30875
|
-
fireHook
|
|
31149
|
+
fireHook,
|
|
31150
|
+
executeParallel,
|
|
31151
|
+
rectifyConflictedStory
|
|
30876
31152
|
};
|
|
30877
31153
|
});
|
|
30878
31154
|
|
|
@@ -31114,7 +31390,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
31114
31390
|
runId,
|
|
31115
31391
|
storyId: ev.storyId,
|
|
31116
31392
|
status: "completed",
|
|
31117
|
-
|
|
31393
|
+
runElapsedMs: ev.runElapsedMs,
|
|
31118
31394
|
cost: ev.cost ?? 0,
|
|
31119
31395
|
tier: ev.modelTier ?? "balanced",
|
|
31120
31396
|
testStrategy: ev.testStrategy ?? "test-after"
|
|
@@ -31136,7 +31412,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
31136
31412
|
runId,
|
|
31137
31413
|
storyId: ev.storyId,
|
|
31138
31414
|
status: "failed",
|
|
31139
|
-
|
|
31415
|
+
runElapsedMs: Date.now() - startTime,
|
|
31140
31416
|
cost: 0,
|
|
31141
31417
|
tier: "balanced",
|
|
31142
31418
|
testStrategy: "test-after"
|
|
@@ -31158,7 +31434,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
31158
31434
|
runId,
|
|
31159
31435
|
storyId: ev.storyId,
|
|
31160
31436
|
status: "paused",
|
|
31161
|
-
|
|
31437
|
+
runElapsedMs: Date.now() - startTime,
|
|
31162
31438
|
cost: 0,
|
|
31163
31439
|
tier: "balanced",
|
|
31164
31440
|
testStrategy: "test-after"
|
|
@@ -31236,7 +31512,7 @@ async function handleDryRun(ctx) {
|
|
|
31236
31512
|
storyId: s.id,
|
|
31237
31513
|
story: s,
|
|
31238
31514
|
passed: true,
|
|
31239
|
-
|
|
31515
|
+
runElapsedMs: 0,
|
|
31240
31516
|
cost: 0,
|
|
31241
31517
|
modelTier: ctx.routing.modelTier,
|
|
31242
31518
|
testStrategy: ctx.routing.testStrategy
|
|
@@ -31268,7 +31544,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
31268
31544
|
storyId: completedStory.id,
|
|
31269
31545
|
storyTitle: completedStory.title,
|
|
31270
31546
|
totalCost: ctx.totalCost + costDelta,
|
|
31271
|
-
|
|
31547
|
+
runElapsedMs: now - ctx.startTime,
|
|
31272
31548
|
storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined
|
|
31273
31549
|
});
|
|
31274
31550
|
pipelineEventBus.emit({
|
|
@@ -31276,7 +31552,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
31276
31552
|
storyId: completedStory.id,
|
|
31277
31553
|
story: completedStory,
|
|
31278
31554
|
passed: true,
|
|
31279
|
-
|
|
31555
|
+
runElapsedMs: Date.now() - ctx.startTime,
|
|
31280
31556
|
cost: costDelta,
|
|
31281
31557
|
modelTier: ctx.routing.modelTier,
|
|
31282
31558
|
testStrategy: ctx.routing.testStrategy
|
|
@@ -31357,7 +31633,8 @@ async function handlePipelineFailure(ctx, pipelineResult) {
|
|
|
31357
31633
|
hooks: ctx.hooks,
|
|
31358
31634
|
feature: ctx.feature,
|
|
31359
31635
|
totalCost: ctx.totalCost,
|
|
31360
|
-
workdir: ctx.workdir
|
|
31636
|
+
workdir: ctx.workdir,
|
|
31637
|
+
attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0
|
|
31361
31638
|
});
|
|
31362
31639
|
prd = escalationResult.prd;
|
|
31363
31640
|
prdDirty = escalationResult.prdDirty;
|
|
@@ -31399,6 +31676,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
31399
31676
|
}
|
|
31400
31677
|
const storyStartTime = Date.now();
|
|
31401
31678
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
31679
|
+
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
31402
31680
|
const pipelineContext = {
|
|
31403
31681
|
config: ctx.config,
|
|
31404
31682
|
prd,
|
|
@@ -31412,7 +31690,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
31412
31690
|
plugins: ctx.pluginRegistry,
|
|
31413
31691
|
storyStartTime: new Date().toISOString(),
|
|
31414
31692
|
storyGitRef: storyGitRef ?? undefined,
|
|
31415
|
-
interaction: ctx.interactionChain ?? undefined
|
|
31693
|
+
interaction: ctx.interactionChain ?? undefined,
|
|
31694
|
+
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
31416
31695
|
};
|
|
31417
31696
|
ctx.statusWriter.setPrd(prd);
|
|
31418
31697
|
ctx.statusWriter.setCurrentStory({
|
|
@@ -64469,7 +64748,8 @@ var TEMPLATE_ROLES = [
|
|
|
64469
64748
|
{ file: "test-writer.md", role: "test-writer" },
|
|
64470
64749
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
64471
64750
|
{ file: "verifier.md", role: "verifier" },
|
|
64472
|
-
{ file: "single-session.md", role: "single-session" }
|
|
64751
|
+
{ file: "single-session.md", role: "single-session" },
|
|
64752
|
+
{ file: "tdd-simple.md", role: "tdd-simple" }
|
|
64473
64753
|
];
|
|
64474
64754
|
var TEMPLATE_HEADER = `<!--
|
|
64475
64755
|
This file controls the role-body section of the nax prompt for this role.
|
|
@@ -64486,7 +64766,7 @@ var TEMPLATE_HEADER = `<!--
|
|
|
64486
64766
|
|
|
64487
64767
|
`;
|
|
64488
64768
|
async function promptsInitCommand(options) {
|
|
64489
|
-
const { workdir, force = false } = options;
|
|
64769
|
+
const { workdir, force = false, autoWireConfig = true } = options;
|
|
64490
64770
|
const templatesDir = join18(workdir, "nax", "templates");
|
|
64491
64771
|
mkdirSync3(templatesDir, { recursive: true });
|
|
64492
64772
|
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync15(join18(templatesDir, f)));
|
|
@@ -64507,7 +64787,9 @@ async function promptsInitCommand(options) {
|
|
|
64507
64787
|
for (const filePath of written) {
|
|
64508
64788
|
console.log(` - ${filePath.replace(`${workdir}/`, "")}`);
|
|
64509
64789
|
}
|
|
64510
|
-
|
|
64790
|
+
if (autoWireConfig) {
|
|
64791
|
+
await autoWirePromptsConfig(workdir);
|
|
64792
|
+
}
|
|
64511
64793
|
return written;
|
|
64512
64794
|
}
|
|
64513
64795
|
async function autoWirePromptsConfig(workdir) {
|
|
@@ -64519,7 +64801,8 @@ async function autoWirePromptsConfig(workdir) {
|
|
|
64519
64801
|
"test-writer": "nax/templates/test-writer.md",
|
|
64520
64802
|
implementer: "nax/templates/implementer.md",
|
|
64521
64803
|
verifier: "nax/templates/verifier.md",
|
|
64522
|
-
"single-session": "nax/templates/single-session.md"
|
|
64804
|
+
"single-session": "nax/templates/single-session.md",
|
|
64805
|
+
"tdd-simple": "nax/templates/tdd-simple.md"
|
|
64523
64806
|
}
|
|
64524
64807
|
}
|
|
64525
64808
|
}, null, 2);
|
|
@@ -64540,7 +64823,8 @@ ${exampleConfig}`);
|
|
|
64540
64823
|
"test-writer": "nax/templates/test-writer.md",
|
|
64541
64824
|
implementer: "nax/templates/implementer.md",
|
|
64542
64825
|
verifier: "nax/templates/verifier.md",
|
|
64543
|
-
"single-session": "nax/templates/single-session.md"
|
|
64826
|
+
"single-session": "nax/templates/single-session.md",
|
|
64827
|
+
"tdd-simple": "nax/templates/tdd-simple.md"
|
|
64544
64828
|
};
|
|
64545
64829
|
if (!config2.prompts) {
|
|
64546
64830
|
config2.prompts = {};
|
|
@@ -64573,6 +64857,33 @@ function formatConfigJson(config2) {
|
|
|
64573
64857
|
return lines.join(`
|
|
64574
64858
|
`);
|
|
64575
64859
|
}
|
|
64860
|
+
var VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"];
|
|
64861
|
+
async function exportPromptCommand(options) {
|
|
64862
|
+
const { role, out } = options;
|
|
64863
|
+
if (!VALID_EXPORT_ROLES.includes(role)) {
|
|
64864
|
+
console.error(`[ERROR] Invalid role: "${role}". Valid roles: ${VALID_EXPORT_ROLES.join(", ")}`);
|
|
64865
|
+
process.exit(1);
|
|
64866
|
+
}
|
|
64867
|
+
const stubStory = {
|
|
64868
|
+
id: "EXAMPLE",
|
|
64869
|
+
title: "Example story",
|
|
64870
|
+
description: "Story ID: EXAMPLE. This is a placeholder story used to demonstrate the default prompt.",
|
|
64871
|
+
acceptanceCriteria: ["AC-1: Example criterion"],
|
|
64872
|
+
tags: [],
|
|
64873
|
+
dependencies: [],
|
|
64874
|
+
status: "pending",
|
|
64875
|
+
passes: false,
|
|
64876
|
+
escalations: [],
|
|
64877
|
+
attempts: 0
|
|
64878
|
+
};
|
|
64879
|
+
const prompt = await PromptBuilder.for(role).story(stubStory).build();
|
|
64880
|
+
if (out) {
|
|
64881
|
+
await Bun.write(out, prompt);
|
|
64882
|
+
console.log(`[OK] Exported prompt for "${role}" to ${out}`);
|
|
64883
|
+
} else {
|
|
64884
|
+
console.log(prompt);
|
|
64885
|
+
}
|
|
64886
|
+
}
|
|
64576
64887
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
64577
64888
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
64578
64889
|
PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).build(),
|
|
@@ -66499,7 +66810,8 @@ init_story_context();
|
|
|
66499
66810
|
init_escalation();
|
|
66500
66811
|
init_escalation();
|
|
66501
66812
|
var _runnerDeps = {
|
|
66502
|
-
fireHook
|
|
66813
|
+
fireHook,
|
|
66814
|
+
runParallelExecution: null
|
|
66503
66815
|
};
|
|
66504
66816
|
async function run(options) {
|
|
66505
66817
|
const {
|
|
@@ -66601,7 +66913,7 @@ async function run(options) {
|
|
|
66601
66913
|
await tryLlmBatchRoute(config2, getAllReadyStories(prd), "routing");
|
|
66602
66914
|
}
|
|
66603
66915
|
if (options.parallel !== undefined) {
|
|
66604
|
-
const
|
|
66916
|
+
const runParallelExecution2 = _runnerDeps.runParallelExecution ?? (await Promise.resolve().then(() => (init_parallel_executor(), exports_parallel_executor))).runParallelExecution;
|
|
66605
66917
|
const parallelResult = await runParallelExecution2({
|
|
66606
66918
|
prdPath,
|
|
66607
66919
|
workdir,
|
|
@@ -66626,6 +66938,7 @@ async function run(options) {
|
|
|
66626
66938
|
prd = parallelResult.prd;
|
|
66627
66939
|
totalCost = parallelResult.totalCost;
|
|
66628
66940
|
storiesCompleted = parallelResult.storiesCompleted;
|
|
66941
|
+
allStoryMetrics.push(...parallelResult.storyMetrics);
|
|
66629
66942
|
if (parallelResult.completed && parallelResult.durationMs !== undefined) {
|
|
66630
66943
|
return {
|
|
66631
66944
|
success: true,
|
|
@@ -66656,8 +66969,8 @@ async function run(options) {
|
|
|
66656
66969
|
}, prd);
|
|
66657
66970
|
prd = sequentialResult.prd;
|
|
66658
66971
|
iterations = sequentialResult.iterations;
|
|
66659
|
-
|
|
66660
|
-
|
|
66972
|
+
totalCost += sequentialResult.totalCost;
|
|
66973
|
+
storiesCompleted += sequentialResult.storiesCompleted;
|
|
66661
66974
|
allStoryMetrics.push(...sequentialResult.allStoryMetrics);
|
|
66662
66975
|
if (config2.acceptance.enabled && isComplete(prd)) {
|
|
66663
66976
|
const { runAcceptanceLoop: runAcceptanceLoop2 } = await Promise.resolve().then(() => (init_acceptance_loop(), exports_acceptance_loop));
|
|
@@ -74144,13 +74457,29 @@ Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cu
|
|
|
74144
74457
|
|
|
74145
74458
|
**Note:** Customize this file to match your project's specific needs.
|
|
74146
74459
|
`);
|
|
74460
|
+
try {
|
|
74461
|
+
await promptsInitCommand({
|
|
74462
|
+
workdir,
|
|
74463
|
+
force: options.force,
|
|
74464
|
+
autoWireConfig: false
|
|
74465
|
+
});
|
|
74466
|
+
} catch (err) {
|
|
74467
|
+
console.error(source_default.red(`Failed to initialize templates: ${err.message}`));
|
|
74468
|
+
process.exit(1);
|
|
74469
|
+
}
|
|
74147
74470
|
console.log(source_default.green("\u2705 Initialized nax"));
|
|
74148
74471
|
console.log(source_default.dim(` ${naxDir}/`));
|
|
74149
74472
|
console.log(source_default.dim(" \u251C\u2500\u2500 config.json"));
|
|
74150
74473
|
console.log(source_default.dim(" \u251C\u2500\u2500 context.md"));
|
|
74151
74474
|
console.log(source_default.dim(" \u251C\u2500\u2500 hooks.json"));
|
|
74152
74475
|
console.log(source_default.dim(" \u251C\u2500\u2500 features/"));
|
|
74153
|
-
console.log(source_default.dim(" \
|
|
74476
|
+
console.log(source_default.dim(" \u251C\u2500\u2500 hooks/"));
|
|
74477
|
+
console.log(source_default.dim(" \u2514\u2500\u2500 templates/"));
|
|
74478
|
+
console.log(source_default.dim(" \u251C\u2500\u2500 test-writer.md"));
|
|
74479
|
+
console.log(source_default.dim(" \u251C\u2500\u2500 implementer.md"));
|
|
74480
|
+
console.log(source_default.dim(" \u251C\u2500\u2500 verifier.md"));
|
|
74481
|
+
console.log(source_default.dim(" \u251C\u2500\u2500 single-session.md"));
|
|
74482
|
+
console.log(source_default.dim(" \u2514\u2500\u2500 tdd-simple.md"));
|
|
74154
74483
|
console.log(source_default.dim(`
|
|
74155
74484
|
Next: nax features create <name>`));
|
|
74156
74485
|
});
|
|
@@ -74611,7 +74940,7 @@ program2.command("accept").description("Override failed acceptance criteria").re
|
|
|
74611
74940
|
process.exit(1);
|
|
74612
74941
|
}
|
|
74613
74942
|
});
|
|
74614
|
-
program2.command("prompts").description("Assemble or initialize prompts").option("-f, --feature <name>", "Feature name (required unless using --init)").option("--init", "Initialize default prompt templates", false).option("--force", "Overwrite existing template files", false).option("--story <id>", "Filter to a single story ID (e.g., US-003)").option("--out <
|
|
74943
|
+
program2.command("prompts").description("Assemble or initialize prompts").option("-f, --feature <name>", "Feature name (required unless using --init or --export)").option("--init", "Initialize default prompt templates", false).option("--export <role>", "Export default prompt for a role to stdout or --out file").option("--force", "Overwrite existing template files", false).option("--story <id>", "Filter to a single story ID (e.g., US-003)").option("--out <path>", "Output file path for --export, or directory for regular prompts (default: stdout)").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
74615
74944
|
let workdir;
|
|
74616
74945
|
try {
|
|
74617
74946
|
workdir = validateDirectory(options.dir);
|
|
@@ -74631,8 +74960,20 @@ program2.command("prompts").description("Assemble or initialize prompts").option
|
|
|
74631
74960
|
}
|
|
74632
74961
|
return;
|
|
74633
74962
|
}
|
|
74963
|
+
if (options.export) {
|
|
74964
|
+
try {
|
|
74965
|
+
await exportPromptCommand({
|
|
74966
|
+
role: options.export,
|
|
74967
|
+
out: options.out
|
|
74968
|
+
});
|
|
74969
|
+
} catch (err) {
|
|
74970
|
+
console.error(source_default.red(`Error: ${err.message}`));
|
|
74971
|
+
process.exit(1);
|
|
74972
|
+
}
|
|
74973
|
+
return;
|
|
74974
|
+
}
|
|
74634
74975
|
if (!options.feature) {
|
|
74635
|
-
console.error(source_default.red("Error: --feature is required (unless using --init)"));
|
|
74976
|
+
console.error(source_default.red("Error: --feature is required (unless using --init or --export)"));
|
|
74636
74977
|
process.exit(1);
|
|
74637
74978
|
}
|
|
74638
74979
|
const config2 = await loadConfig(workdir);
|