@nathapp/nax 0.58.5 → 0.59.0
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 +811 -694
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -3527,6 +3527,8 @@ async function writePromptAudit(entry) {
|
|
|
3527
3527
|
let baseDir;
|
|
3528
3528
|
if (entry.auditDir) {
|
|
3529
3529
|
baseDir = isAbsolute(entry.auditDir) ? entry.auditDir : join(entry.workdir, entry.auditDir);
|
|
3530
|
+
} else if (entry.projectDir) {
|
|
3531
|
+
baseDir = join(entry.projectDir, ".nax", "prompt-audit");
|
|
3530
3532
|
} else {
|
|
3531
3533
|
const wtMarker = `${sep}.nax-wt${sep}`;
|
|
3532
3534
|
const wtIdx = entry.workdir.indexOf(wtMarker);
|
|
@@ -19186,6 +19188,7 @@ class AcpAgentAdapter {
|
|
|
19186
19188
|
prompt: currentPrompt,
|
|
19187
19189
|
sessionName,
|
|
19188
19190
|
workdir: options.workdir,
|
|
19191
|
+
projectDir: options.projectDir,
|
|
19189
19192
|
auditDir: _runAuditConfig.agent.promptAudit.dir,
|
|
19190
19193
|
storyId: options.storyId,
|
|
19191
19194
|
featureName: options.featureName,
|
|
@@ -19521,6 +19524,9 @@ class AcpAgentAdapter {
|
|
|
19521
19524
|
}
|
|
19522
19525
|
return { stories };
|
|
19523
19526
|
}
|
|
19527
|
+
clearUnavailableAgents() {
|
|
19528
|
+
this._unavailableAgents.clear();
|
|
19529
|
+
}
|
|
19524
19530
|
markUnavailable(agentName) {
|
|
19525
19531
|
this._unavailableAgents.add(agentName);
|
|
19526
19532
|
}
|
|
@@ -20089,6 +20095,75 @@ var init_interactive = __esm(() => {
|
|
|
20089
20095
|
init_execution();
|
|
20090
20096
|
});
|
|
20091
20097
|
|
|
20098
|
+
// src/config/path-security.ts
|
|
20099
|
+
import { existsSync as existsSync2, lstatSync, realpathSync } from "fs";
|
|
20100
|
+
import { basename, isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
|
|
20101
|
+
function validateDirectory(dirPath, baseDir) {
|
|
20102
|
+
const resolved = resolve2(dirPath);
|
|
20103
|
+
if (!existsSync2(resolved)) {
|
|
20104
|
+
throw new Error(`Directory does not exist: ${dirPath}`);
|
|
20105
|
+
}
|
|
20106
|
+
let realPath;
|
|
20107
|
+
try {
|
|
20108
|
+
realPath = realpathSync(resolved);
|
|
20109
|
+
} catch (error48) {
|
|
20110
|
+
throw new Error(`Failed to resolve path: ${dirPath} (${error48.message})`);
|
|
20111
|
+
}
|
|
20112
|
+
try {
|
|
20113
|
+
const stats = lstatSync(realPath);
|
|
20114
|
+
if (!stats.isDirectory()) {
|
|
20115
|
+
throw new Error(`Not a directory: ${dirPath}`);
|
|
20116
|
+
}
|
|
20117
|
+
} catch (error48) {
|
|
20118
|
+
throw new Error(`Failed to stat path: ${dirPath} (${error48.message})`);
|
|
20119
|
+
}
|
|
20120
|
+
if (baseDir) {
|
|
20121
|
+
const resolvedBase = resolve2(baseDir);
|
|
20122
|
+
const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
20123
|
+
if (!isWithinDirectory(realPath, realBase)) {
|
|
20124
|
+
throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
|
|
20125
|
+
}
|
|
20126
|
+
}
|
|
20127
|
+
return realPath;
|
|
20128
|
+
}
|
|
20129
|
+
function isWithinDirectory(targetPath, basePath) {
|
|
20130
|
+
const normalizedTarget = normalize(targetPath);
|
|
20131
|
+
const normalizedBase = normalize(basePath);
|
|
20132
|
+
if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
|
|
20133
|
+
return false;
|
|
20134
|
+
}
|
|
20135
|
+
const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
|
|
20136
|
+
const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
|
|
20137
|
+
return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
|
|
20138
|
+
}
|
|
20139
|
+
function validateFilePath(filePath, baseDir) {
|
|
20140
|
+
const resolved = resolve2(filePath);
|
|
20141
|
+
let realPath;
|
|
20142
|
+
try {
|
|
20143
|
+
if (!existsSync2(resolved)) {
|
|
20144
|
+
const parent = resolve2(resolved, "..");
|
|
20145
|
+
if (existsSync2(parent)) {
|
|
20146
|
+
const realParent = realpathSync(parent);
|
|
20147
|
+
realPath = resolve2(realParent, basename(resolved));
|
|
20148
|
+
} else {
|
|
20149
|
+
realPath = resolved;
|
|
20150
|
+
}
|
|
20151
|
+
} else {
|
|
20152
|
+
realPath = realpathSync(resolved);
|
|
20153
|
+
}
|
|
20154
|
+
} catch (error48) {
|
|
20155
|
+
throw new Error(`Failed to resolve path: ${filePath} (${error48.message})`);
|
|
20156
|
+
}
|
|
20157
|
+
const resolvedBase = resolve2(baseDir);
|
|
20158
|
+
const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
20159
|
+
if (!isWithinDirectory(realPath, realBase)) {
|
|
20160
|
+
throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
|
|
20161
|
+
}
|
|
20162
|
+
return realPath;
|
|
20163
|
+
}
|
|
20164
|
+
var MAX_DIRECTORY_DEPTH = 10;
|
|
20165
|
+
var init_path_security = () => {};
|
|
20166
|
+
|
|
20092
20167
|
// src/agents/shared/model-resolution.ts
|
|
20093
20168
|
var exports_model_resolution = {};
|
|
20094
20169
|
__export(exports_model_resolution, {
|
|
@@ -20114,7 +20189,7 @@ var init_model_resolution = __esm(() => {
|
|
|
20114
20189
|
// src/agents/claude/plan.ts
|
|
20115
20190
|
import { mkdtempSync, rmSync } from "fs";
|
|
20116
20191
|
import { tmpdir } from "os";
|
|
20117
|
-
import { join as join3 } from "path";
|
|
20192
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
20118
20193
|
function buildPlanCommand(binary, options) {
|
|
20119
20194
|
const cmd = [binary, "--permission-mode", "plan"];
|
|
20120
20195
|
let modelDef = options.modelDef;
|
|
@@ -20134,17 +20209,12 @@ function buildPlanCommand(binary, options) {
|
|
|
20134
20209
|
|
|
20135
20210
|
${options.prompt}`;
|
|
20136
20211
|
}
|
|
20137
|
-
if (options.
|
|
20138
|
-
|
|
20139
|
-
const inputContent = __require("fs").readFileSync(__require("path").resolve(options.workdir, options.inputFile), "utf-8");
|
|
20140
|
-
fullPrompt = `${fullPrompt}
|
|
20212
|
+
if (options.resolvedInputContent) {
|
|
20213
|
+
fullPrompt = `${fullPrompt}
|
|
20141
20214
|
|
|
20142
20215
|
## Input Requirements
|
|
20143
20216
|
|
|
20144
|
-
${
|
|
20145
|
-
} catch (error48) {
|
|
20146
|
-
throw new Error(`Failed to read input file ${options.inputFile}: ${error48.message}`);
|
|
20147
|
-
}
|
|
20217
|
+
${options.resolvedInputContent}`;
|
|
20148
20218
|
}
|
|
20149
20219
|
if (!options.interactive) {
|
|
20150
20220
|
cmd.push("-p", fullPrompt);
|
|
@@ -20155,7 +20225,13 @@ ${inputContent}`;
|
|
|
20155
20225
|
}
|
|
20156
20226
|
async function runPlan(binary, options, pidRegistry) {
|
|
20157
20227
|
const { resolveBalancedModelDef: resolveBalancedModelDef2 } = await Promise.resolve().then(() => (init_model_resolution(), exports_model_resolution));
|
|
20158
|
-
|
|
20228
|
+
let resolvedOptions = options;
|
|
20229
|
+
if (options.inputFile) {
|
|
20230
|
+
const inputPath = validateFilePath(resolve3(options.workdir, options.inputFile), options.workdir);
|
|
20231
|
+
const resolvedInputContent = await Bun.file(inputPath).text();
|
|
20232
|
+
resolvedOptions = { ...options, resolvedInputContent };
|
|
20233
|
+
}
|
|
20234
|
+
const cmd = buildPlanCommand(binary, resolvedOptions);
|
|
20159
20235
|
let modelDef = options.modelDef;
|
|
20160
20236
|
if (!modelDef) {
|
|
20161
20237
|
if (!options.config) {
|
|
@@ -20224,6 +20300,7 @@ async function runPlan(binary, options, pidRegistry) {
|
|
|
20224
20300
|
}
|
|
20225
20301
|
}
|
|
20226
20302
|
var init_plan = __esm(() => {
|
|
20303
|
+
init_path_security();
|
|
20227
20304
|
init_timeout_handler();
|
|
20228
20305
|
init_logger2();
|
|
20229
20306
|
init_env();
|
|
@@ -20376,8 +20453,8 @@ class ClaudeCodeAdapter {
|
|
|
20376
20453
|
let stdoutTimeoutId;
|
|
20377
20454
|
const stdout = await Promise.race([
|
|
20378
20455
|
new Response(proc.stdout).text(),
|
|
20379
|
-
new Promise((
|
|
20380
|
-
stdoutTimeoutId = setTimeout(() =>
|
|
20456
|
+
new Promise((resolve4) => {
|
|
20457
|
+
stdoutTimeoutId = setTimeout(() => resolve4(""), 5000);
|
|
20381
20458
|
})
|
|
20382
20459
|
]);
|
|
20383
20460
|
clearTimeout(stdoutTimeoutId);
|
|
@@ -20725,7 +20802,12 @@ function createAgentRegistry(config2) {
|
|
|
20725
20802
|
installed: await agent.isInstalled()
|
|
20726
20803
|
})));
|
|
20727
20804
|
}
|
|
20728
|
-
|
|
20805
|
+
function resetStoryState() {
|
|
20806
|
+
for (const adapter of acpCache.values()) {
|
|
20807
|
+
adapter.clearUnavailableAgents();
|
|
20808
|
+
}
|
|
20809
|
+
}
|
|
20810
|
+
return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol, resetStoryState };
|
|
20729
20811
|
}
|
|
20730
20812
|
var ALL_AGENTS;
|
|
20731
20813
|
var init_registry = __esm(() => {
|
|
@@ -20745,75 +20827,6 @@ var init_registry = __esm(() => {
|
|
|
20745
20827
|
];
|
|
20746
20828
|
});
|
|
20747
20829
|
|
|
20748
|
-
// src/config/path-security.ts
|
|
20749
|
-
import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
|
|
20750
|
-
import { basename, isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
|
|
20751
|
-
function validateDirectory(dirPath, baseDir) {
|
|
20752
|
-
const resolved = resolve2(dirPath);
|
|
20753
|
-
if (!existsSync3(resolved)) {
|
|
20754
|
-
throw new Error(`Directory does not exist: ${dirPath}`);
|
|
20755
|
-
}
|
|
20756
|
-
let realPath;
|
|
20757
|
-
try {
|
|
20758
|
-
realPath = realpathSync(resolved);
|
|
20759
|
-
} catch (error48) {
|
|
20760
|
-
throw new Error(`Failed to resolve path: ${dirPath} (${error48.message})`);
|
|
20761
|
-
}
|
|
20762
|
-
try {
|
|
20763
|
-
const stats = lstatSync(realPath);
|
|
20764
|
-
if (!stats.isDirectory()) {
|
|
20765
|
-
throw new Error(`Not a directory: ${dirPath}`);
|
|
20766
|
-
}
|
|
20767
|
-
} catch (error48) {
|
|
20768
|
-
throw new Error(`Failed to stat path: ${dirPath} (${error48.message})`);
|
|
20769
|
-
}
|
|
20770
|
-
if (baseDir) {
|
|
20771
|
-
const resolvedBase = resolve2(baseDir);
|
|
20772
|
-
const realBase = existsSync3(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
20773
|
-
if (!isWithinDirectory(realPath, realBase)) {
|
|
20774
|
-
throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
|
|
20775
|
-
}
|
|
20776
|
-
}
|
|
20777
|
-
return realPath;
|
|
20778
|
-
}
|
|
20779
|
-
function isWithinDirectory(targetPath, basePath) {
|
|
20780
|
-
const normalizedTarget = normalize(targetPath);
|
|
20781
|
-
const normalizedBase = normalize(basePath);
|
|
20782
|
-
if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
|
|
20783
|
-
return false;
|
|
20784
|
-
}
|
|
20785
|
-
const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
|
|
20786
|
-
const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
|
|
20787
|
-
return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
|
|
20788
|
-
}
|
|
20789
|
-
function validateFilePath(filePath, baseDir) {
|
|
20790
|
-
const resolved = resolve2(filePath);
|
|
20791
|
-
let realPath;
|
|
20792
|
-
try {
|
|
20793
|
-
if (!existsSync3(resolved)) {
|
|
20794
|
-
const parent = resolve2(resolved, "..");
|
|
20795
|
-
if (existsSync3(parent)) {
|
|
20796
|
-
const realParent = realpathSync(parent);
|
|
20797
|
-
realPath = resolve2(realParent, basename(resolved));
|
|
20798
|
-
} else {
|
|
20799
|
-
realPath = resolved;
|
|
20800
|
-
}
|
|
20801
|
-
} else {
|
|
20802
|
-
realPath = realpathSync(resolved);
|
|
20803
|
-
}
|
|
20804
|
-
} catch (error48) {
|
|
20805
|
-
throw new Error(`Failed to resolve path: ${filePath} (${error48.message})`);
|
|
20806
|
-
}
|
|
20807
|
-
const resolvedBase = resolve2(baseDir);
|
|
20808
|
-
const realBase = existsSync3(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
20809
|
-
if (!isWithinDirectory(realPath, realBase)) {
|
|
20810
|
-
throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
|
|
20811
|
-
}
|
|
20812
|
-
return realPath;
|
|
20813
|
-
}
|
|
20814
|
-
var MAX_DIRECTORY_DEPTH = 10;
|
|
20815
|
-
var init_path_security = () => {};
|
|
20816
|
-
|
|
20817
20830
|
// src/utils/json-file.ts
|
|
20818
20831
|
import { existsSync as existsSync6 } from "fs";
|
|
20819
20832
|
async function loadJsonFile(path, context = "json-file") {
|
|
@@ -21008,7 +21021,7 @@ function isPlainObject2(value) {
|
|
|
21008
21021
|
|
|
21009
21022
|
// src/config/paths.ts
|
|
21010
21023
|
import { homedir as homedir2 } from "os";
|
|
21011
|
-
import { join as join7, resolve as
|
|
21024
|
+
import { join as join7, resolve as resolve4 } from "path";
|
|
21012
21025
|
function globalConfigDir() {
|
|
21013
21026
|
const override = process.env[GLOBAL_CONFIG_DIR_ENV];
|
|
21014
21027
|
if (override)
|
|
@@ -21016,7 +21029,7 @@ function globalConfigDir() {
|
|
|
21016
21029
|
return join7(homedir2(), ".nax");
|
|
21017
21030
|
}
|
|
21018
21031
|
function projectConfigDir(projectRoot) {
|
|
21019
|
-
return join7(
|
|
21032
|
+
return join7(resolve4(projectRoot), PROJECT_NAX_DIR);
|
|
21020
21033
|
}
|
|
21021
21034
|
var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", PROJECT_NAX_DIR = ".nax";
|
|
21022
21035
|
var init_paths = () => {};
|
|
@@ -21170,12 +21183,12 @@ var init_profile = __esm(() => {
|
|
|
21170
21183
|
|
|
21171
21184
|
// src/config/loader.ts
|
|
21172
21185
|
import { existsSync as existsSync7 } from "fs";
|
|
21173
|
-
import { basename as basename2, dirname as
|
|
21186
|
+
import { basename as basename2, dirname as dirname2, join as join9, resolve as resolve5 } from "path";
|
|
21174
21187
|
function globalConfigPath() {
|
|
21175
21188
|
return join9(globalConfigDir(), "config.json");
|
|
21176
21189
|
}
|
|
21177
21190
|
function findProjectDir(startDir = process.cwd()) {
|
|
21178
|
-
let dir =
|
|
21191
|
+
let dir = resolve5(startDir);
|
|
21179
21192
|
let depth = 0;
|
|
21180
21193
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
21181
21194
|
const candidate = join9(dir, PROJECT_NAX_DIR);
|
|
@@ -21226,7 +21239,7 @@ function applyBatchModeCompat(conf) {
|
|
|
21226
21239
|
async function loadConfig(startDir, cliOverrides) {
|
|
21227
21240
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
21228
21241
|
const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
21229
|
-
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ?
|
|
21242
|
+
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname2(startDir) : startDir : process.cwd();
|
|
21230
21243
|
const profileName = await resolveProfileName(cliOverrides ?? {}, process.env, projectRoot);
|
|
21231
21244
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
21232
21245
|
if (globalConfRaw) {
|
|
@@ -21269,8 +21282,8 @@ ${errors3.join(`
|
|
|
21269
21282
|
}
|
|
21270
21283
|
async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
21271
21284
|
const logger = getLogger();
|
|
21272
|
-
const resolvedRootConfigPath =
|
|
21273
|
-
const rootNaxDir =
|
|
21285
|
+
const resolvedRootConfigPath = resolve5(rootConfigPath);
|
|
21286
|
+
const rootNaxDir = dirname2(resolvedRootConfigPath);
|
|
21274
21287
|
let rootConfigPromise = _rootConfigCache.get(resolvedRootConfigPath);
|
|
21275
21288
|
if (!rootConfigPromise) {
|
|
21276
21289
|
rootConfigPromise = loadConfig(rootNaxDir);
|
|
@@ -21281,7 +21294,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
|
21281
21294
|
logger.debug("config", "No packageDir \u2014 using root config");
|
|
21282
21295
|
return rootConfig;
|
|
21283
21296
|
}
|
|
21284
|
-
const repoRoot =
|
|
21297
|
+
const repoRoot = dirname2(rootNaxDir);
|
|
21285
21298
|
const packageConfigPath = join9(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
|
|
21286
21299
|
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
21287
21300
|
if (!packageOverride) {
|
|
@@ -22645,9 +22658,9 @@ ${request.summary}
|
|
|
22645
22658
|
if (!this.rl) {
|
|
22646
22659
|
throw new Error("CLI plugin not initialized");
|
|
22647
22660
|
}
|
|
22648
|
-
const timeoutPromise = new Promise((
|
|
22661
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
22649
22662
|
setTimeout(() => {
|
|
22650
|
-
|
|
22663
|
+
resolve6({
|
|
22651
22664
|
requestId: request.id,
|
|
22652
22665
|
action: "skip",
|
|
22653
22666
|
respondedBy: "timeout",
|
|
@@ -22799,9 +22812,9 @@ ${request.summary}
|
|
|
22799
22812
|
if (!this.rl) {
|
|
22800
22813
|
throw new Error("CLI plugin not initialized");
|
|
22801
22814
|
}
|
|
22802
|
-
return new Promise((
|
|
22815
|
+
return new Promise((resolve6) => {
|
|
22803
22816
|
this.rl?.question(prompt, (answer) => {
|
|
22804
|
-
|
|
22817
|
+
resolve6(answer);
|
|
22805
22818
|
});
|
|
22806
22819
|
});
|
|
22807
22820
|
}
|
|
@@ -23233,7 +23246,7 @@ class WebhookInteractionPlugin {
|
|
|
23233
23246
|
this.pendingResponses.delete(requestId);
|
|
23234
23247
|
return early;
|
|
23235
23248
|
}
|
|
23236
|
-
return new Promise((
|
|
23249
|
+
return new Promise((resolve6) => {
|
|
23237
23250
|
const existingCallback = this.receiveCallbacks.get(requestId);
|
|
23238
23251
|
if (existingCallback) {
|
|
23239
23252
|
this.clearReceiveTimer(requestId);
|
|
@@ -23247,7 +23260,7 @@ class WebhookInteractionPlugin {
|
|
|
23247
23260
|
const timer = setTimeout(() => {
|
|
23248
23261
|
this.clearReceiveTimer(requestId);
|
|
23249
23262
|
this.receiveCallbacks.delete(requestId);
|
|
23250
|
-
|
|
23263
|
+
resolve6({
|
|
23251
23264
|
requestId,
|
|
23252
23265
|
action: "skip",
|
|
23253
23266
|
respondedBy: "timeout",
|
|
@@ -23258,7 +23271,7 @@ class WebhookInteractionPlugin {
|
|
|
23258
23271
|
this.receiveCallbacks.set(requestId, (response) => {
|
|
23259
23272
|
this.clearReceiveTimer(requestId);
|
|
23260
23273
|
this.receiveCallbacks.delete(requestId);
|
|
23261
|
-
|
|
23274
|
+
resolve6(response);
|
|
23262
23275
|
});
|
|
23263
23276
|
});
|
|
23264
23277
|
}
|
|
@@ -25203,6 +25216,7 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25203
25216
|
const logger = getLogger();
|
|
25204
25217
|
const retryCountMap = new Map;
|
|
25205
25218
|
let i = 0;
|
|
25219
|
+
let stageCostAccum = 0;
|
|
25206
25220
|
while (i < stages.length) {
|
|
25207
25221
|
const stage = stages[i];
|
|
25208
25222
|
if (!stage.enabled(context)) {
|
|
@@ -25221,27 +25235,58 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25221
25235
|
reason: `Stage "${stage.name}" threw error: ${errorMessage(error48)}`
|
|
25222
25236
|
};
|
|
25223
25237
|
eventEmitter?.emit("stage:exit", stage.name, failResult);
|
|
25224
|
-
return {
|
|
25238
|
+
return {
|
|
25239
|
+
success: false,
|
|
25240
|
+
finalAction: "fail",
|
|
25241
|
+
reason: failResult.reason,
|
|
25242
|
+
stoppedAtStage: stage.name,
|
|
25243
|
+
context,
|
|
25244
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25245
|
+
};
|
|
25225
25246
|
}
|
|
25247
|
+
if (result.cost)
|
|
25248
|
+
stageCostAccum += result.cost;
|
|
25226
25249
|
eventEmitter?.emit("stage:exit", stage.name, result);
|
|
25227
25250
|
switch (result.action) {
|
|
25228
25251
|
case "continue":
|
|
25229
25252
|
i++;
|
|
25230
25253
|
continue;
|
|
25231
25254
|
case "skip":
|
|
25232
|
-
return {
|
|
25255
|
+
return {
|
|
25256
|
+
success: false,
|
|
25257
|
+
finalAction: "skip",
|
|
25258
|
+
reason: result.reason,
|
|
25259
|
+
stoppedAtStage: stage.name,
|
|
25260
|
+
context,
|
|
25261
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25262
|
+
};
|
|
25233
25263
|
case "fail":
|
|
25234
|
-
return {
|
|
25264
|
+
return {
|
|
25265
|
+
success: false,
|
|
25266
|
+
finalAction: "fail",
|
|
25267
|
+
reason: result.reason,
|
|
25268
|
+
stoppedAtStage: stage.name,
|
|
25269
|
+
context,
|
|
25270
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25271
|
+
};
|
|
25235
25272
|
case "escalate":
|
|
25236
25273
|
return {
|
|
25237
25274
|
success: false,
|
|
25238
25275
|
finalAction: "escalate",
|
|
25239
25276
|
reason: result.reason ?? "Stage requested escalation to higher tier",
|
|
25240
25277
|
stoppedAtStage: stage.name,
|
|
25241
|
-
context
|
|
25278
|
+
context,
|
|
25279
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25242
25280
|
};
|
|
25243
25281
|
case "pause":
|
|
25244
|
-
return {
|
|
25282
|
+
return {
|
|
25283
|
+
success: false,
|
|
25284
|
+
finalAction: "pause",
|
|
25285
|
+
reason: result.reason,
|
|
25286
|
+
stoppedAtStage: stage.name,
|
|
25287
|
+
context,
|
|
25288
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25289
|
+
};
|
|
25245
25290
|
case "retry": {
|
|
25246
25291
|
const retries = (retryCountMap.get(result.fromStage) ?? 0) + 1;
|
|
25247
25292
|
if (retries > MAX_STAGE_RETRIES) {
|
|
@@ -25251,7 +25296,8 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25251
25296
|
finalAction: "fail",
|
|
25252
25297
|
reason: `Stage "${stage.name}" exceeded max retries (${MAX_STAGE_RETRIES}) for "${result.fromStage}"`,
|
|
25253
25298
|
stoppedAtStage: stage.name,
|
|
25254
|
-
context
|
|
25299
|
+
context,
|
|
25300
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25255
25301
|
};
|
|
25256
25302
|
}
|
|
25257
25303
|
retryCountMap.set(result.fromStage, retries);
|
|
@@ -25263,7 +25309,8 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25263
25309
|
finalAction: "escalate",
|
|
25264
25310
|
reason: `Retry target stage "${result.fromStage}" not found`,
|
|
25265
25311
|
stoppedAtStage: stage.name,
|
|
25266
|
-
context
|
|
25312
|
+
context,
|
|
25313
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25267
25314
|
};
|
|
25268
25315
|
}
|
|
25269
25316
|
logger.debug("pipeline", `Retrying from stage "${result.fromStage}" (attempt ${retries}/${MAX_STAGE_RETRIES})`);
|
|
@@ -25276,7 +25323,12 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25276
25323
|
}
|
|
25277
25324
|
}
|
|
25278
25325
|
}
|
|
25279
|
-
return {
|
|
25326
|
+
return {
|
|
25327
|
+
success: true,
|
|
25328
|
+
finalAction: "complete",
|
|
25329
|
+
context,
|
|
25330
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25331
|
+
};
|
|
25280
25332
|
}
|
|
25281
25333
|
var MAX_STAGE_RETRIES = 5;
|
|
25282
25334
|
var init_runner = __esm(() => {
|
|
@@ -25814,8 +25866,7 @@ var init_acceptance = __esm(() => {
|
|
|
25814
25866
|
acceptanceStage = {
|
|
25815
25867
|
name: "acceptance",
|
|
25816
25868
|
enabled(ctx) {
|
|
25817
|
-
|
|
25818
|
-
if (!effectiveConfig.acceptance.enabled) {
|
|
25869
|
+
if (!ctx.config.acceptance.enabled) {
|
|
25819
25870
|
return false;
|
|
25820
25871
|
}
|
|
25821
25872
|
if (!areAllStoriesComplete(ctx)) {
|
|
@@ -25825,7 +25876,6 @@ var init_acceptance = __esm(() => {
|
|
|
25825
25876
|
},
|
|
25826
25877
|
async execute(ctx) {
|
|
25827
25878
|
const logger = getLogger();
|
|
25828
|
-
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
25829
25879
|
logger.info("acceptance", "Running acceptance tests", { storyId: ctx.story.id });
|
|
25830
25880
|
if (!ctx.featureDir) {
|
|
25831
25881
|
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests", { storyId: ctx.story.id });
|
|
@@ -25833,7 +25883,7 @@ var init_acceptance = __esm(() => {
|
|
|
25833
25883
|
}
|
|
25834
25884
|
const testGroups = ctx.acceptanceTestPaths ?? [
|
|
25835
25885
|
{
|
|
25836
|
-
testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir,
|
|
25886
|
+
testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language),
|
|
25837
25887
|
packageDir: ctx.workdir
|
|
25838
25888
|
}
|
|
25839
25889
|
];
|
|
@@ -25848,7 +25898,7 @@ var init_acceptance = __esm(() => {
|
|
|
25848
25898
|
logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { storyId: ctx.story.id, testPath });
|
|
25849
25899
|
continue;
|
|
25850
25900
|
}
|
|
25851
|
-
const testCmdParts = buildAcceptanceRunCommand(testPath,
|
|
25901
|
+
const testCmdParts = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
|
|
25852
25902
|
logger.info("acceptance", "Running acceptance command", {
|
|
25853
25903
|
storyId: ctx.story.id,
|
|
25854
25904
|
cmd: testCmdParts.join(" "),
|
|
@@ -25943,12 +25993,12 @@ function buildRefinementPrompt(criteria, codebaseContext, options) {
|
|
|
25943
25993
|
`);
|
|
25944
25994
|
const strategySection = buildStrategySection(options);
|
|
25945
25995
|
const refinedExample = buildRefinedExample(options?.testStrategy);
|
|
25996
|
+
const codebaseSection = codebaseContext ? `CODEBASE CONTEXT:
|
|
25997
|
+
${codebaseContext}
|
|
25998
|
+
` : "";
|
|
25946
25999
|
const core2 = `You are an acceptance criteria refinement assistant. Your task is to convert raw acceptance criteria into concrete, machine-verifiable assertions.
|
|
25947
26000
|
|
|
25948
|
-
|
|
25949
|
-
${codebaseContext}
|
|
25950
|
-
${strategySection}
|
|
25951
|
-
ACCEPTANCE CRITERIA TO REFINE:
|
|
26001
|
+
${codebaseSection}${strategySection}ACCEPTANCE CRITERIA TO REFINE:
|
|
25952
26002
|
${criteriaList}
|
|
25953
26003
|
|
|
25954
26004
|
For each criterion, produce a refined version that is concrete and automatically testable where possible.
|
|
@@ -26201,12 +26251,11 @@ ${stderr}` };
|
|
|
26201
26251
|
if (!ctx.featureDir) {
|
|
26202
26252
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
26203
26253
|
}
|
|
26204
|
-
const
|
|
26205
|
-
const
|
|
26206
|
-
const testPathConfig = (ctx.effectiveConfig ?? ctx.config).acceptance.testPath;
|
|
26254
|
+
const language = ctx.config.project?.language;
|
|
26255
|
+
const testPathConfig = ctx.config.acceptance.testPath;
|
|
26207
26256
|
const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
|
|
26208
|
-
const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria);
|
|
26209
|
-
const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-"));
|
|
26257
|
+
const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed").flatMap((s) => s.acceptanceCriteria);
|
|
26258
|
+
const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed");
|
|
26210
26259
|
const workdirGroups = new Map;
|
|
26211
26260
|
for (const story of nonFixStories) {
|
|
26212
26261
|
const wd = story.workdir ?? "";
|
|
@@ -26262,7 +26311,7 @@ ${stderr}` };
|
|
|
26262
26311
|
}
|
|
26263
26312
|
if (shouldGenerate) {
|
|
26264
26313
|
totalCriteria = allCriteria.length;
|
|
26265
|
-
const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.
|
|
26314
|
+
const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
|
|
26266
26315
|
let allRefinedCriteria;
|
|
26267
26316
|
if (ctx.config.acceptance.refinement) {
|
|
26268
26317
|
const maxConcurrency = ctx.config.acceptance.refinementConcurrency ?? 3;
|
|
@@ -26306,7 +26355,7 @@ ${stderr}` };
|
|
|
26306
26355
|
const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
|
|
26307
26356
|
let modelDef;
|
|
26308
26357
|
try {
|
|
26309
|
-
modelDef = resolveModelForAgent(ctx.
|
|
26358
|
+
modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.config.acceptance.model ?? "fast", ctx.rootConfig.autoMode.defaultAgent);
|
|
26310
26359
|
} catch {
|
|
26311
26360
|
const tier = ctx.config.acceptance.model ?? "fast";
|
|
26312
26361
|
modelDef = { provider: "anthropic", model: tier };
|
|
@@ -26342,7 +26391,7 @@ ${stderr}` };
|
|
|
26342
26391
|
}
|
|
26343
26392
|
let redFailCount = 0;
|
|
26344
26393
|
for (const { testPath, packageDir } of testPaths) {
|
|
26345
|
-
const runCmd = buildAcceptanceRunCommand(testPath,
|
|
26394
|
+
const runCmd = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
|
|
26346
26395
|
getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
|
|
26347
26396
|
cmd: runCmd.join(" "),
|
|
26348
26397
|
packageDir
|
|
@@ -26883,10 +26932,11 @@ ${prompt}`,
|
|
|
26883
26932
|
history.push({ role: "implementer", content: prompt });
|
|
26884
26933
|
history.push({ role: "reviewer", content: result.output });
|
|
26885
26934
|
const parsed = parseReviewResponse(result.output);
|
|
26886
|
-
|
|
26935
|
+
const reviewResult = { ...parsed, cost: result.estimatedCost ?? 0 };
|
|
26936
|
+
lastCheckResult = reviewResult;
|
|
26887
26937
|
lastStory = story;
|
|
26888
26938
|
lastSemanticConfig = semanticConfig;
|
|
26889
|
-
return
|
|
26939
|
+
return reviewResult;
|
|
26890
26940
|
},
|
|
26891
26941
|
async reReview(updatedDiff) {
|
|
26892
26942
|
if (!active) {
|
|
@@ -26920,7 +26970,7 @@ ${prompt}`,
|
|
|
26920
26970
|
history.push({ role: "reviewer", content: result.output });
|
|
26921
26971
|
const parsed = parseReviewResponse(result.output);
|
|
26922
26972
|
const deltaSummary = extractDeltaSummary(result.output, previousFindings, parsed.checkResult.findings);
|
|
26923
|
-
const dialogueResult = { ...parsed, deltaSummary };
|
|
26973
|
+
const dialogueResult = { ...parsed, deltaSummary, cost: result.estimatedCost ?? 0 };
|
|
26924
26974
|
lastCheckResult = dialogueResult;
|
|
26925
26975
|
const maxMessages = _config.review?.dialogue?.maxDialogueMessages ?? 20;
|
|
26926
26976
|
if (history.length > maxMessages) {
|
|
@@ -27433,6 +27483,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
27433
27483
|
timeoutSeconds: naxConfig?.execution?.sessionTimeoutSeconds
|
|
27434
27484
|
});
|
|
27435
27485
|
const debateResult = await debateSession.run(prompt);
|
|
27486
|
+
const debateCost = debateResult.totalCostUsd ?? 0;
|
|
27436
27487
|
let passCount = 0;
|
|
27437
27488
|
let failCount = 0;
|
|
27438
27489
|
const allFindings = [];
|
|
@@ -27475,7 +27526,8 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
27475
27526
|
|
|
27476
27527
|
${formatFindings(debateBlocking)}`,
|
|
27477
27528
|
durationMs: durationMs2,
|
|
27478
|
-
findings: toReviewFindings(debateBlocking)
|
|
27529
|
+
findings: toReviewFindings(debateBlocking),
|
|
27530
|
+
cost: debateCost
|
|
27479
27531
|
};
|
|
27480
27532
|
}
|
|
27481
27533
|
logger?.info("review", "Semantic review passed (debate, all findings non-blocking)", {
|
|
@@ -27488,7 +27540,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27488
27540
|
command: "",
|
|
27489
27541
|
exitCode: 0,
|
|
27490
27542
|
output: "Semantic review passed (debate, all findings were unverifiable or informational)",
|
|
27491
|
-
durationMs: durationMs2
|
|
27543
|
+
durationMs: durationMs2,
|
|
27544
|
+
cost: debateCost
|
|
27492
27545
|
};
|
|
27493
27546
|
}
|
|
27494
27547
|
logger?.info("review", "Semantic review passed (debate)", { storyId: story.id, durationMs: durationMs2 });
|
|
@@ -27498,7 +27551,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27498
27551
|
command: "",
|
|
27499
27552
|
exitCode: 0,
|
|
27500
27553
|
output: "Semantic review passed",
|
|
27501
|
-
durationMs: durationMs2
|
|
27554
|
+
durationMs: durationMs2,
|
|
27555
|
+
cost: debateCost
|
|
27502
27556
|
};
|
|
27503
27557
|
}
|
|
27504
27558
|
const implementerSidecarKey = `${story.id}:implementer`;
|
|
@@ -27517,6 +27571,7 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27517
27571
|
}
|
|
27518
27572
|
} catch {}
|
|
27519
27573
|
let rawResponse;
|
|
27574
|
+
let llmCost = 0;
|
|
27520
27575
|
try {
|
|
27521
27576
|
let runErr;
|
|
27522
27577
|
let runSucceeded = false;
|
|
@@ -27533,6 +27588,7 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27533
27588
|
config: naxConfig ?? DEFAULT_CONFIG
|
|
27534
27589
|
});
|
|
27535
27590
|
runOutput = runResult.output;
|
|
27591
|
+
llmCost = runResult.estimatedCost ?? 0;
|
|
27536
27592
|
runSucceeded = true;
|
|
27537
27593
|
} catch (err) {
|
|
27538
27594
|
runErr = err;
|
|
@@ -27548,6 +27604,7 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27548
27604
|
config: naxConfig ?? DEFAULT_CONFIG
|
|
27549
27605
|
});
|
|
27550
27606
|
rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
|
|
27607
|
+
llmCost = typeof completeResult === "string" ? 0 : completeResult.costUsd ?? 0;
|
|
27551
27608
|
}
|
|
27552
27609
|
} catch (err) {
|
|
27553
27610
|
logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
|
|
@@ -27573,7 +27630,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27573
27630
|
command: "",
|
|
27574
27631
|
exitCode: 1,
|
|
27575
27632
|
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
27576
|
-
durationMs: Date.now() - startTime
|
|
27633
|
+
durationMs: Date.now() - startTime,
|
|
27634
|
+
cost: llmCost
|
|
27577
27635
|
};
|
|
27578
27636
|
}
|
|
27579
27637
|
logger?.warn("semantic", "LLM returned invalid JSON \u2014 fail-open", { rawResponse: rawResponse.slice(0, 200) });
|
|
@@ -27583,7 +27641,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27583
27641
|
command: "",
|
|
27584
27642
|
exitCode: 0,
|
|
27585
27643
|
output: "semantic review: could not parse LLM response (fail-open)",
|
|
27586
|
-
durationMs: Date.now() - startTime
|
|
27644
|
+
durationMs: Date.now() - startTime,
|
|
27645
|
+
cost: llmCost
|
|
27587
27646
|
};
|
|
27588
27647
|
}
|
|
27589
27648
|
const blockingFindings = parsed.findings.filter((f) => isBlockingSeverity(f.severity));
|
|
@@ -27620,7 +27679,8 @@ ${formatFindings(blockingFindings)}`;
|
|
|
27620
27679
|
exitCode: 1,
|
|
27621
27680
|
output,
|
|
27622
27681
|
durationMs: durationMs2,
|
|
27623
|
-
findings: toReviewFindings(blockingFindings)
|
|
27682
|
+
findings: toReviewFindings(blockingFindings),
|
|
27683
|
+
cost: llmCost
|
|
27624
27684
|
};
|
|
27625
27685
|
}
|
|
27626
27686
|
if (!parsed.passed && blockingFindings.length === 0) {
|
|
@@ -27632,7 +27692,8 @@ ${formatFindings(blockingFindings)}`;
|
|
|
27632
27692
|
command: "",
|
|
27633
27693
|
exitCode: 0,
|
|
27634
27694
|
output: "Semantic review passed (all findings were unverifiable or informational)",
|
|
27635
|
-
durationMs: durationMs2
|
|
27695
|
+
durationMs: durationMs2,
|
|
27696
|
+
cost: llmCost
|
|
27636
27697
|
};
|
|
27637
27698
|
}
|
|
27638
27699
|
const durationMs = Date.now() - startTime;
|
|
@@ -27645,7 +27706,8 @@ ${formatFindings(blockingFindings)}`;
|
|
|
27645
27706
|
command: "",
|
|
27646
27707
|
exitCode: parsed.passed ? 0 : 1,
|
|
27647
27708
|
output: parsed.passed ? "Semantic review passed" : "Semantic review failed (no findings)",
|
|
27648
|
-
durationMs
|
|
27709
|
+
durationMs,
|
|
27710
|
+
cost: llmCost
|
|
27649
27711
|
};
|
|
27650
27712
|
}
|
|
27651
27713
|
var _semanticDeps, DIFF_CAP_BYTES = 51200;
|
|
@@ -27951,6 +28013,19 @@ class ReviewOrchestrator {
|
|
|
27951
28013
|
}
|
|
27952
28014
|
return { builtIn, success: true, pluginFailed: false };
|
|
27953
28015
|
}
|
|
28016
|
+
reviewFromContext(ctx) {
|
|
28017
|
+
const retrySkipChecks = ctx.retrySkipChecks;
|
|
28018
|
+
ctx.retrySkipChecks = undefined;
|
|
28019
|
+
const agentResolver = ctx.agentGetFn ?? undefined;
|
|
28020
|
+
const agentName = ctx.rootConfig.autoMode?.defaultAgent;
|
|
28021
|
+
const modelResolver = agentName ? (_tier) => agentResolver ? agentResolver(agentName) ?? null : null : undefined;
|
|
28022
|
+
return this.review(ctx.config.review, ctx.workdir, ctx.config.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir, ctx.config.quality?.commands, ctx.story.id, {
|
|
28023
|
+
id: ctx.story.id,
|
|
28024
|
+
title: ctx.story.title,
|
|
28025
|
+
description: ctx.story.description,
|
|
28026
|
+
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
28027
|
+
}, modelResolver, ctx.config, retrySkipChecks, ctx.prd.feature);
|
|
28028
|
+
}
|
|
27954
28029
|
}
|
|
27955
28030
|
var _orchestratorDeps, reviewOrchestrator;
|
|
27956
28031
|
var init_orchestrator = __esm(() => {
|
|
@@ -27966,7 +28041,6 @@ __export(exports_review, {
|
|
|
27966
28041
|
reviewStage: () => reviewStage,
|
|
27967
28042
|
_reviewDeps: () => _reviewDeps
|
|
27968
28043
|
});
|
|
27969
|
-
import { join as join17 } from "path";
|
|
27970
28044
|
var reviewStage, _reviewDeps;
|
|
27971
28045
|
var init_review = __esm(() => {
|
|
27972
28046
|
init_agents();
|
|
@@ -27976,18 +28050,13 @@ var init_review = __esm(() => {
|
|
|
27976
28050
|
init_orchestrator();
|
|
27977
28051
|
reviewStage = {
|
|
27978
28052
|
name: "review",
|
|
27979
|
-
enabled: (ctx) =>
|
|
28053
|
+
enabled: (ctx) => ctx.config.review.enabled,
|
|
27980
28054
|
async execute(ctx) {
|
|
27981
28055
|
const logger = getLogger();
|
|
27982
|
-
const
|
|
27983
|
-
const dialogueEnabled = effectiveConfig.review?.dialogue?.enabled ?? false;
|
|
28056
|
+
const dialogueEnabled = ctx.config.review?.dialogue?.enabled ?? false;
|
|
27984
28057
|
logger.info("review", "Running review phase", { storyId: ctx.story.id });
|
|
27985
|
-
const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
27986
28058
|
const agentResolver = ctx.agentGetFn ?? getAgent;
|
|
27987
|
-
const agentName =
|
|
27988
|
-
const modelResolver = (_tier) => agentName ? agentResolver(agentName) ?? null : null;
|
|
27989
|
-
const retrySkipChecks = ctx.retrySkipChecks;
|
|
27990
|
-
ctx.retrySkipChecks = undefined;
|
|
28059
|
+
const agentName = ctx.rootConfig.autoMode?.defaultAgent;
|
|
27991
28060
|
if (dialogueEnabled && ctx.reviewerSession) {
|
|
27992
28061
|
try {
|
|
27993
28062
|
const diff = ctx.storyGitRef ?? "";
|
|
@@ -28025,8 +28094,8 @@ var init_review = __esm(() => {
|
|
|
28025
28094
|
}
|
|
28026
28095
|
if (dialogueEnabled && !ctx.reviewerSession) {
|
|
28027
28096
|
const agent = agentName ? agentResolver(agentName) ?? null : null;
|
|
28028
|
-
ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id,
|
|
28029
|
-
const semanticConfig =
|
|
28097
|
+
ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id, ctx.workdir, ctx.prd.feature ?? "", ctx.config);
|
|
28098
|
+
const semanticConfig = ctx.config.review?.semantic;
|
|
28030
28099
|
if (semanticConfig && agent) {
|
|
28031
28100
|
try {
|
|
28032
28101
|
const diff = ctx.storyGitRef ?? "";
|
|
@@ -28054,6 +28123,7 @@ var init_review = __esm(() => {
|
|
|
28054
28123
|
],
|
|
28055
28124
|
totalDurationMs: 0
|
|
28056
28125
|
};
|
|
28126
|
+
const dialogueCost = sessionResult.cost ?? 0;
|
|
28057
28127
|
if (passed) {
|
|
28058
28128
|
logger.info("review", "Review passed (dialogue session)", { storyId: ctx.story.id });
|
|
28059
28129
|
} else {
|
|
@@ -28061,7 +28131,7 @@ var init_review = __esm(() => {
|
|
|
28061
28131
|
storyId: ctx.story.id
|
|
28062
28132
|
});
|
|
28063
28133
|
}
|
|
28064
|
-
return { action: "continue" };
|
|
28134
|
+
return { action: "continue", cost: dialogueCost || undefined };
|
|
28065
28135
|
} catch (err) {
|
|
28066
28136
|
logger.warn("review", "ReviewerSession.review() failed \u2014 falling back to one-shot review", {
|
|
28067
28137
|
storyId: ctx.story.id
|
|
@@ -28069,13 +28139,9 @@ var init_review = __esm(() => {
|
|
|
28069
28139
|
}
|
|
28070
28140
|
}
|
|
28071
28141
|
}
|
|
28072
|
-
const result = await reviewOrchestrator.
|
|
28073
|
-
id: ctx.story.id,
|
|
28074
|
-
title: ctx.story.title,
|
|
28075
|
-
description: ctx.story.description,
|
|
28076
|
-
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
28077
|
-
}, modelResolver, ctx.config, retrySkipChecks, ctx.prd.feature);
|
|
28142
|
+
const result = await reviewOrchestrator.reviewFromContext(ctx);
|
|
28078
28143
|
ctx.reviewResult = result.builtIn;
|
|
28144
|
+
const reviewCost = (result.builtIn.checks ?? []).reduce((sum, c) => sum + (c.cost ?? 0), 0) || undefined;
|
|
28079
28145
|
if (!result.success) {
|
|
28080
28146
|
const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
28081
28147
|
const semanticFindings = (result.builtIn.checks ?? []).filter((c) => c.check === "semantic" && !c.success && c.findings?.length).flatMap((c) => c.findings ?? []);
|
|
@@ -28084,29 +28150,29 @@ var init_review = __esm(() => {
|
|
|
28084
28150
|
ctx.reviewFindings = allFindings;
|
|
28085
28151
|
}
|
|
28086
28152
|
if (result.pluginFailed) {
|
|
28087
|
-
if (ctx.interaction && isTriggerEnabled("security-review",
|
|
28088
|
-
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id },
|
|
28153
|
+
if (ctx.interaction && isTriggerEnabled("security-review", ctx.config)) {
|
|
28154
|
+
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
|
|
28089
28155
|
if (!shouldContinue) {
|
|
28090
28156
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
28091
|
-
return { action: "fail", reason: `Review failed: ${result.failureReason}
|
|
28157
|
+
return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
|
|
28092
28158
|
}
|
|
28093
28159
|
logger.warn("review", "Security-review trigger escalated \u2014 retrying story", { storyId: ctx.story.id });
|
|
28094
|
-
return { action: "escalate", reason: `Review failed: ${result.failureReason}
|
|
28160
|
+
return { action: "escalate", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
|
|
28095
28161
|
}
|
|
28096
28162
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
28097
|
-
return { action: "fail", reason: `Review failed: ${result.failureReason}
|
|
28163
|
+
return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
|
|
28098
28164
|
}
|
|
28099
28165
|
logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
|
|
28100
28166
|
reason: result.failureReason,
|
|
28101
28167
|
storyId: ctx.story.id
|
|
28102
28168
|
});
|
|
28103
|
-
return { action: "continue" };
|
|
28169
|
+
return { action: "continue", cost: reviewCost };
|
|
28104
28170
|
}
|
|
28105
28171
|
logger.info("review", "Review passed", {
|
|
28106
28172
|
durationMs: result.builtIn.totalDurationMs,
|
|
28107
28173
|
storyId: ctx.story.id
|
|
28108
28174
|
});
|
|
28109
|
-
return { action: "continue" };
|
|
28175
|
+
return { action: "continue", cost: reviewCost };
|
|
28110
28176
|
}
|
|
28111
28177
|
};
|
|
28112
28178
|
_reviewDeps = {
|
|
@@ -28116,7 +28182,6 @@ var init_review = __esm(() => {
|
|
|
28116
28182
|
});
|
|
28117
28183
|
|
|
28118
28184
|
// src/pipeline/stages/autofix.ts
|
|
28119
|
-
import { join as join18 } from "path";
|
|
28120
28185
|
async function recheckReview(ctx) {
|
|
28121
28186
|
const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
|
|
28122
28187
|
if (!reviewStage2.enabled(ctx))
|
|
@@ -28154,16 +28219,15 @@ Your previous fix attempt (attempt ${attempt}) did not resolve the quality error
|
|
|
28154
28219
|
}
|
|
28155
28220
|
async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir) {
|
|
28156
28221
|
const logger = getLogger();
|
|
28157
|
-
const
|
|
28158
|
-
const
|
|
28159
|
-
const
|
|
28160
|
-
const
|
|
28161
|
-
const urgencyAtAttempt = effectiveConfig.quality.autofix?.urgencyAtAttempt ?? 3;
|
|
28222
|
+
const maxPerCycle = ctx.config.quality.autofix?.maxAttempts ?? 2;
|
|
28223
|
+
const maxTotal = ctx.config.quality.autofix?.maxTotalAttempts ?? 10;
|
|
28224
|
+
const rethinkAtAttempt = ctx.config.quality.autofix?.rethinkAtAttempt ?? 2;
|
|
28225
|
+
const urgencyAtAttempt = ctx.config.quality.autofix?.urgencyAtAttempt ?? 3;
|
|
28162
28226
|
const consumed = ctx.autofixAttempt ?? 0;
|
|
28163
28227
|
const failedChecks = collectFailedChecks(ctx);
|
|
28164
28228
|
if (failedChecks.length === 0) {
|
|
28165
28229
|
logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
|
|
28166
|
-
return false;
|
|
28230
|
+
return { succeeded: false, cost: 0 };
|
|
28167
28231
|
}
|
|
28168
28232
|
if (consumed >= maxTotal) {
|
|
28169
28233
|
logger.warn("autofix", "Global autofix budget exhausted \u2014 escalating", {
|
|
@@ -28171,7 +28235,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28171
28235
|
totalAttempts: consumed,
|
|
28172
28236
|
maxTotalAttempts: maxTotal
|
|
28173
28237
|
});
|
|
28174
|
-
return false;
|
|
28238
|
+
return { succeeded: false, cost: 0 };
|
|
28175
28239
|
}
|
|
28176
28240
|
const remainingBudget = maxTotal - consumed;
|
|
28177
28241
|
const maxAttempts = Math.min(maxPerCycle, remainingBudget);
|
|
@@ -28180,7 +28244,8 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28180
28244
|
attempt: 0,
|
|
28181
28245
|
failedChecks
|
|
28182
28246
|
};
|
|
28183
|
-
|
|
28247
|
+
let autofixCostAccum = 0;
|
|
28248
|
+
const succeeded = await runSharedRectificationLoop({
|
|
28184
28249
|
stage: "autofix",
|
|
28185
28250
|
storyId: ctx.story.id,
|
|
28186
28251
|
maxAttempts,
|
|
@@ -28207,29 +28272,30 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28207
28272
|
},
|
|
28208
28273
|
runAttempt: async (attempt, prompt) => {
|
|
28209
28274
|
ctx.autofixAttempt = consumed + attempt;
|
|
28210
|
-
const agent = agentGetFn(ctx.
|
|
28275
|
+
const agent = agentGetFn(ctx.rootConfig.autoMode.defaultAgent);
|
|
28211
28276
|
if (!agent) {
|
|
28212
28277
|
logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
|
|
28213
28278
|
throw new Error("AUTOFIX_AGENT_NOT_FOUND");
|
|
28214
28279
|
}
|
|
28215
|
-
const modelTier = ctx.story.routing?.modelTier ?? ctx.
|
|
28216
|
-
const modelDef = resolveModelForAgent(ctx.
|
|
28217
|
-
const rectificationWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
28280
|
+
const modelTier = ctx.story.routing?.modelTier ?? ctx.rootConfig.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
|
|
28281
|
+
const modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, modelTier, ctx.rootConfig.autoMode.defaultAgent);
|
|
28218
28282
|
const result = await agent.run({
|
|
28219
28283
|
prompt,
|
|
28220
|
-
workdir:
|
|
28284
|
+
workdir: ctx.workdir,
|
|
28221
28285
|
modelTier,
|
|
28222
28286
|
modelDef,
|
|
28223
28287
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
28224
28288
|
dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
|
|
28225
28289
|
pipelineStage: "rectification",
|
|
28226
28290
|
config: ctx.config,
|
|
28291
|
+
projectDir: ctx.projectDir,
|
|
28227
28292
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
28228
28293
|
storyId: ctx.story.id,
|
|
28229
28294
|
sessionRole: "implementer"
|
|
28230
28295
|
});
|
|
28296
|
+
autofixCostAccum += result.estimatedCost ?? 0;
|
|
28231
28297
|
if (ctx.reviewerSession && result.output) {
|
|
28232
|
-
const maxClarifications =
|
|
28298
|
+
const maxClarifications = ctx.config.review?.dialogue?.maxClarificationsPerAttempt ?? 3;
|
|
28233
28299
|
let clarifyCount = 0;
|
|
28234
28300
|
const clarifyRegex = new RegExp(CLARIFY_REGEX.source, `${CLARIFY_REGEX.flags}g`);
|
|
28235
28301
|
let match;
|
|
@@ -28312,6 +28378,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28312
28378
|
}
|
|
28313
28379
|
throw error48;
|
|
28314
28380
|
});
|
|
28381
|
+
return { succeeded, cost: autofixCostAccum };
|
|
28315
28382
|
}
|
|
28316
28383
|
var CLARIFY_REGEX, autofixStage, _autofixDeps;
|
|
28317
28384
|
var init_autofix = __esm(() => {
|
|
@@ -28329,7 +28396,7 @@ var init_autofix = __esm(() => {
|
|
|
28329
28396
|
return false;
|
|
28330
28397
|
if (ctx.reviewResult.success)
|
|
28331
28398
|
return false;
|
|
28332
|
-
const autofixEnabled =
|
|
28399
|
+
const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
|
|
28333
28400
|
return autofixEnabled;
|
|
28334
28401
|
},
|
|
28335
28402
|
skipReason(ctx) {
|
|
@@ -28343,16 +28410,14 @@ var init_autofix = __esm(() => {
|
|
|
28343
28410
|
if (!reviewResult || reviewResult.success) {
|
|
28344
28411
|
return { action: "continue" };
|
|
28345
28412
|
}
|
|
28346
|
-
const
|
|
28347
|
-
const
|
|
28348
|
-
const formatFixCmd = effectiveConfig.quality.commands.formatFix ?? effectiveConfig.review.commands.formatFix;
|
|
28349
|
-
const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
28413
|
+
const lintFixCmd = ctx.config.quality.commands.lintFix ?? ctx.config.review.commands.lintFix;
|
|
28414
|
+
const formatFixCmd = ctx.config.quality.commands.formatFix ?? ctx.config.review.commands.formatFix;
|
|
28350
28415
|
const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
|
|
28351
28416
|
const hasLintFailure = failedCheckNames.has("lint");
|
|
28352
28417
|
logger.info("autofix", "Starting autofix", {
|
|
28353
28418
|
storyId: ctx.story.id,
|
|
28354
28419
|
failedChecks: [...failedCheckNames],
|
|
28355
|
-
workdir:
|
|
28420
|
+
workdir: ctx.workdir
|
|
28356
28421
|
});
|
|
28357
28422
|
if (hasLintFailure && (lintFixCmd || formatFixCmd)) {
|
|
28358
28423
|
if (lintFixCmd) {
|
|
@@ -28360,7 +28425,7 @@ var init_autofix = __esm(() => {
|
|
|
28360
28425
|
const lintResult = await _autofixDeps.runQualityCommand({
|
|
28361
28426
|
commandName: "lintFix",
|
|
28362
28427
|
command: lintFixCmd,
|
|
28363
|
-
workdir:
|
|
28428
|
+
workdir: ctx.workdir,
|
|
28364
28429
|
storyId: ctx.story.id
|
|
28365
28430
|
});
|
|
28366
28431
|
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id, command: lintFixCmd });
|
|
@@ -28376,7 +28441,7 @@ var init_autofix = __esm(() => {
|
|
|
28376
28441
|
const fmtResult = await _autofixDeps.runQualityCommand({
|
|
28377
28442
|
commandName: "formatFix",
|
|
28378
28443
|
command: formatFixCmd,
|
|
28379
|
-
workdir:
|
|
28444
|
+
workdir: ctx.workdir,
|
|
28380
28445
|
storyId: ctx.story.id
|
|
28381
28446
|
});
|
|
28382
28447
|
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, {
|
|
@@ -28400,7 +28465,7 @@ var init_autofix = __esm(() => {
|
|
|
28400
28465
|
storyId: ctx.story.id
|
|
28401
28466
|
});
|
|
28402
28467
|
}
|
|
28403
|
-
const agentFixed = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd,
|
|
28468
|
+
const { succeeded: agentFixed, cost: agentCost } = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, ctx.workdir);
|
|
28404
28469
|
if (agentFixed) {
|
|
28405
28470
|
if (ctx.reviewResult)
|
|
28406
28471
|
ctx.reviewResult = { ...ctx.reviewResult, success: true };
|
|
@@ -28413,10 +28478,14 @@ var init_autofix = __esm(() => {
|
|
|
28413
28478
|
});
|
|
28414
28479
|
}
|
|
28415
28480
|
logger.info("autofix", "Agent rectification succeeded \u2014 retrying review", { storyId: ctx.story.id });
|
|
28416
|
-
return { action: "retry", fromStage: "review" };
|
|
28481
|
+
return { action: "retry", fromStage: "review", cost: agentCost };
|
|
28417
28482
|
}
|
|
28418
28483
|
logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
|
|
28419
|
-
return {
|
|
28484
|
+
return {
|
|
28485
|
+
action: "escalate",
|
|
28486
|
+
reason: "Autofix exhausted: review still failing after fix attempts",
|
|
28487
|
+
cost: agentCost
|
|
28488
|
+
};
|
|
28420
28489
|
}
|
|
28421
28490
|
};
|
|
28422
28491
|
_autofixDeps = {
|
|
@@ -28486,10 +28555,10 @@ var init_semantic_verdict = __esm(() => {
|
|
|
28486
28555
|
|
|
28487
28556
|
// src/execution/progress.ts
|
|
28488
28557
|
import { appendFile as appendFile2, mkdir } from "fs/promises";
|
|
28489
|
-
import { join as
|
|
28558
|
+
import { join as join17 } from "path";
|
|
28490
28559
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
28491
28560
|
await mkdir(featureDir, { recursive: true });
|
|
28492
|
-
const progressPath =
|
|
28561
|
+
const progressPath = join17(featureDir, "progress.txt");
|
|
28493
28562
|
const timestamp = new Date().toISOString();
|
|
28494
28563
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
28495
28564
|
`;
|
|
@@ -28595,7 +28664,7 @@ function estimateTokens(text) {
|
|
|
28595
28664
|
|
|
28596
28665
|
// src/constitution/loader.ts
|
|
28597
28666
|
import { existsSync as existsSync17 } from "fs";
|
|
28598
|
-
import { join as
|
|
28667
|
+
import { join as join18 } from "path";
|
|
28599
28668
|
function truncateToTokens(text, maxTokens) {
|
|
28600
28669
|
const maxChars = maxTokens * 3;
|
|
28601
28670
|
if (text.length <= maxChars) {
|
|
@@ -28617,7 +28686,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
28617
28686
|
}
|
|
28618
28687
|
let combinedContent = "";
|
|
28619
28688
|
if (!config2.skipGlobal) {
|
|
28620
|
-
const globalPath =
|
|
28689
|
+
const globalPath = join18(globalConfigDir(), config2.path);
|
|
28621
28690
|
if (existsSync17(globalPath)) {
|
|
28622
28691
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
28623
28692
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -28627,7 +28696,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
28627
28696
|
}
|
|
28628
28697
|
}
|
|
28629
28698
|
}
|
|
28630
|
-
const projectPath =
|
|
28699
|
+
const projectPath = join18(projectDir, config2.path);
|
|
28631
28700
|
if (existsSync17(projectPath)) {
|
|
28632
28701
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
28633
28702
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -28675,7 +28744,7 @@ var init_constitution = __esm(() => {
|
|
|
28675
28744
|
});
|
|
28676
28745
|
|
|
28677
28746
|
// src/pipeline/stages/constitution.ts
|
|
28678
|
-
import { dirname as
|
|
28747
|
+
import { dirname as dirname3 } from "path";
|
|
28679
28748
|
var constitutionStage;
|
|
28680
28749
|
var init_constitution2 = __esm(() => {
|
|
28681
28750
|
init_constitution();
|
|
@@ -28685,7 +28754,7 @@ var init_constitution2 = __esm(() => {
|
|
|
28685
28754
|
enabled: (ctx) => ctx.config.constitution.enabled,
|
|
28686
28755
|
async execute(ctx) {
|
|
28687
28756
|
const logger = getLogger();
|
|
28688
|
-
const ngentDir = ctx.featureDir ?
|
|
28757
|
+
const ngentDir = ctx.featureDir ? dirname3(dirname3(ctx.featureDir)) : `${ctx.workdir}/nax`;
|
|
28689
28758
|
const result = await loadConstitution(ngentDir, ctx.config.constitution);
|
|
28690
28759
|
if (result) {
|
|
28691
28760
|
ctx.constitution = result;
|
|
@@ -28850,6 +28919,27 @@ function createStoryContext(story, priority) {
|
|
|
28850
28919
|
return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
28851
28920
|
}
|
|
28852
28921
|
function createDependencyContext(story, priority) {
|
|
28922
|
+
const content = isCompletedDependency(story) ? formatCompletedDependency(story) : formatFullDependency(story);
|
|
28923
|
+
return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
28924
|
+
}
|
|
28925
|
+
function isCompletedDependency(story) {
|
|
28926
|
+
return story.status === "passed" || story.status === "decomposed" || story.status === "skipped";
|
|
28927
|
+
}
|
|
28928
|
+
function formatCompletedDependency(story) {
|
|
28929
|
+
const header = `## ${story.id} (${story.status}): ${story.title}`;
|
|
28930
|
+
if (story.diffSummary) {
|
|
28931
|
+
return `${header}
|
|
28932
|
+
|
|
28933
|
+
**Changes made:**
|
|
28934
|
+
\`\`\`
|
|
28935
|
+
${story.diffSummary}
|
|
28936
|
+
\`\`\``;
|
|
28937
|
+
}
|
|
28938
|
+
return `${header}
|
|
28939
|
+
|
|
28940
|
+
Status: ${story.status} (no diff summary available)`;
|
|
28941
|
+
}
|
|
28942
|
+
function formatFullDependency(story) {
|
|
28853
28943
|
let content = formatStoryAsText(story);
|
|
28854
28944
|
if (story.diffSummary) {
|
|
28855
28945
|
content += `
|
|
@@ -28859,7 +28949,7 @@ function createDependencyContext(story, priority) {
|
|
|
28859
28949
|
${story.diffSummary}
|
|
28860
28950
|
\`\`\``;
|
|
28861
28951
|
}
|
|
28862
|
-
return
|
|
28952
|
+
return content;
|
|
28863
28953
|
}
|
|
28864
28954
|
function createErrorContext(errorMessage2, priority) {
|
|
28865
28955
|
return { type: "error", content: errorMessage2, priority, tokens: estimateTokens(errorMessage2) };
|
|
@@ -29535,7 +29625,8 @@ ${pkgContent.trim()}`;
|
|
|
29535
29625
|
if (built.elements.length === 0 && !packageSection) {
|
|
29536
29626
|
return;
|
|
29537
29627
|
}
|
|
29538
|
-
const
|
|
29628
|
+
const elementsForMarkdown = built.elements.filter((e) => e.type !== "story");
|
|
29629
|
+
const baseMarkdown = elementsForMarkdown.length > 0 ? formatContextAsMarkdown({ ...built, elements: elementsForMarkdown }) : "";
|
|
29539
29630
|
const markdown = packageSection ? `${baseMarkdown}${packageSection}` : baseMarkdown;
|
|
29540
29631
|
return { markdown, builtContext: built };
|
|
29541
29632
|
} catch (error48) {
|
|
@@ -29546,6 +29637,10 @@ ${pkgContent.trim()}`;
|
|
|
29546
29637
|
return;
|
|
29547
29638
|
}
|
|
29548
29639
|
}
|
|
29640
|
+
function buildStoryContextFullFromCtx(ctx) {
|
|
29641
|
+
const packageWorkdir = ctx.story.workdir ? ctx.workdir : undefined;
|
|
29642
|
+
return buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
29643
|
+
}
|
|
29549
29644
|
function getAllReadyStories(prd) {
|
|
29550
29645
|
const storyIds = new Set(prd.userStories.map((s) => s.id));
|
|
29551
29646
|
const completedIds = new Set(prd.userStories.filter((s) => s.passes || s.status === "skipped").map((s) => s.id));
|
|
@@ -29656,7 +29751,6 @@ var init_helpers = __esm(() => {
|
|
|
29656
29751
|
});
|
|
29657
29752
|
|
|
29658
29753
|
// src/pipeline/stages/context.ts
|
|
29659
|
-
import { join as join21 } from "path";
|
|
29660
29754
|
var contextStage;
|
|
29661
29755
|
var init_context2 = __esm(() => {
|
|
29662
29756
|
init_helpers();
|
|
@@ -29666,8 +29760,7 @@ var init_context2 = __esm(() => {
|
|
|
29666
29760
|
enabled: () => true,
|
|
29667
29761
|
async execute(ctx) {
|
|
29668
29762
|
const logger = getLogger();
|
|
29669
|
-
const
|
|
29670
|
-
const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
29763
|
+
const result = await buildStoryContextFullFromCtx(ctx);
|
|
29671
29764
|
if (result) {
|
|
29672
29765
|
ctx.contextMarkdown = result.markdown;
|
|
29673
29766
|
ctx.builtContext = result.builtContext;
|
|
@@ -29800,14 +29893,14 @@ var init_isolation = __esm(() => {
|
|
|
29800
29893
|
|
|
29801
29894
|
// src/context/greenfield.ts
|
|
29802
29895
|
import { readdir } from "fs/promises";
|
|
29803
|
-
import { join as
|
|
29896
|
+
import { join as join19 } from "path";
|
|
29804
29897
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
29805
29898
|
const results = [];
|
|
29806
29899
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
29807
29900
|
try {
|
|
29808
29901
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
29809
29902
|
for (const entry of entries) {
|
|
29810
|
-
const fullPath =
|
|
29903
|
+
const fullPath = join19(dir, entry.name);
|
|
29811
29904
|
if (entry.isDirectory()) {
|
|
29812
29905
|
if (ignoreDirs.has(entry.name))
|
|
29813
29906
|
continue;
|
|
@@ -29873,10 +29966,10 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
|
|
|
29873
29966
|
const timeoutMs = timeoutSeconds * 1000;
|
|
29874
29967
|
let timedOut = false;
|
|
29875
29968
|
const timer = { id: undefined };
|
|
29876
|
-
const timeoutPromise = new Promise((
|
|
29969
|
+
const timeoutPromise = new Promise((resolve8) => {
|
|
29877
29970
|
timer.id = setTimeout(() => {
|
|
29878
29971
|
timedOut = true;
|
|
29879
|
-
|
|
29972
|
+
resolve8();
|
|
29880
29973
|
}, timeoutMs);
|
|
29881
29974
|
});
|
|
29882
29975
|
const processPromise = proc.exited;
|
|
@@ -29885,7 +29978,12 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
|
|
|
29885
29978
|
if (timedOut) {
|
|
29886
29979
|
const pid = proc.pid;
|
|
29887
29980
|
killProcessGroup(pid, "SIGTERM");
|
|
29888
|
-
await
|
|
29981
|
+
await Promise.race([
|
|
29982
|
+
proc.exited,
|
|
29983
|
+
new Promise((resolve8) => {
|
|
29984
|
+
setTimeout(resolve8, gracePeriodMs);
|
|
29985
|
+
})
|
|
29986
|
+
]);
|
|
29889
29987
|
killProcessGroup(pid, "SIGKILL");
|
|
29890
29988
|
const [out, err] = await Promise.all([
|
|
29891
29989
|
raceWithDeadline(stdoutPromise, drainTimeoutMs),
|
|
@@ -30147,13 +30245,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
30147
30245
|
|
|
30148
30246
|
// src/verification/runners.ts
|
|
30149
30247
|
import { existsSync as existsSync18 } from "fs";
|
|
30150
|
-
import { join as
|
|
30248
|
+
import { join as join20 } from "path";
|
|
30151
30249
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
30152
30250
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
30153
30251
|
return { success: true, missingFiles: [] };
|
|
30154
30252
|
const missingFiles = [];
|
|
30155
30253
|
for (const file3 of expectedFiles) {
|
|
30156
|
-
if (!existsSync18(
|
|
30254
|
+
if (!existsSync18(join20(workingDirectory, file3)))
|
|
30157
30255
|
missingFiles.push(file3);
|
|
30158
30256
|
}
|
|
30159
30257
|
if (missingFiles.length > 0) {
|
|
@@ -30478,10 +30576,10 @@ var init_prompts = __esm(() => {
|
|
|
30478
30576
|
});
|
|
30479
30577
|
|
|
30480
30578
|
// src/tdd/rectification-gate.ts
|
|
30481
|
-
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
|
|
30579
|
+
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir) {
|
|
30482
30580
|
const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
|
|
30483
30581
|
if (!rectificationEnabled)
|
|
30484
|
-
return false;
|
|
30582
|
+
return { passed: false, cost: 0 };
|
|
30485
30583
|
const rectificationConfig = config2.execution.rectification;
|
|
30486
30584
|
const testCmd = config2.quality?.commands?.test ?? "bun test";
|
|
30487
30585
|
const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
|
|
@@ -30496,7 +30594,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30496
30594
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
30497
30595
|
const testSummary = _rectificationGateDeps.parseBunTestOutput(fullSuiteResult.output);
|
|
30498
30596
|
if (testSummary.failed > 0) {
|
|
30499
|
-
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
|
|
30597
|
+
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
|
|
30500
30598
|
}
|
|
30501
30599
|
if (testSummary.passed > 0) {
|
|
30502
30600
|
logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
|
|
@@ -30504,7 +30602,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30504
30602
|
exitCode: fullSuiteResult.exitCode,
|
|
30505
30603
|
passedTests: testSummary.passed
|
|
30506
30604
|
});
|
|
30507
|
-
return true;
|
|
30605
|
+
return { passed: true, cost: 0 };
|
|
30508
30606
|
}
|
|
30509
30607
|
logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
|
|
30510
30608
|
storyId: story.id,
|
|
@@ -30512,19 +30610,19 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30512
30610
|
outputLength: fullSuiteResult.output.length,
|
|
30513
30611
|
outputTail: fullSuiteResult.output.slice(-200)
|
|
30514
30612
|
});
|
|
30515
|
-
return false;
|
|
30613
|
+
return { passed: false, cost: 0 };
|
|
30516
30614
|
}
|
|
30517
30615
|
if (fullSuitePassed) {
|
|
30518
30616
|
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
30519
|
-
return true;
|
|
30617
|
+
return { passed: true, cost: 0 };
|
|
30520
30618
|
}
|
|
30521
30619
|
logger.warn("tdd", "Full suite gate execution failed (no output)", {
|
|
30522
30620
|
storyId: story.id,
|
|
30523
30621
|
exitCode: fullSuiteResult.exitCode
|
|
30524
30622
|
});
|
|
30525
|
-
return false;
|
|
30623
|
+
return { passed: false, cost: 0 };
|
|
30526
30624
|
}
|
|
30527
|
-
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
|
|
30625
|
+
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir) {
|
|
30528
30626
|
const rectificationState = {
|
|
30529
30627
|
attempt: 0,
|
|
30530
30628
|
initialFailures: testSummary.failed,
|
|
@@ -30544,6 +30642,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30544
30642
|
...rectificationState,
|
|
30545
30643
|
isolationPassed: true
|
|
30546
30644
|
};
|
|
30645
|
+
let gateCostAccum = 0;
|
|
30547
30646
|
const fixed = await runSharedRectificationLoop({
|
|
30548
30647
|
stage: "tdd",
|
|
30549
30648
|
storyId: story.id,
|
|
@@ -30575,6 +30674,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30575
30674
|
dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
|
|
30576
30675
|
pipelineStage: "rectification",
|
|
30577
30676
|
config: config2,
|
|
30677
|
+
projectDir,
|
|
30578
30678
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
30579
30679
|
featureName,
|
|
30580
30680
|
storyId: story.id,
|
|
@@ -30585,6 +30685,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30585
30685
|
if (!rectifyResult.success && rectifyResult.pid) {
|
|
30586
30686
|
await cleanupProcessTree(rectifyResult.pid);
|
|
30587
30687
|
}
|
|
30688
|
+
gateCostAccum += rectifyResult.estimatedCost ?? 0;
|
|
30588
30689
|
if (rectifyResult.success) {
|
|
30589
30690
|
logger.info("tdd", "Rectification agent session complete", {
|
|
30590
30691
|
storyId: story.id,
|
|
@@ -30645,7 +30746,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30645
30746
|
}
|
|
30646
30747
|
});
|
|
30647
30748
|
if (fixed) {
|
|
30648
|
-
return true;
|
|
30749
|
+
return { passed: true, cost: gateCostAccum };
|
|
30649
30750
|
}
|
|
30650
30751
|
const finalFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
|
|
30651
30752
|
cwd: workdir
|
|
@@ -30657,10 +30758,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30657
30758
|
attempts: rectificationState.attempt,
|
|
30658
30759
|
remainingFailures: rectificationState.currentFailures
|
|
30659
30760
|
});
|
|
30660
|
-
return false;
|
|
30761
|
+
return { passed: false, cost: gateCostAccum };
|
|
30661
30762
|
}
|
|
30662
30763
|
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
30663
|
-
return true;
|
|
30764
|
+
return { passed: true, cost: gateCostAccum };
|
|
30664
30765
|
}
|
|
30665
30766
|
var _rectificationGateDeps;
|
|
30666
30767
|
var init_rectification_gate = __esm(() => {
|
|
@@ -31064,36 +31165,10 @@ Set \`approved: false\` when ANY of these conditions are true:
|
|
|
31064
31165
|
- Critical acceptance criteria are not met
|
|
31065
31166
|
- Code quality is poor (security issues, severe bugs, etc.)
|
|
31066
31167
|
|
|
31067
|
-
**
|
|
31168
|
+
**JSON schema** (fill in all fields with real values):
|
|
31068
31169
|
|
|
31069
31170
|
\`\`\`json
|
|
31070
|
-
{
|
|
31071
|
-
"version": 1,
|
|
31072
|
-
"approved": true,
|
|
31073
|
-
"tests": {
|
|
31074
|
-
"allPassing": true,
|
|
31075
|
-
"passCount": 42,
|
|
31076
|
-
"failCount": 0
|
|
31077
|
-
},
|
|
31078
|
-
"testModifications": {
|
|
31079
|
-
"detected": false,
|
|
31080
|
-
"files": [],
|
|
31081
|
-
"legitimate": true,
|
|
31082
|
-
"reasoning": "No test files were modified by the implementer"
|
|
31083
|
-
},
|
|
31084
|
-
"acceptanceCriteria": {
|
|
31085
|
-
"allMet": true,
|
|
31086
|
-
"criteria": [
|
|
31087
|
-
{ "criterion": "Example criterion", "met": true }
|
|
31088
|
-
]
|
|
31089
|
-
},
|
|
31090
|
-
"quality": {
|
|
31091
|
-
"rating": "good",
|
|
31092
|
-
"issues": []
|
|
31093
|
-
},
|
|
31094
|
-
"fixes": [],
|
|
31095
|
-
"reasoning": "All tests pass, implementation is clean, all acceptance criteria are met."
|
|
31096
|
-
}
|
|
31171
|
+
{"version":1,"approved":true,"tests":{"allPassing":true,"passCount":42,"failCount":0},"testModifications":{"detected":false,"files":[],"legitimate":true,"reasoning":"..."},"acceptanceCriteria":{"allMet":true,"criteria":[{"criterion":"...","met":true}]},"quality":{"rating":"good","issues":[]},"fixes":[],"reasoning":"..."}
|
|
31097
31172
|
\`\`\`
|
|
31098
31173
|
|
|
31099
31174
|
**Field notes:**
|
|
@@ -31110,13 +31185,13 @@ var exports_loader = {};
|
|
|
31110
31185
|
__export(exports_loader, {
|
|
31111
31186
|
loadOverride: () => loadOverride
|
|
31112
31187
|
});
|
|
31113
|
-
import { join as
|
|
31188
|
+
import { join as join21 } from "path";
|
|
31114
31189
|
async function loadOverride(role, workdir, config2) {
|
|
31115
31190
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
31116
31191
|
if (!overridePath) {
|
|
31117
31192
|
return null;
|
|
31118
31193
|
}
|
|
31119
|
-
const absolutePath =
|
|
31194
|
+
const absolutePath = join21(workdir, overridePath);
|
|
31120
31195
|
const file3 = Bun.file(absolutePath);
|
|
31121
31196
|
if (!await file3.exists()) {
|
|
31122
31197
|
return null;
|
|
@@ -31326,7 +31401,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
31326
31401
|
}
|
|
31327
31402
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
31328
31403
|
}
|
|
31329
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge) {
|
|
31404
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge, projectDir) {
|
|
31330
31405
|
const startTime = Date.now();
|
|
31331
31406
|
let prompt;
|
|
31332
31407
|
if (_sessionRunnerDeps.buildPrompt) {
|
|
@@ -31356,6 +31431,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
31356
31431
|
dangerouslySkipPermissions: resolvePermissions(config2, "run").skipPermissions,
|
|
31357
31432
|
pipelineStage: "run",
|
|
31358
31433
|
config: config2,
|
|
31434
|
+
projectDir,
|
|
31359
31435
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
31360
31436
|
featureName,
|
|
31361
31437
|
storyId: story.id,
|
|
@@ -31727,7 +31803,8 @@ async function runThreeSessionTdd(options) {
|
|
|
31727
31803
|
dryRun = false,
|
|
31728
31804
|
lite = false,
|
|
31729
31805
|
_recursionDepth = 0,
|
|
31730
|
-
interactionChain
|
|
31806
|
+
interactionChain,
|
|
31807
|
+
projectDir
|
|
31731
31808
|
} = options;
|
|
31732
31809
|
const logger = getLogger();
|
|
31733
31810
|
const MAX_RECURSION_DEPTH = 2;
|
|
@@ -31786,7 +31863,7 @@ async function runThreeSessionTdd(options) {
|
|
|
31786
31863
|
let session1;
|
|
31787
31864
|
if (!isRetry) {
|
|
31788
31865
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
31789
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
|
|
31866
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }), projectDir);
|
|
31790
31867
|
sessions.push(session1);
|
|
31791
31868
|
}
|
|
31792
31869
|
if (session1 && !session1.success) {
|
|
@@ -31848,7 +31925,7 @@ async function runThreeSessionTdd(options) {
|
|
|
31848
31925
|
});
|
|
31849
31926
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
31850
31927
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
31851
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
|
|
31928
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }), projectDir);
|
|
31852
31929
|
sessions.push(session2);
|
|
31853
31930
|
if (!session2.success) {
|
|
31854
31931
|
needsHumanReview = true;
|
|
@@ -31864,10 +31941,10 @@ async function runThreeSessionTdd(options) {
|
|
|
31864
31941
|
lite
|
|
31865
31942
|
};
|
|
31866
31943
|
}
|
|
31867
|
-
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
|
|
31944
|
+
const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir);
|
|
31868
31945
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
31869
31946
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
31870
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
|
|
31947
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName, undefined, projectDir);
|
|
31871
31948
|
sessions.push(session3);
|
|
31872
31949
|
const verdict = await readVerdict(workdir);
|
|
31873
31950
|
await cleanupVerdict(workdir);
|
|
@@ -31927,7 +32004,7 @@ async function runThreeSessionTdd(options) {
|
|
|
31927
32004
|
needsHumanReview = false;
|
|
31928
32005
|
}
|
|
31929
32006
|
}
|
|
31930
|
-
const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0);
|
|
32007
|
+
const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0) + fullSuiteGateCost;
|
|
31931
32008
|
logger.info("tdd", allSuccessful ? "[OK] Three-session TDD complete" : "[WARN] Three-session TDD needs review", {
|
|
31932
32009
|
storyId: story.id,
|
|
31933
32010
|
success: allSuccessful,
|
|
@@ -31963,6 +32040,22 @@ async function runThreeSessionTdd(options) {
|
|
|
31963
32040
|
fullSuiteGatePassed
|
|
31964
32041
|
};
|
|
31965
32042
|
}
|
|
32043
|
+
function runThreeSessionTddFromCtx(ctx, opts) {
|
|
32044
|
+
return runThreeSessionTdd({
|
|
32045
|
+
agent: opts.agent,
|
|
32046
|
+
story: ctx.story,
|
|
32047
|
+
config: ctx.config,
|
|
32048
|
+
workdir: ctx.workdir,
|
|
32049
|
+
modelTier: ctx.routing.modelTier,
|
|
32050
|
+
featureName: ctx.prd.feature,
|
|
32051
|
+
contextMarkdown: ctx.contextMarkdown,
|
|
32052
|
+
constitution: ctx.constitution?.content,
|
|
32053
|
+
dryRun: opts.dryRun ?? false,
|
|
32054
|
+
lite: opts.lite ?? false,
|
|
32055
|
+
interactionChain: ctx.interaction,
|
|
32056
|
+
projectDir: ctx.projectDir
|
|
32057
|
+
});
|
|
32058
|
+
}
|
|
31966
32059
|
var init_orchestrator2 = __esm(() => {
|
|
31967
32060
|
init_config();
|
|
31968
32061
|
init_greenfield();
|
|
@@ -31985,17 +32078,6 @@ var init_tdd = __esm(() => {
|
|
|
31985
32078
|
});
|
|
31986
32079
|
|
|
31987
32080
|
// src/pipeline/stages/execution.ts
|
|
31988
|
-
import { existsSync as existsSync19 } from "fs";
|
|
31989
|
-
import { join as join25 } from "path";
|
|
31990
|
-
function resolveStoryWorkdir(repoRoot, storyWorkdir) {
|
|
31991
|
-
if (!storyWorkdir)
|
|
31992
|
-
return repoRoot;
|
|
31993
|
-
const resolved = join25(repoRoot, storyWorkdir);
|
|
31994
|
-
if (!existsSync19(resolved)) {
|
|
31995
|
-
throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
|
|
31996
|
-
}
|
|
31997
|
-
return resolved;
|
|
31998
|
-
}
|
|
31999
32081
|
function isAmbiguousOutput(output) {
|
|
32000
32082
|
if (!output)
|
|
32001
32083
|
return false;
|
|
@@ -32042,11 +32124,11 @@ var init_execution2 = __esm(() => {
|
|
|
32042
32124
|
enabled: () => true,
|
|
32043
32125
|
async execute(ctx) {
|
|
32044
32126
|
const logger = getLogger();
|
|
32045
|
-
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.
|
|
32127
|
+
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
|
|
32046
32128
|
if (!agent) {
|
|
32047
32129
|
return {
|
|
32048
32130
|
action: "fail",
|
|
32049
|
-
reason: `Agent "${ctx.
|
|
32131
|
+
reason: `Agent "${ctx.rootConfig.autoMode.defaultAgent}" not found`
|
|
32050
32132
|
};
|
|
32051
32133
|
}
|
|
32052
32134
|
const isTddStrategy = ctx.routing.testStrategy === "three-session-tdd" || ctx.routing.testStrategy === "three-session-tdd-lite";
|
|
@@ -32056,20 +32138,7 @@ var init_execution2 = __esm(() => {
|
|
|
32056
32138
|
storyId: ctx.story.id,
|
|
32057
32139
|
lite: isLiteMode
|
|
32058
32140
|
});
|
|
32059
|
-
const
|
|
32060
|
-
const tddResult = await runThreeSessionTdd({
|
|
32061
|
-
agent,
|
|
32062
|
-
story: ctx.story,
|
|
32063
|
-
config: ctx.config,
|
|
32064
|
-
workdir: effectiveWorkdir,
|
|
32065
|
-
modelTier: ctx.routing.modelTier,
|
|
32066
|
-
featureName: ctx.prd.feature,
|
|
32067
|
-
contextMarkdown: ctx.contextMarkdown,
|
|
32068
|
-
constitution: ctx.constitution?.content,
|
|
32069
|
-
dryRun: false,
|
|
32070
|
-
lite: isLiteMode,
|
|
32071
|
-
interactionChain: ctx.interaction
|
|
32072
|
-
});
|
|
32141
|
+
const tddResult = await runThreeSessionTddFromCtx(ctx, { agent, dryRun: false, lite: isLiteMode });
|
|
32073
32142
|
ctx.agentResult = {
|
|
32074
32143
|
success: tddResult.success,
|
|
32075
32144
|
estimatedCost: tddResult.totalCost,
|
|
@@ -32132,17 +32201,17 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
32132
32201
|
supportedTiers: agent.capabilities.supportedTiers
|
|
32133
32202
|
});
|
|
32134
32203
|
}
|
|
32135
|
-
const storyWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
|
|
32136
32204
|
const keepSessionOpen = !!(ctx.config.review?.enabled === true || ctx.config.execution.rectification?.enabled === true);
|
|
32137
32205
|
const result = await agent.run({
|
|
32138
32206
|
prompt: ctx.prompt,
|
|
32139
|
-
workdir:
|
|
32207
|
+
workdir: ctx.workdir,
|
|
32140
32208
|
modelTier: ctx.routing.modelTier,
|
|
32141
|
-
modelDef: resolveModelForAgent(ctx.
|
|
32209
|
+
modelDef: resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.routing.modelTier, ctx.rootConfig.autoMode.defaultAgent),
|
|
32142
32210
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
32143
32211
|
dangerouslySkipPermissions: resolvePermissions(ctx.config, "run").skipPermissions,
|
|
32144
32212
|
pipelineStage: "run",
|
|
32145
32213
|
config: ctx.config,
|
|
32214
|
+
projectDir: ctx.projectDir,
|
|
32146
32215
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
32147
32216
|
pidRegistry: ctx.pidRegistry,
|
|
32148
32217
|
featureName: ctx.prd.feature,
|
|
@@ -32156,7 +32225,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
32156
32225
|
})
|
|
32157
32226
|
});
|
|
32158
32227
|
ctx.agentResult = result;
|
|
32159
|
-
await autoCommitIfDirty(
|
|
32228
|
+
await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
|
|
32160
32229
|
const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
|
|
32161
32230
|
if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
|
|
32162
32231
|
const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
|
|
@@ -32197,8 +32266,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
32197
32266
|
detectMergeConflict,
|
|
32198
32267
|
checkMergeConflict,
|
|
32199
32268
|
isAmbiguousOutput,
|
|
32200
|
-
checkStoryAmbiguity
|
|
32201
|
-
resolveStoryWorkdir
|
|
32269
|
+
checkStoryAmbiguity
|
|
32202
32270
|
};
|
|
32203
32271
|
});
|
|
32204
32272
|
|
|
@@ -32475,17 +32543,16 @@ var init_prompt = __esm(() => {
|
|
|
32475
32543
|
async execute(ctx) {
|
|
32476
32544
|
const logger = getLogger();
|
|
32477
32545
|
const isBatch = ctx.stories.length > 1;
|
|
32478
|
-
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
32479
32546
|
const acceptanceEntries = await _loadAcceptanceEntries(ctx, logger);
|
|
32480
32547
|
let prompt;
|
|
32481
32548
|
if (isBatch) {
|
|
32482
|
-
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(
|
|
32549
|
+
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).hermeticConfig(ctx.config.quality?.testing);
|
|
32483
32550
|
if (acceptanceEntries.length > 0)
|
|
32484
32551
|
builder.acceptanceContext(acceptanceEntries);
|
|
32485
32552
|
prompt = await builder.build();
|
|
32486
32553
|
} else {
|
|
32487
32554
|
const role = ctx.routing.testStrategy === "no-test" ? "no-test" : "tdd-simple";
|
|
32488
|
-
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(
|
|
32555
|
+
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).hermeticConfig(ctx.config.quality?.testing).noTestJustification(ctx.story.routing?.noTestJustification);
|
|
32489
32556
|
if (acceptanceEntries.length > 0)
|
|
32490
32557
|
builder.acceptanceContext(acceptanceEntries);
|
|
32491
32558
|
prompt = await builder.build();
|
|
@@ -32718,7 +32785,18 @@ async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
|
|
|
32718
32785
|
return { output, totalCostUsd };
|
|
32719
32786
|
}
|
|
32720
32787
|
async function runRectificationLoop2(opts) {
|
|
32721
|
-
const {
|
|
32788
|
+
const {
|
|
32789
|
+
config: config2,
|
|
32790
|
+
workdir,
|
|
32791
|
+
story,
|
|
32792
|
+
testCommand,
|
|
32793
|
+
timeoutSeconds,
|
|
32794
|
+
testOutput,
|
|
32795
|
+
promptPrefix,
|
|
32796
|
+
featureName,
|
|
32797
|
+
agentGetFn,
|
|
32798
|
+
projectDir
|
|
32799
|
+
} = opts;
|
|
32722
32800
|
const logger = getSafeLogger();
|
|
32723
32801
|
const rectificationConfig = config2.execution.rectification;
|
|
32724
32802
|
const testSummary = parseBunTestOutput(testOutput);
|
|
@@ -32729,7 +32807,8 @@ async function runRectificationLoop2(opts) {
|
|
|
32729
32807
|
currentFailures: testSummary.failed,
|
|
32730
32808
|
lastExitCode: 1
|
|
32731
32809
|
};
|
|
32732
|
-
|
|
32810
|
+
let costAccum = 0;
|
|
32811
|
+
const succeeded = await runSharedRectificationLoop({
|
|
32733
32812
|
stage: "rectification",
|
|
32734
32813
|
storyId: story.id,
|
|
32735
32814
|
maxAttempts: rectificationConfig.maxRetries,
|
|
@@ -32810,11 +32889,13 @@ ${rectificationPrompt}`;
|
|
|
32810
32889
|
dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
|
|
32811
32890
|
pipelineStage: "rectification",
|
|
32812
32891
|
config: config2,
|
|
32892
|
+
projectDir,
|
|
32813
32893
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
32814
32894
|
featureName,
|
|
32815
32895
|
storyId: story.id,
|
|
32816
32896
|
sessionRole: "implementer"
|
|
32817
32897
|
});
|
|
32898
|
+
costAccum += agentResult.estimatedCost ?? 0;
|
|
32818
32899
|
if (agentResult.success) {
|
|
32819
32900
|
logger?.info("rectification", `Agent ${label} session complete`, {
|
|
32820
32901
|
storyId: story.id,
|
|
@@ -32934,11 +33015,13 @@ ${escalationPrompt}`;
|
|
|
32934
33015
|
dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
|
|
32935
33016
|
pipelineStage: "rectification",
|
|
32936
33017
|
config: config2,
|
|
33018
|
+
projectDir,
|
|
32937
33019
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
32938
33020
|
featureName,
|
|
32939
33021
|
storyId: story.id,
|
|
32940
33022
|
sessionRole: "implementer"
|
|
32941
33023
|
});
|
|
33024
|
+
costAccum += escalationRunResult.estimatedCost ?? 0;
|
|
32942
33025
|
logger?.info("rectification", "escalated rectification attempt cost", {
|
|
32943
33026
|
storyId: story.id,
|
|
32944
33027
|
escalatedTier,
|
|
@@ -32975,6 +33058,21 @@ ${escalationPrompt}`;
|
|
|
32975
33058
|
}
|
|
32976
33059
|
throw error48;
|
|
32977
33060
|
});
|
|
33061
|
+
return { succeeded, cost: costAccum };
|
|
33062
|
+
}
|
|
33063
|
+
function runRectificationLoopFromCtx(ctx, opts) {
|
|
33064
|
+
return runRectificationLoop2({
|
|
33065
|
+
config: ctx.config,
|
|
33066
|
+
workdir: ctx.workdir,
|
|
33067
|
+
story: ctx.story,
|
|
33068
|
+
testCommand: opts.testCommand,
|
|
33069
|
+
timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
|
|
33070
|
+
testOutput: opts.testOutput,
|
|
33071
|
+
promptPrefix: opts.promptPrefix,
|
|
33072
|
+
featureName: ctx.prd.feature,
|
|
33073
|
+
agentGetFn: ctx.agentGetFn,
|
|
33074
|
+
projectDir: ctx.projectDir
|
|
33075
|
+
});
|
|
32978
33076
|
}
|
|
32979
33077
|
var _rectificationDeps;
|
|
32980
33078
|
var init_rectification_loop = __esm(() => {
|
|
@@ -33036,36 +33134,28 @@ var init_rectify = __esm(() => {
|
|
|
33036
33134
|
attempt: rectifyAttempt,
|
|
33037
33135
|
testOutput
|
|
33038
33136
|
});
|
|
33039
|
-
const
|
|
33040
|
-
const
|
|
33041
|
-
const fixed = await _rectifyDeps.runRectificationLoop({
|
|
33042
|
-
config: ctx.config,
|
|
33043
|
-
workdir: ctx.workdir,
|
|
33044
|
-
story: ctx.story,
|
|
33045
|
-
testCommand,
|
|
33046
|
-
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
33047
|
-
testOutput,
|
|
33048
|
-
agentGetFn: ctx.agentGetFn
|
|
33049
|
-
});
|
|
33137
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
|
|
33138
|
+
const { succeeded, cost } = await _rectifyDeps.runRectificationLoop(ctx, { testCommand, testOutput });
|
|
33050
33139
|
pipelineEventBus.emit({
|
|
33051
33140
|
type: "rectify:completed",
|
|
33052
33141
|
storyId: ctx.story.id,
|
|
33053
33142
|
attempt: rectifyAttempt,
|
|
33054
|
-
fixed
|
|
33143
|
+
fixed: succeeded
|
|
33055
33144
|
});
|
|
33056
|
-
if (
|
|
33145
|
+
if (succeeded) {
|
|
33057
33146
|
logger.info("rectify", "Rectification succeeded \u2014 retrying verify", { storyId: ctx.story.id });
|
|
33058
33147
|
ctx.verifyResult = undefined;
|
|
33059
|
-
return { action: "retry", fromStage: "verify" };
|
|
33148
|
+
return { action: "retry", fromStage: "verify", cost };
|
|
33060
33149
|
}
|
|
33061
33150
|
logger.warn("rectify", "Rectification exhausted \u2014 escalating", { storyId: ctx.story.id });
|
|
33062
33151
|
return {
|
|
33063
33152
|
action: "escalate",
|
|
33064
|
-
reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)
|
|
33153
|
+
reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`,
|
|
33154
|
+
cost
|
|
33065
33155
|
};
|
|
33066
33156
|
}
|
|
33067
33157
|
};
|
|
33068
|
-
_rectifyDeps = { runRectificationLoop:
|
|
33158
|
+
_rectifyDeps = { runRectificationLoop: runRectificationLoopFromCtx };
|
|
33069
33159
|
});
|
|
33070
33160
|
|
|
33071
33161
|
// src/verification/orchestrator-types.ts
|
|
@@ -33172,16 +33262,16 @@ class AcceptanceStrategy {
|
|
|
33172
33262
|
}, timeoutMs);
|
|
33173
33263
|
const exitCode = await Promise.race([
|
|
33174
33264
|
proc.exited,
|
|
33175
|
-
new Promise((
|
|
33265
|
+
new Promise((resolve8) => setTimeout(() => resolve8(124), timeoutMs + 6000))
|
|
33176
33266
|
]);
|
|
33177
33267
|
clearTimeout(timeoutId);
|
|
33178
33268
|
const stdout = await Promise.race([
|
|
33179
33269
|
new Response(proc.stdout).text(),
|
|
33180
|
-
new Promise((
|
|
33270
|
+
new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
|
|
33181
33271
|
]);
|
|
33182
33272
|
const stderr = await Promise.race([
|
|
33183
33273
|
new Response(proc.stderr).text(),
|
|
33184
|
-
new Promise((
|
|
33274
|
+
new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
|
|
33185
33275
|
]);
|
|
33186
33276
|
const durationMs = Date.now() - start;
|
|
33187
33277
|
if (timedOut || exitCode === 124) {
|
|
@@ -33591,34 +33681,31 @@ var init_regression2 = __esm(() => {
|
|
|
33591
33681
|
regressionStage = {
|
|
33592
33682
|
name: "regression",
|
|
33593
33683
|
enabled(ctx) {
|
|
33594
|
-
const
|
|
33595
|
-
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
33684
|
+
const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
|
|
33596
33685
|
if (mode !== "per-story")
|
|
33597
33686
|
return false;
|
|
33598
33687
|
if (ctx.verifyResult && !ctx.verifyResult.success)
|
|
33599
33688
|
return false;
|
|
33600
|
-
const gateEnabled =
|
|
33689
|
+
const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
|
|
33601
33690
|
return gateEnabled;
|
|
33602
33691
|
},
|
|
33603
33692
|
skipReason(ctx) {
|
|
33604
|
-
const
|
|
33605
|
-
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
33693
|
+
const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
|
|
33606
33694
|
if (mode !== "per-story")
|
|
33607
33695
|
return `not needed (regression mode is '${mode}', not 'per-story')`;
|
|
33608
33696
|
return "disabled (regression gate not enabled in config)";
|
|
33609
33697
|
},
|
|
33610
33698
|
async execute(ctx) {
|
|
33611
33699
|
const logger = getLogger();
|
|
33612
|
-
const
|
|
33613
|
-
const
|
|
33614
|
-
const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
33700
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
|
|
33701
|
+
const timeoutSeconds = ctx.config.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
33615
33702
|
logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
|
|
33616
33703
|
const verifyCtx = {
|
|
33617
33704
|
workdir: ctx.workdir,
|
|
33618
33705
|
testCommand,
|
|
33619
33706
|
timeoutSeconds,
|
|
33620
33707
|
storyId: ctx.story.id,
|
|
33621
|
-
acceptOnTimeout:
|
|
33708
|
+
acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
|
|
33622
33709
|
config: ctx.config
|
|
33623
33710
|
};
|
|
33624
33711
|
const result = await _regressionStageDeps.verifyRegression(verifyCtx);
|
|
@@ -34225,7 +34312,6 @@ var init_routing = __esm(() => {
|
|
|
34225
34312
|
});
|
|
34226
34313
|
|
|
34227
34314
|
// src/pipeline/stages/routing.ts
|
|
34228
|
-
import { join as join26 } from "path";
|
|
34229
34315
|
var routingStage, _routingDeps;
|
|
34230
34316
|
var init_routing2 = __esm(() => {
|
|
34231
34317
|
init_greenfield();
|
|
@@ -34238,13 +34324,12 @@ var init_routing2 = __esm(() => {
|
|
|
34238
34324
|
enabled: () => true,
|
|
34239
34325
|
async execute(ctx) {
|
|
34240
34326
|
const logger = getLogger();
|
|
34241
|
-
const
|
|
34242
|
-
const agentName = effectiveConfig.execution?.agent ?? "claude";
|
|
34327
|
+
const agentName = ctx.config.execution?.agent ?? "claude";
|
|
34243
34328
|
const adapter = ctx.agentGetFn ? ctx.agentGetFn(agentName) : undefined;
|
|
34244
34329
|
if (ctx.story.id === ctx.stories[0]?.id) {
|
|
34245
34330
|
_routingDeps.clearCache();
|
|
34246
34331
|
}
|
|
34247
|
-
const decision = await _routingDeps.resolveRouting(ctx.story,
|
|
34332
|
+
const decision = await _routingDeps.resolveRouting(ctx.story, ctx.config, ctx.plugins, adapter);
|
|
34248
34333
|
const TIER_RANK = { fast: 0, balanced: 1, powerful: 2 };
|
|
34249
34334
|
const derivedTier = decision.modelTier;
|
|
34250
34335
|
const previousTier = ctx.story.routing?.modelTier;
|
|
@@ -34262,9 +34347,9 @@ var init_routing2 = __esm(() => {
|
|
|
34262
34347
|
if (ctx.prdPath) {
|
|
34263
34348
|
await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
|
|
34264
34349
|
}
|
|
34265
|
-
const greenfieldDetectionEnabled =
|
|
34350
|
+
const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
|
|
34266
34351
|
if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
|
|
34267
|
-
const greenfieldScanDir = ctx.
|
|
34352
|
+
const greenfieldScanDir = ctx.workdir;
|
|
34268
34353
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
|
|
34269
34354
|
if (isGreenfield) {
|
|
34270
34355
|
logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
|
|
@@ -34315,7 +34400,7 @@ var init_crash_detector = __esm(() => {
|
|
|
34315
34400
|
});
|
|
34316
34401
|
|
|
34317
34402
|
// src/pipeline/stages/verify.ts
|
|
34318
|
-
import { join as
|
|
34403
|
+
import { join as join22 } from "path";
|
|
34319
34404
|
function coerceSmartTestRunner(val) {
|
|
34320
34405
|
if (val === undefined || val === true)
|
|
34321
34406
|
return DEFAULT_SMART_RUNNER_CONFIG2;
|
|
@@ -34331,7 +34416,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
34331
34416
|
}
|
|
34332
34417
|
async function readPackageName(dir) {
|
|
34333
34418
|
try {
|
|
34334
|
-
const content = await Bun.file(
|
|
34419
|
+
const content = await Bun.file(join22(dir, "package.json")).json();
|
|
34335
34420
|
return typeof content.name === "string" ? content.name : null;
|
|
34336
34421
|
} catch {
|
|
34337
34422
|
return null;
|
|
@@ -34364,26 +34449,24 @@ var init_verify = __esm(() => {
|
|
|
34364
34449
|
skipReason: () => "not needed (full-suite gate already passed)",
|
|
34365
34450
|
async execute(ctx) {
|
|
34366
34451
|
const logger = getLogger();
|
|
34367
|
-
|
|
34368
|
-
if (!effectiveConfig.quality.requireTests) {
|
|
34452
|
+
if (!ctx.config.quality.requireTests) {
|
|
34369
34453
|
logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
|
|
34370
34454
|
return { action: "continue" };
|
|
34371
34455
|
}
|
|
34372
|
-
const testCommand =
|
|
34373
|
-
const testScopedTemplate =
|
|
34456
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test;
|
|
34457
|
+
const testScopedTemplate = ctx.config.quality.commands.testScoped;
|
|
34374
34458
|
if (!testCommand) {
|
|
34375
34459
|
logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
|
|
34376
34460
|
return { action: "continue" };
|
|
34377
34461
|
}
|
|
34378
34462
|
logger.info("verify", "Running verification", { storyId: ctx.story.id });
|
|
34379
|
-
const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
34380
34463
|
let effectiveCommand = testCommand;
|
|
34381
34464
|
let isFullSuite = true;
|
|
34382
|
-
const smartRunnerConfig = coerceSmartTestRunner(
|
|
34383
|
-
const regressionMode =
|
|
34465
|
+
const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
|
|
34466
|
+
const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
|
|
34384
34467
|
let resolvedTestScopedTemplate = testScopedTemplate;
|
|
34385
34468
|
if (testScopedTemplate && ctx.story.workdir) {
|
|
34386
|
-
const resolved = await resolvePackageTemplate(testScopedTemplate,
|
|
34469
|
+
const resolved = await resolvePackageTemplate(testScopedTemplate, ctx.workdir);
|
|
34387
34470
|
resolvedTestScopedTemplate = resolved ?? undefined;
|
|
34388
34471
|
}
|
|
34389
34472
|
const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
|
|
@@ -34402,8 +34485,8 @@ var init_verify = __esm(() => {
|
|
|
34402
34485
|
});
|
|
34403
34486
|
}
|
|
34404
34487
|
} else if (smartRunnerConfig.enabled) {
|
|
34405
|
-
const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(
|
|
34406
|
-
const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles,
|
|
34488
|
+
const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(ctx.workdir, ctx.storyGitRef, ctx.story.workdir);
|
|
34489
|
+
const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, ctx.workdir);
|
|
34407
34490
|
if (pass1Files.length > 0) {
|
|
34408
34491
|
logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
|
|
34409
34492
|
storyId: ctx.story.id
|
|
@@ -34411,7 +34494,7 @@ var init_verify = __esm(() => {
|
|
|
34411
34494
|
effectiveCommand = buildScopedCommand2(pass1Files, testCommand, resolvedTestScopedTemplate);
|
|
34412
34495
|
isFullSuite = false;
|
|
34413
34496
|
} else if (smartRunnerConfig.fallback === "import-grep") {
|
|
34414
|
-
const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles,
|
|
34497
|
+
const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, ctx.workdir, smartRunnerConfig.testFilePatterns);
|
|
34415
34498
|
if (pass2Files.length > 0) {
|
|
34416
34499
|
logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
|
|
34417
34500
|
storyId: ctx.story.id
|
|
@@ -34437,10 +34520,10 @@ var init_verify = __esm(() => {
|
|
|
34437
34520
|
command: effectiveCommand
|
|
34438
34521
|
});
|
|
34439
34522
|
const result = await _verifyDeps.regression({
|
|
34440
|
-
workdir:
|
|
34523
|
+
workdir: ctx.workdir,
|
|
34441
34524
|
command: effectiveCommand,
|
|
34442
|
-
timeoutSeconds:
|
|
34443
|
-
acceptOnTimeout:
|
|
34525
|
+
timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
|
|
34526
|
+
acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
|
|
34444
34527
|
});
|
|
34445
34528
|
ctx.verifyResult = {
|
|
34446
34529
|
success: result.success,
|
|
@@ -34457,7 +34540,7 @@ var init_verify = __esm(() => {
|
|
|
34457
34540
|
};
|
|
34458
34541
|
if (!result.success) {
|
|
34459
34542
|
if (result.status === "TIMEOUT") {
|
|
34460
|
-
const timeout =
|
|
34543
|
+
const timeout = ctx.config.execution.verificationTimeoutSeconds;
|
|
34461
34544
|
logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
|
|
34462
34545
|
exitCode: result.status,
|
|
34463
34546
|
storyId: ctx.story.id,
|
|
@@ -34475,7 +34558,7 @@ var init_verify = __esm(() => {
|
|
|
34475
34558
|
if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
|
|
34476
34559
|
return {
|
|
34477
34560
|
action: "escalate",
|
|
34478
|
-
reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${
|
|
34561
|
+
reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${ctx.config.execution.verificationTimeoutSeconds}s (not a code failure)` : `Tests failed with runtime crash (exit code ${result.status ?? "non-zero"})`
|
|
34479
34562
|
};
|
|
34480
34563
|
}
|
|
34481
34564
|
return { action: "continue" };
|
|
@@ -34593,9 +34676,9 @@ __export(exports_init_context, {
|
|
|
34593
34676
|
generateContextTemplate: () => generateContextTemplate,
|
|
34594
34677
|
_initContextDeps: () => _initContextDeps
|
|
34595
34678
|
});
|
|
34596
|
-
import { existsSync as
|
|
34679
|
+
import { existsSync as existsSync21 } from "fs";
|
|
34597
34680
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
34598
|
-
import { basename as
|
|
34681
|
+
import { basename as basename3, join as join26 } from "path";
|
|
34599
34682
|
async function findFiles(dir, maxFiles = 200) {
|
|
34600
34683
|
try {
|
|
34601
34684
|
const proc = Bun.spawnSync([
|
|
@@ -34623,8 +34706,8 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
34623
34706
|
return [];
|
|
34624
34707
|
}
|
|
34625
34708
|
async function readPackageManifest(projectRoot) {
|
|
34626
|
-
const packageJsonPath =
|
|
34627
|
-
if (!
|
|
34709
|
+
const packageJsonPath = join26(projectRoot, "package.json");
|
|
34710
|
+
if (!existsSync21(packageJsonPath)) {
|
|
34628
34711
|
return null;
|
|
34629
34712
|
}
|
|
34630
34713
|
try {
|
|
@@ -34641,8 +34724,8 @@ async function readPackageManifest(projectRoot) {
|
|
|
34641
34724
|
}
|
|
34642
34725
|
}
|
|
34643
34726
|
async function readReadmeSnippet(projectRoot) {
|
|
34644
|
-
const readmePath =
|
|
34645
|
-
if (!
|
|
34727
|
+
const readmePath = join26(projectRoot, "README.md");
|
|
34728
|
+
if (!existsSync21(readmePath)) {
|
|
34646
34729
|
return null;
|
|
34647
34730
|
}
|
|
34648
34731
|
try {
|
|
@@ -34659,8 +34742,8 @@ async function detectEntryPoints(projectRoot) {
|
|
|
34659
34742
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
34660
34743
|
const found = [];
|
|
34661
34744
|
for (const candidate of candidates) {
|
|
34662
|
-
const path13 =
|
|
34663
|
-
if (
|
|
34745
|
+
const path13 = join26(projectRoot, candidate);
|
|
34746
|
+
if (existsSync21(path13)) {
|
|
34664
34747
|
found.push(candidate);
|
|
34665
34748
|
}
|
|
34666
34749
|
}
|
|
@@ -34670,8 +34753,8 @@ async function detectConfigFiles(projectRoot) {
|
|
|
34670
34753
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
34671
34754
|
const found = [];
|
|
34672
34755
|
for (const candidate of candidates) {
|
|
34673
|
-
const path13 =
|
|
34674
|
-
if (
|
|
34756
|
+
const path13 = join26(projectRoot, candidate);
|
|
34757
|
+
if (existsSync21(path13)) {
|
|
34675
34758
|
found.push(candidate);
|
|
34676
34759
|
}
|
|
34677
34760
|
}
|
|
@@ -34683,7 +34766,7 @@ async function scanProject(projectRoot) {
|
|
|
34683
34766
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
34684
34767
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
34685
34768
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
34686
|
-
const projectName = packageManifest?.name ||
|
|
34769
|
+
const projectName = packageManifest?.name || basename3(projectRoot);
|
|
34687
34770
|
return {
|
|
34688
34771
|
projectName,
|
|
34689
34772
|
fileTree,
|
|
@@ -34831,13 +34914,13 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
34831
34914
|
}
|
|
34832
34915
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
34833
34916
|
const logger = getLogger();
|
|
34834
|
-
const naxDir =
|
|
34835
|
-
const contextPath =
|
|
34836
|
-
if (
|
|
34917
|
+
const naxDir = join26(repoRoot, ".nax", "mono", packagePath);
|
|
34918
|
+
const contextPath = join26(naxDir, "context.md");
|
|
34919
|
+
if (existsSync21(contextPath) && !force) {
|
|
34837
34920
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
34838
34921
|
return;
|
|
34839
34922
|
}
|
|
34840
|
-
if (!
|
|
34923
|
+
if (!existsSync21(naxDir)) {
|
|
34841
34924
|
await mkdir2(naxDir, { recursive: true });
|
|
34842
34925
|
}
|
|
34843
34926
|
const content = generatePackageContextTemplate(packagePath);
|
|
@@ -34846,13 +34929,13 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
34846
34929
|
}
|
|
34847
34930
|
async function initContext(projectRoot, options = {}) {
|
|
34848
34931
|
const logger = getLogger();
|
|
34849
|
-
const naxDir =
|
|
34850
|
-
const contextPath =
|
|
34851
|
-
if (
|
|
34932
|
+
const naxDir = join26(projectRoot, ".nax");
|
|
34933
|
+
const contextPath = join26(naxDir, "context.md");
|
|
34934
|
+
if (existsSync21(contextPath) && !options.force) {
|
|
34852
34935
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
34853
34936
|
return;
|
|
34854
34937
|
}
|
|
34855
|
-
if (!
|
|
34938
|
+
if (!existsSync21(naxDir)) {
|
|
34856
34939
|
await mkdir2(naxDir, { recursive: true });
|
|
34857
34940
|
}
|
|
34858
34941
|
const scan = await scanProject(projectRoot);
|
|
@@ -34877,23 +34960,23 @@ var init_init_context = __esm(() => {
|
|
|
34877
34960
|
|
|
34878
34961
|
// src/utils/path-security.ts
|
|
34879
34962
|
import { realpathSync as realpathSync3 } from "fs";
|
|
34880
|
-
import { dirname as
|
|
34963
|
+
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join27, normalize as normalize2, resolve as resolve8 } from "path";
|
|
34881
34964
|
function safeRealpathForComparison(p) {
|
|
34882
34965
|
try {
|
|
34883
34966
|
return realpathSync3(p);
|
|
34884
34967
|
} catch {
|
|
34885
|
-
const parent =
|
|
34968
|
+
const parent = dirname4(p);
|
|
34886
34969
|
if (parent === p)
|
|
34887
34970
|
return normalize2(p);
|
|
34888
34971
|
const resolvedParent = safeRealpathForComparison(parent);
|
|
34889
|
-
return
|
|
34972
|
+
return join27(resolvedParent, p.split("/").pop() ?? "");
|
|
34890
34973
|
}
|
|
34891
34974
|
}
|
|
34892
34975
|
function validateModulePath(modulePath, allowedRoots) {
|
|
34893
34976
|
if (!modulePath) {
|
|
34894
34977
|
return { valid: false, error: "Module path is empty" };
|
|
34895
34978
|
}
|
|
34896
|
-
const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(
|
|
34979
|
+
const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve8(r)));
|
|
34897
34980
|
if (isAbsolute5(modulePath)) {
|
|
34898
34981
|
const normalized = normalize2(modulePath);
|
|
34899
34982
|
const resolved = safeRealpathForComparison(normalized);
|
|
@@ -34903,8 +34986,8 @@ function validateModulePath(modulePath, allowedRoots) {
|
|
|
34903
34986
|
}
|
|
34904
34987
|
} else {
|
|
34905
34988
|
for (let i = 0;i < allowedRoots.length; i++) {
|
|
34906
|
-
const originalRoot =
|
|
34907
|
-
const absoluteInput =
|
|
34989
|
+
const originalRoot = resolve8(allowedRoots[i]);
|
|
34990
|
+
const absoluteInput = resolve8(join27(originalRoot, modulePath));
|
|
34908
34991
|
const resolved = safeRealpathForComparison(absoluteInput);
|
|
34909
34992
|
const resolvedRoot = resolvedRoots[i];
|
|
34910
34993
|
if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
|
|
@@ -35265,11 +35348,11 @@ function getSafeLogger6() {
|
|
|
35265
35348
|
return getSafeLogger();
|
|
35266
35349
|
}
|
|
35267
35350
|
function extractPluginName(pluginPath) {
|
|
35268
|
-
const
|
|
35269
|
-
if (
|
|
35351
|
+
const basename5 = path13.basename(pluginPath);
|
|
35352
|
+
if (basename5 === "index.ts" || basename5 === "index.js" || basename5 === "index.mjs") {
|
|
35270
35353
|
return path13.basename(path13.dirname(pluginPath));
|
|
35271
35354
|
}
|
|
35272
|
-
return
|
|
35355
|
+
return basename5.replace(/\.(ts|js|mjs)$/, "");
|
|
35273
35356
|
}
|
|
35274
35357
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
|
|
35275
35358
|
const loadedPlugins = [];
|
|
@@ -35430,7 +35513,7 @@ async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots =
|
|
|
35430
35513
|
return null;
|
|
35431
35514
|
}
|
|
35432
35515
|
}
|
|
35433
|
-
var _pluginErrorSink = (
|
|
35516
|
+
var _pluginErrorSink = () => {};
|
|
35434
35517
|
var init_loader4 = __esm(() => {
|
|
35435
35518
|
init_logger2();
|
|
35436
35519
|
init_path_security2();
|
|
@@ -35440,19 +35523,19 @@ var init_loader4 = __esm(() => {
|
|
|
35440
35523
|
});
|
|
35441
35524
|
|
|
35442
35525
|
// src/hooks/runner.ts
|
|
35443
|
-
import { join as
|
|
35526
|
+
import { join as join42 } from "path";
|
|
35444
35527
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
35445
35528
|
let globalHooks = { hooks: {} };
|
|
35446
35529
|
let projectHooks = { hooks: {} };
|
|
35447
35530
|
let skipGlobal = false;
|
|
35448
|
-
const projectPath =
|
|
35531
|
+
const projectPath = join42(projectDir, "hooks.json");
|
|
35449
35532
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
35450
35533
|
if (projectData) {
|
|
35451
35534
|
projectHooks = projectData;
|
|
35452
35535
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
35453
35536
|
}
|
|
35454
35537
|
if (!skipGlobal && globalDir) {
|
|
35455
|
-
const globalPath =
|
|
35538
|
+
const globalPath = join42(globalDir, "hooks.json");
|
|
35456
35539
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
35457
35540
|
if (globalData) {
|
|
35458
35541
|
globalHooks = globalData;
|
|
@@ -35652,7 +35735,7 @@ var package_default;
|
|
|
35652
35735
|
var init_package = __esm(() => {
|
|
35653
35736
|
package_default = {
|
|
35654
35737
|
name: "@nathapp/nax",
|
|
35655
|
-
version: "0.
|
|
35738
|
+
version: "0.59.0",
|
|
35656
35739
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
35657
35740
|
type: "module",
|
|
35658
35741
|
bin: {
|
|
@@ -35732,8 +35815,8 @@ var init_version = __esm(() => {
|
|
|
35732
35815
|
NAX_VERSION = package_default.version;
|
|
35733
35816
|
NAX_COMMIT = (() => {
|
|
35734
35817
|
try {
|
|
35735
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
35736
|
-
return "
|
|
35818
|
+
if (/^[0-9a-f]{6,10}$/.test("13990b5b"))
|
|
35819
|
+
return "13990b5b";
|
|
35737
35820
|
} catch {}
|
|
35738
35821
|
try {
|
|
35739
35822
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -36029,18 +36112,18 @@ var init_crash_signals = __esm(() => {
|
|
|
36029
36112
|
|
|
36030
36113
|
// src/execution/crash-recovery.ts
|
|
36031
36114
|
function installCrashHandlers(ctx) {
|
|
36032
|
-
if (
|
|
36033
|
-
|
|
36115
|
+
if (activeCleanup) {
|
|
36116
|
+
activeCleanup();
|
|
36034
36117
|
}
|
|
36035
36118
|
const cleanup = installSignalHandlers(ctx);
|
|
36036
|
-
|
|
36037
|
-
return () => {
|
|
36119
|
+
activeCleanup = () => {
|
|
36038
36120
|
cleanup();
|
|
36039
36121
|
stopHeartbeat();
|
|
36040
|
-
|
|
36122
|
+
activeCleanup = null;
|
|
36041
36123
|
};
|
|
36124
|
+
return activeCleanup;
|
|
36042
36125
|
}
|
|
36043
|
-
var
|
|
36126
|
+
var activeCleanup = null;
|
|
36044
36127
|
var init_crash_recovery = __esm(() => {
|
|
36045
36128
|
init_crash_heartbeat();
|
|
36046
36129
|
init_crash_signals();
|
|
@@ -36361,12 +36444,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
|
|
|
36361
36444
|
semanticVerdicts: options.semanticVerdicts
|
|
36362
36445
|
});
|
|
36363
36446
|
try {
|
|
36447
|
+
const timeoutSeconds = (config2.acceptance?.timeoutMs ?? 120000) / 1000;
|
|
36364
36448
|
const result = await agent.run({
|
|
36365
36449
|
prompt,
|
|
36366
36450
|
workdir,
|
|
36367
36451
|
modelTier: undefined,
|
|
36368
36452
|
modelDef,
|
|
36369
|
-
timeoutSeconds
|
|
36453
|
+
timeoutSeconds,
|
|
36370
36454
|
sessionRole: "diagnose",
|
|
36371
36455
|
acpSessionName: sessionName,
|
|
36372
36456
|
featureName,
|
|
@@ -36375,12 +36459,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
|
|
|
36375
36459
|
});
|
|
36376
36460
|
const diagnosis = parseDiagnosisResult(result.output);
|
|
36377
36461
|
if (diagnosis) {
|
|
36378
|
-
return diagnosis;
|
|
36462
|
+
return { ...diagnosis, cost: result.estimatedCost ?? 0 };
|
|
36379
36463
|
}
|
|
36380
36464
|
return {
|
|
36381
36465
|
verdict: "source_bug",
|
|
36382
36466
|
reasoning: "diagnosis failed \u2014 falling back to source fix",
|
|
36383
|
-
confidence: 0
|
|
36467
|
+
confidence: 0,
|
|
36468
|
+
cost: result.estimatedCost ?? 0
|
|
36384
36469
|
};
|
|
36385
36470
|
} catch {
|
|
36386
36471
|
return {
|
|
@@ -36492,7 +36577,7 @@ __export(exports_acceptance_loop, {
|
|
|
36492
36577
|
_regenerateDeps: () => _regenerateDeps,
|
|
36493
36578
|
_acceptanceLoopDeps: () => _acceptanceLoopDeps
|
|
36494
36579
|
});
|
|
36495
|
-
import path15, { join as
|
|
36580
|
+
import path15, { join as join43 } from "path";
|
|
36496
36581
|
function isStubTestFile(content) {
|
|
36497
36582
|
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
36498
36583
|
}
|
|
@@ -36581,15 +36666,16 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
36581
36666
|
agent: ctx.config.autoMode.defaultAgent,
|
|
36582
36667
|
iteration: iterations
|
|
36583
36668
|
}), ctx.workdir);
|
|
36584
|
-
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(
|
|
36669
|
+
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join43(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
36585
36670
|
const fixContext = {
|
|
36586
|
-
config:
|
|
36587
|
-
|
|
36671
|
+
config: fixEffectiveConfig,
|
|
36672
|
+
rootConfig: ctx.config,
|
|
36588
36673
|
prd,
|
|
36589
36674
|
story,
|
|
36590
36675
|
stories: [story],
|
|
36591
36676
|
routing,
|
|
36592
|
-
|
|
36677
|
+
projectDir: ctx.workdir,
|
|
36678
|
+
workdir: story.workdir ? join43(ctx.workdir, story.workdir) : ctx.workdir,
|
|
36593
36679
|
featureDir: ctx.featureDir,
|
|
36594
36680
|
hooks: ctx.hooks,
|
|
36595
36681
|
plugins: ctx.pluginRegistry,
|
|
@@ -36768,6 +36854,7 @@ async function runFixRouting(options) {
|
|
|
36768
36854
|
storyId,
|
|
36769
36855
|
semanticVerdicts: options.semanticVerdicts
|
|
36770
36856
|
});
|
|
36857
|
+
const diagnosisCost = diagnosis.cost ?? 0;
|
|
36771
36858
|
logger?.info("acceptance.diagnosis", "Diagnosis complete", {
|
|
36772
36859
|
verdict: diagnosis.verdict,
|
|
36773
36860
|
confidence: diagnosis.confidence,
|
|
@@ -36777,7 +36864,7 @@ async function runFixRouting(options) {
|
|
|
36777
36864
|
logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
|
|
36778
36865
|
if (!agent) {
|
|
36779
36866
|
logger?.error("acceptance", "Agent not found for source fix execution");
|
|
36780
|
-
return { fixed: false, cost:
|
|
36867
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36781
36868
|
}
|
|
36782
36869
|
let fixAttempts = 0;
|
|
36783
36870
|
while (fixAttempts < fixMaxRetries) {
|
|
@@ -36799,7 +36886,7 @@ async function runFixRouting(options) {
|
|
|
36799
36886
|
attempt: fixAttempts
|
|
36800
36887
|
});
|
|
36801
36888
|
if (fixResult.success) {
|
|
36802
|
-
return { fixed: true, cost: fixResult.cost, prdDirty: false };
|
|
36889
|
+
return { fixed: true, cost: fixResult.cost + diagnosisCost, prdDirty: false };
|
|
36803
36890
|
}
|
|
36804
36891
|
logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
|
|
36805
36892
|
attempt: fixAttempts,
|
|
@@ -36812,13 +36899,13 @@ async function runFixRouting(options) {
|
|
|
36812
36899
|
break;
|
|
36813
36900
|
}
|
|
36814
36901
|
}
|
|
36815
|
-
return { fixed: false, cost:
|
|
36902
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36816
36903
|
}
|
|
36817
36904
|
if (diagnosis.verdict === "test_bug") {
|
|
36818
36905
|
logger?.info("acceptance", "Diagnosis: test_bug \u2014 regenerating acceptance test");
|
|
36819
36906
|
if (!ctx.featureDir) {
|
|
36820
36907
|
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
36821
|
-
return { fixed: false, cost:
|
|
36908
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36822
36909
|
}
|
|
36823
36910
|
const testPath = await findExistingAcceptanceTestPath({
|
|
36824
36911
|
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
@@ -36835,29 +36922,29 @@ async function runFixRouting(options) {
|
|
|
36835
36922
|
language: ctx.config.project?.language
|
|
36836
36923
|
})
|
|
36837
36924
|
});
|
|
36838
|
-
return { fixed: false, cost:
|
|
36925
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36839
36926
|
}
|
|
36840
36927
|
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
36841
36928
|
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
36842
36929
|
outcome: regenerated ? "success" : "failure"
|
|
36843
36930
|
});
|
|
36844
36931
|
if (!regenerated) {
|
|
36845
|
-
return { fixed: false, cost:
|
|
36932
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36846
36933
|
}
|
|
36847
36934
|
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
36848
36935
|
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
36849
36936
|
if (acceptanceResult.action === "continue") {
|
|
36850
36937
|
logger?.info("acceptance", "Acceptance passed after test regeneration");
|
|
36851
|
-
return { fixed: true, cost:
|
|
36938
|
+
return { fixed: true, cost: diagnosisCost, prdDirty: true };
|
|
36852
36939
|
}
|
|
36853
36940
|
logger?.warn("acceptance", "Acceptance still failing after test regeneration");
|
|
36854
|
-
return { fixed: false, cost:
|
|
36941
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: true };
|
|
36855
36942
|
}
|
|
36856
36943
|
if (diagnosis.verdict === "both") {
|
|
36857
36944
|
logger?.info("acceptance", "Diagnosis: both \u2014 executing source fix then regenerating test if needed");
|
|
36858
36945
|
if (!agent) {
|
|
36859
36946
|
logger?.error("acceptance", "Agent not found for source fix execution");
|
|
36860
|
-
return { fixed: false, cost:
|
|
36947
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36861
36948
|
}
|
|
36862
36949
|
let sourceFixSuccess = false;
|
|
36863
36950
|
let sourceFixCost = 0;
|
|
@@ -36897,19 +36984,19 @@ async function runFixRouting(options) {
|
|
|
36897
36984
|
}
|
|
36898
36985
|
}
|
|
36899
36986
|
if (!sourceFixSuccess) {
|
|
36900
|
-
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
36987
|
+
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36901
36988
|
}
|
|
36902
36989
|
logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
|
|
36903
36990
|
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
36904
36991
|
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
36905
36992
|
if (acceptanceResult.action === "continue") {
|
|
36906
36993
|
logger?.info("acceptance", "Acceptance passed after source fix");
|
|
36907
|
-
return { fixed: true, cost: sourceFixCost, prdDirty: false };
|
|
36994
|
+
return { fixed: true, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36908
36995
|
}
|
|
36909
36996
|
logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
|
|
36910
36997
|
if (!ctx.featureDir) {
|
|
36911
36998
|
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
36912
|
-
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
36999
|
+
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36913
37000
|
}
|
|
36914
37001
|
const testPath = await findExistingAcceptanceTestPath({
|
|
36915
37002
|
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
@@ -36926,15 +37013,15 @@ async function runFixRouting(options) {
|
|
|
36926
37013
|
language: ctx.config.project?.language
|
|
36927
37014
|
})
|
|
36928
37015
|
});
|
|
36929
|
-
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
37016
|
+
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36930
37017
|
}
|
|
36931
37018
|
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
36932
37019
|
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
36933
37020
|
outcome: regenerated ? "success" : "failure"
|
|
36934
37021
|
});
|
|
36935
|
-
return { fixed: regenerated, cost: sourceFixCost, prdDirty: regenerated };
|
|
37022
|
+
return { fixed: regenerated, cost: sourceFixCost + diagnosisCost, prdDirty: regenerated };
|
|
36936
37023
|
}
|
|
36937
|
-
return { fixed: false, cost:
|
|
37024
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36938
37025
|
}
|
|
36939
37026
|
async function runAcceptanceLoop(ctx) {
|
|
36940
37027
|
const logger = getSafeLogger();
|
|
@@ -36950,7 +37037,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
36950
37037
|
const firstStory = prd.userStories[0];
|
|
36951
37038
|
const acceptanceContext = {
|
|
36952
37039
|
config: ctx.config,
|
|
36953
|
-
|
|
37040
|
+
rootConfig: ctx.config,
|
|
36954
37041
|
prd,
|
|
36955
37042
|
story: firstStory,
|
|
36956
37043
|
stories: [firstStory],
|
|
@@ -36960,6 +37047,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
36960
37047
|
testStrategy: "test-after",
|
|
36961
37048
|
reasoning: "Acceptance validation"
|
|
36962
37049
|
},
|
|
37050
|
+
projectDir: ctx.workdir,
|
|
36963
37051
|
workdir: ctx.workdir,
|
|
36964
37052
|
featureDir: ctx.featureDir,
|
|
36965
37053
|
hooks: ctx.hooks,
|
|
@@ -37185,6 +37273,20 @@ async function runDeferredRegression(options) {
|
|
|
37185
37273
|
const testCommand = config2.quality.commands.test ?? "bun test";
|
|
37186
37274
|
const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
37187
37275
|
const maxRectificationAttempts = config2.execution.regressionGate?.maxRectificationAttempts ?? 2;
|
|
37276
|
+
const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
|
|
37277
|
+
const verifyOpts = {
|
|
37278
|
+
workdir,
|
|
37279
|
+
command: testCommand,
|
|
37280
|
+
timeoutSeconds,
|
|
37281
|
+
forceExit: config2.quality.forceExit,
|
|
37282
|
+
detectOpenHandles: config2.quality.detectOpenHandles,
|
|
37283
|
+
detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
|
|
37284
|
+
timeoutRetryCount: 0,
|
|
37285
|
+
gracePeriodMs: config2.quality.gracePeriodMs,
|
|
37286
|
+
drainTimeoutMs: config2.quality.drainTimeoutMs,
|
|
37287
|
+
shell: config2.quality.shell,
|
|
37288
|
+
stripEnvVars: config2.quality.stripEnvVars
|
|
37289
|
+
};
|
|
37188
37290
|
const counts = countStories(prd);
|
|
37189
37291
|
const passedStories = prd.userStories.filter((s) => s.status === "passed");
|
|
37190
37292
|
if (passedStories.length === 0) {
|
|
@@ -37202,19 +37304,7 @@ async function runDeferredRegression(options) {
|
|
|
37202
37304
|
totalStories: counts.total,
|
|
37203
37305
|
passedStories: passedStories.length
|
|
37204
37306
|
});
|
|
37205
|
-
const fullSuiteResult = await _regressionDeps.runVerification(
|
|
37206
|
-
workdir,
|
|
37207
|
-
command: testCommand,
|
|
37208
|
-
timeoutSeconds,
|
|
37209
|
-
forceExit: config2.quality.forceExit,
|
|
37210
|
-
detectOpenHandles: config2.quality.detectOpenHandles,
|
|
37211
|
-
detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
|
|
37212
|
-
timeoutRetryCount: 0,
|
|
37213
|
-
gracePeriodMs: config2.quality.gracePeriodMs,
|
|
37214
|
-
drainTimeoutMs: config2.quality.drainTimeoutMs,
|
|
37215
|
-
shell: config2.quality.shell,
|
|
37216
|
-
stripEnvVars: config2.quality.stripEnvVars
|
|
37217
|
-
});
|
|
37307
|
+
const fullSuiteResult = await _regressionDeps.runVerification(verifyOpts);
|
|
37218
37308
|
if (fullSuiteResult.success) {
|
|
37219
37309
|
logger?.info("regression", "Full suite passed");
|
|
37220
37310
|
return {
|
|
@@ -37226,7 +37316,6 @@ async function runDeferredRegression(options) {
|
|
|
37226
37316
|
affectedStories: []
|
|
37227
37317
|
};
|
|
37228
37318
|
}
|
|
37229
|
-
const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
|
|
37230
37319
|
if (fullSuiteResult.status === "TIMEOUT" && acceptOnTimeout) {
|
|
37231
37320
|
logger?.warn("regression", "Full-suite regression gate timed out (accepted as pass)");
|
|
37232
37321
|
return {
|
|
@@ -37308,6 +37397,8 @@ async function runDeferredRegression(options) {
|
|
|
37308
37397
|
};
|
|
37309
37398
|
}
|
|
37310
37399
|
let rectificationAttempts = 0;
|
|
37400
|
+
let storiesRectified = 0;
|
|
37401
|
+
let currentTestOutput = fullSuiteResult.output;
|
|
37311
37402
|
const affectedStoriesList = Array.from(affectedStoriesObjs.values());
|
|
37312
37403
|
for (const story of affectedStoriesList) {
|
|
37313
37404
|
for (let attempt = 0;attempt < maxRectificationAttempts; attempt++) {
|
|
@@ -37319,32 +37410,51 @@ async function runDeferredRegression(options) {
|
|
|
37319
37410
|
story,
|
|
37320
37411
|
testCommand,
|
|
37321
37412
|
timeoutSeconds,
|
|
37322
|
-
testOutput:
|
|
37413
|
+
testOutput: currentTestOutput,
|
|
37323
37414
|
promptPrefix: `# DEFERRED REGRESSION: Full-Suite Failures
|
|
37324
37415
|
|
|
37325
37416
|
Your story ${story.id} broke tests in the full suite. Fix these regressions.`,
|
|
37326
37417
|
agentGetFn
|
|
37327
37418
|
});
|
|
37328
37419
|
if (fixed) {
|
|
37420
|
+
storiesRectified++;
|
|
37329
37421
|
logger?.info("regression", `Story ${story.id} rectified successfully`);
|
|
37422
|
+
logger?.info("regression", "Re-running full suite after story rectification", {
|
|
37423
|
+
storyId: story.id,
|
|
37424
|
+
storiesRectified,
|
|
37425
|
+
storiesRemaining: affectedStoriesList.length - storiesRectified
|
|
37426
|
+
});
|
|
37427
|
+
const midResult = await _regressionDeps.runVerification(verifyOpts);
|
|
37428
|
+
const midSuccess = midResult.success || midResult.status === "TIMEOUT" && acceptOnTimeout;
|
|
37429
|
+
if (midSuccess) {
|
|
37430
|
+
logger?.info("regression", "Full suite passed after story rectification \u2014 early exit", {
|
|
37431
|
+
storyId: story.id,
|
|
37432
|
+
storiesRectified,
|
|
37433
|
+
storiesSkipped: affectedStoriesList.length - storiesRectified,
|
|
37434
|
+
passCount: midResult.passCount ?? 0
|
|
37435
|
+
});
|
|
37436
|
+
return {
|
|
37437
|
+
success: true,
|
|
37438
|
+
failedTests: testFilesInFailures.size,
|
|
37439
|
+
failedTestFiles: Array.from(testFilesInFailures),
|
|
37440
|
+
passedTests: midResult.passCount ?? 0,
|
|
37441
|
+
rectificationAttempts,
|
|
37442
|
+
affectedStories: Array.from(affectedStories)
|
|
37443
|
+
};
|
|
37444
|
+
}
|
|
37445
|
+
logger?.warn("regression", "Full suite still failing after story rectification \u2014 continuing", {
|
|
37446
|
+
storyId: story.id,
|
|
37447
|
+
failCount: midResult.failCount ?? 0,
|
|
37448
|
+
passCount: midResult.passCount ?? 0
|
|
37449
|
+
});
|
|
37450
|
+
if (midResult.output)
|
|
37451
|
+
currentTestOutput = midResult.output;
|
|
37330
37452
|
break;
|
|
37331
37453
|
}
|
|
37332
37454
|
}
|
|
37333
37455
|
}
|
|
37334
37456
|
logger?.info("regression", "Re-running full suite after rectification");
|
|
37335
|
-
const retryResult = await _regressionDeps.runVerification(
|
|
37336
|
-
workdir,
|
|
37337
|
-
command: testCommand,
|
|
37338
|
-
timeoutSeconds,
|
|
37339
|
-
forceExit: config2.quality.forceExit,
|
|
37340
|
-
detectOpenHandles: config2.quality.detectOpenHandles,
|
|
37341
|
-
detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
|
|
37342
|
-
timeoutRetryCount: 0,
|
|
37343
|
-
gracePeriodMs: config2.quality.gracePeriodMs,
|
|
37344
|
-
drainTimeoutMs: config2.quality.drainTimeoutMs,
|
|
37345
|
-
shell: config2.quality.shell,
|
|
37346
|
-
stripEnvVars: config2.quality.stripEnvVars
|
|
37347
|
-
});
|
|
37457
|
+
const retryResult = await _regressionDeps.runVerification(verifyOpts);
|
|
37348
37458
|
const success2 = retryResult.success || retryResult.status === "TIMEOUT" && acceptOnTimeout;
|
|
37349
37459
|
if (success2) {
|
|
37350
37460
|
logger?.info("regression", "Deferred regression gate passed after rectification");
|
|
@@ -37601,12 +37711,12 @@ var init_headless_formatter = __esm(() => {
|
|
|
37601
37711
|
// src/pipeline/subscribers/events-writer.ts
|
|
37602
37712
|
import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
|
|
37603
37713
|
import { homedir as homedir5 } from "os";
|
|
37604
|
-
import { basename as
|
|
37714
|
+
import { basename as basename6, join as join44 } from "path";
|
|
37605
37715
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
37606
37716
|
const logger = getSafeLogger();
|
|
37607
|
-
const project =
|
|
37608
|
-
const eventsDir =
|
|
37609
|
-
const eventsFile =
|
|
37717
|
+
const project = basename6(workdir);
|
|
37718
|
+
const eventsDir = join44(homedir5(), ".nax", "events", project);
|
|
37719
|
+
const eventsFile = join44(eventsDir, "events.jsonl");
|
|
37610
37720
|
let dirReady = false;
|
|
37611
37721
|
const write = (line) => {
|
|
37612
37722
|
return (async () => {
|
|
@@ -37787,12 +37897,12 @@ var init_interaction2 = __esm(() => {
|
|
|
37787
37897
|
// src/pipeline/subscribers/registry.ts
|
|
37788
37898
|
import { mkdir as mkdir4, writeFile } from "fs/promises";
|
|
37789
37899
|
import { homedir as homedir6 } from "os";
|
|
37790
|
-
import { basename as
|
|
37900
|
+
import { basename as basename7, join as join45 } from "path";
|
|
37791
37901
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
37792
37902
|
const logger = getSafeLogger();
|
|
37793
|
-
const project =
|
|
37794
|
-
const runDir =
|
|
37795
|
-
const metaFile =
|
|
37903
|
+
const project = basename7(workdir);
|
|
37904
|
+
const runDir = join45(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
37905
|
+
const metaFile = join45(runDir, "meta.json");
|
|
37796
37906
|
const unsub = bus.on("run:started", (_ev) => {
|
|
37797
37907
|
return (async () => {
|
|
37798
37908
|
try {
|
|
@@ -37802,8 +37912,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
37802
37912
|
project,
|
|
37803
37913
|
feature,
|
|
37804
37914
|
workdir,
|
|
37805
|
-
statusPath:
|
|
37806
|
-
eventsDir:
|
|
37915
|
+
statusPath: join45(workdir, ".nax", "features", feature, "status.json"),
|
|
37916
|
+
eventsDir: join45(workdir, ".nax", "features", feature, "runs"),
|
|
37807
37917
|
registeredAt: new Date().toISOString()
|
|
37808
37918
|
};
|
|
37809
37919
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -38328,7 +38438,7 @@ function filterOutputFiles(files) {
|
|
|
38328
38438
|
}
|
|
38329
38439
|
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
38330
38440
|
const logger = getSafeLogger();
|
|
38331
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost
|
|
38441
|
+
const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
|
|
38332
38442
|
const prd = ctx.prd;
|
|
38333
38443
|
if (pipelineResult.context.storyMetrics) {
|
|
38334
38444
|
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
@@ -38376,7 +38486,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
|
|
|
38376
38486
|
const logger = getSafeLogger();
|
|
38377
38487
|
let prd = ctx.prd;
|
|
38378
38488
|
let prdDirty = false;
|
|
38379
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost
|
|
38489
|
+
const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
|
|
38380
38490
|
switch (pipelineResult.finalAction) {
|
|
38381
38491
|
case "pause":
|
|
38382
38492
|
markStoryPaused(prd, ctx.story.id);
|
|
@@ -38455,7 +38565,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
38455
38565
|
});
|
|
38456
38566
|
|
|
38457
38567
|
// src/execution/iteration-runner.ts
|
|
38458
|
-
import { join as
|
|
38568
|
+
import { join as join46 } from "path";
|
|
38459
38569
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
38460
38570
|
const logger = getSafeLogger();
|
|
38461
38571
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -38490,15 +38600,16 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
38490
38600
|
}
|
|
38491
38601
|
}
|
|
38492
38602
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
38493
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
38603
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
38494
38604
|
const pipelineContext = {
|
|
38495
|
-
config:
|
|
38496
|
-
|
|
38605
|
+
config: effectiveConfig,
|
|
38606
|
+
rootConfig: ctx.config,
|
|
38497
38607
|
prd,
|
|
38498
38608
|
story,
|
|
38499
38609
|
stories: storiesToExecute,
|
|
38500
38610
|
routing,
|
|
38501
|
-
|
|
38611
|
+
projectDir: ctx.workdir,
|
|
38612
|
+
workdir: story.workdir ? join46(ctx.workdir, story.workdir) : ctx.workdir,
|
|
38502
38613
|
prdPath: ctx.prdPath,
|
|
38503
38614
|
featureDir: ctx.featureDir,
|
|
38504
38615
|
hooks: ctx.hooks,
|
|
@@ -38523,13 +38634,6 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
38523
38634
|
await ctx.statusWriter.update(totalCost, iterations);
|
|
38524
38635
|
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
38525
38636
|
const currentPrd = pipelineResult.context.prd;
|
|
38526
|
-
pipelineContext.agentResult = undefined;
|
|
38527
|
-
pipelineContext.prompt = undefined;
|
|
38528
|
-
pipelineContext.contextMarkdown = undefined;
|
|
38529
|
-
pipelineContext.builtContext = undefined;
|
|
38530
|
-
pipelineContext.verifyResult = undefined;
|
|
38531
|
-
pipelineContext.reviewResult = undefined;
|
|
38532
|
-
pipelineContext.constitution = undefined;
|
|
38533
38637
|
const handlerCtx = {
|
|
38534
38638
|
config: ctx.config,
|
|
38535
38639
|
prd: currentPrd,
|
|
@@ -38552,26 +38656,36 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
38552
38656
|
storyStartTime,
|
|
38553
38657
|
statusWriter: ctx.statusWriter
|
|
38554
38658
|
};
|
|
38659
|
+
let iterResult;
|
|
38555
38660
|
if (pipelineResult.success) {
|
|
38556
|
-
const
|
|
38557
|
-
|
|
38558
|
-
prd:
|
|
38559
|
-
storiesCompletedDelta:
|
|
38560
|
-
costDelta:
|
|
38561
|
-
prdDirty:
|
|
38661
|
+
const r = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
38662
|
+
iterResult = {
|
|
38663
|
+
prd: r.prd,
|
|
38664
|
+
storiesCompletedDelta: r.storiesCompletedDelta,
|
|
38665
|
+
costDelta: r.costDelta,
|
|
38666
|
+
prdDirty: r.prdDirty,
|
|
38562
38667
|
finalAction: pipelineResult.finalAction
|
|
38563
38668
|
};
|
|
38669
|
+
} else {
|
|
38670
|
+
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
38671
|
+
iterResult = {
|
|
38672
|
+
prd: r.prd,
|
|
38673
|
+
storiesCompletedDelta: 0,
|
|
38674
|
+
costDelta: r.costDelta,
|
|
38675
|
+
prdDirty: r.prdDirty,
|
|
38676
|
+
finalAction: pipelineResult.finalAction,
|
|
38677
|
+
reason: pipelineResult.reason,
|
|
38678
|
+
subStoryCount: pipelineResult.subStoryCount
|
|
38679
|
+
};
|
|
38564
38680
|
}
|
|
38565
|
-
|
|
38566
|
-
|
|
38567
|
-
|
|
38568
|
-
|
|
38569
|
-
|
|
38570
|
-
|
|
38571
|
-
|
|
38572
|
-
|
|
38573
|
-
subStoryCount: pipelineResult.subStoryCount
|
|
38574
|
-
};
|
|
38681
|
+
pipelineContext.agentResult = undefined;
|
|
38682
|
+
pipelineContext.prompt = undefined;
|
|
38683
|
+
pipelineContext.contextMarkdown = undefined;
|
|
38684
|
+
pipelineContext.builtContext = undefined;
|
|
38685
|
+
pipelineContext.verifyResult = undefined;
|
|
38686
|
+
pipelineContext.reviewResult = undefined;
|
|
38687
|
+
pipelineContext.constitution = undefined;
|
|
38688
|
+
return iterResult;
|
|
38575
38689
|
}
|
|
38576
38690
|
var _iterationRunnerDeps;
|
|
38577
38691
|
var init_iteration_runner = __esm(() => {
|
|
@@ -38651,6 +38765,7 @@ __export(exports_parallel_worker, {
|
|
|
38651
38765
|
executeStoryInWorktree: () => executeStoryInWorktree,
|
|
38652
38766
|
executeParallelBatch: () => executeParallelBatch
|
|
38653
38767
|
});
|
|
38768
|
+
import { join as join47 } from "path";
|
|
38654
38769
|
async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
|
|
38655
38770
|
const logger = getSafeLogger();
|
|
38656
38771
|
try {
|
|
@@ -38665,10 +38780,12 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
38665
38780
|
}
|
|
38666
38781
|
const pipelineContext = {
|
|
38667
38782
|
...context,
|
|
38668
|
-
|
|
38783
|
+
config: context.config,
|
|
38784
|
+
rootConfig: context.rootConfig,
|
|
38669
38785
|
story,
|
|
38670
38786
|
stories: [story],
|
|
38671
|
-
|
|
38787
|
+
projectDir: context.projectDir,
|
|
38788
|
+
workdir: story.workdir ? join47(worktreePath, story.workdir) : worktreePath,
|
|
38672
38789
|
routing,
|
|
38673
38790
|
storyGitRef: storyGitRef ?? undefined
|
|
38674
38791
|
};
|
|
@@ -38713,7 +38830,7 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
|
|
|
38713
38830
|
}
|
|
38714
38831
|
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
38715
38832
|
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
38716
|
-
const storyContext = storyConfig ? { ...context,
|
|
38833
|
+
const storyContext = storyConfig ? { ...context, config: storyConfig } : context;
|
|
38717
38834
|
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
38718
38835
|
results.totalCost += result.cost;
|
|
38719
38836
|
results.storyCosts.set(story.id, result.cost);
|
|
@@ -38755,19 +38872,19 @@ __export(exports_manager, {
|
|
|
38755
38872
|
_managerDeps: () => _managerDeps,
|
|
38756
38873
|
WorktreeManager: () => WorktreeManager
|
|
38757
38874
|
});
|
|
38758
|
-
import { existsSync as
|
|
38875
|
+
import { existsSync as existsSync29, symlinkSync } from "fs";
|
|
38759
38876
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
38760
|
-
import { join as
|
|
38877
|
+
import { join as join48 } from "path";
|
|
38761
38878
|
|
|
38762
38879
|
class WorktreeManager {
|
|
38763
38880
|
async ensureGitExcludes(projectRoot) {
|
|
38764
38881
|
const logger = getSafeLogger();
|
|
38765
|
-
const infoDir =
|
|
38766
|
-
const excludePath =
|
|
38882
|
+
const infoDir = join48(projectRoot, ".git", "info");
|
|
38883
|
+
const excludePath = join48(infoDir, "exclude");
|
|
38767
38884
|
try {
|
|
38768
38885
|
await mkdir5(infoDir, { recursive: true });
|
|
38769
38886
|
let existing = "";
|
|
38770
|
-
if (
|
|
38887
|
+
if (existsSync29(excludePath)) {
|
|
38771
38888
|
existing = await Bun.file(excludePath).text();
|
|
38772
38889
|
}
|
|
38773
38890
|
const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
@@ -38790,7 +38907,7 @@ ${missing.join(`
|
|
|
38790
38907
|
}
|
|
38791
38908
|
async create(projectRoot, storyId) {
|
|
38792
38909
|
validateStoryId(storyId);
|
|
38793
|
-
const worktreePath =
|
|
38910
|
+
const worktreePath = join48(projectRoot, ".nax-wt", storyId);
|
|
38794
38911
|
const branchName = `nax/${storyId}`;
|
|
38795
38912
|
try {
|
|
38796
38913
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -38831,9 +38948,9 @@ ${missing.join(`
|
|
|
38831
38948
|
}
|
|
38832
38949
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
38833
38950
|
}
|
|
38834
|
-
const nodeModulesSource =
|
|
38835
|
-
if (
|
|
38836
|
-
const nodeModulesTarget =
|
|
38951
|
+
const nodeModulesSource = join48(projectRoot, "node_modules");
|
|
38952
|
+
if (existsSync29(nodeModulesSource)) {
|
|
38953
|
+
const nodeModulesTarget = join48(worktreePath, "node_modules");
|
|
38837
38954
|
try {
|
|
38838
38955
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
38839
38956
|
} catch (error48) {
|
|
@@ -38841,9 +38958,9 @@ ${missing.join(`
|
|
|
38841
38958
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
38842
38959
|
}
|
|
38843
38960
|
}
|
|
38844
|
-
const envSource =
|
|
38845
|
-
if (
|
|
38846
|
-
const envTarget =
|
|
38961
|
+
const envSource = join48(projectRoot, ".env");
|
|
38962
|
+
if (existsSync29(envSource)) {
|
|
38963
|
+
const envTarget = join48(worktreePath, ".env");
|
|
38847
38964
|
try {
|
|
38848
38965
|
symlinkSync(envSource, envTarget, "file");
|
|
38849
38966
|
} catch (error48) {
|
|
@@ -38854,7 +38971,7 @@ ${missing.join(`
|
|
|
38854
38971
|
}
|
|
38855
38972
|
async remove(projectRoot, storyId) {
|
|
38856
38973
|
validateStoryId(storyId);
|
|
38857
|
-
const worktreePath =
|
|
38974
|
+
const worktreePath = join48(projectRoot, ".nax-wt", storyId);
|
|
38858
38975
|
const branchName = `nax/${storyId}`;
|
|
38859
38976
|
try {
|
|
38860
38977
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -39214,10 +39331,11 @@ async function rectifyConflictedStory(options) {
|
|
|
39214
39331
|
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
39215
39332
|
const pipelineContext = {
|
|
39216
39333
|
config: config2,
|
|
39217
|
-
|
|
39334
|
+
rootConfig: config2,
|
|
39218
39335
|
prd,
|
|
39219
39336
|
story,
|
|
39220
39337
|
stories: [story],
|
|
39338
|
+
projectDir: workdir,
|
|
39221
39339
|
workdir: worktreePath,
|
|
39222
39340
|
featureDir: undefined,
|
|
39223
39341
|
hooks,
|
|
@@ -39454,8 +39572,9 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39454
39572
|
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
39455
39573
|
preRunCtx = {
|
|
39456
39574
|
config: ctx.config,
|
|
39457
|
-
|
|
39575
|
+
rootConfig: ctx.config,
|
|
39458
39576
|
prd,
|
|
39577
|
+
projectDir: ctx.workdir,
|
|
39459
39578
|
workdir: ctx.workdir,
|
|
39460
39579
|
featureDir: ctx.featureDir,
|
|
39461
39580
|
story: prd.userStories[0],
|
|
@@ -39500,6 +39619,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39500
39619
|
const readyStories = getAllReadyStories(prd);
|
|
39501
39620
|
const batch = _unifiedExecutorDeps.selectIndependentBatch(readyStories, ctx.parallelCount);
|
|
39502
39621
|
if (batch.length > 1) {
|
|
39622
|
+
ctx.onBeforeStory?.();
|
|
39503
39623
|
for (const story of batch) {
|
|
39504
39624
|
pipelineEventBus.emit({
|
|
39505
39625
|
type: "story:started",
|
|
@@ -39525,8 +39645,9 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39525
39645
|
maxConcurrency: ctx.parallelCount,
|
|
39526
39646
|
pipelineContext: {
|
|
39527
39647
|
config: ctx.config,
|
|
39528
|
-
|
|
39648
|
+
rootConfig: ctx.config,
|
|
39529
39649
|
prd,
|
|
39650
|
+
projectDir: ctx.workdir,
|
|
39530
39651
|
hooks: ctx.hooks,
|
|
39531
39652
|
featureDir: ctx.featureDir,
|
|
39532
39653
|
agentGetFn: ctx.agentGetFn,
|
|
@@ -39640,6 +39761,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39640
39761
|
}
|
|
39641
39762
|
pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
|
|
39642
39763
|
}
|
|
39764
|
+
ctx.onBeforeStory?.();
|
|
39643
39765
|
pipelineEventBus.emit({
|
|
39644
39766
|
type: "story:started",
|
|
39645
39767
|
storyId: singleStory.id,
|
|
@@ -39702,6 +39824,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39702
39824
|
}
|
|
39703
39825
|
pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
|
|
39704
39826
|
}
|
|
39827
|
+
ctx.onBeforeStory?.();
|
|
39705
39828
|
pipelineEventBus.emit({
|
|
39706
39829
|
type: "story:started",
|
|
39707
39830
|
storyId: selection.story.id,
|
|
@@ -39790,16 +39913,16 @@ var init_unified_executor = __esm(() => {
|
|
|
39790
39913
|
});
|
|
39791
39914
|
|
|
39792
39915
|
// src/project/detector.ts
|
|
39793
|
-
import { join as
|
|
39916
|
+
import { join as join49 } from "path";
|
|
39794
39917
|
async function detectLanguage(workdir, pkg) {
|
|
39795
39918
|
const deps = _detectorDeps;
|
|
39796
|
-
if (await deps.fileExists(
|
|
39919
|
+
if (await deps.fileExists(join49(workdir, "go.mod")))
|
|
39797
39920
|
return "go";
|
|
39798
|
-
if (await deps.fileExists(
|
|
39921
|
+
if (await deps.fileExists(join49(workdir, "Cargo.toml")))
|
|
39799
39922
|
return "rust";
|
|
39800
|
-
if (await deps.fileExists(
|
|
39923
|
+
if (await deps.fileExists(join49(workdir, "pyproject.toml")))
|
|
39801
39924
|
return "python";
|
|
39802
|
-
if (await deps.fileExists(
|
|
39925
|
+
if (await deps.fileExists(join49(workdir, "requirements.txt")))
|
|
39803
39926
|
return "python";
|
|
39804
39927
|
if (pkg != null) {
|
|
39805
39928
|
const allDeps = {
|
|
@@ -39859,18 +39982,18 @@ async function detectLintTool(workdir, language) {
|
|
|
39859
39982
|
if (language === "python")
|
|
39860
39983
|
return "ruff";
|
|
39861
39984
|
const deps = _detectorDeps;
|
|
39862
|
-
if (await deps.fileExists(
|
|
39985
|
+
if (await deps.fileExists(join49(workdir, "biome.json")))
|
|
39863
39986
|
return "biome";
|
|
39864
|
-
if (await deps.fileExists(
|
|
39987
|
+
if (await deps.fileExists(join49(workdir, ".eslintrc")))
|
|
39865
39988
|
return "eslint";
|
|
39866
|
-
if (await deps.fileExists(
|
|
39989
|
+
if (await deps.fileExists(join49(workdir, ".eslintrc.js")))
|
|
39867
39990
|
return "eslint";
|
|
39868
|
-
if (await deps.fileExists(
|
|
39991
|
+
if (await deps.fileExists(join49(workdir, ".eslintrc.json")))
|
|
39869
39992
|
return "eslint";
|
|
39870
39993
|
return;
|
|
39871
39994
|
}
|
|
39872
39995
|
async function detectProjectProfile(workdir, existing) {
|
|
39873
|
-
const pkg = await _detectorDeps.readJson(
|
|
39996
|
+
const pkg = await _detectorDeps.readJson(join49(workdir, "package.json"));
|
|
39874
39997
|
const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
|
|
39875
39998
|
const type = existing.type !== undefined ? existing.type : detectType(pkg);
|
|
39876
39999
|
const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
|
|
@@ -39907,7 +40030,7 @@ var init_project = __esm(() => {
|
|
|
39907
40030
|
|
|
39908
40031
|
// src/execution/status-file.ts
|
|
39909
40032
|
import { rename, unlink as unlink3 } from "fs/promises";
|
|
39910
|
-
import { resolve as
|
|
40033
|
+
import { resolve as resolve10 } from "path";
|
|
39911
40034
|
function countProgress(prd) {
|
|
39912
40035
|
const stories = prd.userStories;
|
|
39913
40036
|
const passed = stories.filter((s) => s.status === "passed").length;
|
|
@@ -39952,7 +40075,7 @@ function buildStatusSnapshot(state) {
|
|
|
39952
40075
|
return snapshot;
|
|
39953
40076
|
}
|
|
39954
40077
|
async function writeStatusFile(filePath, status) {
|
|
39955
|
-
const resolvedPath =
|
|
40078
|
+
const resolvedPath = resolve10(filePath);
|
|
39956
40079
|
if (filePath.includes("../") || filePath.includes("..\\")) {
|
|
39957
40080
|
throw new Error("Invalid status file path: path traversal detected");
|
|
39958
40081
|
}
|
|
@@ -39966,7 +40089,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
39966
40089
|
var init_status_file = () => {};
|
|
39967
40090
|
|
|
39968
40091
|
// src/execution/status-writer.ts
|
|
39969
|
-
import { join as
|
|
40092
|
+
import { join as join50 } from "path";
|
|
39970
40093
|
|
|
39971
40094
|
class StatusWriter {
|
|
39972
40095
|
statusFile;
|
|
@@ -40080,7 +40203,7 @@ class StatusWriter {
|
|
|
40080
40203
|
if (!this._prd)
|
|
40081
40204
|
return;
|
|
40082
40205
|
const safeLogger = getSafeLogger();
|
|
40083
|
-
const featureStatusPath =
|
|
40206
|
+
const featureStatusPath = join50(featureDir, "status.json");
|
|
40084
40207
|
const write = async () => {
|
|
40085
40208
|
try {
|
|
40086
40209
|
const base = this.getSnapshot(totalCost, iterations);
|
|
@@ -40291,7 +40414,7 @@ __export(exports_run_initialization, {
|
|
|
40291
40414
|
initializeRun: () => initializeRun,
|
|
40292
40415
|
_reconcileDeps: () => _reconcileDeps
|
|
40293
40416
|
});
|
|
40294
|
-
import { join as
|
|
40417
|
+
import { join as join51 } from "path";
|
|
40295
40418
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
40296
40419
|
const logger = getSafeLogger();
|
|
40297
40420
|
let reconciledCount = 0;
|
|
@@ -40309,7 +40432,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
40309
40432
|
});
|
|
40310
40433
|
continue;
|
|
40311
40434
|
}
|
|
40312
|
-
const effectiveWorkdir = story.workdir ?
|
|
40435
|
+
const effectiveWorkdir = story.workdir ? join51(workdir, story.workdir) : workdir;
|
|
40313
40436
|
try {
|
|
40314
40437
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
40315
40438
|
if (!reviewResult.success) {
|
|
@@ -41035,14 +41158,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41035
41158
|
prevActScopeDepth !== actScopeDepth - 1 && console.error("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ");
|
|
41036
41159
|
actScopeDepth = prevActScopeDepth;
|
|
41037
41160
|
}
|
|
41038
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
41161
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve11, reject) {
|
|
41039
41162
|
var queue = ReactSharedInternals.actQueue;
|
|
41040
41163
|
if (queue !== null)
|
|
41041
41164
|
if (queue.length !== 0)
|
|
41042
41165
|
try {
|
|
41043
41166
|
flushActQueue(queue);
|
|
41044
41167
|
enqueueTask(function() {
|
|
41045
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
41168
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
|
|
41046
41169
|
});
|
|
41047
41170
|
return;
|
|
41048
41171
|
} catch (error48) {
|
|
@@ -41050,7 +41173,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41050
41173
|
}
|
|
41051
41174
|
else
|
|
41052
41175
|
ReactSharedInternals.actQueue = null;
|
|
41053
|
-
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) :
|
|
41176
|
+
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve11(returnValue);
|
|
41054
41177
|
}
|
|
41055
41178
|
function flushActQueue(queue) {
|
|
41056
41179
|
if (!isFlushing) {
|
|
@@ -41226,14 +41349,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41226
41349
|
didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = true, console.error("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"));
|
|
41227
41350
|
});
|
|
41228
41351
|
return {
|
|
41229
|
-
then: function(
|
|
41352
|
+
then: function(resolve11, reject) {
|
|
41230
41353
|
didAwaitActCall = true;
|
|
41231
41354
|
thenable.then(function(returnValue) {
|
|
41232
41355
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
41233
41356
|
if (prevActScopeDepth === 0) {
|
|
41234
41357
|
try {
|
|
41235
41358
|
flushActQueue(queue), enqueueTask(function() {
|
|
41236
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
41359
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
|
|
41237
41360
|
});
|
|
41238
41361
|
} catch (error$0) {
|
|
41239
41362
|
ReactSharedInternals.thrownErrors.push(error$0);
|
|
@@ -41244,7 +41367,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41244
41367
|
reject(_thrownError);
|
|
41245
41368
|
}
|
|
41246
41369
|
} else
|
|
41247
|
-
|
|
41370
|
+
resolve11(returnValue);
|
|
41248
41371
|
}, function(error48) {
|
|
41249
41372
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
41250
41373
|
0 < ReactSharedInternals.thrownErrors.length ? (error48 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(error48)) : reject(error48);
|
|
@@ -41260,11 +41383,11 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41260
41383
|
if (0 < ReactSharedInternals.thrownErrors.length)
|
|
41261
41384
|
throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
|
|
41262
41385
|
return {
|
|
41263
|
-
then: function(
|
|
41386
|
+
then: function(resolve11, reject) {
|
|
41264
41387
|
didAwaitActCall = true;
|
|
41265
41388
|
prevActScopeDepth === 0 ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
|
|
41266
|
-
return recursivelyFlushAsyncActWork(returnValue$jscomp$0,
|
|
41267
|
-
})) :
|
|
41389
|
+
return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve11, reject);
|
|
41390
|
+
})) : resolve11(returnValue$jscomp$0);
|
|
41268
41391
|
}
|
|
41269
41392
|
};
|
|
41270
41393
|
};
|
|
@@ -44106,8 +44229,8 @@ It can also happen if the client has a browser extension installed which messes
|
|
|
44106
44229
|
currentEntangledActionThenable = {
|
|
44107
44230
|
status: "pending",
|
|
44108
44231
|
value: undefined,
|
|
44109
|
-
then: function(
|
|
44110
|
-
entangledListeners.push(
|
|
44232
|
+
then: function(resolve11) {
|
|
44233
|
+
entangledListeners.push(resolve11);
|
|
44111
44234
|
}
|
|
44112
44235
|
};
|
|
44113
44236
|
}
|
|
@@ -44131,8 +44254,8 @@ It can also happen if the client has a browser extension installed which messes
|
|
|
44131
44254
|
status: "pending",
|
|
44132
44255
|
value: null,
|
|
44133
44256
|
reason: null,
|
|
44134
|
-
then: function(
|
|
44135
|
-
listeners.push(
|
|
44257
|
+
then: function(resolve11) {
|
|
44258
|
+
listeners.push(resolve11);
|
|
44136
44259
|
}
|
|
44137
44260
|
};
|
|
44138
44261
|
thenable.then(function() {
|
|
@@ -71522,9 +71645,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
71522
71645
|
|
|
71523
71646
|
// bin/nax.ts
|
|
71524
71647
|
init_source();
|
|
71525
|
-
import { existsSync as
|
|
71648
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
|
|
71526
71649
|
import { homedir as homedir8 } from "os";
|
|
71527
|
-
import { join as
|
|
71650
|
+
import { join as join53 } from "path";
|
|
71528
71651
|
|
|
71529
71652
|
// node_modules/commander/esm.mjs
|
|
71530
71653
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -71550,15 +71673,15 @@ import { join as join11 } from "path";
|
|
|
71550
71673
|
import { createInterface as createInterface2 } from "readline";
|
|
71551
71674
|
|
|
71552
71675
|
// src/analyze/scanner.ts
|
|
71553
|
-
import { existsSync as
|
|
71676
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
71554
71677
|
import { join as join4 } from "path";
|
|
71555
71678
|
async function scanCodebase(workdir) {
|
|
71556
71679
|
const srcPath = join4(workdir, "src");
|
|
71557
71680
|
const packageJsonPath = join4(workdir, "package.json");
|
|
71558
|
-
const fileTree =
|
|
71681
|
+
const fileTree = existsSync3(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
|
|
71559
71682
|
let dependencies = {};
|
|
71560
71683
|
let devDependencies = {};
|
|
71561
|
-
if (
|
|
71684
|
+
if (existsSync3(packageJsonPath)) {
|
|
71562
71685
|
try {
|
|
71563
71686
|
const pkg = await Bun.file(packageJsonPath).json();
|
|
71564
71687
|
dependencies = pkg.dependencies || {};
|
|
@@ -71619,16 +71742,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
|
|
|
71619
71742
|
} else {
|
|
71620
71743
|
patterns.push("Test framework: likely bun:test (no framework dependency)");
|
|
71621
71744
|
}
|
|
71622
|
-
if (
|
|
71745
|
+
if (existsSync3(join4(workdir, "test"))) {
|
|
71623
71746
|
patterns.push("Test directory: test/");
|
|
71624
71747
|
}
|
|
71625
|
-
if (
|
|
71748
|
+
if (existsSync3(join4(workdir, "__tests__"))) {
|
|
71626
71749
|
patterns.push("Test directory: __tests__/");
|
|
71627
71750
|
}
|
|
71628
|
-
if (
|
|
71751
|
+
if (existsSync3(join4(workdir, "tests"))) {
|
|
71629
71752
|
patterns.push("Test directory: tests/");
|
|
71630
71753
|
}
|
|
71631
|
-
const hasTestFiles =
|
|
71754
|
+
const hasTestFiles = existsSync3(join4(workdir, "test")) || existsSync3(join4(workdir, "src"));
|
|
71632
71755
|
if (hasTestFiles) {
|
|
71633
71756
|
patterns.push("Test files: *.test.ts, *.spec.ts");
|
|
71634
71757
|
}
|
|
@@ -71640,7 +71763,7 @@ init_test_strategy();
|
|
|
71640
71763
|
|
|
71641
71764
|
// src/context/generator.ts
|
|
71642
71765
|
init_path_security();
|
|
71643
|
-
import { existsSync as existsSync5
|
|
71766
|
+
import { existsSync as existsSync5 } from "fs";
|
|
71644
71767
|
import { join as join6, relative } from "path";
|
|
71645
71768
|
|
|
71646
71769
|
// src/context/injector.ts
|
|
@@ -71983,7 +72106,6 @@ var windsurfGenerator = {
|
|
|
71983
72106
|
// src/context/generator.ts
|
|
71984
72107
|
var _generatorDeps = {
|
|
71985
72108
|
existsSync: (p) => existsSync5(p),
|
|
71986
|
-
readFileSync: (p, enc) => readFileSync(p, enc),
|
|
71987
72109
|
readTextFile: (p) => Bun.file(p).text(),
|
|
71988
72110
|
writeFile: (p, content) => Bun.write(p, content),
|
|
71989
72111
|
buildProjectMetadata
|
|
@@ -72085,47 +72207,41 @@ async function discoverWorkspacePackages(repoRoot) {
|
|
|
72085
72207
|
}
|
|
72086
72208
|
}
|
|
72087
72209
|
const turboPath = join6(repoRoot, "turbo.json");
|
|
72088
|
-
|
|
72089
|
-
|
|
72090
|
-
|
|
72091
|
-
|
|
72092
|
-
|
|
72093
|
-
|
|
72094
|
-
} catch {}
|
|
72095
|
-
}
|
|
72210
|
+
try {
|
|
72211
|
+
const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
|
|
72212
|
+
if (Array.isArray(turbo.packages)) {
|
|
72213
|
+
await resolveGlobs(turbo.packages);
|
|
72214
|
+
}
|
|
72215
|
+
} catch {}
|
|
72096
72216
|
const pkgPath = join6(repoRoot, "package.json");
|
|
72097
|
-
|
|
72098
|
-
|
|
72099
|
-
|
|
72100
|
-
|
|
72101
|
-
|
|
72102
|
-
|
|
72103
|
-
|
|
72104
|
-
} catch {}
|
|
72105
|
-
}
|
|
72217
|
+
try {
|
|
72218
|
+
const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
|
|
72219
|
+
const ws = pkg.workspaces;
|
|
72220
|
+
const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
72221
|
+
if (patterns.length > 0)
|
|
72222
|
+
await resolveGlobs(patterns);
|
|
72223
|
+
} catch {}
|
|
72106
72224
|
const pnpmPath = join6(repoRoot, "pnpm-workspace.yaml");
|
|
72107
|
-
|
|
72108
|
-
|
|
72109
|
-
|
|
72110
|
-
const lines = raw.split(`
|
|
72225
|
+
try {
|
|
72226
|
+
const raw = await _generatorDeps.readTextFile(pnpmPath);
|
|
72227
|
+
const lines = raw.split(`
|
|
72111
72228
|
`);
|
|
72112
|
-
|
|
72113
|
-
|
|
72114
|
-
|
|
72115
|
-
|
|
72116
|
-
|
|
72117
|
-
|
|
72118
|
-
}
|
|
72119
|
-
if (inPackages && /^\s+-\s+/.test(line)) {
|
|
72120
|
-
patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
|
|
72121
|
-
} else if (inPackages && !/^\s/.test(line)) {
|
|
72122
|
-
break;
|
|
72123
|
-
}
|
|
72229
|
+
let inPackages = false;
|
|
72230
|
+
const patterns = [];
|
|
72231
|
+
for (const line of lines) {
|
|
72232
|
+
if (/^packages\s*:/.test(line)) {
|
|
72233
|
+
inPackages = true;
|
|
72234
|
+
continue;
|
|
72124
72235
|
}
|
|
72125
|
-
if (
|
|
72126
|
-
|
|
72127
|
-
|
|
72128
|
-
|
|
72236
|
+
if (inPackages && /^\s+-\s+/.test(line)) {
|
|
72237
|
+
patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
|
|
72238
|
+
} else if (inPackages && !/^\s/.test(line)) {
|
|
72239
|
+
break;
|
|
72240
|
+
}
|
|
72241
|
+
}
|
|
72242
|
+
if (patterns.length > 0)
|
|
72243
|
+
await resolveGlobs(patterns);
|
|
72244
|
+
} catch {}
|
|
72129
72245
|
return results.sort();
|
|
72130
72246
|
}
|
|
72131
72247
|
async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
|
|
@@ -72657,13 +72773,13 @@ function createCliInteractionBridge() {
|
|
|
72657
72773
|
process.stdout.write(`
|
|
72658
72774
|
\uD83E\uDD16 Agent: ${text}
|
|
72659
72775
|
You: `);
|
|
72660
|
-
return new Promise((
|
|
72776
|
+
return new Promise((resolve6) => {
|
|
72661
72777
|
const rl = createInterface2({ input: process.stdin, terminal: false });
|
|
72662
72778
|
rl.once("line", (line) => {
|
|
72663
72779
|
rl.close();
|
|
72664
|
-
|
|
72780
|
+
resolve6(line.trim());
|
|
72665
72781
|
});
|
|
72666
|
-
rl.once("close", () =>
|
|
72782
|
+
rl.once("close", () => resolve6(""));
|
|
72667
72783
|
});
|
|
72668
72784
|
}
|
|
72669
72785
|
};
|
|
@@ -73168,20 +73284,20 @@ async function displayModelEfficiency(workdir) {
|
|
|
73168
73284
|
// src/cli/status-features.ts
|
|
73169
73285
|
init_source();
|
|
73170
73286
|
import { existsSync as existsSync15, readdirSync as readdirSync4 } from "fs";
|
|
73171
|
-
import { join as join14, resolve as
|
|
73287
|
+
import { join as join14, resolve as resolve7 } from "path";
|
|
73172
73288
|
|
|
73173
73289
|
// src/commands/common.ts
|
|
73174
73290
|
init_path_security();
|
|
73175
73291
|
init_errors();
|
|
73176
73292
|
import { existsSync as existsSync14, readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
73177
|
-
import { join as join12, resolve as
|
|
73293
|
+
import { join as join12, resolve as resolve6 } from "path";
|
|
73178
73294
|
function resolveProject(options = {}) {
|
|
73179
73295
|
const { dir, feature } = options;
|
|
73180
73296
|
let projectRoot;
|
|
73181
73297
|
let naxDir;
|
|
73182
73298
|
let configPath;
|
|
73183
73299
|
if (dir) {
|
|
73184
|
-
projectRoot = realpathSync2(
|
|
73300
|
+
projectRoot = realpathSync2(resolve6(dir));
|
|
73185
73301
|
naxDir = join12(projectRoot, ".nax");
|
|
73186
73302
|
if (!existsSync14(naxDir)) {
|
|
73187
73303
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
@@ -73234,7 +73350,7 @@ No features found in this project.`;
|
|
|
73234
73350
|
};
|
|
73235
73351
|
}
|
|
73236
73352
|
function findProjectRoot(startDir) {
|
|
73237
|
-
let current =
|
|
73353
|
+
let current = resolve6(startDir);
|
|
73238
73354
|
let depth = 0;
|
|
73239
73355
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
73240
73356
|
const naxDir = join12(current, ".nax");
|
|
@@ -73551,7 +73667,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
73551
73667
|
if (options.feature) {
|
|
73552
73668
|
let featureDir;
|
|
73553
73669
|
if (options.dir) {
|
|
73554
|
-
featureDir = join14(
|
|
73670
|
+
featureDir = join14(resolve7(options.dir), ".nax", "features", options.feature);
|
|
73555
73671
|
} else {
|
|
73556
73672
|
const resolved = resolveProject({ feature: options.feature });
|
|
73557
73673
|
if (!resolved.featureDir) {
|
|
@@ -73661,8 +73777,8 @@ async function runsShowCommand(options) {
|
|
|
73661
73777
|
}
|
|
73662
73778
|
// src/cli/prompts-main.ts
|
|
73663
73779
|
init_logger2();
|
|
73664
|
-
import { existsSync as
|
|
73665
|
-
import { join as
|
|
73780
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync3 } from "fs";
|
|
73781
|
+
import { join as join24 } from "path";
|
|
73666
73782
|
|
|
73667
73783
|
// src/pipeline/index.ts
|
|
73668
73784
|
init_runner();
|
|
@@ -73737,7 +73853,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
73737
73853
|
|
|
73738
73854
|
// src/cli/prompts-tdd.ts
|
|
73739
73855
|
init_prompts2();
|
|
73740
|
-
import { join as
|
|
73856
|
+
import { join as join23 } from "path";
|
|
73741
73857
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
73742
73858
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
73743
73859
|
PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
@@ -73756,7 +73872,7 @@ ${frontmatter}---
|
|
|
73756
73872
|
|
|
73757
73873
|
${session.prompt}`;
|
|
73758
73874
|
if (outputDir) {
|
|
73759
|
-
const promptFile =
|
|
73875
|
+
const promptFile = join23(outputDir, `${story.id}.${session.role}.md`);
|
|
73760
73876
|
await Bun.write(promptFile, fullOutput);
|
|
73761
73877
|
logger.info("cli", "Written TDD prompt file", {
|
|
73762
73878
|
storyId: story.id,
|
|
@@ -73772,7 +73888,7 @@ ${"=".repeat(80)}`);
|
|
|
73772
73888
|
}
|
|
73773
73889
|
}
|
|
73774
73890
|
if (outputDir && ctx.contextMarkdown) {
|
|
73775
|
-
const contextFile =
|
|
73891
|
+
const contextFile = join23(outputDir, `${story.id}.context.md`);
|
|
73776
73892
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
73777
73893
|
const contextOutput = `---
|
|
73778
73894
|
${frontmatter}---
|
|
@@ -73786,13 +73902,13 @@ ${ctx.contextMarkdown}`;
|
|
|
73786
73902
|
async function promptsCommand(options) {
|
|
73787
73903
|
const logger = getLogger();
|
|
73788
73904
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
73789
|
-
const naxDir =
|
|
73790
|
-
if (!
|
|
73905
|
+
const naxDir = join24(workdir, ".nax");
|
|
73906
|
+
if (!existsSync19(naxDir)) {
|
|
73791
73907
|
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
73792
73908
|
}
|
|
73793
|
-
const featureDir =
|
|
73794
|
-
const prdPath =
|
|
73795
|
-
if (!
|
|
73909
|
+
const featureDir = join24(naxDir, "features", feature);
|
|
73910
|
+
const prdPath = join24(featureDir, "prd.json");
|
|
73911
|
+
if (!existsSync19(prdPath)) {
|
|
73796
73912
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
73797
73913
|
}
|
|
73798
73914
|
const prd = await loadPRD(prdPath);
|
|
@@ -73813,7 +73929,7 @@ async function promptsCommand(options) {
|
|
|
73813
73929
|
for (const story of stories) {
|
|
73814
73930
|
const ctx = {
|
|
73815
73931
|
config: config2,
|
|
73816
|
-
|
|
73932
|
+
rootConfig: config2,
|
|
73817
73933
|
prd,
|
|
73818
73934
|
story,
|
|
73819
73935
|
stories: [story],
|
|
@@ -73823,6 +73939,7 @@ async function promptsCommand(options) {
|
|
|
73823
73939
|
testStrategy: "test-after",
|
|
73824
73940
|
reasoning: "Placeholder routing"
|
|
73825
73941
|
},
|
|
73942
|
+
projectDir: workdir,
|
|
73826
73943
|
workdir,
|
|
73827
73944
|
featureDir,
|
|
73828
73945
|
hooks: { hooks: {} }
|
|
@@ -73852,10 +73969,10 @@ ${frontmatter}---
|
|
|
73852
73969
|
|
|
73853
73970
|
${ctx.prompt}`;
|
|
73854
73971
|
if (outputDir) {
|
|
73855
|
-
const promptFile =
|
|
73972
|
+
const promptFile = join24(outputDir, `${story.id}.prompt.md`);
|
|
73856
73973
|
await Bun.write(promptFile, fullOutput);
|
|
73857
73974
|
if (ctx.contextMarkdown) {
|
|
73858
|
-
const contextFile =
|
|
73975
|
+
const contextFile = join24(outputDir, `${story.id}.context.md`);
|
|
73859
73976
|
const contextOutput = `---
|
|
73860
73977
|
${frontmatter}---
|
|
73861
73978
|
|
|
@@ -73881,8 +73998,8 @@ ${"=".repeat(80)}`);
|
|
|
73881
73998
|
return processedStories;
|
|
73882
73999
|
}
|
|
73883
74000
|
// src/cli/prompts-init.ts
|
|
73884
|
-
import { existsSync as
|
|
73885
|
-
import { join as
|
|
74001
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync4 } from "fs";
|
|
74002
|
+
import { join as join25 } from "path";
|
|
73886
74003
|
var TEMPLATE_ROLES = [
|
|
73887
74004
|
{ file: "test-writer.md", role: "test-writer" },
|
|
73888
74005
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -73906,9 +74023,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
73906
74023
|
`;
|
|
73907
74024
|
async function promptsInitCommand(options) {
|
|
73908
74025
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
73909
|
-
const templatesDir =
|
|
74026
|
+
const templatesDir = join25(workdir, ".nax", "templates");
|
|
73910
74027
|
mkdirSync4(templatesDir, { recursive: true });
|
|
73911
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) =>
|
|
74028
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync20(join25(templatesDir, f)));
|
|
73912
74029
|
if (existingFiles.length > 0 && !force) {
|
|
73913
74030
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
73914
74031
|
Pass --force to overwrite existing templates.`);
|
|
@@ -73916,7 +74033,7 @@ async function promptsInitCommand(options) {
|
|
|
73916
74033
|
}
|
|
73917
74034
|
const written = [];
|
|
73918
74035
|
for (const template of TEMPLATE_ROLES) {
|
|
73919
|
-
const filePath =
|
|
74036
|
+
const filePath = join25(templatesDir, template.file);
|
|
73920
74037
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
73921
74038
|
const content = TEMPLATE_HEADER + roleBody;
|
|
73922
74039
|
await Bun.write(filePath, content);
|
|
@@ -73932,8 +74049,8 @@ async function promptsInitCommand(options) {
|
|
|
73932
74049
|
return written;
|
|
73933
74050
|
}
|
|
73934
74051
|
async function autoWirePromptsConfig(workdir) {
|
|
73935
|
-
const configPath =
|
|
73936
|
-
if (!
|
|
74052
|
+
const configPath = join25(workdir, "nax.config.json");
|
|
74053
|
+
if (!existsSync20(configPath)) {
|
|
73937
74054
|
const exampleConfig = JSON.stringify({
|
|
73938
74055
|
prompts: {
|
|
73939
74056
|
overrides: {
|
|
@@ -74097,8 +74214,8 @@ function pad(str, width) {
|
|
|
74097
74214
|
init_config();
|
|
74098
74215
|
init_logger2();
|
|
74099
74216
|
init_prd();
|
|
74100
|
-
import { existsSync as
|
|
74101
|
-
import { join as
|
|
74217
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
|
|
74218
|
+
import { join as join30 } from "path";
|
|
74102
74219
|
|
|
74103
74220
|
// src/cli/diagnose-analysis.ts
|
|
74104
74221
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -74297,8 +74414,8 @@ function isProcessAlive2(pid) {
|
|
|
74297
74414
|
}
|
|
74298
74415
|
}
|
|
74299
74416
|
async function loadStatusFile2(workdir) {
|
|
74300
|
-
const statusPath =
|
|
74301
|
-
if (!
|
|
74417
|
+
const statusPath = join30(workdir, ".nax", "status.json");
|
|
74418
|
+
if (!existsSync22(statusPath))
|
|
74302
74419
|
return null;
|
|
74303
74420
|
try {
|
|
74304
74421
|
return await Bun.file(statusPath).json();
|
|
@@ -74325,7 +74442,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
74325
74442
|
}
|
|
74326
74443
|
}
|
|
74327
74444
|
async function checkLock(workdir) {
|
|
74328
|
-
const lockFile = Bun.file(
|
|
74445
|
+
const lockFile = Bun.file(join30(workdir, "nax.lock"));
|
|
74329
74446
|
if (!await lockFile.exists())
|
|
74330
74447
|
return { lockPresent: false };
|
|
74331
74448
|
try {
|
|
@@ -74343,8 +74460,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
74343
74460
|
const logger = getLogger();
|
|
74344
74461
|
const workdir = options.workdir ?? process.cwd();
|
|
74345
74462
|
const naxSubdir = findProjectDir(workdir);
|
|
74346
|
-
let projectDir = naxSubdir ?
|
|
74347
|
-
if (!projectDir &&
|
|
74463
|
+
let projectDir = naxSubdir ? join30(naxSubdir, "..") : null;
|
|
74464
|
+
if (!projectDir && existsSync22(join30(workdir, ".nax"))) {
|
|
74348
74465
|
projectDir = workdir;
|
|
74349
74466
|
}
|
|
74350
74467
|
if (!projectDir)
|
|
@@ -74355,8 +74472,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
74355
74472
|
if (status2) {
|
|
74356
74473
|
feature = status2.run.feature;
|
|
74357
74474
|
} else {
|
|
74358
|
-
const featuresDir =
|
|
74359
|
-
if (!
|
|
74475
|
+
const featuresDir = join30(projectDir, ".nax", "features");
|
|
74476
|
+
if (!existsSync22(featuresDir))
|
|
74360
74477
|
throw new Error("No features found in project");
|
|
74361
74478
|
const features = readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
74362
74479
|
if (features.length === 0)
|
|
@@ -74365,9 +74482,9 @@ async function diagnoseCommand(options = {}) {
|
|
|
74365
74482
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
74366
74483
|
}
|
|
74367
74484
|
}
|
|
74368
|
-
const featureDir =
|
|
74369
|
-
const prdPath =
|
|
74370
|
-
if (!
|
|
74485
|
+
const featureDir = join30(projectDir, ".nax", "features", feature);
|
|
74486
|
+
const prdPath = join30(featureDir, "prd.json");
|
|
74487
|
+
if (!existsSync22(prdPath))
|
|
74371
74488
|
throw new Error(`Feature not found: ${feature}`);
|
|
74372
74489
|
const prd = await loadPRD(prdPath);
|
|
74373
74490
|
const status = await loadStatusFile2(projectDir);
|
|
@@ -74408,8 +74525,8 @@ init_interaction();
|
|
|
74408
74525
|
// src/cli/generate.ts
|
|
74409
74526
|
init_source();
|
|
74410
74527
|
init_loader();
|
|
74411
|
-
import { existsSync as
|
|
74412
|
-
import { join as
|
|
74528
|
+
import { existsSync as existsSync23 } from "fs";
|
|
74529
|
+
import { join as join31 } from "path";
|
|
74413
74530
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
74414
74531
|
async function generateCommand(options) {
|
|
74415
74532
|
const workdir = options.dir ?? process.cwd();
|
|
@@ -74452,7 +74569,7 @@ async function generateCommand(options) {
|
|
|
74452
74569
|
return;
|
|
74453
74570
|
}
|
|
74454
74571
|
if (options.package) {
|
|
74455
|
-
const packageDir =
|
|
74572
|
+
const packageDir = join31(workdir, options.package);
|
|
74456
74573
|
if (dryRun) {
|
|
74457
74574
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
74458
74575
|
}
|
|
@@ -74472,10 +74589,10 @@ async function generateCommand(options) {
|
|
|
74472
74589
|
process.exit(1);
|
|
74473
74590
|
return;
|
|
74474
74591
|
}
|
|
74475
|
-
const contextPath = options.context ?
|
|
74476
|
-
const outputDir = options.output ?
|
|
74592
|
+
const contextPath = options.context ? join31(workdir, options.context) : join31(workdir, ".nax/context.md");
|
|
74593
|
+
const outputDir = options.output ? join31(workdir, options.output) : workdir;
|
|
74477
74594
|
const autoInject = !options.noAutoInject;
|
|
74478
|
-
if (!
|
|
74595
|
+
if (!existsSync23(contextPath)) {
|
|
74479
74596
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
74480
74597
|
console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
|
|
74481
74598
|
process.exit(1);
|
|
@@ -74577,8 +74694,8 @@ async function generateCommand(options) {
|
|
|
74577
74694
|
}
|
|
74578
74695
|
// src/cli/config-display.ts
|
|
74579
74696
|
init_loader();
|
|
74580
|
-
import { existsSync as
|
|
74581
|
-
import { join as
|
|
74697
|
+
import { existsSync as existsSync25 } from "fs";
|
|
74698
|
+
import { join as join33 } from "path";
|
|
74582
74699
|
|
|
74583
74700
|
// src/cli/config-descriptions.ts
|
|
74584
74701
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -74817,10 +74934,10 @@ function deepEqual(a, b) {
|
|
|
74817
74934
|
// src/cli/config-get.ts
|
|
74818
74935
|
init_defaults();
|
|
74819
74936
|
init_loader();
|
|
74820
|
-
import { existsSync as
|
|
74821
|
-
import { join as
|
|
74937
|
+
import { existsSync as existsSync24 } from "fs";
|
|
74938
|
+
import { join as join32 } from "path";
|
|
74822
74939
|
async function loadConfigFile(path15) {
|
|
74823
|
-
if (!
|
|
74940
|
+
if (!existsSync24(path15))
|
|
74824
74941
|
return null;
|
|
74825
74942
|
try {
|
|
74826
74943
|
return await Bun.file(path15).json();
|
|
@@ -74840,7 +74957,7 @@ async function loadProjectConfig() {
|
|
|
74840
74957
|
const projectDir = findProjectDir();
|
|
74841
74958
|
if (!projectDir)
|
|
74842
74959
|
return null;
|
|
74843
|
-
const projectPath =
|
|
74960
|
+
const projectPath = join32(projectDir, "config.json");
|
|
74844
74961
|
return await loadConfigFile(projectPath);
|
|
74845
74962
|
}
|
|
74846
74963
|
|
|
@@ -74900,14 +75017,14 @@ async function configCommand(config2, options = {}) {
|
|
|
74900
75017
|
function determineConfigSources() {
|
|
74901
75018
|
const globalPath = globalConfigPath();
|
|
74902
75019
|
const projectDir = findProjectDir();
|
|
74903
|
-
const projectPath = projectDir ?
|
|
75020
|
+
const projectPath = projectDir ? join33(projectDir, "config.json") : null;
|
|
74904
75021
|
return {
|
|
74905
75022
|
global: fileExists(globalPath) ? globalPath : null,
|
|
74906
75023
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
74907
75024
|
};
|
|
74908
75025
|
}
|
|
74909
75026
|
function fileExists(path15) {
|
|
74910
|
-
return
|
|
75027
|
+
return existsSync25(path15);
|
|
74911
75028
|
}
|
|
74912
75029
|
function displayConfigWithDescriptions(obj, path15, sources, indent = 0) {
|
|
74913
75030
|
const indentStr = " ".repeat(indent);
|
|
@@ -75049,15 +75166,15 @@ init_paths();
|
|
|
75049
75166
|
init_profile();
|
|
75050
75167
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
75051
75168
|
import { readdirSync as readdirSync7 } from "fs";
|
|
75052
|
-
import { join as
|
|
75169
|
+
import { join as join34 } from "path";
|
|
75053
75170
|
var _profileCLIDeps = {
|
|
75054
75171
|
env: process.env
|
|
75055
75172
|
};
|
|
75056
75173
|
var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
|
|
75057
75174
|
var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
|
|
75058
75175
|
async function profileListCommand(startDir) {
|
|
75059
|
-
const globalProfilesDir =
|
|
75060
|
-
const projectProfilesDir =
|
|
75176
|
+
const globalProfilesDir = join34(globalConfigDir(), "profiles");
|
|
75177
|
+
const projectProfilesDir = join34(projectConfigDir(startDir), "profiles");
|
|
75061
75178
|
const globalProfiles = scanProfileDir(globalProfilesDir);
|
|
75062
75179
|
const projectProfiles = scanProfileDir(projectProfilesDir);
|
|
75063
75180
|
const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
@@ -75116,7 +75233,7 @@ function maskProfileValues(obj) {
|
|
|
75116
75233
|
return result;
|
|
75117
75234
|
}
|
|
75118
75235
|
async function profileUseCommand(profileName, startDir) {
|
|
75119
|
-
const configPath =
|
|
75236
|
+
const configPath = join34(projectConfigDir(startDir), "config.json");
|
|
75120
75237
|
const configFile = Bun.file(configPath);
|
|
75121
75238
|
let existing = {};
|
|
75122
75239
|
if (await configFile.exists()) {
|
|
@@ -75135,8 +75252,8 @@ async function profileCurrentCommand(startDir) {
|
|
|
75135
75252
|
return resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
75136
75253
|
}
|
|
75137
75254
|
async function profileCreateCommand(profileName, startDir) {
|
|
75138
|
-
const profilesDir =
|
|
75139
|
-
const profilePath =
|
|
75255
|
+
const profilesDir = join34(projectConfigDir(startDir), "profiles");
|
|
75256
|
+
const profilePath = join34(profilesDir, `${profileName}.json`);
|
|
75140
75257
|
const profileFile = Bun.file(profilePath);
|
|
75141
75258
|
if (await profileFile.exists()) {
|
|
75142
75259
|
throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
|
|
@@ -75201,25 +75318,25 @@ async function diagnose(options) {
|
|
|
75201
75318
|
}
|
|
75202
75319
|
|
|
75203
75320
|
// src/commands/logs.ts
|
|
75204
|
-
import { existsSync as
|
|
75205
|
-
import { join as
|
|
75321
|
+
import { existsSync as existsSync27 } from "fs";
|
|
75322
|
+
import { join as join38 } from "path";
|
|
75206
75323
|
|
|
75207
75324
|
// src/commands/logs-formatter.ts
|
|
75208
75325
|
init_source();
|
|
75209
75326
|
init_formatter();
|
|
75210
75327
|
import { readdirSync as readdirSync9 } from "fs";
|
|
75211
|
-
import { join as
|
|
75328
|
+
import { join as join37 } from "path";
|
|
75212
75329
|
|
|
75213
75330
|
// src/commands/logs-reader.ts
|
|
75214
|
-
import { existsSync as
|
|
75331
|
+
import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
|
|
75215
75332
|
import { readdir as readdir3 } from "fs/promises";
|
|
75216
|
-
import { join as
|
|
75333
|
+
import { join as join36 } from "path";
|
|
75217
75334
|
|
|
75218
75335
|
// src/utils/paths.ts
|
|
75219
75336
|
import { homedir as homedir4 } from "os";
|
|
75220
|
-
import { join as
|
|
75337
|
+
import { join as join35 } from "path";
|
|
75221
75338
|
function getRunsDir() {
|
|
75222
|
-
return process.env.NAX_RUNS_DIR ??
|
|
75339
|
+
return process.env.NAX_RUNS_DIR ?? join35(homedir4(), ".nax", "runs");
|
|
75223
75340
|
}
|
|
75224
75341
|
|
|
75225
75342
|
// src/commands/logs-reader.ts
|
|
@@ -75236,7 +75353,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
75236
75353
|
}
|
|
75237
75354
|
let matched = null;
|
|
75238
75355
|
for (const entry of entries) {
|
|
75239
|
-
const metaPath =
|
|
75356
|
+
const metaPath = join36(runsDir, entry, "meta.json");
|
|
75240
75357
|
try {
|
|
75241
75358
|
const meta3 = await Bun.file(metaPath).json();
|
|
75242
75359
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -75248,7 +75365,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
75248
75365
|
if (!matched) {
|
|
75249
75366
|
throw new Error(`Run not found in registry: ${runId}`);
|
|
75250
75367
|
}
|
|
75251
|
-
if (!
|
|
75368
|
+
if (!existsSync26(matched.eventsDir)) {
|
|
75252
75369
|
console.log(`Log directory unavailable for run: ${runId}`);
|
|
75253
75370
|
return null;
|
|
75254
75371
|
}
|
|
@@ -75258,14 +75375,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
75258
75375
|
return null;
|
|
75259
75376
|
}
|
|
75260
75377
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
75261
|
-
return
|
|
75378
|
+
return join36(matched.eventsDir, specificFile ?? files[0]);
|
|
75262
75379
|
}
|
|
75263
75380
|
async function selectRunFile(runsDir) {
|
|
75264
75381
|
const files = readdirSync8(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
75265
75382
|
if (files.length === 0) {
|
|
75266
75383
|
return null;
|
|
75267
75384
|
}
|
|
75268
|
-
return
|
|
75385
|
+
return join36(runsDir, files[0]);
|
|
75269
75386
|
}
|
|
75270
75387
|
async function extractRunSummary(filePath) {
|
|
75271
75388
|
const file3 = Bun.file(filePath);
|
|
@@ -75350,7 +75467,7 @@ Runs:
|
|
|
75350
75467
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
75351
75468
|
console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
75352
75469
|
for (const file3 of files) {
|
|
75353
|
-
const filePath =
|
|
75470
|
+
const filePath = join37(runsDir, file3);
|
|
75354
75471
|
const summary = await extractRunSummary(filePath);
|
|
75355
75472
|
const timestamp = file3.replace(".jsonl", "");
|
|
75356
75473
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -75464,7 +75581,7 @@ async function logsCommand(options) {
|
|
|
75464
75581
|
return;
|
|
75465
75582
|
}
|
|
75466
75583
|
const resolved = resolveProject({ dir: options.dir });
|
|
75467
|
-
const naxDir =
|
|
75584
|
+
const naxDir = join38(resolved.projectDir, ".nax");
|
|
75468
75585
|
const configPath = resolved.configPath;
|
|
75469
75586
|
const configFile = Bun.file(configPath);
|
|
75470
75587
|
const config2 = await configFile.json();
|
|
@@ -75472,9 +75589,9 @@ async function logsCommand(options) {
|
|
|
75472
75589
|
if (!featureName) {
|
|
75473
75590
|
throw new Error("No feature specified in config.json");
|
|
75474
75591
|
}
|
|
75475
|
-
const featureDir =
|
|
75476
|
-
const runsDir =
|
|
75477
|
-
if (!
|
|
75592
|
+
const featureDir = join38(naxDir, "features", featureName);
|
|
75593
|
+
const runsDir = join38(featureDir, "runs");
|
|
75594
|
+
if (!existsSync27(runsDir)) {
|
|
75478
75595
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
75479
75596
|
}
|
|
75480
75597
|
if (options.list) {
|
|
@@ -75497,8 +75614,8 @@ init_source();
|
|
|
75497
75614
|
init_config();
|
|
75498
75615
|
init_prd();
|
|
75499
75616
|
init_precheck();
|
|
75500
|
-
import { existsSync as
|
|
75501
|
-
import { join as
|
|
75617
|
+
import { existsSync as existsSync28 } from "fs";
|
|
75618
|
+
import { join as join39 } from "path";
|
|
75502
75619
|
async function precheckCommand(options) {
|
|
75503
75620
|
const resolved = resolveProject({
|
|
75504
75621
|
dir: options.dir,
|
|
@@ -75520,14 +75637,14 @@ async function precheckCommand(options) {
|
|
|
75520
75637
|
process.exit(1);
|
|
75521
75638
|
}
|
|
75522
75639
|
}
|
|
75523
|
-
const naxDir =
|
|
75524
|
-
const featureDir =
|
|
75525
|
-
const prdPath =
|
|
75526
|
-
if (!
|
|
75640
|
+
const naxDir = join39(resolved.projectDir, ".nax");
|
|
75641
|
+
const featureDir = join39(naxDir, "features", featureName);
|
|
75642
|
+
const prdPath = join39(featureDir, "prd.json");
|
|
75643
|
+
if (!existsSync28(featureDir)) {
|
|
75527
75644
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
75528
75645
|
process.exit(1);
|
|
75529
75646
|
}
|
|
75530
|
-
if (!
|
|
75647
|
+
if (!existsSync28(prdPath)) {
|
|
75531
75648
|
console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
|
|
75532
75649
|
console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
|
|
75533
75650
|
process.exit(EXIT_CODES.INVALID_PRD);
|
|
@@ -75544,7 +75661,7 @@ async function precheckCommand(options) {
|
|
|
75544
75661
|
// src/commands/runs.ts
|
|
75545
75662
|
init_source();
|
|
75546
75663
|
import { readdir as readdir4 } from "fs/promises";
|
|
75547
|
-
import { join as
|
|
75664
|
+
import { join as join40 } from "path";
|
|
75548
75665
|
var DEFAULT_LIMIT = 20;
|
|
75549
75666
|
var _runsCmdDeps = {
|
|
75550
75667
|
getRunsDir
|
|
@@ -75599,7 +75716,7 @@ async function runsCommand(options = {}) {
|
|
|
75599
75716
|
}
|
|
75600
75717
|
const rows = [];
|
|
75601
75718
|
for (const entry of entries) {
|
|
75602
|
-
const metaPath =
|
|
75719
|
+
const metaPath = join40(runsDir, entry, "meta.json");
|
|
75603
75720
|
let meta3;
|
|
75604
75721
|
try {
|
|
75605
75722
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -75676,7 +75793,7 @@ async function runsCommand(options = {}) {
|
|
|
75676
75793
|
|
|
75677
75794
|
// src/commands/unlock.ts
|
|
75678
75795
|
init_source();
|
|
75679
|
-
import { join as
|
|
75796
|
+
import { join as join41 } from "path";
|
|
75680
75797
|
function isProcessAlive3(pid) {
|
|
75681
75798
|
try {
|
|
75682
75799
|
process.kill(pid, 0);
|
|
@@ -75691,7 +75808,7 @@ function formatLockAge(ageMs) {
|
|
|
75691
75808
|
}
|
|
75692
75809
|
async function unlockCommand(options) {
|
|
75693
75810
|
const workdir = options.dir ?? process.cwd();
|
|
75694
|
-
const lockPath =
|
|
75811
|
+
const lockPath = join41(workdir, "nax.lock");
|
|
75695
75812
|
const lockFile = Bun.file(lockPath);
|
|
75696
75813
|
const exists = await lockFile.exists();
|
|
75697
75814
|
if (!exists) {
|
|
@@ -75767,11 +75884,9 @@ async function runCompletionPhase(options) {
|
|
|
75767
75884
|
const regressionAlreadyPassed = postRunStatus?.regression?.status === "passed";
|
|
75768
75885
|
if (acceptanceAlreadyPassed && regressionAlreadyPassed) {
|
|
75769
75886
|
logger?.info("execution", "Post-run phases already passed \u2014 skipping acceptance and regression");
|
|
75770
|
-
console.info("Post-run phases already passed \u2014 skipping acceptance and regression");
|
|
75771
75887
|
} else {
|
|
75772
75888
|
if (acceptanceAlreadyPassed) {
|
|
75773
75889
|
logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
|
|
75774
|
-
console.info("Acceptance already passed \u2014 skipping acceptance phase");
|
|
75775
75890
|
} else if (options.config.acceptance.enabled && isComplete(options.prd)) {
|
|
75776
75891
|
options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
|
|
75777
75892
|
const acceptanceResult = await _runnerCompletionDeps.runAcceptanceLoop({
|
|
@@ -75970,6 +76085,7 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
75970
76085
|
startTime: options.startTime,
|
|
75971
76086
|
parallelCount: options.parallel,
|
|
75972
76087
|
agentGetFn: options.agentGetFn,
|
|
76088
|
+
onBeforeStory: options.onBeforeStory,
|
|
75973
76089
|
pidRegistry: options.pidRegistry,
|
|
75974
76090
|
interactionChain: options.interactionChain,
|
|
75975
76091
|
batchPlan
|
|
@@ -76094,6 +76210,7 @@ async function run(options) {
|
|
|
76094
76210
|
headless,
|
|
76095
76211
|
parallel,
|
|
76096
76212
|
agentGetFn,
|
|
76213
|
+
onBeforeStory: () => registry2.resetStoryState(),
|
|
76097
76214
|
pidRegistry,
|
|
76098
76215
|
interactionChain
|
|
76099
76216
|
}, prd, pluginRegistry);
|
|
@@ -81677,8 +81794,8 @@ class Ink {
|
|
|
81677
81794
|
}
|
|
81678
81795
|
}
|
|
81679
81796
|
async waitUntilExit() {
|
|
81680
|
-
this.exitPromise ||= new Promise((
|
|
81681
|
-
this.resolveExitPromise =
|
|
81797
|
+
this.exitPromise ||= new Promise((resolve11, reject2) => {
|
|
81798
|
+
this.resolveExitPromise = resolve11;
|
|
81682
81799
|
this.rejectExitPromise = reject2;
|
|
81683
81800
|
});
|
|
81684
81801
|
if (!this.beforeExitHandler) {
|
|
@@ -83491,7 +83608,7 @@ async function promptForConfirmation(question) {
|
|
|
83491
83608
|
if (!process.stdin.isTTY) {
|
|
83492
83609
|
return true;
|
|
83493
83610
|
}
|
|
83494
|
-
return new Promise((
|
|
83611
|
+
return new Promise((resolve11) => {
|
|
83495
83612
|
process.stdout.write(source_default.bold(`${question} [Y/n] `));
|
|
83496
83613
|
process.stdin.setRawMode(true);
|
|
83497
83614
|
process.stdin.resume();
|
|
@@ -83504,9 +83621,9 @@ async function promptForConfirmation(question) {
|
|
|
83504
83621
|
process.stdout.write(`
|
|
83505
83622
|
`);
|
|
83506
83623
|
if (answer === "n") {
|
|
83507
|
-
|
|
83624
|
+
resolve11(false);
|
|
83508
83625
|
} else {
|
|
83509
|
-
|
|
83626
|
+
resolve11(true);
|
|
83510
83627
|
}
|
|
83511
83628
|
};
|
|
83512
83629
|
process.stdin.on("data", handler);
|
|
@@ -83535,15 +83652,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
83535
83652
|
}
|
|
83536
83653
|
return;
|
|
83537
83654
|
}
|
|
83538
|
-
const naxDir =
|
|
83539
|
-
if (
|
|
83655
|
+
const naxDir = join53(workdir, ".nax");
|
|
83656
|
+
if (existsSync31(naxDir) && !options.force) {
|
|
83540
83657
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
83541
83658
|
return;
|
|
83542
83659
|
}
|
|
83543
|
-
mkdirSync7(
|
|
83544
|
-
mkdirSync7(
|
|
83545
|
-
await Bun.write(
|
|
83546
|
-
await Bun.write(
|
|
83660
|
+
mkdirSync7(join53(naxDir, "features"), { recursive: true });
|
|
83661
|
+
mkdirSync7(join53(naxDir, "hooks"), { recursive: true });
|
|
83662
|
+
await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
83663
|
+
await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
|
|
83547
83664
|
hooks: {
|
|
83548
83665
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
83549
83666
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -83551,12 +83668,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
83551
83668
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
83552
83669
|
}
|
|
83553
83670
|
}, null, 2));
|
|
83554
|
-
await Bun.write(
|
|
83671
|
+
await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
|
|
83555
83672
|
*.tmp
|
|
83556
83673
|
.paused.json
|
|
83557
83674
|
.nax-verifier-verdict.json
|
|
83558
83675
|
`);
|
|
83559
|
-
await Bun.write(
|
|
83676
|
+
await Bun.write(join53(naxDir, "context.md"), `# Project Context
|
|
83560
83677
|
|
|
83561
83678
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
83562
83679
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -83653,7 +83770,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83653
83770
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
83654
83771
|
process.exit(1);
|
|
83655
83772
|
}
|
|
83656
|
-
if (options.from && !
|
|
83773
|
+
if (options.from && !existsSync31(options.from)) {
|
|
83657
83774
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
83658
83775
|
process.exit(1);
|
|
83659
83776
|
}
|
|
@@ -83686,10 +83803,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83686
83803
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
83687
83804
|
process.exit(1);
|
|
83688
83805
|
}
|
|
83689
|
-
const featureDir =
|
|
83690
|
-
const prdPath =
|
|
83806
|
+
const featureDir = join53(naxDir, "features", options.feature);
|
|
83807
|
+
const prdPath = join53(featureDir, "prd.json");
|
|
83691
83808
|
if (options.plan && options.from) {
|
|
83692
|
-
if (
|
|
83809
|
+
if (existsSync31(prdPath) && !options.force) {
|
|
83693
83810
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
83694
83811
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
83695
83812
|
process.exit(1);
|
|
@@ -83709,10 +83826,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83709
83826
|
}
|
|
83710
83827
|
}
|
|
83711
83828
|
try {
|
|
83712
|
-
const planLogDir =
|
|
83829
|
+
const planLogDir = join53(featureDir, "plan");
|
|
83713
83830
|
mkdirSync7(planLogDir, { recursive: true });
|
|
83714
83831
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
83715
|
-
const planLogPath =
|
|
83832
|
+
const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
|
|
83716
83833
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
83717
83834
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
83718
83835
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -83751,15 +83868,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83751
83868
|
process.exit(1);
|
|
83752
83869
|
}
|
|
83753
83870
|
}
|
|
83754
|
-
if (!
|
|
83871
|
+
if (!existsSync31(prdPath)) {
|
|
83755
83872
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
83756
83873
|
process.exit(1);
|
|
83757
83874
|
}
|
|
83758
83875
|
resetLogger();
|
|
83759
|
-
const runsDir =
|
|
83876
|
+
const runsDir = join53(featureDir, "runs");
|
|
83760
83877
|
mkdirSync7(runsDir, { recursive: true });
|
|
83761
83878
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
83762
|
-
const logFilePath =
|
|
83879
|
+
const logFilePath = join53(runsDir, `${runId}.jsonl`);
|
|
83763
83880
|
const isTTY = process.stdout.isTTY ?? false;
|
|
83764
83881
|
const headlessFlag = options.headless ?? false;
|
|
83765
83882
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -83775,7 +83892,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83775
83892
|
config2.autoMode.defaultAgent = options.agent;
|
|
83776
83893
|
}
|
|
83777
83894
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
83778
|
-
const globalNaxDir =
|
|
83895
|
+
const globalNaxDir = join53(homedir8(), ".nax");
|
|
83779
83896
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
83780
83897
|
const eventEmitter = new PipelineEventEmitter;
|
|
83781
83898
|
let tuiInstance;
|
|
@@ -83798,7 +83915,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83798
83915
|
} else {
|
|
83799
83916
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
83800
83917
|
}
|
|
83801
|
-
const statusFilePath =
|
|
83918
|
+
const statusFilePath = join53(workdir, ".nax", "status.json");
|
|
83802
83919
|
let parallel;
|
|
83803
83920
|
if (options.parallel !== undefined) {
|
|
83804
83921
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -83824,9 +83941,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83824
83941
|
headless: useHeadless,
|
|
83825
83942
|
skipPrecheck: options.skipPrecheck ?? false
|
|
83826
83943
|
});
|
|
83827
|
-
const latestSymlink =
|
|
83944
|
+
const latestSymlink = join53(runsDir, "latest.jsonl");
|
|
83828
83945
|
try {
|
|
83829
|
-
if (
|
|
83946
|
+
if (existsSync31(latestSymlink)) {
|
|
83830
83947
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
83831
83948
|
}
|
|
83832
83949
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -83862,9 +83979,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
83862
83979
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
83863
83980
|
process.exit(1);
|
|
83864
83981
|
}
|
|
83865
|
-
const featureDir =
|
|
83982
|
+
const featureDir = join53(naxDir, "features", name);
|
|
83866
83983
|
mkdirSync7(featureDir, { recursive: true });
|
|
83867
|
-
await Bun.write(
|
|
83984
|
+
await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
|
|
83868
83985
|
|
|
83869
83986
|
## Overview
|
|
83870
83987
|
|
|
@@ -83897,7 +84014,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
83897
84014
|
|
|
83898
84015
|
<!-- What this feature explicitly does NOT cover. -->
|
|
83899
84016
|
`);
|
|
83900
|
-
await Bun.write(
|
|
84017
|
+
await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
|
|
83901
84018
|
|
|
83902
84019
|
Created: ${new Date().toISOString()}
|
|
83903
84020
|
|
|
@@ -83923,8 +84040,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
83923
84040
|
console.error(source_default.red("nax not initialized."));
|
|
83924
84041
|
process.exit(1);
|
|
83925
84042
|
}
|
|
83926
|
-
const featuresDir =
|
|
83927
|
-
if (!
|
|
84043
|
+
const featuresDir = join53(naxDir, "features");
|
|
84044
|
+
if (!existsSync31(featuresDir)) {
|
|
83928
84045
|
console.log(source_default.dim("No features yet."));
|
|
83929
84046
|
return;
|
|
83930
84047
|
}
|
|
@@ -83938,8 +84055,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
83938
84055
|
Features:
|
|
83939
84056
|
`));
|
|
83940
84057
|
for (const name of entries) {
|
|
83941
|
-
const prdPath =
|
|
83942
|
-
if (
|
|
84058
|
+
const prdPath = join53(featuresDir, name, "prd.json");
|
|
84059
|
+
if (existsSync31(prdPath)) {
|
|
83943
84060
|
const prd = await loadPRD(prdPath);
|
|
83944
84061
|
const c = countStories(prd);
|
|
83945
84062
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -83973,10 +84090,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
83973
84090
|
cliOverrides.profile = options.profile;
|
|
83974
84091
|
}
|
|
83975
84092
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
83976
|
-
const featureLogDir =
|
|
84093
|
+
const featureLogDir = join53(naxDir, "features", options.feature, "plan");
|
|
83977
84094
|
mkdirSync7(featureLogDir, { recursive: true });
|
|
83978
84095
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
83979
|
-
const planLogPath =
|
|
84096
|
+
const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
|
|
83980
84097
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
83981
84098
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
83982
84099
|
try {
|