@nathapp/nax 0.82.1 → 0.82.2

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.
Files changed (3) hide show
  1. package/README.md +75 -42
  2. package/dist/nax.js +922 -573
  3. 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.1",
2671
+ version: "0.82.2",
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 true;
18404
- return segments[0] === ".nax" && segments[1] === "features" && segments[segments.length - 1] === "prd.json";
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))}. A command whose every segment matches a granted form is checked further: paths ` + "and redirect targets must stay inside the repository root, and `.git/` access, denied flags, unexpanded " + "`$VAR`, glob or brace characters, `~`, and a bare or option-shaped `cd` are refused without asking. A command " + "outside the granted forms, or one using a construct that cannot be analysed (e.g. command or process " + "substitution, backticks, here-documents, subshells, `2>&1`, `#` comments), 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. A command matching a deny rule is refused without asking unless it cannot " + "be analysed. Each segment of a `&&`/`||`/`;`/`|` chain is checked separately. " + BACKGROUND_PROCESSES_KILLED_SENTENCE;
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
- await writeFile(target, source.replace(oldString, newString), "utf8");
19848
- return { content: `edited ${target}` };
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("an empty command");
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 refused("an unbalanced single quote");
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 refused("an unbalanced double quote");
21062
+ return refusedHere("an unbalanced double quote");
20996
21063
  const inner = command.slice(i + 1, end);
20997
21064
  if (inner.includes("$("))
20998
- return refused("a command substitution `$(...)`");
21065
+ return refusedHere("a command substitution `$(...)`");
20999
21066
  if (inner.includes("`"))
21000
- return refused("a backtick command substitution");
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 refused("a trailing backslash");
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 refused("a subshell `( ... )`");
21084
+ return refusedHere("a subshell `( ... )`");
21018
21085
  if (char === "!" && !started)
21019
- return refused("a `!` negation");
21086
+ return refusedHere("a `!` negation");
21020
21087
  if (char === "#" && !started)
21021
- return refused("a `#` comment");
21088
+ return refusedHere("a `#` comment");
21022
21089
  if (char === "$" && next === "(")
21023
- return refused("a command substitution `$(...)`");
21090
+ return refusedHere("a command substitution `$(...)`");
21024
21091
  if (char === "`")
21025
- return refused("a backtick command substitution");
21092
+ return refusedHere("a backtick command substitution");
21026
21093
  if ((char === "<" || char === ">") && next === "(") {
21027
- return refused("a process substitution `<(...)` / `>(...)`");
21094
+ return refusedHere("a process substitution `<(...)` / `>(...)`");
21028
21095
  }
21029
21096
  if (char === "<" && next === "<")
21030
- return refused("a here-document `<<`");
21097
+ return refusedHere("a here-document `<<`");
21031
21098
  if ((char === "<" || char === ">") && next === "&") {
21032
- return refused("file-descriptor duplication (`2>&1`)");
21099
+ return refusedHere("file-descriptor duplication (`2>&1`)");
21033
21100
  }
21034
21101
  if (char === "&" && next === ">")
21035
- return refused("the `&>` redirection form");
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 refused(error49);
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 refused(error49);
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 refused(error49);
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 refused(error48);
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 { refusal: deny(`cd target "${cdResult.text}" is option-shaped, and this gate does not model it`) };
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
- if (lexed.kind === "refused") {
21260
- 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);
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 lexed.segments) {
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
- for (const segment of lexed.segments) {
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
- if (isNaxOwnedWritePath(rel))
21310
- return candidate;
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(`${tool} command names "${hit}", which nax owns and no tool may modify -- ` + "change it through nax rather than by writing its file");
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(`${tool} command redirects into "${hit}", which nax owns and no tool may modify -- ` + "change it through nax rather than by writing its file");
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 header2 = `[${bounded2 ? `${lineCount}+` : `${lineCount}`} lines]`;
22650
- return { content: prefix === "" ? header2 : `${header2}
22651
- ${prefix}` };
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 trailingNewline = body.endsWith(`
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 header = `[lines ${offset}-${endLine} of ${totalLabel}]
22686
- `;
22687
- return { content: `${header}${selected}` };
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 intermediate lists. 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.",
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
- return {
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 (calls.length === 0)
24657
+ if (partialFlushed)
24455
24658
  return;
24456
- await mkdir7(opts.dir, { recursive: true });
24457
- const body = JSON.stringify({
24458
- schemaVersion: TOOL_AUDIT_SCHEMA_VERSION,
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),
@@ -26847,7 +27050,7 @@ var init_schemas_execution = __esm(() => {
26847
27050
  });
26848
27051
 
26849
27052
  // 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;
27053
+ 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
27054
  var init_schemas_infra = __esm(() => {
26852
27055
  init_zod();
26853
27056
  init_agent_defaults();
@@ -27098,10 +27301,19 @@ var init_schemas_infra = __esm(() => {
27098
27301
  PrecheckConfigSchema = exports_external.object({
27099
27302
  storySizeGate: StorySizeGateConfigSchema
27100
27303
  });
27304
+ PROMPT_OVERRIDE_ROLES = ["no-test", "test-writer", "implementer", "verifier", "tdd-simple"];
27101
27305
  PromptsConfigSchema = exports_external.object({
27102
- overrides: exports_external.record(exports_external.string().refine((key) => ["no-test", "test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
27103
- message: "Role must be one of: no-test, test-writer, implementer, verifier, single-session, tdd-simple"
27104
- }), exports_external.string().min(1, "Override path must be non-empty")).optional(),
27306
+ overrides: exports_external.record(exports_external.string(), exports_external.string().min(1, "Override path must be non-empty")).superRefine((overrides, ctx) => {
27307
+ for (const key of Object.keys(overrides)) {
27308
+ if (!PROMPT_OVERRIDE_ROLES.includes(key)) {
27309
+ ctx.addIssue({
27310
+ code: "custom",
27311
+ message: `Role must be one of: ${PROMPT_OVERRIDE_ROLES.join(", ")}`,
27312
+ path: [key]
27313
+ });
27314
+ }
27315
+ }
27316
+ }).optional(),
27105
27317
  behavioralGuardrails: exports_external.enum(["off", "lite", "strict"]).default("lite")
27106
27318
  });
27107
27319
  ProjectProfileSchema = exports_external.object({
@@ -27477,7 +27689,7 @@ var init_schemas3 = __esm(() => {
27477
27689
  agents: { enabled: true, strategy: "off", profiles: [] }
27478
27690
  }),
27479
27691
  execution: ExecutionConfigSchema.default({
27480
- maxIterations: 10,
27692
+ maxIterations: 20,
27481
27693
  iterationDelayMs: 2000,
27482
27694
  costLimit: 30,
27483
27695
  sessionTimeoutSeconds: 3600,
@@ -27522,8 +27734,7 @@ var init_schemas3 = __esm(() => {
27522
27734
  },
27523
27735
  autofix: {
27524
27736
  enabled: true,
27525
- maxAttempts: 3,
27526
- enforceTestWriterIsolation: true
27737
+ maxAttempts: 3
27527
27738
  },
27528
27739
  forceExit: false,
27529
27740
  detectOpenHandles: true,
@@ -40501,23 +40712,6 @@ function lintDiagnosticToFinding(d, workdir, tool) {
40501
40712
  var init_lint = __esm(() => {
40502
40713
  init_path_utils();
40503
40714
  });
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
40715
  // src/findings/adapters/test-failure.ts
40522
40716
  function testFailureToFinding(failure) {
40523
40717
  const frames = (failure.stackTrace ?? []).slice(0, MAX_FRAMES_IN_MESSAGE);
@@ -41362,7 +41556,7 @@ function buildBehavioralGuardrailsSection(role, level, _variant, _isolation) {
41362
41556
  if (role === "test-writer") {
41363
41557
  return buildTestWriterGuardrails(level);
41364
41558
  }
41365
- if (role === "single-session" || role === "tdd-simple" || role === "batch") {
41559
+ if (role === "tdd-simple" || role === "batch") {
41366
41560
  return buildCombinedGuardrails(level);
41367
41561
  }
41368
41562
  return buildImplementerGuardrails(level);
@@ -41486,7 +41680,7 @@ ${body}`;
41486
41680
  }
41487
41681
  var HERMETIC_ROLES, LANGUAGE_GUIDANCE;
41488
41682
  var init_hermetic = __esm(() => {
41489
- HERMETIC_ROLES = new Set(["test-writer", "implementer", "tdd-simple", "batch", "single-session"]);
41683
+ HERMETIC_ROLES = new Set(["test-writer", "implementer", "tdd-simple", "batch"]);
41490
41684
  LANGUAGE_GUIDANCE = {
41491
41685
  go: "Define interfaces for external dependencies. Use constructor injection. Test with interface mocks \u2014 no real I/O in tests.",
41492
41686
  rust: "Use trait objects or generics for external deps. Mock with the mockall crate. Use #[cfg(test)] modules.",
@@ -41682,11 +41876,6 @@ isolation scope: Implement source code in src/ to make tests pass. Do not modify
41682
41876
  return `${header}
41683
41877
 
41684
41878
  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
41879
  }
41691
41880
  return `${header}
41692
41881
 
@@ -41727,7 +41916,11 @@ freely. Every other path under \`.nax/\` stays off limits.
41727
41916
  - A test under \`.nax/\` is NOT a reason to skip writing source-tree tests. \`.nax/\` is generated
41728
41917
  scaffolding, not real coverage of the package's code.
41729
41918
  - A source-tree test is NOT a reason to remove a test under \`.nax/\`. The two serve different
41730
- purposes and must coexist.`;
41919
+ purposes and must coexist.
41920
+
41921
+ nax updates \`.nax/features/<feature>/prd.json\` itself during a run, so it shows as modified in
41922
+ \`git status\` \u2014 do not diff or revert it: this story's criteria are already in this prompt, and if you
41923
+ need its contents, read it with the \`Read\` tool; shell commands that name it may be refused.`;
41731
41924
  }
41732
41925
 
41733
41926
  // src/prompts/sections/out-of-scope.ts
@@ -41891,25 +42084,6 @@ Instructions:
41891
42084
  - Do NOT perform semantic acceptance review; semantic/adversarial review stages own acceptance criteria and broad code-quality findings
41892
42085
  - Write a detailed verdict with reasoning
41893
42086
  - 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
42087
  }
41914
42088
  if (role === "batch") {
41915
42089
  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 +42143,11 @@ or is read by another step. A failed run's scratchpad is retained for inspection
41969
42143
  before failing outlive that run, and are cleared at the next run's start. Treat every file as disposable,
41970
42144
  and overwrite freely.
41971
42145
 
42146
+ To try a snippet that imports project code or dependencies, write it under \`${dir}\` with
42147
+ \`ScratchpadWrite\` and run it from there: relative imports resolve from \`${dir}\`, so prefer the
42148
+ project's package names or path aliases where it has them. A script written outside the repository,
42149
+ such as in \`/tmp\`, cannot resolve the project's modules, and the file tools cannot write there.
42150
+
41972
42151
  \`${dir}\` is the one directory under \`.nax/\` you may write to. Every other path under \`.nax/\`
41973
42152
  must still never be moved, renamed, or deleted.`;
41974
42153
  }
@@ -42199,7 +42378,7 @@ An adversarial reviewer will audit your tests after implementation and BLOCK the
42199
42378
  }
42200
42379
  var AUTHORING_ROLES;
42201
42380
  var init_test_quality = __esm(() => {
42202
- AUTHORING_ROLES = new Set(["test-writer", "single-session", "tdd-simple", "batch"]);
42381
+ AUTHORING_ROLES = new Set(["test-writer", "tdd-simple", "batch"]);
42203
42382
  });
42204
42383
 
42205
42384
  // src/prompts/sections/verdict.ts
@@ -42399,7 +42578,7 @@ ACCEPTANCE TEST FILE: ${p.acceptanceTestPath}
42399
42578
 
42400
42579
  SOURCE FILES (auto-detected from imports, up to ${p.maxFileLines} lines each):
42401
42580
  ${p.sourceFilesSection}
42402
- ${p.verdictSection}
42581
+
42403
42582
  Respond with ONLY a JSON object in this exact format (no markdown, no extra text):
42404
42583
  ${responseSchema}`;
42405
42584
  }
@@ -42478,16 +42657,10 @@ ${f.content}
42478
42657
  \`\`\``).join(`
42479
42658
 
42480
42659
  `) : "(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
42660
  return this.buildDiagnosisPromptTemplate({
42487
42661
  truncatedOutput,
42488
42662
  acceptanceTestPath: p.acceptanceTestPath ?? "(path unavailable \u2014 inspect test output for file references)",
42489
42663
  sourceFilesSection,
42490
- verdictSection,
42491
42664
  maxFileLines: MAX_FILE_LINES
42492
42665
  });
42493
42666
  }
@@ -53204,8 +53377,8 @@ var init_version = __esm(() => {
53204
53377
  NAX_AI_VERSION = CATALOG_VERSION;
53205
53378
  NAX_COMMIT = (() => {
53206
53379
  try {
53207
- if (/^[0-9a-f]{6,10}$/.test("3e4ec026"))
53208
- return "3e4ec026";
53380
+ if (/^[0-9a-f]{6,10}$/.test("7c21771a"))
53381
+ return "7c21771a";
53209
53382
  } catch {}
53210
53383
  try {
53211
53384
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -53764,7 +53937,7 @@ function attachCostSubscriber(bus, aggregator, runId, projectKey) {
53764
53937
  offCompleted();
53765
53938
  };
53766
53939
  }
53767
- var _costSubscriberDeps, COST_ROW_SCHEMA_VERSION = 6;
53940
+ var _costSubscriberDeps, COST_ROW_SCHEMA_VERSION = 7;
53768
53941
  var init_cost2 = __esm(() => {
53769
53942
  init_agents();
53770
53943
  init_version();
@@ -53886,8 +54059,33 @@ function toUsageEntry(event, runId) {
53886
54059
  costUsd: event.costUsd
53887
54060
  };
53888
54061
  }
53889
- function attachUsageAuditSubscriber(bus, auditor, runId) {
53890
- return bus.onAgentStream((event) => {
54062
+ function toOneShotEntry(event, runId) {
54063
+ if (event.kind !== "complete")
54064
+ return null;
54065
+ const tu = event.tokenUsage;
54066
+ const wireExact = event.exactCostUsd;
54067
+ const costUsd = typeof wireExact === "number" && Number.isFinite(wireExact) ? wireExact : event.estimatedCostUsd;
54068
+ if (!tu && (costUsd ?? 0) === 0)
54069
+ return null;
54070
+ return {
54071
+ ts: event.timestamp,
54072
+ runId,
54073
+ scopeId: event.scopeId,
54074
+ streamCallId: event.callId ?? "one-shot",
54075
+ sessionName: event.sessionName,
54076
+ storyId: event.storyId,
54077
+ stage: event.stage,
54078
+ agentName: event.agentName,
54079
+ cadence: "one-shot",
54080
+ input: tu?.inputTokens,
54081
+ output: tu?.outputTokens,
54082
+ cacheRead: tu?.cacheReadInputTokens,
54083
+ cacheWrite: tu?.cacheCreationInputTokens,
54084
+ costUsd
54085
+ };
54086
+ }
54087
+ function attachUsageAuditSubscriber(bus, dispatchEvents, auditor, runId) {
54088
+ const offStream = bus.onAgentStream((event) => {
53891
54089
  switch (event.kind) {
53892
54090
  case "agent.usage_update":
53893
54091
  auditor.record(toUsageEntry(event, runId));
@@ -53896,6 +54094,15 @@ function attachUsageAuditSubscriber(bus, auditor, runId) {
53896
54094
  break;
53897
54095
  }
53898
54096
  });
54097
+ const offDispatch = dispatchEvents.onDispatch((event) => {
54098
+ const entry = toOneShotEntry(event, runId);
54099
+ if (entry)
54100
+ auditor.record(entry);
54101
+ });
54102
+ return () => {
54103
+ offStream();
54104
+ offDispatch();
54105
+ };
53899
54106
  }
53900
54107
 
53901
54108
  // src/runtime/middleware/index.ts
@@ -53907,15 +54114,255 @@ var init_middleware = __esm(() => {
53907
54114
  init_logging();
53908
54115
  });
53909
54116
 
54117
+ // src/runtime/usage-auditor.ts
54118
+ import { appendFileSync as appendFileSync2 } from "fs";
54119
+ import { mkdir as mkdir16 } from "fs/promises";
54120
+ import { join as join45 } from "path";
54121
+ function createNoOpUsageAuditor() {
54122
+ return {
54123
+ record() {},
54124
+ async flush() {}
54125
+ };
54126
+ }
54127
+ function deriveSessionRole(sessionName) {
54128
+ for (const role of ROLES_BY_DESCENDING_LENGTH) {
54129
+ if (sessionName === role || sessionName.endsWith(`-${role}`))
54130
+ return role;
54131
+ }
54132
+ return;
54133
+ }
54134
+
54135
+ class UsageAuditor {
54136
+ _queue = Promise.resolve();
54137
+ _dirCreated = false;
54138
+ _dir;
54139
+ _jsonlPath;
54140
+ constructor(runId, dir) {
54141
+ this._dir = dir;
54142
+ this._jsonlPath = join45(dir, `${runId}.jsonl`);
54143
+ }
54144
+ record(entry) {
54145
+ this._queue = this._queue.then(() => this._writeEntry(entry)).catch((err) => {
54146
+ const sysErr = err;
54147
+ getSafeLogger()?.warn("audit", "usage-audit write failed", {
54148
+ path: this._jsonlPath,
54149
+ error: errorMessage(err),
54150
+ code: sysErr?.code,
54151
+ errno: sysErr?.errno,
54152
+ syscall: sysErr?.syscall,
54153
+ ts: entry.ts,
54154
+ storyId: entry.storyId,
54155
+ sessionName: entry.sessionName,
54156
+ agentName: entry.agentName,
54157
+ stage: entry.stage,
54158
+ streamCallId: entry.streamCallId
54159
+ });
54160
+ });
54161
+ }
54162
+ async _writeEntry(entry) {
54163
+ if (!this._dirCreated) {
54164
+ await mkdir16(this._dir, { recursive: true });
54165
+ this._dirCreated = true;
54166
+ }
54167
+ const row = { ...entry, sessionRole: deriveSessionRole(entry.sessionName) };
54168
+ await _usageAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(row)}
54169
+ `);
54170
+ }
54171
+ async flush() {
54172
+ await this._queue;
54173
+ }
54174
+ }
54175
+ var _usageAuditorDeps, ROLES_BY_DESCENDING_LENGTH;
54176
+ var init_usage_auditor = __esm(() => {
54177
+ init_logger2();
54178
+ init_session_role();
54179
+ _usageAuditorDeps = {
54180
+ appendLine: async (path6, data) => {
54181
+ appendFileSync2(path6, data, "utf8");
54182
+ }
54183
+ };
54184
+ ROLES_BY_DESCENDING_LENGTH = [...KNOWN_SESSION_ROLES].sort((a, b) => b.length - a.length);
54185
+ });
54186
+
54187
+ // src/runtime/in-flight-usage.ts
54188
+ function hasSpend(entry) {
54189
+ const { input, output, cacheRead, cacheWrite } = entry.tokens;
54190
+ return entry.costUsd > 0 || input + output + cacheRead + cacheWrite > 0;
54191
+ }
54192
+ function attachInFlightUsageTracker(streamBus, dispatchEvents) {
54193
+ const entries = new Map;
54194
+ const latestEndedBySession = new Map;
54195
+ let endedSeq = 0;
54196
+ const entryFor = (event) => {
54197
+ let entry = entries.get(event.callId);
54198
+ if (entry === undefined) {
54199
+ entry = {
54200
+ streamCallId: event.callId,
54201
+ agentName: event.agentName,
54202
+ model: "unknown",
54203
+ sessionName: event.sessionName,
54204
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
54205
+ costUsd: 0,
54206
+ roundTrips: 0
54207
+ };
54208
+ entries.set(event.callId, entry);
54209
+ }
54210
+ entry.agentName = event.agentName;
54211
+ entry.sessionName = event.sessionName;
54212
+ if (event.storyId !== undefined)
54213
+ entry.storyId = event.storyId;
54214
+ if (event.stage !== undefined)
54215
+ entry.stage = event.stage;
54216
+ if (event.scopeId !== undefined)
54217
+ entry.scopeId = event.scopeId;
54218
+ return entry;
54219
+ };
54220
+ const onUsageUpdate = (event) => {
54221
+ if (event.cadence !== "round-trip")
54222
+ return;
54223
+ const entry = entryFor(event);
54224
+ entry.tokens.input += event.inputTokens ?? 0;
54225
+ entry.tokens.output += event.outputTokens ?? 0;
54226
+ entry.tokens.cacheRead += event.cacheRead ?? 0;
54227
+ entry.tokens.cacheWrite += event.cacheWrite ?? 0;
54228
+ entry.costUsd += event.costUsd ?? 0;
54229
+ if (event.roundTrip !== undefined)
54230
+ entry.roundTrips += 1;
54231
+ };
54232
+ const onCallStarted = (event) => {
54233
+ const entry = entryFor(event);
54234
+ entry.model = event.model === "" ? "unknown" : event.model;
54235
+ };
54236
+ const forget = (callId) => {
54237
+ entries.delete(callId);
54238
+ for (const [sessionName, remembered] of latestEndedBySession) {
54239
+ if (remembered === callId)
54240
+ latestEndedBySession.delete(sessionName);
54241
+ }
54242
+ };
54243
+ const onCallEnded = (event) => {
54244
+ latestEndedBySession.set(event.sessionName, event.callId);
54245
+ if (event.status === "success" || event.status === "timeout") {
54246
+ forget(event.callId);
54247
+ return;
54248
+ }
54249
+ const existing2 = entries.get(event.callId);
54250
+ if (existing2 === undefined)
54251
+ return;
54252
+ const entry = entryFor(event);
54253
+ entry.endedStatus = event.status;
54254
+ endedSeq += 1;
54255
+ entry.endedSeq = endedSeq;
54256
+ };
54257
+ const onDispatch = (event) => {
54258
+ if (event.kind !== "session-turn")
54259
+ return;
54260
+ const callId = latestEndedBySession.get(event.sessionName);
54261
+ if (callId === undefined)
54262
+ return;
54263
+ const entry = entries.get(callId);
54264
+ if (entry !== undefined && entry.endedStatus === "cancelled")
54265
+ forget(callId);
54266
+ };
54267
+ const matchesScope = (entry, event) => {
54268
+ if (entry.scopeId === undefined)
54269
+ return false;
54270
+ return entry.scopeId === event.scopeId || entry.scopeId === event.callId;
54271
+ };
54272
+ const onDispatchError = (event) => {
54273
+ const hasUsage = event.tokenUsage !== undefined;
54274
+ const costUsd = event.exactCostUsd ?? event.estimatedCostUsd ?? 0;
54275
+ if (!hasUsage && !(Number.isFinite(costUsd) && costUsd > 0))
54276
+ return;
54277
+ let target;
54278
+ for (const entry of entries.values()) {
54279
+ if (entry.endedStatus === undefined || !matchesScope(entry, event))
54280
+ continue;
54281
+ if (target === undefined || (entry.endedSeq ?? 0) > (target.endedSeq ?? 0))
54282
+ target = entry;
54283
+ }
54284
+ if (target !== undefined)
54285
+ forget(target.streamCallId);
54286
+ };
54287
+ const onStream = (event) => {
54288
+ if (event.kind === "agent.usage_update")
54289
+ onUsageUpdate(event);
54290
+ else if (event.kind === "agent.call_started")
54291
+ onCallStarted(event);
54292
+ else if (event.kind === "agent.call_ended")
54293
+ onCallEnded(event);
54294
+ };
54295
+ const residuals = () => {
54296
+ const out = [];
54297
+ for (const entry of entries.values()) {
54298
+ if (!hasSpend(entry))
54299
+ continue;
54300
+ out.push({
54301
+ streamCallId: entry.streamCallId,
54302
+ agentName: entry.agentName,
54303
+ model: entry.model,
54304
+ sessionName: entry.sessionName,
54305
+ ...entry.storyId !== undefined ? { storyId: entry.storyId } : {},
54306
+ ...entry.stage !== undefined ? { stage: entry.stage } : {},
54307
+ ...entry.scopeId !== undefined ? { scopeId: entry.scopeId } : {},
54308
+ tokens: { ...entry.tokens },
54309
+ costUsd: entry.costUsd,
54310
+ roundTrips: entry.roundTrips
54311
+ });
54312
+ }
54313
+ return out;
54314
+ };
54315
+ const offStream = streamBus.onAgentStream(onStream);
54316
+ const offDispatch = dispatchEvents.onDispatch(onDispatch);
54317
+ const offError = dispatchEvents.onDispatchError(onDispatchError);
54318
+ return {
54319
+ tracker: { residuals },
54320
+ off: () => {
54321
+ offStream();
54322
+ offDispatch();
54323
+ offError();
54324
+ }
54325
+ };
54326
+ }
54327
+ function toPartialCostEvent(residual, runId, projectKey) {
54328
+ const sessionRole = deriveSessionRole(residual.sessionName);
54329
+ return {
54330
+ ts: Date.now(),
54331
+ runId,
54332
+ ...projectKey !== undefined ? { projectKey } : {},
54333
+ schemaVersion: COST_ROW_SCHEMA_VERSION,
54334
+ partial: true,
54335
+ agentName: residual.agentName,
54336
+ model: residual.model,
54337
+ ...sessionRole !== undefined ? { sessionRole } : {},
54338
+ ...residual.stage !== undefined ? { stage: residual.stage } : {},
54339
+ ...residual.storyId !== undefined ? { storyId: residual.storyId } : {},
54340
+ ...residual.scopeId !== undefined ? { scopeId: residual.scopeId } : {},
54341
+ callId: residual.streamCallId,
54342
+ tokens: { ...residual.tokens },
54343
+ roundTrips: residual.roundTrips,
54344
+ roundTripUnit: "model-call",
54345
+ costUsd: residual.costUsd,
54346
+ estimatedCostUsd: residual.costUsd,
54347
+ exactCostUsd: residual.costUsd,
54348
+ confidence: "estimated",
54349
+ durationMs: 0
54350
+ };
54351
+ }
54352
+ var init_in_flight_usage = __esm(() => {
54353
+ init_middleware();
54354
+ init_usage_auditor();
54355
+ });
54356
+
53910
54357
  // src/runtime/packages.ts
53911
- import { isAbsolute as isAbsolute15, join as join45, relative as relative17 } from "path";
54358
+ import { isAbsolute as isAbsolute15, join as join46, relative as relative17 } from "path";
53912
54359
  function packageWorkdir(view) {
53913
54360
  const { packageDir, repoRoot } = view;
53914
54361
  if (!packageDir)
53915
54362
  return repoRoot;
53916
54363
  if (!repoRoot || isAbsolute15(packageDir))
53917
54364
  return packageDir;
53918
- return join45(repoRoot, packageDir);
54365
+ return join46(repoRoot, packageDir);
53919
54366
  }
53920
54367
  function createPackageView(config2, packageDir, repoRoot, hasOverride, overlay) {
53921
54368
  const memo = new Map;
@@ -54028,7 +54475,7 @@ function storyExecRoot(view) {
54028
54475
  const segments = packageDir.split("/");
54029
54476
  if (segments[0] !== ".nax-wt" || segments.length < 2)
54030
54477
  return repoRoot;
54031
- return join45(repoRoot, segments[0], segments[1]);
54478
+ return join46(repoRoot, segments[0], segments[1]);
54032
54479
  }
54033
54480
  var _packagesDeps;
54034
54481
  var init_packages = __esm(() => {
@@ -54161,9 +54608,9 @@ var init_paths2 = __esm(() => {
54161
54608
  });
54162
54609
 
54163
54610
  // src/runtime/prompt-auditor.ts
54164
- import { appendFileSync as appendFileSync2 } from "fs";
54165
- import { mkdir as mkdir16 } from "fs/promises";
54166
- import { join as join46 } from "path";
54611
+ import { appendFileSync as appendFileSync3 } from "fs";
54612
+ import { mkdir as mkdir17 } from "fs/promises";
54613
+ import { join as join47 } from "path";
54167
54614
  function createNoOpPromptAuditor() {
54168
54615
  return {
54169
54616
  record() {},
@@ -54243,8 +54690,8 @@ class PromptAuditor {
54243
54690
  _featureDir;
54244
54691
  _turnOrdinals = new Map;
54245
54692
  constructor(runId, flushDir, featureName) {
54246
- this._featureDir = join46(flushDir, featureName);
54247
- this._jsonlPath = join46(this._featureDir, `${runId}.jsonl`);
54693
+ this._featureDir = join47(flushDir, featureName);
54694
+ this._jsonlPath = join47(this._featureDir, `${runId}.jsonl`);
54248
54695
  }
54249
54696
  record(entry) {
54250
54697
  this._enqueue(entry.callType === "run" ? { ...entry, turn: this._nextTurn(entry) } : entry);
@@ -54282,7 +54729,7 @@ class PromptAuditor {
54282
54729
  async _writeEntry(entry) {
54283
54730
  if (!this._dirCreated) {
54284
54731
  try {
54285
- await mkdir16(this._featureDir, { recursive: true });
54732
+ await mkdir17(this._featureDir, { recursive: true });
54286
54733
  } catch (err) {
54287
54734
  throw tagAuditError(err, "jsonl");
54288
54735
  }
@@ -54299,7 +54746,7 @@ class PromptAuditor {
54299
54746
  return;
54300
54747
  const filename = deriveTxtFilename(entry);
54301
54748
  try {
54302
- await _promptAuditorDeps.write(join46(this._featureDir, filename), buildTxtContent(safeEntry));
54749
+ await _promptAuditorDeps.write(join47(this._featureDir, filename), buildTxtContent(safeEntry));
54303
54750
  } catch (err) {
54304
54751
  throw tagAuditError(err, "txt");
54305
54752
  }
@@ -54313,80 +54760,10 @@ var init_prompt_auditor = __esm(() => {
54313
54760
  init_logger2();
54314
54761
  _promptAuditorDeps = {
54315
54762
  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
54763
  appendLine: async (path7, data) => {
54386
54764
  appendFileSync3(path7, data, "utf8");
54387
54765
  }
54388
54766
  };
54389
- ROLES_BY_DESCENDING_LENGTH = [...KNOWN_SESSION_ROLES].sort((a, b) => b.length - a.length);
54390
54767
  });
54391
54768
 
54392
54769
  // src/agents/factory.ts
@@ -66183,6 +66560,7 @@ __export(exports_runtime, {
66183
66560
  _usageAuditorDeps: () => _usageAuditorDeps,
66184
66561
  attachAgentIdleWatchdog: () => attachAgentIdleWatchdog,
66185
66562
  attachAgentStreamLogging: () => attachAgentStreamLogging,
66563
+ attachInFlightUsageTracker: () => attachInFlightUsageTracker,
66186
66564
  attachUsageAuditSubscriber: () => attachUsageAuditSubscriber,
66187
66565
  claimProjectIdentity: () => claimProjectIdentity,
66188
66566
  createNoOpCostAggregator: () => createNoOpCostAggregator,
@@ -66205,6 +66583,7 @@ __export(exports_runtime, {
66205
66583
  spinTerminalNotice: () => spinTerminalNotice,
66206
66584
  storyExecRoot: () => storyExecRoot,
66207
66585
  storySpendUsd: () => storySpendUsd,
66586
+ toPartialCostEvent: () => toPartialCostEvent,
66208
66587
  totalSpendUsd: () => totalSpendUsd,
66209
66588
  writeProjectIdentity: () => writeProjectIdentity
66210
66589
  });
@@ -66296,9 +66675,10 @@ function createRuntime(config2, workdir, opts) {
66296
66675
  const offCost = attachCostSubscriber(dispatchEvents, costAggregator, runId, getProjectKey(config2, workdir));
66297
66676
  const offAudit = attachAuditSubscriber(dispatchEvents, promptAuditor, runId);
66298
66677
  const offReviewAudit = attachReviewAuditSubscriber(dispatchEvents, reviewAuditor, runId);
66299
- const offUsageAudit = attachUsageAuditSubscriber(agentStreamEvents, usageAuditor, runId);
66678
+ const offUsageAudit = attachUsageAuditSubscriber(agentStreamEvents, dispatchEvents, usageAuditor, runId);
66300
66679
  const offAgentStreamLogging = attachAgentStreamLogging(agentStreamEvents, runId);
66301
66680
  const offWatchdog = attachAgentIdleWatchdog(agentStreamEvents, watchdogControllerRegistry, config2);
66681
+ const { tracker: inFlightTracker, off: offInFlightUsage } = attachInFlightUsageTracker(agentStreamEvents, dispatchEvents);
66302
66682
  const packages = createPackageRegistry(configLoader, workdir);
66303
66683
  const logger = getLogger();
66304
66684
  const quarantineMemo = createQuarantineMemo();
@@ -66366,6 +66746,7 @@ function createRuntime(config2, workdir, opts) {
66366
66746
  offUsageAudit();
66367
66747
  offAgentStreamLogging();
66368
66748
  offWatchdog();
66749
+ offInFlightUsage();
66369
66750
  if (opts?.parentSignal && parentAbortHandler) {
66370
66751
  opts.parentSignal.removeEventListener("abort", parentAbortHandler);
66371
66752
  }
@@ -66375,6 +66756,10 @@ function createRuntime(config2, workdir, opts) {
66375
66756
  sessionManager.close();
66376
66757
  await mcpPool.close();
66377
66758
  await writeMcpRollup(outputDir, buildMcpRollup({ runId, events: mcpPool.events(), withheld: mcpWithheld })).catch((error48) => logger.warn("runtime", "mcp rollup write failed", { error: String(error48) }));
66759
+ for (const residual of inFlightTracker.residuals()) {
66760
+ costAggregator.record(toPartialCostEvent(residual, runId, projectKey));
66761
+ }
66762
+ await flushOpenToolAuditSinks(runId);
66378
66763
  const results = await Promise.allSettled([
66379
66764
  promptAuditor.flush(),
66380
66765
  usageAuditor.flush(),
@@ -66394,6 +66779,7 @@ var init_runtime2 = __esm(() => {
66394
66779
  init_agent_stream_events();
66395
66780
  init_cost_aggregator();
66396
66781
  init_dispatch_events();
66782
+ init_in_flight_usage();
66397
66783
  init_middleware();
66398
66784
  init_packages();
66399
66785
  init_paths2();
@@ -66402,6 +66788,7 @@ var init_runtime2 = __esm(() => {
66402
66788
  init_session_role();
66403
66789
  init_spin_breaker();
66404
66790
  init_usage_auditor();
66791
+ init_tools();
66405
66792
  init_factory();
66406
66793
  init_manager2();
66407
66794
  init_config();
@@ -66415,6 +66802,7 @@ var init_runtime2 = __esm(() => {
66415
66802
  init_agent_stream_events();
66416
66803
  init_cost_aggregator();
66417
66804
  init_dispatch_events();
66805
+ init_in_flight_usage();
66418
66806
  init_middleware();
66419
66807
  init_packages();
66420
66808
  init_paths2();
@@ -66826,7 +67214,6 @@ var init_feature_context_filter = __esm(() => {
66826
67214
  implementer: ["all", "implementer"],
66827
67215
  "test-writer": ["all", "test-writer"],
66828
67216
  verifier: ["all", "verifier"],
66829
- "single-session": ["all", "implementer", "test-writer"],
66830
67217
  "tdd-simple": ["all", "implementer", "test-writer"],
66831
67218
  "no-test": ["all", "implementer"],
66832
67219
  batch: ["all", "implementer", "test-writer"]
@@ -68073,8 +68460,7 @@ var init_acceptance_diagnose = __esm(() => {
68073
68460
  testOutput: input.testOutput,
68074
68461
  testFileContent: input.testFileContent,
68075
68462
  acceptanceTestPath: input.acceptanceTestPath,
68076
- sourceFiles: input.sourceFiles,
68077
- semanticVerdicts: input.semanticVerdicts
68463
+ sourceFiles: input.sourceFiles
68078
68464
  });
68079
68465
  return {
68080
68466
  role: { id: "role", content: "", overridable: false },
@@ -79097,7 +79483,7 @@ var init_config_descriptions = __esm(() => {
79097
79483
  "routing.llm.mode": "Routing mode: one-shot | per-story | hybrid",
79098
79484
  "routing.llm.timeoutMs": "Timeout for LLM routing call in milliseconds",
79099
79485
  execution: "Execution limits and timeouts",
79100
- "execution.maxIterations": "Max iterations per feature run (auto-calculated if not set)",
79486
+ "execution.maxIterations": "Max iterations per feature run \u2014 each story attempt, parallel batch and the final completion pass counts one",
79101
79487
  "execution.iterationDelayMs": "Delay between iterations in milliseconds",
79102
79488
  "execution.costLimit": "Max cost in USD before pausing execution (override per run with `nax run --max-cost`)",
79103
79489
  "execution.sessionTimeoutSeconds": "Timeout per agent coding session in seconds",
@@ -79253,7 +79639,6 @@ var init_config_descriptions = __esm(() => {
79253
79639
  "prompts.overrides.test-writer": 'Path to custom test-writer prompt (e.g., ".nax/prompts/test-writer.md")',
79254
79640
  "prompts.overrides.implementer": 'Path to custom implementer prompt (e.g., ".nax/prompts/implementer.md")',
79255
79641
  "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
79642
  agent: "Agent protocol configuration (ACP-003)",
79258
79643
  "agent.protocol": "Protocol for agent communication: 'acp' (default) | 'native' | 'hybrid'",
79259
79644
  "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 +79941,7 @@ function displayConfigWithDescriptions(obj, path10, sources, indent = 0) {
79556
79941
  if (description) {
79557
79942
  console.log(`${indentStr}# prompts.overrides: ${description}`);
79558
79943
  }
79559
- const roles = ["test-writer", "implementer", "verifier", "single-session"];
79944
+ const roles = ["test-writer", "implementer", "verifier"];
79560
79945
  console.log(`${indentStr}overrides:`);
79561
79946
  for (const role of roles) {
79562
79947
  const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
@@ -79601,7 +79986,7 @@ function displayConfigWithDescriptions(obj, path10, sources, indent = 0) {
79601
79986
  if (description) {
79602
79987
  console.log(`# prompts.overrides: ${description}`);
79603
79988
  }
79604
- const roles = ["test-writer", "implementer", "verifier", "single-session"];
79989
+ const roles = ["test-writer", "implementer", "verifier"];
79605
79990
  console.log("overrides:");
79606
79991
  for (const role of roles) {
79607
79992
  const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
@@ -80630,75 +81015,6 @@ var init_hardening = __esm(() => {
80630
81015
  };
80631
81016
  });
80632
81017
 
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
81018
  // src/acceptance/index.ts
80703
81019
  var exports_acceptance = {};
80704
81020
  __export(exports_acceptance, {
@@ -80711,11 +81027,9 @@ __export(exports_acceptance, {
80711
81027
  groupStoriesByPackage: () => groupStoriesByPackage,
80712
81028
  isStubTestContent: () => isStubTestContent,
80713
81029
  loadAcceptanceTestContent: () => loadAcceptanceTestContent,
80714
- loadSemanticVerdicts: () => loadSemanticVerdicts,
80715
81030
  loadSourceFilesForDiagnosis: () => loadSourceFilesForDiagnosis,
80716
81031
  parseAcceptanceCriteria: () => parseAcceptanceCriteria,
80717
81032
  parseRefinementResponse: () => parseRefinementResponse,
80718
- persistSemanticVerdict: () => persistSemanticVerdict,
80719
81033
  refinementWouldFallback: () => refinementWouldFallback,
80720
81034
  resolveAcceptanceFeatureTestPath: () => resolveAcceptanceFeatureTestPath,
80721
81035
  resolveSuggestedPackageFeatureTestPath: () => resolveSuggestedPackageFeatureTestPath,
@@ -80729,7 +81043,6 @@ var init_acceptance2 = __esm(() => {
80729
81043
  init_generator();
80730
81044
  init_hardening();
80731
81045
  init_refinement();
80732
- init_semantic_verdict();
80733
81046
  init_test_path();
80734
81047
  });
80735
81048
 
@@ -81725,11 +82038,11 @@ var init_generate = __esm(() => {
81725
82038
  // src/cli/init-context.ts
81726
82039
  import { mkdir as mkdir21, readdir as readdir7 } from "fs/promises";
81727
82040
  import { basename as basename11, join as join74, relative as relative23, sep as sep12 } from "path";
81728
- async function bunFileExists(path12) {
81729
- return Bun.file(path12).exists();
82041
+ async function bunFileExists(path11) {
82042
+ return Bun.file(path11).exists();
81730
82043
  }
81731
- async function mkdirp(path12) {
81732
- await mkdir21(path12, { recursive: true });
82044
+ async function mkdirp(path11) {
82045
+ await mkdir21(path11, { recursive: true });
81733
82046
  }
81734
82047
  async function findFiles(dir, maxFiles = 200) {
81735
82048
  const files = [];
@@ -81792,8 +82105,8 @@ async function detectEntryPoints(projectRoot) {
81792
82105
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
81793
82106
  const found = [];
81794
82107
  for (const candidate of candidates) {
81795
- const path12 = join74(projectRoot, candidate);
81796
- if (await bunFileExists(path12)) {
82108
+ const path11 = join74(projectRoot, candidate);
82109
+ if (await bunFileExists(path11)) {
81797
82110
  found.push(candidate);
81798
82111
  }
81799
82112
  }
@@ -81803,8 +82116,8 @@ async function detectConfigFiles(projectRoot) {
81803
82116
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
81804
82117
  const found = [];
81805
82118
  for (const candidate of candidates) {
81806
- const path12 = join74(projectRoot, candidate);
81807
- if (await bunFileExists(path12)) {
82119
+ const path11 = join74(projectRoot, candidate);
82120
+ if (await bunFileExists(path11)) {
81808
82121
  found.push(candidate);
81809
82122
  }
81810
82123
  }
@@ -82633,9 +82946,9 @@ var init_scanner = __esm(() => {
82633
82946
  _scannerDeps = {
82634
82947
  discoverWorkspacePackages: (workdir) => discoverWorkspacePackages2(workdir),
82635
82948
  detectLanguage: (pkgDir) => detectLanguage(pkgDir),
82636
- readPackageJson: async (path12) => {
82949
+ readPackageJson: async (path11) => {
82637
82950
  try {
82638
- const file2 = Bun.file(path12);
82951
+ const file2 = Bun.file(path11);
82639
82952
  if (!await file2.exists())
82640
82953
  return null;
82641
82954
  return await file2.json();
@@ -82656,6 +82969,7 @@ var init_analyze = __esm(() => {
82656
82969
  function createHumanAskLink(opts) {
82657
82970
  let queue = Promise.resolve();
82658
82971
  let activeId;
82972
+ const canRemember = (req) => opts.onRemember !== undefined && req.command !== undefined;
82659
82973
  const deny3 = (decidedBy) => ({
82660
82974
  decision: "deny",
82661
82975
  decidedBy
@@ -82793,7 +83107,7 @@ function createHumanAskLink(opts) {
82793
83107
  `stage: ${req.stage}`
82794
83108
  ].join(`
82795
83109
  `),
82796
- options: OPTIONS,
83110
+ options: canRemember(req) ? [ALLOW_ONCE, ALLOW_REMEMBER, DENY] : [ALLOW_ONCE, DENY],
82797
83111
  timeout: opts.timeoutMs,
82798
83112
  fallback: "abort",
82799
83113
  createdAt: Date.now(),
@@ -82811,7 +83125,7 @@ function createHumanAskLink(opts) {
82811
83125
  if (!PERMITS.has(action)) {
82812
83126
  outcome = deny3("human");
82813
83127
  } else {
82814
- if (action === "allow-remember" && opts.onRemember) {
83128
+ if (action === "allow-remember" && canRemember(req) && opts.onRemember) {
82815
83129
  try {
82816
83130
  await opts.onRemember(req);
82817
83131
  } catch (err) {
@@ -82937,7 +83251,7 @@ function createHumanAskLink(opts) {
82937
83251
  async function cancelPendingAsk(link) {
82938
83252
  await link.cancel();
82939
83253
  }
82940
- var MAX_COMMAND_CHARS = 3500, maskedFooter = (count) => `${count} secret value(s) masked; the approved command contains them`, _askLinkDeps, OPTIONS, PERMITS;
83254
+ 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
83255
  var init_ask_link = __esm(() => {
82942
83256
  init_permissions();
82943
83257
  init_logger2();
@@ -82946,11 +83260,9 @@ var init_ask_link = __esm(() => {
82946
83260
  clearTimeout: (id) => clearTimeout(id),
82947
83261
  ASK_KEEPALIVE_MS: 60000
82948
83262
  };
82949
- OPTIONS = [
82950
- { key: "allow", label: "Allow once" },
82951
- { key: "allow-remember", label: "Allow + remember" },
82952
- { key: "deny", label: "Deny" }
82953
- ];
83263
+ ALLOW_ONCE = { key: "allow", label: "Allow once" };
83264
+ ALLOW_REMEMBER = { key: "allow-remember", label: "Allow + remember" };
83265
+ DENY = { key: "deny", label: "Deny" };
82954
83266
  PERMITS = new Set(["allow", "allow-remember"]);
82955
83267
  });
82956
83268
 
@@ -83124,10 +83436,10 @@ async function buildDispatchAskWiring(opts, deps = _dispatchAskDeps) {
83124
83436
  stage: req.stage,
83125
83437
  command: req.command ?? "",
83126
83438
  root: req.root ?? opts.repoRoot,
83127
- origin: "escalate",
83128
- matchedRule: null,
83439
+ origin: req.matchedRule !== undefined ? "askRule" : "escalate",
83440
+ matchedRule: req.matchedRule ?? null,
83129
83441
  approvedAt: new Date().toISOString(),
83130
- approvedBy: "telegram",
83442
+ approvedBy: opts.config.interaction?.plugin ?? "unknown",
83131
83443
  naxCommit: NAX_COMMIT
83132
83444
  })
83133
83445
  });
@@ -83205,6 +83517,25 @@ async function collectEffectiveRunStageModes(opts, deps = _dispatchAskDeps) {
83205
83517
  })));
83206
83518
  return collectRunStageModes([opts.rootConfig, ...opts.extraConfigs ?? [], ...packageConfigs]);
83207
83519
  }
83520
+ async function buildApprovalsSeal(opts, deps = _dispatchAskDeps) {
83521
+ const stageModes = await collectEffectiveRunStageModes({ projectDir: opts.projectDir, rootConfig: opts.rootConfig, packageDirs: opts.packageDirs }, deps);
83522
+ const forgeCapable = isForgeCapable(stageModes, opts.rootConfig.execution?.sandbox?.enabled === true);
83523
+ if (!forgeCapable)
83524
+ return async () => {};
83525
+ const approvalsFile = approvalsPath(opts.outputDir);
83526
+ const runId = opts.runId;
83527
+ return async () => {
83528
+ try {
83529
+ await deps.prepareApprovalsStore({ approvalsFile, runId, forgeCapable: true });
83530
+ } catch (error48) {
83531
+ getSafeLogger()?.warn("permissions", "[approvals] could not update the store's taint marker", {
83532
+ approvalsFile,
83533
+ forgeCapable: true,
83534
+ error: error48
83535
+ });
83536
+ }
83537
+ };
83538
+ }
83208
83539
  async function buildRunDispatchAskWiring(opts, deps = _dispatchAskDeps) {
83209
83540
  const stageModes = await collectEffectiveRunStageModes({
83210
83541
  projectDir: opts.projectDir,
@@ -83218,6 +83549,7 @@ var DEFAULT_APPROVAL_TIMEOUT_MS = 600000, APPROVAL_AUDIT_DIR = "approval-audit",
83218
83549
  var init_dispatch_ask = __esm(() => {
83219
83550
  init_command_safety();
83220
83551
  init_config();
83552
+ init_logger2();
83221
83553
  init_permissions();
83222
83554
  init_version();
83223
83555
  init_ask_link();
@@ -88334,6 +88666,11 @@ async function performTeardown(ctx) {
88334
88666
  if (ctx.pidRegistry) {
88335
88667
  await ctx.pidRegistry.killAll();
88336
88668
  }
88669
+ if (ctx.sealApprovals) {
88670
+ await ctx.sealApprovals().catch(() => {
88671
+ return;
88672
+ });
88673
+ }
88337
88674
  }
88338
88675
  function getSignalNumber(signal) {
88339
88676
  const signalMap = {
@@ -88490,7 +88827,7 @@ var init_crash_recovery = __esm(() => {
88490
88827
  });
88491
88828
 
88492
88829
  // src/execution/ensure-package-dirs.ts
88493
- import path12 from "path";
88830
+ import path11 from "path";
88494
88831
  async function ensureStoryPackageDirs(prd, workdir, deps = _ensurePackageDirsDeps) {
88495
88832
  const logger = getSafeLogger();
88496
88833
  const relToStoryId = new Map;
@@ -88503,8 +88840,8 @@ async function ensureStoryPackageDirs(prd, workdir, deps = _ensurePackageDirsDep
88503
88840
  }
88504
88841
  const created = [];
88505
88842
  for (const [rel, storyId] of relToStoryId) {
88506
- const abs = path12.resolve(workdir, rel);
88507
- const rootWithSep = workdir.endsWith(path12.sep) ? workdir : workdir + path12.sep;
88843
+ const abs = path11.resolve(workdir, rel);
88844
+ const rootWithSep = workdir.endsWith(path11.sep) ? workdir : workdir + path11.sep;
88508
88845
  if (abs !== workdir && !abs.startsWith(rootWithSep)) {
88509
88846
  logger?.warn("execution", "Skipping story workdir outside repo root", {
88510
88847
  storyId,
@@ -88655,8 +88992,8 @@ async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
88655
88992
  return false;
88656
88993
  const lines = content.split(`
88657
88994
  `);
88658
- const start = Math.max(0, triple.line - 1 - CONTEXT_LINES);
88659
- const end = Math.min(lines.length, triple.line + CONTEXT_LINES);
88995
+ const start = Math.max(0, triple.line - 1 - CONTEXT_LINES2);
88996
+ const end = Math.min(lines.length, triple.line + CONTEXT_LINES2);
88660
88997
  const window2 = lines.slice(start, end).join(`
88661
88998
  `);
88662
88999
  return normalizeWs2(window2).toLowerCase().includes(normalizeWs2(triple.quote).toLowerCase());
@@ -88681,14 +89018,14 @@ async function verifyEscalationQuotes(reason, workdir, storyId, deps = _quoteInt
88681
89018
  }
88682
89019
  return verified;
88683
89020
  }
88684
- var _quoteIntegrityDeps, CONTEXT_LINES = 3;
89021
+ var _quoteIntegrityDeps, CONTEXT_LINES2 = 3;
88685
89022
  var init_quote_integrity = __esm(() => {
88686
89023
  init_logger2();
88687
89024
  init_path_security2();
88688
89025
  _quoteIntegrityDeps = {
88689
- readFile: async (path13) => {
89026
+ readFile: async (path12) => {
88690
89027
  try {
88691
- return await Bun.file(path13).text();
89028
+ return await Bun.file(path12).text();
88692
89029
  } catch {
88693
89030
  return null;
88694
89031
  }
@@ -89128,7 +89465,7 @@ var init_escalation = __esm(() => {
89128
89465
  import { randomUUID as randomUUID10 } from "crypto";
89129
89466
  import { rename as rename5, unlink as unlink5 } from "fs/promises";
89130
89467
  import { hostname as hostname3 } from "os";
89131
- import path13 from "path";
89468
+ import path12 from "path";
89132
89469
  function getSafeLogger3() {
89133
89470
  try {
89134
89471
  return getLogger();
@@ -89208,7 +89545,7 @@ async function claimReclaimableLock(lockPath, observedContent, lockData) {
89208
89545
  return { action: "discard" };
89209
89546
  }
89210
89547
  async function acquireLock(workdir) {
89211
- const lockPath = path13.join(workdir, "nax.lock");
89548
+ const lockPath = path12.join(workdir, "nax.lock");
89212
89549
  const lockFile = Bun.file(lockPath);
89213
89550
  try {
89214
89551
  const exists2 = await lockFile.exists();
@@ -89261,7 +89598,7 @@ async function acquireLock(workdir) {
89261
89598
  }
89262
89599
  }
89263
89600
  async function releaseLock(workdir) {
89264
- const lockPath = path13.join(workdir, "nax.lock");
89601
+ const lockPath = path12.join(workdir, "nax.lock");
89265
89602
  try {
89266
89603
  await unlink5(lockPath);
89267
89604
  } catch (error48) {
@@ -89286,7 +89623,7 @@ var init_lock2 = __esm(() => {
89286
89623
  // src/execution/feature-lock.ts
89287
89624
  import { mkdir as mkdir24, rename as rename6, unlink as unlink6 } from "fs/promises";
89288
89625
  import { hostname as hostname4 } from "os";
89289
- import path14 from "path";
89626
+ import path13 from "path";
89290
89627
  function validateFeatureId2(featureId) {
89291
89628
  if (!featureId || featureId.length === 0) {
89292
89629
  throw new NaxError("Feature ID cannot be empty", "INVALID_FEATURE_ID", { stage: "feature-lock" });
@@ -89316,7 +89653,7 @@ function getSafeLogger4() {
89316
89653
  }
89317
89654
  function featureLockPath(outputDir, feature) {
89318
89655
  validateFeatureId2(feature);
89319
- return path14.join(outputDir, "features", feature, "nax.lock");
89656
+ return path13.join(outputDir, "features", feature, "nax.lock");
89320
89657
  }
89321
89658
  function lockHost() {
89322
89659
  return _featureLockDeps.host();
@@ -89377,7 +89714,7 @@ function emptyHolder(feature) {
89377
89714
  }
89378
89715
  async function acquireFeatureLock(args) {
89379
89716
  const lockPath = _featureLockDeps.featureLockPath(args.outputDir, args.feature);
89380
- const featureDir2 = path14.dirname(lockPath);
89717
+ const featureDir2 = path13.dirname(lockPath);
89381
89718
  await mkdir24(featureDir2, { recursive: true });
89382
89719
  const lockFile = Bun.file(lockPath);
89383
89720
  if (await lockFile.exists()) {
@@ -90834,7 +91171,7 @@ var init_acceptance3 = __esm(() => {
90834
91171
  });
90835
91172
 
90836
91173
  // src/pipeline/stages/acceptance-setup.ts
90837
- import path15 from "path";
91174
+ import path14 from "path";
90838
91175
  function computeACFingerprint(criteria) {
90839
91176
  const sorted = [...criteria].sort().join(`
90840
91177
  `);
@@ -90844,7 +91181,7 @@ function computeACFingerprint(criteria) {
90844
91181
  }
90845
91182
  function computeAcceptanceLayoutFingerprint(workdir, groups) {
90846
91183
  const layout = groups.map(({ testPath, stories }) => ({
90847
- testPath: path15.relative(workdir, testPath).replaceAll(path15.sep, "/"),
91184
+ testPath: path14.relative(workdir, testPath).replaceAll(path14.sep, "/"),
90848
91185
  storyIds: stories.map((story) => story.id).sort()
90849
91186
  })).sort((a, b) => a.testPath.localeCompare(b.testPath));
90850
91187
  const hasher = new Bun.CryptoHasher("sha256");
@@ -90854,14 +91191,14 @@ function computeAcceptanceLayoutFingerprint(workdir, groups) {
90854
91191
  async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
90855
91192
  const language = ctx.config.project?.language;
90856
91193
  const testPathConfig = ctx.config.acceptance.testPath;
90857
- const metaPath = path15.join(featureDir2, "acceptance-meta.json");
91194
+ const metaPath = path14.join(featureDir2, "acceptance-meta.json");
90858
91195
  const allCriteria = ctx.prd.userStories.filter(isInAcceptanceScope).flatMap((s) => s.acceptanceCriteria);
90859
91196
  const featureName = ctx.prd.feature ?? ctx.prd.featureName;
90860
91197
  const groups = await groupStoriesByPackage(ctx.prd, ctx.workdir, featureName, testPathConfig, language);
90861
91198
  const nonFixStories = groups.flatMap((g) => g.stories);
90862
91199
  const groupConfigs = new Map;
90863
91200
  for (const group of groups) {
90864
- const relativeWorkdir = path15.relative(ctx.projectDir, group.packageDir);
91201
+ const relativeWorkdir = path14.relative(ctx.projectDir, group.packageDir);
90865
91202
  let config2 = ctx.config;
90866
91203
  if (relativeWorkdir && relativeWorkdir !== ".") {
90867
91204
  try {
@@ -90906,7 +91243,6 @@ async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
90906
91243
  await _acceptanceSetupDeps.deleteFile(testPath);
90907
91244
  }
90908
91245
  }
90909
- await _acceptanceSetupDeps.deleteSemanticVerdicts(featureDir2);
90910
91246
  shouldGenerate = true;
90911
91247
  regenerated = true;
90912
91248
  } else {
@@ -91014,7 +91350,7 @@ async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
91014
91350
  testable: c.testable,
91015
91351
  storyId: c.storyId
91016
91352
  })), null, 2);
91017
- await _acceptanceSetupDeps.writeFile(path15.join(featureDir2, "acceptance-refined.json"), refinedJsonContent);
91353
+ await _acceptanceSetupDeps.writeFile(path14.join(featureDir2, "acceptance-refined.json"), refinedJsonContent);
91018
91354
  }
91019
91355
  if (sawDispatchFailure) {
91020
91356
  getSafeLogger()?.warn("acceptance-setup", "generation dispatch failed; not recording acceptance meta so the next run regenerates", { storyId: ctx.story?.id, metaPath });
@@ -91126,21 +91462,6 @@ var init_acceptance_setup = __esm(() => {
91126
91462
  throw err;
91127
91463
  }
91128
91464
  },
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
91465
  readMeta: async (metaPath) => {
91145
91466
  const f = Bun.file(metaPath);
91146
91467
  if (!await f.exists())
@@ -91345,7 +91666,6 @@ async function getDiffFilePaths(workdir, baseRef) {
91345
91666
  }
91346
91667
  var MAX_DIFF_TEXT_CHARS = 8000, HIGH_MEMORY_TELEMETRY_BYTES, STREAM_DRAIN_DEADLINE_MS = 2000, completionStage, _completionDeps;
91347
91668
  var init_completion = __esm(() => {
91348
- init_acceptance2();
91349
91669
  init_config();
91350
91670
  init_engine();
91351
91671
  init_fragments();
@@ -91465,7 +91785,6 @@ var init_completion = __esm(() => {
91465
91785
  };
91466
91786
  _completionDeps = {
91467
91787
  checkReviewGate,
91468
- persistSemanticVerdict,
91469
91788
  savePRD,
91470
91789
  getDiffText,
91471
91790
  getDiffFilePaths,
@@ -92241,9 +92560,9 @@ var init_prompt2 = __esm(() => {
92241
92560
  });
92242
92561
 
92243
92562
  // src/pipeline/stages/queue-check.ts
92244
- import path16 from "path";
92563
+ import path15 from "path";
92245
92564
  function resolvePrdPath(ctx) {
92246
- return path16.join(ctx.featureDir ?? featureDir(ctx.workdir, "unknown"), "prd.json");
92565
+ return path15.join(ctx.featureDir ?? featureDir(ctx.workdir, "unknown"), "prd.json");
92247
92566
  }
92248
92567
  function logDroppedCommands(logger, ctx, queueCommands, currentIndex) {
92249
92568
  const dropped = queueCommands.slice(currentIndex + 1);
@@ -92294,10 +92613,10 @@ async function processQueueCommands(ctx, logger, queueCommands) {
92294
92613
  }
92295
92614
  if (cmd.type === "INJECT") {
92296
92615
  try {
92297
- if (path16.isAbsolute(cmd.storyFile)) {
92616
+ if (path15.isAbsolute(cmd.storyFile)) {
92298
92617
  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
92618
  }
92300
- const storyFilePath = validateFilePath(path16.join(ctx.workdir, cmd.storyFile), ctx.workdir);
92619
+ const storyFilePath = validateFilePath(path15.join(ctx.workdir, cmd.storyFile), ctx.workdir);
92301
92620
  const raw = await Bun.file(storyFilePath).json();
92302
92621
  const existingIds = new Set(ctx.prd.userStories.map((s) => s.id));
92303
92622
  const story = validateInjectedStory(raw, existingIds);
@@ -93675,7 +93994,7 @@ var init_hooks = __esm(() => {
93675
93994
  });
93676
93995
 
93677
93996
  // src/execution/lifecycle/acceptance-helpers.ts
93678
- import path17 from "path";
93997
+ import path16 from "path";
93679
93998
  function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2) {
93680
93999
  const matchedEntry = failedPackage ? acceptanceTestPaths?.find((entry) => entry.testPath === failedPackage.testPath || entry.packageDir === failedPackage.packageDir) : undefined;
93681
94000
  const selectedPathEntry = matchedEntry ?? acceptanceTestPaths?.[0];
@@ -93698,10 +94017,7 @@ function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2)
93698
94017
  function isStubTestFile(content) {
93699
94018
  return isStubTestContent(content);
93700
94019
  }
93701
- function isTestLevelFailure(failedACs, totalACs, semanticVerdicts) {
93702
- if (semanticVerdicts && semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
93703
- return true;
93704
- }
94020
+ function isTestLevelFailure(failedACs, totalACs) {
93705
94021
  const failedCount = typeof failedACs === "number" ? failedACs : failedACs.length;
93706
94022
  const hasACError = Array.isArray(failedACs) && failedACs.includes("AC-ERROR");
93707
94023
  if (hasACError)
@@ -93713,7 +94029,7 @@ function isTestLevelFailure(failedACs, totalACs, semanticVerdicts) {
93713
94029
  async function loadSpecContent(featureDir2) {
93714
94030
  if (!featureDir2)
93715
94031
  return "";
93716
- const specPath = path17.join(featureDir2, "spec.md");
94032
+ const specPath = path16.join(featureDir2, "spec.md");
93717
94033
  const specFile = Bun.file(specPath);
93718
94034
  return await specFile.exists() ? await specFile.text() : "";
93719
94035
  }
@@ -93733,7 +94049,7 @@ async function loadAcceptanceTestContent2(featureDir2, testPaths, configuredTest
93733
94049
  }
93734
94050
  if (!configuredTestPath)
93735
94051
  return [];
93736
- const resolvedPath = path17.join(featureDir2, configuredTestPath);
94052
+ const resolvedPath = path16.join(featureDir2, configuredTestPath);
93737
94053
  const testFile = Bun.file(resolvedPath);
93738
94054
  const content = await testFile.exists() ? await testFile.text() : "";
93739
94055
  return [{ content, path: resolvedPath }];
@@ -93763,7 +94079,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
93763
94079
  const { unlink: unlink7 } = await import("fs/promises");
93764
94080
  await unlink7(testPath);
93765
94081
  if (acceptanceContext.featureDir) {
93766
- const metaPath = path17.join(acceptanceContext.featureDir, "acceptance-meta.json");
94082
+ const metaPath = path16.join(acceptanceContext.featureDir, "acceptance-meta.json");
93767
94083
  try {
93768
94084
  await unlink7(metaPath);
93769
94085
  } catch {}
@@ -93779,7 +94095,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
93779
94095
  const diffOutput = await _regenerateDeps.spawnGitDiff(repoRoot, storyGitRef, pathspec);
93780
94096
  const changedFilesRaw = diffOutput.split(`
93781
94097
  `).map((f) => f.trim()).filter((f) => f.length > 0);
93782
- const packageDir = storyPkg && acceptanceContext.projectDir ? path17.join(acceptanceContext.projectDir, storyPkg) : undefined;
94098
+ const packageDir = storyPkg && acceptanceContext.projectDir ? path16.join(acceptanceContext.projectDir, storyPkg) : undefined;
93783
94099
  const ignoreMatchers = acceptanceContext.naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
93784
94100
  const changedFiles2 = filterNaxInternalPaths(changedFilesRaw, ignoreMatchers);
93785
94101
  const MAX_BYTES = 51200;
@@ -93791,7 +94107,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
93791
94107
  for (const file2 of changedFiles2) {
93792
94108
  if (totalBytes >= MAX_BYTES)
93793
94109
  break;
93794
- const filePath = path17.join(repoRoot, file2);
94110
+ const filePath = path16.join(repoRoot, file2);
93795
94111
  try {
93796
94112
  const fileContent = await _regenerateDeps.readFile(filePath);
93797
94113
  const remaining = MAX_BYTES - totalBytes;
@@ -93878,7 +94194,7 @@ function fixCallCtx(ctx, packageDir, config2) {
93878
94194
  }
93879
94195
  async function resolveAcceptanceDiagnosis(opts) {
93880
94196
  const logger = getSafeLogger();
93881
- const { ctx, failures, totalACs, strategy, semanticVerdicts, diagnosisOpts } = opts;
94197
+ const { ctx, failures, totalACs, strategy, diagnosisOpts } = opts;
93882
94198
  const storyId = diagnosisOpts.storyId;
93883
94199
  if (strategy === "implement-only") {
93884
94200
  logger?.info("acceptance.diagnosis", "Fast path: implement-only strategy \u2192 source_bug", { storyId });
@@ -93888,19 +94204,6 @@ async function resolveAcceptanceDiagnosis(opts) {
93888
94204
  confidence: 1
93889
94205
  };
93890
94206
  }
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
94207
  if (isTestLevelFailure(failures.failedACs, totalACs)) {
93905
94208
  logger?.info("acceptance.diagnosis", "Fast path: test-level failure heuristic \u2192 test_bug", {
93906
94209
  storyId,
@@ -93922,8 +94225,7 @@ async function resolveAcceptanceDiagnosis(opts) {
93922
94225
  testOutput: diagnosisOpts.testOutput,
93923
94226
  testFileContent: diagnosisOpts.testFileContent,
93924
94227
  acceptanceTestPath: diagnosisOpts.acceptanceTestPath,
93925
- sourceFiles,
93926
- semanticVerdicts
94228
+ sourceFiles
93927
94229
  });
93928
94230
  }
93929
94231
  var _diagnosisDeps;
@@ -94211,7 +94513,6 @@ async function runAcceptanceLoop(ctx) {
94211
94513
  continue;
94212
94514
  }
94213
94515
  }
94214
- const semanticVerdicts = ctx.featureDir ? await _acceptanceLoopDeps.loadSemanticVerdicts(ctx.featureDir) : [];
94215
94516
  const totalACs = prd.userStories.filter((s) => !isLegacyFixStory(s)).flatMap((s) => s.acceptanceCriteria).length;
94216
94517
  if (!ctx.runtime) {
94217
94518
  logger?.error("acceptance", "Runtime not found for diagnosis", { storyId: firstStory?.id });
@@ -94234,7 +94535,6 @@ async function runAcceptanceLoop(ctx) {
94234
94535
  failures: pkgFailures,
94235
94536
  totalACs,
94236
94537
  strategy,
94237
- semanticVerdicts,
94238
94538
  diagnosisOpts: {
94239
94539
  testOutput: pkg.output,
94240
94540
  testFileContent,
@@ -94280,7 +94580,6 @@ var init_acceptance_loop = __esm(() => {
94280
94580
  init_acceptance_helpers();
94281
94581
  init_acceptance_helpers();
94282
94582
  _acceptanceLoopDeps = {
94283
- loadSemanticVerdicts,
94284
94583
  loadAcceptanceTestContent
94285
94584
  };
94286
94585
  _acceptanceFixCycleDeps = {
@@ -94509,7 +94808,7 @@ var init_scratchpad_wipe = __esm(() => {
94509
94808
  init_logger2();
94510
94809
  init_tools();
94511
94810
  _scratchpadWipeDeps = {
94512
- remove: (path18) => rm7(path18, { recursive: true, force: true })
94811
+ remove: (path17) => rm7(path17, { recursive: true, force: true })
94513
94812
  };
94514
94813
  });
94515
94814
 
@@ -94661,6 +94960,13 @@ async function cleanupRun(options) {
94661
94960
  } catch (error48) {
94662
94961
  logger?.warn("plugins", "Plugin teardown failed", { error: error48 });
94663
94962
  }
94963
+ if (options.sealApprovals) {
94964
+ try {
94965
+ await options.sealApprovals();
94966
+ } catch (error48) {
94967
+ logger?.warn("permissions", "End-of-run approvals seal failed \u2014 continuing teardown", { error: error48 });
94968
+ }
94969
+ }
94664
94970
  if (interactionChain) {
94665
94971
  try {
94666
94972
  await interactionChain.destroy();
@@ -97098,8 +97404,8 @@ async function defaultRun(cmd, opts) {
97098
97404
  clearTimeout(timer);
97099
97405
  }
97100
97406
  }
97101
- async function defaultReadText(path18) {
97102
- const file2 = Bun.file(path18);
97407
+ async function defaultReadText(path17) {
97408
+ const file2 = Bun.file(path17);
97103
97409
  if (!await file2.exists())
97104
97410
  return null;
97105
97411
  return file2.text();
@@ -97199,10 +97505,10 @@ var init_pr = __esm(() => {
97199
97505
  });
97200
97506
 
97201
97507
  // src/forge/template.ts
97202
- import * as path18 from "path";
97508
+ import * as path17 from "path";
97203
97509
  async function firstExisting(workdir, deps, paths) {
97204
97510
  for (const relPath of paths) {
97205
- const content = await deps.readText(path18.join(workdir, relPath));
97511
+ const content = await deps.readText(path17.join(workdir, relPath));
97206
97512
  if (content !== null)
97207
97513
  return content;
97208
97514
  }
@@ -97417,7 +97723,7 @@ var init_pr_body = __esm(() => {
97417
97723
  });
97418
97724
 
97419
97725
  // src/plugins/builtin/auto-pr/index.ts
97420
- import * as path19 from "path";
97726
+ import * as path18 from "path";
97421
97727
  async function defaultRun2(cmd, opts) {
97422
97728
  const argv = cmd[0] === "git" ? hardenedGitArgv(cmd) : cmd;
97423
97729
  const proc = Bun.spawn(argv, { cwd: opts.cwd, env: gitSpawnEnv(), stdout: "pipe", stderr: "pipe" });
@@ -97443,8 +97749,8 @@ async function defaultRun2(cmd, opts) {
97443
97749
  clearTimeout(timer);
97444
97750
  }
97445
97751
  }
97446
- async function defaultReadText2(path20) {
97447
- const file2 = Bun.file(path20);
97752
+ async function defaultReadText2(path19) {
97753
+ const file2 = Bun.file(path19);
97448
97754
  if (!await file2.exists())
97449
97755
  return null;
97450
97756
  return file2.text();
@@ -97474,8 +97780,8 @@ function getStorySummary(context) {
97474
97780
  function relativePrdPath(workdir, prdPath) {
97475
97781
  if (!prdPath)
97476
97782
  return prdPath;
97477
- const rel = path19.relative(workdir, prdPath);
97478
- return rel && !rel.startsWith("..") && !path19.isAbsolute(rel) ? rel : prdPath;
97783
+ const rel = path18.relative(workdir, prdPath);
97784
+ return rel && !rel.startsWith("..") && !path18.isAbsolute(rel) ? rel : prdPath;
97479
97785
  }
97480
97786
  function toPrBodyContext(context) {
97481
97787
  const summary = getStorySummary(context);
@@ -97764,7 +98070,7 @@ var init_auto_prune = __esm(() => {
97764
98070
  });
97765
98071
 
97766
98072
  // src/plugins/builtin/curator/collect.ts
97767
- import * as path20 from "path";
98073
+ import * as path19 from "path";
97768
98074
  function now() {
97769
98075
  return new Date().toISOString();
97770
98076
  }
@@ -97822,7 +98128,7 @@ function tokenCount(story) {
97822
98128
  }
97823
98129
  async function collectFromMetrics(context) {
97824
98130
  const observations = [];
97825
- const metricsPath = path20.join(context.outputDir, "metrics.json");
98131
+ const metricsPath = path19.join(context.outputDir, "metrics.json");
97826
98132
  try {
97827
98133
  const data = await readJsonFile(metricsPath);
97828
98134
  const runs = Array.isArray(data) ? data : [data];
@@ -97898,11 +98204,11 @@ function findingMessage(finding) {
97898
98204
  }
97899
98205
  async function collectFromReviewAudit(context) {
97900
98206
  const observations = [];
97901
- const auditDir = path20.join(context.outputDir, "review-audit");
98207
+ const auditDir = path19.join(context.outputDir, "review-audit");
97902
98208
  try {
97903
98209
  const glob = new Bun.Glob("**/*.json");
97904
98210
  for await (const file2 of glob.scan({ cwd: auditDir, absolute: false })) {
97905
- const fullPath = path20.join(auditDir, file2);
98211
+ const fullPath = path19.join(auditDir, file2);
97906
98212
  try {
97907
98213
  const audit = asRecord3(await readJsonFile(fullPath));
97908
98214
  if (!audit)
@@ -97953,7 +98259,7 @@ async function collectFromContextManifests(context) {
97953
98259
  try {
97954
98260
  const glob = new Bun.Glob("*/stories/*/context-manifest-*.json");
97955
98261
  for await (const file2 of glob.scan({ cwd: featuresRoot, absolute: false })) {
97956
- const fullPath = path20.join(featuresRoot, file2);
98262
+ const fullPath = path19.join(featuresRoot, file2);
97957
98263
  try {
97958
98264
  const parts = file2.split("/");
97959
98265
  const featureId = parts[0] ?? context.feature;
@@ -98560,9 +98866,9 @@ function renderProposals(proposals, runId, observationCount, provenance) {
98560
98866
 
98561
98867
  // src/plugins/builtin/curator/rollup.ts
98562
98868
  import { appendFile as appendFile7, mkdir as mkdir26, writeFile as writeFile11 } from "fs/promises";
98563
- import * as path21 from "path";
98869
+ import * as path20 from "path";
98564
98870
  async function appendToRollup(observations, rollupPath) {
98565
- const dir = path21.dirname(rollupPath);
98871
+ const dir = path20.dirname(rollupPath);
98566
98872
  await mkdir26(dir, { recursive: true }).catch(() => {});
98567
98873
  const { withPathFileLock: withPathFileLock2 } = await Promise.resolve().then(() => (init_path_file_lock(), exports_path_file_lock));
98568
98874
  await withPathFileLock2(rollupPath, () => appendToRollupUnlocked(observations, rollupPath)).catch(() => {});
@@ -98651,7 +98957,7 @@ var init_rollup2 = __esm(() => {
98651
98957
 
98652
98958
  // src/plugins/builtin/curator/index.ts
98653
98959
  import { mkdir as mkdir27 } from "fs/promises";
98654
- import * as path22 from "path";
98960
+ import * as path21 from "path";
98655
98961
  function getCuratorEnabled(context) {
98656
98962
  const cfg = context.config;
98657
98963
  if (!cfg)
@@ -98727,7 +99033,7 @@ var init_curator = __esm(() => {
98727
99033
  const observations = await collectObservations(curatorContext);
98728
99034
  if (context.outputDir) {
98729
99035
  const { observationsPath, rollupPath } = resolveCuratorOutputs(curatorContext);
98730
- const runDir = path22.dirname(observationsPath);
99036
+ const runDir = path21.dirname(observationsPath);
98731
99037
  await mkdir27(runDir, { recursive: true });
98732
99038
  await Bun.write(observationsPath, observations.map((o) => JSON.stringify(o)).join(`
98733
99039
  `) + (observations.length > 0 ? `
@@ -98765,7 +99071,7 @@ var init_curator = __esm(() => {
98765
99071
  const windowHasObservations = window2.observations.length > 0;
98766
99072
  const provenance = windowHasObservations ? { runCount: window2.runIds.length, observationCount: window2.observations.length } : { runCount: 1, observationCount: observations.length };
98767
99073
  const markdown = renderProposals(proposals, context.runId, observations.length, provenance);
98768
- const proposalsMdPath = path22.join(runDir, "curator-proposals.md");
99074
+ const proposalsMdPath = path21.join(runDir, "curator-proposals.md");
98769
99075
  await Bun.write(proposalsMdPath, markdown);
98770
99076
  }
98771
99077
  return {
@@ -99148,14 +99454,14 @@ var init_validator = __esm(() => {
99148
99454
 
99149
99455
  // src/plugins/loader.ts
99150
99456
  import * as fs from "fs/promises";
99151
- import * as path23 from "path";
99457
+ import * as path22 from "path";
99152
99458
  function getSafeLogger7() {
99153
99459
  return getSafeLogger();
99154
99460
  }
99155
99461
  function extractPluginName(pluginPath) {
99156
- const basename13 = path23.basename(pluginPath);
99462
+ const basename13 = path22.basename(pluginPath);
99157
99463
  if (basename13 === "index.ts" || basename13 === "index.js" || basename13 === "index.mjs") {
99158
- return path23.basename(path23.dirname(pluginPath));
99464
+ return path22.basename(path22.dirname(pluginPath));
99159
99465
  }
99160
99466
  return basename13.replace(/\.(ts|js|mjs)$/, "");
99161
99467
  }
@@ -99302,7 +99608,7 @@ async function discoverPlugins(dir, isTestFileFn) {
99302
99608
  try {
99303
99609
  const entries = await fs.readdir(dir, { withFileTypes: true });
99304
99610
  for (const entry of entries) {
99305
- const fullPath = path23.join(dir, entry.name);
99611
+ const fullPath = path22.join(dir, entry.name);
99306
99612
  if (entry.isFile()) {
99307
99613
  if (isPluginFile(entry.name, isTestFileFn)) {
99308
99614
  discovered.push({ path: fullPath });
@@ -99310,7 +99616,7 @@ async function discoverPlugins(dir, isTestFileFn) {
99310
99616
  } else if (entry.isDirectory()) {
99311
99617
  const indexPaths = ["index.ts", "index.js", "index.mjs"];
99312
99618
  for (const indexFile of indexPaths) {
99313
- const indexPath = path23.join(fullPath, indexFile);
99619
+ const indexPath = path22.join(fullPath, indexFile);
99314
99620
  try {
99315
99621
  await fs.access(indexPath);
99316
99622
  discovered.push({ path: indexPath });
@@ -99335,13 +99641,13 @@ function isPluginFile(filename, isTestFileFn) {
99335
99641
  return !FALLBACK_TEST_FILE_RE.test(filename);
99336
99642
  }
99337
99643
  function resolveModulePath(modulePath, projectRoot) {
99338
- if (path23.isAbsolute(modulePath) || !modulePath.startsWith("./") && !modulePath.startsWith("../")) {
99644
+ if (path22.isAbsolute(modulePath) || !modulePath.startsWith("./") && !modulePath.startsWith("../")) {
99339
99645
  return modulePath;
99340
99646
  }
99341
99647
  if (projectRoot) {
99342
- return path23.resolve(projectRoot, modulePath);
99648
+ return path22.resolve(projectRoot, modulePath);
99343
99649
  }
99344
- return path23.resolve(modulePath);
99650
+ return path22.resolve(modulePath);
99345
99651
  }
99346
99652
  async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots, originalPath) {
99347
99653
  let attemptedPath = initialModulePath;
@@ -99563,8 +99869,8 @@ var init_language_commands = __esm(() => {
99563
99869
 
99564
99870
  // src/review/scoped-lint.ts
99565
99871
  import { join as join100, relative as relative26 } from "path";
99566
- function normalizePath3(path24) {
99567
- return path24.replaceAll("\\", "/").replace(/^\.\//, "");
99872
+ function normalizePath3(path23) {
99873
+ return path23.replaceAll("\\", "/").replace(/^\.\//, "");
99568
99874
  }
99569
99875
  function isSupportedDerivedScopedCommand(command) {
99570
99876
  const commands = normalizeCommandSpec(command);
@@ -99799,7 +100105,7 @@ var init_scoped_lint = __esm(() => {
99799
100105
  listChangedFiles,
99800
100106
  findPackageDir,
99801
100107
  runLintCommand,
99802
- fileExists: (path24) => Bun.file(path24).exists()
100108
+ fileExists: (path23) => Bun.file(path23).exists()
99803
100109
  };
99804
100110
  });
99805
100111
 
@@ -100260,7 +100566,7 @@ var init_paused_story_prompts = __esm(() => {
100260
100566
  });
100261
100567
 
100262
100568
  // src/execution/lifecycle/run-setup-init.ts
100263
- import path24 from "path";
100569
+ import path23 from "path";
100264
100570
  async function initializeAfterLock(options) {
100265
100571
  const logger = getSafeLogger();
100266
100572
  const { config: config2, workdir, feature, dryRun, runtime, interactionChain, runId, agentGetFn, statusWriter, deps } = options;
@@ -100292,8 +100598,8 @@ async function initializeAfterLock(options) {
100292
100598
  explicit: Object.fromEntries(explicitFields.map((f) => [f, existingProjectConfig[f]])),
100293
100599
  detected: Object.fromEntries(autodetectedFields.map((f) => [f, detectedProfile[f]]))
100294
100600
  });
100295
- const globalPluginsDir = path24.join(globalConfigDir(), "plugins");
100296
- const projectPluginsDir = path24.join(workdir, ".nax", "plugins");
100601
+ const globalPluginsDir = path23.join(globalConfigDir(), "plugins");
100602
+ const projectPluginsDir = path23.join(workdir, ".nax", "plugins");
100297
100603
  const configPlugins = config2.plugins || [];
100298
100604
  const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
100299
100605
  const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
@@ -101252,7 +101558,7 @@ __export(exports_migrate, {
101252
101558
  });
101253
101559
  import { existsSync as existsSync30 } from "fs";
101254
101560
  import { mkdir as mkdir28, readdir as readdir10, rename as rename9 } from "fs/promises";
101255
- import path25 from "path";
101561
+ import path24 from "path";
101256
101562
  async function detectGeneratedContent(naxDir) {
101257
101563
  if (!existsSync30(naxDir))
101258
101564
  return [];
@@ -101265,17 +101571,17 @@ async function detectGeneratedContent(naxDir) {
101265
101571
  }
101266
101572
  for (const entry of entries) {
101267
101573
  if (GENERATED_NAMES.has(entry)) {
101268
- candidates.push({ name: entry, srcPath: path25.join(naxDir, entry) });
101574
+ candidates.push({ name: entry, srcPath: path24.join(naxDir, entry) });
101269
101575
  }
101270
101576
  }
101271
- const featuresDir2 = path25.join(naxDir, "features");
101577
+ const featuresDir2 = path24.join(naxDir, "features");
101272
101578
  if (existsSync30(featuresDir2)) {
101273
101579
  let featureDirs = [];
101274
101580
  try {
101275
101581
  featureDirs = await readdir10(featuresDir2);
101276
101582
  } catch {}
101277
101583
  for (const fid of featureDirs) {
101278
- const featureDir2 = path25.join(featuresDir2, fid);
101584
+ const featureDir2 = path24.join(featuresDir2, fid);
101279
101585
  let subEntries = [];
101280
101586
  try {
101281
101587
  subEntries = await readdir10(featureDir2);
@@ -101285,12 +101591,12 @@ async function detectGeneratedContent(naxDir) {
101285
101591
  for (const sub of subEntries) {
101286
101592
  if (GENERATED_FEATURE_SUBNAMES.has(sub)) {
101287
101593
  candidates.push({
101288
- name: path25.join("features", fid, sub),
101289
- srcPath: path25.join(featureDir2, sub)
101594
+ name: path24.join("features", fid, sub),
101595
+ srcPath: path24.join(featureDir2, sub)
101290
101596
  });
101291
101597
  }
101292
101598
  if (sub === "stories") {
101293
- const storiesDir = path25.join(featureDir2, "stories");
101599
+ const storiesDir = path24.join(featureDir2, "stories");
101294
101600
  let storyDirs = [];
101295
101601
  try {
101296
101602
  storyDirs = await readdir10(storiesDir);
@@ -101298,7 +101604,7 @@ async function detectGeneratedContent(naxDir) {
101298
101604
  continue;
101299
101605
  }
101300
101606
  for (const sid of storyDirs) {
101301
- const storyDir = path25.join(storiesDir, sid);
101607
+ const storyDir = path24.join(storiesDir, sid);
101302
101608
  let storyEntries = [];
101303
101609
  try {
101304
101610
  storyEntries = await readdir10(storyDir);
@@ -101308,8 +101614,8 @@ async function detectGeneratedContent(naxDir) {
101308
101614
  for (const se of storyEntries) {
101309
101615
  if (se.startsWith("context-manifest-") && se.endsWith(".json")) {
101310
101616
  candidates.push({
101311
- name: path25.join("features", fid, "stories", sid, se),
101312
- srcPath: path25.join(storyDir, se)
101617
+ name: path24.join("features", fid, "stories", sid, se),
101618
+ srcPath: path24.join(storyDir, se)
101313
101619
  });
101314
101620
  }
101315
101621
  }
@@ -101330,15 +101636,15 @@ async function migrateCommand(options) {
101330
101636
  name: options.reclaim
101331
101637
  });
101332
101638
  }
101333
- const src = path25.join(globalConfigDir(), options.reclaim);
101639
+ const src = path24.join(globalConfigDir(), options.reclaim);
101334
101640
  if (!existsSync30(src)) {
101335
101641
  throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
101336
101642
  stage: "migrate",
101337
101643
  name: options.reclaim
101338
101644
  });
101339
101645
  }
101340
- const archiveBase = path25.join(globalConfigDir(), "_archive");
101341
- const archiveDest = path25.join(archiveBase, `${options.reclaim}-${Date.now()}`);
101646
+ const archiveBase = path24.join(globalConfigDir(), "_archive");
101647
+ const archiveDest = path24.join(archiveBase, `${options.reclaim}-${Date.now()}`);
101342
101648
  await mkdir28(archiveBase, { recursive: true });
101343
101649
  await rename9(src, archiveDest);
101344
101650
  logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
@@ -101378,8 +101684,8 @@ async function migrateCommand(options) {
101378
101684
  logger.info("migrate", `Merged: identity for "${options.merge}" updated`, { storyId: "_migrate" });
101379
101685
  return;
101380
101686
  }
101381
- const naxDir = path25.join(options.workdir, ".nax");
101382
- const configPath = path25.join(naxDir, "config.json");
101687
+ const naxDir = path24.join(options.workdir, ".nax");
101688
+ const configPath = path24.join(naxDir, "config.json");
101383
101689
  if (!existsSync30(configPath)) {
101384
101690
  throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
101385
101691
  stage: "migrate",
@@ -101395,7 +101701,7 @@ async function migrateCommand(options) {
101395
101701
  cause: e
101396
101702
  });
101397
101703
  }
101398
- const projectKey = config2.name?.trim() || path25.basename(options.workdir);
101704
+ const projectKey = config2.name?.trim() || path24.basename(options.workdir);
101399
101705
  const destBase = projectOutputDir(projectKey, config2.outputDir);
101400
101706
  const candidates = await detectGeneratedContent(naxDir);
101401
101707
  if (candidates.length === 0) {
@@ -101404,7 +101710,7 @@ async function migrateCommand(options) {
101404
101710
  }
101405
101711
  if (options.dryRun) {
101406
101712
  for (const c of candidates) {
101407
- logger.info("migrate", `[dry-run] Would move: ${c.srcPath} -> ${path25.join(destBase, c.name)}`, {
101713
+ logger.info("migrate", `[dry-run] Would move: ${c.srcPath} -> ${path24.join(destBase, c.name)}`, {
101408
101714
  storyId: "_migrate"
101409
101715
  });
101410
101716
  }
@@ -101413,8 +101719,8 @@ async function migrateCommand(options) {
101413
101719
  await mkdir28(destBase, { recursive: true });
101414
101720
  let moved = 0;
101415
101721
  for (const candidate of candidates) {
101416
- const dest = path25.join(destBase, candidate.name);
101417
- await mkdir28(path25.dirname(dest), { recursive: true });
101722
+ const dest = path24.join(destBase, candidate.name);
101723
+ await mkdir28(path24.dirname(dest), { recursive: true });
101418
101724
  if (existsSync30(dest)) {
101419
101725
  throw new NaxError(`Migration conflict: destination already exists.
101420
101726
  Source: ${candidate.srcPath}
@@ -101444,7 +101750,7 @@ async function migrateCommand(options) {
101444
101750
  moved++;
101445
101751
  logger.info("migrate", `Moved: ${candidate.name}`, { storyId: "_migrate" });
101446
101752
  }
101447
- await Bun.write(path25.join(destBase, ".migrated-from"), JSON.stringify({ from: options.workdir, migratedAt: new Date().toISOString() }, null, 2));
101753
+ await Bun.write(path24.join(destBase, ".migrated-from"), JSON.stringify({ from: options.workdir, migratedAt: new Date().toISOString() }, null, 2));
101448
101754
  logger.info("migrate", `Migration complete: ${moved} entries moved`, {
101449
101755
  storyId: "_migrate",
101450
101756
  destBase
@@ -101815,10 +102121,10 @@ function renderReport(timeline, options = {}) {
101815
102121
  // src/commands/replay.ts
101816
102122
  import { existsSync as existsSync32 } from "fs";
101817
102123
  import { dirname as dirname20 } from "path";
101818
- async function readJsonlLenient(path26) {
101819
- if (!existsSync32(path26))
102124
+ async function readJsonlLenient(path25) {
102125
+ if (!existsSync32(path25))
101820
102126
  return [];
101821
- const content = await Bun.file(path26).text();
102127
+ const content = await Bun.file(path25).text();
101822
102128
  const lines = content.split(`
101823
102129
  `);
101824
102130
  const entries = [];
@@ -101832,11 +102138,11 @@ async function readJsonlLenient(path26) {
101832
102138
  }
101833
102139
  return entries;
101834
102140
  }
101835
- async function readJsonOrUndefined(path26) {
101836
- if (!existsSync32(path26))
102141
+ async function readJsonOrUndefined(path25) {
102142
+ if (!existsSync32(path25))
101837
102143
  return;
101838
102144
  try {
101839
- return await Bun.file(path26).json();
102145
+ return await Bun.file(path25).json();
101840
102146
  } catch {
101841
102147
  return;
101842
102148
  }
@@ -102549,7 +102855,7 @@ __export(exports_precheck_runner, {
102549
102855
  runPrecheckValidation: () => runPrecheckValidation
102550
102856
  });
102551
102857
  import { mkdirSync as mkdirSync5 } from "fs";
102552
- import path26 from "path";
102858
+ import path25 from "path";
102553
102859
  async function runPrecheckValidation(ctx) {
102554
102860
  const logger = getSafeLogger();
102555
102861
  if (process.env.NAX_PRECHECK !== "1") {
@@ -102558,7 +102864,7 @@ async function runPrecheckValidation(ctx) {
102558
102864
  }
102559
102865
  logger?.info("precheck", "Running precheck validations...");
102560
102866
  const { runPrecheck: runPrecheck3 } = await Promise.resolve().then(() => (init_precheck2(), exports_precheck));
102561
- const projectKey = ctx.config.name?.trim() || path26.basename(ctx.workdir);
102867
+ const projectKey = ctx.config.name?.trim() || path25.basename(ctx.workdir);
102562
102868
  const outputDir = projectOutputDir(projectKey, ctx.config.outputDir);
102563
102869
  const featureLock = ctx.featureName !== undefined ? { outputDir, feature: ctx.featureName } : undefined;
102564
102870
  const precheckResult = await runPrecheck3(ctx.config, ctx.prd, {
@@ -102568,7 +102874,7 @@ async function runPrecheckValidation(ctx) {
102568
102874
  ...featureLock !== undefined ? { featureLock } : {}
102569
102875
  });
102570
102876
  if (ctx.logFilePath) {
102571
- mkdirSync5(path26.dirname(ctx.logFilePath), { recursive: true });
102877
+ mkdirSync5(path25.dirname(ctx.logFilePath), { recursive: true });
102572
102878
  const precheckLog = {
102573
102879
  type: "precheck",
102574
102880
  timestamp: new Date().toISOString(),
@@ -102666,7 +102972,7 @@ __export(exports_run_setup, {
102666
102972
  warnInertBashStages: () => warnInertBashStages,
102667
102973
  warnProfileMismatch: () => warnProfileMismatch
102668
102974
  });
102669
- import path27 from "path";
102975
+ import path26 from "path";
102670
102976
  async function setupRun(options) {
102671
102977
  const logger = getSafeLogger();
102672
102978
  warnFallbackMisconfiguration(options.config, options.agentGetFn, logger);
@@ -102731,6 +103037,7 @@ async function setupRun(options) {
102731
103037
  }
102732
103038
  await runtime.pidRegistry.cleanupStale();
102733
103039
  let cleanupCrashHandlers;
103040
+ let sealApprovals;
102734
103041
  try {
102735
103042
  cleanupCrashHandlers = _runSetupDeps.installCrashHandlers({
102736
103043
  statusWriter,
@@ -102748,6 +103055,9 @@ async function setupRun(options) {
102748
103055
  emitError: (reason) => {
102749
103056
  pipelineEventBus.emit({ type: "run:errored", reason, feature: options.feature });
102750
103057
  },
103058
+ sealApprovals: async () => {
103059
+ await sealApprovals?.();
103060
+ },
102751
103061
  onShutdown: async (abortSignal) => {
102752
103062
  await closeAllRunSessions(sessionManager, options.agentGetFn, { force: true, signal: abortSignal });
102753
103063
  await runtime.close().catch((err) => {
@@ -102760,7 +103070,7 @@ async function setupRun(options) {
102760
103070
  statusWriter.setPrd(prd);
102761
103071
  {
102762
103072
  const { detectGeneratedContent: detectGeneratedContent2, migrateCommand: migrateCommand2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
102763
- const naxDir = path27.join(workdir, ".nax");
103073
+ const naxDir = path26.join(workdir, ".nax");
102764
103074
  const candidates = await detectGeneratedContent2(naxDir).catch(() => []);
102765
103075
  if (candidates.length > 0) {
102766
103076
  logger?.info("setup", "Found generated content under .nax/ \u2014 migrating to output dir", {
@@ -102787,7 +103097,7 @@ async function setupRun(options) {
102787
103097
  remoteUrl = new TextDecoder().decode(gitResult.stdout).trim() || null;
102788
103098
  }
102789
103099
  } catch {}
102790
- const projectKey = config2.name?.trim() || path27.basename(workdir);
103100
+ const projectKey = config2.name?.trim() || path26.basename(workdir);
102791
103101
  await claimProjectIdentity2(projectKey, workdir, remoteUrl).catch((err) => {
102792
103102
  if (err instanceof NaxError && err.code === "RUN_NAME_COLLISION") {
102793
103103
  throw err;
@@ -102871,6 +103181,19 @@ async function setupRun(options) {
102871
103181
  sweepFeatureTranscripts: _runSetupDeps.sweepFeatureTranscripts
102872
103182
  }
102873
103183
  });
103184
+ try {
103185
+ sealApprovals = await _runSetupDeps.buildApprovalsSeal({
103186
+ projectDir: options.workdir,
103187
+ rootConfig: options.config,
103188
+ packageDirs: initResult.prd.userStories.map(storyPackageDir),
103189
+ outputDir: runtime.outputDir,
103190
+ runId: options.runId
103191
+ });
103192
+ } catch (error48) {
103193
+ await releaseFeatureLock({ outputDir: runtime.outputDir, feature, runId });
103194
+ await releaseLock(workdir);
103195
+ throw error48;
103196
+ }
102874
103197
  return {
102875
103198
  statusWriter,
102876
103199
  sessionManager,
@@ -102880,7 +103203,8 @@ async function setupRun(options) {
102880
103203
  storyCounts: initResult.storyCounts,
102881
103204
  interactionChain: initResult.interactionChain,
102882
103205
  shutdownController,
102883
- runtime
103206
+ runtime,
103207
+ sealApprovals
102884
103208
  };
102885
103209
  } catch (error48) {
102886
103210
  cleanupCrashHandlers?.();
@@ -102909,6 +103233,7 @@ var init_run_setup = __esm(() => {
102909
103233
  init_test_runners();
102910
103234
  init_tools();
102911
103235
  init_git_env();
103236
+ init_path_frame();
102912
103237
  init_crash_recovery();
102913
103238
  init_feature_lock();
102914
103239
  init_helpers();
@@ -102921,6 +103246,7 @@ var init_run_setup = __esm(() => {
102921
103246
  createRuntime,
102922
103247
  installCrashHandlers,
102923
103248
  sweepFeatureTranscripts,
103249
+ buildApprovalsSeal,
102924
103250
  acquireLock,
102925
103251
  acquireFeatureLock
102926
103252
  };
@@ -104177,7 +104503,7 @@ var init_queue_file_lock = __esm(() => {
104177
104503
 
104178
104504
  // src/execution/queue-handler.ts
104179
104505
  import { rename as rename10, unlink as unlink11 } from "fs/promises";
104180
- import path28 from "path";
104506
+ import path27 from "path";
104181
104507
  function getSafeLogger8() {
104182
104508
  try {
104183
104509
  return getLogger();
@@ -104209,8 +104535,8 @@ async function claimCommandsLocked(queuePath, processingPath, logger) {
104209
104535
  return result.commands;
104210
104536
  }
104211
104537
  async function readQueueFile(workdir) {
104212
- const queuePath = path28.join(workdir, ".queue.txt");
104213
- const processingPath = path28.join(workdir, ".queue.txt.processing");
104538
+ const queuePath = path27.join(workdir, ".queue.txt");
104539
+ const processingPath = path27.join(workdir, ".queue.txt.processing");
104214
104540
  const logger = getSafeLogger8();
104215
104541
  try {
104216
104542
  return await withQueueFileLock(queuePath, async () => {
@@ -104225,8 +104551,8 @@ async function readQueueFile(workdir) {
104225
104551
  }
104226
104552
  }
104227
104553
  async function processQueueFile(workdir, processor) {
104228
- const queuePath = path28.join(workdir, ".queue.txt");
104229
- const processingPath = path28.join(workdir, ".queue.txt.processing");
104554
+ const queuePath = path27.join(workdir, ".queue.txt");
104555
+ const processingPath = path27.join(workdir, ".queue.txt.processing");
104230
104556
  const logger = getSafeLogger8();
104231
104557
  try {
104232
104558
  return await withQueueFileLock(queuePath, async () => {
@@ -104251,7 +104577,7 @@ async function processQueueFile(workdir, processor) {
104251
104577
  }
104252
104578
  }
104253
104579
  async function drainQueueAtBatchBoundary(workdir, prd) {
104254
- if (!await Bun.file(path28.join(workdir, ".queue.txt")).exists()) {
104580
+ if (!await Bun.file(path27.join(workdir, ".queue.txt")).exists()) {
104255
104581
  return { paused: false };
104256
104582
  }
104257
104583
  const logger = getSafeLogger8();
@@ -104307,10 +104633,10 @@ async function applyBatchBoundaryCommands(workdir, prd, commands, logger) {
104307
104633
  }
104308
104634
  if (cmd.type === "INJECT") {
104309
104635
  try {
104310
- if (path28.isAbsolute(cmd.storyFile)) {
104636
+ if (path27.isAbsolute(cmd.storyFile)) {
104311
104637
  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
104638
  }
104313
- const storyFilePath = validateFilePath(path28.join(workdir, cmd.storyFile), workdir);
104639
+ const storyFilePath = validateFilePath(path27.join(workdir, cmd.storyFile), workdir);
104314
104640
  const raw = await Bun.file(storyFilePath).json();
104315
104641
  const existingIds = new Set(prd.userStories.map((s) => s.id));
104316
104642
  const story = validateInjectedStory(raw, existingIds);
@@ -104330,8 +104656,8 @@ async function applyBatchBoundaryCommands(workdir, prd, commands, logger) {
104330
104656
  return { paused: false };
104331
104657
  }
104332
104658
  async function clearQueueFile(workdir) {
104333
- const queuePath = path28.join(workdir, ".queue.txt");
104334
- const processingPath = path28.join(workdir, ".queue.txt.processing");
104659
+ const queuePath = path27.join(workdir, ".queue.txt");
104660
+ const processingPath = path27.join(workdir, ".queue.txt.processing");
104335
104661
  const logger = getSafeLogger8();
104336
104662
  try {
104337
104663
  await withQueueFileLock(queuePath, async () => {
@@ -105750,12 +106076,12 @@ var init_body = __esm(() => {
105750
106076
  // src/finish/pr/context.ts
105751
106077
  import { readFile as readFile8 } from "fs/promises";
105752
106078
  import { join as join113 } from "path";
105753
- async function readJson(path29) {
106079
+ async function readJson(path28) {
105754
106080
  let text;
105755
106081
  try {
105756
- text = await _finishPrDeps.readText(path29);
106082
+ text = await _finishPrDeps.readText(path28);
105757
106083
  } catch (error48) {
105758
- _finishPrDeps.warn("[finish-pr] Failed to read PR context artifact", { path: path29, error: error48 });
106084
+ _finishPrDeps.warn("[finish-pr] Failed to read PR context artifact", { path: path28, error: error48 });
105759
106085
  return;
105760
106086
  }
105761
106087
  if (text === null)
@@ -105763,7 +106089,7 @@ async function readJson(path29) {
105763
106089
  try {
105764
106090
  return JSON.parse(text);
105765
106091
  } catch (error48) {
105766
- _finishPrDeps.warn("[finish-pr] Failed to parse PR context artifact", { path: path29, error: error48 });
106092
+ _finishPrDeps.warn("[finish-pr] Failed to parse PR context artifact", { path: path28, error: error48 });
105767
106093
  return;
105768
106094
  }
105769
106095
  }
@@ -105847,9 +106173,9 @@ var init_context5 = __esm(() => {
105847
106173
  init_pr_title();
105848
106174
  _finishPrDeps = {
105849
106175
  run: defaultForgeDeps.run,
105850
- readText: async (path29) => {
106176
+ readText: async (path28) => {
105851
106177
  try {
105852
- return await readFile8(path29, "utf8");
106178
+ return await readFile8(path28, "utf8");
105853
106179
  } catch (err) {
105854
106180
  if (err.code === "ENOENT")
105855
106181
  return null;
@@ -106738,7 +107064,7 @@ var init_finish = __esm(() => {
106738
107064
  });
106739
107065
 
106740
107066
  // src/execution/runner-completion.ts
106741
- import path29 from "path";
107067
+ import path28 from "path";
106742
107068
  async function runCompletionPhase(options) {
106743
107069
  const logger = getSafeLogger();
106744
107070
  logger?.debug("execution", "Completion phase started", {
@@ -106759,7 +107085,7 @@ async function runCompletionPhase(options) {
106759
107085
  const acceptanceStartTime = Date.now();
106760
107086
  pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
106761
107087
  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 = path29.relative(options.workdir, g.packageDir);
107088
+ const relativeWorkdir = path28.relative(options.workdir, g.packageDir);
106763
107089
  let groupConfig = options.config;
106764
107090
  if (relativeWorkdir && relativeWorkdir !== ".") {
106765
107091
  try {
@@ -106890,7 +107216,7 @@ async function runCompletionPhase(options) {
106890
107216
  exitReason: options.exitReason,
106891
107217
  runtime: options.runtime,
106892
107218
  abortSignal: options.abortSignal,
106893
- isSequential: options.parallel === undefined,
107219
+ isSequential: options.parallel === undefined || !(options.parallel > 1),
106894
107220
  interactionChain: options.interactionChain
106895
107221
  });
106896
107222
  const { durationMs, runCompletedAt, finalCounts, reportedTotal, pluginGateFailed } = completionResult;
@@ -107496,7 +107822,7 @@ __export(exports_parallel_batch, {
107496
107822
  _parallelBatchDeps: () => _parallelBatchDeps,
107497
107823
  runParallelBatch: () => runParallelBatch
107498
107824
  });
107499
- import path30 from "path";
107825
+ import path29 from "path";
107500
107826
  async function runParallelBatch(options) {
107501
107827
  const { stories, ctx, prd } = options;
107502
107828
  const { workdir, config: config2, maxConcurrency, pipelineContext, eventEmitter, agentGetFn, hooks, pluginRegistry } = ctx;
@@ -107531,7 +107857,7 @@ async function runParallelBatch(options) {
107531
107857
  }
107532
107858
  worktreePaths.set(story.id, storyWorktreePath(workdir, worktreeId));
107533
107859
  }
107534
- const rootConfigPath = path30.join(workdir, ".nax", "config.json");
107860
+ const rootConfigPath = path29.join(workdir, ".nax", "config.json");
107535
107861
  const profileOverride = profileOverrideFromConfig(config2);
107536
107862
  const storyEffectiveConfigs = new Map;
107537
107863
  const configResults = await Promise.allSettled(stories.filter((story) => storyPackageDir(story)).map(async (story) => {
@@ -108514,7 +108840,8 @@ async function run(options) {
108514
108840
  pluginRegistry,
108515
108841
  interactionChain,
108516
108842
  shutdownController,
108517
- runtime
108843
+ runtime,
108844
+ sealApprovals
108518
108845
  } = setupResult;
108519
108846
  prd = setupResult.prd;
108520
108847
  const agentManager = runtime.agentManager;
@@ -108636,7 +108963,8 @@ async function run(options) {
108636
108963
  projectKey: runtime.projectKey,
108637
108964
  curatorRollupPath: runtime.curatorRollupPath,
108638
108965
  logFilePath,
108639
- config: config2
108966
+ config: config2,
108967
+ sealApprovals
108640
108968
  });
108641
108969
  logger?.debug("execution", "Runner finally \u2014 cleanupRun done, run() returning");
108642
108970
  } finally {
@@ -109101,12 +109429,12 @@ async function checkDependenciesInstalled(workdir) {
109101
109429
  { path: "vendor" }
109102
109430
  ];
109103
109431
  const found = [];
109104
- for (const { path: path31 } of depPaths) {
109105
- const fullPath = `${workdir}/${path31}`;
109432
+ for (const { path: path30 } of depPaths) {
109433
+ const fullPath = `${workdir}/${path30}`;
109106
109434
  if (existsSync36(fullPath)) {
109107
109435
  const stats = statSync6(fullPath);
109108
109436
  if (stats.isDirectory()) {
109109
- found.push(path31);
109437
+ found.push(path30);
109110
109438
  }
109111
109439
  }
109112
109440
  }
@@ -110535,8 +110863,8 @@ var init_plan_runtime = __esm(() => {
110535
110863
  init_git_env();
110536
110864
  init_plan_helpers();
110537
110865
  _planDeps = {
110538
- readFile: (path31) => Bun.file(path31).text(),
110539
- writeFile: (path31, content) => Bun.write(path31, content).then(() => {}),
110866
+ readFile: (path30) => Bun.file(path30).text(),
110867
+ writeFile: (path30, content) => Bun.write(path30, content).then(() => {}),
110540
110868
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
110541
110869
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
110542
110870
  claimProjectIdentity,
@@ -110545,10 +110873,10 @@ var init_plan_runtime = __esm(() => {
110545
110873
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd, ...opts.env ? { env: opts.env } : {} } : {});
110546
110874
  return { stdout: result.stdout, exitCode: result.exitCode };
110547
110875
  },
110548
- mkdirp: (path31) => Bun.spawn(["mkdir", "-p", path31]).exited.then(() => {}),
110549
- existsSync: (path31) => existsSync38(path31),
110876
+ mkdirp: (path30) => Bun.spawn(["mkdir", "-p", path30]).exited.then(() => {}),
110877
+ existsSync: (path30) => existsSync38(path30),
110550
110878
  discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages2(repoRoot),
110551
- readPackageJsonAt: (path31) => Bun.file(path31).json().catch(() => null),
110879
+ readPackageJsonAt: (path30) => Bun.file(path30).json().catch(() => null),
110552
110880
  createInteractionBridge: () => createCliInteractionBridge(),
110553
110881
  initInteractionChain: (cfg, headless) => initInteractionChain(cfg, headless),
110554
110882
  runPrecheck: async (config2, prd, opts) => {
@@ -110569,7 +110897,7 @@ function assertSpecLintClean(specContent, options) {
110569
110897
  return [];
110570
110898
  const findings = lintSpecContent(specContent, {
110571
110899
  maxAcCount: options.maxAcCount,
110572
- fileExists: (path31) => existsSync39(join118(options.workdir, path31))
110900
+ fileExists: (path30) => existsSync39(join118(options.workdir, path30))
110573
110901
  });
110574
110902
  const blocking = findings.filter((finding) => BLOCKING_SPEC_LINT_CODES.has(finding.code));
110575
110903
  if (blocking.length > 0) {
@@ -110629,7 +110957,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
110629
110957
  }));
110630
110958
  const codebaseContext = buildSourceRootsSection(normalizedRoots);
110631
110959
  const relativePackages = [
110632
- ...new Set(sourceRoots.map((root) => root.path).filter((path31) => path31 !== ".").map((path31) => path31.startsWith("/") ? path31.replace(`${workdir}/`, "") : path31))
110960
+ ...new Set(sourceRoots.map((root) => root.path).filter((path30) => path30 !== ".").map((path30) => path30.startsWith("/") ? path30.replace(`${workdir}/`, "") : path30))
110633
110961
  ];
110634
110962
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
110635
110963
  const packageJson = await deps.readPackageJsonAt(join119(workdir, relativePath, "package.json"));
@@ -110772,7 +111100,7 @@ var init_persist_prd = __esm(() => {
110772
111100
  init_prd();
110773
111101
  init_finalize_routing();
110774
111102
  _persistPrdDeps = {
110775
- existsSync: (path31) => defaultExistsSync(path31),
111103
+ existsSync: (path30) => defaultExistsSync(path30),
110776
111104
  discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages2(repoRoot)
110777
111105
  };
110778
111106
  });
@@ -111035,10 +111363,10 @@ var init_plan2 = __esm(() => {
111035
111363
  });
111036
111364
 
111037
111365
  // src/cli/plugins.ts
111038
- import * as path31 from "path";
111366
+ import * as path30 from "path";
111039
111367
  async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
111040
- const globalPluginsDir = overrideGlobalPluginsDir ?? path31.join(globalConfigDir(), "plugins");
111041
- const projectPluginsDir = path31.join(workdir, ".nax", "plugins");
111368
+ const globalPluginsDir = overrideGlobalPluginsDir ?? path30.join(globalConfigDir(), "plugins");
111369
+ const projectPluginsDir = path30.join(workdir, ".nax", "plugins");
111042
111370
  const configPlugins = config2.plugins || [];
111043
111371
  const registry4 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
111044
111372
  const plugins = registry4.plugins;
@@ -111088,10 +111416,10 @@ function formatSource(type, sourcePath) {
111088
111416
  return `built-in (${sourcePath})`;
111089
111417
  }
111090
111418
  if (type === "global") {
111091
- return `global (${path31.basename(sourcePath)})`;
111419
+ return `global (${path30.basename(sourcePath)})`;
111092
111420
  }
111093
111421
  if (type === "project") {
111094
- return `project (${path31.basename(sourcePath)})`;
111422
+ return `project (${path30.basename(sourcePath)})`;
111095
111423
  }
111096
111424
  return `config (${sourcePath})`;
111097
111425
  }
@@ -111133,7 +111461,7 @@ async function exportPromptCommand(options) {
111133
111461
  var VALID_EXPORT_ROLES;
111134
111462
  var init_prompts_export = __esm(() => {
111135
111463
  init_prompts();
111136
- VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"];
111464
+ VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "tdd-simple"];
111137
111465
  });
111138
111466
 
111139
111467
  // src/cli/prompts-init.ts
@@ -111175,7 +111503,6 @@ async function autoWirePromptsConfig(workdir) {
111175
111503
  "test-writer": ".nax/templates/test-writer.md",
111176
111504
  implementer: ".nax/templates/implementer.md",
111177
111505
  verifier: ".nax/templates/verifier.md",
111178
- "single-session": ".nax/templates/single-session.md",
111179
111506
  "tdd-simple": ".nax/templates/tdd-simple.md"
111180
111507
  }
111181
111508
  }
@@ -111196,7 +111523,6 @@ ${exampleConfig}`);
111196
111523
  "test-writer": ".nax/templates/test-writer.md",
111197
111524
  implementer: ".nax/templates/implementer.md",
111198
111525
  verifier: ".nax/templates/verifier.md",
111199
- "single-session": ".nax/templates/single-session.md",
111200
111526
  "tdd-simple": ".nax/templates/tdd-simple.md"
111201
111527
  };
111202
111528
  prompts.overrides = overrides;
@@ -111255,7 +111581,6 @@ var init_prompts_init = __esm(() => {
111255
111581
  { file: "test-writer.md", role: "test-writer" },
111256
111582
  { file: "implementer.md", role: "implementer", variant: "standard" },
111257
111583
  { file: "verifier.md", role: "verifier" },
111258
- { file: "single-session.md", role: "single-session" },
111259
111584
  { file: "tdd-simple.md", role: "tdd-simple" }
111260
111585
  ];
111261
111586
  });
@@ -111493,8 +111818,8 @@ async function resolveRunProfileOverride(opts) {
111493
111818
  return cliChain;
111494
111819
  if (opts.envProfile)
111495
111820
  return;
111496
- const readJson2 = opts._readJson ?? (async (path32) => {
111497
- const file2 = Bun.file(path32);
111821
+ const readJson2 = opts._readJson ?? (async (path31) => {
111822
+ const file2 = Bun.file(path31);
111498
111823
  if (!await file2.exists())
111499
111824
  return;
111500
111825
  return file2.json();
@@ -111874,11 +112199,11 @@ var init_rules_cli_deps = __esm(() => {
111874
112199
  init_logger2();
111875
112200
  init_rules_lint();
111876
112201
  _rulesCLIDeps = {
111877
- readFile: async (path32) => Bun.file(path32).text(),
111878
- writeFile: async (path32, content) => {
111879
- await Bun.write(path32, content);
112202
+ readFile: async (path31) => Bun.file(path31).text(),
112203
+ writeFile: async (path31, content) => {
112204
+ await Bun.write(path31, content);
111880
112205
  },
111881
- fileExists: async (path32) => Bun.file(path32).exists(),
112206
+ fileExists: async (path31) => Bun.file(path31).exists(),
111882
112207
  globInDir: (dir) => {
111883
112208
  try {
111884
112209
  return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join127(dir, f));
@@ -111886,8 +112211,8 @@ var init_rules_cli_deps = __esm(() => {
111886
112211
  return [];
111887
112212
  }
111888
112213
  },
111889
- mkdir: async (path32) => {
111890
- await mkdir33(path32, { recursive: true });
112214
+ mkdir: async (path31) => {
112215
+ await mkdir33(path31, { recursive: true });
111891
112216
  },
111892
112217
  globCanonicalRuleFiles: (workdir) => _rulesLintDeps.globCanonicalRuleFiles(workdir),
111893
112218
  globHasMatch: (pattern, cwd) => _rulesLintDeps.globHasMatch(pattern, cwd),
@@ -112404,10 +112729,10 @@ var init_setup_analyze = __esm(() => {
112404
112729
  init_detect();
112405
112730
  CANONICAL_SCRIPTS = ["build", "test", "lint", "type-check", "lint:fix"];
112406
112731
  _analyzeRepoDeps = {
112407
- fileExists: async (path32) => Bun.file(path32).exists(),
112408
- readJson: async (path32) => {
112732
+ fileExists: async (path31) => Bun.file(path31).exists(),
112733
+ readJson: async (path31) => {
112409
112734
  try {
112410
- const f = Bun.file(path32);
112735
+ const f = Bun.file(path31);
112411
112736
  if (!await f.exists())
112412
112737
  return null;
112413
112738
  return JSON.parse(await f.text());
@@ -112462,9 +112787,9 @@ async function fillScripts(workdir, analysis) {
112462
112787
  var TYPE_CHECK_KEY = "type-check", TYPE_CHECK_SCRIPT = "tsc --noEmit -p tsconfig.json", TYPE_CHECK_TURBO_PASSTHROUGH = "turbo run type-check", _fillScriptsDeps;
112463
112788
  var init_setup_fill = __esm(() => {
112464
112789
  _fillScriptsDeps = {
112465
- readJson: async (path32) => {
112790
+ readJson: async (path31) => {
112466
112791
  try {
112467
- const f = Bun.file(path32);
112792
+ const f = Bun.file(path31);
112468
112793
  if (!await f.exists())
112469
112794
  return null;
112470
112795
  return JSON.parse(await f.text());
@@ -112472,8 +112797,8 @@ var init_setup_fill = __esm(() => {
112472
112797
  return null;
112473
112798
  }
112474
112799
  },
112475
- writeFile: async (path32, content) => {
112476
- await Bun.write(path32, content);
112800
+ writeFile: async (path31, content) => {
112801
+ await Bun.write(path31, content);
112477
112802
  }
112478
112803
  };
112479
112804
  });
@@ -112531,9 +112856,9 @@ async function writeSetupConfig(workdir, config2, monoConfigs, _opts, deps = _wr
112531
112856
  var _writeSetupDeps;
112532
112857
  var init_setup_write = __esm(() => {
112533
112858
  _writeSetupDeps = {
112534
- writeFile: (path32, content) => Bun.write(path32, content).then(() => {}),
112535
- mkdir: async (path32) => {
112536
- const proc = Bun.spawn(["mkdir", "-p", path32]);
112859
+ writeFile: (path31, content) => Bun.write(path31, content).then(() => {}),
112860
+ mkdir: async (path31) => {
112861
+ const proc = Bun.spawn(["mkdir", "-p", path31]);
112537
112862
  await proc.exited;
112538
112863
  }
112539
112864
  };
@@ -112617,7 +112942,7 @@ var init_setup = __esm(() => {
112617
112942
  },
112618
112943
  generateSetupPlan: (ctx, analysis) => generateSetupPlan(ctx, analysis),
112619
112944
  runGate: (workdir, config2) => runSetupGate(workdir, config2),
112620
- fileExists: (path32) => Bun.file(path32).exists(),
112945
+ fileExists: (path31) => Bun.file(path31).exists(),
112621
112946
  writeSetupConfig: (workdir, config2, monoConfigs, opts) => writeSetupConfig(workdir, config2, monoConfigs, opts),
112622
112947
  stdout: (msg) => {
112623
112948
  process.stdout.write(`${msg}
@@ -112634,7 +112959,7 @@ var init_setup = __esm(() => {
112634
112959
  import { existsSync as existsSync43 } from "fs";
112635
112960
  import { join as join135 } from "path";
112636
112961
  async function resolveSpecPaths(options, deps) {
112637
- const explicit = (options.paths ?? []).filter((path32) => path32.trim().length > 0);
112962
+ const explicit = (options.paths ?? []).filter((path31) => path31.trim().length > 0);
112638
112963
  if (explicit.length > 0)
112639
112964
  return [...explicit];
112640
112965
  if (!options.feature)
@@ -112662,7 +112987,7 @@ async function lintOne(specPath, options, deps) {
112662
112987
  }
112663
112988
  const findings = lintSpecContent(content, {
112664
112989
  maxAcCount: options.maxAcCount,
112665
- fileExists: (path32) => deps.fileExists(join135(options.dir, path32))
112990
+ fileExists: (path31) => deps.fileExists(join135(options.dir, path31))
112666
112991
  });
112667
112992
  return {
112668
112993
  specPath,
@@ -112728,7 +113053,7 @@ var init_spec_lint_command = __esm(() => {
112728
113053
  init_prd();
112729
113054
  init_features_resolve();
112730
113055
  _specLintCommandDeps = {
112731
- readFile: async (path32) => Bun.file(path32).text(),
113056
+ readFile: async (path31) => Bun.file(path31).text(),
112732
113057
  fileExists: existsSync43,
112733
113058
  write: (line) => {
112734
113059
  console.log(line);
@@ -114648,11 +114973,11 @@ var require_react_reconciler_development = __commonJS(function(exports, module)
114648
114973
  fiber = fiber.next, id--;
114649
114974
  return fiber;
114650
114975
  }
114651
- function copyWithSetImpl(obj, path32, index, value) {
114652
- if (index >= path32.length)
114976
+ function copyWithSetImpl(obj, path31, index, value) {
114977
+ if (index >= path31.length)
114653
114978
  return value;
114654
- var key = path32[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
114655
- updated[key] = copyWithSetImpl(obj[key], path32, index + 1, value);
114979
+ var key = path31[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
114980
+ updated[key] = copyWithSetImpl(obj[key], path31, index + 1, value);
114656
114981
  return updated;
114657
114982
  }
114658
114983
  function copyWithRename(obj, oldPath, newPath) {
@@ -114672,11 +114997,11 @@ var require_react_reconciler_development = __commonJS(function(exports, module)
114672
114997
  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
114998
  return updated;
114674
114999
  }
114675
- function copyWithDeleteImpl(obj, path32, index) {
114676
- var key = path32[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
114677
- if (index + 1 === path32.length)
115000
+ function copyWithDeleteImpl(obj, path31, index) {
115001
+ var key = path31[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
115002
+ if (index + 1 === path31.length)
114678
115003
  return isArrayImpl(updated) ? updated.splice(key, 1) : delete updated[key], updated;
114679
- updated[key] = copyWithDeleteImpl(obj[key], path32, index + 1);
115004
+ updated[key] = copyWithDeleteImpl(obj[key], path31, index + 1);
114680
115005
  return updated;
114681
115006
  }
114682
115007
  function shouldSuspendImpl() {
@@ -124700,29 +125025,29 @@ Check the top-level render call using <` + componentName2 + ">.");
124700
125025
  var didWarnAboutNestedUpdates = false;
124701
125026
  var didWarnAboutFindNodeInStrictMode = {};
124702
125027
  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, path32, value) {
125028
+ overrideHookState = function(fiber, id, path31, value) {
124704
125029
  id = findHook(fiber, id);
124705
- id !== null && (path32 = copyWithSetImpl(id.memoizedState, path32, 0, value), id.memoizedState = path32, id.baseState = path32, fiber.memoizedProps = assign2({}, fiber.memoizedProps), path32 = enqueueConcurrentRenderForLane(fiber, 2), path32 !== null && scheduleUpdateOnFiber(path32, fiber, 2));
125030
+ 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
125031
  };
124707
- overrideHookStateDeletePath = function(fiber, id, path32) {
125032
+ overrideHookStateDeletePath = function(fiber, id, path31) {
124708
125033
  id = findHook(fiber, id);
124709
- id !== null && (path32 = copyWithDeleteImpl(id.memoizedState, path32, 0), id.memoizedState = path32, id.baseState = path32, fiber.memoizedProps = assign2({}, fiber.memoizedProps), path32 = enqueueConcurrentRenderForLane(fiber, 2), path32 !== null && scheduleUpdateOnFiber(path32, fiber, 2));
125034
+ 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
125035
  };
124711
125036
  overrideHookStateRenamePath = function(fiber, id, oldPath, newPath) {
124712
125037
  id = findHook(fiber, id);
124713
125038
  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
125039
  };
124715
- overrideProps = function(fiber, path32, value) {
124716
- fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path32, 0, value);
125040
+ overrideProps = function(fiber, path31, value) {
125041
+ fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path31, 0, value);
124717
125042
  fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
124718
- path32 = enqueueConcurrentRenderForLane(fiber, 2);
124719
- path32 !== null && scheduleUpdateOnFiber(path32, fiber, 2);
125043
+ path31 = enqueueConcurrentRenderForLane(fiber, 2);
125044
+ path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2);
124720
125045
  };
124721
- overridePropsDeletePath = function(fiber, path32) {
124722
- fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path32, 0);
125046
+ overridePropsDeletePath = function(fiber, path31) {
125047
+ fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path31, 0);
124723
125048
  fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
124724
- path32 = enqueueConcurrentRenderForLane(fiber, 2);
124725
- path32 !== null && scheduleUpdateOnFiber(path32, fiber, 2);
125049
+ path31 = enqueueConcurrentRenderForLane(fiber, 2);
125050
+ path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2);
124726
125051
  };
124727
125052
  overridePropsRenamePath = function(fiber, oldPath, newPath) {
124728
125053
  fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
@@ -126361,11 +126686,38 @@ init_config_profile();
126361
126686
  init_features_resolve();
126362
126687
  init_generate();
126363
126688
 
126689
+ // src/cli/run-max-iterations.ts
126690
+ function parseMaxIterationsFlag(raw) {
126691
+ if (raw === undefined)
126692
+ return { ok: true, value: undefined };
126693
+ const value = Number.parseInt(raw, 10);
126694
+ if (!Number.isFinite(value) || value < 1) {
126695
+ return { ok: false, message: "--max-iterations must be a positive integer" };
126696
+ }
126697
+ return { ok: true, value };
126698
+ }
126699
+ function applyMaxIterationsFlag(config2, flag) {
126700
+ if (flag === undefined)
126701
+ return { ...config2, execution: { ...config2.execution } };
126702
+ return { ...config2, execution: { ...config2.execution, maxIterations: flag } };
126703
+ }
126704
+
126364
126705
  // src/cli/run-mode.ts
126365
126706
  function resolveUseHeadless(input) {
126366
126707
  return !input.isTTY || input.headlessFlag || input.headlessEnv || input.formatterMode === "json";
126367
126708
  }
126368
126709
 
126710
+ // src/cli/run-parallel.ts
126711
+ function parseParallelFlag(raw) {
126712
+ if (raw === undefined)
126713
+ return { ok: true, value: undefined };
126714
+ const value = Number.parseInt(raw, 10);
126715
+ if (!Number.isFinite(value) || value < 1) {
126716
+ return { ok: false, message: "--parallel must be a positive integer (omit it to run sequentially)" };
126717
+ }
126718
+ return { ok: true, value };
126719
+ }
126720
+
126369
126721
  // bin/nax.ts
126370
126722
  init_status_dispatch();
126371
126723
 
@@ -126384,14 +126736,14 @@ function resolveEffective(detected, configPatterns) {
126384
126736
  return "detected";
126385
126737
  return "none";
126386
126738
  }
126387
- async function loadRawConfig(path32) {
126388
- const f = Bun.file(path32);
126739
+ async function loadRawConfig(path31) {
126740
+ const f = Bun.file(path31);
126389
126741
  if (!await f.exists())
126390
126742
  return {};
126391
126743
  return JSON.parse(await f.text());
126392
126744
  }
126393
- async function writeRawConfig(path32, data) {
126394
- await Bun.write(path32, `${JSON.stringify(data, null, 2)}
126745
+ async function writeRawConfig(path31, data) {
126746
+ await Bun.write(path31, `${JSON.stringify(data, null, 2)}
126395
126747
  `);
126396
126748
  }
126397
126749
  function deepSet(obj, keyPath, value) {
@@ -131475,8 +131827,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
131475
131827
  }
131476
131828
 
131477
131829
  // node_modules/ink/build/components/ErrorOverview.js
131478
- var cleanupPath = (path32) => {
131479
- return path32?.replace(`file://${cwd()}/`, "");
131830
+ var cleanupPath = (path31) => {
131831
+ return path31?.replace(`file://${cwd()}/`, "");
131480
131832
  };
131481
131833
  var stackUtils = new import_stack_utils.default({
131482
131834
  cwd: cwd(),
@@ -134517,7 +134869,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
134517
134869
  });
134518
134870
  process.exit(exitCode);
134519
134871
  });
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", "20").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 (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--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) => {
134872
+ 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
134873
  try {
134522
134874
  validateFeatureName(options.feature);
134523
134875
  } catch (err) {
@@ -134531,6 +134883,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
134531
134883
  console.error(source_default.red(`Invalid directory: ${err.message}`));
134532
134884
  process.exit(1);
134533
134885
  }
134886
+ const maxIterationsFlag = parseMaxIterationsFlag(options.maxIterations);
134887
+ if (!maxIterationsFlag.ok) {
134888
+ console.error(source_default.red(maxIterationsFlag.message));
134889
+ process.exit(1);
134890
+ }
134891
+ const parallelFlag = parseParallelFlag(options.parallel);
134892
+ if (!parallelFlag.ok) {
134893
+ console.error(source_default.red(parallelFlag.message));
134894
+ process.exit(1);
134895
+ }
134896
+ const parallel = parallelFlag.value;
134534
134897
  try {
134535
134898
  const { assertCompareAgentExclusive: assertCompareAgentExclusive2 } = await Promise.resolve().then(() => (init_preflight(), exports_preflight));
134536
134899
  assertCompareAgentExclusive2({ compare: options.compare, agent: options.agent });
@@ -134720,12 +135083,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
134720
135083
  config2.agent ??= {};
134721
135084
  config2.agent.default = options.agent;
134722
135085
  }
134723
- const maxIterations = Number.parseInt(options.maxIterations, 10);
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;
135086
+ config2 = applyMaxIterationsFlag(config2, maxIterationsFlag.value);
134729
135087
  if (options.maxCost !== undefined) {
134730
135088
  const maxCost = Number(options.maxCost);
134731
135089
  if (!Number.isFinite(maxCost) || maxCost <= 0) {
@@ -134765,15 +135123,6 @@ program2.command("run").description("Run the orchestration loop for a feature").
134765
135123
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
134766
135124
  }
134767
135125
  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
135126
  if (scheduleGate.target) {
134778
135127
  const scheduleController = new AbortController;
134779
135128
  const onSigint = () => scheduleController.abort();
@@ -135205,8 +135554,8 @@ configProfileCmd.command("current").description("Show the currently active profi
135205
135554
  });
135206
135555
  configProfileCmd.command("create <name>").description("Create a new empty profile").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (name, options) => {
135207
135556
  try {
135208
- const path32 = await profileCreateCommand(name, options.dir);
135209
- console.log(`Created profile at: ${path32}`);
135557
+ const path31 = await profileCreateCommand(name, options.dir);
135558
+ console.log(`Created profile at: ${path31}`);
135210
135559
  } catch (err) {
135211
135560
  console.error(source_default.red(`Error: ${err.message}`));
135212
135561
  process.exit(1);