@nathapp/nax 0.58.5 → 0.59.1
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 +1402 -887
- 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);
|
|
@@ -18267,7 +18269,16 @@ var init_schemas3 = __esm(() => {
|
|
|
18267
18269
|
modelTier: ModelTierSchema.default("balanced"),
|
|
18268
18270
|
rules: exports_external.array(exports_external.string()).default([]),
|
|
18269
18271
|
timeoutMs: exports_external.number().int().positive().default(600000),
|
|
18270
|
-
excludePatterns: exports_external.array(exports_external.string()).default([
|
|
18272
|
+
excludePatterns: exports_external.array(exports_external.string()).default([
|
|
18273
|
+
":!test/",
|
|
18274
|
+
":!tests/",
|
|
18275
|
+
":!*_test.go",
|
|
18276
|
+
":!*.test.ts",
|
|
18277
|
+
":!*.spec.ts",
|
|
18278
|
+
":!**/__tests__/",
|
|
18279
|
+
":!.nax/",
|
|
18280
|
+
":!.nax-pids"
|
|
18281
|
+
])
|
|
18271
18282
|
});
|
|
18272
18283
|
ReviewDialogueConfigSchema = exports_external.object({
|
|
18273
18284
|
enabled: exports_external.boolean().default(false),
|
|
@@ -18612,7 +18623,16 @@ var init_schemas3 = __esm(() => {
|
|
|
18612
18623
|
modelTier: "balanced",
|
|
18613
18624
|
rules: [],
|
|
18614
18625
|
timeoutMs: 600000,
|
|
18615
|
-
excludePatterns: [
|
|
18626
|
+
excludePatterns: [
|
|
18627
|
+
":!test/",
|
|
18628
|
+
":!tests/",
|
|
18629
|
+
":!*_test.go",
|
|
18630
|
+
":!*.test.ts",
|
|
18631
|
+
":!*.spec.ts",
|
|
18632
|
+
":!**/__tests__/",
|
|
18633
|
+
":!.nax/",
|
|
18634
|
+
":!.nax-pids"
|
|
18635
|
+
]
|
|
18616
18636
|
},
|
|
18617
18637
|
dialogue: {
|
|
18618
18638
|
enabled: false,
|
|
@@ -19186,6 +19206,7 @@ class AcpAgentAdapter {
|
|
|
19186
19206
|
prompt: currentPrompt,
|
|
19187
19207
|
sessionName,
|
|
19188
19208
|
workdir: options.workdir,
|
|
19209
|
+
projectDir: options.projectDir,
|
|
19189
19210
|
auditDir: _runAuditConfig.agent.promptAudit.dir,
|
|
19190
19211
|
storyId: options.storyId,
|
|
19191
19212
|
featureName: options.featureName,
|
|
@@ -19239,11 +19260,15 @@ class AcpAgentAdapter {
|
|
|
19239
19260
|
}
|
|
19240
19261
|
runState.succeeded = !timedOut && lastResponse?.stopReason === "end_turn";
|
|
19241
19262
|
} finally {
|
|
19263
|
+
const isSessionBroken = !runState.succeeded && lastResponse?.stopReason === "error";
|
|
19242
19264
|
if (runState.succeeded && !options.keepSessionOpen) {
|
|
19243
19265
|
await closeAcpSession(session);
|
|
19244
19266
|
if (options.featureName && options.storyId) {
|
|
19245
19267
|
await clearAcpSession(options.workdir, options.featureName, options.storyId, options.sessionRole);
|
|
19246
19268
|
}
|
|
19269
|
+
} else if (isSessionBroken) {
|
|
19270
|
+
getSafeLogger()?.debug("acp-adapter", "Closing broken session for retry", { sessionName });
|
|
19271
|
+
await closeAcpSession(session);
|
|
19247
19272
|
} else if (!runState.succeeded) {
|
|
19248
19273
|
getSafeLogger()?.info("acp-adapter", "Keeping session open for retry", { sessionName });
|
|
19249
19274
|
} else {
|
|
@@ -19492,7 +19517,7 @@ class AcpAgentAdapter {
|
|
|
19492
19517
|
this.markUnavailable(this.name);
|
|
19493
19518
|
throw new Error("[acp-adapter] plan() returned empty spec content");
|
|
19494
19519
|
}
|
|
19495
|
-
return { specContent };
|
|
19520
|
+
return { specContent, costUsd: result.estimatedCost };
|
|
19496
19521
|
}
|
|
19497
19522
|
async decompose(options) {
|
|
19498
19523
|
const model = options.modelDef?.model;
|
|
@@ -19521,6 +19546,9 @@ class AcpAgentAdapter {
|
|
|
19521
19546
|
}
|
|
19522
19547
|
return { stories };
|
|
19523
19548
|
}
|
|
19549
|
+
clearUnavailableAgents() {
|
|
19550
|
+
this._unavailableAgents.clear();
|
|
19551
|
+
}
|
|
19524
19552
|
markUnavailable(agentName) {
|
|
19525
19553
|
this._unavailableAgents.add(agentName);
|
|
19526
19554
|
}
|
|
@@ -20089,6 +20117,75 @@ var init_interactive = __esm(() => {
|
|
|
20089
20117
|
init_execution();
|
|
20090
20118
|
});
|
|
20091
20119
|
|
|
20120
|
+
// src/config/path-security.ts
|
|
20121
|
+
import { existsSync as existsSync2, lstatSync, realpathSync } from "fs";
|
|
20122
|
+
import { basename, isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
|
|
20123
|
+
function validateDirectory(dirPath, baseDir) {
|
|
20124
|
+
const resolved = resolve2(dirPath);
|
|
20125
|
+
if (!existsSync2(resolved)) {
|
|
20126
|
+
throw new Error(`Directory does not exist: ${dirPath}`);
|
|
20127
|
+
}
|
|
20128
|
+
let realPath;
|
|
20129
|
+
try {
|
|
20130
|
+
realPath = realpathSync(resolved);
|
|
20131
|
+
} catch (error48) {
|
|
20132
|
+
throw new Error(`Failed to resolve path: ${dirPath} (${error48.message})`);
|
|
20133
|
+
}
|
|
20134
|
+
try {
|
|
20135
|
+
const stats = lstatSync(realPath);
|
|
20136
|
+
if (!stats.isDirectory()) {
|
|
20137
|
+
throw new Error(`Not a directory: ${dirPath}`);
|
|
20138
|
+
}
|
|
20139
|
+
} catch (error48) {
|
|
20140
|
+
throw new Error(`Failed to stat path: ${dirPath} (${error48.message})`);
|
|
20141
|
+
}
|
|
20142
|
+
if (baseDir) {
|
|
20143
|
+
const resolvedBase = resolve2(baseDir);
|
|
20144
|
+
const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
20145
|
+
if (!isWithinDirectory(realPath, realBase)) {
|
|
20146
|
+
throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
|
|
20147
|
+
}
|
|
20148
|
+
}
|
|
20149
|
+
return realPath;
|
|
20150
|
+
}
|
|
20151
|
+
function isWithinDirectory(targetPath, basePath) {
|
|
20152
|
+
const normalizedTarget = normalize(targetPath);
|
|
20153
|
+
const normalizedBase = normalize(basePath);
|
|
20154
|
+
if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
|
|
20155
|
+
return false;
|
|
20156
|
+
}
|
|
20157
|
+
const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
|
|
20158
|
+
const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
|
|
20159
|
+
return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
|
|
20160
|
+
}
|
|
20161
|
+
function validateFilePath(filePath, baseDir) {
|
|
20162
|
+
const resolved = resolve2(filePath);
|
|
20163
|
+
let realPath;
|
|
20164
|
+
try {
|
|
20165
|
+
if (!existsSync2(resolved)) {
|
|
20166
|
+
const parent = resolve2(resolved, "..");
|
|
20167
|
+
if (existsSync2(parent)) {
|
|
20168
|
+
const realParent = realpathSync(parent);
|
|
20169
|
+
realPath = resolve2(realParent, basename(resolved));
|
|
20170
|
+
} else {
|
|
20171
|
+
realPath = resolved;
|
|
20172
|
+
}
|
|
20173
|
+
} else {
|
|
20174
|
+
realPath = realpathSync(resolved);
|
|
20175
|
+
}
|
|
20176
|
+
} catch (error48) {
|
|
20177
|
+
throw new Error(`Failed to resolve path: ${filePath} (${error48.message})`);
|
|
20178
|
+
}
|
|
20179
|
+
const resolvedBase = resolve2(baseDir);
|
|
20180
|
+
const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
20181
|
+
if (!isWithinDirectory(realPath, realBase)) {
|
|
20182
|
+
throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
|
|
20183
|
+
}
|
|
20184
|
+
return realPath;
|
|
20185
|
+
}
|
|
20186
|
+
var MAX_DIRECTORY_DEPTH = 10;
|
|
20187
|
+
var init_path_security = () => {};
|
|
20188
|
+
|
|
20092
20189
|
// src/agents/shared/model-resolution.ts
|
|
20093
20190
|
var exports_model_resolution = {};
|
|
20094
20191
|
__export(exports_model_resolution, {
|
|
@@ -20114,7 +20211,7 @@ var init_model_resolution = __esm(() => {
|
|
|
20114
20211
|
// src/agents/claude/plan.ts
|
|
20115
20212
|
import { mkdtempSync, rmSync } from "fs";
|
|
20116
20213
|
import { tmpdir } from "os";
|
|
20117
|
-
import { join as join3 } from "path";
|
|
20214
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
20118
20215
|
function buildPlanCommand(binary, options) {
|
|
20119
20216
|
const cmd = [binary, "--permission-mode", "plan"];
|
|
20120
20217
|
let modelDef = options.modelDef;
|
|
@@ -20134,17 +20231,12 @@ function buildPlanCommand(binary, options) {
|
|
|
20134
20231
|
|
|
20135
20232
|
${options.prompt}`;
|
|
20136
20233
|
}
|
|
20137
|
-
if (options.
|
|
20138
|
-
|
|
20139
|
-
const inputContent = __require("fs").readFileSync(__require("path").resolve(options.workdir, options.inputFile), "utf-8");
|
|
20140
|
-
fullPrompt = `${fullPrompt}
|
|
20234
|
+
if (options.resolvedInputContent) {
|
|
20235
|
+
fullPrompt = `${fullPrompt}
|
|
20141
20236
|
|
|
20142
20237
|
## Input Requirements
|
|
20143
20238
|
|
|
20144
|
-
${
|
|
20145
|
-
} catch (error48) {
|
|
20146
|
-
throw new Error(`Failed to read input file ${options.inputFile}: ${error48.message}`);
|
|
20147
|
-
}
|
|
20239
|
+
${options.resolvedInputContent}`;
|
|
20148
20240
|
}
|
|
20149
20241
|
if (!options.interactive) {
|
|
20150
20242
|
cmd.push("-p", fullPrompt);
|
|
@@ -20155,7 +20247,13 @@ ${inputContent}`;
|
|
|
20155
20247
|
}
|
|
20156
20248
|
async function runPlan(binary, options, pidRegistry) {
|
|
20157
20249
|
const { resolveBalancedModelDef: resolveBalancedModelDef2 } = await Promise.resolve().then(() => (init_model_resolution(), exports_model_resolution));
|
|
20158
|
-
|
|
20250
|
+
let resolvedOptions = options;
|
|
20251
|
+
if (options.inputFile) {
|
|
20252
|
+
const inputPath = validateFilePath(resolve3(options.workdir, options.inputFile), options.workdir);
|
|
20253
|
+
const resolvedInputContent = await Bun.file(inputPath).text();
|
|
20254
|
+
resolvedOptions = { ...options, resolvedInputContent };
|
|
20255
|
+
}
|
|
20256
|
+
const cmd = buildPlanCommand(binary, resolvedOptions);
|
|
20159
20257
|
let modelDef = options.modelDef;
|
|
20160
20258
|
if (!modelDef) {
|
|
20161
20259
|
if (!options.config) {
|
|
@@ -20224,6 +20322,7 @@ async function runPlan(binary, options, pidRegistry) {
|
|
|
20224
20322
|
}
|
|
20225
20323
|
}
|
|
20226
20324
|
var init_plan = __esm(() => {
|
|
20325
|
+
init_path_security();
|
|
20227
20326
|
init_timeout_handler();
|
|
20228
20327
|
init_logger2();
|
|
20229
20328
|
init_env();
|
|
@@ -20376,8 +20475,8 @@ class ClaudeCodeAdapter {
|
|
|
20376
20475
|
let stdoutTimeoutId;
|
|
20377
20476
|
const stdout = await Promise.race([
|
|
20378
20477
|
new Response(proc.stdout).text(),
|
|
20379
|
-
new Promise((
|
|
20380
|
-
stdoutTimeoutId = setTimeout(() =>
|
|
20478
|
+
new Promise((resolve4) => {
|
|
20479
|
+
stdoutTimeoutId = setTimeout(() => resolve4(""), 5000);
|
|
20381
20480
|
})
|
|
20382
20481
|
]);
|
|
20383
20482
|
clearTimeout(stdoutTimeoutId);
|
|
@@ -20725,7 +20824,12 @@ function createAgentRegistry(config2) {
|
|
|
20725
20824
|
installed: await agent.isInstalled()
|
|
20726
20825
|
})));
|
|
20727
20826
|
}
|
|
20728
|
-
|
|
20827
|
+
function resetStoryState() {
|
|
20828
|
+
for (const adapter of acpCache.values()) {
|
|
20829
|
+
adapter.clearUnavailableAgents();
|
|
20830
|
+
}
|
|
20831
|
+
}
|
|
20832
|
+
return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol, resetStoryState };
|
|
20729
20833
|
}
|
|
20730
20834
|
var ALL_AGENTS;
|
|
20731
20835
|
var init_registry = __esm(() => {
|
|
@@ -20745,75 +20849,6 @@ var init_registry = __esm(() => {
|
|
|
20745
20849
|
];
|
|
20746
20850
|
});
|
|
20747
20851
|
|
|
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
20852
|
// src/utils/json-file.ts
|
|
20818
20853
|
import { existsSync as existsSync6 } from "fs";
|
|
20819
20854
|
async function loadJsonFile(path, context = "json-file") {
|
|
@@ -21008,7 +21043,7 @@ function isPlainObject2(value) {
|
|
|
21008
21043
|
|
|
21009
21044
|
// src/config/paths.ts
|
|
21010
21045
|
import { homedir as homedir2 } from "os";
|
|
21011
|
-
import { join as join7, resolve as
|
|
21046
|
+
import { join as join7, resolve as resolve4 } from "path";
|
|
21012
21047
|
function globalConfigDir() {
|
|
21013
21048
|
const override = process.env[GLOBAL_CONFIG_DIR_ENV];
|
|
21014
21049
|
if (override)
|
|
@@ -21016,7 +21051,7 @@ function globalConfigDir() {
|
|
|
21016
21051
|
return join7(homedir2(), ".nax");
|
|
21017
21052
|
}
|
|
21018
21053
|
function projectConfigDir(projectRoot) {
|
|
21019
|
-
return join7(
|
|
21054
|
+
return join7(resolve4(projectRoot), PROJECT_NAX_DIR);
|
|
21020
21055
|
}
|
|
21021
21056
|
var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", PROJECT_NAX_DIR = ".nax";
|
|
21022
21057
|
var init_paths = () => {};
|
|
@@ -21170,12 +21205,12 @@ var init_profile = __esm(() => {
|
|
|
21170
21205
|
|
|
21171
21206
|
// src/config/loader.ts
|
|
21172
21207
|
import { existsSync as existsSync7 } from "fs";
|
|
21173
|
-
import { basename as basename2, dirname as
|
|
21208
|
+
import { basename as basename2, dirname as dirname2, join as join9, resolve as resolve5 } from "path";
|
|
21174
21209
|
function globalConfigPath() {
|
|
21175
21210
|
return join9(globalConfigDir(), "config.json");
|
|
21176
21211
|
}
|
|
21177
21212
|
function findProjectDir(startDir = process.cwd()) {
|
|
21178
|
-
let dir =
|
|
21213
|
+
let dir = resolve5(startDir);
|
|
21179
21214
|
let depth = 0;
|
|
21180
21215
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
21181
21216
|
const candidate = join9(dir, PROJECT_NAX_DIR);
|
|
@@ -21226,7 +21261,7 @@ function applyBatchModeCompat(conf) {
|
|
|
21226
21261
|
async function loadConfig(startDir, cliOverrides) {
|
|
21227
21262
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
21228
21263
|
const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
21229
|
-
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ?
|
|
21264
|
+
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname2(startDir) : startDir : process.cwd();
|
|
21230
21265
|
const profileName = await resolveProfileName(cliOverrides ?? {}, process.env, projectRoot);
|
|
21231
21266
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
21232
21267
|
if (globalConfRaw) {
|
|
@@ -21269,8 +21304,8 @@ ${errors3.join(`
|
|
|
21269
21304
|
}
|
|
21270
21305
|
async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
21271
21306
|
const logger = getLogger();
|
|
21272
|
-
const resolvedRootConfigPath =
|
|
21273
|
-
const rootNaxDir =
|
|
21307
|
+
const resolvedRootConfigPath = resolve5(rootConfigPath);
|
|
21308
|
+
const rootNaxDir = dirname2(resolvedRootConfigPath);
|
|
21274
21309
|
let rootConfigPromise = _rootConfigCache.get(resolvedRootConfigPath);
|
|
21275
21310
|
if (!rootConfigPromise) {
|
|
21276
21311
|
rootConfigPromise = loadConfig(rootNaxDir);
|
|
@@ -21281,7 +21316,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
|
21281
21316
|
logger.debug("config", "No packageDir \u2014 using root config");
|
|
21282
21317
|
return rootConfig;
|
|
21283
21318
|
}
|
|
21284
|
-
const repoRoot =
|
|
21319
|
+
const repoRoot = dirname2(rootNaxDir);
|
|
21285
21320
|
const packageConfigPath = join9(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
|
|
21286
21321
|
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
21287
21322
|
if (!packageOverride) {
|
|
@@ -21508,25 +21543,89 @@ function pipelineStageForDebate(stage) {
|
|
|
21508
21543
|
}
|
|
21509
21544
|
}
|
|
21510
21545
|
function resolveModelDefForDebater(debater, tier, config2) {
|
|
21511
|
-
|
|
21512
|
-
|
|
21546
|
+
const modelOverride = debater.model;
|
|
21547
|
+
let effectiveTier = tier;
|
|
21548
|
+
if (modelOverride) {
|
|
21549
|
+
const aliasedTier = MODEL_SHORTHAND_TIERS[modelOverride.toLowerCase()];
|
|
21550
|
+
if (aliasedTier) {
|
|
21551
|
+
effectiveTier = aliasedTier;
|
|
21552
|
+
} else if (!isTierLabel(modelOverride)) {
|
|
21553
|
+
return resolveModel(modelOverride);
|
|
21554
|
+
}
|
|
21513
21555
|
}
|
|
21514
21556
|
const configModels = config2?.models ?? DEFAULT_CONFIG.models;
|
|
21515
21557
|
const configDefaultAgent = config2?.autoMode?.defaultAgent ?? DEFAULT_CONFIG.autoMode.defaultAgent;
|
|
21516
21558
|
try {
|
|
21517
|
-
return resolveModelForAgent(configModels, debater.agent,
|
|
21559
|
+
return resolveModelForAgent(configModels, debater.agent, effectiveTier, configDefaultAgent);
|
|
21518
21560
|
} catch {}
|
|
21519
21561
|
try {
|
|
21520
|
-
return resolveModelForAgent(DEFAULT_CONFIG.models, DEFAULT_CONFIG.autoMode.defaultAgent,
|
|
21562
|
+
return resolveModelForAgent(DEFAULT_CONFIG.models, DEFAULT_CONFIG.autoMode.defaultAgent, effectiveTier, DEFAULT_CONFIG.autoMode.defaultAgent);
|
|
21521
21563
|
} catch {
|
|
21522
21564
|
return resolveModelForAgent(configModels, debater.agent, "fast", configDefaultAgent);
|
|
21523
21565
|
}
|
|
21524
21566
|
}
|
|
21525
|
-
async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName) {
|
|
21567
|
+
async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext) {
|
|
21526
21568
|
const resolverConfig = stageConfig.resolver;
|
|
21527
21569
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21570
|
+
if (reviewerSession && resolverContext) {
|
|
21571
|
+
try {
|
|
21572
|
+
const debateCtx = {
|
|
21573
|
+
resolverType: resolverConfig.type
|
|
21574
|
+
};
|
|
21575
|
+
if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
|
|
21576
|
+
const failOpen = resolverConfig.type === "majority-fail-open";
|
|
21577
|
+
const rawOutcome = majorityResolver(proposalOutputs, failOpen);
|
|
21578
|
+
let passCount = 0;
|
|
21579
|
+
let failCount = 0;
|
|
21580
|
+
for (const proposal of proposalOutputs) {
|
|
21581
|
+
try {
|
|
21582
|
+
const stripped = proposal.trim().replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
21583
|
+
const parsed = JSON.parse(stripped);
|
|
21584
|
+
if (typeof parsed.passed === "boolean" && parsed.passed)
|
|
21585
|
+
passCount++;
|
|
21586
|
+
else if (failOpen)
|
|
21587
|
+
passCount++;
|
|
21588
|
+
else
|
|
21589
|
+
failCount++;
|
|
21590
|
+
} catch {
|
|
21591
|
+
if (failOpen)
|
|
21592
|
+
passCount++;
|
|
21593
|
+
else
|
|
21594
|
+
failCount++;
|
|
21595
|
+
}
|
|
21596
|
+
}
|
|
21597
|
+
debateCtx.majorityVote = { passed: rawOutcome === "passed", passCount, failCount };
|
|
21598
|
+
}
|
|
21599
|
+
const story = {
|
|
21600
|
+
id: resolverContext.story.id,
|
|
21601
|
+
title: resolverContext.story.title,
|
|
21602
|
+
description: "",
|
|
21603
|
+
acceptanceCriteria: resolverContext.story.acceptanceCriteria
|
|
21604
|
+
};
|
|
21605
|
+
let dialogueResult;
|
|
21606
|
+
if (resolverContext.isReReview) {
|
|
21607
|
+
dialogueResult = await reviewerSession.reReviewDebate(resolverContext.labeledProposals, critiqueOutputs, resolverContext.diff, debateCtx);
|
|
21608
|
+
} else {
|
|
21609
|
+
dialogueResult = await reviewerSession.resolveDebate(resolverContext.labeledProposals, critiqueOutputs, resolverContext.diff, story, resolverContext.semanticConfig, debateCtx);
|
|
21610
|
+
}
|
|
21611
|
+
const outcome = dialogueResult.checkResult.success ? "passed" : "failed";
|
|
21612
|
+
return {
|
|
21613
|
+
outcome,
|
|
21614
|
+
resolverCostUsd: dialogueResult.cost ?? 0,
|
|
21615
|
+
dialogueResult
|
|
21616
|
+
};
|
|
21617
|
+
} catch (err) {
|
|
21618
|
+
logger?.warn("debate", "ReviewerSession.resolveDebate() failed \u2014 falling back to stateless resolver", {
|
|
21619
|
+
storyId,
|
|
21620
|
+
error: err instanceof Error ? err.message : String(err)
|
|
21621
|
+
});
|
|
21622
|
+
}
|
|
21623
|
+
}
|
|
21624
|
+
if (reviewerSession && !resolverContext) {
|
|
21625
|
+
logger?.warn("debate", "ReviewerSession provided but resolverContext is undefined \u2014 falling back to stateless resolver", { storyId });
|
|
21626
|
+
}
|
|
21528
21627
|
if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
|
|
21529
|
-
if (workdir !== undefined) {
|
|
21628
|
+
if (workdir !== undefined && !reviewerSession) {
|
|
21530
21629
|
logger?.warn("debate", "majority resolver does not support implementer session resumption \u2014 switch to synthesis or custom resolver for context-aware semantic review");
|
|
21531
21630
|
}
|
|
21532
21631
|
return {
|
|
@@ -21552,7 +21651,8 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21552
21651
|
});
|
|
21553
21652
|
return {
|
|
21554
21653
|
outcome: "passed",
|
|
21555
|
-
resolverCostUsd: resolverResult.costUsd
|
|
21654
|
+
resolverCostUsd: resolverResult.costUsd,
|
|
21655
|
+
output: resolverResult.output
|
|
21556
21656
|
};
|
|
21557
21657
|
}
|
|
21558
21658
|
return { outcome: "passed", resolverCostUsd: 0 };
|
|
@@ -21573,12 +21673,13 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21573
21673
|
});
|
|
21574
21674
|
return {
|
|
21575
21675
|
outcome: "passed",
|
|
21576
|
-
resolverCostUsd: resolverResult.costUsd
|
|
21676
|
+
resolverCostUsd: resolverResult.costUsd,
|
|
21677
|
+
output: resolverResult.output
|
|
21577
21678
|
};
|
|
21578
21679
|
}
|
|
21579
21680
|
return { outcome: "passed", resolverCostUsd: 0 };
|
|
21580
21681
|
}
|
|
21581
|
-
var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
|
|
21682
|
+
var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps, MODEL_SHORTHAND_TIERS;
|
|
21582
21683
|
var init_session_helpers = __esm(() => {
|
|
21583
21684
|
init_adapter();
|
|
21584
21685
|
init_registry();
|
|
@@ -21590,6 +21691,11 @@ var init_session_helpers = __esm(() => {
|
|
|
21590
21691
|
getSafeLogger,
|
|
21591
21692
|
readFile: (path) => Bun.file(path).text()
|
|
21592
21693
|
};
|
|
21694
|
+
MODEL_SHORTHAND_TIERS = {
|
|
21695
|
+
haiku: "fast",
|
|
21696
|
+
sonnet: "balanced",
|
|
21697
|
+
opus: "powerful"
|
|
21698
|
+
};
|
|
21593
21699
|
});
|
|
21594
21700
|
|
|
21595
21701
|
// src/debate/concurrency.ts
|
|
@@ -21775,7 +21881,11 @@ async function runStateful(ctx, prompt) {
|
|
|
21775
21881
|
critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value.output);
|
|
21776
21882
|
}
|
|
21777
21883
|
const proposalOutputs = successfulProposals.map((s) => s.output);
|
|
21778
|
-
const
|
|
21884
|
+
const fullResolverContext = ctx.resolverContextInput ? {
|
|
21885
|
+
...ctx.resolverContextInput,
|
|
21886
|
+
labeledProposals: successfulProposals.map((s) => ({ debater: s.debater.agent, output: s.output }))
|
|
21887
|
+
} : undefined;
|
|
21888
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
|
|
21779
21889
|
totalCostUsd += outcome.resolverCostUsd;
|
|
21780
21890
|
const proposals = successfulProposals.map((s) => ({
|
|
21781
21891
|
debater: s.debater,
|
|
@@ -21802,6 +21912,50 @@ var init_session_stateful = __esm(() => {
|
|
|
21802
21912
|
});
|
|
21803
21913
|
|
|
21804
21914
|
// src/debate/session-hybrid.ts
|
|
21915
|
+
async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix) {
|
|
21916
|
+
const logger = _debateSessionDeps.getSafeLogger();
|
|
21917
|
+
const config2 = ctx.stageConfig;
|
|
21918
|
+
const rebuttals = [];
|
|
21919
|
+
let costUsd = 0;
|
|
21920
|
+
const proposalList = proposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
21921
|
+
try {
|
|
21922
|
+
for (let round = 1;round <= config2.rounds; round++) {
|
|
21923
|
+
const priorRebuttals = rebuttals.filter((r) => r.round < round).map((r) => r.output);
|
|
21924
|
+
for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
|
|
21925
|
+
const proposal = proposals[debaterIdx];
|
|
21926
|
+
const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
|
|
21927
|
+
logger?.info("debate:rebuttal-start", "debate:rebuttal-start", {
|
|
21928
|
+
storyId: ctx.storyId,
|
|
21929
|
+
round,
|
|
21930
|
+
debaterIndex: debaterIdx
|
|
21931
|
+
});
|
|
21932
|
+
const rebuttalPrompt = buildRebuttalContext(originalPrompt, proposalList, priorRebuttals, debaterIdx);
|
|
21933
|
+
try {
|
|
21934
|
+
const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
|
|
21935
|
+
costUsd += turnResult.cost;
|
|
21936
|
+
rebuttals.push({ debater: proposal.debater, round, output: turnResult.output });
|
|
21937
|
+
} catch (err) {
|
|
21938
|
+
logger?.warn("debate", "debate:rebuttal-failed", {
|
|
21939
|
+
storyId: ctx.storyId,
|
|
21940
|
+
round,
|
|
21941
|
+
debaterIndex: debaterIdx,
|
|
21942
|
+
error: err instanceof Error ? err.message : String(err)
|
|
21943
|
+
});
|
|
21944
|
+
}
|
|
21945
|
+
}
|
|
21946
|
+
}
|
|
21947
|
+
} finally {
|
|
21948
|
+
for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
|
|
21949
|
+
const proposal = proposals[debaterIdx];
|
|
21950
|
+
const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
|
|
21951
|
+
try {
|
|
21952
|
+
const closeCost = await closeStatefulSession(ctx, proposal.adapter, proposal.debater, sessionRole);
|
|
21953
|
+
costUsd += closeCost;
|
|
21954
|
+
} catch {}
|
|
21955
|
+
}
|
|
21956
|
+
}
|
|
21957
|
+
return { rebuttals, costUsd };
|
|
21958
|
+
}
|
|
21805
21959
|
async function runHybrid(ctx, prompt) {
|
|
21806
21960
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21807
21961
|
const config2 = ctx.stageConfig;
|
|
@@ -21870,45 +22024,14 @@ async function runHybrid(ctx, prompt) {
|
|
|
21870
22024
|
}
|
|
21871
22025
|
const proposalOutputs = successfulProposals.map((s) => s.output);
|
|
21872
22026
|
const proposalList = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
21873
|
-
const rebuttals =
|
|
21874
|
-
|
|
21875
|
-
for (let round = 1;round <= config2.rounds; round++) {
|
|
21876
|
-
const priorRebuttals = rebuttals.filter((r) => r.round < round).map((r) => r.output);
|
|
21877
|
-
for (let debaterIdx = 0;debaterIdx < successfulProposals.length; debaterIdx++) {
|
|
21878
|
-
const proposal = successfulProposals[debaterIdx];
|
|
21879
|
-
const sessionRole = `debate-hybrid-${debaterIdx}`;
|
|
21880
|
-
logger?.info("debate:rebuttal-start", "debate:rebuttal-start", {
|
|
21881
|
-
storyId: ctx.storyId,
|
|
21882
|
-
round,
|
|
21883
|
-
debaterIndex: debaterIdx
|
|
21884
|
-
});
|
|
21885
|
-
const rebuttalPrompt = buildRebuttalContext(prompt, proposalList, priorRebuttals, debaterIdx);
|
|
21886
|
-
try {
|
|
21887
|
-
const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
|
|
21888
|
-
totalCostUsd += turnResult.cost;
|
|
21889
|
-
rebuttals.push({ debater: proposal.debater, round, output: turnResult.output });
|
|
21890
|
-
} catch (err) {
|
|
21891
|
-
logger?.warn("debate", "debate:rebuttal-failed", {
|
|
21892
|
-
storyId: ctx.storyId,
|
|
21893
|
-
round,
|
|
21894
|
-
debaterIndex: debaterIdx,
|
|
21895
|
-
error: err instanceof Error ? err.message : String(err)
|
|
21896
|
-
});
|
|
21897
|
-
}
|
|
21898
|
-
}
|
|
21899
|
-
}
|
|
21900
|
-
} finally {
|
|
21901
|
-
for (let debaterIdx = 0;debaterIdx < successfulProposals.length; debaterIdx++) {
|
|
21902
|
-
const proposal = successfulProposals[debaterIdx];
|
|
21903
|
-
const sessionRole = `debate-hybrid-${debaterIdx}`;
|
|
21904
|
-
try {
|
|
21905
|
-
const closeCost = await closeStatefulSession(ctx, proposal.adapter, proposal.debater, sessionRole);
|
|
21906
|
-
totalCostUsd += closeCost;
|
|
21907
|
-
} catch {}
|
|
21908
|
-
}
|
|
21909
|
-
}
|
|
22027
|
+
const { rebuttals, costUsd: rebuttalCost } = await runRebuttalLoop(ctx, successfulProposals, prompt, "debate-hybrid");
|
|
22028
|
+
totalCostUsd += rebuttalCost;
|
|
21910
22029
|
const critiqueOutputs = rebuttals.map((r) => r.output);
|
|
21911
|
-
const
|
|
22030
|
+
const fullResolverContext = ctx.resolverContextInput ? {
|
|
22031
|
+
...ctx.resolverContextInput,
|
|
22032
|
+
labeledProposals: successfulProposals.map((s) => ({ debater: s.debater.agent, output: s.output }))
|
|
22033
|
+
} : undefined;
|
|
22034
|
+
const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
|
|
21912
22035
|
totalCostUsd += resolveResult.resolverCostUsd;
|
|
21913
22036
|
return {
|
|
21914
22037
|
storyId: ctx.storyId,
|
|
@@ -22051,7 +22174,11 @@ async function runOneShot(ctx, prompt) {
|
|
|
22051
22174
|
critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value.output);
|
|
22052
22175
|
}
|
|
22053
22176
|
const proposalOutputs = successful.map((p) => p.output);
|
|
22054
|
-
const
|
|
22177
|
+
const fullResolverContext = ctx.resolverContextInput ? {
|
|
22178
|
+
...ctx.resolverContextInput,
|
|
22179
|
+
labeledProposals: successful.map((p) => ({ debater: p.debater.agent, output: p.output }))
|
|
22180
|
+
} : undefined;
|
|
22181
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutMs, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
|
|
22055
22182
|
totalCostUsd += outcome.resolverCostUsd;
|
|
22056
22183
|
const proposals = successful.map((p) => ({
|
|
22057
22184
|
debater: p.debater,
|
|
@@ -22083,7 +22210,7 @@ async function runPlan2(ctx, basePrompt, opts) {
|
|
|
22083
22210
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22084
22211
|
const config2 = ctx.stageConfig;
|
|
22085
22212
|
const debaters = config2.debaters ?? [];
|
|
22086
|
-
|
|
22213
|
+
let totalCostUsd = 0;
|
|
22087
22214
|
const resolved = [];
|
|
22088
22215
|
for (const debater of debaters) {
|
|
22089
22216
|
const adapter = _debateSessionDeps.getAgent(debater.agent, ctx.config);
|
|
@@ -22106,13 +22233,16 @@ async function runPlan2(ctx, basePrompt, opts) {
|
|
|
22106
22233
|
|
|
22107
22234
|
Write the PRD JSON directly to this file path: ${tempOutputPath}
|
|
22108
22235
|
Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.`;
|
|
22109
|
-
|
|
22236
|
+
const modelTier = modelTierFromDebater(debater);
|
|
22237
|
+
const modelDef = resolveModelDefForDebater(debater, modelTier, ctx.config);
|
|
22238
|
+
const planResult = await adapter.plan({
|
|
22110
22239
|
prompt: debaterPrompt,
|
|
22111
22240
|
workdir: opts.workdir,
|
|
22112
22241
|
interactive: false,
|
|
22113
22242
|
timeoutSeconds: opts.timeoutSeconds,
|
|
22114
22243
|
config: ctx.config,
|
|
22115
|
-
modelTier
|
|
22244
|
+
modelTier,
|
|
22245
|
+
modelDef,
|
|
22116
22246
|
dangerouslySkipPermissions: opts.dangerouslySkipPermissions,
|
|
22117
22247
|
maxInteractionTurns: opts.maxInteractionTurns,
|
|
22118
22248
|
featureName: opts.feature,
|
|
@@ -22120,13 +22250,14 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22120
22250
|
sessionRole: `plan-${i}`
|
|
22121
22251
|
});
|
|
22122
22252
|
const output = await _debateSessionDeps.readFile(tempOutputPath);
|
|
22123
|
-
return { debater, adapter, output, cost: 0 };
|
|
22253
|
+
return { debater, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22124
22254
|
}), concurrencyLimit);
|
|
22125
22255
|
const successful = [];
|
|
22126
22256
|
for (let i = 0;i < settled.length; i++) {
|
|
22127
22257
|
const res = settled[i];
|
|
22128
22258
|
if (res.status === "fulfilled") {
|
|
22129
22259
|
successful.push(res.value);
|
|
22260
|
+
totalCostUsd += res.value.cost;
|
|
22130
22261
|
} else {
|
|
22131
22262
|
const { debater } = resolved[i];
|
|
22132
22263
|
logger?.warn("debate", "debate:debater-failed", {
|
|
@@ -22178,9 +22309,30 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22178
22309
|
};
|
|
22179
22310
|
}
|
|
22180
22311
|
const proposalOutputs = successful.map((p) => p.output);
|
|
22312
|
+
const mode = ctx.stageConfig.mode ?? "panel";
|
|
22313
|
+
const sessionMode = ctx.stageConfig.sessionMode ?? "one-shot";
|
|
22314
|
+
let critiqueOutputs = [];
|
|
22315
|
+
let rebuttalList;
|
|
22316
|
+
if (mode === "hybrid" && sessionMode === "stateful") {
|
|
22317
|
+
const hybridCtx = {
|
|
22318
|
+
storyId: ctx.storyId,
|
|
22319
|
+
stage: ctx.stage,
|
|
22320
|
+
stageConfig: ctx.stageConfig,
|
|
22321
|
+
config: ctx.config,
|
|
22322
|
+
workdir: opts.workdir,
|
|
22323
|
+
featureName: opts.feature,
|
|
22324
|
+
timeoutSeconds: opts.timeoutSeconds ?? 600
|
|
22325
|
+
};
|
|
22326
|
+
const { rebuttals, costUsd } = await runRebuttalLoop(hybridCtx, successful, basePrompt, "plan-hybrid");
|
|
22327
|
+
critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22328
|
+
rebuttalList = rebuttals;
|
|
22329
|
+
totalCostUsd += costUsd;
|
|
22330
|
+
} else if (mode === "hybrid") {
|
|
22331
|
+
logger?.warn("debate", "hybrid mode requires sessionMode: stateful for plan \u2014 running as panel");
|
|
22332
|
+
}
|
|
22181
22333
|
const resolverTimeoutMs = (ctx.stageConfig.timeoutSeconds ?? 600) * 1000;
|
|
22182
|
-
const outcome = await resolveOutcome(proposalOutputs,
|
|
22183
|
-
const winningOutput = successful[0].output;
|
|
22334
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature);
|
|
22335
|
+
const winningOutput = outcome.output ?? successful[0].output;
|
|
22184
22336
|
const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
|
|
22185
22337
|
logger?.info("debate", "debate:result", {
|
|
22186
22338
|
storyId: ctx.storyId,
|
|
@@ -22191,16 +22343,18 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22191
22343
|
storyId: ctx.storyId,
|
|
22192
22344
|
stage: ctx.stage,
|
|
22193
22345
|
outcome: outcome.outcome,
|
|
22194
|
-
rounds: 1,
|
|
22346
|
+
rounds: rebuttalList ? config2.rounds : 1,
|
|
22195
22347
|
debaters: successful.map((p) => p.debater.agent),
|
|
22196
22348
|
resolverType: config2.resolver.type,
|
|
22197
22349
|
proposals,
|
|
22350
|
+
rebuttals: rebuttalList,
|
|
22198
22351
|
output: winningOutput,
|
|
22199
22352
|
totalCostUsd
|
|
22200
22353
|
};
|
|
22201
22354
|
}
|
|
22202
22355
|
var init_session_plan = __esm(() => {
|
|
22203
22356
|
init_session_helpers();
|
|
22357
|
+
init_session_hybrid();
|
|
22204
22358
|
});
|
|
22205
22359
|
|
|
22206
22360
|
// src/debate/session.ts
|
|
@@ -22212,6 +22366,8 @@ class DebateSession {
|
|
|
22212
22366
|
workdir;
|
|
22213
22367
|
featureName;
|
|
22214
22368
|
timeoutSeconds;
|
|
22369
|
+
reviewerSession;
|
|
22370
|
+
resolverContextInput;
|
|
22215
22371
|
get timeoutMs() {
|
|
22216
22372
|
return this.timeoutSeconds * 1000;
|
|
22217
22373
|
}
|
|
@@ -22223,6 +22379,8 @@ class DebateSession {
|
|
|
22223
22379
|
this.workdir = opts.workdir ?? process.cwd();
|
|
22224
22380
|
this.featureName = opts.featureName ?? opts.stage;
|
|
22225
22381
|
this.timeoutSeconds = opts.timeoutSeconds ?? opts.stageConfig.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
22382
|
+
this.reviewerSession = opts.reviewerSession;
|
|
22383
|
+
this.resolverContextInput = opts.resolverContextInput;
|
|
22226
22384
|
}
|
|
22227
22385
|
async run(prompt) {
|
|
22228
22386
|
const sessionMode = this.stageConfig.sessionMode ?? "one-shot";
|
|
@@ -22236,7 +22394,9 @@ class DebateSession {
|
|
|
22236
22394
|
config: this.config,
|
|
22237
22395
|
workdir: this.workdir,
|
|
22238
22396
|
featureName: this.featureName,
|
|
22239
|
-
timeoutSeconds: this.timeoutSeconds
|
|
22397
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
22398
|
+
reviewerSession: this.reviewerSession,
|
|
22399
|
+
resolverContextInput: this.resolverContextInput
|
|
22240
22400
|
}, prompt);
|
|
22241
22401
|
}
|
|
22242
22402
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
@@ -22246,7 +22406,11 @@ class DebateSession {
|
|
|
22246
22406
|
stage: this.stage,
|
|
22247
22407
|
stageConfig: this.stageConfig,
|
|
22248
22408
|
config: this.config,
|
|
22249
|
-
timeoutMs: this.timeoutMs
|
|
22409
|
+
timeoutMs: this.timeoutMs,
|
|
22410
|
+
workdir: this.workdir,
|
|
22411
|
+
featureName: this.featureName,
|
|
22412
|
+
reviewerSession: this.reviewerSession,
|
|
22413
|
+
resolverContextInput: this.resolverContextInput
|
|
22250
22414
|
}, prompt);
|
|
22251
22415
|
}
|
|
22252
22416
|
if (sessionMode === "stateful") {
|
|
@@ -22257,7 +22421,9 @@ class DebateSession {
|
|
|
22257
22421
|
config: this.config,
|
|
22258
22422
|
workdir: this.workdir,
|
|
22259
22423
|
featureName: this.featureName,
|
|
22260
|
-
timeoutSeconds: this.timeoutSeconds
|
|
22424
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
22425
|
+
reviewerSession: this.reviewerSession,
|
|
22426
|
+
resolverContextInput: this.resolverContextInput
|
|
22261
22427
|
}, prompt);
|
|
22262
22428
|
}
|
|
22263
22429
|
return runOneShot({
|
|
@@ -22265,7 +22431,11 @@ class DebateSession {
|
|
|
22265
22431
|
stage: this.stage,
|
|
22266
22432
|
stageConfig: this.stageConfig,
|
|
22267
22433
|
config: this.config,
|
|
22268
|
-
timeoutMs: this.timeoutMs
|
|
22434
|
+
timeoutMs: this.timeoutMs,
|
|
22435
|
+
workdir: this.workdir,
|
|
22436
|
+
featureName: this.featureName,
|
|
22437
|
+
reviewerSession: this.reviewerSession,
|
|
22438
|
+
resolverContextInput: this.resolverContextInput
|
|
22269
22439
|
}, prompt);
|
|
22270
22440
|
}
|
|
22271
22441
|
async runPlan(basePrompt, opts) {
|
|
@@ -22600,30 +22770,41 @@ class CLIInteractionPlugin {
|
|
|
22600
22770
|
}
|
|
22601
22771
|
async send(request) {
|
|
22602
22772
|
this.pendingRequests.set(request.id, request);
|
|
22603
|
-
|
|
22604
|
-
${"=".repeat(80)}
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22773
|
+
process.stdout.write(`
|
|
22774
|
+
${"=".repeat(80)}
|
|
22775
|
+
`);
|
|
22776
|
+
process.stdout.write(`[INTERACTION] ${request.stage.toUpperCase()} \u2014 ${request.type.toUpperCase()}
|
|
22777
|
+
`);
|
|
22778
|
+
process.stdout.write(`${"=".repeat(80)}
|
|
22779
|
+
`);
|
|
22780
|
+
process.stdout.write(`
|
|
22608
22781
|
${request.summary}
|
|
22782
|
+
|
|
22609
22783
|
`);
|
|
22610
22784
|
if (request.detail) {
|
|
22611
|
-
|
|
22612
|
-
|
|
22785
|
+
process.stdout.write(`${request.detail}
|
|
22786
|
+
`);
|
|
22787
|
+
process.stdout.write(`
|
|
22788
|
+
`);
|
|
22613
22789
|
}
|
|
22614
22790
|
if (request.options && request.options.length > 0) {
|
|
22615
|
-
|
|
22791
|
+
process.stdout.write(`Options:
|
|
22792
|
+
`);
|
|
22616
22793
|
for (const opt of request.options) {
|
|
22617
22794
|
const desc = opt.description ? ` \u2014 ${opt.description}` : "";
|
|
22618
|
-
|
|
22795
|
+
process.stdout.write(` [${opt.key}] ${opt.label}${desc}
|
|
22796
|
+
`);
|
|
22619
22797
|
}
|
|
22620
|
-
|
|
22798
|
+
process.stdout.write(`
|
|
22799
|
+
`);
|
|
22621
22800
|
}
|
|
22622
22801
|
if (request.timeout) {
|
|
22623
22802
|
const timeoutSec = Math.floor(request.timeout / 1000);
|
|
22624
|
-
|
|
22803
|
+
process.stdout.write(`[Timeout: ${timeoutSec}s | Fallback: ${request.fallback}]
|
|
22804
|
+
`);
|
|
22625
22805
|
}
|
|
22626
|
-
|
|
22806
|
+
process.stdout.write(`${"=".repeat(80)}
|
|
22807
|
+
|
|
22627
22808
|
`);
|
|
22628
22809
|
}
|
|
22629
22810
|
async receive(requestId, timeout = 60000) {
|
|
@@ -22645,9 +22826,9 @@ ${request.summary}
|
|
|
22645
22826
|
if (!this.rl) {
|
|
22646
22827
|
throw new Error("CLI plugin not initialized");
|
|
22647
22828
|
}
|
|
22648
|
-
const timeoutPromise = new Promise((
|
|
22829
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
22649
22830
|
setTimeout(() => {
|
|
22650
|
-
|
|
22831
|
+
resolve6({
|
|
22651
22832
|
requestId: request.id,
|
|
22652
22833
|
action: "skip",
|
|
22653
22834
|
respondedBy: "timeout",
|
|
@@ -22799,9 +22980,9 @@ ${request.summary}
|
|
|
22799
22980
|
if (!this.rl) {
|
|
22800
22981
|
throw new Error("CLI plugin not initialized");
|
|
22801
22982
|
}
|
|
22802
|
-
return new Promise((
|
|
22983
|
+
return new Promise((resolve6) => {
|
|
22803
22984
|
this.rl?.question(prompt, (answer) => {
|
|
22804
|
-
|
|
22985
|
+
resolve6(answer);
|
|
22805
22986
|
});
|
|
22806
22987
|
});
|
|
22807
22988
|
}
|
|
@@ -23233,7 +23414,7 @@ class WebhookInteractionPlugin {
|
|
|
23233
23414
|
this.pendingResponses.delete(requestId);
|
|
23234
23415
|
return early;
|
|
23235
23416
|
}
|
|
23236
|
-
return new Promise((
|
|
23417
|
+
return new Promise((resolve6) => {
|
|
23237
23418
|
const existingCallback = this.receiveCallbacks.get(requestId);
|
|
23238
23419
|
if (existingCallback) {
|
|
23239
23420
|
this.clearReceiveTimer(requestId);
|
|
@@ -23247,7 +23428,7 @@ class WebhookInteractionPlugin {
|
|
|
23247
23428
|
const timer = setTimeout(() => {
|
|
23248
23429
|
this.clearReceiveTimer(requestId);
|
|
23249
23430
|
this.receiveCallbacks.delete(requestId);
|
|
23250
|
-
|
|
23431
|
+
resolve6({
|
|
23251
23432
|
requestId,
|
|
23252
23433
|
action: "skip",
|
|
23253
23434
|
respondedBy: "timeout",
|
|
@@ -23258,7 +23439,7 @@ class WebhookInteractionPlugin {
|
|
|
23258
23439
|
this.receiveCallbacks.set(requestId, (response) => {
|
|
23259
23440
|
this.clearReceiveTimer(requestId);
|
|
23260
23441
|
this.receiveCallbacks.delete(requestId);
|
|
23261
|
-
|
|
23442
|
+
resolve6(response);
|
|
23262
23443
|
});
|
|
23263
23444
|
});
|
|
23264
23445
|
}
|
|
@@ -25203,6 +25384,7 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25203
25384
|
const logger = getLogger();
|
|
25204
25385
|
const retryCountMap = new Map;
|
|
25205
25386
|
let i = 0;
|
|
25387
|
+
let stageCostAccum = 0;
|
|
25206
25388
|
while (i < stages.length) {
|
|
25207
25389
|
const stage = stages[i];
|
|
25208
25390
|
if (!stage.enabled(context)) {
|
|
@@ -25221,27 +25403,58 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25221
25403
|
reason: `Stage "${stage.name}" threw error: ${errorMessage(error48)}`
|
|
25222
25404
|
};
|
|
25223
25405
|
eventEmitter?.emit("stage:exit", stage.name, failResult);
|
|
25224
|
-
return {
|
|
25406
|
+
return {
|
|
25407
|
+
success: false,
|
|
25408
|
+
finalAction: "fail",
|
|
25409
|
+
reason: failResult.reason,
|
|
25410
|
+
stoppedAtStage: stage.name,
|
|
25411
|
+
context,
|
|
25412
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25413
|
+
};
|
|
25225
25414
|
}
|
|
25415
|
+
if (result.cost)
|
|
25416
|
+
stageCostAccum += result.cost;
|
|
25226
25417
|
eventEmitter?.emit("stage:exit", stage.name, result);
|
|
25227
25418
|
switch (result.action) {
|
|
25228
25419
|
case "continue":
|
|
25229
25420
|
i++;
|
|
25230
25421
|
continue;
|
|
25231
25422
|
case "skip":
|
|
25232
|
-
return {
|
|
25423
|
+
return {
|
|
25424
|
+
success: false,
|
|
25425
|
+
finalAction: "skip",
|
|
25426
|
+
reason: result.reason,
|
|
25427
|
+
stoppedAtStage: stage.name,
|
|
25428
|
+
context,
|
|
25429
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25430
|
+
};
|
|
25233
25431
|
case "fail":
|
|
25234
|
-
return {
|
|
25432
|
+
return {
|
|
25433
|
+
success: false,
|
|
25434
|
+
finalAction: "fail",
|
|
25435
|
+
reason: result.reason,
|
|
25436
|
+
stoppedAtStage: stage.name,
|
|
25437
|
+
context,
|
|
25438
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25439
|
+
};
|
|
25235
25440
|
case "escalate":
|
|
25236
25441
|
return {
|
|
25237
25442
|
success: false,
|
|
25238
25443
|
finalAction: "escalate",
|
|
25239
25444
|
reason: result.reason ?? "Stage requested escalation to higher tier",
|
|
25240
25445
|
stoppedAtStage: stage.name,
|
|
25241
|
-
context
|
|
25446
|
+
context,
|
|
25447
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25242
25448
|
};
|
|
25243
25449
|
case "pause":
|
|
25244
|
-
return {
|
|
25450
|
+
return {
|
|
25451
|
+
success: false,
|
|
25452
|
+
finalAction: "pause",
|
|
25453
|
+
reason: result.reason,
|
|
25454
|
+
stoppedAtStage: stage.name,
|
|
25455
|
+
context,
|
|
25456
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25457
|
+
};
|
|
25245
25458
|
case "retry": {
|
|
25246
25459
|
const retries = (retryCountMap.get(result.fromStage) ?? 0) + 1;
|
|
25247
25460
|
if (retries > MAX_STAGE_RETRIES) {
|
|
@@ -25251,7 +25464,8 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25251
25464
|
finalAction: "fail",
|
|
25252
25465
|
reason: `Stage "${stage.name}" exceeded max retries (${MAX_STAGE_RETRIES}) for "${result.fromStage}"`,
|
|
25253
25466
|
stoppedAtStage: stage.name,
|
|
25254
|
-
context
|
|
25467
|
+
context,
|
|
25468
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25255
25469
|
};
|
|
25256
25470
|
}
|
|
25257
25471
|
retryCountMap.set(result.fromStage, retries);
|
|
@@ -25263,7 +25477,8 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25263
25477
|
finalAction: "escalate",
|
|
25264
25478
|
reason: `Retry target stage "${result.fromStage}" not found`,
|
|
25265
25479
|
stoppedAtStage: stage.name,
|
|
25266
|
-
context
|
|
25480
|
+
context,
|
|
25481
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25267
25482
|
};
|
|
25268
25483
|
}
|
|
25269
25484
|
logger.debug("pipeline", `Retrying from stage "${result.fromStage}" (attempt ${retries}/${MAX_STAGE_RETRIES})`);
|
|
@@ -25276,7 +25491,12 @@ async function runPipeline(stages, context, eventEmitter) {
|
|
|
25276
25491
|
}
|
|
25277
25492
|
}
|
|
25278
25493
|
}
|
|
25279
|
-
return {
|
|
25494
|
+
return {
|
|
25495
|
+
success: true,
|
|
25496
|
+
finalAction: "complete",
|
|
25497
|
+
context,
|
|
25498
|
+
stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
|
|
25499
|
+
};
|
|
25280
25500
|
}
|
|
25281
25501
|
var MAX_STAGE_RETRIES = 5;
|
|
25282
25502
|
var init_runner = __esm(() => {
|
|
@@ -25814,8 +26034,7 @@ var init_acceptance = __esm(() => {
|
|
|
25814
26034
|
acceptanceStage = {
|
|
25815
26035
|
name: "acceptance",
|
|
25816
26036
|
enabled(ctx) {
|
|
25817
|
-
|
|
25818
|
-
if (!effectiveConfig.acceptance.enabled) {
|
|
26037
|
+
if (!ctx.config.acceptance.enabled) {
|
|
25819
26038
|
return false;
|
|
25820
26039
|
}
|
|
25821
26040
|
if (!areAllStoriesComplete(ctx)) {
|
|
@@ -25825,7 +26044,6 @@ var init_acceptance = __esm(() => {
|
|
|
25825
26044
|
},
|
|
25826
26045
|
async execute(ctx) {
|
|
25827
26046
|
const logger = getLogger();
|
|
25828
|
-
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
25829
26047
|
logger.info("acceptance", "Running acceptance tests", { storyId: ctx.story.id });
|
|
25830
26048
|
if (!ctx.featureDir) {
|
|
25831
26049
|
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests", { storyId: ctx.story.id });
|
|
@@ -25833,7 +26051,7 @@ var init_acceptance = __esm(() => {
|
|
|
25833
26051
|
}
|
|
25834
26052
|
const testGroups = ctx.acceptanceTestPaths ?? [
|
|
25835
26053
|
{
|
|
25836
|
-
testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir,
|
|
26054
|
+
testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language),
|
|
25837
26055
|
packageDir: ctx.workdir
|
|
25838
26056
|
}
|
|
25839
26057
|
];
|
|
@@ -25848,7 +26066,7 @@ var init_acceptance = __esm(() => {
|
|
|
25848
26066
|
logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { storyId: ctx.story.id, testPath });
|
|
25849
26067
|
continue;
|
|
25850
26068
|
}
|
|
25851
|
-
const testCmdParts = buildAcceptanceRunCommand(testPath,
|
|
26069
|
+
const testCmdParts = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
|
|
25852
26070
|
logger.info("acceptance", "Running acceptance command", {
|
|
25853
26071
|
storyId: ctx.story.id,
|
|
25854
26072
|
cmd: testCmdParts.join(" "),
|
|
@@ -25943,12 +26161,12 @@ function buildRefinementPrompt(criteria, codebaseContext, options) {
|
|
|
25943
26161
|
`);
|
|
25944
26162
|
const strategySection = buildStrategySection(options);
|
|
25945
26163
|
const refinedExample = buildRefinedExample(options?.testStrategy);
|
|
26164
|
+
const codebaseSection = codebaseContext ? `CODEBASE CONTEXT:
|
|
26165
|
+
${codebaseContext}
|
|
26166
|
+
` : "";
|
|
25946
26167
|
const core2 = `You are an acceptance criteria refinement assistant. Your task is to convert raw acceptance criteria into concrete, machine-verifiable assertions.
|
|
25947
26168
|
|
|
25948
|
-
|
|
25949
|
-
${codebaseContext}
|
|
25950
|
-
${strategySection}
|
|
25951
|
-
ACCEPTANCE CRITERIA TO REFINE:
|
|
26169
|
+
${codebaseSection}${strategySection}ACCEPTANCE CRITERIA TO REFINE:
|
|
25952
26170
|
${criteriaList}
|
|
25953
26171
|
|
|
25954
26172
|
For each criterion, produce a refined version that is concrete and automatically testable where possible.
|
|
@@ -26201,12 +26419,11 @@ ${stderr}` };
|
|
|
26201
26419
|
if (!ctx.featureDir) {
|
|
26202
26420
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
26203
26421
|
}
|
|
26204
|
-
const
|
|
26205
|
-
const
|
|
26206
|
-
const testPathConfig = (ctx.effectiveConfig ?? ctx.config).acceptance.testPath;
|
|
26422
|
+
const language = ctx.config.project?.language;
|
|
26423
|
+
const testPathConfig = ctx.config.acceptance.testPath;
|
|
26207
26424
|
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-"));
|
|
26425
|
+
const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed").flatMap((s) => s.acceptanceCriteria);
|
|
26426
|
+
const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed");
|
|
26210
26427
|
const workdirGroups = new Map;
|
|
26211
26428
|
for (const story of nonFixStories) {
|
|
26212
26429
|
const wd = story.workdir ?? "";
|
|
@@ -26262,7 +26479,7 @@ ${stderr}` };
|
|
|
26262
26479
|
}
|
|
26263
26480
|
if (shouldGenerate) {
|
|
26264
26481
|
totalCriteria = allCriteria.length;
|
|
26265
|
-
const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.
|
|
26482
|
+
const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
|
|
26266
26483
|
let allRefinedCriteria;
|
|
26267
26484
|
if (ctx.config.acceptance.refinement) {
|
|
26268
26485
|
const maxConcurrency = ctx.config.acceptance.refinementConcurrency ?? 3;
|
|
@@ -26306,7 +26523,7 @@ ${stderr}` };
|
|
|
26306
26523
|
const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
|
|
26307
26524
|
let modelDef;
|
|
26308
26525
|
try {
|
|
26309
|
-
modelDef = resolveModelForAgent(ctx.
|
|
26526
|
+
modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.config.acceptance.model ?? "fast", ctx.rootConfig.autoMode.defaultAgent);
|
|
26310
26527
|
} catch {
|
|
26311
26528
|
const tier = ctx.config.acceptance.model ?? "fast";
|
|
26312
26529
|
modelDef = { provider: "anthropic", model: tier };
|
|
@@ -26342,7 +26559,7 @@ ${stderr}` };
|
|
|
26342
26559
|
}
|
|
26343
26560
|
let redFailCount = 0;
|
|
26344
26561
|
for (const { testPath, packageDir } of testPaths) {
|
|
26345
|
-
const runCmd = buildAcceptanceRunCommand(testPath,
|
|
26562
|
+
const runCmd = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
|
|
26346
26563
|
getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
|
|
26347
26564
|
cmd: runCmd.join(" "),
|
|
26348
26565
|
packageDir
|
|
@@ -26365,59 +26582,6 @@ ${stderr}` };
|
|
|
26365
26582
|
};
|
|
26366
26583
|
});
|
|
26367
26584
|
|
|
26368
|
-
// src/agents/claude/index.ts
|
|
26369
|
-
var init_claude = __esm(() => {
|
|
26370
|
-
init_adapter3();
|
|
26371
|
-
init_execution();
|
|
26372
|
-
});
|
|
26373
|
-
|
|
26374
|
-
// src/agents/shared/validation.ts
|
|
26375
|
-
function validateAgentForTier(agent, tier) {
|
|
26376
|
-
return agent.capabilities.supportedTiers.includes(tier);
|
|
26377
|
-
}
|
|
26378
|
-
function validateAgentFeature(agent, feature) {
|
|
26379
|
-
return agent.capabilities.features.has(feature);
|
|
26380
|
-
}
|
|
26381
|
-
function describeAgentCapabilities(agent) {
|
|
26382
|
-
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
26383
|
-
const features = Array.from(agent.capabilities.features).join(",");
|
|
26384
|
-
const maxTokens = agent.capabilities.maxContextTokens;
|
|
26385
|
-
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
26386
|
-
}
|
|
26387
|
-
|
|
26388
|
-
// src/agents/index.ts
|
|
26389
|
-
var exports_agents = {};
|
|
26390
|
-
__export(exports_agents, {
|
|
26391
|
-
validateAgentForTier: () => validateAgentForTier,
|
|
26392
|
-
validateAgentFeature: () => validateAgentFeature,
|
|
26393
|
-
parseTokenUsage: () => parseTokenUsage,
|
|
26394
|
-
getInstalledAgents: () => getInstalledAgents,
|
|
26395
|
-
getAllAgentNames: () => getAllAgentNames,
|
|
26396
|
-
getAgentVersions: () => getAgentVersions,
|
|
26397
|
-
getAgentVersion: () => getAgentVersion,
|
|
26398
|
-
getAgent: () => getAgent,
|
|
26399
|
-
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
26400
|
-
estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
|
|
26401
|
-
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
26402
|
-
estimateCostByDuration: () => estimateCostByDuration,
|
|
26403
|
-
estimateCost: () => estimateCost,
|
|
26404
|
-
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
26405
|
-
checkAgentHealth: () => checkAgentHealth,
|
|
26406
|
-
MODEL_PRICING: () => MODEL_PRICING,
|
|
26407
|
-
CompleteError: () => CompleteError,
|
|
26408
|
-
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
26409
|
-
COST_RATES: () => COST_RATES,
|
|
26410
|
-
AllAgentsUnavailableError: () => AllAgentsUnavailableError
|
|
26411
|
-
});
|
|
26412
|
-
var init_agents = __esm(() => {
|
|
26413
|
-
init_types2();
|
|
26414
|
-
init_claude();
|
|
26415
|
-
init_registry();
|
|
26416
|
-
init_cost();
|
|
26417
|
-
init_version_detection();
|
|
26418
|
-
init_errors();
|
|
26419
|
-
});
|
|
26420
|
-
|
|
26421
26585
|
// src/quality/runner.ts
|
|
26422
26586
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
26423
26587
|
async function runQualityCommand(opts) {
|
|
@@ -26720,7 +26884,60 @@ Do NOT add new features \u2014 only fix the identified issues.
|
|
|
26720
26884
|
Commit your fixes when done.${scopeConstraint}`;
|
|
26721
26885
|
}
|
|
26722
26886
|
|
|
26723
|
-
// src/
|
|
26887
|
+
// src/agents/claude/index.ts
|
|
26888
|
+
var init_claude = __esm(() => {
|
|
26889
|
+
init_adapter3();
|
|
26890
|
+
init_execution();
|
|
26891
|
+
});
|
|
26892
|
+
|
|
26893
|
+
// src/agents/shared/validation.ts
|
|
26894
|
+
function validateAgentForTier(agent, tier) {
|
|
26895
|
+
return agent.capabilities.supportedTiers.includes(tier);
|
|
26896
|
+
}
|
|
26897
|
+
function validateAgentFeature(agent, feature) {
|
|
26898
|
+
return agent.capabilities.features.has(feature);
|
|
26899
|
+
}
|
|
26900
|
+
function describeAgentCapabilities(agent) {
|
|
26901
|
+
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
26902
|
+
const features = Array.from(agent.capabilities.features).join(",");
|
|
26903
|
+
const maxTokens = agent.capabilities.maxContextTokens;
|
|
26904
|
+
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
26905
|
+
}
|
|
26906
|
+
|
|
26907
|
+
// src/agents/index.ts
|
|
26908
|
+
var exports_agents = {};
|
|
26909
|
+
__export(exports_agents, {
|
|
26910
|
+
validateAgentForTier: () => validateAgentForTier,
|
|
26911
|
+
validateAgentFeature: () => validateAgentFeature,
|
|
26912
|
+
parseTokenUsage: () => parseTokenUsage,
|
|
26913
|
+
getInstalledAgents: () => getInstalledAgents,
|
|
26914
|
+
getAllAgentNames: () => getAllAgentNames,
|
|
26915
|
+
getAgentVersions: () => getAgentVersions,
|
|
26916
|
+
getAgentVersion: () => getAgentVersion,
|
|
26917
|
+
getAgent: () => getAgent,
|
|
26918
|
+
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
26919
|
+
estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
|
|
26920
|
+
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
26921
|
+
estimateCostByDuration: () => estimateCostByDuration,
|
|
26922
|
+
estimateCost: () => estimateCost,
|
|
26923
|
+
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
26924
|
+
checkAgentHealth: () => checkAgentHealth,
|
|
26925
|
+
MODEL_PRICING: () => MODEL_PRICING,
|
|
26926
|
+
CompleteError: () => CompleteError,
|
|
26927
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
26928
|
+
COST_RATES: () => COST_RATES,
|
|
26929
|
+
AllAgentsUnavailableError: () => AllAgentsUnavailableError
|
|
26930
|
+
});
|
|
26931
|
+
var init_agents = __esm(() => {
|
|
26932
|
+
init_types2();
|
|
26933
|
+
init_claude();
|
|
26934
|
+
init_registry();
|
|
26935
|
+
init_cost();
|
|
26936
|
+
init_version_detection();
|
|
26937
|
+
init_errors();
|
|
26938
|
+
});
|
|
26939
|
+
|
|
26940
|
+
// src/review/dialogue-prompts.ts
|
|
26724
26941
|
function buildReviewPrompt(diff, story, _semanticConfig) {
|
|
26725
26942
|
const criteria = story.acceptanceCriteria.map((c) => `- ${c}`).join(`
|
|
26726
26943
|
`);
|
|
@@ -26754,6 +26971,98 @@ function buildReReviewPrompt(updatedDiff, previousFindings) {
|
|
|
26754
26971
|
].join(`
|
|
26755
26972
|
`);
|
|
26756
26973
|
}
|
|
26974
|
+
function buildProposalsSection(proposals) {
|
|
26975
|
+
return proposals.map((p) => `### ${p.debater}
|
|
26976
|
+
${p.output}`).join(`
|
|
26977
|
+
|
|
26978
|
+
`);
|
|
26979
|
+
}
|
|
26980
|
+
function buildCritiquesSection(critiques) {
|
|
26981
|
+
if (critiques.length === 0)
|
|
26982
|
+
return "";
|
|
26983
|
+
return `
|
|
26984
|
+
|
|
26985
|
+
## Critiques
|
|
26986
|
+
${critiques.map((c, i) => `### Critique ${i + 1}
|
|
26987
|
+
${c}`).join(`
|
|
26988
|
+
|
|
26989
|
+
`)}`;
|
|
26990
|
+
}
|
|
26991
|
+
function buildVoteTallyLine(ctx) {
|
|
26992
|
+
if (!ctx.majorityVote)
|
|
26993
|
+
return "";
|
|
26994
|
+
const { passCount, failCount } = ctx.majorityVote;
|
|
26995
|
+
const failOpenNote = ctx.resolverType === "majority-fail-open" ? " (unparseable proposals count as pass)" : " (unparseable proposals count as fail)";
|
|
26996
|
+
return `
|
|
26997
|
+
|
|
26998
|
+
The preliminary majority vote is: **${passCount} passed, ${failCount} failed**${failOpenNote}. Verify the failing findings with tools before giving your authoritative verdict.`;
|
|
26999
|
+
}
|
|
27000
|
+
function buildResolverFraming(ctx) {
|
|
27001
|
+
switch (ctx.resolverType) {
|
|
27002
|
+
case "majority-fail-closed":
|
|
27003
|
+
case "majority-fail-open":
|
|
27004
|
+
return "You are the authoritative reviewer resolving a debate. A preliminary vote was taken \u2014 see tally below. Verify disputed findings using tools (READ files, GREP for usage) and give your final verdict.";
|
|
27005
|
+
case "synthesis":
|
|
27006
|
+
return "You are a synthesis reviewer. Synthesize the debater proposals into a single, coherent, tool-verified verdict. Use READ and GREP to verify claims before ruling.";
|
|
27007
|
+
case "custom":
|
|
27008
|
+
return "You are the judge. Evaluate the debater proposals independently. Verify claims with tools (READ, GREP) and give your final authoritative verdict.";
|
|
27009
|
+
default:
|
|
27010
|
+
return "You are the reviewer. Evaluate the debater proposals and give your final authoritative verdict.";
|
|
27011
|
+
}
|
|
27012
|
+
}
|
|
27013
|
+
function buildDebateResolverPrompt(proposals, critiques, diff, story, _semanticConfig, resolverContext) {
|
|
27014
|
+
const criteria = story.acceptanceCriteria.map((c) => `- ${c}`).join(`
|
|
27015
|
+
`);
|
|
27016
|
+
const framing = buildResolverFraming(resolverContext);
|
|
27017
|
+
const voteTally = buildVoteTallyLine(resolverContext);
|
|
27018
|
+
const proposalsSection = buildProposalsSection(proposals);
|
|
27019
|
+
const critiquesSection = buildCritiquesSection(critiques);
|
|
27020
|
+
return [
|
|
27021
|
+
framing,
|
|
27022
|
+
"",
|
|
27023
|
+
`## Story ${story.id}: ${story.title}`,
|
|
27024
|
+
"",
|
|
27025
|
+
"## Acceptance Criteria",
|
|
27026
|
+
criteria,
|
|
27027
|
+
"",
|
|
27028
|
+
"## Debater Proposals",
|
|
27029
|
+
proposalsSection,
|
|
27030
|
+
critiquesSection,
|
|
27031
|
+
"",
|
|
27032
|
+
"## Diff",
|
|
27033
|
+
diff,
|
|
27034
|
+
voteTally,
|
|
27035
|
+
"",
|
|
27036
|
+
"Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string } }"
|
|
27037
|
+
].filter((line) => line !== undefined).join(`
|
|
27038
|
+
`);
|
|
27039
|
+
}
|
|
27040
|
+
function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFindings, resolverContext) {
|
|
27041
|
+
const framing = buildResolverFraming(resolverContext);
|
|
27042
|
+
const findingsList = previousFindings.length > 0 ? previousFindings.map((f) => `- ${f.ruleId}: ${f.message}`).join(`
|
|
27043
|
+
`) : "(none)";
|
|
27044
|
+
const proposalsSection = buildProposalsSection(proposals);
|
|
27045
|
+
const critiquesSection = buildCritiquesSection(critiques);
|
|
27046
|
+
return [
|
|
27047
|
+
`${framing} This is a re-review after implementer changes.`,
|
|
27048
|
+
"",
|
|
27049
|
+
"## Previous Findings",
|
|
27050
|
+
findingsList,
|
|
27051
|
+
"",
|
|
27052
|
+
"## Updated Debater Proposals",
|
|
27053
|
+
proposalsSection,
|
|
27054
|
+
critiquesSection,
|
|
27055
|
+
"",
|
|
27056
|
+
"## Updated Diff",
|
|
27057
|
+
updatedDiff,
|
|
27058
|
+
"",
|
|
27059
|
+
"Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string }, deltaSummary: string }",
|
|
27060
|
+
"deltaSummary should describe which previous findings are resolved vs still present."
|
|
27061
|
+
].filter((line) => line !== undefined).join(`
|
|
27062
|
+
`);
|
|
27063
|
+
}
|
|
27064
|
+
|
|
27065
|
+
// src/review/dialogue.ts
|
|
26757
27066
|
function extractDeltaSummary(rawOutput, previousFindings, newFindings) {
|
|
26758
27067
|
try {
|
|
26759
27068
|
const parsed = JSON.parse(rawOutput);
|
|
@@ -26825,6 +27134,7 @@ function createReviewerSession(agent, storyId, workdir, featureName, _config) {
|
|
|
26825
27134
|
let lastCheckResult = null;
|
|
26826
27135
|
let lastStory = null;
|
|
26827
27136
|
let lastSemanticConfig = null;
|
|
27137
|
+
let lastWasDebateResolve = false;
|
|
26828
27138
|
const sessionState = {
|
|
26829
27139
|
generation: 1,
|
|
26830
27140
|
pendingCompactionContext: null
|
|
@@ -26883,10 +27193,12 @@ ${prompt}`,
|
|
|
26883
27193
|
history.push({ role: "implementer", content: prompt });
|
|
26884
27194
|
history.push({ role: "reviewer", content: result.output });
|
|
26885
27195
|
const parsed = parseReviewResponse(result.output);
|
|
26886
|
-
|
|
27196
|
+
const reviewResult = { ...parsed, cost: result.estimatedCost ?? 0 };
|
|
27197
|
+
lastCheckResult = reviewResult;
|
|
26887
27198
|
lastStory = story;
|
|
26888
27199
|
lastSemanticConfig = semanticConfig;
|
|
26889
|
-
|
|
27200
|
+
lastWasDebateResolve = false;
|
|
27201
|
+
return reviewResult;
|
|
26890
27202
|
},
|
|
26891
27203
|
async reReview(updatedDiff) {
|
|
26892
27204
|
if (!active) {
|
|
@@ -26920,7 +27232,7 @@ ${prompt}`,
|
|
|
26920
27232
|
history.push({ role: "reviewer", content: result.output });
|
|
26921
27233
|
const parsed = parseReviewResponse(result.output);
|
|
26922
27234
|
const deltaSummary = extractDeltaSummary(result.output, previousFindings, parsed.checkResult.findings);
|
|
26923
|
-
const dialogueResult = { ...parsed, deltaSummary };
|
|
27235
|
+
const dialogueResult = { ...parsed, deltaSummary, cost: result.estimatedCost ?? 0 };
|
|
26924
27236
|
lastCheckResult = dialogueResult;
|
|
26925
27237
|
const maxMessages = _config.review?.dialogue?.maxDialogueMessages ?? 20;
|
|
26926
27238
|
if (history.length > maxMessages) {
|
|
@@ -26955,6 +27267,76 @@ ${prompt}`,
|
|
|
26955
27267
|
history.push({ role: "reviewer", content: result.output });
|
|
26956
27268
|
return result.output;
|
|
26957
27269
|
},
|
|
27270
|
+
async resolveDebate(proposals, critiques, diff, story, semanticConfig, resolverContext) {
|
|
27271
|
+
if (!active) {
|
|
27272
|
+
throw new NaxError(`[dialogue] ReviewerSession for story ${storyId} has been destroyed`, "REVIEWER_SESSION_DESTROYED", { stage: "review", storyId, featureName });
|
|
27273
|
+
}
|
|
27274
|
+
const prompt = buildDebateResolverPrompt(proposals, critiques, diff, story, semanticConfig, resolverContext);
|
|
27275
|
+
const { modelTier, modelDef, timeoutSeconds } = resolveRunParams(semanticConfig);
|
|
27276
|
+
const { effectivePrompt, acpSessionName } = buildEffectiveRunArgs(prompt);
|
|
27277
|
+
const result = await agent.run({
|
|
27278
|
+
prompt: effectivePrompt,
|
|
27279
|
+
workdir,
|
|
27280
|
+
modelTier,
|
|
27281
|
+
modelDef,
|
|
27282
|
+
timeoutSeconds,
|
|
27283
|
+
sessionRole: "reviewer",
|
|
27284
|
+
keepSessionOpen: true,
|
|
27285
|
+
pipelineStage: "review",
|
|
27286
|
+
config: _config,
|
|
27287
|
+
storyId,
|
|
27288
|
+
featureName,
|
|
27289
|
+
acpSessionName
|
|
27290
|
+
});
|
|
27291
|
+
history.push({ role: "implementer", content: prompt });
|
|
27292
|
+
history.push({ role: "reviewer", content: result.output });
|
|
27293
|
+
const parsed = parseReviewResponse(result.output);
|
|
27294
|
+
const reviewResult = { ...parsed, cost: result.estimatedCost ?? 0 };
|
|
27295
|
+
lastCheckResult = reviewResult;
|
|
27296
|
+
lastStory = story;
|
|
27297
|
+
lastSemanticConfig = semanticConfig;
|
|
27298
|
+
lastWasDebateResolve = true;
|
|
27299
|
+
return reviewResult;
|
|
27300
|
+
},
|
|
27301
|
+
async reReviewDebate(proposals, critiques, updatedDiff, resolverContext) {
|
|
27302
|
+
if (!active) {
|
|
27303
|
+
throw new NaxError(`[dialogue] ReviewerSession for story ${storyId} has been destroyed`, "REVIEWER_SESSION_DESTROYED", { stage: "review", storyId, featureName });
|
|
27304
|
+
}
|
|
27305
|
+
if (!lastCheckResult || !lastSemanticConfig || !lastWasDebateResolve) {
|
|
27306
|
+
throw new NaxError(`[dialogue] reReviewDebate() called before any resolveDebate() on story ${storyId}`, "NO_REVIEW_RESULT", { stage: "review", storyId });
|
|
27307
|
+
}
|
|
27308
|
+
const previousFindings = lastCheckResult.checkResult.findings;
|
|
27309
|
+
const prompt = buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFindings, resolverContext);
|
|
27310
|
+
const { modelTier, modelDef, timeoutSeconds } = resolveRunParams(lastSemanticConfig);
|
|
27311
|
+
const { effectivePrompt, acpSessionName } = buildEffectiveRunArgs(prompt);
|
|
27312
|
+
const result = await agent.run({
|
|
27313
|
+
prompt: effectivePrompt,
|
|
27314
|
+
workdir,
|
|
27315
|
+
modelTier,
|
|
27316
|
+
modelDef,
|
|
27317
|
+
timeoutSeconds,
|
|
27318
|
+
sessionRole: "reviewer",
|
|
27319
|
+
keepSessionOpen: true,
|
|
27320
|
+
pipelineStage: "review",
|
|
27321
|
+
config: _config,
|
|
27322
|
+
storyId,
|
|
27323
|
+
featureName,
|
|
27324
|
+
acpSessionName
|
|
27325
|
+
});
|
|
27326
|
+
history.push({ role: "implementer", content: prompt });
|
|
27327
|
+
history.push({ role: "reviewer", content: result.output });
|
|
27328
|
+
const parsed = parseReviewResponse(result.output);
|
|
27329
|
+
const deltaSummary = extractDeltaSummary(result.output, previousFindings, parsed.checkResult.findings);
|
|
27330
|
+
const dialogueResult = { ...parsed, deltaSummary, cost: result.estimatedCost ?? 0 };
|
|
27331
|
+
lastCheckResult = dialogueResult;
|
|
27332
|
+
const maxMessages = _config.review?.dialogue?.maxDialogueMessages ?? 20;
|
|
27333
|
+
if (history.length > maxMessages) {
|
|
27334
|
+
const compactedSummary = compactHistory(history);
|
|
27335
|
+
sessionState.generation++;
|
|
27336
|
+
sessionState.pendingCompactionContext = compactedSummary;
|
|
27337
|
+
}
|
|
27338
|
+
return dialogueResult;
|
|
27339
|
+
},
|
|
26958
27340
|
getVerdict() {
|
|
26959
27341
|
if (!lastCheckResult || !lastStory) {
|
|
26960
27342
|
throw new NaxError(`[dialogue] getVerdict() called before any review() on story ${storyId}`, "NO_REVIEW_RESULT", { stage: "review", storyId });
|
|
@@ -27190,10 +27572,8 @@ var init_language_commands = __esm(() => {
|
|
|
27190
27572
|
// src/review/semantic.ts
|
|
27191
27573
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
27192
27574
|
async function collectDiff(workdir, storyGitRef, excludePatterns) {
|
|
27193
|
-
const
|
|
27194
|
-
|
|
27195
|
-
cmd.push("--", ".", ...excludePatterns);
|
|
27196
|
-
}
|
|
27575
|
+
const merged = [...new Set([...excludePatterns, ...ALWAYS_EXCLUDED])];
|
|
27576
|
+
const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`, "--", ".", ...merged];
|
|
27197
27577
|
const proc = _semanticDeps.spawn({
|
|
27198
27578
|
cmd,
|
|
27199
27579
|
cwd: workdir,
|
|
@@ -27353,7 +27733,7 @@ function toReviewFindings(findings) {
|
|
|
27353
27733
|
source: "semantic-review"
|
|
27354
27734
|
}));
|
|
27355
27735
|
}
|
|
27356
|
-
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig, featureName) {
|
|
27736
|
+
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig, featureName, resolverSession) {
|
|
27357
27737
|
const startTime = Date.now();
|
|
27358
27738
|
const logger = getSafeLogger();
|
|
27359
27739
|
if (featureName === undefined) {
|
|
@@ -27423,6 +27803,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
27423
27803
|
const reviewDebateEnabled = naxConfig?.debate?.enabled && naxConfig?.debate?.stages?.review?.enabled;
|
|
27424
27804
|
if (reviewDebateEnabled) {
|
|
27425
27805
|
const reviewStageConfig = naxConfig?.debate?.stages.review;
|
|
27806
|
+
const isReReview = resolverSession !== undefined && resolverSession.history.length > 0;
|
|
27426
27807
|
const debateSession = _semanticDeps.createDebateSession({
|
|
27427
27808
|
storyId: story.id,
|
|
27428
27809
|
stage: "review",
|
|
@@ -27430,25 +27811,69 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
27430
27811
|
config: naxConfig ?? DEFAULT_CONFIG,
|
|
27431
27812
|
workdir,
|
|
27432
27813
|
featureName,
|
|
27433
|
-
timeoutSeconds: naxConfig?.execution?.sessionTimeoutSeconds
|
|
27814
|
+
timeoutSeconds: naxConfig?.execution?.sessionTimeoutSeconds,
|
|
27815
|
+
reviewerSession: resolverSession,
|
|
27816
|
+
resolverContextInput: resolverSession ? {
|
|
27817
|
+
diff,
|
|
27818
|
+
story: { id: story.id, title: story.title, acceptanceCriteria: story.acceptanceCriteria },
|
|
27819
|
+
semanticConfig,
|
|
27820
|
+
resolverType: reviewStageConfig.resolver.type,
|
|
27821
|
+
isReReview
|
|
27822
|
+
} : undefined
|
|
27434
27823
|
});
|
|
27824
|
+
const historyLenBefore = resolverSession?.history.length ?? 0;
|
|
27435
27825
|
const debateResult = await debateSession.run(prompt);
|
|
27436
|
-
|
|
27437
|
-
|
|
27826
|
+
const debateCost = debateResult.totalCostUsd ?? 0;
|
|
27827
|
+
const sessionUsed = resolverSession && resolverSession.history.length > historyLenBefore;
|
|
27828
|
+
if (sessionUsed) {
|
|
27829
|
+
const durationMs3 = Date.now() - startTime;
|
|
27830
|
+
try {
|
|
27831
|
+
const verdict = resolverSession.getVerdict();
|
|
27832
|
+
const findings = verdict.findings ?? [];
|
|
27833
|
+
if (!verdict.passed && findings.length > 0) {
|
|
27834
|
+
logger?.warn("review", `Semantic review failed (debate+dialogue): ${findings.length} findings`, {
|
|
27835
|
+
storyId: story.id,
|
|
27836
|
+
durationMs: durationMs3
|
|
27837
|
+
});
|
|
27838
|
+
return {
|
|
27839
|
+
check: "semantic",
|
|
27840
|
+
success: false,
|
|
27841
|
+
command: "",
|
|
27842
|
+
exitCode: 1,
|
|
27843
|
+
output: `Semantic review failed:
|
|
27844
|
+
|
|
27845
|
+
${findings.map((f) => `${f.ruleId}: ${f.message}`).join(`
|
|
27846
|
+
`)}`,
|
|
27847
|
+
durationMs: durationMs3,
|
|
27848
|
+
findings,
|
|
27849
|
+
cost: debateCost
|
|
27850
|
+
};
|
|
27851
|
+
}
|
|
27852
|
+
const label = verdict.passed ? "Semantic review passed (debate+dialogue)" : "Semantic review passed (debate+dialogue, all findings non-blocking)";
|
|
27853
|
+
logger?.info("review", label, { storyId: story.id, durationMs: durationMs3 });
|
|
27854
|
+
return {
|
|
27855
|
+
check: "semantic",
|
|
27856
|
+
success: true,
|
|
27857
|
+
command: "",
|
|
27858
|
+
exitCode: 0,
|
|
27859
|
+
output: label,
|
|
27860
|
+
durationMs: durationMs3,
|
|
27861
|
+
cost: debateCost
|
|
27862
|
+
};
|
|
27863
|
+
} catch {
|
|
27864
|
+
logger?.warn("review", "getVerdict() failed after debate+dialogue \u2014 falling back to stateless verdict", {
|
|
27865
|
+
storyId: story.id
|
|
27866
|
+
});
|
|
27867
|
+
}
|
|
27868
|
+
}
|
|
27869
|
+
const resolverPassed = debateResult.outcome === "passed";
|
|
27438
27870
|
const allFindings = [];
|
|
27439
27871
|
for (const p of debateResult.proposals) {
|
|
27440
27872
|
const parsed2 = parseLLMResponse(p.output);
|
|
27441
27873
|
if (parsed2) {
|
|
27442
|
-
if (parsed2.passed)
|
|
27443
|
-
passCount++;
|
|
27444
|
-
else
|
|
27445
|
-
failCount++;
|
|
27446
27874
|
allFindings.push(...parsed2.findings);
|
|
27447
|
-
} else {
|
|
27448
|
-
failCount++;
|
|
27449
27875
|
}
|
|
27450
27876
|
}
|
|
27451
|
-
const majorityPassed = passCount > failCount;
|
|
27452
27877
|
const seen = new Set;
|
|
27453
27878
|
const deduped = [];
|
|
27454
27879
|
for (const f of allFindings) {
|
|
@@ -27460,7 +27885,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
27460
27885
|
}
|
|
27461
27886
|
const debateBlocking = deduped.filter((f) => isBlockingSeverity(f.severity));
|
|
27462
27887
|
const durationMs2 = Date.now() - startTime;
|
|
27463
|
-
if (!
|
|
27888
|
+
if (!resolverPassed) {
|
|
27464
27889
|
if (debateBlocking.length > 0) {
|
|
27465
27890
|
logger?.warn("review", `Semantic review failed (debate): ${debateBlocking.length} findings`, {
|
|
27466
27891
|
storyId: story.id,
|
|
@@ -27475,7 +27900,8 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
27475
27900
|
|
|
27476
27901
|
${formatFindings(debateBlocking)}`,
|
|
27477
27902
|
durationMs: durationMs2,
|
|
27478
|
-
findings: toReviewFindings(debateBlocking)
|
|
27903
|
+
findings: toReviewFindings(debateBlocking),
|
|
27904
|
+
cost: debateCost
|
|
27479
27905
|
};
|
|
27480
27906
|
}
|
|
27481
27907
|
logger?.info("review", "Semantic review passed (debate, all findings non-blocking)", {
|
|
@@ -27488,7 +27914,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27488
27914
|
command: "",
|
|
27489
27915
|
exitCode: 0,
|
|
27490
27916
|
output: "Semantic review passed (debate, all findings were unverifiable or informational)",
|
|
27491
|
-
durationMs: durationMs2
|
|
27917
|
+
durationMs: durationMs2,
|
|
27918
|
+
cost: debateCost
|
|
27492
27919
|
};
|
|
27493
27920
|
}
|
|
27494
27921
|
logger?.info("review", "Semantic review passed (debate)", { storyId: story.id, durationMs: durationMs2 });
|
|
@@ -27498,7 +27925,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27498
27925
|
command: "",
|
|
27499
27926
|
exitCode: 0,
|
|
27500
27927
|
output: "Semantic review passed",
|
|
27501
|
-
durationMs: durationMs2
|
|
27928
|
+
durationMs: durationMs2,
|
|
27929
|
+
cost: debateCost
|
|
27502
27930
|
};
|
|
27503
27931
|
}
|
|
27504
27932
|
const implementerSidecarKey = `${story.id}:implementer`;
|
|
@@ -27517,6 +27945,7 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27517
27945
|
}
|
|
27518
27946
|
} catch {}
|
|
27519
27947
|
let rawResponse;
|
|
27948
|
+
let llmCost = 0;
|
|
27520
27949
|
try {
|
|
27521
27950
|
let runErr;
|
|
27522
27951
|
let runSucceeded = false;
|
|
@@ -27533,6 +27962,7 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27533
27962
|
config: naxConfig ?? DEFAULT_CONFIG
|
|
27534
27963
|
});
|
|
27535
27964
|
runOutput = runResult.output;
|
|
27965
|
+
llmCost = runResult.estimatedCost ?? 0;
|
|
27536
27966
|
runSucceeded = true;
|
|
27537
27967
|
} catch (err) {
|
|
27538
27968
|
runErr = err;
|
|
@@ -27548,6 +27978,7 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27548
27978
|
config: naxConfig ?? DEFAULT_CONFIG
|
|
27549
27979
|
});
|
|
27550
27980
|
rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
|
|
27981
|
+
llmCost = typeof completeResult === "string" ? 0 : completeResult.costUsd ?? 0;
|
|
27551
27982
|
}
|
|
27552
27983
|
} catch (err) {
|
|
27553
27984
|
logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
|
|
@@ -27573,7 +28004,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27573
28004
|
command: "",
|
|
27574
28005
|
exitCode: 1,
|
|
27575
28006
|
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
27576
|
-
durationMs: Date.now() - startTime
|
|
28007
|
+
durationMs: Date.now() - startTime,
|
|
28008
|
+
cost: llmCost
|
|
27577
28009
|
};
|
|
27578
28010
|
}
|
|
27579
28011
|
logger?.warn("semantic", "LLM returned invalid JSON \u2014 fail-open", { rawResponse: rawResponse.slice(0, 200) });
|
|
@@ -27583,7 +28015,8 @@ ${formatFindings(debateBlocking)}`,
|
|
|
27583
28015
|
command: "",
|
|
27584
28016
|
exitCode: 0,
|
|
27585
28017
|
output: "semantic review: could not parse LLM response (fail-open)",
|
|
27586
|
-
durationMs: Date.now() - startTime
|
|
28018
|
+
durationMs: Date.now() - startTime,
|
|
28019
|
+
cost: llmCost
|
|
27587
28020
|
};
|
|
27588
28021
|
}
|
|
27589
28022
|
const blockingFindings = parsed.findings.filter((f) => isBlockingSeverity(f.severity));
|
|
@@ -27620,7 +28053,8 @@ ${formatFindings(blockingFindings)}`;
|
|
|
27620
28053
|
exitCode: 1,
|
|
27621
28054
|
output,
|
|
27622
28055
|
durationMs: durationMs2,
|
|
27623
|
-
findings: toReviewFindings(blockingFindings)
|
|
28056
|
+
findings: toReviewFindings(blockingFindings),
|
|
28057
|
+
cost: llmCost
|
|
27624
28058
|
};
|
|
27625
28059
|
}
|
|
27626
28060
|
if (!parsed.passed && blockingFindings.length === 0) {
|
|
@@ -27632,7 +28066,8 @@ ${formatFindings(blockingFindings)}`;
|
|
|
27632
28066
|
command: "",
|
|
27633
28067
|
exitCode: 0,
|
|
27634
28068
|
output: "Semantic review passed (all findings were unverifiable or informational)",
|
|
27635
|
-
durationMs: durationMs2
|
|
28069
|
+
durationMs: durationMs2,
|
|
28070
|
+
cost: llmCost
|
|
27636
28071
|
};
|
|
27637
28072
|
}
|
|
27638
28073
|
const durationMs = Date.now() - startTime;
|
|
@@ -27645,10 +28080,11 @@ ${formatFindings(blockingFindings)}`;
|
|
|
27645
28080
|
command: "",
|
|
27646
28081
|
exitCode: parsed.passed ? 0 : 1,
|
|
27647
28082
|
output: parsed.passed ? "Semantic review passed" : "Semantic review failed (no findings)",
|
|
27648
|
-
durationMs
|
|
28083
|
+
durationMs,
|
|
28084
|
+
cost: llmCost
|
|
27649
28085
|
};
|
|
27650
28086
|
}
|
|
27651
|
-
var _semanticDeps, DIFF_CAP_BYTES = 51200;
|
|
28087
|
+
var _semanticDeps, DIFF_CAP_BYTES = 51200, ALWAYS_EXCLUDED;
|
|
27652
28088
|
var init_semantic = __esm(() => {
|
|
27653
28089
|
init_adapter();
|
|
27654
28090
|
init_config();
|
|
@@ -27662,6 +28098,7 @@ var init_semantic = __esm(() => {
|
|
|
27662
28098
|
createDebateSession: (opts) => new DebateSession(opts),
|
|
27663
28099
|
readAcpSession
|
|
27664
28100
|
};
|
|
28101
|
+
ALWAYS_EXCLUDED = [":!.nax/", ":!.nax-pids"];
|
|
27665
28102
|
});
|
|
27666
28103
|
|
|
27667
28104
|
// src/review/runner.ts
|
|
@@ -27746,7 +28183,7 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
27746
28183
|
return [];
|
|
27747
28184
|
}
|
|
27748
28185
|
}
|
|
27749
|
-
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName) {
|
|
28186
|
+
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName, resolverSession) {
|
|
27750
28187
|
const startTime = Date.now();
|
|
27751
28188
|
const logger = getSafeLogger();
|
|
27752
28189
|
const checks3 = [];
|
|
@@ -27803,10 +28240,19 @@ Stage and commit these files before running review.`
|
|
|
27803
28240
|
modelTier: "balanced",
|
|
27804
28241
|
rules: [],
|
|
27805
28242
|
timeoutMs: 600000,
|
|
27806
|
-
excludePatterns: [
|
|
28243
|
+
excludePatterns: [
|
|
28244
|
+
":!test/",
|
|
28245
|
+
":!tests/",
|
|
28246
|
+
":!*_test.go",
|
|
28247
|
+
":!*.test.ts",
|
|
28248
|
+
":!*.spec.ts",
|
|
28249
|
+
":!**/__tests__/",
|
|
28250
|
+
":!.nax/",
|
|
28251
|
+
":!.nax-pids"
|
|
28252
|
+
]
|
|
27807
28253
|
};
|
|
27808
28254
|
const runSemantic = _reviewSemanticDeps.runSemanticReview;
|
|
27809
|
-
const result2 =
|
|
28255
|
+
const result2 = await runSemantic(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig, featureName, resolverSession);
|
|
27810
28256
|
checks3.push(result2);
|
|
27811
28257
|
if (!result2.success && !firstFailure) {
|
|
27812
28258
|
firstFailure = `${checkName} failed`;
|
|
@@ -27888,9 +28334,9 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
27888
28334
|
}
|
|
27889
28335
|
|
|
27890
28336
|
class ReviewOrchestrator {
|
|
27891
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig, retrySkipChecks, featureName) {
|
|
28337
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig, retrySkipChecks, featureName, resolverSession) {
|
|
27892
28338
|
const logger = getSafeLogger();
|
|
27893
|
-
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName);
|
|
28339
|
+
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName, resolverSession);
|
|
27894
28340
|
if (!builtIn.success) {
|
|
27895
28341
|
return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
|
|
27896
28342
|
}
|
|
@@ -27951,6 +28397,21 @@ class ReviewOrchestrator {
|
|
|
27951
28397
|
}
|
|
27952
28398
|
return { builtIn, success: true, pluginFailed: false };
|
|
27953
28399
|
}
|
|
28400
|
+
reviewFromContext(ctx) {
|
|
28401
|
+
const retrySkipChecks = ctx.retrySkipChecks;
|
|
28402
|
+
ctx.retrySkipChecks = undefined;
|
|
28403
|
+
const agentResolver = ctx.agentGetFn ?? undefined;
|
|
28404
|
+
const agentName = ctx.rootConfig.autoMode?.defaultAgent;
|
|
28405
|
+
const modelResolver = agentName ? (_tier) => agentResolver ? agentResolver(agentName) ?? null : null : undefined;
|
|
28406
|
+
const reviewDebateEnabled = ctx.rootConfig?.debate?.enabled && ctx.rootConfig?.debate?.stages?.review?.enabled;
|
|
28407
|
+
const resolverSession = reviewDebateEnabled ? ctx.reviewerSession : undefined;
|
|
28408
|
+
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, {
|
|
28409
|
+
id: ctx.story.id,
|
|
28410
|
+
title: ctx.story.title,
|
|
28411
|
+
description: ctx.story.description,
|
|
28412
|
+
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
28413
|
+
}, modelResolver, ctx.config, retrySkipChecks, ctx.prd.feature, resolverSession);
|
|
28414
|
+
}
|
|
27954
28415
|
}
|
|
27955
28416
|
var _orchestratorDeps, reviewOrchestrator;
|
|
27956
28417
|
var init_orchestrator = __esm(() => {
|
|
@@ -27966,7 +28427,6 @@ __export(exports_review, {
|
|
|
27966
28427
|
reviewStage: () => reviewStage,
|
|
27967
28428
|
_reviewDeps: () => _reviewDeps
|
|
27968
28429
|
});
|
|
27969
|
-
import { join as join17 } from "path";
|
|
27970
28430
|
var reviewStage, _reviewDeps;
|
|
27971
28431
|
var init_review = __esm(() => {
|
|
27972
28432
|
init_agents();
|
|
@@ -27976,19 +28436,15 @@ var init_review = __esm(() => {
|
|
|
27976
28436
|
init_orchestrator();
|
|
27977
28437
|
reviewStage = {
|
|
27978
28438
|
name: "review",
|
|
27979
|
-
enabled: (ctx) =>
|
|
28439
|
+
enabled: (ctx) => ctx.config.review.enabled,
|
|
27980
28440
|
async execute(ctx) {
|
|
27981
28441
|
const logger = getLogger();
|
|
27982
|
-
const
|
|
27983
|
-
const dialogueEnabled =
|
|
28442
|
+
const reviewDebateEnabled = ctx.rootConfig?.debate?.enabled && ctx.rootConfig?.debate?.stages?.review?.enabled;
|
|
28443
|
+
const dialogueEnabled = ctx.config.review?.dialogue?.enabled ?? false;
|
|
27984
28444
|
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
28445
|
const agentResolver = ctx.agentGetFn ?? getAgent;
|
|
27987
|
-
const agentName =
|
|
27988
|
-
|
|
27989
|
-
const retrySkipChecks = ctx.retrySkipChecks;
|
|
27990
|
-
ctx.retrySkipChecks = undefined;
|
|
27991
|
-
if (dialogueEnabled && ctx.reviewerSession) {
|
|
28446
|
+
const agentName = ctx.rootConfig.autoMode?.defaultAgent;
|
|
28447
|
+
if (dialogueEnabled && !reviewDebateEnabled && ctx.reviewerSession) {
|
|
27992
28448
|
try {
|
|
27993
28449
|
const diff = ctx.storyGitRef ?? "";
|
|
27994
28450
|
const reReviewResult = await ctx.reviewerSession.reReview(diff);
|
|
@@ -28025,57 +28481,56 @@ var init_review = __esm(() => {
|
|
|
28025
28481
|
}
|
|
28026
28482
|
if (dialogueEnabled && !ctx.reviewerSession) {
|
|
28027
28483
|
const agent = agentName ? agentResolver(agentName) ?? null : null;
|
|
28028
|
-
ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id,
|
|
28029
|
-
|
|
28030
|
-
|
|
28031
|
-
|
|
28032
|
-
|
|
28033
|
-
|
|
28034
|
-
|
|
28035
|
-
|
|
28036
|
-
|
|
28037
|
-
|
|
28038
|
-
|
|
28039
|
-
|
|
28040
|
-
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28484
|
+
ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id, ctx.workdir, ctx.prd.feature ?? "", ctx.config);
|
|
28485
|
+
if (!reviewDebateEnabled) {
|
|
28486
|
+
const semanticConfig = ctx.config.review?.semantic;
|
|
28487
|
+
if (semanticConfig && agent) {
|
|
28488
|
+
try {
|
|
28489
|
+
const diff = ctx.storyGitRef ?? "";
|
|
28490
|
+
const story = {
|
|
28491
|
+
id: ctx.story.id,
|
|
28492
|
+
title: ctx.story.title,
|
|
28493
|
+
description: ctx.story.description,
|
|
28494
|
+
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
28495
|
+
};
|
|
28496
|
+
const sessionResult = await ctx.reviewerSession.review(diff, story, semanticConfig);
|
|
28497
|
+
const passed = sessionResult.checkResult.success;
|
|
28498
|
+
ctx.reviewResult = {
|
|
28499
|
+
success: passed,
|
|
28500
|
+
checks: passed ? [] : [
|
|
28501
|
+
{
|
|
28502
|
+
check: "semantic",
|
|
28503
|
+
success: false,
|
|
28504
|
+
command: "reviewer-session-review",
|
|
28505
|
+
exitCode: 1,
|
|
28506
|
+
output: sessionResult.checkResult.findings.map((f) => f.message).join(`
|
|
28050
28507
|
`),
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28056
|
-
|
|
28057
|
-
|
|
28058
|
-
|
|
28059
|
-
|
|
28060
|
-
|
|
28508
|
+
durationMs: 0,
|
|
28509
|
+
findings: sessionResult.checkResult.findings
|
|
28510
|
+
}
|
|
28511
|
+
],
|
|
28512
|
+
totalDurationMs: 0
|
|
28513
|
+
};
|
|
28514
|
+
const dialogueCost = sessionResult.cost ?? 0;
|
|
28515
|
+
if (passed) {
|
|
28516
|
+
logger.info("review", "Review passed (dialogue session)", { storyId: ctx.story.id });
|
|
28517
|
+
} else {
|
|
28518
|
+
logger.warn("review", "Review failed (dialogue session) \u2014 handing off to autofix", {
|
|
28519
|
+
storyId: ctx.story.id
|
|
28520
|
+
});
|
|
28521
|
+
}
|
|
28522
|
+
return { action: "continue", cost: dialogueCost || undefined };
|
|
28523
|
+
} catch (err) {
|
|
28524
|
+
logger.warn("review", "ReviewerSession.review() failed \u2014 falling back to one-shot review", {
|
|
28061
28525
|
storyId: ctx.story.id
|
|
28062
28526
|
});
|
|
28063
28527
|
}
|
|
28064
|
-
return { action: "continue" };
|
|
28065
|
-
} catch (err) {
|
|
28066
|
-
logger.warn("review", "ReviewerSession.review() failed \u2014 falling back to one-shot review", {
|
|
28067
|
-
storyId: ctx.story.id
|
|
28068
|
-
});
|
|
28069
28528
|
}
|
|
28070
28529
|
}
|
|
28071
28530
|
}
|
|
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);
|
|
28531
|
+
const result = await reviewOrchestrator.reviewFromContext(ctx);
|
|
28078
28532
|
ctx.reviewResult = result.builtIn;
|
|
28533
|
+
const reviewCost = (result.builtIn.checks ?? []).reduce((sum, c) => sum + (c.cost ?? 0), 0) || undefined;
|
|
28079
28534
|
if (!result.success) {
|
|
28080
28535
|
const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
28081
28536
|
const semanticFindings = (result.builtIn.checks ?? []).filter((c) => c.check === "semantic" && !c.success && c.findings?.length).flatMap((c) => c.findings ?? []);
|
|
@@ -28084,29 +28539,29 @@ var init_review = __esm(() => {
|
|
|
28084
28539
|
ctx.reviewFindings = allFindings;
|
|
28085
28540
|
}
|
|
28086
28541
|
if (result.pluginFailed) {
|
|
28087
|
-
if (ctx.interaction && isTriggerEnabled("security-review",
|
|
28088
|
-
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id },
|
|
28542
|
+
if (ctx.interaction && isTriggerEnabled("security-review", ctx.config)) {
|
|
28543
|
+
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
|
|
28089
28544
|
if (!shouldContinue) {
|
|
28090
28545
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
28091
|
-
return { action: "fail", reason: `Review failed: ${result.failureReason}
|
|
28546
|
+
return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
|
|
28092
28547
|
}
|
|
28093
28548
|
logger.warn("review", "Security-review trigger escalated \u2014 retrying story", { storyId: ctx.story.id });
|
|
28094
|
-
return { action: "escalate", reason: `Review failed: ${result.failureReason}
|
|
28549
|
+
return { action: "escalate", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
|
|
28095
28550
|
}
|
|
28096
28551
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
28097
|
-
return { action: "fail", reason: `Review failed: ${result.failureReason}
|
|
28552
|
+
return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
|
|
28098
28553
|
}
|
|
28099
28554
|
logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
|
|
28100
28555
|
reason: result.failureReason,
|
|
28101
28556
|
storyId: ctx.story.id
|
|
28102
28557
|
});
|
|
28103
|
-
return { action: "continue" };
|
|
28558
|
+
return { action: "continue", cost: reviewCost };
|
|
28104
28559
|
}
|
|
28105
28560
|
logger.info("review", "Review passed", {
|
|
28106
28561
|
durationMs: result.builtIn.totalDurationMs,
|
|
28107
28562
|
storyId: ctx.story.id
|
|
28108
28563
|
});
|
|
28109
|
-
return { action: "continue" };
|
|
28564
|
+
return { action: "continue", cost: reviewCost };
|
|
28110
28565
|
}
|
|
28111
28566
|
};
|
|
28112
28567
|
_reviewDeps = {
|
|
@@ -28116,7 +28571,6 @@ var init_review = __esm(() => {
|
|
|
28116
28571
|
});
|
|
28117
28572
|
|
|
28118
28573
|
// src/pipeline/stages/autofix.ts
|
|
28119
|
-
import { join as join18 } from "path";
|
|
28120
28574
|
async function recheckReview(ctx) {
|
|
28121
28575
|
const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
|
|
28122
28576
|
if (!reviewStage2.enabled(ctx))
|
|
@@ -28154,16 +28608,15 @@ Your previous fix attempt (attempt ${attempt}) did not resolve the quality error
|
|
|
28154
28608
|
}
|
|
28155
28609
|
async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir) {
|
|
28156
28610
|
const logger = getLogger();
|
|
28157
|
-
const
|
|
28158
|
-
const
|
|
28159
|
-
const
|
|
28160
|
-
const
|
|
28161
|
-
const urgencyAtAttempt = effectiveConfig.quality.autofix?.urgencyAtAttempt ?? 3;
|
|
28611
|
+
const maxPerCycle = ctx.config.quality.autofix?.maxAttempts ?? 2;
|
|
28612
|
+
const maxTotal = ctx.config.quality.autofix?.maxTotalAttempts ?? 10;
|
|
28613
|
+
const rethinkAtAttempt = ctx.config.quality.autofix?.rethinkAtAttempt ?? 2;
|
|
28614
|
+
const urgencyAtAttempt = ctx.config.quality.autofix?.urgencyAtAttempt ?? 3;
|
|
28162
28615
|
const consumed = ctx.autofixAttempt ?? 0;
|
|
28163
28616
|
const failedChecks = collectFailedChecks(ctx);
|
|
28164
28617
|
if (failedChecks.length === 0) {
|
|
28165
28618
|
logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
|
|
28166
|
-
return false;
|
|
28619
|
+
return { succeeded: false, cost: 0 };
|
|
28167
28620
|
}
|
|
28168
28621
|
if (consumed >= maxTotal) {
|
|
28169
28622
|
logger.warn("autofix", "Global autofix budget exhausted \u2014 escalating", {
|
|
@@ -28171,16 +28624,17 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28171
28624
|
totalAttempts: consumed,
|
|
28172
28625
|
maxTotalAttempts: maxTotal
|
|
28173
28626
|
});
|
|
28174
|
-
return false;
|
|
28627
|
+
return { succeeded: false, cost: 0 };
|
|
28175
28628
|
}
|
|
28176
28629
|
const remainingBudget = maxTotal - consumed;
|
|
28177
28630
|
const maxAttempts = Math.min(maxPerCycle, remainingBudget);
|
|
28178
|
-
const agentGetFn = ctx.agentGetFn ?? _autofixDeps.getAgent;
|
|
28631
|
+
const agentGetFn = ctx.agentGetFn ?? ((name) => _autofixDeps.getAgent(name, ctx.rootConfig));
|
|
28179
28632
|
const loopState = {
|
|
28180
28633
|
attempt: 0,
|
|
28181
28634
|
failedChecks
|
|
28182
28635
|
};
|
|
28183
|
-
|
|
28636
|
+
let autofixCostAccum = 0;
|
|
28637
|
+
const succeeded = await runSharedRectificationLoop({
|
|
28184
28638
|
stage: "autofix",
|
|
28185
28639
|
storyId: ctx.story.id,
|
|
28186
28640
|
maxAttempts,
|
|
@@ -28207,29 +28661,30 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28207
28661
|
},
|
|
28208
28662
|
runAttempt: async (attempt, prompt) => {
|
|
28209
28663
|
ctx.autofixAttempt = consumed + attempt;
|
|
28210
|
-
const agent = agentGetFn(ctx.
|
|
28664
|
+
const agent = agentGetFn(ctx.rootConfig.autoMode.defaultAgent);
|
|
28211
28665
|
if (!agent) {
|
|
28212
28666
|
logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
|
|
28213
28667
|
throw new Error("AUTOFIX_AGENT_NOT_FOUND");
|
|
28214
28668
|
}
|
|
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;
|
|
28669
|
+
const modelTier = ctx.story.routing?.modelTier ?? ctx.rootConfig.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
|
|
28670
|
+
const modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, modelTier, ctx.rootConfig.autoMode.defaultAgent);
|
|
28218
28671
|
const result = await agent.run({
|
|
28219
28672
|
prompt,
|
|
28220
|
-
workdir:
|
|
28673
|
+
workdir: ctx.workdir,
|
|
28221
28674
|
modelTier,
|
|
28222
28675
|
modelDef,
|
|
28223
28676
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
28224
28677
|
dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
|
|
28225
28678
|
pipelineStage: "rectification",
|
|
28226
28679
|
config: ctx.config,
|
|
28680
|
+
projectDir: ctx.projectDir,
|
|
28227
28681
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
28228
28682
|
storyId: ctx.story.id,
|
|
28229
28683
|
sessionRole: "implementer"
|
|
28230
28684
|
});
|
|
28685
|
+
autofixCostAccum += result.estimatedCost ?? 0;
|
|
28231
28686
|
if (ctx.reviewerSession && result.output) {
|
|
28232
|
-
const maxClarifications =
|
|
28687
|
+
const maxClarifications = ctx.config.review?.dialogue?.maxClarificationsPerAttempt ?? 3;
|
|
28233
28688
|
let clarifyCount = 0;
|
|
28234
28689
|
const clarifyRegex = new RegExp(CLARIFY_REGEX.source, `${CLARIFY_REGEX.flags}g`);
|
|
28235
28690
|
let match;
|
|
@@ -28312,10 +28767,11 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28312
28767
|
}
|
|
28313
28768
|
throw error48;
|
|
28314
28769
|
});
|
|
28770
|
+
return { succeeded, cost: autofixCostAccum };
|
|
28315
28771
|
}
|
|
28316
28772
|
var CLARIFY_REGEX, autofixStage, _autofixDeps;
|
|
28317
28773
|
var init_autofix = __esm(() => {
|
|
28318
|
-
|
|
28774
|
+
init_registry();
|
|
28319
28775
|
init_config();
|
|
28320
28776
|
init_loader();
|
|
28321
28777
|
init_logger2();
|
|
@@ -28329,7 +28785,7 @@ var init_autofix = __esm(() => {
|
|
|
28329
28785
|
return false;
|
|
28330
28786
|
if (ctx.reviewResult.success)
|
|
28331
28787
|
return false;
|
|
28332
|
-
const autofixEnabled =
|
|
28788
|
+
const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
|
|
28333
28789
|
return autofixEnabled;
|
|
28334
28790
|
},
|
|
28335
28791
|
skipReason(ctx) {
|
|
@@ -28343,16 +28799,14 @@ var init_autofix = __esm(() => {
|
|
|
28343
28799
|
if (!reviewResult || reviewResult.success) {
|
|
28344
28800
|
return { action: "continue" };
|
|
28345
28801
|
}
|
|
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;
|
|
28802
|
+
const lintFixCmd = ctx.config.quality.commands.lintFix ?? ctx.config.review.commands.lintFix;
|
|
28803
|
+
const formatFixCmd = ctx.config.quality.commands.formatFix ?? ctx.config.review.commands.formatFix;
|
|
28350
28804
|
const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
|
|
28351
28805
|
const hasLintFailure = failedCheckNames.has("lint");
|
|
28352
28806
|
logger.info("autofix", "Starting autofix", {
|
|
28353
28807
|
storyId: ctx.story.id,
|
|
28354
28808
|
failedChecks: [...failedCheckNames],
|
|
28355
|
-
workdir:
|
|
28809
|
+
workdir: ctx.workdir
|
|
28356
28810
|
});
|
|
28357
28811
|
if (hasLintFailure && (lintFixCmd || formatFixCmd)) {
|
|
28358
28812
|
if (lintFixCmd) {
|
|
@@ -28360,7 +28814,7 @@ var init_autofix = __esm(() => {
|
|
|
28360
28814
|
const lintResult = await _autofixDeps.runQualityCommand({
|
|
28361
28815
|
commandName: "lintFix",
|
|
28362
28816
|
command: lintFixCmd,
|
|
28363
|
-
workdir:
|
|
28817
|
+
workdir: ctx.workdir,
|
|
28364
28818
|
storyId: ctx.story.id
|
|
28365
28819
|
});
|
|
28366
28820
|
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id, command: lintFixCmd });
|
|
@@ -28376,7 +28830,7 @@ var init_autofix = __esm(() => {
|
|
|
28376
28830
|
const fmtResult = await _autofixDeps.runQualityCommand({
|
|
28377
28831
|
commandName: "formatFix",
|
|
28378
28832
|
command: formatFixCmd,
|
|
28379
|
-
workdir:
|
|
28833
|
+
workdir: ctx.workdir,
|
|
28380
28834
|
storyId: ctx.story.id
|
|
28381
28835
|
});
|
|
28382
28836
|
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, {
|
|
@@ -28393,6 +28847,14 @@ var init_autofix = __esm(() => {
|
|
|
28393
28847
|
const recheckPassed = await _autofixDeps.recheckReview(ctx);
|
|
28394
28848
|
pipelineEventBus.emit({ type: "autofix:completed", storyId: ctx.story.id, fixed: recheckPassed });
|
|
28395
28849
|
if (recheckPassed) {
|
|
28850
|
+
const passedChecks = (ctx.reviewResult?.checks ?? []).filter((c) => c.success).map((c) => c.check);
|
|
28851
|
+
if (passedChecks.length > 0) {
|
|
28852
|
+
ctx.retrySkipChecks = new Set(passedChecks);
|
|
28853
|
+
logger.debug("autofix", "Skipping already-passed checks on retry", {
|
|
28854
|
+
storyId: ctx.story.id,
|
|
28855
|
+
skippedChecks: passedChecks
|
|
28856
|
+
});
|
|
28857
|
+
}
|
|
28396
28858
|
logger.info("autofix", "Mechanical autofix succeeded \u2014 retrying review", { storyId: ctx.story.id });
|
|
28397
28859
|
return { action: "retry", fromStage: "review" };
|
|
28398
28860
|
}
|
|
@@ -28400,7 +28862,7 @@ var init_autofix = __esm(() => {
|
|
|
28400
28862
|
storyId: ctx.story.id
|
|
28401
28863
|
});
|
|
28402
28864
|
}
|
|
28403
|
-
const agentFixed = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd,
|
|
28865
|
+
const { succeeded: agentFixed, cost: agentCost } = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, ctx.workdir);
|
|
28404
28866
|
if (agentFixed) {
|
|
28405
28867
|
if (ctx.reviewResult)
|
|
28406
28868
|
ctx.reviewResult = { ...ctx.reviewResult, success: true };
|
|
@@ -28413,14 +28875,18 @@ var init_autofix = __esm(() => {
|
|
|
28413
28875
|
});
|
|
28414
28876
|
}
|
|
28415
28877
|
logger.info("autofix", "Agent rectification succeeded \u2014 retrying review", { storyId: ctx.story.id });
|
|
28416
|
-
return { action: "retry", fromStage: "review" };
|
|
28878
|
+
return { action: "retry", fromStage: "review", cost: agentCost };
|
|
28417
28879
|
}
|
|
28418
28880
|
logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
|
|
28419
|
-
return {
|
|
28881
|
+
return {
|
|
28882
|
+
action: "escalate",
|
|
28883
|
+
reason: "Autofix exhausted: review still failing after fix attempts",
|
|
28884
|
+
cost: agentCost
|
|
28885
|
+
};
|
|
28420
28886
|
}
|
|
28421
28887
|
};
|
|
28422
28888
|
_autofixDeps = {
|
|
28423
|
-
getAgent,
|
|
28889
|
+
getAgent: (name, config2) => createAgentRegistry(config2).getAgent(name),
|
|
28424
28890
|
runQualityCommand,
|
|
28425
28891
|
recheckReview,
|
|
28426
28892
|
runAgentRectification: (ctx, lintFixCmd, formatFixCmd, effectiveWorkdir) => runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir),
|
|
@@ -28486,10 +28952,10 @@ var init_semantic_verdict = __esm(() => {
|
|
|
28486
28952
|
|
|
28487
28953
|
// src/execution/progress.ts
|
|
28488
28954
|
import { appendFile as appendFile2, mkdir } from "fs/promises";
|
|
28489
|
-
import { join as
|
|
28955
|
+
import { join as join17 } from "path";
|
|
28490
28956
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
28491
28957
|
await mkdir(featureDir, { recursive: true });
|
|
28492
|
-
const progressPath =
|
|
28958
|
+
const progressPath = join17(featureDir, "progress.txt");
|
|
28493
28959
|
const timestamp = new Date().toISOString();
|
|
28494
28960
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
28495
28961
|
`;
|
|
@@ -28514,7 +28980,7 @@ var init_completion = __esm(() => {
|
|
|
28514
28980
|
const logger = getLogger();
|
|
28515
28981
|
const isBatch = ctx.stories.length > 1;
|
|
28516
28982
|
const sessionCost = ctx.agentResult?.estimatedCost || 0;
|
|
28517
|
-
const prdPath = ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json
|
|
28983
|
+
const prdPath = ctx.prdPath ?? (ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json`);
|
|
28518
28984
|
const storyStartTime = ctx.storyStartTime || new Date().toISOString();
|
|
28519
28985
|
if (isBatch) {
|
|
28520
28986
|
ctx.storyMetrics = collectBatchMetrics(ctx, storyStartTime);
|
|
@@ -28565,7 +29031,7 @@ var init_completion = __esm(() => {
|
|
|
28565
29031
|
};
|
|
28566
29032
|
await _completionDeps.persistSemanticVerdict(ctx.featureDir, ctx.story.id, verdict);
|
|
28567
29033
|
}
|
|
28568
|
-
await savePRD(ctx.prd, prdPath);
|
|
29034
|
+
await _completionDeps.savePRD(ctx.prd, prdPath);
|
|
28569
29035
|
const updatedCounts = countStories(ctx.prd);
|
|
28570
29036
|
logger.info("completion", "Progress update", {
|
|
28571
29037
|
storyId: ctx.story.id,
|
|
@@ -28584,7 +29050,8 @@ var init_completion = __esm(() => {
|
|
|
28584
29050
|
};
|
|
28585
29051
|
_completionDeps = {
|
|
28586
29052
|
checkReviewGate,
|
|
28587
|
-
persistSemanticVerdict
|
|
29053
|
+
persistSemanticVerdict,
|
|
29054
|
+
savePRD
|
|
28588
29055
|
};
|
|
28589
29056
|
});
|
|
28590
29057
|
|
|
@@ -28595,7 +29062,7 @@ function estimateTokens(text) {
|
|
|
28595
29062
|
|
|
28596
29063
|
// src/constitution/loader.ts
|
|
28597
29064
|
import { existsSync as existsSync17 } from "fs";
|
|
28598
|
-
import { join as
|
|
29065
|
+
import { join as join18 } from "path";
|
|
28599
29066
|
function truncateToTokens(text, maxTokens) {
|
|
28600
29067
|
const maxChars = maxTokens * 3;
|
|
28601
29068
|
if (text.length <= maxChars) {
|
|
@@ -28617,7 +29084,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
28617
29084
|
}
|
|
28618
29085
|
let combinedContent = "";
|
|
28619
29086
|
if (!config2.skipGlobal) {
|
|
28620
|
-
const globalPath =
|
|
29087
|
+
const globalPath = join18(globalConfigDir(), config2.path);
|
|
28621
29088
|
if (existsSync17(globalPath)) {
|
|
28622
29089
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
28623
29090
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -28627,7 +29094,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
28627
29094
|
}
|
|
28628
29095
|
}
|
|
28629
29096
|
}
|
|
28630
|
-
const projectPath =
|
|
29097
|
+
const projectPath = join18(projectDir, config2.path);
|
|
28631
29098
|
if (existsSync17(projectPath)) {
|
|
28632
29099
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
28633
29100
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -28675,7 +29142,7 @@ var init_constitution = __esm(() => {
|
|
|
28675
29142
|
});
|
|
28676
29143
|
|
|
28677
29144
|
// src/pipeline/stages/constitution.ts
|
|
28678
|
-
import { dirname as
|
|
29145
|
+
import { dirname as dirname3 } from "path";
|
|
28679
29146
|
var constitutionStage;
|
|
28680
29147
|
var init_constitution2 = __esm(() => {
|
|
28681
29148
|
init_constitution();
|
|
@@ -28685,7 +29152,7 @@ var init_constitution2 = __esm(() => {
|
|
|
28685
29152
|
enabled: (ctx) => ctx.config.constitution.enabled,
|
|
28686
29153
|
async execute(ctx) {
|
|
28687
29154
|
const logger = getLogger();
|
|
28688
|
-
const ngentDir = ctx.featureDir ?
|
|
29155
|
+
const ngentDir = ctx.featureDir ? dirname3(dirname3(ctx.featureDir)) : `${ctx.workdir}/nax`;
|
|
28689
29156
|
const result = await loadConstitution(ngentDir, ctx.config.constitution);
|
|
28690
29157
|
if (result) {
|
|
28691
29158
|
ctx.constitution = result;
|
|
@@ -28850,6 +29317,27 @@ function createStoryContext(story, priority) {
|
|
|
28850
29317
|
return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
28851
29318
|
}
|
|
28852
29319
|
function createDependencyContext(story, priority) {
|
|
29320
|
+
const content = isCompletedDependency(story) ? formatCompletedDependency(story) : formatFullDependency(story);
|
|
29321
|
+
return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
29322
|
+
}
|
|
29323
|
+
function isCompletedDependency(story) {
|
|
29324
|
+
return story.status === "passed" || story.status === "decomposed" || story.status === "skipped";
|
|
29325
|
+
}
|
|
29326
|
+
function formatCompletedDependency(story) {
|
|
29327
|
+
const header = `## ${story.id} (${story.status}): ${story.title}`;
|
|
29328
|
+
if (story.diffSummary) {
|
|
29329
|
+
return `${header}
|
|
29330
|
+
|
|
29331
|
+
**Changes made:**
|
|
29332
|
+
\`\`\`
|
|
29333
|
+
${story.diffSummary}
|
|
29334
|
+
\`\`\``;
|
|
29335
|
+
}
|
|
29336
|
+
return `${header}
|
|
29337
|
+
|
|
29338
|
+
Status: ${story.status} (no diff summary available)`;
|
|
29339
|
+
}
|
|
29340
|
+
function formatFullDependency(story) {
|
|
28853
29341
|
let content = formatStoryAsText(story);
|
|
28854
29342
|
if (story.diffSummary) {
|
|
28855
29343
|
content += `
|
|
@@ -28859,7 +29347,7 @@ function createDependencyContext(story, priority) {
|
|
|
28859
29347
|
${story.diffSummary}
|
|
28860
29348
|
\`\`\``;
|
|
28861
29349
|
}
|
|
28862
|
-
return
|
|
29350
|
+
return content;
|
|
28863
29351
|
}
|
|
28864
29352
|
function createErrorContext(errorMessage2, priority) {
|
|
28865
29353
|
return { type: "error", content: errorMessage2, priority, tokens: estimateTokens(errorMessage2) };
|
|
@@ -29535,7 +30023,8 @@ ${pkgContent.trim()}`;
|
|
|
29535
30023
|
if (built.elements.length === 0 && !packageSection) {
|
|
29536
30024
|
return;
|
|
29537
30025
|
}
|
|
29538
|
-
const
|
|
30026
|
+
const elementsForMarkdown = built.elements.filter((e) => e.type !== "story");
|
|
30027
|
+
const baseMarkdown = elementsForMarkdown.length > 0 ? formatContextAsMarkdown({ ...built, elements: elementsForMarkdown }) : "";
|
|
29539
30028
|
const markdown = packageSection ? `${baseMarkdown}${packageSection}` : baseMarkdown;
|
|
29540
30029
|
return { markdown, builtContext: built };
|
|
29541
30030
|
} catch (error48) {
|
|
@@ -29546,6 +30035,10 @@ ${pkgContent.trim()}`;
|
|
|
29546
30035
|
return;
|
|
29547
30036
|
}
|
|
29548
30037
|
}
|
|
30038
|
+
function buildStoryContextFullFromCtx(ctx) {
|
|
30039
|
+
const packageWorkdir = ctx.story.workdir ? ctx.workdir : undefined;
|
|
30040
|
+
return buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
30041
|
+
}
|
|
29549
30042
|
function getAllReadyStories(prd) {
|
|
29550
30043
|
const storyIds = new Set(prd.userStories.map((s) => s.id));
|
|
29551
30044
|
const completedIds = new Set(prd.userStories.filter((s) => s.passes || s.status === "skipped").map((s) => s.id));
|
|
@@ -29656,7 +30149,6 @@ var init_helpers = __esm(() => {
|
|
|
29656
30149
|
});
|
|
29657
30150
|
|
|
29658
30151
|
// src/pipeline/stages/context.ts
|
|
29659
|
-
import { join as join21 } from "path";
|
|
29660
30152
|
var contextStage;
|
|
29661
30153
|
var init_context2 = __esm(() => {
|
|
29662
30154
|
init_helpers();
|
|
@@ -29666,8 +30158,7 @@ var init_context2 = __esm(() => {
|
|
|
29666
30158
|
enabled: () => true,
|
|
29667
30159
|
async execute(ctx) {
|
|
29668
30160
|
const logger = getLogger();
|
|
29669
|
-
const
|
|
29670
|
-
const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
30161
|
+
const result = await buildStoryContextFullFromCtx(ctx);
|
|
29671
30162
|
if (result) {
|
|
29672
30163
|
ctx.contextMarkdown = result.markdown;
|
|
29673
30164
|
ctx.builtContext = result.builtContext;
|
|
@@ -29800,14 +30291,14 @@ var init_isolation = __esm(() => {
|
|
|
29800
30291
|
|
|
29801
30292
|
// src/context/greenfield.ts
|
|
29802
30293
|
import { readdir } from "fs/promises";
|
|
29803
|
-
import { join as
|
|
30294
|
+
import { join as join19 } from "path";
|
|
29804
30295
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
29805
30296
|
const results = [];
|
|
29806
30297
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
29807
30298
|
try {
|
|
29808
30299
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
29809
30300
|
for (const entry of entries) {
|
|
29810
|
-
const fullPath =
|
|
30301
|
+
const fullPath = join19(dir, entry.name);
|
|
29811
30302
|
if (entry.isDirectory()) {
|
|
29812
30303
|
if (ignoreDirs.has(entry.name))
|
|
29813
30304
|
continue;
|
|
@@ -29873,10 +30364,10 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
|
|
|
29873
30364
|
const timeoutMs = timeoutSeconds * 1000;
|
|
29874
30365
|
let timedOut = false;
|
|
29875
30366
|
const timer = { id: undefined };
|
|
29876
|
-
const timeoutPromise = new Promise((
|
|
30367
|
+
const timeoutPromise = new Promise((resolve8) => {
|
|
29877
30368
|
timer.id = setTimeout(() => {
|
|
29878
30369
|
timedOut = true;
|
|
29879
|
-
|
|
30370
|
+
resolve8();
|
|
29880
30371
|
}, timeoutMs);
|
|
29881
30372
|
});
|
|
29882
30373
|
const processPromise = proc.exited;
|
|
@@ -29885,7 +30376,12 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
|
|
|
29885
30376
|
if (timedOut) {
|
|
29886
30377
|
const pid = proc.pid;
|
|
29887
30378
|
killProcessGroup(pid, "SIGTERM");
|
|
29888
|
-
await
|
|
30379
|
+
await Promise.race([
|
|
30380
|
+
proc.exited,
|
|
30381
|
+
new Promise((resolve8) => {
|
|
30382
|
+
setTimeout(resolve8, gracePeriodMs);
|
|
30383
|
+
})
|
|
30384
|
+
]);
|
|
29889
30385
|
killProcessGroup(pid, "SIGKILL");
|
|
29890
30386
|
const [out, err] = await Promise.all([
|
|
29891
30387
|
raceWithDeadline(stdoutPromise, drainTimeoutMs),
|
|
@@ -30147,13 +30643,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
30147
30643
|
|
|
30148
30644
|
// src/verification/runners.ts
|
|
30149
30645
|
import { existsSync as existsSync18 } from "fs";
|
|
30150
|
-
import { join as
|
|
30646
|
+
import { join as join20 } from "path";
|
|
30151
30647
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
30152
30648
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
30153
30649
|
return { success: true, missingFiles: [] };
|
|
30154
30650
|
const missingFiles = [];
|
|
30155
30651
|
for (const file3 of expectedFiles) {
|
|
30156
|
-
if (!existsSync18(
|
|
30652
|
+
if (!existsSync18(join20(workingDirectory, file3)))
|
|
30157
30653
|
missingFiles.push(file3);
|
|
30158
30654
|
}
|
|
30159
30655
|
if (missingFiles.length > 0) {
|
|
@@ -30478,10 +30974,10 @@ var init_prompts = __esm(() => {
|
|
|
30478
30974
|
});
|
|
30479
30975
|
|
|
30480
30976
|
// src/tdd/rectification-gate.ts
|
|
30481
|
-
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
|
|
30977
|
+
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir) {
|
|
30482
30978
|
const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
|
|
30483
30979
|
if (!rectificationEnabled)
|
|
30484
|
-
return false;
|
|
30980
|
+
return { passed: false, cost: 0 };
|
|
30485
30981
|
const rectificationConfig = config2.execution.rectification;
|
|
30486
30982
|
const testCmd = config2.quality?.commands?.test ?? "bun test";
|
|
30487
30983
|
const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
|
|
@@ -30496,7 +30992,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30496
30992
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
30497
30993
|
const testSummary = _rectificationGateDeps.parseBunTestOutput(fullSuiteResult.output);
|
|
30498
30994
|
if (testSummary.failed > 0) {
|
|
30499
|
-
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
|
|
30995
|
+
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
|
|
30500
30996
|
}
|
|
30501
30997
|
if (testSummary.passed > 0) {
|
|
30502
30998
|
logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
|
|
@@ -30504,7 +31000,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30504
31000
|
exitCode: fullSuiteResult.exitCode,
|
|
30505
31001
|
passedTests: testSummary.passed
|
|
30506
31002
|
});
|
|
30507
|
-
return true;
|
|
31003
|
+
return { passed: true, cost: 0 };
|
|
30508
31004
|
}
|
|
30509
31005
|
logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
|
|
30510
31006
|
storyId: story.id,
|
|
@@ -30512,19 +31008,19 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30512
31008
|
outputLength: fullSuiteResult.output.length,
|
|
30513
31009
|
outputTail: fullSuiteResult.output.slice(-200)
|
|
30514
31010
|
});
|
|
30515
|
-
return false;
|
|
31011
|
+
return { passed: false, cost: 0 };
|
|
30516
31012
|
}
|
|
30517
31013
|
if (fullSuitePassed) {
|
|
30518
31014
|
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
30519
|
-
return true;
|
|
31015
|
+
return { passed: true, cost: 0 };
|
|
30520
31016
|
}
|
|
30521
31017
|
logger.warn("tdd", "Full suite gate execution failed (no output)", {
|
|
30522
31018
|
storyId: story.id,
|
|
30523
31019
|
exitCode: fullSuiteResult.exitCode
|
|
30524
31020
|
});
|
|
30525
|
-
return false;
|
|
31021
|
+
return { passed: false, cost: 0 };
|
|
30526
31022
|
}
|
|
30527
|
-
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
|
|
31023
|
+
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir) {
|
|
30528
31024
|
const rectificationState = {
|
|
30529
31025
|
attempt: 0,
|
|
30530
31026
|
initialFailures: testSummary.failed,
|
|
@@ -30544,6 +31040,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30544
31040
|
...rectificationState,
|
|
30545
31041
|
isolationPassed: true
|
|
30546
31042
|
};
|
|
31043
|
+
let gateCostAccum = 0;
|
|
30547
31044
|
const fixed = await runSharedRectificationLoop({
|
|
30548
31045
|
stage: "tdd",
|
|
30549
31046
|
storyId: story.id,
|
|
@@ -30575,6 +31072,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30575
31072
|
dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
|
|
30576
31073
|
pipelineStage: "rectification",
|
|
30577
31074
|
config: config2,
|
|
31075
|
+
projectDir,
|
|
30578
31076
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
30579
31077
|
featureName,
|
|
30580
31078
|
storyId: story.id,
|
|
@@ -30585,6 +31083,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30585
31083
|
if (!rectifyResult.success && rectifyResult.pid) {
|
|
30586
31084
|
await cleanupProcessTree(rectifyResult.pid);
|
|
30587
31085
|
}
|
|
31086
|
+
gateCostAccum += rectifyResult.estimatedCost ?? 0;
|
|
30588
31087
|
if (rectifyResult.success) {
|
|
30589
31088
|
logger.info("tdd", "Rectification agent session complete", {
|
|
30590
31089
|
storyId: story.id,
|
|
@@ -30645,7 +31144,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30645
31144
|
}
|
|
30646
31145
|
});
|
|
30647
31146
|
if (fixed) {
|
|
30648
|
-
return true;
|
|
31147
|
+
return { passed: true, cost: gateCostAccum };
|
|
30649
31148
|
}
|
|
30650
31149
|
const finalFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
|
|
30651
31150
|
cwd: workdir
|
|
@@ -30657,10 +31156,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
30657
31156
|
attempts: rectificationState.attempt,
|
|
30658
31157
|
remainingFailures: rectificationState.currentFailures
|
|
30659
31158
|
});
|
|
30660
|
-
return false;
|
|
31159
|
+
return { passed: false, cost: gateCostAccum };
|
|
30661
31160
|
}
|
|
30662
31161
|
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
30663
|
-
return true;
|
|
31162
|
+
return { passed: true, cost: gateCostAccum };
|
|
30664
31163
|
}
|
|
30665
31164
|
var _rectificationGateDeps;
|
|
30666
31165
|
var init_rectification_gate = __esm(() => {
|
|
@@ -31064,36 +31563,10 @@ Set \`approved: false\` when ANY of these conditions are true:
|
|
|
31064
31563
|
- Critical acceptance criteria are not met
|
|
31065
31564
|
- Code quality is poor (security issues, severe bugs, etc.)
|
|
31066
31565
|
|
|
31067
|
-
**
|
|
31566
|
+
**JSON schema** (fill in all fields with real values):
|
|
31068
31567
|
|
|
31069
31568
|
\`\`\`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
|
-
}
|
|
31569
|
+
{"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
31570
|
\`\`\`
|
|
31098
31571
|
|
|
31099
31572
|
**Field notes:**
|
|
@@ -31110,13 +31583,13 @@ var exports_loader = {};
|
|
|
31110
31583
|
__export(exports_loader, {
|
|
31111
31584
|
loadOverride: () => loadOverride
|
|
31112
31585
|
});
|
|
31113
|
-
import { join as
|
|
31586
|
+
import { join as join21 } from "path";
|
|
31114
31587
|
async function loadOverride(role, workdir, config2) {
|
|
31115
31588
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
31116
31589
|
if (!overridePath) {
|
|
31117
31590
|
return null;
|
|
31118
31591
|
}
|
|
31119
|
-
const absolutePath =
|
|
31592
|
+
const absolutePath = join21(workdir, overridePath);
|
|
31120
31593
|
const file3 = Bun.file(absolutePath);
|
|
31121
31594
|
if (!await file3.exists()) {
|
|
31122
31595
|
return null;
|
|
@@ -31326,7 +31799,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
31326
31799
|
}
|
|
31327
31800
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
31328
31801
|
}
|
|
31329
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge) {
|
|
31802
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge, projectDir) {
|
|
31330
31803
|
const startTime = Date.now();
|
|
31331
31804
|
let prompt;
|
|
31332
31805
|
if (_sessionRunnerDeps.buildPrompt) {
|
|
@@ -31356,6 +31829,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
31356
31829
|
dangerouslySkipPermissions: resolvePermissions(config2, "run").skipPermissions,
|
|
31357
31830
|
pipelineStage: "run",
|
|
31358
31831
|
config: config2,
|
|
31832
|
+
projectDir,
|
|
31359
31833
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
31360
31834
|
featureName,
|
|
31361
31835
|
storyId: story.id,
|
|
@@ -31727,7 +32201,8 @@ async function runThreeSessionTdd(options) {
|
|
|
31727
32201
|
dryRun = false,
|
|
31728
32202
|
lite = false,
|
|
31729
32203
|
_recursionDepth = 0,
|
|
31730
|
-
interactionChain
|
|
32204
|
+
interactionChain,
|
|
32205
|
+
projectDir
|
|
31731
32206
|
} = options;
|
|
31732
32207
|
const logger = getLogger();
|
|
31733
32208
|
const MAX_RECURSION_DEPTH = 2;
|
|
@@ -31786,7 +32261,7 @@ async function runThreeSessionTdd(options) {
|
|
|
31786
32261
|
let session1;
|
|
31787
32262
|
if (!isRetry) {
|
|
31788
32263
|
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" }));
|
|
32264
|
+
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
32265
|
sessions.push(session1);
|
|
31791
32266
|
}
|
|
31792
32267
|
if (session1 && !session1.success) {
|
|
@@ -31848,7 +32323,7 @@ async function runThreeSessionTdd(options) {
|
|
|
31848
32323
|
});
|
|
31849
32324
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
31850
32325
|
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" }));
|
|
32326
|
+
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
32327
|
sessions.push(session2);
|
|
31853
32328
|
if (!session2.success) {
|
|
31854
32329
|
needsHumanReview = true;
|
|
@@ -31864,10 +32339,10 @@ async function runThreeSessionTdd(options) {
|
|
|
31864
32339
|
lite
|
|
31865
32340
|
};
|
|
31866
32341
|
}
|
|
31867
|
-
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
|
|
32342
|
+
const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir);
|
|
31868
32343
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
31869
32344
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
31870
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
|
|
32345
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName, undefined, projectDir);
|
|
31871
32346
|
sessions.push(session3);
|
|
31872
32347
|
const verdict = await readVerdict(workdir);
|
|
31873
32348
|
await cleanupVerdict(workdir);
|
|
@@ -31927,7 +32402,7 @@ async function runThreeSessionTdd(options) {
|
|
|
31927
32402
|
needsHumanReview = false;
|
|
31928
32403
|
}
|
|
31929
32404
|
}
|
|
31930
|
-
const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0);
|
|
32405
|
+
const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0) + fullSuiteGateCost;
|
|
31931
32406
|
logger.info("tdd", allSuccessful ? "[OK] Three-session TDD complete" : "[WARN] Three-session TDD needs review", {
|
|
31932
32407
|
storyId: story.id,
|
|
31933
32408
|
success: allSuccessful,
|
|
@@ -31963,6 +32438,22 @@ async function runThreeSessionTdd(options) {
|
|
|
31963
32438
|
fullSuiteGatePassed
|
|
31964
32439
|
};
|
|
31965
32440
|
}
|
|
32441
|
+
function runThreeSessionTddFromCtx(ctx, opts) {
|
|
32442
|
+
return runThreeSessionTdd({
|
|
32443
|
+
agent: opts.agent,
|
|
32444
|
+
story: ctx.story,
|
|
32445
|
+
config: ctx.config,
|
|
32446
|
+
workdir: ctx.workdir,
|
|
32447
|
+
modelTier: ctx.routing.modelTier,
|
|
32448
|
+
featureName: ctx.prd.feature,
|
|
32449
|
+
contextMarkdown: ctx.contextMarkdown,
|
|
32450
|
+
constitution: ctx.constitution?.content,
|
|
32451
|
+
dryRun: opts.dryRun ?? false,
|
|
32452
|
+
lite: opts.lite ?? false,
|
|
32453
|
+
interactionChain: ctx.interaction,
|
|
32454
|
+
projectDir: ctx.projectDir
|
|
32455
|
+
});
|
|
32456
|
+
}
|
|
31966
32457
|
var init_orchestrator2 = __esm(() => {
|
|
31967
32458
|
init_config();
|
|
31968
32459
|
init_greenfield();
|
|
@@ -31985,17 +32476,6 @@ var init_tdd = __esm(() => {
|
|
|
31985
32476
|
});
|
|
31986
32477
|
|
|
31987
32478
|
// 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
32479
|
function isAmbiguousOutput(output) {
|
|
32000
32480
|
if (!output)
|
|
32001
32481
|
return false;
|
|
@@ -32042,11 +32522,11 @@ var init_execution2 = __esm(() => {
|
|
|
32042
32522
|
enabled: () => true,
|
|
32043
32523
|
async execute(ctx) {
|
|
32044
32524
|
const logger = getLogger();
|
|
32045
|
-
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.
|
|
32525
|
+
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
|
|
32046
32526
|
if (!agent) {
|
|
32047
32527
|
return {
|
|
32048
32528
|
action: "fail",
|
|
32049
|
-
reason: `Agent "${ctx.
|
|
32529
|
+
reason: `Agent "${ctx.rootConfig.autoMode.defaultAgent}" not found`
|
|
32050
32530
|
};
|
|
32051
32531
|
}
|
|
32052
32532
|
const isTddStrategy = ctx.routing.testStrategy === "three-session-tdd" || ctx.routing.testStrategy === "three-session-tdd-lite";
|
|
@@ -32056,20 +32536,7 @@ var init_execution2 = __esm(() => {
|
|
|
32056
32536
|
storyId: ctx.story.id,
|
|
32057
32537
|
lite: isLiteMode
|
|
32058
32538
|
});
|
|
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
|
-
});
|
|
32539
|
+
const tddResult = await runThreeSessionTddFromCtx(ctx, { agent, dryRun: false, lite: isLiteMode });
|
|
32073
32540
|
ctx.agentResult = {
|
|
32074
32541
|
success: tddResult.success,
|
|
32075
32542
|
estimatedCost: tddResult.totalCost,
|
|
@@ -32132,17 +32599,17 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
32132
32599
|
supportedTiers: agent.capabilities.supportedTiers
|
|
32133
32600
|
});
|
|
32134
32601
|
}
|
|
32135
|
-
const storyWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
|
|
32136
32602
|
const keepSessionOpen = !!(ctx.config.review?.enabled === true || ctx.config.execution.rectification?.enabled === true);
|
|
32137
32603
|
const result = await agent.run({
|
|
32138
32604
|
prompt: ctx.prompt,
|
|
32139
|
-
workdir:
|
|
32605
|
+
workdir: ctx.workdir,
|
|
32140
32606
|
modelTier: ctx.routing.modelTier,
|
|
32141
|
-
modelDef: resolveModelForAgent(ctx.
|
|
32607
|
+
modelDef: resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.routing.modelTier, ctx.rootConfig.autoMode.defaultAgent),
|
|
32142
32608
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
32143
32609
|
dangerouslySkipPermissions: resolvePermissions(ctx.config, "run").skipPermissions,
|
|
32144
32610
|
pipelineStage: "run",
|
|
32145
32611
|
config: ctx.config,
|
|
32612
|
+
projectDir: ctx.projectDir,
|
|
32146
32613
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
32147
32614
|
pidRegistry: ctx.pidRegistry,
|
|
32148
32615
|
featureName: ctx.prd.feature,
|
|
@@ -32156,7 +32623,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
32156
32623
|
})
|
|
32157
32624
|
});
|
|
32158
32625
|
ctx.agentResult = result;
|
|
32159
|
-
await autoCommitIfDirty(
|
|
32626
|
+
await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
|
|
32160
32627
|
const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
|
|
32161
32628
|
if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
|
|
32162
32629
|
const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
|
|
@@ -32197,8 +32664,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
32197
32664
|
detectMergeConflict,
|
|
32198
32665
|
checkMergeConflict,
|
|
32199
32666
|
isAmbiguousOutput,
|
|
32200
|
-
checkStoryAmbiguity
|
|
32201
|
-
resolveStoryWorkdir
|
|
32667
|
+
checkStoryAmbiguity
|
|
32202
32668
|
};
|
|
32203
32669
|
});
|
|
32204
32670
|
|
|
@@ -32475,17 +32941,16 @@ var init_prompt = __esm(() => {
|
|
|
32475
32941
|
async execute(ctx) {
|
|
32476
32942
|
const logger = getLogger();
|
|
32477
32943
|
const isBatch = ctx.stories.length > 1;
|
|
32478
|
-
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
32479
32944
|
const acceptanceEntries = await _loadAcceptanceEntries(ctx, logger);
|
|
32480
32945
|
let prompt;
|
|
32481
32946
|
if (isBatch) {
|
|
32482
|
-
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(
|
|
32947
|
+
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
32948
|
if (acceptanceEntries.length > 0)
|
|
32484
32949
|
builder.acceptanceContext(acceptanceEntries);
|
|
32485
32950
|
prompt = await builder.build();
|
|
32486
32951
|
} else {
|
|
32487
32952
|
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(
|
|
32953
|
+
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
32954
|
if (acceptanceEntries.length > 0)
|
|
32490
32955
|
builder.acceptanceContext(acceptanceEntries);
|
|
32491
32956
|
prompt = await builder.build();
|
|
@@ -32718,7 +33183,18 @@ async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
|
|
|
32718
33183
|
return { output, totalCostUsd };
|
|
32719
33184
|
}
|
|
32720
33185
|
async function runRectificationLoop2(opts) {
|
|
32721
|
-
const {
|
|
33186
|
+
const {
|
|
33187
|
+
config: config2,
|
|
33188
|
+
workdir,
|
|
33189
|
+
story,
|
|
33190
|
+
testCommand,
|
|
33191
|
+
timeoutSeconds,
|
|
33192
|
+
testOutput,
|
|
33193
|
+
promptPrefix,
|
|
33194
|
+
featureName,
|
|
33195
|
+
agentGetFn,
|
|
33196
|
+
projectDir
|
|
33197
|
+
} = opts;
|
|
32722
33198
|
const logger = getSafeLogger();
|
|
32723
33199
|
const rectificationConfig = config2.execution.rectification;
|
|
32724
33200
|
const testSummary = parseBunTestOutput(testOutput);
|
|
@@ -32729,7 +33205,8 @@ async function runRectificationLoop2(opts) {
|
|
|
32729
33205
|
currentFailures: testSummary.failed,
|
|
32730
33206
|
lastExitCode: 1
|
|
32731
33207
|
};
|
|
32732
|
-
|
|
33208
|
+
let costAccum = 0;
|
|
33209
|
+
const succeeded = await runSharedRectificationLoop({
|
|
32733
33210
|
stage: "rectification",
|
|
32734
33211
|
storyId: story.id,
|
|
32735
33212
|
maxAttempts: rectificationConfig.maxRetries,
|
|
@@ -32810,11 +33287,13 @@ ${rectificationPrompt}`;
|
|
|
32810
33287
|
dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
|
|
32811
33288
|
pipelineStage: "rectification",
|
|
32812
33289
|
config: config2,
|
|
33290
|
+
projectDir,
|
|
32813
33291
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
32814
33292
|
featureName,
|
|
32815
33293
|
storyId: story.id,
|
|
32816
33294
|
sessionRole: "implementer"
|
|
32817
33295
|
});
|
|
33296
|
+
costAccum += agentResult.estimatedCost ?? 0;
|
|
32818
33297
|
if (agentResult.success) {
|
|
32819
33298
|
logger?.info("rectification", `Agent ${label} session complete`, {
|
|
32820
33299
|
storyId: story.id,
|
|
@@ -32934,11 +33413,13 @@ ${escalationPrompt}`;
|
|
|
32934
33413
|
dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
|
|
32935
33414
|
pipelineStage: "rectification",
|
|
32936
33415
|
config: config2,
|
|
33416
|
+
projectDir,
|
|
32937
33417
|
maxInteractionTurns: config2.agent?.maxInteractionTurns,
|
|
32938
33418
|
featureName,
|
|
32939
33419
|
storyId: story.id,
|
|
32940
33420
|
sessionRole: "implementer"
|
|
32941
33421
|
});
|
|
33422
|
+
costAccum += escalationRunResult.estimatedCost ?? 0;
|
|
32942
33423
|
logger?.info("rectification", "escalated rectification attempt cost", {
|
|
32943
33424
|
storyId: story.id,
|
|
32944
33425
|
escalatedTier,
|
|
@@ -32975,6 +33456,21 @@ ${escalationPrompt}`;
|
|
|
32975
33456
|
}
|
|
32976
33457
|
throw error48;
|
|
32977
33458
|
});
|
|
33459
|
+
return { succeeded, cost: costAccum };
|
|
33460
|
+
}
|
|
33461
|
+
function runRectificationLoopFromCtx(ctx, opts) {
|
|
33462
|
+
return runRectificationLoop2({
|
|
33463
|
+
config: ctx.config,
|
|
33464
|
+
workdir: ctx.workdir,
|
|
33465
|
+
story: ctx.story,
|
|
33466
|
+
testCommand: opts.testCommand,
|
|
33467
|
+
timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
|
|
33468
|
+
testOutput: opts.testOutput,
|
|
33469
|
+
promptPrefix: opts.promptPrefix,
|
|
33470
|
+
featureName: ctx.prd.feature,
|
|
33471
|
+
agentGetFn: ctx.agentGetFn,
|
|
33472
|
+
projectDir: ctx.projectDir
|
|
33473
|
+
});
|
|
32978
33474
|
}
|
|
32979
33475
|
var _rectificationDeps;
|
|
32980
33476
|
var init_rectification_loop = __esm(() => {
|
|
@@ -33036,36 +33532,28 @@ var init_rectify = __esm(() => {
|
|
|
33036
33532
|
attempt: rectifyAttempt,
|
|
33037
33533
|
testOutput
|
|
33038
33534
|
});
|
|
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
|
-
});
|
|
33535
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
|
|
33536
|
+
const { succeeded, cost } = await _rectifyDeps.runRectificationLoop(ctx, { testCommand, testOutput });
|
|
33050
33537
|
pipelineEventBus.emit({
|
|
33051
33538
|
type: "rectify:completed",
|
|
33052
33539
|
storyId: ctx.story.id,
|
|
33053
33540
|
attempt: rectifyAttempt,
|
|
33054
|
-
fixed
|
|
33541
|
+
fixed: succeeded
|
|
33055
33542
|
});
|
|
33056
|
-
if (
|
|
33543
|
+
if (succeeded) {
|
|
33057
33544
|
logger.info("rectify", "Rectification succeeded \u2014 retrying verify", { storyId: ctx.story.id });
|
|
33058
33545
|
ctx.verifyResult = undefined;
|
|
33059
|
-
return { action: "retry", fromStage: "verify" };
|
|
33546
|
+
return { action: "retry", fromStage: "verify", cost };
|
|
33060
33547
|
}
|
|
33061
33548
|
logger.warn("rectify", "Rectification exhausted \u2014 escalating", { storyId: ctx.story.id });
|
|
33062
33549
|
return {
|
|
33063
33550
|
action: "escalate",
|
|
33064
|
-
reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)
|
|
33551
|
+
reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`,
|
|
33552
|
+
cost
|
|
33065
33553
|
};
|
|
33066
33554
|
}
|
|
33067
33555
|
};
|
|
33068
|
-
_rectifyDeps = { runRectificationLoop:
|
|
33556
|
+
_rectifyDeps = { runRectificationLoop: runRectificationLoopFromCtx };
|
|
33069
33557
|
});
|
|
33070
33558
|
|
|
33071
33559
|
// src/verification/orchestrator-types.ts
|
|
@@ -33172,16 +33660,16 @@ class AcceptanceStrategy {
|
|
|
33172
33660
|
}, timeoutMs);
|
|
33173
33661
|
const exitCode = await Promise.race([
|
|
33174
33662
|
proc.exited,
|
|
33175
|
-
new Promise((
|
|
33663
|
+
new Promise((resolve8) => setTimeout(() => resolve8(124), timeoutMs + 6000))
|
|
33176
33664
|
]);
|
|
33177
33665
|
clearTimeout(timeoutId);
|
|
33178
33666
|
const stdout = await Promise.race([
|
|
33179
33667
|
new Response(proc.stdout).text(),
|
|
33180
|
-
new Promise((
|
|
33668
|
+
new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
|
|
33181
33669
|
]);
|
|
33182
33670
|
const stderr = await Promise.race([
|
|
33183
33671
|
new Response(proc.stderr).text(),
|
|
33184
|
-
new Promise((
|
|
33672
|
+
new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
|
|
33185
33673
|
]);
|
|
33186
33674
|
const durationMs = Date.now() - start;
|
|
33187
33675
|
if (timedOut || exitCode === 124) {
|
|
@@ -33591,34 +34079,31 @@ var init_regression2 = __esm(() => {
|
|
|
33591
34079
|
regressionStage = {
|
|
33592
34080
|
name: "regression",
|
|
33593
34081
|
enabled(ctx) {
|
|
33594
|
-
const
|
|
33595
|
-
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
34082
|
+
const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
|
|
33596
34083
|
if (mode !== "per-story")
|
|
33597
34084
|
return false;
|
|
33598
34085
|
if (ctx.verifyResult && !ctx.verifyResult.success)
|
|
33599
34086
|
return false;
|
|
33600
|
-
const gateEnabled =
|
|
34087
|
+
const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
|
|
33601
34088
|
return gateEnabled;
|
|
33602
34089
|
},
|
|
33603
34090
|
skipReason(ctx) {
|
|
33604
|
-
const
|
|
33605
|
-
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
34091
|
+
const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
|
|
33606
34092
|
if (mode !== "per-story")
|
|
33607
34093
|
return `not needed (regression mode is '${mode}', not 'per-story')`;
|
|
33608
34094
|
return "disabled (regression gate not enabled in config)";
|
|
33609
34095
|
},
|
|
33610
34096
|
async execute(ctx) {
|
|
33611
34097
|
const logger = getLogger();
|
|
33612
|
-
const
|
|
33613
|
-
const
|
|
33614
|
-
const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
34098
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
|
|
34099
|
+
const timeoutSeconds = ctx.config.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
33615
34100
|
logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
|
|
33616
34101
|
const verifyCtx = {
|
|
33617
34102
|
workdir: ctx.workdir,
|
|
33618
34103
|
testCommand,
|
|
33619
34104
|
timeoutSeconds,
|
|
33620
34105
|
storyId: ctx.story.id,
|
|
33621
|
-
acceptOnTimeout:
|
|
34106
|
+
acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
|
|
33622
34107
|
config: ctx.config
|
|
33623
34108
|
};
|
|
33624
34109
|
const result = await _regressionStageDeps.verifyRegression(verifyCtx);
|
|
@@ -34225,7 +34710,6 @@ var init_routing = __esm(() => {
|
|
|
34225
34710
|
});
|
|
34226
34711
|
|
|
34227
34712
|
// src/pipeline/stages/routing.ts
|
|
34228
|
-
import { join as join26 } from "path";
|
|
34229
34713
|
var routingStage, _routingDeps;
|
|
34230
34714
|
var init_routing2 = __esm(() => {
|
|
34231
34715
|
init_greenfield();
|
|
@@ -34238,13 +34722,12 @@ var init_routing2 = __esm(() => {
|
|
|
34238
34722
|
enabled: () => true,
|
|
34239
34723
|
async execute(ctx) {
|
|
34240
34724
|
const logger = getLogger();
|
|
34241
|
-
const
|
|
34242
|
-
const agentName = effectiveConfig.execution?.agent ?? "claude";
|
|
34725
|
+
const agentName = ctx.config.execution?.agent ?? "claude";
|
|
34243
34726
|
const adapter = ctx.agentGetFn ? ctx.agentGetFn(agentName) : undefined;
|
|
34244
34727
|
if (ctx.story.id === ctx.stories[0]?.id) {
|
|
34245
34728
|
_routingDeps.clearCache();
|
|
34246
34729
|
}
|
|
34247
|
-
const decision = await _routingDeps.resolveRouting(ctx.story,
|
|
34730
|
+
const decision = await _routingDeps.resolveRouting(ctx.story, ctx.config, ctx.plugins, adapter);
|
|
34248
34731
|
const TIER_RANK = { fast: 0, balanced: 1, powerful: 2 };
|
|
34249
34732
|
const derivedTier = decision.modelTier;
|
|
34250
34733
|
const previousTier = ctx.story.routing?.modelTier;
|
|
@@ -34262,9 +34745,9 @@ var init_routing2 = __esm(() => {
|
|
|
34262
34745
|
if (ctx.prdPath) {
|
|
34263
34746
|
await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
|
|
34264
34747
|
}
|
|
34265
|
-
const greenfieldDetectionEnabled =
|
|
34748
|
+
const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
|
|
34266
34749
|
if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
|
|
34267
|
-
const greenfieldScanDir = ctx.
|
|
34750
|
+
const greenfieldScanDir = ctx.workdir;
|
|
34268
34751
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
|
|
34269
34752
|
if (isGreenfield) {
|
|
34270
34753
|
logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
|
|
@@ -34315,7 +34798,7 @@ var init_crash_detector = __esm(() => {
|
|
|
34315
34798
|
});
|
|
34316
34799
|
|
|
34317
34800
|
// src/pipeline/stages/verify.ts
|
|
34318
|
-
import { join as
|
|
34801
|
+
import { join as join22 } from "path";
|
|
34319
34802
|
function coerceSmartTestRunner(val) {
|
|
34320
34803
|
if (val === undefined || val === true)
|
|
34321
34804
|
return DEFAULT_SMART_RUNNER_CONFIG2;
|
|
@@ -34331,7 +34814,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
34331
34814
|
}
|
|
34332
34815
|
async function readPackageName(dir) {
|
|
34333
34816
|
try {
|
|
34334
|
-
const content = await Bun.file(
|
|
34817
|
+
const content = await Bun.file(join22(dir, "package.json")).json();
|
|
34335
34818
|
return typeof content.name === "string" ? content.name : null;
|
|
34336
34819
|
} catch {
|
|
34337
34820
|
return null;
|
|
@@ -34364,26 +34847,24 @@ var init_verify = __esm(() => {
|
|
|
34364
34847
|
skipReason: () => "not needed (full-suite gate already passed)",
|
|
34365
34848
|
async execute(ctx) {
|
|
34366
34849
|
const logger = getLogger();
|
|
34367
|
-
|
|
34368
|
-
if (!effectiveConfig.quality.requireTests) {
|
|
34850
|
+
if (!ctx.config.quality.requireTests) {
|
|
34369
34851
|
logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
|
|
34370
34852
|
return { action: "continue" };
|
|
34371
34853
|
}
|
|
34372
|
-
const testCommand =
|
|
34373
|
-
const testScopedTemplate =
|
|
34854
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test;
|
|
34855
|
+
const testScopedTemplate = ctx.config.quality.commands.testScoped;
|
|
34374
34856
|
if (!testCommand) {
|
|
34375
34857
|
logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
|
|
34376
34858
|
return { action: "continue" };
|
|
34377
34859
|
}
|
|
34378
34860
|
logger.info("verify", "Running verification", { storyId: ctx.story.id });
|
|
34379
|
-
const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
34380
34861
|
let effectiveCommand = testCommand;
|
|
34381
34862
|
let isFullSuite = true;
|
|
34382
|
-
const smartRunnerConfig = coerceSmartTestRunner(
|
|
34383
|
-
const regressionMode =
|
|
34863
|
+
const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
|
|
34864
|
+
const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
|
|
34384
34865
|
let resolvedTestScopedTemplate = testScopedTemplate;
|
|
34385
34866
|
if (testScopedTemplate && ctx.story.workdir) {
|
|
34386
|
-
const resolved = await resolvePackageTemplate(testScopedTemplate,
|
|
34867
|
+
const resolved = await resolvePackageTemplate(testScopedTemplate, ctx.workdir);
|
|
34387
34868
|
resolvedTestScopedTemplate = resolved ?? undefined;
|
|
34388
34869
|
}
|
|
34389
34870
|
const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
|
|
@@ -34402,8 +34883,8 @@ var init_verify = __esm(() => {
|
|
|
34402
34883
|
});
|
|
34403
34884
|
}
|
|
34404
34885
|
} else if (smartRunnerConfig.enabled) {
|
|
34405
|
-
const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(
|
|
34406
|
-
const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles,
|
|
34886
|
+
const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(ctx.workdir, ctx.storyGitRef, ctx.story.workdir);
|
|
34887
|
+
const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, ctx.workdir);
|
|
34407
34888
|
if (pass1Files.length > 0) {
|
|
34408
34889
|
logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
|
|
34409
34890
|
storyId: ctx.story.id
|
|
@@ -34411,7 +34892,7 @@ var init_verify = __esm(() => {
|
|
|
34411
34892
|
effectiveCommand = buildScopedCommand2(pass1Files, testCommand, resolvedTestScopedTemplate);
|
|
34412
34893
|
isFullSuite = false;
|
|
34413
34894
|
} else if (smartRunnerConfig.fallback === "import-grep") {
|
|
34414
|
-
const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles,
|
|
34895
|
+
const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, ctx.workdir, smartRunnerConfig.testFilePatterns);
|
|
34415
34896
|
if (pass2Files.length > 0) {
|
|
34416
34897
|
logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
|
|
34417
34898
|
storyId: ctx.story.id
|
|
@@ -34437,10 +34918,10 @@ var init_verify = __esm(() => {
|
|
|
34437
34918
|
command: effectiveCommand
|
|
34438
34919
|
});
|
|
34439
34920
|
const result = await _verifyDeps.regression({
|
|
34440
|
-
workdir:
|
|
34921
|
+
workdir: ctx.workdir,
|
|
34441
34922
|
command: effectiveCommand,
|
|
34442
|
-
timeoutSeconds:
|
|
34443
|
-
acceptOnTimeout:
|
|
34923
|
+
timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
|
|
34924
|
+
acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
|
|
34444
34925
|
});
|
|
34445
34926
|
ctx.verifyResult = {
|
|
34446
34927
|
success: result.success,
|
|
@@ -34457,7 +34938,7 @@ var init_verify = __esm(() => {
|
|
|
34457
34938
|
};
|
|
34458
34939
|
if (!result.success) {
|
|
34459
34940
|
if (result.status === "TIMEOUT") {
|
|
34460
|
-
const timeout =
|
|
34941
|
+
const timeout = ctx.config.execution.verificationTimeoutSeconds;
|
|
34461
34942
|
logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
|
|
34462
34943
|
exitCode: result.status,
|
|
34463
34944
|
storyId: ctx.story.id,
|
|
@@ -34475,7 +34956,7 @@ var init_verify = __esm(() => {
|
|
|
34475
34956
|
if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
|
|
34476
34957
|
return {
|
|
34477
34958
|
action: "escalate",
|
|
34478
|
-
reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${
|
|
34959
|
+
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
34960
|
};
|
|
34480
34961
|
}
|
|
34481
34962
|
return { action: "continue" };
|
|
@@ -34593,9 +35074,9 @@ __export(exports_init_context, {
|
|
|
34593
35074
|
generateContextTemplate: () => generateContextTemplate,
|
|
34594
35075
|
_initContextDeps: () => _initContextDeps
|
|
34595
35076
|
});
|
|
34596
|
-
import { existsSync as
|
|
35077
|
+
import { existsSync as existsSync21 } from "fs";
|
|
34597
35078
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
34598
|
-
import { basename as
|
|
35079
|
+
import { basename as basename3, join as join26 } from "path";
|
|
34599
35080
|
async function findFiles(dir, maxFiles = 200) {
|
|
34600
35081
|
try {
|
|
34601
35082
|
const proc = Bun.spawnSync([
|
|
@@ -34623,8 +35104,8 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
34623
35104
|
return [];
|
|
34624
35105
|
}
|
|
34625
35106
|
async function readPackageManifest(projectRoot) {
|
|
34626
|
-
const packageJsonPath =
|
|
34627
|
-
if (!
|
|
35107
|
+
const packageJsonPath = join26(projectRoot, "package.json");
|
|
35108
|
+
if (!existsSync21(packageJsonPath)) {
|
|
34628
35109
|
return null;
|
|
34629
35110
|
}
|
|
34630
35111
|
try {
|
|
@@ -34641,8 +35122,8 @@ async function readPackageManifest(projectRoot) {
|
|
|
34641
35122
|
}
|
|
34642
35123
|
}
|
|
34643
35124
|
async function readReadmeSnippet(projectRoot) {
|
|
34644
|
-
const readmePath =
|
|
34645
|
-
if (!
|
|
35125
|
+
const readmePath = join26(projectRoot, "README.md");
|
|
35126
|
+
if (!existsSync21(readmePath)) {
|
|
34646
35127
|
return null;
|
|
34647
35128
|
}
|
|
34648
35129
|
try {
|
|
@@ -34659,8 +35140,8 @@ async function detectEntryPoints(projectRoot) {
|
|
|
34659
35140
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
34660
35141
|
const found = [];
|
|
34661
35142
|
for (const candidate of candidates) {
|
|
34662
|
-
const path13 =
|
|
34663
|
-
if (
|
|
35143
|
+
const path13 = join26(projectRoot, candidate);
|
|
35144
|
+
if (existsSync21(path13)) {
|
|
34664
35145
|
found.push(candidate);
|
|
34665
35146
|
}
|
|
34666
35147
|
}
|
|
@@ -34670,8 +35151,8 @@ async function detectConfigFiles(projectRoot) {
|
|
|
34670
35151
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
34671
35152
|
const found = [];
|
|
34672
35153
|
for (const candidate of candidates) {
|
|
34673
|
-
const path13 =
|
|
34674
|
-
if (
|
|
35154
|
+
const path13 = join26(projectRoot, candidate);
|
|
35155
|
+
if (existsSync21(path13)) {
|
|
34675
35156
|
found.push(candidate);
|
|
34676
35157
|
}
|
|
34677
35158
|
}
|
|
@@ -34683,7 +35164,7 @@ async function scanProject(projectRoot) {
|
|
|
34683
35164
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
34684
35165
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
34685
35166
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
34686
|
-
const projectName = packageManifest?.name ||
|
|
35167
|
+
const projectName = packageManifest?.name || basename3(projectRoot);
|
|
34687
35168
|
return {
|
|
34688
35169
|
projectName,
|
|
34689
35170
|
fileTree,
|
|
@@ -34831,13 +35312,13 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
34831
35312
|
}
|
|
34832
35313
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
34833
35314
|
const logger = getLogger();
|
|
34834
|
-
const naxDir =
|
|
34835
|
-
const contextPath =
|
|
34836
|
-
if (
|
|
35315
|
+
const naxDir = join26(repoRoot, ".nax", "mono", packagePath);
|
|
35316
|
+
const contextPath = join26(naxDir, "context.md");
|
|
35317
|
+
if (existsSync21(contextPath) && !force) {
|
|
34837
35318
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
34838
35319
|
return;
|
|
34839
35320
|
}
|
|
34840
|
-
if (!
|
|
35321
|
+
if (!existsSync21(naxDir)) {
|
|
34841
35322
|
await mkdir2(naxDir, { recursive: true });
|
|
34842
35323
|
}
|
|
34843
35324
|
const content = generatePackageContextTemplate(packagePath);
|
|
@@ -34846,13 +35327,13 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
34846
35327
|
}
|
|
34847
35328
|
async function initContext(projectRoot, options = {}) {
|
|
34848
35329
|
const logger = getLogger();
|
|
34849
|
-
const naxDir =
|
|
34850
|
-
const contextPath =
|
|
34851
|
-
if (
|
|
35330
|
+
const naxDir = join26(projectRoot, ".nax");
|
|
35331
|
+
const contextPath = join26(naxDir, "context.md");
|
|
35332
|
+
if (existsSync21(contextPath) && !options.force) {
|
|
34852
35333
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
34853
35334
|
return;
|
|
34854
35335
|
}
|
|
34855
|
-
if (!
|
|
35336
|
+
if (!existsSync21(naxDir)) {
|
|
34856
35337
|
await mkdir2(naxDir, { recursive: true });
|
|
34857
35338
|
}
|
|
34858
35339
|
const scan = await scanProject(projectRoot);
|
|
@@ -34877,23 +35358,23 @@ var init_init_context = __esm(() => {
|
|
|
34877
35358
|
|
|
34878
35359
|
// src/utils/path-security.ts
|
|
34879
35360
|
import { realpathSync as realpathSync3 } from "fs";
|
|
34880
|
-
import { dirname as
|
|
35361
|
+
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join27, normalize as normalize2, resolve as resolve8 } from "path";
|
|
34881
35362
|
function safeRealpathForComparison(p) {
|
|
34882
35363
|
try {
|
|
34883
35364
|
return realpathSync3(p);
|
|
34884
35365
|
} catch {
|
|
34885
|
-
const parent =
|
|
35366
|
+
const parent = dirname4(p);
|
|
34886
35367
|
if (parent === p)
|
|
34887
35368
|
return normalize2(p);
|
|
34888
35369
|
const resolvedParent = safeRealpathForComparison(parent);
|
|
34889
|
-
return
|
|
35370
|
+
return join27(resolvedParent, p.split("/").pop() ?? "");
|
|
34890
35371
|
}
|
|
34891
35372
|
}
|
|
34892
35373
|
function validateModulePath(modulePath, allowedRoots) {
|
|
34893
35374
|
if (!modulePath) {
|
|
34894
35375
|
return { valid: false, error: "Module path is empty" };
|
|
34895
35376
|
}
|
|
34896
|
-
const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(
|
|
35377
|
+
const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve8(r)));
|
|
34897
35378
|
if (isAbsolute5(modulePath)) {
|
|
34898
35379
|
const normalized = normalize2(modulePath);
|
|
34899
35380
|
const resolved = safeRealpathForComparison(normalized);
|
|
@@ -34903,8 +35384,8 @@ function validateModulePath(modulePath, allowedRoots) {
|
|
|
34903
35384
|
}
|
|
34904
35385
|
} else {
|
|
34905
35386
|
for (let i = 0;i < allowedRoots.length; i++) {
|
|
34906
|
-
const originalRoot =
|
|
34907
|
-
const absoluteInput =
|
|
35387
|
+
const originalRoot = resolve8(allowedRoots[i]);
|
|
35388
|
+
const absoluteInput = resolve8(join27(originalRoot, modulePath));
|
|
34908
35389
|
const resolved = safeRealpathForComparison(absoluteInput);
|
|
34909
35390
|
const resolvedRoot = resolvedRoots[i];
|
|
34910
35391
|
if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
|
|
@@ -35265,11 +35746,11 @@ function getSafeLogger6() {
|
|
|
35265
35746
|
return getSafeLogger();
|
|
35266
35747
|
}
|
|
35267
35748
|
function extractPluginName(pluginPath) {
|
|
35268
|
-
const
|
|
35269
|
-
if (
|
|
35749
|
+
const basename5 = path13.basename(pluginPath);
|
|
35750
|
+
if (basename5 === "index.ts" || basename5 === "index.js" || basename5 === "index.mjs") {
|
|
35270
35751
|
return path13.basename(path13.dirname(pluginPath));
|
|
35271
35752
|
}
|
|
35272
|
-
return
|
|
35753
|
+
return basename5.replace(/\.(ts|js|mjs)$/, "");
|
|
35273
35754
|
}
|
|
35274
35755
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
|
|
35275
35756
|
const loadedPlugins = [];
|
|
@@ -35430,7 +35911,7 @@ async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots =
|
|
|
35430
35911
|
return null;
|
|
35431
35912
|
}
|
|
35432
35913
|
}
|
|
35433
|
-
var _pluginErrorSink = (
|
|
35914
|
+
var _pluginErrorSink = () => {};
|
|
35434
35915
|
var init_loader4 = __esm(() => {
|
|
35435
35916
|
init_logger2();
|
|
35436
35917
|
init_path_security2();
|
|
@@ -35440,19 +35921,19 @@ var init_loader4 = __esm(() => {
|
|
|
35440
35921
|
});
|
|
35441
35922
|
|
|
35442
35923
|
// src/hooks/runner.ts
|
|
35443
|
-
import { join as
|
|
35924
|
+
import { join as join42 } from "path";
|
|
35444
35925
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
35445
35926
|
let globalHooks = { hooks: {} };
|
|
35446
35927
|
let projectHooks = { hooks: {} };
|
|
35447
35928
|
let skipGlobal = false;
|
|
35448
|
-
const projectPath =
|
|
35929
|
+
const projectPath = join42(projectDir, "hooks.json");
|
|
35449
35930
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
35450
35931
|
if (projectData) {
|
|
35451
35932
|
projectHooks = projectData;
|
|
35452
35933
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
35453
35934
|
}
|
|
35454
35935
|
if (!skipGlobal && globalDir) {
|
|
35455
|
-
const globalPath =
|
|
35936
|
+
const globalPath = join42(globalDir, "hooks.json");
|
|
35456
35937
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
35457
35938
|
if (globalData) {
|
|
35458
35939
|
globalHooks = globalData;
|
|
@@ -35652,7 +36133,7 @@ var package_default;
|
|
|
35652
36133
|
var init_package = __esm(() => {
|
|
35653
36134
|
package_default = {
|
|
35654
36135
|
name: "@nathapp/nax",
|
|
35655
|
-
version: "0.
|
|
36136
|
+
version: "0.59.1",
|
|
35656
36137
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
35657
36138
|
type: "module",
|
|
35658
36139
|
bin: {
|
|
@@ -35732,8 +36213,8 @@ var init_version = __esm(() => {
|
|
|
35732
36213
|
NAX_VERSION = package_default.version;
|
|
35733
36214
|
NAX_COMMIT = (() => {
|
|
35734
36215
|
try {
|
|
35735
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
35736
|
-
return "
|
|
36216
|
+
if (/^[0-9a-f]{6,10}$/.test("b8492d03"))
|
|
36217
|
+
return "b8492d03";
|
|
35737
36218
|
} catch {}
|
|
35738
36219
|
try {
|
|
35739
36220
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -36029,18 +36510,18 @@ var init_crash_signals = __esm(() => {
|
|
|
36029
36510
|
|
|
36030
36511
|
// src/execution/crash-recovery.ts
|
|
36031
36512
|
function installCrashHandlers(ctx) {
|
|
36032
|
-
if (
|
|
36033
|
-
|
|
36513
|
+
if (activeCleanup) {
|
|
36514
|
+
activeCleanup();
|
|
36034
36515
|
}
|
|
36035
36516
|
const cleanup = installSignalHandlers(ctx);
|
|
36036
|
-
|
|
36037
|
-
return () => {
|
|
36517
|
+
activeCleanup = () => {
|
|
36038
36518
|
cleanup();
|
|
36039
36519
|
stopHeartbeat();
|
|
36040
|
-
|
|
36520
|
+
activeCleanup = null;
|
|
36041
36521
|
};
|
|
36522
|
+
return activeCleanup;
|
|
36042
36523
|
}
|
|
36043
|
-
var
|
|
36524
|
+
var activeCleanup = null;
|
|
36044
36525
|
var init_crash_recovery = __esm(() => {
|
|
36045
36526
|
init_crash_heartbeat();
|
|
36046
36527
|
init_crash_signals();
|
|
@@ -36361,12 +36842,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
|
|
|
36361
36842
|
semanticVerdicts: options.semanticVerdicts
|
|
36362
36843
|
});
|
|
36363
36844
|
try {
|
|
36845
|
+
const timeoutSeconds = (config2.acceptance?.timeoutMs ?? 120000) / 1000;
|
|
36364
36846
|
const result = await agent.run({
|
|
36365
36847
|
prompt,
|
|
36366
36848
|
workdir,
|
|
36367
36849
|
modelTier: undefined,
|
|
36368
36850
|
modelDef,
|
|
36369
|
-
timeoutSeconds
|
|
36851
|
+
timeoutSeconds,
|
|
36370
36852
|
sessionRole: "diagnose",
|
|
36371
36853
|
acpSessionName: sessionName,
|
|
36372
36854
|
featureName,
|
|
@@ -36375,12 +36857,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
|
|
|
36375
36857
|
});
|
|
36376
36858
|
const diagnosis = parseDiagnosisResult(result.output);
|
|
36377
36859
|
if (diagnosis) {
|
|
36378
|
-
return diagnosis;
|
|
36860
|
+
return { ...diagnosis, cost: result.estimatedCost ?? 0 };
|
|
36379
36861
|
}
|
|
36380
36862
|
return {
|
|
36381
36863
|
verdict: "source_bug",
|
|
36382
36864
|
reasoning: "diagnosis failed \u2014 falling back to source fix",
|
|
36383
|
-
confidence: 0
|
|
36865
|
+
confidence: 0,
|
|
36866
|
+
cost: result.estimatedCost ?? 0
|
|
36384
36867
|
};
|
|
36385
36868
|
} catch {
|
|
36386
36869
|
return {
|
|
@@ -36492,7 +36975,7 @@ __export(exports_acceptance_loop, {
|
|
|
36492
36975
|
_regenerateDeps: () => _regenerateDeps,
|
|
36493
36976
|
_acceptanceLoopDeps: () => _acceptanceLoopDeps
|
|
36494
36977
|
});
|
|
36495
|
-
import path15, { join as
|
|
36978
|
+
import path15, { join as join43 } from "path";
|
|
36496
36979
|
function isStubTestFile(content) {
|
|
36497
36980
|
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
36498
36981
|
}
|
|
@@ -36581,15 +37064,16 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
36581
37064
|
agent: ctx.config.autoMode.defaultAgent,
|
|
36582
37065
|
iteration: iterations
|
|
36583
37066
|
}), ctx.workdir);
|
|
36584
|
-
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(
|
|
37067
|
+
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join43(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
36585
37068
|
const fixContext = {
|
|
36586
|
-
config:
|
|
36587
|
-
|
|
37069
|
+
config: fixEffectiveConfig,
|
|
37070
|
+
rootConfig: ctx.config,
|
|
36588
37071
|
prd,
|
|
36589
37072
|
story,
|
|
36590
37073
|
stories: [story],
|
|
36591
37074
|
routing,
|
|
36592
|
-
|
|
37075
|
+
projectDir: ctx.workdir,
|
|
37076
|
+
workdir: story.workdir ? join43(ctx.workdir, story.workdir) : ctx.workdir,
|
|
36593
37077
|
featureDir: ctx.featureDir,
|
|
36594
37078
|
hooks: ctx.hooks,
|
|
36595
37079
|
plugins: ctx.pluginRegistry,
|
|
@@ -36768,6 +37252,7 @@ async function runFixRouting(options) {
|
|
|
36768
37252
|
storyId,
|
|
36769
37253
|
semanticVerdicts: options.semanticVerdicts
|
|
36770
37254
|
});
|
|
37255
|
+
const diagnosisCost = diagnosis.cost ?? 0;
|
|
36771
37256
|
logger?.info("acceptance.diagnosis", "Diagnosis complete", {
|
|
36772
37257
|
verdict: diagnosis.verdict,
|
|
36773
37258
|
confidence: diagnosis.confidence,
|
|
@@ -36777,7 +37262,7 @@ async function runFixRouting(options) {
|
|
|
36777
37262
|
logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
|
|
36778
37263
|
if (!agent) {
|
|
36779
37264
|
logger?.error("acceptance", "Agent not found for source fix execution");
|
|
36780
|
-
return { fixed: false, cost:
|
|
37265
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36781
37266
|
}
|
|
36782
37267
|
let fixAttempts = 0;
|
|
36783
37268
|
while (fixAttempts < fixMaxRetries) {
|
|
@@ -36799,7 +37284,7 @@ async function runFixRouting(options) {
|
|
|
36799
37284
|
attempt: fixAttempts
|
|
36800
37285
|
});
|
|
36801
37286
|
if (fixResult.success) {
|
|
36802
|
-
return { fixed: true, cost: fixResult.cost, prdDirty: false };
|
|
37287
|
+
return { fixed: true, cost: fixResult.cost + diagnosisCost, prdDirty: false };
|
|
36803
37288
|
}
|
|
36804
37289
|
logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
|
|
36805
37290
|
attempt: fixAttempts,
|
|
@@ -36812,13 +37297,13 @@ async function runFixRouting(options) {
|
|
|
36812
37297
|
break;
|
|
36813
37298
|
}
|
|
36814
37299
|
}
|
|
36815
|
-
return { fixed: false, cost:
|
|
37300
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36816
37301
|
}
|
|
36817
37302
|
if (diagnosis.verdict === "test_bug") {
|
|
36818
37303
|
logger?.info("acceptance", "Diagnosis: test_bug \u2014 regenerating acceptance test");
|
|
36819
37304
|
if (!ctx.featureDir) {
|
|
36820
37305
|
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
36821
|
-
return { fixed: false, cost:
|
|
37306
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36822
37307
|
}
|
|
36823
37308
|
const testPath = await findExistingAcceptanceTestPath({
|
|
36824
37309
|
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
@@ -36835,29 +37320,29 @@ async function runFixRouting(options) {
|
|
|
36835
37320
|
language: ctx.config.project?.language
|
|
36836
37321
|
})
|
|
36837
37322
|
});
|
|
36838
|
-
return { fixed: false, cost:
|
|
37323
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36839
37324
|
}
|
|
36840
37325
|
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
36841
37326
|
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
36842
37327
|
outcome: regenerated ? "success" : "failure"
|
|
36843
37328
|
});
|
|
36844
37329
|
if (!regenerated) {
|
|
36845
|
-
return { fixed: false, cost:
|
|
37330
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36846
37331
|
}
|
|
36847
37332
|
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
36848
37333
|
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
36849
37334
|
if (acceptanceResult.action === "continue") {
|
|
36850
37335
|
logger?.info("acceptance", "Acceptance passed after test regeneration");
|
|
36851
|
-
return { fixed: true, cost:
|
|
37336
|
+
return { fixed: true, cost: diagnosisCost, prdDirty: true };
|
|
36852
37337
|
}
|
|
36853
37338
|
logger?.warn("acceptance", "Acceptance still failing after test regeneration");
|
|
36854
|
-
return { fixed: false, cost:
|
|
37339
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: true };
|
|
36855
37340
|
}
|
|
36856
37341
|
if (diagnosis.verdict === "both") {
|
|
36857
37342
|
logger?.info("acceptance", "Diagnosis: both \u2014 executing source fix then regenerating test if needed");
|
|
36858
37343
|
if (!agent) {
|
|
36859
37344
|
logger?.error("acceptance", "Agent not found for source fix execution");
|
|
36860
|
-
return { fixed: false, cost:
|
|
37345
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36861
37346
|
}
|
|
36862
37347
|
let sourceFixSuccess = false;
|
|
36863
37348
|
let sourceFixCost = 0;
|
|
@@ -36897,19 +37382,19 @@ async function runFixRouting(options) {
|
|
|
36897
37382
|
}
|
|
36898
37383
|
}
|
|
36899
37384
|
if (!sourceFixSuccess) {
|
|
36900
|
-
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
37385
|
+
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36901
37386
|
}
|
|
36902
37387
|
logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
|
|
36903
37388
|
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
36904
37389
|
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
36905
37390
|
if (acceptanceResult.action === "continue") {
|
|
36906
37391
|
logger?.info("acceptance", "Acceptance passed after source fix");
|
|
36907
|
-
return { fixed: true, cost: sourceFixCost, prdDirty: false };
|
|
37392
|
+
return { fixed: true, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36908
37393
|
}
|
|
36909
37394
|
logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
|
|
36910
37395
|
if (!ctx.featureDir) {
|
|
36911
37396
|
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
36912
|
-
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
37397
|
+
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36913
37398
|
}
|
|
36914
37399
|
const testPath = await findExistingAcceptanceTestPath({
|
|
36915
37400
|
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
@@ -36926,15 +37411,15 @@ async function runFixRouting(options) {
|
|
|
36926
37411
|
language: ctx.config.project?.language
|
|
36927
37412
|
})
|
|
36928
37413
|
});
|
|
36929
|
-
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
37414
|
+
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
36930
37415
|
}
|
|
36931
37416
|
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
36932
37417
|
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
36933
37418
|
outcome: regenerated ? "success" : "failure"
|
|
36934
37419
|
});
|
|
36935
|
-
return { fixed: regenerated, cost: sourceFixCost, prdDirty: regenerated };
|
|
37420
|
+
return { fixed: regenerated, cost: sourceFixCost + diagnosisCost, prdDirty: regenerated };
|
|
36936
37421
|
}
|
|
36937
|
-
return { fixed: false, cost:
|
|
37422
|
+
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
36938
37423
|
}
|
|
36939
37424
|
async function runAcceptanceLoop(ctx) {
|
|
36940
37425
|
const logger = getSafeLogger();
|
|
@@ -36950,7 +37435,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
36950
37435
|
const firstStory = prd.userStories[0];
|
|
36951
37436
|
const acceptanceContext = {
|
|
36952
37437
|
config: ctx.config,
|
|
36953
|
-
|
|
37438
|
+
rootConfig: ctx.config,
|
|
36954
37439
|
prd,
|
|
36955
37440
|
story: firstStory,
|
|
36956
37441
|
stories: [firstStory],
|
|
@@ -36960,6 +37445,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
36960
37445
|
testStrategy: "test-after",
|
|
36961
37446
|
reasoning: "Acceptance validation"
|
|
36962
37447
|
},
|
|
37448
|
+
projectDir: ctx.workdir,
|
|
36963
37449
|
workdir: ctx.workdir,
|
|
36964
37450
|
featureDir: ctx.featureDir,
|
|
36965
37451
|
hooks: ctx.hooks,
|
|
@@ -37185,6 +37671,20 @@ async function runDeferredRegression(options) {
|
|
|
37185
37671
|
const testCommand = config2.quality.commands.test ?? "bun test";
|
|
37186
37672
|
const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
37187
37673
|
const maxRectificationAttempts = config2.execution.regressionGate?.maxRectificationAttempts ?? 2;
|
|
37674
|
+
const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
|
|
37675
|
+
const verifyOpts = {
|
|
37676
|
+
workdir,
|
|
37677
|
+
command: testCommand,
|
|
37678
|
+
timeoutSeconds,
|
|
37679
|
+
forceExit: config2.quality.forceExit,
|
|
37680
|
+
detectOpenHandles: config2.quality.detectOpenHandles,
|
|
37681
|
+
detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
|
|
37682
|
+
timeoutRetryCount: 0,
|
|
37683
|
+
gracePeriodMs: config2.quality.gracePeriodMs,
|
|
37684
|
+
drainTimeoutMs: config2.quality.drainTimeoutMs,
|
|
37685
|
+
shell: config2.quality.shell,
|
|
37686
|
+
stripEnvVars: config2.quality.stripEnvVars
|
|
37687
|
+
};
|
|
37188
37688
|
const counts = countStories(prd);
|
|
37189
37689
|
const passedStories = prd.userStories.filter((s) => s.status === "passed");
|
|
37190
37690
|
if (passedStories.length === 0) {
|
|
@@ -37202,19 +37702,7 @@ async function runDeferredRegression(options) {
|
|
|
37202
37702
|
totalStories: counts.total,
|
|
37203
37703
|
passedStories: passedStories.length
|
|
37204
37704
|
});
|
|
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
|
-
});
|
|
37705
|
+
const fullSuiteResult = await _regressionDeps.runVerification(verifyOpts);
|
|
37218
37706
|
if (fullSuiteResult.success) {
|
|
37219
37707
|
logger?.info("regression", "Full suite passed");
|
|
37220
37708
|
return {
|
|
@@ -37226,7 +37714,6 @@ async function runDeferredRegression(options) {
|
|
|
37226
37714
|
affectedStories: []
|
|
37227
37715
|
};
|
|
37228
37716
|
}
|
|
37229
|
-
const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
|
|
37230
37717
|
if (fullSuiteResult.status === "TIMEOUT" && acceptOnTimeout) {
|
|
37231
37718
|
logger?.warn("regression", "Full-suite regression gate timed out (accepted as pass)");
|
|
37232
37719
|
return {
|
|
@@ -37308,6 +37795,8 @@ async function runDeferredRegression(options) {
|
|
|
37308
37795
|
};
|
|
37309
37796
|
}
|
|
37310
37797
|
let rectificationAttempts = 0;
|
|
37798
|
+
let storiesRectified = 0;
|
|
37799
|
+
let currentTestOutput = fullSuiteResult.output;
|
|
37311
37800
|
const affectedStoriesList = Array.from(affectedStoriesObjs.values());
|
|
37312
37801
|
for (const story of affectedStoriesList) {
|
|
37313
37802
|
for (let attempt = 0;attempt < maxRectificationAttempts; attempt++) {
|
|
@@ -37319,32 +37808,51 @@ async function runDeferredRegression(options) {
|
|
|
37319
37808
|
story,
|
|
37320
37809
|
testCommand,
|
|
37321
37810
|
timeoutSeconds,
|
|
37322
|
-
testOutput:
|
|
37811
|
+
testOutput: currentTestOutput,
|
|
37323
37812
|
promptPrefix: `# DEFERRED REGRESSION: Full-Suite Failures
|
|
37324
37813
|
|
|
37325
37814
|
Your story ${story.id} broke tests in the full suite. Fix these regressions.`,
|
|
37326
37815
|
agentGetFn
|
|
37327
37816
|
});
|
|
37328
37817
|
if (fixed) {
|
|
37818
|
+
storiesRectified++;
|
|
37329
37819
|
logger?.info("regression", `Story ${story.id} rectified successfully`);
|
|
37820
|
+
logger?.info("regression", "Re-running full suite after story rectification", {
|
|
37821
|
+
storyId: story.id,
|
|
37822
|
+
storiesRectified,
|
|
37823
|
+
storiesRemaining: affectedStoriesList.length - storiesRectified
|
|
37824
|
+
});
|
|
37825
|
+
const midResult = await _regressionDeps.runVerification(verifyOpts);
|
|
37826
|
+
const midSuccess = midResult.success || midResult.status === "TIMEOUT" && acceptOnTimeout;
|
|
37827
|
+
if (midSuccess) {
|
|
37828
|
+
logger?.info("regression", "Full suite passed after story rectification \u2014 early exit", {
|
|
37829
|
+
storyId: story.id,
|
|
37830
|
+
storiesRectified,
|
|
37831
|
+
storiesSkipped: affectedStoriesList.length - storiesRectified,
|
|
37832
|
+
passCount: midResult.passCount ?? 0
|
|
37833
|
+
});
|
|
37834
|
+
return {
|
|
37835
|
+
success: true,
|
|
37836
|
+
failedTests: testFilesInFailures.size,
|
|
37837
|
+
failedTestFiles: Array.from(testFilesInFailures),
|
|
37838
|
+
passedTests: midResult.passCount ?? 0,
|
|
37839
|
+
rectificationAttempts,
|
|
37840
|
+
affectedStories: Array.from(affectedStories)
|
|
37841
|
+
};
|
|
37842
|
+
}
|
|
37843
|
+
logger?.warn("regression", "Full suite still failing after story rectification \u2014 continuing", {
|
|
37844
|
+
storyId: story.id,
|
|
37845
|
+
failCount: midResult.failCount ?? 0,
|
|
37846
|
+
passCount: midResult.passCount ?? 0
|
|
37847
|
+
});
|
|
37848
|
+
if (midResult.output)
|
|
37849
|
+
currentTestOutput = midResult.output;
|
|
37330
37850
|
break;
|
|
37331
37851
|
}
|
|
37332
37852
|
}
|
|
37333
37853
|
}
|
|
37334
37854
|
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
|
-
});
|
|
37855
|
+
const retryResult = await _regressionDeps.runVerification(verifyOpts);
|
|
37348
37856
|
const success2 = retryResult.success || retryResult.status === "TIMEOUT" && acceptOnTimeout;
|
|
37349
37857
|
if (success2) {
|
|
37350
37858
|
logger?.info("regression", "Deferred regression gate passed after rectification");
|
|
@@ -37601,12 +38109,12 @@ var init_headless_formatter = __esm(() => {
|
|
|
37601
38109
|
// src/pipeline/subscribers/events-writer.ts
|
|
37602
38110
|
import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
|
|
37603
38111
|
import { homedir as homedir5 } from "os";
|
|
37604
|
-
import { basename as
|
|
38112
|
+
import { basename as basename6, join as join44 } from "path";
|
|
37605
38113
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
37606
38114
|
const logger = getSafeLogger();
|
|
37607
|
-
const project =
|
|
37608
|
-
const eventsDir =
|
|
37609
|
-
const eventsFile =
|
|
38115
|
+
const project = basename6(workdir);
|
|
38116
|
+
const eventsDir = join44(homedir5(), ".nax", "events", project);
|
|
38117
|
+
const eventsFile = join44(eventsDir, "events.jsonl");
|
|
37610
38118
|
let dirReady = false;
|
|
37611
38119
|
const write = (line) => {
|
|
37612
38120
|
return (async () => {
|
|
@@ -37787,12 +38295,12 @@ var init_interaction2 = __esm(() => {
|
|
|
37787
38295
|
// src/pipeline/subscribers/registry.ts
|
|
37788
38296
|
import { mkdir as mkdir4, writeFile } from "fs/promises";
|
|
37789
38297
|
import { homedir as homedir6 } from "os";
|
|
37790
|
-
import { basename as
|
|
38298
|
+
import { basename as basename7, join as join45 } from "path";
|
|
37791
38299
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
37792
38300
|
const logger = getSafeLogger();
|
|
37793
|
-
const project =
|
|
37794
|
-
const runDir =
|
|
37795
|
-
const metaFile =
|
|
38301
|
+
const project = basename7(workdir);
|
|
38302
|
+
const runDir = join45(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
38303
|
+
const metaFile = join45(runDir, "meta.json");
|
|
37796
38304
|
const unsub = bus.on("run:started", (_ev) => {
|
|
37797
38305
|
return (async () => {
|
|
37798
38306
|
try {
|
|
@@ -37802,8 +38310,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
37802
38310
|
project,
|
|
37803
38311
|
feature,
|
|
37804
38312
|
workdir,
|
|
37805
|
-
statusPath:
|
|
37806
|
-
eventsDir:
|
|
38313
|
+
statusPath: join45(workdir, ".nax", "features", feature, "status.json"),
|
|
38314
|
+
eventsDir: join45(workdir, ".nax", "features", feature, "runs"),
|
|
37807
38315
|
registeredAt: new Date().toISOString()
|
|
37808
38316
|
};
|
|
37809
38317
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -38328,7 +38836,7 @@ function filterOutputFiles(files) {
|
|
|
38328
38836
|
}
|
|
38329
38837
|
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
38330
38838
|
const logger = getSafeLogger();
|
|
38331
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost
|
|
38839
|
+
const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
|
|
38332
38840
|
const prd = ctx.prd;
|
|
38333
38841
|
if (pipelineResult.context.storyMetrics) {
|
|
38334
38842
|
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
@@ -38376,7 +38884,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
|
|
|
38376
38884
|
const logger = getSafeLogger();
|
|
38377
38885
|
let prd = ctx.prd;
|
|
38378
38886
|
let prdDirty = false;
|
|
38379
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost
|
|
38887
|
+
const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
|
|
38380
38888
|
switch (pipelineResult.finalAction) {
|
|
38381
38889
|
case "pause":
|
|
38382
38890
|
markStoryPaused(prd, ctx.story.id);
|
|
@@ -38455,7 +38963,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
38455
38963
|
});
|
|
38456
38964
|
|
|
38457
38965
|
// src/execution/iteration-runner.ts
|
|
38458
|
-
import { join as
|
|
38966
|
+
import { join as join46 } from "path";
|
|
38459
38967
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
38460
38968
|
const logger = getSafeLogger();
|
|
38461
38969
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -38490,15 +38998,16 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
38490
38998
|
}
|
|
38491
38999
|
}
|
|
38492
39000
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
38493
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
39001
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
38494
39002
|
const pipelineContext = {
|
|
38495
|
-
config:
|
|
38496
|
-
|
|
39003
|
+
config: effectiveConfig,
|
|
39004
|
+
rootConfig: ctx.config,
|
|
38497
39005
|
prd,
|
|
38498
39006
|
story,
|
|
38499
39007
|
stories: storiesToExecute,
|
|
38500
39008
|
routing,
|
|
38501
|
-
|
|
39009
|
+
projectDir: ctx.workdir,
|
|
39010
|
+
workdir: story.workdir ? join46(ctx.workdir, story.workdir) : ctx.workdir,
|
|
38502
39011
|
prdPath: ctx.prdPath,
|
|
38503
39012
|
featureDir: ctx.featureDir,
|
|
38504
39013
|
hooks: ctx.hooks,
|
|
@@ -38523,13 +39032,6 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
38523
39032
|
await ctx.statusWriter.update(totalCost, iterations);
|
|
38524
39033
|
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
38525
39034
|
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
39035
|
const handlerCtx = {
|
|
38534
39036
|
config: ctx.config,
|
|
38535
39037
|
prd: currentPrd,
|
|
@@ -38552,26 +39054,36 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
38552
39054
|
storyStartTime,
|
|
38553
39055
|
statusWriter: ctx.statusWriter
|
|
38554
39056
|
};
|
|
39057
|
+
let iterResult;
|
|
38555
39058
|
if (pipelineResult.success) {
|
|
38556
|
-
const
|
|
38557
|
-
|
|
38558
|
-
prd:
|
|
38559
|
-
storiesCompletedDelta:
|
|
38560
|
-
costDelta:
|
|
38561
|
-
prdDirty:
|
|
39059
|
+
const r = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
39060
|
+
iterResult = {
|
|
39061
|
+
prd: r.prd,
|
|
39062
|
+
storiesCompletedDelta: r.storiesCompletedDelta,
|
|
39063
|
+
costDelta: r.costDelta,
|
|
39064
|
+
prdDirty: r.prdDirty,
|
|
38562
39065
|
finalAction: pipelineResult.finalAction
|
|
38563
39066
|
};
|
|
39067
|
+
} else {
|
|
39068
|
+
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
39069
|
+
iterResult = {
|
|
39070
|
+
prd: r.prd,
|
|
39071
|
+
storiesCompletedDelta: 0,
|
|
39072
|
+
costDelta: r.costDelta,
|
|
39073
|
+
prdDirty: r.prdDirty,
|
|
39074
|
+
finalAction: pipelineResult.finalAction,
|
|
39075
|
+
reason: pipelineResult.reason,
|
|
39076
|
+
subStoryCount: pipelineResult.subStoryCount
|
|
39077
|
+
};
|
|
38564
39078
|
}
|
|
38565
|
-
|
|
38566
|
-
|
|
38567
|
-
|
|
38568
|
-
|
|
38569
|
-
|
|
38570
|
-
|
|
38571
|
-
|
|
38572
|
-
|
|
38573
|
-
subStoryCount: pipelineResult.subStoryCount
|
|
38574
|
-
};
|
|
39079
|
+
pipelineContext.agentResult = undefined;
|
|
39080
|
+
pipelineContext.prompt = undefined;
|
|
39081
|
+
pipelineContext.contextMarkdown = undefined;
|
|
39082
|
+
pipelineContext.builtContext = undefined;
|
|
39083
|
+
pipelineContext.verifyResult = undefined;
|
|
39084
|
+
pipelineContext.reviewResult = undefined;
|
|
39085
|
+
pipelineContext.constitution = undefined;
|
|
39086
|
+
return iterResult;
|
|
38575
39087
|
}
|
|
38576
39088
|
var _iterationRunnerDeps;
|
|
38577
39089
|
var init_iteration_runner = __esm(() => {
|
|
@@ -38651,6 +39163,7 @@ __export(exports_parallel_worker, {
|
|
|
38651
39163
|
executeStoryInWorktree: () => executeStoryInWorktree,
|
|
38652
39164
|
executeParallelBatch: () => executeParallelBatch
|
|
38653
39165
|
});
|
|
39166
|
+
import { join as join47 } from "path";
|
|
38654
39167
|
async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
|
|
38655
39168
|
const logger = getSafeLogger();
|
|
38656
39169
|
try {
|
|
@@ -38665,10 +39178,12 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
38665
39178
|
}
|
|
38666
39179
|
const pipelineContext = {
|
|
38667
39180
|
...context,
|
|
38668
|
-
|
|
39181
|
+
config: context.config,
|
|
39182
|
+
rootConfig: context.rootConfig,
|
|
38669
39183
|
story,
|
|
38670
39184
|
stories: [story],
|
|
38671
|
-
|
|
39185
|
+
projectDir: context.projectDir,
|
|
39186
|
+
workdir: story.workdir ? join47(worktreePath, story.workdir) : worktreePath,
|
|
38672
39187
|
routing,
|
|
38673
39188
|
storyGitRef: storyGitRef ?? undefined
|
|
38674
39189
|
};
|
|
@@ -38713,7 +39228,7 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
|
|
|
38713
39228
|
}
|
|
38714
39229
|
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
38715
39230
|
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
38716
|
-
const storyContext = storyConfig ? { ...context,
|
|
39231
|
+
const storyContext = storyConfig ? { ...context, config: storyConfig } : context;
|
|
38717
39232
|
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
38718
39233
|
results.totalCost += result.cost;
|
|
38719
39234
|
results.storyCosts.set(story.id, result.cost);
|
|
@@ -38755,19 +39270,19 @@ __export(exports_manager, {
|
|
|
38755
39270
|
_managerDeps: () => _managerDeps,
|
|
38756
39271
|
WorktreeManager: () => WorktreeManager
|
|
38757
39272
|
});
|
|
38758
|
-
import { existsSync as
|
|
39273
|
+
import { existsSync as existsSync29, symlinkSync } from "fs";
|
|
38759
39274
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
38760
|
-
import { join as
|
|
39275
|
+
import { join as join48 } from "path";
|
|
38761
39276
|
|
|
38762
39277
|
class WorktreeManager {
|
|
38763
39278
|
async ensureGitExcludes(projectRoot) {
|
|
38764
39279
|
const logger = getSafeLogger();
|
|
38765
|
-
const infoDir =
|
|
38766
|
-
const excludePath =
|
|
39280
|
+
const infoDir = join48(projectRoot, ".git", "info");
|
|
39281
|
+
const excludePath = join48(infoDir, "exclude");
|
|
38767
39282
|
try {
|
|
38768
39283
|
await mkdir5(infoDir, { recursive: true });
|
|
38769
39284
|
let existing = "";
|
|
38770
|
-
if (
|
|
39285
|
+
if (existsSync29(excludePath)) {
|
|
38771
39286
|
existing = await Bun.file(excludePath).text();
|
|
38772
39287
|
}
|
|
38773
39288
|
const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
@@ -38790,7 +39305,7 @@ ${missing.join(`
|
|
|
38790
39305
|
}
|
|
38791
39306
|
async create(projectRoot, storyId) {
|
|
38792
39307
|
validateStoryId(storyId);
|
|
38793
|
-
const worktreePath =
|
|
39308
|
+
const worktreePath = join48(projectRoot, ".nax-wt", storyId);
|
|
38794
39309
|
const branchName = `nax/${storyId}`;
|
|
38795
39310
|
try {
|
|
38796
39311
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -38831,9 +39346,9 @@ ${missing.join(`
|
|
|
38831
39346
|
}
|
|
38832
39347
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
38833
39348
|
}
|
|
38834
|
-
const nodeModulesSource =
|
|
38835
|
-
if (
|
|
38836
|
-
const nodeModulesTarget =
|
|
39349
|
+
const nodeModulesSource = join48(projectRoot, "node_modules");
|
|
39350
|
+
if (existsSync29(nodeModulesSource)) {
|
|
39351
|
+
const nodeModulesTarget = join48(worktreePath, "node_modules");
|
|
38837
39352
|
try {
|
|
38838
39353
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
38839
39354
|
} catch (error48) {
|
|
@@ -38841,9 +39356,9 @@ ${missing.join(`
|
|
|
38841
39356
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
38842
39357
|
}
|
|
38843
39358
|
}
|
|
38844
|
-
const envSource =
|
|
38845
|
-
if (
|
|
38846
|
-
const envTarget =
|
|
39359
|
+
const envSource = join48(projectRoot, ".env");
|
|
39360
|
+
if (existsSync29(envSource)) {
|
|
39361
|
+
const envTarget = join48(worktreePath, ".env");
|
|
38847
39362
|
try {
|
|
38848
39363
|
symlinkSync(envSource, envTarget, "file");
|
|
38849
39364
|
} catch (error48) {
|
|
@@ -38854,7 +39369,7 @@ ${missing.join(`
|
|
|
38854
39369
|
}
|
|
38855
39370
|
async remove(projectRoot, storyId) {
|
|
38856
39371
|
validateStoryId(storyId);
|
|
38857
|
-
const worktreePath =
|
|
39372
|
+
const worktreePath = join48(projectRoot, ".nax-wt", storyId);
|
|
38858
39373
|
const branchName = `nax/${storyId}`;
|
|
38859
39374
|
try {
|
|
38860
39375
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -39214,10 +39729,11 @@ async function rectifyConflictedStory(options) {
|
|
|
39214
39729
|
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
39215
39730
|
const pipelineContext = {
|
|
39216
39731
|
config: config2,
|
|
39217
|
-
|
|
39732
|
+
rootConfig: config2,
|
|
39218
39733
|
prd,
|
|
39219
39734
|
story,
|
|
39220
39735
|
stories: [story],
|
|
39736
|
+
projectDir: workdir,
|
|
39221
39737
|
workdir: worktreePath,
|
|
39222
39738
|
featureDir: undefined,
|
|
39223
39739
|
hooks,
|
|
@@ -39454,8 +39970,9 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39454
39970
|
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
39455
39971
|
preRunCtx = {
|
|
39456
39972
|
config: ctx.config,
|
|
39457
|
-
|
|
39973
|
+
rootConfig: ctx.config,
|
|
39458
39974
|
prd,
|
|
39975
|
+
projectDir: ctx.workdir,
|
|
39459
39976
|
workdir: ctx.workdir,
|
|
39460
39977
|
featureDir: ctx.featureDir,
|
|
39461
39978
|
story: prd.userStories[0],
|
|
@@ -39500,6 +40017,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39500
40017
|
const readyStories = getAllReadyStories(prd);
|
|
39501
40018
|
const batch = _unifiedExecutorDeps.selectIndependentBatch(readyStories, ctx.parallelCount);
|
|
39502
40019
|
if (batch.length > 1) {
|
|
40020
|
+
ctx.onBeforeStory?.();
|
|
39503
40021
|
for (const story of batch) {
|
|
39504
40022
|
pipelineEventBus.emit({
|
|
39505
40023
|
type: "story:started",
|
|
@@ -39525,8 +40043,9 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39525
40043
|
maxConcurrency: ctx.parallelCount,
|
|
39526
40044
|
pipelineContext: {
|
|
39527
40045
|
config: ctx.config,
|
|
39528
|
-
|
|
40046
|
+
rootConfig: ctx.config,
|
|
39529
40047
|
prd,
|
|
40048
|
+
projectDir: ctx.workdir,
|
|
39530
40049
|
hooks: ctx.hooks,
|
|
39531
40050
|
featureDir: ctx.featureDir,
|
|
39532
40051
|
agentGetFn: ctx.agentGetFn,
|
|
@@ -39640,6 +40159,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39640
40159
|
}
|
|
39641
40160
|
pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
|
|
39642
40161
|
}
|
|
40162
|
+
ctx.onBeforeStory?.();
|
|
39643
40163
|
pipelineEventBus.emit({
|
|
39644
40164
|
type: "story:started",
|
|
39645
40165
|
storyId: singleStory.id,
|
|
@@ -39702,6 +40222,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
39702
40222
|
}
|
|
39703
40223
|
pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
|
|
39704
40224
|
}
|
|
40225
|
+
ctx.onBeforeStory?.();
|
|
39705
40226
|
pipelineEventBus.emit({
|
|
39706
40227
|
type: "story:started",
|
|
39707
40228
|
storyId: selection.story.id,
|
|
@@ -39790,16 +40311,16 @@ var init_unified_executor = __esm(() => {
|
|
|
39790
40311
|
});
|
|
39791
40312
|
|
|
39792
40313
|
// src/project/detector.ts
|
|
39793
|
-
import { join as
|
|
40314
|
+
import { join as join49 } from "path";
|
|
39794
40315
|
async function detectLanguage(workdir, pkg) {
|
|
39795
40316
|
const deps = _detectorDeps;
|
|
39796
|
-
if (await deps.fileExists(
|
|
40317
|
+
if (await deps.fileExists(join49(workdir, "go.mod")))
|
|
39797
40318
|
return "go";
|
|
39798
|
-
if (await deps.fileExists(
|
|
40319
|
+
if (await deps.fileExists(join49(workdir, "Cargo.toml")))
|
|
39799
40320
|
return "rust";
|
|
39800
|
-
if (await deps.fileExists(
|
|
40321
|
+
if (await deps.fileExists(join49(workdir, "pyproject.toml")))
|
|
39801
40322
|
return "python";
|
|
39802
|
-
if (await deps.fileExists(
|
|
40323
|
+
if (await deps.fileExists(join49(workdir, "requirements.txt")))
|
|
39803
40324
|
return "python";
|
|
39804
40325
|
if (pkg != null) {
|
|
39805
40326
|
const allDeps = {
|
|
@@ -39859,18 +40380,18 @@ async function detectLintTool(workdir, language) {
|
|
|
39859
40380
|
if (language === "python")
|
|
39860
40381
|
return "ruff";
|
|
39861
40382
|
const deps = _detectorDeps;
|
|
39862
|
-
if (await deps.fileExists(
|
|
40383
|
+
if (await deps.fileExists(join49(workdir, "biome.json")))
|
|
39863
40384
|
return "biome";
|
|
39864
|
-
if (await deps.fileExists(
|
|
40385
|
+
if (await deps.fileExists(join49(workdir, ".eslintrc")))
|
|
39865
40386
|
return "eslint";
|
|
39866
|
-
if (await deps.fileExists(
|
|
40387
|
+
if (await deps.fileExists(join49(workdir, ".eslintrc.js")))
|
|
39867
40388
|
return "eslint";
|
|
39868
|
-
if (await deps.fileExists(
|
|
40389
|
+
if (await deps.fileExists(join49(workdir, ".eslintrc.json")))
|
|
39869
40390
|
return "eslint";
|
|
39870
40391
|
return;
|
|
39871
40392
|
}
|
|
39872
40393
|
async function detectProjectProfile(workdir, existing) {
|
|
39873
|
-
const pkg = await _detectorDeps.readJson(
|
|
40394
|
+
const pkg = await _detectorDeps.readJson(join49(workdir, "package.json"));
|
|
39874
40395
|
const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
|
|
39875
40396
|
const type = existing.type !== undefined ? existing.type : detectType(pkg);
|
|
39876
40397
|
const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
|
|
@@ -39907,7 +40428,7 @@ var init_project = __esm(() => {
|
|
|
39907
40428
|
|
|
39908
40429
|
// src/execution/status-file.ts
|
|
39909
40430
|
import { rename, unlink as unlink3 } from "fs/promises";
|
|
39910
|
-
import { resolve as
|
|
40431
|
+
import { resolve as resolve10 } from "path";
|
|
39911
40432
|
function countProgress(prd) {
|
|
39912
40433
|
const stories = prd.userStories;
|
|
39913
40434
|
const passed = stories.filter((s) => s.status === "passed").length;
|
|
@@ -39952,7 +40473,7 @@ function buildStatusSnapshot(state) {
|
|
|
39952
40473
|
return snapshot;
|
|
39953
40474
|
}
|
|
39954
40475
|
async function writeStatusFile(filePath, status) {
|
|
39955
|
-
const resolvedPath =
|
|
40476
|
+
const resolvedPath = resolve10(filePath);
|
|
39956
40477
|
if (filePath.includes("../") || filePath.includes("..\\")) {
|
|
39957
40478
|
throw new Error("Invalid status file path: path traversal detected");
|
|
39958
40479
|
}
|
|
@@ -39966,7 +40487,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
39966
40487
|
var init_status_file = () => {};
|
|
39967
40488
|
|
|
39968
40489
|
// src/execution/status-writer.ts
|
|
39969
|
-
import { join as
|
|
40490
|
+
import { join as join50 } from "path";
|
|
39970
40491
|
|
|
39971
40492
|
class StatusWriter {
|
|
39972
40493
|
statusFile;
|
|
@@ -40080,7 +40601,7 @@ class StatusWriter {
|
|
|
40080
40601
|
if (!this._prd)
|
|
40081
40602
|
return;
|
|
40082
40603
|
const safeLogger = getSafeLogger();
|
|
40083
|
-
const featureStatusPath =
|
|
40604
|
+
const featureStatusPath = join50(featureDir, "status.json");
|
|
40084
40605
|
const write = async () => {
|
|
40085
40606
|
try {
|
|
40086
40607
|
const base = this.getSnapshot(totalCost, iterations);
|
|
@@ -40291,7 +40812,7 @@ __export(exports_run_initialization, {
|
|
|
40291
40812
|
initializeRun: () => initializeRun,
|
|
40292
40813
|
_reconcileDeps: () => _reconcileDeps
|
|
40293
40814
|
});
|
|
40294
|
-
import { join as
|
|
40815
|
+
import { join as join51 } from "path";
|
|
40295
40816
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
40296
40817
|
const logger = getSafeLogger();
|
|
40297
40818
|
let reconciledCount = 0;
|
|
@@ -40309,7 +40830,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
40309
40830
|
});
|
|
40310
40831
|
continue;
|
|
40311
40832
|
}
|
|
40312
|
-
const effectiveWorkdir = story.workdir ?
|
|
40833
|
+
const effectiveWorkdir = story.workdir ? join51(workdir, story.workdir) : workdir;
|
|
40313
40834
|
try {
|
|
40314
40835
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
40315
40836
|
if (!reviewResult.success) {
|
|
@@ -40373,7 +40894,7 @@ function validateStoryCount(counts, config2) {
|
|
|
40373
40894
|
}
|
|
40374
40895
|
function logActiveProtocol(config2) {
|
|
40375
40896
|
const logger = getSafeLogger();
|
|
40376
|
-
const protocol = config2.agent?.protocol
|
|
40897
|
+
const protocol = config2.agent?.protocol;
|
|
40377
40898
|
logger?.info("run-initialization", `Agent protocol: ${protocol}`, { protocol });
|
|
40378
40899
|
}
|
|
40379
40900
|
async function initializeRun(ctx) {
|
|
@@ -41035,14 +41556,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41035
41556
|
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
41557
|
actScopeDepth = prevActScopeDepth;
|
|
41037
41558
|
}
|
|
41038
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
41559
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve11, reject) {
|
|
41039
41560
|
var queue = ReactSharedInternals.actQueue;
|
|
41040
41561
|
if (queue !== null)
|
|
41041
41562
|
if (queue.length !== 0)
|
|
41042
41563
|
try {
|
|
41043
41564
|
flushActQueue(queue);
|
|
41044
41565
|
enqueueTask(function() {
|
|
41045
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
41566
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
|
|
41046
41567
|
});
|
|
41047
41568
|
return;
|
|
41048
41569
|
} catch (error48) {
|
|
@@ -41050,7 +41571,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41050
41571
|
}
|
|
41051
41572
|
else
|
|
41052
41573
|
ReactSharedInternals.actQueue = null;
|
|
41053
|
-
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) :
|
|
41574
|
+
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve11(returnValue);
|
|
41054
41575
|
}
|
|
41055
41576
|
function flushActQueue(queue) {
|
|
41056
41577
|
if (!isFlushing) {
|
|
@@ -41226,14 +41747,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41226
41747
|
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
41748
|
});
|
|
41228
41749
|
return {
|
|
41229
|
-
then: function(
|
|
41750
|
+
then: function(resolve11, reject) {
|
|
41230
41751
|
didAwaitActCall = true;
|
|
41231
41752
|
thenable.then(function(returnValue) {
|
|
41232
41753
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
41233
41754
|
if (prevActScopeDepth === 0) {
|
|
41234
41755
|
try {
|
|
41235
41756
|
flushActQueue(queue), enqueueTask(function() {
|
|
41236
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
41757
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
|
|
41237
41758
|
});
|
|
41238
41759
|
} catch (error$0) {
|
|
41239
41760
|
ReactSharedInternals.thrownErrors.push(error$0);
|
|
@@ -41244,7 +41765,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41244
41765
|
reject(_thrownError);
|
|
41245
41766
|
}
|
|
41246
41767
|
} else
|
|
41247
|
-
|
|
41768
|
+
resolve11(returnValue);
|
|
41248
41769
|
}, function(error48) {
|
|
41249
41770
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
41250
41771
|
0 < ReactSharedInternals.thrownErrors.length ? (error48 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(error48)) : reject(error48);
|
|
@@ -41260,11 +41781,11 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
41260
41781
|
if (0 < ReactSharedInternals.thrownErrors.length)
|
|
41261
41782
|
throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
|
|
41262
41783
|
return {
|
|
41263
|
-
then: function(
|
|
41784
|
+
then: function(resolve11, reject) {
|
|
41264
41785
|
didAwaitActCall = true;
|
|
41265
41786
|
prevActScopeDepth === 0 ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
|
|
41266
|
-
return recursivelyFlushAsyncActWork(returnValue$jscomp$0,
|
|
41267
|
-
})) :
|
|
41787
|
+
return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve11, reject);
|
|
41788
|
+
})) : resolve11(returnValue$jscomp$0);
|
|
41268
41789
|
}
|
|
41269
41790
|
};
|
|
41270
41791
|
};
|
|
@@ -44106,8 +44627,8 @@ It can also happen if the client has a browser extension installed which messes
|
|
|
44106
44627
|
currentEntangledActionThenable = {
|
|
44107
44628
|
status: "pending",
|
|
44108
44629
|
value: undefined,
|
|
44109
|
-
then: function(
|
|
44110
|
-
entangledListeners.push(
|
|
44630
|
+
then: function(resolve11) {
|
|
44631
|
+
entangledListeners.push(resolve11);
|
|
44111
44632
|
}
|
|
44112
44633
|
};
|
|
44113
44634
|
}
|
|
@@ -44131,8 +44652,8 @@ It can also happen if the client has a browser extension installed which messes
|
|
|
44131
44652
|
status: "pending",
|
|
44132
44653
|
value: null,
|
|
44133
44654
|
reason: null,
|
|
44134
|
-
then: function(
|
|
44135
|
-
listeners.push(
|
|
44655
|
+
then: function(resolve11) {
|
|
44656
|
+
listeners.push(resolve11);
|
|
44136
44657
|
}
|
|
44137
44658
|
};
|
|
44138
44659
|
thenable.then(function() {
|
|
@@ -71522,9 +72043,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
71522
72043
|
|
|
71523
72044
|
// bin/nax.ts
|
|
71524
72045
|
init_source();
|
|
71525
|
-
import { existsSync as
|
|
72046
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
|
|
71526
72047
|
import { homedir as homedir8 } from "os";
|
|
71527
|
-
import { join as
|
|
72048
|
+
import { join as join53 } from "path";
|
|
71528
72049
|
|
|
71529
72050
|
// node_modules/commander/esm.mjs
|
|
71530
72051
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -71550,15 +72071,15 @@ import { join as join11 } from "path";
|
|
|
71550
72071
|
import { createInterface as createInterface2 } from "readline";
|
|
71551
72072
|
|
|
71552
72073
|
// src/analyze/scanner.ts
|
|
71553
|
-
import { existsSync as
|
|
72074
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
71554
72075
|
import { join as join4 } from "path";
|
|
71555
72076
|
async function scanCodebase(workdir) {
|
|
71556
72077
|
const srcPath = join4(workdir, "src");
|
|
71557
72078
|
const packageJsonPath = join4(workdir, "package.json");
|
|
71558
|
-
const fileTree =
|
|
72079
|
+
const fileTree = existsSync3(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
|
|
71559
72080
|
let dependencies = {};
|
|
71560
72081
|
let devDependencies = {};
|
|
71561
|
-
if (
|
|
72082
|
+
if (existsSync3(packageJsonPath)) {
|
|
71562
72083
|
try {
|
|
71563
72084
|
const pkg = await Bun.file(packageJsonPath).json();
|
|
71564
72085
|
dependencies = pkg.dependencies || {};
|
|
@@ -71619,16 +72140,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
|
|
|
71619
72140
|
} else {
|
|
71620
72141
|
patterns.push("Test framework: likely bun:test (no framework dependency)");
|
|
71621
72142
|
}
|
|
71622
|
-
if (
|
|
72143
|
+
if (existsSync3(join4(workdir, "test"))) {
|
|
71623
72144
|
patterns.push("Test directory: test/");
|
|
71624
72145
|
}
|
|
71625
|
-
if (
|
|
72146
|
+
if (existsSync3(join4(workdir, "__tests__"))) {
|
|
71626
72147
|
patterns.push("Test directory: __tests__/");
|
|
71627
72148
|
}
|
|
71628
|
-
if (
|
|
72149
|
+
if (existsSync3(join4(workdir, "tests"))) {
|
|
71629
72150
|
patterns.push("Test directory: tests/");
|
|
71630
72151
|
}
|
|
71631
|
-
const hasTestFiles =
|
|
72152
|
+
const hasTestFiles = existsSync3(join4(workdir, "test")) || existsSync3(join4(workdir, "src"));
|
|
71632
72153
|
if (hasTestFiles) {
|
|
71633
72154
|
patterns.push("Test files: *.test.ts, *.spec.ts");
|
|
71634
72155
|
}
|
|
@@ -71640,7 +72161,7 @@ init_test_strategy();
|
|
|
71640
72161
|
|
|
71641
72162
|
// src/context/generator.ts
|
|
71642
72163
|
init_path_security();
|
|
71643
|
-
import { existsSync as existsSync5
|
|
72164
|
+
import { existsSync as existsSync5 } from "fs";
|
|
71644
72165
|
import { join as join6, relative } from "path";
|
|
71645
72166
|
|
|
71646
72167
|
// src/context/injector.ts
|
|
@@ -71983,7 +72504,6 @@ var windsurfGenerator = {
|
|
|
71983
72504
|
// src/context/generator.ts
|
|
71984
72505
|
var _generatorDeps = {
|
|
71985
72506
|
existsSync: (p) => existsSync5(p),
|
|
71986
|
-
readFileSync: (p, enc) => readFileSync(p, enc),
|
|
71987
72507
|
readTextFile: (p) => Bun.file(p).text(),
|
|
71988
72508
|
writeFile: (p, content) => Bun.write(p, content),
|
|
71989
72509
|
buildProjectMetadata
|
|
@@ -72085,47 +72605,41 @@ async function discoverWorkspacePackages(repoRoot) {
|
|
|
72085
72605
|
}
|
|
72086
72606
|
}
|
|
72087
72607
|
const turboPath = join6(repoRoot, "turbo.json");
|
|
72088
|
-
|
|
72089
|
-
|
|
72090
|
-
|
|
72091
|
-
|
|
72092
|
-
|
|
72093
|
-
|
|
72094
|
-
} catch {}
|
|
72095
|
-
}
|
|
72608
|
+
try {
|
|
72609
|
+
const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
|
|
72610
|
+
if (Array.isArray(turbo.packages)) {
|
|
72611
|
+
await resolveGlobs(turbo.packages);
|
|
72612
|
+
}
|
|
72613
|
+
} catch {}
|
|
72096
72614
|
const pkgPath = join6(repoRoot, "package.json");
|
|
72097
|
-
|
|
72098
|
-
|
|
72099
|
-
|
|
72100
|
-
|
|
72101
|
-
|
|
72102
|
-
|
|
72103
|
-
|
|
72104
|
-
} catch {}
|
|
72105
|
-
}
|
|
72615
|
+
try {
|
|
72616
|
+
const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
|
|
72617
|
+
const ws = pkg.workspaces;
|
|
72618
|
+
const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
72619
|
+
if (patterns.length > 0)
|
|
72620
|
+
await resolveGlobs(patterns);
|
|
72621
|
+
} catch {}
|
|
72106
72622
|
const pnpmPath = join6(repoRoot, "pnpm-workspace.yaml");
|
|
72107
|
-
|
|
72108
|
-
|
|
72109
|
-
|
|
72110
|
-
const lines = raw.split(`
|
|
72623
|
+
try {
|
|
72624
|
+
const raw = await _generatorDeps.readTextFile(pnpmPath);
|
|
72625
|
+
const lines = raw.split(`
|
|
72111
72626
|
`);
|
|
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
|
-
}
|
|
72627
|
+
let inPackages = false;
|
|
72628
|
+
const patterns = [];
|
|
72629
|
+
for (const line of lines) {
|
|
72630
|
+
if (/^packages\s*:/.test(line)) {
|
|
72631
|
+
inPackages = true;
|
|
72632
|
+
continue;
|
|
72124
72633
|
}
|
|
72125
|
-
if (
|
|
72126
|
-
|
|
72127
|
-
|
|
72128
|
-
|
|
72634
|
+
if (inPackages && /^\s+-\s+/.test(line)) {
|
|
72635
|
+
patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
|
|
72636
|
+
} else if (inPackages && !/^\s/.test(line)) {
|
|
72637
|
+
break;
|
|
72638
|
+
}
|
|
72639
|
+
}
|
|
72640
|
+
if (patterns.length > 0)
|
|
72641
|
+
await resolveGlobs(patterns);
|
|
72642
|
+
} catch {}
|
|
72129
72643
|
return results.sort();
|
|
72130
72644
|
}
|
|
72131
72645
|
async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
|
|
@@ -72657,13 +73171,13 @@ function createCliInteractionBridge() {
|
|
|
72657
73171
|
process.stdout.write(`
|
|
72658
73172
|
\uD83E\uDD16 Agent: ${text}
|
|
72659
73173
|
You: `);
|
|
72660
|
-
return new Promise((
|
|
73174
|
+
return new Promise((resolve6) => {
|
|
72661
73175
|
const rl = createInterface2({ input: process.stdin, terminal: false });
|
|
72662
73176
|
rl.once("line", (line) => {
|
|
72663
73177
|
rl.close();
|
|
72664
|
-
|
|
73178
|
+
resolve6(line.trim());
|
|
72665
73179
|
});
|
|
72666
|
-
rl.once("close", () =>
|
|
73180
|
+
rl.once("close", () => resolve6(""));
|
|
72667
73181
|
});
|
|
72668
73182
|
}
|
|
72669
73183
|
};
|
|
@@ -73168,20 +73682,20 @@ async function displayModelEfficiency(workdir) {
|
|
|
73168
73682
|
// src/cli/status-features.ts
|
|
73169
73683
|
init_source();
|
|
73170
73684
|
import { existsSync as existsSync15, readdirSync as readdirSync4 } from "fs";
|
|
73171
|
-
import { join as join14, resolve as
|
|
73685
|
+
import { join as join14, resolve as resolve7 } from "path";
|
|
73172
73686
|
|
|
73173
73687
|
// src/commands/common.ts
|
|
73174
73688
|
init_path_security();
|
|
73175
73689
|
init_errors();
|
|
73176
73690
|
import { existsSync as existsSync14, readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
73177
|
-
import { join as join12, resolve as
|
|
73691
|
+
import { join as join12, resolve as resolve6 } from "path";
|
|
73178
73692
|
function resolveProject(options = {}) {
|
|
73179
73693
|
const { dir, feature } = options;
|
|
73180
73694
|
let projectRoot;
|
|
73181
73695
|
let naxDir;
|
|
73182
73696
|
let configPath;
|
|
73183
73697
|
if (dir) {
|
|
73184
|
-
projectRoot = realpathSync2(
|
|
73698
|
+
projectRoot = realpathSync2(resolve6(dir));
|
|
73185
73699
|
naxDir = join12(projectRoot, ".nax");
|
|
73186
73700
|
if (!existsSync14(naxDir)) {
|
|
73187
73701
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
@@ -73234,7 +73748,7 @@ No features found in this project.`;
|
|
|
73234
73748
|
};
|
|
73235
73749
|
}
|
|
73236
73750
|
function findProjectRoot(startDir) {
|
|
73237
|
-
let current =
|
|
73751
|
+
let current = resolve6(startDir);
|
|
73238
73752
|
let depth = 0;
|
|
73239
73753
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
73240
73754
|
const naxDir = join12(current, ".nax");
|
|
@@ -73551,7 +74065,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
73551
74065
|
if (options.feature) {
|
|
73552
74066
|
let featureDir;
|
|
73553
74067
|
if (options.dir) {
|
|
73554
|
-
featureDir = join14(
|
|
74068
|
+
featureDir = join14(resolve7(options.dir), ".nax", "features", options.feature);
|
|
73555
74069
|
} else {
|
|
73556
74070
|
const resolved = resolveProject({ feature: options.feature });
|
|
73557
74071
|
if (!resolved.featureDir) {
|
|
@@ -73661,8 +74175,8 @@ async function runsShowCommand(options) {
|
|
|
73661
74175
|
}
|
|
73662
74176
|
// src/cli/prompts-main.ts
|
|
73663
74177
|
init_logger2();
|
|
73664
|
-
import { existsSync as
|
|
73665
|
-
import { join as
|
|
74178
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync3 } from "fs";
|
|
74179
|
+
import { join as join24 } from "path";
|
|
73666
74180
|
|
|
73667
74181
|
// src/pipeline/index.ts
|
|
73668
74182
|
init_runner();
|
|
@@ -73737,7 +74251,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
73737
74251
|
|
|
73738
74252
|
// src/cli/prompts-tdd.ts
|
|
73739
74253
|
init_prompts2();
|
|
73740
|
-
import { join as
|
|
74254
|
+
import { join as join23 } from "path";
|
|
73741
74255
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
73742
74256
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
73743
74257
|
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 +74270,7 @@ ${frontmatter}---
|
|
|
73756
74270
|
|
|
73757
74271
|
${session.prompt}`;
|
|
73758
74272
|
if (outputDir) {
|
|
73759
|
-
const promptFile =
|
|
74273
|
+
const promptFile = join23(outputDir, `${story.id}.${session.role}.md`);
|
|
73760
74274
|
await Bun.write(promptFile, fullOutput);
|
|
73761
74275
|
logger.info("cli", "Written TDD prompt file", {
|
|
73762
74276
|
storyId: story.id,
|
|
@@ -73772,7 +74286,7 @@ ${"=".repeat(80)}`);
|
|
|
73772
74286
|
}
|
|
73773
74287
|
}
|
|
73774
74288
|
if (outputDir && ctx.contextMarkdown) {
|
|
73775
|
-
const contextFile =
|
|
74289
|
+
const contextFile = join23(outputDir, `${story.id}.context.md`);
|
|
73776
74290
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
73777
74291
|
const contextOutput = `---
|
|
73778
74292
|
${frontmatter}---
|
|
@@ -73786,13 +74300,13 @@ ${ctx.contextMarkdown}`;
|
|
|
73786
74300
|
async function promptsCommand(options) {
|
|
73787
74301
|
const logger = getLogger();
|
|
73788
74302
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
73789
|
-
const naxDir =
|
|
73790
|
-
if (!
|
|
74303
|
+
const naxDir = join24(workdir, ".nax");
|
|
74304
|
+
if (!existsSync19(naxDir)) {
|
|
73791
74305
|
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
73792
74306
|
}
|
|
73793
|
-
const featureDir =
|
|
73794
|
-
const prdPath =
|
|
73795
|
-
if (!
|
|
74307
|
+
const featureDir = join24(naxDir, "features", feature);
|
|
74308
|
+
const prdPath = join24(featureDir, "prd.json");
|
|
74309
|
+
if (!existsSync19(prdPath)) {
|
|
73796
74310
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
73797
74311
|
}
|
|
73798
74312
|
const prd = await loadPRD(prdPath);
|
|
@@ -73813,7 +74327,7 @@ async function promptsCommand(options) {
|
|
|
73813
74327
|
for (const story of stories) {
|
|
73814
74328
|
const ctx = {
|
|
73815
74329
|
config: config2,
|
|
73816
|
-
|
|
74330
|
+
rootConfig: config2,
|
|
73817
74331
|
prd,
|
|
73818
74332
|
story,
|
|
73819
74333
|
stories: [story],
|
|
@@ -73823,6 +74337,7 @@ async function promptsCommand(options) {
|
|
|
73823
74337
|
testStrategy: "test-after",
|
|
73824
74338
|
reasoning: "Placeholder routing"
|
|
73825
74339
|
},
|
|
74340
|
+
projectDir: workdir,
|
|
73826
74341
|
workdir,
|
|
73827
74342
|
featureDir,
|
|
73828
74343
|
hooks: { hooks: {} }
|
|
@@ -73852,10 +74367,10 @@ ${frontmatter}---
|
|
|
73852
74367
|
|
|
73853
74368
|
${ctx.prompt}`;
|
|
73854
74369
|
if (outputDir) {
|
|
73855
|
-
const promptFile =
|
|
74370
|
+
const promptFile = join24(outputDir, `${story.id}.prompt.md`);
|
|
73856
74371
|
await Bun.write(promptFile, fullOutput);
|
|
73857
74372
|
if (ctx.contextMarkdown) {
|
|
73858
|
-
const contextFile =
|
|
74373
|
+
const contextFile = join24(outputDir, `${story.id}.context.md`);
|
|
73859
74374
|
const contextOutput = `---
|
|
73860
74375
|
${frontmatter}---
|
|
73861
74376
|
|
|
@@ -73881,8 +74396,8 @@ ${"=".repeat(80)}`);
|
|
|
73881
74396
|
return processedStories;
|
|
73882
74397
|
}
|
|
73883
74398
|
// src/cli/prompts-init.ts
|
|
73884
|
-
import { existsSync as
|
|
73885
|
-
import { join as
|
|
74399
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync4 } from "fs";
|
|
74400
|
+
import { join as join25 } from "path";
|
|
73886
74401
|
var TEMPLATE_ROLES = [
|
|
73887
74402
|
{ file: "test-writer.md", role: "test-writer" },
|
|
73888
74403
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -73906,9 +74421,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
73906
74421
|
`;
|
|
73907
74422
|
async function promptsInitCommand(options) {
|
|
73908
74423
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
73909
|
-
const templatesDir =
|
|
74424
|
+
const templatesDir = join25(workdir, ".nax", "templates");
|
|
73910
74425
|
mkdirSync4(templatesDir, { recursive: true });
|
|
73911
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) =>
|
|
74426
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync20(join25(templatesDir, f)));
|
|
73912
74427
|
if (existingFiles.length > 0 && !force) {
|
|
73913
74428
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
73914
74429
|
Pass --force to overwrite existing templates.`);
|
|
@@ -73916,7 +74431,7 @@ async function promptsInitCommand(options) {
|
|
|
73916
74431
|
}
|
|
73917
74432
|
const written = [];
|
|
73918
74433
|
for (const template of TEMPLATE_ROLES) {
|
|
73919
|
-
const filePath =
|
|
74434
|
+
const filePath = join25(templatesDir, template.file);
|
|
73920
74435
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
73921
74436
|
const content = TEMPLATE_HEADER + roleBody;
|
|
73922
74437
|
await Bun.write(filePath, content);
|
|
@@ -73932,8 +74447,8 @@ async function promptsInitCommand(options) {
|
|
|
73932
74447
|
return written;
|
|
73933
74448
|
}
|
|
73934
74449
|
async function autoWirePromptsConfig(workdir) {
|
|
73935
|
-
const configPath =
|
|
73936
|
-
if (!
|
|
74450
|
+
const configPath = join25(workdir, "nax.config.json");
|
|
74451
|
+
if (!existsSync20(configPath)) {
|
|
73937
74452
|
const exampleConfig = JSON.stringify({
|
|
73938
74453
|
prompts: {
|
|
73939
74454
|
overrides: {
|
|
@@ -74097,8 +74612,8 @@ function pad(str, width) {
|
|
|
74097
74612
|
init_config();
|
|
74098
74613
|
init_logger2();
|
|
74099
74614
|
init_prd();
|
|
74100
|
-
import { existsSync as
|
|
74101
|
-
import { join as
|
|
74615
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
|
|
74616
|
+
import { join as join30 } from "path";
|
|
74102
74617
|
|
|
74103
74618
|
// src/cli/diagnose-analysis.ts
|
|
74104
74619
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -74297,8 +74812,8 @@ function isProcessAlive2(pid) {
|
|
|
74297
74812
|
}
|
|
74298
74813
|
}
|
|
74299
74814
|
async function loadStatusFile2(workdir) {
|
|
74300
|
-
const statusPath =
|
|
74301
|
-
if (!
|
|
74815
|
+
const statusPath = join30(workdir, ".nax", "status.json");
|
|
74816
|
+
if (!existsSync22(statusPath))
|
|
74302
74817
|
return null;
|
|
74303
74818
|
try {
|
|
74304
74819
|
return await Bun.file(statusPath).json();
|
|
@@ -74325,7 +74840,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
74325
74840
|
}
|
|
74326
74841
|
}
|
|
74327
74842
|
async function checkLock(workdir) {
|
|
74328
|
-
const lockFile = Bun.file(
|
|
74843
|
+
const lockFile = Bun.file(join30(workdir, "nax.lock"));
|
|
74329
74844
|
if (!await lockFile.exists())
|
|
74330
74845
|
return { lockPresent: false };
|
|
74331
74846
|
try {
|
|
@@ -74343,8 +74858,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
74343
74858
|
const logger = getLogger();
|
|
74344
74859
|
const workdir = options.workdir ?? process.cwd();
|
|
74345
74860
|
const naxSubdir = findProjectDir(workdir);
|
|
74346
|
-
let projectDir = naxSubdir ?
|
|
74347
|
-
if (!projectDir &&
|
|
74861
|
+
let projectDir = naxSubdir ? join30(naxSubdir, "..") : null;
|
|
74862
|
+
if (!projectDir && existsSync22(join30(workdir, ".nax"))) {
|
|
74348
74863
|
projectDir = workdir;
|
|
74349
74864
|
}
|
|
74350
74865
|
if (!projectDir)
|
|
@@ -74355,8 +74870,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
74355
74870
|
if (status2) {
|
|
74356
74871
|
feature = status2.run.feature;
|
|
74357
74872
|
} else {
|
|
74358
|
-
const featuresDir =
|
|
74359
|
-
if (!
|
|
74873
|
+
const featuresDir = join30(projectDir, ".nax", "features");
|
|
74874
|
+
if (!existsSync22(featuresDir))
|
|
74360
74875
|
throw new Error("No features found in project");
|
|
74361
74876
|
const features = readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
74362
74877
|
if (features.length === 0)
|
|
@@ -74365,9 +74880,9 @@ async function diagnoseCommand(options = {}) {
|
|
|
74365
74880
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
74366
74881
|
}
|
|
74367
74882
|
}
|
|
74368
|
-
const featureDir =
|
|
74369
|
-
const prdPath =
|
|
74370
|
-
if (!
|
|
74883
|
+
const featureDir = join30(projectDir, ".nax", "features", feature);
|
|
74884
|
+
const prdPath = join30(featureDir, "prd.json");
|
|
74885
|
+
if (!existsSync22(prdPath))
|
|
74371
74886
|
throw new Error(`Feature not found: ${feature}`);
|
|
74372
74887
|
const prd = await loadPRD(prdPath);
|
|
74373
74888
|
const status = await loadStatusFile2(projectDir);
|
|
@@ -74408,8 +74923,8 @@ init_interaction();
|
|
|
74408
74923
|
// src/cli/generate.ts
|
|
74409
74924
|
init_source();
|
|
74410
74925
|
init_loader();
|
|
74411
|
-
import { existsSync as
|
|
74412
|
-
import { join as
|
|
74926
|
+
import { existsSync as existsSync23 } from "fs";
|
|
74927
|
+
import { join as join31 } from "path";
|
|
74413
74928
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
74414
74929
|
async function generateCommand(options) {
|
|
74415
74930
|
const workdir = options.dir ?? process.cwd();
|
|
@@ -74452,7 +74967,7 @@ async function generateCommand(options) {
|
|
|
74452
74967
|
return;
|
|
74453
74968
|
}
|
|
74454
74969
|
if (options.package) {
|
|
74455
|
-
const packageDir =
|
|
74970
|
+
const packageDir = join31(workdir, options.package);
|
|
74456
74971
|
if (dryRun) {
|
|
74457
74972
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
74458
74973
|
}
|
|
@@ -74472,10 +74987,10 @@ async function generateCommand(options) {
|
|
|
74472
74987
|
process.exit(1);
|
|
74473
74988
|
return;
|
|
74474
74989
|
}
|
|
74475
|
-
const contextPath = options.context ?
|
|
74476
|
-
const outputDir = options.output ?
|
|
74990
|
+
const contextPath = options.context ? join31(workdir, options.context) : join31(workdir, ".nax/context.md");
|
|
74991
|
+
const outputDir = options.output ? join31(workdir, options.output) : workdir;
|
|
74477
74992
|
const autoInject = !options.noAutoInject;
|
|
74478
|
-
if (!
|
|
74993
|
+
if (!existsSync23(contextPath)) {
|
|
74479
74994
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
74480
74995
|
console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
|
|
74481
74996
|
process.exit(1);
|
|
@@ -74577,8 +75092,8 @@ async function generateCommand(options) {
|
|
|
74577
75092
|
}
|
|
74578
75093
|
// src/cli/config-display.ts
|
|
74579
75094
|
init_loader();
|
|
74580
|
-
import { existsSync as
|
|
74581
|
-
import { join as
|
|
75095
|
+
import { existsSync as existsSync25 } from "fs";
|
|
75096
|
+
import { join as join33 } from "path";
|
|
74582
75097
|
|
|
74583
75098
|
// src/cli/config-descriptions.ts
|
|
74584
75099
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -74817,10 +75332,10 @@ function deepEqual(a, b) {
|
|
|
74817
75332
|
// src/cli/config-get.ts
|
|
74818
75333
|
init_defaults();
|
|
74819
75334
|
init_loader();
|
|
74820
|
-
import { existsSync as
|
|
74821
|
-
import { join as
|
|
75335
|
+
import { existsSync as existsSync24 } from "fs";
|
|
75336
|
+
import { join as join32 } from "path";
|
|
74822
75337
|
async function loadConfigFile(path15) {
|
|
74823
|
-
if (!
|
|
75338
|
+
if (!existsSync24(path15))
|
|
74824
75339
|
return null;
|
|
74825
75340
|
try {
|
|
74826
75341
|
return await Bun.file(path15).json();
|
|
@@ -74840,7 +75355,7 @@ async function loadProjectConfig() {
|
|
|
74840
75355
|
const projectDir = findProjectDir();
|
|
74841
75356
|
if (!projectDir)
|
|
74842
75357
|
return null;
|
|
74843
|
-
const projectPath =
|
|
75358
|
+
const projectPath = join32(projectDir, "config.json");
|
|
74844
75359
|
return await loadConfigFile(projectPath);
|
|
74845
75360
|
}
|
|
74846
75361
|
|
|
@@ -74900,14 +75415,14 @@ async function configCommand(config2, options = {}) {
|
|
|
74900
75415
|
function determineConfigSources() {
|
|
74901
75416
|
const globalPath = globalConfigPath();
|
|
74902
75417
|
const projectDir = findProjectDir();
|
|
74903
|
-
const projectPath = projectDir ?
|
|
75418
|
+
const projectPath = projectDir ? join33(projectDir, "config.json") : null;
|
|
74904
75419
|
return {
|
|
74905
75420
|
global: fileExists(globalPath) ? globalPath : null,
|
|
74906
75421
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
74907
75422
|
};
|
|
74908
75423
|
}
|
|
74909
75424
|
function fileExists(path15) {
|
|
74910
|
-
return
|
|
75425
|
+
return existsSync25(path15);
|
|
74911
75426
|
}
|
|
74912
75427
|
function displayConfigWithDescriptions(obj, path15, sources, indent = 0) {
|
|
74913
75428
|
const indentStr = " ".repeat(indent);
|
|
@@ -75049,15 +75564,15 @@ init_paths();
|
|
|
75049
75564
|
init_profile();
|
|
75050
75565
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
75051
75566
|
import { readdirSync as readdirSync7 } from "fs";
|
|
75052
|
-
import { join as
|
|
75567
|
+
import { join as join34 } from "path";
|
|
75053
75568
|
var _profileCLIDeps = {
|
|
75054
75569
|
env: process.env
|
|
75055
75570
|
};
|
|
75056
75571
|
var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
|
|
75057
75572
|
var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
|
|
75058
75573
|
async function profileListCommand(startDir) {
|
|
75059
|
-
const globalProfilesDir =
|
|
75060
|
-
const projectProfilesDir =
|
|
75574
|
+
const globalProfilesDir = join34(globalConfigDir(), "profiles");
|
|
75575
|
+
const projectProfilesDir = join34(projectConfigDir(startDir), "profiles");
|
|
75061
75576
|
const globalProfiles = scanProfileDir(globalProfilesDir);
|
|
75062
75577
|
const projectProfiles = scanProfileDir(projectProfilesDir);
|
|
75063
75578
|
const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
@@ -75116,7 +75631,7 @@ function maskProfileValues(obj) {
|
|
|
75116
75631
|
return result;
|
|
75117
75632
|
}
|
|
75118
75633
|
async function profileUseCommand(profileName, startDir) {
|
|
75119
|
-
const configPath =
|
|
75634
|
+
const configPath = join34(projectConfigDir(startDir), "config.json");
|
|
75120
75635
|
const configFile = Bun.file(configPath);
|
|
75121
75636
|
let existing = {};
|
|
75122
75637
|
if (await configFile.exists()) {
|
|
@@ -75135,8 +75650,8 @@ async function profileCurrentCommand(startDir) {
|
|
|
75135
75650
|
return resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
75136
75651
|
}
|
|
75137
75652
|
async function profileCreateCommand(profileName, startDir) {
|
|
75138
|
-
const profilesDir =
|
|
75139
|
-
const profilePath =
|
|
75653
|
+
const profilesDir = join34(projectConfigDir(startDir), "profiles");
|
|
75654
|
+
const profilePath = join34(profilesDir, `${profileName}.json`);
|
|
75140
75655
|
const profileFile = Bun.file(profilePath);
|
|
75141
75656
|
if (await profileFile.exists()) {
|
|
75142
75657
|
throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
|
|
@@ -75201,25 +75716,25 @@ async function diagnose(options) {
|
|
|
75201
75716
|
}
|
|
75202
75717
|
|
|
75203
75718
|
// src/commands/logs.ts
|
|
75204
|
-
import { existsSync as
|
|
75205
|
-
import { join as
|
|
75719
|
+
import { existsSync as existsSync27 } from "fs";
|
|
75720
|
+
import { join as join38 } from "path";
|
|
75206
75721
|
|
|
75207
75722
|
// src/commands/logs-formatter.ts
|
|
75208
75723
|
init_source();
|
|
75209
75724
|
init_formatter();
|
|
75210
75725
|
import { readdirSync as readdirSync9 } from "fs";
|
|
75211
|
-
import { join as
|
|
75726
|
+
import { join as join37 } from "path";
|
|
75212
75727
|
|
|
75213
75728
|
// src/commands/logs-reader.ts
|
|
75214
|
-
import { existsSync as
|
|
75729
|
+
import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
|
|
75215
75730
|
import { readdir as readdir3 } from "fs/promises";
|
|
75216
|
-
import { join as
|
|
75731
|
+
import { join as join36 } from "path";
|
|
75217
75732
|
|
|
75218
75733
|
// src/utils/paths.ts
|
|
75219
75734
|
import { homedir as homedir4 } from "os";
|
|
75220
|
-
import { join as
|
|
75735
|
+
import { join as join35 } from "path";
|
|
75221
75736
|
function getRunsDir() {
|
|
75222
|
-
return process.env.NAX_RUNS_DIR ??
|
|
75737
|
+
return process.env.NAX_RUNS_DIR ?? join35(homedir4(), ".nax", "runs");
|
|
75223
75738
|
}
|
|
75224
75739
|
|
|
75225
75740
|
// src/commands/logs-reader.ts
|
|
@@ -75236,7 +75751,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
75236
75751
|
}
|
|
75237
75752
|
let matched = null;
|
|
75238
75753
|
for (const entry of entries) {
|
|
75239
|
-
const metaPath =
|
|
75754
|
+
const metaPath = join36(runsDir, entry, "meta.json");
|
|
75240
75755
|
try {
|
|
75241
75756
|
const meta3 = await Bun.file(metaPath).json();
|
|
75242
75757
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -75248,7 +75763,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
75248
75763
|
if (!matched) {
|
|
75249
75764
|
throw new Error(`Run not found in registry: ${runId}`);
|
|
75250
75765
|
}
|
|
75251
|
-
if (!
|
|
75766
|
+
if (!existsSync26(matched.eventsDir)) {
|
|
75252
75767
|
console.log(`Log directory unavailable for run: ${runId}`);
|
|
75253
75768
|
return null;
|
|
75254
75769
|
}
|
|
@@ -75258,14 +75773,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
75258
75773
|
return null;
|
|
75259
75774
|
}
|
|
75260
75775
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
75261
|
-
return
|
|
75776
|
+
return join36(matched.eventsDir, specificFile ?? files[0]);
|
|
75262
75777
|
}
|
|
75263
75778
|
async function selectRunFile(runsDir) {
|
|
75264
75779
|
const files = readdirSync8(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
75265
75780
|
if (files.length === 0) {
|
|
75266
75781
|
return null;
|
|
75267
75782
|
}
|
|
75268
|
-
return
|
|
75783
|
+
return join36(runsDir, files[0]);
|
|
75269
75784
|
}
|
|
75270
75785
|
async function extractRunSummary(filePath) {
|
|
75271
75786
|
const file3 = Bun.file(filePath);
|
|
@@ -75350,7 +75865,7 @@ Runs:
|
|
|
75350
75865
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
75351
75866
|
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
75867
|
for (const file3 of files) {
|
|
75353
|
-
const filePath =
|
|
75868
|
+
const filePath = join37(runsDir, file3);
|
|
75354
75869
|
const summary = await extractRunSummary(filePath);
|
|
75355
75870
|
const timestamp = file3.replace(".jsonl", "");
|
|
75356
75871
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -75464,7 +75979,7 @@ async function logsCommand(options) {
|
|
|
75464
75979
|
return;
|
|
75465
75980
|
}
|
|
75466
75981
|
const resolved = resolveProject({ dir: options.dir });
|
|
75467
|
-
const naxDir =
|
|
75982
|
+
const naxDir = join38(resolved.projectDir, ".nax");
|
|
75468
75983
|
const configPath = resolved.configPath;
|
|
75469
75984
|
const configFile = Bun.file(configPath);
|
|
75470
75985
|
const config2 = await configFile.json();
|
|
@@ -75472,9 +75987,9 @@ async function logsCommand(options) {
|
|
|
75472
75987
|
if (!featureName) {
|
|
75473
75988
|
throw new Error("No feature specified in config.json");
|
|
75474
75989
|
}
|
|
75475
|
-
const featureDir =
|
|
75476
|
-
const runsDir =
|
|
75477
|
-
if (!
|
|
75990
|
+
const featureDir = join38(naxDir, "features", featureName);
|
|
75991
|
+
const runsDir = join38(featureDir, "runs");
|
|
75992
|
+
if (!existsSync27(runsDir)) {
|
|
75478
75993
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
75479
75994
|
}
|
|
75480
75995
|
if (options.list) {
|
|
@@ -75497,8 +76012,8 @@ init_source();
|
|
|
75497
76012
|
init_config();
|
|
75498
76013
|
init_prd();
|
|
75499
76014
|
init_precheck();
|
|
75500
|
-
import { existsSync as
|
|
75501
|
-
import { join as
|
|
76015
|
+
import { existsSync as existsSync28 } from "fs";
|
|
76016
|
+
import { join as join39 } from "path";
|
|
75502
76017
|
async function precheckCommand(options) {
|
|
75503
76018
|
const resolved = resolveProject({
|
|
75504
76019
|
dir: options.dir,
|
|
@@ -75520,14 +76035,14 @@ async function precheckCommand(options) {
|
|
|
75520
76035
|
process.exit(1);
|
|
75521
76036
|
}
|
|
75522
76037
|
}
|
|
75523
|
-
const naxDir =
|
|
75524
|
-
const featureDir =
|
|
75525
|
-
const prdPath =
|
|
75526
|
-
if (!
|
|
76038
|
+
const naxDir = join39(resolved.projectDir, ".nax");
|
|
76039
|
+
const featureDir = join39(naxDir, "features", featureName);
|
|
76040
|
+
const prdPath = join39(featureDir, "prd.json");
|
|
76041
|
+
if (!existsSync28(featureDir)) {
|
|
75527
76042
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
75528
76043
|
process.exit(1);
|
|
75529
76044
|
}
|
|
75530
|
-
if (!
|
|
76045
|
+
if (!existsSync28(prdPath)) {
|
|
75531
76046
|
console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
|
|
75532
76047
|
console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
|
|
75533
76048
|
process.exit(EXIT_CODES.INVALID_PRD);
|
|
@@ -75544,7 +76059,7 @@ async function precheckCommand(options) {
|
|
|
75544
76059
|
// src/commands/runs.ts
|
|
75545
76060
|
init_source();
|
|
75546
76061
|
import { readdir as readdir4 } from "fs/promises";
|
|
75547
|
-
import { join as
|
|
76062
|
+
import { join as join40 } from "path";
|
|
75548
76063
|
var DEFAULT_LIMIT = 20;
|
|
75549
76064
|
var _runsCmdDeps = {
|
|
75550
76065
|
getRunsDir
|
|
@@ -75599,7 +76114,7 @@ async function runsCommand(options = {}) {
|
|
|
75599
76114
|
}
|
|
75600
76115
|
const rows = [];
|
|
75601
76116
|
for (const entry of entries) {
|
|
75602
|
-
const metaPath =
|
|
76117
|
+
const metaPath = join40(runsDir, entry, "meta.json");
|
|
75603
76118
|
let meta3;
|
|
75604
76119
|
try {
|
|
75605
76120
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -75676,7 +76191,7 @@ async function runsCommand(options = {}) {
|
|
|
75676
76191
|
|
|
75677
76192
|
// src/commands/unlock.ts
|
|
75678
76193
|
init_source();
|
|
75679
|
-
import { join as
|
|
76194
|
+
import { join as join41 } from "path";
|
|
75680
76195
|
function isProcessAlive3(pid) {
|
|
75681
76196
|
try {
|
|
75682
76197
|
process.kill(pid, 0);
|
|
@@ -75691,7 +76206,7 @@ function formatLockAge(ageMs) {
|
|
|
75691
76206
|
}
|
|
75692
76207
|
async function unlockCommand(options) {
|
|
75693
76208
|
const workdir = options.dir ?? process.cwd();
|
|
75694
|
-
const lockPath =
|
|
76209
|
+
const lockPath = join41(workdir, "nax.lock");
|
|
75695
76210
|
const lockFile = Bun.file(lockPath);
|
|
75696
76211
|
const exists = await lockFile.exists();
|
|
75697
76212
|
if (!exists) {
|
|
@@ -75767,11 +76282,9 @@ async function runCompletionPhase(options) {
|
|
|
75767
76282
|
const regressionAlreadyPassed = postRunStatus?.regression?.status === "passed";
|
|
75768
76283
|
if (acceptanceAlreadyPassed && regressionAlreadyPassed) {
|
|
75769
76284
|
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
76285
|
} else {
|
|
75772
76286
|
if (acceptanceAlreadyPassed) {
|
|
75773
76287
|
logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
|
|
75774
|
-
console.info("Acceptance already passed \u2014 skipping acceptance phase");
|
|
75775
76288
|
} else if (options.config.acceptance.enabled && isComplete(options.prd)) {
|
|
75776
76289
|
options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
|
|
75777
76290
|
const acceptanceResult = await _runnerCompletionDeps.runAcceptanceLoop({
|
|
@@ -75970,6 +76483,7 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
75970
76483
|
startTime: options.startTime,
|
|
75971
76484
|
parallelCount: options.parallel,
|
|
75972
76485
|
agentGetFn: options.agentGetFn,
|
|
76486
|
+
onBeforeStory: options.onBeforeStory,
|
|
75973
76487
|
pidRegistry: options.pidRegistry,
|
|
75974
76488
|
interactionChain: options.interactionChain,
|
|
75975
76489
|
batchPlan
|
|
@@ -76094,6 +76608,7 @@ async function run(options) {
|
|
|
76094
76608
|
headless,
|
|
76095
76609
|
parallel,
|
|
76096
76610
|
agentGetFn,
|
|
76611
|
+
onBeforeStory: () => registry2.resetStoryState(),
|
|
76097
76612
|
pidRegistry,
|
|
76098
76613
|
interactionChain
|
|
76099
76614
|
}, prd, pluginRegistry);
|
|
@@ -81677,8 +82192,8 @@ class Ink {
|
|
|
81677
82192
|
}
|
|
81678
82193
|
}
|
|
81679
82194
|
async waitUntilExit() {
|
|
81680
|
-
this.exitPromise ||= new Promise((
|
|
81681
|
-
this.resolveExitPromise =
|
|
82195
|
+
this.exitPromise ||= new Promise((resolve11, reject2) => {
|
|
82196
|
+
this.resolveExitPromise = resolve11;
|
|
81682
82197
|
this.rejectExitPromise = reject2;
|
|
81683
82198
|
});
|
|
81684
82199
|
if (!this.beforeExitHandler) {
|
|
@@ -83491,7 +84006,7 @@ async function promptForConfirmation(question) {
|
|
|
83491
84006
|
if (!process.stdin.isTTY) {
|
|
83492
84007
|
return true;
|
|
83493
84008
|
}
|
|
83494
|
-
return new Promise((
|
|
84009
|
+
return new Promise((resolve11) => {
|
|
83495
84010
|
process.stdout.write(source_default.bold(`${question} [Y/n] `));
|
|
83496
84011
|
process.stdin.setRawMode(true);
|
|
83497
84012
|
process.stdin.resume();
|
|
@@ -83504,9 +84019,9 @@ async function promptForConfirmation(question) {
|
|
|
83504
84019
|
process.stdout.write(`
|
|
83505
84020
|
`);
|
|
83506
84021
|
if (answer === "n") {
|
|
83507
|
-
|
|
84022
|
+
resolve11(false);
|
|
83508
84023
|
} else {
|
|
83509
|
-
|
|
84024
|
+
resolve11(true);
|
|
83510
84025
|
}
|
|
83511
84026
|
};
|
|
83512
84027
|
process.stdin.on("data", handler);
|
|
@@ -83535,15 +84050,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
83535
84050
|
}
|
|
83536
84051
|
return;
|
|
83537
84052
|
}
|
|
83538
|
-
const naxDir =
|
|
83539
|
-
if (
|
|
84053
|
+
const naxDir = join53(workdir, ".nax");
|
|
84054
|
+
if (existsSync31(naxDir) && !options.force) {
|
|
83540
84055
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
83541
84056
|
return;
|
|
83542
84057
|
}
|
|
83543
|
-
mkdirSync7(
|
|
83544
|
-
mkdirSync7(
|
|
83545
|
-
await Bun.write(
|
|
83546
|
-
await Bun.write(
|
|
84058
|
+
mkdirSync7(join53(naxDir, "features"), { recursive: true });
|
|
84059
|
+
mkdirSync7(join53(naxDir, "hooks"), { recursive: true });
|
|
84060
|
+
await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
84061
|
+
await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
|
|
83547
84062
|
hooks: {
|
|
83548
84063
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
83549
84064
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -83551,12 +84066,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
83551
84066
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
83552
84067
|
}
|
|
83553
84068
|
}, null, 2));
|
|
83554
|
-
await Bun.write(
|
|
84069
|
+
await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
|
|
83555
84070
|
*.tmp
|
|
83556
84071
|
.paused.json
|
|
83557
84072
|
.nax-verifier-verdict.json
|
|
83558
84073
|
`);
|
|
83559
|
-
await Bun.write(
|
|
84074
|
+
await Bun.write(join53(naxDir, "context.md"), `# Project Context
|
|
83560
84075
|
|
|
83561
84076
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
83562
84077
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -83653,7 +84168,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83653
84168
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
83654
84169
|
process.exit(1);
|
|
83655
84170
|
}
|
|
83656
|
-
if (options.from && !
|
|
84171
|
+
if (options.from && !existsSync31(options.from)) {
|
|
83657
84172
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
83658
84173
|
process.exit(1);
|
|
83659
84174
|
}
|
|
@@ -83686,10 +84201,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83686
84201
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
83687
84202
|
process.exit(1);
|
|
83688
84203
|
}
|
|
83689
|
-
const featureDir =
|
|
83690
|
-
const prdPath =
|
|
84204
|
+
const featureDir = join53(naxDir, "features", options.feature);
|
|
84205
|
+
const prdPath = join53(featureDir, "prd.json");
|
|
83691
84206
|
if (options.plan && options.from) {
|
|
83692
|
-
if (
|
|
84207
|
+
if (existsSync31(prdPath) && !options.force) {
|
|
83693
84208
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
83694
84209
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
83695
84210
|
process.exit(1);
|
|
@@ -83709,10 +84224,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83709
84224
|
}
|
|
83710
84225
|
}
|
|
83711
84226
|
try {
|
|
83712
|
-
const planLogDir =
|
|
84227
|
+
const planLogDir = join53(featureDir, "plan");
|
|
83713
84228
|
mkdirSync7(planLogDir, { recursive: true });
|
|
83714
84229
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
83715
|
-
const planLogPath =
|
|
84230
|
+
const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
|
|
83716
84231
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
83717
84232
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
83718
84233
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -83751,15 +84266,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83751
84266
|
process.exit(1);
|
|
83752
84267
|
}
|
|
83753
84268
|
}
|
|
83754
|
-
if (!
|
|
84269
|
+
if (!existsSync31(prdPath)) {
|
|
83755
84270
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
83756
84271
|
process.exit(1);
|
|
83757
84272
|
}
|
|
83758
84273
|
resetLogger();
|
|
83759
|
-
const runsDir =
|
|
84274
|
+
const runsDir = join53(featureDir, "runs");
|
|
83760
84275
|
mkdirSync7(runsDir, { recursive: true });
|
|
83761
84276
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
83762
|
-
const logFilePath =
|
|
84277
|
+
const logFilePath = join53(runsDir, `${runId}.jsonl`);
|
|
83763
84278
|
const isTTY = process.stdout.isTTY ?? false;
|
|
83764
84279
|
const headlessFlag = options.headless ?? false;
|
|
83765
84280
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -83775,7 +84290,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83775
84290
|
config2.autoMode.defaultAgent = options.agent;
|
|
83776
84291
|
}
|
|
83777
84292
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
83778
|
-
const globalNaxDir =
|
|
84293
|
+
const globalNaxDir = join53(homedir8(), ".nax");
|
|
83779
84294
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
83780
84295
|
const eventEmitter = new PipelineEventEmitter;
|
|
83781
84296
|
let tuiInstance;
|
|
@@ -83798,7 +84313,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83798
84313
|
} else {
|
|
83799
84314
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
83800
84315
|
}
|
|
83801
|
-
const statusFilePath =
|
|
84316
|
+
const statusFilePath = join53(workdir, ".nax", "status.json");
|
|
83802
84317
|
let parallel;
|
|
83803
84318
|
if (options.parallel !== undefined) {
|
|
83804
84319
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -83824,9 +84339,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
83824
84339
|
headless: useHeadless,
|
|
83825
84340
|
skipPrecheck: options.skipPrecheck ?? false
|
|
83826
84341
|
});
|
|
83827
|
-
const latestSymlink =
|
|
84342
|
+
const latestSymlink = join53(runsDir, "latest.jsonl");
|
|
83828
84343
|
try {
|
|
83829
|
-
if (
|
|
84344
|
+
if (existsSync31(latestSymlink)) {
|
|
83830
84345
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
83831
84346
|
}
|
|
83832
84347
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -83862,9 +84377,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
83862
84377
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
83863
84378
|
process.exit(1);
|
|
83864
84379
|
}
|
|
83865
|
-
const featureDir =
|
|
84380
|
+
const featureDir = join53(naxDir, "features", name);
|
|
83866
84381
|
mkdirSync7(featureDir, { recursive: true });
|
|
83867
|
-
await Bun.write(
|
|
84382
|
+
await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
|
|
83868
84383
|
|
|
83869
84384
|
## Overview
|
|
83870
84385
|
|
|
@@ -83897,7 +84412,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
83897
84412
|
|
|
83898
84413
|
<!-- What this feature explicitly does NOT cover. -->
|
|
83899
84414
|
`);
|
|
83900
|
-
await Bun.write(
|
|
84415
|
+
await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
|
|
83901
84416
|
|
|
83902
84417
|
Created: ${new Date().toISOString()}
|
|
83903
84418
|
|
|
@@ -83923,8 +84438,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
83923
84438
|
console.error(source_default.red("nax not initialized."));
|
|
83924
84439
|
process.exit(1);
|
|
83925
84440
|
}
|
|
83926
|
-
const featuresDir =
|
|
83927
|
-
if (!
|
|
84441
|
+
const featuresDir = join53(naxDir, "features");
|
|
84442
|
+
if (!existsSync31(featuresDir)) {
|
|
83928
84443
|
console.log(source_default.dim("No features yet."));
|
|
83929
84444
|
return;
|
|
83930
84445
|
}
|
|
@@ -83938,8 +84453,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
83938
84453
|
Features:
|
|
83939
84454
|
`));
|
|
83940
84455
|
for (const name of entries) {
|
|
83941
|
-
const prdPath =
|
|
83942
|
-
if (
|
|
84456
|
+
const prdPath = join53(featuresDir, name, "prd.json");
|
|
84457
|
+
if (existsSync31(prdPath)) {
|
|
83943
84458
|
const prd = await loadPRD(prdPath);
|
|
83944
84459
|
const c = countStories(prd);
|
|
83945
84460
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -83973,10 +84488,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
83973
84488
|
cliOverrides.profile = options.profile;
|
|
83974
84489
|
}
|
|
83975
84490
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
83976
|
-
const featureLogDir =
|
|
84491
|
+
const featureLogDir = join53(naxDir, "features", options.feature, "plan");
|
|
83977
84492
|
mkdirSync7(featureLogDir, { recursive: true });
|
|
83978
84493
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
83979
|
-
const planLogPath =
|
|
84494
|
+
const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
|
|
83980
84495
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
83981
84496
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
83982
84497
|
try {
|