@nathapp/nax 0.70.0-canary.4 → 0.70.0-canary.6
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 +377 -161
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -16828,7 +16828,8 @@ var init_schemas_execution = __esm(() => {
|
|
|
16828
16828
|
escalation: exports_external.object({
|
|
16829
16829
|
enabled: exports_external.boolean(),
|
|
16830
16830
|
tierOrder: exports_external.array(TierConfigSchema).min(1, { message: "tierOrder must have at least one tier" }),
|
|
16831
|
-
escalateEntireBatch: exports_external.boolean().optional()
|
|
16831
|
+
escalateEntireBatch: exports_external.boolean().optional(),
|
|
16832
|
+
resetMode: exports_external.enum(["initial", "last"]).default("initial")
|
|
16832
16833
|
})
|
|
16833
16834
|
});
|
|
16834
16835
|
RectificationConfigSchema = exports_external.object({
|
|
@@ -16852,11 +16853,13 @@ var init_schemas_execution = __esm(() => {
|
|
|
16852
16853
|
SmartTestRunnerConfigSchema = exports_external.object({
|
|
16853
16854
|
enabled: exports_external.boolean().default(true),
|
|
16854
16855
|
testFilePatterns: exports_external.array(exports_external.string()).optional(),
|
|
16855
|
-
fallback: exports_external.enum(["import-grep", "full-suite"]).default("import-grep")
|
|
16856
|
+
fallback: exports_external.enum(["import-grep", "full-suite"]).default("import-grep"),
|
|
16857
|
+
maxScanFiles: exports_external.number().int().min(1).max(5000).default(200)
|
|
16856
16858
|
});
|
|
16857
16859
|
SMART_TEST_RUNNER_DEFAULT = {
|
|
16858
16860
|
enabled: true,
|
|
16859
|
-
fallback: "import-grep"
|
|
16861
|
+
fallback: "import-grep",
|
|
16862
|
+
maxScanFiles: 200
|
|
16860
16863
|
};
|
|
16861
16864
|
smartTestRunnerFieldSchema = exports_external.preprocess((val) => {
|
|
16862
16865
|
if (typeof val === "boolean") {
|
|
@@ -16870,7 +16873,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
16870
16873
|
}).superRefine((value, ctx) => {
|
|
16871
16874
|
if (value.mode !== "provision" && value.setupCommand !== null) {
|
|
16872
16875
|
ctx.addIssue({
|
|
16873
|
-
code:
|
|
16876
|
+
code: "custom",
|
|
16874
16877
|
path: ["setupCommand"],
|
|
16875
16878
|
message: "execution.worktreeDependencies.setupCommand requires mode 'provision'"
|
|
16876
16879
|
});
|
|
@@ -17352,7 +17355,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17352
17355
|
{ tier: "balanced", attempts: 3 },
|
|
17353
17356
|
{ tier: "powerful", attempts: 2 }
|
|
17354
17357
|
],
|
|
17355
|
-
escalateEntireBatch: true
|
|
17358
|
+
escalateEntireBatch: true,
|
|
17359
|
+
resetMode: "initial"
|
|
17356
17360
|
}
|
|
17357
17361
|
}),
|
|
17358
17362
|
routing: RoutingConfigSchema.default({
|
|
@@ -17665,7 +17669,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17665
17669
|
}
|
|
17666
17670
|
})),
|
|
17667
17671
|
curator: CuratorConfigSchema.optional(),
|
|
17668
|
-
profile: exports_external.string().default("default")
|
|
17672
|
+
profile: exports_external.string().default("default"),
|
|
17673
|
+
profileChain: exports_external.array(exports_external.string()).default([])
|
|
17669
17674
|
}).refine((data) => data.version === 1, {
|
|
17670
17675
|
message: "Invalid version: expected 1",
|
|
17671
17676
|
path: ["version"]
|
|
@@ -18728,7 +18733,11 @@ async function loadProfile(profileName, projectRoot) {
|
|
|
18728
18733
|
if (!globalExists && !projectExists) {
|
|
18729
18734
|
const available = await listAvailableProfileNames(projectRoot);
|
|
18730
18735
|
const availableList = available.length > 0 ? available.join(", ") : "(none)";
|
|
18731
|
-
throw new
|
|
18736
|
+
throw new NaxError(`Profile "${profileName}" not found. Available: ${availableList}`, "PROFILE_NOT_FOUND", {
|
|
18737
|
+
stage: "config",
|
|
18738
|
+
profileName,
|
|
18739
|
+
available
|
|
18740
|
+
});
|
|
18732
18741
|
}
|
|
18733
18742
|
let base = {};
|
|
18734
18743
|
if (globalExists) {
|
|
@@ -18760,30 +18769,59 @@ async function loadProfileEnv(profileName, projectRoot) {
|
|
|
18760
18769
|
}
|
|
18761
18770
|
return merged;
|
|
18762
18771
|
}
|
|
18763
|
-
|
|
18764
|
-
if (
|
|
18765
|
-
return
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
return config2.profile;
|
|
18772
|
+
function parseProfileList(input) {
|
|
18773
|
+
if (input == null)
|
|
18774
|
+
return [];
|
|
18775
|
+
const parts = Array.isArray(input) ? input : [input];
|
|
18776
|
+
const out = [];
|
|
18777
|
+
for (const part of parts) {
|
|
18778
|
+
if (typeof part !== "string")
|
|
18779
|
+
continue;
|
|
18780
|
+
for (const segment of part.split(",")) {
|
|
18781
|
+
const trimmed = segment.trim();
|
|
18782
|
+
if (trimmed)
|
|
18783
|
+
out.push(trimmed);
|
|
18776
18784
|
}
|
|
18777
18785
|
}
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18786
|
+
return out;
|
|
18787
|
+
}
|
|
18788
|
+
function profileOverrideFromConfig(config2) {
|
|
18789
|
+
if (config2.profileChain && config2.profileChain.length > 0) {
|
|
18790
|
+
return { profile: config2.profileChain };
|
|
18791
|
+
}
|
|
18792
|
+
if (config2.profile && config2.profile !== "default") {
|
|
18793
|
+
return { profile: [config2.profile] };
|
|
18785
18794
|
}
|
|
18786
|
-
return
|
|
18795
|
+
return;
|
|
18796
|
+
}
|
|
18797
|
+
async function readProfileChainFromConfig(dir) {
|
|
18798
|
+
const configFile = Bun.file(join2(dir, "config.json"));
|
|
18799
|
+
if (!await configFile.exists())
|
|
18800
|
+
return [];
|
|
18801
|
+
const config2 = await configFile.json();
|
|
18802
|
+
return parseProfileList(config2.profile);
|
|
18803
|
+
}
|
|
18804
|
+
function isDefaultOnlyChain(chain) {
|
|
18805
|
+
return chain.length === 0 || chain.length === 1 && chain[0] === "default";
|
|
18806
|
+
}
|
|
18807
|
+
async function resolveProfileNames(cliOptions, env2, projectRoot) {
|
|
18808
|
+
const fromCli = parseProfileList(cliOptions.profile);
|
|
18809
|
+
if (fromCli.length)
|
|
18810
|
+
return fromCli;
|
|
18811
|
+
const fromEnv = parseProfileList(env2.NAX_PROFILE);
|
|
18812
|
+
if (fromEnv.length)
|
|
18813
|
+
return fromEnv;
|
|
18814
|
+
const projectChain = await readProfileChainFromConfig(projectConfigDir(projectRoot));
|
|
18815
|
+
if (!isDefaultOnlyChain(projectChain))
|
|
18816
|
+
return projectChain;
|
|
18817
|
+
const globalChain = await readProfileChainFromConfig(globalConfigDir());
|
|
18818
|
+
if (!isDefaultOnlyChain(globalChain))
|
|
18819
|
+
return globalChain;
|
|
18820
|
+
return ["default"];
|
|
18821
|
+
}
|
|
18822
|
+
async function resolveProfileName(cliOptions, env2, projectRoot) {
|
|
18823
|
+
const chain = await resolveProfileNames(cliOptions, env2, projectRoot);
|
|
18824
|
+
return chain[chain.length - 1] ?? "default";
|
|
18787
18825
|
}
|
|
18788
18826
|
async function listAvailableProfileNames(projectRoot) {
|
|
18789
18827
|
const entries = await listProfiles(projectRoot);
|
|
@@ -18811,6 +18849,7 @@ async function listProfiles(projectRoot) {
|
|
|
18811
18849
|
return entries;
|
|
18812
18850
|
}
|
|
18813
18851
|
var init_profile = __esm(() => {
|
|
18852
|
+
init_errors();
|
|
18814
18853
|
init_paths();
|
|
18815
18854
|
});
|
|
18816
18855
|
|
|
@@ -18993,7 +19032,8 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
18993
19032
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
18994
19033
|
const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
18995
19034
|
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname(startDir) : startDir : process.cwd();
|
|
18996
|
-
const
|
|
19035
|
+
const profileChain = await resolveProfileNames(cliOverrides ?? {}, process.env, projectRoot);
|
|
19036
|
+
const overlayChain = profileChain.filter((name) => name && name !== "default");
|
|
18997
19037
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
18998
19038
|
let logger = null;
|
|
18999
19039
|
try {
|
|
@@ -19012,16 +19052,17 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
19012
19052
|
rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
19013
19053
|
}
|
|
19014
19054
|
}
|
|
19015
|
-
|
|
19016
|
-
const profileData = await loadProfile(
|
|
19055
|
+
for (const name of overlayChain) {
|
|
19056
|
+
const profileData = await loadProfile(name, projectRoot);
|
|
19017
19057
|
rawConfig = deepMergeConfig(rawConfig, profileData);
|
|
19018
|
-
await loadProfileEnv(
|
|
19058
|
+
await loadProfileEnv(name, projectRoot);
|
|
19019
19059
|
}
|
|
19020
19060
|
if (cliOverrides) {
|
|
19021
19061
|
rawConfig = deepMergeConfig(rawConfig, cliOverrides);
|
|
19022
19062
|
}
|
|
19023
|
-
rawConfig.profile =
|
|
19024
|
-
|
|
19063
|
+
rawConfig.profile = overlayChain.length > 0 ? overlayChain.join("+") : "default";
|
|
19064
|
+
rawConfig.profileChain = overlayChain;
|
|
19065
|
+
const hasMergedConfigs = globalConfRaw || projDir !== null || cliOverrides !== undefined || overlayChain.length > 0;
|
|
19025
19066
|
if (!hasMergedConfigs) {
|
|
19026
19067
|
return structuredClone(DEFAULT_CONFIG);
|
|
19027
19068
|
}
|
|
@@ -19051,11 +19092,14 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19051
19092
|
const logger = getLogger();
|
|
19052
19093
|
const resolvedRootConfigPath = resolve3(rootConfigPath);
|
|
19053
19094
|
const rootNaxDir = dirname(resolvedRootConfigPath);
|
|
19054
|
-
const profileKey = cliOverrides?.profile
|
|
19095
|
+
const profileKey = parseProfileList(cliOverrides?.profile).join(",");
|
|
19055
19096
|
const cacheKey = profileKey ? `${resolvedRootConfigPath}:${profileKey}` : resolvedRootConfigPath;
|
|
19056
19097
|
let rootConfigPromise = _rootConfigCache.get(cacheKey);
|
|
19057
19098
|
if (!rootConfigPromise) {
|
|
19058
|
-
rootConfigPromise = loadConfig(rootNaxDir, cliOverrides)
|
|
19099
|
+
rootConfigPromise = loadConfig(rootNaxDir, cliOverrides).catch((err) => {
|
|
19100
|
+
_rootConfigCache.delete(cacheKey);
|
|
19101
|
+
throw err;
|
|
19102
|
+
});
|
|
19059
19103
|
if (_rootConfigCache.size >= ROOT_CONFIG_CACHE_MAX) {
|
|
19060
19104
|
const firstKey = _rootConfigCache.keys().next().value;
|
|
19061
19105
|
if (firstKey !== undefined)
|
|
@@ -19081,22 +19125,29 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19081
19125
|
logger.debug("config", "Per-package config loaded", { packageConfigPath, packageDir });
|
|
19082
19126
|
const { profile: packageProfile, ...packageFields } = packageOverride;
|
|
19083
19127
|
let merged = mergePackageConfig(rootConfig, packageFields);
|
|
19084
|
-
|
|
19128
|
+
const packageChain = parseProfileList(packageProfile).filter((name) => name && name !== "default");
|
|
19129
|
+
if (packageChain.length > 0) {
|
|
19085
19130
|
const packageRoot = join3(repoRoot, packageDir);
|
|
19086
|
-
|
|
19087
|
-
const
|
|
19088
|
-
|
|
19131
|
+
let rawMerged = merged;
|
|
19132
|
+
for (const name of packageChain) {
|
|
19133
|
+
const profileData = await loadProfile(name, packageRoot);
|
|
19134
|
+
rawMerged = deepMergeConfig(rawMerged, profileData);
|
|
19135
|
+
}
|
|
19136
|
+
rawMerged.profile = packageChain.join("+");
|
|
19137
|
+
rawMerged.profileChain = packageChain;
|
|
19089
19138
|
rejectLegacyAgentKeys(rawMerged);
|
|
19090
19139
|
rejectLegacyRectificationKeys(rawMerged);
|
|
19091
19140
|
const result = NaxConfigSchema.safeParse(rawMerged);
|
|
19092
|
-
if (result.success) {
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
packageDir,
|
|
19097
|
-
packageProfile
|
|
19141
|
+
if (!result.success) {
|
|
19142
|
+
const errors3 = result.error.issues.map((err) => {
|
|
19143
|
+
const path = String(err.path.join("."));
|
|
19144
|
+
return path ? `${path}: ${err.message}` : err.message;
|
|
19098
19145
|
});
|
|
19146
|
+
throw new NaxError(`Per-package profile "${packageChain.join("+")}" produced an invalid config for package "${packageDir}":
|
|
19147
|
+
${errors3.join(`
|
|
19148
|
+
`)}`, "PER_PACKAGE_PROFILE_INVALID", { stage: "config", packageDir, profileChain: packageChain });
|
|
19099
19149
|
}
|
|
19150
|
+
merged = result.data;
|
|
19100
19151
|
}
|
|
19101
19152
|
return merged;
|
|
19102
19153
|
}
|
|
@@ -19504,6 +19555,7 @@ __export(exports_config, {
|
|
|
19504
19555
|
routingConfigSelector: () => routingConfigSelector,
|
|
19505
19556
|
reviewConfigSelector: () => reviewConfigSelector,
|
|
19506
19557
|
resolveTestStrategy: () => resolveTestStrategy,
|
|
19558
|
+
resolveProfileNames: () => resolveProfileNames,
|
|
19507
19559
|
resolveProfileName: () => resolveProfileName,
|
|
19508
19560
|
resolveModelForAgent: () => resolveModelForAgent,
|
|
19509
19561
|
resolveModel: () => resolveModel,
|
|
@@ -19514,9 +19566,11 @@ __export(exports_config, {
|
|
|
19514
19566
|
qualityConfigSelector: () => qualityConfigSelector,
|
|
19515
19567
|
promptLoaderConfigSelector: () => promptLoaderConfigSelector,
|
|
19516
19568
|
projectConfigDir: () => projectConfigDir,
|
|
19569
|
+
profileOverrideFromConfig: () => profileOverrideFromConfig,
|
|
19517
19570
|
precheckConfigSelector: () => precheckConfigSelector,
|
|
19518
19571
|
planConfigSelector: () => planConfigSelector,
|
|
19519
19572
|
pickSelector: () => pickSelector,
|
|
19573
|
+
parseProfileList: () => parseProfileList,
|
|
19520
19574
|
mergePackageConfig: () => mergePackageConfig,
|
|
19521
19575
|
loadProfileEnv: () => loadProfileEnv,
|
|
19522
19576
|
loadProfile: () => loadProfile,
|
|
@@ -21286,12 +21340,13 @@ function parseAcpxJsonLine(line, state) {
|
|
|
21286
21340
|
}
|
|
21287
21341
|
return;
|
|
21288
21342
|
}
|
|
21289
|
-
if (event.
|
|
21343
|
+
if (event.result && typeof event.result === "string") {
|
|
21344
|
+
state.text = event.result;
|
|
21345
|
+
} else if (event.content && typeof event.content === "string") {
|
|
21290
21346
|
state.text += event.content;
|
|
21291
|
-
if (event.text && typeof event.text === "string")
|
|
21347
|
+
} else if (event.text && typeof event.text === "string") {
|
|
21292
21348
|
state.text += event.text;
|
|
21293
|
-
|
|
21294
|
-
state.text = event.result;
|
|
21349
|
+
}
|
|
21295
21350
|
if (event.cumulative_token_usage)
|
|
21296
21351
|
state.tokenUsage = event.cumulative_token_usage;
|
|
21297
21352
|
if (event.usage) {
|
|
@@ -21626,7 +21681,11 @@ class AgentManager {
|
|
|
21626
21681
|
_registry;
|
|
21627
21682
|
_unavailable = new Map;
|
|
21628
21683
|
_prunedFallback = new Set;
|
|
21629
|
-
_emitter =
|
|
21684
|
+
_emitter = (() => {
|
|
21685
|
+
const ee = new EventEmitter;
|
|
21686
|
+
ee.setMaxListeners(MAX_EMITTER_LISTENERS);
|
|
21687
|
+
return ee;
|
|
21688
|
+
})();
|
|
21630
21689
|
_logger;
|
|
21631
21690
|
_middleware;
|
|
21632
21691
|
_runId;
|
|
@@ -22225,6 +22284,9 @@ class AgentManager {
|
|
|
22225
22284
|
throw err;
|
|
22226
22285
|
}
|
|
22227
22286
|
}
|
|
22287
|
+
close() {
|
|
22288
|
+
this._emitter.removeAllListeners();
|
|
22289
|
+
}
|
|
22228
22290
|
_resolveRegistry() {
|
|
22229
22291
|
this._registry ??= createAgentRegistry(this._config);
|
|
22230
22292
|
return this._registry;
|
|
@@ -22233,7 +22295,7 @@ class AgentManager {
|
|
|
22233
22295
|
this._emitter.emit(event, payload);
|
|
22234
22296
|
}
|
|
22235
22297
|
}
|
|
22236
|
-
var _agentManagerDeps;
|
|
22298
|
+
var MAX_EMITTER_LISTENERS = 100, _agentManagerDeps;
|
|
22237
22299
|
var init_manager = __esm(() => {
|
|
22238
22300
|
init_errors();
|
|
22239
22301
|
init_logger2();
|
|
@@ -25144,7 +25206,7 @@ function extractSearchTerms(sourceFile) {
|
|
|
25144
25206
|
const basename4 = parts[parts.length - 1];
|
|
25145
25207
|
return [`/${basename4}`, withoutExt];
|
|
25146
25208
|
}
|
|
25147
|
-
async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
25209
|
+
async function importGrepFallback(sourceFiles, workdir, testFilePatterns, maxScanFiles = MAX_GREP_TEST_FILES) {
|
|
25148
25210
|
if (sourceFiles.length === 0 || testFilePatterns.length === 0)
|
|
25149
25211
|
return [];
|
|
25150
25212
|
const searchTerms = sourceFiles.flatMap(extractSearchTerms);
|
|
@@ -25152,11 +25214,11 @@ async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
|
25152
25214
|
outer:
|
|
25153
25215
|
for (const pattern of testFilePatterns) {
|
|
25154
25216
|
const g = _bunDeps.glob(pattern);
|
|
25155
|
-
for await (const file3 of g.scan(workdir)) {
|
|
25217
|
+
for await (const file3 of g.scan({ cwd: workdir, absolute: false })) {
|
|
25156
25218
|
testFilePaths.push(`${workdir}/${file3}`);
|
|
25157
|
-
if (testFilePaths.length >=
|
|
25219
|
+
if (testFilePaths.length >= maxScanFiles) {
|
|
25158
25220
|
getSafeLogger()?.debug("smart-runner", "import-grep glob cap reached \u2014 results truncated", {
|
|
25159
|
-
cap:
|
|
25221
|
+
cap: maxScanFiles
|
|
25160
25222
|
});
|
|
25161
25223
|
break outer;
|
|
25162
25224
|
}
|
|
@@ -25404,7 +25466,7 @@ async function selectScopedTests(input) {
|
|
|
25404
25466
|
if (smartCfg.fallback !== "import-grep") {
|
|
25405
25467
|
return fullSuite();
|
|
25406
25468
|
}
|
|
25407
|
-
const pass2Files = await _scopedSelectionDeps.importGrepFallback(nonTestFiles, input.workdir, mappingGlobs);
|
|
25469
|
+
const pass2Files = await _scopedSelectionDeps.importGrepFallback(nonTestFiles, input.workdir, mappingGlobs, smartCfg.maxScanFiles);
|
|
25408
25470
|
if (pass2Files.length > threshold) {
|
|
25409
25471
|
logger.warn("verify[scoped]", `Scoped test file count ${pass2Files.length} exceeds threshold ${threshold} \u2014 falling back to full suite`, { storyId: input.storyId });
|
|
25410
25472
|
return fullSuite({ scopeTestFallback: true, thresholdFallback: true });
|
|
@@ -25425,7 +25487,8 @@ var init_scoped_selection = __esm(() => {
|
|
|
25425
25487
|
DEFAULT_SMART_RUNNER_CONFIG = {
|
|
25426
25488
|
enabled: true,
|
|
25427
25489
|
testFilePatterns: [...DEFAULT_TEST_FILE_PATTERNS],
|
|
25428
|
-
fallback: "import-grep"
|
|
25490
|
+
fallback: "import-grep",
|
|
25491
|
+
maxScanFiles: MAX_GREP_TEST_FILES
|
|
25429
25492
|
};
|
|
25430
25493
|
_scopedSelectionDeps = {
|
|
25431
25494
|
getChangedNonTestFiles: _smartRunnerDeps.getChangedNonTestFiles,
|
|
@@ -27573,7 +27636,7 @@ class SessionScratchProvider {
|
|
|
27573
27636
|
if (!dirs || dirs.length === 0) {
|
|
27574
27637
|
return { chunks: [], pullTools: [] };
|
|
27575
27638
|
}
|
|
27576
|
-
const ignoreMatchers = await resolveNaxIgnorePatterns(request.repoRoot, request.packageDir);
|
|
27639
|
+
const ignoreMatchers = request.naxIgnoreIndex?.getMatchers(request.packageDir) ?? await resolveNaxIgnorePatterns(request.repoRoot, request.packageDir);
|
|
27577
27640
|
const chunks = [];
|
|
27578
27641
|
for (const dir of dirs) {
|
|
27579
27642
|
const chunk = await readScratchDir(dir, request.agentId, ignoreMatchers);
|
|
@@ -27895,7 +27958,8 @@ var init_static_rules = __esm(() => {
|
|
|
27895
27958
|
|
|
27896
27959
|
// src/prd/types.ts
|
|
27897
27960
|
function getContextFiles(story) {
|
|
27898
|
-
const
|
|
27961
|
+
const legacyFiles = story.relevantFiles;
|
|
27962
|
+
const files = story.contextFiles ?? legacyFiles ?? [];
|
|
27899
27963
|
return files.map((f) => typeof f === "string" ? f : f.path);
|
|
27900
27964
|
}
|
|
27901
27965
|
function getExpectedFiles(story) {
|
|
@@ -28407,16 +28471,28 @@ function markStoryFailed(prd, storyId, failureCategory, failureStage, statusWrit
|
|
|
28407
28471
|
}
|
|
28408
28472
|
}
|
|
28409
28473
|
}
|
|
28410
|
-
function resetFailedStoriesToPending(prd,
|
|
28474
|
+
function resetFailedStoriesToPending(prd, opts = {}) {
|
|
28475
|
+
const { resetRef = false, storyIsolation, resetMode = "initial" } = opts;
|
|
28411
28476
|
const reset = [];
|
|
28412
28477
|
for (const story of prd.userStories) {
|
|
28413
|
-
if (story.status
|
|
28414
|
-
|
|
28415
|
-
|
|
28416
|
-
|
|
28417
|
-
|
|
28418
|
-
|
|
28478
|
+
if (story.status !== "failed")
|
|
28479
|
+
continue;
|
|
28480
|
+
story.status = "pending";
|
|
28481
|
+
story.attempts = 0;
|
|
28482
|
+
if (resetMode === "initial" && story.routing) {
|
|
28483
|
+
story.routing = {
|
|
28484
|
+
...story.routing,
|
|
28485
|
+
...story.routing.initialModelTier !== undefined && {
|
|
28486
|
+
modelTier: story.routing.initialModelTier
|
|
28487
|
+
},
|
|
28488
|
+
agent: story.routing.initialAgent
|
|
28489
|
+
};
|
|
28490
|
+
story.escalations = [];
|
|
28419
28491
|
}
|
|
28492
|
+
if (resetRef || storyIsolation === "worktree") {
|
|
28493
|
+
story.storyGitRef = undefined;
|
|
28494
|
+
}
|
|
28495
|
+
reset.push(story);
|
|
28420
28496
|
}
|
|
28421
28497
|
return reset;
|
|
28422
28498
|
}
|
|
@@ -28553,6 +28629,7 @@ async function scanTestFiles(options) {
|
|
|
28553
28629
|
const patterns = deriveTestPatterns(contextFiles, resolvedTestGlobs);
|
|
28554
28630
|
allowedBasenames = new Set(patterns);
|
|
28555
28631
|
}
|
|
28632
|
+
const maxScanFiles = options.maxScanFiles ?? DEFAULT_MAX_SCAN_FILES;
|
|
28556
28633
|
const glob = new Glob2(testPattern);
|
|
28557
28634
|
const files = [];
|
|
28558
28635
|
for await (const filePath of glob.scan({ cwd: scanDir, absolute: false })) {
|
|
@@ -28562,6 +28639,13 @@ async function scanTestFiles(options) {
|
|
|
28562
28639
|
continue;
|
|
28563
28640
|
}
|
|
28564
28641
|
}
|
|
28642
|
+
if (files.length >= maxScanFiles) {
|
|
28643
|
+
getLogger().debug("test-scanner", "Glob cap reached \u2014 results truncated", {
|
|
28644
|
+
cap: maxScanFiles,
|
|
28645
|
+
scanDir
|
|
28646
|
+
});
|
|
28647
|
+
break;
|
|
28648
|
+
}
|
|
28565
28649
|
const fullPath = path.join(scanDir, filePath);
|
|
28566
28650
|
try {
|
|
28567
28651
|
const source = await Bun.file(fullPath).text();
|
|
@@ -28658,6 +28742,7 @@ async function generateTestCoverageSummary(options) {
|
|
|
28658
28742
|
const tokens = estimateTokens4(summary);
|
|
28659
28743
|
return { files, totalTests, summary, tokens };
|
|
28660
28744
|
}
|
|
28745
|
+
var DEFAULT_MAX_SCAN_FILES = 200;
|
|
28661
28746
|
var init_test_scanner = __esm(() => {
|
|
28662
28747
|
init_logger2();
|
|
28663
28748
|
init_conventions();
|
|
@@ -28692,12 +28777,14 @@ class TestCoverageProvider {
|
|
|
28692
28777
|
const resolved = await _testCoverageProviderDeps.resolveTestFilePatterns(this.config, request.repoRoot, relPackageDir);
|
|
28693
28778
|
const contextFiles = _testCoverageProviderDeps.getContextFiles(this.story);
|
|
28694
28779
|
const globs = resolved.patterns ?? resolved.globs;
|
|
28780
|
+
const smartCfg = coerceSmartRunner(this.config.execution?.smartTestRunner);
|
|
28695
28781
|
const scanOptions = {
|
|
28696
28782
|
workdir: request.packageDir,
|
|
28697
28783
|
testDir: tcConfig.testDir,
|
|
28698
28784
|
maxTokens: tcConfig.maxTokens ?? 500,
|
|
28699
28785
|
detail: tcConfig.detail ?? "names-and-counts",
|
|
28700
28786
|
scopeToStory: tcConfig.scopeToStory ?? true,
|
|
28787
|
+
maxScanFiles: smartCfg.maxScanFiles,
|
|
28701
28788
|
contextFiles,
|
|
28702
28789
|
resolvedTestGlobs: globs
|
|
28703
28790
|
};
|
|
@@ -28729,6 +28816,7 @@ class TestCoverageProvider {
|
|
|
28729
28816
|
}
|
|
28730
28817
|
var _testCoverageProviderDeps;
|
|
28731
28818
|
var init_test_coverage = __esm(() => {
|
|
28819
|
+
init_test_runners();
|
|
28732
28820
|
init_logger2();
|
|
28733
28821
|
init_prd();
|
|
28734
28822
|
init_resolver();
|
|
@@ -29602,10 +29690,12 @@ function formatPriorFailures(failures) {
|
|
|
29602
29690
|
return "";
|
|
29603
29691
|
}
|
|
29604
29692
|
const parts = [];
|
|
29605
|
-
parts.push(`## Prior Failures (Structured Context)
|
|
29606
|
-
`);
|
|
29607
29693
|
for (const failure of failures) {
|
|
29608
29694
|
parts.push(`### Attempt ${failure.attempt} \u2014 ${failure.modelTier}`);
|
|
29695
|
+
if (failure.agent) {
|
|
29696
|
+
const profilePart = failure.agentProfileId ? ` (profile: ${failure.agentProfileId})` : "";
|
|
29697
|
+
parts.push(`**Agent:** ${failure.agent}${profilePart}`);
|
|
29698
|
+
}
|
|
29609
29699
|
parts.push(`**Stage:** ${failure.stage}`);
|
|
29610
29700
|
parts.push(`**Summary:** ${failure.summary}`);
|
|
29611
29701
|
if (failure.testFailures && failure.testFailures.length > 0) {
|
|
@@ -29711,10 +29801,14 @@ function formatContextAsMarkdown(built) {
|
|
|
29711
29801
|
byType.set(element.type, existing);
|
|
29712
29802
|
}
|
|
29713
29803
|
renderSection(sections, byType, "progress", `## Progress
|
|
29804
|
+
`, renderSimple);
|
|
29805
|
+
renderSection(sections, byType, "prior-failures", `## Prior Failures (Structured Context)
|
|
29714
29806
|
`, renderSimple);
|
|
29715
29807
|
renderErrorSection(sections, byType);
|
|
29716
29808
|
renderSection(sections, byType, "test-coverage", "", renderSimple);
|
|
29717
29809
|
renderSection(sections, byType, "story", `## Current Story
|
|
29810
|
+
`, renderSimple);
|
|
29811
|
+
renderSection(sections, byType, "planning-analysis", `## Planning Analysis
|
|
29718
29812
|
`, renderSimple);
|
|
29719
29813
|
renderSection(sections, byType, "dependency", `## Dependency Stories
|
|
29720
29814
|
`, renderSimple);
|
|
@@ -30026,10 +30120,9 @@ async function gitLsFiles2(workdir) {
|
|
|
30026
30120
|
stdout: "pipe",
|
|
30027
30121
|
stderr: "pipe"
|
|
30028
30122
|
});
|
|
30029
|
-
const exitCode = await proc.exited;
|
|
30123
|
+
const [exitCode, output] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
30030
30124
|
if (exitCode !== 0)
|
|
30031
30125
|
return null;
|
|
30032
|
-
const output = await new Response(proc.stdout).text();
|
|
30033
30126
|
return output.split(`
|
|
30034
30127
|
`).filter(Boolean);
|
|
30035
30128
|
} catch {
|
|
@@ -33167,12 +33260,16 @@ async function collectDiff(workdir, storyGitRef, excludePatterns, options) {
|
|
|
33167
33260
|
stdout: "pipe",
|
|
33168
33261
|
stderr: "pipe"
|
|
33169
33262
|
});
|
|
33170
|
-
const [exitCode, stdout] = await Promise.all([
|
|
33263
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
33171
33264
|
proc.exited,
|
|
33172
33265
|
new Response(proc.stdout).text(),
|
|
33173
33266
|
new Response(proc.stderr).text()
|
|
33174
33267
|
]);
|
|
33175
|
-
|
|
33268
|
+
if (exitCode !== 0) {
|
|
33269
|
+
getSafeLogger()?.warn("diff-utils", "git diff failed \u2014 skipping review diff", { storyGitRef, stderr });
|
|
33270
|
+
return null;
|
|
33271
|
+
}
|
|
33272
|
+
return stdout;
|
|
33176
33273
|
}
|
|
33177
33274
|
async function collectDiffStat(workdir, storyGitRef, options) {
|
|
33178
33275
|
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
@@ -33423,6 +33520,9 @@ async function prepareSemanticReviewInput(args) {
|
|
|
33423
33520
|
return { effectiveRef, stat, diff: undefined, excludePatterns };
|
|
33424
33521
|
}
|
|
33425
33522
|
const rawDiff = await collectDiff(workdir, effectiveRef, excludePatterns, { naxIgnoreIndex, packageDir });
|
|
33523
|
+
if (rawDiff === null) {
|
|
33524
|
+
return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "git diff failed" };
|
|
33525
|
+
}
|
|
33426
33526
|
const diff = truncateDiff(rawDiff, rawDiff.length > DIFF_CAP_BYTES ? stat : undefined);
|
|
33427
33527
|
if (!diff) {
|
|
33428
33528
|
return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "no production code changes" };
|
|
@@ -41050,10 +41150,9 @@ async function listChangedFiles(workdir, baseRef) {
|
|
|
41050
41150
|
stdout: "pipe",
|
|
41051
41151
|
stderr: "pipe"
|
|
41052
41152
|
});
|
|
41053
|
-
const exitCode = await proc.exited;
|
|
41153
|
+
const [exitCode, output] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
41054
41154
|
if (exitCode !== 0)
|
|
41055
41155
|
return null;
|
|
41056
|
-
const output = await new Response(proc.stdout).text();
|
|
41057
41156
|
return output.split(`
|
|
41058
41157
|
`).map((line) => line.trim()).filter(Boolean).map(normalizePath3);
|
|
41059
41158
|
}
|
|
@@ -45726,6 +45825,8 @@ class PidRegistry {
|
|
|
45726
45825
|
pidsFilePath;
|
|
45727
45826
|
pids = new Set;
|
|
45728
45827
|
frozen = false;
|
|
45828
|
+
_writing = false;
|
|
45829
|
+
_pendingWrite = false;
|
|
45729
45830
|
writeQueueTail = Promise.resolve();
|
|
45730
45831
|
constructor(workdir, _platform) {
|
|
45731
45832
|
this.workdir = workdir;
|
|
@@ -46023,11 +46124,22 @@ class PidRegistry {
|
|
|
46023
46124
|
}
|
|
46024
46125
|
}
|
|
46025
46126
|
enqueueWrite() {
|
|
46026
|
-
|
|
46127
|
+
if (this._writing) {
|
|
46128
|
+
this._pendingWrite = true;
|
|
46129
|
+
return this.writeQueueTail;
|
|
46130
|
+
}
|
|
46131
|
+
this._writing = true;
|
|
46132
|
+
this.writeQueueTail = this.writePidsFile().catch((err) => {
|
|
46027
46133
|
getSafeLogger()?.warn("pid-registry", "Failed to flush PID file \u2014 on-disk registry may be stale", {
|
|
46028
46134
|
error: errorMessage(err)
|
|
46029
46135
|
});
|
|
46030
|
-
}))
|
|
46136
|
+
}).then(async () => {
|
|
46137
|
+
this._writing = false;
|
|
46138
|
+
if (this._pendingWrite) {
|
|
46139
|
+
this._pendingWrite = false;
|
|
46140
|
+
await this.enqueueWrite();
|
|
46141
|
+
}
|
|
46142
|
+
});
|
|
46031
46143
|
return this.writeQueueTail;
|
|
46032
46144
|
}
|
|
46033
46145
|
}
|
|
@@ -46589,6 +46701,7 @@ class SessionManager {
|
|
|
46589
46701
|
}
|
|
46590
46702
|
this._busySessions.delete(handle.id);
|
|
46591
46703
|
this._cancelledSessions.delete(handle.id);
|
|
46704
|
+
this._clearWatchdogCancelledCalls(handle.id);
|
|
46592
46705
|
}
|
|
46593
46706
|
async sendPrompt(handle, prompt, opts) {
|
|
46594
46707
|
if (this._cancelledSessions.has(handle.id)) {
|
|
@@ -46673,6 +46786,10 @@ class SessionManager {
|
|
|
46673
46786
|
sweepOrphans(ttlMs = DEFAULT_ORPHAN_TTL_MS) {
|
|
46674
46787
|
return sweepOrphansImpl(this._sessions, ttlMs);
|
|
46675
46788
|
}
|
|
46789
|
+
close() {
|
|
46790
|
+
this._agentStreamUnsubscribe?.();
|
|
46791
|
+
this._agentStreamUnsubscribe = undefined;
|
|
46792
|
+
}
|
|
46676
46793
|
}
|
|
46677
46794
|
var NULL_PROTOCOL_IDS;
|
|
46678
46795
|
var init_manager2 = __esm(() => {
|
|
@@ -46937,6 +47054,9 @@ function createRuntime(config2, workdir, opts) {
|
|
|
46937
47054
|
if (opts?.parentSignal && parentAbortHandler) {
|
|
46938
47055
|
opts.parentSignal.removeEventListener("abort", parentAbortHandler);
|
|
46939
47056
|
}
|
|
47057
|
+
agentManager.close();
|
|
47058
|
+
if (sessionManager instanceof SessionManager)
|
|
47059
|
+
sessionManager.close();
|
|
46940
47060
|
const results = await Promise.allSettled([promptAuditor.flush(), reviewAuditor.flush(), costAggregator.drain()]);
|
|
46941
47061
|
for (const r of results) {
|
|
46942
47062
|
if (r.status === "rejected") {
|
|
@@ -52413,6 +52533,7 @@ async function collectStoryMetrics(ctx, storyStartTime) {
|
|
|
52413
52533
|
startedAt: storyStartTime,
|
|
52414
52534
|
completedAt: new Date().toISOString(),
|
|
52415
52535
|
fullSuiteGatePassed,
|
|
52536
|
+
...ctx.fullSuiteGateFailingFiles && ctx.fullSuiteGateFailingFiles.length > 0 ? { failingTestFiles: ctx.fullSuiteGateFailingFiles } : {},
|
|
52416
52537
|
runtimeCrashes: ctx.storyRuntimeCrashes ?? 0,
|
|
52417
52538
|
tokens: agentResult?.tokenUsage ? new TokenUsage({
|
|
52418
52539
|
inputTokens: agentResult.tokenUsage.inputTokens,
|
|
@@ -52475,17 +52596,18 @@ async function saveRunMetrics(outputDir, runMetrics) {
|
|
|
52475
52596
|
}
|
|
52476
52597
|
}
|
|
52477
52598
|
const hasTokenData = totalInputTokens > 0 || totalOutputTokens > 0 || totalCacheReadInputTokens > 0 || totalCacheCreationInputTokens > 0;
|
|
52478
|
-
|
|
52479
|
-
runMetrics
|
|
52599
|
+
const finalMetrics = hasTokenData ? {
|
|
52600
|
+
...runMetrics,
|
|
52601
|
+
totalTokens: new TokenUsage({
|
|
52480
52602
|
inputTokens: totalInputTokens,
|
|
52481
52603
|
outputTokens: totalOutputTokens,
|
|
52482
52604
|
cacheReadInputTokens: totalCacheReadInputTokens,
|
|
52483
52605
|
cacheCreationInputTokens: totalCacheCreationInputTokens
|
|
52484
|
-
})
|
|
52485
|
-
}
|
|
52606
|
+
})
|
|
52607
|
+
} : runMetrics;
|
|
52486
52608
|
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
52487
52609
|
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
52488
|
-
allMetrics.push(
|
|
52610
|
+
allMetrics.push(finalMetrics);
|
|
52489
52611
|
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
52490
52612
|
}
|
|
52491
52613
|
async function loadRunMetrics(outputDir) {
|
|
@@ -54652,29 +54774,27 @@ async function rollbackToRef(workdir, ref) {
|
|
|
54652
54774
|
stdout: "pipe",
|
|
54653
54775
|
stderr: "pipe"
|
|
54654
54776
|
});
|
|
54655
|
-
const exitCode = await resetProc.exited;
|
|
54777
|
+
const [exitCode, resetStderr] = await Promise.all([resetProc.exited, new Response(resetProc.stderr).text()]);
|
|
54656
54778
|
if (exitCode !== 0) {
|
|
54657
|
-
|
|
54658
|
-
|
|
54659
|
-
throw new Error(`Git rollback failed: ${stderr}`);
|
|
54779
|
+
logger.error("tdd", "Failed to rollback git changes", { ref, stderr: resetStderr });
|
|
54780
|
+
throw new Error(`Git rollback failed: ${resetStderr}`);
|
|
54660
54781
|
}
|
|
54661
54782
|
const cleanProc = _rollbackDeps.spawn(["git", "clean", "-fd"], {
|
|
54662
54783
|
cwd: workdir,
|
|
54663
54784
|
stdout: "pipe",
|
|
54664
54785
|
stderr: "pipe"
|
|
54665
54786
|
});
|
|
54666
|
-
const cleanExitCode = await cleanProc.exited;
|
|
54787
|
+
const [cleanExitCode, cleanStderr] = await Promise.all([cleanProc.exited, new Response(cleanProc.stderr).text()]);
|
|
54667
54788
|
if (cleanExitCode !== 0) {
|
|
54668
|
-
|
|
54669
|
-
logger.warn("tdd", "Failed to clean untracked files", { stderr });
|
|
54789
|
+
logger.warn("tdd", "Failed to clean untracked files", { stderr: cleanStderr });
|
|
54670
54790
|
}
|
|
54671
54791
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
54672
54792
|
}
|
|
54673
54793
|
async function captureSnapshotRef(workdir, storyId) {
|
|
54674
54794
|
await _rollbackDeps.autoCommitIfDirty(workdir, "non-blocking-fix-snapshot", "snapshot", storyId);
|
|
54675
54795
|
const proc = _rollbackDeps.spawn(["git", "rev-parse", "HEAD"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
54676
|
-
const
|
|
54677
|
-
const
|
|
54796
|
+
const [exitCode, shaRaw] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
54797
|
+
const sha = shaRaw.trim();
|
|
54678
54798
|
if (exitCode !== 0) {
|
|
54679
54799
|
throw new NaxError("git rev-parse HEAD failed in non-blocking-fix snapshot", "SNAPSHOT_REF_FAILED", {
|
|
54680
54800
|
storyId,
|
|
@@ -54946,6 +55066,15 @@ function extractPhaseFindings(output) {
|
|
|
54946
55066
|
const success2 = "success" in record2 ? record2.success === true : ("passed" in record2) ? record2.passed === true : findings.length === 0;
|
|
54947
55067
|
return success2 ? [] : findings;
|
|
54948
55068
|
}
|
|
55069
|
+
function gateFailureKeys(gateOutput) {
|
|
55070
|
+
const keys = new Set;
|
|
55071
|
+
for (const f of extractPhaseFindings(gateOutput)) {
|
|
55072
|
+
if (f.source !== "test-runner")
|
|
55073
|
+
continue;
|
|
55074
|
+
keys.add(`${f.file ?? ""}::${f.rule ?? ""}`);
|
|
55075
|
+
}
|
|
55076
|
+
return keys;
|
|
55077
|
+
}
|
|
54949
55078
|
function shouldSkipPhaseForRectification(phase, state, phaseOutputs) {
|
|
54950
55079
|
if (phase.kind !== "full-suite-gate")
|
|
54951
55080
|
return false;
|
|
@@ -55335,6 +55464,8 @@ class ExecutionPlan {
|
|
|
55335
55464
|
break;
|
|
55336
55465
|
}
|
|
55337
55466
|
}
|
|
55467
|
+
const gateName = this.state.fullSuiteGate?.slot.op.name;
|
|
55468
|
+
const preRectGateFailureKeys = gateName ? gateFailureKeys(phaseOutputs[gateName]) : new Set;
|
|
55338
55469
|
const rectResult = await runRectification(this.ctx, this.state, phaseCosts, phaseOutputs);
|
|
55339
55470
|
if (this.state.rectification && (!rectResult.rectificationExhausted || rectResult.liteScopeIncomplete)) {
|
|
55340
55471
|
let resumeRectifyUsed = false;
|
|
@@ -55428,9 +55559,12 @@ class ExecutionPlan {
|
|
|
55428
55559
|
});
|
|
55429
55560
|
}
|
|
55430
55561
|
const verifierName = this.state.verifier?.slot.op.name;
|
|
55431
|
-
const
|
|
55432
|
-
const
|
|
55433
|
-
|
|
55562
|
+
const verifierExplicitlyPassed = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
|
|
55563
|
+
const gateRegressedDuringRect = gateName !== undefined && [...gateFailureKeys(phaseOutputs[gateName])].some((k) => !preRectGateFailureKeys.has(k));
|
|
55564
|
+
const verifierPassedSsot = verifierExplicitlyPassed && !gateRegressedDuringRect;
|
|
55565
|
+
if (verifierExplicitlyPassed && gateRegressedDuringRect) {
|
|
55566
|
+
logger?.warn("story-orchestrator", "Gate regressed during rectification after verifier passed \u2014 verifier verdict is stale, failing story", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
|
|
55567
|
+
} else if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName], this.ctx.storyId)) {
|
|
55434
55568
|
logger?.warn("story-orchestrator", "Full-suite gate failed but verifier judged story OK \u2014 treating gate failures as unrelated regressions", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
|
|
55435
55569
|
}
|
|
55436
55570
|
const success2 = Object.entries(phaseOutputs).every(([name, output]) => {
|
|
@@ -55468,7 +55602,8 @@ class ExecutionPlan {
|
|
|
55468
55602
|
totalCostUsd,
|
|
55469
55603
|
durationMs,
|
|
55470
55604
|
phaseOutputs,
|
|
55471
|
-
...rectResult
|
|
55605
|
+
...rectResult,
|
|
55606
|
+
gateRegressedDuringRect
|
|
55472
55607
|
};
|
|
55473
55608
|
}
|
|
55474
55609
|
}
|
|
@@ -55664,7 +55799,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
55664
55799
|
if (pkgQuality?.commands?.formatFix || pkgQuality?.commands?.formatFixScoped) {
|
|
55665
55800
|
strategies.push(makeMechanicalFormatFixStrategy());
|
|
55666
55801
|
}
|
|
55667
|
-
|
|
55802
|
+
const fullSuiteGatePhasePresent = Boolean(inputs.fullSuiteGate) && (isThreeSession || regressionMode === "per-story");
|
|
55803
|
+
const verifyScopedPhasePresent = !isThreeSession && Boolean(inputs.verifyScoped);
|
|
55804
|
+
if (fullSuiteGatePhasePresent || verifyScopedPhasePresent) {
|
|
55668
55805
|
strategies.push(makeFullSuiteRectifyStrategy(story, config2, sink));
|
|
55669
55806
|
}
|
|
55670
55807
|
if (config2.quality.autofix?.enabled !== false) {
|
|
@@ -56117,7 +56254,7 @@ function extractPauseReason(phaseOutputs) {
|
|
|
56117
56254
|
}
|
|
56118
56255
|
return;
|
|
56119
56256
|
}
|
|
56120
|
-
function deriveTddFailureCategory(phaseOutputs, unfixedFindings) {
|
|
56257
|
+
function deriveTddFailureCategory(phaseOutputs, unfixedFindings, gateRegressedDuringRect) {
|
|
56121
56258
|
const testWriterOutput = phaseOutputs[testWriterOp.name];
|
|
56122
56259
|
if (testWriterOutput?.success === false) {
|
|
56123
56260
|
return "session-failure";
|
|
@@ -56133,7 +56270,7 @@ function deriveTddFailureCategory(phaseOutputs, unfixedFindings) {
|
|
|
56133
56270
|
}
|
|
56134
56271
|
return "tests-failing";
|
|
56135
56272
|
}
|
|
56136
|
-
const verifierPassed = verifierOutput?.success === true;
|
|
56273
|
+
const verifierPassed = verifierOutput?.success === true && !gateRegressedDuringRect;
|
|
56137
56274
|
if (!verifierPassed && unfixedFindings && unfixedFindings.length > 0) {
|
|
56138
56275
|
const rectOutput = phaseOutputs.rectification;
|
|
56139
56276
|
if (rectOutput?.exitReason && EXHAUSTED_EXIT_REASONS.has(rectOutput.exitReason) && unfixedFindings.some((f) => f.source === "test-runner")) {
|
|
@@ -56182,6 +56319,11 @@ async function applyPostRunInspection(ctx, planResult, opts) {
|
|
|
56182
56319
|
if (fullSuiteGateOutput?.passed) {
|
|
56183
56320
|
ctx.fullSuiteGatePassed = true;
|
|
56184
56321
|
}
|
|
56322
|
+
const gateFailingFiles = [
|
|
56323
|
+
...new Set((fullSuiteGateOutput?.findings ?? []).map((f) => f.file).filter((f) => !!f))
|
|
56324
|
+
];
|
|
56325
|
+
if (gateFailingFiles.length > 0)
|
|
56326
|
+
ctx.fullSuiteGateFailingFiles = gateFailingFiles;
|
|
56185
56327
|
ctx.selfVerification = parseSelfVerificationMarker(agentResult.output ?? "", ctx.workdir);
|
|
56186
56328
|
const selfVerificationFailed = ctx.selfVerification.lint === "fail" || ctx.selfVerification.typecheck === "fail";
|
|
56187
56329
|
if (ctx.config.context?.v2?.enabled && ctx.sessionScratchDir) {
|
|
@@ -56248,7 +56390,7 @@ async function applyPostRunInspection(ctx, planResult, opts) {
|
|
|
56248
56390
|
}
|
|
56249
56391
|
}
|
|
56250
56392
|
const pauseReason = extractPauseReason(planResult.phaseOutputs);
|
|
56251
|
-
const failureCategory = isTdd && !planResult.success ? deriveTddFailureCategory(planResult.phaseOutputs, planResult.unfixedFindings) : undefined;
|
|
56393
|
+
const failureCategory = isTdd && !planResult.success ? deriveTddFailureCategory(planResult.phaseOutputs, planResult.unfixedFindings, planResult.gateRegressedDuringRect) : undefined;
|
|
56252
56394
|
if (isTdd && !planResult.success && !failureCategory) {
|
|
56253
56395
|
const phaseSignals = {};
|
|
56254
56396
|
for (const [name, output] of Object.entries(planResult.phaseOutputs)) {
|
|
@@ -57103,6 +57245,7 @@ var init_routing2 = __esm(() => {
|
|
|
57103
57245
|
const neverEscalated = !hasEscalationRecords;
|
|
57104
57246
|
const initialAgent = ctx.story.routing?.initialAgent ?? (neverEscalated ? routing.agent : undefined);
|
|
57105
57247
|
const initialProfileId = ctx.story.routing?.initialProfileId ?? (neverEscalated ? ctx.story.routing?.agentProfileId : undefined);
|
|
57248
|
+
const initialModelTier = ctx.story.routing?.initialModelTier ?? (neverEscalated ? routing.modelTier : undefined);
|
|
57106
57249
|
ctx.story.routing = {
|
|
57107
57250
|
...ctx.story.routing ?? {},
|
|
57108
57251
|
complexity: routing.complexity,
|
|
@@ -57112,7 +57255,8 @@ var init_routing2 = __esm(() => {
|
|
|
57112
57255
|
modelTier: routing.modelTier,
|
|
57113
57256
|
...routing.agent !== undefined && { agent: routing.agent },
|
|
57114
57257
|
...initialAgent !== undefined && { initialAgent },
|
|
57115
|
-
...initialProfileId !== undefined && { initialProfileId }
|
|
57258
|
+
...initialProfileId !== undefined && { initialProfileId },
|
|
57259
|
+
...initialModelTier !== undefined && { initialModelTier }
|
|
57116
57260
|
};
|
|
57117
57261
|
if (ctx.prdPath) {
|
|
57118
57262
|
await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
|
|
@@ -60325,7 +60469,7 @@ var package_default;
|
|
|
60325
60469
|
var init_package = __esm(() => {
|
|
60326
60470
|
package_default = {
|
|
60327
60471
|
name: "@nathapp/nax",
|
|
60328
|
-
version: "0.70.0-canary.
|
|
60472
|
+
version: "0.70.0-canary.6",
|
|
60329
60473
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
60330
60474
|
type: "module",
|
|
60331
60475
|
bin: {
|
|
@@ -60420,8 +60564,8 @@ var init_version = __esm(() => {
|
|
|
60420
60564
|
NAX_VERSION = package_default.version;
|
|
60421
60565
|
NAX_COMMIT = (() => {
|
|
60422
60566
|
try {
|
|
60423
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
60424
|
-
return "
|
|
60567
|
+
if (/^[0-9a-f]{6,10}$/.test("4423f3a5"))
|
|
60568
|
+
return "4423f3a5";
|
|
60425
60569
|
} catch {}
|
|
60426
60570
|
try {
|
|
60427
60571
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -60441,11 +60585,11 @@ var init_version = __esm(() => {
|
|
|
60441
60585
|
|
|
60442
60586
|
// src/execution/crash-heartbeat.ts
|
|
60443
60587
|
import { appendFileSync as appendFileSync2 } from "fs";
|
|
60444
|
-
async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60588
|
+
async function heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60445
60589
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
60446
|
-
while (
|
|
60590
|
+
while (gen === _heartbeatGen && _heartbeatActive) {
|
|
60447
60591
|
await _heartbeatDeps.sleep(60000);
|
|
60448
|
-
if (!
|
|
60592
|
+
if (gen !== _heartbeatGen || !_heartbeatActive)
|
|
60449
60593
|
break;
|
|
60450
60594
|
try {
|
|
60451
60595
|
logger?.debug("crash-recovery", "Heartbeat");
|
|
@@ -60474,9 +60618,9 @@ async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFil
|
|
|
60474
60618
|
}
|
|
60475
60619
|
function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60476
60620
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
60477
|
-
|
|
60478
|
-
|
|
60479
|
-
heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath).catch((err) => {
|
|
60621
|
+
_heartbeatActive = true;
|
|
60622
|
+
const gen = ++_heartbeatGen;
|
|
60623
|
+
heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jsonlFilePath).catch((err) => {
|
|
60480
60624
|
_heartbeatDeps.getSafeLogger()?.warn("crash-recovery", "Heartbeat loop crashed; status updates stopped", {
|
|
60481
60625
|
error: err instanceof Error ? err.message : String(err)
|
|
60482
60626
|
});
|
|
@@ -60484,12 +60628,13 @@ function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath
|
|
|
60484
60628
|
logger?.debug("crash-recovery", "Heartbeat started (60s interval)");
|
|
60485
60629
|
}
|
|
60486
60630
|
function stopHeartbeat() {
|
|
60487
|
-
if (
|
|
60488
|
-
|
|
60631
|
+
if (_heartbeatActive) {
|
|
60632
|
+
_heartbeatActive = false;
|
|
60633
|
+
_heartbeatGen++;
|
|
60489
60634
|
getSafeLogger()?.debug("crash-recovery", "Heartbeat stopped");
|
|
60490
60635
|
}
|
|
60491
60636
|
}
|
|
60492
|
-
var _heartbeatDeps,
|
|
60637
|
+
var _heartbeatDeps, _heartbeatGen = 0, _heartbeatActive = false;
|
|
60493
60638
|
var init_crash_heartbeat = __esm(() => {
|
|
60494
60639
|
init_logger2();
|
|
60495
60640
|
_heartbeatDeps = {
|
|
@@ -61374,6 +61519,15 @@ async function findResponsibleStory(testFile, workdir, passedStories) {
|
|
|
61374
61519
|
}
|
|
61375
61520
|
return;
|
|
61376
61521
|
}
|
|
61522
|
+
function findResponsibleStoryByTransition(testFile, snapshots) {
|
|
61523
|
+
const ordered = [...snapshots].sort((a, b) => a.completedAt.localeCompare(b.completedAt) || a.storyId.localeCompare(b.storyId));
|
|
61524
|
+
for (const snap of ordered) {
|
|
61525
|
+
if (snap.failingTestFiles?.includes(testFile)) {
|
|
61526
|
+
return snap.storyId;
|
|
61527
|
+
}
|
|
61528
|
+
}
|
|
61529
|
+
return;
|
|
61530
|
+
}
|
|
61377
61531
|
async function runDeferredRegression(options) {
|
|
61378
61532
|
const logger = getSafeLogger();
|
|
61379
61533
|
const { config: config2, prd, workdir, runtime } = options;
|
|
@@ -61521,8 +61675,21 @@ async function runDeferredRegression(options) {
|
|
|
61521
61675
|
}
|
|
61522
61676
|
} else {
|
|
61523
61677
|
const testFilesArray = Array.from(testFilesInFailures);
|
|
61678
|
+
const snapshots = options.storyMetrics ?? [];
|
|
61679
|
+
const passedById = new Map(passedStories.map((s) => [s.id, s]));
|
|
61524
61680
|
for (const testFile of testFilesArray) {
|
|
61525
|
-
|
|
61681
|
+
let responsibleStory;
|
|
61682
|
+
const transitionId = findResponsibleStoryByTransition(testFile, snapshots);
|
|
61683
|
+
if (transitionId && passedById.has(transitionId)) {
|
|
61684
|
+
responsibleStory = passedById.get(transitionId);
|
|
61685
|
+
logger?.info("regression", "Mapped test to story via gate transition", {
|
|
61686
|
+
storyId: transitionId,
|
|
61687
|
+
testFile
|
|
61688
|
+
});
|
|
61689
|
+
}
|
|
61690
|
+
if (!responsibleStory) {
|
|
61691
|
+
responsibleStory = await findResponsibleStory(testFile, workdir, passedStories);
|
|
61692
|
+
}
|
|
61526
61693
|
if (responsibleStory) {
|
|
61527
61694
|
affectedStories.add(responsibleStory.id);
|
|
61528
61695
|
affectedStoriesObjs.set(responsibleStory.id, responsibleStory);
|
|
@@ -61707,7 +61874,12 @@ async function handleRunCompletion(options) {
|
|
|
61707
61874
|
config: config2,
|
|
61708
61875
|
prd,
|
|
61709
61876
|
workdir,
|
|
61710
|
-
runtime: options.runtime
|
|
61877
|
+
runtime: options.runtime,
|
|
61878
|
+
storyMetrics: options.isSequential === false ? undefined : allStoryMetrics.map((m) => ({
|
|
61879
|
+
storyId: m.storyId,
|
|
61880
|
+
completedAt: m.completedAt,
|
|
61881
|
+
failingTestFiles: m.failingTestFiles
|
|
61882
|
+
}))
|
|
61711
61883
|
});
|
|
61712
61884
|
const lastRunAt = new Date().toISOString();
|
|
61713
61885
|
logger?.info("regression", "Deferred regression gate completed", {
|
|
@@ -62656,9 +62828,8 @@ ${missing.join(`
|
|
|
62656
62828
|
stdout: "pipe",
|
|
62657
62829
|
stderr: "pipe"
|
|
62658
62830
|
});
|
|
62659
|
-
const exitCode = await proc.exited;
|
|
62831
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62660
62832
|
if (exitCode !== 0) {
|
|
62661
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62662
62833
|
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
62663
62834
|
}
|
|
62664
62835
|
} catch (error48) {
|
|
@@ -62691,9 +62862,8 @@ ${missing.join(`
|
|
|
62691
62862
|
stdout: "pipe",
|
|
62692
62863
|
stderr: "pipe"
|
|
62693
62864
|
});
|
|
62694
|
-
const exitCode = await proc.exited;
|
|
62865
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62695
62866
|
if (exitCode !== 0) {
|
|
62696
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62697
62867
|
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
62698
62868
|
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
62699
62869
|
}
|
|
@@ -62711,9 +62881,8 @@ ${missing.join(`
|
|
|
62711
62881
|
stdout: "pipe",
|
|
62712
62882
|
stderr: "pipe"
|
|
62713
62883
|
});
|
|
62714
|
-
const exitCode = await proc.exited;
|
|
62884
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62715
62885
|
if (exitCode !== 0) {
|
|
62716
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62717
62886
|
if (!stderr.includes("not found")) {
|
|
62718
62887
|
const logger = getSafeLogger();
|
|
62719
62888
|
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
@@ -62733,12 +62902,14 @@ ${missing.join(`
|
|
|
62733
62902
|
stdout: "pipe",
|
|
62734
62903
|
stderr: "pipe"
|
|
62735
62904
|
});
|
|
62736
|
-
const exitCode = await
|
|
62905
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
62906
|
+
proc.exited,
|
|
62907
|
+
new Response(proc.stderr).text(),
|
|
62908
|
+
new Response(proc.stdout).text()
|
|
62909
|
+
]);
|
|
62737
62910
|
if (exitCode !== 0) {
|
|
62738
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62739
62911
|
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
62740
62912
|
}
|
|
62741
|
-
const stdout = await new Response(proc.stdout).text();
|
|
62742
62913
|
return this.parseWorktreeList(stdout);
|
|
62743
62914
|
} catch (error48) {
|
|
62744
62915
|
if (error48 instanceof Error) {
|
|
@@ -62850,9 +63021,11 @@ class MergeEngine {
|
|
|
62850
63021
|
stdout: "pipe",
|
|
62851
63022
|
stderr: "pipe"
|
|
62852
63023
|
});
|
|
62853
|
-
const exitCode = await
|
|
62854
|
-
|
|
62855
|
-
|
|
63024
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
63025
|
+
mergeProc.exited,
|
|
63026
|
+
new Response(mergeProc.stderr).text(),
|
|
63027
|
+
new Response(mergeProc.stdout).text()
|
|
63028
|
+
]);
|
|
62856
63029
|
if (exitCode === 0) {
|
|
62857
63030
|
try {
|
|
62858
63031
|
await this.worktreeManager.remove(projectRoot, storyId);
|
|
@@ -62979,19 +63152,25 @@ ${stderr}`;
|
|
|
62979
63152
|
stdout: "pipe",
|
|
62980
63153
|
stderr: "pipe"
|
|
62981
63154
|
});
|
|
62982
|
-
const exitCode = await
|
|
63155
|
+
const [exitCode, currentBranchRaw] = await Promise.all([
|
|
63156
|
+
currentBranchProc.exited,
|
|
63157
|
+
new Response(currentBranchProc.stdout).text()
|
|
63158
|
+
]);
|
|
62983
63159
|
if (exitCode !== 0) {
|
|
62984
63160
|
throw new Error("Failed to get current branch");
|
|
62985
63161
|
}
|
|
62986
|
-
const currentBranch =
|
|
63162
|
+
const currentBranch = currentBranchRaw.trim();
|
|
62987
63163
|
const rebaseProc = _mergeDeps.spawn(["git", "rebase", currentBranch], {
|
|
62988
63164
|
cwd: worktreePath,
|
|
62989
63165
|
stdout: "pipe",
|
|
62990
63166
|
stderr: "pipe"
|
|
62991
63167
|
});
|
|
62992
|
-
const rebaseExitCode = await
|
|
63168
|
+
const [rebaseExitCode, rebaseStderr] = await Promise.all([
|
|
63169
|
+
rebaseProc.exited,
|
|
63170
|
+
new Response(rebaseProc.stderr).text()
|
|
63171
|
+
]);
|
|
62993
63172
|
if (rebaseExitCode !== 0) {
|
|
62994
|
-
const stderr =
|
|
63173
|
+
const stderr = rebaseStderr;
|
|
62995
63174
|
const abortProc = _mergeDeps.spawn(["git", "rebase", "--abort"], {
|
|
62996
63175
|
cwd: worktreePath,
|
|
62997
63176
|
stdout: "pipe",
|
|
@@ -63014,11 +63193,10 @@ ${stderr}`;
|
|
|
63014
63193
|
stdout: "pipe",
|
|
63015
63194
|
stderr: "pipe"
|
|
63016
63195
|
});
|
|
63017
|
-
const exitCode = await proc.exited;
|
|
63196
|
+
const [exitCode, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
63018
63197
|
if (exitCode !== 0) {
|
|
63019
63198
|
return [];
|
|
63020
63199
|
}
|
|
63021
|
-
const stdout = await new Response(proc.stdout).text();
|
|
63022
63200
|
return stdout.trim().split(`
|
|
63023
63201
|
`).filter((line) => line.length > 0);
|
|
63024
63202
|
} catch {
|
|
@@ -63234,13 +63412,17 @@ function buildEscalationFailure(story, currentTier, reviewFindings, cost, pipeli
|
|
|
63234
63412
|
summary,
|
|
63235
63413
|
reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
|
|
63236
63414
|
cost: cost ?? 0,
|
|
63237
|
-
timestamp: new Date().toISOString()
|
|
63415
|
+
timestamp: new Date().toISOString(),
|
|
63416
|
+
...story.routing?.agent !== undefined ? { agent: story.routing.agent } : {},
|
|
63417
|
+
...story.routing?.agentProfileId !== undefined ? { agentProfileId: story.routing.agentProfileId } : {}
|
|
63238
63418
|
};
|
|
63239
63419
|
}
|
|
63240
|
-
function buildEscalationRecord(currentTier, nextTier, reason) {
|
|
63420
|
+
function buildEscalationRecord(currentTier, nextTier, reason, agents) {
|
|
63241
63421
|
return {
|
|
63242
63422
|
fromTier: currentTier,
|
|
63243
63423
|
toTier: nextTier,
|
|
63424
|
+
...agents?.fromAgent !== undefined ? { fromAgent: agents.fromAgent } : {},
|
|
63425
|
+
...agents?.toAgent !== undefined ? { toAgent: agents.toAgent } : {},
|
|
63244
63426
|
reason,
|
|
63245
63427
|
timestamp: new Date().toISOString()
|
|
63246
63428
|
};
|
|
@@ -63337,7 +63519,7 @@ async function handleTierEscalation(ctx) {
|
|
|
63337
63519
|
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
63338
63520
|
const isChangingTier = currentStoryTier !== escalatedTier;
|
|
63339
63521
|
const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
|
|
63340
|
-
const escalationRecord = isChangingTier || shouldSwitchToTestAfter ? buildEscalationRecord(currentStoryTier, shouldSwitchToTestAfter ? currentStoryTier : escalatedTier, ctx.pipelineResult.reason ?? "Escalated to next retry path") : undefined;
|
|
63522
|
+
const escalationRecord = isChangingTier || shouldSwitchToTestAfter ? buildEscalationRecord(currentStoryTier, shouldSwitchToTestAfter ? currentStoryTier : escalatedTier, ctx.pipelineResult.reason ?? "Escalated to next retry path", { fromAgent: s.routing?.agent, toAgent: nextAgent }) : undefined;
|
|
63341
63523
|
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost, verifiedPipelineReason, escalateFailureCategory);
|
|
63342
63524
|
return {
|
|
63343
63525
|
...s,
|
|
@@ -63758,7 +63940,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
63758
63940
|
}
|
|
63759
63941
|
}
|
|
63760
63942
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
63761
|
-
const profileOverride = ctx.config
|
|
63943
|
+
const profileOverride = profileOverrideFromConfig(ctx.config);
|
|
63762
63944
|
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join81(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
63763
63945
|
let dependencyContext;
|
|
63764
63946
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
@@ -63904,6 +64086,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
63904
64086
|
}
|
|
63905
64087
|
var _iterationRunnerDeps;
|
|
63906
64088
|
var init_iteration_runner = __esm(() => {
|
|
64089
|
+
init_config();
|
|
63907
64090
|
init_loader();
|
|
63908
64091
|
init_logger2();
|
|
63909
64092
|
init_runner4();
|
|
@@ -64134,7 +64317,7 @@ async function runParallelBatch(options) {
|
|
|
64134
64317
|
worktreePaths.set(story.id, path22.join(workdir, ".nax-wt", story.id));
|
|
64135
64318
|
}
|
|
64136
64319
|
const rootConfigPath = path22.join(workdir, ".nax", "config.json");
|
|
64137
|
-
const profileOverride = config2
|
|
64320
|
+
const profileOverride = profileOverrideFromConfig(config2);
|
|
64138
64321
|
const storyEffectiveConfigs = new Map;
|
|
64139
64322
|
const configResults = await Promise.allSettled(stories.filter((story) => story.workdir).map(async (story) => {
|
|
64140
64323
|
try {
|
|
@@ -64295,6 +64478,7 @@ async function runParallelBatch(options) {
|
|
|
64295
64478
|
}
|
|
64296
64479
|
var _parallelBatchDeps;
|
|
64297
64480
|
var init_parallel_batch = __esm(() => {
|
|
64481
|
+
init_config();
|
|
64298
64482
|
init_loader();
|
|
64299
64483
|
init_logger2();
|
|
64300
64484
|
init_dependencies();
|
|
@@ -64361,13 +64545,15 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64361
64545
|
};
|
|
64362
64546
|
for (const fn of _prevRunUnsubscribers)
|
|
64363
64547
|
fn();
|
|
64364
|
-
_prevRunUnsubscribers = [
|
|
64548
|
+
_prevRunUnsubscribers = [];
|
|
64549
|
+
const thisRunUnsubscribers = [
|
|
64365
64550
|
wireHooks(pipelineEventBus, ctx.hooks, ctx.workdir, ctx.feature),
|
|
64366
64551
|
wireReporters(pipelineEventBus, ctx.pluginRegistry, ctx.runId, ctx.startTime),
|
|
64367
64552
|
wireInteraction(pipelineEventBus, ctx.interactionChain, ctx.config),
|
|
64368
64553
|
wireEventsWriter(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir),
|
|
64369
64554
|
wireRegistry(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir, ctx.runtime.outputDir)
|
|
64370
64555
|
];
|
|
64556
|
+
_prevRunUnsubscribers = thisRunUnsubscribers;
|
|
64371
64557
|
pipelineEventBus.emit({
|
|
64372
64558
|
type: "run:started",
|
|
64373
64559
|
feature: ctx.feature,
|
|
@@ -64384,6 +64570,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64384
64570
|
deferredReview
|
|
64385
64571
|
});
|
|
64386
64572
|
startHeartbeat(ctx.statusWriter, () => totalCost, () => iterations, ctx.logFilePath);
|
|
64573
|
+
let _executeThrew = false;
|
|
64387
64574
|
try {
|
|
64388
64575
|
if (isComplete(prd)) {
|
|
64389
64576
|
logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
|
|
@@ -64790,7 +64977,16 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64790
64977
|
}, ctx.eventEmitter);
|
|
64791
64978
|
}
|
|
64792
64979
|
return buildResult2("max-iterations");
|
|
64793
|
-
}
|
|
64980
|
+
} catch (err) {
|
|
64981
|
+
_executeThrew = true;
|
|
64982
|
+
throw err;
|
|
64983
|
+
} finally {
|
|
64984
|
+
if (_executeThrew && _prevRunUnsubscribers === thisRunUnsubscribers) {
|
|
64985
|
+
for (const fn of thisRunUnsubscribers)
|
|
64986
|
+
fn();
|
|
64987
|
+
_prevRunUnsubscribers = [];
|
|
64988
|
+
}
|
|
64989
|
+
}
|
|
64794
64990
|
}
|
|
64795
64991
|
function reconcileBatchOutcome(prd, batchResult) {
|
|
64796
64992
|
for (const story of batchResult.completed) {
|
|
@@ -65542,7 +65738,8 @@ async function initializeRun(ctx) {
|
|
|
65542
65738
|
prd = await reconcileState(prd, ctx.prdPath, ctx.workdir, ctx.config);
|
|
65543
65739
|
const resetRef = ctx.config.review?.semantic?.resetRefOnRerun ?? false;
|
|
65544
65740
|
const storyIsolation = ctx.config.execution.storyIsolation;
|
|
65545
|
-
const
|
|
65741
|
+
const resetMode = ctx.config.autoMode.escalation.resetMode;
|
|
65742
|
+
const resetStories = resetFailedStoriesToPending(prd, { resetRef, storyIsolation, resetMode });
|
|
65546
65743
|
if (resetStories.length > 0) {
|
|
65547
65744
|
const resetIds = resetStories.map((s) => s.id);
|
|
65548
65745
|
logger?.info("run-initialization", "Reset failed stories to pending for re-run", { storyIds: resetIds });
|
|
@@ -65882,6 +66079,7 @@ async function setupRun(options) {
|
|
|
65882
66079
|
const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
|
|
65883
66080
|
const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
|
|
65884
66081
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins, isTestFileFn);
|
|
66082
|
+
clearCache();
|
|
65885
66083
|
logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
|
|
65886
66084
|
plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides }))
|
|
65887
66085
|
});
|
|
@@ -65935,6 +66133,7 @@ async function setupRun(options) {
|
|
|
65935
66133
|
var _runSetupDeps;
|
|
65936
66134
|
var init_run_setup = __esm(() => {
|
|
65937
66135
|
init_pipeline();
|
|
66136
|
+
init_routing();
|
|
65938
66137
|
init_test_runners();
|
|
65939
66138
|
init_paths();
|
|
65940
66139
|
init_errors();
|
|
@@ -95771,7 +95970,8 @@ function finalizePrdRouting(prd, agentRouting, profileName) {
|
|
|
95771
95970
|
agentProfileId: assignment.agentProfileId,
|
|
95772
95971
|
profileModelTier: assignment.profileModelTier,
|
|
95773
95972
|
initialAgent: story.routing?.initialAgent ?? assignment.agent,
|
|
95774
|
-
initialProfileId: story.routing?.initialProfileId ?? assignment.agentProfileId
|
|
95973
|
+
initialProfileId: story.routing?.initialProfileId ?? assignment.agentProfileId,
|
|
95974
|
+
initialModelTier: story.routing?.initialModelTier ?? assignment.profileModelTier
|
|
95775
95975
|
};
|
|
95776
95976
|
return { ...story, routing };
|
|
95777
95977
|
});
|
|
@@ -97864,8 +98064,9 @@ async function rulesLintCommand(options) {
|
|
|
97864
98064
|
init_config();
|
|
97865
98065
|
init_logger2();
|
|
97866
98066
|
async function resolveRunProfileOverride(opts) {
|
|
97867
|
-
|
|
97868
|
-
|
|
98067
|
+
const cliChain = parseProfileList(opts.cliProfile);
|
|
98068
|
+
if (cliChain.length > 0)
|
|
98069
|
+
return cliChain;
|
|
97869
98070
|
if (opts.envProfile)
|
|
97870
98071
|
return;
|
|
97871
98072
|
const readJson = opts._readJson ?? (async (path19) => {
|
|
@@ -97876,15 +98077,15 @@ async function resolveRunProfileOverride(opts) {
|
|
|
97876
98077
|
});
|
|
97877
98078
|
try {
|
|
97878
98079
|
const prd = await readJson(opts.prdPath);
|
|
97879
|
-
|
|
97880
|
-
|
|
97881
|
-
|
|
97882
|
-
return name;
|
|
98080
|
+
const rp = prd?.routingProfile;
|
|
98081
|
+
const prdChain = parseProfileList(typeof rp === "string" || Array.isArray(rp) ? rp : undefined);
|
|
98082
|
+
if (prdChain.length > 0) {
|
|
97883
98083
|
const listNames = opts._listProfileNames ?? (async () => (await listProfiles(opts.projectRoot)).map((p) => p.name));
|
|
97884
98084
|
const available = await listNames();
|
|
97885
|
-
|
|
97886
|
-
|
|
97887
|
-
|
|
98085
|
+
const missing = prdChain.filter((name) => name !== "default" && !available.includes(name));
|
|
98086
|
+
if (missing.length === 0)
|
|
98087
|
+
return prdChain;
|
|
98088
|
+
getSafeLogger()?.warn("run", `PRD was planned with config profile(s) "${prdChain.join(",")}" but ${missing.join(", ")} not found \u2014 continuing with current config resolution`, { storyId: "prd", plannedProfile: prdChain.join(","), missing });
|
|
97888
98089
|
}
|
|
97889
98090
|
} catch {}
|
|
97890
98091
|
return;
|
|
@@ -105271,6 +105472,7 @@ var import_react28 = __toESM(require_react(), 1);
|
|
|
105271
105472
|
var import_react35 = __toESM(require_react(), 1);
|
|
105272
105473
|
|
|
105273
105474
|
// src/utils/queue-writer.ts
|
|
105475
|
+
var _writeChains = new Map;
|
|
105274
105476
|
async function writeQueueCommand(queueFilePath, command) {
|
|
105275
105477
|
let commandLine2;
|
|
105276
105478
|
switch (command.type) {
|
|
@@ -105288,13 +105490,22 @@ async function writeQueueCommand(queueFilePath, command) {
|
|
|
105288
105490
|
throw new Error(`Unhandled queue command: ${_exhaustive}`);
|
|
105289
105491
|
}
|
|
105290
105492
|
}
|
|
105291
|
-
const
|
|
105292
|
-
const
|
|
105293
|
-
|
|
105493
|
+
const chain = _writeChains.get(queueFilePath) ?? Promise.resolve();
|
|
105494
|
+
const next = chain.then(async () => {
|
|
105495
|
+
const existing = await Bun.file(queueFilePath).text().catch(() => "");
|
|
105496
|
+
const content = existing ? `${existing.trimEnd()}
|
|
105294
105497
|
${commandLine2}
|
|
105295
105498
|
` : `${commandLine2}
|
|
105296
105499
|
`;
|
|
105297
|
-
|
|
105500
|
+
await Bun.write(queueFilePath, content);
|
|
105501
|
+
});
|
|
105502
|
+
const settled = next.catch(() => {});
|
|
105503
|
+
_writeChains.set(queueFilePath, settled);
|
|
105504
|
+
settled.then(() => {
|
|
105505
|
+
if (_writeChains.get(queueFilePath) === settled)
|
|
105506
|
+
_writeChains.delete(queueFilePath);
|
|
105507
|
+
});
|
|
105508
|
+
await next;
|
|
105298
105509
|
}
|
|
105299
105510
|
|
|
105300
105511
|
// src/tui/components/CostOverlay.tsx
|
|
@@ -106939,6 +107150,9 @@ function renderTui(props) {
|
|
|
106939
107150
|
init_version();
|
|
106940
107151
|
var program2 = new Command;
|
|
106941
107152
|
program2.name("nax").description("AI Coding Agent Orchestrator \u2014 loops until done").version(NAX_VERSION);
|
|
107153
|
+
function collectProfile(value, previous) {
|
|
107154
|
+
return previous.concat(value);
|
|
107155
|
+
}
|
|
106942
107156
|
async function promptForConfirmation(question) {
|
|
106943
107157
|
if (!process.stdin.isTTY) {
|
|
106944
107158
|
return true;
|
|
@@ -107136,7 +107350,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
|
|
|
107136
107350
|
});
|
|
107137
107351
|
process.exit(exitCode);
|
|
107138
107352
|
});
|
|
107139
|
-
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false).option("--force", "Force overwrite existing prd.json when using --plan", false).option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).option("--profile <name>", "Profile to
|
|
107353
|
+
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false).option("--force", "Force overwrite existing prd.json when using --plan", false).option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).option("--profile <name>", "Profile(s) to overlay (comma-separated or repeated; later overrides earlier)", collectProfile, []).action(async (options) => {
|
|
107140
107354
|
let workdir;
|
|
107141
107355
|
try {
|
|
107142
107356
|
workdir = validateDirectory(options.dir);
|
|
@@ -107173,13 +107387,14 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
107173
107387
|
}
|
|
107174
107388
|
const naxDir = findProjectDir(workdir);
|
|
107175
107389
|
const cliOverrides = {};
|
|
107390
|
+
const cliProfiles = options.profile ?? [];
|
|
107176
107391
|
const profileOverride = naxDir ? await resolveRunProfileOverride({
|
|
107177
107392
|
prdPath: join87(naxDir, "features", options.feature, "prd.json"),
|
|
107178
107393
|
projectRoot: workdir,
|
|
107179
|
-
cliProfile:
|
|
107394
|
+
cliProfile: cliProfiles,
|
|
107180
107395
|
envProfile: process.env.NAX_PROFILE
|
|
107181
|
-
}) :
|
|
107182
|
-
if (profileOverride) {
|
|
107396
|
+
}) : cliProfiles;
|
|
107397
|
+
if (profileOverride && profileOverride.length > 0) {
|
|
107183
107398
|
cliOverrides.profile = profileOverride;
|
|
107184
107399
|
}
|
|
107185
107400
|
const config2 = await loadConfig(naxDir ?? undefined, cliOverrides);
|
|
@@ -107480,7 +107695,7 @@ Features:
|
|
|
107480
107695
|
}
|
|
107481
107696
|
console.log();
|
|
107482
107697
|
});
|
|
107483
|
-
program2.command("plan [description]").description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')").option("--from <spec-path>", "Path to spec file (required unless --decompose is used)").requiredOption("-f, --feature <name>", "Feature name (required)").option("--auto", "Run in one-shot LLM mode (alias: --one-shot)", false).option("--one-shot", "Run in one-shot LLM mode (alias: --auto)", false).option("-b, --branch <branch>", "Override default branch name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--decompose <storyId>", "Decompose an existing story into sub-stories").option("--profile <name>", "Profile to
|
|
107698
|
+
program2.command("plan [description]").description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')").option("--from <spec-path>", "Path to spec file (required unless --decompose is used)").requiredOption("-f, --feature <name>", "Feature name (required)").option("--auto", "Run in one-shot LLM mode (alias: --one-shot)", false).option("--one-shot", "Run in one-shot LLM mode (alias: --auto)", false).option("-b, --branch <branch>", "Override default branch name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--decompose <storyId>", "Decompose an existing story into sub-stories").option("--profile <name>", "Profile(s) to overlay (comma-separated or repeated; later overrides earlier)", collectProfile, []).action(async (description, options) => {
|
|
107484
107699
|
if (description) {
|
|
107485
107700
|
console.error(source_default.red(`Error: Positional args removed in plan v2.
|
|
107486
107701
|
|
|
@@ -107500,8 +107715,9 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
107500
107715
|
process.exit(1);
|
|
107501
107716
|
}
|
|
107502
107717
|
const cliOverrides = {};
|
|
107503
|
-
|
|
107504
|
-
|
|
107718
|
+
const cliProfiles = options.profile ?? [];
|
|
107719
|
+
if (cliProfiles.length > 0) {
|
|
107720
|
+
cliOverrides.profile = cliProfiles;
|
|
107505
107721
|
}
|
|
107506
107722
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
107507
107723
|
const featureLogDir = join87(naxDir, "features", options.feature, "plan");
|