@nathapp/nax 0.36.0 → 0.36.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 +543 -154
- package/package.json +1 -1
- package/src/agents/claude-decompose.ts +3 -3
- package/src/cli/constitution.ts +0 -92
- package/src/constitution/generator.ts +0 -33
- package/src/constitution/index.ts +2 -1
- package/src/constitution/loader.ts +1 -13
- package/src/context/builder.ts +1 -2
- package/src/context/elements.ts +1 -12
- package/src/context/index.ts +2 -1
- package/src/context/test-scanner.ts +1 -1
- 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/interaction/chain.ts +17 -1
- 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/execution.ts +23 -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/prompts/builder.ts +13 -6
- package/src/prompts/sections/conventions.ts +5 -7
- package/src/prompts/sections/isolation.ts +7 -7
- package/src/prompts/sections/role-task.ts +64 -64
- package/src/review/orchestrator.ts +11 -1
- package/src/routing/strategies/llm-prompts.ts +1 -1
- package/src/routing/strategies/llm.ts +3 -3
- package/src/tdd/index.ts +2 -3
- package/src/tdd/isolation.ts +0 -13
- package/src/tdd/orchestrator.ts +5 -0
- package/src/tdd/prompts.ts +1 -231
- package/src/tdd/session-runner.ts +2 -0
- package/src/tdd/types.ts +2 -1
- package/src/tdd/verdict.ts +20 -2
- package/src/verification/crash-detector.ts +34 -0
- package/src/verification/orchestrator-types.ts +8 -1
- package/src/verification/parser.ts +0 -10
- package/src/verification/rectification-loop.ts +2 -51
- package/src/worktree/dispatcher.ts +0 -59
package/dist/nax.js
CHANGED
|
@@ -3914,7 +3914,7 @@ ${output.slice(0, 500)}`);
|
|
|
3914
3914
|
acceptanceCriteria: Array.isArray(record.acceptanceCriteria) ? record.acceptanceCriteria : ["Implementation complete"],
|
|
3915
3915
|
tags: Array.isArray(record.tags) ? record.tags : [],
|
|
3916
3916
|
dependencies: Array.isArray(record.dependencies) ? record.dependencies : [],
|
|
3917
|
-
complexity:
|
|
3917
|
+
complexity: coerceComplexity(record.complexity),
|
|
3918
3918
|
contextFiles: Array.isArray(record.contextFiles) ? record.contextFiles : Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
|
|
3919
3919
|
relevantFiles: Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
|
|
3920
3920
|
reasoning: String(record.reasoning || "No reasoning provided"),
|
|
@@ -3928,7 +3928,7 @@ ${output.slice(0, 500)}`);
|
|
|
3928
3928
|
}
|
|
3929
3929
|
return stories;
|
|
3930
3930
|
}
|
|
3931
|
-
function
|
|
3931
|
+
function coerceComplexity(value) {
|
|
3932
3932
|
if (value === "simple" || value === "medium" || value === "complex" || value === "expert") {
|
|
3933
3933
|
return value;
|
|
3934
3934
|
}
|
|
@@ -19471,7 +19471,7 @@ Your complexity classification will determine the execution strategy:
|
|
|
19471
19471
|
Respond with ONLY this JSON (no markdown, no explanation):
|
|
19472
19472
|
{"complexity":"simple|medium|complex|expert","modelTier":"fast|balanced|powerful","reasoning":"<one line>"}`;
|
|
19473
19473
|
}
|
|
19474
|
-
function
|
|
19474
|
+
function buildBatchRoutingPrompt(stories, config2) {
|
|
19475
19475
|
const storyBlocks = stories.map((story, idx) => {
|
|
19476
19476
|
const criteria = story.acceptanceCriteria.map((c, i) => ` ${i + 1}. ${c}`).join(`
|
|
19477
19477
|
`);
|
|
@@ -19651,7 +19651,7 @@ async function routeBatch(stories, context) {
|
|
|
19651
19651
|
throw new Error("No agent adapter available for batch routing (AA-003)");
|
|
19652
19652
|
}
|
|
19653
19653
|
const modelTier = llmConfig.model ?? "fast";
|
|
19654
|
-
const prompt =
|
|
19654
|
+
const prompt = buildBatchRoutingPrompt(stories, config2);
|
|
19655
19655
|
try {
|
|
19656
19656
|
const output = await callLlm(adapter, modelTier, prompt, config2);
|
|
19657
19657
|
const decisions = parseBatchResponse(output, stories, config2);
|
|
@@ -20063,6 +20063,22 @@ function computeStoryContentHash(story) {
|
|
|
20063
20063
|
}
|
|
20064
20064
|
|
|
20065
20065
|
// src/routing/index.ts
|
|
20066
|
+
var exports_routing = {};
|
|
20067
|
+
__export(exports_routing, {
|
|
20068
|
+
tryLlmBatchRoute: () => tryLlmBatchRoute,
|
|
20069
|
+
routeTask: () => routeTask,
|
|
20070
|
+
routeStory: () => routeStory,
|
|
20071
|
+
manualStrategy: () => manualStrategy,
|
|
20072
|
+
loadCustomStrategy: () => loadCustomStrategy,
|
|
20073
|
+
llmStrategy: () => llmStrategy,
|
|
20074
|
+
keywordStrategy: () => keywordStrategy,
|
|
20075
|
+
determineTestStrategy: () => determineTestStrategy2,
|
|
20076
|
+
computeStoryContentHash: () => computeStoryContentHash,
|
|
20077
|
+
complexityToModelTier: () => complexityToModelTier2,
|
|
20078
|
+
classifyComplexity: () => classifyComplexity2,
|
|
20079
|
+
buildStrategyChain: () => buildStrategyChain,
|
|
20080
|
+
StrategyChain: () => StrategyChain
|
|
20081
|
+
});
|
|
20066
20082
|
var init_routing = __esm(() => {
|
|
20067
20083
|
init_router();
|
|
20068
20084
|
init_chain();
|
|
@@ -20073,7 +20089,7 @@ var init_routing = __esm(() => {
|
|
|
20073
20089
|
});
|
|
20074
20090
|
|
|
20075
20091
|
// src/decompose/validators/complexity.ts
|
|
20076
|
-
function
|
|
20092
|
+
function validateComplexity(substories, maxComplexity) {
|
|
20077
20093
|
const errors3 = [];
|
|
20078
20094
|
const warnings = [];
|
|
20079
20095
|
const maxOrder = COMPLEXITY_ORDER[maxComplexity];
|
|
@@ -20385,7 +20401,7 @@ function runAllValidators(originalStory, substories, existingStories, config2) {
|
|
|
20385
20401
|
const results = [
|
|
20386
20402
|
validateOverlap(substories, existingStories),
|
|
20387
20403
|
validateCoverage(originalStory, substories),
|
|
20388
|
-
|
|
20404
|
+
validateComplexity(substories, maxComplexity),
|
|
20389
20405
|
validateDependencies(substories, existingIds)
|
|
20390
20406
|
];
|
|
20391
20407
|
const errors3 = results.flatMap((r) => r.errors);
|
|
@@ -20657,7 +20673,7 @@ var package_default;
|
|
|
20657
20673
|
var init_package = __esm(() => {
|
|
20658
20674
|
package_default = {
|
|
20659
20675
|
name: "@nathapp/nax",
|
|
20660
|
-
version: "0.36.
|
|
20676
|
+
version: "0.36.2",
|
|
20661
20677
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
20662
20678
|
type: "module",
|
|
20663
20679
|
bin: {
|
|
@@ -20718,8 +20734,8 @@ var init_version = __esm(() => {
|
|
|
20718
20734
|
NAX_VERSION = package_default.version;
|
|
20719
20735
|
NAX_COMMIT = (() => {
|
|
20720
20736
|
try {
|
|
20721
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
20722
|
-
return "
|
|
20737
|
+
if (/^[0-9a-f]{6,10}$/.test("eb77e7d"))
|
|
20738
|
+
return "eb77e7d";
|
|
20723
20739
|
} catch {}
|
|
20724
20740
|
try {
|
|
20725
20741
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -20791,9 +20807,10 @@ function collectStoryMetrics(ctx, storyStartTime) {
|
|
|
20791
20807
|
const routing = ctx.routing;
|
|
20792
20808
|
const agentResult = ctx.agentResult;
|
|
20793
20809
|
const escalationCount = story.escalations?.length || 0;
|
|
20794
|
-
const
|
|
20810
|
+
const priorFailureCount = story.priorFailures?.length || 0;
|
|
20811
|
+
const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
|
|
20795
20812
|
const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
|
|
20796
|
-
const firstPassSuccess = agentResult?.success === true && escalationCount === 0;
|
|
20813
|
+
const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
|
|
20797
20814
|
const modelEntry = ctx.config.models[routing.modelTier];
|
|
20798
20815
|
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
20799
20816
|
const modelUsed = modelDef?.model || routing.modelTier;
|
|
@@ -20809,12 +20826,13 @@ function collectStoryMetrics(ctx, storyStartTime) {
|
|
|
20809
20826
|
attempts,
|
|
20810
20827
|
finalTier,
|
|
20811
20828
|
success: agentResult?.success || false,
|
|
20812
|
-
cost: agentResult?.estimatedCost || 0,
|
|
20829
|
+
cost: (ctx.accumulatedAttemptCost ?? 0) + (agentResult?.estimatedCost || 0),
|
|
20813
20830
|
durationMs: agentResult?.durationMs || 0,
|
|
20814
20831
|
firstPassSuccess,
|
|
20815
20832
|
startedAt: storyStartTime,
|
|
20816
20833
|
completedAt: new Date().toISOString(),
|
|
20817
|
-
fullSuiteGatePassed
|
|
20834
|
+
fullSuiteGatePassed,
|
|
20835
|
+
runtimeCrashes: ctx.storyRuntimeCrashes ?? 0
|
|
20818
20836
|
};
|
|
20819
20837
|
}
|
|
20820
20838
|
function collectBatchMetrics(ctx, storyStartTime) {
|
|
@@ -20844,7 +20862,8 @@ function collectBatchMetrics(ctx, storyStartTime) {
|
|
|
20844
20862
|
firstPassSuccess: true,
|
|
20845
20863
|
startedAt: storyStartTime,
|
|
20846
20864
|
completedAt: new Date().toISOString(),
|
|
20847
|
-
fullSuiteGatePassed: false
|
|
20865
|
+
fullSuiteGatePassed: false,
|
|
20866
|
+
runtimeCrashes: 0
|
|
20848
20867
|
};
|
|
20849
20868
|
});
|
|
20850
20869
|
}
|
|
@@ -21092,6 +21111,12 @@ class InteractionChain {
|
|
|
21092
21111
|
async prompt(request) {
|
|
21093
21112
|
await this.send(request);
|
|
21094
21113
|
const response = await this.receive(request.id, request.timeout);
|
|
21114
|
+
if (response.action === "choose" && response.value && request.options) {
|
|
21115
|
+
const matched = request.options.find((o) => o.key === response.value);
|
|
21116
|
+
if (matched) {
|
|
21117
|
+
return { ...response, action: matched.key };
|
|
21118
|
+
}
|
|
21119
|
+
}
|
|
21095
21120
|
return response;
|
|
21096
21121
|
}
|
|
21097
21122
|
async cancel(requestId) {
|
|
@@ -22221,6 +22246,11 @@ var init_interaction = __esm(() => {
|
|
|
22221
22246
|
});
|
|
22222
22247
|
|
|
22223
22248
|
// src/pipeline/runner.ts
|
|
22249
|
+
var exports_runner = {};
|
|
22250
|
+
__export(exports_runner, {
|
|
22251
|
+
runPipeline: () => runPipeline,
|
|
22252
|
+
MAX_STAGE_RETRIES: () => MAX_STAGE_RETRIES
|
|
22253
|
+
});
|
|
22224
22254
|
async function runPipeline(stages, context, eventEmitter) {
|
|
22225
22255
|
const logger = getLogger();
|
|
22226
22256
|
const retryCountMap = new Map;
|
|
@@ -22681,9 +22711,17 @@ class ReviewOrchestrator {
|
|
|
22681
22711
|
const changedFiles = await getChangedFiles(workdir);
|
|
22682
22712
|
const pluginResults = [];
|
|
22683
22713
|
for (const reviewer of reviewers) {
|
|
22684
|
-
logger?.info("review", `Running plugin reviewer: ${reviewer.name}
|
|
22714
|
+
logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
|
|
22715
|
+
changedFiles: changedFiles.length
|
|
22716
|
+
});
|
|
22685
22717
|
try {
|
|
22686
22718
|
const result = await reviewer.check(workdir, changedFiles);
|
|
22719
|
+
logger?.info("review", `Plugin reviewer result: ${reviewer.name}`, {
|
|
22720
|
+
passed: result.passed,
|
|
22721
|
+
exitCode: result.exitCode,
|
|
22722
|
+
output: result.output?.slice(0, 500),
|
|
22723
|
+
findings: result.findings?.length ?? 0
|
|
22724
|
+
});
|
|
22687
22725
|
pluginResults.push({
|
|
22688
22726
|
name: reviewer.name,
|
|
22689
22727
|
passed: result.passed,
|
|
@@ -22702,6 +22740,7 @@ class ReviewOrchestrator {
|
|
|
22702
22740
|
}
|
|
22703
22741
|
} catch (error48) {
|
|
22704
22742
|
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
22743
|
+
logger?.warn("review", `Plugin reviewer threw error: ${reviewer.name}`, { error: errorMsg });
|
|
22705
22744
|
pluginResults.push({ name: reviewer.name, passed: false, output: "", error: errorMsg });
|
|
22706
22745
|
builtIn.pluginReviewers = pluginResults;
|
|
22707
22746
|
return {
|
|
@@ -22931,7 +22970,7 @@ var init_completion = __esm(() => {
|
|
|
22931
22970
|
storyId: completedStory.id,
|
|
22932
22971
|
story: completedStory,
|
|
22933
22972
|
passed: true,
|
|
22934
|
-
|
|
22973
|
+
runElapsedMs: storyMetric?.durationMs ?? 0,
|
|
22935
22974
|
cost: costPerStory,
|
|
22936
22975
|
modelTier: ctx.routing?.modelTier,
|
|
22937
22976
|
testStrategy: ctx.routing?.testStrategy
|
|
@@ -22959,12 +22998,14 @@ var init_completion = __esm(() => {
|
|
|
22959
22998
|
};
|
|
22960
22999
|
});
|
|
22961
23000
|
|
|
23001
|
+
// src/optimizer/types.ts
|
|
23002
|
+
function estimateTokens(text) {
|
|
23003
|
+
return Math.ceil(text.length / 4);
|
|
23004
|
+
}
|
|
23005
|
+
|
|
22962
23006
|
// src/constitution/loader.ts
|
|
22963
23007
|
import { existsSync as existsSync13 } from "fs";
|
|
22964
23008
|
import { join as join14 } from "path";
|
|
22965
|
-
function estimateTokens(text) {
|
|
22966
|
-
return Math.ceil(text.length / 3);
|
|
22967
|
-
}
|
|
22968
23009
|
function truncateToTokens(text, maxTokens) {
|
|
22969
23010
|
const maxChars = maxTokens * 3;
|
|
22970
23011
|
if (text.length <= maxChars) {
|
|
@@ -23214,32 +23255,29 @@ var init_auto_detect = __esm(() => {
|
|
|
23214
23255
|
});
|
|
23215
23256
|
|
|
23216
23257
|
// src/context/elements.ts
|
|
23217
|
-
function estimateTokens2(text) {
|
|
23218
|
-
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
23219
|
-
}
|
|
23220
23258
|
function createStoryContext(story, priority) {
|
|
23221
23259
|
const content = formatStoryAsText(story);
|
|
23222
|
-
return { type: "story", storyId: story.id, content, priority, tokens:
|
|
23260
|
+
return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
23223
23261
|
}
|
|
23224
23262
|
function createDependencyContext(story, priority) {
|
|
23225
23263
|
const content = formatStoryAsText(story);
|
|
23226
|
-
return { type: "dependency", storyId: story.id, content, priority, tokens:
|
|
23264
|
+
return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
23227
23265
|
}
|
|
23228
23266
|
function createErrorContext(errorMessage, priority) {
|
|
23229
|
-
return { type: "error", content: errorMessage, priority, tokens:
|
|
23267
|
+
return { type: "error", content: errorMessage, priority, tokens: estimateTokens(errorMessage) };
|
|
23230
23268
|
}
|
|
23231
23269
|
function createProgressContext(progressText, priority) {
|
|
23232
|
-
return { type: "progress", content: progressText, priority, tokens:
|
|
23270
|
+
return { type: "progress", content: progressText, priority, tokens: estimateTokens(progressText) };
|
|
23233
23271
|
}
|
|
23234
23272
|
function createFileContext(filePath, content, priority) {
|
|
23235
|
-
return { type: "file", filePath, content, priority, tokens:
|
|
23273
|
+
return { type: "file", filePath, content, priority, tokens: estimateTokens(content) };
|
|
23236
23274
|
}
|
|
23237
23275
|
function createTestCoverageContext(content, tokens, priority) {
|
|
23238
23276
|
return { type: "test-coverage", content, priority, tokens };
|
|
23239
23277
|
}
|
|
23240
23278
|
function createPriorFailuresContext(failures, priority) {
|
|
23241
23279
|
const content = formatPriorFailures(failures);
|
|
23242
|
-
return { type: "prior-failures", content, priority, tokens:
|
|
23280
|
+
return { type: "prior-failures", content, priority, tokens: estimateTokens(content) };
|
|
23243
23281
|
}
|
|
23244
23282
|
function formatPriorFailures(failures) {
|
|
23245
23283
|
if (!failures || failures.length === 0) {
|
|
@@ -23310,7 +23348,6 @@ function formatStoryAsText(story) {
|
|
|
23310
23348
|
return parts.join(`
|
|
23311
23349
|
`);
|
|
23312
23350
|
}
|
|
23313
|
-
var CHARS_PER_TOKEN = 3;
|
|
23314
23351
|
var init_elements = __esm(() => {
|
|
23315
23352
|
init_logger2();
|
|
23316
23353
|
});
|
|
@@ -23467,7 +23504,7 @@ function truncateToTokenBudget(files, maxTokens, preferredDetail) {
|
|
|
23467
23504
|
for (let i = startIndex;i < detailLevels.length; i++) {
|
|
23468
23505
|
const detail = detailLevels[i];
|
|
23469
23506
|
const summary = formatTestSummary(files, detail);
|
|
23470
|
-
const tokens =
|
|
23507
|
+
const tokens = estimateTokens(summary);
|
|
23471
23508
|
if (tokens <= maxTokens) {
|
|
23472
23509
|
return { summary, detail, truncated: i !== startIndex };
|
|
23473
23510
|
}
|
|
@@ -23477,7 +23514,7 @@ function truncateToTokenBudget(files, maxTokens, preferredDetail) {
|
|
|
23477
23514
|
truncatedFiles = truncatedFiles.slice(0, truncatedFiles.length - 1);
|
|
23478
23515
|
const summary = `${formatTestSummary(truncatedFiles, "names-only")}
|
|
23479
23516
|
... and ${files.length - truncatedFiles.length} more test files`;
|
|
23480
|
-
if (
|
|
23517
|
+
if (estimateTokens(summary) <= maxTokens) {
|
|
23481
23518
|
return { summary, detail: "names-only", truncated: true };
|
|
23482
23519
|
}
|
|
23483
23520
|
}
|
|
@@ -23500,13 +23537,12 @@ async function generateTestCoverageSummary(options) {
|
|
|
23500
23537
|
}
|
|
23501
23538
|
const totalTests = files.reduce((sum, f) => sum + f.testCount, 0);
|
|
23502
23539
|
const { summary } = truncateToTokenBudget(files, maxTokens, detail);
|
|
23503
|
-
const tokens =
|
|
23540
|
+
const tokens = estimateTokens(summary);
|
|
23504
23541
|
return { files, totalTests, summary, tokens };
|
|
23505
23542
|
}
|
|
23506
23543
|
var COMMON_TEST_DIRS;
|
|
23507
23544
|
var init_test_scanner = __esm(() => {
|
|
23508
23545
|
init_logger2();
|
|
23509
|
-
init_builder3();
|
|
23510
23546
|
COMMON_TEST_DIRS = ["test", "tests", "__tests__", "src/__tests__", "spec"];
|
|
23511
23547
|
});
|
|
23512
23548
|
|
|
@@ -24758,7 +24794,7 @@ var init_cleanup = __esm(() => {
|
|
|
24758
24794
|
});
|
|
24759
24795
|
|
|
24760
24796
|
// src/tdd/prompts.ts
|
|
24761
|
-
function buildImplementerRectificationPrompt(failures, story,
|
|
24797
|
+
function buildImplementerRectificationPrompt(failures, story, _contextMarkdown, config2) {
|
|
24762
24798
|
return createRectificationPrompt(failures, story, config2);
|
|
24763
24799
|
}
|
|
24764
24800
|
var init_prompts = __esm(() => {
|
|
@@ -24908,9 +24944,9 @@ var init_rectification_gate = __esm(() => {
|
|
|
24908
24944
|
function buildConventionsSection() {
|
|
24909
24945
|
return `# Conventions
|
|
24910
24946
|
|
|
24911
|
-
|
|
24947
|
+
Follow existing code patterns and conventions. Write idiomatic, maintainable code.
|
|
24912
24948
|
|
|
24913
|
-
|
|
24949
|
+
Commit your changes when done using conventional commit format (e.g. \`feat:\`, \`fix:\`, \`test:\`).`;
|
|
24914
24950
|
}
|
|
24915
24951
|
|
|
24916
24952
|
// src/prompts/sections/isolation.ts
|
|
@@ -24919,29 +24955,39 @@ function buildIsolationSection(roleOrMode, mode) {
|
|
|
24919
24955
|
return buildIsolationSection("test-writer", roleOrMode);
|
|
24920
24956
|
}
|
|
24921
24957
|
const role = roleOrMode;
|
|
24922
|
-
const header =
|
|
24923
|
-
|
|
24924
|
-
`;
|
|
24958
|
+
const header = "# Isolation Rules";
|
|
24925
24959
|
const footer = `
|
|
24926
24960
|
|
|
24927
24961
|
${TEST_FILTER_RULE}`;
|
|
24928
24962
|
if (role === "test-writer") {
|
|
24929
24963
|
const m = mode ?? "strict";
|
|
24930
24964
|
if (m === "strict") {
|
|
24931
|
-
return `${header}
|
|
24965
|
+
return `${header}
|
|
24966
|
+
|
|
24967
|
+
isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
|
|
24932
24968
|
}
|
|
24933
|
-
return `${header}
|
|
24969
|
+
return `${header}
|
|
24970
|
+
|
|
24971
|
+
isolation scope: Create test files in test/. MAY read src/ files and MAY import from src/ to ensure correct types/interfaces. May create minimal stubs in src/ if needed to make imports work, but do NOT implement real logic.${footer}`;
|
|
24934
24972
|
}
|
|
24935
24973
|
if (role === "implementer") {
|
|
24936
|
-
return `${header}
|
|
24974
|
+
return `${header}
|
|
24975
|
+
|
|
24976
|
+
isolation scope: Implement source code in src/ to make tests pass. Do not modify test files. Run tests frequently to track progress.${footer}`;
|
|
24937
24977
|
}
|
|
24938
24978
|
if (role === "verifier") {
|
|
24939
|
-
return `${header}
|
|
24979
|
+
return `${header}
|
|
24980
|
+
|
|
24981
|
+
isolation scope: Read-only inspection. Review all test results, implementation code, and acceptance criteria compliance. You MAY write a verdict file (.nax-verifier-verdict.json) and apply legitimate fixes if needed.${footer}`;
|
|
24940
24982
|
}
|
|
24941
24983
|
if (role === "single-session") {
|
|
24942
|
-
return `${header}
|
|
24984
|
+
return `${header}
|
|
24985
|
+
|
|
24986
|
+
isolation scope: Create test files in test/ directory, then implement source code in src/ to make tests pass. Both directories are in scope for this session.${footer}`;
|
|
24943
24987
|
}
|
|
24944
|
-
return `${header}
|
|
24988
|
+
return `${header}
|
|
24989
|
+
|
|
24990
|
+
isolation scope: You may modify both src/ and test/ files. Write failing tests FIRST, then implement to make them pass.`;
|
|
24945
24991
|
}
|
|
24946
24992
|
var TEST_FILTER_RULE;
|
|
24947
24993
|
var init_isolation2 = __esm(() => {
|
|
@@ -24959,76 +25005,76 @@ function buildRoleTaskSection(roleOrVariant, variant) {
|
|
|
24959
25005
|
if (v === "standard") {
|
|
24960
25006
|
return `# Role: Implementer
|
|
24961
25007
|
|
|
24962
|
-
|
|
25008
|
+
Your task: make failing tests pass.
|
|
24963
25009
|
|
|
24964
|
-
|
|
24965
|
-
|
|
24966
|
-
|
|
24967
|
-
|
|
24968
|
-
|
|
24969
|
-
|
|
25010
|
+
Instructions:
|
|
25011
|
+
- Implement source code in src/ to make tests pass
|
|
25012
|
+
- Do NOT modify test files
|
|
25013
|
+
- Run tests frequently to track progress
|
|
25014
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25015
|
+
- Goal: all tests green, all changes committed`;
|
|
24970
25016
|
}
|
|
24971
25017
|
return `# Role: Implementer (Lite)
|
|
24972
25018
|
|
|
24973
|
-
|
|
25019
|
+
Your task: Write tests AND implement the feature in a single session.
|
|
24974
25020
|
|
|
24975
|
-
|
|
24976
|
-
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
25021
|
+
Instructions:
|
|
25022
|
+
- Write tests first (test/ directory), then implement (src/ directory)
|
|
25023
|
+
- All tests must pass by the end
|
|
25024
|
+
- Use Bun test (describe/test/expect)
|
|
25025
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25026
|
+
- Goal: all tests green, all criteria met, all changes committed`;
|
|
24981
25027
|
}
|
|
24982
25028
|
if (role === "test-writer") {
|
|
24983
25029
|
return `# Role: Test-Writer
|
|
24984
25030
|
|
|
24985
|
-
|
|
25031
|
+
Your task: Write comprehensive failing tests for the feature.
|
|
24986
25032
|
|
|
24987
|
-
|
|
24988
|
-
|
|
24989
|
-
|
|
24990
|
-
|
|
24991
|
-
|
|
24992
|
-
|
|
24993
|
-
|
|
25033
|
+
Instructions:
|
|
25034
|
+
- Create test files in test/ directory that cover acceptance criteria
|
|
25035
|
+
- Tests must fail initially (RED phase) \u2014 the feature is not yet implemented
|
|
25036
|
+
- Use Bun test (describe/test/expect)
|
|
25037
|
+
- Write clear test names that document expected behavior
|
|
25038
|
+
- Focus on behavior, not implementation details
|
|
25039
|
+
- Goal: comprehensive test suite ready for implementation`;
|
|
24994
25040
|
}
|
|
24995
25041
|
if (role === "verifier") {
|
|
24996
25042
|
return `# Role: Verifier
|
|
24997
25043
|
|
|
24998
|
-
|
|
25044
|
+
Your task: Review and verify the implementation against acceptance criteria.
|
|
24999
25045
|
|
|
25000
|
-
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
25006
|
-
|
|
25046
|
+
Instructions:
|
|
25047
|
+
- Review all test results \u2014 verify tests pass
|
|
25048
|
+
- Check that implementation meets all acceptance criteria
|
|
25049
|
+
- Inspect code quality, error handling, and edge cases
|
|
25050
|
+
- Verify test modifications (if any) are legitimate fixes
|
|
25051
|
+
- Write a detailed verdict with reasoning
|
|
25052
|
+
- Goal: provide comprehensive verification and quality assurance`;
|
|
25007
25053
|
}
|
|
25008
25054
|
if (role === "single-session") {
|
|
25009
25055
|
return `# Role: Single-Session
|
|
25010
25056
|
|
|
25011
|
-
|
|
25057
|
+
Your task: Write tests AND implement the feature in a single focused session.
|
|
25012
25058
|
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
25019
|
-
|
|
25059
|
+
Instructions:
|
|
25060
|
+
- Phase 1: Write comprehensive tests (test/ directory)
|
|
25061
|
+
- Phase 2: Implement to make all tests pass (src/ directory)
|
|
25062
|
+
- Use Bun test (describe/test/expect)
|
|
25063
|
+
- Run tests frequently throughout implementation
|
|
25064
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25065
|
+
- Goal: all tests passing, all changes committed, full story complete`;
|
|
25020
25066
|
}
|
|
25021
25067
|
return `# Role: TDD-Simple
|
|
25022
25068
|
|
|
25023
|
-
|
|
25069
|
+
Your task: Write failing tests FIRST, then implement to make them pass.
|
|
25024
25070
|
|
|
25025
|
-
|
|
25026
|
-
|
|
25027
|
-
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
25071
|
+
Instructions:
|
|
25072
|
+
- RED phase: Write failing tests FIRST for the acceptance criteria
|
|
25073
|
+
- RED phase: Run the tests to confirm they fail
|
|
25074
|
+
- GREEN phase: Implement the minimum code to make tests pass
|
|
25075
|
+
- REFACTOR phase: Refactor while keeping tests green
|
|
25076
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25077
|
+
- Goal: all tests passing, feature complete, all changes committed`;
|
|
25032
25078
|
}
|
|
25033
25079
|
|
|
25034
25080
|
// src/prompts/sections/story.ts
|
|
@@ -25046,6 +25092,69 @@ ${story.description}
|
|
|
25046
25092
|
${criteria}`;
|
|
25047
25093
|
}
|
|
25048
25094
|
|
|
25095
|
+
// src/prompts/sections/verdict.ts
|
|
25096
|
+
function buildVerdictSection(story) {
|
|
25097
|
+
return `# Verdict Instructions
|
|
25098
|
+
|
|
25099
|
+
## Write Verdict File
|
|
25100
|
+
|
|
25101
|
+
After completing your verification, you **MUST** write a verdict file at the **project root**:
|
|
25102
|
+
|
|
25103
|
+
**File:** \`.nax-verifier-verdict.json\`
|
|
25104
|
+
|
|
25105
|
+
Set \`approved: true\` when ALL of these conditions are met:
|
|
25106
|
+
- All tests pass
|
|
25107
|
+
- Implementation is clean and follows conventions
|
|
25108
|
+
- All acceptance criteria met
|
|
25109
|
+
- Any test modifications by implementer are legitimate fixes
|
|
25110
|
+
|
|
25111
|
+
Set \`approved: false\` when ANY of these conditions are true:
|
|
25112
|
+
- Tests are failing and you cannot fix them
|
|
25113
|
+
- The implementer loosened test assertions to mask bugs
|
|
25114
|
+
- Critical acceptance criteria are not met
|
|
25115
|
+
- Code quality is poor (security issues, severe bugs, etc.)
|
|
25116
|
+
|
|
25117
|
+
**Full JSON schema example** (fill in all fields with real values):
|
|
25118
|
+
|
|
25119
|
+
\`\`\`json
|
|
25120
|
+
{
|
|
25121
|
+
"version": 1,
|
|
25122
|
+
"approved": true,
|
|
25123
|
+
"tests": {
|
|
25124
|
+
"allPassing": true,
|
|
25125
|
+
"passCount": 42,
|
|
25126
|
+
"failCount": 0
|
|
25127
|
+
},
|
|
25128
|
+
"testModifications": {
|
|
25129
|
+
"detected": false,
|
|
25130
|
+
"files": [],
|
|
25131
|
+
"legitimate": true,
|
|
25132
|
+
"reasoning": "No test files were modified by the implementer"
|
|
25133
|
+
},
|
|
25134
|
+
"acceptanceCriteria": {
|
|
25135
|
+
"allMet": true,
|
|
25136
|
+
"criteria": [
|
|
25137
|
+
{ "criterion": "Example criterion", "met": true }
|
|
25138
|
+
]
|
|
25139
|
+
},
|
|
25140
|
+
"quality": {
|
|
25141
|
+
"rating": "good",
|
|
25142
|
+
"issues": []
|
|
25143
|
+
},
|
|
25144
|
+
"fixes": [],
|
|
25145
|
+
"reasoning": "All tests pass, implementation is clean, all acceptance criteria are met."
|
|
25146
|
+
}
|
|
25147
|
+
\`\`\`
|
|
25148
|
+
|
|
25149
|
+
**Field notes:**
|
|
25150
|
+
- \`quality.rating\` must be one of: \`"good"\`, \`"acceptable"\`, \`"poor"\`
|
|
25151
|
+
- \`testModifications.files\` \u2014 list any test files the implementer changed
|
|
25152
|
+
- \`fixes\` \u2014 list any fixes you applied yourself during this verification session
|
|
25153
|
+
- \`reasoning\` \u2014 brief summary of your overall assessment
|
|
25154
|
+
|
|
25155
|
+
When done, commit any fixes with message: "fix: verify and adjust ${story.title}"`;
|
|
25156
|
+
}
|
|
25157
|
+
|
|
25049
25158
|
// src/prompts/loader.ts
|
|
25050
25159
|
var exports_loader = {};
|
|
25051
25160
|
__export(exports_loader, {
|
|
@@ -25121,6 +25230,9 @@ ${this._constitution}`);
|
|
|
25121
25230
|
if (this._story) {
|
|
25122
25231
|
sections.push(buildStorySection(this._story));
|
|
25123
25232
|
}
|
|
25233
|
+
if (this._role === "verifier" && this._story) {
|
|
25234
|
+
sections.push(buildVerdictSection(this._story));
|
|
25235
|
+
}
|
|
25124
25236
|
const isolation = this._options.isolation;
|
|
25125
25237
|
sections.push(buildIsolationSection(this._role, isolation));
|
|
25126
25238
|
if (this._contextMd) {
|
|
@@ -25209,7 +25321,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
25209
25321
|
}
|
|
25210
25322
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
25211
25323
|
}
|
|
25212
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false) {
|
|
25324
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution) {
|
|
25213
25325
|
const startTime = Date.now();
|
|
25214
25326
|
let prompt;
|
|
25215
25327
|
switch (role) {
|
|
@@ -25217,7 +25329,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
25217
25329
|
prompt = await PromptBuilder.for("test-writer", { isolation: lite ? "lite" : "strict" }).withLoader(workdir, config2).story(story).context(contextMarkdown).build();
|
|
25218
25330
|
break;
|
|
25219
25331
|
case "implementer":
|
|
25220
|
-
prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" }).withLoader(workdir, config2).story(story).context(contextMarkdown).build();
|
|
25332
|
+
prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" }).withLoader(workdir, config2).story(story).context(contextMarkdown).constitution(constitution).build();
|
|
25221
25333
|
break;
|
|
25222
25334
|
case "verifier":
|
|
25223
25335
|
prompt = await PromptBuilder.for("verifier").withLoader(workdir, config2).story(story).context(contextMarkdown).build();
|
|
@@ -25361,7 +25473,7 @@ function isValidVerdict(obj) {
|
|
|
25361
25473
|
function coerceVerdict(obj) {
|
|
25362
25474
|
try {
|
|
25363
25475
|
const verdictStr = String(obj.verdict ?? "").toUpperCase();
|
|
25364
|
-
const approved = verdictStr === "PASS" || verdictStr === "APPROVED" || obj.approved === true;
|
|
25476
|
+
const approved = verdictStr === "PASS" || verdictStr === "APPROVED" || verdictStr.startsWith("VERIFIED") || verdictStr.includes("ALL ACCEPTANCE CRITERIA MET") || obj.approved === true;
|
|
25365
25477
|
let passCount = 0;
|
|
25366
25478
|
let failCount = 0;
|
|
25367
25479
|
let allPassing = approved;
|
|
@@ -25456,13 +25568,24 @@ async function readVerdict(workdir) {
|
|
|
25456
25568
|
if (!exists) {
|
|
25457
25569
|
return null;
|
|
25458
25570
|
}
|
|
25571
|
+
let rawText;
|
|
25572
|
+
try {
|
|
25573
|
+
rawText = await file2.text();
|
|
25574
|
+
} catch (readErr) {
|
|
25575
|
+
logger.warn("tdd", "Failed to read verifier verdict file", {
|
|
25576
|
+
path: verdictPath,
|
|
25577
|
+
error: String(readErr)
|
|
25578
|
+
});
|
|
25579
|
+
return null;
|
|
25580
|
+
}
|
|
25459
25581
|
let parsed;
|
|
25460
25582
|
try {
|
|
25461
|
-
parsed =
|
|
25583
|
+
parsed = JSON.parse(rawText);
|
|
25462
25584
|
} catch (parseErr) {
|
|
25463
25585
|
logger.warn("tdd", "Verifier verdict file is not valid JSON \u2014 ignoring", {
|
|
25464
25586
|
path: verdictPath,
|
|
25465
|
-
error: String(parseErr)
|
|
25587
|
+
error: String(parseErr),
|
|
25588
|
+
rawContent: rawText.slice(0, 1000)
|
|
25466
25589
|
});
|
|
25467
25590
|
return null;
|
|
25468
25591
|
}
|
|
@@ -25564,6 +25687,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25564
25687
|
workdir,
|
|
25565
25688
|
modelTier,
|
|
25566
25689
|
contextMarkdown,
|
|
25690
|
+
constitution,
|
|
25567
25691
|
dryRun = false,
|
|
25568
25692
|
lite = false,
|
|
25569
25693
|
_recursionDepth = 0
|
|
@@ -25625,7 +25749,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25625
25749
|
let session1;
|
|
25626
25750
|
if (!isRetry) {
|
|
25627
25751
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
25628
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite);
|
|
25752
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution);
|
|
25629
25753
|
sessions.push(session1);
|
|
25630
25754
|
}
|
|
25631
25755
|
if (session1 && !session1.success) {
|
|
@@ -25687,7 +25811,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25687
25811
|
});
|
|
25688
25812
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
25689
25813
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
25690
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite);
|
|
25814
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution);
|
|
25691
25815
|
sessions.push(session2);
|
|
25692
25816
|
if (!session2.success) {
|
|
25693
25817
|
needsHumanReview = true;
|
|
@@ -25706,7 +25830,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25706
25830
|
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger);
|
|
25707
25831
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
25708
25832
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
25709
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false);
|
|
25833
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution);
|
|
25710
25834
|
sessions.push(session3);
|
|
25711
25835
|
const verdict = await readVerdict(workdir);
|
|
25712
25836
|
await cleanupVerdict(workdir);
|
|
@@ -25886,6 +26010,7 @@ var init_execution = __esm(() => {
|
|
|
25886
26010
|
workdir: ctx.workdir,
|
|
25887
26011
|
modelTier: ctx.routing.modelTier,
|
|
25888
26012
|
contextMarkdown: ctx.contextMarkdown,
|
|
26013
|
+
constitution: ctx.constitution?.content,
|
|
25889
26014
|
dryRun: false,
|
|
25890
26015
|
lite: isLiteMode
|
|
25891
26016
|
});
|
|
@@ -25909,6 +26034,28 @@ var init_execution = __esm(() => {
|
|
|
25909
26034
|
lite: tddResult.lite,
|
|
25910
26035
|
failureCategory: tddResult.failureCategory
|
|
25911
26036
|
});
|
|
26037
|
+
if (ctx.interaction) {
|
|
26038
|
+
try {
|
|
26039
|
+
await ctx.interaction.send({
|
|
26040
|
+
id: `human-review-${ctx.story.id}-${Date.now()}`,
|
|
26041
|
+
type: "notify",
|
|
26042
|
+
featureName: ctx.featureDir ? ctx.featureDir.split("/").pop() ?? "unknown" : "unknown",
|
|
26043
|
+
storyId: ctx.story.id,
|
|
26044
|
+
stage: "execution",
|
|
26045
|
+
summary: `\u26A0\uFE0F Human review needed: ${ctx.story.id}`,
|
|
26046
|
+
detail: `Story: ${ctx.story.title}
|
|
26047
|
+
Reason: ${tddResult.reviewReason ?? "No reason provided"}
|
|
26048
|
+
Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
26049
|
+
fallback: "continue",
|
|
26050
|
+
createdAt: Date.now()
|
|
26051
|
+
});
|
|
26052
|
+
} catch (notifyErr) {
|
|
26053
|
+
logger.warn("execution", "Failed to send human review notification", {
|
|
26054
|
+
storyId: ctx.story.id,
|
|
26055
|
+
error: String(notifyErr)
|
|
26056
|
+
});
|
|
26057
|
+
}
|
|
26058
|
+
}
|
|
25912
26059
|
}
|
|
25913
26060
|
return routeTddFailure(tddResult.failureCategory, isLiteMode, ctx, tddResult.reviewReason);
|
|
25914
26061
|
}
|
|
@@ -25979,16 +26126,11 @@ var init_execution = __esm(() => {
|
|
|
25979
26126
|
};
|
|
25980
26127
|
});
|
|
25981
26128
|
|
|
25982
|
-
// src/optimizer/types.ts
|
|
25983
|
-
function estimateTokens4(text) {
|
|
25984
|
-
return Math.ceil(text.length / 4);
|
|
25985
|
-
}
|
|
25986
|
-
|
|
25987
26129
|
// src/optimizer/noop.optimizer.ts
|
|
25988
26130
|
class NoopOptimizer {
|
|
25989
26131
|
name = "noop";
|
|
25990
26132
|
async optimize(input) {
|
|
25991
|
-
const tokens =
|
|
26133
|
+
const tokens = estimateTokens(input.prompt);
|
|
25992
26134
|
return {
|
|
25993
26135
|
prompt: input.prompt,
|
|
25994
26136
|
originalTokens: tokens,
|
|
@@ -26004,7 +26146,7 @@ var init_noop_optimizer = () => {};
|
|
|
26004
26146
|
class RuleBasedOptimizer {
|
|
26005
26147
|
name = "rule-based";
|
|
26006
26148
|
async optimize(input) {
|
|
26007
|
-
const originalTokens =
|
|
26149
|
+
const originalTokens = estimateTokens(input.prompt);
|
|
26008
26150
|
const appliedRules = [];
|
|
26009
26151
|
let optimized = input.prompt;
|
|
26010
26152
|
const config2 = {
|
|
@@ -26033,13 +26175,13 @@ class RuleBasedOptimizer {
|
|
|
26033
26175
|
}
|
|
26034
26176
|
}
|
|
26035
26177
|
if (config2.maxPromptTokens) {
|
|
26036
|
-
const currentTokens =
|
|
26178
|
+
const currentTokens = estimateTokens(optimized);
|
|
26037
26179
|
if (currentTokens > config2.maxPromptTokens) {
|
|
26038
26180
|
optimized = this.trimToMaxTokens(optimized, config2.maxPromptTokens);
|
|
26039
26181
|
appliedRules.push("maxPromptTokens");
|
|
26040
26182
|
}
|
|
26041
26183
|
}
|
|
26042
|
-
const optimizedTokens =
|
|
26184
|
+
const optimizedTokens = estimateTokens(optimized);
|
|
26043
26185
|
const savings = originalTokens > 0 ? (originalTokens - optimizedTokens) / originalTokens : 0;
|
|
26044
26186
|
return {
|
|
26045
26187
|
prompt: optimized,
|
|
@@ -26082,7 +26224,7 @@ ${newContextSection}`);
|
|
|
26082
26224
|
return prompt;
|
|
26083
26225
|
}
|
|
26084
26226
|
trimToMaxTokens(prompt, maxTokens) {
|
|
26085
|
-
const currentTokens =
|
|
26227
|
+
const currentTokens = estimateTokens(prompt);
|
|
26086
26228
|
if (currentTokens <= maxTokens) {
|
|
26087
26229
|
return prompt;
|
|
26088
26230
|
}
|
|
@@ -26221,7 +26363,7 @@ var init_optimizer2 = __esm(() => {
|
|
|
26221
26363
|
});
|
|
26222
26364
|
|
|
26223
26365
|
// src/execution/prompts.ts
|
|
26224
|
-
function
|
|
26366
|
+
function buildBatchPrompt(stories, contextMarkdown, constitution) {
|
|
26225
26367
|
const storyPrompts = stories.map((story, idx) => {
|
|
26226
26368
|
return `## Story ${idx + 1}: ${story.id} \u2014 ${story.title}
|
|
26227
26369
|
|
|
@@ -26279,7 +26421,7 @@ var init_prompt = __esm(() => {
|
|
|
26279
26421
|
const isBatch = ctx.stories.length > 1;
|
|
26280
26422
|
let prompt;
|
|
26281
26423
|
if (isBatch) {
|
|
26282
|
-
prompt =
|
|
26424
|
+
prompt = buildBatchPrompt(ctx.stories, ctx.contextMarkdown, ctx.constitution);
|
|
26283
26425
|
} else {
|
|
26284
26426
|
const role = ctx.routing.testStrategy === "tdd-simple" ? "tdd-simple" : "single-session";
|
|
26285
26427
|
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content);
|
|
@@ -26567,7 +26709,6 @@ ${rectificationPrompt}`;
|
|
|
26567
26709
|
var init_rectification_loop = __esm(() => {
|
|
26568
26710
|
init_agents();
|
|
26569
26711
|
init_config();
|
|
26570
|
-
init_progress();
|
|
26571
26712
|
init_test_output_parser();
|
|
26572
26713
|
init_logger2();
|
|
26573
26714
|
init_prd();
|
|
@@ -27339,6 +27480,22 @@ var init_routing2 = __esm(() => {
|
|
|
27339
27480
|
};
|
|
27340
27481
|
});
|
|
27341
27482
|
|
|
27483
|
+
// src/verification/crash-detector.ts
|
|
27484
|
+
function detectRuntimeCrash(output) {
|
|
27485
|
+
if (!output)
|
|
27486
|
+
return false;
|
|
27487
|
+
return CRASH_PATTERNS.some((pattern) => output.includes(pattern));
|
|
27488
|
+
}
|
|
27489
|
+
var CRASH_PATTERNS;
|
|
27490
|
+
var init_crash_detector = __esm(() => {
|
|
27491
|
+
CRASH_PATTERNS = [
|
|
27492
|
+
"panic(main thread)",
|
|
27493
|
+
"Segmentation fault",
|
|
27494
|
+
"Bun has crashed",
|
|
27495
|
+
"oh no: Bun has crashed"
|
|
27496
|
+
];
|
|
27497
|
+
});
|
|
27498
|
+
|
|
27342
27499
|
// src/pipeline/stages/verify.ts
|
|
27343
27500
|
function coerceSmartTestRunner(val) {
|
|
27344
27501
|
if (val === undefined || val === true)
|
|
@@ -27356,6 +27513,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
27356
27513
|
var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
|
|
27357
27514
|
var init_verify = __esm(() => {
|
|
27358
27515
|
init_logger2();
|
|
27516
|
+
init_crash_detector();
|
|
27359
27517
|
init_runners();
|
|
27360
27518
|
init_smart_runner();
|
|
27361
27519
|
DEFAULT_SMART_RUNNER_CONFIG2 = {
|
|
@@ -27427,7 +27585,7 @@ var init_verify = __esm(() => {
|
|
|
27427
27585
|
});
|
|
27428
27586
|
ctx.verifyResult = {
|
|
27429
27587
|
success: result.success,
|
|
27430
|
-
status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : "TEST_FAILURE",
|
|
27588
|
+
status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : detectRuntimeCrash(result.output) ? "RUNTIME_CRASH" : "TEST_FAILURE",
|
|
27431
27589
|
storyId: ctx.story.id,
|
|
27432
27590
|
strategy: "scoped",
|
|
27433
27591
|
passCount: result.passCount ?? 0,
|
|
@@ -27478,6 +27636,25 @@ var init_verify = __esm(() => {
|
|
|
27478
27636
|
});
|
|
27479
27637
|
|
|
27480
27638
|
// src/pipeline/stages/index.ts
|
|
27639
|
+
var exports_stages = {};
|
|
27640
|
+
__export(exports_stages, {
|
|
27641
|
+
verifyStage: () => verifyStage,
|
|
27642
|
+
routingStage: () => routingStage,
|
|
27643
|
+
reviewStage: () => reviewStage,
|
|
27644
|
+
regressionStage: () => regressionStage,
|
|
27645
|
+
rectifyStage: () => rectifyStage,
|
|
27646
|
+
queueCheckStage: () => queueCheckStage,
|
|
27647
|
+
promptStage: () => promptStage,
|
|
27648
|
+
postRunPipeline: () => postRunPipeline,
|
|
27649
|
+
optimizerStage: () => optimizerStage,
|
|
27650
|
+
executionStage: () => executionStage,
|
|
27651
|
+
defaultPipeline: () => defaultPipeline,
|
|
27652
|
+
contextStage: () => contextStage,
|
|
27653
|
+
constitutionStage: () => constitutionStage,
|
|
27654
|
+
completionStage: () => completionStage,
|
|
27655
|
+
autofixStage: () => autofixStage,
|
|
27656
|
+
acceptanceStage: () => acceptanceStage
|
|
27657
|
+
});
|
|
27481
27658
|
var defaultPipeline, postRunPipeline;
|
|
27482
27659
|
var init_stages = __esm(() => {
|
|
27483
27660
|
init_acceptance2();
|
|
@@ -29127,11 +29304,12 @@ var init_crash_recovery = __esm(() => {
|
|
|
29127
29304
|
|
|
29128
29305
|
// src/execution/escalation/escalation.ts
|
|
29129
29306
|
function escalateTier(currentTier, tierOrder) {
|
|
29130
|
-
const
|
|
29307
|
+
const getName = (t) => t.tier ?? t.name ?? null;
|
|
29308
|
+
const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
|
|
29131
29309
|
if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
|
|
29132
29310
|
return null;
|
|
29133
29311
|
}
|
|
29134
|
-
return tierOrder[currentIndex + 1]
|
|
29312
|
+
return getName(tierOrder[currentIndex + 1]);
|
|
29135
29313
|
}
|
|
29136
29314
|
function calculateMaxIterations(tierOrder) {
|
|
29137
29315
|
return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
|
|
@@ -29228,13 +29406,14 @@ var init_tier_outcome = __esm(() => {
|
|
|
29228
29406
|
});
|
|
29229
29407
|
|
|
29230
29408
|
// src/execution/escalation/tier-escalation.ts
|
|
29231
|
-
function buildEscalationFailure(story, currentTier, reviewFindings) {
|
|
29409
|
+
function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
|
|
29232
29410
|
return {
|
|
29233
29411
|
attempt: (story.attempts ?? 0) + 1,
|
|
29234
29412
|
modelTier: currentTier,
|
|
29235
29413
|
stage: "escalation",
|
|
29236
29414
|
summary: `Failed with tier ${currentTier}, escalating to next tier`,
|
|
29237
29415
|
reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
|
|
29416
|
+
cost: cost ?? 0,
|
|
29238
29417
|
timestamp: new Date().toISOString()
|
|
29239
29418
|
};
|
|
29240
29419
|
}
|
|
@@ -29247,6 +29426,8 @@ function resolveMaxAttemptsOutcome(failureCategory) {
|
|
|
29247
29426
|
case "verifier-rejected":
|
|
29248
29427
|
case "greenfield-no-tests":
|
|
29249
29428
|
return "pause";
|
|
29429
|
+
case "runtime-crash":
|
|
29430
|
+
return "pause";
|
|
29250
29431
|
case "session-failure":
|
|
29251
29432
|
case "tests-failing":
|
|
29252
29433
|
return "fail";
|
|
@@ -29270,8 +29451,17 @@ async function tryLlmBatchRoute2(config2, stories, label = "routing") {
|
|
|
29270
29451
|
});
|
|
29271
29452
|
}
|
|
29272
29453
|
}
|
|
29454
|
+
function shouldRetrySameTier(verifyResult) {
|
|
29455
|
+
return verifyResult?.status === "RUNTIME_CRASH";
|
|
29456
|
+
}
|
|
29273
29457
|
async function handleTierEscalation(ctx) {
|
|
29274
29458
|
const logger = getSafeLogger();
|
|
29459
|
+
if (shouldRetrySameTier(ctx.verifyResult)) {
|
|
29460
|
+
logger?.warn("escalation", "Runtime crash detected \u2014 retrying same tier (transient, not a code issue)", {
|
|
29461
|
+
storyId: ctx.story.id
|
|
29462
|
+
});
|
|
29463
|
+
return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
|
|
29464
|
+
}
|
|
29275
29465
|
const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
|
|
29276
29466
|
const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
|
|
29277
29467
|
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
@@ -29325,7 +29515,7 @@ async function handleTierEscalation(ctx) {
|
|
|
29325
29515
|
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
29326
29516
|
const isChangingTier = currentStoryTier !== nextTier;
|
|
29327
29517
|
const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
|
|
29328
|
-
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings);
|
|
29518
|
+
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
29329
29519
|
return {
|
|
29330
29520
|
...s,
|
|
29331
29521
|
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
@@ -29335,7 +29525,7 @@ async function handleTierEscalation(ctx) {
|
|
|
29335
29525
|
};
|
|
29336
29526
|
})
|
|
29337
29527
|
};
|
|
29338
|
-
await savePRD(updatedPrd, ctx.prdPath);
|
|
29528
|
+
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
29339
29529
|
for (const story of storiesToEscalate) {
|
|
29340
29530
|
clearCacheForStory(story.id);
|
|
29341
29531
|
}
|
|
@@ -29348,6 +29538,7 @@ async function handleTierEscalation(ctx) {
|
|
|
29348
29538
|
prd: updatedPrd
|
|
29349
29539
|
};
|
|
29350
29540
|
}
|
|
29541
|
+
var _tierEscalationDeps;
|
|
29351
29542
|
var init_tier_escalation = __esm(() => {
|
|
29352
29543
|
init_hooks();
|
|
29353
29544
|
init_logger2();
|
|
@@ -29357,6 +29548,9 @@ var init_tier_escalation = __esm(() => {
|
|
|
29357
29548
|
init_helpers();
|
|
29358
29549
|
init_progress();
|
|
29359
29550
|
init_tier_outcome();
|
|
29551
|
+
_tierEscalationDeps = {
|
|
29552
|
+
savePRD
|
|
29553
|
+
};
|
|
29360
29554
|
});
|
|
29361
29555
|
|
|
29362
29556
|
// src/execution/escalation/index.ts
|
|
@@ -29958,6 +30152,10 @@ var init_headless_formatter = __esm(() => {
|
|
|
29958
30152
|
});
|
|
29959
30153
|
|
|
29960
30154
|
// src/worktree/manager.ts
|
|
30155
|
+
var exports_manager = {};
|
|
30156
|
+
__export(exports_manager, {
|
|
30157
|
+
WorktreeManager: () => WorktreeManager
|
|
30158
|
+
});
|
|
29961
30159
|
import { existsSync as existsSync26, symlinkSync } from "fs";
|
|
29962
30160
|
import { join as join32 } from "path";
|
|
29963
30161
|
|
|
@@ -30102,6 +30300,11 @@ var init_manager = __esm(() => {
|
|
|
30102
30300
|
});
|
|
30103
30301
|
|
|
30104
30302
|
// src/worktree/merge.ts
|
|
30303
|
+
var exports_merge = {};
|
|
30304
|
+
__export(exports_merge, {
|
|
30305
|
+
MergeEngine: () => MergeEngine
|
|
30306
|
+
});
|
|
30307
|
+
|
|
30105
30308
|
class MergeEngine {
|
|
30106
30309
|
worktreeManager;
|
|
30107
30310
|
constructor(worktreeManager) {
|
|
@@ -30380,10 +30583,12 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
|
|
|
30380
30583
|
const logger = getSafeLogger();
|
|
30381
30584
|
const worktreeManager = new WorktreeManager;
|
|
30382
30585
|
const results = {
|
|
30383
|
-
|
|
30384
|
-
|
|
30586
|
+
pipelinePassed: [],
|
|
30587
|
+
merged: [],
|
|
30588
|
+
failed: [],
|
|
30385
30589
|
totalCost: 0,
|
|
30386
|
-
|
|
30590
|
+
mergeConflicts: [],
|
|
30591
|
+
storyCosts: new Map
|
|
30387
30592
|
};
|
|
30388
30593
|
const worktreeSetup = [];
|
|
30389
30594
|
for (const story of stories) {
|
|
@@ -30396,7 +30601,7 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
|
|
|
30396
30601
|
worktreePath
|
|
30397
30602
|
});
|
|
30398
30603
|
} catch (error48) {
|
|
30399
|
-
results.
|
|
30604
|
+
results.failed.push({
|
|
30400
30605
|
story,
|
|
30401
30606
|
error: `Failed to create worktree: ${error48 instanceof Error ? error48.message : String(error48)}`
|
|
30402
30607
|
});
|
|
@@ -30411,14 +30616,15 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
|
|
|
30411
30616
|
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
30412
30617
|
const executePromise = executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter).then((result) => {
|
|
30413
30618
|
results.totalCost += result.cost;
|
|
30619
|
+
results.storyCosts.set(story.id, result.cost);
|
|
30414
30620
|
if (result.success) {
|
|
30415
|
-
results.
|
|
30621
|
+
results.pipelinePassed.push(story);
|
|
30416
30622
|
logger?.info("parallel", "Story execution succeeded", {
|
|
30417
30623
|
storyId: story.id,
|
|
30418
30624
|
cost: result.cost
|
|
30419
30625
|
});
|
|
30420
30626
|
} else {
|
|
30421
|
-
results.
|
|
30627
|
+
results.failed.push({ story, error: result.error || "Unknown error" });
|
|
30422
30628
|
logger?.error("parallel", "Story execution failed", {
|
|
30423
30629
|
storyId: story.id,
|
|
30424
30630
|
error: result.error
|
|
@@ -30458,6 +30664,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30458
30664
|
let storiesCompleted = 0;
|
|
30459
30665
|
let totalCost = 0;
|
|
30460
30666
|
const currentPrd = prd;
|
|
30667
|
+
const allMergeConflicts = [];
|
|
30461
30668
|
for (let batchIndex = 0;batchIndex < batches.length; batchIndex++) {
|
|
30462
30669
|
const batch = batches[batchIndex];
|
|
30463
30670
|
logger?.info("parallel", `Executing batch ${batchIndex + 1}/${batches.length}`, {
|
|
@@ -30474,8 +30681,8 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30474
30681
|
};
|
|
30475
30682
|
const batchResult = await executeParallelBatch(batch, projectRoot, config2, currentPrd, baseContext, maxConcurrency, eventEmitter);
|
|
30476
30683
|
totalCost += batchResult.totalCost;
|
|
30477
|
-
if (batchResult.
|
|
30478
|
-
const successfulIds = batchResult.
|
|
30684
|
+
if (batchResult.pipelinePassed.length > 0) {
|
|
30685
|
+
const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
|
|
30479
30686
|
const deps = buildDependencyMap(batch);
|
|
30480
30687
|
logger?.info("parallel", "Merging successful stories", {
|
|
30481
30688
|
storyIds: successfulIds
|
|
@@ -30485,15 +30692,19 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30485
30692
|
if (mergeResult.success) {
|
|
30486
30693
|
markStoryPassed(currentPrd, mergeResult.storyId);
|
|
30487
30694
|
storiesCompleted++;
|
|
30695
|
+
const mergedStory = batchResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
|
|
30696
|
+
if (mergedStory)
|
|
30697
|
+
batchResult.merged.push(mergedStory);
|
|
30488
30698
|
logger?.info("parallel", "Story merged successfully", {
|
|
30489
30699
|
storyId: mergeResult.storyId,
|
|
30490
30700
|
retryCount: mergeResult.retryCount
|
|
30491
30701
|
});
|
|
30492
30702
|
} else {
|
|
30493
30703
|
markStoryFailed(currentPrd, mergeResult.storyId);
|
|
30494
|
-
batchResult.
|
|
30704
|
+
batchResult.mergeConflicts.push({
|
|
30495
30705
|
storyId: mergeResult.storyId,
|
|
30496
|
-
conflictFiles: mergeResult.conflictFiles || []
|
|
30706
|
+
conflictFiles: mergeResult.conflictFiles || [],
|
|
30707
|
+
originalCost: batchResult.storyCosts.get(mergeResult.storyId) ?? 0
|
|
30497
30708
|
});
|
|
30498
30709
|
logger?.error("parallel", "Merge conflict", {
|
|
30499
30710
|
storyId: mergeResult.storyId,
|
|
@@ -30506,7 +30717,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30506
30717
|
}
|
|
30507
30718
|
}
|
|
30508
30719
|
}
|
|
30509
|
-
for (const { story, error: error48 } of batchResult.
|
|
30720
|
+
for (const { story, error: error48 } of batchResult.failed) {
|
|
30510
30721
|
markStoryFailed(currentPrd, story.id);
|
|
30511
30722
|
logger?.error("parallel", "Cleaning up failed story worktree", {
|
|
30512
30723
|
storyId: story.id,
|
|
@@ -30522,10 +30733,12 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30522
30733
|
}
|
|
30523
30734
|
}
|
|
30524
30735
|
await savePRD(currentPrd, prdPath);
|
|
30736
|
+
allMergeConflicts.push(...batchResult.mergeConflicts);
|
|
30525
30737
|
logger?.info("parallel", `Batch ${batchIndex + 1} complete`, {
|
|
30526
|
-
|
|
30527
|
-
|
|
30528
|
-
|
|
30738
|
+
pipelinePassed: batchResult.pipelinePassed.length,
|
|
30739
|
+
merged: batchResult.merged.length,
|
|
30740
|
+
failed: batchResult.failed.length,
|
|
30741
|
+
mergeConflicts: batchResult.mergeConflicts.length,
|
|
30529
30742
|
batchCost: batchResult.totalCost
|
|
30530
30743
|
});
|
|
30531
30744
|
}
|
|
@@ -30533,7 +30746,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
30533
30746
|
storiesCompleted,
|
|
30534
30747
|
totalCost
|
|
30535
30748
|
});
|
|
30536
|
-
return { storiesCompleted, totalCost, updatedPrd: currentPrd };
|
|
30749
|
+
return { storiesCompleted, totalCost, updatedPrd: currentPrd, mergeConflicts: allMergeConflicts };
|
|
30537
30750
|
}
|
|
30538
30751
|
var init_parallel = __esm(() => {
|
|
30539
30752
|
init_logger2();
|
|
@@ -30625,6 +30838,123 @@ __export(exports_parallel_executor, {
|
|
|
30625
30838
|
});
|
|
30626
30839
|
import * as os5 from "os";
|
|
30627
30840
|
import path15 from "path";
|
|
30841
|
+
async function rectifyConflictedStory(options) {
|
|
30842
|
+
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter } = options;
|
|
30843
|
+
const logger = getSafeLogger();
|
|
30844
|
+
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
30845
|
+
try {
|
|
30846
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
30847
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
30848
|
+
const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
30849
|
+
const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
|
|
30850
|
+
const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
|
|
30851
|
+
const worktreeManager = new WorktreeManager2;
|
|
30852
|
+
const mergeEngine = new MergeEngine2(worktreeManager);
|
|
30853
|
+
try {
|
|
30854
|
+
await worktreeManager.remove(workdir, storyId);
|
|
30855
|
+
} catch {}
|
|
30856
|
+
await worktreeManager.create(workdir, storyId);
|
|
30857
|
+
const worktreePath = path15.join(workdir, ".nax-wt", storyId);
|
|
30858
|
+
const story = prd.userStories.find((s) => s.id === storyId);
|
|
30859
|
+
if (!story) {
|
|
30860
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
30861
|
+
}
|
|
30862
|
+
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
30863
|
+
const pipelineContext = {
|
|
30864
|
+
config: config2,
|
|
30865
|
+
prd,
|
|
30866
|
+
story,
|
|
30867
|
+
stories: [story],
|
|
30868
|
+
workdir: worktreePath,
|
|
30869
|
+
featureDir: undefined,
|
|
30870
|
+
hooks,
|
|
30871
|
+
plugins: pluginRegistry,
|
|
30872
|
+
storyStartTime: new Date().toISOString(),
|
|
30873
|
+
routing
|
|
30874
|
+
};
|
|
30875
|
+
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
30876
|
+
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
30877
|
+
if (!pipelineResult.success) {
|
|
30878
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
30879
|
+
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
30880
|
+
}
|
|
30881
|
+
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
30882
|
+
const mergeResult = mergeResults[0];
|
|
30883
|
+
if (!mergeResult || !mergeResult.success) {
|
|
30884
|
+
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
30885
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
30886
|
+
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
30887
|
+
}
|
|
30888
|
+
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
30889
|
+
storyId,
|
|
30890
|
+
originalCost: options.originalCost,
|
|
30891
|
+
rectificationCost: cost
|
|
30892
|
+
});
|
|
30893
|
+
return { success: true, storyId, cost };
|
|
30894
|
+
} catch (error48) {
|
|
30895
|
+
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
30896
|
+
storyId,
|
|
30897
|
+
error: error48 instanceof Error ? error48.message : String(error48)
|
|
30898
|
+
});
|
|
30899
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
30900
|
+
}
|
|
30901
|
+
}
|
|
30902
|
+
async function runRectificationPass(conflictedStories, options, prd) {
|
|
30903
|
+
const logger = getSafeLogger();
|
|
30904
|
+
const { workdir, config: config2, hooks, pluginRegistry, eventEmitter } = options;
|
|
30905
|
+
const rectificationMetrics = [];
|
|
30906
|
+
let rectifiedCount = 0;
|
|
30907
|
+
let stillConflictingCount = 0;
|
|
30908
|
+
let additionalCost = 0;
|
|
30909
|
+
logger?.info("parallel", "Starting merge conflict rectification", {
|
|
30910
|
+
stories: conflictedStories.map((s) => s.storyId),
|
|
30911
|
+
totalConflicts: conflictedStories.length
|
|
30912
|
+
});
|
|
30913
|
+
for (const conflictInfo of conflictedStories) {
|
|
30914
|
+
const result = await _parallelExecutorDeps.rectifyConflictedStory({
|
|
30915
|
+
...conflictInfo,
|
|
30916
|
+
workdir,
|
|
30917
|
+
config: config2,
|
|
30918
|
+
hooks,
|
|
30919
|
+
pluginRegistry,
|
|
30920
|
+
prd,
|
|
30921
|
+
eventEmitter
|
|
30922
|
+
});
|
|
30923
|
+
additionalCost += result.cost;
|
|
30924
|
+
if (result.success) {
|
|
30925
|
+
markStoryPassed(prd, result.storyId);
|
|
30926
|
+
rectifiedCount++;
|
|
30927
|
+
rectificationMetrics.push({
|
|
30928
|
+
storyId: result.storyId,
|
|
30929
|
+
complexity: "unknown",
|
|
30930
|
+
modelTier: "parallel",
|
|
30931
|
+
modelUsed: "parallel",
|
|
30932
|
+
attempts: 1,
|
|
30933
|
+
finalTier: "parallel",
|
|
30934
|
+
success: true,
|
|
30935
|
+
cost: result.cost,
|
|
30936
|
+
durationMs: 0,
|
|
30937
|
+
firstPassSuccess: false,
|
|
30938
|
+
startedAt: new Date().toISOString(),
|
|
30939
|
+
completedAt: new Date().toISOString(),
|
|
30940
|
+
source: "rectification",
|
|
30941
|
+
rectifiedFromConflict: true,
|
|
30942
|
+
originalCost: conflictInfo.originalCost,
|
|
30943
|
+
rectificationCost: result.cost
|
|
30944
|
+
});
|
|
30945
|
+
} else {
|
|
30946
|
+
const isFinalConflict = result.finalConflict === true;
|
|
30947
|
+
if (isFinalConflict) {
|
|
30948
|
+
stillConflictingCount++;
|
|
30949
|
+
}
|
|
30950
|
+
}
|
|
30951
|
+
}
|
|
30952
|
+
logger?.info("parallel", "Rectification complete", {
|
|
30953
|
+
rectified: rectifiedCount,
|
|
30954
|
+
stillConflicting: stillConflictingCount
|
|
30955
|
+
});
|
|
30956
|
+
return { rectifiedCount, stillConflictingCount, additionalCost, updatedPrd: prd, rectificationMetrics };
|
|
30957
|
+
}
|
|
30628
30958
|
async function runParallelExecution(options, initialPrd) {
|
|
30629
30959
|
const logger = getSafeLogger();
|
|
30630
30960
|
const {
|
|
@@ -30649,7 +30979,14 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30649
30979
|
const readyStories = getAllReadyStories(prd);
|
|
30650
30980
|
if (readyStories.length === 0) {
|
|
30651
30981
|
logger?.info("parallel", "No stories ready for parallel execution");
|
|
30652
|
-
return {
|
|
30982
|
+
return {
|
|
30983
|
+
prd,
|
|
30984
|
+
totalCost,
|
|
30985
|
+
storiesCompleted,
|
|
30986
|
+
completed: false,
|
|
30987
|
+
storyMetrics: [],
|
|
30988
|
+
rectificationStats: { rectified: 0, stillConflicting: 0 }
|
|
30989
|
+
};
|
|
30653
30990
|
}
|
|
30654
30991
|
const maxConcurrency = parallelCount === 0 ? os5.cpus().length : Math.max(1, parallelCount);
|
|
30655
30992
|
logger?.info("parallel", "Starting parallel execution mode", {
|
|
@@ -30667,15 +31004,45 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30667
31004
|
}))
|
|
30668
31005
|
}
|
|
30669
31006
|
});
|
|
31007
|
+
const initialPassedIds = new Set(initialPrd.userStories.filter((s) => s.status === "passed").map((s) => s.id));
|
|
31008
|
+
const batchStartedAt = new Date().toISOString();
|
|
31009
|
+
const batchStartMs = Date.now();
|
|
31010
|
+
const batchStoryMetrics = [];
|
|
31011
|
+
let conflictedStories = [];
|
|
30670
31012
|
try {
|
|
30671
|
-
const parallelResult = await executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
|
|
31013
|
+
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
|
|
31014
|
+
const batchDurationMs = Date.now() - batchStartMs;
|
|
31015
|
+
const batchCompletedAt = new Date().toISOString();
|
|
30672
31016
|
prd = parallelResult.updatedPrd;
|
|
30673
31017
|
storiesCompleted += parallelResult.storiesCompleted;
|
|
30674
31018
|
totalCost += parallelResult.totalCost;
|
|
30675
|
-
|
|
30676
|
-
|
|
30677
|
-
|
|
30678
|
-
|
|
31019
|
+
conflictedStories = parallelResult.mergeConflicts ?? [];
|
|
31020
|
+
const newlyPassedStories = prd.userStories.filter((s) => s.status === "passed" && !initialPassedIds.has(s.id));
|
|
31021
|
+
const costPerStory = newlyPassedStories.length > 0 ? parallelResult.totalCost / newlyPassedStories.length : 0;
|
|
31022
|
+
for (const story of newlyPassedStories) {
|
|
31023
|
+
batchStoryMetrics.push({
|
|
31024
|
+
storyId: story.id,
|
|
31025
|
+
complexity: "unknown",
|
|
31026
|
+
modelTier: "parallel",
|
|
31027
|
+
modelUsed: "parallel",
|
|
31028
|
+
attempts: 1,
|
|
31029
|
+
finalTier: "parallel",
|
|
31030
|
+
success: true,
|
|
31031
|
+
cost: costPerStory,
|
|
31032
|
+
durationMs: batchDurationMs,
|
|
31033
|
+
firstPassSuccess: true,
|
|
31034
|
+
startedAt: batchStartedAt,
|
|
31035
|
+
completedAt: batchCompletedAt,
|
|
31036
|
+
source: "parallel"
|
|
31037
|
+
});
|
|
31038
|
+
}
|
|
31039
|
+
allStoryMetrics.push(...batchStoryMetrics);
|
|
31040
|
+
for (const conflict of conflictedStories) {
|
|
31041
|
+
logger?.info("parallel", "Merge conflict detected - scheduling for rectification", {
|
|
31042
|
+
storyId: conflict.storyId,
|
|
31043
|
+
conflictFiles: conflict.conflictFiles
|
|
31044
|
+
});
|
|
31045
|
+
}
|
|
30679
31046
|
statusWriter.setPrd(prd);
|
|
30680
31047
|
await statusWriter.update(totalCost, iterations, {
|
|
30681
31048
|
parallel: {
|
|
@@ -30693,6 +31060,19 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30693
31060
|
});
|
|
30694
31061
|
throw error48;
|
|
30695
31062
|
}
|
|
31063
|
+
let rectificationStats = { rectified: 0, stillConflicting: 0 };
|
|
31064
|
+
if (conflictedStories.length > 0) {
|
|
31065
|
+
const rectResult = await runRectificationPass(conflictedStories, options, prd);
|
|
31066
|
+
prd = rectResult.updatedPrd;
|
|
31067
|
+
storiesCompleted += rectResult.rectifiedCount;
|
|
31068
|
+
totalCost += rectResult.additionalCost;
|
|
31069
|
+
rectificationStats = {
|
|
31070
|
+
rectified: rectResult.rectifiedCount,
|
|
31071
|
+
stillConflicting: rectResult.stillConflictingCount
|
|
31072
|
+
};
|
|
31073
|
+
batchStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
31074
|
+
allStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
31075
|
+
}
|
|
30696
31076
|
if (isComplete(prd)) {
|
|
30697
31077
|
logger?.info("execution", "All stories complete!", {
|
|
30698
31078
|
feature,
|
|
@@ -30742,10 +31122,12 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
30742
31122
|
totalCost,
|
|
30743
31123
|
storiesCompleted,
|
|
30744
31124
|
completed: true,
|
|
30745
|
-
durationMs
|
|
31125
|
+
durationMs,
|
|
31126
|
+
storyMetrics: batchStoryMetrics,
|
|
31127
|
+
rectificationStats
|
|
30746
31128
|
};
|
|
30747
31129
|
}
|
|
30748
|
-
return { prd, totalCost, storiesCompleted, completed: false };
|
|
31130
|
+
return { prd, totalCost, storiesCompleted, completed: false, storyMetrics: batchStoryMetrics, rectificationStats };
|
|
30749
31131
|
}
|
|
30750
31132
|
var _parallelExecutorDeps;
|
|
30751
31133
|
var init_parallel_executor = __esm(() => {
|
|
@@ -30755,7 +31137,9 @@ var init_parallel_executor = __esm(() => {
|
|
|
30755
31137
|
init_helpers();
|
|
30756
31138
|
init_parallel();
|
|
30757
31139
|
_parallelExecutorDeps = {
|
|
30758
|
-
fireHook
|
|
31140
|
+
fireHook,
|
|
31141
|
+
executeParallel,
|
|
31142
|
+
rectifyConflictedStory
|
|
30759
31143
|
};
|
|
30760
31144
|
});
|
|
30761
31145
|
|
|
@@ -30997,7 +31381,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
30997
31381
|
runId,
|
|
30998
31382
|
storyId: ev.storyId,
|
|
30999
31383
|
status: "completed",
|
|
31000
|
-
|
|
31384
|
+
runElapsedMs: ev.runElapsedMs,
|
|
31001
31385
|
cost: ev.cost ?? 0,
|
|
31002
31386
|
tier: ev.modelTier ?? "balanced",
|
|
31003
31387
|
testStrategy: ev.testStrategy ?? "test-after"
|
|
@@ -31019,7 +31403,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
31019
31403
|
runId,
|
|
31020
31404
|
storyId: ev.storyId,
|
|
31021
31405
|
status: "failed",
|
|
31022
|
-
|
|
31406
|
+
runElapsedMs: Date.now() - startTime,
|
|
31023
31407
|
cost: 0,
|
|
31024
31408
|
tier: "balanced",
|
|
31025
31409
|
testStrategy: "test-after"
|
|
@@ -31041,7 +31425,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
31041
31425
|
runId,
|
|
31042
31426
|
storyId: ev.storyId,
|
|
31043
31427
|
status: "paused",
|
|
31044
|
-
|
|
31428
|
+
runElapsedMs: Date.now() - startTime,
|
|
31045
31429
|
cost: 0,
|
|
31046
31430
|
tier: "balanced",
|
|
31047
31431
|
testStrategy: "test-after"
|
|
@@ -31119,7 +31503,7 @@ async function handleDryRun(ctx) {
|
|
|
31119
31503
|
storyId: s.id,
|
|
31120
31504
|
story: s,
|
|
31121
31505
|
passed: true,
|
|
31122
|
-
|
|
31506
|
+
runElapsedMs: 0,
|
|
31123
31507
|
cost: 0,
|
|
31124
31508
|
modelTier: ctx.routing.modelTier,
|
|
31125
31509
|
testStrategy: ctx.routing.testStrategy
|
|
@@ -31151,7 +31535,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
31151
31535
|
storyId: completedStory.id,
|
|
31152
31536
|
storyTitle: completedStory.title,
|
|
31153
31537
|
totalCost: ctx.totalCost + costDelta,
|
|
31154
|
-
|
|
31538
|
+
runElapsedMs: now - ctx.startTime,
|
|
31155
31539
|
storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined
|
|
31156
31540
|
});
|
|
31157
31541
|
pipelineEventBus.emit({
|
|
@@ -31159,7 +31543,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
31159
31543
|
storyId: completedStory.id,
|
|
31160
31544
|
story: completedStory,
|
|
31161
31545
|
passed: true,
|
|
31162
|
-
|
|
31546
|
+
runElapsedMs: Date.now() - ctx.startTime,
|
|
31163
31547
|
cost: costDelta,
|
|
31164
31548
|
modelTier: ctx.routing.modelTier,
|
|
31165
31549
|
testStrategy: ctx.routing.testStrategy
|
|
@@ -31240,7 +31624,8 @@ async function handlePipelineFailure(ctx, pipelineResult) {
|
|
|
31240
31624
|
hooks: ctx.hooks,
|
|
31241
31625
|
feature: ctx.feature,
|
|
31242
31626
|
totalCost: ctx.totalCost,
|
|
31243
|
-
workdir: ctx.workdir
|
|
31627
|
+
workdir: ctx.workdir,
|
|
31628
|
+
attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0
|
|
31244
31629
|
});
|
|
31245
31630
|
prd = escalationResult.prd;
|
|
31246
31631
|
prdDirty = escalationResult.prdDirty;
|
|
@@ -31282,6 +31667,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
31282
31667
|
}
|
|
31283
31668
|
const storyStartTime = Date.now();
|
|
31284
31669
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
31670
|
+
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
31285
31671
|
const pipelineContext = {
|
|
31286
31672
|
config: ctx.config,
|
|
31287
31673
|
prd,
|
|
@@ -31295,7 +31681,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
31295
31681
|
plugins: ctx.pluginRegistry,
|
|
31296
31682
|
storyStartTime: new Date().toISOString(),
|
|
31297
31683
|
storyGitRef: storyGitRef ?? undefined,
|
|
31298
|
-
interaction: ctx.interactionChain ?? undefined
|
|
31684
|
+
interaction: ctx.interactionChain ?? undefined,
|
|
31685
|
+
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
31299
31686
|
};
|
|
31300
31687
|
ctx.statusWriter.setPrd(prd);
|
|
31301
31688
|
ctx.statusWriter.setCurrentStory({
|
|
@@ -66382,7 +66769,8 @@ init_story_context();
|
|
|
66382
66769
|
init_escalation();
|
|
66383
66770
|
init_escalation();
|
|
66384
66771
|
var _runnerDeps = {
|
|
66385
|
-
fireHook
|
|
66772
|
+
fireHook,
|
|
66773
|
+
runParallelExecution: null
|
|
66386
66774
|
};
|
|
66387
66775
|
async function run(options) {
|
|
66388
66776
|
const {
|
|
@@ -66484,7 +66872,7 @@ async function run(options) {
|
|
|
66484
66872
|
await tryLlmBatchRoute(config2, getAllReadyStories(prd), "routing");
|
|
66485
66873
|
}
|
|
66486
66874
|
if (options.parallel !== undefined) {
|
|
66487
|
-
const
|
|
66875
|
+
const runParallelExecution2 = _runnerDeps.runParallelExecution ?? (await Promise.resolve().then(() => (init_parallel_executor(), exports_parallel_executor))).runParallelExecution;
|
|
66488
66876
|
const parallelResult = await runParallelExecution2({
|
|
66489
66877
|
prdPath,
|
|
66490
66878
|
workdir,
|
|
@@ -66509,6 +66897,7 @@ async function run(options) {
|
|
|
66509
66897
|
prd = parallelResult.prd;
|
|
66510
66898
|
totalCost = parallelResult.totalCost;
|
|
66511
66899
|
storiesCompleted = parallelResult.storiesCompleted;
|
|
66900
|
+
allStoryMetrics.push(...parallelResult.storyMetrics);
|
|
66512
66901
|
if (parallelResult.completed && parallelResult.durationMs !== undefined) {
|
|
66513
66902
|
return {
|
|
66514
66903
|
success: true,
|
|
@@ -66539,8 +66928,8 @@ async function run(options) {
|
|
|
66539
66928
|
}, prd);
|
|
66540
66929
|
prd = sequentialResult.prd;
|
|
66541
66930
|
iterations = sequentialResult.iterations;
|
|
66542
|
-
|
|
66543
|
-
|
|
66931
|
+
totalCost += sequentialResult.totalCost;
|
|
66932
|
+
storiesCompleted += sequentialResult.storiesCompleted;
|
|
66544
66933
|
allStoryMetrics.push(...sequentialResult.allStoryMetrics);
|
|
66545
66934
|
if (config2.acceptance.enabled && isComplete(prd)) {
|
|
66546
66935
|
const { runAcceptanceLoop: runAcceptanceLoop2 } = await Promise.resolve().then(() => (init_acceptance_loop(), exports_acceptance_loop));
|