@nathapp/nax 0.70.0-canary.3 → 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 +378 -159
- 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 {
|
|
@@ -32092,6 +32164,19 @@ ${STEP3_SHARED_RULES}
|
|
|
32092
32164
|
- **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
|
|
32093
32165
|
- **Path anchor (CRITICAL \u2014 do NOT deviate)**: Write the test file to this exact path: \`${p.targetTestFilePath}\`. This path is intentional and computed by the orchestrator \u2014 do not change it based on what you observe in the project. In particular: if you see a \`.nax/features/\` directory at the repo root, that is for stories scoped to the repo root. When a story belongs to a specific package (e.g. \`packages/core\`), its acceptance test lives inside that package's \`.nax/features/\` directory so the test runner can resolve the package's imports correctly. The package root is 3 levels above the test file (\`../../../\` relative to the test file).
|
|
32094
32166
|
- **Process cwd**: When spawning child processes to invoke a CLI or binary, set the working directory to the **package root** (\`join(import.meta.dir, "../../..")\`) as your default \u2014 unless your Step 2 exploration reveals the CLI uses a different working directory convention (e.g. reads config from \`~/.config/\`, or resolves paths relative to a flag value). Always check how the CLI resolves file paths before assuming.${implSection}`;
|
|
32167
|
+
}
|
|
32168
|
+
buildPathCorrection(targetTestFilePath) {
|
|
32169
|
+
return `The acceptance test file was NOT found at the required path. You likely wrote it to a different filename or directory (for example, by renaming a dotfile or replacing dashes with underscores).
|
|
32170
|
+
|
|
32171
|
+
Move (or re-write) the acceptance test you just created so it lives at EXACTLY this path:
|
|
32172
|
+
${targetTestFilePath}
|
|
32173
|
+
|
|
32174
|
+
Requirements:
|
|
32175
|
+
- The file must be at that exact path \u2014 same directory and same filename, including any leading dot and dashes. Do NOT sanitize, rename, or relocate it.
|
|
32176
|
+
- Preserve the test content you already wrote. Do not regenerate, weaken, or stub the assertions.
|
|
32177
|
+
- If you wrote it somewhere else, delete the misplaced copy after moving it so only the canonical path remains.
|
|
32178
|
+
|
|
32179
|
+
After writing the file to the exact path above, reply with a brief confirmation only.`;
|
|
32095
32180
|
}
|
|
32096
32181
|
buildGeneratorFromSpecPrompt(p) {
|
|
32097
32182
|
return `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${p.featureName}" feature.
|
|
@@ -33154,12 +33239,16 @@ async function collectDiff(workdir, storyGitRef, excludePatterns, options) {
|
|
|
33154
33239
|
stdout: "pipe",
|
|
33155
33240
|
stderr: "pipe"
|
|
33156
33241
|
});
|
|
33157
|
-
const [exitCode, stdout] = await Promise.all([
|
|
33242
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
33158
33243
|
proc.exited,
|
|
33159
33244
|
new Response(proc.stdout).text(),
|
|
33160
33245
|
new Response(proc.stderr).text()
|
|
33161
33246
|
]);
|
|
33162
|
-
|
|
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;
|
|
33163
33252
|
}
|
|
33164
33253
|
async function collectDiffStat(workdir, storyGitRef, options) {
|
|
33165
33254
|
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
@@ -33410,6 +33499,9 @@ async function prepareSemanticReviewInput(args) {
|
|
|
33410
33499
|
return { effectiveRef, stat, diff: undefined, excludePatterns };
|
|
33411
33500
|
}
|
|
33412
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
|
+
}
|
|
33413
33505
|
const diff = truncateDiff(rawDiff, rawDiff.length > DIFF_CAP_BYTES ? stat : undefined);
|
|
33414
33506
|
if (!diff) {
|
|
33415
33507
|
return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "no production code changes" };
|
|
@@ -34759,6 +34851,40 @@ ${outputFormat}`, overridable: false }
|
|
|
34759
34851
|
};
|
|
34760
34852
|
});
|
|
34761
34853
|
|
|
34854
|
+
// src/operations/self-heal.ts
|
|
34855
|
+
function makeSelfHealStep(spec) {
|
|
34856
|
+
return {
|
|
34857
|
+
async run(ctx) {
|
|
34858
|
+
const deviations = await spec.detect(ctx.input);
|
|
34859
|
+
if (deviations.length === 0)
|
|
34860
|
+
return null;
|
|
34861
|
+
if (spec.log) {
|
|
34862
|
+
getSafeLogger()?.info(spec.log.kind, spec.log.message, spec.log.meta?.(ctx.input, deviations) ?? {});
|
|
34863
|
+
}
|
|
34864
|
+
return ctx.send(spec.buildRepair(deviations, ctx.input));
|
|
34865
|
+
}
|
|
34866
|
+
};
|
|
34867
|
+
}
|
|
34868
|
+
async function runSelfHealChain(ctx, seed, steps) {
|
|
34869
|
+
let last = seed;
|
|
34870
|
+
let totalCost = seed.estimatedCostUsd ?? 0;
|
|
34871
|
+
for (const step of steps) {
|
|
34872
|
+
try {
|
|
34873
|
+
const turn = await step.run(ctx);
|
|
34874
|
+
if (turn) {
|
|
34875
|
+
totalCost += turn.estimatedCostUsd ?? 0;
|
|
34876
|
+
last = turn;
|
|
34877
|
+
}
|
|
34878
|
+
} catch (err) {
|
|
34879
|
+
getSafeLogger()?.warn("self-heal", "step threw \u2014 skipping", { error: errorMessage(err) });
|
|
34880
|
+
}
|
|
34881
|
+
}
|
|
34882
|
+
return { ...last, estimatedCostUsd: totalCost };
|
|
34883
|
+
}
|
|
34884
|
+
var init_self_heal = __esm(() => {
|
|
34885
|
+
init_logger2();
|
|
34886
|
+
});
|
|
34887
|
+
|
|
34762
34888
|
// src/operations/plan-refine.ts
|
|
34763
34889
|
import { join as join21 } from "path";
|
|
34764
34890
|
function hasToken(text, tokens) {
|
|
@@ -34897,6 +35023,28 @@ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
|
|
|
34897
35023
|
return prd;
|
|
34898
35024
|
return { ...prd, userStories: results.map((r) => r.story) };
|
|
34899
35025
|
}
|
|
35026
|
+
function verbatimSelfHealStep(builder) {
|
|
35027
|
+
return makeSelfHealStep({
|
|
35028
|
+
detect: (input) => readMissingVerbatimAcs(input),
|
|
35029
|
+
buildRepair: (missing, input) => builder.buildVerbatimRepair(missing, input.outputPath),
|
|
35030
|
+
log: {
|
|
35031
|
+
kind: "plan",
|
|
35032
|
+
message: "Refine dropped [verbatim] spec ACs \u2014 issuing one repair turn",
|
|
35033
|
+
meta: (input, missing) => ({ featureName: input.featureName, missingCount: missing.length })
|
|
35034
|
+
}
|
|
35035
|
+
});
|
|
35036
|
+
}
|
|
35037
|
+
function specDriftSelfHealStep(builder) {
|
|
35038
|
+
return makeSelfHealStep({
|
|
35039
|
+
detect: (input) => readSpecDriftViolations(input),
|
|
35040
|
+
buildRepair: (drifted, input) => builder.buildSpecDriftRepair(drifted, input.outputPath),
|
|
35041
|
+
log: {
|
|
35042
|
+
kind: "plan",
|
|
35043
|
+
message: "specGuard: spec-drift violations found \u2014 issuing one repair turn",
|
|
35044
|
+
meta: (input, drifted) => ({ featureName: input.featureName, violationCount: drifted.length })
|
|
35045
|
+
}
|
|
35046
|
+
});
|
|
35047
|
+
}
|
|
34900
35048
|
var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
|
|
34901
35049
|
var init_plan_refine = __esm(() => {
|
|
34902
35050
|
init_retry();
|
|
@@ -34906,6 +35054,7 @@ var init_plan_refine = __esm(() => {
|
|
|
34906
35054
|
init_prd();
|
|
34907
35055
|
init_schema2();
|
|
34908
35056
|
init_prompts();
|
|
35057
|
+
init_self_heal();
|
|
34909
35058
|
init_verbatim_warn();
|
|
34910
35059
|
_planRefineDeps = {
|
|
34911
35060
|
readFile: async (path3) => {
|
|
@@ -34985,31 +35134,15 @@ ${outputFormat}`,
|
|
|
34985
35134
|
const specGuard = ctx.input.specGuard ?? false;
|
|
34986
35135
|
const turn1 = await ctx.sendWithParseRetry(initialPrompt);
|
|
34987
35136
|
const turn2 = await ctx.send(builder.buildRefineContinuation(ctx.input.outputPath, specGuard));
|
|
34988
|
-
|
|
34989
|
-
|
|
34990
|
-
|
|
34991
|
-
|
|
34992
|
-
|
|
34993
|
-
|
|
34994
|
-
|
|
34995
|
-
|
|
34996
|
-
|
|
34997
|
-
totalCost += turn3.estimatedCostUsd ?? 0;
|
|
34998
|
-
last = turn3;
|
|
34999
|
-
}
|
|
35000
|
-
if (specGuard) {
|
|
35001
|
-
const drifted = await readSpecDriftViolations(ctx.input);
|
|
35002
|
-
if (drifted.length > 0) {
|
|
35003
|
-
getSafeLogger()?.info("plan", "specGuard: spec-drift violations found \u2014 issuing one repair turn", {
|
|
35004
|
-
featureName: ctx.input.featureName,
|
|
35005
|
-
violationCount: drifted.length
|
|
35006
|
-
});
|
|
35007
|
-
const turn4 = await ctx.send(builder.buildSpecDriftRepair(drifted, ctx.input.outputPath));
|
|
35008
|
-
totalCost += turn4.estimatedCostUsd ?? 0;
|
|
35009
|
-
last = turn4;
|
|
35010
|
-
}
|
|
35011
|
-
}
|
|
35012
|
-
return { ...last, estimatedCostUsd: totalCost };
|
|
35137
|
+
const seed = {
|
|
35138
|
+
...turn2,
|
|
35139
|
+
estimatedCostUsd: (turn1.estimatedCostUsd ?? 0) + (turn2.estimatedCostUsd ?? 0)
|
|
35140
|
+
};
|
|
35141
|
+
const steps = [
|
|
35142
|
+
verbatimSelfHealStep(builder),
|
|
35143
|
+
...specGuard ? [specDriftSelfHealStep(builder)] : []
|
|
35144
|
+
];
|
|
35145
|
+
return runSelfHealChain(ctx, seed, steps);
|
|
35013
35146
|
},
|
|
35014
35147
|
parse(output, input) {
|
|
35015
35148
|
return validatePlanOutput(output, input.featureName, input.branchName);
|
|
@@ -36127,11 +36260,32 @@ function isStubTestContent(content) {
|
|
|
36127
36260
|
}
|
|
36128
36261
|
|
|
36129
36262
|
// src/operations/acceptance-generate.ts
|
|
36130
|
-
|
|
36263
|
+
function pathCorrectionStep() {
|
|
36264
|
+
return makeSelfHealStep({
|
|
36265
|
+
detect: async (input) => await _acceptanceGenerateDeps.fileExists(input.targetTestFilePath) ? [] : [input.targetTestFilePath],
|
|
36266
|
+
buildRepair: (_deviations, input) => new AcceptancePromptBuilder().buildPathCorrection(input.targetTestFilePath),
|
|
36267
|
+
log: {
|
|
36268
|
+
kind: "acceptance",
|
|
36269
|
+
message: "Acceptance test not found at target path \u2014 issuing one corrective turn",
|
|
36270
|
+
meta: (input) => ({ targetTestFilePath: input.targetTestFilePath })
|
|
36271
|
+
}
|
|
36272
|
+
});
|
|
36273
|
+
}
|
|
36274
|
+
var _acceptanceGenerateDeps, acceptanceGenerateOp;
|
|
36131
36275
|
var init_acceptance_generate = __esm(() => {
|
|
36132
36276
|
init_generator();
|
|
36133
36277
|
init_config();
|
|
36134
36278
|
init_prompts();
|
|
36279
|
+
init_self_heal();
|
|
36280
|
+
_acceptanceGenerateDeps = {
|
|
36281
|
+
fileExists: async (path4) => {
|
|
36282
|
+
try {
|
|
36283
|
+
return await Bun.file(path4).exists();
|
|
36284
|
+
} catch {
|
|
36285
|
+
return false;
|
|
36286
|
+
}
|
|
36287
|
+
}
|
|
36288
|
+
};
|
|
36135
36289
|
acceptanceGenerateOp = {
|
|
36136
36290
|
kind: "run",
|
|
36137
36291
|
name: "acceptance-generate",
|
|
@@ -36153,6 +36307,10 @@ var init_acceptance_generate = __esm(() => {
|
|
|
36153
36307
|
task: { id: "task", content: prompt, overridable: false }
|
|
36154
36308
|
};
|
|
36155
36309
|
},
|
|
36310
|
+
async hopBody(initialPrompt, ctx) {
|
|
36311
|
+
const turn1 = await ctx.sendWithParseRetry(initialPrompt);
|
|
36312
|
+
return runSelfHealChain(ctx, turn1, [pathCorrectionStep()]);
|
|
36313
|
+
},
|
|
36156
36314
|
parse(output, _input, _ctx) {
|
|
36157
36315
|
return { testCode: extractTestCode(output) };
|
|
36158
36316
|
},
|
|
@@ -40163,6 +40321,7 @@ var init_operations = __esm(() => {
|
|
|
40163
40321
|
init_call();
|
|
40164
40322
|
init_plan();
|
|
40165
40323
|
init_plan_refine();
|
|
40324
|
+
init_self_heal();
|
|
40166
40325
|
init_verbatim_warn();
|
|
40167
40326
|
init_decompose2();
|
|
40168
40327
|
init_build_hop_callback();
|
|
@@ -40970,10 +41129,9 @@ async function listChangedFiles(workdir, baseRef) {
|
|
|
40970
41129
|
stdout: "pipe",
|
|
40971
41130
|
stderr: "pipe"
|
|
40972
41131
|
});
|
|
40973
|
-
const exitCode = await proc.exited;
|
|
41132
|
+
const [exitCode, output] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
40974
41133
|
if (exitCode !== 0)
|
|
40975
41134
|
return null;
|
|
40976
|
-
const output = await new Response(proc.stdout).text();
|
|
40977
41135
|
return output.split(`
|
|
40978
41136
|
`).map((line) => line.trim()).filter(Boolean).map(normalizePath3);
|
|
40979
41137
|
}
|
|
@@ -45646,6 +45804,8 @@ class PidRegistry {
|
|
|
45646
45804
|
pidsFilePath;
|
|
45647
45805
|
pids = new Set;
|
|
45648
45806
|
frozen = false;
|
|
45807
|
+
_writing = false;
|
|
45808
|
+
_pendingWrite = false;
|
|
45649
45809
|
writeQueueTail = Promise.resolve();
|
|
45650
45810
|
constructor(workdir, _platform) {
|
|
45651
45811
|
this.workdir = workdir;
|
|
@@ -45943,11 +46103,22 @@ class PidRegistry {
|
|
|
45943
46103
|
}
|
|
45944
46104
|
}
|
|
45945
46105
|
enqueueWrite() {
|
|
45946
|
-
|
|
46106
|
+
if (this._writing) {
|
|
46107
|
+
this._pendingWrite = true;
|
|
46108
|
+
return this.writeQueueTail;
|
|
46109
|
+
}
|
|
46110
|
+
this._writing = true;
|
|
46111
|
+
this.writeQueueTail = this.writePidsFile().catch((err) => {
|
|
45947
46112
|
getSafeLogger()?.warn("pid-registry", "Failed to flush PID file \u2014 on-disk registry may be stale", {
|
|
45948
46113
|
error: errorMessage(err)
|
|
45949
46114
|
});
|
|
45950
|
-
}))
|
|
46115
|
+
}).then(async () => {
|
|
46116
|
+
this._writing = false;
|
|
46117
|
+
if (this._pendingWrite) {
|
|
46118
|
+
this._pendingWrite = false;
|
|
46119
|
+
await this.enqueueWrite();
|
|
46120
|
+
}
|
|
46121
|
+
});
|
|
45951
46122
|
return this.writeQueueTail;
|
|
45952
46123
|
}
|
|
45953
46124
|
}
|
|
@@ -46509,6 +46680,7 @@ class SessionManager {
|
|
|
46509
46680
|
}
|
|
46510
46681
|
this._busySessions.delete(handle.id);
|
|
46511
46682
|
this._cancelledSessions.delete(handle.id);
|
|
46683
|
+
this._clearWatchdogCancelledCalls(handle.id);
|
|
46512
46684
|
}
|
|
46513
46685
|
async sendPrompt(handle, prompt, opts) {
|
|
46514
46686
|
if (this._cancelledSessions.has(handle.id)) {
|
|
@@ -46593,6 +46765,10 @@ class SessionManager {
|
|
|
46593
46765
|
sweepOrphans(ttlMs = DEFAULT_ORPHAN_TTL_MS) {
|
|
46594
46766
|
return sweepOrphansImpl(this._sessions, ttlMs);
|
|
46595
46767
|
}
|
|
46768
|
+
close() {
|
|
46769
|
+
this._agentStreamUnsubscribe?.();
|
|
46770
|
+
this._agentStreamUnsubscribe = undefined;
|
|
46771
|
+
}
|
|
46596
46772
|
}
|
|
46597
46773
|
var NULL_PROTOCOL_IDS;
|
|
46598
46774
|
var init_manager2 = __esm(() => {
|
|
@@ -46857,6 +47033,9 @@ function createRuntime(config2, workdir, opts) {
|
|
|
46857
47033
|
if (opts?.parentSignal && parentAbortHandler) {
|
|
46858
47034
|
opts.parentSignal.removeEventListener("abort", parentAbortHandler);
|
|
46859
47035
|
}
|
|
47036
|
+
agentManager.close();
|
|
47037
|
+
if (sessionManager instanceof SessionManager)
|
|
47038
|
+
sessionManager.close();
|
|
46860
47039
|
const results = await Promise.allSettled([promptAuditor.flush(), reviewAuditor.flush(), costAggregator.drain()]);
|
|
46861
47040
|
for (const r of results) {
|
|
46862
47041
|
if (r.status === "rejected") {
|
|
@@ -52395,17 +52574,18 @@ async function saveRunMetrics(outputDir, runMetrics) {
|
|
|
52395
52574
|
}
|
|
52396
52575
|
}
|
|
52397
52576
|
const hasTokenData = totalInputTokens > 0 || totalOutputTokens > 0 || totalCacheReadInputTokens > 0 || totalCacheCreationInputTokens > 0;
|
|
52398
|
-
|
|
52399
|
-
runMetrics
|
|
52577
|
+
const finalMetrics = hasTokenData ? {
|
|
52578
|
+
...runMetrics,
|
|
52579
|
+
totalTokens: new TokenUsage({
|
|
52400
52580
|
inputTokens: totalInputTokens,
|
|
52401
52581
|
outputTokens: totalOutputTokens,
|
|
52402
52582
|
cacheReadInputTokens: totalCacheReadInputTokens,
|
|
52403
52583
|
cacheCreationInputTokens: totalCacheCreationInputTokens
|
|
52404
|
-
})
|
|
52405
|
-
}
|
|
52584
|
+
})
|
|
52585
|
+
} : runMetrics;
|
|
52406
52586
|
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
52407
52587
|
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
52408
|
-
allMetrics.push(
|
|
52588
|
+
allMetrics.push(finalMetrics);
|
|
52409
52589
|
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
52410
52590
|
}
|
|
52411
52591
|
async function loadRunMetrics(outputDir) {
|
|
@@ -54572,29 +54752,27 @@ async function rollbackToRef(workdir, ref) {
|
|
|
54572
54752
|
stdout: "pipe",
|
|
54573
54753
|
stderr: "pipe"
|
|
54574
54754
|
});
|
|
54575
|
-
const exitCode = await resetProc.exited;
|
|
54755
|
+
const [exitCode, resetStderr] = await Promise.all([resetProc.exited, new Response(resetProc.stderr).text()]);
|
|
54576
54756
|
if (exitCode !== 0) {
|
|
54577
|
-
|
|
54578
|
-
|
|
54579
|
-
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}`);
|
|
54580
54759
|
}
|
|
54581
54760
|
const cleanProc = _rollbackDeps.spawn(["git", "clean", "-fd"], {
|
|
54582
54761
|
cwd: workdir,
|
|
54583
54762
|
stdout: "pipe",
|
|
54584
54763
|
stderr: "pipe"
|
|
54585
54764
|
});
|
|
54586
|
-
const cleanExitCode = await cleanProc.exited;
|
|
54765
|
+
const [cleanExitCode, cleanStderr] = await Promise.all([cleanProc.exited, new Response(cleanProc.stderr).text()]);
|
|
54587
54766
|
if (cleanExitCode !== 0) {
|
|
54588
|
-
|
|
54589
|
-
logger.warn("tdd", "Failed to clean untracked files", { stderr });
|
|
54767
|
+
logger.warn("tdd", "Failed to clean untracked files", { stderr: cleanStderr });
|
|
54590
54768
|
}
|
|
54591
54769
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
54592
54770
|
}
|
|
54593
54771
|
async function captureSnapshotRef(workdir, storyId) {
|
|
54594
54772
|
await _rollbackDeps.autoCommitIfDirty(workdir, "non-blocking-fix-snapshot", "snapshot", storyId);
|
|
54595
54773
|
const proc = _rollbackDeps.spawn(["git", "rev-parse", "HEAD"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
54596
|
-
const
|
|
54597
|
-
const
|
|
54774
|
+
const [exitCode, shaRaw] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
54775
|
+
const sha = shaRaw.trim();
|
|
54598
54776
|
if (exitCode !== 0) {
|
|
54599
54777
|
throw new NaxError("git rev-parse HEAD failed in non-blocking-fix snapshot", "SNAPSHOT_REF_FAILED", {
|
|
54600
54778
|
storyId,
|
|
@@ -55584,7 +55762,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
55584
55762
|
if (pkgQuality?.commands?.formatFix || pkgQuality?.commands?.formatFixScoped) {
|
|
55585
55763
|
strategies.push(makeMechanicalFormatFixStrategy());
|
|
55586
55764
|
}
|
|
55587
|
-
|
|
55765
|
+
const fullSuiteGatePhasePresent = Boolean(inputs.fullSuiteGate) && (isThreeSession || regressionMode === "per-story");
|
|
55766
|
+
const verifyScopedPhasePresent = !isThreeSession && Boolean(inputs.verifyScoped);
|
|
55767
|
+
if (fullSuiteGatePhasePresent || verifyScopedPhasePresent) {
|
|
55588
55768
|
strategies.push(makeFullSuiteRectifyStrategy(story, config2, sink));
|
|
55589
55769
|
}
|
|
55590
55770
|
if (config2.quality.autofix?.enabled !== false) {
|
|
@@ -60245,7 +60425,7 @@ var package_default;
|
|
|
60245
60425
|
var init_package = __esm(() => {
|
|
60246
60426
|
package_default = {
|
|
60247
60427
|
name: "@nathapp/nax",
|
|
60248
|
-
version: "0.70.0-canary.
|
|
60428
|
+
version: "0.70.0-canary.5",
|
|
60249
60429
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
60250
60430
|
type: "module",
|
|
60251
60431
|
bin: {
|
|
@@ -60340,8 +60520,8 @@ var init_version = __esm(() => {
|
|
|
60340
60520
|
NAX_VERSION = package_default.version;
|
|
60341
60521
|
NAX_COMMIT = (() => {
|
|
60342
60522
|
try {
|
|
60343
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
60344
|
-
return "
|
|
60523
|
+
if (/^[0-9a-f]{6,10}$/.test("e8c2ab46"))
|
|
60524
|
+
return "e8c2ab46";
|
|
60345
60525
|
} catch {}
|
|
60346
60526
|
try {
|
|
60347
60527
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -60361,11 +60541,11 @@ var init_version = __esm(() => {
|
|
|
60361
60541
|
|
|
60362
60542
|
// src/execution/crash-heartbeat.ts
|
|
60363
60543
|
import { appendFileSync as appendFileSync2 } from "fs";
|
|
60364
|
-
async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60544
|
+
async function heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60365
60545
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
60366
|
-
while (
|
|
60546
|
+
while (gen === _heartbeatGen && _heartbeatActive) {
|
|
60367
60547
|
await _heartbeatDeps.sleep(60000);
|
|
60368
|
-
if (!
|
|
60548
|
+
if (gen !== _heartbeatGen || !_heartbeatActive)
|
|
60369
60549
|
break;
|
|
60370
60550
|
try {
|
|
60371
60551
|
logger?.debug("crash-recovery", "Heartbeat");
|
|
@@ -60394,9 +60574,9 @@ async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFil
|
|
|
60394
60574
|
}
|
|
60395
60575
|
function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
60396
60576
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
60397
|
-
|
|
60398
|
-
|
|
60399
|
-
heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath).catch((err) => {
|
|
60577
|
+
_heartbeatActive = true;
|
|
60578
|
+
const gen = ++_heartbeatGen;
|
|
60579
|
+
heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jsonlFilePath).catch((err) => {
|
|
60400
60580
|
_heartbeatDeps.getSafeLogger()?.warn("crash-recovery", "Heartbeat loop crashed; status updates stopped", {
|
|
60401
60581
|
error: err instanceof Error ? err.message : String(err)
|
|
60402
60582
|
});
|
|
@@ -60404,12 +60584,13 @@ function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath
|
|
|
60404
60584
|
logger?.debug("crash-recovery", "Heartbeat started (60s interval)");
|
|
60405
60585
|
}
|
|
60406
60586
|
function stopHeartbeat() {
|
|
60407
|
-
if (
|
|
60408
|
-
|
|
60587
|
+
if (_heartbeatActive) {
|
|
60588
|
+
_heartbeatActive = false;
|
|
60589
|
+
_heartbeatGen++;
|
|
60409
60590
|
getSafeLogger()?.debug("crash-recovery", "Heartbeat stopped");
|
|
60410
60591
|
}
|
|
60411
60592
|
}
|
|
60412
|
-
var _heartbeatDeps,
|
|
60593
|
+
var _heartbeatDeps, _heartbeatGen = 0, _heartbeatActive = false;
|
|
60413
60594
|
var init_crash_heartbeat = __esm(() => {
|
|
60414
60595
|
init_logger2();
|
|
60415
60596
|
_heartbeatDeps = {
|
|
@@ -62576,9 +62757,8 @@ ${missing.join(`
|
|
|
62576
62757
|
stdout: "pipe",
|
|
62577
62758
|
stderr: "pipe"
|
|
62578
62759
|
});
|
|
62579
|
-
const exitCode = await proc.exited;
|
|
62760
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62580
62761
|
if (exitCode !== 0) {
|
|
62581
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62582
62762
|
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
62583
62763
|
}
|
|
62584
62764
|
} catch (error48) {
|
|
@@ -62611,9 +62791,8 @@ ${missing.join(`
|
|
|
62611
62791
|
stdout: "pipe",
|
|
62612
62792
|
stderr: "pipe"
|
|
62613
62793
|
});
|
|
62614
|
-
const exitCode = await proc.exited;
|
|
62794
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62615
62795
|
if (exitCode !== 0) {
|
|
62616
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62617
62796
|
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
62618
62797
|
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
62619
62798
|
}
|
|
@@ -62631,9 +62810,8 @@ ${missing.join(`
|
|
|
62631
62810
|
stdout: "pipe",
|
|
62632
62811
|
stderr: "pipe"
|
|
62633
62812
|
});
|
|
62634
|
-
const exitCode = await proc.exited;
|
|
62813
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
62635
62814
|
if (exitCode !== 0) {
|
|
62636
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62637
62815
|
if (!stderr.includes("not found")) {
|
|
62638
62816
|
const logger = getSafeLogger();
|
|
62639
62817
|
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
@@ -62653,12 +62831,14 @@ ${missing.join(`
|
|
|
62653
62831
|
stdout: "pipe",
|
|
62654
62832
|
stderr: "pipe"
|
|
62655
62833
|
});
|
|
62656
|
-
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
|
+
]);
|
|
62657
62839
|
if (exitCode !== 0) {
|
|
62658
|
-
const stderr = await new Response(proc.stderr).text();
|
|
62659
62840
|
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
62660
62841
|
}
|
|
62661
|
-
const stdout = await new Response(proc.stdout).text();
|
|
62662
62842
|
return this.parseWorktreeList(stdout);
|
|
62663
62843
|
} catch (error48) {
|
|
62664
62844
|
if (error48 instanceof Error) {
|
|
@@ -62770,9 +62950,11 @@ class MergeEngine {
|
|
|
62770
62950
|
stdout: "pipe",
|
|
62771
62951
|
stderr: "pipe"
|
|
62772
62952
|
});
|
|
62773
|
-
const exitCode = await
|
|
62774
|
-
|
|
62775
|
-
|
|
62953
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
62954
|
+
mergeProc.exited,
|
|
62955
|
+
new Response(mergeProc.stderr).text(),
|
|
62956
|
+
new Response(mergeProc.stdout).text()
|
|
62957
|
+
]);
|
|
62776
62958
|
if (exitCode === 0) {
|
|
62777
62959
|
try {
|
|
62778
62960
|
await this.worktreeManager.remove(projectRoot, storyId);
|
|
@@ -62899,19 +63081,25 @@ ${stderr}`;
|
|
|
62899
63081
|
stdout: "pipe",
|
|
62900
63082
|
stderr: "pipe"
|
|
62901
63083
|
});
|
|
62902
|
-
const exitCode = await
|
|
63084
|
+
const [exitCode, currentBranchRaw] = await Promise.all([
|
|
63085
|
+
currentBranchProc.exited,
|
|
63086
|
+
new Response(currentBranchProc.stdout).text()
|
|
63087
|
+
]);
|
|
62903
63088
|
if (exitCode !== 0) {
|
|
62904
63089
|
throw new Error("Failed to get current branch");
|
|
62905
63090
|
}
|
|
62906
|
-
const currentBranch =
|
|
63091
|
+
const currentBranch = currentBranchRaw.trim();
|
|
62907
63092
|
const rebaseProc = _mergeDeps.spawn(["git", "rebase", currentBranch], {
|
|
62908
63093
|
cwd: worktreePath,
|
|
62909
63094
|
stdout: "pipe",
|
|
62910
63095
|
stderr: "pipe"
|
|
62911
63096
|
});
|
|
62912
|
-
const rebaseExitCode = await
|
|
63097
|
+
const [rebaseExitCode, rebaseStderr] = await Promise.all([
|
|
63098
|
+
rebaseProc.exited,
|
|
63099
|
+
new Response(rebaseProc.stderr).text()
|
|
63100
|
+
]);
|
|
62913
63101
|
if (rebaseExitCode !== 0) {
|
|
62914
|
-
const stderr =
|
|
63102
|
+
const stderr = rebaseStderr;
|
|
62915
63103
|
const abortProc = _mergeDeps.spawn(["git", "rebase", "--abort"], {
|
|
62916
63104
|
cwd: worktreePath,
|
|
62917
63105
|
stdout: "pipe",
|
|
@@ -62934,11 +63122,10 @@ ${stderr}`;
|
|
|
62934
63122
|
stdout: "pipe",
|
|
62935
63123
|
stderr: "pipe"
|
|
62936
63124
|
});
|
|
62937
|
-
const exitCode = await proc.exited;
|
|
63125
|
+
const [exitCode, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
62938
63126
|
if (exitCode !== 0) {
|
|
62939
63127
|
return [];
|
|
62940
63128
|
}
|
|
62941
|
-
const stdout = await new Response(proc.stdout).text();
|
|
62942
63129
|
return stdout.trim().split(`
|
|
62943
63130
|
`).filter((line) => line.length > 0);
|
|
62944
63131
|
} catch {
|
|
@@ -63678,7 +63865,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
63678
63865
|
}
|
|
63679
63866
|
}
|
|
63680
63867
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
63681
|
-
const profileOverride = ctx.config
|
|
63868
|
+
const profileOverride = profileOverrideFromConfig(ctx.config);
|
|
63682
63869
|
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join81(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
63683
63870
|
let dependencyContext;
|
|
63684
63871
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
@@ -63824,6 +64011,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
63824
64011
|
}
|
|
63825
64012
|
var _iterationRunnerDeps;
|
|
63826
64013
|
var init_iteration_runner = __esm(() => {
|
|
64014
|
+
init_config();
|
|
63827
64015
|
init_loader();
|
|
63828
64016
|
init_logger2();
|
|
63829
64017
|
init_runner4();
|
|
@@ -64054,7 +64242,7 @@ async function runParallelBatch(options) {
|
|
|
64054
64242
|
worktreePaths.set(story.id, path22.join(workdir, ".nax-wt", story.id));
|
|
64055
64243
|
}
|
|
64056
64244
|
const rootConfigPath = path22.join(workdir, ".nax", "config.json");
|
|
64057
|
-
const profileOverride = config2
|
|
64245
|
+
const profileOverride = profileOverrideFromConfig(config2);
|
|
64058
64246
|
const storyEffectiveConfigs = new Map;
|
|
64059
64247
|
const configResults = await Promise.allSettled(stories.filter((story) => story.workdir).map(async (story) => {
|
|
64060
64248
|
try {
|
|
@@ -64215,6 +64403,7 @@ async function runParallelBatch(options) {
|
|
|
64215
64403
|
}
|
|
64216
64404
|
var _parallelBatchDeps;
|
|
64217
64405
|
var init_parallel_batch = __esm(() => {
|
|
64406
|
+
init_config();
|
|
64218
64407
|
init_loader();
|
|
64219
64408
|
init_logger2();
|
|
64220
64409
|
init_dependencies();
|
|
@@ -64281,13 +64470,15 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64281
64470
|
};
|
|
64282
64471
|
for (const fn of _prevRunUnsubscribers)
|
|
64283
64472
|
fn();
|
|
64284
|
-
_prevRunUnsubscribers = [
|
|
64473
|
+
_prevRunUnsubscribers = [];
|
|
64474
|
+
const thisRunUnsubscribers = [
|
|
64285
64475
|
wireHooks(pipelineEventBus, ctx.hooks, ctx.workdir, ctx.feature),
|
|
64286
64476
|
wireReporters(pipelineEventBus, ctx.pluginRegistry, ctx.runId, ctx.startTime),
|
|
64287
64477
|
wireInteraction(pipelineEventBus, ctx.interactionChain, ctx.config),
|
|
64288
64478
|
wireEventsWriter(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir),
|
|
64289
64479
|
wireRegistry(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir, ctx.runtime.outputDir)
|
|
64290
64480
|
];
|
|
64481
|
+
_prevRunUnsubscribers = thisRunUnsubscribers;
|
|
64291
64482
|
pipelineEventBus.emit({
|
|
64292
64483
|
type: "run:started",
|
|
64293
64484
|
feature: ctx.feature,
|
|
@@ -64304,6 +64495,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64304
64495
|
deferredReview
|
|
64305
64496
|
});
|
|
64306
64497
|
startHeartbeat(ctx.statusWriter, () => totalCost, () => iterations, ctx.logFilePath);
|
|
64498
|
+
let _executeThrew = false;
|
|
64307
64499
|
try {
|
|
64308
64500
|
if (isComplete(prd)) {
|
|
64309
64501
|
logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
|
|
@@ -64710,7 +64902,16 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
64710
64902
|
}, ctx.eventEmitter);
|
|
64711
64903
|
}
|
|
64712
64904
|
return buildResult2("max-iterations");
|
|
64713
|
-
}
|
|
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
|
+
}
|
|
64714
64915
|
}
|
|
64715
64916
|
function reconcileBatchOutcome(prd, batchResult) {
|
|
64716
64917
|
for (const story of batchResult.completed) {
|
|
@@ -65802,6 +66003,7 @@ async function setupRun(options) {
|
|
|
65802
66003
|
const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
|
|
65803
66004
|
const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
|
|
65804
66005
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins, isTestFileFn);
|
|
66006
|
+
clearCache();
|
|
65805
66007
|
logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
|
|
65806
66008
|
plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides }))
|
|
65807
66009
|
});
|
|
@@ -65855,6 +66057,7 @@ async function setupRun(options) {
|
|
|
65855
66057
|
var _runSetupDeps;
|
|
65856
66058
|
var init_run_setup = __esm(() => {
|
|
65857
66059
|
init_pipeline();
|
|
66060
|
+
init_routing();
|
|
65858
66061
|
init_test_runners();
|
|
65859
66062
|
init_paths();
|
|
65860
66063
|
init_errors();
|
|
@@ -97784,8 +97987,9 @@ async function rulesLintCommand(options) {
|
|
|
97784
97987
|
init_config();
|
|
97785
97988
|
init_logger2();
|
|
97786
97989
|
async function resolveRunProfileOverride(opts) {
|
|
97787
|
-
|
|
97788
|
-
|
|
97990
|
+
const cliChain = parseProfileList(opts.cliProfile);
|
|
97991
|
+
if (cliChain.length > 0)
|
|
97992
|
+
return cliChain;
|
|
97789
97993
|
if (opts.envProfile)
|
|
97790
97994
|
return;
|
|
97791
97995
|
const readJson = opts._readJson ?? (async (path19) => {
|
|
@@ -97796,15 +98000,15 @@ async function resolveRunProfileOverride(opts) {
|
|
|
97796
98000
|
});
|
|
97797
98001
|
try {
|
|
97798
98002
|
const prd = await readJson(opts.prdPath);
|
|
97799
|
-
|
|
97800
|
-
|
|
97801
|
-
|
|
97802
|
-
return name;
|
|
98003
|
+
const rp = prd?.routingProfile;
|
|
98004
|
+
const prdChain = parseProfileList(typeof rp === "string" || Array.isArray(rp) ? rp : undefined);
|
|
98005
|
+
if (prdChain.length > 0) {
|
|
97803
98006
|
const listNames = opts._listProfileNames ?? (async () => (await listProfiles(opts.projectRoot)).map((p) => p.name));
|
|
97804
98007
|
const available = await listNames();
|
|
97805
|
-
|
|
97806
|
-
|
|
97807
|
-
|
|
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 });
|
|
97808
98012
|
}
|
|
97809
98013
|
} catch {}
|
|
97810
98014
|
return;
|
|
@@ -105191,6 +105395,7 @@ var import_react28 = __toESM(require_react(), 1);
|
|
|
105191
105395
|
var import_react35 = __toESM(require_react(), 1);
|
|
105192
105396
|
|
|
105193
105397
|
// src/utils/queue-writer.ts
|
|
105398
|
+
var _writeChains = new Map;
|
|
105194
105399
|
async function writeQueueCommand(queueFilePath, command) {
|
|
105195
105400
|
let commandLine2;
|
|
105196
105401
|
switch (command.type) {
|
|
@@ -105208,13 +105413,22 @@ async function writeQueueCommand(queueFilePath, command) {
|
|
|
105208
105413
|
throw new Error(`Unhandled queue command: ${_exhaustive}`);
|
|
105209
105414
|
}
|
|
105210
105415
|
}
|
|
105211
|
-
const
|
|
105212
|
-
const
|
|
105213
|
-
|
|
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()}
|
|
105214
105420
|
${commandLine2}
|
|
105215
105421
|
` : `${commandLine2}
|
|
105216
105422
|
`;
|
|
105217
|
-
|
|
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;
|
|
105218
105432
|
}
|
|
105219
105433
|
|
|
105220
105434
|
// src/tui/components/CostOverlay.tsx
|
|
@@ -106859,6 +107073,9 @@ function renderTui(props) {
|
|
|
106859
107073
|
init_version();
|
|
106860
107074
|
var program2 = new Command;
|
|
106861
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
|
+
}
|
|
106862
107079
|
async function promptForConfirmation(question) {
|
|
106863
107080
|
if (!process.stdin.isTTY) {
|
|
106864
107081
|
return true;
|
|
@@ -107056,7 +107273,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
|
|
|
107056
107273
|
});
|
|
107057
107274
|
process.exit(exitCode);
|
|
107058
107275
|
});
|
|
107059
|
-
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) => {
|
|
107060
107277
|
let workdir;
|
|
107061
107278
|
try {
|
|
107062
107279
|
workdir = validateDirectory(options.dir);
|
|
@@ -107093,13 +107310,14 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
107093
107310
|
}
|
|
107094
107311
|
const naxDir = findProjectDir(workdir);
|
|
107095
107312
|
const cliOverrides = {};
|
|
107313
|
+
const cliProfiles = options.profile ?? [];
|
|
107096
107314
|
const profileOverride = naxDir ? await resolveRunProfileOverride({
|
|
107097
107315
|
prdPath: join87(naxDir, "features", options.feature, "prd.json"),
|
|
107098
107316
|
projectRoot: workdir,
|
|
107099
|
-
cliProfile:
|
|
107317
|
+
cliProfile: cliProfiles,
|
|
107100
107318
|
envProfile: process.env.NAX_PROFILE
|
|
107101
|
-
}) :
|
|
107102
|
-
if (profileOverride) {
|
|
107319
|
+
}) : cliProfiles;
|
|
107320
|
+
if (profileOverride && profileOverride.length > 0) {
|
|
107103
107321
|
cliOverrides.profile = profileOverride;
|
|
107104
107322
|
}
|
|
107105
107323
|
const config2 = await loadConfig(naxDir ?? undefined, cliOverrides);
|
|
@@ -107400,7 +107618,7 @@ Features:
|
|
|
107400
107618
|
}
|
|
107401
107619
|
console.log();
|
|
107402
107620
|
});
|
|
107403
|
-
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) => {
|
|
107404
107622
|
if (description) {
|
|
107405
107623
|
console.error(source_default.red(`Error: Positional args removed in plan v2.
|
|
107406
107624
|
|
|
@@ -107420,8 +107638,9 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
107420
107638
|
process.exit(1);
|
|
107421
107639
|
}
|
|
107422
107640
|
const cliOverrides = {};
|
|
107423
|
-
|
|
107424
|
-
|
|
107641
|
+
const cliProfiles = options.profile ?? [];
|
|
107642
|
+
if (cliProfiles.length > 0) {
|
|
107643
|
+
cliOverrides.profile = cliProfiles;
|
|
107425
107644
|
}
|
|
107426
107645
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
107427
107646
|
const featureLogDir = join87(naxDir, "features", options.feature, "plan");
|