@nathapp/nax 0.82.1 → 0.82.3
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/README.md +75 -42
- package/dist/nax.js +1059 -594
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -2668,7 +2668,7 @@ var package_default;
|
|
|
2668
2668
|
var init_package = __esm(() => {
|
|
2669
2669
|
package_default = {
|
|
2670
2670
|
name: "@nathapp/nax",
|
|
2671
|
-
version: "0.82.
|
|
2671
|
+
version: "0.82.3",
|
|
2672
2672
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
2673
2673
|
type: "module",
|
|
2674
2674
|
bin: {
|
|
@@ -18398,10 +18398,26 @@ function isNaxConfigFile(root, resolved) {
|
|
|
18398
18398
|
return segments.length === 2 || segments.length >= 4 && segments[1] === "mono";
|
|
18399
18399
|
}
|
|
18400
18400
|
function isNaxOwnedWritePath(rel) {
|
|
18401
|
+
return naxOwnedKind(rel) !== undefined;
|
|
18402
|
+
}
|
|
18403
|
+
function naxOwnedKind(rel) {
|
|
18401
18404
|
const segments = rel.split("/");
|
|
18402
18405
|
if (segments.length === 1 && QUEUE_CONTROL_FILES.has(segments[0] ?? ""))
|
|
18403
|
-
return
|
|
18404
|
-
|
|
18406
|
+
return "queue";
|
|
18407
|
+
if (segments[0] === ".nax" && segments[1] === "features" && segments[segments.length - 1] === "prd.json") {
|
|
18408
|
+
return "prd";
|
|
18409
|
+
}
|
|
18410
|
+
return;
|
|
18411
|
+
}
|
|
18412
|
+
function naxOwnedBashRefusal(tool, kind, hit, verb) {
|
|
18413
|
+
switch (kind) {
|
|
18414
|
+
case "prd":
|
|
18415
|
+
return `${tool} command ${verb} "${hit}", which holds this story's acceptance criteria. ` + "nax updates it itself during the run, so it shows as modified. " + "Bash commands naming it are refused, reads included -- leave it as is. " + "To view it, use the `Read` tool.";
|
|
18416
|
+
case "queue":
|
|
18417
|
+
return `${tool} command ${verb} "${hit}", which is nax's run-control queue. ` + "Bash commands naming it are refused, reads included -- change the run through the queue command.";
|
|
18418
|
+
case "config":
|
|
18419
|
+
return `${tool} command ${verb} "${hit}", which is nax configuration. ` + "Bash commands naming it are refused, reads included -- nax configuration is not changed from inside a run.";
|
|
18420
|
+
}
|
|
18405
18421
|
}
|
|
18406
18422
|
function naxOwnedWriteRefusal(tool, rel, exemptRel) {
|
|
18407
18423
|
if (!NAX_OWNED_WRITE_TOOLS.has(tool))
|
|
@@ -19566,7 +19582,7 @@ function capitalize(text) {
|
|
|
19566
19582
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
19567
19583
|
}
|
|
19568
19584
|
function escalateDescription(shell, patterns) {
|
|
19569
|
-
return `Run one shell command string under ${shell}. ${PREFER_STRUCTURED_TOOLS_SENTENCE}` + `${capitalize(describeGrants(patterns))}.
|
|
19585
|
+
return `Run one shell command string under ${shell}. ${PREFER_STRUCTURED_TOOLS_SENTENCE}` + `${capitalize(describeGrants(patterns))}. Every command, granted or not, is checked first: paths ` + "and redirect targets must stay inside the repository root, and `.git/` access, denied flags, unexpanded " + "`$VAR`, glob or brace characters, `~`, a bare `cd`, and a command matching a deny rule are " + "refused without asking. For a command using a construct that cannot be analysed (e.g. command or process " + "substitution, backticks, here-documents, subshells, `2>&1`, `#` comments, an option-shaped `cd`), these " + "checks cover the part before that construct. A command outside the granted forms, or one using a construct " + "that cannot be analysed, is not refused: it is sent to a human for approval (unless an identical command " + "was already approved and remembered) and, if they allow it, runs exactly as written; it is refused if they " + "deny it or do not answer in time, so prefer the granted forms. Each segment of a `&&`/`||`/`;`/`|` chain is " + "checked separately. " + BACKGROUND_PROCESSES_KILLED_SENTENCE;
|
|
19570
19586
|
}
|
|
19571
19587
|
function rawDescription(shell, containment = RAW_UNCONTAINED) {
|
|
19572
19588
|
return `Run one shell command string under ${shell}. ${PREFER_STRUCTURED_TOOLS_SENTENCE}` + "This stage runs under raw mode (ADR-030): pipes, redirects, command substitution ($(...), backticks), " + "process substitution, here-documents and subshells all work here -- nothing is refused for being unparseable, " + "and Bash allow/deny/ask rules configured for this stage are NOT consulted. " + containment + "The only refusal is a command the lexer CAN parse that names or redirects into one of the exact file " + "paths nax owns -- .nax/config.json, .nax/mono/*/config.json, " + ".nax/features/**/prd.json, or the root queue-control files -- change those through nax rather than by " + "writing them directly. That screen is advisory, not a boundary: it matches exact file paths only, so a " + "command using command substitution, a directory target (cp x .nax/), a glob, a nested shell (sh -c '...'), " + "tar -C or dd of=, or a symlink alias all skip it; use the sandbox for a boundary. " + BACKGROUND_PROCESSES_KILLED_SENTENCE;
|
|
@@ -19779,6 +19795,46 @@ var init_delete = __esm(() => {
|
|
|
19779
19795
|
};
|
|
19780
19796
|
});
|
|
19781
19797
|
|
|
19798
|
+
// src/tools/edit-region.ts
|
|
19799
|
+
function countNewlines(text) {
|
|
19800
|
+
let count = 0;
|
|
19801
|
+
for (let i = 0;i < text.length; i += 1) {
|
|
19802
|
+
if (text[i] === `
|
|
19803
|
+
`)
|
|
19804
|
+
count += 1;
|
|
19805
|
+
}
|
|
19806
|
+
return count;
|
|
19807
|
+
}
|
|
19808
|
+
function composeEditRegion({ updated, matchIndex, newStringLength }) {
|
|
19809
|
+
const lines = splitModelLines(updated);
|
|
19810
|
+
const total = lines.length;
|
|
19811
|
+
if (total === 0)
|
|
19812
|
+
return "[file is now empty]";
|
|
19813
|
+
const start = Math.min(total, 1 + countNewlines(updated.slice(0, matchIndex)));
|
|
19814
|
+
const end = newStringLength > 0 ? Math.min(total, 1 + countNewlines(updated.slice(0, matchIndex + newStringLength - 1))) : start;
|
|
19815
|
+
const a = Math.max(1, start - CONTEXT_LINES);
|
|
19816
|
+
const b = Math.min(total, end + CONTEXT_LINES);
|
|
19817
|
+
const header = `[lines ${a}-${b} of ${total}]`;
|
|
19818
|
+
const selected = end - start + 1 > MAX_REGION_LINES_SHOWN ? [
|
|
19819
|
+
...lines.slice(a - 1, start + 2),
|
|
19820
|
+
`[... lines ${start + 3}-${end - 3} not shown ...]`,
|
|
19821
|
+
...lines.slice(end - 3, b)
|
|
19822
|
+
] : lines.slice(a - 1, b);
|
|
19823
|
+
let view = [header, ...selected].join(`
|
|
19824
|
+
`);
|
|
19825
|
+
while (view.endsWith(`
|
|
19826
|
+
`))
|
|
19827
|
+
view = view.slice(0, -1);
|
|
19828
|
+
return view;
|
|
19829
|
+
}
|
|
19830
|
+
function replaceUniqueLiteral(source, oldString, newString, matchIndex) {
|
|
19831
|
+
return source.slice(0, matchIndex) + newString + source.slice(matchIndex + oldString.length);
|
|
19832
|
+
}
|
|
19833
|
+
var CONTEXT_LINES = 3, MAX_REGION_LINES_SHOWN = 8;
|
|
19834
|
+
var init_edit_region = __esm(() => {
|
|
19835
|
+
init_truncate();
|
|
19836
|
+
});
|
|
19837
|
+
|
|
19782
19838
|
// src/tools/edit.ts
|
|
19783
19839
|
import { readFile, stat, writeFile } from "fs/promises";
|
|
19784
19840
|
function countOccurrences(haystack, needle) {
|
|
@@ -19792,11 +19848,17 @@ function countOccurrences(haystack, needle) {
|
|
|
19792
19848
|
}
|
|
19793
19849
|
return count;
|
|
19794
19850
|
}
|
|
19795
|
-
var editTool;
|
|
19851
|
+
var _editDeps, editTool;
|
|
19796
19852
|
var init_edit = __esm(() => {
|
|
19853
|
+
init_edit_region();
|
|
19854
|
+
_editDeps = {
|
|
19855
|
+
stat: (path) => stat(path),
|
|
19856
|
+
readFile: (path, encoding) => readFile(path, encoding),
|
|
19857
|
+
writeFile: (path, data, encoding) => writeFile(path, data, encoding)
|
|
19858
|
+
};
|
|
19797
19859
|
editTool = {
|
|
19798
19860
|
name: "Edit",
|
|
19799
|
-
description: "Replace one exact occurrence of old_string with new_string in a repository file. Fails if the match is absent or ambiguous.",
|
|
19861
|
+
description: "Replace one exact occurrence of old_string with new_string in a repository file. Fails if the match is absent or ambiguous. On success the result shows the edited lines with up to 3 lines of context and their line range, so you do not need to Read the file again to check the edit.",
|
|
19800
19862
|
inputSchema: {
|
|
19801
19863
|
type: "object",
|
|
19802
19864
|
properties: {
|
|
@@ -19817,7 +19879,7 @@ var init_edit = __esm(() => {
|
|
|
19817
19879
|
return { content: "old_string and new_string must be strings", isError: true };
|
|
19818
19880
|
}
|
|
19819
19881
|
try {
|
|
19820
|
-
const { size } = await stat(target);
|
|
19882
|
+
const { size } = await _editDeps.stat(target);
|
|
19821
19883
|
if (size > ctx.maxFileBytes) {
|
|
19822
19884
|
return {
|
|
19823
19885
|
content: `the file is ${size} bytes, which exceeds the ${ctx.maxFileBytes}-byte file ceiling -- refusing to edit ${target}`,
|
|
@@ -19829,7 +19891,7 @@ var init_edit = __esm(() => {
|
|
|
19829
19891
|
}
|
|
19830
19892
|
let source;
|
|
19831
19893
|
try {
|
|
19832
|
-
source = await readFile(target, "utf8");
|
|
19894
|
+
source = await _editDeps.readFile(target, "utf8");
|
|
19833
19895
|
} catch (err) {
|
|
19834
19896
|
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
19835
19897
|
}
|
|
@@ -19844,8 +19906,12 @@ var init_edit = __esm(() => {
|
|
|
19844
19906
|
};
|
|
19845
19907
|
}
|
|
19846
19908
|
try {
|
|
19847
|
-
|
|
19848
|
-
|
|
19909
|
+
const matchIndex = source.indexOf(oldString);
|
|
19910
|
+
const updated = replaceUniqueLiteral(source, oldString, newString, matchIndex);
|
|
19911
|
+
await _editDeps.writeFile(target, updated, "utf8");
|
|
19912
|
+
const region = composeEditRegion({ updated, matchIndex, newStringLength: newString.length });
|
|
19913
|
+
return { content: `edited ${target}
|
|
19914
|
+
${region}` };
|
|
19849
19915
|
} catch (err) {
|
|
19850
19916
|
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
19851
19917
|
}
|
|
@@ -20927,9 +20993,6 @@ var ASK_NO_CHANNEL_REASON = "matched an ask rule requiring human approval; no ap
|
|
|
20927
20993
|
var init_ask = () => {};
|
|
20928
20994
|
|
|
20929
20995
|
// src/permissions/bash-lex.ts
|
|
20930
|
-
function refused(construct) {
|
|
20931
|
-
return { kind: "refused", construct };
|
|
20932
|
-
}
|
|
20933
20996
|
function doubleQuoteEnd(command, from) {
|
|
20934
20997
|
for (let i = from;i < command.length; i += 1) {
|
|
20935
20998
|
const char = command[i];
|
|
@@ -20944,7 +21007,7 @@ function doubleQuoteEnd(command, from) {
|
|
|
20944
21007
|
}
|
|
20945
21008
|
function lexBashCommand(command) {
|
|
20946
21009
|
if (command.trim() === "")
|
|
20947
|
-
return refused
|
|
21010
|
+
return { kind: "refused", construct: "an empty command", prefix: [] };
|
|
20948
21011
|
const segments = [];
|
|
20949
21012
|
let tokens = [];
|
|
20950
21013
|
let redirects = [];
|
|
@@ -20952,6 +21015,10 @@ function lexBashCommand(command) {
|
|
|
20952
21015
|
let opaque = false;
|
|
20953
21016
|
let started = false;
|
|
20954
21017
|
let pendingRedirect;
|
|
21018
|
+
function refusedHere(construct) {
|
|
21019
|
+
const prefix = tokens.length === 0 && redirects.length === 0 ? [...segments] : [...segments, { tokens, redirects }];
|
|
21020
|
+
return { kind: "refused", construct, prefix };
|
|
21021
|
+
}
|
|
20955
21022
|
function flushWord() {
|
|
20956
21023
|
if (!started)
|
|
20957
21024
|
return;
|
|
@@ -20983,7 +21050,7 @@ function lexBashCommand(command) {
|
|
|
20983
21050
|
if (char === "'") {
|
|
20984
21051
|
const end = command.indexOf("'", i + 1);
|
|
20985
21052
|
if (end === -1)
|
|
20986
|
-
return
|
|
21053
|
+
return refusedHere("an unbalanced single quote");
|
|
20987
21054
|
word += command.slice(i + 1, end);
|
|
20988
21055
|
started = true;
|
|
20989
21056
|
i = end + 1;
|
|
@@ -20992,12 +21059,12 @@ function lexBashCommand(command) {
|
|
|
20992
21059
|
if (char === '"') {
|
|
20993
21060
|
const end = doubleQuoteEnd(command, i + 1);
|
|
20994
21061
|
if (end === -1)
|
|
20995
|
-
return
|
|
21062
|
+
return refusedHere("an unbalanced double quote");
|
|
20996
21063
|
const inner = command.slice(i + 1, end);
|
|
20997
21064
|
if (inner.includes("$("))
|
|
20998
|
-
return
|
|
21065
|
+
return refusedHere("a command substitution `$(...)`");
|
|
20999
21066
|
if (inner.includes("`"))
|
|
21000
|
-
return
|
|
21067
|
+
return refusedHere("a backtick command substitution");
|
|
21001
21068
|
if (inner.includes("$"))
|
|
21002
21069
|
opaque = true;
|
|
21003
21070
|
word += inner;
|
|
@@ -21007,32 +21074,32 @@ function lexBashCommand(command) {
|
|
|
21007
21074
|
}
|
|
21008
21075
|
if (char === "\\") {
|
|
21009
21076
|
if (next === undefined)
|
|
21010
|
-
return
|
|
21077
|
+
return refusedHere("a trailing backslash");
|
|
21011
21078
|
word += next;
|
|
21012
21079
|
started = true;
|
|
21013
21080
|
i += 2;
|
|
21014
21081
|
continue;
|
|
21015
21082
|
}
|
|
21016
21083
|
if (char === "(" || char === ")")
|
|
21017
|
-
return
|
|
21084
|
+
return refusedHere("a subshell `( ... )`");
|
|
21018
21085
|
if (char === "!" && !started)
|
|
21019
|
-
return
|
|
21086
|
+
return refusedHere("a `!` negation");
|
|
21020
21087
|
if (char === "#" && !started)
|
|
21021
|
-
return
|
|
21088
|
+
return refusedHere("a `#` comment");
|
|
21022
21089
|
if (char === "$" && next === "(")
|
|
21023
|
-
return
|
|
21090
|
+
return refusedHere("a command substitution `$(...)`");
|
|
21024
21091
|
if (char === "`")
|
|
21025
|
-
return
|
|
21092
|
+
return refusedHere("a backtick command substitution");
|
|
21026
21093
|
if ((char === "<" || char === ">") && next === "(") {
|
|
21027
|
-
return
|
|
21094
|
+
return refusedHere("a process substitution `<(...)` / `>(...)`");
|
|
21028
21095
|
}
|
|
21029
21096
|
if (char === "<" && next === "<")
|
|
21030
|
-
return
|
|
21097
|
+
return refusedHere("a here-document `<<`");
|
|
21031
21098
|
if ((char === "<" || char === ">") && next === "&") {
|
|
21032
|
-
return
|
|
21099
|
+
return refusedHere("file-descriptor duplication (`2>&1`)");
|
|
21033
21100
|
}
|
|
21034
21101
|
if (char === "&" && next === ">")
|
|
21035
|
-
return
|
|
21102
|
+
return refusedHere("the `&>` redirection form");
|
|
21036
21103
|
if (char === "$") {
|
|
21037
21104
|
opaque = true;
|
|
21038
21105
|
word += char;
|
|
@@ -21049,21 +21116,21 @@ function lexBashCommand(command) {
|
|
|
21049
21116
|
` || char === ";") {
|
|
21050
21117
|
const error49 = flushSegment(";");
|
|
21051
21118
|
if (error49 !== undefined)
|
|
21052
|
-
return
|
|
21119
|
+
return refusedHere(error49);
|
|
21053
21120
|
i += 1;
|
|
21054
21121
|
continue;
|
|
21055
21122
|
}
|
|
21056
21123
|
if (char === "&" && next === "&" || char === "|" && next === "|") {
|
|
21057
21124
|
const error49 = flushSegment(char === "&" ? "&&" : "||");
|
|
21058
21125
|
if (error49 !== undefined)
|
|
21059
|
-
return
|
|
21126
|
+
return refusedHere(error49);
|
|
21060
21127
|
i += 2;
|
|
21061
21128
|
continue;
|
|
21062
21129
|
}
|
|
21063
21130
|
if (char === "|" || char === "&") {
|
|
21064
21131
|
const error49 = flushSegment(char);
|
|
21065
21132
|
if (error49 !== undefined)
|
|
21066
|
-
return
|
|
21133
|
+
return refusedHere(error49);
|
|
21067
21134
|
i += 1;
|
|
21068
21135
|
continue;
|
|
21069
21136
|
}
|
|
@@ -21088,7 +21155,7 @@ function lexBashCommand(command) {
|
|
|
21088
21155
|
}
|
|
21089
21156
|
const error48 = flushSegment();
|
|
21090
21157
|
if (error48 !== undefined)
|
|
21091
|
-
return
|
|
21158
|
+
return refusedHere(error48);
|
|
21092
21159
|
return { kind: "ok", segments };
|
|
21093
21160
|
}
|
|
21094
21161
|
|
|
@@ -21219,7 +21286,9 @@ function checkPayload(args, segment, cwd) {
|
|
|
21219
21286
|
case "no-target":
|
|
21220
21287
|
return { refusal: deny("`cd` with no target is refused") };
|
|
21221
21288
|
case "option-shaped":
|
|
21222
|
-
return {
|
|
21289
|
+
return {
|
|
21290
|
+
refusal: deny(`cd target "${cdResult.text}" is option-shaped, and this gate does not model it`, false, true)
|
|
21291
|
+
};
|
|
21223
21292
|
case "opaque":
|
|
21224
21293
|
case "unresolved":
|
|
21225
21294
|
return { refusal: deny(`cd target "${cdResult.text}" is not inside the permitted root`, true) };
|
|
@@ -21256,29 +21325,30 @@ function checkBashCommand(args) {
|
|
|
21256
21325
|
if (command.trim() === "")
|
|
21257
21326
|
return deny(`"command" must not be empty`);
|
|
21258
21327
|
const lexed = lexBashCommand(command);
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
}
|
|
21262
|
-
for (const segment of lexed.segments) {
|
|
21328
|
+
const segments = lexed.kind === "ok" ? lexed.segments : lexed.prefix;
|
|
21329
|
+
for (const segment of segments) {
|
|
21263
21330
|
if (args.denyEntry !== undefined && matchesSegment(args.denyEntry, segment)) {
|
|
21264
21331
|
return deny(`${tool} segment "${render(segment)}" is denied for this stage by rule ${ruleExpr(tool, args.denyEntry, segment)}`);
|
|
21265
21332
|
}
|
|
21266
21333
|
}
|
|
21267
|
-
for (const segment of lexed.segments) {
|
|
21268
|
-
if (args.grant.unconditional || matchesSegment(args.grant, segment))
|
|
21269
|
-
continue;
|
|
21270
|
-
const granted = args.grant.raw.filter((pattern) => pattern !== "*").join(", ");
|
|
21271
|
-
const alternatives = granted === "" ? "no command forms are granted for this stage" : `granted forms: ${granted}`;
|
|
21272
|
-
return deny(`${tool} is not granted "${render(segment)}" -- ${alternatives}`, false, true);
|
|
21273
|
-
}
|
|
21274
21334
|
let cwd = [args.initialPath];
|
|
21275
|
-
for (const segment of
|
|
21335
|
+
for (const segment of segments) {
|
|
21276
21336
|
const result = checkPayload(args, segment, cwd);
|
|
21277
21337
|
if (result.refusal !== undefined)
|
|
21278
21338
|
return result.refusal;
|
|
21279
21339
|
cwd = nextWorkingDirectories(segment, cwd, result.cdTargets);
|
|
21280
21340
|
}
|
|
21281
|
-
|
|
21341
|
+
if (lexed.kind === "refused") {
|
|
21342
|
+
return deny(`command contains ${lexed.construct}, which cannot be analysed and is therefore refused -- ` + "rewrite it without that construct, or use a structured tool", false, true);
|
|
21343
|
+
}
|
|
21344
|
+
for (const segment of segments) {
|
|
21345
|
+
if (args.grant.unconditional || matchesSegment(args.grant, segment))
|
|
21346
|
+
continue;
|
|
21347
|
+
const granted = args.grant.raw.filter((pattern) => pattern !== "*").join(", ");
|
|
21348
|
+
const alternatives = granted === "" ? "no command forms are granted for this stage" : `granted forms: ${granted}`;
|
|
21349
|
+
return deny(`${tool} is not granted "${render(segment)}" -- ${alternatives}`, false, true);
|
|
21350
|
+
}
|
|
21351
|
+
for (const segment of segments) {
|
|
21282
21352
|
if (args.askEntry !== undefined && matchesSegment(args.askEntry, segment)) {
|
|
21283
21353
|
return { kind: "ask", rule: ruleExpr(tool, args.askEntry, segment) };
|
|
21284
21354
|
}
|
|
@@ -21299,15 +21369,16 @@ function protectedHit(args, candidate, cwd) {
|
|
|
21299
21369
|
for (const directory of cwd) {
|
|
21300
21370
|
const lexical = realOrRaw(resolve10(directory, candidate));
|
|
21301
21371
|
if (isNaxConfigFile(args.root, lexical))
|
|
21302
|
-
return candidate;
|
|
21372
|
+
return { kind: "config", hit: candidate };
|
|
21303
21373
|
const resolved = args.resolvePath(candidate, directory);
|
|
21304
21374
|
if (resolved === null)
|
|
21305
21375
|
continue;
|
|
21306
21376
|
const rel = relative5(args.root, resolved).split(sep4).join("/");
|
|
21307
21377
|
if (rel.startsWith(".."))
|
|
21308
21378
|
continue;
|
|
21309
|
-
|
|
21310
|
-
|
|
21379
|
+
const kind = naxOwnedKind(rel);
|
|
21380
|
+
if (kind !== undefined)
|
|
21381
|
+
return { kind, hit: candidate };
|
|
21311
21382
|
}
|
|
21312
21383
|
return;
|
|
21313
21384
|
}
|
|
@@ -21327,7 +21398,7 @@ function screenRawBashCommand(args) {
|
|
|
21327
21398
|
continue;
|
|
21328
21399
|
const hit = protectedHit(args, token.text, cwd);
|
|
21329
21400
|
if (hit !== undefined) {
|
|
21330
|
-
return deny2(
|
|
21401
|
+
return deny2(naxOwnedBashRefusal(tool, hit.kind, hit.hit, "names"));
|
|
21331
21402
|
}
|
|
21332
21403
|
}
|
|
21333
21404
|
for (const redirect of segment.redirects) {
|
|
@@ -21335,7 +21406,7 @@ function screenRawBashCommand(args) {
|
|
|
21335
21406
|
continue;
|
|
21336
21407
|
const hit = protectedHit(args, redirect.target, cwd);
|
|
21337
21408
|
if (hit !== undefined) {
|
|
21338
|
-
return deny2(
|
|
21409
|
+
return deny2(naxOwnedBashRefusal(tool, hit.kind, hit.hit, "redirects into"));
|
|
21339
21410
|
}
|
|
21340
21411
|
}
|
|
21341
21412
|
const cdResult = cdTargetsFor(segment, cwd, args.resolvePath);
|
|
@@ -22593,6 +22664,47 @@ var init_provider_advertise = __esm(() => {
|
|
|
22593
22664
|
init_provider_types();
|
|
22594
22665
|
});
|
|
22595
22666
|
|
|
22667
|
+
// src/tools/read-continuation.ts
|
|
22668
|
+
function limitStopFooter(input) {
|
|
22669
|
+
const { nextOffset, totalIsFloor, endLine, totalLines } = input;
|
|
22670
|
+
const remaining = Math.max(0, totalLines - endLine);
|
|
22671
|
+
const countLabel = totalIsFloor ? `${remaining}+` : `${remaining}`;
|
|
22672
|
+
return `[${countLabel} more lines in file. Use offset=${nextOffset} to continue.]`;
|
|
22673
|
+
}
|
|
22674
|
+
function shouldAppendLimitStopFooter(hasLimit, endLine, totalLines) {
|
|
22675
|
+
return hasLimit && endLine < totalLines;
|
|
22676
|
+
}
|
|
22677
|
+
function capFooter(input) {
|
|
22678
|
+
const { firstLine: firstLine2, lastLine, totalLabel } = input;
|
|
22679
|
+
return `[Showing lines ${firstLine2}-${lastLine} of ${totalLabel}. Use offset=${lastLine + 1} to continue.]`;
|
|
22680
|
+
}
|
|
22681
|
+
function applyCapCut(input) {
|
|
22682
|
+
const { header, lines, firstLine: firstLine2, totalLabel, limitStopFooter: limitStopFooter2, unshapedBody, maxBytes, maxLines } = input;
|
|
22683
|
+
const candidate = limitStopFooter2 === "" ? unshapedBody : `${unshapedBody}
|
|
22684
|
+
${limitStopFooter2}`;
|
|
22685
|
+
if (splitModelLines(candidate).length <= maxLines && Buffer.byteLength(candidate, "utf8") <= maxBytes) {
|
|
22686
|
+
return { content: candidate };
|
|
22687
|
+
}
|
|
22688
|
+
for (let k = Math.min(lines.length, maxLines - 2);k >= 1; k -= 1) {
|
|
22689
|
+
if (1 + k + 1 > maxLines)
|
|
22690
|
+
continue;
|
|
22691
|
+
const lastLine = firstLine2 + k - 1;
|
|
22692
|
+
const footer = capFooter({ firstLine: firstLine2, lastLine, totalLabel });
|
|
22693
|
+
const bodyJoined = lines.slice(0, k).join(`
|
|
22694
|
+
`);
|
|
22695
|
+
const cut = `${header}
|
|
22696
|
+
${bodyJoined}
|
|
22697
|
+
${footer}`;
|
|
22698
|
+
if (Buffer.byteLength(cut, "utf8") <= maxBytes) {
|
|
22699
|
+
return { content: cut };
|
|
22700
|
+
}
|
|
22701
|
+
}
|
|
22702
|
+
return { content: candidate };
|
|
22703
|
+
}
|
|
22704
|
+
var init_read_continuation = __esm(() => {
|
|
22705
|
+
init_truncate();
|
|
22706
|
+
});
|
|
22707
|
+
|
|
22596
22708
|
// src/tools/read.ts
|
|
22597
22709
|
function parsePositiveInt(value, field) {
|
|
22598
22710
|
if (typeof value !== "number" || !Number.isInteger(value))
|
|
@@ -22612,11 +22724,12 @@ function countLines(prefix) {
|
|
|
22612
22724
|
var UNSUPPORTED_RANGE_ALIASES, readTool;
|
|
22613
22725
|
var init_read = __esm(() => {
|
|
22614
22726
|
init_bounded_io();
|
|
22727
|
+
init_read_continuation();
|
|
22615
22728
|
init_truncate();
|
|
22616
22729
|
UNSUPPORTED_RANGE_ALIASES = ["start_line", "end_line", "start", "end", "line", "lineEnd", "size"];
|
|
22617
22730
|
readTool = {
|
|
22618
22731
|
name: "Read",
|
|
22619
|
-
description: "Read a UTF-8 text file from the repository. Paths are relative to the repository root. " + "Optionally pass offset (1-based line number to start from) and/or limit (maximum number of " + "lines to return) to read a slice instead of the whole file.",
|
|
22732
|
+
description: "Read a UTF-8 text file from the repository. Paths are relative to the repository root. " + "Optionally pass offset (1-based line number to start from) and/or limit (maximum number of " + "lines to return) to read a slice instead of the whole file. " + "Use Read to examine files instead of cat, sed, head, tail or awk in Bash. " + "A read that stops before the end of the file ends with a line naming the offset to continue from. " + "For a large file, read the part you need with offset/limit; when you need the whole file, continue with offset until complete. " + `Output is capped at ${MODEL_MAX_LINES} lines or ${MODEL_MAX_BYTES} bytes.`,
|
|
22620
22733
|
inputSchema: {
|
|
22621
22734
|
type: "object",
|
|
22622
22735
|
properties: {
|
|
@@ -22646,9 +22759,21 @@ var init_read = __esm(() => {
|
|
|
22646
22759
|
const prefix = await readPrefix(target, readCeiling);
|
|
22647
22760
|
const bounded2 = Buffer.byteLength(prefix, "utf8") > readCeiling;
|
|
22648
22761
|
const lineCount = countLines(prefix);
|
|
22649
|
-
const
|
|
22650
|
-
|
|
22651
|
-
|
|
22762
|
+
const totalLabel2 = bounded2 ? `${lineCount}+` : `${lineCount}`;
|
|
22763
|
+
const header = `[${totalLabel2} lines]`;
|
|
22764
|
+
const unshaped = prefix === "" ? header : `${header}
|
|
22765
|
+
${prefix}`;
|
|
22766
|
+
const result2 = applyCapCut({
|
|
22767
|
+
header,
|
|
22768
|
+
lines: splitModelLines(prefix),
|
|
22769
|
+
firstLine: 1,
|
|
22770
|
+
totalLabel: totalLabel2,
|
|
22771
|
+
limitStopFooter: "",
|
|
22772
|
+
unshapedBody: unshaped,
|
|
22773
|
+
maxBytes: ctx.maxBytes,
|
|
22774
|
+
maxLines: MODEL_MAX_LINES
|
|
22775
|
+
});
|
|
22776
|
+
return { content: result2.content };
|
|
22652
22777
|
}
|
|
22653
22778
|
let offset = 1;
|
|
22654
22779
|
if (hasOffset) {
|
|
@@ -22666,25 +22791,38 @@ ${prefix}` };
|
|
|
22666
22791
|
}
|
|
22667
22792
|
const body = await readPrefix(target, ctx.maxFileBytes);
|
|
22668
22793
|
const bounded = Buffer.byteLength(body, "utf8") > ctx.maxFileBytes;
|
|
22669
|
-
const
|
|
22670
|
-
`);
|
|
22671
|
-
const lines = trailingNewline ? body.slice(0, -1).split(`
|
|
22672
|
-
`) : body.split(`
|
|
22673
|
-
`);
|
|
22674
|
-
const totalLines = lines.length;
|
|
22794
|
+
const totalLines = countLines(body);
|
|
22675
22795
|
const totalLabel = bounded ? `${totalLines}+` : `${totalLines}`;
|
|
22676
22796
|
if (offset > totalLines) {
|
|
22677
22797
|
return {
|
|
22678
22798
|
content: bounded ? `offset ${offset} is past the first ${totalLines} lines, which is all that could be read within the ${ctx.maxFileBytes}-byte ceiling` : `offset ${offset} is past the end of the file -- it has ${totalLines} lines`
|
|
22679
22799
|
};
|
|
22680
22800
|
}
|
|
22801
|
+
const lines = splitModelLines(body);
|
|
22681
22802
|
const startIndex = offset - 1;
|
|
22682
22803
|
const endLine = limit === undefined ? totalLines : Math.min(startIndex + limit, totalLines);
|
|
22683
22804
|
const selected = lines.slice(startIndex, endLine).join(`
|
|
22684
22805
|
`);
|
|
22685
|
-
const
|
|
22686
|
-
|
|
22687
|
-
|
|
22806
|
+
const headerLine = `[lines ${offset}-${endLine} of ${totalLabel}]`;
|
|
22807
|
+
const limitStop = shouldAppendLimitStopFooter(limit !== undefined, endLine, totalLines) ? limitStopFooter({
|
|
22808
|
+
nextOffset: endLine + 1,
|
|
22809
|
+
totalIsFloor: bounded,
|
|
22810
|
+
endLine,
|
|
22811
|
+
totalLines
|
|
22812
|
+
}) : "";
|
|
22813
|
+
const unshapedBody = `${headerLine}
|
|
22814
|
+
${selected}`;
|
|
22815
|
+
const result = applyCapCut({
|
|
22816
|
+
header: headerLine,
|
|
22817
|
+
lines: lines.slice(startIndex, endLine),
|
|
22818
|
+
firstLine: offset,
|
|
22819
|
+
totalLabel,
|
|
22820
|
+
limitStopFooter: limitStop,
|
|
22821
|
+
unshapedBody,
|
|
22822
|
+
maxBytes: ctx.maxBytes,
|
|
22823
|
+
maxLines: MODEL_MAX_LINES
|
|
22824
|
+
});
|
|
22825
|
+
return { content: result.content };
|
|
22688
22826
|
} catch (err) {
|
|
22689
22827
|
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
22690
22828
|
}
|
|
@@ -24199,7 +24337,7 @@ var init_scratchpad = __esm(() => {
|
|
|
24199
24337
|
init_truncate();
|
|
24200
24338
|
scratchpadWriteTool = {
|
|
24201
24339
|
name: "ScratchpadWrite",
|
|
24202
|
-
description: "Write a throwaway file to your scratchpad at .nax/scratchpad/. Use it for notes to yourself, command output you want to re-read, or
|
|
24340
|
+
description: "Write a throwaway file to your scratchpad at .nax/scratchpad/. Use it for notes to yourself, command output you want to re-read, intermediate lists, or a probe script to run against the project's code. It is never committed and is wiped when a run finishes (a failed run's scratchpad is retained for inspection until the next run starts and clears it). Paths are relative to the scratchpad and cannot reach the repository.",
|
|
24203
24341
|
inputSchema: {
|
|
24204
24342
|
type: "object",
|
|
24205
24343
|
properties: {
|
|
@@ -24441,33 +24579,97 @@ var init_spill = __esm(() => {
|
|
|
24441
24579
|
// src/tools/tool-audit.ts
|
|
24442
24580
|
import { mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
|
|
24443
24581
|
import { join as join15 } from "path";
|
|
24582
|
+
function registerToolAuditSink(runId, sink) {
|
|
24583
|
+
const sinks = openToolAuditSinks.get(runId);
|
|
24584
|
+
if (sinks === undefined) {
|
|
24585
|
+
openToolAuditSinks.set(runId, new Set([sink]));
|
|
24586
|
+
return;
|
|
24587
|
+
}
|
|
24588
|
+
sinks.add(sink);
|
|
24589
|
+
}
|
|
24590
|
+
function unregisterToolAuditSink(runId, sink) {
|
|
24591
|
+
const sinks = openToolAuditSinks.get(runId);
|
|
24592
|
+
if (sinks === undefined)
|
|
24593
|
+
return;
|
|
24594
|
+
sinks.delete(sink);
|
|
24595
|
+
if (sinks.size === 0)
|
|
24596
|
+
openToolAuditSinks.delete(runId);
|
|
24597
|
+
}
|
|
24598
|
+
async function flushOpenToolAuditSinks(runId) {
|
|
24599
|
+
const sinks = openToolAuditSinks.get(runId);
|
|
24600
|
+
if (sinks === undefined)
|
|
24601
|
+
return;
|
|
24602
|
+
openToolAuditSinks.delete(runId);
|
|
24603
|
+
await Promise.all([...sinks].map(async (sink) => {
|
|
24604
|
+
try {
|
|
24605
|
+
await sink.flushPartial();
|
|
24606
|
+
} catch (error48) {
|
|
24607
|
+
getSafeLogger()?.warn("tools", "tool-audit partial flush failed", { runId, error: errorMessage(error48) });
|
|
24608
|
+
}
|
|
24609
|
+
}));
|
|
24610
|
+
}
|
|
24444
24611
|
function createNoOpToolAuditSink() {
|
|
24445
24612
|
return { record() {}, async flush() {} };
|
|
24446
24613
|
}
|
|
24614
|
+
function nextFileStamp() {
|
|
24615
|
+
const now = Date.now();
|
|
24616
|
+
lastFileStamp = now > lastFileStamp ? now : lastFileStamp + 1;
|
|
24617
|
+
return lastFileStamp;
|
|
24618
|
+
}
|
|
24447
24619
|
function createToolAuditSink(opts) {
|
|
24448
24620
|
const calls = [];
|
|
24449
|
-
|
|
24621
|
+
const runId = opts.header?.runId;
|
|
24622
|
+
let partialFlushed = false;
|
|
24623
|
+
const write = async (partial2) => {
|
|
24624
|
+
if (calls.length === 0)
|
|
24625
|
+
return;
|
|
24626
|
+
await mkdir7(opts.dir, { recursive: true });
|
|
24627
|
+
const body = JSON.stringify({
|
|
24628
|
+
schemaVersion: TOOL_AUDIT_SCHEMA_VERSION,
|
|
24629
|
+
...opts.header ?? {},
|
|
24630
|
+
sessionName: opts.sessionName,
|
|
24631
|
+
...partial2 ? { partial: true } : {},
|
|
24632
|
+
calls: redactRowStrings(calls)
|
|
24633
|
+
}, null, 2);
|
|
24634
|
+
const prefix = runId !== undefined ? `${runId}-` : "";
|
|
24635
|
+
await writeFile5(join15(opts.dir, `${prefix}${nextFileStamp()}-${opts.sessionName}.json`), body);
|
|
24636
|
+
};
|
|
24637
|
+
const sink = {
|
|
24450
24638
|
record(entry) {
|
|
24639
|
+
if (partialFlushed) {
|
|
24640
|
+
getSafeLogger()?.warn("tools", "tool-audit row recorded after the partial flush was dropped", {
|
|
24641
|
+
runId,
|
|
24642
|
+
tool: entry.tool
|
|
24643
|
+
});
|
|
24644
|
+
return;
|
|
24645
|
+
}
|
|
24451
24646
|
calls.push(entry);
|
|
24452
24647
|
},
|
|
24648
|
+
async flushPartial() {
|
|
24649
|
+
if (partialFlushed)
|
|
24650
|
+
return;
|
|
24651
|
+
await write(true);
|
|
24652
|
+
partialFlushed = true;
|
|
24653
|
+
if (runId !== undefined)
|
|
24654
|
+
unregisterToolAuditSink(runId, sink);
|
|
24655
|
+
},
|
|
24453
24656
|
async flush() {
|
|
24454
|
-
if (
|
|
24657
|
+
if (partialFlushed)
|
|
24455
24658
|
return;
|
|
24456
|
-
|
|
24457
|
-
|
|
24458
|
-
|
|
24459
|
-
...opts.header ?? {},
|
|
24460
|
-
sessionName: opts.sessionName,
|
|
24461
|
-
calls: redactRowStrings(calls)
|
|
24462
|
-
}, null, 2);
|
|
24463
|
-
const prefix = opts.header?.runId !== undefined ? `${opts.header.runId}-` : "";
|
|
24464
|
-
await writeFile5(join15(opts.dir, `${prefix}${Date.now()}-${opts.sessionName}.json`), body);
|
|
24659
|
+
if (runId !== undefined)
|
|
24660
|
+
unregisterToolAuditSink(runId, sink);
|
|
24661
|
+
await write(false);
|
|
24465
24662
|
}
|
|
24466
24663
|
};
|
|
24664
|
+
if (runId !== undefined)
|
|
24665
|
+
registerToolAuditSink(runId, sink);
|
|
24666
|
+
return sink;
|
|
24467
24667
|
}
|
|
24468
|
-
var TOOL_AUDIT_SCHEMA_VERSION = 1;
|
|
24668
|
+
var openToolAuditSinks, TOOL_AUDIT_SCHEMA_VERSION = 1, lastFileStamp = 0;
|
|
24469
24669
|
var init_tool_audit = __esm(() => {
|
|
24670
|
+
init_logger2();
|
|
24470
24671
|
init_permissions();
|
|
24672
|
+
openToolAuditSinks = new Map;
|
|
24471
24673
|
});
|
|
24472
24674
|
|
|
24473
24675
|
// src/tools/write.ts
|
|
@@ -24677,6 +24879,7 @@ function createCodingToolRuntime(opts) {
|
|
|
24677
24879
|
tool: policyIdentity,
|
|
24678
24880
|
stage: opts.pipelineStage ?? "unknown",
|
|
24679
24881
|
rule: verdict.rule ?? verdict.reason,
|
|
24882
|
+
...verdict.rule !== undefined ? { matchedRule: verdict.rule } : {},
|
|
24680
24883
|
summary: ask.summary,
|
|
24681
24884
|
...ask.unshowable ? { unshowable: true } : {},
|
|
24682
24885
|
...typeof input[tool.scope.commandField ?? ""] === "string" ? { command: input[tool.scope.commandField] } : {},
|
|
@@ -25605,7 +25808,9 @@ var init_config_guards = __esm(() => {
|
|
|
25605
25808
|
"review.gateLLMChecksOnMechanicalPass": "LLM review checks are sequenced by the story orchestrator; this key never had an effect after #1859",
|
|
25606
25809
|
debate: "the multi-agent debate subsystem was removed; a config that enabled debate.stages.plan without setting plan.mode was resolving to the debate plan strategy and now plans with `single` \u2014 set plan.mode explicitly to `single` or `refine`",
|
|
25607
25810
|
"plan.citationThreshold": "this key only fed the removed pipeline plan mode",
|
|
25608
|
-
"plan.criticModel": "this key only fed the removed pipeline plan mode"
|
|
25811
|
+
"plan.criticModel": "this key only fed the removed pipeline plan mode",
|
|
25812
|
+
"quality.autofix.enforceTestWriterIsolation": "the mock-structure handoff path this key guarded was removed in #1084; the key has had no effect since",
|
|
25813
|
+
"prompts.overrides.single-session": "the single-session prompt role is not used by any run since fb3cfad3e; test-after stories use the tdd-simple role, so override tdd-simple instead"
|
|
25609
25814
|
};
|
|
25610
25815
|
});
|
|
25611
25816
|
|
|
@@ -26775,12 +26980,10 @@ var init_schemas_execution = __esm(() => {
|
|
|
26775
26980
|
}).default({ format: "auto" }),
|
|
26776
26981
|
autofix: exports_external.object({
|
|
26777
26982
|
enabled: exports_external.boolean().default(true),
|
|
26778
|
-
maxAttempts: exports_external.number().int().min(1).default(3)
|
|
26779
|
-
enforceTestWriterIsolation: exports_external.boolean().default(true)
|
|
26983
|
+
maxAttempts: exports_external.number().int().min(1).default(3)
|
|
26780
26984
|
}).default({
|
|
26781
26985
|
enabled: true,
|
|
26782
|
-
maxAttempts: 3
|
|
26783
|
-
enforceTestWriterIsolation: true
|
|
26986
|
+
maxAttempts: 3
|
|
26784
26987
|
}),
|
|
26785
26988
|
forceExit: exports_external.boolean().default(false),
|
|
26786
26989
|
detectOpenHandles: exports_external.boolean().default(true),
|
|
@@ -26832,6 +27035,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
26832
27035
|
}).default({ testWriter: "fast", verifier: "fast" }),
|
|
26833
27036
|
verifierTimeoutSeconds: exports_external.number().int().min(60).max(7200).default(1800),
|
|
26834
27037
|
testWriterAllowedPaths: exports_external.array(exports_external.string()).optional(),
|
|
27038
|
+
testWriterCommitHooks: exports_external.enum(["skip", "run"]).optional(),
|
|
26835
27039
|
rollbackOnFailure: exports_external.boolean().optional(),
|
|
26836
27040
|
greenfieldDetection: exports_external.boolean().optional()
|
|
26837
27041
|
});
|
|
@@ -26847,7 +27051,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
26847
27051
|
});
|
|
26848
27052
|
|
|
26849
27053
|
// src/config/schemas-infra.ts
|
|
26850
|
-
var PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, LlmRoutingConfigSchema, AgentRoutingProfileSchema, AgentRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, UsageAuditConfigSchema, FallbackTargetSchema, AgentFallbackConfigSchema, DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG, AgentIdleWatchdogConfigSchema, DEFAULT_AGENT_SPIN_BREAKER_CONFIG, AgentSpinBreakerConfigSchema, AgentAcpConfigSchema, AgentNativeTransportRetryConfigSchema, AgentNativeConfigSchema, AgentTimeoutRetryConfigSchema, DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, CuratorThresholdsSchema, CuratorRetentionConfigSchema, CuratorConfigSchema;
|
|
27054
|
+
var PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, LlmRoutingConfigSchema, AgentRoutingProfileSchema, AgentRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, UsageAuditConfigSchema, FallbackTargetSchema, AgentFallbackConfigSchema, DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG, AgentIdleWatchdogConfigSchema, DEFAULT_AGENT_SPIN_BREAKER_CONFIG, AgentSpinBreakerConfigSchema, AgentAcpConfigSchema, AgentNativeTransportRetryConfigSchema, AgentNativeConfigSchema, AgentTimeoutRetryConfigSchema, DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG, AgentConfigSchema, PrecheckConfigSchema, PROMPT_OVERRIDE_ROLES, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, CuratorThresholdsSchema, CuratorRetentionConfigSchema, CuratorConfigSchema;
|
|
26851
27055
|
var init_schemas_infra = __esm(() => {
|
|
26852
27056
|
init_zod();
|
|
26853
27057
|
init_agent_defaults();
|
|
@@ -27098,10 +27302,19 @@ var init_schemas_infra = __esm(() => {
|
|
|
27098
27302
|
PrecheckConfigSchema = exports_external.object({
|
|
27099
27303
|
storySizeGate: StorySizeGateConfigSchema
|
|
27100
27304
|
});
|
|
27305
|
+
PROMPT_OVERRIDE_ROLES = ["no-test", "test-writer", "implementer", "verifier", "tdd-simple"];
|
|
27101
27306
|
PromptsConfigSchema = exports_external.object({
|
|
27102
|
-
overrides: exports_external.record(exports_external.string().
|
|
27103
|
-
|
|
27104
|
-
|
|
27307
|
+
overrides: exports_external.record(exports_external.string(), exports_external.string().min(1, "Override path must be non-empty")).superRefine((overrides, ctx) => {
|
|
27308
|
+
for (const key of Object.keys(overrides)) {
|
|
27309
|
+
if (!PROMPT_OVERRIDE_ROLES.includes(key)) {
|
|
27310
|
+
ctx.addIssue({
|
|
27311
|
+
code: "custom",
|
|
27312
|
+
message: `Role must be one of: ${PROMPT_OVERRIDE_ROLES.join(", ")}`,
|
|
27313
|
+
path: [key]
|
|
27314
|
+
});
|
|
27315
|
+
}
|
|
27316
|
+
}
|
|
27317
|
+
}).optional(),
|
|
27105
27318
|
behavioralGuardrails: exports_external.enum(["off", "lite", "strict"]).default("lite")
|
|
27106
27319
|
});
|
|
27107
27320
|
ProjectProfileSchema = exports_external.object({
|
|
@@ -27477,7 +27690,7 @@ var init_schemas3 = __esm(() => {
|
|
|
27477
27690
|
agents: { enabled: true, strategy: "off", profiles: [] }
|
|
27478
27691
|
}),
|
|
27479
27692
|
execution: ExecutionConfigSchema.default({
|
|
27480
|
-
maxIterations:
|
|
27693
|
+
maxIterations: 20,
|
|
27481
27694
|
iterationDelayMs: 2000,
|
|
27482
27695
|
costLimit: 30,
|
|
27483
27696
|
sessionTimeoutSeconds: 3600,
|
|
@@ -27522,8 +27735,7 @@ var init_schemas3 = __esm(() => {
|
|
|
27522
27735
|
},
|
|
27523
27736
|
autofix: {
|
|
27524
27737
|
enabled: true,
|
|
27525
|
-
maxAttempts: 3
|
|
27526
|
-
enforceTestWriterIsolation: true
|
|
27738
|
+
maxAttempts: 3
|
|
27527
27739
|
},
|
|
27528
27740
|
forceExit: false,
|
|
27529
27741
|
detectOpenHandles: true,
|
|
@@ -40501,23 +40713,6 @@ function lintDiagnosticToFinding(d, workdir, tool) {
|
|
|
40501
40713
|
var init_lint = __esm(() => {
|
|
40502
40714
|
init_path_utils();
|
|
40503
40715
|
});
|
|
40504
|
-
// src/findings/adapters/semantic-review.ts
|
|
40505
|
-
function reviewFindingToFinding(f) {
|
|
40506
|
-
return {
|
|
40507
|
-
source: "semantic-review",
|
|
40508
|
-
severity: f.severity,
|
|
40509
|
-
category: f.category ?? "",
|
|
40510
|
-
rule: f.ruleId,
|
|
40511
|
-
file: f.file,
|
|
40512
|
-
line: f.line,
|
|
40513
|
-
column: f.column,
|
|
40514
|
-
endLine: f.endLine,
|
|
40515
|
-
endColumn: f.endColumn,
|
|
40516
|
-
message: f.message,
|
|
40517
|
-
fixTarget: "source"
|
|
40518
|
-
};
|
|
40519
|
-
}
|
|
40520
|
-
|
|
40521
40716
|
// src/findings/adapters/test-failure.ts
|
|
40522
40717
|
function testFailureToFinding(failure) {
|
|
40523
40718
|
const frames = (failure.stackTrace ?? []).slice(0, MAX_FRAMES_IN_MESSAGE);
|
|
@@ -41362,7 +41557,7 @@ function buildBehavioralGuardrailsSection(role, level, _variant, _isolation) {
|
|
|
41362
41557
|
if (role === "test-writer") {
|
|
41363
41558
|
return buildTestWriterGuardrails(level);
|
|
41364
41559
|
}
|
|
41365
|
-
if (role === "
|
|
41560
|
+
if (role === "tdd-simple" || role === "batch") {
|
|
41366
41561
|
return buildCombinedGuardrails(level);
|
|
41367
41562
|
}
|
|
41368
41563
|
return buildImplementerGuardrails(level);
|
|
@@ -41486,7 +41681,7 @@ ${body}`;
|
|
|
41486
41681
|
}
|
|
41487
41682
|
var HERMETIC_ROLES, LANGUAGE_GUIDANCE;
|
|
41488
41683
|
var init_hermetic = __esm(() => {
|
|
41489
|
-
HERMETIC_ROLES = new Set(["test-writer", "implementer", "tdd-simple", "batch"
|
|
41684
|
+
HERMETIC_ROLES = new Set(["test-writer", "implementer", "tdd-simple", "batch"]);
|
|
41490
41685
|
LANGUAGE_GUIDANCE = {
|
|
41491
41686
|
go: "Define interfaces for external dependencies. Use constructor injection. Test with interface mocks \u2014 no real I/O in tests.",
|
|
41492
41687
|
rust: "Use trait objects or generics for external deps. Mock with the mockall crate. Use #[cfg(test)] modules.",
|
|
@@ -41682,11 +41877,6 @@ isolation scope: Implement source code in src/ to make tests pass. Do not modify
|
|
|
41682
41877
|
return `${header}
|
|
41683
41878
|
|
|
41684
41879
|
isolation scope: Read-only TDD integrity inspection. Review story-scoped test results and test-file modifications. Do NOT apply source or test fixes. You MAY write only the verdict file (.nax-verifier-verdict.json).${footer}`;
|
|
41685
|
-
}
|
|
41686
|
-
if (role === "single-session") {
|
|
41687
|
-
return `${header}
|
|
41688
|
-
|
|
41689
|
-
isolation scope: Create test files in test/ directory, then implement source code in src/ to make tests pass. Both directories are in scope for this session.${footer}`;
|
|
41690
41880
|
}
|
|
41691
41881
|
return `${header}
|
|
41692
41882
|
|
|
@@ -41727,7 +41917,11 @@ freely. Every other path under \`.nax/\` stays off limits.
|
|
|
41727
41917
|
- A test under \`.nax/\` is NOT a reason to skip writing source-tree tests. \`.nax/\` is generated
|
|
41728
41918
|
scaffolding, not real coverage of the package's code.
|
|
41729
41919
|
- A source-tree test is NOT a reason to remove a test under \`.nax/\`. The two serve different
|
|
41730
|
-
purposes and must coexist
|
|
41920
|
+
purposes and must coexist.
|
|
41921
|
+
|
|
41922
|
+
nax updates \`.nax/features/<feature>/prd.json\` itself during a run, so it shows as modified in
|
|
41923
|
+
\`git status\` \u2014 do not diff or revert it: this story's criteria are already in this prompt, and if you
|
|
41924
|
+
need its contents, read it with the \`Read\` tool; shell commands that name it may be refused.`;
|
|
41731
41925
|
}
|
|
41732
41926
|
|
|
41733
41927
|
// src/prompts/sections/out-of-scope.ts
|
|
@@ -41851,6 +42045,7 @@ Workflow:
|
|
|
41851
42045
|
|
|
41852
42046
|
Rules:
|
|
41853
42047
|
- Stubs are NOT implementations. The implementer in the next session writes real logic.
|
|
42048
|
+
- Do not commit. When your session ends, nax commits the files you changed as the RED state.
|
|
41854
42049
|
- Each test name describes ONE behavior. Use AC IDs in test names when available (e.g. \`it('AC4: throws Division by zero when b === 0')\`).
|
|
41855
42050
|
- Assert on observable outputs.
|
|
41856
42051
|
- ${frameworkHint}
|
|
@@ -41867,10 +42062,12 @@ Workflow:
|
|
|
41867
42062
|
2. Break the work into small tasks before writing: treat each AC as one task and note the test name(s) you will write (success + boundary) and which file they belong in. This per-AC list is your checklist.
|
|
41868
42063
|
3. Create test files in the location the project uses for tests (project context names it).
|
|
41869
42064
|
4. For each AC: write at least one test for the success path AND at least one for a boundary/failure path (zero, empty, negative, missing, throws). ACs worded as "throws X" require a test asserting the throw.
|
|
41870
|
-
5. Run the new test files. Confirm every test fails with an ASSERTION failure
|
|
42065
|
+
5. Run the new test files. Confirm every test fails with an ASSERTION failure, not an import error or a runtime crash before the assertion. A test that errors before reaching its assertion does not prove the behavior is missing.
|
|
41871
42066
|
|
|
41872
42067
|
Rules:
|
|
41873
42068
|
- Do NOT create or modify any source files. Read source for types/interfaces only.
|
|
42069
|
+
- A type-check error that exists only because the implementer has not yet added a field, parameter or export the acceptance criteria require is the expected RED state. Do not work around it with type casts, type-checker suppression comments, allow-list tags or throwaway type-probe scripts; type each test as the finished code will be.
|
|
42070
|
+
- Do not commit. When your session ends, nax commits the files you changed as the RED state.
|
|
41874
42071
|
- Each test name describes ONE behavior; each test asserts ONE behavior. When the AC has a number or ID, prefix the test name (e.g. \`it('AC4: throws Division by zero when b === 0')\`).
|
|
41875
42072
|
- Assert on observable outputs (return values, thrown errors, file contents, log output, boundary state). Do not assert on private helpers, internal call counts, or implementation-level mocks unless the AC requires it.
|
|
41876
42073
|
- ${frameworkHint}
|
|
@@ -41891,25 +42088,6 @@ Instructions:
|
|
|
41891
42088
|
- Do NOT perform semantic acceptance review; semantic/adversarial review stages own acceptance criteria and broad code-quality findings
|
|
41892
42089
|
- Write a detailed verdict with reasoning
|
|
41893
42090
|
- Goal: verify story-scoped tests pass and test integrity was preserved`;
|
|
41894
|
-
}
|
|
41895
|
-
if (role === "single-session") {
|
|
41896
|
-
return `# Role: Single-Session
|
|
41897
|
-
|
|
41898
|
-
Your task: write tests AND implement the feature in one session.
|
|
41899
|
-
|
|
41900
|
-
Workflow:
|
|
41901
|
-
1. Read the acceptance criteria. For each AC, plan one success-path test and one boundary/failure test.
|
|
41902
|
-
2. Create test files in the location the project uses for tests. Cover every AC.
|
|
41903
|
-
3. Run the tests to confirm they fail with ASSERTION failures \u2014 NOT import errors or compile errors. A test that errors before reaching its assertion does not prove the behavior is missing.
|
|
41904
|
-
4. Implement source code in the package's source location to make the tests pass.
|
|
41905
|
-
5. After each meaningful change, re-run only the scoped test files \u2014 never the full suite.
|
|
41906
|
-
6. When all scoped tests pass, stage and commit ALL changed files: \`${gitCommitInstruction(commitMsg)}\`.
|
|
41907
|
-
|
|
41908
|
-
Rules:
|
|
41909
|
-
- Each test name describes ONE behavior; use AC IDs when available.
|
|
41910
|
-
- Assert on observable outputs.
|
|
41911
|
-
- ${frameworkHint}
|
|
41912
|
-
- Goal: every AC has at least one passing test; all changes committed.`;
|
|
41913
42091
|
}
|
|
41914
42092
|
if (role === "batch") {
|
|
41915
42093
|
const verifyCmdLine = testCmd ? ` - Re-run only the scoped test files after each meaningful change: ${testCmd}` : " - Re-run only the scoped test files after each meaningful change";
|
|
@@ -41969,6 +42147,11 @@ or is read by another step. A failed run's scratchpad is retained for inspection
|
|
|
41969
42147
|
before failing outlive that run, and are cleared at the next run's start. Treat every file as disposable,
|
|
41970
42148
|
and overwrite freely.
|
|
41971
42149
|
|
|
42150
|
+
To try a snippet that imports project code or dependencies, write it under \`${dir}\` with
|
|
42151
|
+
\`ScratchpadWrite\` and run it from there: relative imports resolve from \`${dir}\`, so prefer the
|
|
42152
|
+
project's package names or path aliases where it has them. A script written outside the repository,
|
|
42153
|
+
such as in \`/tmp\`, cannot resolve the project's modules, and the file tools cannot write there.
|
|
42154
|
+
|
|
41972
42155
|
\`${dir}\` is the one directory under \`.nax/\` you may write to. Every other path under \`.nax/\`
|
|
41973
42156
|
must still never be moved, renamed, or deleted.`;
|
|
41974
42157
|
}
|
|
@@ -42199,7 +42382,7 @@ An adversarial reviewer will audit your tests after implementation and BLOCK the
|
|
|
42199
42382
|
}
|
|
42200
42383
|
var AUTHORING_ROLES;
|
|
42201
42384
|
var init_test_quality = __esm(() => {
|
|
42202
|
-
AUTHORING_ROLES = new Set(["test-writer", "
|
|
42385
|
+
AUTHORING_ROLES = new Set(["test-writer", "tdd-simple", "batch"]);
|
|
42203
42386
|
});
|
|
42204
42387
|
|
|
42205
42388
|
// src/prompts/sections/verdict.ts
|
|
@@ -42399,7 +42582,7 @@ ACCEPTANCE TEST FILE: ${p.acceptanceTestPath}
|
|
|
42399
42582
|
|
|
42400
42583
|
SOURCE FILES (auto-detected from imports, up to ${p.maxFileLines} lines each):
|
|
42401
42584
|
${p.sourceFilesSection}
|
|
42402
|
-
|
|
42585
|
+
|
|
42403
42586
|
Respond with ONLY a JSON object in this exact format (no markdown, no extra text):
|
|
42404
42587
|
${responseSchema}`;
|
|
42405
42588
|
}
|
|
@@ -42478,16 +42661,10 @@ ${f.content}
|
|
|
42478
42661
|
\`\`\``).join(`
|
|
42479
42662
|
|
|
42480
42663
|
`) : "(No source files could be resolved from imports)";
|
|
42481
|
-
const verdictSection = p.semanticVerdicts && p.semanticVerdicts.length > 0 ? `
|
|
42482
|
-
SEMANTIC VERDICTS:
|
|
42483
|
-
${p.semanticVerdicts.map((v) => `- ${v.storyId}: ${v.passed ? "likely test bug (semantic review confirmed AC implementation)" : "unconfirmed"}`).join(`
|
|
42484
|
-
`)}
|
|
42485
|
-
` : "";
|
|
42486
42664
|
return this.buildDiagnosisPromptTemplate({
|
|
42487
42665
|
truncatedOutput,
|
|
42488
42666
|
acceptanceTestPath: p.acceptanceTestPath ?? "(path unavailable \u2014 inspect test output for file references)",
|
|
42489
42667
|
sourceFilesSection,
|
|
42490
|
-
verdictSection,
|
|
42491
42668
|
maxFileLines: MAX_FILE_LINES
|
|
42492
42669
|
});
|
|
42493
42670
|
}
|
|
@@ -53204,8 +53381,8 @@ var init_version = __esm(() => {
|
|
|
53204
53381
|
NAX_AI_VERSION = CATALOG_VERSION;
|
|
53205
53382
|
NAX_COMMIT = (() => {
|
|
53206
53383
|
try {
|
|
53207
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
53208
|
-
return "
|
|
53384
|
+
if (/^[0-9a-f]{6,10}$/.test("db5bc9ae"))
|
|
53385
|
+
return "db5bc9ae";
|
|
53209
53386
|
} catch {}
|
|
53210
53387
|
try {
|
|
53211
53388
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -53764,7 +53941,7 @@ function attachCostSubscriber(bus, aggregator, runId, projectKey) {
|
|
|
53764
53941
|
offCompleted();
|
|
53765
53942
|
};
|
|
53766
53943
|
}
|
|
53767
|
-
var _costSubscriberDeps, COST_ROW_SCHEMA_VERSION =
|
|
53944
|
+
var _costSubscriberDeps, COST_ROW_SCHEMA_VERSION = 7;
|
|
53768
53945
|
var init_cost2 = __esm(() => {
|
|
53769
53946
|
init_agents();
|
|
53770
53947
|
init_version();
|
|
@@ -53886,8 +54063,33 @@ function toUsageEntry(event, runId) {
|
|
|
53886
54063
|
costUsd: event.costUsd
|
|
53887
54064
|
};
|
|
53888
54065
|
}
|
|
53889
|
-
function
|
|
53890
|
-
|
|
54066
|
+
function toOneShotEntry(event, runId) {
|
|
54067
|
+
if (event.kind !== "complete")
|
|
54068
|
+
return null;
|
|
54069
|
+
const tu = event.tokenUsage;
|
|
54070
|
+
const wireExact = event.exactCostUsd;
|
|
54071
|
+
const costUsd = typeof wireExact === "number" && Number.isFinite(wireExact) ? wireExact : event.estimatedCostUsd;
|
|
54072
|
+
if (!tu && (costUsd ?? 0) === 0)
|
|
54073
|
+
return null;
|
|
54074
|
+
return {
|
|
54075
|
+
ts: event.timestamp,
|
|
54076
|
+
runId,
|
|
54077
|
+
scopeId: event.scopeId,
|
|
54078
|
+
streamCallId: event.callId ?? "one-shot",
|
|
54079
|
+
sessionName: event.sessionName,
|
|
54080
|
+
storyId: event.storyId,
|
|
54081
|
+
stage: event.stage,
|
|
54082
|
+
agentName: event.agentName,
|
|
54083
|
+
cadence: "one-shot",
|
|
54084
|
+
input: tu?.inputTokens,
|
|
54085
|
+
output: tu?.outputTokens,
|
|
54086
|
+
cacheRead: tu?.cacheReadInputTokens,
|
|
54087
|
+
cacheWrite: tu?.cacheCreationInputTokens,
|
|
54088
|
+
costUsd
|
|
54089
|
+
};
|
|
54090
|
+
}
|
|
54091
|
+
function attachUsageAuditSubscriber(bus, dispatchEvents, auditor, runId) {
|
|
54092
|
+
const offStream = bus.onAgentStream((event) => {
|
|
53891
54093
|
switch (event.kind) {
|
|
53892
54094
|
case "agent.usage_update":
|
|
53893
54095
|
auditor.record(toUsageEntry(event, runId));
|
|
@@ -53896,6 +54098,15 @@ function attachUsageAuditSubscriber(bus, auditor, runId) {
|
|
|
53896
54098
|
break;
|
|
53897
54099
|
}
|
|
53898
54100
|
});
|
|
54101
|
+
const offDispatch = dispatchEvents.onDispatch((event) => {
|
|
54102
|
+
const entry = toOneShotEntry(event, runId);
|
|
54103
|
+
if (entry)
|
|
54104
|
+
auditor.record(entry);
|
|
54105
|
+
});
|
|
54106
|
+
return () => {
|
|
54107
|
+
offStream();
|
|
54108
|
+
offDispatch();
|
|
54109
|
+
};
|
|
53899
54110
|
}
|
|
53900
54111
|
|
|
53901
54112
|
// src/runtime/middleware/index.ts
|
|
@@ -53907,15 +54118,255 @@ var init_middleware = __esm(() => {
|
|
|
53907
54118
|
init_logging();
|
|
53908
54119
|
});
|
|
53909
54120
|
|
|
54121
|
+
// src/runtime/usage-auditor.ts
|
|
54122
|
+
import { appendFileSync as appendFileSync2 } from "fs";
|
|
54123
|
+
import { mkdir as mkdir16 } from "fs/promises";
|
|
54124
|
+
import { join as join45 } from "path";
|
|
54125
|
+
function createNoOpUsageAuditor() {
|
|
54126
|
+
return {
|
|
54127
|
+
record() {},
|
|
54128
|
+
async flush() {}
|
|
54129
|
+
};
|
|
54130
|
+
}
|
|
54131
|
+
function deriveSessionRole(sessionName) {
|
|
54132
|
+
for (const role of ROLES_BY_DESCENDING_LENGTH) {
|
|
54133
|
+
if (sessionName === role || sessionName.endsWith(`-${role}`))
|
|
54134
|
+
return role;
|
|
54135
|
+
}
|
|
54136
|
+
return;
|
|
54137
|
+
}
|
|
54138
|
+
|
|
54139
|
+
class UsageAuditor {
|
|
54140
|
+
_queue = Promise.resolve();
|
|
54141
|
+
_dirCreated = false;
|
|
54142
|
+
_dir;
|
|
54143
|
+
_jsonlPath;
|
|
54144
|
+
constructor(runId, dir) {
|
|
54145
|
+
this._dir = dir;
|
|
54146
|
+
this._jsonlPath = join45(dir, `${runId}.jsonl`);
|
|
54147
|
+
}
|
|
54148
|
+
record(entry) {
|
|
54149
|
+
this._queue = this._queue.then(() => this._writeEntry(entry)).catch((err) => {
|
|
54150
|
+
const sysErr = err;
|
|
54151
|
+
getSafeLogger()?.warn("audit", "usage-audit write failed", {
|
|
54152
|
+
path: this._jsonlPath,
|
|
54153
|
+
error: errorMessage(err),
|
|
54154
|
+
code: sysErr?.code,
|
|
54155
|
+
errno: sysErr?.errno,
|
|
54156
|
+
syscall: sysErr?.syscall,
|
|
54157
|
+
ts: entry.ts,
|
|
54158
|
+
storyId: entry.storyId,
|
|
54159
|
+
sessionName: entry.sessionName,
|
|
54160
|
+
agentName: entry.agentName,
|
|
54161
|
+
stage: entry.stage,
|
|
54162
|
+
streamCallId: entry.streamCallId
|
|
54163
|
+
});
|
|
54164
|
+
});
|
|
54165
|
+
}
|
|
54166
|
+
async _writeEntry(entry) {
|
|
54167
|
+
if (!this._dirCreated) {
|
|
54168
|
+
await mkdir16(this._dir, { recursive: true });
|
|
54169
|
+
this._dirCreated = true;
|
|
54170
|
+
}
|
|
54171
|
+
const row = { ...entry, sessionRole: deriveSessionRole(entry.sessionName) };
|
|
54172
|
+
await _usageAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(row)}
|
|
54173
|
+
`);
|
|
54174
|
+
}
|
|
54175
|
+
async flush() {
|
|
54176
|
+
await this._queue;
|
|
54177
|
+
}
|
|
54178
|
+
}
|
|
54179
|
+
var _usageAuditorDeps, ROLES_BY_DESCENDING_LENGTH;
|
|
54180
|
+
var init_usage_auditor = __esm(() => {
|
|
54181
|
+
init_logger2();
|
|
54182
|
+
init_session_role();
|
|
54183
|
+
_usageAuditorDeps = {
|
|
54184
|
+
appendLine: async (path6, data) => {
|
|
54185
|
+
appendFileSync2(path6, data, "utf8");
|
|
54186
|
+
}
|
|
54187
|
+
};
|
|
54188
|
+
ROLES_BY_DESCENDING_LENGTH = [...KNOWN_SESSION_ROLES].sort((a, b) => b.length - a.length);
|
|
54189
|
+
});
|
|
54190
|
+
|
|
54191
|
+
// src/runtime/in-flight-usage.ts
|
|
54192
|
+
function hasSpend(entry) {
|
|
54193
|
+
const { input, output, cacheRead, cacheWrite } = entry.tokens;
|
|
54194
|
+
return entry.costUsd > 0 || input + output + cacheRead + cacheWrite > 0;
|
|
54195
|
+
}
|
|
54196
|
+
function attachInFlightUsageTracker(streamBus, dispatchEvents) {
|
|
54197
|
+
const entries = new Map;
|
|
54198
|
+
const latestEndedBySession = new Map;
|
|
54199
|
+
let endedSeq = 0;
|
|
54200
|
+
const entryFor = (event) => {
|
|
54201
|
+
let entry = entries.get(event.callId);
|
|
54202
|
+
if (entry === undefined) {
|
|
54203
|
+
entry = {
|
|
54204
|
+
streamCallId: event.callId,
|
|
54205
|
+
agentName: event.agentName,
|
|
54206
|
+
model: "unknown",
|
|
54207
|
+
sessionName: event.sessionName,
|
|
54208
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
54209
|
+
costUsd: 0,
|
|
54210
|
+
roundTrips: 0
|
|
54211
|
+
};
|
|
54212
|
+
entries.set(event.callId, entry);
|
|
54213
|
+
}
|
|
54214
|
+
entry.agentName = event.agentName;
|
|
54215
|
+
entry.sessionName = event.sessionName;
|
|
54216
|
+
if (event.storyId !== undefined)
|
|
54217
|
+
entry.storyId = event.storyId;
|
|
54218
|
+
if (event.stage !== undefined)
|
|
54219
|
+
entry.stage = event.stage;
|
|
54220
|
+
if (event.scopeId !== undefined)
|
|
54221
|
+
entry.scopeId = event.scopeId;
|
|
54222
|
+
return entry;
|
|
54223
|
+
};
|
|
54224
|
+
const onUsageUpdate = (event) => {
|
|
54225
|
+
if (event.cadence !== "round-trip")
|
|
54226
|
+
return;
|
|
54227
|
+
const entry = entryFor(event);
|
|
54228
|
+
entry.tokens.input += event.inputTokens ?? 0;
|
|
54229
|
+
entry.tokens.output += event.outputTokens ?? 0;
|
|
54230
|
+
entry.tokens.cacheRead += event.cacheRead ?? 0;
|
|
54231
|
+
entry.tokens.cacheWrite += event.cacheWrite ?? 0;
|
|
54232
|
+
entry.costUsd += event.costUsd ?? 0;
|
|
54233
|
+
if (event.roundTrip !== undefined)
|
|
54234
|
+
entry.roundTrips += 1;
|
|
54235
|
+
};
|
|
54236
|
+
const onCallStarted = (event) => {
|
|
54237
|
+
const entry = entryFor(event);
|
|
54238
|
+
entry.model = event.model === "" ? "unknown" : event.model;
|
|
54239
|
+
};
|
|
54240
|
+
const forget = (callId) => {
|
|
54241
|
+
entries.delete(callId);
|
|
54242
|
+
for (const [sessionName, remembered] of latestEndedBySession) {
|
|
54243
|
+
if (remembered === callId)
|
|
54244
|
+
latestEndedBySession.delete(sessionName);
|
|
54245
|
+
}
|
|
54246
|
+
};
|
|
54247
|
+
const onCallEnded = (event) => {
|
|
54248
|
+
latestEndedBySession.set(event.sessionName, event.callId);
|
|
54249
|
+
if (event.status === "success" || event.status === "timeout") {
|
|
54250
|
+
forget(event.callId);
|
|
54251
|
+
return;
|
|
54252
|
+
}
|
|
54253
|
+
const existing2 = entries.get(event.callId);
|
|
54254
|
+
if (existing2 === undefined)
|
|
54255
|
+
return;
|
|
54256
|
+
const entry = entryFor(event);
|
|
54257
|
+
entry.endedStatus = event.status;
|
|
54258
|
+
endedSeq += 1;
|
|
54259
|
+
entry.endedSeq = endedSeq;
|
|
54260
|
+
};
|
|
54261
|
+
const onDispatch = (event) => {
|
|
54262
|
+
if (event.kind !== "session-turn")
|
|
54263
|
+
return;
|
|
54264
|
+
const callId = latestEndedBySession.get(event.sessionName);
|
|
54265
|
+
if (callId === undefined)
|
|
54266
|
+
return;
|
|
54267
|
+
const entry = entries.get(callId);
|
|
54268
|
+
if (entry !== undefined && entry.endedStatus === "cancelled")
|
|
54269
|
+
forget(callId);
|
|
54270
|
+
};
|
|
54271
|
+
const matchesScope = (entry, event) => {
|
|
54272
|
+
if (entry.scopeId === undefined)
|
|
54273
|
+
return false;
|
|
54274
|
+
return entry.scopeId === event.scopeId || entry.scopeId === event.callId;
|
|
54275
|
+
};
|
|
54276
|
+
const onDispatchError = (event) => {
|
|
54277
|
+
const hasUsage = event.tokenUsage !== undefined;
|
|
54278
|
+
const costUsd = event.exactCostUsd ?? event.estimatedCostUsd ?? 0;
|
|
54279
|
+
if (!hasUsage && !(Number.isFinite(costUsd) && costUsd > 0))
|
|
54280
|
+
return;
|
|
54281
|
+
let target;
|
|
54282
|
+
for (const entry of entries.values()) {
|
|
54283
|
+
if (entry.endedStatus === undefined || !matchesScope(entry, event))
|
|
54284
|
+
continue;
|
|
54285
|
+
if (target === undefined || (entry.endedSeq ?? 0) > (target.endedSeq ?? 0))
|
|
54286
|
+
target = entry;
|
|
54287
|
+
}
|
|
54288
|
+
if (target !== undefined)
|
|
54289
|
+
forget(target.streamCallId);
|
|
54290
|
+
};
|
|
54291
|
+
const onStream = (event) => {
|
|
54292
|
+
if (event.kind === "agent.usage_update")
|
|
54293
|
+
onUsageUpdate(event);
|
|
54294
|
+
else if (event.kind === "agent.call_started")
|
|
54295
|
+
onCallStarted(event);
|
|
54296
|
+
else if (event.kind === "agent.call_ended")
|
|
54297
|
+
onCallEnded(event);
|
|
54298
|
+
};
|
|
54299
|
+
const residuals = () => {
|
|
54300
|
+
const out = [];
|
|
54301
|
+
for (const entry of entries.values()) {
|
|
54302
|
+
if (!hasSpend(entry))
|
|
54303
|
+
continue;
|
|
54304
|
+
out.push({
|
|
54305
|
+
streamCallId: entry.streamCallId,
|
|
54306
|
+
agentName: entry.agentName,
|
|
54307
|
+
model: entry.model,
|
|
54308
|
+
sessionName: entry.sessionName,
|
|
54309
|
+
...entry.storyId !== undefined ? { storyId: entry.storyId } : {},
|
|
54310
|
+
...entry.stage !== undefined ? { stage: entry.stage } : {},
|
|
54311
|
+
...entry.scopeId !== undefined ? { scopeId: entry.scopeId } : {},
|
|
54312
|
+
tokens: { ...entry.tokens },
|
|
54313
|
+
costUsd: entry.costUsd,
|
|
54314
|
+
roundTrips: entry.roundTrips
|
|
54315
|
+
});
|
|
54316
|
+
}
|
|
54317
|
+
return out;
|
|
54318
|
+
};
|
|
54319
|
+
const offStream = streamBus.onAgentStream(onStream);
|
|
54320
|
+
const offDispatch = dispatchEvents.onDispatch(onDispatch);
|
|
54321
|
+
const offError = dispatchEvents.onDispatchError(onDispatchError);
|
|
54322
|
+
return {
|
|
54323
|
+
tracker: { residuals },
|
|
54324
|
+
off: () => {
|
|
54325
|
+
offStream();
|
|
54326
|
+
offDispatch();
|
|
54327
|
+
offError();
|
|
54328
|
+
}
|
|
54329
|
+
};
|
|
54330
|
+
}
|
|
54331
|
+
function toPartialCostEvent(residual, runId, projectKey) {
|
|
54332
|
+
const sessionRole = deriveSessionRole(residual.sessionName);
|
|
54333
|
+
return {
|
|
54334
|
+
ts: Date.now(),
|
|
54335
|
+
runId,
|
|
54336
|
+
...projectKey !== undefined ? { projectKey } : {},
|
|
54337
|
+
schemaVersion: COST_ROW_SCHEMA_VERSION,
|
|
54338
|
+
partial: true,
|
|
54339
|
+
agentName: residual.agentName,
|
|
54340
|
+
model: residual.model,
|
|
54341
|
+
...sessionRole !== undefined ? { sessionRole } : {},
|
|
54342
|
+
...residual.stage !== undefined ? { stage: residual.stage } : {},
|
|
54343
|
+
...residual.storyId !== undefined ? { storyId: residual.storyId } : {},
|
|
54344
|
+
...residual.scopeId !== undefined ? { scopeId: residual.scopeId } : {},
|
|
54345
|
+
callId: residual.streamCallId,
|
|
54346
|
+
tokens: { ...residual.tokens },
|
|
54347
|
+
roundTrips: residual.roundTrips,
|
|
54348
|
+
roundTripUnit: "model-call",
|
|
54349
|
+
costUsd: residual.costUsd,
|
|
54350
|
+
estimatedCostUsd: residual.costUsd,
|
|
54351
|
+
exactCostUsd: residual.costUsd,
|
|
54352
|
+
confidence: "estimated",
|
|
54353
|
+
durationMs: 0
|
|
54354
|
+
};
|
|
54355
|
+
}
|
|
54356
|
+
var init_in_flight_usage = __esm(() => {
|
|
54357
|
+
init_middleware();
|
|
54358
|
+
init_usage_auditor();
|
|
54359
|
+
});
|
|
54360
|
+
|
|
53910
54361
|
// src/runtime/packages.ts
|
|
53911
|
-
import { isAbsolute as isAbsolute15, join as
|
|
54362
|
+
import { isAbsolute as isAbsolute15, join as join46, relative as relative17 } from "path";
|
|
53912
54363
|
function packageWorkdir(view) {
|
|
53913
54364
|
const { packageDir, repoRoot } = view;
|
|
53914
54365
|
if (!packageDir)
|
|
53915
54366
|
return repoRoot;
|
|
53916
54367
|
if (!repoRoot || isAbsolute15(packageDir))
|
|
53917
54368
|
return packageDir;
|
|
53918
|
-
return
|
|
54369
|
+
return join46(repoRoot, packageDir);
|
|
53919
54370
|
}
|
|
53920
54371
|
function createPackageView(config2, packageDir, repoRoot, hasOverride, overlay) {
|
|
53921
54372
|
const memo = new Map;
|
|
@@ -54028,7 +54479,7 @@ function storyExecRoot(view) {
|
|
|
54028
54479
|
const segments = packageDir.split("/");
|
|
54029
54480
|
if (segments[0] !== ".nax-wt" || segments.length < 2)
|
|
54030
54481
|
return repoRoot;
|
|
54031
|
-
return
|
|
54482
|
+
return join46(repoRoot, segments[0], segments[1]);
|
|
54032
54483
|
}
|
|
54033
54484
|
var _packagesDeps;
|
|
54034
54485
|
var init_packages = __esm(() => {
|
|
@@ -54161,9 +54612,9 @@ var init_paths2 = __esm(() => {
|
|
|
54161
54612
|
});
|
|
54162
54613
|
|
|
54163
54614
|
// src/runtime/prompt-auditor.ts
|
|
54164
|
-
import { appendFileSync as
|
|
54165
|
-
import { mkdir as
|
|
54166
|
-
import { join as
|
|
54615
|
+
import { appendFileSync as appendFileSync3 } from "fs";
|
|
54616
|
+
import { mkdir as mkdir17 } from "fs/promises";
|
|
54617
|
+
import { join as join47 } from "path";
|
|
54167
54618
|
function createNoOpPromptAuditor() {
|
|
54168
54619
|
return {
|
|
54169
54620
|
record() {},
|
|
@@ -54243,8 +54694,8 @@ class PromptAuditor {
|
|
|
54243
54694
|
_featureDir;
|
|
54244
54695
|
_turnOrdinals = new Map;
|
|
54245
54696
|
constructor(runId, flushDir, featureName) {
|
|
54246
|
-
this._featureDir =
|
|
54247
|
-
this._jsonlPath =
|
|
54697
|
+
this._featureDir = join47(flushDir, featureName);
|
|
54698
|
+
this._jsonlPath = join47(this._featureDir, `${runId}.jsonl`);
|
|
54248
54699
|
}
|
|
54249
54700
|
record(entry) {
|
|
54250
54701
|
this._enqueue(entry.callType === "run" ? { ...entry, turn: this._nextTurn(entry) } : entry);
|
|
@@ -54282,7 +54733,7 @@ class PromptAuditor {
|
|
|
54282
54733
|
async _writeEntry(entry) {
|
|
54283
54734
|
if (!this._dirCreated) {
|
|
54284
54735
|
try {
|
|
54285
|
-
await
|
|
54736
|
+
await mkdir17(this._featureDir, { recursive: true });
|
|
54286
54737
|
} catch (err) {
|
|
54287
54738
|
throw tagAuditError(err, "jsonl");
|
|
54288
54739
|
}
|
|
@@ -54299,7 +54750,7 @@ class PromptAuditor {
|
|
|
54299
54750
|
return;
|
|
54300
54751
|
const filename = deriveTxtFilename(entry);
|
|
54301
54752
|
try {
|
|
54302
|
-
await _promptAuditorDeps.write(
|
|
54753
|
+
await _promptAuditorDeps.write(join47(this._featureDir, filename), buildTxtContent(safeEntry));
|
|
54303
54754
|
} catch (err) {
|
|
54304
54755
|
throw tagAuditError(err, "txt");
|
|
54305
54756
|
}
|
|
@@ -54313,80 +54764,10 @@ var init_prompt_auditor = __esm(() => {
|
|
|
54313
54764
|
init_logger2();
|
|
54314
54765
|
_promptAuditorDeps = {
|
|
54315
54766
|
write: (path7, data) => Bun.write(path7, data),
|
|
54316
|
-
appendLine: async (path7, data) => {
|
|
54317
|
-
appendFileSync2(path7, data, "utf8");
|
|
54318
|
-
}
|
|
54319
|
-
};
|
|
54320
|
-
});
|
|
54321
|
-
|
|
54322
|
-
// src/runtime/usage-auditor.ts
|
|
54323
|
-
import { appendFileSync as appendFileSync3 } from "fs";
|
|
54324
|
-
import { mkdir as mkdir17 } from "fs/promises";
|
|
54325
|
-
import { join as join47 } from "path";
|
|
54326
|
-
function createNoOpUsageAuditor() {
|
|
54327
|
-
return {
|
|
54328
|
-
record() {},
|
|
54329
|
-
async flush() {}
|
|
54330
|
-
};
|
|
54331
|
-
}
|
|
54332
|
-
function deriveSessionRole(sessionName) {
|
|
54333
|
-
for (const role of ROLES_BY_DESCENDING_LENGTH) {
|
|
54334
|
-
if (sessionName === role || sessionName.endsWith(`-${role}`))
|
|
54335
|
-
return role;
|
|
54336
|
-
}
|
|
54337
|
-
return;
|
|
54338
|
-
}
|
|
54339
|
-
|
|
54340
|
-
class UsageAuditor {
|
|
54341
|
-
_queue = Promise.resolve();
|
|
54342
|
-
_dirCreated = false;
|
|
54343
|
-
_dir;
|
|
54344
|
-
_jsonlPath;
|
|
54345
|
-
constructor(runId, dir) {
|
|
54346
|
-
this._dir = dir;
|
|
54347
|
-
this._jsonlPath = join47(dir, `${runId}.jsonl`);
|
|
54348
|
-
}
|
|
54349
|
-
record(entry) {
|
|
54350
|
-
this._queue = this._queue.then(() => this._writeEntry(entry)).catch((err) => {
|
|
54351
|
-
const sysErr = err;
|
|
54352
|
-
getSafeLogger()?.warn("audit", "usage-audit write failed", {
|
|
54353
|
-
path: this._jsonlPath,
|
|
54354
|
-
error: errorMessage(err),
|
|
54355
|
-
code: sysErr?.code,
|
|
54356
|
-
errno: sysErr?.errno,
|
|
54357
|
-
syscall: sysErr?.syscall,
|
|
54358
|
-
ts: entry.ts,
|
|
54359
|
-
storyId: entry.storyId,
|
|
54360
|
-
sessionName: entry.sessionName,
|
|
54361
|
-
agentName: entry.agentName,
|
|
54362
|
-
stage: entry.stage,
|
|
54363
|
-
streamCallId: entry.streamCallId
|
|
54364
|
-
});
|
|
54365
|
-
});
|
|
54366
|
-
}
|
|
54367
|
-
async _writeEntry(entry) {
|
|
54368
|
-
if (!this._dirCreated) {
|
|
54369
|
-
await mkdir17(this._dir, { recursive: true });
|
|
54370
|
-
this._dirCreated = true;
|
|
54371
|
-
}
|
|
54372
|
-
const row = { ...entry, sessionRole: deriveSessionRole(entry.sessionName) };
|
|
54373
|
-
await _usageAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(row)}
|
|
54374
|
-
`);
|
|
54375
|
-
}
|
|
54376
|
-
async flush() {
|
|
54377
|
-
await this._queue;
|
|
54378
|
-
}
|
|
54379
|
-
}
|
|
54380
|
-
var _usageAuditorDeps, ROLES_BY_DESCENDING_LENGTH;
|
|
54381
|
-
var init_usage_auditor = __esm(() => {
|
|
54382
|
-
init_logger2();
|
|
54383
|
-
init_session_role();
|
|
54384
|
-
_usageAuditorDeps = {
|
|
54385
54767
|
appendLine: async (path7, data) => {
|
|
54386
54768
|
appendFileSync3(path7, data, "utf8");
|
|
54387
54769
|
}
|
|
54388
54770
|
};
|
|
54389
|
-
ROLES_BY_DESCENDING_LENGTH = [...KNOWN_SESSION_ROLES].sort((a, b) => b.length - a.length);
|
|
54390
54771
|
});
|
|
54391
54772
|
|
|
54392
54773
|
// src/agents/factory.ts
|
|
@@ -66183,6 +66564,7 @@ __export(exports_runtime, {
|
|
|
66183
66564
|
_usageAuditorDeps: () => _usageAuditorDeps,
|
|
66184
66565
|
attachAgentIdleWatchdog: () => attachAgentIdleWatchdog,
|
|
66185
66566
|
attachAgentStreamLogging: () => attachAgentStreamLogging,
|
|
66567
|
+
attachInFlightUsageTracker: () => attachInFlightUsageTracker,
|
|
66186
66568
|
attachUsageAuditSubscriber: () => attachUsageAuditSubscriber,
|
|
66187
66569
|
claimProjectIdentity: () => claimProjectIdentity,
|
|
66188
66570
|
createNoOpCostAggregator: () => createNoOpCostAggregator,
|
|
@@ -66205,6 +66587,7 @@ __export(exports_runtime, {
|
|
|
66205
66587
|
spinTerminalNotice: () => spinTerminalNotice,
|
|
66206
66588
|
storyExecRoot: () => storyExecRoot,
|
|
66207
66589
|
storySpendUsd: () => storySpendUsd,
|
|
66590
|
+
toPartialCostEvent: () => toPartialCostEvent,
|
|
66208
66591
|
totalSpendUsd: () => totalSpendUsd,
|
|
66209
66592
|
writeProjectIdentity: () => writeProjectIdentity
|
|
66210
66593
|
});
|
|
@@ -66296,9 +66679,10 @@ function createRuntime(config2, workdir, opts) {
|
|
|
66296
66679
|
const offCost = attachCostSubscriber(dispatchEvents, costAggregator, runId, getProjectKey(config2, workdir));
|
|
66297
66680
|
const offAudit = attachAuditSubscriber(dispatchEvents, promptAuditor, runId);
|
|
66298
66681
|
const offReviewAudit = attachReviewAuditSubscriber(dispatchEvents, reviewAuditor, runId);
|
|
66299
|
-
const offUsageAudit = attachUsageAuditSubscriber(agentStreamEvents, usageAuditor, runId);
|
|
66682
|
+
const offUsageAudit = attachUsageAuditSubscriber(agentStreamEvents, dispatchEvents, usageAuditor, runId);
|
|
66300
66683
|
const offAgentStreamLogging = attachAgentStreamLogging(agentStreamEvents, runId);
|
|
66301
66684
|
const offWatchdog = attachAgentIdleWatchdog(agentStreamEvents, watchdogControllerRegistry, config2);
|
|
66685
|
+
const { tracker: inFlightTracker, off: offInFlightUsage } = attachInFlightUsageTracker(agentStreamEvents, dispatchEvents);
|
|
66302
66686
|
const packages = createPackageRegistry(configLoader, workdir);
|
|
66303
66687
|
const logger = getLogger();
|
|
66304
66688
|
const quarantineMemo = createQuarantineMemo();
|
|
@@ -66366,6 +66750,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
66366
66750
|
offUsageAudit();
|
|
66367
66751
|
offAgentStreamLogging();
|
|
66368
66752
|
offWatchdog();
|
|
66753
|
+
offInFlightUsage();
|
|
66369
66754
|
if (opts?.parentSignal && parentAbortHandler) {
|
|
66370
66755
|
opts.parentSignal.removeEventListener("abort", parentAbortHandler);
|
|
66371
66756
|
}
|
|
@@ -66375,6 +66760,10 @@ function createRuntime(config2, workdir, opts) {
|
|
|
66375
66760
|
sessionManager.close();
|
|
66376
66761
|
await mcpPool.close();
|
|
66377
66762
|
await writeMcpRollup(outputDir, buildMcpRollup({ runId, events: mcpPool.events(), withheld: mcpWithheld })).catch((error48) => logger.warn("runtime", "mcp rollup write failed", { error: String(error48) }));
|
|
66763
|
+
for (const residual of inFlightTracker.residuals()) {
|
|
66764
|
+
costAggregator.record(toPartialCostEvent(residual, runId, projectKey));
|
|
66765
|
+
}
|
|
66766
|
+
await flushOpenToolAuditSinks(runId);
|
|
66378
66767
|
const results = await Promise.allSettled([
|
|
66379
66768
|
promptAuditor.flush(),
|
|
66380
66769
|
usageAuditor.flush(),
|
|
@@ -66394,6 +66783,7 @@ var init_runtime2 = __esm(() => {
|
|
|
66394
66783
|
init_agent_stream_events();
|
|
66395
66784
|
init_cost_aggregator();
|
|
66396
66785
|
init_dispatch_events();
|
|
66786
|
+
init_in_flight_usage();
|
|
66397
66787
|
init_middleware();
|
|
66398
66788
|
init_packages();
|
|
66399
66789
|
init_paths2();
|
|
@@ -66402,6 +66792,7 @@ var init_runtime2 = __esm(() => {
|
|
|
66402
66792
|
init_session_role();
|
|
66403
66793
|
init_spin_breaker();
|
|
66404
66794
|
init_usage_auditor();
|
|
66795
|
+
init_tools();
|
|
66405
66796
|
init_factory();
|
|
66406
66797
|
init_manager2();
|
|
66407
66798
|
init_config();
|
|
@@ -66415,6 +66806,7 @@ var init_runtime2 = __esm(() => {
|
|
|
66415
66806
|
init_agent_stream_events();
|
|
66416
66807
|
init_cost_aggregator();
|
|
66417
66808
|
init_dispatch_events();
|
|
66809
|
+
init_in_flight_usage();
|
|
66418
66810
|
init_middleware();
|
|
66419
66811
|
init_packages();
|
|
66420
66812
|
init_paths2();
|
|
@@ -66826,7 +67218,6 @@ var init_feature_context_filter = __esm(() => {
|
|
|
66826
67218
|
implementer: ["all", "implementer"],
|
|
66827
67219
|
"test-writer": ["all", "test-writer"],
|
|
66828
67220
|
verifier: ["all", "verifier"],
|
|
66829
|
-
"single-session": ["all", "implementer", "test-writer"],
|
|
66830
67221
|
"tdd-simple": ["all", "implementer", "test-writer"],
|
|
66831
67222
|
"no-test": ["all", "implementer"],
|
|
66832
67223
|
batch: ["all", "implementer", "test-writer"]
|
|
@@ -68073,8 +68464,7 @@ var init_acceptance_diagnose = __esm(() => {
|
|
|
68073
68464
|
testOutput: input.testOutput,
|
|
68074
68465
|
testFileContent: input.testFileContent,
|
|
68075
68466
|
acceptanceTestPath: input.acceptanceTestPath,
|
|
68076
|
-
sourceFiles: input.sourceFiles
|
|
68077
|
-
semanticVerdicts: input.semanticVerdicts
|
|
68467
|
+
sourceFiles: input.sourceFiles
|
|
68078
68468
|
});
|
|
68079
68469
|
return {
|
|
68080
68470
|
role: { id: "role", content: "", overridable: false },
|
|
@@ -72703,8 +73093,8 @@ async function getChangedFiles(workdir, fromRef = "HEAD") {
|
|
|
72703
73093
|
{ stdout: output, stderr, exitCode },
|
|
72704
73094
|
{ stdout: statusOutput, stderr: statusStderr, exitCode: statusExitCode }
|
|
72705
73095
|
] = await Promise.all([
|
|
72706
|
-
runGitBounded(["diff", "--name-only", fromRef], workdir),
|
|
72707
|
-
runGitBounded(["status", "--porcelain"], workdir)
|
|
73096
|
+
runGitBounded(["diff", "--name-only", "-z", fromRef], workdir),
|
|
73097
|
+
runGitBounded(["status", "--porcelain", "-z"], workdir)
|
|
72708
73098
|
]);
|
|
72709
73099
|
if (exitCode !== 0) {
|
|
72710
73100
|
throw new NaxError(`git diff --name-only ${fromRef} failed (exit ${exitCode}): ${stderr.trim()}`, "GIT_DIFF_FAILED", {
|
|
@@ -72720,10 +73110,8 @@ async function getChangedFiles(workdir, fromRef = "HEAD") {
|
|
|
72720
73110
|
exitCode: statusExitCode
|
|
72721
73111
|
});
|
|
72722
73112
|
}
|
|
72723
|
-
const diffFiles = output.
|
|
72724
|
-
|
|
72725
|
-
const untrackedFiles = statusOutput.split(`
|
|
72726
|
-
`).filter((line) => line.startsWith("??")).map((line) => line.slice(2).trim()).filter(Boolean);
|
|
73113
|
+
const diffFiles = output.split("\x00").filter(Boolean);
|
|
73114
|
+
const untrackedFiles = statusOutput.split("\x00").filter((line) => line.startsWith("??")).map((line) => line.slice(3)).filter(Boolean);
|
|
72727
73115
|
return [...new Set([...diffFiles, ...untrackedFiles])];
|
|
72728
73116
|
}
|
|
72729
73117
|
async function getAddedLinesPerFile(workdir, fromRef = "HEAD") {
|
|
@@ -76079,20 +76467,7 @@ var init_write_test = __esm(() => {
|
|
|
76079
76467
|
stage: "run",
|
|
76080
76468
|
session: { role: "test-writer", lifetime: "warm" },
|
|
76081
76469
|
config: tddConfigSelector,
|
|
76082
|
-
tools: [
|
|
76083
|
-
"Read",
|
|
76084
|
-
"Glob",
|
|
76085
|
-
"Grep",
|
|
76086
|
-
"Write",
|
|
76087
|
-
"Edit",
|
|
76088
|
-
"Delete",
|
|
76089
|
-
"Git",
|
|
76090
|
-
"RunCommand",
|
|
76091
|
-
"GitCommit",
|
|
76092
|
-
"Exec",
|
|
76093
|
-
"Bash",
|
|
76094
|
-
"RequestCapability"
|
|
76095
|
-
],
|
|
76470
|
+
tools: ["Read", "Glob", "Grep", "Write", "Edit", "Delete", "Git", "RunCommand", "Exec", "Bash", "RequestCapability"],
|
|
76096
76471
|
model: (_input, ctx) => ctx.config.tdd?.sessionTiers?.testWriter,
|
|
76097
76472
|
keepOpen: (_input, ctx) => shouldKeepSessionOpen(ctx.config, "test-writer"),
|
|
76098
76473
|
build(input, _ctx) {
|
|
@@ -79097,7 +79472,7 @@ var init_config_descriptions = __esm(() => {
|
|
|
79097
79472
|
"routing.llm.mode": "Routing mode: one-shot | per-story | hybrid",
|
|
79098
79473
|
"routing.llm.timeoutMs": "Timeout for LLM routing call in milliseconds",
|
|
79099
79474
|
execution: "Execution limits and timeouts",
|
|
79100
|
-
"execution.maxIterations": "Max iterations per feature run
|
|
79475
|
+
"execution.maxIterations": "Max iterations per feature run \u2014 each story attempt, parallel batch and the final completion pass counts one",
|
|
79101
79476
|
"execution.iterationDelayMs": "Delay between iterations in milliseconds",
|
|
79102
79477
|
"execution.costLimit": "Max cost in USD before pausing execution (override per run with `nax run --max-cost`)",
|
|
79103
79478
|
"execution.sessionTimeoutSeconds": "Timeout per agent coding session in seconds",
|
|
@@ -79176,6 +79551,7 @@ var init_config_descriptions = __esm(() => {
|
|
|
79176
79551
|
"tdd.sessionTiers.verifier": "Model tier for verifier session",
|
|
79177
79552
|
"tdd.verifierTimeoutSeconds": "Wall-clock budget for one verifier turn in seconds (default: 1800). Its own knob, not execution.sessionTimeoutSeconds",
|
|
79178
79553
|
"tdd.testWriterAllowedPaths": "Glob patterns for files test-writer can modify",
|
|
79554
|
+
"tdd.testWriterCommitHooks": 'Git hooks on the RED commit nax makes after the test-writer phase: "skip" (default, --no-verify) | "run"',
|
|
79179
79555
|
"tdd.rollbackOnFailure": "Rollback git changes when TDD fails",
|
|
79180
79556
|
"tdd.greenfieldDetection": "Force tdd-simple on projects with no test files",
|
|
79181
79557
|
constitution: "Constitution settings (core rules and constraints)",
|
|
@@ -79253,7 +79629,6 @@ var init_config_descriptions = __esm(() => {
|
|
|
79253
79629
|
"prompts.overrides.test-writer": 'Path to custom test-writer prompt (e.g., ".nax/prompts/test-writer.md")',
|
|
79254
79630
|
"prompts.overrides.implementer": 'Path to custom implementer prompt (e.g., ".nax/prompts/implementer.md")',
|
|
79255
79631
|
"prompts.overrides.verifier": 'Path to custom verifier prompt (e.g., ".nax/prompts/verifier.md")',
|
|
79256
|
-
"prompts.overrides.single-session": 'Path to custom single-session prompt (e.g., ".nax/prompts/single-session.md")',
|
|
79257
79632
|
agent: "Agent protocol configuration (ACP-003)",
|
|
79258
79633
|
"agent.protocol": "Protocol for agent communication: 'acp' (default) | 'native' | 'hybrid'",
|
|
79259
79634
|
"agent.native.catalogOverrides": "Native only. Explicit catalog entries for model ids newer than the bundled pi-ai snapshot, plus optional provider-wide baseUrl/headers.",
|
|
@@ -79556,7 +79931,7 @@ function displayConfigWithDescriptions(obj, path10, sources, indent = 0) {
|
|
|
79556
79931
|
if (description) {
|
|
79557
79932
|
console.log(`${indentStr}# prompts.overrides: ${description}`);
|
|
79558
79933
|
}
|
|
79559
|
-
const roles = ["test-writer", "implementer", "verifier"
|
|
79934
|
+
const roles = ["test-writer", "implementer", "verifier"];
|
|
79560
79935
|
console.log(`${indentStr}overrides:`);
|
|
79561
79936
|
for (const role of roles) {
|
|
79562
79937
|
const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
|
|
@@ -79601,7 +79976,7 @@ function displayConfigWithDescriptions(obj, path10, sources, indent = 0) {
|
|
|
79601
79976
|
if (description) {
|
|
79602
79977
|
console.log(`# prompts.overrides: ${description}`);
|
|
79603
79978
|
}
|
|
79604
|
-
const roles = ["test-writer", "implementer", "verifier"
|
|
79979
|
+
const roles = ["test-writer", "implementer", "verifier"];
|
|
79605
79980
|
console.log("overrides:");
|
|
79606
79981
|
for (const role of roles) {
|
|
79607
79982
|
const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
|
|
@@ -80630,75 +81005,6 @@ var init_hardening = __esm(() => {
|
|
|
80630
81005
|
};
|
|
80631
81006
|
});
|
|
80632
81007
|
|
|
80633
|
-
// src/acceptance/semantic-verdict.ts
|
|
80634
|
-
import path11 from "path";
|
|
80635
|
-
async function persistSemanticVerdict(featureDir2, storyId, verdict) {
|
|
80636
|
-
const dir = path11.join(featureDir2, "semantic-verdicts");
|
|
80637
|
-
await _semanticVerdictDeps.mkdirp(dir);
|
|
80638
|
-
const filePath = path11.join(dir, `${storyId}.json`);
|
|
80639
|
-
await _semanticVerdictDeps.writeFile(filePath, JSON.stringify(verdict, null, 2));
|
|
80640
|
-
}
|
|
80641
|
-
function migrateSemanticVerdict(verdict) {
|
|
80642
|
-
if (!verdict.findings?.length)
|
|
80643
|
-
return verdict;
|
|
80644
|
-
const first = verdict.findings[0];
|
|
80645
|
-
if ("source" in first)
|
|
80646
|
-
return verdict;
|
|
80647
|
-
return {
|
|
80648
|
-
...verdict,
|
|
80649
|
-
findings: verdict.findings.map((f) => reviewFindingToFinding(f))
|
|
80650
|
-
};
|
|
80651
|
-
}
|
|
80652
|
-
async function loadSemanticVerdicts(featureDir2) {
|
|
80653
|
-
const dir = path11.join(featureDir2, "semantic-verdicts");
|
|
80654
|
-
let files;
|
|
80655
|
-
try {
|
|
80656
|
-
files = await _semanticVerdictDeps.readdir(dir);
|
|
80657
|
-
} catch (err) {
|
|
80658
|
-
if (err.code === "ENOENT")
|
|
80659
|
-
return [];
|
|
80660
|
-
throw err;
|
|
80661
|
-
}
|
|
80662
|
-
const results = [];
|
|
80663
|
-
for (const file2 of files) {
|
|
80664
|
-
if (!file2.endsWith(".json"))
|
|
80665
|
-
continue;
|
|
80666
|
-
const filePath = path11.join(dir, file2);
|
|
80667
|
-
const content = await _semanticVerdictDeps.readFile(filePath);
|
|
80668
|
-
try {
|
|
80669
|
-
const parsed = JSON.parse(content);
|
|
80670
|
-
results.push(migrateSemanticVerdict(parsed));
|
|
80671
|
-
} catch {
|
|
80672
|
-
_semanticVerdictDeps.logDebug(`Skipping invalid JSON in semantic-verdicts/${file2}`);
|
|
80673
|
-
}
|
|
80674
|
-
}
|
|
80675
|
-
return results;
|
|
80676
|
-
}
|
|
80677
|
-
var _semanticVerdictDeps;
|
|
80678
|
-
var init_semantic_verdict = __esm(() => {
|
|
80679
|
-
init_findings();
|
|
80680
|
-
init_logger2();
|
|
80681
|
-
_semanticVerdictDeps = {
|
|
80682
|
-
mkdirp: async (dir) => {
|
|
80683
|
-
const { mkdir: mkdir21 } = await import("fs/promises");
|
|
80684
|
-
await mkdir21(dir, { recursive: true });
|
|
80685
|
-
},
|
|
80686
|
-
writeFile: async (filePath, content) => {
|
|
80687
|
-
await Bun.write(filePath, content);
|
|
80688
|
-
},
|
|
80689
|
-
readdir: async (dir) => {
|
|
80690
|
-
const { readdir: readdir7 } = await import("fs/promises");
|
|
80691
|
-
return readdir7(dir);
|
|
80692
|
-
},
|
|
80693
|
-
readFile: async (filePath) => {
|
|
80694
|
-
return Bun.file(filePath).text();
|
|
80695
|
-
},
|
|
80696
|
-
logDebug: (msg) => {
|
|
80697
|
-
getLogger()?.debug("semantic-verdict", msg);
|
|
80698
|
-
}
|
|
80699
|
-
};
|
|
80700
|
-
});
|
|
80701
|
-
|
|
80702
81008
|
// src/acceptance/index.ts
|
|
80703
81009
|
var exports_acceptance = {};
|
|
80704
81010
|
__export(exports_acceptance, {
|
|
@@ -80711,11 +81017,9 @@ __export(exports_acceptance, {
|
|
|
80711
81017
|
groupStoriesByPackage: () => groupStoriesByPackage,
|
|
80712
81018
|
isStubTestContent: () => isStubTestContent,
|
|
80713
81019
|
loadAcceptanceTestContent: () => loadAcceptanceTestContent,
|
|
80714
|
-
loadSemanticVerdicts: () => loadSemanticVerdicts,
|
|
80715
81020
|
loadSourceFilesForDiagnosis: () => loadSourceFilesForDiagnosis,
|
|
80716
81021
|
parseAcceptanceCriteria: () => parseAcceptanceCriteria,
|
|
80717
81022
|
parseRefinementResponse: () => parseRefinementResponse,
|
|
80718
|
-
persistSemanticVerdict: () => persistSemanticVerdict,
|
|
80719
81023
|
refinementWouldFallback: () => refinementWouldFallback,
|
|
80720
81024
|
resolveAcceptanceFeatureTestPath: () => resolveAcceptanceFeatureTestPath,
|
|
80721
81025
|
resolveSuggestedPackageFeatureTestPath: () => resolveSuggestedPackageFeatureTestPath,
|
|
@@ -80729,7 +81033,6 @@ var init_acceptance2 = __esm(() => {
|
|
|
80729
81033
|
init_generator();
|
|
80730
81034
|
init_hardening();
|
|
80731
81035
|
init_refinement();
|
|
80732
|
-
init_semantic_verdict();
|
|
80733
81036
|
init_test_path();
|
|
80734
81037
|
});
|
|
80735
81038
|
|
|
@@ -81725,11 +82028,11 @@ var init_generate = __esm(() => {
|
|
|
81725
82028
|
// src/cli/init-context.ts
|
|
81726
82029
|
import { mkdir as mkdir21, readdir as readdir7 } from "fs/promises";
|
|
81727
82030
|
import { basename as basename11, join as join74, relative as relative23, sep as sep12 } from "path";
|
|
81728
|
-
async function bunFileExists(
|
|
81729
|
-
return Bun.file(
|
|
82031
|
+
async function bunFileExists(path11) {
|
|
82032
|
+
return Bun.file(path11).exists();
|
|
81730
82033
|
}
|
|
81731
|
-
async function mkdirp(
|
|
81732
|
-
await mkdir21(
|
|
82034
|
+
async function mkdirp(path11) {
|
|
82035
|
+
await mkdir21(path11, { recursive: true });
|
|
81733
82036
|
}
|
|
81734
82037
|
async function findFiles(dir, maxFiles = 200) {
|
|
81735
82038
|
const files = [];
|
|
@@ -81792,8 +82095,8 @@ async function detectEntryPoints(projectRoot) {
|
|
|
81792
82095
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
81793
82096
|
const found = [];
|
|
81794
82097
|
for (const candidate of candidates) {
|
|
81795
|
-
const
|
|
81796
|
-
if (await bunFileExists(
|
|
82098
|
+
const path11 = join74(projectRoot, candidate);
|
|
82099
|
+
if (await bunFileExists(path11)) {
|
|
81797
82100
|
found.push(candidate);
|
|
81798
82101
|
}
|
|
81799
82102
|
}
|
|
@@ -81803,8 +82106,8 @@ async function detectConfigFiles(projectRoot) {
|
|
|
81803
82106
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
81804
82107
|
const found = [];
|
|
81805
82108
|
for (const candidate of candidates) {
|
|
81806
|
-
const
|
|
81807
|
-
if (await bunFileExists(
|
|
82109
|
+
const path11 = join74(projectRoot, candidate);
|
|
82110
|
+
if (await bunFileExists(path11)) {
|
|
81808
82111
|
found.push(candidate);
|
|
81809
82112
|
}
|
|
81810
82113
|
}
|
|
@@ -82633,9 +82936,9 @@ var init_scanner = __esm(() => {
|
|
|
82633
82936
|
_scannerDeps = {
|
|
82634
82937
|
discoverWorkspacePackages: (workdir) => discoverWorkspacePackages2(workdir),
|
|
82635
82938
|
detectLanguage: (pkgDir) => detectLanguage(pkgDir),
|
|
82636
|
-
readPackageJson: async (
|
|
82939
|
+
readPackageJson: async (path11) => {
|
|
82637
82940
|
try {
|
|
82638
|
-
const file2 = Bun.file(
|
|
82941
|
+
const file2 = Bun.file(path11);
|
|
82639
82942
|
if (!await file2.exists())
|
|
82640
82943
|
return null;
|
|
82641
82944
|
return await file2.json();
|
|
@@ -82656,6 +82959,7 @@ var init_analyze = __esm(() => {
|
|
|
82656
82959
|
function createHumanAskLink(opts) {
|
|
82657
82960
|
let queue = Promise.resolve();
|
|
82658
82961
|
let activeId;
|
|
82962
|
+
const canRemember = (req) => opts.onRemember !== undefined && req.command !== undefined;
|
|
82659
82963
|
const deny3 = (decidedBy) => ({
|
|
82660
82964
|
decision: "deny",
|
|
82661
82965
|
decidedBy
|
|
@@ -82793,7 +83097,7 @@ function createHumanAskLink(opts) {
|
|
|
82793
83097
|
`stage: ${req.stage}`
|
|
82794
83098
|
].join(`
|
|
82795
83099
|
`),
|
|
82796
|
-
options:
|
|
83100
|
+
options: canRemember(req) ? [ALLOW_ONCE, ALLOW_REMEMBER, DENY] : [ALLOW_ONCE, DENY],
|
|
82797
83101
|
timeout: opts.timeoutMs,
|
|
82798
83102
|
fallback: "abort",
|
|
82799
83103
|
createdAt: Date.now(),
|
|
@@ -82811,7 +83115,7 @@ function createHumanAskLink(opts) {
|
|
|
82811
83115
|
if (!PERMITS.has(action)) {
|
|
82812
83116
|
outcome = deny3("human");
|
|
82813
83117
|
} else {
|
|
82814
|
-
if (action === "allow-remember" && opts.onRemember) {
|
|
83118
|
+
if (action === "allow-remember" && canRemember(req) && opts.onRemember) {
|
|
82815
83119
|
try {
|
|
82816
83120
|
await opts.onRemember(req);
|
|
82817
83121
|
} catch (err) {
|
|
@@ -82937,7 +83241,7 @@ function createHumanAskLink(opts) {
|
|
|
82937
83241
|
async function cancelPendingAsk(link) {
|
|
82938
83242
|
await link.cancel();
|
|
82939
83243
|
}
|
|
82940
|
-
var MAX_COMMAND_CHARS = 3500, maskedFooter = (count) => `${count} secret value(s) masked; the approved command contains them`, _askLinkDeps,
|
|
83244
|
+
var MAX_COMMAND_CHARS = 3500, maskedFooter = (count) => `${count} secret value(s) masked; the approved command contains them`, _askLinkDeps, ALLOW_ONCE, ALLOW_REMEMBER, DENY, PERMITS;
|
|
82941
83245
|
var init_ask_link = __esm(() => {
|
|
82942
83246
|
init_permissions();
|
|
82943
83247
|
init_logger2();
|
|
@@ -82946,11 +83250,9 @@ var init_ask_link = __esm(() => {
|
|
|
82946
83250
|
clearTimeout: (id) => clearTimeout(id),
|
|
82947
83251
|
ASK_KEEPALIVE_MS: 60000
|
|
82948
83252
|
};
|
|
82949
|
-
|
|
82950
|
-
|
|
82951
|
-
|
|
82952
|
-
{ key: "deny", label: "Deny" }
|
|
82953
|
-
];
|
|
83253
|
+
ALLOW_ONCE = { key: "allow", label: "Allow once" };
|
|
83254
|
+
ALLOW_REMEMBER = { key: "allow-remember", label: "Allow + remember" };
|
|
83255
|
+
DENY = { key: "deny", label: "Deny" };
|
|
82954
83256
|
PERMITS = new Set(["allow", "allow-remember"]);
|
|
82955
83257
|
});
|
|
82956
83258
|
|
|
@@ -83124,10 +83426,10 @@ async function buildDispatchAskWiring(opts, deps = _dispatchAskDeps) {
|
|
|
83124
83426
|
stage: req.stage,
|
|
83125
83427
|
command: req.command ?? "",
|
|
83126
83428
|
root: req.root ?? opts.repoRoot,
|
|
83127
|
-
origin: "escalate",
|
|
83128
|
-
matchedRule: null,
|
|
83429
|
+
origin: req.matchedRule !== undefined ? "askRule" : "escalate",
|
|
83430
|
+
matchedRule: req.matchedRule ?? null,
|
|
83129
83431
|
approvedAt: new Date().toISOString(),
|
|
83130
|
-
approvedBy: "
|
|
83432
|
+
approvedBy: opts.config.interaction?.plugin ?? "unknown",
|
|
83131
83433
|
naxCommit: NAX_COMMIT
|
|
83132
83434
|
})
|
|
83133
83435
|
});
|
|
@@ -83205,6 +83507,25 @@ async function collectEffectiveRunStageModes(opts, deps = _dispatchAskDeps) {
|
|
|
83205
83507
|
})));
|
|
83206
83508
|
return collectRunStageModes([opts.rootConfig, ...opts.extraConfigs ?? [], ...packageConfigs]);
|
|
83207
83509
|
}
|
|
83510
|
+
async function buildApprovalsSeal(opts, deps = _dispatchAskDeps) {
|
|
83511
|
+
const stageModes = await collectEffectiveRunStageModes({ projectDir: opts.projectDir, rootConfig: opts.rootConfig, packageDirs: opts.packageDirs }, deps);
|
|
83512
|
+
const forgeCapable = isForgeCapable(stageModes, opts.rootConfig.execution?.sandbox?.enabled === true);
|
|
83513
|
+
if (!forgeCapable)
|
|
83514
|
+
return async () => {};
|
|
83515
|
+
const approvalsFile = approvalsPath(opts.outputDir);
|
|
83516
|
+
const runId = opts.runId;
|
|
83517
|
+
return async () => {
|
|
83518
|
+
try {
|
|
83519
|
+
await deps.prepareApprovalsStore({ approvalsFile, runId, forgeCapable: true });
|
|
83520
|
+
} catch (error48) {
|
|
83521
|
+
getSafeLogger()?.warn("permissions", "[approvals] could not update the store's taint marker", {
|
|
83522
|
+
approvalsFile,
|
|
83523
|
+
forgeCapable: true,
|
|
83524
|
+
error: error48
|
|
83525
|
+
});
|
|
83526
|
+
}
|
|
83527
|
+
};
|
|
83528
|
+
}
|
|
83208
83529
|
async function buildRunDispatchAskWiring(opts, deps = _dispatchAskDeps) {
|
|
83209
83530
|
const stageModes = await collectEffectiveRunStageModes({
|
|
83210
83531
|
projectDir: opts.projectDir,
|
|
@@ -83218,6 +83539,7 @@ var DEFAULT_APPROVAL_TIMEOUT_MS = 600000, APPROVAL_AUDIT_DIR = "approval-audit",
|
|
|
83218
83539
|
var init_dispatch_ask = __esm(() => {
|
|
83219
83540
|
init_command_safety();
|
|
83220
83541
|
init_config();
|
|
83542
|
+
init_logger2();
|
|
83221
83543
|
init_permissions();
|
|
83222
83544
|
init_version();
|
|
83223
83545
|
init_ask_link();
|
|
@@ -86076,12 +86398,108 @@ var init_cleanup = __esm(() => {
|
|
|
86076
86398
|
};
|
|
86077
86399
|
});
|
|
86078
86400
|
|
|
86401
|
+
// src/tdd/red-commit.ts
|
|
86402
|
+
function redCommitMessage(storyId) {
|
|
86403
|
+
return `chore(${storyId}): auto-commit after test-writer session (RED)`;
|
|
86404
|
+
}
|
|
86405
|
+
async function commitRedState(opts, deps = _redCommitDeps) {
|
|
86406
|
+
if (opts.dryRun)
|
|
86407
|
+
return { status: "skipped", reason: "dry-run" };
|
|
86408
|
+
try {
|
|
86409
|
+
return await commitFromRoot(opts, deps);
|
|
86410
|
+
} catch (err) {
|
|
86411
|
+
return { status: "failed", reason: errorMessage(err) };
|
|
86412
|
+
}
|
|
86413
|
+
}
|
|
86414
|
+
async function commitFromRoot(opts, deps) {
|
|
86415
|
+
const top = await deps.git(["rev-parse", "--show-toplevel"], opts.workdir, RED_COMMIT_GIT_TIMEOUT_MS);
|
|
86416
|
+
if (top.exitCode !== 0)
|
|
86417
|
+
return { status: "failed", reason: `git rev-parse failed: ${top.stderr.trim()}` };
|
|
86418
|
+
const gitRoot = top.stdout.trim();
|
|
86419
|
+
if (isBlocked2(gitRoot, opts))
|
|
86420
|
+
return { status: "skipped", reason: "blocked-worktree" };
|
|
86421
|
+
const changed = await deps.getChangedFiles(opts.workdir, opts.beforeRef);
|
|
86422
|
+
const files = await expandUntrackedDirs(gitRoot, changed, deps);
|
|
86423
|
+
const { kept } = await deps.partitionNaxOwnedPaths(gitRoot, files);
|
|
86424
|
+
if (kept.length === 0)
|
|
86425
|
+
return NOTHING;
|
|
86426
|
+
const addOpts = { pathspecs: kept, timeoutMs: RED_COMMIT_GIT_TIMEOUT_MS };
|
|
86427
|
+
const added = await gitlinkSafeAdd(deps.git, gitRoot, addOpts);
|
|
86428
|
+
if (added.exitCode !== 0)
|
|
86429
|
+
return { status: "failed", reason: `git add failed: ${added.stderr.trim()}` };
|
|
86430
|
+
const staged = await deps.git(["diff", "--cached", "--quiet", "--", ...kept], gitRoot, RED_COMMIT_GIT_TIMEOUT_MS);
|
|
86431
|
+
if (staged.timedOut || staged.exitCode !== 0 && staged.exitCode !== 1) {
|
|
86432
|
+
return { status: "failed", reason: `git diff --cached failed: ${staged.stderr.trim()}` };
|
|
86433
|
+
}
|
|
86434
|
+
if (staged.exitCode === 0)
|
|
86435
|
+
return NOTHING;
|
|
86436
|
+
const noVerify = opts.hooks === "skip" ? ["--no-verify"] : [];
|
|
86437
|
+
const argv = ["commit", "--only", "-m", redCommitMessage(opts.storyId), ...noVerify, "--", ...kept];
|
|
86438
|
+
const committed = await deps.git(argv, gitRoot, RED_COMMIT_GIT_TIMEOUT_MS);
|
|
86439
|
+
if (committed.exitCode !== 0) {
|
|
86440
|
+
const detail = committed.stderr.trim() || `exit ${committed.exitCode}`;
|
|
86441
|
+
return { status: "failed", reason: `git commit failed: ${detail}` };
|
|
86442
|
+
}
|
|
86443
|
+
return { status: "committed", files: kept, hooksSkipped: opts.hooks === "skip" };
|
|
86444
|
+
}
|
|
86445
|
+
function isBlocked2(gitRoot, opts) {
|
|
86446
|
+
if (!opts.blockedWorktrees?.size)
|
|
86447
|
+
return false;
|
|
86448
|
+
const root = realOrRaw(gitRoot);
|
|
86449
|
+
const blocked = [...opts.blockedWorktrees].filter((tree) => realOrRaw(tree) === root);
|
|
86450
|
+
if (blocked.length === 0)
|
|
86451
|
+
return false;
|
|
86452
|
+
const message = "Refusing to commit the RED state \u2014 working tree may still hold an unreverted mutation";
|
|
86453
|
+
getSafeLogger()?.error("tdd", message, {
|
|
86454
|
+
storyId: opts.storyId,
|
|
86455
|
+
workdir: opts.workdir,
|
|
86456
|
+
blocked,
|
|
86457
|
+
hint: "Check the mutation-check log for the file and line, restore it, then commit manually."
|
|
86458
|
+
});
|
|
86459
|
+
return true;
|
|
86460
|
+
}
|
|
86461
|
+
async function expandUntrackedDirs(gitRoot, paths, deps) {
|
|
86462
|
+
const out = [];
|
|
86463
|
+
for (const path11 of paths) {
|
|
86464
|
+
if (!path11.endsWith("/")) {
|
|
86465
|
+
out.push(path11);
|
|
86466
|
+
continue;
|
|
86467
|
+
}
|
|
86468
|
+
const args = ["ls-files", "--others", "--exclude-standard", "-z", "--", path11];
|
|
86469
|
+
const listed = await deps.git(args, gitRoot, RED_COMMIT_GIT_TIMEOUT_MS);
|
|
86470
|
+
if (listed.exitCode !== 0) {
|
|
86471
|
+
throw new NaxError(`git ls-files failed: ${listed.stderr.trim()}`, "GIT_LS_FILES_FAILED", {
|
|
86472
|
+
stage: "tdd-red-commit",
|
|
86473
|
+
path: path11
|
|
86474
|
+
});
|
|
86475
|
+
}
|
|
86476
|
+
out.push(...listed.stdout.split("\x00").filter(Boolean));
|
|
86477
|
+
}
|
|
86478
|
+
return out;
|
|
86479
|
+
}
|
|
86480
|
+
var RED_COMMIT_GIT_TIMEOUT_MS = 30000, _redCommitDeps, NOTHING;
|
|
86481
|
+
var init_red_commit = __esm(() => {
|
|
86482
|
+
init_errors();
|
|
86483
|
+
init_logger2();
|
|
86484
|
+
init_tools();
|
|
86485
|
+
init_git();
|
|
86486
|
+
init_realpath();
|
|
86487
|
+
init_isolation2();
|
|
86488
|
+
_redCommitDeps = {
|
|
86489
|
+
git: (args, cwd, timeoutMs) => gitWithTimeout(args, cwd, timeoutMs),
|
|
86490
|
+
getChangedFiles,
|
|
86491
|
+
partitionNaxOwnedPaths
|
|
86492
|
+
};
|
|
86493
|
+
NOTHING = { status: "skipped", reason: "nothing-to-commit" };
|
|
86494
|
+
});
|
|
86495
|
+
|
|
86079
86496
|
// src/tdd/index.ts
|
|
86080
86497
|
var init_tdd = __esm(() => {
|
|
86081
86498
|
init_operations();
|
|
86082
86499
|
init_test_runners();
|
|
86083
86500
|
init_cleanup();
|
|
86084
86501
|
init_isolation2();
|
|
86502
|
+
init_red_commit();
|
|
86085
86503
|
init_rollback();
|
|
86086
86504
|
init_verdict();
|
|
86087
86505
|
});
|
|
@@ -86805,6 +87223,9 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
86805
87223
|
}
|
|
86806
87224
|
}
|
|
86807
87225
|
}
|
|
87226
|
+
if (isTddPhase && opName === "test-writer" && !inRectification && beforeRef && outcome === "passed") {
|
|
87227
|
+
await commitTestWriterRedState(ctx, beforeRef);
|
|
87228
|
+
}
|
|
86808
87229
|
return output;
|
|
86809
87230
|
} catch (err) {
|
|
86810
87231
|
const noDispatch = toNoDispatchCheckResult(opName, err, Date.now() - phaseStartedAt);
|
|
@@ -86905,6 +87326,32 @@ function derivePhaseOutcome(output) {
|
|
|
86905
87326
|
return "skipped";
|
|
86906
87327
|
return "failed";
|
|
86907
87328
|
}
|
|
87329
|
+
async function commitTestWriterRedState(ctx, beforeRef) {
|
|
87330
|
+
const config2 = ctx.config ?? ctx.runtime.configLoader.current();
|
|
87331
|
+
const result = await _storyOrchestratorDeps.commitRedState({
|
|
87332
|
+
workdir: ctx.packageDir,
|
|
87333
|
+
beforeRef,
|
|
87334
|
+
storyId: ctx.storyId ?? "story",
|
|
87335
|
+
hooks: config2.tdd?.testWriterCommitHooks ?? "skip",
|
|
87336
|
+
dryRun: ctx.runtime.dryRun,
|
|
87337
|
+
blockedWorktrees: ctx.runtime.dirtyWorktrees
|
|
87338
|
+
});
|
|
87339
|
+
logRedCommit(ctx.storyId, result);
|
|
87340
|
+
}
|
|
87341
|
+
function logRedCommit(storyId, result) {
|
|
87342
|
+
const logger = getSafeLogger();
|
|
87343
|
+
if (result.status === "committed") {
|
|
87344
|
+
logger?.info("tdd", "RED state committed", {
|
|
87345
|
+
storyId,
|
|
87346
|
+
files: result.files.length,
|
|
87347
|
+
hooksSkipped: result.hooksSkipped
|
|
87348
|
+
});
|
|
87349
|
+
} else if (result.status === "skipped") {
|
|
87350
|
+
logger?.debug("tdd", "RED state commit skipped", { storyId, reason: result.reason });
|
|
87351
|
+
} else {
|
|
87352
|
+
logger?.warn("tdd", "RED state not committed", { storyId, reason: result.reason });
|
|
87353
|
+
}
|
|
87354
|
+
}
|
|
86908
87355
|
function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
|
|
86909
87356
|
if (!enabled)
|
|
86910
87357
|
return strategies;
|
|
@@ -86951,6 +87398,7 @@ var init_run_phase = __esm(() => {
|
|
|
86951
87398
|
callOp,
|
|
86952
87399
|
runFixCycle,
|
|
86953
87400
|
captureGitRef,
|
|
87401
|
+
commitRedState,
|
|
86954
87402
|
cleanupVerdict,
|
|
86955
87403
|
prepareSemanticReviewInput,
|
|
86956
87404
|
prepareAdversarialReviewInput,
|
|
@@ -88334,6 +88782,11 @@ async function performTeardown(ctx) {
|
|
|
88334
88782
|
if (ctx.pidRegistry) {
|
|
88335
88783
|
await ctx.pidRegistry.killAll();
|
|
88336
88784
|
}
|
|
88785
|
+
if (ctx.sealApprovals) {
|
|
88786
|
+
await ctx.sealApprovals().catch(() => {
|
|
88787
|
+
return;
|
|
88788
|
+
});
|
|
88789
|
+
}
|
|
88337
88790
|
}
|
|
88338
88791
|
function getSignalNumber(signal) {
|
|
88339
88792
|
const signalMap = {
|
|
@@ -88490,7 +88943,7 @@ var init_crash_recovery = __esm(() => {
|
|
|
88490
88943
|
});
|
|
88491
88944
|
|
|
88492
88945
|
// src/execution/ensure-package-dirs.ts
|
|
88493
|
-
import
|
|
88946
|
+
import path11 from "path";
|
|
88494
88947
|
async function ensureStoryPackageDirs(prd, workdir, deps = _ensurePackageDirsDeps) {
|
|
88495
88948
|
const logger = getSafeLogger();
|
|
88496
88949
|
const relToStoryId = new Map;
|
|
@@ -88503,8 +88956,8 @@ async function ensureStoryPackageDirs(prd, workdir, deps = _ensurePackageDirsDep
|
|
|
88503
88956
|
}
|
|
88504
88957
|
const created = [];
|
|
88505
88958
|
for (const [rel, storyId] of relToStoryId) {
|
|
88506
|
-
const abs =
|
|
88507
|
-
const rootWithSep = workdir.endsWith(
|
|
88959
|
+
const abs = path11.resolve(workdir, rel);
|
|
88960
|
+
const rootWithSep = workdir.endsWith(path11.sep) ? workdir : workdir + path11.sep;
|
|
88508
88961
|
if (abs !== workdir && !abs.startsWith(rootWithSep)) {
|
|
88509
88962
|
logger?.warn("execution", "Skipping story workdir outside repo root", {
|
|
88510
88963
|
storyId,
|
|
@@ -88655,8 +89108,8 @@ async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
|
|
|
88655
89108
|
return false;
|
|
88656
89109
|
const lines = content.split(`
|
|
88657
89110
|
`);
|
|
88658
|
-
const start = Math.max(0, triple.line - 1 -
|
|
88659
|
-
const end = Math.min(lines.length, triple.line +
|
|
89111
|
+
const start = Math.max(0, triple.line - 1 - CONTEXT_LINES2);
|
|
89112
|
+
const end = Math.min(lines.length, triple.line + CONTEXT_LINES2);
|
|
88660
89113
|
const window2 = lines.slice(start, end).join(`
|
|
88661
89114
|
`);
|
|
88662
89115
|
return normalizeWs2(window2).toLowerCase().includes(normalizeWs2(triple.quote).toLowerCase());
|
|
@@ -88681,14 +89134,14 @@ async function verifyEscalationQuotes(reason, workdir, storyId, deps = _quoteInt
|
|
|
88681
89134
|
}
|
|
88682
89135
|
return verified;
|
|
88683
89136
|
}
|
|
88684
|
-
var _quoteIntegrityDeps,
|
|
89137
|
+
var _quoteIntegrityDeps, CONTEXT_LINES2 = 3;
|
|
88685
89138
|
var init_quote_integrity = __esm(() => {
|
|
88686
89139
|
init_logger2();
|
|
88687
89140
|
init_path_security2();
|
|
88688
89141
|
_quoteIntegrityDeps = {
|
|
88689
|
-
readFile: async (
|
|
89142
|
+
readFile: async (path12) => {
|
|
88690
89143
|
try {
|
|
88691
|
-
return await Bun.file(
|
|
89144
|
+
return await Bun.file(path12).text();
|
|
88692
89145
|
} catch {
|
|
88693
89146
|
return null;
|
|
88694
89147
|
}
|
|
@@ -89128,7 +89581,7 @@ var init_escalation = __esm(() => {
|
|
|
89128
89581
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
89129
89582
|
import { rename as rename5, unlink as unlink5 } from "fs/promises";
|
|
89130
89583
|
import { hostname as hostname3 } from "os";
|
|
89131
|
-
import
|
|
89584
|
+
import path12 from "path";
|
|
89132
89585
|
function getSafeLogger3() {
|
|
89133
89586
|
try {
|
|
89134
89587
|
return getLogger();
|
|
@@ -89208,7 +89661,7 @@ async function claimReclaimableLock(lockPath, observedContent, lockData) {
|
|
|
89208
89661
|
return { action: "discard" };
|
|
89209
89662
|
}
|
|
89210
89663
|
async function acquireLock(workdir) {
|
|
89211
|
-
const lockPath =
|
|
89664
|
+
const lockPath = path12.join(workdir, "nax.lock");
|
|
89212
89665
|
const lockFile = Bun.file(lockPath);
|
|
89213
89666
|
try {
|
|
89214
89667
|
const exists2 = await lockFile.exists();
|
|
@@ -89261,7 +89714,7 @@ async function acquireLock(workdir) {
|
|
|
89261
89714
|
}
|
|
89262
89715
|
}
|
|
89263
89716
|
async function releaseLock(workdir) {
|
|
89264
|
-
const lockPath =
|
|
89717
|
+
const lockPath = path12.join(workdir, "nax.lock");
|
|
89265
89718
|
try {
|
|
89266
89719
|
await unlink5(lockPath);
|
|
89267
89720
|
} catch (error48) {
|
|
@@ -89286,7 +89739,7 @@ var init_lock2 = __esm(() => {
|
|
|
89286
89739
|
// src/execution/feature-lock.ts
|
|
89287
89740
|
import { mkdir as mkdir24, rename as rename6, unlink as unlink6 } from "fs/promises";
|
|
89288
89741
|
import { hostname as hostname4 } from "os";
|
|
89289
|
-
import
|
|
89742
|
+
import path13 from "path";
|
|
89290
89743
|
function validateFeatureId2(featureId) {
|
|
89291
89744
|
if (!featureId || featureId.length === 0) {
|
|
89292
89745
|
throw new NaxError("Feature ID cannot be empty", "INVALID_FEATURE_ID", { stage: "feature-lock" });
|
|
@@ -89316,7 +89769,7 @@ function getSafeLogger4() {
|
|
|
89316
89769
|
}
|
|
89317
89770
|
function featureLockPath(outputDir, feature) {
|
|
89318
89771
|
validateFeatureId2(feature);
|
|
89319
|
-
return
|
|
89772
|
+
return path13.join(outputDir, "features", feature, "nax.lock");
|
|
89320
89773
|
}
|
|
89321
89774
|
function lockHost() {
|
|
89322
89775
|
return _featureLockDeps.host();
|
|
@@ -89377,7 +89830,7 @@ function emptyHolder(feature) {
|
|
|
89377
89830
|
}
|
|
89378
89831
|
async function acquireFeatureLock(args) {
|
|
89379
89832
|
const lockPath = _featureLockDeps.featureLockPath(args.outputDir, args.feature);
|
|
89380
|
-
const featureDir2 =
|
|
89833
|
+
const featureDir2 = path13.dirname(lockPath);
|
|
89381
89834
|
await mkdir24(featureDir2, { recursive: true });
|
|
89382
89835
|
const lockFile = Bun.file(lockPath);
|
|
89383
89836
|
if (await lockFile.exists()) {
|
|
@@ -90834,7 +91287,7 @@ var init_acceptance3 = __esm(() => {
|
|
|
90834
91287
|
});
|
|
90835
91288
|
|
|
90836
91289
|
// src/pipeline/stages/acceptance-setup.ts
|
|
90837
|
-
import
|
|
91290
|
+
import path14 from "path";
|
|
90838
91291
|
function computeACFingerprint(criteria) {
|
|
90839
91292
|
const sorted = [...criteria].sort().join(`
|
|
90840
91293
|
`);
|
|
@@ -90844,7 +91297,7 @@ function computeACFingerprint(criteria) {
|
|
|
90844
91297
|
}
|
|
90845
91298
|
function computeAcceptanceLayoutFingerprint(workdir, groups) {
|
|
90846
91299
|
const layout = groups.map(({ testPath, stories }) => ({
|
|
90847
|
-
testPath:
|
|
91300
|
+
testPath: path14.relative(workdir, testPath).replaceAll(path14.sep, "/"),
|
|
90848
91301
|
storyIds: stories.map((story) => story.id).sort()
|
|
90849
91302
|
})).sort((a, b) => a.testPath.localeCompare(b.testPath));
|
|
90850
91303
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
@@ -90854,14 +91307,14 @@ function computeAcceptanceLayoutFingerprint(workdir, groups) {
|
|
|
90854
91307
|
async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
|
|
90855
91308
|
const language = ctx.config.project?.language;
|
|
90856
91309
|
const testPathConfig = ctx.config.acceptance.testPath;
|
|
90857
|
-
const metaPath =
|
|
91310
|
+
const metaPath = path14.join(featureDir2, "acceptance-meta.json");
|
|
90858
91311
|
const allCriteria = ctx.prd.userStories.filter(isInAcceptanceScope).flatMap((s) => s.acceptanceCriteria);
|
|
90859
91312
|
const featureName = ctx.prd.feature ?? ctx.prd.featureName;
|
|
90860
91313
|
const groups = await groupStoriesByPackage(ctx.prd, ctx.workdir, featureName, testPathConfig, language);
|
|
90861
91314
|
const nonFixStories = groups.flatMap((g) => g.stories);
|
|
90862
91315
|
const groupConfigs = new Map;
|
|
90863
91316
|
for (const group of groups) {
|
|
90864
|
-
const relativeWorkdir =
|
|
91317
|
+
const relativeWorkdir = path14.relative(ctx.projectDir, group.packageDir);
|
|
90865
91318
|
let config2 = ctx.config;
|
|
90866
91319
|
if (relativeWorkdir && relativeWorkdir !== ".") {
|
|
90867
91320
|
try {
|
|
@@ -90906,7 +91359,6 @@ async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
|
|
|
90906
91359
|
await _acceptanceSetupDeps.deleteFile(testPath);
|
|
90907
91360
|
}
|
|
90908
91361
|
}
|
|
90909
|
-
await _acceptanceSetupDeps.deleteSemanticVerdicts(featureDir2);
|
|
90910
91362
|
shouldGenerate = true;
|
|
90911
91363
|
regenerated = true;
|
|
90912
91364
|
} else {
|
|
@@ -91014,7 +91466,7 @@ async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
|
|
|
91014
91466
|
testable: c.testable,
|
|
91015
91467
|
storyId: c.storyId
|
|
91016
91468
|
})), null, 2);
|
|
91017
|
-
await _acceptanceSetupDeps.writeFile(
|
|
91469
|
+
await _acceptanceSetupDeps.writeFile(path14.join(featureDir2, "acceptance-refined.json"), refinedJsonContent);
|
|
91018
91470
|
}
|
|
91019
91471
|
if (sawDispatchFailure) {
|
|
91020
91472
|
getSafeLogger()?.warn("acceptance-setup", "generation dispatch failed; not recording acceptance meta so the next run regenerates", { storyId: ctx.story?.id, metaPath });
|
|
@@ -91126,21 +91578,6 @@ var init_acceptance_setup = __esm(() => {
|
|
|
91126
91578
|
throw err;
|
|
91127
91579
|
}
|
|
91128
91580
|
},
|
|
91129
|
-
deleteSemanticVerdicts: async (featureDir2) => {
|
|
91130
|
-
const dir = `${featureDir2}/semantic-verdicts`;
|
|
91131
|
-
const { readdir: readdir8, unlink: unlink7 } = await import("fs/promises");
|
|
91132
|
-
let files;
|
|
91133
|
-
try {
|
|
91134
|
-
files = await readdir8(dir);
|
|
91135
|
-
} catch (err) {
|
|
91136
|
-
if (err.code === "ENOENT")
|
|
91137
|
-
return;
|
|
91138
|
-
throw err;
|
|
91139
|
-
}
|
|
91140
|
-
for (const file2 of files) {
|
|
91141
|
-
await unlink7(`${dir}/${file2}`);
|
|
91142
|
-
}
|
|
91143
|
-
},
|
|
91144
91581
|
readMeta: async (metaPath) => {
|
|
91145
91582
|
const f = Bun.file(metaPath);
|
|
91146
91583
|
if (!await f.exists())
|
|
@@ -91345,7 +91782,6 @@ async function getDiffFilePaths(workdir, baseRef) {
|
|
|
91345
91782
|
}
|
|
91346
91783
|
var MAX_DIFF_TEXT_CHARS = 8000, HIGH_MEMORY_TELEMETRY_BYTES, STREAM_DRAIN_DEADLINE_MS = 2000, completionStage, _completionDeps;
|
|
91347
91784
|
var init_completion = __esm(() => {
|
|
91348
|
-
init_acceptance2();
|
|
91349
91785
|
init_config();
|
|
91350
91786
|
init_engine();
|
|
91351
91787
|
init_fragments();
|
|
@@ -91465,7 +91901,6 @@ var init_completion = __esm(() => {
|
|
|
91465
91901
|
};
|
|
91466
91902
|
_completionDeps = {
|
|
91467
91903
|
checkReviewGate,
|
|
91468
|
-
persistSemanticVerdict,
|
|
91469
91904
|
savePRD,
|
|
91470
91905
|
getDiffText,
|
|
91471
91906
|
getDiffFilePaths,
|
|
@@ -92241,9 +92676,9 @@ var init_prompt2 = __esm(() => {
|
|
|
92241
92676
|
});
|
|
92242
92677
|
|
|
92243
92678
|
// src/pipeline/stages/queue-check.ts
|
|
92244
|
-
import
|
|
92679
|
+
import path15 from "path";
|
|
92245
92680
|
function resolvePrdPath(ctx) {
|
|
92246
|
-
return
|
|
92681
|
+
return path15.join(ctx.featureDir ?? featureDir(ctx.workdir, "unknown"), "prd.json");
|
|
92247
92682
|
}
|
|
92248
92683
|
function logDroppedCommands(logger, ctx, queueCommands, currentIndex) {
|
|
92249
92684
|
const dropped = queueCommands.slice(currentIndex + 1);
|
|
@@ -92294,10 +92729,10 @@ async function processQueueCommands(ctx, logger, queueCommands) {
|
|
|
92294
92729
|
}
|
|
92295
92730
|
if (cmd.type === "INJECT") {
|
|
92296
92731
|
try {
|
|
92297
|
-
if (
|
|
92732
|
+
if (path15.isAbsolute(cmd.storyFile)) {
|
|
92298
92733
|
throw new NaxError(`INJECT storyFile must be a relative path within the workspace: ${cmd.storyFile}`, "INJECT_PATH_ABSOLUTE", { stage: "queue-check", storyId: ctx.story?.id ?? "unknown", storyFile: cmd.storyFile });
|
|
92299
92734
|
}
|
|
92300
|
-
const storyFilePath = validateFilePath(
|
|
92735
|
+
const storyFilePath = validateFilePath(path15.join(ctx.workdir, cmd.storyFile), ctx.workdir);
|
|
92301
92736
|
const raw = await Bun.file(storyFilePath).json();
|
|
92302
92737
|
const existingIds = new Set(ctx.prd.userStories.map((s) => s.id));
|
|
92303
92738
|
const story = validateInjectedStory(raw, existingIds);
|
|
@@ -93675,7 +94110,7 @@ var init_hooks = __esm(() => {
|
|
|
93675
94110
|
});
|
|
93676
94111
|
|
|
93677
94112
|
// src/execution/lifecycle/acceptance-helpers.ts
|
|
93678
|
-
import
|
|
94113
|
+
import path16 from "path";
|
|
93679
94114
|
function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2) {
|
|
93680
94115
|
const matchedEntry = failedPackage ? acceptanceTestPaths?.find((entry) => entry.testPath === failedPackage.testPath || entry.packageDir === failedPackage.packageDir) : undefined;
|
|
93681
94116
|
const selectedPathEntry = matchedEntry ?? acceptanceTestPaths?.[0];
|
|
@@ -93698,10 +94133,7 @@ function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2)
|
|
|
93698
94133
|
function isStubTestFile(content) {
|
|
93699
94134
|
return isStubTestContent(content);
|
|
93700
94135
|
}
|
|
93701
|
-
function isTestLevelFailure(failedACs, totalACs
|
|
93702
|
-
if (semanticVerdicts && semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
|
|
93703
|
-
return true;
|
|
93704
|
-
}
|
|
94136
|
+
function isTestLevelFailure(failedACs, totalACs) {
|
|
93705
94137
|
const failedCount = typeof failedACs === "number" ? failedACs : failedACs.length;
|
|
93706
94138
|
const hasACError = Array.isArray(failedACs) && failedACs.includes("AC-ERROR");
|
|
93707
94139
|
if (hasACError)
|
|
@@ -93713,7 +94145,7 @@ function isTestLevelFailure(failedACs, totalACs, semanticVerdicts) {
|
|
|
93713
94145
|
async function loadSpecContent(featureDir2) {
|
|
93714
94146
|
if (!featureDir2)
|
|
93715
94147
|
return "";
|
|
93716
|
-
const specPath =
|
|
94148
|
+
const specPath = path16.join(featureDir2, "spec.md");
|
|
93717
94149
|
const specFile = Bun.file(specPath);
|
|
93718
94150
|
return await specFile.exists() ? await specFile.text() : "";
|
|
93719
94151
|
}
|
|
@@ -93733,7 +94165,7 @@ async function loadAcceptanceTestContent2(featureDir2, testPaths, configuredTest
|
|
|
93733
94165
|
}
|
|
93734
94166
|
if (!configuredTestPath)
|
|
93735
94167
|
return [];
|
|
93736
|
-
const resolvedPath =
|
|
94168
|
+
const resolvedPath = path16.join(featureDir2, configuredTestPath);
|
|
93737
94169
|
const testFile = Bun.file(resolvedPath);
|
|
93738
94170
|
const content = await testFile.exists() ? await testFile.text() : "";
|
|
93739
94171
|
return [{ content, path: resolvedPath }];
|
|
@@ -93763,7 +94195,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
93763
94195
|
const { unlink: unlink7 } = await import("fs/promises");
|
|
93764
94196
|
await unlink7(testPath);
|
|
93765
94197
|
if (acceptanceContext.featureDir) {
|
|
93766
|
-
const metaPath =
|
|
94198
|
+
const metaPath = path16.join(acceptanceContext.featureDir, "acceptance-meta.json");
|
|
93767
94199
|
try {
|
|
93768
94200
|
await unlink7(metaPath);
|
|
93769
94201
|
} catch {}
|
|
@@ -93779,7 +94211,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
93779
94211
|
const diffOutput = await _regenerateDeps.spawnGitDiff(repoRoot, storyGitRef, pathspec);
|
|
93780
94212
|
const changedFilesRaw = diffOutput.split(`
|
|
93781
94213
|
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
93782
|
-
const packageDir = storyPkg && acceptanceContext.projectDir ?
|
|
94214
|
+
const packageDir = storyPkg && acceptanceContext.projectDir ? path16.join(acceptanceContext.projectDir, storyPkg) : undefined;
|
|
93783
94215
|
const ignoreMatchers = acceptanceContext.naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
|
|
93784
94216
|
const changedFiles2 = filterNaxInternalPaths(changedFilesRaw, ignoreMatchers);
|
|
93785
94217
|
const MAX_BYTES = 51200;
|
|
@@ -93791,7 +94223,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
93791
94223
|
for (const file2 of changedFiles2) {
|
|
93792
94224
|
if (totalBytes >= MAX_BYTES)
|
|
93793
94225
|
break;
|
|
93794
|
-
const filePath =
|
|
94226
|
+
const filePath = path16.join(repoRoot, file2);
|
|
93795
94227
|
try {
|
|
93796
94228
|
const fileContent = await _regenerateDeps.readFile(filePath);
|
|
93797
94229
|
const remaining = MAX_BYTES - totalBytes;
|
|
@@ -93878,7 +94310,7 @@ function fixCallCtx(ctx, packageDir, config2) {
|
|
|
93878
94310
|
}
|
|
93879
94311
|
async function resolveAcceptanceDiagnosis(opts) {
|
|
93880
94312
|
const logger = getSafeLogger();
|
|
93881
|
-
const { ctx, failures, totalACs, strategy,
|
|
94313
|
+
const { ctx, failures, totalACs, strategy, diagnosisOpts } = opts;
|
|
93882
94314
|
const storyId = diagnosisOpts.storyId;
|
|
93883
94315
|
if (strategy === "implement-only") {
|
|
93884
94316
|
logger?.info("acceptance.diagnosis", "Fast path: implement-only strategy \u2192 source_bug", { storyId });
|
|
@@ -93888,19 +94320,6 @@ async function resolveAcceptanceDiagnosis(opts) {
|
|
|
93888
94320
|
confidence: 1
|
|
93889
94321
|
};
|
|
93890
94322
|
}
|
|
93891
|
-
const SENTINELS = ["AC-ERROR", "AC-HOOK"];
|
|
93892
|
-
const hasOnlySentinels = failures.failedACs.length > 0 && failures.failedACs.every((ac) => SENTINELS.includes(ac));
|
|
93893
|
-
if (!hasOnlySentinels && semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
|
|
93894
|
-
logger?.info("acceptance.diagnosis", "Fast path: all semantic verdicts passed \u2192 test_bug", {
|
|
93895
|
-
storyId,
|
|
93896
|
-
verdictCount: semanticVerdicts.length
|
|
93897
|
-
});
|
|
93898
|
-
return {
|
|
93899
|
-
verdict: "test_bug",
|
|
93900
|
-
reasoning: `Semantic review confirmed all ${semanticVerdicts.length} ACs are implemented \u2014 failure is a test generation issue`,
|
|
93901
|
-
confidence: 1
|
|
93902
|
-
};
|
|
93903
|
-
}
|
|
93904
94323
|
if (isTestLevelFailure(failures.failedACs, totalACs)) {
|
|
93905
94324
|
logger?.info("acceptance.diagnosis", "Fast path: test-level failure heuristic \u2192 test_bug", {
|
|
93906
94325
|
storyId,
|
|
@@ -93922,8 +94341,7 @@ async function resolveAcceptanceDiagnosis(opts) {
|
|
|
93922
94341
|
testOutput: diagnosisOpts.testOutput,
|
|
93923
94342
|
testFileContent: diagnosisOpts.testFileContent,
|
|
93924
94343
|
acceptanceTestPath: diagnosisOpts.acceptanceTestPath,
|
|
93925
|
-
sourceFiles
|
|
93926
|
-
semanticVerdicts
|
|
94344
|
+
sourceFiles
|
|
93927
94345
|
});
|
|
93928
94346
|
}
|
|
93929
94347
|
var _diagnosisDeps;
|
|
@@ -94211,7 +94629,6 @@ async function runAcceptanceLoop(ctx) {
|
|
|
94211
94629
|
continue;
|
|
94212
94630
|
}
|
|
94213
94631
|
}
|
|
94214
|
-
const semanticVerdicts = ctx.featureDir ? await _acceptanceLoopDeps.loadSemanticVerdicts(ctx.featureDir) : [];
|
|
94215
94632
|
const totalACs = prd.userStories.filter((s) => !isLegacyFixStory(s)).flatMap((s) => s.acceptanceCriteria).length;
|
|
94216
94633
|
if (!ctx.runtime) {
|
|
94217
94634
|
logger?.error("acceptance", "Runtime not found for diagnosis", { storyId: firstStory?.id });
|
|
@@ -94234,7 +94651,6 @@ async function runAcceptanceLoop(ctx) {
|
|
|
94234
94651
|
failures: pkgFailures,
|
|
94235
94652
|
totalACs,
|
|
94236
94653
|
strategy,
|
|
94237
|
-
semanticVerdicts,
|
|
94238
94654
|
diagnosisOpts: {
|
|
94239
94655
|
testOutput: pkg.output,
|
|
94240
94656
|
testFileContent,
|
|
@@ -94280,7 +94696,6 @@ var init_acceptance_loop = __esm(() => {
|
|
|
94280
94696
|
init_acceptance_helpers();
|
|
94281
94697
|
init_acceptance_helpers();
|
|
94282
94698
|
_acceptanceLoopDeps = {
|
|
94283
|
-
loadSemanticVerdicts,
|
|
94284
94699
|
loadAcceptanceTestContent
|
|
94285
94700
|
};
|
|
94286
94701
|
_acceptanceFixCycleDeps = {
|
|
@@ -94509,7 +94924,7 @@ var init_scratchpad_wipe = __esm(() => {
|
|
|
94509
94924
|
init_logger2();
|
|
94510
94925
|
init_tools();
|
|
94511
94926
|
_scratchpadWipeDeps = {
|
|
94512
|
-
remove: (
|
|
94927
|
+
remove: (path17) => rm7(path17, { recursive: true, force: true })
|
|
94513
94928
|
};
|
|
94514
94929
|
});
|
|
94515
94930
|
|
|
@@ -94661,6 +95076,13 @@ async function cleanupRun(options) {
|
|
|
94661
95076
|
} catch (error48) {
|
|
94662
95077
|
logger?.warn("plugins", "Plugin teardown failed", { error: error48 });
|
|
94663
95078
|
}
|
|
95079
|
+
if (options.sealApprovals) {
|
|
95080
|
+
try {
|
|
95081
|
+
await options.sealApprovals();
|
|
95082
|
+
} catch (error48) {
|
|
95083
|
+
logger?.warn("permissions", "End-of-run approvals seal failed \u2014 continuing teardown", { error: error48 });
|
|
95084
|
+
}
|
|
95085
|
+
}
|
|
94664
95086
|
if (interactionChain) {
|
|
94665
95087
|
try {
|
|
94666
95088
|
await interactionChain.destroy();
|
|
@@ -97098,8 +97520,8 @@ async function defaultRun(cmd, opts) {
|
|
|
97098
97520
|
clearTimeout(timer);
|
|
97099
97521
|
}
|
|
97100
97522
|
}
|
|
97101
|
-
async function defaultReadText(
|
|
97102
|
-
const file2 = Bun.file(
|
|
97523
|
+
async function defaultReadText(path17) {
|
|
97524
|
+
const file2 = Bun.file(path17);
|
|
97103
97525
|
if (!await file2.exists())
|
|
97104
97526
|
return null;
|
|
97105
97527
|
return file2.text();
|
|
@@ -97199,10 +97621,10 @@ var init_pr = __esm(() => {
|
|
|
97199
97621
|
});
|
|
97200
97622
|
|
|
97201
97623
|
// src/forge/template.ts
|
|
97202
|
-
import * as
|
|
97624
|
+
import * as path17 from "path";
|
|
97203
97625
|
async function firstExisting(workdir, deps, paths) {
|
|
97204
97626
|
for (const relPath of paths) {
|
|
97205
|
-
const content = await deps.readText(
|
|
97627
|
+
const content = await deps.readText(path17.join(workdir, relPath));
|
|
97206
97628
|
if (content !== null)
|
|
97207
97629
|
return content;
|
|
97208
97630
|
}
|
|
@@ -97417,7 +97839,7 @@ var init_pr_body = __esm(() => {
|
|
|
97417
97839
|
});
|
|
97418
97840
|
|
|
97419
97841
|
// src/plugins/builtin/auto-pr/index.ts
|
|
97420
|
-
import * as
|
|
97842
|
+
import * as path18 from "path";
|
|
97421
97843
|
async function defaultRun2(cmd, opts) {
|
|
97422
97844
|
const argv = cmd[0] === "git" ? hardenedGitArgv(cmd) : cmd;
|
|
97423
97845
|
const proc = Bun.spawn(argv, { cwd: opts.cwd, env: gitSpawnEnv(), stdout: "pipe", stderr: "pipe" });
|
|
@@ -97443,8 +97865,8 @@ async function defaultRun2(cmd, opts) {
|
|
|
97443
97865
|
clearTimeout(timer);
|
|
97444
97866
|
}
|
|
97445
97867
|
}
|
|
97446
|
-
async function defaultReadText2(
|
|
97447
|
-
const file2 = Bun.file(
|
|
97868
|
+
async function defaultReadText2(path19) {
|
|
97869
|
+
const file2 = Bun.file(path19);
|
|
97448
97870
|
if (!await file2.exists())
|
|
97449
97871
|
return null;
|
|
97450
97872
|
return file2.text();
|
|
@@ -97474,8 +97896,8 @@ function getStorySummary(context) {
|
|
|
97474
97896
|
function relativePrdPath(workdir, prdPath) {
|
|
97475
97897
|
if (!prdPath)
|
|
97476
97898
|
return prdPath;
|
|
97477
|
-
const rel =
|
|
97478
|
-
return rel && !rel.startsWith("..") && !
|
|
97899
|
+
const rel = path18.relative(workdir, prdPath);
|
|
97900
|
+
return rel && !rel.startsWith("..") && !path18.isAbsolute(rel) ? rel : prdPath;
|
|
97479
97901
|
}
|
|
97480
97902
|
function toPrBodyContext(context) {
|
|
97481
97903
|
const summary = getStorySummary(context);
|
|
@@ -97764,7 +98186,7 @@ var init_auto_prune = __esm(() => {
|
|
|
97764
98186
|
});
|
|
97765
98187
|
|
|
97766
98188
|
// src/plugins/builtin/curator/collect.ts
|
|
97767
|
-
import * as
|
|
98189
|
+
import * as path19 from "path";
|
|
97768
98190
|
function now() {
|
|
97769
98191
|
return new Date().toISOString();
|
|
97770
98192
|
}
|
|
@@ -97822,7 +98244,7 @@ function tokenCount(story) {
|
|
|
97822
98244
|
}
|
|
97823
98245
|
async function collectFromMetrics(context) {
|
|
97824
98246
|
const observations = [];
|
|
97825
|
-
const metricsPath =
|
|
98247
|
+
const metricsPath = path19.join(context.outputDir, "metrics.json");
|
|
97826
98248
|
try {
|
|
97827
98249
|
const data = await readJsonFile(metricsPath);
|
|
97828
98250
|
const runs = Array.isArray(data) ? data : [data];
|
|
@@ -97898,11 +98320,11 @@ function findingMessage(finding) {
|
|
|
97898
98320
|
}
|
|
97899
98321
|
async function collectFromReviewAudit(context) {
|
|
97900
98322
|
const observations = [];
|
|
97901
|
-
const auditDir =
|
|
98323
|
+
const auditDir = path19.join(context.outputDir, "review-audit");
|
|
97902
98324
|
try {
|
|
97903
98325
|
const glob = new Bun.Glob("**/*.json");
|
|
97904
98326
|
for await (const file2 of glob.scan({ cwd: auditDir, absolute: false })) {
|
|
97905
|
-
const fullPath =
|
|
98327
|
+
const fullPath = path19.join(auditDir, file2);
|
|
97906
98328
|
try {
|
|
97907
98329
|
const audit = asRecord3(await readJsonFile(fullPath));
|
|
97908
98330
|
if (!audit)
|
|
@@ -97953,7 +98375,7 @@ async function collectFromContextManifests(context) {
|
|
|
97953
98375
|
try {
|
|
97954
98376
|
const glob = new Bun.Glob("*/stories/*/context-manifest-*.json");
|
|
97955
98377
|
for await (const file2 of glob.scan({ cwd: featuresRoot, absolute: false })) {
|
|
97956
|
-
const fullPath =
|
|
98378
|
+
const fullPath = path19.join(featuresRoot, file2);
|
|
97957
98379
|
try {
|
|
97958
98380
|
const parts = file2.split("/");
|
|
97959
98381
|
const featureId = parts[0] ?? context.feature;
|
|
@@ -98560,9 +98982,9 @@ function renderProposals(proposals, runId, observationCount, provenance) {
|
|
|
98560
98982
|
|
|
98561
98983
|
// src/plugins/builtin/curator/rollup.ts
|
|
98562
98984
|
import { appendFile as appendFile7, mkdir as mkdir26, writeFile as writeFile11 } from "fs/promises";
|
|
98563
|
-
import * as
|
|
98985
|
+
import * as path20 from "path";
|
|
98564
98986
|
async function appendToRollup(observations, rollupPath) {
|
|
98565
|
-
const dir =
|
|
98987
|
+
const dir = path20.dirname(rollupPath);
|
|
98566
98988
|
await mkdir26(dir, { recursive: true }).catch(() => {});
|
|
98567
98989
|
const { withPathFileLock: withPathFileLock2 } = await Promise.resolve().then(() => (init_path_file_lock(), exports_path_file_lock));
|
|
98568
98990
|
await withPathFileLock2(rollupPath, () => appendToRollupUnlocked(observations, rollupPath)).catch(() => {});
|
|
@@ -98651,7 +99073,7 @@ var init_rollup2 = __esm(() => {
|
|
|
98651
99073
|
|
|
98652
99074
|
// src/plugins/builtin/curator/index.ts
|
|
98653
99075
|
import { mkdir as mkdir27 } from "fs/promises";
|
|
98654
|
-
import * as
|
|
99076
|
+
import * as path21 from "path";
|
|
98655
99077
|
function getCuratorEnabled(context) {
|
|
98656
99078
|
const cfg = context.config;
|
|
98657
99079
|
if (!cfg)
|
|
@@ -98727,7 +99149,7 @@ var init_curator = __esm(() => {
|
|
|
98727
99149
|
const observations = await collectObservations(curatorContext);
|
|
98728
99150
|
if (context.outputDir) {
|
|
98729
99151
|
const { observationsPath, rollupPath } = resolveCuratorOutputs(curatorContext);
|
|
98730
|
-
const runDir =
|
|
99152
|
+
const runDir = path21.dirname(observationsPath);
|
|
98731
99153
|
await mkdir27(runDir, { recursive: true });
|
|
98732
99154
|
await Bun.write(observationsPath, observations.map((o) => JSON.stringify(o)).join(`
|
|
98733
99155
|
`) + (observations.length > 0 ? `
|
|
@@ -98765,7 +99187,7 @@ var init_curator = __esm(() => {
|
|
|
98765
99187
|
const windowHasObservations = window2.observations.length > 0;
|
|
98766
99188
|
const provenance = windowHasObservations ? { runCount: window2.runIds.length, observationCount: window2.observations.length } : { runCount: 1, observationCount: observations.length };
|
|
98767
99189
|
const markdown = renderProposals(proposals, context.runId, observations.length, provenance);
|
|
98768
|
-
const proposalsMdPath =
|
|
99190
|
+
const proposalsMdPath = path21.join(runDir, "curator-proposals.md");
|
|
98769
99191
|
await Bun.write(proposalsMdPath, markdown);
|
|
98770
99192
|
}
|
|
98771
99193
|
return {
|
|
@@ -99148,14 +99570,14 @@ var init_validator = __esm(() => {
|
|
|
99148
99570
|
|
|
99149
99571
|
// src/plugins/loader.ts
|
|
99150
99572
|
import * as fs from "fs/promises";
|
|
99151
|
-
import * as
|
|
99573
|
+
import * as path22 from "path";
|
|
99152
99574
|
function getSafeLogger7() {
|
|
99153
99575
|
return getSafeLogger();
|
|
99154
99576
|
}
|
|
99155
99577
|
function extractPluginName(pluginPath) {
|
|
99156
|
-
const basename13 =
|
|
99578
|
+
const basename13 = path22.basename(pluginPath);
|
|
99157
99579
|
if (basename13 === "index.ts" || basename13 === "index.js" || basename13 === "index.mjs") {
|
|
99158
|
-
return
|
|
99580
|
+
return path22.basename(path22.dirname(pluginPath));
|
|
99159
99581
|
}
|
|
99160
99582
|
return basename13.replace(/\.(ts|js|mjs)$/, "");
|
|
99161
99583
|
}
|
|
@@ -99302,7 +99724,7 @@ async function discoverPlugins(dir, isTestFileFn) {
|
|
|
99302
99724
|
try {
|
|
99303
99725
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
99304
99726
|
for (const entry of entries) {
|
|
99305
|
-
const fullPath =
|
|
99727
|
+
const fullPath = path22.join(dir, entry.name);
|
|
99306
99728
|
if (entry.isFile()) {
|
|
99307
99729
|
if (isPluginFile(entry.name, isTestFileFn)) {
|
|
99308
99730
|
discovered.push({ path: fullPath });
|
|
@@ -99310,7 +99732,7 @@ async function discoverPlugins(dir, isTestFileFn) {
|
|
|
99310
99732
|
} else if (entry.isDirectory()) {
|
|
99311
99733
|
const indexPaths = ["index.ts", "index.js", "index.mjs"];
|
|
99312
99734
|
for (const indexFile of indexPaths) {
|
|
99313
|
-
const indexPath =
|
|
99735
|
+
const indexPath = path22.join(fullPath, indexFile);
|
|
99314
99736
|
try {
|
|
99315
99737
|
await fs.access(indexPath);
|
|
99316
99738
|
discovered.push({ path: indexPath });
|
|
@@ -99335,13 +99757,13 @@ function isPluginFile(filename, isTestFileFn) {
|
|
|
99335
99757
|
return !FALLBACK_TEST_FILE_RE.test(filename);
|
|
99336
99758
|
}
|
|
99337
99759
|
function resolveModulePath(modulePath, projectRoot) {
|
|
99338
|
-
if (
|
|
99760
|
+
if (path22.isAbsolute(modulePath) || !modulePath.startsWith("./") && !modulePath.startsWith("../")) {
|
|
99339
99761
|
return modulePath;
|
|
99340
99762
|
}
|
|
99341
99763
|
if (projectRoot) {
|
|
99342
|
-
return
|
|
99764
|
+
return path22.resolve(projectRoot, modulePath);
|
|
99343
99765
|
}
|
|
99344
|
-
return
|
|
99766
|
+
return path22.resolve(modulePath);
|
|
99345
99767
|
}
|
|
99346
99768
|
async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots, originalPath) {
|
|
99347
99769
|
let attemptedPath = initialModulePath;
|
|
@@ -99563,8 +99985,8 @@ var init_language_commands = __esm(() => {
|
|
|
99563
99985
|
|
|
99564
99986
|
// src/review/scoped-lint.ts
|
|
99565
99987
|
import { join as join100, relative as relative26 } from "path";
|
|
99566
|
-
function normalizePath3(
|
|
99567
|
-
return
|
|
99988
|
+
function normalizePath3(path23) {
|
|
99989
|
+
return path23.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
99568
99990
|
}
|
|
99569
99991
|
function isSupportedDerivedScopedCommand(command) {
|
|
99570
99992
|
const commands = normalizeCommandSpec(command);
|
|
@@ -99799,7 +100221,7 @@ var init_scoped_lint = __esm(() => {
|
|
|
99799
100221
|
listChangedFiles,
|
|
99800
100222
|
findPackageDir,
|
|
99801
100223
|
runLintCommand,
|
|
99802
|
-
fileExists: (
|
|
100224
|
+
fileExists: (path23) => Bun.file(path23).exists()
|
|
99803
100225
|
};
|
|
99804
100226
|
});
|
|
99805
100227
|
|
|
@@ -100260,7 +100682,7 @@ var init_paused_story_prompts = __esm(() => {
|
|
|
100260
100682
|
});
|
|
100261
100683
|
|
|
100262
100684
|
// src/execution/lifecycle/run-setup-init.ts
|
|
100263
|
-
import
|
|
100685
|
+
import path23 from "path";
|
|
100264
100686
|
async function initializeAfterLock(options) {
|
|
100265
100687
|
const logger = getSafeLogger();
|
|
100266
100688
|
const { config: config2, workdir, feature, dryRun, runtime, interactionChain, runId, agentGetFn, statusWriter, deps } = options;
|
|
@@ -100292,8 +100714,8 @@ async function initializeAfterLock(options) {
|
|
|
100292
100714
|
explicit: Object.fromEntries(explicitFields.map((f) => [f, existingProjectConfig[f]])),
|
|
100293
100715
|
detected: Object.fromEntries(autodetectedFields.map((f) => [f, detectedProfile[f]]))
|
|
100294
100716
|
});
|
|
100295
|
-
const globalPluginsDir =
|
|
100296
|
-
const projectPluginsDir =
|
|
100717
|
+
const globalPluginsDir = path23.join(globalConfigDir(), "plugins");
|
|
100718
|
+
const projectPluginsDir = path23.join(workdir, ".nax", "plugins");
|
|
100297
100719
|
const configPlugins = config2.plugins || [];
|
|
100298
100720
|
const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
|
|
100299
100721
|
const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
|
|
@@ -101252,7 +101674,7 @@ __export(exports_migrate, {
|
|
|
101252
101674
|
});
|
|
101253
101675
|
import { existsSync as existsSync30 } from "fs";
|
|
101254
101676
|
import { mkdir as mkdir28, readdir as readdir10, rename as rename9 } from "fs/promises";
|
|
101255
|
-
import
|
|
101677
|
+
import path24 from "path";
|
|
101256
101678
|
async function detectGeneratedContent(naxDir) {
|
|
101257
101679
|
if (!existsSync30(naxDir))
|
|
101258
101680
|
return [];
|
|
@@ -101265,17 +101687,17 @@ async function detectGeneratedContent(naxDir) {
|
|
|
101265
101687
|
}
|
|
101266
101688
|
for (const entry of entries) {
|
|
101267
101689
|
if (GENERATED_NAMES.has(entry)) {
|
|
101268
|
-
candidates.push({ name: entry, srcPath:
|
|
101690
|
+
candidates.push({ name: entry, srcPath: path24.join(naxDir, entry) });
|
|
101269
101691
|
}
|
|
101270
101692
|
}
|
|
101271
|
-
const featuresDir2 =
|
|
101693
|
+
const featuresDir2 = path24.join(naxDir, "features");
|
|
101272
101694
|
if (existsSync30(featuresDir2)) {
|
|
101273
101695
|
let featureDirs = [];
|
|
101274
101696
|
try {
|
|
101275
101697
|
featureDirs = await readdir10(featuresDir2);
|
|
101276
101698
|
} catch {}
|
|
101277
101699
|
for (const fid of featureDirs) {
|
|
101278
|
-
const featureDir2 =
|
|
101700
|
+
const featureDir2 = path24.join(featuresDir2, fid);
|
|
101279
101701
|
let subEntries = [];
|
|
101280
101702
|
try {
|
|
101281
101703
|
subEntries = await readdir10(featureDir2);
|
|
@@ -101285,12 +101707,12 @@ async function detectGeneratedContent(naxDir) {
|
|
|
101285
101707
|
for (const sub of subEntries) {
|
|
101286
101708
|
if (GENERATED_FEATURE_SUBNAMES.has(sub)) {
|
|
101287
101709
|
candidates.push({
|
|
101288
|
-
name:
|
|
101289
|
-
srcPath:
|
|
101710
|
+
name: path24.join("features", fid, sub),
|
|
101711
|
+
srcPath: path24.join(featureDir2, sub)
|
|
101290
101712
|
});
|
|
101291
101713
|
}
|
|
101292
101714
|
if (sub === "stories") {
|
|
101293
|
-
const storiesDir =
|
|
101715
|
+
const storiesDir = path24.join(featureDir2, "stories");
|
|
101294
101716
|
let storyDirs = [];
|
|
101295
101717
|
try {
|
|
101296
101718
|
storyDirs = await readdir10(storiesDir);
|
|
@@ -101298,7 +101720,7 @@ async function detectGeneratedContent(naxDir) {
|
|
|
101298
101720
|
continue;
|
|
101299
101721
|
}
|
|
101300
101722
|
for (const sid of storyDirs) {
|
|
101301
|
-
const storyDir =
|
|
101723
|
+
const storyDir = path24.join(storiesDir, sid);
|
|
101302
101724
|
let storyEntries = [];
|
|
101303
101725
|
try {
|
|
101304
101726
|
storyEntries = await readdir10(storyDir);
|
|
@@ -101308,8 +101730,8 @@ async function detectGeneratedContent(naxDir) {
|
|
|
101308
101730
|
for (const se of storyEntries) {
|
|
101309
101731
|
if (se.startsWith("context-manifest-") && se.endsWith(".json")) {
|
|
101310
101732
|
candidates.push({
|
|
101311
|
-
name:
|
|
101312
|
-
srcPath:
|
|
101733
|
+
name: path24.join("features", fid, "stories", sid, se),
|
|
101734
|
+
srcPath: path24.join(storyDir, se)
|
|
101313
101735
|
});
|
|
101314
101736
|
}
|
|
101315
101737
|
}
|
|
@@ -101330,15 +101752,15 @@ async function migrateCommand(options) {
|
|
|
101330
101752
|
name: options.reclaim
|
|
101331
101753
|
});
|
|
101332
101754
|
}
|
|
101333
|
-
const src =
|
|
101755
|
+
const src = path24.join(globalConfigDir(), options.reclaim);
|
|
101334
101756
|
if (!existsSync30(src)) {
|
|
101335
101757
|
throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
|
|
101336
101758
|
stage: "migrate",
|
|
101337
101759
|
name: options.reclaim
|
|
101338
101760
|
});
|
|
101339
101761
|
}
|
|
101340
|
-
const archiveBase =
|
|
101341
|
-
const archiveDest =
|
|
101762
|
+
const archiveBase = path24.join(globalConfigDir(), "_archive");
|
|
101763
|
+
const archiveDest = path24.join(archiveBase, `${options.reclaim}-${Date.now()}`);
|
|
101342
101764
|
await mkdir28(archiveBase, { recursive: true });
|
|
101343
101765
|
await rename9(src, archiveDest);
|
|
101344
101766
|
logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
|
|
@@ -101378,8 +101800,8 @@ async function migrateCommand(options) {
|
|
|
101378
101800
|
logger.info("migrate", `Merged: identity for "${options.merge}" updated`, { storyId: "_migrate" });
|
|
101379
101801
|
return;
|
|
101380
101802
|
}
|
|
101381
|
-
const naxDir =
|
|
101382
|
-
const configPath =
|
|
101803
|
+
const naxDir = path24.join(options.workdir, ".nax");
|
|
101804
|
+
const configPath = path24.join(naxDir, "config.json");
|
|
101383
101805
|
if (!existsSync30(configPath)) {
|
|
101384
101806
|
throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
|
|
101385
101807
|
stage: "migrate",
|
|
@@ -101395,7 +101817,7 @@ async function migrateCommand(options) {
|
|
|
101395
101817
|
cause: e
|
|
101396
101818
|
});
|
|
101397
101819
|
}
|
|
101398
|
-
const projectKey = config2.name?.trim() ||
|
|
101820
|
+
const projectKey = config2.name?.trim() || path24.basename(options.workdir);
|
|
101399
101821
|
const destBase = projectOutputDir(projectKey, config2.outputDir);
|
|
101400
101822
|
const candidates = await detectGeneratedContent(naxDir);
|
|
101401
101823
|
if (candidates.length === 0) {
|
|
@@ -101404,7 +101826,7 @@ async function migrateCommand(options) {
|
|
|
101404
101826
|
}
|
|
101405
101827
|
if (options.dryRun) {
|
|
101406
101828
|
for (const c of candidates) {
|
|
101407
|
-
logger.info("migrate", `[dry-run] Would move: ${c.srcPath} -> ${
|
|
101829
|
+
logger.info("migrate", `[dry-run] Would move: ${c.srcPath} -> ${path24.join(destBase, c.name)}`, {
|
|
101408
101830
|
storyId: "_migrate"
|
|
101409
101831
|
});
|
|
101410
101832
|
}
|
|
@@ -101413,8 +101835,8 @@ async function migrateCommand(options) {
|
|
|
101413
101835
|
await mkdir28(destBase, { recursive: true });
|
|
101414
101836
|
let moved = 0;
|
|
101415
101837
|
for (const candidate of candidates) {
|
|
101416
|
-
const dest =
|
|
101417
|
-
await mkdir28(
|
|
101838
|
+
const dest = path24.join(destBase, candidate.name);
|
|
101839
|
+
await mkdir28(path24.dirname(dest), { recursive: true });
|
|
101418
101840
|
if (existsSync30(dest)) {
|
|
101419
101841
|
throw new NaxError(`Migration conflict: destination already exists.
|
|
101420
101842
|
Source: ${candidate.srcPath}
|
|
@@ -101444,7 +101866,7 @@ async function migrateCommand(options) {
|
|
|
101444
101866
|
moved++;
|
|
101445
101867
|
logger.info("migrate", `Moved: ${candidate.name}`, { storyId: "_migrate" });
|
|
101446
101868
|
}
|
|
101447
|
-
await Bun.write(
|
|
101869
|
+
await Bun.write(path24.join(destBase, ".migrated-from"), JSON.stringify({ from: options.workdir, migratedAt: new Date().toISOString() }, null, 2));
|
|
101448
101870
|
logger.info("migrate", `Migration complete: ${moved} entries moved`, {
|
|
101449
101871
|
storyId: "_migrate",
|
|
101450
101872
|
destBase
|
|
@@ -101815,10 +102237,10 @@ function renderReport(timeline, options = {}) {
|
|
|
101815
102237
|
// src/commands/replay.ts
|
|
101816
102238
|
import { existsSync as existsSync32 } from "fs";
|
|
101817
102239
|
import { dirname as dirname20 } from "path";
|
|
101818
|
-
async function readJsonlLenient(
|
|
101819
|
-
if (!existsSync32(
|
|
102240
|
+
async function readJsonlLenient(path25) {
|
|
102241
|
+
if (!existsSync32(path25))
|
|
101820
102242
|
return [];
|
|
101821
|
-
const content = await Bun.file(
|
|
102243
|
+
const content = await Bun.file(path25).text();
|
|
101822
102244
|
const lines = content.split(`
|
|
101823
102245
|
`);
|
|
101824
102246
|
const entries = [];
|
|
@@ -101832,11 +102254,11 @@ async function readJsonlLenient(path26) {
|
|
|
101832
102254
|
}
|
|
101833
102255
|
return entries;
|
|
101834
102256
|
}
|
|
101835
|
-
async function readJsonOrUndefined(
|
|
101836
|
-
if (!existsSync32(
|
|
102257
|
+
async function readJsonOrUndefined(path25) {
|
|
102258
|
+
if (!existsSync32(path25))
|
|
101837
102259
|
return;
|
|
101838
102260
|
try {
|
|
101839
|
-
return await Bun.file(
|
|
102261
|
+
return await Bun.file(path25).json();
|
|
101840
102262
|
} catch {
|
|
101841
102263
|
return;
|
|
101842
102264
|
}
|
|
@@ -102549,7 +102971,7 @@ __export(exports_precheck_runner, {
|
|
|
102549
102971
|
runPrecheckValidation: () => runPrecheckValidation
|
|
102550
102972
|
});
|
|
102551
102973
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
102552
|
-
import
|
|
102974
|
+
import path25 from "path";
|
|
102553
102975
|
async function runPrecheckValidation(ctx) {
|
|
102554
102976
|
const logger = getSafeLogger();
|
|
102555
102977
|
if (process.env.NAX_PRECHECK !== "1") {
|
|
@@ -102558,7 +102980,7 @@ async function runPrecheckValidation(ctx) {
|
|
|
102558
102980
|
}
|
|
102559
102981
|
logger?.info("precheck", "Running precheck validations...");
|
|
102560
102982
|
const { runPrecheck: runPrecheck3 } = await Promise.resolve().then(() => (init_precheck2(), exports_precheck));
|
|
102561
|
-
const projectKey = ctx.config.name?.trim() ||
|
|
102983
|
+
const projectKey = ctx.config.name?.trim() || path25.basename(ctx.workdir);
|
|
102562
102984
|
const outputDir = projectOutputDir(projectKey, ctx.config.outputDir);
|
|
102563
102985
|
const featureLock = ctx.featureName !== undefined ? { outputDir, feature: ctx.featureName } : undefined;
|
|
102564
102986
|
const precheckResult = await runPrecheck3(ctx.config, ctx.prd, {
|
|
@@ -102568,7 +102990,7 @@ async function runPrecheckValidation(ctx) {
|
|
|
102568
102990
|
...featureLock !== undefined ? { featureLock } : {}
|
|
102569
102991
|
});
|
|
102570
102992
|
if (ctx.logFilePath) {
|
|
102571
|
-
mkdirSync5(
|
|
102993
|
+
mkdirSync5(path25.dirname(ctx.logFilePath), { recursive: true });
|
|
102572
102994
|
const precheckLog = {
|
|
102573
102995
|
type: "precheck",
|
|
102574
102996
|
timestamp: new Date().toISOString(),
|
|
@@ -102666,7 +103088,7 @@ __export(exports_run_setup, {
|
|
|
102666
103088
|
warnInertBashStages: () => warnInertBashStages,
|
|
102667
103089
|
warnProfileMismatch: () => warnProfileMismatch
|
|
102668
103090
|
});
|
|
102669
|
-
import
|
|
103091
|
+
import path26 from "path";
|
|
102670
103092
|
async function setupRun(options) {
|
|
102671
103093
|
const logger = getSafeLogger();
|
|
102672
103094
|
warnFallbackMisconfiguration(options.config, options.agentGetFn, logger);
|
|
@@ -102731,6 +103153,7 @@ async function setupRun(options) {
|
|
|
102731
103153
|
}
|
|
102732
103154
|
await runtime.pidRegistry.cleanupStale();
|
|
102733
103155
|
let cleanupCrashHandlers;
|
|
103156
|
+
let sealApprovals;
|
|
102734
103157
|
try {
|
|
102735
103158
|
cleanupCrashHandlers = _runSetupDeps.installCrashHandlers({
|
|
102736
103159
|
statusWriter,
|
|
@@ -102748,6 +103171,9 @@ async function setupRun(options) {
|
|
|
102748
103171
|
emitError: (reason) => {
|
|
102749
103172
|
pipelineEventBus.emit({ type: "run:errored", reason, feature: options.feature });
|
|
102750
103173
|
},
|
|
103174
|
+
sealApprovals: async () => {
|
|
103175
|
+
await sealApprovals?.();
|
|
103176
|
+
},
|
|
102751
103177
|
onShutdown: async (abortSignal) => {
|
|
102752
103178
|
await closeAllRunSessions(sessionManager, options.agentGetFn, { force: true, signal: abortSignal });
|
|
102753
103179
|
await runtime.close().catch((err) => {
|
|
@@ -102760,7 +103186,7 @@ async function setupRun(options) {
|
|
|
102760
103186
|
statusWriter.setPrd(prd);
|
|
102761
103187
|
{
|
|
102762
103188
|
const { detectGeneratedContent: detectGeneratedContent2, migrateCommand: migrateCommand2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
|
|
102763
|
-
const naxDir =
|
|
103189
|
+
const naxDir = path26.join(workdir, ".nax");
|
|
102764
103190
|
const candidates = await detectGeneratedContent2(naxDir).catch(() => []);
|
|
102765
103191
|
if (candidates.length > 0) {
|
|
102766
103192
|
logger?.info("setup", "Found generated content under .nax/ \u2014 migrating to output dir", {
|
|
@@ -102787,7 +103213,7 @@ async function setupRun(options) {
|
|
|
102787
103213
|
remoteUrl = new TextDecoder().decode(gitResult.stdout).trim() || null;
|
|
102788
103214
|
}
|
|
102789
103215
|
} catch {}
|
|
102790
|
-
const projectKey = config2.name?.trim() ||
|
|
103216
|
+
const projectKey = config2.name?.trim() || path26.basename(workdir);
|
|
102791
103217
|
await claimProjectIdentity2(projectKey, workdir, remoteUrl).catch((err) => {
|
|
102792
103218
|
if (err instanceof NaxError && err.code === "RUN_NAME_COLLISION") {
|
|
102793
103219
|
throw err;
|
|
@@ -102871,6 +103297,19 @@ async function setupRun(options) {
|
|
|
102871
103297
|
sweepFeatureTranscripts: _runSetupDeps.sweepFeatureTranscripts
|
|
102872
103298
|
}
|
|
102873
103299
|
});
|
|
103300
|
+
try {
|
|
103301
|
+
sealApprovals = await _runSetupDeps.buildApprovalsSeal({
|
|
103302
|
+
projectDir: options.workdir,
|
|
103303
|
+
rootConfig: options.config,
|
|
103304
|
+
packageDirs: initResult.prd.userStories.map(storyPackageDir),
|
|
103305
|
+
outputDir: runtime.outputDir,
|
|
103306
|
+
runId: options.runId
|
|
103307
|
+
});
|
|
103308
|
+
} catch (error48) {
|
|
103309
|
+
await releaseFeatureLock({ outputDir: runtime.outputDir, feature, runId });
|
|
103310
|
+
await releaseLock(workdir);
|
|
103311
|
+
throw error48;
|
|
103312
|
+
}
|
|
102874
103313
|
return {
|
|
102875
103314
|
statusWriter,
|
|
102876
103315
|
sessionManager,
|
|
@@ -102880,7 +103319,8 @@ async function setupRun(options) {
|
|
|
102880
103319
|
storyCounts: initResult.storyCounts,
|
|
102881
103320
|
interactionChain: initResult.interactionChain,
|
|
102882
103321
|
shutdownController,
|
|
102883
|
-
runtime
|
|
103322
|
+
runtime,
|
|
103323
|
+
sealApprovals
|
|
102884
103324
|
};
|
|
102885
103325
|
} catch (error48) {
|
|
102886
103326
|
cleanupCrashHandlers?.();
|
|
@@ -102909,6 +103349,7 @@ var init_run_setup = __esm(() => {
|
|
|
102909
103349
|
init_test_runners();
|
|
102910
103350
|
init_tools();
|
|
102911
103351
|
init_git_env();
|
|
103352
|
+
init_path_frame();
|
|
102912
103353
|
init_crash_recovery();
|
|
102913
103354
|
init_feature_lock();
|
|
102914
103355
|
init_helpers();
|
|
@@ -102921,6 +103362,7 @@ var init_run_setup = __esm(() => {
|
|
|
102921
103362
|
createRuntime,
|
|
102922
103363
|
installCrashHandlers,
|
|
102923
103364
|
sweepFeatureTranscripts,
|
|
103365
|
+
buildApprovalsSeal,
|
|
102924
103366
|
acquireLock,
|
|
102925
103367
|
acquireFeatureLock
|
|
102926
103368
|
};
|
|
@@ -104177,7 +104619,7 @@ var init_queue_file_lock = __esm(() => {
|
|
|
104177
104619
|
|
|
104178
104620
|
// src/execution/queue-handler.ts
|
|
104179
104621
|
import { rename as rename10, unlink as unlink11 } from "fs/promises";
|
|
104180
|
-
import
|
|
104622
|
+
import path27 from "path";
|
|
104181
104623
|
function getSafeLogger8() {
|
|
104182
104624
|
try {
|
|
104183
104625
|
return getLogger();
|
|
@@ -104209,8 +104651,8 @@ async function claimCommandsLocked(queuePath, processingPath, logger) {
|
|
|
104209
104651
|
return result.commands;
|
|
104210
104652
|
}
|
|
104211
104653
|
async function readQueueFile(workdir) {
|
|
104212
|
-
const queuePath =
|
|
104213
|
-
const processingPath =
|
|
104654
|
+
const queuePath = path27.join(workdir, ".queue.txt");
|
|
104655
|
+
const processingPath = path27.join(workdir, ".queue.txt.processing");
|
|
104214
104656
|
const logger = getSafeLogger8();
|
|
104215
104657
|
try {
|
|
104216
104658
|
return await withQueueFileLock(queuePath, async () => {
|
|
@@ -104225,8 +104667,8 @@ async function readQueueFile(workdir) {
|
|
|
104225
104667
|
}
|
|
104226
104668
|
}
|
|
104227
104669
|
async function processQueueFile(workdir, processor) {
|
|
104228
|
-
const queuePath =
|
|
104229
|
-
const processingPath =
|
|
104670
|
+
const queuePath = path27.join(workdir, ".queue.txt");
|
|
104671
|
+
const processingPath = path27.join(workdir, ".queue.txt.processing");
|
|
104230
104672
|
const logger = getSafeLogger8();
|
|
104231
104673
|
try {
|
|
104232
104674
|
return await withQueueFileLock(queuePath, async () => {
|
|
@@ -104251,7 +104693,7 @@ async function processQueueFile(workdir, processor) {
|
|
|
104251
104693
|
}
|
|
104252
104694
|
}
|
|
104253
104695
|
async function drainQueueAtBatchBoundary(workdir, prd) {
|
|
104254
|
-
if (!await Bun.file(
|
|
104696
|
+
if (!await Bun.file(path27.join(workdir, ".queue.txt")).exists()) {
|
|
104255
104697
|
return { paused: false };
|
|
104256
104698
|
}
|
|
104257
104699
|
const logger = getSafeLogger8();
|
|
@@ -104307,10 +104749,10 @@ async function applyBatchBoundaryCommands(workdir, prd, commands, logger) {
|
|
|
104307
104749
|
}
|
|
104308
104750
|
if (cmd.type === "INJECT") {
|
|
104309
104751
|
try {
|
|
104310
|
-
if (
|
|
104752
|
+
if (path27.isAbsolute(cmd.storyFile)) {
|
|
104311
104753
|
throw new NaxError(`INJECT storyFile must be a relative path within the workspace: ${cmd.storyFile}`, "INJECT_PATH_ABSOLUTE", { stage: "queue-batch-boundary", storyFile: cmd.storyFile });
|
|
104312
104754
|
}
|
|
104313
|
-
const storyFilePath = validateFilePath(
|
|
104755
|
+
const storyFilePath = validateFilePath(path27.join(workdir, cmd.storyFile), workdir);
|
|
104314
104756
|
const raw = await Bun.file(storyFilePath).json();
|
|
104315
104757
|
const existingIds = new Set(prd.userStories.map((s) => s.id));
|
|
104316
104758
|
const story = validateInjectedStory(raw, existingIds);
|
|
@@ -104330,8 +104772,8 @@ async function applyBatchBoundaryCommands(workdir, prd, commands, logger) {
|
|
|
104330
104772
|
return { paused: false };
|
|
104331
104773
|
}
|
|
104332
104774
|
async function clearQueueFile(workdir) {
|
|
104333
|
-
const queuePath =
|
|
104334
|
-
const processingPath =
|
|
104775
|
+
const queuePath = path27.join(workdir, ".queue.txt");
|
|
104776
|
+
const processingPath = path27.join(workdir, ".queue.txt.processing");
|
|
104335
104777
|
const logger = getSafeLogger8();
|
|
104336
104778
|
try {
|
|
104337
104779
|
await withQueueFileLock(queuePath, async () => {
|
|
@@ -105750,12 +106192,12 @@ var init_body = __esm(() => {
|
|
|
105750
106192
|
// src/finish/pr/context.ts
|
|
105751
106193
|
import { readFile as readFile8 } from "fs/promises";
|
|
105752
106194
|
import { join as join113 } from "path";
|
|
105753
|
-
async function readJson(
|
|
106195
|
+
async function readJson(path28) {
|
|
105754
106196
|
let text;
|
|
105755
106197
|
try {
|
|
105756
|
-
text = await _finishPrDeps.readText(
|
|
106198
|
+
text = await _finishPrDeps.readText(path28);
|
|
105757
106199
|
} catch (error48) {
|
|
105758
|
-
_finishPrDeps.warn("[finish-pr] Failed to read PR context artifact", { path:
|
|
106200
|
+
_finishPrDeps.warn("[finish-pr] Failed to read PR context artifact", { path: path28, error: error48 });
|
|
105759
106201
|
return;
|
|
105760
106202
|
}
|
|
105761
106203
|
if (text === null)
|
|
@@ -105763,7 +106205,7 @@ async function readJson(path29) {
|
|
|
105763
106205
|
try {
|
|
105764
106206
|
return JSON.parse(text);
|
|
105765
106207
|
} catch (error48) {
|
|
105766
|
-
_finishPrDeps.warn("[finish-pr] Failed to parse PR context artifact", { path:
|
|
106208
|
+
_finishPrDeps.warn("[finish-pr] Failed to parse PR context artifact", { path: path28, error: error48 });
|
|
105767
106209
|
return;
|
|
105768
106210
|
}
|
|
105769
106211
|
}
|
|
@@ -105847,9 +106289,9 @@ var init_context5 = __esm(() => {
|
|
|
105847
106289
|
init_pr_title();
|
|
105848
106290
|
_finishPrDeps = {
|
|
105849
106291
|
run: defaultForgeDeps.run,
|
|
105850
|
-
readText: async (
|
|
106292
|
+
readText: async (path28) => {
|
|
105851
106293
|
try {
|
|
105852
|
-
return await readFile8(
|
|
106294
|
+
return await readFile8(path28, "utf8");
|
|
105853
106295
|
} catch (err) {
|
|
105854
106296
|
if (err.code === "ENOENT")
|
|
105855
106297
|
return null;
|
|
@@ -106738,7 +107180,7 @@ var init_finish = __esm(() => {
|
|
|
106738
107180
|
});
|
|
106739
107181
|
|
|
106740
107182
|
// src/execution/runner-completion.ts
|
|
106741
|
-
import
|
|
107183
|
+
import path28 from "path";
|
|
106742
107184
|
async function runCompletionPhase(options) {
|
|
106743
107185
|
const logger = getSafeLogger();
|
|
106744
107186
|
logger?.debug("execution", "Completion phase started", {
|
|
@@ -106759,7 +107201,7 @@ async function runCompletionPhase(options) {
|
|
|
106759
107201
|
const acceptanceStartTime = Date.now();
|
|
106760
107202
|
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
|
|
106761
107203
|
const acceptanceTestPaths = options.featureDir ? await Promise.all((await groupStoriesByPackage(options.prd, options.workdir, options.feature, options.config.acceptance.testPath, options.config.project?.language)).map(async (g) => {
|
|
106762
|
-
const relativeWorkdir =
|
|
107204
|
+
const relativeWorkdir = path28.relative(options.workdir, g.packageDir);
|
|
106763
107205
|
let groupConfig = options.config;
|
|
106764
107206
|
if (relativeWorkdir && relativeWorkdir !== ".") {
|
|
106765
107207
|
try {
|
|
@@ -106890,7 +107332,7 @@ async function runCompletionPhase(options) {
|
|
|
106890
107332
|
exitReason: options.exitReason,
|
|
106891
107333
|
runtime: options.runtime,
|
|
106892
107334
|
abortSignal: options.abortSignal,
|
|
106893
|
-
isSequential: options.parallel === undefined,
|
|
107335
|
+
isSequential: options.parallel === undefined || !(options.parallel > 1),
|
|
106894
107336
|
interactionChain: options.interactionChain
|
|
106895
107337
|
});
|
|
106896
107338
|
const { durationMs, runCompletedAt, finalCounts, reportedTotal, pluginGateFailed } = completionResult;
|
|
@@ -107496,7 +107938,7 @@ __export(exports_parallel_batch, {
|
|
|
107496
107938
|
_parallelBatchDeps: () => _parallelBatchDeps,
|
|
107497
107939
|
runParallelBatch: () => runParallelBatch
|
|
107498
107940
|
});
|
|
107499
|
-
import
|
|
107941
|
+
import path29 from "path";
|
|
107500
107942
|
async function runParallelBatch(options) {
|
|
107501
107943
|
const { stories, ctx, prd } = options;
|
|
107502
107944
|
const { workdir, config: config2, maxConcurrency, pipelineContext, eventEmitter, agentGetFn, hooks, pluginRegistry } = ctx;
|
|
@@ -107531,7 +107973,7 @@ async function runParallelBatch(options) {
|
|
|
107531
107973
|
}
|
|
107532
107974
|
worktreePaths.set(story.id, storyWorktreePath(workdir, worktreeId));
|
|
107533
107975
|
}
|
|
107534
|
-
const rootConfigPath =
|
|
107976
|
+
const rootConfigPath = path29.join(workdir, ".nax", "config.json");
|
|
107535
107977
|
const profileOverride = profileOverrideFromConfig(config2);
|
|
107536
107978
|
const storyEffectiveConfigs = new Map;
|
|
107537
107979
|
const configResults = await Promise.allSettled(stories.filter((story) => storyPackageDir(story)).map(async (story) => {
|
|
@@ -108514,7 +108956,8 @@ async function run(options) {
|
|
|
108514
108956
|
pluginRegistry,
|
|
108515
108957
|
interactionChain,
|
|
108516
108958
|
shutdownController,
|
|
108517
|
-
runtime
|
|
108959
|
+
runtime,
|
|
108960
|
+
sealApprovals
|
|
108518
108961
|
} = setupResult;
|
|
108519
108962
|
prd = setupResult.prd;
|
|
108520
108963
|
const agentManager = runtime.agentManager;
|
|
@@ -108636,7 +109079,8 @@ async function run(options) {
|
|
|
108636
109079
|
projectKey: runtime.projectKey,
|
|
108637
109080
|
curatorRollupPath: runtime.curatorRollupPath,
|
|
108638
109081
|
logFilePath,
|
|
108639
|
-
config: config2
|
|
109082
|
+
config: config2,
|
|
109083
|
+
sealApprovals
|
|
108640
109084
|
});
|
|
108641
109085
|
logger?.debug("execution", "Runner finally \u2014 cleanupRun done, run() returning");
|
|
108642
109086
|
} finally {
|
|
@@ -109101,12 +109545,12 @@ async function checkDependenciesInstalled(workdir) {
|
|
|
109101
109545
|
{ path: "vendor" }
|
|
109102
109546
|
];
|
|
109103
109547
|
const found = [];
|
|
109104
|
-
for (const { path:
|
|
109105
|
-
const fullPath = `${workdir}/${
|
|
109548
|
+
for (const { path: path30 } of depPaths) {
|
|
109549
|
+
const fullPath = `${workdir}/${path30}`;
|
|
109106
109550
|
if (existsSync36(fullPath)) {
|
|
109107
109551
|
const stats = statSync6(fullPath);
|
|
109108
109552
|
if (stats.isDirectory()) {
|
|
109109
|
-
found.push(
|
|
109553
|
+
found.push(path30);
|
|
109110
109554
|
}
|
|
109111
109555
|
}
|
|
109112
109556
|
}
|
|
@@ -110535,8 +110979,8 @@ var init_plan_runtime = __esm(() => {
|
|
|
110535
110979
|
init_git_env();
|
|
110536
110980
|
init_plan_helpers();
|
|
110537
110981
|
_planDeps = {
|
|
110538
|
-
readFile: (
|
|
110539
|
-
writeFile: (
|
|
110982
|
+
readFile: (path30) => Bun.file(path30).text(),
|
|
110983
|
+
writeFile: (path30, content) => Bun.write(path30, content).then(() => {}),
|
|
110540
110984
|
scanSourceRoots: (workdir) => scanSourceRoots(workdir),
|
|
110541
110985
|
createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
|
|
110542
110986
|
claimProjectIdentity,
|
|
@@ -110545,10 +110989,10 @@ var init_plan_runtime = __esm(() => {
|
|
|
110545
110989
|
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd, ...opts.env ? { env: opts.env } : {} } : {});
|
|
110546
110990
|
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
110547
110991
|
},
|
|
110548
|
-
mkdirp: (
|
|
110549
|
-
existsSync: (
|
|
110992
|
+
mkdirp: (path30) => Bun.spawn(["mkdir", "-p", path30]).exited.then(() => {}),
|
|
110993
|
+
existsSync: (path30) => existsSync38(path30),
|
|
110550
110994
|
discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages2(repoRoot),
|
|
110551
|
-
readPackageJsonAt: (
|
|
110995
|
+
readPackageJsonAt: (path30) => Bun.file(path30).json().catch(() => null),
|
|
110552
110996
|
createInteractionBridge: () => createCliInteractionBridge(),
|
|
110553
110997
|
initInteractionChain: (cfg, headless) => initInteractionChain(cfg, headless),
|
|
110554
110998
|
runPrecheck: async (config2, prd, opts) => {
|
|
@@ -110569,7 +111013,7 @@ function assertSpecLintClean(specContent, options) {
|
|
|
110569
111013
|
return [];
|
|
110570
111014
|
const findings = lintSpecContent(specContent, {
|
|
110571
111015
|
maxAcCount: options.maxAcCount,
|
|
110572
|
-
fileExists: (
|
|
111016
|
+
fileExists: (path30) => existsSync39(join118(options.workdir, path30))
|
|
110573
111017
|
});
|
|
110574
111018
|
const blocking = findings.filter((finding) => BLOCKING_SPEC_LINT_CODES.has(finding.code));
|
|
110575
111019
|
if (blocking.length > 0) {
|
|
@@ -110629,7 +111073,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
110629
111073
|
}));
|
|
110630
111074
|
const codebaseContext = buildSourceRootsSection(normalizedRoots);
|
|
110631
111075
|
const relativePackages = [
|
|
110632
|
-
...new Set(sourceRoots.map((root) => root.path).filter((
|
|
111076
|
+
...new Set(sourceRoots.map((root) => root.path).filter((path30) => path30 !== ".").map((path30) => path30.startsWith("/") ? path30.replace(`${workdir}/`, "") : path30))
|
|
110633
111077
|
];
|
|
110634
111078
|
const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
|
|
110635
111079
|
const packageJson = await deps.readPackageJsonAt(join119(workdir, relativePath, "package.json"));
|
|
@@ -110772,7 +111216,7 @@ var init_persist_prd = __esm(() => {
|
|
|
110772
111216
|
init_prd();
|
|
110773
111217
|
init_finalize_routing();
|
|
110774
111218
|
_persistPrdDeps = {
|
|
110775
|
-
existsSync: (
|
|
111219
|
+
existsSync: (path30) => defaultExistsSync(path30),
|
|
110776
111220
|
discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages2(repoRoot)
|
|
110777
111221
|
};
|
|
110778
111222
|
});
|
|
@@ -111035,10 +111479,10 @@ var init_plan2 = __esm(() => {
|
|
|
111035
111479
|
});
|
|
111036
111480
|
|
|
111037
111481
|
// src/cli/plugins.ts
|
|
111038
|
-
import * as
|
|
111482
|
+
import * as path30 from "path";
|
|
111039
111483
|
async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
|
|
111040
|
-
const globalPluginsDir = overrideGlobalPluginsDir ??
|
|
111041
|
-
const projectPluginsDir =
|
|
111484
|
+
const globalPluginsDir = overrideGlobalPluginsDir ?? path30.join(globalConfigDir(), "plugins");
|
|
111485
|
+
const projectPluginsDir = path30.join(workdir, ".nax", "plugins");
|
|
111042
111486
|
const configPlugins = config2.plugins || [];
|
|
111043
111487
|
const registry4 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
111044
111488
|
const plugins = registry4.plugins;
|
|
@@ -111088,10 +111532,10 @@ function formatSource(type, sourcePath) {
|
|
|
111088
111532
|
return `built-in (${sourcePath})`;
|
|
111089
111533
|
}
|
|
111090
111534
|
if (type === "global") {
|
|
111091
|
-
return `global (${
|
|
111535
|
+
return `global (${path30.basename(sourcePath)})`;
|
|
111092
111536
|
}
|
|
111093
111537
|
if (type === "project") {
|
|
111094
|
-
return `project (${
|
|
111538
|
+
return `project (${path30.basename(sourcePath)})`;
|
|
111095
111539
|
}
|
|
111096
111540
|
return `config (${sourcePath})`;
|
|
111097
111541
|
}
|
|
@@ -111133,7 +111577,7 @@ async function exportPromptCommand(options) {
|
|
|
111133
111577
|
var VALID_EXPORT_ROLES;
|
|
111134
111578
|
var init_prompts_export = __esm(() => {
|
|
111135
111579
|
init_prompts();
|
|
111136
|
-
VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "
|
|
111580
|
+
VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "tdd-simple"];
|
|
111137
111581
|
});
|
|
111138
111582
|
|
|
111139
111583
|
// src/cli/prompts-init.ts
|
|
@@ -111175,7 +111619,6 @@ async function autoWirePromptsConfig(workdir) {
|
|
|
111175
111619
|
"test-writer": ".nax/templates/test-writer.md",
|
|
111176
111620
|
implementer: ".nax/templates/implementer.md",
|
|
111177
111621
|
verifier: ".nax/templates/verifier.md",
|
|
111178
|
-
"single-session": ".nax/templates/single-session.md",
|
|
111179
111622
|
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
111180
111623
|
}
|
|
111181
111624
|
}
|
|
@@ -111196,7 +111639,6 @@ ${exampleConfig}`);
|
|
|
111196
111639
|
"test-writer": ".nax/templates/test-writer.md",
|
|
111197
111640
|
implementer: ".nax/templates/implementer.md",
|
|
111198
111641
|
verifier: ".nax/templates/verifier.md",
|
|
111199
|
-
"single-session": ".nax/templates/single-session.md",
|
|
111200
111642
|
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
111201
111643
|
};
|
|
111202
111644
|
prompts.overrides = overrides;
|
|
@@ -111255,7 +111697,6 @@ var init_prompts_init = __esm(() => {
|
|
|
111255
111697
|
{ file: "test-writer.md", role: "test-writer" },
|
|
111256
111698
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
111257
111699
|
{ file: "verifier.md", role: "verifier" },
|
|
111258
|
-
{ file: "single-session.md", role: "single-session" },
|
|
111259
111700
|
{ file: "tdd-simple.md", role: "tdd-simple" }
|
|
111260
111701
|
];
|
|
111261
111702
|
});
|
|
@@ -111493,8 +111934,8 @@ async function resolveRunProfileOverride(opts) {
|
|
|
111493
111934
|
return cliChain;
|
|
111494
111935
|
if (opts.envProfile)
|
|
111495
111936
|
return;
|
|
111496
|
-
const readJson2 = opts._readJson ?? (async (
|
|
111497
|
-
const file2 = Bun.file(
|
|
111937
|
+
const readJson2 = opts._readJson ?? (async (path31) => {
|
|
111938
|
+
const file2 = Bun.file(path31);
|
|
111498
111939
|
if (!await file2.exists())
|
|
111499
111940
|
return;
|
|
111500
111941
|
return file2.json();
|
|
@@ -111874,11 +112315,11 @@ var init_rules_cli_deps = __esm(() => {
|
|
|
111874
112315
|
init_logger2();
|
|
111875
112316
|
init_rules_lint();
|
|
111876
112317
|
_rulesCLIDeps = {
|
|
111877
|
-
readFile: async (
|
|
111878
|
-
writeFile: async (
|
|
111879
|
-
await Bun.write(
|
|
112318
|
+
readFile: async (path31) => Bun.file(path31).text(),
|
|
112319
|
+
writeFile: async (path31, content) => {
|
|
112320
|
+
await Bun.write(path31, content);
|
|
111880
112321
|
},
|
|
111881
|
-
fileExists: async (
|
|
112322
|
+
fileExists: async (path31) => Bun.file(path31).exists(),
|
|
111882
112323
|
globInDir: (dir) => {
|
|
111883
112324
|
try {
|
|
111884
112325
|
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join127(dir, f));
|
|
@@ -111886,8 +112327,8 @@ var init_rules_cli_deps = __esm(() => {
|
|
|
111886
112327
|
return [];
|
|
111887
112328
|
}
|
|
111888
112329
|
},
|
|
111889
|
-
mkdir: async (
|
|
111890
|
-
await mkdir33(
|
|
112330
|
+
mkdir: async (path31) => {
|
|
112331
|
+
await mkdir33(path31, { recursive: true });
|
|
111891
112332
|
},
|
|
111892
112333
|
globCanonicalRuleFiles: (workdir) => _rulesLintDeps.globCanonicalRuleFiles(workdir),
|
|
111893
112334
|
globHasMatch: (pattern, cwd) => _rulesLintDeps.globHasMatch(pattern, cwd),
|
|
@@ -112404,10 +112845,10 @@ var init_setup_analyze = __esm(() => {
|
|
|
112404
112845
|
init_detect();
|
|
112405
112846
|
CANONICAL_SCRIPTS = ["build", "test", "lint", "type-check", "lint:fix"];
|
|
112406
112847
|
_analyzeRepoDeps = {
|
|
112407
|
-
fileExists: async (
|
|
112408
|
-
readJson: async (
|
|
112848
|
+
fileExists: async (path31) => Bun.file(path31).exists(),
|
|
112849
|
+
readJson: async (path31) => {
|
|
112409
112850
|
try {
|
|
112410
|
-
const f = Bun.file(
|
|
112851
|
+
const f = Bun.file(path31);
|
|
112411
112852
|
if (!await f.exists())
|
|
112412
112853
|
return null;
|
|
112413
112854
|
return JSON.parse(await f.text());
|
|
@@ -112462,9 +112903,9 @@ async function fillScripts(workdir, analysis) {
|
|
|
112462
112903
|
var TYPE_CHECK_KEY = "type-check", TYPE_CHECK_SCRIPT = "tsc --noEmit -p tsconfig.json", TYPE_CHECK_TURBO_PASSTHROUGH = "turbo run type-check", _fillScriptsDeps;
|
|
112463
112904
|
var init_setup_fill = __esm(() => {
|
|
112464
112905
|
_fillScriptsDeps = {
|
|
112465
|
-
readJson: async (
|
|
112906
|
+
readJson: async (path31) => {
|
|
112466
112907
|
try {
|
|
112467
|
-
const f = Bun.file(
|
|
112908
|
+
const f = Bun.file(path31);
|
|
112468
112909
|
if (!await f.exists())
|
|
112469
112910
|
return null;
|
|
112470
112911
|
return JSON.parse(await f.text());
|
|
@@ -112472,8 +112913,8 @@ var init_setup_fill = __esm(() => {
|
|
|
112472
112913
|
return null;
|
|
112473
112914
|
}
|
|
112474
112915
|
},
|
|
112475
|
-
writeFile: async (
|
|
112476
|
-
await Bun.write(
|
|
112916
|
+
writeFile: async (path31, content) => {
|
|
112917
|
+
await Bun.write(path31, content);
|
|
112477
112918
|
}
|
|
112478
112919
|
};
|
|
112479
112920
|
});
|
|
@@ -112531,9 +112972,9 @@ async function writeSetupConfig(workdir, config2, monoConfigs, _opts, deps = _wr
|
|
|
112531
112972
|
var _writeSetupDeps;
|
|
112532
112973
|
var init_setup_write = __esm(() => {
|
|
112533
112974
|
_writeSetupDeps = {
|
|
112534
|
-
writeFile: (
|
|
112535
|
-
mkdir: async (
|
|
112536
|
-
const proc = Bun.spawn(["mkdir", "-p",
|
|
112975
|
+
writeFile: (path31, content) => Bun.write(path31, content).then(() => {}),
|
|
112976
|
+
mkdir: async (path31) => {
|
|
112977
|
+
const proc = Bun.spawn(["mkdir", "-p", path31]);
|
|
112537
112978
|
await proc.exited;
|
|
112538
112979
|
}
|
|
112539
112980
|
};
|
|
@@ -112617,7 +113058,7 @@ var init_setup = __esm(() => {
|
|
|
112617
113058
|
},
|
|
112618
113059
|
generateSetupPlan: (ctx, analysis) => generateSetupPlan(ctx, analysis),
|
|
112619
113060
|
runGate: (workdir, config2) => runSetupGate(workdir, config2),
|
|
112620
|
-
fileExists: (
|
|
113061
|
+
fileExists: (path31) => Bun.file(path31).exists(),
|
|
112621
113062
|
writeSetupConfig: (workdir, config2, monoConfigs, opts) => writeSetupConfig(workdir, config2, monoConfigs, opts),
|
|
112622
113063
|
stdout: (msg) => {
|
|
112623
113064
|
process.stdout.write(`${msg}
|
|
@@ -112634,7 +113075,7 @@ var init_setup = __esm(() => {
|
|
|
112634
113075
|
import { existsSync as existsSync43 } from "fs";
|
|
112635
113076
|
import { join as join135 } from "path";
|
|
112636
113077
|
async function resolveSpecPaths(options, deps) {
|
|
112637
|
-
const explicit = (options.paths ?? []).filter((
|
|
113078
|
+
const explicit = (options.paths ?? []).filter((path31) => path31.trim().length > 0);
|
|
112638
113079
|
if (explicit.length > 0)
|
|
112639
113080
|
return [...explicit];
|
|
112640
113081
|
if (!options.feature)
|
|
@@ -112662,7 +113103,7 @@ async function lintOne(specPath, options, deps) {
|
|
|
112662
113103
|
}
|
|
112663
113104
|
const findings = lintSpecContent(content, {
|
|
112664
113105
|
maxAcCount: options.maxAcCount,
|
|
112665
|
-
fileExists: (
|
|
113106
|
+
fileExists: (path31) => deps.fileExists(join135(options.dir, path31))
|
|
112666
113107
|
});
|
|
112667
113108
|
return {
|
|
112668
113109
|
specPath,
|
|
@@ -112728,7 +113169,7 @@ var init_spec_lint_command = __esm(() => {
|
|
|
112728
113169
|
init_prd();
|
|
112729
113170
|
init_features_resolve();
|
|
112730
113171
|
_specLintCommandDeps = {
|
|
112731
|
-
readFile: async (
|
|
113172
|
+
readFile: async (path31) => Bun.file(path31).text(),
|
|
112732
113173
|
fileExists: existsSync43,
|
|
112733
113174
|
write: (line) => {
|
|
112734
113175
|
console.log(line);
|
|
@@ -114648,11 +115089,11 @@ var require_react_reconciler_development = __commonJS(function(exports, module)
|
|
|
114648
115089
|
fiber = fiber.next, id--;
|
|
114649
115090
|
return fiber;
|
|
114650
115091
|
}
|
|
114651
|
-
function copyWithSetImpl(obj,
|
|
114652
|
-
if (index >=
|
|
115092
|
+
function copyWithSetImpl(obj, path31, index, value) {
|
|
115093
|
+
if (index >= path31.length)
|
|
114653
115094
|
return value;
|
|
114654
|
-
var key =
|
|
114655
|
-
updated[key] = copyWithSetImpl(obj[key],
|
|
115095
|
+
var key = path31[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
|
|
115096
|
+
updated[key] = copyWithSetImpl(obj[key], path31, index + 1, value);
|
|
114656
115097
|
return updated;
|
|
114657
115098
|
}
|
|
114658
115099
|
function copyWithRename(obj, oldPath, newPath) {
|
|
@@ -114672,11 +115113,11 @@ var require_react_reconciler_development = __commonJS(function(exports, module)
|
|
|
114672
115113
|
index + 1 === oldPath.length ? (updated[newPath[index]] = updated[oldKey], isArrayImpl(updated) ? updated.splice(oldKey, 1) : delete updated[oldKey]) : updated[oldKey] = copyWithRenameImpl(obj[oldKey], oldPath, newPath, index + 1);
|
|
114673
115114
|
return updated;
|
|
114674
115115
|
}
|
|
114675
|
-
function copyWithDeleteImpl(obj,
|
|
114676
|
-
var key =
|
|
114677
|
-
if (index + 1 ===
|
|
115116
|
+
function copyWithDeleteImpl(obj, path31, index) {
|
|
115117
|
+
var key = path31[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
|
|
115118
|
+
if (index + 1 === path31.length)
|
|
114678
115119
|
return isArrayImpl(updated) ? updated.splice(key, 1) : delete updated[key], updated;
|
|
114679
|
-
updated[key] = copyWithDeleteImpl(obj[key],
|
|
115120
|
+
updated[key] = copyWithDeleteImpl(obj[key], path31, index + 1);
|
|
114680
115121
|
return updated;
|
|
114681
115122
|
}
|
|
114682
115123
|
function shouldSuspendImpl() {
|
|
@@ -124700,29 +125141,29 @@ Check the top-level render call using <` + componentName2 + ">.");
|
|
|
124700
125141
|
var didWarnAboutNestedUpdates = false;
|
|
124701
125142
|
var didWarnAboutFindNodeInStrictMode = {};
|
|
124702
125143
|
var overrideHookState = null, overrideHookStateDeletePath = null, overrideHookStateRenamePath = null, overrideProps = null, overridePropsDeletePath = null, overridePropsRenamePath = null, scheduleUpdate = null, scheduleRetry = null, setErrorHandler = null, setSuspenseHandler = null;
|
|
124703
|
-
overrideHookState = function(fiber, id,
|
|
125144
|
+
overrideHookState = function(fiber, id, path31, value) {
|
|
124704
125145
|
id = findHook(fiber, id);
|
|
124705
|
-
id !== null && (
|
|
125146
|
+
id !== null && (path31 = copyWithSetImpl(id.memoizedState, path31, 0, value), id.memoizedState = path31, id.baseState = path31, fiber.memoizedProps = assign2({}, fiber.memoizedProps), path31 = enqueueConcurrentRenderForLane(fiber, 2), path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2));
|
|
124706
125147
|
};
|
|
124707
|
-
overrideHookStateDeletePath = function(fiber, id,
|
|
125148
|
+
overrideHookStateDeletePath = function(fiber, id, path31) {
|
|
124708
125149
|
id = findHook(fiber, id);
|
|
124709
|
-
id !== null && (
|
|
125150
|
+
id !== null && (path31 = copyWithDeleteImpl(id.memoizedState, path31, 0), id.memoizedState = path31, id.baseState = path31, fiber.memoizedProps = assign2({}, fiber.memoizedProps), path31 = enqueueConcurrentRenderForLane(fiber, 2), path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2));
|
|
124710
125151
|
};
|
|
124711
125152
|
overrideHookStateRenamePath = function(fiber, id, oldPath, newPath) {
|
|
124712
125153
|
id = findHook(fiber, id);
|
|
124713
125154
|
id !== null && (oldPath = copyWithRename(id.memoizedState, oldPath, newPath), id.memoizedState = oldPath, id.baseState = oldPath, fiber.memoizedProps = assign2({}, fiber.memoizedProps), oldPath = enqueueConcurrentRenderForLane(fiber, 2), oldPath !== null && scheduleUpdateOnFiber(oldPath, fiber, 2));
|
|
124714
125155
|
};
|
|
124715
|
-
overrideProps = function(fiber,
|
|
124716
|
-
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps,
|
|
125156
|
+
overrideProps = function(fiber, path31, value) {
|
|
125157
|
+
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path31, 0, value);
|
|
124717
125158
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
124718
|
-
|
|
124719
|
-
|
|
125159
|
+
path31 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
125160
|
+
path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2);
|
|
124720
125161
|
};
|
|
124721
|
-
overridePropsDeletePath = function(fiber,
|
|
124722
|
-
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps,
|
|
125162
|
+
overridePropsDeletePath = function(fiber, path31) {
|
|
125163
|
+
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path31, 0);
|
|
124723
125164
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
124724
|
-
|
|
124725
|
-
|
|
125165
|
+
path31 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
125166
|
+
path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2);
|
|
124726
125167
|
};
|
|
124727
125168
|
overridePropsRenamePath = function(fiber, oldPath, newPath) {
|
|
124728
125169
|
fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
|
|
@@ -126361,11 +126802,38 @@ init_config_profile();
|
|
|
126361
126802
|
init_features_resolve();
|
|
126362
126803
|
init_generate();
|
|
126363
126804
|
|
|
126805
|
+
// src/cli/run-max-iterations.ts
|
|
126806
|
+
function parseMaxIterationsFlag(raw) {
|
|
126807
|
+
if (raw === undefined)
|
|
126808
|
+
return { ok: true, value: undefined };
|
|
126809
|
+
const value = Number.parseInt(raw, 10);
|
|
126810
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
126811
|
+
return { ok: false, message: "--max-iterations must be a positive integer" };
|
|
126812
|
+
}
|
|
126813
|
+
return { ok: true, value };
|
|
126814
|
+
}
|
|
126815
|
+
function applyMaxIterationsFlag(config2, flag) {
|
|
126816
|
+
if (flag === undefined)
|
|
126817
|
+
return { ...config2, execution: { ...config2.execution } };
|
|
126818
|
+
return { ...config2, execution: { ...config2.execution, maxIterations: flag } };
|
|
126819
|
+
}
|
|
126820
|
+
|
|
126364
126821
|
// src/cli/run-mode.ts
|
|
126365
126822
|
function resolveUseHeadless(input) {
|
|
126366
126823
|
return !input.isTTY || input.headlessFlag || input.headlessEnv || input.formatterMode === "json";
|
|
126367
126824
|
}
|
|
126368
126825
|
|
|
126826
|
+
// src/cli/run-parallel.ts
|
|
126827
|
+
function parseParallelFlag(raw) {
|
|
126828
|
+
if (raw === undefined)
|
|
126829
|
+
return { ok: true, value: undefined };
|
|
126830
|
+
const value = Number.parseInt(raw, 10);
|
|
126831
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
126832
|
+
return { ok: false, message: "--parallel must be a positive integer (omit it to run sequentially)" };
|
|
126833
|
+
}
|
|
126834
|
+
return { ok: true, value };
|
|
126835
|
+
}
|
|
126836
|
+
|
|
126369
126837
|
// bin/nax.ts
|
|
126370
126838
|
init_status_dispatch();
|
|
126371
126839
|
|
|
@@ -126384,14 +126852,14 @@ function resolveEffective(detected, configPatterns) {
|
|
|
126384
126852
|
return "detected";
|
|
126385
126853
|
return "none";
|
|
126386
126854
|
}
|
|
126387
|
-
async function loadRawConfig(
|
|
126388
|
-
const f = Bun.file(
|
|
126855
|
+
async function loadRawConfig(path31) {
|
|
126856
|
+
const f = Bun.file(path31);
|
|
126389
126857
|
if (!await f.exists())
|
|
126390
126858
|
return {};
|
|
126391
126859
|
return JSON.parse(await f.text());
|
|
126392
126860
|
}
|
|
126393
|
-
async function writeRawConfig(
|
|
126394
|
-
await Bun.write(
|
|
126861
|
+
async function writeRawConfig(path31, data) {
|
|
126862
|
+
await Bun.write(path31, `${JSON.stringify(data, null, 2)}
|
|
126395
126863
|
`);
|
|
126396
126864
|
}
|
|
126397
126865
|
function deepSet(obj, keyPath, value) {
|
|
@@ -131475,8 +131943,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
|
|
|
131475
131943
|
}
|
|
131476
131944
|
|
|
131477
131945
|
// node_modules/ink/build/components/ErrorOverview.js
|
|
131478
|
-
var cleanupPath = (
|
|
131479
|
-
return
|
|
131946
|
+
var cleanupPath = (path31) => {
|
|
131947
|
+
return path31?.replace(`file://${cwd()}/`, "");
|
|
131480
131948
|
};
|
|
131481
131949
|
var stackUtils = new import_stack_utils.default({
|
|
131482
131950
|
cwd: cwd(),
|
|
@@ -134517,7 +134985,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
|
|
|
134517
134985
|
});
|
|
134518
134986
|
process.exit(exitCode);
|
|
134519
134987
|
});
|
|
134520
|
-
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations"
|
|
134988
|
+
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations").option("--max-cost <usd>", "Override cost limit (USD) for this run \u2014 aborts execution when exceeded").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (omit = sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--no-spec-lint", "Plan even when the spec declares sections that extract to nothing").option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false).option("--force", "Force overwrite existing prd.json when using --plan", false).option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).option("--profile <name>", "Profile(s) to overlay (comma-separated or repeated; later overrides earlier)", collectProfile, []).option("--schedule <when>", "Defer run start until <when> (e.g. 30m, 1h30m, 17:00, 2026-07-02T02:00)").option("--compare <agents>", "Bake-off mode: comma-separated list of contestant agents (e.g. claude,codex)").option("--fresh", "Ignore any existing checkpoint.jsonl and re-run every incomplete story from scratch", false).addOption(new Option("--no-resume", "Alias for --fresh: never auto-resume from a prior checkpoint").default("__UNSET__")).action(async (options) => {
|
|
134521
134989
|
try {
|
|
134522
134990
|
validateFeatureName(options.feature);
|
|
134523
134991
|
} catch (err) {
|
|
@@ -134531,6 +134999,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
134531
134999
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
134532
135000
|
process.exit(1);
|
|
134533
135001
|
}
|
|
135002
|
+
const maxIterationsFlag = parseMaxIterationsFlag(options.maxIterations);
|
|
135003
|
+
if (!maxIterationsFlag.ok) {
|
|
135004
|
+
console.error(source_default.red(maxIterationsFlag.message));
|
|
135005
|
+
process.exit(1);
|
|
135006
|
+
}
|
|
135007
|
+
const parallelFlag = parseParallelFlag(options.parallel);
|
|
135008
|
+
if (!parallelFlag.ok) {
|
|
135009
|
+
console.error(source_default.red(parallelFlag.message));
|
|
135010
|
+
process.exit(1);
|
|
135011
|
+
}
|
|
135012
|
+
const parallel = parallelFlag.value;
|
|
134534
135013
|
try {
|
|
134535
135014
|
const { assertCompareAgentExclusive: assertCompareAgentExclusive2 } = await Promise.resolve().then(() => (init_preflight(), exports_preflight));
|
|
134536
135015
|
assertCompareAgentExclusive2({ compare: options.compare, agent: options.agent });
|
|
@@ -134720,12 +135199,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
134720
135199
|
config2.agent ??= {};
|
|
134721
135200
|
config2.agent.default = options.agent;
|
|
134722
135201
|
}
|
|
134723
|
-
|
|
134724
|
-
if (!Number.isFinite(maxIterations) || maxIterations < 1) {
|
|
134725
|
-
console.error(source_default.red("--max-iterations must be a positive integer"));
|
|
134726
|
-
process.exit(1);
|
|
134727
|
-
}
|
|
134728
|
-
config2.execution.maxIterations = maxIterations;
|
|
135202
|
+
config2 = applyMaxIterationsFlag(config2, maxIterationsFlag.value);
|
|
134729
135203
|
if (options.maxCost !== undefined) {
|
|
134730
135204
|
const maxCost = Number(options.maxCost);
|
|
134731
135205
|
if (!Number.isFinite(maxCost) || maxCost <= 0) {
|
|
@@ -134765,15 +135239,6 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
134765
135239
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
134766
135240
|
}
|
|
134767
135241
|
const statusFilePath = join142(outputDir, "status.json");
|
|
134768
|
-
let parallel;
|
|
134769
|
-
if (options.parallel !== undefined) {
|
|
134770
|
-
parallel = Number.parseInt(options.parallel, 10);
|
|
134771
|
-
if (Number.isNaN(parallel) || parallel < 0) {
|
|
134772
|
-
tuiInstance?.unmount();
|
|
134773
|
-
console.error(source_default.red("--parallel must be a non-negative integer"));
|
|
134774
|
-
process.exit(1);
|
|
134775
|
-
}
|
|
134776
|
-
}
|
|
134777
135242
|
if (scheduleGate.target) {
|
|
134778
135243
|
const scheduleController = new AbortController;
|
|
134779
135244
|
const onSigint = () => scheduleController.abort();
|
|
@@ -135205,8 +135670,8 @@ configProfileCmd.command("current").description("Show the currently active profi
|
|
|
135205
135670
|
});
|
|
135206
135671
|
configProfileCmd.command("create <name>").description("Create a new empty profile").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (name, options) => {
|
|
135207
135672
|
try {
|
|
135208
|
-
const
|
|
135209
|
-
console.log(`Created profile at: ${
|
|
135673
|
+
const path31 = await profileCreateCommand(name, options.dir);
|
|
135674
|
+
console.log(`Created profile at: ${path31}`);
|
|
135210
135675
|
} catch (err) {
|
|
135211
135676
|
console.error(source_default.red(`Error: ${err.message}`));
|
|
135212
135677
|
process.exit(1);
|