@nathapp/nax 0.54.8 → 0.54.10
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 +1999 -2118
- package/package.json +3 -1
package/dist/nax.js
CHANGED
|
@@ -3004,7 +3004,7 @@ ${JSON.stringify(entry.data, null, 2)}`;
|
|
|
3004
3004
|
}
|
|
3005
3005
|
close() {}
|
|
3006
3006
|
}
|
|
3007
|
-
function initLogger(options) {
|
|
3007
|
+
function initLogger(options = { level: "warn" }) {
|
|
3008
3008
|
if (instance) {
|
|
3009
3009
|
throw new Error("Logger already initialized. Call getLogger() to access existing instance.");
|
|
3010
3010
|
}
|
|
@@ -17805,7 +17805,7 @@ var init_zod = __esm(() => {
|
|
|
17805
17805
|
});
|
|
17806
17806
|
|
|
17807
17807
|
// src/config/schemas.ts
|
|
17808
|
-
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, ProjectProfileSchema, NaxConfigSchema;
|
|
17808
|
+
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, NaxConfigSchema;
|
|
17809
17809
|
var init_schemas3 = __esm(() => {
|
|
17810
17810
|
init_zod();
|
|
17811
17811
|
TokenPricingSchema = exports_external.object({
|
|
@@ -18107,6 +18107,10 @@ var init_schemas3 = __esm(() => {
|
|
|
18107
18107
|
testFramework: exports_external.string().optional(),
|
|
18108
18108
|
lintTool: exports_external.string().optional()
|
|
18109
18109
|
});
|
|
18110
|
+
VALID_AGENT_TYPES = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
18111
|
+
GenerateConfigSchema = exports_external.object({
|
|
18112
|
+
agents: exports_external.array(exports_external.enum(VALID_AGENT_TYPES)).optional()
|
|
18113
|
+
});
|
|
18110
18114
|
NaxConfigSchema = exports_external.object({
|
|
18111
18115
|
version: exports_external.number(),
|
|
18112
18116
|
models: ModelMapSchema,
|
|
@@ -18130,6 +18134,7 @@ var init_schemas3 = __esm(() => {
|
|
|
18130
18134
|
precheck: PrecheckConfigSchema.optional(),
|
|
18131
18135
|
prompts: PromptsConfigSchema.optional(),
|
|
18132
18136
|
decompose: DecomposeConfigSchema.optional(),
|
|
18137
|
+
generate: GenerateConfigSchema.optional(),
|
|
18133
18138
|
project: ProjectProfileSchema.optional()
|
|
18134
18139
|
}).refine((data) => data.version === 1, {
|
|
18135
18140
|
message: "Invalid version: expected 1",
|
|
@@ -21101,7 +21106,7 @@ var init_paths = () => {};
|
|
|
21101
21106
|
|
|
21102
21107
|
// src/config/loader.ts
|
|
21103
21108
|
import { existsSync as existsSync5 } from "fs";
|
|
21104
|
-
import { dirname, join as join6, resolve as resolve3 } from "path";
|
|
21109
|
+
import { basename, dirname, join as join6, resolve as resolve3 } from "path";
|
|
21105
21110
|
function globalConfigPath() {
|
|
21106
21111
|
return join6(globalConfigDir(), "config.json");
|
|
21107
21112
|
}
|
|
@@ -21154,14 +21159,14 @@ function applyBatchModeCompat(conf) {
|
|
|
21154
21159
|
}
|
|
21155
21160
|
return conf;
|
|
21156
21161
|
}
|
|
21157
|
-
async function loadConfig(
|
|
21162
|
+
async function loadConfig(startDir, cliOverrides) {
|
|
21158
21163
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
21159
21164
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
21160
21165
|
if (globalConfRaw) {
|
|
21161
21166
|
const globalConf = applyBatchModeCompat(applyRemovedStrategyCompat(globalConfRaw));
|
|
21162
21167
|
rawConfig = deepMergeConfig(rawConfig, globalConf);
|
|
21163
21168
|
}
|
|
21164
|
-
const projDir =
|
|
21169
|
+
const projDir = startDir ? basename(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
21165
21170
|
if (projDir) {
|
|
21166
21171
|
const projConf = await loadJsonFile(join6(projDir, "config.json"), "config");
|
|
21167
21172
|
if (projConf) {
|
|
@@ -22365,7 +22370,7 @@ var package_default;
|
|
|
22365
22370
|
var init_package = __esm(() => {
|
|
22366
22371
|
package_default = {
|
|
22367
22372
|
name: "@nathapp/nax",
|
|
22368
|
-
version: "0.54.
|
|
22373
|
+
version: "0.54.10",
|
|
22369
22374
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22370
22375
|
type: "module",
|
|
22371
22376
|
bin: {
|
|
@@ -22377,8 +22382,10 @@ var init_package = __esm(() => {
|
|
|
22377
22382
|
build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
|
|
22378
22383
|
typecheck: "bun x tsc --noEmit",
|
|
22379
22384
|
lint: "bun x biome check src/ bin/",
|
|
22385
|
+
"lint:fix": "bun x biome lint --write src/ bin/",
|
|
22380
22386
|
release: "bun scripts/release.ts",
|
|
22381
22387
|
test: "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
|
|
22388
|
+
"test:bail": "bun test test/unit/ --timeout=60000 --bail && bun test test/integration/ --timeout=60000 --bail && bun test test/ui/ --timeout=60000 --bail",
|
|
22382
22389
|
"test:watch": "bun test --watch",
|
|
22383
22390
|
"test:unit": "bun test ./test/unit/ --timeout=60000",
|
|
22384
22391
|
"test:integration": "bun test ./test/integration/ --timeout=60000",
|
|
@@ -22442,8 +22449,8 @@ var init_version = __esm(() => {
|
|
|
22442
22449
|
NAX_VERSION = package_default.version;
|
|
22443
22450
|
NAX_COMMIT = (() => {
|
|
22444
22451
|
try {
|
|
22445
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22446
|
-
return "
|
|
22452
|
+
if (/^[0-9a-f]{6,10}$/.test("cf3ead2"))
|
|
22453
|
+
return "cf3ead2";
|
|
22447
22454
|
} catch {}
|
|
22448
22455
|
try {
|
|
22449
22456
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -23193,14 +23200,22 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
23193
23200
|
if (!this.botToken)
|
|
23194
23201
|
return [];
|
|
23195
23202
|
try {
|
|
23196
|
-
const
|
|
23197
|
-
|
|
23198
|
-
|
|
23199
|
-
|
|
23200
|
-
|
|
23201
|
-
|
|
23202
|
-
|
|
23203
|
-
|
|
23203
|
+
const controller = new AbortController;
|
|
23204
|
+
const timer = setTimeout(() => controller.abort(), 8000);
|
|
23205
|
+
let response;
|
|
23206
|
+
try {
|
|
23207
|
+
response = await fetch(`https://api.telegram.org/bot${this.botToken}/getUpdates`, {
|
|
23208
|
+
method: "POST",
|
|
23209
|
+
headers: { "Content-Type": "application/json" },
|
|
23210
|
+
body: JSON.stringify({
|
|
23211
|
+
offset: this.lastUpdateId + 1,
|
|
23212
|
+
timeout: 1
|
|
23213
|
+
}),
|
|
23214
|
+
signal: controller.signal
|
|
23215
|
+
});
|
|
23216
|
+
} finally {
|
|
23217
|
+
clearTimeout(timer);
|
|
23218
|
+
}
|
|
23204
23219
|
if (!response.ok) {
|
|
23205
23220
|
const errorBody = await response.text().catch(() => "");
|
|
23206
23221
|
throw new Error(`Telegram getUpdates error (${response.status}): ${errorBody || response.statusText}`);
|
|
@@ -23282,7 +23297,8 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
23282
23297
|
body: JSON.stringify({
|
|
23283
23298
|
chat_id: this.chatId,
|
|
23284
23299
|
message_id: lastId,
|
|
23285
|
-
text: "\u23F1 EXPIRED \u2014 Interaction timed out"
|
|
23300
|
+
text: "\u23F1 EXPIRED \u2014 Interaction timed out",
|
|
23301
|
+
reply_markup: { inline_keyboard: [] }
|
|
23286
23302
|
})
|
|
23287
23303
|
});
|
|
23288
23304
|
} catch {} finally {
|
|
@@ -24007,8 +24023,10 @@ async function checkStoryAmbiguity(context, config2, chain) {
|
|
|
24007
24023
|
async function checkReviewGate(context, config2, chain) {
|
|
24008
24024
|
if (!isTriggerEnabled("review-gate", config2))
|
|
24009
24025
|
return true;
|
|
24026
|
+
const { fallback } = getTriggerConfig("review-gate", config2);
|
|
24010
24027
|
const response = await executeTrigger("review-gate", context, config2, chain);
|
|
24011
|
-
|
|
24028
|
+
const effectiveAction = chain.applyFallback(response, fallback);
|
|
24029
|
+
return effectiveAction === "approve";
|
|
24012
24030
|
}
|
|
24013
24031
|
async function checkStoryOversized(context, config2, chain) {
|
|
24014
24032
|
if (!isTriggerEnabled("story-oversized", config2))
|
|
@@ -24718,6 +24736,36 @@ async function captureGitRef(workdir) {
|
|
|
24718
24736
|
return;
|
|
24719
24737
|
}
|
|
24720
24738
|
}
|
|
24739
|
+
async function isGitRefValid(workdir, ref) {
|
|
24740
|
+
try {
|
|
24741
|
+
const { exitCode } = await gitWithTimeout(["cat-file", "-e", `${ref}^{commit}`], workdir);
|
|
24742
|
+
return exitCode === 0;
|
|
24743
|
+
} catch {
|
|
24744
|
+
return false;
|
|
24745
|
+
}
|
|
24746
|
+
}
|
|
24747
|
+
async function getMergeBase(workdir) {
|
|
24748
|
+
for (const branch of ["origin/main", "origin/master"]) {
|
|
24749
|
+
try {
|
|
24750
|
+
const { stdout, exitCode } = await gitWithTimeout(["merge-base", "HEAD", branch], workdir);
|
|
24751
|
+
if (exitCode === 0) {
|
|
24752
|
+
const sha = stdout.trim();
|
|
24753
|
+
if (sha)
|
|
24754
|
+
return sha;
|
|
24755
|
+
}
|
|
24756
|
+
} catch {}
|
|
24757
|
+
}
|
|
24758
|
+
try {
|
|
24759
|
+
const { stdout, exitCode } = await gitWithTimeout(["rev-list", "--max-parents=0", "HEAD"], workdir);
|
|
24760
|
+
if (exitCode === 0) {
|
|
24761
|
+
const sha = stdout.trim().split(`
|
|
24762
|
+
`)[0];
|
|
24763
|
+
if (sha)
|
|
24764
|
+
return sha;
|
|
24765
|
+
}
|
|
24766
|
+
} catch {}
|
|
24767
|
+
return;
|
|
24768
|
+
}
|
|
24721
24769
|
async function hasCommitsForStory(workdir, storyId, maxCommits = 20) {
|
|
24722
24770
|
try {
|
|
24723
24771
|
const { stdout, exitCode } = await gitWithTimeout(["log", `-${maxCommits}`, "--oneline", "--grep", storyId], workdir);
|
|
@@ -25009,7 +25057,21 @@ function toReviewFindings(findings) {
|
|
|
25009
25057
|
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig) {
|
|
25010
25058
|
const startTime = Date.now();
|
|
25011
25059
|
const logger = getSafeLogger();
|
|
25012
|
-
|
|
25060
|
+
let effectiveRef;
|
|
25061
|
+
if (storyGitRef && await _semanticDeps.isGitRefValid(workdir, storyGitRef)) {
|
|
25062
|
+
effectiveRef = storyGitRef;
|
|
25063
|
+
} else {
|
|
25064
|
+
const fallback = await _semanticDeps.getMergeBase(workdir);
|
|
25065
|
+
if (fallback) {
|
|
25066
|
+
logger?.info("review", "storyGitRef missing or invalid \u2014 using merge-base fallback", {
|
|
25067
|
+
storyId: story.id,
|
|
25068
|
+
storyGitRef,
|
|
25069
|
+
fallback
|
|
25070
|
+
});
|
|
25071
|
+
effectiveRef = fallback;
|
|
25072
|
+
}
|
|
25073
|
+
}
|
|
25074
|
+
if (!effectiveRef) {
|
|
25013
25075
|
return {
|
|
25014
25076
|
check: "semantic",
|
|
25015
25077
|
success: true,
|
|
@@ -25024,9 +25086,9 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
25024
25086
|
modelTier: semanticConfig.modelTier,
|
|
25025
25087
|
configProvided: !!naxConfig
|
|
25026
25088
|
});
|
|
25027
|
-
const rawDiff = await collectDiff(workdir,
|
|
25089
|
+
const rawDiff = await collectDiff(workdir, effectiveRef, semanticConfig.excludePatterns);
|
|
25028
25090
|
const needsTruncation = rawDiff.length > DIFF_CAP_BYTES;
|
|
25029
|
-
const stat = needsTruncation ? await collectDiffStat(workdir,
|
|
25091
|
+
const stat = needsTruncation ? await collectDiffStat(workdir, effectiveRef) : undefined;
|
|
25030
25092
|
const diff = truncateDiff(rawDiff, stat);
|
|
25031
25093
|
const agent = modelResolver(semanticConfig.modelTier);
|
|
25032
25094
|
if (!agent) {
|
|
@@ -25134,8 +25196,11 @@ ${formatFindings(parsed.findings)}`;
|
|
|
25134
25196
|
var _semanticDeps, DIFF_CAP_BYTES = 51200;
|
|
25135
25197
|
var init_semantic = __esm(() => {
|
|
25136
25198
|
init_logger2();
|
|
25199
|
+
init_git();
|
|
25137
25200
|
_semanticDeps = {
|
|
25138
|
-
spawn: spawn2
|
|
25201
|
+
spawn: spawn2,
|
|
25202
|
+
isGitRefValid,
|
|
25203
|
+
getMergeBase
|
|
25139
25204
|
};
|
|
25140
25205
|
});
|
|
25141
25206
|
|
|
@@ -26259,8 +26324,8 @@ function extractTestStructure(source) {
|
|
|
26259
26324
|
function deriveTestPatterns(contextFiles) {
|
|
26260
26325
|
const patterns = new Set;
|
|
26261
26326
|
for (const filePath of contextFiles) {
|
|
26262
|
-
const
|
|
26263
|
-
const basenameNoExt =
|
|
26327
|
+
const basename2 = path6.basename(filePath);
|
|
26328
|
+
const basenameNoExt = basename2.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
26264
26329
|
patterns.add(`${basenameNoExt}.test.ts`);
|
|
26265
26330
|
patterns.add(`${basenameNoExt}.test.js`);
|
|
26266
26331
|
patterns.add(`${basenameNoExt}.test.tsx`);
|
|
@@ -26313,8 +26378,8 @@ async function scanTestFiles(options) {
|
|
|
26313
26378
|
const files = [];
|
|
26314
26379
|
for await (const filePath of glob.scan({ cwd: scanDir, absolute: false })) {
|
|
26315
26380
|
if (allowedBasenames !== null) {
|
|
26316
|
-
const
|
|
26317
|
-
if (!allowedBasenames.has(
|
|
26381
|
+
const basename2 = path6.basename(filePath);
|
|
26382
|
+
if (!allowedBasenames.has(basename2)) {
|
|
26318
26383
|
continue;
|
|
26319
26384
|
}
|
|
26320
26385
|
}
|
|
@@ -30303,8 +30368,8 @@ function extractSearchTerms(sourceFile) {
|
|
|
30303
30368
|
const withoutSrc = sourceFile.replace(/^src\//, "");
|
|
30304
30369
|
const withoutExt = withoutSrc.replace(/\.ts$/, "");
|
|
30305
30370
|
const parts = withoutExt.split("/");
|
|
30306
|
-
const
|
|
30307
|
-
return [`/${
|
|
30371
|
+
const basename2 = parts[parts.length - 1];
|
|
30372
|
+
return [`/${basename2}`, withoutExt];
|
|
30308
30373
|
}
|
|
30309
30374
|
async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
30310
30375
|
if (sourceFiles.length === 0 || testFilePatterns.length === 0)
|
|
@@ -31065,6 +31130,27 @@ var init_stages = __esm(() => {
|
|
|
31065
31130
|
preRunPipeline = [acceptanceSetupStage];
|
|
31066
31131
|
});
|
|
31067
31132
|
|
|
31133
|
+
// src/utils/gitignore.ts
|
|
31134
|
+
var NAX_GITIGNORE_ENTRIES;
|
|
31135
|
+
var init_gitignore = __esm(() => {
|
|
31136
|
+
NAX_GITIGNORE_ENTRIES = [
|
|
31137
|
+
".nax-verifier-verdict.json",
|
|
31138
|
+
"nax.lock",
|
|
31139
|
+
".nax/**/runs/",
|
|
31140
|
+
".nax/metrics.json",
|
|
31141
|
+
".nax/features/*/status.json",
|
|
31142
|
+
".nax/features/*/plan/",
|
|
31143
|
+
".nax/features/*/acp-sessions.json",
|
|
31144
|
+
".nax/features/*/interactions/",
|
|
31145
|
+
".nax/features/*/progress.txt",
|
|
31146
|
+
".nax/features/*/acceptance-refined.json",
|
|
31147
|
+
".nax-pids",
|
|
31148
|
+
".nax-wt/",
|
|
31149
|
+
"**/.nax-acceptance*",
|
|
31150
|
+
"**/.nax/features/*/"
|
|
31151
|
+
];
|
|
31152
|
+
});
|
|
31153
|
+
|
|
31068
31154
|
// src/cli/init-context.ts
|
|
31069
31155
|
var exports_init_context = {};
|
|
31070
31156
|
__export(exports_init_context, {
|
|
@@ -31077,7 +31163,7 @@ __export(exports_init_context, {
|
|
|
31077
31163
|
});
|
|
31078
31164
|
import { existsSync as existsSync20 } from "fs";
|
|
31079
31165
|
import { mkdir } from "fs/promises";
|
|
31080
|
-
import { basename as
|
|
31166
|
+
import { basename as basename3, join as join30 } from "path";
|
|
31081
31167
|
async function findFiles(dir, maxFiles = 200) {
|
|
31082
31168
|
try {
|
|
31083
31169
|
const proc = Bun.spawnSync([
|
|
@@ -31165,7 +31251,7 @@ async function scanProject(projectRoot) {
|
|
|
31165
31251
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
31166
31252
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
31167
31253
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
31168
|
-
const projectName = packageManifest?.name ||
|
|
31254
|
+
const projectName = packageManifest?.name || basename3(projectRoot);
|
|
31169
31255
|
return {
|
|
31170
31256
|
projectName,
|
|
31171
31257
|
fileTree,
|
|
@@ -31747,11 +31833,11 @@ function getSafeLogger6() {
|
|
|
31747
31833
|
return getSafeLogger();
|
|
31748
31834
|
}
|
|
31749
31835
|
function extractPluginName(pluginPath) {
|
|
31750
|
-
const
|
|
31751
|
-
if (
|
|
31836
|
+
const basename5 = path12.basename(pluginPath);
|
|
31837
|
+
if (basename5 === "index.ts" || basename5 === "index.js" || basename5 === "index.mjs") {
|
|
31752
31838
|
return path12.basename(path12.dirname(pluginPath));
|
|
31753
31839
|
}
|
|
31754
|
-
return
|
|
31840
|
+
return basename5.replace(/\.(ts|js|mjs)$/, "");
|
|
31755
31841
|
}
|
|
31756
31842
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
|
|
31757
31843
|
const loadedPlugins = [];
|
|
@@ -33956,2119 +34042,1742 @@ var init_headless_formatter = __esm(() => {
|
|
|
33956
34042
|
init_version();
|
|
33957
34043
|
});
|
|
33958
34044
|
|
|
33959
|
-
// src/
|
|
33960
|
-
|
|
33961
|
-
|
|
33962
|
-
|
|
33963
|
-
|
|
33964
|
-
|
|
33965
|
-
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
|
|
33970
|
-
|
|
33971
|
-
const worktreePath = join47(projectRoot, ".nax-wt", storyId);
|
|
33972
|
-
const branchName = `nax/${storyId}`;
|
|
33973
|
-
try {
|
|
33974
|
-
const proc = _managerDeps.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
33975
|
-
cwd: projectRoot,
|
|
33976
|
-
stdout: "pipe",
|
|
33977
|
-
stderr: "pipe"
|
|
33978
|
-
});
|
|
33979
|
-
const exitCode = await proc.exited;
|
|
33980
|
-
if (exitCode !== 0) {
|
|
33981
|
-
const stderr = await new Response(proc.stderr).text();
|
|
33982
|
-
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
33983
|
-
}
|
|
33984
|
-
} catch (error48) {
|
|
33985
|
-
if (error48 instanceof Error) {
|
|
33986
|
-
if (error48.message.includes("not a git repository")) {
|
|
33987
|
-
throw new Error(`Not a git repository: ${projectRoot}`);
|
|
33988
|
-
}
|
|
33989
|
-
if (error48.message.includes("already exists")) {
|
|
33990
|
-
throw new Error(`Worktree for story ${storyId} already exists at ${worktreePath}`);
|
|
33991
|
-
}
|
|
33992
|
-
throw error48;
|
|
33993
|
-
}
|
|
33994
|
-
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
33995
|
-
}
|
|
33996
|
-
const nodeModulesSource = join47(projectRoot, "node_modules");
|
|
33997
|
-
if (existsSync32(nodeModulesSource)) {
|
|
33998
|
-
const nodeModulesTarget = join47(worktreePath, "node_modules");
|
|
33999
|
-
try {
|
|
34000
|
-
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
34001
|
-
} catch (error48) {
|
|
34002
|
-
await this.remove(projectRoot, storyId);
|
|
34003
|
-
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
34004
|
-
}
|
|
34005
|
-
}
|
|
34006
|
-
const envSource = join47(projectRoot, ".env");
|
|
34007
|
-
if (existsSync32(envSource)) {
|
|
34008
|
-
const envTarget = join47(worktreePath, ".env");
|
|
34045
|
+
// src/pipeline/subscribers/events-writer.ts
|
|
34046
|
+
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
34047
|
+
import { homedir as homedir5 } from "os";
|
|
34048
|
+
import { basename as basename6, join as join47 } from "path";
|
|
34049
|
+
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
34050
|
+
const logger = getSafeLogger();
|
|
34051
|
+
const project = basename6(workdir);
|
|
34052
|
+
const eventsDir = join47(homedir5(), ".nax", "events", project);
|
|
34053
|
+
const eventsFile = join47(eventsDir, "events.jsonl");
|
|
34054
|
+
let dirReady = false;
|
|
34055
|
+
const write = (line) => {
|
|
34056
|
+
return (async () => {
|
|
34009
34057
|
try {
|
|
34010
|
-
|
|
34011
|
-
|
|
34012
|
-
|
|
34013
|
-
throw new Error(`Failed to symlink .env: ${errorMessage(error48)}`);
|
|
34014
|
-
}
|
|
34015
|
-
}
|
|
34016
|
-
}
|
|
34017
|
-
async remove(projectRoot, storyId) {
|
|
34018
|
-
validateStoryId(storyId);
|
|
34019
|
-
const worktreePath = join47(projectRoot, ".nax-wt", storyId);
|
|
34020
|
-
const branchName = `nax/${storyId}`;
|
|
34021
|
-
try {
|
|
34022
|
-
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
34023
|
-
cwd: projectRoot,
|
|
34024
|
-
stdout: "pipe",
|
|
34025
|
-
stderr: "pipe"
|
|
34026
|
-
});
|
|
34027
|
-
const exitCode = await proc.exited;
|
|
34028
|
-
if (exitCode !== 0) {
|
|
34029
|
-
const stderr = await new Response(proc.stderr).text();
|
|
34030
|
-
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
34031
|
-
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
34032
|
-
}
|
|
34033
|
-
throw new Error(`Failed to remove worktree: ${stderr || "unknown error"}`);
|
|
34034
|
-
}
|
|
34035
|
-
} catch (error48) {
|
|
34036
|
-
if (error48 instanceof Error) {
|
|
34037
|
-
throw error48;
|
|
34038
|
-
}
|
|
34039
|
-
throw new Error(`Failed to remove worktree: ${String(error48)}`);
|
|
34040
|
-
}
|
|
34041
|
-
try {
|
|
34042
|
-
const proc = _managerDeps.spawn(["git", "branch", "-D", branchName], {
|
|
34043
|
-
cwd: projectRoot,
|
|
34044
|
-
stdout: "pipe",
|
|
34045
|
-
stderr: "pipe"
|
|
34046
|
-
});
|
|
34047
|
-
const exitCode = await proc.exited;
|
|
34048
|
-
if (exitCode !== 0) {
|
|
34049
|
-
const stderr = await new Response(proc.stderr).text();
|
|
34050
|
-
if (!stderr.includes("not found")) {
|
|
34051
|
-
const logger = getSafeLogger();
|
|
34052
|
-
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
34058
|
+
if (!dirReady) {
|
|
34059
|
+
await mkdir2(eventsDir, { recursive: true });
|
|
34060
|
+
dirReady = true;
|
|
34053
34061
|
}
|
|
34054
|
-
|
|
34055
|
-
} catch (error48) {
|
|
34056
|
-
const logger = getSafeLogger();
|
|
34057
|
-
logger?.warn("worktree", `Failed to delete branch ${branchName}`, {
|
|
34058
|
-
error: errorMessage(error48)
|
|
34059
|
-
});
|
|
34060
|
-
}
|
|
34061
|
-
}
|
|
34062
|
-
async list(projectRoot) {
|
|
34063
|
-
try {
|
|
34064
|
-
const proc = _managerDeps.spawn(["git", "worktree", "list", "--porcelain"], {
|
|
34065
|
-
cwd: projectRoot,
|
|
34066
|
-
stdout: "pipe",
|
|
34067
|
-
stderr: "pipe"
|
|
34068
|
-
});
|
|
34069
|
-
const exitCode = await proc.exited;
|
|
34070
|
-
if (exitCode !== 0) {
|
|
34071
|
-
const stderr = await new Response(proc.stderr).text();
|
|
34072
|
-
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
34073
|
-
}
|
|
34074
|
-
const stdout = await new Response(proc.stdout).text();
|
|
34075
|
-
return this.parseWorktreeList(stdout);
|
|
34076
|
-
} catch (error48) {
|
|
34077
|
-
if (error48 instanceof Error) {
|
|
34078
|
-
throw error48;
|
|
34079
|
-
}
|
|
34080
|
-
throw new Error(`Failed to list worktrees: ${String(error48)}`);
|
|
34081
|
-
}
|
|
34082
|
-
}
|
|
34083
|
-
parseWorktreeList(output) {
|
|
34084
|
-
const worktrees = [];
|
|
34085
|
-
const lines = output.trim().split(`
|
|
34062
|
+
await appendFile2(eventsFile, `${JSON.stringify(line)}
|
|
34086
34063
|
`);
|
|
34087
|
-
|
|
34088
|
-
|
|
34089
|
-
|
|
34090
|
-
|
|
34091
|
-
|
|
34092
|
-
currentWorktree.branch = line.substring("branch ".length).replace("refs/heads/", "");
|
|
34093
|
-
} else if (line === "") {
|
|
34094
|
-
if (currentWorktree.path && currentWorktree.branch) {
|
|
34095
|
-
worktrees.push(currentWorktree);
|
|
34096
|
-
}
|
|
34097
|
-
currentWorktree = {};
|
|
34064
|
+
} catch (err) {
|
|
34065
|
+
logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
|
|
34066
|
+
event: line.event,
|
|
34067
|
+
error: String(err)
|
|
34068
|
+
});
|
|
34098
34069
|
}
|
|
34099
|
-
}
|
|
34100
|
-
|
|
34101
|
-
|
|
34102
|
-
|
|
34103
|
-
return
|
|
34104
|
-
}
|
|
34070
|
+
})();
|
|
34071
|
+
};
|
|
34072
|
+
const unsubs = [];
|
|
34073
|
+
unsubs.push(bus.on("run:started", (_ev) => {
|
|
34074
|
+
return write({ ts: new Date().toISOString(), event: "run:started", runId, feature, project });
|
|
34075
|
+
}));
|
|
34076
|
+
unsubs.push(bus.on("story:started", (ev) => {
|
|
34077
|
+
return write({
|
|
34078
|
+
ts: new Date().toISOString(),
|
|
34079
|
+
event: "story:started",
|
|
34080
|
+
runId,
|
|
34081
|
+
feature,
|
|
34082
|
+
project,
|
|
34083
|
+
storyId: ev.storyId
|
|
34084
|
+
});
|
|
34085
|
+
}));
|
|
34086
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34087
|
+
return write({
|
|
34088
|
+
ts: new Date().toISOString(),
|
|
34089
|
+
event: "story:completed",
|
|
34090
|
+
runId,
|
|
34091
|
+
feature,
|
|
34092
|
+
project,
|
|
34093
|
+
storyId: ev.storyId
|
|
34094
|
+
});
|
|
34095
|
+
}));
|
|
34096
|
+
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
34097
|
+
return write({
|
|
34098
|
+
ts: new Date().toISOString(),
|
|
34099
|
+
event: "story:decomposed",
|
|
34100
|
+
runId,
|
|
34101
|
+
feature,
|
|
34102
|
+
project,
|
|
34103
|
+
storyId: ev.storyId,
|
|
34104
|
+
data: { subStoryCount: ev.subStoryCount }
|
|
34105
|
+
});
|
|
34106
|
+
}));
|
|
34107
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34108
|
+
return write({
|
|
34109
|
+
ts: new Date().toISOString(),
|
|
34110
|
+
event: "story:failed",
|
|
34111
|
+
runId,
|
|
34112
|
+
feature,
|
|
34113
|
+
project,
|
|
34114
|
+
storyId: ev.storyId
|
|
34115
|
+
});
|
|
34116
|
+
}));
|
|
34117
|
+
unsubs.push(bus.on("run:completed", (_ev) => {
|
|
34118
|
+
return write({ ts: new Date().toISOString(), event: "on-complete", runId, feature, project });
|
|
34119
|
+
}));
|
|
34120
|
+
unsubs.push(bus.on("run:paused", (ev) => {
|
|
34121
|
+
return write({
|
|
34122
|
+
ts: new Date().toISOString(),
|
|
34123
|
+
event: "run:paused",
|
|
34124
|
+
runId,
|
|
34125
|
+
feature,
|
|
34126
|
+
project,
|
|
34127
|
+
...ev.storyId !== undefined && { storyId: ev.storyId }
|
|
34128
|
+
});
|
|
34129
|
+
}));
|
|
34130
|
+
return () => {
|
|
34131
|
+
for (const u of unsubs)
|
|
34132
|
+
u();
|
|
34133
|
+
};
|
|
34105
34134
|
}
|
|
34106
|
-
var
|
|
34107
|
-
var init_manager = __esm(() => {
|
|
34135
|
+
var init_events_writer = __esm(() => {
|
|
34108
34136
|
init_logger2();
|
|
34109
|
-
init_bun_deps();
|
|
34110
|
-
_managerDeps = {
|
|
34111
|
-
spawn
|
|
34112
|
-
};
|
|
34113
34137
|
});
|
|
34114
34138
|
|
|
34115
|
-
// src/
|
|
34116
|
-
|
|
34117
|
-
|
|
34118
|
-
|
|
34119
|
-
|
|
34139
|
+
// src/pipeline/subscribers/hooks.ts
|
|
34140
|
+
function wireHooks(bus, hooks, workdir, feature) {
|
|
34141
|
+
const logger = getSafeLogger();
|
|
34142
|
+
const safe = (name, fn) => {
|
|
34143
|
+
return fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) })).catch(() => {});
|
|
34144
|
+
};
|
|
34145
|
+
const unsubs = [];
|
|
34146
|
+
unsubs.push(bus.on("run:started", (ev) => {
|
|
34147
|
+
return safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
|
|
34148
|
+
}));
|
|
34149
|
+
unsubs.push(bus.on("story:started", (ev) => {
|
|
34150
|
+
return safe("on-story-start", () => fireHook(hooks, "on-story-start", hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }), workdir));
|
|
34151
|
+
}));
|
|
34152
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34153
|
+
return safe("on-story-complete", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }), workdir));
|
|
34154
|
+
}));
|
|
34155
|
+
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
34156
|
+
return safe("on-story-complete (decomposed)", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "decomposed", subStoryCount: ev.subStoryCount }), workdir));
|
|
34157
|
+
}));
|
|
34158
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34159
|
+
return safe("on-story-fail", () => fireHook(hooks, "on-story-fail", hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }), workdir));
|
|
34160
|
+
}));
|
|
34161
|
+
unsubs.push(bus.on("story:paused", (ev) => {
|
|
34162
|
+
return safe("on-pause (story)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
34163
|
+
}));
|
|
34164
|
+
unsubs.push(bus.on("run:paused", (ev) => {
|
|
34165
|
+
return safe("on-pause (run)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
34166
|
+
}));
|
|
34167
|
+
unsubs.push(bus.on("run:completed", (ev) => {
|
|
34168
|
+
return safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
|
|
34169
|
+
}));
|
|
34170
|
+
unsubs.push(bus.on("run:resumed", (ev) => {
|
|
34171
|
+
return safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
|
|
34172
|
+
}));
|
|
34173
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34174
|
+
return safe("on-session-end (completed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "passed" }), workdir));
|
|
34175
|
+
}));
|
|
34176
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34177
|
+
return safe("on-session-end (failed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "failed" }), workdir));
|
|
34178
|
+
}));
|
|
34179
|
+
unsubs.push(bus.on("run:errored", (ev) => {
|
|
34180
|
+
return safe("on-error", () => fireHook(hooks, "on-error", hookCtx(feature, { reason: ev.reason }), workdir));
|
|
34181
|
+
}));
|
|
34182
|
+
return () => {
|
|
34183
|
+
for (const u of unsubs)
|
|
34184
|
+
u();
|
|
34185
|
+
};
|
|
34186
|
+
}
|
|
34187
|
+
var init_hooks2 = __esm(() => {
|
|
34188
|
+
init_story_context();
|
|
34189
|
+
init_hooks();
|
|
34190
|
+
init_logger2();
|
|
34120
34191
|
});
|
|
34121
34192
|
|
|
34122
|
-
|
|
34123
|
-
|
|
34124
|
-
|
|
34125
|
-
|
|
34126
|
-
|
|
34127
|
-
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34193
|
+
// src/pipeline/subscribers/interaction.ts
|
|
34194
|
+
function wireInteraction(bus, interactionChain, config2) {
|
|
34195
|
+
const logger = getSafeLogger();
|
|
34196
|
+
const unsubs = [];
|
|
34197
|
+
if (interactionChain && isTriggerEnabled("human-review", config2)) {
|
|
34198
|
+
unsubs.push(bus.on("human-review:requested", (ev) => {
|
|
34199
|
+
executeTrigger("human-review", {
|
|
34200
|
+
featureName: ev.feature ?? "",
|
|
34201
|
+
storyId: ev.storyId,
|
|
34202
|
+
iteration: ev.attempts ?? 0,
|
|
34203
|
+
reason: ev.reason
|
|
34204
|
+
}, config2, interactionChain).catch((err) => {
|
|
34205
|
+
logger?.warn("interaction-subscriber", "human-review trigger failed", {
|
|
34206
|
+
storyId: ev.storyId,
|
|
34207
|
+
error: String(err)
|
|
34208
|
+
});
|
|
34134
34209
|
});
|
|
34135
|
-
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34210
|
+
}));
|
|
34211
|
+
}
|
|
34212
|
+
if (interactionChain && isTriggerEnabled("max-retries", config2)) {
|
|
34213
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34214
|
+
if (!ev.countsTowardEscalation) {
|
|
34215
|
+
return;
|
|
34216
|
+
}
|
|
34217
|
+
executeTrigger("max-retries", {
|
|
34218
|
+
featureName: ev.feature ?? "",
|
|
34219
|
+
storyId: ev.storyId,
|
|
34220
|
+
iteration: ev.attempts ?? 0
|
|
34221
|
+
}, config2, interactionChain).then((response) => {
|
|
34222
|
+
if (response.action === "abort") {
|
|
34223
|
+
logger?.warn("interaction-subscriber", "max-retries abort requested", {
|
|
34224
|
+
storyId: ev.storyId
|
|
34145
34225
|
});
|
|
34146
34226
|
}
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
return {
|
|
34155
|
-
success: false,
|
|
34156
|
-
conflictFiles
|
|
34157
|
-
};
|
|
34158
|
-
}
|
|
34159
|
-
throw new Error(`Merge failed: ${stderr || stdout || "unknown error"}`);
|
|
34160
|
-
} catch (error48) {
|
|
34161
|
-
if (error48 instanceof Error) {
|
|
34162
|
-
throw error48;
|
|
34163
|
-
}
|
|
34164
|
-
throw new Error(`Failed to merge branch ${branchName}: ${String(error48)}`);
|
|
34165
|
-
}
|
|
34227
|
+
}).catch((err) => {
|
|
34228
|
+
logger?.warn("interaction-subscriber", "max-retries trigger failed", {
|
|
34229
|
+
storyId: ev.storyId,
|
|
34230
|
+
error: String(err)
|
|
34231
|
+
});
|
|
34232
|
+
});
|
|
34233
|
+
}));
|
|
34166
34234
|
}
|
|
34167
|
-
|
|
34168
|
-
const
|
|
34169
|
-
|
|
34170
|
-
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
|
|
34174
|
-
|
|
34175
|
-
|
|
34176
|
-
|
|
34177
|
-
|
|
34178
|
-
|
|
34235
|
+
return () => {
|
|
34236
|
+
for (const u of unsubs)
|
|
34237
|
+
u();
|
|
34238
|
+
};
|
|
34239
|
+
}
|
|
34240
|
+
var init_interaction2 = __esm(() => {
|
|
34241
|
+
init_triggers();
|
|
34242
|
+
init_logger2();
|
|
34243
|
+
});
|
|
34244
|
+
|
|
34245
|
+
// src/pipeline/subscribers/registry.ts
|
|
34246
|
+
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
34247
|
+
import { homedir as homedir6 } from "os";
|
|
34248
|
+
import { basename as basename7, join as join48 } from "path";
|
|
34249
|
+
function wireRegistry(bus, feature, runId, workdir) {
|
|
34250
|
+
const logger = getSafeLogger();
|
|
34251
|
+
const project = basename7(workdir);
|
|
34252
|
+
const runDir = join48(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
34253
|
+
const metaFile = join48(runDir, "meta.json");
|
|
34254
|
+
const unsub = bus.on("run:started", (_ev) => {
|
|
34255
|
+
return (async () => {
|
|
34256
|
+
try {
|
|
34257
|
+
await mkdir3(runDir, { recursive: true });
|
|
34258
|
+
const meta3 = {
|
|
34259
|
+
runId,
|
|
34260
|
+
project,
|
|
34261
|
+
feature,
|
|
34262
|
+
workdir,
|
|
34263
|
+
statusPath: join48(workdir, ".nax", "features", feature, "status.json"),
|
|
34264
|
+
eventsDir: join48(workdir, ".nax", "features", feature, "runs"),
|
|
34265
|
+
registeredAt: new Date().toISOString()
|
|
34266
|
+
};
|
|
34267
|
+
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
34268
|
+
} catch (err) {
|
|
34269
|
+
logger?.warn("registry-writer", "Failed to write meta.json (non-fatal)", {
|
|
34270
|
+
path: metaFile,
|
|
34271
|
+
error: String(err)
|
|
34179
34272
|
});
|
|
34180
|
-
failedStories.add(storyId);
|
|
34181
|
-
continue;
|
|
34182
34273
|
}
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
|
|
34186
|
-
|
|
34187
|
-
|
|
34188
|
-
|
|
34189
|
-
|
|
34190
|
-
|
|
34191
|
-
|
|
34192
|
-
|
|
34193
|
-
|
|
34274
|
+
})();
|
|
34275
|
+
});
|
|
34276
|
+
return unsub;
|
|
34277
|
+
}
|
|
34278
|
+
var init_registry3 = __esm(() => {
|
|
34279
|
+
init_logger2();
|
|
34280
|
+
});
|
|
34281
|
+
|
|
34282
|
+
// src/pipeline/subscribers/reporters.ts
|
|
34283
|
+
function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
34284
|
+
const logger = getSafeLogger();
|
|
34285
|
+
const safe = (name, fn) => {
|
|
34286
|
+
return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
|
|
34287
|
+
};
|
|
34288
|
+
const unsubs = [];
|
|
34289
|
+
unsubs.push(bus.on("run:started", (ev) => {
|
|
34290
|
+
return safe("onRunStart", async () => {
|
|
34291
|
+
const reporters = pluginRegistry.getReporters();
|
|
34292
|
+
for (const r of reporters) {
|
|
34293
|
+
if (r.onRunStart) {
|
|
34294
|
+
try {
|
|
34295
|
+
await r.onRunStart({
|
|
34296
|
+
runId,
|
|
34297
|
+
feature: ev.feature,
|
|
34298
|
+
totalStories: ev.totalStories,
|
|
34299
|
+
startTime: new Date(startTime).toISOString()
|
|
34194
34300
|
});
|
|
34195
|
-
|
|
34196
|
-
|
|
34301
|
+
} catch (err) {
|
|
34302
|
+
logger?.warn("plugins", `Reporter '${r.name}' onRunStart failed`, { error: err });
|
|
34197
34303
|
}
|
|
34198
|
-
results.push({
|
|
34199
|
-
success: true,
|
|
34200
|
-
storyId,
|
|
34201
|
-
retryCount: 1
|
|
34202
|
-
});
|
|
34203
|
-
} catch (error48) {
|
|
34204
|
-
results.push({
|
|
34205
|
-
success: false,
|
|
34206
|
-
storyId,
|
|
34207
|
-
conflictFiles: result.conflictFiles,
|
|
34208
|
-
retryCount: 1
|
|
34209
|
-
});
|
|
34210
|
-
failedStories.add(storyId);
|
|
34211
34304
|
}
|
|
34212
|
-
} else if (result.success) {
|
|
34213
|
-
results.push({
|
|
34214
|
-
success: true,
|
|
34215
|
-
storyId,
|
|
34216
|
-
retryCount: 0
|
|
34217
|
-
});
|
|
34218
|
-
} else {
|
|
34219
|
-
results.push({
|
|
34220
|
-
success: false,
|
|
34221
|
-
storyId,
|
|
34222
|
-
retryCount: 0
|
|
34223
|
-
});
|
|
34224
|
-
failedStories.add(storyId);
|
|
34225
|
-
}
|
|
34226
|
-
}
|
|
34227
|
-
return results;
|
|
34228
|
-
}
|
|
34229
|
-
topologicalSort(storyIds, dependencies) {
|
|
34230
|
-
const visited = new Set;
|
|
34231
|
-
const sorted = [];
|
|
34232
|
-
const visiting = new Set;
|
|
34233
|
-
const visit = (storyId) => {
|
|
34234
|
-
if (visited.has(storyId)) {
|
|
34235
|
-
return;
|
|
34236
34305
|
}
|
|
34237
|
-
|
|
34238
|
-
|
|
34239
|
-
|
|
34240
|
-
|
|
34241
|
-
const
|
|
34242
|
-
for (const
|
|
34243
|
-
if (
|
|
34244
|
-
|
|
34306
|
+
});
|
|
34307
|
+
}));
|
|
34308
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34309
|
+
return safe("onStoryComplete(completed)", async () => {
|
|
34310
|
+
const reporters = pluginRegistry.getReporters();
|
|
34311
|
+
for (const r of reporters) {
|
|
34312
|
+
if (r.onStoryComplete) {
|
|
34313
|
+
try {
|
|
34314
|
+
await r.onStoryComplete({
|
|
34315
|
+
runId,
|
|
34316
|
+
storyId: ev.storyId,
|
|
34317
|
+
status: "completed",
|
|
34318
|
+
runElapsedMs: ev.runElapsedMs,
|
|
34319
|
+
cost: ev.cost ?? 0,
|
|
34320
|
+
tier: ev.modelTier ?? "balanced",
|
|
34321
|
+
testStrategy: ev.testStrategy ?? "test-after"
|
|
34322
|
+
});
|
|
34323
|
+
} catch (err) {
|
|
34324
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
34325
|
+
}
|
|
34245
34326
|
}
|
|
34246
34327
|
}
|
|
34247
|
-
|
|
34248
|
-
|
|
34249
|
-
|
|
34250
|
-
|
|
34251
|
-
|
|
34252
|
-
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34257
|
-
|
|
34258
|
-
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34264
|
-
|
|
34265
|
-
|
|
34266
|
-
|
|
34328
|
+
});
|
|
34329
|
+
}));
|
|
34330
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34331
|
+
return safe("onStoryComplete(failed)", async () => {
|
|
34332
|
+
const reporters = pluginRegistry.getReporters();
|
|
34333
|
+
for (const r of reporters) {
|
|
34334
|
+
if (r.onStoryComplete) {
|
|
34335
|
+
try {
|
|
34336
|
+
await r.onStoryComplete({
|
|
34337
|
+
runId,
|
|
34338
|
+
storyId: ev.storyId,
|
|
34339
|
+
status: "failed",
|
|
34340
|
+
runElapsedMs: Date.now() - startTime,
|
|
34341
|
+
cost: 0,
|
|
34342
|
+
tier: "balanced",
|
|
34343
|
+
testStrategy: "test-after"
|
|
34344
|
+
});
|
|
34345
|
+
} catch (err) {
|
|
34346
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
34347
|
+
}
|
|
34348
|
+
}
|
|
34267
34349
|
}
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
|
|
34271
|
-
|
|
34272
|
-
|
|
34273
|
-
|
|
34274
|
-
|
|
34275
|
-
|
|
34276
|
-
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34283
|
-
|
|
34350
|
+
});
|
|
34351
|
+
}));
|
|
34352
|
+
unsubs.push(bus.on("story:paused", (ev) => {
|
|
34353
|
+
return safe("onStoryComplete(paused)", async () => {
|
|
34354
|
+
const reporters = pluginRegistry.getReporters();
|
|
34355
|
+
for (const r of reporters) {
|
|
34356
|
+
if (r.onStoryComplete) {
|
|
34357
|
+
try {
|
|
34358
|
+
await r.onStoryComplete({
|
|
34359
|
+
runId,
|
|
34360
|
+
storyId: ev.storyId,
|
|
34361
|
+
status: "paused",
|
|
34362
|
+
runElapsedMs: Date.now() - startTime,
|
|
34363
|
+
cost: 0,
|
|
34364
|
+
tier: "balanced",
|
|
34365
|
+
testStrategy: "test-after"
|
|
34366
|
+
});
|
|
34367
|
+
} catch (err) {
|
|
34368
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
34369
|
+
}
|
|
34370
|
+
}
|
|
34284
34371
|
}
|
|
34285
|
-
}
|
|
34286
|
-
|
|
34287
|
-
|
|
34372
|
+
});
|
|
34373
|
+
}));
|
|
34374
|
+
unsubs.push(bus.on("run:completed", (ev) => {
|
|
34375
|
+
return safe("onRunEnd", async () => {
|
|
34376
|
+
const reporters = pluginRegistry.getReporters();
|
|
34377
|
+
for (const r of reporters) {
|
|
34378
|
+
if (r.onRunEnd) {
|
|
34379
|
+
try {
|
|
34380
|
+
await r.onRunEnd({
|
|
34381
|
+
runId,
|
|
34382
|
+
totalDurationMs: Date.now() - startTime,
|
|
34383
|
+
totalCost: ev.totalCost ?? 0,
|
|
34384
|
+
storySummary: {
|
|
34385
|
+
completed: ev.passedStories,
|
|
34386
|
+
failed: ev.failedStories,
|
|
34387
|
+
skipped: 0,
|
|
34388
|
+
paused: 0
|
|
34389
|
+
}
|
|
34390
|
+
});
|
|
34391
|
+
} catch (err) {
|
|
34392
|
+
logger?.warn("plugins", `Reporter '${r.name}' onRunEnd failed`, { error: err });
|
|
34393
|
+
}
|
|
34394
|
+
}
|
|
34288
34395
|
}
|
|
34289
|
-
|
|
34290
|
-
|
|
34396
|
+
});
|
|
34397
|
+
}));
|
|
34398
|
+
return () => {
|
|
34399
|
+
for (const u of unsubs)
|
|
34400
|
+
u();
|
|
34401
|
+
};
|
|
34402
|
+
}
|
|
34403
|
+
var init_reporters = __esm(() => {
|
|
34404
|
+
init_logger2();
|
|
34405
|
+
});
|
|
34406
|
+
|
|
34407
|
+
// src/execution/deferred-review.ts
|
|
34408
|
+
var {spawn: spawn5 } = globalThis.Bun;
|
|
34409
|
+
async function captureRunStartRef(workdir) {
|
|
34410
|
+
try {
|
|
34411
|
+
const proc = _deferredReviewDeps.spawn({
|
|
34412
|
+
cmd: ["git", "rev-parse", "HEAD"],
|
|
34413
|
+
cwd: workdir,
|
|
34414
|
+
stdout: "pipe",
|
|
34415
|
+
stderr: "pipe"
|
|
34416
|
+
});
|
|
34417
|
+
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
34418
|
+
return stdout.trim();
|
|
34419
|
+
} catch {
|
|
34420
|
+
return "";
|
|
34291
34421
|
}
|
|
34292
|
-
|
|
34293
|
-
|
|
34294
|
-
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
|
|
34304
|
-
|
|
34305
|
-
|
|
34306
|
-
} catch {
|
|
34307
|
-
return [];
|
|
34308
|
-
}
|
|
34422
|
+
}
|
|
34423
|
+
async function getChangedFilesForDeferred(workdir, baseRef) {
|
|
34424
|
+
try {
|
|
34425
|
+
const proc = _deferredReviewDeps.spawn({
|
|
34426
|
+
cmd: ["git", "diff", "--name-only", `${baseRef}...HEAD`],
|
|
34427
|
+
cwd: workdir,
|
|
34428
|
+
stdout: "pipe",
|
|
34429
|
+
stderr: "pipe"
|
|
34430
|
+
});
|
|
34431
|
+
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
34432
|
+
return stdout.trim().split(`
|
|
34433
|
+
`).filter(Boolean);
|
|
34434
|
+
} catch {
|
|
34435
|
+
return [];
|
|
34309
34436
|
}
|
|
34310
|
-
|
|
34437
|
+
}
|
|
34438
|
+
async function runDeferredReview(workdir, reviewConfig, plugins, runStartRef) {
|
|
34439
|
+
if (!reviewConfig || reviewConfig.pluginMode !== "deferred") {
|
|
34440
|
+
return;
|
|
34441
|
+
}
|
|
34442
|
+
const reviewers = plugins.getReviewers();
|
|
34443
|
+
if (reviewers.length === 0) {
|
|
34444
|
+
return;
|
|
34445
|
+
}
|
|
34446
|
+
const changedFiles = await getChangedFilesForDeferred(workdir, runStartRef);
|
|
34447
|
+
const reviewerResults = [];
|
|
34448
|
+
let anyFailed = false;
|
|
34449
|
+
for (const reviewer of reviewers) {
|
|
34311
34450
|
try {
|
|
34312
|
-
const
|
|
34313
|
-
|
|
34314
|
-
|
|
34315
|
-
|
|
34451
|
+
const result = await reviewer.check(workdir, changedFiles);
|
|
34452
|
+
reviewerResults.push({
|
|
34453
|
+
name: reviewer.name,
|
|
34454
|
+
passed: result.passed,
|
|
34455
|
+
output: result.output,
|
|
34456
|
+
exitCode: result.exitCode
|
|
34316
34457
|
});
|
|
34317
|
-
|
|
34458
|
+
if (!result.passed) {
|
|
34459
|
+
anyFailed = true;
|
|
34460
|
+
}
|
|
34318
34461
|
} catch (error48) {
|
|
34319
|
-
const
|
|
34320
|
-
|
|
34321
|
-
|
|
34462
|
+
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
34463
|
+
reviewerResults.push({
|
|
34464
|
+
name: reviewer.name,
|
|
34465
|
+
passed: false,
|
|
34466
|
+
output: "",
|
|
34467
|
+
error: errorMsg
|
|
34322
34468
|
});
|
|
34469
|
+
anyFailed = true;
|
|
34323
34470
|
}
|
|
34324
34471
|
}
|
|
34472
|
+
return { runStartRef, changedFiles, reviewerResults, anyFailed };
|
|
34325
34473
|
}
|
|
34326
|
-
var
|
|
34327
|
-
var
|
|
34328
|
-
|
|
34329
|
-
init_bun_deps();
|
|
34330
|
-
_mergeDeps = {
|
|
34331
|
-
spawn
|
|
34332
|
-
};
|
|
34474
|
+
var _deferredReviewDeps;
|
|
34475
|
+
var init_deferred_review = __esm(() => {
|
|
34476
|
+
_deferredReviewDeps = { spawn: spawn5 };
|
|
34333
34477
|
});
|
|
34334
34478
|
|
|
34335
|
-
// src/execution/
|
|
34336
|
-
|
|
34479
|
+
// src/execution/executor-types.ts
|
|
34480
|
+
function buildPreviewRouting(story, config2) {
|
|
34481
|
+
const cached2 = story.routing;
|
|
34482
|
+
const defaultComplexity = "medium";
|
|
34483
|
+
const defaultTier = "balanced";
|
|
34484
|
+
const defaultStrategy = "test-after";
|
|
34485
|
+
return {
|
|
34486
|
+
complexity: cached2?.complexity ?? defaultComplexity,
|
|
34487
|
+
modelTier: cached2?.modelTier ?? config2.autoMode.complexityRouting?.[defaultComplexity] ?? defaultTier,
|
|
34488
|
+
testStrategy: cached2?.testStrategy ?? defaultStrategy,
|
|
34489
|
+
reasoning: cached2 ? "cached from story.routing" : "preview (pending pipeline routing stage)"
|
|
34490
|
+
};
|
|
34491
|
+
}
|
|
34492
|
+
|
|
34493
|
+
// src/execution/dry-run.ts
|
|
34494
|
+
async function handleDryRun(ctx) {
|
|
34337
34495
|
const logger = getSafeLogger();
|
|
34338
|
-
|
|
34339
|
-
|
|
34340
|
-
|
|
34341
|
-
|
|
34342
|
-
|
|
34343
|
-
|
|
34344
|
-
|
|
34345
|
-
|
|
34346
|
-
|
|
34347
|
-
|
|
34348
|
-
|
|
34349
|
-
|
|
34496
|
+
ctx.statusWriter.setPrd(ctx.prd);
|
|
34497
|
+
ctx.statusWriter.setCurrentStory({
|
|
34498
|
+
storyId: ctx.storiesToExecute[0].id,
|
|
34499
|
+
title: ctx.storiesToExecute[0].title,
|
|
34500
|
+
complexity: ctx.routing.complexity,
|
|
34501
|
+
tddStrategy: ctx.routing.testStrategy,
|
|
34502
|
+
model: ctx.routing.modelTier,
|
|
34503
|
+
attempt: (ctx.storiesToExecute[0].attempts ?? 0) + 1,
|
|
34504
|
+
phase: "routing"
|
|
34505
|
+
});
|
|
34506
|
+
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
34507
|
+
for (const s of ctx.storiesToExecute) {
|
|
34508
|
+
logger?.info("execution", "[DRY RUN] Would execute agent here", {
|
|
34509
|
+
storyId: s.id,
|
|
34510
|
+
storyTitle: s.title,
|
|
34511
|
+
modelTier: ctx.routing.modelTier,
|
|
34512
|
+
complexity: ctx.routing.complexity,
|
|
34513
|
+
testStrategy: ctx.routing.testStrategy
|
|
34350
34514
|
});
|
|
34351
|
-
const result = await runPipeline(defaultPipeline, pipelineContext, eventEmitter);
|
|
34352
|
-
return {
|
|
34353
|
-
success: result.success,
|
|
34354
|
-
cost: result.context.agentResult?.estimatedCost || 0,
|
|
34355
|
-
error: result.success ? undefined : result.reason
|
|
34356
|
-
};
|
|
34357
|
-
} catch (error48) {
|
|
34358
|
-
return {
|
|
34359
|
-
success: false,
|
|
34360
|
-
cost: 0,
|
|
34361
|
-
error: errorMessage(error48)
|
|
34362
|
-
};
|
|
34363
34515
|
}
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34368
|
-
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
|
|
34372
|
-
|
|
34373
|
-
|
|
34374
|
-
|
|
34375
|
-
|
|
34376
|
-
|
|
34377
|
-
|
|
34378
|
-
if (!worktreePath) {
|
|
34379
|
-
results.failed.push({
|
|
34380
|
-
story,
|
|
34381
|
-
error: "Worktree not created"
|
|
34382
|
-
});
|
|
34383
|
-
continue;
|
|
34384
|
-
}
|
|
34385
|
-
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
34386
|
-
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
34387
|
-
const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
|
|
34388
|
-
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
34389
|
-
results.totalCost += result.cost;
|
|
34390
|
-
results.storyCosts.set(story.id, result.cost);
|
|
34391
|
-
if (result.success) {
|
|
34392
|
-
results.pipelinePassed.push(story);
|
|
34393
|
-
logger?.info("parallel", "Story execution succeeded", {
|
|
34394
|
-
storyId: story.id,
|
|
34395
|
-
cost: result.cost
|
|
34396
|
-
});
|
|
34397
|
-
} else {
|
|
34398
|
-
results.failed.push({ story, error: result.error || "Unknown error" });
|
|
34399
|
-
logger?.error("parallel", "Story execution failed", {
|
|
34400
|
-
storyId: story.id,
|
|
34401
|
-
error: result.error
|
|
34402
|
-
});
|
|
34403
|
-
}
|
|
34404
|
-
}).finally(() => {
|
|
34405
|
-
executing.delete(executePromise);
|
|
34516
|
+
for (const s of ctx.storiesToExecute) {
|
|
34517
|
+
markStoryPassed(ctx.prd, s.id);
|
|
34518
|
+
}
|
|
34519
|
+
await savePRD(ctx.prd, ctx.prdPath);
|
|
34520
|
+
for (const s of ctx.storiesToExecute) {
|
|
34521
|
+
pipelineEventBus.emit({
|
|
34522
|
+
type: "story:completed",
|
|
34523
|
+
storyId: s.id,
|
|
34524
|
+
story: s,
|
|
34525
|
+
passed: true,
|
|
34526
|
+
runElapsedMs: 0,
|
|
34527
|
+
cost: 0,
|
|
34528
|
+
modelTier: ctx.routing.modelTier,
|
|
34529
|
+
testStrategy: ctx.routing.testStrategy
|
|
34406
34530
|
});
|
|
34407
|
-
executing.add(executePromise);
|
|
34408
|
-
if (executing.size >= maxConcurrency) {
|
|
34409
|
-
await Promise.race(executing);
|
|
34410
|
-
}
|
|
34411
34531
|
}
|
|
34412
|
-
|
|
34413
|
-
|
|
34532
|
+
ctx.statusWriter.setPrd(ctx.prd);
|
|
34533
|
+
ctx.statusWriter.setCurrentStory(null);
|
|
34534
|
+
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
34535
|
+
return { storiesCompletedDelta: ctx.storiesToExecute.length, prdDirty: true };
|
|
34414
34536
|
}
|
|
34415
|
-
var
|
|
34537
|
+
var init_dry_run = __esm(() => {
|
|
34416
34538
|
init_logger2();
|
|
34417
|
-
|
|
34418
|
-
|
|
34419
|
-
init_routing();
|
|
34539
|
+
init_event_bus();
|
|
34540
|
+
init_prd();
|
|
34420
34541
|
});
|
|
34421
34542
|
|
|
34422
|
-
// src/execution/
|
|
34423
|
-
|
|
34424
|
-
|
|
34425
|
-
|
|
34426
|
-
|
|
34427
|
-
|
|
34428
|
-
|
|
34429
|
-
|
|
34430
|
-
|
|
34431
|
-
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
if (depsCompleted) {
|
|
34437
|
-
batch.push(story);
|
|
34438
|
-
}
|
|
34439
|
-
}
|
|
34440
|
-
if (batch.length === 0) {
|
|
34441
|
-
const remaining = stories.filter((s) => !processed.has(s.id));
|
|
34442
|
-
const logger = getSafeLogger();
|
|
34443
|
-
logger?.error("parallel", "Cannot resolve story dependencies", {
|
|
34444
|
-
remainingStories: remaining.map((s) => s.id)
|
|
34445
|
-
});
|
|
34446
|
-
throw new Error("Circular dependency or missing dependency detected");
|
|
34447
|
-
}
|
|
34448
|
-
for (const story of batch) {
|
|
34449
|
-
processed.add(story.id);
|
|
34543
|
+
// src/execution/escalation/tier-outcome.ts
|
|
34544
|
+
async function handleNoTierAvailable(ctx, failureCategory) {
|
|
34545
|
+
const logger = getSafeLogger();
|
|
34546
|
+
const outcome = resolveMaxAttemptsOutcome(failureCategory);
|
|
34547
|
+
if (outcome === "pause") {
|
|
34548
|
+
const pausedPrd = { ...ctx.prd };
|
|
34549
|
+
markStoryPaused(pausedPrd, ctx.story.id);
|
|
34550
|
+
await savePRD(pausedPrd, ctx.prdPath);
|
|
34551
|
+
logger?.warn("execution", "Story paused - no tier available (needs human review)", {
|
|
34552
|
+
storyId: ctx.story.id,
|
|
34553
|
+
failureCategory
|
|
34554
|
+
});
|
|
34555
|
+
if (ctx.featureDir) {
|
|
34556
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Execution stopped (needs human review)`);
|
|
34450
34557
|
}
|
|
34451
|
-
|
|
34452
|
-
|
|
34453
|
-
|
|
34454
|
-
}
|
|
34455
|
-
|
|
34456
|
-
|
|
34457
|
-
|
|
34458
|
-
deps[story.id] = story.dependencies;
|
|
34558
|
+
pipelineEventBus.emit({
|
|
34559
|
+
type: "story:paused",
|
|
34560
|
+
storyId: ctx.story.id,
|
|
34561
|
+
reason: `Execution stopped (${failureCategory ?? "unknown"} requires human review)`,
|
|
34562
|
+
cost: ctx.totalCost
|
|
34563
|
+
});
|
|
34564
|
+
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
34459
34565
|
}
|
|
34460
|
-
|
|
34461
|
-
|
|
34462
|
-
|
|
34463
|
-
|
|
34464
|
-
|
|
34566
|
+
const failedPrd = { ...ctx.prd };
|
|
34567
|
+
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
34568
|
+
await savePRD(failedPrd, ctx.prdPath);
|
|
34569
|
+
logger?.error("execution", "Story failed - execution failed", {
|
|
34570
|
+
storyId: ctx.story.id
|
|
34571
|
+
});
|
|
34572
|
+
if (ctx.featureDir) {
|
|
34573
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Execution failed`);
|
|
34465
34574
|
}
|
|
34466
|
-
|
|
34575
|
+
pipelineEventBus.emit({
|
|
34576
|
+
type: "story:failed",
|
|
34577
|
+
storyId: ctx.story.id,
|
|
34578
|
+
story: ctx.story,
|
|
34579
|
+
reason: "Execution failed",
|
|
34580
|
+
countsTowardEscalation: true
|
|
34581
|
+
});
|
|
34582
|
+
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
34467
34583
|
}
|
|
34468
|
-
async function
|
|
34584
|
+
async function handleMaxAttemptsReached(ctx, failureCategory) {
|
|
34469
34585
|
const logger = getSafeLogger();
|
|
34470
|
-
const
|
|
34471
|
-
|
|
34472
|
-
|
|
34473
|
-
|
|
34474
|
-
|
|
34475
|
-
|
|
34476
|
-
|
|
34477
|
-
|
|
34478
|
-
logger?.info("parallel", "Grouped stories into batches", {
|
|
34479
|
-
batchCount: batches.length,
|
|
34480
|
-
batches: batches.map((b, i) => ({ index: i, storyCount: b.length, storyIds: b.map((s) => s.id) }))
|
|
34481
|
-
});
|
|
34482
|
-
let storiesCompleted = 0;
|
|
34483
|
-
let totalCost = 0;
|
|
34484
|
-
const currentPrd = prd;
|
|
34485
|
-
const allMergeConflicts = [];
|
|
34486
|
-
for (let batchIndex = 0;batchIndex < batches.length; batchIndex++) {
|
|
34487
|
-
const batch = batches[batchIndex];
|
|
34488
|
-
logger?.info("parallel", `Executing batch ${batchIndex + 1}/${batches.length}`, {
|
|
34489
|
-
storyCount: batch.length,
|
|
34490
|
-
storyIds: batch.map((s) => s.id)
|
|
34586
|
+
const outcome = resolveMaxAttemptsOutcome(failureCategory);
|
|
34587
|
+
if (outcome === "pause") {
|
|
34588
|
+
const pausedPrd = { ...ctx.prd };
|
|
34589
|
+
markStoryPaused(pausedPrd, ctx.story.id);
|
|
34590
|
+
await savePRD(pausedPrd, ctx.prdPath);
|
|
34591
|
+
logger?.warn("execution", "Story paused - max attempts reached (needs human review)", {
|
|
34592
|
+
storyId: ctx.story.id,
|
|
34593
|
+
failureCategory
|
|
34491
34594
|
});
|
|
34492
|
-
|
|
34493
|
-
|
|
34494
|
-
effectiveConfig: config2,
|
|
34495
|
-
prd: currentPrd,
|
|
34496
|
-
featureDir,
|
|
34497
|
-
hooks,
|
|
34498
|
-
plugins,
|
|
34499
|
-
storyStartTime: new Date().toISOString(),
|
|
34500
|
-
agentGetFn,
|
|
34501
|
-
pidRegistry,
|
|
34502
|
-
interaction: interactionChain ?? undefined
|
|
34503
|
-
};
|
|
34504
|
-
const worktreePaths = new Map;
|
|
34505
|
-
const storyEffectiveConfigs = new Map;
|
|
34506
|
-
for (const story of batch) {
|
|
34507
|
-
const worktreePath = join48(projectRoot, ".nax-wt", story.id);
|
|
34508
|
-
try {
|
|
34509
|
-
await worktreeManager.create(projectRoot, story.id);
|
|
34510
|
-
worktreePaths.set(story.id, worktreePath);
|
|
34511
|
-
logger?.info("parallel", "Created worktree for story", {
|
|
34512
|
-
storyId: story.id,
|
|
34513
|
-
worktreePath
|
|
34514
|
-
});
|
|
34515
|
-
if (story.workdir) {
|
|
34516
|
-
const pkgNodeModulesSrc = join48(projectRoot, story.workdir, "node_modules");
|
|
34517
|
-
const pkgNodeModulesDst = join48(worktreePath, story.workdir, "node_modules");
|
|
34518
|
-
if (existsSync33(pkgNodeModulesSrc) && !existsSync33(pkgNodeModulesDst)) {
|
|
34519
|
-
try {
|
|
34520
|
-
symlinkSync2(pkgNodeModulesSrc, pkgNodeModulesDst, "dir");
|
|
34521
|
-
logger?.debug("parallel", "Symlinked package node_modules", {
|
|
34522
|
-
storyId: story.id,
|
|
34523
|
-
src: pkgNodeModulesSrc
|
|
34524
|
-
});
|
|
34525
|
-
} catch (symlinkError) {
|
|
34526
|
-
logger?.warn("parallel", "Failed to symlink package node_modules \u2014 test runner may not find deps", {
|
|
34527
|
-
storyId: story.id,
|
|
34528
|
-
error: errorMessage(symlinkError)
|
|
34529
|
-
});
|
|
34530
|
-
}
|
|
34531
|
-
}
|
|
34532
|
-
}
|
|
34533
|
-
const rootConfigPath = join48(projectRoot, ".nax", "config.json");
|
|
34534
|
-
const effectiveConfig = story.workdir ? await loadConfigForWorkdir(rootConfigPath, story.workdir) : config2;
|
|
34535
|
-
storyEffectiveConfigs.set(story.id, effectiveConfig);
|
|
34536
|
-
} catch (error48) {
|
|
34537
|
-
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
34538
|
-
logger?.error("parallel", "Failed to create worktree", {
|
|
34539
|
-
storyId: story.id,
|
|
34540
|
-
error: errorMessage(error48)
|
|
34541
|
-
});
|
|
34542
|
-
}
|
|
34543
|
-
}
|
|
34544
|
-
const batchResult = await executeParallelBatch(batch, projectRoot, config2, baseContext, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs);
|
|
34545
|
-
totalCost += batchResult.totalCost;
|
|
34546
|
-
if (batchResult.pipelinePassed.length > 0) {
|
|
34547
|
-
const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
|
|
34548
|
-
const deps = buildDependencyMap(batch);
|
|
34549
|
-
logger?.info("parallel", "Merging successful stories", {
|
|
34550
|
-
storyIds: successfulIds
|
|
34551
|
-
});
|
|
34552
|
-
const mergeResults = await mergeEngine.mergeAll(projectRoot, successfulIds, deps);
|
|
34553
|
-
for (const mergeResult of mergeResults) {
|
|
34554
|
-
if (mergeResult.success) {
|
|
34555
|
-
markStoryPassed(currentPrd, mergeResult.storyId);
|
|
34556
|
-
storiesCompleted++;
|
|
34557
|
-
const mergedStory = batchResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
|
|
34558
|
-
if (mergedStory)
|
|
34559
|
-
batchResult.merged.push(mergedStory);
|
|
34560
|
-
logger?.info("parallel", "Story merged successfully", {
|
|
34561
|
-
storyId: mergeResult.storyId,
|
|
34562
|
-
retryCount: mergeResult.retryCount
|
|
34563
|
-
});
|
|
34564
|
-
} else {
|
|
34565
|
-
markStoryFailed(currentPrd, mergeResult.storyId, undefined, undefined);
|
|
34566
|
-
batchResult.mergeConflicts.push({
|
|
34567
|
-
storyId: mergeResult.storyId,
|
|
34568
|
-
conflictFiles: mergeResult.conflictFiles || [],
|
|
34569
|
-
originalCost: batchResult.storyCosts.get(mergeResult.storyId) ?? 0
|
|
34570
|
-
});
|
|
34571
|
-
logger?.error("parallel", "Merge conflict", {
|
|
34572
|
-
storyId: mergeResult.storyId,
|
|
34573
|
-
conflictFiles: mergeResult.conflictFiles
|
|
34574
|
-
});
|
|
34575
|
-
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
34576
|
-
storyId: mergeResult.storyId,
|
|
34577
|
-
worktreePath: join48(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
34578
|
-
});
|
|
34579
|
-
}
|
|
34580
|
-
}
|
|
34581
|
-
}
|
|
34582
|
-
for (const { story, error: error48 } of batchResult.failed) {
|
|
34583
|
-
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
34584
|
-
logger?.error("parallel", "Cleaning up failed story worktree", {
|
|
34585
|
-
storyId: story.id,
|
|
34586
|
-
error: error48
|
|
34587
|
-
});
|
|
34588
|
-
try {
|
|
34589
|
-
await worktreeManager.remove(projectRoot, story.id);
|
|
34590
|
-
} catch (cleanupError) {
|
|
34591
|
-
logger?.warn("parallel", "Failed to clean up worktree", {
|
|
34592
|
-
storyId: story.id,
|
|
34593
|
-
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
34594
|
-
});
|
|
34595
|
-
}
|
|
34595
|
+
if (ctx.featureDir) {
|
|
34596
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Max attempts reached (needs human review)`);
|
|
34596
34597
|
}
|
|
34597
|
-
|
|
34598
|
-
|
|
34599
|
-
|
|
34600
|
-
|
|
34601
|
-
|
|
34602
|
-
failed: batchResult.failed.length,
|
|
34603
|
-
mergeConflicts: batchResult.mergeConflicts.length,
|
|
34604
|
-
batchCost: batchResult.totalCost
|
|
34598
|
+
pipelineEventBus.emit({
|
|
34599
|
+
type: "story:paused",
|
|
34600
|
+
storyId: ctx.story.id,
|
|
34601
|
+
reason: `Max attempts reached (${failureCategory ?? "unknown"} requires human review)`,
|
|
34602
|
+
cost: ctx.totalCost
|
|
34605
34603
|
});
|
|
34604
|
+
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
34606
34605
|
}
|
|
34607
|
-
|
|
34608
|
-
|
|
34609
|
-
|
|
34606
|
+
const failedPrd = { ...ctx.prd };
|
|
34607
|
+
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
34608
|
+
await savePRD(failedPrd, ctx.prdPath);
|
|
34609
|
+
logger?.error("execution", "Story failed - max attempts reached", {
|
|
34610
|
+
storyId: ctx.story.id,
|
|
34611
|
+
failureCategory
|
|
34610
34612
|
});
|
|
34611
|
-
|
|
34612
|
-
}
|
|
34613
|
-
var init_parallel_coordinator = __esm(() => {
|
|
34614
|
-
init_loader();
|
|
34615
|
-
init_logger2();
|
|
34616
|
-
init_prd();
|
|
34617
|
-
init_manager();
|
|
34618
|
-
init_merge();
|
|
34619
|
-
init_parallel_worker();
|
|
34620
|
-
});
|
|
34621
|
-
|
|
34622
|
-
// src/execution/parallel.ts
|
|
34623
|
-
var init_parallel = __esm(() => {
|
|
34624
|
-
init_parallel_coordinator();
|
|
34625
|
-
});
|
|
34626
|
-
|
|
34627
|
-
// src/execution/parallel-executor-rectify.ts
|
|
34628
|
-
var exports_parallel_executor_rectify = {};
|
|
34629
|
-
__export(exports_parallel_executor_rectify, {
|
|
34630
|
-
rectifyConflictedStory: () => rectifyConflictedStory
|
|
34631
|
-
});
|
|
34632
|
-
import path15 from "path";
|
|
34633
|
-
async function rectifyConflictedStory(options) {
|
|
34634
|
-
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter, agentGetFn } = options;
|
|
34635
|
-
const logger = getSafeLogger();
|
|
34636
|
-
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
34637
|
-
try {
|
|
34638
|
-
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
34639
|
-
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
34640
|
-
const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
34641
|
-
const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
|
|
34642
|
-
const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
|
|
34643
|
-
const worktreeManager = new WorktreeManager2;
|
|
34644
|
-
const mergeEngine = new MergeEngine2(worktreeManager);
|
|
34645
|
-
try {
|
|
34646
|
-
await worktreeManager.remove(workdir, storyId);
|
|
34647
|
-
} catch {}
|
|
34648
|
-
await worktreeManager.create(workdir, storyId);
|
|
34649
|
-
const worktreePath = path15.join(workdir, ".nax-wt", storyId);
|
|
34650
|
-
const story = prd.userStories.find((s) => s.id === storyId);
|
|
34651
|
-
if (!story) {
|
|
34652
|
-
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
34653
|
-
}
|
|
34654
|
-
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
34655
|
-
const pipelineContext = {
|
|
34656
|
-
config: config2,
|
|
34657
|
-
effectiveConfig: config2,
|
|
34658
|
-
prd,
|
|
34659
|
-
story,
|
|
34660
|
-
stories: [story],
|
|
34661
|
-
workdir: worktreePath,
|
|
34662
|
-
featureDir: undefined,
|
|
34663
|
-
hooks,
|
|
34664
|
-
plugins: pluginRegistry,
|
|
34665
|
-
storyStartTime: new Date().toISOString(),
|
|
34666
|
-
routing,
|
|
34667
|
-
agentGetFn
|
|
34668
|
-
};
|
|
34669
|
-
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
34670
|
-
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
34671
|
-
if (!pipelineResult.success) {
|
|
34672
|
-
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
34673
|
-
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
34674
|
-
}
|
|
34675
|
-
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
34676
|
-
const mergeResult = mergeResults[0];
|
|
34677
|
-
if (!mergeResult || !mergeResult.success) {
|
|
34678
|
-
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
34679
|
-
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
34680
|
-
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
34681
|
-
}
|
|
34682
|
-
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
34683
|
-
storyId,
|
|
34684
|
-
originalCost: options.originalCost,
|
|
34685
|
-
rectificationCost: cost
|
|
34686
|
-
});
|
|
34687
|
-
return { success: true, storyId, cost };
|
|
34688
|
-
} catch (error48) {
|
|
34689
|
-
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
34690
|
-
storyId,
|
|
34691
|
-
error: errorMessage(error48)
|
|
34692
|
-
});
|
|
34693
|
-
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
34613
|
+
if (ctx.featureDir) {
|
|
34614
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Max attempts reached`);
|
|
34694
34615
|
}
|
|
34616
|
+
pipelineEventBus.emit({
|
|
34617
|
+
type: "story:failed",
|
|
34618
|
+
storyId: ctx.story.id,
|
|
34619
|
+
story: ctx.story,
|
|
34620
|
+
reason: "Max attempts reached",
|
|
34621
|
+
countsTowardEscalation: true
|
|
34622
|
+
});
|
|
34623
|
+
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
34695
34624
|
}
|
|
34696
|
-
var
|
|
34625
|
+
var init_tier_outcome = __esm(() => {
|
|
34697
34626
|
init_logger2();
|
|
34627
|
+
init_event_bus();
|
|
34628
|
+
init_prd();
|
|
34629
|
+
init_progress();
|
|
34630
|
+
init_tier_escalation();
|
|
34698
34631
|
});
|
|
34699
34632
|
|
|
34700
|
-
// src/execution/
|
|
34701
|
-
|
|
34633
|
+
// src/execution/escalation/tier-escalation.ts
|
|
34634
|
+
function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
|
|
34635
|
+
const stage = reviewFindings && reviewFindings.length > 0 ? "review" : "escalation";
|
|
34636
|
+
return {
|
|
34637
|
+
attempt: (story.attempts ?? 0) + 1,
|
|
34638
|
+
modelTier: currentTier,
|
|
34639
|
+
stage,
|
|
34640
|
+
summary: `Failed with tier ${currentTier}, escalating to next tier`,
|
|
34641
|
+
reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
|
|
34642
|
+
cost: cost ?? 0,
|
|
34643
|
+
timestamp: new Date().toISOString()
|
|
34644
|
+
};
|
|
34645
|
+
}
|
|
34646
|
+
function resolveMaxAttemptsOutcome(failureCategory) {
|
|
34647
|
+
if (!failureCategory) {
|
|
34648
|
+
return "fail";
|
|
34649
|
+
}
|
|
34650
|
+
switch (failureCategory) {
|
|
34651
|
+
case "isolation-violation":
|
|
34652
|
+
case "verifier-rejected":
|
|
34653
|
+
case "greenfield-no-tests":
|
|
34654
|
+
return "pause";
|
|
34655
|
+
case "runtime-crash":
|
|
34656
|
+
return "pause";
|
|
34657
|
+
case "session-failure":
|
|
34658
|
+
case "tests-failing":
|
|
34659
|
+
return "fail";
|
|
34660
|
+
default:
|
|
34661
|
+
return "fail";
|
|
34662
|
+
}
|
|
34663
|
+
}
|
|
34664
|
+
function shouldRetrySameTier(verifyResult) {
|
|
34665
|
+
return verifyResult?.status === "RUNTIME_CRASH";
|
|
34666
|
+
}
|
|
34667
|
+
async function handleTierEscalation(ctx) {
|
|
34702
34668
|
const logger = getSafeLogger();
|
|
34703
|
-
|
|
34704
|
-
|
|
34705
|
-
|
|
34706
|
-
return importedRectify(opts);
|
|
34707
|
-
});
|
|
34708
|
-
const rectificationMetrics = [];
|
|
34709
|
-
let rectifiedCount = 0;
|
|
34710
|
-
let stillConflictingCount = 0;
|
|
34711
|
-
let additionalCost = 0;
|
|
34712
|
-
logger?.info("parallel", "Starting merge conflict rectification", {
|
|
34713
|
-
stories: conflictedStories.map((s) => s.storyId),
|
|
34714
|
-
totalConflicts: conflictedStories.length
|
|
34715
|
-
});
|
|
34716
|
-
for (const conflictInfo of conflictedStories) {
|
|
34717
|
-
const result = await rectify({
|
|
34718
|
-
...conflictInfo,
|
|
34719
|
-
workdir,
|
|
34720
|
-
config: config2,
|
|
34721
|
-
hooks,
|
|
34722
|
-
pluginRegistry,
|
|
34723
|
-
prd,
|
|
34724
|
-
eventEmitter,
|
|
34725
|
-
agentGetFn
|
|
34669
|
+
if (shouldRetrySameTier(ctx.verifyResult)) {
|
|
34670
|
+
logger?.warn("escalation", "Runtime crash detected \u2014 retrying same tier (transient, not a code issue)", {
|
|
34671
|
+
storyId: ctx.story.id
|
|
34726
34672
|
});
|
|
34727
|
-
|
|
34728
|
-
|
|
34729
|
-
|
|
34730
|
-
|
|
34731
|
-
|
|
34732
|
-
|
|
34733
|
-
|
|
34734
|
-
|
|
34735
|
-
|
|
34736
|
-
|
|
34737
|
-
|
|
34738
|
-
|
|
34739
|
-
|
|
34740
|
-
|
|
34741
|
-
|
|
34742
|
-
|
|
34743
|
-
|
|
34744
|
-
|
|
34745
|
-
|
|
34746
|
-
|
|
34747
|
-
|
|
34673
|
+
return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
|
|
34674
|
+
}
|
|
34675
|
+
const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
|
|
34676
|
+
const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
|
|
34677
|
+
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
34678
|
+
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
34679
|
+
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
34680
|
+
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
34681
|
+
const escalateRetryAsTestAfter = escalateFailureCategory === "greenfield-no-tests";
|
|
34682
|
+
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
34683
|
+
if (!nextTier || !ctx.config.autoMode.escalation.enabled) {
|
|
34684
|
+
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
34685
|
+
}
|
|
34686
|
+
const maxAttempts = calculateMaxIterations(ctx.config.autoMode.escalation.tierOrder);
|
|
34687
|
+
const canEscalate = storiesToEscalate.every((s) => (s.attempts ?? 0) < maxAttempts);
|
|
34688
|
+
if (!canEscalate) {
|
|
34689
|
+
return await handleMaxAttemptsReached(ctx, escalateFailureCategory);
|
|
34690
|
+
}
|
|
34691
|
+
for (const s of storiesToEscalate) {
|
|
34692
|
+
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34693
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34694
|
+
if (shouldSwitchToTestAfter) {
|
|
34695
|
+
logger?.warn("escalation", "Switching strategy to test-after (greenfield-no-tests fallback)", {
|
|
34696
|
+
storyId: s.id,
|
|
34697
|
+
fromStrategy: currentTestStrategy,
|
|
34698
|
+
toStrategy: "test-after"
|
|
34748
34699
|
});
|
|
34749
34700
|
} else {
|
|
34750
|
-
|
|
34751
|
-
|
|
34752
|
-
|
|
34753
|
-
|
|
34701
|
+
logger?.warn("escalation", "Escalating story to next tier", {
|
|
34702
|
+
storyId: s.id,
|
|
34703
|
+
nextTier,
|
|
34704
|
+
retryAsLite: escalateRetryAsLite
|
|
34705
|
+
});
|
|
34754
34706
|
}
|
|
34755
34707
|
}
|
|
34756
|
-
|
|
34757
|
-
|
|
34758
|
-
|
|
34759
|
-
|
|
34760
|
-
|
|
34708
|
+
const pipelineReason = ctx.pipelineResult.reason ? `: ${ctx.pipelineResult.reason}` : "";
|
|
34709
|
+
const errorMessage2 = `Attempt ${ctx.story.attempts + 1} failed with model tier: ${ctx.routing.modelTier}${ctx.isBatchExecution ? " (in batch)" : ""}${pipelineReason}`;
|
|
34710
|
+
const updatedPrd = {
|
|
34711
|
+
...ctx.prd,
|
|
34712
|
+
userStories: ctx.prd.userStories.map((s) => {
|
|
34713
|
+
const shouldEscalate = storiesToEscalate.some((story) => story.id === s.id);
|
|
34714
|
+
if (!shouldEscalate)
|
|
34715
|
+
return s;
|
|
34716
|
+
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34717
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34718
|
+
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
34719
|
+
const updatedRouting = {
|
|
34720
|
+
...baseRouting,
|
|
34721
|
+
modelTier: shouldSwitchToTestAfter ? baseRouting.modelTier : nextTier,
|
|
34722
|
+
...escalateRetryAsLite ? { testStrategy: "three-session-tdd-lite" } : {},
|
|
34723
|
+
...shouldSwitchToTestAfter ? { testStrategy: "test-after" } : {}
|
|
34724
|
+
};
|
|
34725
|
+
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
34726
|
+
const isChangingTier = currentStoryTier !== nextTier;
|
|
34727
|
+
const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
|
|
34728
|
+
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
34729
|
+
return {
|
|
34730
|
+
...s,
|
|
34731
|
+
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
34732
|
+
routing: updatedRouting,
|
|
34733
|
+
priorErrors: [...s.priorErrors || [], errorMessage2],
|
|
34734
|
+
priorFailures: [...s.priorFailures || [], escalationFailure]
|
|
34735
|
+
};
|
|
34736
|
+
})
|
|
34737
|
+
};
|
|
34738
|
+
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
34739
|
+
for (const story of storiesToEscalate) {
|
|
34740
|
+
clearCacheForStory(story.id);
|
|
34741
|
+
}
|
|
34742
|
+
if (routingMode === "hybrid") {
|
|
34743
|
+
await tryLlmBatchRoute(ctx.config, storiesToEscalate, "hybrid-re-route-pipeline");
|
|
34744
|
+
}
|
|
34745
|
+
return {
|
|
34746
|
+
outcome: "escalated",
|
|
34747
|
+
prdDirty: true,
|
|
34748
|
+
prd: updatedPrd
|
|
34749
|
+
};
|
|
34761
34750
|
}
|
|
34762
|
-
var
|
|
34751
|
+
var _tierEscalationDeps;
|
|
34752
|
+
var init_tier_escalation = __esm(() => {
|
|
34753
|
+
init_hooks();
|
|
34763
34754
|
init_logger2();
|
|
34764
34755
|
init_prd();
|
|
34756
|
+
init_routing();
|
|
34757
|
+
init_llm();
|
|
34758
|
+
init_escalation();
|
|
34759
|
+
init_helpers();
|
|
34760
|
+
init_progress();
|
|
34761
|
+
init_tier_outcome();
|
|
34762
|
+
_tierEscalationDeps = {
|
|
34763
|
+
savePRD
|
|
34764
|
+
};
|
|
34765
34765
|
});
|
|
34766
34766
|
|
|
34767
|
-
// src/execution/
|
|
34768
|
-
var
|
|
34769
|
-
|
|
34770
|
-
handleParallelCompletion: () => handleParallelCompletion
|
|
34767
|
+
// src/execution/escalation/index.ts
|
|
34768
|
+
var init_escalation = __esm(() => {
|
|
34769
|
+
init_tier_escalation();
|
|
34771
34770
|
});
|
|
34772
|
-
|
|
34771
|
+
|
|
34772
|
+
// src/execution/pipeline-result-handler.ts
|
|
34773
|
+
function filterOutputFiles(files) {
|
|
34774
|
+
const NOISE = [
|
|
34775
|
+
/\.test\.(ts|js|tsx|jsx)$/,
|
|
34776
|
+
/\.spec\.(ts|js|tsx|jsx)$/,
|
|
34777
|
+
/package-lock\.json$/,
|
|
34778
|
+
/bun\.lock(b?)$/,
|
|
34779
|
+
/\.gitignore$/,
|
|
34780
|
+
/^nax\//
|
|
34781
|
+
];
|
|
34782
|
+
return files.filter((f) => !NOISE.some((p) => p.test(f))).slice(0, 15);
|
|
34783
|
+
}
|
|
34784
|
+
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
34773
34785
|
const logger = getSafeLogger();
|
|
34774
|
-
const
|
|
34775
|
-
|
|
34776
|
-
|
|
34777
|
-
|
|
34778
|
-
|
|
34779
|
-
|
|
34780
|
-
|
|
34781
|
-
|
|
34782
|
-
|
|
34783
|
-
|
|
34784
|
-
|
|
34785
|
-
|
|
34786
|
-
|
|
34787
|
-
|
|
34788
|
-
|
|
34789
|
-
|
|
34790
|
-
|
|
34791
|
-
|
|
34792
|
-
totalCost,
|
|
34793
|
-
totalStories: allStoryMetrics.length,
|
|
34794
|
-
storiesCompleted,
|
|
34795
|
-
storiesFailed: countStories(prd).failed,
|
|
34796
|
-
totalDurationMs: durationMs,
|
|
34797
|
-
stories: allStoryMetrics
|
|
34798
|
-
};
|
|
34799
|
-
await saveRunMetrics(workdir, runMetrics);
|
|
34800
|
-
const finalCounts = countStories(prd);
|
|
34801
|
-
logger?.info("run.complete", "Feature execution completed", {
|
|
34802
|
-
runId,
|
|
34803
|
-
feature,
|
|
34804
|
-
success: true,
|
|
34805
|
-
totalStories: finalCounts.total,
|
|
34806
|
-
storiesCompleted,
|
|
34807
|
-
storiesFailed: finalCounts.failed,
|
|
34808
|
-
storiesPending: finalCounts.pending,
|
|
34809
|
-
totalCost,
|
|
34810
|
-
durationMs
|
|
34811
|
-
});
|
|
34812
|
-
const reporters = pluginRegistry.getReporters();
|
|
34813
|
-
for (const reporter of reporters) {
|
|
34814
|
-
if (reporter.onRunEnd) {
|
|
34786
|
+
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
34787
|
+
const prd = ctx.prd;
|
|
34788
|
+
if (pipelineResult.context.storyMetrics) {
|
|
34789
|
+
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
34790
|
+
}
|
|
34791
|
+
const storiesCompletedDelta = ctx.storiesToExecute.length;
|
|
34792
|
+
for (const completedStory of ctx.storiesToExecute) {
|
|
34793
|
+
const now = Date.now();
|
|
34794
|
+
logger?.info("story.complete", "Story completed successfully", {
|
|
34795
|
+
storyId: completedStory.id,
|
|
34796
|
+
storyTitle: completedStory.title,
|
|
34797
|
+
totalCost: ctx.totalCost + costDelta,
|
|
34798
|
+
runElapsedMs: now - ctx.startTime,
|
|
34799
|
+
storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined
|
|
34800
|
+
});
|
|
34801
|
+
}
|
|
34802
|
+
if (ctx.storyGitRef) {
|
|
34803
|
+
for (const completedStory of ctx.storiesToExecute) {
|
|
34815
34804
|
try {
|
|
34816
|
-
await
|
|
34817
|
-
|
|
34818
|
-
|
|
34819
|
-
|
|
34820
|
-
|
|
34821
|
-
|
|
34822
|
-
|
|
34823
|
-
|
|
34824
|
-
|
|
34825
|
-
|
|
34805
|
+
const rawFiles = await captureOutputFiles(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
34806
|
+
const filtered = filterOutputFiles(rawFiles);
|
|
34807
|
+
if (filtered.length > 0) {
|
|
34808
|
+
completedStory.outputFiles = filtered;
|
|
34809
|
+
}
|
|
34810
|
+
const diffSummary = await captureDiffSummary(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
34811
|
+
if (diffSummary) {
|
|
34812
|
+
completedStory.diffSummary = diffSummary;
|
|
34813
|
+
}
|
|
34814
|
+
} catch {}
|
|
34815
|
+
}
|
|
34816
|
+
}
|
|
34817
|
+
const updatedCounts = countStories(prd);
|
|
34818
|
+
logger?.info("progress", "Progress update", {
|
|
34819
|
+
totalStories: updatedCounts.total,
|
|
34820
|
+
passedStories: updatedCounts.passed,
|
|
34821
|
+
failedStories: updatedCounts.failed,
|
|
34822
|
+
pendingStories: updatedCounts.pending,
|
|
34823
|
+
totalCost: ctx.totalCost + costDelta,
|
|
34824
|
+
costLimit: ctx.config.execution.costLimit,
|
|
34825
|
+
elapsedMs: Date.now() - ctx.startTime,
|
|
34826
|
+
storyDurationMs: ctx.storyStartTime ? Date.now() - ctx.storyStartTime : undefined
|
|
34827
|
+
});
|
|
34828
|
+
return { storiesCompletedDelta, costDelta, prd, prdDirty: true };
|
|
34829
|
+
}
|
|
34830
|
+
async function handlePipelineFailure(ctx, pipelineResult) {
|
|
34831
|
+
const logger = getSafeLogger();
|
|
34832
|
+
let prd = ctx.prd;
|
|
34833
|
+
let prdDirty = false;
|
|
34834
|
+
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
34835
|
+
switch (pipelineResult.finalAction) {
|
|
34836
|
+
case "pause":
|
|
34837
|
+
markStoryPaused(prd, ctx.story.id);
|
|
34838
|
+
await savePRD(prd, ctx.prdPath);
|
|
34839
|
+
prdDirty = true;
|
|
34840
|
+
logger?.warn("pipeline", "Story paused", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
34841
|
+
pipelineEventBus.emit({
|
|
34842
|
+
type: "story:paused",
|
|
34843
|
+
storyId: ctx.story.id,
|
|
34844
|
+
reason: pipelineResult.reason || "Pipeline paused",
|
|
34845
|
+
cost: ctx.totalCost
|
|
34846
|
+
});
|
|
34847
|
+
break;
|
|
34848
|
+
case "skip":
|
|
34849
|
+
logger?.warn("pipeline", "Story skipped", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
34850
|
+
prdDirty = true;
|
|
34851
|
+
break;
|
|
34852
|
+
case "fail":
|
|
34853
|
+
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage);
|
|
34854
|
+
await savePRD(prd, ctx.prdPath);
|
|
34855
|
+
prdDirty = true;
|
|
34856
|
+
logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
34857
|
+
if (ctx.featureDir) {
|
|
34858
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${pipelineResult.reason}`);
|
|
34859
|
+
}
|
|
34860
|
+
pipelineEventBus.emit({
|
|
34861
|
+
type: "story:failed",
|
|
34862
|
+
storyId: ctx.story.id,
|
|
34863
|
+
story: ctx.story,
|
|
34864
|
+
reason: pipelineResult.reason || "Pipeline failed",
|
|
34865
|
+
countsTowardEscalation: true,
|
|
34866
|
+
feature: ctx.feature,
|
|
34867
|
+
attempts: ctx.story.attempts
|
|
34868
|
+
});
|
|
34869
|
+
if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxRetries) {
|
|
34870
|
+
await pipelineEventBus.emitAsync({
|
|
34871
|
+
type: "human-review:requested",
|
|
34872
|
+
storyId: ctx.story.id,
|
|
34873
|
+
reason: pipelineResult.reason || "Max retries exceeded",
|
|
34874
|
+
feature: ctx.feature,
|
|
34875
|
+
attempts: ctx.story.attempts
|
|
34826
34876
|
});
|
|
34827
|
-
} catch (error48) {
|
|
34828
|
-
logger?.warn("plugins", `Reporter '${reporter.name}' onRunEnd failed`, { error: error48 });
|
|
34829
34877
|
}
|
|
34878
|
+
break;
|
|
34879
|
+
case "escalate": {
|
|
34880
|
+
const escalationResult = await handleTierEscalation({
|
|
34881
|
+
story: ctx.story,
|
|
34882
|
+
storiesToExecute: ctx.storiesToExecute,
|
|
34883
|
+
isBatchExecution: ctx.isBatchExecution,
|
|
34884
|
+
routing: ctx.routing,
|
|
34885
|
+
pipelineResult,
|
|
34886
|
+
config: ctx.config,
|
|
34887
|
+
prd,
|
|
34888
|
+
prdPath: ctx.prdPath,
|
|
34889
|
+
featureDir: ctx.featureDir,
|
|
34890
|
+
hooks: ctx.hooks,
|
|
34891
|
+
feature: ctx.feature,
|
|
34892
|
+
totalCost: ctx.totalCost,
|
|
34893
|
+
workdir: ctx.workdir,
|
|
34894
|
+
attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0
|
|
34895
|
+
});
|
|
34896
|
+
prd = escalationResult.prd;
|
|
34897
|
+
prdDirty = escalationResult.prdDirty;
|
|
34898
|
+
break;
|
|
34830
34899
|
}
|
|
34831
34900
|
}
|
|
34901
|
+
return { prd, prdDirty, costDelta };
|
|
34832
34902
|
}
|
|
34833
|
-
var
|
|
34903
|
+
var init_pipeline_result_handler = __esm(() => {
|
|
34834
34904
|
init_logger2();
|
|
34835
|
-
|
|
34905
|
+
init_event_bus();
|
|
34836
34906
|
init_prd();
|
|
34907
|
+
init_git();
|
|
34908
|
+
init_escalation();
|
|
34909
|
+
init_progress();
|
|
34837
34910
|
});
|
|
34838
34911
|
|
|
34839
|
-
// src/execution/
|
|
34840
|
-
|
|
34841
|
-
|
|
34842
|
-
runParallelExecution: () => runParallelExecution,
|
|
34843
|
-
_parallelExecutorDeps: () => _parallelExecutorDeps
|
|
34844
|
-
});
|
|
34845
|
-
import * as os4 from "os";
|
|
34846
|
-
import path16 from "path";
|
|
34847
|
-
async function runParallelExecution(options, initialPrd) {
|
|
34912
|
+
// src/execution/iteration-runner.ts
|
|
34913
|
+
import { join as join49 } from "path";
|
|
34914
|
+
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34848
34915
|
const logger = getSafeLogger();
|
|
34849
|
-
const {
|
|
34850
|
-
|
|
34851
|
-
|
|
34852
|
-
config: config2,
|
|
34853
|
-
hooks,
|
|
34854
|
-
feature,
|
|
34855
|
-
featureDir,
|
|
34856
|
-
parallelCount,
|
|
34857
|
-
eventEmitter,
|
|
34858
|
-
statusWriter,
|
|
34859
|
-
runId,
|
|
34860
|
-
startedAt,
|
|
34861
|
-
startTime,
|
|
34862
|
-
pluginRegistry,
|
|
34863
|
-
formatterMode,
|
|
34864
|
-
headless
|
|
34865
|
-
} = options;
|
|
34866
|
-
let { totalCost, iterations, storiesCompleted, allStoryMetrics } = options;
|
|
34867
|
-
let prd = initialPrd;
|
|
34868
|
-
const readyStories = getAllReadyStories(prd);
|
|
34869
|
-
if (readyStories.length === 0) {
|
|
34870
|
-
logger?.info("parallel", "No stories ready for parallel execution");
|
|
34871
|
-
return {
|
|
34872
|
-
prd,
|
|
34873
|
-
totalCost,
|
|
34874
|
-
storiesCompleted,
|
|
34875
|
-
completed: false,
|
|
34876
|
-
storyMetrics: [],
|
|
34877
|
-
rectificationStats: { rectified: 0, stillConflicting: 0 }
|
|
34878
|
-
};
|
|
34879
|
-
}
|
|
34880
|
-
const maxConcurrency = parallelCount === 0 ? os4.cpus().length : Math.max(1, parallelCount);
|
|
34881
|
-
logger?.info("parallel", "Starting parallel execution mode", {
|
|
34882
|
-
totalStories: readyStories.length,
|
|
34883
|
-
maxConcurrency
|
|
34884
|
-
});
|
|
34885
|
-
statusWriter.setPrd(prd);
|
|
34886
|
-
await statusWriter.update(totalCost, iterations, {
|
|
34887
|
-
parallel: {
|
|
34888
|
-
enabled: true,
|
|
34889
|
-
maxConcurrency,
|
|
34890
|
-
activeStories: readyStories.map((s) => ({
|
|
34891
|
-
storyId: s.id,
|
|
34892
|
-
worktreePath: path16.join(workdir, ".nax-wt", s.id)
|
|
34893
|
-
}))
|
|
34894
|
-
}
|
|
34895
|
-
});
|
|
34896
|
-
const initialPassedIds = new Set(initialPrd.userStories.filter((s) => s.status === "passed").map((s) => s.id));
|
|
34897
|
-
const batchStartedAt = new Date().toISOString();
|
|
34898
|
-
const batchStartMs = Date.now();
|
|
34899
|
-
const batchStoryMetrics = [];
|
|
34900
|
-
let conflictedStories = [];
|
|
34901
|
-
try {
|
|
34902
|
-
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter, options.agentGetFn, options.pidRegistry, options.interactionChain);
|
|
34903
|
-
const batchDurationMs = Date.now() - batchStartMs;
|
|
34904
|
-
const batchCompletedAt = new Date().toISOString();
|
|
34905
|
-
prd = parallelResult.updatedPrd;
|
|
34906
|
-
storiesCompleted += parallelResult.storiesCompleted;
|
|
34907
|
-
totalCost += parallelResult.totalCost;
|
|
34908
|
-
conflictedStories = parallelResult.mergeConflicts ?? [];
|
|
34909
|
-
const newlyPassedStories = prd.userStories.filter((s) => s.status === "passed" && !initialPassedIds.has(s.id));
|
|
34910
|
-
const costPerStory = newlyPassedStories.length > 0 ? parallelResult.totalCost / newlyPassedStories.length : 0;
|
|
34911
|
-
for (const story of newlyPassedStories) {
|
|
34912
|
-
batchStoryMetrics.push({
|
|
34913
|
-
storyId: story.id,
|
|
34914
|
-
complexity: "unknown",
|
|
34915
|
-
modelTier: "parallel",
|
|
34916
|
-
modelUsed: "parallel",
|
|
34917
|
-
attempts: 1,
|
|
34918
|
-
finalTier: "parallel",
|
|
34919
|
-
success: true,
|
|
34920
|
-
cost: costPerStory,
|
|
34921
|
-
durationMs: batchDurationMs,
|
|
34922
|
-
firstPassSuccess: true,
|
|
34923
|
-
startedAt: batchStartedAt,
|
|
34924
|
-
completedAt: batchCompletedAt,
|
|
34925
|
-
source: "parallel"
|
|
34926
|
-
});
|
|
34927
|
-
}
|
|
34928
|
-
allStoryMetrics.push(...batchStoryMetrics);
|
|
34929
|
-
for (const conflict of conflictedStories) {
|
|
34930
|
-
logger?.info("parallel", "Merge conflict detected - scheduling for rectification", {
|
|
34931
|
-
storyId: conflict.storyId,
|
|
34932
|
-
conflictFiles: conflict.conflictFiles
|
|
34933
|
-
});
|
|
34934
|
-
}
|
|
34935
|
-
statusWriter.setPrd(prd);
|
|
34936
|
-
await statusWriter.update(totalCost, iterations, {
|
|
34937
|
-
parallel: {
|
|
34938
|
-
enabled: true,
|
|
34939
|
-
maxConcurrency,
|
|
34940
|
-
activeStories: []
|
|
34941
|
-
}
|
|
34942
|
-
});
|
|
34943
|
-
} catch (error48) {
|
|
34944
|
-
logger?.error("parallel", "Parallel execution failed", {
|
|
34945
|
-
error: errorMessage(error48)
|
|
34946
|
-
});
|
|
34947
|
-
await statusWriter.update(totalCost, iterations, {
|
|
34948
|
-
parallel: undefined
|
|
34949
|
-
});
|
|
34950
|
-
throw error48;
|
|
34951
|
-
}
|
|
34952
|
-
let rectificationStats = { rectified: 0, stillConflicting: 0 };
|
|
34953
|
-
if (conflictedStories.length > 0) {
|
|
34954
|
-
const rectResult = await runRectificationPass(conflictedStories, options, prd, _parallelExecutorDeps.rectifyConflictedStory);
|
|
34955
|
-
prd = rectResult.updatedPrd;
|
|
34956
|
-
storiesCompleted += rectResult.rectifiedCount;
|
|
34957
|
-
totalCost += rectResult.additionalCost;
|
|
34958
|
-
rectificationStats = {
|
|
34959
|
-
rectified: rectResult.rectifiedCount,
|
|
34960
|
-
stillConflicting: rectResult.stillConflictingCount
|
|
34961
|
-
};
|
|
34962
|
-
batchStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
34963
|
-
allStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
34964
|
-
}
|
|
34965
|
-
if (isComplete(prd)) {
|
|
34966
|
-
logger?.info("execution", "All stories complete!", {
|
|
34967
|
-
feature,
|
|
34968
|
-
totalCost
|
|
34969
|
-
});
|
|
34970
|
-
await _parallelExecutorDeps.fireHook(hooks, "on-all-stories-complete", hookCtx(feature, { status: "passed", cost: totalCost }), workdir);
|
|
34971
|
-
await _parallelExecutorDeps.fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: totalCost }), workdir);
|
|
34972
|
-
const durationMs = Date.now() - startTime;
|
|
34973
|
-
const runCompletedAt = new Date().toISOString();
|
|
34974
|
-
const { handleParallelCompletion: handleParallelCompletion2 } = await Promise.resolve().then(() => (init_parallel_lifecycle(), exports_parallel_lifecycle));
|
|
34975
|
-
await handleParallelCompletion2({
|
|
34976
|
-
runId,
|
|
34977
|
-
feature,
|
|
34978
|
-
startedAt,
|
|
34979
|
-
completedAt: runCompletedAt,
|
|
34916
|
+
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
34917
|
+
if (ctx.dryRun) {
|
|
34918
|
+
const dryRunResult = await handleDryRun({
|
|
34980
34919
|
prd,
|
|
34981
|
-
|
|
34920
|
+
prdPath: ctx.prdPath,
|
|
34921
|
+
storiesToExecute,
|
|
34922
|
+
routing,
|
|
34923
|
+
statusWriter: ctx.statusWriter,
|
|
34924
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
34925
|
+
runId: ctx.runId,
|
|
34982
34926
|
totalCost,
|
|
34983
|
-
|
|
34984
|
-
durationMs,
|
|
34985
|
-
workdir,
|
|
34986
|
-
pluginRegistry
|
|
34927
|
+
iterations
|
|
34987
34928
|
});
|
|
34988
|
-
const finalCounts = countStories(prd);
|
|
34989
|
-
statusWriter.setPrd(prd);
|
|
34990
|
-
statusWriter.setCurrentStory(null);
|
|
34991
|
-
statusWriter.setRunStatus("completed");
|
|
34992
|
-
await statusWriter.update(totalCost, iterations);
|
|
34993
|
-
if (headless && formatterMode !== "json") {
|
|
34994
|
-
const { outputRunFooter: outputRunFooter2 } = await Promise.resolve().then(() => (init_headless_formatter(), exports_headless_formatter));
|
|
34995
|
-
outputRunFooter2({
|
|
34996
|
-
finalCounts: {
|
|
34997
|
-
total: finalCounts.total,
|
|
34998
|
-
passed: finalCounts.passed,
|
|
34999
|
-
failed: finalCounts.failed,
|
|
35000
|
-
skipped: finalCounts.skipped
|
|
35001
|
-
},
|
|
35002
|
-
durationMs,
|
|
35003
|
-
totalCost,
|
|
35004
|
-
startedAt,
|
|
35005
|
-
completedAt: runCompletedAt,
|
|
35006
|
-
formatterMode
|
|
35007
|
-
});
|
|
35008
|
-
}
|
|
35009
34929
|
return {
|
|
35010
34930
|
prd,
|
|
35011
|
-
|
|
35012
|
-
|
|
35013
|
-
|
|
35014
|
-
durationMs,
|
|
35015
|
-
storyMetrics: batchStoryMetrics,
|
|
35016
|
-
rectificationStats
|
|
34931
|
+
storiesCompletedDelta: dryRunResult.storiesCompletedDelta,
|
|
34932
|
+
costDelta: 0,
|
|
34933
|
+
prdDirty: dryRunResult.prdDirty
|
|
35017
34934
|
};
|
|
35018
34935
|
}
|
|
35019
|
-
|
|
35020
|
-
|
|
35021
|
-
|
|
35022
|
-
|
|
35023
|
-
|
|
35024
|
-
|
|
35025
|
-
|
|
35026
|
-
|
|
35027
|
-
|
|
35028
|
-
|
|
35029
|
-
init_parallel_executor_rectify();
|
|
35030
|
-
_parallelExecutorDeps = {
|
|
35031
|
-
fireHook,
|
|
35032
|
-
executeParallel,
|
|
35033
|
-
rectifyConflictedStory
|
|
35034
|
-
};
|
|
35035
|
-
});
|
|
35036
|
-
|
|
35037
|
-
// src/pipeline/subscribers/events-writer.ts
|
|
35038
|
-
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
35039
|
-
import { homedir as homedir5 } from "os";
|
|
35040
|
-
import { basename as basename5, join as join49 } from "path";
|
|
35041
|
-
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
35042
|
-
const logger = getSafeLogger();
|
|
35043
|
-
const project = basename5(workdir);
|
|
35044
|
-
const eventsDir = join49(homedir5(), ".nax", "events", project);
|
|
35045
|
-
const eventsFile = join49(eventsDir, "events.jsonl");
|
|
35046
|
-
let dirReady = false;
|
|
35047
|
-
const write = (line) => {
|
|
35048
|
-
return (async () => {
|
|
35049
|
-
try {
|
|
35050
|
-
if (!dirReady) {
|
|
35051
|
-
await mkdir2(eventsDir, { recursive: true });
|
|
35052
|
-
dirReady = true;
|
|
35053
|
-
}
|
|
35054
|
-
await appendFile2(eventsFile, `${JSON.stringify(line)}
|
|
35055
|
-
`);
|
|
35056
|
-
} catch (err) {
|
|
35057
|
-
logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
|
|
35058
|
-
event: line.event,
|
|
35059
|
-
error: String(err)
|
|
35060
|
-
});
|
|
35061
|
-
}
|
|
35062
|
-
})();
|
|
35063
|
-
};
|
|
35064
|
-
const unsubs = [];
|
|
35065
|
-
unsubs.push(bus.on("run:started", (_ev) => {
|
|
35066
|
-
return write({ ts: new Date().toISOString(), event: "run:started", runId, feature, project });
|
|
35067
|
-
}));
|
|
35068
|
-
unsubs.push(bus.on("story:started", (ev) => {
|
|
35069
|
-
return write({
|
|
35070
|
-
ts: new Date().toISOString(),
|
|
35071
|
-
event: "story:started",
|
|
35072
|
-
runId,
|
|
35073
|
-
feature,
|
|
35074
|
-
project,
|
|
35075
|
-
storyId: ev.storyId
|
|
35076
|
-
});
|
|
35077
|
-
}));
|
|
35078
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35079
|
-
return write({
|
|
35080
|
-
ts: new Date().toISOString(),
|
|
35081
|
-
event: "story:completed",
|
|
35082
|
-
runId,
|
|
35083
|
-
feature,
|
|
35084
|
-
project,
|
|
35085
|
-
storyId: ev.storyId
|
|
35086
|
-
});
|
|
35087
|
-
}));
|
|
35088
|
-
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
35089
|
-
return write({
|
|
35090
|
-
ts: new Date().toISOString(),
|
|
35091
|
-
event: "story:decomposed",
|
|
35092
|
-
runId,
|
|
35093
|
-
feature,
|
|
35094
|
-
project,
|
|
35095
|
-
storyId: ev.storyId,
|
|
35096
|
-
data: { subStoryCount: ev.subStoryCount }
|
|
35097
|
-
});
|
|
35098
|
-
}));
|
|
35099
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35100
|
-
return write({
|
|
35101
|
-
ts: new Date().toISOString(),
|
|
35102
|
-
event: "story:failed",
|
|
35103
|
-
runId,
|
|
35104
|
-
feature,
|
|
35105
|
-
project,
|
|
35106
|
-
storyId: ev.storyId
|
|
35107
|
-
});
|
|
35108
|
-
}));
|
|
35109
|
-
unsubs.push(bus.on("run:completed", (_ev) => {
|
|
35110
|
-
return write({ ts: new Date().toISOString(), event: "on-complete", runId, feature, project });
|
|
35111
|
-
}));
|
|
35112
|
-
unsubs.push(bus.on("run:paused", (ev) => {
|
|
35113
|
-
return write({
|
|
35114
|
-
ts: new Date().toISOString(),
|
|
35115
|
-
event: "run:paused",
|
|
35116
|
-
runId,
|
|
35117
|
-
feature,
|
|
35118
|
-
project,
|
|
35119
|
-
...ev.storyId !== undefined && { storyId: ev.storyId }
|
|
35120
|
-
});
|
|
35121
|
-
}));
|
|
35122
|
-
return () => {
|
|
35123
|
-
for (const u of unsubs)
|
|
35124
|
-
u();
|
|
35125
|
-
};
|
|
35126
|
-
}
|
|
35127
|
-
var init_events_writer = __esm(() => {
|
|
35128
|
-
init_logger2();
|
|
35129
|
-
});
|
|
35130
|
-
|
|
35131
|
-
// src/pipeline/subscribers/hooks.ts
|
|
35132
|
-
function wireHooks(bus, hooks, workdir, feature) {
|
|
35133
|
-
const logger = getSafeLogger();
|
|
35134
|
-
const safe = (name, fn) => {
|
|
35135
|
-
return fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) })).catch(() => {});
|
|
35136
|
-
};
|
|
35137
|
-
const unsubs = [];
|
|
35138
|
-
unsubs.push(bus.on("run:started", (ev) => {
|
|
35139
|
-
return safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
|
|
35140
|
-
}));
|
|
35141
|
-
unsubs.push(bus.on("story:started", (ev) => {
|
|
35142
|
-
return safe("on-story-start", () => fireHook(hooks, "on-story-start", hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }), workdir));
|
|
35143
|
-
}));
|
|
35144
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35145
|
-
return safe("on-story-complete", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }), workdir));
|
|
35146
|
-
}));
|
|
35147
|
-
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
35148
|
-
return safe("on-story-complete (decomposed)", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "decomposed", subStoryCount: ev.subStoryCount }), workdir));
|
|
35149
|
-
}));
|
|
35150
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35151
|
-
return safe("on-story-fail", () => fireHook(hooks, "on-story-fail", hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }), workdir));
|
|
35152
|
-
}));
|
|
35153
|
-
unsubs.push(bus.on("story:paused", (ev) => {
|
|
35154
|
-
return safe("on-pause (story)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
35155
|
-
}));
|
|
35156
|
-
unsubs.push(bus.on("run:paused", (ev) => {
|
|
35157
|
-
return safe("on-pause (run)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
35158
|
-
}));
|
|
35159
|
-
unsubs.push(bus.on("run:completed", (ev) => {
|
|
35160
|
-
return safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
|
|
35161
|
-
}));
|
|
35162
|
-
unsubs.push(bus.on("run:resumed", (ev) => {
|
|
35163
|
-
return safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
|
|
35164
|
-
}));
|
|
35165
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35166
|
-
return safe("on-session-end (completed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "passed" }), workdir));
|
|
35167
|
-
}));
|
|
35168
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35169
|
-
return safe("on-session-end (failed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "failed" }), workdir));
|
|
35170
|
-
}));
|
|
35171
|
-
unsubs.push(bus.on("run:errored", (ev) => {
|
|
35172
|
-
return safe("on-error", () => fireHook(hooks, "on-error", hookCtx(feature, { reason: ev.reason }), workdir));
|
|
35173
|
-
}));
|
|
35174
|
-
return () => {
|
|
35175
|
-
for (const u of unsubs)
|
|
35176
|
-
u();
|
|
35177
|
-
};
|
|
35178
|
-
}
|
|
35179
|
-
var init_hooks2 = __esm(() => {
|
|
35180
|
-
init_story_context();
|
|
35181
|
-
init_hooks();
|
|
35182
|
-
init_logger2();
|
|
35183
|
-
});
|
|
35184
|
-
|
|
35185
|
-
// src/pipeline/subscribers/interaction.ts
|
|
35186
|
-
function wireInteraction(bus, interactionChain, config2) {
|
|
35187
|
-
const logger = getSafeLogger();
|
|
35188
|
-
const unsubs = [];
|
|
35189
|
-
if (interactionChain && isTriggerEnabled("human-review", config2)) {
|
|
35190
|
-
unsubs.push(bus.on("human-review:requested", (ev) => {
|
|
35191
|
-
executeTrigger("human-review", {
|
|
35192
|
-
featureName: ev.feature ?? "",
|
|
35193
|
-
storyId: ev.storyId,
|
|
35194
|
-
iteration: ev.attempts ?? 0,
|
|
35195
|
-
reason: ev.reason
|
|
35196
|
-
}, config2, interactionChain).catch((err) => {
|
|
35197
|
-
logger?.warn("interaction-subscriber", "human-review trigger failed", {
|
|
35198
|
-
storyId: ev.storyId,
|
|
35199
|
-
error: String(err)
|
|
35200
|
-
});
|
|
35201
|
-
});
|
|
35202
|
-
}));
|
|
35203
|
-
}
|
|
35204
|
-
if (interactionChain && isTriggerEnabled("max-retries", config2)) {
|
|
35205
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35206
|
-
if (!ev.countsTowardEscalation) {
|
|
35207
|
-
return;
|
|
35208
|
-
}
|
|
35209
|
-
executeTrigger("max-retries", {
|
|
35210
|
-
featureName: ev.feature ?? "",
|
|
35211
|
-
storyId: ev.storyId,
|
|
35212
|
-
iteration: ev.attempts ?? 0
|
|
35213
|
-
}, config2, interactionChain).then((response) => {
|
|
35214
|
-
if (response.action === "abort") {
|
|
35215
|
-
logger?.warn("interaction-subscriber", "max-retries abort requested", {
|
|
35216
|
-
storyId: ev.storyId
|
|
35217
|
-
});
|
|
35218
|
-
}
|
|
35219
|
-
}).catch((err) => {
|
|
35220
|
-
logger?.warn("interaction-subscriber", "max-retries trigger failed", {
|
|
35221
|
-
storyId: ev.storyId,
|
|
35222
|
-
error: String(err)
|
|
35223
|
-
});
|
|
35224
|
-
});
|
|
35225
|
-
}));
|
|
34936
|
+
const storyStartTime = Date.now();
|
|
34937
|
+
let storyGitRef;
|
|
34938
|
+
if (story.storyGitRef && await isGitRefValid(ctx.workdir, story.storyGitRef)) {
|
|
34939
|
+
storyGitRef = story.storyGitRef;
|
|
34940
|
+
} else {
|
|
34941
|
+
storyGitRef = await captureGitRef(ctx.workdir);
|
|
34942
|
+
if (storyGitRef) {
|
|
34943
|
+
story.storyGitRef = storyGitRef;
|
|
34944
|
+
await savePRD(prd, ctx.prdPath);
|
|
34945
|
+
}
|
|
35226
34946
|
}
|
|
35227
|
-
|
|
35228
|
-
|
|
35229
|
-
|
|
34947
|
+
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
34948
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
34949
|
+
const pipelineContext = {
|
|
34950
|
+
config: ctx.config,
|
|
34951
|
+
effectiveConfig,
|
|
34952
|
+
prd,
|
|
34953
|
+
story,
|
|
34954
|
+
stories: storiesToExecute,
|
|
34955
|
+
routing,
|
|
34956
|
+
workdir: ctx.workdir,
|
|
34957
|
+
prdPath: ctx.prdPath,
|
|
34958
|
+
featureDir: ctx.featureDir,
|
|
34959
|
+
hooks: ctx.hooks,
|
|
34960
|
+
plugins: ctx.pluginRegistry,
|
|
34961
|
+
storyStartTime: new Date().toISOString(),
|
|
34962
|
+
storyGitRef: storyGitRef ?? undefined,
|
|
34963
|
+
interaction: ctx.interactionChain ?? undefined,
|
|
34964
|
+
agentGetFn: ctx.agentGetFn,
|
|
34965
|
+
pidRegistry: ctx.pidRegistry,
|
|
34966
|
+
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
35230
34967
|
};
|
|
35231
|
-
|
|
35232
|
-
|
|
35233
|
-
|
|
35234
|
-
|
|
35235
|
-
|
|
35236
|
-
|
|
35237
|
-
|
|
35238
|
-
|
|
35239
|
-
|
|
35240
|
-
import { basename as basename6, join as join50 } from "path";
|
|
35241
|
-
function wireRegistry(bus, feature, runId, workdir) {
|
|
35242
|
-
const logger = getSafeLogger();
|
|
35243
|
-
const project = basename6(workdir);
|
|
35244
|
-
const runDir = join50(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
35245
|
-
const metaFile = join50(runDir, "meta.json");
|
|
35246
|
-
const unsub = bus.on("run:started", (_ev) => {
|
|
35247
|
-
return (async () => {
|
|
35248
|
-
try {
|
|
35249
|
-
await mkdir3(runDir, { recursive: true });
|
|
35250
|
-
const meta3 = {
|
|
35251
|
-
runId,
|
|
35252
|
-
project,
|
|
35253
|
-
feature,
|
|
35254
|
-
workdir,
|
|
35255
|
-
statusPath: join50(workdir, ".nax", "features", feature, "status.json"),
|
|
35256
|
-
eventsDir: join50(workdir, ".nax", "features", feature, "runs"),
|
|
35257
|
-
registeredAt: new Date().toISOString()
|
|
35258
|
-
};
|
|
35259
|
-
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
35260
|
-
} catch (err) {
|
|
35261
|
-
logger?.warn("registry-writer", "Failed to write meta.json (non-fatal)", {
|
|
35262
|
-
path: metaFile,
|
|
35263
|
-
error: String(err)
|
|
35264
|
-
});
|
|
35265
|
-
}
|
|
35266
|
-
})();
|
|
34968
|
+
ctx.statusWriter.setPrd(prd);
|
|
34969
|
+
ctx.statusWriter.setCurrentStory({
|
|
34970
|
+
storyId: story.id,
|
|
34971
|
+
title: story.title,
|
|
34972
|
+
complexity: routing.complexity,
|
|
34973
|
+
tddStrategy: routing.testStrategy,
|
|
34974
|
+
model: routing.modelTier,
|
|
34975
|
+
attempt: (story.attempts ?? 0) + 1,
|
|
34976
|
+
phase: "routing"
|
|
35267
34977
|
});
|
|
35268
|
-
|
|
35269
|
-
|
|
35270
|
-
|
|
35271
|
-
|
|
35272
|
-
|
|
35273
|
-
|
|
35274
|
-
|
|
35275
|
-
|
|
35276
|
-
|
|
35277
|
-
|
|
35278
|
-
|
|
34978
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
34979
|
+
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
34980
|
+
const currentPrd = pipelineResult.context.prd;
|
|
34981
|
+
const handlerCtx = {
|
|
34982
|
+
config: ctx.config,
|
|
34983
|
+
prd: currentPrd,
|
|
34984
|
+
prdPath: ctx.prdPath,
|
|
34985
|
+
workdir: ctx.workdir,
|
|
34986
|
+
featureDir: ctx.featureDir,
|
|
34987
|
+
hooks: ctx.hooks,
|
|
34988
|
+
feature: ctx.feature,
|
|
34989
|
+
totalCost,
|
|
34990
|
+
startTime: ctx.startTime,
|
|
34991
|
+
runId: ctx.runId,
|
|
34992
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
34993
|
+
story,
|
|
34994
|
+
storiesToExecute,
|
|
34995
|
+
routing: pipelineResult.context.routing ?? routing,
|
|
34996
|
+
isBatchExecution,
|
|
34997
|
+
allStoryMetrics,
|
|
34998
|
+
storyGitRef,
|
|
34999
|
+
interactionChain: ctx.interactionChain,
|
|
35000
|
+
storyStartTime
|
|
35279
35001
|
};
|
|
35280
|
-
|
|
35281
|
-
|
|
35282
|
-
return
|
|
35283
|
-
|
|
35284
|
-
|
|
35285
|
-
|
|
35286
|
-
|
|
35287
|
-
|
|
35288
|
-
|
|
35289
|
-
|
|
35290
|
-
|
|
35291
|
-
|
|
35292
|
-
|
|
35293
|
-
|
|
35294
|
-
|
|
35295
|
-
|
|
35296
|
-
|
|
35297
|
-
|
|
35298
|
-
|
|
35299
|
-
}));
|
|
35300
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35301
|
-
return safe("onStoryComplete(completed)", async () => {
|
|
35302
|
-
const reporters = pluginRegistry.getReporters();
|
|
35303
|
-
for (const r of reporters) {
|
|
35304
|
-
if (r.onStoryComplete) {
|
|
35305
|
-
try {
|
|
35306
|
-
await r.onStoryComplete({
|
|
35307
|
-
runId,
|
|
35308
|
-
storyId: ev.storyId,
|
|
35309
|
-
status: "completed",
|
|
35310
|
-
runElapsedMs: ev.runElapsedMs,
|
|
35311
|
-
cost: ev.cost ?? 0,
|
|
35312
|
-
tier: ev.modelTier ?? "balanced",
|
|
35313
|
-
testStrategy: ev.testStrategy ?? "test-after"
|
|
35314
|
-
});
|
|
35315
|
-
} catch (err) {
|
|
35316
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
35317
|
-
}
|
|
35318
|
-
}
|
|
35319
|
-
}
|
|
35320
|
-
});
|
|
35321
|
-
}));
|
|
35322
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35323
|
-
return safe("onStoryComplete(failed)", async () => {
|
|
35324
|
-
const reporters = pluginRegistry.getReporters();
|
|
35325
|
-
for (const r of reporters) {
|
|
35326
|
-
if (r.onStoryComplete) {
|
|
35327
|
-
try {
|
|
35328
|
-
await r.onStoryComplete({
|
|
35329
|
-
runId,
|
|
35330
|
-
storyId: ev.storyId,
|
|
35331
|
-
status: "failed",
|
|
35332
|
-
runElapsedMs: Date.now() - startTime,
|
|
35333
|
-
cost: 0,
|
|
35334
|
-
tier: "balanced",
|
|
35335
|
-
testStrategy: "test-after"
|
|
35336
|
-
});
|
|
35337
|
-
} catch (err) {
|
|
35338
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
35339
|
-
}
|
|
35340
|
-
}
|
|
35341
|
-
}
|
|
35342
|
-
});
|
|
35343
|
-
}));
|
|
35344
|
-
unsubs.push(bus.on("story:paused", (ev) => {
|
|
35345
|
-
return safe("onStoryComplete(paused)", async () => {
|
|
35346
|
-
const reporters = pluginRegistry.getReporters();
|
|
35347
|
-
for (const r of reporters) {
|
|
35348
|
-
if (r.onStoryComplete) {
|
|
35349
|
-
try {
|
|
35350
|
-
await r.onStoryComplete({
|
|
35351
|
-
runId,
|
|
35352
|
-
storyId: ev.storyId,
|
|
35353
|
-
status: "paused",
|
|
35354
|
-
runElapsedMs: Date.now() - startTime,
|
|
35355
|
-
cost: 0,
|
|
35356
|
-
tier: "balanced",
|
|
35357
|
-
testStrategy: "test-after"
|
|
35358
|
-
});
|
|
35359
|
-
} catch (err) {
|
|
35360
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
35361
|
-
}
|
|
35362
|
-
}
|
|
35363
|
-
}
|
|
35364
|
-
});
|
|
35365
|
-
}));
|
|
35366
|
-
unsubs.push(bus.on("run:completed", (ev) => {
|
|
35367
|
-
return safe("onRunEnd", async () => {
|
|
35368
|
-
const reporters = pluginRegistry.getReporters();
|
|
35369
|
-
for (const r of reporters) {
|
|
35370
|
-
if (r.onRunEnd) {
|
|
35371
|
-
try {
|
|
35372
|
-
await r.onRunEnd({
|
|
35373
|
-
runId,
|
|
35374
|
-
totalDurationMs: Date.now() - startTime,
|
|
35375
|
-
totalCost: ev.totalCost ?? 0,
|
|
35376
|
-
storySummary: {
|
|
35377
|
-
completed: ev.passedStories,
|
|
35378
|
-
failed: ev.failedStories,
|
|
35379
|
-
skipped: 0,
|
|
35380
|
-
paused: 0
|
|
35381
|
-
}
|
|
35382
|
-
});
|
|
35383
|
-
} catch (err) {
|
|
35384
|
-
logger?.warn("plugins", `Reporter '${r.name}' onRunEnd failed`, { error: err });
|
|
35385
|
-
}
|
|
35386
|
-
}
|
|
35387
|
-
}
|
|
35388
|
-
});
|
|
35389
|
-
}));
|
|
35390
|
-
return () => {
|
|
35391
|
-
for (const u of unsubs)
|
|
35392
|
-
u();
|
|
35002
|
+
if (pipelineResult.success) {
|
|
35003
|
+
const r2 = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
35004
|
+
return {
|
|
35005
|
+
prd: r2.prd,
|
|
35006
|
+
storiesCompletedDelta: r2.storiesCompletedDelta,
|
|
35007
|
+
costDelta: r2.costDelta,
|
|
35008
|
+
prdDirty: r2.prdDirty,
|
|
35009
|
+
finalAction: pipelineResult.finalAction
|
|
35010
|
+
};
|
|
35011
|
+
}
|
|
35012
|
+
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
35013
|
+
return {
|
|
35014
|
+
prd: r.prd,
|
|
35015
|
+
storiesCompletedDelta: 0,
|
|
35016
|
+
costDelta: r.costDelta,
|
|
35017
|
+
prdDirty: r.prdDirty,
|
|
35018
|
+
finalAction: pipelineResult.finalAction,
|
|
35019
|
+
reason: pipelineResult.reason,
|
|
35020
|
+
subStoryCount: pipelineResult.subStoryCount
|
|
35393
35021
|
};
|
|
35394
35022
|
}
|
|
35395
|
-
var
|
|
35023
|
+
var _iterationRunnerDeps;
|
|
35024
|
+
var init_iteration_runner = __esm(() => {
|
|
35025
|
+
init_loader();
|
|
35396
35026
|
init_logger2();
|
|
35027
|
+
init_runner();
|
|
35028
|
+
init_stages();
|
|
35029
|
+
init_prd();
|
|
35030
|
+
init_git();
|
|
35031
|
+
init_dry_run();
|
|
35032
|
+
init_pipeline_result_handler();
|
|
35033
|
+
_iterationRunnerDeps = {
|
|
35034
|
+
loadConfigForWorkdir
|
|
35035
|
+
};
|
|
35397
35036
|
});
|
|
35398
35037
|
|
|
35399
|
-
// src/execution/
|
|
35400
|
-
|
|
35401
|
-
|
|
35402
|
-
|
|
35403
|
-
const
|
|
35404
|
-
|
|
35405
|
-
|
|
35406
|
-
stdout: "pipe",
|
|
35407
|
-
stderr: "pipe"
|
|
35408
|
-
});
|
|
35409
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
35410
|
-
return stdout.trim();
|
|
35411
|
-
} catch {
|
|
35412
|
-
return "";
|
|
35413
|
-
}
|
|
35414
|
-
}
|
|
35415
|
-
async function getChangedFilesForDeferred(workdir, baseRef) {
|
|
35416
|
-
try {
|
|
35417
|
-
const proc = _deferredReviewDeps.spawn({
|
|
35418
|
-
cmd: ["git", "diff", "--name-only", `${baseRef}...HEAD`],
|
|
35419
|
-
cwd: workdir,
|
|
35420
|
-
stdout: "pipe",
|
|
35421
|
-
stderr: "pipe"
|
|
35422
|
-
});
|
|
35423
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
35424
|
-
return stdout.trim().split(`
|
|
35425
|
-
`).filter(Boolean);
|
|
35426
|
-
} catch {
|
|
35427
|
-
return [];
|
|
35428
|
-
}
|
|
35429
|
-
}
|
|
35430
|
-
async function runDeferredReview(workdir, reviewConfig, plugins, runStartRef) {
|
|
35431
|
-
if (!reviewConfig || reviewConfig.pluginMode !== "deferred") {
|
|
35432
|
-
return;
|
|
35433
|
-
}
|
|
35434
|
-
const reviewers = plugins.getReviewers();
|
|
35435
|
-
if (reviewers.length === 0) {
|
|
35436
|
-
return;
|
|
35437
|
-
}
|
|
35438
|
-
const changedFiles = await getChangedFilesForDeferred(workdir, runStartRef);
|
|
35439
|
-
const reviewerResults = [];
|
|
35440
|
-
let anyFailed = false;
|
|
35441
|
-
for (const reviewer of reviewers) {
|
|
35442
|
-
try {
|
|
35443
|
-
const result = await reviewer.check(workdir, changedFiles);
|
|
35444
|
-
reviewerResults.push({
|
|
35445
|
-
name: reviewer.name,
|
|
35446
|
-
passed: result.passed,
|
|
35447
|
-
output: result.output,
|
|
35448
|
-
exitCode: result.exitCode
|
|
35449
|
-
});
|
|
35450
|
-
if (!result.passed) {
|
|
35451
|
-
anyFailed = true;
|
|
35452
|
-
}
|
|
35453
|
-
} catch (error48) {
|
|
35454
|
-
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
35455
|
-
reviewerResults.push({
|
|
35456
|
-
name: reviewer.name,
|
|
35457
|
-
passed: false,
|
|
35458
|
-
output: "",
|
|
35459
|
-
error: errorMsg
|
|
35460
|
-
});
|
|
35461
|
-
anyFailed = true;
|
|
35038
|
+
// src/execution/story-selector.ts
|
|
35039
|
+
function selectNextStories(prd, config2, batchPlan, currentBatchIndex, lastStoryId, useBatch) {
|
|
35040
|
+
if (useBatch && currentBatchIndex < batchPlan.length) {
|
|
35041
|
+
const batch = batchPlan[currentBatchIndex];
|
|
35042
|
+
const storiesToExecute = batch.stories.filter((s) => !s.passes && s.status !== "passed" && s.status !== "skipped" && s.status !== "blocked" && s.status !== "failed" && s.status !== "paused" && s.status !== "decomposed");
|
|
35043
|
+
if (storiesToExecute.length === 0) {
|
|
35044
|
+
return { selection: null, nextBatchIndex: currentBatchIndex + 1 };
|
|
35462
35045
|
}
|
|
35046
|
+
const story2 = storiesToExecute[0];
|
|
35047
|
+
return {
|
|
35048
|
+
selection: {
|
|
35049
|
+
story: story2,
|
|
35050
|
+
storiesToExecute,
|
|
35051
|
+
routing: buildPreviewRouting(story2, config2),
|
|
35052
|
+
isBatchExecution: batch.isBatch && storiesToExecute.length > 1
|
|
35053
|
+
},
|
|
35054
|
+
nextBatchIndex: currentBatchIndex + 1
|
|
35055
|
+
};
|
|
35463
35056
|
}
|
|
35464
|
-
|
|
35057
|
+
const story = getNextStory(prd, lastStoryId, config2.execution.rectification?.maxRetries ?? 2);
|
|
35058
|
+
if (!story)
|
|
35059
|
+
return null;
|
|
35060
|
+
return {
|
|
35061
|
+
selection: {
|
|
35062
|
+
story,
|
|
35063
|
+
storiesToExecute: [story],
|
|
35064
|
+
routing: buildPreviewRouting(story, config2),
|
|
35065
|
+
isBatchExecution: false
|
|
35066
|
+
},
|
|
35067
|
+
nextBatchIndex: currentBatchIndex
|
|
35068
|
+
};
|
|
35465
35069
|
}
|
|
35466
|
-
|
|
35467
|
-
|
|
35468
|
-
|
|
35469
|
-
|
|
35470
|
-
|
|
35471
|
-
|
|
35472
|
-
|
|
35473
|
-
|
|
35474
|
-
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
|
|
35479
|
-
tddStrategy: ctx.routing.testStrategy,
|
|
35480
|
-
model: ctx.routing.modelTier,
|
|
35481
|
-
attempt: (ctx.storiesToExecute[0].attempts ?? 0) + 1,
|
|
35482
|
-
phase: "routing"
|
|
35483
|
-
});
|
|
35484
|
-
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
35485
|
-
for (const s of ctx.storiesToExecute) {
|
|
35486
|
-
logger?.info("execution", "[DRY RUN] Would execute agent here", {
|
|
35487
|
-
storyId: s.id,
|
|
35488
|
-
storyTitle: s.title,
|
|
35489
|
-
modelTier: ctx.routing.modelTier,
|
|
35490
|
-
complexity: ctx.routing.complexity,
|
|
35491
|
-
testStrategy: ctx.routing.testStrategy
|
|
35492
|
-
});
|
|
35493
|
-
}
|
|
35494
|
-
for (const s of ctx.storiesToExecute) {
|
|
35495
|
-
markStoryPassed(ctx.prd, s.id);
|
|
35496
|
-
}
|
|
35497
|
-
await savePRD(ctx.prd, ctx.prdPath);
|
|
35498
|
-
for (const s of ctx.storiesToExecute) {
|
|
35499
|
-
pipelineEventBus.emit({
|
|
35500
|
-
type: "story:completed",
|
|
35501
|
-
storyId: s.id,
|
|
35502
|
-
story: s,
|
|
35503
|
-
passed: true,
|
|
35504
|
-
runElapsedMs: 0,
|
|
35505
|
-
cost: 0,
|
|
35506
|
-
modelTier: ctx.routing.modelTier,
|
|
35507
|
-
testStrategy: ctx.routing.testStrategy
|
|
35070
|
+
function selectIndependentBatch(stories, maxCount) {
|
|
35071
|
+
const storyMap = new Map(stories.map((s) => [s.id, s]));
|
|
35072
|
+
const result = [];
|
|
35073
|
+
for (const story of stories) {
|
|
35074
|
+
if (result.length >= maxCount)
|
|
35075
|
+
break;
|
|
35076
|
+
if (story.passes || story.status === "passed" || story.status === "skipped" || story.status === "failed" || story.status === "paused" || story.status === "decomposed")
|
|
35077
|
+
continue;
|
|
35078
|
+
const allDepsFulfilled = story.dependencies.every((depId) => {
|
|
35079
|
+
const dep = storyMap.get(depId);
|
|
35080
|
+
if (!dep)
|
|
35081
|
+
return true;
|
|
35082
|
+
return dep.passes || dep.status === "passed";
|
|
35508
35083
|
});
|
|
35084
|
+
if (allDepsFulfilled) {
|
|
35085
|
+
result.push(story);
|
|
35086
|
+
}
|
|
35509
35087
|
}
|
|
35510
|
-
|
|
35511
|
-
ctx.statusWriter.setCurrentStory(null);
|
|
35512
|
-
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
35513
|
-
return { storiesCompletedDelta: ctx.storiesToExecute.length, prdDirty: true };
|
|
35088
|
+
return result;
|
|
35514
35089
|
}
|
|
35515
|
-
var
|
|
35090
|
+
var init_story_selector = __esm(() => {
|
|
35516
35091
|
init_logger2();
|
|
35517
|
-
init_event_bus();
|
|
35518
35092
|
init_prd();
|
|
35519
35093
|
});
|
|
35520
35094
|
|
|
35521
|
-
// src/execution/
|
|
35522
|
-
|
|
35095
|
+
// src/execution/parallel-worker.ts
|
|
35096
|
+
var exports_parallel_worker = {};
|
|
35097
|
+
__export(exports_parallel_worker, {
|
|
35098
|
+
executeStoryInWorktree: () => executeStoryInWorktree,
|
|
35099
|
+
executeParallelBatch: () => executeParallelBatch
|
|
35100
|
+
});
|
|
35101
|
+
async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
|
|
35523
35102
|
const logger = getSafeLogger();
|
|
35524
|
-
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
|
|
35529
|
-
|
|
35530
|
-
|
|
35531
|
-
|
|
35532
|
-
}
|
|
35533
|
-
|
|
35534
|
-
|
|
35535
|
-
|
|
35536
|
-
pipelineEventBus.emit({
|
|
35537
|
-
type: "story:paused",
|
|
35538
|
-
storyId: ctx.story.id,
|
|
35539
|
-
reason: `Execution stopped (${failureCategory ?? "unknown"} requires human review)`,
|
|
35540
|
-
cost: ctx.totalCost
|
|
35103
|
+
try {
|
|
35104
|
+
const pipelineContext = {
|
|
35105
|
+
...context,
|
|
35106
|
+
effectiveConfig: context.effectiveConfig ?? context.config,
|
|
35107
|
+
story,
|
|
35108
|
+
stories: [story],
|
|
35109
|
+
workdir: worktreePath,
|
|
35110
|
+
routing
|
|
35111
|
+
};
|
|
35112
|
+
logger?.debug("parallel", "Executing story in worktree", {
|
|
35113
|
+
storyId: story.id,
|
|
35114
|
+
worktreePath
|
|
35541
35115
|
});
|
|
35542
|
-
|
|
35543
|
-
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
|
|
35547
|
-
|
|
35548
|
-
|
|
35549
|
-
})
|
|
35550
|
-
|
|
35551
|
-
|
|
35116
|
+
const result = await runPipeline(defaultPipeline, pipelineContext, eventEmitter);
|
|
35117
|
+
return {
|
|
35118
|
+
success: result.success,
|
|
35119
|
+
cost: result.context.agentResult?.estimatedCost || 0,
|
|
35120
|
+
error: result.success ? undefined : result.reason,
|
|
35121
|
+
pipelineResult: result
|
|
35122
|
+
};
|
|
35123
|
+
} catch (error48) {
|
|
35124
|
+
return {
|
|
35125
|
+
success: false,
|
|
35126
|
+
cost: 0,
|
|
35127
|
+
error: errorMessage(error48)
|
|
35128
|
+
};
|
|
35552
35129
|
}
|
|
35553
|
-
pipelineEventBus.emit({
|
|
35554
|
-
type: "story:failed",
|
|
35555
|
-
storyId: ctx.story.id,
|
|
35556
|
-
story: ctx.story,
|
|
35557
|
-
reason: "Execution failed",
|
|
35558
|
-
countsTowardEscalation: true
|
|
35559
|
-
});
|
|
35560
|
-
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
35561
35130
|
}
|
|
35562
|
-
async function
|
|
35131
|
+
async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs) {
|
|
35563
35132
|
const logger = getSafeLogger();
|
|
35564
|
-
const
|
|
35565
|
-
|
|
35566
|
-
|
|
35567
|
-
|
|
35568
|
-
|
|
35569
|
-
|
|
35570
|
-
|
|
35571
|
-
|
|
35572
|
-
|
|
35573
|
-
|
|
35574
|
-
|
|
35133
|
+
const results = {
|
|
35134
|
+
pipelinePassed: [],
|
|
35135
|
+
merged: [],
|
|
35136
|
+
failed: [],
|
|
35137
|
+
totalCost: 0,
|
|
35138
|
+
mergeConflicts: [],
|
|
35139
|
+
storyCosts: new Map
|
|
35140
|
+
};
|
|
35141
|
+
const executing = new Set;
|
|
35142
|
+
for (const story of stories) {
|
|
35143
|
+
const worktreePath = worktreePaths.get(story.id);
|
|
35144
|
+
if (!worktreePath) {
|
|
35145
|
+
results.failed.push({
|
|
35146
|
+
story,
|
|
35147
|
+
error: "Worktree not created"
|
|
35148
|
+
});
|
|
35149
|
+
continue;
|
|
35575
35150
|
}
|
|
35576
|
-
|
|
35577
|
-
|
|
35578
|
-
|
|
35579
|
-
|
|
35580
|
-
|
|
35151
|
+
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
35152
|
+
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
35153
|
+
const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
|
|
35154
|
+
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
35155
|
+
results.totalCost += result.cost;
|
|
35156
|
+
results.storyCosts.set(story.id, result.cost);
|
|
35157
|
+
if (result.success) {
|
|
35158
|
+
results.pipelinePassed.push(story);
|
|
35159
|
+
logger?.info("parallel", "Story execution succeeded", {
|
|
35160
|
+
storyId: story.id,
|
|
35161
|
+
cost: result.cost
|
|
35162
|
+
});
|
|
35163
|
+
} else {
|
|
35164
|
+
results.failed.push({ story, error: result.error || "Unknown error", pipelineResult: result.pipelineResult });
|
|
35165
|
+
logger?.error("parallel", "Story execution failed", {
|
|
35166
|
+
storyId: story.id,
|
|
35167
|
+
error: result.error
|
|
35168
|
+
});
|
|
35169
|
+
}
|
|
35170
|
+
}).finally(() => {
|
|
35171
|
+
executing.delete(executePromise);
|
|
35581
35172
|
});
|
|
35582
|
-
|
|
35583
|
-
|
|
35584
|
-
|
|
35585
|
-
|
|
35586
|
-
await savePRD(failedPrd, ctx.prdPath);
|
|
35587
|
-
logger?.error("execution", "Story failed - max attempts reached", {
|
|
35588
|
-
storyId: ctx.story.id,
|
|
35589
|
-
failureCategory
|
|
35590
|
-
});
|
|
35591
|
-
if (ctx.featureDir) {
|
|
35592
|
-
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Max attempts reached`);
|
|
35173
|
+
executing.add(executePromise);
|
|
35174
|
+
if (executing.size >= maxConcurrency) {
|
|
35175
|
+
await Promise.race(executing);
|
|
35176
|
+
}
|
|
35593
35177
|
}
|
|
35594
|
-
|
|
35595
|
-
|
|
35596
|
-
storyId: ctx.story.id,
|
|
35597
|
-
story: ctx.story,
|
|
35598
|
-
reason: "Max attempts reached",
|
|
35599
|
-
countsTowardEscalation: true
|
|
35600
|
-
});
|
|
35601
|
-
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
35178
|
+
await Promise.all(executing);
|
|
35179
|
+
return results;
|
|
35602
35180
|
}
|
|
35603
|
-
var
|
|
35181
|
+
var init_parallel_worker = __esm(() => {
|
|
35604
35182
|
init_logger2();
|
|
35605
|
-
|
|
35606
|
-
|
|
35607
|
-
|
|
35608
|
-
init_tier_escalation();
|
|
35183
|
+
init_runner();
|
|
35184
|
+
init_stages();
|
|
35185
|
+
init_routing();
|
|
35609
35186
|
});
|
|
35610
35187
|
|
|
35611
|
-
// src/
|
|
35612
|
-
|
|
35613
|
-
|
|
35614
|
-
|
|
35615
|
-
|
|
35616
|
-
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
35623
|
-
|
|
35624
|
-
|
|
35625
|
-
|
|
35626
|
-
|
|
35627
|
-
|
|
35628
|
-
|
|
35629
|
-
|
|
35630
|
-
|
|
35631
|
-
|
|
35632
|
-
|
|
35633
|
-
|
|
35634
|
-
|
|
35635
|
-
|
|
35636
|
-
|
|
35637
|
-
|
|
35638
|
-
|
|
35639
|
-
|
|
35640
|
-
|
|
35641
|
-
|
|
35642
|
-
|
|
35643
|
-
|
|
35644
|
-
}
|
|
35645
|
-
|
|
35646
|
-
|
|
35647
|
-
|
|
35648
|
-
|
|
35649
|
-
storyId: ctx.story.id
|
|
35650
|
-
});
|
|
35651
|
-
return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
|
|
35652
|
-
}
|
|
35653
|
-
const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
|
|
35654
|
-
const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
|
|
35655
|
-
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
35656
|
-
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
35657
|
-
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
35658
|
-
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
35659
|
-
const escalateRetryAsTestAfter = escalateFailureCategory === "greenfield-no-tests";
|
|
35660
|
-
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
35661
|
-
if (!nextTier || !ctx.config.autoMode.escalation.enabled) {
|
|
35662
|
-
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
35188
|
+
// src/worktree/manager.ts
|
|
35189
|
+
var exports_manager = {};
|
|
35190
|
+
__export(exports_manager, {
|
|
35191
|
+
_managerDeps: () => _managerDeps,
|
|
35192
|
+
WorktreeManager: () => WorktreeManager
|
|
35193
|
+
});
|
|
35194
|
+
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
35195
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
35196
|
+
import { join as join50 } from "path";
|
|
35197
|
+
|
|
35198
|
+
class WorktreeManager {
|
|
35199
|
+
async ensureGitExcludes(projectRoot) {
|
|
35200
|
+
const logger = getSafeLogger();
|
|
35201
|
+
const infoDir = join50(projectRoot, ".git", "info");
|
|
35202
|
+
const excludePath = join50(infoDir, "exclude");
|
|
35203
|
+
try {
|
|
35204
|
+
await mkdir4(infoDir, { recursive: true });
|
|
35205
|
+
let existing = "";
|
|
35206
|
+
if (existsSync32(excludePath)) {
|
|
35207
|
+
existing = await Bun.file(excludePath).text();
|
|
35208
|
+
}
|
|
35209
|
+
const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
35210
|
+
if (missing.length === 0)
|
|
35211
|
+
return;
|
|
35212
|
+
const section = `
|
|
35213
|
+
# nax \u2014 generated files (auto-added by nax parallel)
|
|
35214
|
+
${missing.join(`
|
|
35215
|
+
`)}
|
|
35216
|
+
`;
|
|
35217
|
+
await Bun.write(excludePath, existing + section);
|
|
35218
|
+
logger?.info("worktree", "Updated .git/info/exclude with nax entries", {
|
|
35219
|
+
added: missing.length
|
|
35220
|
+
});
|
|
35221
|
+
} catch (error48) {
|
|
35222
|
+
logger?.warn("worktree", "Failed to update .git/info/exclude", {
|
|
35223
|
+
error: errorMessage(error48)
|
|
35224
|
+
});
|
|
35225
|
+
}
|
|
35663
35226
|
}
|
|
35664
|
-
|
|
35665
|
-
|
|
35666
|
-
|
|
35667
|
-
|
|
35227
|
+
async create(projectRoot, storyId) {
|
|
35228
|
+
validateStoryId(storyId);
|
|
35229
|
+
const worktreePath = join50(projectRoot, ".nax-wt", storyId);
|
|
35230
|
+
const branchName = `nax/${storyId}`;
|
|
35231
|
+
try {
|
|
35232
|
+
const proc = _managerDeps.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
35233
|
+
cwd: projectRoot,
|
|
35234
|
+
stdout: "pipe",
|
|
35235
|
+
stderr: "pipe"
|
|
35236
|
+
});
|
|
35237
|
+
const exitCode = await proc.exited;
|
|
35238
|
+
if (exitCode !== 0) {
|
|
35239
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35240
|
+
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
35241
|
+
}
|
|
35242
|
+
} catch (error48) {
|
|
35243
|
+
if (error48 instanceof Error) {
|
|
35244
|
+
if (error48.message.includes("not a git repository")) {
|
|
35245
|
+
throw new Error(`Not a git repository: ${projectRoot}`);
|
|
35246
|
+
}
|
|
35247
|
+
if (error48.message.includes("already exists")) {
|
|
35248
|
+
throw new Error(`Worktree for story ${storyId} already exists at ${worktreePath}`);
|
|
35249
|
+
}
|
|
35250
|
+
throw error48;
|
|
35251
|
+
}
|
|
35252
|
+
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
35253
|
+
}
|
|
35254
|
+
const nodeModulesSource = join50(projectRoot, "node_modules");
|
|
35255
|
+
if (existsSync32(nodeModulesSource)) {
|
|
35256
|
+
const nodeModulesTarget = join50(worktreePath, "node_modules");
|
|
35257
|
+
try {
|
|
35258
|
+
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
35259
|
+
} catch (error48) {
|
|
35260
|
+
await this.remove(projectRoot, storyId);
|
|
35261
|
+
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
35262
|
+
}
|
|
35263
|
+
}
|
|
35264
|
+
const envSource = join50(projectRoot, ".env");
|
|
35265
|
+
if (existsSync32(envSource)) {
|
|
35266
|
+
const envTarget = join50(worktreePath, ".env");
|
|
35267
|
+
try {
|
|
35268
|
+
symlinkSync(envSource, envTarget, "file");
|
|
35269
|
+
} catch (error48) {
|
|
35270
|
+
await this.remove(projectRoot, storyId);
|
|
35271
|
+
throw new Error(`Failed to symlink .env: ${errorMessage(error48)}`);
|
|
35272
|
+
}
|
|
35273
|
+
}
|
|
35668
35274
|
}
|
|
35669
|
-
|
|
35670
|
-
|
|
35671
|
-
const
|
|
35672
|
-
|
|
35673
|
-
|
|
35674
|
-
|
|
35675
|
-
|
|
35676
|
-
|
|
35275
|
+
async remove(projectRoot, storyId) {
|
|
35276
|
+
validateStoryId(storyId);
|
|
35277
|
+
const worktreePath = join50(projectRoot, ".nax-wt", storyId);
|
|
35278
|
+
const branchName = `nax/${storyId}`;
|
|
35279
|
+
try {
|
|
35280
|
+
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
35281
|
+
cwd: projectRoot,
|
|
35282
|
+
stdout: "pipe",
|
|
35283
|
+
stderr: "pipe"
|
|
35677
35284
|
});
|
|
35678
|
-
|
|
35679
|
-
|
|
35680
|
-
|
|
35681
|
-
|
|
35682
|
-
|
|
35285
|
+
const exitCode = await proc.exited;
|
|
35286
|
+
if (exitCode !== 0) {
|
|
35287
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35288
|
+
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
35289
|
+
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
35290
|
+
}
|
|
35291
|
+
throw new Error(`Failed to remove worktree: ${stderr || "unknown error"}`);
|
|
35292
|
+
}
|
|
35293
|
+
} catch (error48) {
|
|
35294
|
+
if (error48 instanceof Error) {
|
|
35295
|
+
throw error48;
|
|
35296
|
+
}
|
|
35297
|
+
throw new Error(`Failed to remove worktree: ${String(error48)}`);
|
|
35298
|
+
}
|
|
35299
|
+
try {
|
|
35300
|
+
const proc = _managerDeps.spawn(["git", "branch", "-D", branchName], {
|
|
35301
|
+
cwd: projectRoot,
|
|
35302
|
+
stdout: "pipe",
|
|
35303
|
+
stderr: "pipe"
|
|
35304
|
+
});
|
|
35305
|
+
const exitCode = await proc.exited;
|
|
35306
|
+
if (exitCode !== 0) {
|
|
35307
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35308
|
+
if (!stderr.includes("not found")) {
|
|
35309
|
+
const logger = getSafeLogger();
|
|
35310
|
+
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
35311
|
+
}
|
|
35312
|
+
}
|
|
35313
|
+
} catch (error48) {
|
|
35314
|
+
const logger = getSafeLogger();
|
|
35315
|
+
logger?.warn("worktree", `Failed to delete branch ${branchName}`, {
|
|
35316
|
+
error: errorMessage(error48)
|
|
35683
35317
|
});
|
|
35684
35318
|
}
|
|
35685
35319
|
}
|
|
35686
|
-
|
|
35687
|
-
|
|
35688
|
-
|
|
35689
|
-
|
|
35690
|
-
|
|
35691
|
-
|
|
35692
|
-
|
|
35693
|
-
|
|
35694
|
-
|
|
35695
|
-
|
|
35696
|
-
|
|
35697
|
-
|
|
35698
|
-
|
|
35699
|
-
|
|
35700
|
-
|
|
35701
|
-
|
|
35702
|
-
|
|
35703
|
-
|
|
35704
|
-
|
|
35705
|
-
|
|
35706
|
-
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
35707
|
-
return {
|
|
35708
|
-
...s,
|
|
35709
|
-
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
35710
|
-
routing: updatedRouting,
|
|
35711
|
-
priorErrors: [...s.priorErrors || [], errorMessage2],
|
|
35712
|
-
priorFailures: [...s.priorFailures || [], escalationFailure]
|
|
35713
|
-
};
|
|
35714
|
-
})
|
|
35715
|
-
};
|
|
35716
|
-
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
35717
|
-
for (const story of storiesToEscalate) {
|
|
35718
|
-
clearCacheForStory(story.id);
|
|
35320
|
+
async list(projectRoot) {
|
|
35321
|
+
try {
|
|
35322
|
+
const proc = _managerDeps.spawn(["git", "worktree", "list", "--porcelain"], {
|
|
35323
|
+
cwd: projectRoot,
|
|
35324
|
+
stdout: "pipe",
|
|
35325
|
+
stderr: "pipe"
|
|
35326
|
+
});
|
|
35327
|
+
const exitCode = await proc.exited;
|
|
35328
|
+
if (exitCode !== 0) {
|
|
35329
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35330
|
+
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
35331
|
+
}
|
|
35332
|
+
const stdout = await new Response(proc.stdout).text();
|
|
35333
|
+
return this.parseWorktreeList(stdout);
|
|
35334
|
+
} catch (error48) {
|
|
35335
|
+
if (error48 instanceof Error) {
|
|
35336
|
+
throw error48;
|
|
35337
|
+
}
|
|
35338
|
+
throw new Error(`Failed to list worktrees: ${String(error48)}`);
|
|
35339
|
+
}
|
|
35719
35340
|
}
|
|
35720
|
-
|
|
35721
|
-
|
|
35341
|
+
parseWorktreeList(output) {
|
|
35342
|
+
const worktrees = [];
|
|
35343
|
+
const lines = output.trim().split(`
|
|
35344
|
+
`);
|
|
35345
|
+
let currentWorktree = {};
|
|
35346
|
+
for (const line of lines) {
|
|
35347
|
+
if (line.startsWith("worktree ")) {
|
|
35348
|
+
currentWorktree.path = line.substring("worktree ".length);
|
|
35349
|
+
} else if (line.startsWith("branch ")) {
|
|
35350
|
+
currentWorktree.branch = line.substring("branch ".length).replace("refs/heads/", "");
|
|
35351
|
+
} else if (line === "") {
|
|
35352
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
35353
|
+
worktrees.push(currentWorktree);
|
|
35354
|
+
}
|
|
35355
|
+
currentWorktree = {};
|
|
35356
|
+
}
|
|
35357
|
+
}
|
|
35358
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
35359
|
+
worktrees.push(currentWorktree);
|
|
35360
|
+
}
|
|
35361
|
+
return worktrees;
|
|
35722
35362
|
}
|
|
35723
|
-
return {
|
|
35724
|
-
outcome: "escalated",
|
|
35725
|
-
prdDirty: true,
|
|
35726
|
-
prd: updatedPrd
|
|
35727
|
-
};
|
|
35728
35363
|
}
|
|
35729
|
-
var
|
|
35730
|
-
var
|
|
35731
|
-
init_hooks();
|
|
35364
|
+
var _managerDeps;
|
|
35365
|
+
var init_manager = __esm(() => {
|
|
35732
35366
|
init_logger2();
|
|
35733
|
-
|
|
35734
|
-
|
|
35735
|
-
|
|
35736
|
-
|
|
35737
|
-
init_helpers();
|
|
35738
|
-
init_progress();
|
|
35739
|
-
init_tier_outcome();
|
|
35740
|
-
_tierEscalationDeps = {
|
|
35741
|
-
savePRD
|
|
35367
|
+
init_bun_deps();
|
|
35368
|
+
init_gitignore();
|
|
35369
|
+
_managerDeps = {
|
|
35370
|
+
spawn
|
|
35742
35371
|
};
|
|
35743
35372
|
});
|
|
35744
35373
|
|
|
35745
|
-
// src/
|
|
35746
|
-
var
|
|
35747
|
-
|
|
35374
|
+
// src/worktree/merge.ts
|
|
35375
|
+
var exports_merge = {};
|
|
35376
|
+
__export(exports_merge, {
|
|
35377
|
+
_mergeDeps: () => _mergeDeps,
|
|
35378
|
+
MergeEngine: () => MergeEngine
|
|
35748
35379
|
});
|
|
35749
35380
|
|
|
35750
|
-
|
|
35751
|
-
|
|
35752
|
-
|
|
35753
|
-
|
|
35754
|
-
/\.spec\.(ts|js|tsx|jsx)$/,
|
|
35755
|
-
/package-lock\.json$/,
|
|
35756
|
-
/bun\.lock(b?)$/,
|
|
35757
|
-
/\.gitignore$/,
|
|
35758
|
-
/^nax\//
|
|
35759
|
-
];
|
|
35760
|
-
return files.filter((f) => !NOISE.some((p) => p.test(f))).slice(0, 15);
|
|
35761
|
-
}
|
|
35762
|
-
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
35763
|
-
const logger = getSafeLogger();
|
|
35764
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
35765
|
-
const prd = ctx.prd;
|
|
35766
|
-
if (pipelineResult.context.storyMetrics) {
|
|
35767
|
-
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
35381
|
+
class MergeEngine {
|
|
35382
|
+
worktreeManager;
|
|
35383
|
+
constructor(worktreeManager) {
|
|
35384
|
+
this.worktreeManager = worktreeManager;
|
|
35768
35385
|
}
|
|
35769
|
-
|
|
35770
|
-
|
|
35771
|
-
|
|
35772
|
-
|
|
35773
|
-
|
|
35774
|
-
|
|
35775
|
-
|
|
35776
|
-
|
|
35777
|
-
|
|
35778
|
-
|
|
35386
|
+
async merge(projectRoot, storyId) {
|
|
35387
|
+
const branchName = `nax/${storyId}`;
|
|
35388
|
+
try {
|
|
35389
|
+
const mergeProc = _mergeDeps.spawn(["git", "merge", "--no-ff", branchName, "-m", `Merge branch '${branchName}'`], {
|
|
35390
|
+
cwd: projectRoot,
|
|
35391
|
+
stdout: "pipe",
|
|
35392
|
+
stderr: "pipe"
|
|
35393
|
+
});
|
|
35394
|
+
const exitCode = await mergeProc.exited;
|
|
35395
|
+
const stderr = await new Response(mergeProc.stderr).text();
|
|
35396
|
+
const stdout = await new Response(mergeProc.stdout).text();
|
|
35397
|
+
if (exitCode === 0) {
|
|
35398
|
+
try {
|
|
35399
|
+
await this.worktreeManager.remove(projectRoot, storyId);
|
|
35400
|
+
} catch (error48) {
|
|
35401
|
+
const logger = getSafeLogger();
|
|
35402
|
+
logger?.warn("worktree", `Failed to cleanup worktree for ${storyId}`, {
|
|
35403
|
+
error: errorMessage(error48)
|
|
35404
|
+
});
|
|
35405
|
+
}
|
|
35406
|
+
return { success: true };
|
|
35407
|
+
}
|
|
35408
|
+
const output = `${stdout}
|
|
35409
|
+
${stderr}`;
|
|
35410
|
+
if (output.includes("CONFLICT") || output.includes("conflict") || output.includes("Automatic merge failed")) {
|
|
35411
|
+
const conflictFiles = await this.getConflictFiles(projectRoot);
|
|
35412
|
+
await this.abortMerge(projectRoot);
|
|
35413
|
+
return {
|
|
35414
|
+
success: false,
|
|
35415
|
+
conflictFiles
|
|
35416
|
+
};
|
|
35417
|
+
}
|
|
35418
|
+
throw new Error(`Merge failed: ${stderr || stdout || "unknown error"}`);
|
|
35419
|
+
} catch (error48) {
|
|
35420
|
+
if (error48 instanceof Error) {
|
|
35421
|
+
throw error48;
|
|
35422
|
+
}
|
|
35423
|
+
throw new Error(`Failed to merge branch ${branchName}: ${String(error48)}`);
|
|
35424
|
+
}
|
|
35779
35425
|
}
|
|
35780
|
-
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35784
|
-
|
|
35785
|
-
|
|
35786
|
-
|
|
35426
|
+
async mergeAll(projectRoot, storyIds, dependencies) {
|
|
35427
|
+
const orderedStories = this.topologicalSort(storyIds, dependencies);
|
|
35428
|
+
const results = [];
|
|
35429
|
+
const failedStories = new Set;
|
|
35430
|
+
for (const storyId of orderedStories) {
|
|
35431
|
+
const deps = dependencies[storyId] || [];
|
|
35432
|
+
const hasFailedDeps = deps.some((dep) => failedStories.has(dep));
|
|
35433
|
+
if (hasFailedDeps) {
|
|
35434
|
+
results.push({
|
|
35435
|
+
success: false,
|
|
35436
|
+
storyId,
|
|
35437
|
+
conflictFiles: []
|
|
35438
|
+
});
|
|
35439
|
+
failedStories.add(storyId);
|
|
35440
|
+
continue;
|
|
35441
|
+
}
|
|
35442
|
+
let result = await this.merge(projectRoot, storyId);
|
|
35443
|
+
if (!result.success && result.conflictFiles) {
|
|
35444
|
+
try {
|
|
35445
|
+
await this.rebaseWorktree(projectRoot, storyId);
|
|
35446
|
+
result = await this.merge(projectRoot, storyId);
|
|
35447
|
+
if (!result.success) {
|
|
35448
|
+
results.push({
|
|
35449
|
+
success: false,
|
|
35450
|
+
storyId,
|
|
35451
|
+
conflictFiles: result.conflictFiles,
|
|
35452
|
+
retryCount: 1
|
|
35453
|
+
});
|
|
35454
|
+
failedStories.add(storyId);
|
|
35455
|
+
continue;
|
|
35456
|
+
}
|
|
35457
|
+
results.push({
|
|
35458
|
+
success: true,
|
|
35459
|
+
storyId,
|
|
35460
|
+
retryCount: 1
|
|
35461
|
+
});
|
|
35462
|
+
} catch (error48) {
|
|
35463
|
+
results.push({
|
|
35464
|
+
success: false,
|
|
35465
|
+
storyId,
|
|
35466
|
+
conflictFiles: result.conflictFiles,
|
|
35467
|
+
retryCount: 1
|
|
35468
|
+
});
|
|
35469
|
+
failedStories.add(storyId);
|
|
35787
35470
|
}
|
|
35788
|
-
|
|
35789
|
-
|
|
35790
|
-
|
|
35471
|
+
} else if (result.success) {
|
|
35472
|
+
results.push({
|
|
35473
|
+
success: true,
|
|
35474
|
+
storyId,
|
|
35475
|
+
retryCount: 0
|
|
35476
|
+
});
|
|
35477
|
+
} else {
|
|
35478
|
+
results.push({
|
|
35479
|
+
success: false,
|
|
35480
|
+
storyId,
|
|
35481
|
+
retryCount: 0
|
|
35482
|
+
});
|
|
35483
|
+
failedStories.add(storyId);
|
|
35484
|
+
}
|
|
35485
|
+
}
|
|
35486
|
+
return results;
|
|
35487
|
+
}
|
|
35488
|
+
topologicalSort(storyIds, dependencies) {
|
|
35489
|
+
const visited = new Set;
|
|
35490
|
+
const sorted = [];
|
|
35491
|
+
const visiting = new Set;
|
|
35492
|
+
const visit = (storyId) => {
|
|
35493
|
+
if (visited.has(storyId)) {
|
|
35494
|
+
return;
|
|
35495
|
+
}
|
|
35496
|
+
if (visiting.has(storyId)) {
|
|
35497
|
+
throw new Error(`Circular dependency detected involving ${storyId}`);
|
|
35498
|
+
}
|
|
35499
|
+
visiting.add(storyId);
|
|
35500
|
+
const deps = dependencies[storyId] || [];
|
|
35501
|
+
for (const dep of deps) {
|
|
35502
|
+
if (storyIds.includes(dep)) {
|
|
35503
|
+
visit(dep);
|
|
35791
35504
|
}
|
|
35792
|
-
}
|
|
35505
|
+
}
|
|
35506
|
+
visiting.delete(storyId);
|
|
35507
|
+
visited.add(storyId);
|
|
35508
|
+
sorted.push(storyId);
|
|
35509
|
+
};
|
|
35510
|
+
for (const storyId of storyIds) {
|
|
35511
|
+
visit(storyId);
|
|
35793
35512
|
}
|
|
35513
|
+
return sorted;
|
|
35794
35514
|
}
|
|
35795
|
-
|
|
35796
|
-
|
|
35797
|
-
|
|
35798
|
-
|
|
35799
|
-
|
|
35800
|
-
|
|
35801
|
-
|
|
35802
|
-
costLimit: ctx.config.execution.costLimit,
|
|
35803
|
-
elapsedMs: Date.now() - ctx.startTime,
|
|
35804
|
-
storyDurationMs: ctx.storyStartTime ? Date.now() - ctx.storyStartTime : undefined
|
|
35805
|
-
});
|
|
35806
|
-
return { storiesCompletedDelta, costDelta, prd, prdDirty: true };
|
|
35807
|
-
}
|
|
35808
|
-
async function handlePipelineFailure(ctx, pipelineResult) {
|
|
35809
|
-
const logger = getSafeLogger();
|
|
35810
|
-
let prd = ctx.prd;
|
|
35811
|
-
let prdDirty = false;
|
|
35812
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
35813
|
-
switch (pipelineResult.finalAction) {
|
|
35814
|
-
case "pause":
|
|
35815
|
-
markStoryPaused(prd, ctx.story.id);
|
|
35816
|
-
await savePRD(prd, ctx.prdPath);
|
|
35817
|
-
prdDirty = true;
|
|
35818
|
-
logger?.warn("pipeline", "Story paused", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
35819
|
-
pipelineEventBus.emit({
|
|
35820
|
-
type: "story:paused",
|
|
35821
|
-
storyId: ctx.story.id,
|
|
35822
|
-
reason: pipelineResult.reason || "Pipeline paused",
|
|
35823
|
-
cost: ctx.totalCost
|
|
35515
|
+
async rebaseWorktree(projectRoot, storyId) {
|
|
35516
|
+
const worktreePath = `${projectRoot}/.nax-wt/${storyId}`;
|
|
35517
|
+
try {
|
|
35518
|
+
const currentBranchProc = _mergeDeps.spawn(["git", "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
35519
|
+
cwd: projectRoot,
|
|
35520
|
+
stdout: "pipe",
|
|
35521
|
+
stderr: "pipe"
|
|
35824
35522
|
});
|
|
35825
|
-
|
|
35826
|
-
|
|
35827
|
-
|
|
35828
|
-
prdDirty = true;
|
|
35829
|
-
break;
|
|
35830
|
-
case "fail":
|
|
35831
|
-
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage);
|
|
35832
|
-
await savePRD(prd, ctx.prdPath);
|
|
35833
|
-
prdDirty = true;
|
|
35834
|
-
logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
35835
|
-
if (ctx.featureDir) {
|
|
35836
|
-
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${pipelineResult.reason}`);
|
|
35523
|
+
const exitCode = await currentBranchProc.exited;
|
|
35524
|
+
if (exitCode !== 0) {
|
|
35525
|
+
throw new Error("Failed to get current branch");
|
|
35837
35526
|
}
|
|
35838
|
-
|
|
35839
|
-
|
|
35840
|
-
|
|
35841
|
-
|
|
35842
|
-
|
|
35843
|
-
countsTowardEscalation: true,
|
|
35844
|
-
feature: ctx.feature,
|
|
35845
|
-
attempts: ctx.story.attempts
|
|
35527
|
+
const currentBranch = (await new Response(currentBranchProc.stdout).text()).trim();
|
|
35528
|
+
const rebaseProc = _mergeDeps.spawn(["git", "rebase", currentBranch], {
|
|
35529
|
+
cwd: worktreePath,
|
|
35530
|
+
stdout: "pipe",
|
|
35531
|
+
stderr: "pipe"
|
|
35846
35532
|
});
|
|
35847
|
-
|
|
35848
|
-
|
|
35849
|
-
|
|
35850
|
-
|
|
35851
|
-
|
|
35852
|
-
|
|
35853
|
-
|
|
35533
|
+
const rebaseExitCode = await rebaseProc.exited;
|
|
35534
|
+
if (rebaseExitCode !== 0) {
|
|
35535
|
+
const stderr = await new Response(rebaseProc.stderr).text();
|
|
35536
|
+
const abortProc = _mergeDeps.spawn(["git", "rebase", "--abort"], {
|
|
35537
|
+
cwd: worktreePath,
|
|
35538
|
+
stdout: "pipe",
|
|
35539
|
+
stderr: "pipe"
|
|
35854
35540
|
});
|
|
35541
|
+
await abortProc.exited;
|
|
35542
|
+
throw new Error(`Rebase failed: ${stderr || "unknown error"}`);
|
|
35855
35543
|
}
|
|
35856
|
-
|
|
35857
|
-
|
|
35858
|
-
|
|
35859
|
-
|
|
35860
|
-
|
|
35861
|
-
|
|
35862
|
-
|
|
35863
|
-
|
|
35864
|
-
|
|
35865
|
-
|
|
35866
|
-
|
|
35867
|
-
|
|
35868
|
-
|
|
35869
|
-
|
|
35870
|
-
|
|
35871
|
-
|
|
35872
|
-
|
|
35544
|
+
} catch (error48) {
|
|
35545
|
+
if (error48 instanceof Error) {
|
|
35546
|
+
throw error48;
|
|
35547
|
+
}
|
|
35548
|
+
throw new Error(`Failed to rebase worktree ${storyId}: ${String(error48)}`);
|
|
35549
|
+
}
|
|
35550
|
+
}
|
|
35551
|
+
async getConflictFiles(projectRoot) {
|
|
35552
|
+
try {
|
|
35553
|
+
const proc = _mergeDeps.spawn(["git", "diff", "--name-only", "--diff-filter=U"], {
|
|
35554
|
+
cwd: projectRoot,
|
|
35555
|
+
stdout: "pipe",
|
|
35556
|
+
stderr: "pipe"
|
|
35557
|
+
});
|
|
35558
|
+
const exitCode = await proc.exited;
|
|
35559
|
+
if (exitCode !== 0) {
|
|
35560
|
+
return [];
|
|
35561
|
+
}
|
|
35562
|
+
const stdout = await new Response(proc.stdout).text();
|
|
35563
|
+
return stdout.trim().split(`
|
|
35564
|
+
`).filter((line) => line.length > 0);
|
|
35565
|
+
} catch {
|
|
35566
|
+
return [];
|
|
35567
|
+
}
|
|
35568
|
+
}
|
|
35569
|
+
async abortMerge(projectRoot) {
|
|
35570
|
+
try {
|
|
35571
|
+
const proc = _mergeDeps.spawn(["git", "merge", "--abort"], {
|
|
35572
|
+
cwd: projectRoot,
|
|
35573
|
+
stdout: "pipe",
|
|
35574
|
+
stderr: "pipe"
|
|
35575
|
+
});
|
|
35576
|
+
await proc.exited;
|
|
35577
|
+
} catch (error48) {
|
|
35578
|
+
const logger = getSafeLogger();
|
|
35579
|
+
logger?.warn("worktree", "Failed to abort merge", {
|
|
35580
|
+
error: errorMessage(error48)
|
|
35873
35581
|
});
|
|
35874
|
-
prd = escalationResult.prd;
|
|
35875
|
-
prdDirty = escalationResult.prdDirty;
|
|
35876
|
-
break;
|
|
35877
35582
|
}
|
|
35878
35583
|
}
|
|
35879
|
-
return { prd, prdDirty, costDelta };
|
|
35880
35584
|
}
|
|
35881
|
-
var
|
|
35585
|
+
var _mergeDeps;
|
|
35586
|
+
var init_merge = __esm(() => {
|
|
35882
35587
|
init_logger2();
|
|
35883
|
-
|
|
35884
|
-
|
|
35885
|
-
|
|
35886
|
-
|
|
35887
|
-
init_progress();
|
|
35588
|
+
init_bun_deps();
|
|
35589
|
+
_mergeDeps = {
|
|
35590
|
+
spawn
|
|
35591
|
+
};
|
|
35888
35592
|
});
|
|
35889
35593
|
|
|
35890
|
-
// src/execution/
|
|
35891
|
-
|
|
35892
|
-
|
|
35594
|
+
// src/execution/merge-conflict-rectify.ts
|
|
35595
|
+
var exports_merge_conflict_rectify = {};
|
|
35596
|
+
__export(exports_merge_conflict_rectify, {
|
|
35597
|
+
rectifyConflictedStory: () => rectifyConflictedStory
|
|
35598
|
+
});
|
|
35599
|
+
import path15 from "path";
|
|
35600
|
+
async function rectifyConflictedStory(options) {
|
|
35601
|
+
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter, agentGetFn } = options;
|
|
35893
35602
|
const logger = getSafeLogger();
|
|
35894
|
-
|
|
35895
|
-
|
|
35896
|
-
const
|
|
35603
|
+
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
35604
|
+
try {
|
|
35605
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
35606
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
35607
|
+
const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
35608
|
+
const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
|
|
35609
|
+
const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
|
|
35610
|
+
const worktreeManager = new WorktreeManager2;
|
|
35611
|
+
const mergeEngine = new MergeEngine2(worktreeManager);
|
|
35612
|
+
try {
|
|
35613
|
+
await worktreeManager.remove(workdir, storyId);
|
|
35614
|
+
} catch {}
|
|
35615
|
+
await worktreeManager.create(workdir, storyId);
|
|
35616
|
+
const worktreePath = path15.join(workdir, ".nax-wt", storyId);
|
|
35617
|
+
const story = prd.userStories.find((s) => s.id === storyId);
|
|
35618
|
+
if (!story) {
|
|
35619
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
35620
|
+
}
|
|
35621
|
+
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
35622
|
+
const pipelineContext = {
|
|
35623
|
+
config: config2,
|
|
35624
|
+
effectiveConfig: config2,
|
|
35897
35625
|
prd,
|
|
35898
|
-
|
|
35899
|
-
|
|
35626
|
+
story,
|
|
35627
|
+
stories: [story],
|
|
35628
|
+
workdir: worktreePath,
|
|
35629
|
+
featureDir: undefined,
|
|
35630
|
+
hooks,
|
|
35631
|
+
plugins: pluginRegistry,
|
|
35632
|
+
storyStartTime: new Date().toISOString(),
|
|
35900
35633
|
routing,
|
|
35901
|
-
|
|
35902
|
-
pluginRegistry: ctx.pluginRegistry,
|
|
35903
|
-
runId: ctx.runId,
|
|
35904
|
-
totalCost,
|
|
35905
|
-
iterations
|
|
35906
|
-
});
|
|
35907
|
-
return {
|
|
35908
|
-
prd,
|
|
35909
|
-
storiesCompletedDelta: dryRunResult.storiesCompletedDelta,
|
|
35910
|
-
costDelta: 0,
|
|
35911
|
-
prdDirty: dryRunResult.prdDirty
|
|
35912
|
-
};
|
|
35913
|
-
}
|
|
35914
|
-
const storyStartTime = Date.now();
|
|
35915
|
-
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
35916
|
-
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
35917
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join51(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
35918
|
-
const pipelineContext = {
|
|
35919
|
-
config: ctx.config,
|
|
35920
|
-
effectiveConfig,
|
|
35921
|
-
prd,
|
|
35922
|
-
story,
|
|
35923
|
-
stories: storiesToExecute,
|
|
35924
|
-
routing,
|
|
35925
|
-
workdir: ctx.workdir,
|
|
35926
|
-
prdPath: ctx.prdPath,
|
|
35927
|
-
featureDir: ctx.featureDir,
|
|
35928
|
-
hooks: ctx.hooks,
|
|
35929
|
-
plugins: ctx.pluginRegistry,
|
|
35930
|
-
storyStartTime: new Date().toISOString(),
|
|
35931
|
-
storyGitRef: storyGitRef ?? undefined,
|
|
35932
|
-
interaction: ctx.interactionChain ?? undefined,
|
|
35933
|
-
agentGetFn: ctx.agentGetFn,
|
|
35934
|
-
pidRegistry: ctx.pidRegistry,
|
|
35935
|
-
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
35936
|
-
};
|
|
35937
|
-
ctx.statusWriter.setPrd(prd);
|
|
35938
|
-
ctx.statusWriter.setCurrentStory({
|
|
35939
|
-
storyId: story.id,
|
|
35940
|
-
title: story.title,
|
|
35941
|
-
complexity: routing.complexity,
|
|
35942
|
-
tddStrategy: routing.testStrategy,
|
|
35943
|
-
model: routing.modelTier,
|
|
35944
|
-
attempt: (story.attempts ?? 0) + 1,
|
|
35945
|
-
phase: "routing"
|
|
35946
|
-
});
|
|
35947
|
-
await ctx.statusWriter.update(totalCost, iterations);
|
|
35948
|
-
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
35949
|
-
const currentPrd = pipelineResult.context.prd;
|
|
35950
|
-
const handlerCtx = {
|
|
35951
|
-
config: ctx.config,
|
|
35952
|
-
prd: currentPrd,
|
|
35953
|
-
prdPath: ctx.prdPath,
|
|
35954
|
-
workdir: ctx.workdir,
|
|
35955
|
-
featureDir: ctx.featureDir,
|
|
35956
|
-
hooks: ctx.hooks,
|
|
35957
|
-
feature: ctx.feature,
|
|
35958
|
-
totalCost,
|
|
35959
|
-
startTime: ctx.startTime,
|
|
35960
|
-
runId: ctx.runId,
|
|
35961
|
-
pluginRegistry: ctx.pluginRegistry,
|
|
35962
|
-
story,
|
|
35963
|
-
storiesToExecute,
|
|
35964
|
-
routing: pipelineResult.context.routing ?? routing,
|
|
35965
|
-
isBatchExecution,
|
|
35966
|
-
allStoryMetrics,
|
|
35967
|
-
storyGitRef,
|
|
35968
|
-
interactionChain: ctx.interactionChain,
|
|
35969
|
-
storyStartTime
|
|
35970
|
-
};
|
|
35971
|
-
if (pipelineResult.success) {
|
|
35972
|
-
const r2 = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
35973
|
-
return {
|
|
35974
|
-
prd: r2.prd,
|
|
35975
|
-
storiesCompletedDelta: r2.storiesCompletedDelta,
|
|
35976
|
-
costDelta: r2.costDelta,
|
|
35977
|
-
prdDirty: r2.prdDirty,
|
|
35978
|
-
finalAction: pipelineResult.finalAction
|
|
35634
|
+
agentGetFn
|
|
35979
35635
|
};
|
|
35636
|
+
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
35637
|
+
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
35638
|
+
if (!pipelineResult.success) {
|
|
35639
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
35640
|
+
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
35641
|
+
}
|
|
35642
|
+
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
35643
|
+
const mergeResult = mergeResults[0];
|
|
35644
|
+
if (!mergeResult || !mergeResult.success) {
|
|
35645
|
+
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
35646
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
35647
|
+
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
35648
|
+
}
|
|
35649
|
+
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
35650
|
+
storyId,
|
|
35651
|
+
originalCost: options.originalCost,
|
|
35652
|
+
rectificationCost: cost
|
|
35653
|
+
});
|
|
35654
|
+
return { success: true, storyId, cost };
|
|
35655
|
+
} catch (error48) {
|
|
35656
|
+
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
35657
|
+
storyId,
|
|
35658
|
+
error: errorMessage(error48)
|
|
35659
|
+
});
|
|
35660
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
35980
35661
|
}
|
|
35981
|
-
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
35982
|
-
return {
|
|
35983
|
-
prd: r.prd,
|
|
35984
|
-
storiesCompletedDelta: 0,
|
|
35985
|
-
costDelta: r.costDelta,
|
|
35986
|
-
prdDirty: r.prdDirty,
|
|
35987
|
-
finalAction: pipelineResult.finalAction,
|
|
35988
|
-
reason: pipelineResult.reason,
|
|
35989
|
-
subStoryCount: pipelineResult.subStoryCount
|
|
35990
|
-
};
|
|
35991
35662
|
}
|
|
35992
|
-
var
|
|
35993
|
-
var init_iteration_runner = __esm(() => {
|
|
35994
|
-
init_loader();
|
|
35663
|
+
var init_merge_conflict_rectify = __esm(() => {
|
|
35995
35664
|
init_logger2();
|
|
35996
|
-
init_runner();
|
|
35997
|
-
init_stages();
|
|
35998
|
-
init_git();
|
|
35999
|
-
init_dry_run();
|
|
36000
|
-
init_pipeline_result_handler();
|
|
36001
|
-
_iterationRunnerDeps = {
|
|
36002
|
-
loadConfigForWorkdir
|
|
36003
|
-
};
|
|
36004
35665
|
});
|
|
36005
35666
|
|
|
36006
|
-
// src/execution/
|
|
36007
|
-
|
|
36008
|
-
|
|
36009
|
-
|
|
36010
|
-
|
|
36011
|
-
|
|
36012
|
-
|
|
36013
|
-
|
|
36014
|
-
|
|
36015
|
-
|
|
36016
|
-
|
|
36017
|
-
|
|
36018
|
-
|
|
36019
|
-
|
|
36020
|
-
|
|
36021
|
-
|
|
36022
|
-
|
|
36023
|
-
|
|
36024
|
-
|
|
36025
|
-
|
|
36026
|
-
|
|
35667
|
+
// src/execution/parallel-batch.ts
|
|
35668
|
+
var exports_parallel_batch = {};
|
|
35669
|
+
__export(exports_parallel_batch, {
|
|
35670
|
+
runParallelBatch: () => runParallelBatch,
|
|
35671
|
+
_parallelBatchDeps: () => _parallelBatchDeps
|
|
35672
|
+
});
|
|
35673
|
+
import path16 from "path";
|
|
35674
|
+
async function runParallelBatch(options) {
|
|
35675
|
+
const { stories, ctx, prd } = options;
|
|
35676
|
+
const { workdir, config: config2, maxConcurrency, pipelineContext, eventEmitter, agentGetFn, hooks, pluginRegistry } = ctx;
|
|
35677
|
+
const worktreeManager = await _parallelBatchDeps.createWorktreeManager();
|
|
35678
|
+
const worktreePaths = new Map;
|
|
35679
|
+
const storyStartTimes = new Map;
|
|
35680
|
+
for (const story of stories) {
|
|
35681
|
+
storyStartTimes.set(story.id, Date.now());
|
|
35682
|
+
await worktreeManager.create(workdir, story.id);
|
|
35683
|
+
worktreePaths.set(story.id, path16.join(workdir, ".nax-wt", story.id));
|
|
35684
|
+
}
|
|
35685
|
+
const workerResult = await _parallelBatchDeps.executeParallelBatch(stories, workdir, config2, pipelineContext, worktreePaths, maxConcurrency, eventEmitter);
|
|
35686
|
+
const batchEndMs = Date.now();
|
|
35687
|
+
const completed = workerResult.merged;
|
|
35688
|
+
const failed = workerResult.failed.map((f) => ({
|
|
35689
|
+
story: f.story,
|
|
35690
|
+
pipelineResult: f.pipelineResult ?? {
|
|
35691
|
+
success: false,
|
|
35692
|
+
finalAction: "fail",
|
|
35693
|
+
reason: f.error,
|
|
35694
|
+
context: { ...pipelineContext, story: f.story, stories: [f.story], workdir }
|
|
36027
35695
|
}
|
|
36028
|
-
|
|
36029
|
-
|
|
36030
|
-
|
|
36031
|
-
|
|
36032
|
-
storiesToExecute,
|
|
36033
|
-
routing: buildPreviewRouting(story2, config2),
|
|
36034
|
-
isBatchExecution: batch.isBatch && storiesToExecute.length > 1
|
|
36035
|
-
},
|
|
36036
|
-
nextBatchIndex: currentBatchIndex + 1
|
|
36037
|
-
};
|
|
35696
|
+
}));
|
|
35697
|
+
const storyEndTimes = new Map;
|
|
35698
|
+
for (const story of [...workerResult.pipelinePassed, ...workerResult.merged]) {
|
|
35699
|
+
storyEndTimes.set(story.id, batchEndMs);
|
|
36038
35700
|
}
|
|
36039
|
-
const story
|
|
36040
|
-
|
|
36041
|
-
|
|
36042
|
-
|
|
36043
|
-
|
|
36044
|
-
|
|
36045
|
-
|
|
36046
|
-
|
|
36047
|
-
|
|
35701
|
+
for (const { story } of workerResult.failed) {
|
|
35702
|
+
storyEndTimes.set(story.id, batchEndMs);
|
|
35703
|
+
}
|
|
35704
|
+
const mergeConflicts = [];
|
|
35705
|
+
for (const conflict of workerResult.mergeConflicts) {
|
|
35706
|
+
const story = stories.find((s) => s.id === conflict.storyId);
|
|
35707
|
+
if (!story)
|
|
35708
|
+
continue;
|
|
35709
|
+
try {
|
|
35710
|
+
const rectResult = await _parallelBatchDeps.rectifyConflictedStory({
|
|
35711
|
+
...conflict,
|
|
35712
|
+
workdir,
|
|
35713
|
+
config: config2,
|
|
35714
|
+
hooks,
|
|
35715
|
+
pluginRegistry,
|
|
35716
|
+
prd,
|
|
35717
|
+
eventEmitter,
|
|
35718
|
+
agentGetFn
|
|
35719
|
+
});
|
|
35720
|
+
mergeConflicts.push({ story, rectified: rectResult.success, cost: rectResult.cost });
|
|
35721
|
+
} catch (err) {
|
|
35722
|
+
const logger = getSafeLogger();
|
|
35723
|
+
logger?.warn("[parallel-batch]", "rectification failed for story", {
|
|
35724
|
+
storyId: story.id,
|
|
35725
|
+
error: err.message
|
|
35726
|
+
});
|
|
35727
|
+
mergeConflicts.push({ story, rectified: false, cost: 0 });
|
|
35728
|
+
}
|
|
35729
|
+
storyEndTimes.set(conflict.storyId, Date.now());
|
|
35730
|
+
}
|
|
35731
|
+
const storyCosts = workerResult.storyCosts;
|
|
35732
|
+
const totalCost = [...storyCosts.values()].reduce((sum, c) => sum + c, 0);
|
|
35733
|
+
const storyDurations = new Map;
|
|
35734
|
+
for (const story of stories) {
|
|
35735
|
+
const startMs = storyStartTimes.get(story.id);
|
|
35736
|
+
const endMs = storyEndTimes.get(story.id);
|
|
35737
|
+
if (startMs !== undefined && endMs !== undefined) {
|
|
35738
|
+
storyDurations.set(story.id, endMs - startMs);
|
|
35739
|
+
}
|
|
35740
|
+
}
|
|
35741
|
+
return { completed, failed, mergeConflicts, storyCosts, storyDurations, totalCost };
|
|
35742
|
+
}
|
|
35743
|
+
var _parallelBatchDeps;
|
|
35744
|
+
var init_parallel_batch = __esm(() => {
|
|
35745
|
+
init_logger2();
|
|
35746
|
+
_parallelBatchDeps = {
|
|
35747
|
+
executeParallelBatch: async (_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter) => {
|
|
35748
|
+
const { executeParallelBatch: executeParallelBatch2 } = await Promise.resolve().then(() => (init_parallel_worker(), exports_parallel_worker));
|
|
35749
|
+
return executeParallelBatch2(_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter);
|
|
36048
35750
|
},
|
|
36049
|
-
|
|
35751
|
+
createWorktreeManager: async () => {
|
|
35752
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
35753
|
+
return new WorktreeManager2;
|
|
35754
|
+
},
|
|
35755
|
+
createMergeEngine: async (worktreeManager) => {
|
|
35756
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
35757
|
+
return new MergeEngine2(worktreeManager);
|
|
35758
|
+
},
|
|
35759
|
+
rectifyConflictedStory: async (opts) => {
|
|
35760
|
+
const { rectifyConflictedStory: rectifyConflictedStory2 } = await Promise.resolve().then(() => (init_merge_conflict_rectify(), exports_merge_conflict_rectify));
|
|
35761
|
+
return rectifyConflictedStory2(opts);
|
|
35762
|
+
}
|
|
36050
35763
|
};
|
|
36051
|
-
}
|
|
36052
|
-
var init_story_selector = __esm(() => {
|
|
36053
|
-
init_prd();
|
|
36054
35764
|
});
|
|
36055
35765
|
|
|
36056
|
-
// src/execution/
|
|
36057
|
-
var
|
|
36058
|
-
__export(
|
|
36059
|
-
|
|
35766
|
+
// src/execution/unified-executor.ts
|
|
35767
|
+
var exports_unified_executor = {};
|
|
35768
|
+
__export(exports_unified_executor, {
|
|
35769
|
+
executeUnified: () => executeUnified,
|
|
35770
|
+
_unifiedExecutorDeps: () => _unifiedExecutorDeps
|
|
36060
35771
|
});
|
|
36061
|
-
async function
|
|
35772
|
+
async function executeUnified(ctx, initialPrd) {
|
|
36062
35773
|
const logger = getSafeLogger();
|
|
36063
|
-
let
|
|
36064
|
-
|
|
36065
|
-
|
|
36066
|
-
|
|
36067
|
-
|
|
36068
|
-
|
|
36069
|
-
|
|
36070
|
-
0
|
|
36071
|
-
];
|
|
35774
|
+
let prd = initialPrd;
|
|
35775
|
+
let prdDirty = false;
|
|
35776
|
+
let iterations = 0;
|
|
35777
|
+
let storiesCompleted = 0;
|
|
35778
|
+
let totalCost = 0;
|
|
35779
|
+
let lastStoryId = null;
|
|
35780
|
+
let currentBatchIndex = 0;
|
|
36072
35781
|
const allStoryMetrics = [];
|
|
36073
35782
|
let warningSent = false;
|
|
36074
35783
|
let deferredReview;
|
|
@@ -36095,20 +35804,22 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36095
35804
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef);
|
|
36096
35805
|
return buildResult2("completed");
|
|
36097
35806
|
}
|
|
36098
|
-
|
|
36099
|
-
|
|
36100
|
-
|
|
36101
|
-
|
|
36102
|
-
|
|
36103
|
-
|
|
36104
|
-
|
|
36105
|
-
|
|
36106
|
-
|
|
36107
|
-
|
|
36108
|
-
|
|
36109
|
-
|
|
36110
|
-
|
|
36111
|
-
|
|
35807
|
+
if (ctx.config.acceptance?.enabled) {
|
|
35808
|
+
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
35809
|
+
const preRunCtx = {
|
|
35810
|
+
config: ctx.config,
|
|
35811
|
+
effectiveConfig: ctx.config,
|
|
35812
|
+
prd,
|
|
35813
|
+
workdir: ctx.workdir,
|
|
35814
|
+
featureDir: ctx.featureDir,
|
|
35815
|
+
story: prd.userStories[0],
|
|
35816
|
+
stories: prd.userStories,
|
|
35817
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
|
|
35818
|
+
hooks: ctx.hooks,
|
|
35819
|
+
agentGetFn: ctx.agentGetFn
|
|
35820
|
+
};
|
|
35821
|
+
await runPipeline(preRunPipeline, preRunCtx, ctx.eventEmitter);
|
|
35822
|
+
}
|
|
36112
35823
|
while (iterations < ctx.config.execution.maxIterations) {
|
|
36113
35824
|
iterations++;
|
|
36114
35825
|
if (Math.round(process.memoryUsage().heapUsed / 1024 / 1024) > 1024)
|
|
@@ -36120,13 +35831,203 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36120
35831
|
if (isComplete(prd)) {
|
|
36121
35832
|
if (ctx.interactionChain && isTriggerEnabled("pre-merge", ctx.config)) {
|
|
36122
35833
|
const shouldProceed = await checkPreMerge({ featureName: ctx.feature, totalStories: prd.userStories.length, cost: totalCost }, ctx.config, ctx.interactionChain);
|
|
36123
|
-
if (!shouldProceed)
|
|
35834
|
+
if (!shouldProceed)
|
|
36124
35835
|
return buildResult2("pre-merge-aborted");
|
|
36125
|
-
}
|
|
36126
35836
|
}
|
|
36127
35837
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef);
|
|
36128
35838
|
return buildResult2("completed");
|
|
36129
35839
|
}
|
|
35840
|
+
const costLimit = ctx.config.execution.costLimit;
|
|
35841
|
+
if ((ctx.parallelCount ?? 0) > 0) {
|
|
35842
|
+
const readyStories = getAllReadyStories(prd);
|
|
35843
|
+
const batch = _unifiedExecutorDeps.selectIndependentBatch(readyStories, ctx.parallelCount);
|
|
35844
|
+
if (batch.length > 1) {
|
|
35845
|
+
for (const story of batch) {
|
|
35846
|
+
pipelineEventBus.emit({
|
|
35847
|
+
type: "story:started",
|
|
35848
|
+
storyId: story.id,
|
|
35849
|
+
story,
|
|
35850
|
+
workdir: ctx.workdir,
|
|
35851
|
+
modelTier: story.routing?.modelTier ?? ctx.config.autoMode.complexityRouting?.[story.routing?.complexity ?? "medium"] ?? "balanced",
|
|
35852
|
+
agent: ctx.config.autoMode.defaultAgent,
|
|
35853
|
+
iteration: iterations
|
|
35854
|
+
});
|
|
35855
|
+
}
|
|
35856
|
+
const batchStartedAt = new Date().toISOString();
|
|
35857
|
+
const storyStartMs = new Map;
|
|
35858
|
+
for (const s of batch)
|
|
35859
|
+
storyStartMs.set(s.id, Date.now());
|
|
35860
|
+
const batchResult = await _unifiedExecutorDeps.runParallelBatch({
|
|
35861
|
+
stories: batch,
|
|
35862
|
+
ctx: {
|
|
35863
|
+
workdir: ctx.workdir,
|
|
35864
|
+
config: ctx.config,
|
|
35865
|
+
hooks: ctx.hooks,
|
|
35866
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
35867
|
+
maxConcurrency: ctx.parallelCount,
|
|
35868
|
+
pipelineContext: {
|
|
35869
|
+
config: ctx.config,
|
|
35870
|
+
effectiveConfig: ctx.config,
|
|
35871
|
+
prd,
|
|
35872
|
+
hooks: ctx.hooks,
|
|
35873
|
+
featureDir: ctx.featureDir,
|
|
35874
|
+
agentGetFn: ctx.agentGetFn,
|
|
35875
|
+
pidRegistry: ctx.pidRegistry
|
|
35876
|
+
},
|
|
35877
|
+
eventEmitter: ctx.eventEmitter,
|
|
35878
|
+
agentGetFn: ctx.agentGetFn
|
|
35879
|
+
},
|
|
35880
|
+
prd
|
|
35881
|
+
});
|
|
35882
|
+
for (const { story, pipelineResult } of batchResult.failed) {
|
|
35883
|
+
const storyRouting = prd.userStories.find((s) => s.id === story.id)?.routing;
|
|
35884
|
+
await handlePipelineFailure({
|
|
35885
|
+
config: ctx.config,
|
|
35886
|
+
prd,
|
|
35887
|
+
prdPath: ctx.prdPath,
|
|
35888
|
+
workdir: ctx.workdir,
|
|
35889
|
+
featureDir: ctx.featureDir,
|
|
35890
|
+
hooks: ctx.hooks,
|
|
35891
|
+
feature: ctx.feature,
|
|
35892
|
+
totalCost,
|
|
35893
|
+
startTime: ctx.startTime,
|
|
35894
|
+
runId: ctx.runId,
|
|
35895
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
35896
|
+
story,
|
|
35897
|
+
storiesToExecute: [story],
|
|
35898
|
+
routing: {
|
|
35899
|
+
complexity: storyRouting?.complexity ?? "medium",
|
|
35900
|
+
modelTier: storyRouting?.modelTier ?? "balanced",
|
|
35901
|
+
testStrategy: storyRouting?.testStrategy ?? "test-after",
|
|
35902
|
+
reasoning: storyRouting?.reasoning ?? ""
|
|
35903
|
+
},
|
|
35904
|
+
isBatchExecution: false,
|
|
35905
|
+
allStoryMetrics,
|
|
35906
|
+
storyGitRef: null,
|
|
35907
|
+
interactionChain: ctx.interactionChain
|
|
35908
|
+
}, pipelineResult);
|
|
35909
|
+
}
|
|
35910
|
+
totalCost += batchResult.totalCost;
|
|
35911
|
+
storiesCompleted += batchResult.completed.length;
|
|
35912
|
+
prdDirty = true;
|
|
35913
|
+
const batchCompletedAt = new Date().toISOString();
|
|
35914
|
+
for (const story of batchResult.completed) {
|
|
35915
|
+
const storyCost = batchResult.storyCosts.get(story.id) ?? 0;
|
|
35916
|
+
const storyStartTime = storyStartMs.get(story.id) ?? Date.now();
|
|
35917
|
+
const storyDuration = batchResult.storyDurations?.get(story.id) ?? Date.now() - storyStartTime;
|
|
35918
|
+
allStoryMetrics.push({
|
|
35919
|
+
storyId: story.id,
|
|
35920
|
+
complexity: story.routing?.complexity ?? "medium",
|
|
35921
|
+
modelTier: story.routing?.modelTier ?? "balanced",
|
|
35922
|
+
modelUsed: ctx.config.autoMode.defaultAgent,
|
|
35923
|
+
attempts: 1,
|
|
35924
|
+
finalTier: story.routing?.modelTier ?? "balanced",
|
|
35925
|
+
success: true,
|
|
35926
|
+
cost: storyCost,
|
|
35927
|
+
durationMs: storyDuration,
|
|
35928
|
+
firstPassSuccess: true,
|
|
35929
|
+
startedAt: batchStartedAt,
|
|
35930
|
+
completedAt: batchCompletedAt,
|
|
35931
|
+
source: "parallel"
|
|
35932
|
+
});
|
|
35933
|
+
}
|
|
35934
|
+
for (const conflict of batchResult.mergeConflicts) {
|
|
35935
|
+
if (conflict.rectified) {
|
|
35936
|
+
const storyStartTime = storyStartMs.get(conflict.story.id) ?? Date.now();
|
|
35937
|
+
const storyDuration = batchResult.storyDurations?.get(conflict.story.id) ?? Date.now() - storyStartTime;
|
|
35938
|
+
allStoryMetrics.push({
|
|
35939
|
+
storyId: conflict.story.id,
|
|
35940
|
+
complexity: conflict.story.routing?.complexity ?? "medium",
|
|
35941
|
+
modelTier: conflict.story.routing?.modelTier ?? "balanced",
|
|
35942
|
+
modelUsed: ctx.config.autoMode.defaultAgent,
|
|
35943
|
+
attempts: 1,
|
|
35944
|
+
finalTier: conflict.story.routing?.modelTier ?? "balanced",
|
|
35945
|
+
success: true,
|
|
35946
|
+
cost: batchResult.storyCosts.get(conflict.story.id) ?? 0,
|
|
35947
|
+
durationMs: storyDuration,
|
|
35948
|
+
firstPassSuccess: false,
|
|
35949
|
+
startedAt: batchStartedAt,
|
|
35950
|
+
completedAt: batchCompletedAt,
|
|
35951
|
+
source: "rectification",
|
|
35952
|
+
rectificationCost: conflict.cost
|
|
35953
|
+
});
|
|
35954
|
+
}
|
|
35955
|
+
}
|
|
35956
|
+
if (totalCost >= costLimit) {
|
|
35957
|
+
return buildResult2("cost-limit");
|
|
35958
|
+
}
|
|
35959
|
+
continue;
|
|
35960
|
+
}
|
|
35961
|
+
if (batch.length === 1) {
|
|
35962
|
+
const singleStory = batch[0];
|
|
35963
|
+
const singleSelection = {
|
|
35964
|
+
story: singleStory,
|
|
35965
|
+
storiesToExecute: [singleStory],
|
|
35966
|
+
routing: buildPreviewRouting(singleStory, ctx.config),
|
|
35967
|
+
isBatchExecution: false
|
|
35968
|
+
};
|
|
35969
|
+
if (!ctx.useBatch)
|
|
35970
|
+
lastStoryId = singleStory.id;
|
|
35971
|
+
if (totalCost >= costLimit) {
|
|
35972
|
+
const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: totalCost, limit: costLimit }, ctx.config, ctx.interactionChain) : false;
|
|
35973
|
+
if (!shouldProceed) {
|
|
35974
|
+
pipelineEventBus.emit({
|
|
35975
|
+
type: "run:paused",
|
|
35976
|
+
reason: `Cost limit reached: $${totalCost.toFixed(2)}`,
|
|
35977
|
+
storyId: singleStory.id,
|
|
35978
|
+
cost: totalCost
|
|
35979
|
+
});
|
|
35980
|
+
return buildResult2("cost-limit");
|
|
35981
|
+
}
|
|
35982
|
+
pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
|
|
35983
|
+
}
|
|
35984
|
+
pipelineEventBus.emit({
|
|
35985
|
+
type: "story:started",
|
|
35986
|
+
storyId: singleStory.id,
|
|
35987
|
+
story: singleStory,
|
|
35988
|
+
workdir: ctx.workdir,
|
|
35989
|
+
modelTier: singleSelection.routing.modelTier,
|
|
35990
|
+
agent: ctx.config.autoMode.defaultAgent,
|
|
35991
|
+
iteration: iterations
|
|
35992
|
+
});
|
|
35993
|
+
const singleIter = await _unifiedExecutorDeps.runIteration(ctx, prd, singleSelection, iterations, totalCost, allStoryMetrics);
|
|
35994
|
+
[prd, storiesCompleted, totalCost, prdDirty] = [
|
|
35995
|
+
singleIter.prd,
|
|
35996
|
+
storiesCompleted + singleIter.storiesCompletedDelta,
|
|
35997
|
+
totalCost + singleIter.costDelta,
|
|
35998
|
+
singleIter.prdDirty
|
|
35999
|
+
];
|
|
36000
|
+
if (singleIter.finalAction === "decomposed") {
|
|
36001
|
+
iterations--;
|
|
36002
|
+
pipelineEventBus.emit({
|
|
36003
|
+
type: "story:decomposed",
|
|
36004
|
+
storyId: singleStory.id,
|
|
36005
|
+
story: singleStory,
|
|
36006
|
+
subStoryCount: singleIter.subStoryCount ?? 0
|
|
36007
|
+
});
|
|
36008
|
+
if (singleIter.prdDirty) {
|
|
36009
|
+
prd = await loadPRD(ctx.prdPath);
|
|
36010
|
+
prdDirty = false;
|
|
36011
|
+
}
|
|
36012
|
+
ctx.statusWriter.setPrd(prd);
|
|
36013
|
+
continue;
|
|
36014
|
+
}
|
|
36015
|
+
if (singleIter.prdDirty) {
|
|
36016
|
+
prd = await loadPRD(ctx.prdPath);
|
|
36017
|
+
prdDirty = false;
|
|
36018
|
+
}
|
|
36019
|
+
ctx.statusWriter.setPrd(prd);
|
|
36020
|
+
ctx.statusWriter.setCurrentStory(null);
|
|
36021
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
36022
|
+
if (isStalled(prd)) {
|
|
36023
|
+
pipelineEventBus.emit({ type: "run:paused", reason: "All remaining stories blocked", cost: totalCost });
|
|
36024
|
+
return buildResult2("stalled");
|
|
36025
|
+
}
|
|
36026
|
+
if (ctx.config.execution.iterationDelayMs > 0)
|
|
36027
|
+
await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
36028
|
+
continue;
|
|
36029
|
+
}
|
|
36030
|
+
}
|
|
36130
36031
|
const selected = selectNextStories(prd, ctx.config, ctx.batchPlan, currentBatchIndex, lastStoryId, ctx.useBatch);
|
|
36131
36032
|
if (!selected)
|
|
36132
36033
|
return buildResult2("no-stories");
|
|
@@ -36138,8 +36039,8 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36138
36039
|
const { selection } = selected;
|
|
36139
36040
|
if (!ctx.useBatch)
|
|
36140
36041
|
lastStoryId = selection.story.id;
|
|
36141
|
-
if (totalCost >=
|
|
36142
|
-
const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: totalCost, limit:
|
|
36042
|
+
if (totalCost >= costLimit) {
|
|
36043
|
+
const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: totalCost, limit: costLimit }, ctx.config, ctx.interactionChain) : false;
|
|
36143
36044
|
if (!shouldProceed) {
|
|
36144
36045
|
pipelineEventBus.emit({
|
|
36145
36046
|
type: "run:paused",
|
|
@@ -36160,7 +36061,7 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36160
36061
|
agent: ctx.config.autoMode.defaultAgent,
|
|
36161
36062
|
iteration: iterations
|
|
36162
36063
|
});
|
|
36163
|
-
const iter = await runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics);
|
|
36064
|
+
const iter = await _unifiedExecutorDeps.runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics);
|
|
36164
36065
|
[prd, storiesCompleted, totalCost, prdDirty] = [
|
|
36165
36066
|
iter.prd,
|
|
36166
36067
|
storiesCompleted + iter.storiesCompletedDelta,
|
|
@@ -36183,7 +36084,6 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36183
36084
|
continue;
|
|
36184
36085
|
}
|
|
36185
36086
|
if (ctx.interactionChain && isTriggerEnabled("cost-warning", ctx.config) && !warningSent) {
|
|
36186
|
-
const costLimit = ctx.config.execution.costLimit;
|
|
36187
36087
|
const triggerCfg = ctx.config.interaction?.triggers?.["cost-warning"];
|
|
36188
36088
|
const threshold = typeof triggerCfg === "object" ? triggerCfg.threshold ?? 0.8 : 0.8;
|
|
36189
36089
|
if (totalCost >= costLimit * threshold) {
|
|
@@ -36205,12 +36105,17 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36205
36105
|
if (ctx.config.execution.iterationDelayMs > 0)
|
|
36206
36106
|
await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
36207
36107
|
}
|
|
36208
|
-
|
|
36209
|
-
|
|
36108
|
+
if (ctx.config.acceptance?.enabled) {
|
|
36109
|
+
logger?.info("execution", "Running post-run pipeline (acceptance tests)");
|
|
36110
|
+
await runPipeline(postRunPipeline, { config: ctx.config, prd, workdir: ctx.workdir, story: prd.userStories[0] }, ctx.eventEmitter);
|
|
36111
|
+
}
|
|
36210
36112
|
return buildResult2("max-iterations");
|
|
36211
|
-
} finally {
|
|
36113
|
+
} finally {
|
|
36114
|
+
stopHeartbeat();
|
|
36115
|
+
}
|
|
36212
36116
|
}
|
|
36213
|
-
var
|
|
36117
|
+
var _unifiedExecutorDeps;
|
|
36118
|
+
var init_unified_executor = __esm(() => {
|
|
36214
36119
|
init_triggers();
|
|
36215
36120
|
init_logger2();
|
|
36216
36121
|
init_event_bus();
|
|
@@ -36224,21 +36129,31 @@ var init_sequential_executor = __esm(() => {
|
|
|
36224
36129
|
init_prd();
|
|
36225
36130
|
init_crash_recovery();
|
|
36226
36131
|
init_deferred_review();
|
|
36132
|
+
init_helpers();
|
|
36227
36133
|
init_iteration_runner();
|
|
36134
|
+
init_pipeline_result_handler();
|
|
36228
36135
|
init_story_selector();
|
|
36136
|
+
_unifiedExecutorDeps = {
|
|
36137
|
+
runParallelBatch: async (opts) => {
|
|
36138
|
+
const { runParallelBatch: runParallelBatch2 } = await Promise.resolve().then(() => (init_parallel_batch(), exports_parallel_batch));
|
|
36139
|
+
return runParallelBatch2(opts);
|
|
36140
|
+
},
|
|
36141
|
+
runIteration,
|
|
36142
|
+
selectIndependentBatch
|
|
36143
|
+
};
|
|
36229
36144
|
});
|
|
36230
36145
|
|
|
36231
36146
|
// src/project/detector.ts
|
|
36232
|
-
import { join as
|
|
36147
|
+
import { join as join51 } from "path";
|
|
36233
36148
|
async function detectLanguage(workdir, pkg) {
|
|
36234
36149
|
const deps = _detectorDeps;
|
|
36235
|
-
if (await deps.fileExists(
|
|
36150
|
+
if (await deps.fileExists(join51(workdir, "go.mod")))
|
|
36236
36151
|
return "go";
|
|
36237
|
-
if (await deps.fileExists(
|
|
36152
|
+
if (await deps.fileExists(join51(workdir, "Cargo.toml")))
|
|
36238
36153
|
return "rust";
|
|
36239
|
-
if (await deps.fileExists(
|
|
36154
|
+
if (await deps.fileExists(join51(workdir, "pyproject.toml")))
|
|
36240
36155
|
return "python";
|
|
36241
|
-
if (await deps.fileExists(
|
|
36156
|
+
if (await deps.fileExists(join51(workdir, "requirements.txt")))
|
|
36242
36157
|
return "python";
|
|
36243
36158
|
if (pkg != null) {
|
|
36244
36159
|
const allDeps = {
|
|
@@ -36298,18 +36213,18 @@ async function detectLintTool(workdir, language) {
|
|
|
36298
36213
|
if (language === "python")
|
|
36299
36214
|
return "ruff";
|
|
36300
36215
|
const deps = _detectorDeps;
|
|
36301
|
-
if (await deps.fileExists(
|
|
36216
|
+
if (await deps.fileExists(join51(workdir, "biome.json")))
|
|
36302
36217
|
return "biome";
|
|
36303
|
-
if (await deps.fileExists(
|
|
36218
|
+
if (await deps.fileExists(join51(workdir, ".eslintrc")))
|
|
36304
36219
|
return "eslint";
|
|
36305
|
-
if (await deps.fileExists(
|
|
36220
|
+
if (await deps.fileExists(join51(workdir, ".eslintrc.js")))
|
|
36306
36221
|
return "eslint";
|
|
36307
|
-
if (await deps.fileExists(
|
|
36222
|
+
if (await deps.fileExists(join51(workdir, ".eslintrc.json")))
|
|
36308
36223
|
return "eslint";
|
|
36309
36224
|
return;
|
|
36310
36225
|
}
|
|
36311
36226
|
async function detectProjectProfile(workdir, existing) {
|
|
36312
|
-
const pkg = await _detectorDeps.readJson(
|
|
36227
|
+
const pkg = await _detectorDeps.readJson(join51(workdir, "package.json"));
|
|
36313
36228
|
const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
|
|
36314
36229
|
const type = existing.type !== undefined ? existing.type : detectType(pkg);
|
|
36315
36230
|
const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
|
|
@@ -36402,7 +36317,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
36402
36317
|
var init_status_file = () => {};
|
|
36403
36318
|
|
|
36404
36319
|
// src/execution/status-writer.ts
|
|
36405
|
-
import { join as
|
|
36320
|
+
import { join as join52 } from "path";
|
|
36406
36321
|
|
|
36407
36322
|
class StatusWriter {
|
|
36408
36323
|
statusFile;
|
|
@@ -36470,7 +36385,7 @@ class StatusWriter {
|
|
|
36470
36385
|
if (!this._prd)
|
|
36471
36386
|
return;
|
|
36472
36387
|
const safeLogger = getSafeLogger();
|
|
36473
|
-
const featureStatusPath =
|
|
36388
|
+
const featureStatusPath = join52(featureDir, "status.json");
|
|
36474
36389
|
try {
|
|
36475
36390
|
const base = this.getSnapshot(totalCost, iterations);
|
|
36476
36391
|
if (!base) {
|
|
@@ -36678,7 +36593,7 @@ __export(exports_run_initialization, {
|
|
|
36678
36593
|
initializeRun: () => initializeRun,
|
|
36679
36594
|
_reconcileDeps: () => _reconcileDeps
|
|
36680
36595
|
});
|
|
36681
|
-
import { join as
|
|
36596
|
+
import { join as join53 } from "path";
|
|
36682
36597
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
36683
36598
|
const logger = getSafeLogger();
|
|
36684
36599
|
let reconciledCount = 0;
|
|
@@ -36696,7 +36611,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
36696
36611
|
});
|
|
36697
36612
|
continue;
|
|
36698
36613
|
}
|
|
36699
|
-
const effectiveWorkdir = story.workdir ?
|
|
36614
|
+
const effectiveWorkdir = story.workdir ? join53(workdir, story.workdir) : workdir;
|
|
36700
36615
|
try {
|
|
36701
36616
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
36702
36617
|
if (!reviewResult.success) {
|
|
@@ -36798,7 +36713,7 @@ __export(exports_run_setup, {
|
|
|
36798
36713
|
setupRun: () => setupRun,
|
|
36799
36714
|
_runSetupDeps: () => _runSetupDeps
|
|
36800
36715
|
});
|
|
36801
|
-
import * as
|
|
36716
|
+
import * as os3 from "os";
|
|
36802
36717
|
import path18 from "path";
|
|
36803
36718
|
async function setupRun(options) {
|
|
36804
36719
|
const logger = getSafeLogger();
|
|
@@ -36895,7 +36810,7 @@ async function setupRun(options) {
|
|
|
36895
36810
|
explicit: Object.fromEntries(explicitFields.map((f) => [f, existingProjectConfig[f]])),
|
|
36896
36811
|
detected: Object.fromEntries(autodetectedFields.map((f) => [f, detectedProfile[f]]))
|
|
36897
36812
|
});
|
|
36898
|
-
const globalPluginsDir = path18.join(
|
|
36813
|
+
const globalPluginsDir = path18.join(os3.homedir(), ".nax", "plugins");
|
|
36899
36814
|
const projectPluginsDir = path18.join(workdir, ".nax", "plugins");
|
|
36900
36815
|
const configPlugins = config2.plugins || [];
|
|
36901
36816
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
@@ -67904,9 +67819,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
67904
67819
|
|
|
67905
67820
|
// bin/nax.ts
|
|
67906
67821
|
init_source();
|
|
67907
|
-
import { existsSync as
|
|
67822
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
67908
67823
|
import { homedir as homedir8 } from "os";
|
|
67909
|
-
import { join as
|
|
67824
|
+
import { join as join55 } from "path";
|
|
67910
67825
|
|
|
67911
67826
|
// node_modules/commander/esm.mjs
|
|
67912
67827
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -70462,6 +70377,7 @@ async function exportPromptCommand(options) {
|
|
|
70462
70377
|
// src/cli/init.ts
|
|
70463
70378
|
init_paths();
|
|
70464
70379
|
init_logger2();
|
|
70380
|
+
init_gitignore();
|
|
70465
70381
|
init_init_context();
|
|
70466
70382
|
// src/cli/plugins.ts
|
|
70467
70383
|
init_loader4();
|
|
@@ -70845,7 +70761,7 @@ import { existsSync as existsSync22 } from "fs";
|
|
|
70845
70761
|
import { join as join35 } from "path";
|
|
70846
70762
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
70847
70763
|
async function generateCommand(options) {
|
|
70848
|
-
const workdir = process.cwd();
|
|
70764
|
+
const workdir = options.dir ?? process.cwd();
|
|
70849
70765
|
const dryRun = options.dryRun ?? false;
|
|
70850
70766
|
let config2;
|
|
70851
70767
|
try {
|
|
@@ -72219,50 +72135,8 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72219
72135
|
if (options.useBatch) {
|
|
72220
72136
|
await tryLlmBatchRoute(options.config, readyStories, "routing");
|
|
72221
72137
|
}
|
|
72222
|
-
|
|
72223
|
-
|
|
72224
|
-
const parallelResult = await runParallelExecution2({
|
|
72225
|
-
prdPath: options.prdPath,
|
|
72226
|
-
workdir: options.workdir,
|
|
72227
|
-
config: options.config,
|
|
72228
|
-
hooks: options.hooks,
|
|
72229
|
-
feature: options.feature,
|
|
72230
|
-
featureDir: options.featureDir,
|
|
72231
|
-
parallelCount: options.parallel,
|
|
72232
|
-
eventEmitter: options.eventEmitter,
|
|
72233
|
-
statusWriter: options.statusWriter,
|
|
72234
|
-
runId: options.runId,
|
|
72235
|
-
startedAt: options.startedAt,
|
|
72236
|
-
startTime: options.startTime,
|
|
72237
|
-
totalCost,
|
|
72238
|
-
iterations,
|
|
72239
|
-
storiesCompleted,
|
|
72240
|
-
allStoryMetrics,
|
|
72241
|
-
pluginRegistry,
|
|
72242
|
-
formatterMode: options.formatterMode,
|
|
72243
|
-
headless: options.headless,
|
|
72244
|
-
agentGetFn: options.agentGetFn,
|
|
72245
|
-
pidRegistry: options.pidRegistry,
|
|
72246
|
-
interactionChain: options.interactionChain
|
|
72247
|
-
}, prd);
|
|
72248
|
-
prd = parallelResult.prd;
|
|
72249
|
-
totalCost = parallelResult.totalCost;
|
|
72250
|
-
storiesCompleted = parallelResult.storiesCompleted;
|
|
72251
|
-
allStoryMetrics.push(...parallelResult.storyMetrics);
|
|
72252
|
-
if (parallelResult.completed && parallelResult.durationMs !== undefined) {
|
|
72253
|
-
return {
|
|
72254
|
-
prd,
|
|
72255
|
-
iterations,
|
|
72256
|
-
storiesCompleted,
|
|
72257
|
-
totalCost,
|
|
72258
|
-
allStoryMetrics,
|
|
72259
|
-
completedEarly: true,
|
|
72260
|
-
durationMs: parallelResult.durationMs
|
|
72261
|
-
};
|
|
72262
|
-
}
|
|
72263
|
-
}
|
|
72264
|
-
const { executeSequential: executeSequential2 } = await Promise.resolve().then(() => (init_sequential_executor(), exports_sequential_executor));
|
|
72265
|
-
const sequentialResult = await executeSequential2({
|
|
72138
|
+
const { executeUnified: executeUnified2 } = await Promise.resolve().then(() => (init_unified_executor(), exports_unified_executor));
|
|
72139
|
+
const unifiedResult = await executeUnified2({
|
|
72266
72140
|
prdPath: options.prdPath,
|
|
72267
72141
|
workdir: options.workdir,
|
|
72268
72142
|
config: options.config,
|
|
@@ -72277,23 +72151,18 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72277
72151
|
logFilePath: options.logFilePath,
|
|
72278
72152
|
runId: options.runId,
|
|
72279
72153
|
startTime: options.startTime,
|
|
72280
|
-
|
|
72154
|
+
parallelCount: options.parallel,
|
|
72281
72155
|
agentGetFn: options.agentGetFn,
|
|
72282
72156
|
pidRegistry: options.pidRegistry,
|
|
72283
|
-
interactionChain: options.interactionChain
|
|
72157
|
+
interactionChain: options.interactionChain,
|
|
72158
|
+
batchPlan
|
|
72284
72159
|
}, prd);
|
|
72285
|
-
prd =
|
|
72286
|
-
iterations =
|
|
72287
|
-
|
|
72288
|
-
|
|
72289
|
-
allStoryMetrics.push(...
|
|
72290
|
-
return {
|
|
72291
|
-
prd,
|
|
72292
|
-
iterations,
|
|
72293
|
-
storiesCompleted,
|
|
72294
|
-
totalCost,
|
|
72295
|
-
allStoryMetrics
|
|
72296
|
-
};
|
|
72160
|
+
prd = unifiedResult.prd;
|
|
72161
|
+
iterations = unifiedResult.iterations;
|
|
72162
|
+
storiesCompleted = unifiedResult.storiesCompleted;
|
|
72163
|
+
totalCost = unifiedResult.totalCost;
|
|
72164
|
+
allStoryMetrics.push(...unifiedResult.allStoryMetrics);
|
|
72165
|
+
return { prd, iterations, storiesCompleted, totalCost, allStoryMetrics };
|
|
72297
72166
|
}
|
|
72298
72167
|
|
|
72299
72168
|
// src/execution/runner-setup.ts
|
|
@@ -72327,10 +72196,6 @@ async function runSetupPhase(options) {
|
|
|
72327
72196
|
// src/execution/runner.ts
|
|
72328
72197
|
init_escalation();
|
|
72329
72198
|
init_escalation();
|
|
72330
|
-
var _runnerDeps = {
|
|
72331
|
-
fireHook,
|
|
72332
|
-
runParallelExecution: null
|
|
72333
|
-
};
|
|
72334
72199
|
async function run(options) {
|
|
72335
72200
|
const {
|
|
72336
72201
|
prdPath,
|
|
@@ -72404,7 +72269,6 @@ async function run(options) {
|
|
|
72404
72269
|
formatterMode,
|
|
72405
72270
|
headless,
|
|
72406
72271
|
parallel,
|
|
72407
|
-
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
|
|
72408
72272
|
agentGetFn,
|
|
72409
72273
|
pidRegistry,
|
|
72410
72274
|
interactionChain
|
|
@@ -72660,7 +72524,7 @@ __export(exports_base, {
|
|
|
72660
72524
|
ConEmu: () => ConEmu
|
|
72661
72525
|
});
|
|
72662
72526
|
import process4 from "process";
|
|
72663
|
-
import
|
|
72527
|
+
import os4 from "os";
|
|
72664
72528
|
|
|
72665
72529
|
// node_modules/environment/index.js
|
|
72666
72530
|
var isBrowser = globalThis.window?.document !== undefined;
|
|
@@ -72759,7 +72623,7 @@ var isOldWindows = () => {
|
|
|
72759
72623
|
if (isBrowser || !isWindows2) {
|
|
72760
72624
|
return false;
|
|
72761
72625
|
}
|
|
72762
|
-
const parts =
|
|
72626
|
+
const parts = os4.release().split(".");
|
|
72763
72627
|
const major = Number(parts[0]);
|
|
72764
72628
|
const build = Number(parts[2] ?? 0);
|
|
72765
72629
|
if (major < 10) {
|
|
@@ -79838,15 +79702,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
79838
79702
|
}
|
|
79839
79703
|
return;
|
|
79840
79704
|
}
|
|
79841
|
-
const naxDir =
|
|
79842
|
-
if (
|
|
79705
|
+
const naxDir = join55(workdir, ".nax");
|
|
79706
|
+
if (existsSync34(naxDir) && !options.force) {
|
|
79843
79707
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
79844
79708
|
return;
|
|
79845
79709
|
}
|
|
79846
|
-
mkdirSync6(
|
|
79847
|
-
mkdirSync6(
|
|
79848
|
-
await Bun.write(
|
|
79849
|
-
await Bun.write(
|
|
79710
|
+
mkdirSync6(join55(naxDir, "features"), { recursive: true });
|
|
79711
|
+
mkdirSync6(join55(naxDir, "hooks"), { recursive: true });
|
|
79712
|
+
await Bun.write(join55(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
79713
|
+
await Bun.write(join55(naxDir, "hooks.json"), JSON.stringify({
|
|
79850
79714
|
hooks: {
|
|
79851
79715
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
79852
79716
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -79854,12 +79718,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
79854
79718
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
79855
79719
|
}
|
|
79856
79720
|
}, null, 2));
|
|
79857
|
-
await Bun.write(
|
|
79721
|
+
await Bun.write(join55(naxDir, ".gitignore"), `# nax temp files
|
|
79858
79722
|
*.tmp
|
|
79859
79723
|
.paused.json
|
|
79860
79724
|
.nax-verifier-verdict.json
|
|
79861
79725
|
`);
|
|
79862
|
-
await Bun.write(
|
|
79726
|
+
await Bun.write(join55(naxDir, "context.md"), `# Project Context
|
|
79863
79727
|
|
|
79864
79728
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
79865
79729
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -79956,7 +79820,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79956
79820
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
79957
79821
|
process.exit(1);
|
|
79958
79822
|
}
|
|
79959
|
-
if (options.from && !
|
|
79823
|
+
if (options.from && !existsSync34(options.from)) {
|
|
79960
79824
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
79961
79825
|
process.exit(1);
|
|
79962
79826
|
}
|
|
@@ -79985,10 +79849,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79985
79849
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
79986
79850
|
process.exit(1);
|
|
79987
79851
|
}
|
|
79988
|
-
const featureDir =
|
|
79989
|
-
const prdPath =
|
|
79852
|
+
const featureDir = join55(naxDir, "features", options.feature);
|
|
79853
|
+
const prdPath = join55(featureDir, "prd.json");
|
|
79990
79854
|
if (options.plan && options.from) {
|
|
79991
|
-
if (
|
|
79855
|
+
if (existsSync34(prdPath) && !options.force) {
|
|
79992
79856
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
79993
79857
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
79994
79858
|
process.exit(1);
|
|
@@ -80008,10 +79872,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80008
79872
|
}
|
|
80009
79873
|
}
|
|
80010
79874
|
try {
|
|
80011
|
-
const planLogDir =
|
|
79875
|
+
const planLogDir = join55(featureDir, "plan");
|
|
80012
79876
|
mkdirSync6(planLogDir, { recursive: true });
|
|
80013
79877
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
80014
|
-
const planLogPath =
|
|
79878
|
+
const planLogPath = join55(planLogDir, `${planLogId}.jsonl`);
|
|
80015
79879
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
80016
79880
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
80017
79881
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -80044,15 +79908,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80044
79908
|
process.exit(1);
|
|
80045
79909
|
}
|
|
80046
79910
|
}
|
|
80047
|
-
if (!
|
|
79911
|
+
if (!existsSync34(prdPath)) {
|
|
80048
79912
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
80049
79913
|
process.exit(1);
|
|
80050
79914
|
}
|
|
80051
79915
|
resetLogger();
|
|
80052
|
-
const runsDir =
|
|
79916
|
+
const runsDir = join55(featureDir, "runs");
|
|
80053
79917
|
mkdirSync6(runsDir, { recursive: true });
|
|
80054
79918
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
80055
|
-
const logFilePath =
|
|
79919
|
+
const logFilePath = join55(runsDir, `${runId}.jsonl`);
|
|
80056
79920
|
const isTTY = process.stdout.isTTY ?? false;
|
|
80057
79921
|
const headlessFlag = options.headless ?? false;
|
|
80058
79922
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -80068,7 +79932,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80068
79932
|
config2.autoMode.defaultAgent = options.agent;
|
|
80069
79933
|
}
|
|
80070
79934
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
80071
|
-
const globalNaxDir =
|
|
79935
|
+
const globalNaxDir = join55(homedir8(), ".nax");
|
|
80072
79936
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
80073
79937
|
const eventEmitter = new PipelineEventEmitter;
|
|
80074
79938
|
let tuiInstance;
|
|
@@ -80091,7 +79955,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80091
79955
|
} else {
|
|
80092
79956
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
80093
79957
|
}
|
|
80094
|
-
const statusFilePath =
|
|
79958
|
+
const statusFilePath = join55(workdir, ".nax", "status.json");
|
|
80095
79959
|
let parallel;
|
|
80096
79960
|
if (options.parallel !== undefined) {
|
|
80097
79961
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -80117,9 +79981,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80117
79981
|
headless: useHeadless,
|
|
80118
79982
|
skipPrecheck: options.skipPrecheck ?? false
|
|
80119
79983
|
});
|
|
80120
|
-
const latestSymlink =
|
|
79984
|
+
const latestSymlink = join55(runsDir, "latest.jsonl");
|
|
80121
79985
|
try {
|
|
80122
|
-
if (
|
|
79986
|
+
if (existsSync34(latestSymlink)) {
|
|
80123
79987
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
80124
79988
|
}
|
|
80125
79989
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -80155,9 +80019,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
80155
80019
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
80156
80020
|
process.exit(1);
|
|
80157
80021
|
}
|
|
80158
|
-
const featureDir =
|
|
80022
|
+
const featureDir = join55(naxDir, "features", name);
|
|
80159
80023
|
mkdirSync6(featureDir, { recursive: true });
|
|
80160
|
-
await Bun.write(
|
|
80024
|
+
await Bun.write(join55(featureDir, "spec.md"), `# Feature: ${name}
|
|
80161
80025
|
|
|
80162
80026
|
## Overview
|
|
80163
80027
|
|
|
@@ -80190,7 +80054,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
80190
80054
|
|
|
80191
80055
|
<!-- What this feature explicitly does NOT cover. -->
|
|
80192
80056
|
`);
|
|
80193
|
-
await Bun.write(
|
|
80057
|
+
await Bun.write(join55(featureDir, "progress.txt"), `# Progress: ${name}
|
|
80194
80058
|
|
|
80195
80059
|
Created: ${new Date().toISOString()}
|
|
80196
80060
|
|
|
@@ -80216,8 +80080,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
80216
80080
|
console.error(source_default.red("nax not initialized."));
|
|
80217
80081
|
process.exit(1);
|
|
80218
80082
|
}
|
|
80219
|
-
const featuresDir =
|
|
80220
|
-
if (!
|
|
80083
|
+
const featuresDir = join55(naxDir, "features");
|
|
80084
|
+
if (!existsSync34(featuresDir)) {
|
|
80221
80085
|
console.log(source_default.dim("No features yet."));
|
|
80222
80086
|
return;
|
|
80223
80087
|
}
|
|
@@ -80231,8 +80095,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
80231
80095
|
Features:
|
|
80232
80096
|
`));
|
|
80233
80097
|
for (const name of entries) {
|
|
80234
|
-
const prdPath =
|
|
80235
|
-
if (
|
|
80098
|
+
const prdPath = join55(featuresDir, name, "prd.json");
|
|
80099
|
+
if (existsSync34(prdPath)) {
|
|
80236
80100
|
const prd = await loadPRD(prdPath);
|
|
80237
80101
|
const c = countStories(prd);
|
|
80238
80102
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -80262,10 +80126,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
80262
80126
|
process.exit(1);
|
|
80263
80127
|
}
|
|
80264
80128
|
const config2 = await loadConfig(workdir);
|
|
80265
|
-
const featureLogDir =
|
|
80129
|
+
const featureLogDir = join55(naxDir, "features", options.feature, "plan");
|
|
80266
80130
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
80267
80131
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
80268
|
-
const planLogPath =
|
|
80132
|
+
const planLogPath = join55(featureLogDir, `${planLogId}.jsonl`);
|
|
80269
80133
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
80270
80134
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
80271
80135
|
try {
|
|
@@ -80302,8 +80166,8 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
80302
80166
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
80303
80167
|
process.exit(1);
|
|
80304
80168
|
}
|
|
80305
|
-
const featureDir =
|
|
80306
|
-
if (!
|
|
80169
|
+
const featureDir = join55(naxDir, "features", options.feature);
|
|
80170
|
+
if (!existsSync34(featureDir)) {
|
|
80307
80171
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
80308
80172
|
process.exit(1);
|
|
80309
80173
|
}
|
|
@@ -80318,7 +80182,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
80318
80182
|
specPath: options.from,
|
|
80319
80183
|
reclassify: options.reclassify
|
|
80320
80184
|
});
|
|
80321
|
-
const prdPath =
|
|
80185
|
+
const prdPath = join55(featureDir, "prd.json");
|
|
80322
80186
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
80323
80187
|
const c = countStories(prd);
|
|
80324
80188
|
console.log(source_default.green(`
|
|
@@ -80351,9 +80215,17 @@ program2.command("agents").description("List available coding agents with status
|
|
|
80351
80215
|
process.exit(1);
|
|
80352
80216
|
}
|
|
80353
80217
|
});
|
|
80354
|
-
program2.command("config").description("Display effective merged configuration").option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
|
|
80218
|
+
program2.command("config").description("Display effective merged configuration").option("-d, --dir <path>", "Project directory", process.cwd()).option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
|
|
80219
|
+
let workdir;
|
|
80220
|
+
try {
|
|
80221
|
+
workdir = validateDirectory(options.dir);
|
|
80222
|
+
} catch (err) {
|
|
80223
|
+
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
80224
|
+
process.exit(1);
|
|
80225
|
+
return;
|
|
80226
|
+
}
|
|
80355
80227
|
try {
|
|
80356
|
-
const config2 = await loadConfig();
|
|
80228
|
+
const config2 = await loadConfig(workdir);
|
|
80357
80229
|
await configCommand(config2, { explain: options.explain, diff: options.diff });
|
|
80358
80230
|
} catch (err) {
|
|
80359
80231
|
console.error(source_default.red(`Error: ${err.message}`));
|
|
@@ -80547,9 +80419,18 @@ program2.command("prompts").description("Assemble or initialize prompts").option
|
|
|
80547
80419
|
process.exit(1);
|
|
80548
80420
|
}
|
|
80549
80421
|
});
|
|
80550
|
-
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
|
|
80422
|
+
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-d, --dir <path>", "Project directory", process.cwd()).option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
|
|
80423
|
+
let workdir;
|
|
80424
|
+
try {
|
|
80425
|
+
workdir = validateDirectory(options.dir);
|
|
80426
|
+
} catch (err) {
|
|
80427
|
+
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
80428
|
+
process.exit(1);
|
|
80429
|
+
return;
|
|
80430
|
+
}
|
|
80551
80431
|
try {
|
|
80552
80432
|
await generateCommand({
|
|
80433
|
+
dir: workdir,
|
|
80553
80434
|
context: options.context,
|
|
80554
80435
|
output: options.output,
|
|
80555
80436
|
agent: options.agent,
|