@nathapp/nax 0.70.0-canary.4 → 0.70.0-canary.5
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 +272 -133
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -16852,11 +16852,13 @@ var init_schemas_execution = __esm(() => {
|
|
|
16852
16852
|
SmartTestRunnerConfigSchema = exports_external.object({
|
|
16853
16853
|
enabled: exports_external.boolean().default(true),
|
|
16854
16854
|
testFilePatterns: exports_external.array(exports_external.string()).optional(),
|
|
16855
|
-
fallback: exports_external.enum(["import-grep", "full-suite"]).default("import-grep")
|
|
16855
|
+
fallback: exports_external.enum(["import-grep", "full-suite"]).default("import-grep"),
|
|
16856
|
+
maxScanFiles: exports_external.number().int().min(1).max(5000).default(200)
|
|
16856
16857
|
});
|
|
16857
16858
|
SMART_TEST_RUNNER_DEFAULT = {
|
|
16858
16859
|
enabled: true,
|
|
16859
|
-
fallback: "import-grep"
|
|
16860
|
+
fallback: "import-grep",
|
|
16861
|
+
maxScanFiles: 200
|
|
16860
16862
|
};
|
|
16861
16863
|
smartTestRunnerFieldSchema = exports_external.preprocess((val) => {
|
|
16862
16864
|
if (typeof val === "boolean") {
|
|
@@ -17665,7 +17667,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17665
17667
|
}
|
|
17666
17668
|
})),
|
|
17667
17669
|
curator: CuratorConfigSchema.optional(),
|
|
17668
|
-
profile: exports_external.string().default("default")
|
|
17670
|
+
profile: exports_external.string().default("default"),
|
|
17671
|
+
profileChain: exports_external.array(exports_external.string()).default([])
|
|
17669
17672
|
}).refine((data) => data.version === 1, {
|
|
17670
17673
|
message: "Invalid version: expected 1",
|
|
17671
17674
|
path: ["version"]
|
|
@@ -18728,7 +18731,11 @@ async function loadProfile(profileName, projectRoot) {
|
|
|
18728
18731
|
if (!globalExists && !projectExists) {
|
|
18729
18732
|
const available = await listAvailableProfileNames(projectRoot);
|
|
18730
18733
|
const availableList = available.length > 0 ? available.join(", ") : "(none)";
|
|
18731
|
-
throw new
|
|
18734
|
+
throw new NaxError(`Profile "${profileName}" not found. Available: ${availableList}`, "PROFILE_NOT_FOUND", {
|
|
18735
|
+
stage: "config",
|
|
18736
|
+
profileName,
|
|
18737
|
+
available
|
|
18738
|
+
});
|
|
18732
18739
|
}
|
|
18733
18740
|
let base = {};
|
|
18734
18741
|
if (globalExists) {
|
|
@@ -18760,30 +18767,59 @@ async function loadProfileEnv(profileName, projectRoot) {
|
|
|
18760
18767
|
}
|
|
18761
18768
|
return merged;
|
|
18762
18769
|
}
|
|
18763
|
-
|
|
18764
|
-
if (
|
|
18765
|
-
return
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
return config2.profile;
|
|
18770
|
+
function parseProfileList(input) {
|
|
18771
|
+
if (input == null)
|
|
18772
|
+
return [];
|
|
18773
|
+
const parts = Array.isArray(input) ? input : [input];
|
|
18774
|
+
const out = [];
|
|
18775
|
+
for (const part of parts) {
|
|
18776
|
+
if (typeof part !== "string")
|
|
18777
|
+
continue;
|
|
18778
|
+
for (const segment of part.split(",")) {
|
|
18779
|
+
const trimmed = segment.trim();
|
|
18780
|
+
if (trimmed)
|
|
18781
|
+
out.push(trimmed);
|
|
18776
18782
|
}
|
|
18777
18783
|
}
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18784
|
+
return out;
|
|
18785
|
+
}
|
|
18786
|
+
function profileOverrideFromConfig(config2) {
|
|
18787
|
+
if (config2.profileChain && config2.profileChain.length > 0) {
|
|
18788
|
+
return { profile: config2.profileChain };
|
|
18789
|
+
}
|
|
18790
|
+
if (config2.profile && config2.profile !== "default") {
|
|
18791
|
+
return { profile: [config2.profile] };
|
|
18785
18792
|
}
|
|
18786
|
-
return
|
|
18793
|
+
return;
|
|
18794
|
+
}
|
|
18795
|
+
async function readProfileChainFromConfig(dir) {
|
|
18796
|
+
const configFile = Bun.file(join2(dir, "config.json"));
|
|
18797
|
+
if (!await configFile.exists())
|
|
18798
|
+
return [];
|
|
18799
|
+
const config2 = await configFile.json();
|
|
18800
|
+
return parseProfileList(config2.profile);
|
|
18801
|
+
}
|
|
18802
|
+
function isDefaultOnlyChain(chain) {
|
|
18803
|
+
return chain.length === 0 || chain.length === 1 && chain[0] === "default";
|
|
18804
|
+
}
|
|
18805
|
+
async function resolveProfileNames(cliOptions, env2, projectRoot) {
|
|
18806
|
+
const fromCli = parseProfileList(cliOptions.profile);
|
|
18807
|
+
if (fromCli.length)
|
|
18808
|
+
return fromCli;
|
|
18809
|
+
const fromEnv = parseProfileList(env2.NAX_PROFILE);
|
|
18810
|
+
if (fromEnv.length)
|
|
18811
|
+
return fromEnv;
|
|
18812
|
+
const projectChain = await readProfileChainFromConfig(projectConfigDir(projectRoot));
|
|
18813
|
+
if (!isDefaultOnlyChain(projectChain))
|
|
18814
|
+
return projectChain;
|
|
18815
|
+
const globalChain = await readProfileChainFromConfig(globalConfigDir());
|
|
18816
|
+
if (!isDefaultOnlyChain(globalChain))
|
|
18817
|
+
return globalChain;
|
|
18818
|
+
return ["default"];
|
|
18819
|
+
}
|
|
18820
|
+
async function resolveProfileName(cliOptions, env2, projectRoot) {
|
|
18821
|
+
const chain = await resolveProfileNames(cliOptions, env2, projectRoot);
|
|
18822
|
+
return chain[chain.length - 1] ?? "default";
|
|
18787
18823
|
}
|
|
18788
18824
|
async function listAvailableProfileNames(projectRoot) {
|
|
18789
18825
|
const entries = await listProfiles(projectRoot);
|
|
@@ -18811,6 +18847,7 @@ async function listProfiles(projectRoot) {
|
|
|
18811
18847
|
return entries;
|
|
18812
18848
|
}
|
|
18813
18849
|
var init_profile = __esm(() => {
|
|
18850
|
+
init_errors();
|
|
18814
18851
|
init_paths();
|
|
18815
18852
|
});
|
|
18816
18853
|
|
|
@@ -18993,7 +19030,8 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
18993
19030
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
18994
19031
|
const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
18995
19032
|
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname(startDir) : startDir : process.cwd();
|
|
18996
|
-
const
|
|
19033
|
+
const profileChain = await resolveProfileNames(cliOverrides ?? {}, process.env, projectRoot);
|
|
19034
|
+
const overlayChain = profileChain.filter((name) => name && name !== "default");
|
|
18997
19035
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
18998
19036
|
let logger = null;
|
|
18999
19037
|
try {
|
|
@@ -19012,16 +19050,17 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
19012
19050
|
rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
19013
19051
|
}
|
|
19014
19052
|
}
|
|
19015
|
-
|
|
19016
|
-
const profileData = await loadProfile(
|
|
19053
|
+
for (const name of overlayChain) {
|
|
19054
|
+
const profileData = await loadProfile(name, projectRoot);
|
|
19017
19055
|
rawConfig = deepMergeConfig(rawConfig, profileData);
|
|
19018
|
-
await loadProfileEnv(
|
|
19056
|
+
await loadProfileEnv(name, projectRoot);
|
|
19019
19057
|
}
|
|
19020
19058
|
if (cliOverrides) {
|
|
19021
19059
|
rawConfig = deepMergeConfig(rawConfig, cliOverrides);
|
|
19022
19060
|
}
|
|
19023
|
-
rawConfig.profile =
|
|
19024
|
-
|
|
19061
|
+
rawConfig.profile = overlayChain.length > 0 ? overlayChain.join("+") : "default";
|
|
19062
|
+
rawConfig.profileChain = overlayChain;
|
|
19063
|
+
const hasMergedConfigs = globalConfRaw || projDir !== null || cliOverrides !== undefined || overlayChain.length > 0;
|
|
19025
19064
|
if (!hasMergedConfigs) {
|
|
19026
19065
|
return structuredClone(DEFAULT_CONFIG);
|
|
19027
19066
|
}
|
|
@@ -19051,11 +19090,14 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19051
19090
|
const logger = getLogger();
|
|
19052
19091
|
const resolvedRootConfigPath = resolve3(rootConfigPath);
|
|
19053
19092
|
const rootNaxDir = dirname(resolvedRootConfigPath);
|
|
19054
|
-
const profileKey = cliOverrides?.profile
|
|
19093
|
+
const profileKey = parseProfileList(cliOverrides?.profile).join(",");
|
|
19055
19094
|
const cacheKey = profileKey ? `${resolvedRootConfigPath}:${profileKey}` : resolvedRootConfigPath;
|
|
19056
19095
|
let rootConfigPromise = _rootConfigCache.get(cacheKey);
|
|
19057
19096
|
if (!rootConfigPromise) {
|
|
19058
|
-
rootConfigPromise = loadConfig(rootNaxDir, cliOverrides)
|
|
19097
|
+
rootConfigPromise = loadConfig(rootNaxDir, cliOverrides).catch((err) => {
|
|
19098
|
+
_rootConfigCache.delete(cacheKey);
|
|
19099
|
+
throw err;
|
|
19100
|
+
});
|
|
19059
19101
|
if (_rootConfigCache.size >= ROOT_CONFIG_CACHE_MAX) {
|
|
19060
19102
|
const firstKey = _rootConfigCache.keys().next().value;
|
|
19061
19103
|
if (firstKey !== undefined)
|
|
@@ -19081,22 +19123,29 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19081
19123
|
logger.debug("config", "Per-package config loaded", { packageConfigPath, packageDir });
|
|
19082
19124
|
const { profile: packageProfile, ...packageFields } = packageOverride;
|
|
19083
19125
|
let merged = mergePackageConfig(rootConfig, packageFields);
|
|
19084
|
-
|
|
19126
|
+
const packageChain = parseProfileList(packageProfile).filter((name) => name && name !== "default");
|
|
19127
|
+
if (packageChain.length > 0) {
|
|
19085
19128
|
const packageRoot = join3(repoRoot, packageDir);
|
|
19086
|
-
|
|
19087
|
-
const
|
|
19088
|
-
|
|
19129
|
+
let rawMerged = merged;
|
|
19130
|
+
for (const name of packageChain) {
|
|
19131
|
+
const profileData = await loadProfile(name, packageRoot);
|
|
19132
|
+
rawMerged = deepMergeConfig(rawMerged, profileData);
|
|
19133
|
+
}
|
|
19134
|
+
rawMerged.profile = packageChain.join("+");
|
|
19135
|
+
rawMerged.profileChain = packageChain;
|
|
19089
19136
|
rejectLegacyAgentKeys(rawMerged);
|
|
19090
19137
|
rejectLegacyRectificationKeys(rawMerged);
|
|
19091
19138
|
const result = NaxConfigSchema.safeParse(rawMerged);
|
|
19092
|
-
if (result.success) {
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
packageDir,
|
|
19097
|
-
packageProfile
|
|
19139
|
+
if (!result.success) {
|
|
19140
|
+
const errors3 = result.error.issues.map((err) => {
|
|
19141
|
+
const path = String(err.path.join("."));
|
|
19142
|
+
return path ? `${path}: ${err.message}` : err.message;
|
|
19098
19143
|
});
|
|
19144
|
+
throw new NaxError(`Per-package profile "${packageChain.join("+")}" produced an invalid config for package "${packageDir}":
|
|
19145
|
+
${errors3.join(`
|
|
19146
|
+
`)}`, "PER_PACKAGE_PROFILE_INVALID", { stage: "config", packageDir, profileChain: packageChain });
|
|
19099
19147
|
}
|
|
19148
|
+
merged = result.data;
|
|
19100
19149
|
}
|
|
19101
19150
|
return merged;
|
|
19102
19151
|
}
|
|
@@ -19504,6 +19553,7 @@ __export(exports_config, {
|
|
|
19504
19553
|
routingConfigSelector: () => routingConfigSelector,
|
|
19505
19554
|
reviewConfigSelector: () => reviewConfigSelector,
|
|
19506
19555
|
resolveTestStrategy: () => resolveTestStrategy,
|
|
19556
|
+
resolveProfileNames: () => resolveProfileNames,
|
|
19507
19557
|
resolveProfileName: () => resolveProfileName,
|
|
19508
19558
|
resolveModelForAgent: () => resolveModelForAgent,
|
|
19509
19559
|
resolveModel: () => resolveModel,
|
|
@@ -19514,9 +19564,11 @@ __export(exports_config, {
|
|
|
19514
19564
|
qualityConfigSelector: () => qualityConfigSelector,
|
|
19515
19565
|
promptLoaderConfigSelector: () => promptLoaderConfigSelector,
|
|
19516
19566
|
projectConfigDir: () => projectConfigDir,
|
|
19567
|
+
profileOverrideFromConfig: () => profileOverrideFromConfig,
|
|
19517
19568
|
precheckConfigSelector: () => precheckConfigSelector,
|
|
19518
19569
|
planConfigSelector: () => planConfigSelector,
|
|
19519
19570
|
pickSelector: () => pickSelector,
|
|
19571
|
+
parseProfileList: () => parseProfileList,
|
|
19520
19572
|
mergePackageConfig: () => mergePackageConfig,
|
|
19521
19573
|
loadProfileEnv: () => loadProfileEnv,
|
|
19522
19574
|
loadProfile: () => loadProfile,
|
|
@@ -21286,12 +21338,13 @@ function parseAcpxJsonLine(line, state) {
|
|
|
21286
21338
|
}
|
|
21287
21339
|
return;
|
|
21288
21340
|
}
|
|
21289
|
-
if (event.
|
|
21341
|
+
if (event.result && typeof event.result === "string") {
|
|
21342
|
+
state.text = event.result;
|
|
21343
|
+
} else if (event.content && typeof event.content === "string") {
|
|
21290
21344
|
state.text += event.content;
|
|
21291
|
-
if (event.text && typeof event.text === "string")
|
|
21345
|
+
} else if (event.text && typeof event.text === "string") {
|
|
21292
21346
|
state.text += event.text;
|
|
21293
|
-
|
|
21294
|
-
state.text = event.result;
|
|
21347
|
+
}
|
|
21295
21348
|
if (event.cumulative_token_usage)
|
|
21296
21349
|
state.tokenUsage = event.cumulative_token_usage;
|
|
21297
21350
|
if (event.usage) {
|
|
@@ -21626,7 +21679,11 @@ class AgentManager {
|
|
|
21626
21679
|
_registry;
|
|
21627
21680
|
_unavailable = new Map;
|
|
21628
21681
|
_prunedFallback = new Set;
|
|
21629
|
-
_emitter =
|
|
21682
|
+
_emitter = (() => {
|
|
21683
|
+
const ee = new EventEmitter;
|
|
21684
|
+
ee.setMaxListeners(MAX_EMITTER_LISTENERS);
|
|
21685
|
+
return ee;
|
|
21686
|
+
})();
|
|
21630
21687
|
_logger;
|
|
21631
21688
|
_middleware;
|
|
21632
21689
|
_runId;
|
|
@@ -22225,6 +22282,9 @@ class AgentManager {
|
|
|
22225
22282
|
throw err;
|
|
22226
22283
|
}
|
|
22227
22284
|
}
|
|
22285
|
+
close() {
|
|
22286
|
+
this._emitter.removeAllListeners();
|
|
22287
|
+
}
|
|
22228
22288
|
_resolveRegistry() {
|
|
22229
22289
|
this._registry ??= createAgentRegistry(this._config);
|
|
22230
22290
|
return this._registry;
|
|
@@ -22233,7 +22293,7 @@ class AgentManager {
|
|
|
22233
22293
|
this._emitter.emit(event, payload);
|
|
22234
22294
|
}
|
|
22235
22295
|
}
|
|
22236
|
-
var _agentManagerDeps;
|
|
22296
|
+
var MAX_EMITTER_LISTENERS = 100, _agentManagerDeps;
|
|
22237
22297
|
var init_manager = __esm(() => {
|
|
22238
22298
|
init_errors();
|
|
22239
22299
|
init_logger2();
|
|
@@ -25144,7 +25204,7 @@ function extractSearchTerms(sourceFile) {
|
|
|
25144
25204
|
const basename4 = parts[parts.length - 1];
|
|
25145
25205
|
return [`/${basename4}`, withoutExt];
|
|
25146
25206
|
}
|
|
25147
|
-
async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
25207
|
+
async function importGrepFallback(sourceFiles, workdir, testFilePatterns, maxScanFiles = MAX_GREP_TEST_FILES) {
|
|
25148
25208
|
if (sourceFiles.length === 0 || testFilePatterns.length === 0)
|
|
25149
25209
|
return [];
|
|
25150
25210
|
const searchTerms = sourceFiles.flatMap(extractSearchTerms);
|
|
@@ -25152,11 +25212,11 @@ async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
|
25152
25212
|
outer:
|
|
25153
25213
|
for (const pattern of testFilePatterns) {
|
|
25154
25214
|
const g = _bunDeps.glob(pattern);
|
|
25155
|
-
for await (const file3 of g.scan(workdir)) {
|
|
25215
|
+
for await (const file3 of g.scan({ cwd: workdir, absolute: false })) {
|
|
25156
25216
|
testFilePaths.push(`${workdir}/${file3}`);
|
|
25157
|
-
if (testFilePaths.length >=
|
|
25217
|
+
if (testFilePaths.length >= maxScanFiles) {
|
|
25158
25218
|
getSafeLogger()?.debug("smart-runner", "import-grep glob cap reached \u2014 results truncated", {
|
|
25159
|
-
cap:
|
|
25219
|
+
cap: maxScanFiles
|
|
25160
25220
|
});
|
|
25161
25221
|
break outer;
|
|
25162
25222
|
}
|
|
@@ -25404,7 +25464,7 @@ async function selectScopedTests(input) {
|
|
|
25404
25464
|
if (smartCfg.fallback !== "import-grep") {
|
|
25405
25465
|
return fullSuite();
|
|
25406
25466
|
}
|
|
25407
|
-
const pass2Files = await _scopedSelectionDeps.importGrepFallback(nonTestFiles, input.workdir, mappingGlobs);
|
|
25467
|
+
const pass2Files = await _scopedSelectionDeps.importGrepFallback(nonTestFiles, input.workdir, mappingGlobs, smartCfg.maxScanFiles);
|
|
25408
25468
|
if (pass2Files.length > threshold) {
|
|
25409
25469
|
logger.warn("verify[scoped]", `Scoped test file count ${pass2Files.length} exceeds threshold ${threshold} \u2014 falling back to full suite`, { storyId: input.storyId });
|
|
25410
25470
|
return fullSuite({ scopeTestFallback: true, thresholdFallback: true });
|
|
@@ -25425,7 +25485,8 @@ var init_scoped_selection = __esm(() => {
|
|
|
25425
25485
|
DEFAULT_SMART_RUNNER_CONFIG = {
|
|
25426
25486
|
enabled: true,
|
|
25427
25487
|
testFilePatterns: [...DEFAULT_TEST_FILE_PATTERNS],
|
|
25428
|
-
fallback: "import-grep"
|
|
25488
|
+
fallback: "import-grep",
|
|
25489
|
+
maxScanFiles: MAX_GREP_TEST_FILES
|
|
25429
25490
|
};
|
|
25430
25491
|
_scopedSelectionDeps = {
|
|
25431
25492
|
getChangedNonTestFiles: _smartRunnerDeps.getChangedNonTestFiles,
|
|
@@ -27573,7 +27634,7 @@ class SessionScratchProvider {
|
|
|
27573
27634
|
if (!dirs || dirs.length === 0) {
|
|
27574
27635
|
return { chunks: [], pullTools: [] };
|
|
27575
27636
|
}
|
|
27576
|
-
const ignoreMatchers = await resolveNaxIgnorePatterns(request.repoRoot, request.packageDir);
|
|
27637
|
+
const ignoreMatchers = request.naxIgnoreIndex?.getMatchers(request.packageDir) ?? await resolveNaxIgnorePatterns(request.repoRoot, request.packageDir);
|
|
27577
27638
|
const chunks = [];
|
|
27578
27639
|
for (const dir of dirs) {
|
|
27579
27640
|
const chunk = await readScratchDir(dir, request.agentId, ignoreMatchers);
|
|
@@ -28553,6 +28614,7 @@ async function scanTestFiles(options) {
|
|
|
28553
28614
|
const patterns = deriveTestPatterns(contextFiles, resolvedTestGlobs);
|
|
28554
28615
|
allowedBasenames = new Set(patterns);
|
|
28555
28616
|
}
|
|
28617
|
+
const maxScanFiles = options.maxScanFiles ?? DEFAULT_MAX_SCAN_FILES;
|
|
28556
28618
|
const glob = new Glob2(testPattern);
|
|
28557
28619
|
const files = [];
|
|
28558
28620
|
for await (const filePath of glob.scan({ cwd: scanDir, absolute: false })) {
|
|
@@ -28562,6 +28624,13 @@ async function scanTestFiles(options) {
|
|
|
28562
28624
|
continue;
|
|
28563
28625
|
}
|
|
28564
28626
|
}
|
|
28627
|
+
if (files.length >= maxScanFiles) {
|
|
28628
|
+
getLogger().debug("test-scanner", "Glob cap reached \u2014 results truncated", {
|
|
28629
|
+
cap: maxScanFiles,
|
|
28630
|
+
scanDir
|
|
28631
|
+
});
|
|
28632
|
+
break;
|
|
28633
|
+
}
|
|
28565
28634
|
const fullPath = path.join(scanDir, filePath);
|
|
28566
28635
|
try {
|
|
28567
28636
|
const source = await Bun.file(fullPath).text();
|
|
@@ -28658,6 +28727,7 @@ async function generateTestCoverageSummary(options) {
|
|
|
28658
28727
|
const tokens = estimateTokens4(summary);
|
|
28659
28728
|
return { files, totalTests, summary, tokens };
|
|
28660
28729
|
}
|
|
28730
|
+
var DEFAULT_MAX_SCAN_FILES = 200;
|
|
28661
28731
|
var init_test_scanner = __esm(() => {
|
|
28662
28732
|
init_logger2();
|
|
28663
28733
|
init_conventions();
|
|
@@ -28692,12 +28762,14 @@ class TestCoverageProvider {
|
|
|
28692
28762
|
const resolved = await _testCoverageProviderDeps.resolveTestFilePatterns(this.config, request.repoRoot, relPackageDir);
|
|
28693
28763
|
const contextFiles = _testCoverageProviderDeps.getContextFiles(this.story);
|
|
28694
28764
|
const globs = resolved.patterns ?? resolved.globs;
|
|
28765
|
+
const smartCfg = coerceSmartRunner(this.config.execution?.smartTestRunner);
|
|
28695
28766
|
const scanOptions = {
|
|
28696
28767
|
workdir: request.packageDir,
|
|
28697
28768
|
testDir: tcConfig.testDir,
|
|
28698
28769
|
maxTokens: tcConfig.maxTokens ?? 500,
|
|
28699
28770
|
detail: tcConfig.detail ?? "names-and-counts",
|
|
28700
28771
|
scopeToStory: tcConfig.scopeToStory ?? true,
|
|
28772
|
+
maxScanFiles: smartCfg.maxScanFiles,
|
|
28701
28773
|
contextFiles,
|
|
28702
28774
|
resolvedTestGlobs: globs
|
|
28703
28775
|
};
|
|
@@ -28729,6 +28801,7 @@ class TestCoverageProvider {
|
|
|
28729
28801
|
}
|
|
28730
28802
|
var _testCoverageProviderDeps;
|
|
28731
28803
|
var init_test_coverage = __esm(() => {
|
|
28804
|
+
init_test_runners();
|
|
28732
28805
|
init_logger2();
|
|
28733
28806
|
init_prd();
|
|
28734
28807
|
init_resolver();
|
|
@@ -30026,10 +30099,9 @@ async function gitLsFiles2(workdir) {
|
|
|
30026
30099
|
stdout: "pipe",
|
|
30027
30100
|
stderr: "pipe"
|
|
30028
30101
|
});
|
|
30029
|
-
const exitCode = await proc.exited;
|
|
30102
|
+
const [exitCode, output] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
30030
30103
|
if (exitCode !== 0)
|
|
30031
30104
|
return null;
|
|
30032
|
-
const output = await new Response(proc.stdout).text();
|
|
30033
30105
|
return output.split(`
|
|
30034
30106
|
`).filter(Boolean);
|
|
30035
30107
|
} catch {
|
|
@@ -33167,12 +33239,16 @@ async function collectDiff(workdir, storyGitRef, excludePatterns, options) {
|
|
|
33167
33239
|
stdout: "pipe",
|
|
33168
33240
|
stderr: "pipe"
|
|
33169
33241
|
});
|
|
33170
|
-
const [exitCode, stdout] = await Promise.all([
|
|
33242
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
33171
33243
|
proc.exited,
|
|
33172
33244
|
new Response(proc.stdout).text(),
|
|
33173
33245
|
new Response(proc.stderr).text()
|
|
33174
33246
|
]);
|
|
33175
|
-
|
|
33247
|
+
if (exitCode !== 0) {
|
|
33248
|
+
getSafeLogger()?.warn("diff-utils", "git diff failed \u2014 skipping review diff", { storyGitRef, stderr });
|
|
33249
|
+
return null;
|
|
33250
|
+
}
|
|
33251
|
+
return stdout;
|
|
33176
33252
|
}
|
|
33177
33253
|
async function collectDiffStat(workdir, storyGitRef, options) {
|
|
33178
33254
|
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
@@ -33423,6 +33499,9 @@ async function prepareSemanticReviewInput(args) {
|
|
|
33423
33499
|
return { effectiveRef, stat, diff: undefined, excludePatterns };
|
|
33424
33500
|
}
|
|
33425
33501
|
const rawDiff = await collectDiff(workdir, effectiveRef, excludePatterns, { naxIgnoreIndex, packageDir });
|
|
33502
|
+
if (rawDiff === null) {
|
|
33503
|
+
return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "git diff failed" };
|
|
33504
|
+
}
|
|
33426
33505
|
const diff = truncateDiff(rawDiff, rawDiff.length > DIFF_CAP_BYTES ? stat : undefined);
|
|
33427
33506
|
if (!diff) {
|
|
33428
33507
|
return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "no production code changes" };
|
|
@@ -41050,10 +41129,9 @@ async function listChangedFiles(workdir, baseRef) {
|
|
|
41050
41129
|
stdout: "pipe",
|
|
41051
41130
|
stderr: "pipe"
|
|
41052
41131
|
});
|
|
41053
|
-
const exitCode = await proc.exited;
|
|
41132
|
+
const [exitCode, output] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
41054
41133
|
if (exitCode !== 0)
|
|
41055
41134
|
return null;
|
|
41056
|
-
const output = await new Response(proc.stdout).text();
|
|
41057
41135
|
return output.split(`
|
|
41058
41136
|
`).map((line) => line.trim()).filter(Boolean).map(normalizePath3);
|
|
41059
41137
|
}
|
|
@@ -45726,6 +45804,8 @@ class PidRegistry {
|
|
|
45726
45804
|
pidsFilePath;
|
|
45727
45805
|
pids = new Set;
|
|
45728
45806
|
frozen = false;
|
|
45807
|
+
_writing = false;
|
|
45808
|
+
_pendingWrite = false;
|
|
45729
45809
|
writeQueueTail = Promise.resolve();
|
|
45730
45810
|
constructor(workdir, _platform) {
|
|
45731
45811
|
this.workdir = workdir;
|
|
@@ -46023,11 +46103,22 @@ class PidRegistry {
|
|
|
46023
46103
|
}
|
|
46024
46104
|
}
|
|
46025
46105
|
enqueueWrite() {
|
|
46026
|
-
|
|
46106
|
+
if (this._writing) {
|
|
46107
|
+
this._pendingWrite = true;
|
|
46108
|
+
return this.writeQueueTail;
|
|
46109
|
+
}
|
|
46110
|
+
this._writing = true;
|
|
46111
|
+
this.writeQueueTail = this.writePidsFile().catch((err) => {
|
|
46027
46112
|
getSafeLogger()?.warn("pid-registry", "Failed to flush PID file \u2014 on-disk registry may be stale", {
|
|
46028
46113
|
error: errorMessage(err)
|
|
46029
46114
|
});
|
|
46030
|
-
}))
|
|
46115
|
+
}).then(async () => {
|
|
46116
|
+
this._writing = false;
|
|
46117
|
+
if (this._pendingWrite) {
|
|
46118
|
+
this._pendingWrite = false;
|
|
46119
|
+
await this.enqueueWrite();
|
|
46120
|
+
}
|
|
46121
|
+
});
|
|
46031
46122
|
return this.writeQueueTail;
|
|
46032
46123
|
}
|
|
46033
46124
|
}
|
|
@@ -46589,6 +46680,7 @@ class SessionManager {
|
|
|
46589
46680
|
}
|
|
46590
46681
|
this._busySessions.delete(handle.id);
|
|
46591
46682
|
this._cancelledSessions.delete(handle.id);
|
|
46683
|
+
this._clearWatchdogCancelledCalls(handle.id);
|
|
46592
46684
|
}
|
|
46593
46685
|
async sendPrompt(handle, prompt, opts) {
|
|
46594
46686
|
if (this._cancelledSessions.has(handle.id)) {
|
|
@@ -46673,6 +46765,10 @@ class SessionManager {
|
|
|
46673
46765
|
sweepOrphans(ttlMs = DEFAULT_ORPHAN_TTL_MS) {
|
|
46674
46766
|
return sweepOrphansImpl(this._sessions, ttlMs);
|
|
46675
46767
|
}
|
|
46768
|
+
close() {
|
|
46769
|
+
this._agentStreamUnsubscribe?.();
|
|
46770
|
+
this._agentStreamUnsubscribe = undefined;
|
|
46771
|
+
}
|
|
46676
46772
|
}
|
|
46677
46773
|
var NULL_PROTOCOL_IDS;
|
|
46678
46774
|
var init_manager2 = __esm(() => {
|
|
@@ -46937,6 +47033,9 @@ function createRuntime(config2, workdir, opts) {
|
|
|
46937
47033
|
if (opts?.parentSignal && parentAbortHandler) {
|
|
46938
47034
|
opts.parentSignal.removeEventListener("abort", parentAbortHandler);
|
|
46939
47035
|
}
|
|
47036
|
+
agentManager.close();
|
|
47037
|
+
if (sessionManager instanceof SessionManager)
|
|
47038
|
+
sessionManager.close();
|
|
46940
47039
|
const results = await Promise.allSettled([promptAuditor.flush(), reviewAuditor.flush(), costAggregator.drain()]);
|
|
46941
47040
|
for (const r of results) {
|
|
46942
47041
|
if (r.status === "rejected") {
|
|
@@ -52475,17 +52574,18 @@ async function saveRunMetrics(outputDir, runMetrics) {
|
|
|
52475
52574
|
}
|
|
52476
52575
|
}
|
|
52477
52576
|
const hasTokenData = totalInputTokens > 0 || totalOutputTokens > 0 || totalCacheReadInputTokens > 0 || totalCacheCreationInputTokens > 0;
|
|
52478
|
-
|
|
52479
|
-
runMetrics
|
|
52577
|
+
const finalMetrics = hasTokenData ? {
|
|
52578
|
+
...runMetrics,
|
|
52579
|
+
totalTokens: new TokenUsage({
|
|
52480
52580
|
inputTokens: totalInputTokens,
|
|
52481
52581
|
outputTokens: totalOutputTokens,
|
|
52482
52582
|
cacheReadInputTokens: totalCacheReadInputTokens,
|
|
52483
52583
|
cacheCreationInputTokens: totalCacheCreationInputTokens
|
|
52484
|
-
})
|
|
52485
|
-
}
|
|
52584
|
+
})
|
|
52585
|
+
} : runMetrics;
|
|
52486
52586
|
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
52487
52587
|
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
52488
|
-
allMetrics.push(
|
|
52588
|
+
allMetrics.push(finalMetrics);
|
|
52489
52589
|
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
52490
52590
|
}
|
|
52491
52591
|
async function loadRunMetrics(outputDir) {
|
|
@@ -54652,29 +54752,27 @@ async function rollbackToRef(workdir, ref) {
|
|
|
54652
54752
|
stdout: "pipe",
|
|
54653
54753
|
stderr: "pipe"
|
|
54654
54754
|
});
|
|
54655
|
-
const exitCode = await resetProc.exited;
|
|
54755
|
+
const [exitCode, resetStderr] = await Promise.all([resetProc.exited, new Response(resetProc.stderr).text()]);
|
|
54656
54756
|
if (exitCode !== 0) {
|
|
54657
|
-
|
|
54658
|
-
|
|
54659
|
-
throw new Error(`Git rollback failed: ${stderr}`);
|
|
54757
|
+
logger.error("tdd", "Failed to rollback git changes", { ref, stderr: resetStderr });
|
|
54758
|
+
throw new Error(`Git rollback failed: ${resetStderr}`);
|
|
54660
54759
|
}
|
|
54661
54760
|
const cleanProc = _rollbackDeps.spawn(["git", "clean", "-fd"], {
|
|
54662
54761
|
cwd: workdir,
|
|
54663
54762
|
stdout: "pipe",
|
|
54664
54763
|
stderr: "pipe"
|
|
54665
54764
|
});
|
|
54666
|
-
const cleanExitCode = await cleanProc.exited;
|
|
54765
|
+
const [cleanExitCode, cleanStderr] = await Promise.all([cleanProc.exited, new Response(cleanProc.stderr).text()]);
|
|
54667
54766
|
if (cleanExitCode !== 0) {
|
|
54668
|
-
|
|
54669
|
-
logger.warn("tdd", "Failed to clean untracked files", { stderr });
|
|
54767
|
+
logger.warn("tdd", "Failed to clean untracked files", { stderr: cleanStderr });
|
|
54670
54768
|
}
|
|
54671
54769
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
54672
54770
|
}
|
|
54673
54771
|
async function captureSnapshotRef(workdir, storyId) {
|
|
54674
54772
|
await _rollbackDeps.autoCommitIfDirty(workdir, "non-blocking-fix-snapshot", "snapshot", storyId);
|
|
54675
54773
|
const proc = _rollbackDeps.spawn(["git", "rev-parse", "HEAD"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
54676
|
-
const
|
|
54677
|
-
const
|
|
54774
|
+
const [exitCode, shaRaw] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
54775
|
+
const sha = shaRaw.trim();
|
|
54678
54776
|
if (exitCode !== 0) {
|
|
54679
54777
|
throw new NaxError("git rev-parse HEAD failed in non-blocking-fix snapshot", "SNAPSHOT_REF_FAILED", {
|
|
54680
54778
|
storyId,
|
|
@@ -55664,7 +55762,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
55664
55762
|
if (pkgQuality?.commands?.formatFix || pkgQuality?.commands?.formatFixScoped) {
|
|
55665
55763
|
strategies.push(makeMechanicalFormatFixStrategy());
|
|
55666
55764
|
}
|
|
55667
|
-
|
|
55765
|
+
const fullSuiteGatePhasePresent = Boolean(inputs.fullSuiteGate) && (isThreeSession || regressionMode === "per-story");
|
|
55766
|
+
const verifyScopedPhasePresent = !isThreeSession && Boolean(inputs.verifyScoped);
|
|
55767
|
+
if (fullSuiteGatePhasePresent || verifyScopedPhasePresent) {
|
|
55668
55768
|
strategies.push(makeFullSuiteRectifyStrategy(story, config2, sink));
|
|
55669
55769
|
}
|
|
55670
55770
|
if (config2.quality.autofix?.enabled !== false) {
|
|
@@ -60325,7 +60425,7 @@ var package_default;
|
|
|
60325
60425
|
var init_package = __esm(() => {
|
|
60326
60426
|
package_default = {
|
|
60327
60427
|
name: "@nathapp/nax",
|
|
60328
|
-
version: "0.70.0-canary.
|
|
60428
|
+
version: "0.70.0-canary.5",
|
|
60329
60429
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
60330
60430
|
type: "module",
|
|
60331
60431
|
bin: {
|
|
@@ -60420,8 +60520,8 @@ var init_version = __esm(() => {
|
|
|
60420
60520
|
NAX_VERSION = package_default.version;
|
|
60421
60521
|
NAX_COMMIT = (() => {
|
|
60422
60522
|
try {
|
|
60423
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
60424
|
-
return "
|
|
60523
|
+
if (/^[0-9a-f]{6,10}$/.test("e8c2ab46"))
|
|
60524
|
+
return "e8c2ab46";
|
|
60425
60525
|
} catch {}
|
|
60426
60526
|
try {
|
|
60427
60527
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -60441,11 +60541,11 @@ var init_version = __esm(() => {
|
|
|
60441
60541
|
|
|
60442
60542
|
// src/execution/crash-heartbeat.ts
|
|
60443
60543
|
import { appendFileSync as appendFileSync2 } from "fs";
|
|
60444
|
-
async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60544
|
+
async function heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60445
60545
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
60446
|
-
while (
|
|
60546
|
+
while (gen === _heartbeatGen && _heartbeatActive) {
|
|
60447
60547
|
await _heartbeatDeps.sleep(60000);
|
|
60448
|
-
if (!
|
|
60548
|
+
if (gen !== _heartbeatGen || !_heartbeatActive)
|
|
60449
60549
|
break;
|
|
60450
60550
|
try {
|
|
60451
60551
|
logger?.debug("crash-recovery", "Heartbeat");
|
|
@@ -60474,9 +60574,9 @@ async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFil
|
|
|
60474
60574
|
}
|
|
60475
60575
|
function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60476
60576
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
60477
|
-
|
|
60478
|
-
|
|
60479
|
-
heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath).catch((err) => {
|
|
60577
|
+
_heartbeatActive = true;
|
|
60578
|
+
const gen = ++_heartbeatGen;
|
|
60579
|
+
heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jsonlFilePath).catch((err) => {
|
|
60480
60580
|
_heartbeatDeps.getSafeLogger()?.warn("crash-recovery", "Heartbeat loop crashed; status updates stopped", {
|
|
60481
60581
|
error: err instanceof Error ? err.message : String(err)
|
|
60482
60582
|
});
|
|
@@ -60484,12 +60584,13 @@ function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath
|
|
|
60484
60584
|
logger?.debug("crash-recovery", "Heartbeat started (60s interval)");
|
|
60485
60585
|
}
|
|
60486
60586
|
function stopHeartbeat() {
|
|
60487
|
-
if (
|
|
60488
|
-
|
|
60587
|
+
if (_heartbeatActive) {
|
|
60588
|
+
_heartbeatActive = false;
|
|
60589
|
+
_heartbeatGen++;
|
|
60489
60590
|
getSafeLogger()?.debug("crash-recovery", "Heartbeat stopped");
|
|
60490
60591
|
}
|
|
60491
60592
|
}
|
|
60492
|
-
var _heartbeatDeps,
|
|
60593
|
+
var _heartbeatDeps, _heartbeatGen = 0, _heartbeatActive = false;
|
|
60493
60594
|
var init_crash_heartbeat = __esm(() => {
|
|
60494
60595
|
init_logger2();
|
|
60495
60596
|
_heartbeatDeps = {
|
|
@@ -62656,9 +62757,8 @@ ${missing.join(`
|
|
|
62656
62757
|
stdout: "pipe",
|
|
62657
62758
|
stderr: "pipe"
|
|
62658
62759
|
});
|
|
62659
|
-
const exitCode = await proc.exited;
|
|
62760
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62660
62761
|
if (exitCode !== 0) {
|
|
62661
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62662
62762
|
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
62663
62763
|
}
|
|
62664
62764
|
} catch (error48) {
|
|
@@ -62691,9 +62791,8 @@ ${missing.join(`
|
|
|
62691
62791
|
stdout: "pipe",
|
|
62692
62792
|
stderr: "pipe"
|
|
62693
62793
|
});
|
|
62694
|
-
const exitCode = await proc.exited;
|
|
62794
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62695
62795
|
if (exitCode !== 0) {
|
|
62696
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62697
62796
|
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
62698
62797
|
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
62699
62798
|
}
|
|
@@ -62711,9 +62810,8 @@ ${missing.join(`
|
|
|
62711
62810
|
stdout: "pipe",
|
|
62712
62811
|
stderr: "pipe"
|
|
62713
62812
|
});
|
|
62714
|
-
const exitCode = await proc.exited;
|
|
62813
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62715
62814
|
if (exitCode !== 0) {
|
|
62716
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62717
62815
|
if (!stderr.includes("not found")) {
|
|
62718
62816
|
const logger = getSafeLogger();
|
|
62719
62817
|
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
@@ -62733,12 +62831,14 @@ ${missing.join(`
|
|
|
62733
62831
|
stdout: "pipe",
|
|
62734
62832
|
stderr: "pipe"
|
|
62735
62833
|
});
|
|
62736
|
-
const exitCode = await
|
|
62834
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
62835
|
+
proc.exited,
|
|
62836
|
+
new Response(proc.stderr).text(),
|
|
62837
|
+
new Response(proc.stdout).text()
|
|
62838
|
+
]);
|
|
62737
62839
|
if (exitCode !== 0) {
|
|
62738
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62739
62840
|
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
62740
62841
|
}
|
|
62741
|
-
const stdout = await new Response(proc.stdout).text();
|
|
62742
62842
|
return this.parseWorktreeList(stdout);
|
|
62743
62843
|
} catch (error48) {
|
|
62744
62844
|
if (error48 instanceof Error) {
|
|
@@ -62850,9 +62950,11 @@ class MergeEngine {
|
|
|
62850
62950
|
stdout: "pipe",
|
|
62851
62951
|
stderr: "pipe"
|
|
62852
62952
|
});
|
|
62853
|
-
const exitCode = await
|
|
62854
|
-
|
|
62855
|
-
|
|
62953
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
62954
|
+
mergeProc.exited,
|
|
62955
|
+
new Response(mergeProc.stderr).text(),
|
|
62956
|
+
new Response(mergeProc.stdout).text()
|
|
62957
|
+
]);
|
|
62856
62958
|
if (exitCode === 0) {
|
|
62857
62959
|
try {
|
|
62858
62960
|
await this.worktreeManager.remove(projectRoot, storyId);
|
|
@@ -62979,19 +63081,25 @@ ${stderr}`;
|
|
|
62979
63081
|
stdout: "pipe",
|
|
62980
63082
|
stderr: "pipe"
|
|
62981
63083
|
});
|
|
62982
|
-
const exitCode = await
|
|
63084
|
+
const [exitCode, currentBranchRaw] = await Promise.all([
|
|
63085
|
+
currentBranchProc.exited,
|
|
63086
|
+
new Response(currentBranchProc.stdout).text()
|
|
63087
|
+
]);
|
|
62983
63088
|
if (exitCode !== 0) {
|
|
62984
63089
|
throw new Error("Failed to get current branch");
|
|
62985
63090
|
}
|
|
62986
|
-
const currentBranch =
|
|
63091
|
+
const currentBranch = currentBranchRaw.trim();
|
|
62987
63092
|
const rebaseProc = _mergeDeps.spawn(["git", "rebase", currentBranch], {
|
|
62988
63093
|
cwd: worktreePath,
|
|
62989
63094
|
stdout: "pipe",
|
|
62990
63095
|
stderr: "pipe"
|
|
62991
63096
|
});
|
|
62992
|
-
const rebaseExitCode = await
|
|
63097
|
+
const [rebaseExitCode, rebaseStderr] = await Promise.all([
|
|
63098
|
+
rebaseProc.exited,
|
|
63099
|
+
new Response(rebaseProc.stderr).text()
|
|
63100
|
+
]);
|
|
62993
63101
|
if (rebaseExitCode !== 0) {
|
|
62994
|
-
const stderr =
|
|
63102
|
+
const stderr = rebaseStderr;
|
|
62995
63103
|
const abortProc = _mergeDeps.spawn(["git", "rebase", "--abort"], {
|
|
62996
63104
|
cwd: worktreePath,
|
|
62997
63105
|
stdout: "pipe",
|
|
@@ -63014,11 +63122,10 @@ ${stderr}`;
|
|
|
63014
63122
|
stdout: "pipe",
|
|
63015
63123
|
stderr: "pipe"
|
|
63016
63124
|
});
|
|
63017
|
-
const exitCode = await proc.exited;
|
|
63125
|
+
const [exitCode, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
63018
63126
|
if (exitCode !== 0) {
|
|
63019
63127
|
return [];
|
|
63020
63128
|
}
|
|
63021
|
-
const stdout = await new Response(proc.stdout).text();
|
|
63022
63129
|
return stdout.trim().split(`
|
|
63023
63130
|
`).filter((line) => line.length > 0);
|
|
63024
63131
|
} catch {
|
|
@@ -63758,7 +63865,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
63758
63865
|
}
|
|
63759
63866
|
}
|
|
63760
63867
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
63761
|
-
const profileOverride = ctx.config
|
|
63868
|
+
const profileOverride = profileOverrideFromConfig(ctx.config);
|
|
63762
63869
|
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join81(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
63763
63870
|
let dependencyContext;
|
|
63764
63871
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
@@ -63904,6 +64011,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
63904
64011
|
}
|
|
63905
64012
|
var _iterationRunnerDeps;
|
|
63906
64013
|
var init_iteration_runner = __esm(() => {
|
|
64014
|
+
init_config();
|
|
63907
64015
|
init_loader();
|
|
63908
64016
|
init_logger2();
|
|
63909
64017
|
init_runner4();
|
|
@@ -64134,7 +64242,7 @@ async function runParallelBatch(options) {
|
|
|
64134
64242
|
worktreePaths.set(story.id, path22.join(workdir, ".nax-wt", story.id));
|
|
64135
64243
|
}
|
|
64136
64244
|
const rootConfigPath = path22.join(workdir, ".nax", "config.json");
|
|
64137
|
-
const profileOverride = config2
|
|
64245
|
+
const profileOverride = profileOverrideFromConfig(config2);
|
|
64138
64246
|
const storyEffectiveConfigs = new Map;
|
|
64139
64247
|
const configResults = await Promise.allSettled(stories.filter((story) => story.workdir).map(async (story) => {
|
|
64140
64248
|
try {
|
|
@@ -64295,6 +64403,7 @@ async function runParallelBatch(options) {
|
|
|
64295
64403
|
}
|
|
64296
64404
|
var _parallelBatchDeps;
|
|
64297
64405
|
var init_parallel_batch = __esm(() => {
|
|
64406
|
+
init_config();
|
|
64298
64407
|
init_loader();
|
|
64299
64408
|
init_logger2();
|
|
64300
64409
|
init_dependencies();
|
|
@@ -64361,13 +64470,15 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64361
64470
|
};
|
|
64362
64471
|
for (const fn of _prevRunUnsubscribers)
|
|
64363
64472
|
fn();
|
|
64364
|
-
_prevRunUnsubscribers = [
|
|
64473
|
+
_prevRunUnsubscribers = [];
|
|
64474
|
+
const thisRunUnsubscribers = [
|
|
64365
64475
|
wireHooks(pipelineEventBus, ctx.hooks, ctx.workdir, ctx.feature),
|
|
64366
64476
|
wireReporters(pipelineEventBus, ctx.pluginRegistry, ctx.runId, ctx.startTime),
|
|
64367
64477
|
wireInteraction(pipelineEventBus, ctx.interactionChain, ctx.config),
|
|
64368
64478
|
wireEventsWriter(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir),
|
|
64369
64479
|
wireRegistry(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir, ctx.runtime.outputDir)
|
|
64370
64480
|
];
|
|
64481
|
+
_prevRunUnsubscribers = thisRunUnsubscribers;
|
|
64371
64482
|
pipelineEventBus.emit({
|
|
64372
64483
|
type: "run:started",
|
|
64373
64484
|
feature: ctx.feature,
|
|
@@ -64384,6 +64495,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64384
64495
|
deferredReview
|
|
64385
64496
|
});
|
|
64386
64497
|
startHeartbeat(ctx.statusWriter, () => totalCost, () => iterations, ctx.logFilePath);
|
|
64498
|
+
let _executeThrew = false;
|
|
64387
64499
|
try {
|
|
64388
64500
|
if (isComplete(prd)) {
|
|
64389
64501
|
logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
|
|
@@ -64790,7 +64902,16 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64790
64902
|
}, ctx.eventEmitter);
|
|
64791
64903
|
}
|
|
64792
64904
|
return buildResult2("max-iterations");
|
|
64793
|
-
}
|
|
64905
|
+
} catch (err) {
|
|
64906
|
+
_executeThrew = true;
|
|
64907
|
+
throw err;
|
|
64908
|
+
} finally {
|
|
64909
|
+
if (_executeThrew && _prevRunUnsubscribers === thisRunUnsubscribers) {
|
|
64910
|
+
for (const fn of thisRunUnsubscribers)
|
|
64911
|
+
fn();
|
|
64912
|
+
_prevRunUnsubscribers = [];
|
|
64913
|
+
}
|
|
64914
|
+
}
|
|
64794
64915
|
}
|
|
64795
64916
|
function reconcileBatchOutcome(prd, batchResult) {
|
|
64796
64917
|
for (const story of batchResult.completed) {
|
|
@@ -65882,6 +66003,7 @@ async function setupRun(options) {
|
|
|
65882
66003
|
const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
|
|
65883
66004
|
const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
|
|
65884
66005
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins, isTestFileFn);
|
|
66006
|
+
clearCache();
|
|
65885
66007
|
logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
|
|
65886
66008
|
plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides }))
|
|
65887
66009
|
});
|
|
@@ -65935,6 +66057,7 @@ async function setupRun(options) {
|
|
|
65935
66057
|
var _runSetupDeps;
|
|
65936
66058
|
var init_run_setup = __esm(() => {
|
|
65937
66059
|
init_pipeline();
|
|
66060
|
+
init_routing();
|
|
65938
66061
|
init_test_runners();
|
|
65939
66062
|
init_paths();
|
|
65940
66063
|
init_errors();
|
|
@@ -97864,8 +97987,9 @@ async function rulesLintCommand(options) {
|
|
|
97864
97987
|
init_config();
|
|
97865
97988
|
init_logger2();
|
|
97866
97989
|
async function resolveRunProfileOverride(opts) {
|
|
97867
|
-
|
|
97868
|
-
|
|
97990
|
+
const cliChain = parseProfileList(opts.cliProfile);
|
|
97991
|
+
if (cliChain.length > 0)
|
|
97992
|
+
return cliChain;
|
|
97869
97993
|
if (opts.envProfile)
|
|
97870
97994
|
return;
|
|
97871
97995
|
const readJson = opts._readJson ?? (async (path19) => {
|
|
@@ -97876,15 +98000,15 @@ async function resolveRunProfileOverride(opts) {
|
|
|
97876
98000
|
});
|
|
97877
98001
|
try {
|
|
97878
98002
|
const prd = await readJson(opts.prdPath);
|
|
97879
|
-
|
|
97880
|
-
|
|
97881
|
-
|
|
97882
|
-
return name;
|
|
98003
|
+
const rp = prd?.routingProfile;
|
|
98004
|
+
const prdChain = parseProfileList(typeof rp === "string" || Array.isArray(rp) ? rp : undefined);
|
|
98005
|
+
if (prdChain.length > 0) {
|
|
97883
98006
|
const listNames = opts._listProfileNames ?? (async () => (await listProfiles(opts.projectRoot)).map((p) => p.name));
|
|
97884
98007
|
const available = await listNames();
|
|
97885
|
-
|
|
97886
|
-
|
|
97887
|
-
|
|
98008
|
+
const missing = prdChain.filter((name) => name !== "default" && !available.includes(name));
|
|
98009
|
+
if (missing.length === 0)
|
|
98010
|
+
return prdChain;
|
|
98011
|
+
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
98012
|
}
|
|
97889
98013
|
} catch {}
|
|
97890
98014
|
return;
|
|
@@ -105271,6 +105395,7 @@ var import_react28 = __toESM(require_react(), 1);
|
|
|
105271
105395
|
var import_react35 = __toESM(require_react(), 1);
|
|
105272
105396
|
|
|
105273
105397
|
// src/utils/queue-writer.ts
|
|
105398
|
+
var _writeChains = new Map;
|
|
105274
105399
|
async function writeQueueCommand(queueFilePath, command) {
|
|
105275
105400
|
let commandLine2;
|
|
105276
105401
|
switch (command.type) {
|
|
@@ -105288,13 +105413,22 @@ async function writeQueueCommand(queueFilePath, command) {
|
|
|
105288
105413
|
throw new Error(`Unhandled queue command: ${_exhaustive}`);
|
|
105289
105414
|
}
|
|
105290
105415
|
}
|
|
105291
|
-
const
|
|
105292
|
-
const
|
|
105293
|
-
|
|
105416
|
+
const chain = _writeChains.get(queueFilePath) ?? Promise.resolve();
|
|
105417
|
+
const next = chain.then(async () => {
|
|
105418
|
+
const existing = await Bun.file(queueFilePath).text().catch(() => "");
|
|
105419
|
+
const content = existing ? `${existing.trimEnd()}
|
|
105294
105420
|
${commandLine2}
|
|
105295
105421
|
` : `${commandLine2}
|
|
105296
105422
|
`;
|
|
105297
|
-
|
|
105423
|
+
await Bun.write(queueFilePath, content);
|
|
105424
|
+
});
|
|
105425
|
+
const settled = next.catch(() => {});
|
|
105426
|
+
_writeChains.set(queueFilePath, settled);
|
|
105427
|
+
settled.then(() => {
|
|
105428
|
+
if (_writeChains.get(queueFilePath) === settled)
|
|
105429
|
+
_writeChains.delete(queueFilePath);
|
|
105430
|
+
});
|
|
105431
|
+
await next;
|
|
105298
105432
|
}
|
|
105299
105433
|
|
|
105300
105434
|
// src/tui/components/CostOverlay.tsx
|
|
@@ -106939,6 +107073,9 @@ function renderTui(props) {
|
|
|
106939
107073
|
init_version();
|
|
106940
107074
|
var program2 = new Command;
|
|
106941
107075
|
program2.name("nax").description("AI Coding Agent Orchestrator \u2014 loops until done").version(NAX_VERSION);
|
|
107076
|
+
function collectProfile(value, previous) {
|
|
107077
|
+
return previous.concat(value);
|
|
107078
|
+
}
|
|
106942
107079
|
async function promptForConfirmation(question) {
|
|
106943
107080
|
if (!process.stdin.isTTY) {
|
|
106944
107081
|
return true;
|
|
@@ -107136,7 +107273,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
|
|
|
107136
107273
|
});
|
|
107137
107274
|
process.exit(exitCode);
|
|
107138
107275
|
});
|
|
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
|
|
107276
|
+
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
107277
|
let workdir;
|
|
107141
107278
|
try {
|
|
107142
107279
|
workdir = validateDirectory(options.dir);
|
|
@@ -107173,13 +107310,14 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
107173
107310
|
}
|
|
107174
107311
|
const naxDir = findProjectDir(workdir);
|
|
107175
107312
|
const cliOverrides = {};
|
|
107313
|
+
const cliProfiles = options.profile ?? [];
|
|
107176
107314
|
const profileOverride = naxDir ? await resolveRunProfileOverride({
|
|
107177
107315
|
prdPath: join87(naxDir, "features", options.feature, "prd.json"),
|
|
107178
107316
|
projectRoot: workdir,
|
|
107179
|
-
cliProfile:
|
|
107317
|
+
cliProfile: cliProfiles,
|
|
107180
107318
|
envProfile: process.env.NAX_PROFILE
|
|
107181
|
-
}) :
|
|
107182
|
-
if (profileOverride) {
|
|
107319
|
+
}) : cliProfiles;
|
|
107320
|
+
if (profileOverride && profileOverride.length > 0) {
|
|
107183
107321
|
cliOverrides.profile = profileOverride;
|
|
107184
107322
|
}
|
|
107185
107323
|
const config2 = await loadConfig(naxDir ?? undefined, cliOverrides);
|
|
@@ -107480,7 +107618,7 @@ Features:
|
|
|
107480
107618
|
}
|
|
107481
107619
|
console.log();
|
|
107482
107620
|
});
|
|
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
|
|
107621
|
+
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
107622
|
if (description) {
|
|
107485
107623
|
console.error(source_default.red(`Error: Positional args removed in plan v2.
|
|
107486
107624
|
|
|
@@ -107500,8 +107638,9 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
107500
107638
|
process.exit(1);
|
|
107501
107639
|
}
|
|
107502
107640
|
const cliOverrides = {};
|
|
107503
|
-
|
|
107504
|
-
|
|
107641
|
+
const cliProfiles = options.profile ?? [];
|
|
107642
|
+
if (cliProfiles.length > 0) {
|
|
107643
|
+
cliOverrides.profile = cliProfiles;
|
|
107505
107644
|
}
|
|
107506
107645
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
107507
107646
|
const featureLogDir = join87(naxDir, "features", options.feature, "plan");
|