@nathapp/nax 0.82.0 → 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 +78 -44
  2. package/dist/nax.js +1271 -658
  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.0",
2671
+ version: "0.82.2",
2672
2672
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
2673
2673
  type: "module",
2674
2674
  bin: {
@@ -4047,6 +4047,26 @@ var init_types2 = __esm(() => {
4047
4047
  };
4048
4048
  });
4049
4049
 
4050
+ // src/config/agent-defaults.ts
4051
+ function isBuiltInModelMap(agent, entry) {
4052
+ if (!Object.hasOwn(DEFAULT_MODEL_MAPS, agent) || typeof entry !== "object" || entry === null)
4053
+ return false;
4054
+ const builtIn = DEFAULT_MODEL_MAPS[agent];
4055
+ const tiers = Object.entries(entry);
4056
+ return tiers.length === Object.keys(builtIn).length && tiers.every(([tier, value]) => Object.hasOwn(builtIn, tier) && builtIn[tier] === value);
4057
+ }
4058
+ var DEFAULT_AGENT_PROTOCOL = "hybrid", DEFAULT_AGENT_NAME = "native", NATIVE_AGENT_NAME = "native", DEFAULT_MODEL_MAPS;
4059
+ var init_agent_defaults = __esm(() => {
4060
+ DEFAULT_MODEL_MAPS = {
4061
+ claude: { fast: "haiku", balanced: "sonnet", powerful: "opus" },
4062
+ native: {
4063
+ fast: "anthropic/claude-haiku-4-5",
4064
+ balanced: "anthropic/claude-sonnet-5",
4065
+ powerful: "anthropic/claude-opus-5-5"
4066
+ }
4067
+ };
4068
+ });
4069
+
4050
4070
  // node_modules/zod/v4/core/core.js
4051
4071
  function $constructor(name, initializer, params) {
4052
4072
  function init(inst, def) {
@@ -17936,7 +17956,7 @@ var init_schemas_sandbox = __esm(() => {
17936
17956
  SANDBOX_GLOB_CHARS = /[*?[\]{}]/;
17937
17957
  literalPath = exports_external.string().refine((p) => !SANDBOX_GLOB_CHARS.test(p), "sandbox paths must be literal: no * ? [ ] { } (spec F1)");
17938
17958
  SandboxConfigSchema = exports_external.object({
17939
- enabled: exports_external.boolean().default(false),
17959
+ enabled: exports_external.boolean().default(true),
17940
17960
  backend: exports_external.enum(["srt"]).default("srt"),
17941
17961
  filesystem: exports_external.object({
17942
17962
  allowWrite: exports_external.array(literalPath).default([]),
@@ -18184,7 +18204,7 @@ async function runArgv(options) {
18184
18204
  stdoutController.abort();
18185
18205
  stderrController.abort();
18186
18206
  };
18187
- const timerId = setTimeout(() => {
18207
+ const timerId = _argvExecDeps.setTimeout(() => {
18188
18208
  timedOut = true;
18189
18209
  killGroup();
18190
18210
  stopReaders();
@@ -18201,7 +18221,7 @@ async function runArgv(options) {
18201
18221
  const graceMs = _argvExecDeps.drainGraceMs;
18202
18222
  let graceTimerId;
18203
18223
  const gracePromise = new Promise((resolve3) => {
18204
- graceTimerId = setTimeout(() => resolve3("expired"), graceMs);
18224
+ graceTimerId = _argvExecDeps.setTimeout(() => resolve3("expired"), graceMs);
18205
18225
  });
18206
18226
  const stdoutSettled = await Promise.race([
18207
18227
  stdoutPromise,
@@ -18212,7 +18232,7 @@ async function runArgv(options) {
18212
18232
  gracePromise.then(() => "expired")
18213
18233
  ]);
18214
18234
  if (graceTimerId !== undefined)
18215
- clearTimeout(graceTimerId);
18235
+ _argvExecDeps.clearTimeout(graceTimerId);
18216
18236
  const stdoutClosed = stdoutSettled !== "expired";
18217
18237
  const stderrClosed = stderrSettled !== "expired";
18218
18238
  if (!stdoutClosed || !stderrClosed) {
@@ -18225,7 +18245,7 @@ async function runArgv(options) {
18225
18245
  }
18226
18246
  const stdoutFinal = stdoutClosed ? stdoutSettled : await stdoutPromise;
18227
18247
  const stderrFinal = stderrClosed ? stderrSettled : await stderrPromise;
18228
- clearTimeout(timerId);
18248
+ _argvExecDeps.clearTimeout(timerId);
18229
18249
  signal?.removeEventListener("abort", onAbort);
18230
18250
  return {
18231
18251
  exitCode,
@@ -18242,7 +18262,9 @@ var init_argv_exec = __esm(() => {
18242
18262
  _argvExecDeps = {
18243
18263
  spawn,
18244
18264
  killProcessGroup,
18245
- drainGraceMs: DRAIN_GRACE_MS
18265
+ drainGraceMs: DRAIN_GRACE_MS,
18266
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
18267
+ clearTimeout: (id) => clearTimeout(id)
18246
18268
  };
18247
18269
  });
18248
18270
 
@@ -18376,10 +18398,26 @@ function isNaxConfigFile(root, resolved) {
18376
18398
  return segments.length === 2 || segments.length >= 4 && segments[1] === "mono";
18377
18399
  }
18378
18400
  function isNaxOwnedWritePath(rel) {
18401
+ return naxOwnedKind(rel) !== undefined;
18402
+ }
18403
+ function naxOwnedKind(rel) {
18379
18404
  const segments = rel.split("/");
18380
18405
  if (segments.length === 1 && QUEUE_CONTROL_FILES.has(segments[0] ?? ""))
18381
- return true;
18382
- 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
+ }
18383
18421
  }
18384
18422
  function naxOwnedWriteRefusal(tool, rel, exemptRel) {
18385
18423
  if (!NAX_OWNED_WRITE_TOOLS.has(tool))
@@ -19544,7 +19582,7 @@ function capitalize(text) {
19544
19582
  return text.charAt(0).toUpperCase() + text.slice(1);
19545
19583
  }
19546
19584
  function escalateDescription(shell, patterns) {
19547
- 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;
19548
19586
  }
19549
19587
  function rawDescription(shell, containment = RAW_UNCONTAINED) {
19550
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;
@@ -19638,7 +19676,8 @@ ${launched.stderr}`;
19638
19676
  isError: launched.timedOut || launched.exitCode !== 0 || launched.aborted === true,
19639
19677
  audit: {
19640
19678
  executed: launched.executed,
19641
- ...launched.sandbox !== undefined ? { sandbox: launched.sandbox } : {}
19679
+ ...launched.sandbox !== undefined ? { sandbox: launched.sandbox } : {},
19680
+ ...!launched.timedOut && launched.aborted !== true ? { exitCode: launched.exitCode } : {}
19642
19681
  },
19643
19682
  resultBytesPreTruncation: Buffer.byteLength(body, "utf8")
19644
19683
  };
@@ -19756,6 +19795,46 @@ var init_delete = __esm(() => {
19756
19795
  };
19757
19796
  });
19758
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
+
19759
19838
  // src/tools/edit.ts
19760
19839
  import { readFile, stat, writeFile } from "fs/promises";
19761
19840
  function countOccurrences(haystack, needle) {
@@ -19769,11 +19848,17 @@ function countOccurrences(haystack, needle) {
19769
19848
  }
19770
19849
  return count;
19771
19850
  }
19772
- var editTool;
19851
+ var _editDeps, editTool;
19773
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
+ };
19774
19859
  editTool = {
19775
19860
  name: "Edit",
19776
- 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.",
19777
19862
  inputSchema: {
19778
19863
  type: "object",
19779
19864
  properties: {
@@ -19794,7 +19879,7 @@ var init_edit = __esm(() => {
19794
19879
  return { content: "old_string and new_string must be strings", isError: true };
19795
19880
  }
19796
19881
  try {
19797
- const { size } = await stat(target);
19882
+ const { size } = await _editDeps.stat(target);
19798
19883
  if (size > ctx.maxFileBytes) {
19799
19884
  return {
19800
19885
  content: `the file is ${size} bytes, which exceeds the ${ctx.maxFileBytes}-byte file ceiling -- refusing to edit ${target}`,
@@ -19806,7 +19891,7 @@ var init_edit = __esm(() => {
19806
19891
  }
19807
19892
  let source;
19808
19893
  try {
19809
- source = await readFile(target, "utf8");
19894
+ source = await _editDeps.readFile(target, "utf8");
19810
19895
  } catch (err) {
19811
19896
  return { content: err instanceof Error ? err.message : String(err), isError: true };
19812
19897
  }
@@ -19821,8 +19906,12 @@ var init_edit = __esm(() => {
19821
19906
  };
19822
19907
  }
19823
19908
  try {
19824
- await writeFile(target, source.replace(oldString, newString), "utf8");
19825
- 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}` };
19826
19915
  } catch (err) {
19827
19916
  return { content: err instanceof Error ? err.message : String(err), isError: true };
19828
19917
  }
@@ -20904,9 +20993,6 @@ var ASK_NO_CHANNEL_REASON = "matched an ask rule requiring human approval; no ap
20904
20993
  var init_ask = () => {};
20905
20994
 
20906
20995
  // src/permissions/bash-lex.ts
20907
- function refused(construct) {
20908
- return { kind: "refused", construct };
20909
- }
20910
20996
  function doubleQuoteEnd(command, from) {
20911
20997
  for (let i = from;i < command.length; i += 1) {
20912
20998
  const char = command[i];
@@ -20921,7 +21007,7 @@ function doubleQuoteEnd(command, from) {
20921
21007
  }
20922
21008
  function lexBashCommand(command) {
20923
21009
  if (command.trim() === "")
20924
- return refused("an empty command");
21010
+ return { kind: "refused", construct: "an empty command", prefix: [] };
20925
21011
  const segments = [];
20926
21012
  let tokens = [];
20927
21013
  let redirects = [];
@@ -20929,6 +21015,10 @@ function lexBashCommand(command) {
20929
21015
  let opaque = false;
20930
21016
  let started = false;
20931
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
+ }
20932
21022
  function flushWord() {
20933
21023
  if (!started)
20934
21024
  return;
@@ -20960,7 +21050,7 @@ function lexBashCommand(command) {
20960
21050
  if (char === "'") {
20961
21051
  const end = command.indexOf("'", i + 1);
20962
21052
  if (end === -1)
20963
- return refused("an unbalanced single quote");
21053
+ return refusedHere("an unbalanced single quote");
20964
21054
  word += command.slice(i + 1, end);
20965
21055
  started = true;
20966
21056
  i = end + 1;
@@ -20969,12 +21059,12 @@ function lexBashCommand(command) {
20969
21059
  if (char === '"') {
20970
21060
  const end = doubleQuoteEnd(command, i + 1);
20971
21061
  if (end === -1)
20972
- return refused("an unbalanced double quote");
21062
+ return refusedHere("an unbalanced double quote");
20973
21063
  const inner = command.slice(i + 1, end);
20974
21064
  if (inner.includes("$("))
20975
- return refused("a command substitution `$(...)`");
21065
+ return refusedHere("a command substitution `$(...)`");
20976
21066
  if (inner.includes("`"))
20977
- return refused("a backtick command substitution");
21067
+ return refusedHere("a backtick command substitution");
20978
21068
  if (inner.includes("$"))
20979
21069
  opaque = true;
20980
21070
  word += inner;
@@ -20984,32 +21074,32 @@ function lexBashCommand(command) {
20984
21074
  }
20985
21075
  if (char === "\\") {
20986
21076
  if (next === undefined)
20987
- return refused("a trailing backslash");
21077
+ return refusedHere("a trailing backslash");
20988
21078
  word += next;
20989
21079
  started = true;
20990
21080
  i += 2;
20991
21081
  continue;
20992
21082
  }
20993
21083
  if (char === "(" || char === ")")
20994
- return refused("a subshell `( ... )`");
21084
+ return refusedHere("a subshell `( ... )`");
20995
21085
  if (char === "!" && !started)
20996
- return refused("a `!` negation");
21086
+ return refusedHere("a `!` negation");
20997
21087
  if (char === "#" && !started)
20998
- return refused("a `#` comment");
21088
+ return refusedHere("a `#` comment");
20999
21089
  if (char === "$" && next === "(")
21000
- return refused("a command substitution `$(...)`");
21090
+ return refusedHere("a command substitution `$(...)`");
21001
21091
  if (char === "`")
21002
- return refused("a backtick command substitution");
21092
+ return refusedHere("a backtick command substitution");
21003
21093
  if ((char === "<" || char === ">") && next === "(") {
21004
- return refused("a process substitution `<(...)` / `>(...)`");
21094
+ return refusedHere("a process substitution `<(...)` / `>(...)`");
21005
21095
  }
21006
21096
  if (char === "<" && next === "<")
21007
- return refused("a here-document `<<`");
21097
+ return refusedHere("a here-document `<<`");
21008
21098
  if ((char === "<" || char === ">") && next === "&") {
21009
- return refused("file-descriptor duplication (`2>&1`)");
21099
+ return refusedHere("file-descriptor duplication (`2>&1`)");
21010
21100
  }
21011
21101
  if (char === "&" && next === ">")
21012
- return refused("the `&>` redirection form");
21102
+ return refusedHere("the `&>` redirection form");
21013
21103
  if (char === "$") {
21014
21104
  opaque = true;
21015
21105
  word += char;
@@ -21026,21 +21116,21 @@ function lexBashCommand(command) {
21026
21116
  ` || char === ";") {
21027
21117
  const error49 = flushSegment(";");
21028
21118
  if (error49 !== undefined)
21029
- return refused(error49);
21119
+ return refusedHere(error49);
21030
21120
  i += 1;
21031
21121
  continue;
21032
21122
  }
21033
21123
  if (char === "&" && next === "&" || char === "|" && next === "|") {
21034
21124
  const error49 = flushSegment(char === "&" ? "&&" : "||");
21035
21125
  if (error49 !== undefined)
21036
- return refused(error49);
21126
+ return refusedHere(error49);
21037
21127
  i += 2;
21038
21128
  continue;
21039
21129
  }
21040
21130
  if (char === "|" || char === "&") {
21041
21131
  const error49 = flushSegment(char);
21042
21132
  if (error49 !== undefined)
21043
- return refused(error49);
21133
+ return refusedHere(error49);
21044
21134
  i += 1;
21045
21135
  continue;
21046
21136
  }
@@ -21065,7 +21155,7 @@ function lexBashCommand(command) {
21065
21155
  }
21066
21156
  const error48 = flushSegment();
21067
21157
  if (error48 !== undefined)
21068
- return refused(error48);
21158
+ return refusedHere(error48);
21069
21159
  return { kind: "ok", segments };
21070
21160
  }
21071
21161
 
@@ -21196,7 +21286,9 @@ function checkPayload(args, segment, cwd) {
21196
21286
  case "no-target":
21197
21287
  return { refusal: deny("`cd` with no target is refused") };
21198
21288
  case "option-shaped":
21199
- 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
+ };
21200
21292
  case "opaque":
21201
21293
  case "unresolved":
21202
21294
  return { refusal: deny(`cd target "${cdResult.text}" is not inside the permitted root`, true) };
@@ -21233,29 +21325,30 @@ function checkBashCommand(args) {
21233
21325
  if (command.trim() === "")
21234
21326
  return deny(`"command" must not be empty`);
21235
21327
  const lexed = lexBashCommand(command);
21236
- if (lexed.kind === "refused") {
21237
- 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);
21238
- }
21239
- for (const segment of lexed.segments) {
21328
+ const segments = lexed.kind === "ok" ? lexed.segments : lexed.prefix;
21329
+ for (const segment of segments) {
21240
21330
  if (args.denyEntry !== undefined && matchesSegment(args.denyEntry, segment)) {
21241
21331
  return deny(`${tool} segment "${render(segment)}" is denied for this stage by rule ${ruleExpr(tool, args.denyEntry, segment)}`);
21242
21332
  }
21243
21333
  }
21244
- for (const segment of lexed.segments) {
21245
- if (args.grant.unconditional || matchesSegment(args.grant, segment))
21246
- continue;
21247
- const granted = args.grant.raw.filter((pattern) => pattern !== "*").join(", ");
21248
- const alternatives = granted === "" ? "no command forms are granted for this stage" : `granted forms: ${granted}`;
21249
- return deny(`${tool} is not granted "${render(segment)}" -- ${alternatives}`, false, true);
21250
- }
21251
21334
  let cwd = [args.initialPath];
21252
- for (const segment of lexed.segments) {
21335
+ for (const segment of segments) {
21253
21336
  const result = checkPayload(args, segment, cwd);
21254
21337
  if (result.refusal !== undefined)
21255
21338
  return result.refusal;
21256
21339
  cwd = nextWorkingDirectories(segment, cwd, result.cdTargets);
21257
21340
  }
21258
- 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) {
21259
21352
  if (args.askEntry !== undefined && matchesSegment(args.askEntry, segment)) {
21260
21353
  return { kind: "ask", rule: ruleExpr(tool, args.askEntry, segment) };
21261
21354
  }
@@ -21276,15 +21369,16 @@ function protectedHit(args, candidate, cwd) {
21276
21369
  for (const directory of cwd) {
21277
21370
  const lexical = realOrRaw(resolve10(directory, candidate));
21278
21371
  if (isNaxConfigFile(args.root, lexical))
21279
- return candidate;
21372
+ return { kind: "config", hit: candidate };
21280
21373
  const resolved = args.resolvePath(candidate, directory);
21281
21374
  if (resolved === null)
21282
21375
  continue;
21283
21376
  const rel = relative5(args.root, resolved).split(sep4).join("/");
21284
21377
  if (rel.startsWith(".."))
21285
21378
  continue;
21286
- if (isNaxOwnedWritePath(rel))
21287
- return candidate;
21379
+ const kind = naxOwnedKind(rel);
21380
+ if (kind !== undefined)
21381
+ return { kind, hit: candidate };
21288
21382
  }
21289
21383
  return;
21290
21384
  }
@@ -21304,7 +21398,7 @@ function screenRawBashCommand(args) {
21304
21398
  continue;
21305
21399
  const hit = protectedHit(args, token.text, cwd);
21306
21400
  if (hit !== undefined) {
21307
- 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"));
21308
21402
  }
21309
21403
  }
21310
21404
  for (const redirect of segment.redirects) {
@@ -21312,7 +21406,7 @@ function screenRawBashCommand(args) {
21312
21406
  continue;
21313
21407
  const hit = protectedHit(args, redirect.target, cwd);
21314
21408
  if (hit !== undefined) {
21315
- 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"));
21316
21410
  }
21317
21411
  }
21318
21412
  const cdResult = cdTargetsFor(segment, cwd, args.resolvePath);
@@ -22570,6 +22664,47 @@ var init_provider_advertise = __esm(() => {
22570
22664
  init_provider_types();
22571
22665
  });
22572
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
+
22573
22708
  // src/tools/read.ts
22574
22709
  function parsePositiveInt(value, field) {
22575
22710
  if (typeof value !== "number" || !Number.isInteger(value))
@@ -22589,11 +22724,12 @@ function countLines(prefix) {
22589
22724
  var UNSUPPORTED_RANGE_ALIASES, readTool;
22590
22725
  var init_read = __esm(() => {
22591
22726
  init_bounded_io();
22727
+ init_read_continuation();
22592
22728
  init_truncate();
22593
22729
  UNSUPPORTED_RANGE_ALIASES = ["start_line", "end_line", "start", "end", "line", "lineEnd", "size"];
22594
22730
  readTool = {
22595
22731
  name: "Read",
22596
- 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.`,
22597
22733
  inputSchema: {
22598
22734
  type: "object",
22599
22735
  properties: {
@@ -22623,9 +22759,21 @@ var init_read = __esm(() => {
22623
22759
  const prefix = await readPrefix(target, readCeiling);
22624
22760
  const bounded2 = Buffer.byteLength(prefix, "utf8") > readCeiling;
22625
22761
  const lineCount = countLines(prefix);
22626
- const header2 = `[${bounded2 ? `${lineCount}+` : `${lineCount}`} lines]`;
22627
- return { content: prefix === "" ? header2 : `${header2}
22628
- ${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 };
22629
22777
  }
22630
22778
  let offset = 1;
22631
22779
  if (hasOffset) {
@@ -22643,25 +22791,38 @@ ${prefix}` };
22643
22791
  }
22644
22792
  const body = await readPrefix(target, ctx.maxFileBytes);
22645
22793
  const bounded = Buffer.byteLength(body, "utf8") > ctx.maxFileBytes;
22646
- const trailingNewline = body.endsWith(`
22647
- `);
22648
- const lines = trailingNewline ? body.slice(0, -1).split(`
22649
- `) : body.split(`
22650
- `);
22651
- const totalLines = lines.length;
22794
+ const totalLines = countLines(body);
22652
22795
  const totalLabel = bounded ? `${totalLines}+` : `${totalLines}`;
22653
22796
  if (offset > totalLines) {
22654
22797
  return {
22655
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`
22656
22799
  };
22657
22800
  }
22801
+ const lines = splitModelLines(body);
22658
22802
  const startIndex = offset - 1;
22659
22803
  const endLine = limit === undefined ? totalLines : Math.min(startIndex + limit, totalLines);
22660
22804
  const selected = lines.slice(startIndex, endLine).join(`
22661
22805
  `);
22662
- const header = `[lines ${offset}-${endLine} of ${totalLabel}]
22663
- `;
22664
- 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 };
22665
22826
  } catch (err) {
22666
22827
  return { content: err instanceof Error ? err.message : String(err), isError: true };
22667
22828
  }
@@ -23563,15 +23724,36 @@ var init_types3 = __esm(() => {
23563
23724
  });
23564
23725
 
23565
23726
  // src/command-safety/rule-scorer.ts
23566
- function scoreRules(command) {
23727
+ import { posix } from "path";
23728
+ function escapeRegExp(text) {
23729
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23730
+ }
23731
+ function usableRoot(root) {
23732
+ if (root === undefined || !root.startsWith("/"))
23733
+ return;
23734
+ const normalized = posix.normalize(root).replace(/\/+$/, "");
23735
+ return normalized.split("/").filter(Boolean).length >= MIN_ROOT_SEGMENTS ? normalized : undefined;
23736
+ }
23737
+ function maskProjectRoot(command, root) {
23738
+ const usable = usableRoot(root);
23739
+ if (usable === undefined)
23740
+ return command;
23741
+ const underRoot = new RegExp(`(?<=^|[${PATH_END}=:])${escapeRegExp(usable)}((?:/[^${PATH_END}]*)?)(?=$|[${PATH_END}])`, "g");
23742
+ return command.replace(underRoot, (match, rest, offset) => {
23743
+ CLEAN_TAIL.lastIndex = offset + match.length;
23744
+ return /\.\.|\\|,/.test(rest) || !CLEAN_TAIL.test(command) ? match : `.${rest}`;
23745
+ });
23746
+ }
23747
+ function scoreRules(command, context = {}) {
23567
23748
  try {
23568
- const hits = Object.fromEntries(QUESTION_IDS.map((id) => [id, RULES[id].some((re) => re.test(command))]));
23749
+ const masked = maskProjectRoot(command, context.root);
23750
+ const hits = Object.fromEntries(QUESTION_IDS.map((id) => [id, RULES[id].some((re) => re.test(id === "outside_project" ? masked : command))]));
23569
23751
  return { version: RULE_SET_VERSION, hits };
23570
23752
  } catch (err) {
23571
23753
  return { version: RULE_SET_VERSION, hits: NO_HITS, error: errorMessage(err) };
23572
23754
  }
23573
23755
  }
23574
- var RULE_SET_VERSION = 1, RULES, NO_HITS;
23756
+ var RULE_SET_VERSION = 2, RULES, NO_HITS, PATH_END, CLEAN_TAIL, MIN_ROOT_SEGMENTS = 2;
23575
23757
  var init_rule_scorer = __esm(() => {
23576
23758
  init_types3();
23577
23759
  RULES = {
@@ -23617,6 +23799,8 @@ var init_rule_scorer = __esm(() => {
23617
23799
  privilege: [/(?:^|[\s;&|(])(?:sudo|doas)\s/, /\b(?:chmod|chown|chgrp)\b/]
23618
23800
  };
23619
23801
  NO_HITS = Object.freeze(Object.fromEntries(QUESTION_IDS.map((id) => [id, false])));
23802
+ PATH_END = String.raw`\s'"\`;&|<>()`;
23803
+ CLEAN_TAIL = /['")]*(?=$|[\s;&|<>])/y;
23620
23804
  });
23621
23805
 
23622
23806
  // src/command-safety/shadow.ts
@@ -23664,6 +23848,7 @@ function createCommandShadow(opts) {
23664
23848
  const { obs } = entry;
23665
23849
  const r = model.result;
23666
23850
  const cwd = entry.run?.cwd ?? obs.cwd;
23851
+ const rules = cwd !== obs.cwd ? scoreRules(obs.command, { root: cwd }) : entry.rules;
23667
23852
  return {
23668
23853
  at: _commandShadowDeps.now(),
23669
23854
  runId: opts.runId,
@@ -23676,7 +23861,7 @@ function createCommandShadow(opts) {
23676
23861
  ...cwd !== undefined ? { cwd } : {},
23677
23862
  mechanical: obs.mechanical,
23678
23863
  outcome,
23679
- rules: entry.rules,
23864
+ rules,
23680
23865
  ...callIdentifiers(obs),
23681
23866
  model: {
23682
23867
  status: model.cached && r.status === "answered" ? "cached" : r.status,
@@ -23694,7 +23879,7 @@ function createCommandShadow(opts) {
23694
23879
  try {
23695
23880
  if (entries.has(key))
23696
23881
  return;
23697
- const entry = { obs, rules: scoreRules(obs.command), written: false };
23882
+ const entry = { obs, rules: scoreRules(obs.command, { root: obs.cwd }), written: false };
23698
23883
  entries.set(key, entry);
23699
23884
  const { promise: promise2, cached: cached2 } = classifyCached(obs.command);
23700
23885
  track(inFlight2, promise2.then((result) => {
@@ -24152,7 +24337,7 @@ var init_scratchpad = __esm(() => {
24152
24337
  init_truncate();
24153
24338
  scratchpadWriteTool = {
24154
24339
  name: "ScratchpadWrite",
24155
- 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.",
24156
24341
  inputSchema: {
24157
24342
  type: "object",
24158
24343
  properties: {
@@ -24394,33 +24579,97 @@ var init_spill = __esm(() => {
24394
24579
  // src/tools/tool-audit.ts
24395
24580
  import { mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
24396
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
+ }
24397
24611
  function createNoOpToolAuditSink() {
24398
24612
  return { record() {}, async flush() {} };
24399
24613
  }
24614
+ function nextFileStamp() {
24615
+ const now = Date.now();
24616
+ lastFileStamp = now > lastFileStamp ? now : lastFileStamp + 1;
24617
+ return lastFileStamp;
24618
+ }
24400
24619
  function createToolAuditSink(opts) {
24401
24620
  const calls = [];
24402
- 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 = {
24403
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
+ }
24404
24646
  calls.push(entry);
24405
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
+ },
24406
24656
  async flush() {
24407
- if (calls.length === 0)
24657
+ if (partialFlushed)
24408
24658
  return;
24409
- await mkdir7(opts.dir, { recursive: true });
24410
- const body = JSON.stringify({
24411
- schemaVersion: TOOL_AUDIT_SCHEMA_VERSION,
24412
- ...opts.header ?? {},
24413
- sessionName: opts.sessionName,
24414
- calls: redactRowStrings(calls)
24415
- }, null, 2);
24416
- const prefix = opts.header?.runId !== undefined ? `${opts.header.runId}-` : "";
24417
- await writeFile5(join15(opts.dir, `${prefix}${Date.now()}-${opts.sessionName}.json`), body);
24659
+ if (runId !== undefined)
24660
+ unregisterToolAuditSink(runId, sink);
24661
+ await write(false);
24418
24662
  }
24419
24663
  };
24664
+ if (runId !== undefined)
24665
+ registerToolAuditSink(runId, sink);
24666
+ return sink;
24420
24667
  }
24421
- var TOOL_AUDIT_SCHEMA_VERSION = 1;
24668
+ var openToolAuditSinks, TOOL_AUDIT_SCHEMA_VERSION = 1, lastFileStamp = 0;
24422
24669
  var init_tool_audit = __esm(() => {
24670
+ init_logger2();
24423
24671
  init_permissions();
24672
+ openToolAuditSinks = new Map;
24424
24673
  });
24425
24674
 
24426
24675
  // src/tools/write.ts
@@ -24524,6 +24773,7 @@ function createCodingToolRuntime(opts) {
24524
24773
  ...audit?.target !== undefined ? { target: audit.target } : {},
24525
24774
  ...audit?.approval !== undefined ? { approval: audit.approval } : {},
24526
24775
  ...audit?.sandbox !== undefined ? { sandbox: audit.sandbox } : {},
24776
+ ...audit?.exitCode !== undefined ? { exitCode: audit.exitCode } : {},
24527
24777
  ...provider !== undefined ? { provider } : {},
24528
24778
  ...resultBytesPreTruncation !== undefined ? { resultBytesPreTruncation } : {},
24529
24779
  ...opts.callId !== undefined ? { callId: opts.callId } : {},
@@ -24629,6 +24879,7 @@ function createCodingToolRuntime(opts) {
24629
24879
  tool: policyIdentity,
24630
24880
  stage: opts.pipelineStage ?? "unknown",
24631
24881
  rule: verdict.rule ?? verdict.reason,
24882
+ ...verdict.rule !== undefined ? { matchedRule: verdict.rule } : {},
24632
24883
  summary: ask.summary,
24633
24884
  ...ask.unshowable ? { unshowable: true } : {},
24634
24885
  ...typeof input[tool.scope.commandField ?? ""] === "string" ? { command: input[tool.scope.commandField] } : {},
@@ -25557,7 +25808,9 @@ var init_config_guards = __esm(() => {
25557
25808
  "review.gateLLMChecksOnMechanicalPass": "LLM review checks are sequenced by the story orchestrator; this key never had an effect after #1859",
25558
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`",
25559
25810
  "plan.citationThreshold": "this key only fed the removed pipeline plan mode",
25560
- "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"
25561
25814
  };
25562
25815
  });
25563
25816
 
@@ -26727,12 +26980,10 @@ var init_schemas_execution = __esm(() => {
26727
26980
  }).default({ format: "auto" }),
26728
26981
  autofix: exports_external.object({
26729
26982
  enabled: exports_external.boolean().default(true),
26730
- maxAttempts: exports_external.number().int().min(1).default(3),
26731
- enforceTestWriterIsolation: exports_external.boolean().default(true)
26983
+ maxAttempts: exports_external.number().int().min(1).default(3)
26732
26984
  }).default({
26733
26985
  enabled: true,
26734
- maxAttempts: 3,
26735
- enforceTestWriterIsolation: true
26986
+ maxAttempts: 3
26736
26987
  }),
26737
26988
  forceExit: exports_external.boolean().default(false),
26738
26989
  detectOpenHandles: exports_external.boolean().default(true),
@@ -26799,9 +27050,10 @@ var init_schemas_execution = __esm(() => {
26799
27050
  });
26800
27051
 
26801
27052
  // src/config/schemas-infra.ts
26802
- 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;
26803
27054
  var init_schemas_infra = __esm(() => {
26804
27055
  init_zod();
27056
+ init_agent_defaults();
26805
27057
  init_schemas_model();
26806
27058
  PlanConfigSchema = exports_external.object({
26807
27059
  model: ConfiguredModelSchema,
@@ -27021,8 +27273,8 @@ var init_schemas_infra = __esm(() => {
27021
27273
  budgetMultiplier: 0.5
27022
27274
  };
27023
27275
  AgentConfigSchema = exports_external.object({
27024
- protocol: exports_external.enum(["acp", "native", "hybrid"]).default("acp"),
27025
- default: exports_external.string().trim().min(1, "agent.default must be non-empty").default("claude"),
27276
+ protocol: exports_external.enum(["acp", "native", "hybrid"]).default(DEFAULT_AGENT_PROTOCOL),
27277
+ default: exports_external.string().trim().min(1, "agent.default must be non-empty").default(DEFAULT_AGENT_NAME),
27026
27278
  maxInteractionTurns: exports_external.number().int().min(1).max(100).default(20),
27027
27279
  promptAudit: PromptAuditConfigSchema.default({ enabled: false }),
27028
27280
  usageAudit: UsageAuditConfigSchema.default({ enabled: false }),
@@ -27049,10 +27301,19 @@ var init_schemas_infra = __esm(() => {
27049
27301
  PrecheckConfigSchema = exports_external.object({
27050
27302
  storySizeGate: StorySizeGateConfigSchema
27051
27303
  });
27304
+ PROMPT_OVERRIDE_ROLES = ["no-test", "test-writer", "implementer", "verifier", "tdd-simple"];
27052
27305
  PromptsConfigSchema = exports_external.object({
27053
- overrides: exports_external.record(exports_external.string().refine((key) => ["no-test", "test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
27054
- message: "Role must be one of: no-test, test-writer, implementer, verifier, single-session, tdd-simple"
27055
- }), 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(),
27056
27317
  behavioralGuardrails: exports_external.enum(["off", "lite", "strict"]).default("lite")
27057
27318
  });
27058
27319
  ProjectProfileSchema = exports_external.object({
@@ -27101,19 +27362,29 @@ var init_schemas_infra = __esm(() => {
27101
27362
  function asModelDef(entry) {
27102
27363
  return typeof entry === "object" && entry !== null ? entry : null;
27103
27364
  }
27365
+ function declaredModelAgents(models) {
27366
+ return Object.entries(models ?? {}).filter(([agent, entry]) => !isBuiltInModelMap(agent, entry)).map(([agent]) => agent);
27367
+ }
27104
27368
  function validateProtocolGate(data, ctx) {
27105
- const protocol = data.agent?.protocol ?? DEFAULT_PROTOCOL;
27106
- const modelAgents = Object.keys(data.models ?? {});
27107
- if (protocol === DEFAULT_PROTOCOL && modelAgents.includes(NATIVE)) {
27369
+ const protocol = data.agent?.protocol ?? DEFAULT_AGENT_PROTOCOL;
27370
+ const modelAgents = declaredModelAgents(data.models);
27371
+ if (protocol === "acp" && (data.agent?.default ?? DEFAULT_AGENT_NAME) === NATIVE_AGENT_NAME) {
27108
27372
  ctx.addIssue({
27109
27373
  code: "custom",
27110
- path: ["models", NATIVE],
27374
+ path: ["agent", "default"],
27375
+ message: 'agent.protocol "acp" cannot reach agent.default "native", which is also the built-in default when agent.default is unset. Set agent.default to an acpx agent such as "claude", or use agent.protocol "hybrid".'
27376
+ });
27377
+ }
27378
+ if (protocol === "acp" && modelAgents.includes(NATIVE_AGENT_NAME)) {
27379
+ ctx.addIssue({
27380
+ code: "custom",
27381
+ path: ["models", NATIVE_AGENT_NAME],
27111
27382
  message: 'models.native requires agent.protocol "hybrid" or "native" (it is "acp"). Set agent.protocol, or remove the native entry.'
27112
27383
  });
27113
27384
  }
27114
- if (protocol === NATIVE) {
27385
+ if (protocol === NATIVE_AGENT_NAME) {
27115
27386
  for (const agent of modelAgents) {
27116
- if (agent === NATIVE)
27387
+ if (agent === NATIVE_AGENT_NAME)
27117
27388
  continue;
27118
27389
  ctx.addIssue({
27119
27390
  code: "custom",
@@ -27121,7 +27392,7 @@ function validateProtocolGate(data, ctx) {
27121
27392
  message: `agent.protocol "native" permits only models.native; "${agent}" is an acpx agent. Use "hybrid" to run both.`
27122
27393
  });
27123
27394
  }
27124
- if ((data.agent?.default ?? DEFAULT_AGENT) !== NATIVE) {
27395
+ if ((data.agent?.default ?? DEFAULT_AGENT_NAME) !== NATIVE_AGENT_NAME) {
27125
27396
  ctx.addIssue({
27126
27397
  code: "custom",
27127
27398
  path: ["agent", "default"],
@@ -27133,7 +27404,7 @@ function validateProtocolGate(data, ctx) {
27133
27404
  validateFallbackLadderAgents(data, ctx);
27134
27405
  }
27135
27406
  function validateNativeModelIds(data, ctx) {
27136
- for (const [tier, entry] of Object.entries(data.models?.[NATIVE] ?? {})) {
27407
+ for (const [tier, entry] of Object.entries(data.models?.[NATIVE_AGENT_NAME] ?? {})) {
27137
27408
  if (entry === undefined)
27138
27409
  continue;
27139
27410
  const def = asModelDef(entry);
@@ -27145,8 +27416,8 @@ function validateNativeModelIds(data, ctx) {
27145
27416
  const sibling = def && typeof def.provider === "string" ? def.provider.trim() : "";
27146
27417
  ctx.addIssue({
27147
27418
  code: "custom",
27148
- path: ["models", NATIVE, tier, ...def ? ["model"] : []],
27149
- message: sibling.length > 0 ? `models.${NATIVE}.${tier}.model "${modelId}" must be written "provider/model". The sibling "provider" field is not used on the native path \u2014 put it in the model id: "${sibling}/${modelId}".` : `models.${NATIVE}.${tier} "${modelId}" must be written "provider/model" (e.g. "openai/gpt-5.4-mini"). There is no default provider.`
27419
+ path: ["models", NATIVE_AGENT_NAME, tier, ...def ? ["model"] : []],
27420
+ message: sibling.length > 0 ? `models.${NATIVE_AGENT_NAME}.${tier}.model "${modelId}" must be written "provider/model". The sibling "provider" field is not used on the native path \u2014 put it in the model id: "${sibling}/${modelId}".` : `models.${NATIVE_AGENT_NAME}.${tier} "${modelId}" must be written "provider/model" (e.g. "openai/gpt-5.4-mini"). There is no default provider.`
27150
27421
  });
27151
27422
  }
27152
27423
  }
@@ -27161,12 +27432,12 @@ function rungAgent(value) {
27161
27432
  return;
27162
27433
  }
27163
27434
  function validateFallbackLadderAgents(data, ctx) {
27164
- const protocol = data.agent?.protocol ?? DEFAULT_PROTOCOL;
27435
+ const protocol = data.agent?.protocol ?? DEFAULT_AGENT_PROTOCOL;
27165
27436
  if (protocol === "hybrid")
27166
27437
  return;
27167
27438
  const map2 = data.agent?.fallback?.map ?? {};
27168
27439
  const reject = (agent, path) => {
27169
- const permitted = protocol === NATIVE ? `only "${NATIVE}"` : `no native agent`;
27440
+ const permitted = protocol === NATIVE_AGENT_NAME ? `only "${NATIVE_AGENT_NAME}"` : `no native agent`;
27170
27441
  ctx.addIssue({
27171
27442
  code: "custom",
27172
27443
  path,
@@ -27174,7 +27445,7 @@ function validateFallbackLadderAgents(data, ctx) {
27174
27445
  });
27175
27446
  };
27176
27447
  for (const [from, rungs] of Object.entries(map2)) {
27177
- const offending = (agent) => protocol === NATIVE ? agent !== NATIVE : agent === NATIVE;
27448
+ const offending = (agent) => protocol === NATIVE_AGENT_NAME ? agent !== NATIVE_AGENT_NAME : agent === NATIVE_AGENT_NAME;
27178
27449
  if (offending(from))
27179
27450
  reject(from, ["agent", "fallback", "map", from]);
27180
27451
  (rungs ?? []).forEach((rung, index) => {
@@ -27184,9 +27455,9 @@ function validateFallbackLadderAgents(data, ctx) {
27184
27455
  });
27185
27456
  }
27186
27457
  }
27187
- var NATIVE = "native", DEFAULT_PROTOCOL = "acp", DEFAULT_AGENT = "claude";
27188
27458
  var init_schemas_protocol_gate = __esm(() => {
27189
27459
  init_model_spec();
27460
+ init_agent_defaults();
27190
27461
  });
27191
27462
 
27192
27463
  // src/config/schemas-reporters.ts
@@ -27350,6 +27621,7 @@ var init_schemas_review = __esm(() => {
27350
27621
  var NaxConfigSchema;
27351
27622
  var init_schemas3 = __esm(() => {
27352
27623
  init_zod();
27624
+ init_agent_defaults();
27353
27625
  init_bash_approval();
27354
27626
  init_schema_types();
27355
27627
  init_schemas_context();
@@ -27379,13 +27651,7 @@ var init_schemas3 = __esm(() => {
27379
27651
  message: "outputDir must be absolute or start with ~/"
27380
27652
  }),
27381
27653
  version: exports_external.number().default(1),
27382
- models: ModelMapSchema.default({
27383
- claude: {
27384
- fast: "haiku",
27385
- balanced: "sonnet",
27386
- powerful: "opus"
27387
- }
27388
- }),
27654
+ models: ModelMapSchema.default(structuredClone(DEFAULT_MODEL_MAPS)),
27389
27655
  autoMode: AutoModeConfigSchema.default({
27390
27656
  enabled: true,
27391
27657
  complexityRouting: {
@@ -27423,7 +27689,7 @@ var init_schemas3 = __esm(() => {
27423
27689
  agents: { enabled: true, strategy: "off", profiles: [] }
27424
27690
  }),
27425
27691
  execution: ExecutionConfigSchema.default({
27426
- maxIterations: 10,
27692
+ maxIterations: 20,
27427
27693
  iterationDelayMs: 2000,
27428
27694
  costLimit: 30,
27429
27695
  sessionTimeoutSeconds: 3600,
@@ -27468,8 +27734,7 @@ var init_schemas3 = __esm(() => {
27468
27734
  },
27469
27735
  autofix: {
27470
27736
  enabled: true,
27471
- maxAttempts: 3,
27472
- enforceTestWriterIsolation: true
27737
+ maxAttempts: 3
27473
27738
  },
27474
27739
  forceExit: false,
27475
27740
  detectOpenHandles: true,
@@ -27596,8 +27861,8 @@ var init_schemas3 = __esm(() => {
27596
27861
  }
27597
27862
  }),
27598
27863
  agent: AgentConfigSchema.optional().default({
27599
- protocol: "acp",
27600
- default: "claude",
27864
+ protocol: DEFAULT_AGENT_PROTOCOL,
27865
+ default: DEFAULT_AGENT_NAME,
27601
27866
  maxInteractionTurns: 20,
27602
27867
  promptAudit: { enabled: false },
27603
27868
  usageAudit: { enabled: false },
@@ -27699,7 +27964,7 @@ var init_schemas3 = __esm(() => {
27699
27964
  for (const [pi, profile] of profiles.entries()) {
27700
27965
  const { agent: pAgent, model: pModel } = profile.target;
27701
27966
  const targetTier = MODEL_SHORTHAND_TIERS[pModel.toLowerCase()] ?? pModel;
27702
- const namesTier = resolveTierMembership(data.models ?? {}, pAgent, targetTier, data.agent?.default ?? "claude").isTier;
27967
+ const namesTier = resolveTierMembership(data.models ?? {}, pAgent, targetTier, data.agent?.default ?? DEFAULT_AGENT_NAME).isTier;
27703
27968
  const hasMatchingRung = tierOrder.some((r) => r.tier === targetTier && r.agent === pAgent);
27704
27969
  if (namesTier && !hasMatchingRung) {
27705
27970
  ctx.addIssue({
@@ -28312,6 +28577,57 @@ function trackedSpawnDeadlines(config2) {
28312
28577
  };
28313
28578
  }
28314
28579
 
28580
+ // src/config/unreferenced-agent-models.ts
28581
+ function isPin(value) {
28582
+ return typeof value.agent === "string" && (("model" in value) || ("tier" in value));
28583
+ }
28584
+ function pinAgents(value, found) {
28585
+ if (Array.isArray(value)) {
28586
+ for (const item of value)
28587
+ pinAgents(item, found);
28588
+ return;
28589
+ }
28590
+ if (typeof value !== "object" || value === null)
28591
+ return;
28592
+ const record3 = value;
28593
+ if (isPin(record3))
28594
+ found.add(record3.agent);
28595
+ for (const child of Object.values(record3))
28596
+ pinAgents(child, found);
28597
+ }
28598
+ function fallbackAgents(config2) {
28599
+ const fallback = config2.agent?.fallback;
28600
+ if (fallback?.enabled !== true)
28601
+ return [];
28602
+ return Object.values(fallback.map ?? {}).flatMap((rungs) => (rungs ?? []).map((rung) => typeof rung === "string" ? rung : rung.agent));
28603
+ }
28604
+ function findUnreferencedAgentModels(config2, storyAgents = []) {
28605
+ const reached = new Set([
28606
+ config2.agent?.default ?? DEFAULT_AGENT_NAME,
28607
+ ...fallbackAgents(config2),
28608
+ ...storyAgents
28609
+ ]);
28610
+ for (const [key, value] of Object.entries(config2)) {
28611
+ if (!SKIPPED_ROOT_KEYS.has(key))
28612
+ pinAgents(value, reached);
28613
+ }
28614
+ return Object.entries(config2.models ?? {}).filter(([agent, map2]) => agent !== NATIVE_AGENT_NAME && !isBuiltInModelMap(agent, map2) && !reached.has(agent)).map(([agent]) => agent);
28615
+ }
28616
+ function describeUnreferencedAgentModels(agents, config2) {
28617
+ const maps = agents.map((agent) => `models.${agent}`).join(", ");
28618
+ const head = `${maps} is declared but nothing dispatches to it: no agent.default, enabled fallback rung, pin, escalation rung, ` + `complexity route, routing profile or PRD story names ${agents.join(", ")}.`;
28619
+ if (config2.agent?.protocol === "native") {
28620
+ return `${head} Under agent.protocol "native" acpx agents cannot run; remove the map or use protocol "hybrid".`;
28621
+ }
28622
+ const defaultAgent = config2.agent?.default ?? DEFAULT_AGENT_NAME;
28623
+ return `${head} Unassigned work runs on agent.default "${defaultAgent}". ` + `Set agent.default "${agents[0]}" to run it, or reference it from a pin or fallback rung.`;
28624
+ }
28625
+ var SKIPPED_ROOT_KEYS;
28626
+ var init_unreferenced_agent_models = __esm(() => {
28627
+ init_agent_defaults();
28628
+ SKIPPED_ROOT_KEYS = new Set(["models", "agent"]);
28629
+ });
28630
+
28315
28631
  // src/config/validate.ts
28316
28632
  function validateConfig(config2) {
28317
28633
  const errors3 = [];
@@ -28322,7 +28638,7 @@ function validateConfig(config2) {
28322
28638
  if (!config2.models) {
28323
28639
  errors3.push("models mapping is required");
28324
28640
  } else {
28325
- const defaultAgent = config2.agent?.default ?? "claude";
28641
+ const defaultAgent = config2.agent?.default ?? DEFAULT_AGENT_NAME;
28326
28642
  const agentModels = config2.models[defaultAgent];
28327
28643
  if (!agentModels) {
28328
28644
  errors3.push(`models.${defaultAgent} is required (default agent has no model map)`);
@@ -28370,13 +28686,13 @@ function validateConfig(config2) {
28370
28686
  }
28371
28687
  if (config2.models && config2.agent?.fallback?.map) {
28372
28688
  const modelKeys = Object.keys(config2.models);
28373
- const fallbackAgents = new Set;
28689
+ const fallbackAgents2 = new Set;
28374
28690
  for (const [primary, candidates] of Object.entries(config2.agent.fallback.map)) {
28375
- fallbackAgents.add(primary);
28691
+ fallbackAgents2.add(primary);
28376
28692
  for (const c of candidates)
28377
- fallbackAgents.add(typeof c === "string" ? c : c.agent);
28693
+ fallbackAgents2.add(typeof c === "string" ? c : c.agent);
28378
28694
  }
28379
- for (const agent of fallbackAgents) {
28695
+ for (const agent of fallbackAgents2) {
28380
28696
  if (!modelKeys.includes(agent)) {
28381
28697
  errors3.push(`agent.fallback.map: agent "${agent}" is not a key in models (available: ${modelKeys.join(", ")})`);
28382
28698
  } else {
@@ -28395,7 +28711,7 @@ function validateConfig(config2) {
28395
28711
  errors3.push(`autoMode.escalation.tierOrder: tier "${tc.tier}" agent "${tc.agent}" is not a key in models (available: ${modelKeys.join(", ")})`);
28396
28712
  }
28397
28713
  if (tc.agent === undefined) {
28398
- const owner = config2.agent?.default ?? "claude";
28714
+ const owner = config2.agent?.default ?? DEFAULT_AGENT_NAME;
28399
28715
  const ownerMap = config2.models[owner];
28400
28716
  if (ownerMap && ownerMap[tc.tier] === undefined) {
28401
28717
  errors3.push(`autoMode.escalation.tierOrder: tier "${tc.tier}" does not resolve under agent "${owner}" (the default agent)`);
@@ -28403,7 +28719,7 @@ function validateConfig(config2) {
28403
28719
  }
28404
28720
  }
28405
28721
  }
28406
- const defaultAgentKey = config2.agent?.default ?? "claude";
28722
+ const defaultAgentKey = config2.agent?.default ?? DEFAULT_AGENT_NAME;
28407
28723
  const complexities = ["simple", "medium", "complex", "expert"];
28408
28724
  for (const complexity of complexities) {
28409
28725
  const entry = config2.autoMode.complexityRouting[complexity];
@@ -28430,6 +28746,9 @@ function validateConfig(config2) {
28430
28746
  errors: errors3
28431
28747
  };
28432
28748
  }
28749
+ var init_validate = __esm(() => {
28750
+ init_agent_defaults();
28751
+ });
28433
28752
 
28434
28753
  // src/config/index.ts
28435
28754
  var exports_config = {};
@@ -28448,6 +28767,8 @@ __export(exports_config, {
28448
28767
  ContextV2ConfigSchema: () => ContextV2ConfigSchema,
28449
28768
  CuratorRetentionConfigSchema: () => CuratorRetentionConfigSchema,
28450
28769
  DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG: () => DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG,
28770
+ DEFAULT_AGENT_NAME: () => DEFAULT_AGENT_NAME,
28771
+ DEFAULT_AGENT_PROTOCOL: () => DEFAULT_AGENT_PROTOCOL,
28451
28772
  DEFAULT_AGENT_SPIN_BREAKER_CONFIG: () => DEFAULT_AGENT_SPIN_BREAKER_CONFIG,
28452
28773
  DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG: () => DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG,
28453
28774
  DEFAULT_CONFIG: () => DEFAULT_CONFIG,
@@ -28460,6 +28781,7 @@ __export(exports_config, {
28460
28781
  MODEL_SHORTHAND_TIERS: () => MODEL_SHORTHAND_TIERS,
28461
28782
  McpConfigSchema: () => McpConfigSchema,
28462
28783
  ModelTierSchema: () => ModelTierSchema,
28784
+ NATIVE_AGENT_NAME: () => NATIVE_AGENT_NAME,
28463
28785
  NaxConfigSchema: () => NaxConfigSchema,
28464
28786
  PROJECT_FEATURES_DIR: () => PROJECT_FEATURES_DIR,
28465
28787
  PROJECT_NAX_DIR: () => PROJECT_NAX_DIR,
@@ -28489,17 +28811,20 @@ __export(exports_config, {
28489
28811
  createConfigLoader: () => createConfigLoader,
28490
28812
  decomposeConfigSelector: () => decomposeConfigSelector,
28491
28813
  deepMergeConfig: () => deepMergeConfig,
28814
+ describeUnreferencedAgentModels: () => describeUnreferencedAgentModels,
28492
28815
  executionGatesConfigSelector: () => executionGatesConfigSelector,
28493
28816
  featureDir: () => featureDir,
28494
28817
  featuresDir: () => featuresDir,
28495
28818
  findInertBashStages: () => findInertBashStages,
28496
28819
  findProjectDir: () => findProjectDir,
28820
+ findUnreferencedAgentModels: () => findUnreferencedAgentModels,
28497
28821
  finishConfigSelector: () => finishConfigSelector,
28498
28822
  getAcQualityRules: () => getAcQualityRules,
28499
28823
  getProjectKey: () => getProjectKey,
28500
28824
  globalConfigDir: () => globalConfigDir,
28501
28825
  globalConfigPath: () => globalConfigPath,
28502
28826
  interactionConfigSelector: () => interactionConfigSelector,
28827
+ isBuiltInModelMap: () => isBuiltInModelMap,
28503
28828
  isSingleSessionTestOwningStrategy: () => isSingleSessionTestOwningStrategy,
28504
28829
  isThreeSessionStrategy: () => isThreeSessionStrategy,
28505
28830
  isUnrecognizedLiteralModel: () => isUnrecognizedLiteralModel,
@@ -28549,6 +28874,7 @@ __export(exports_config, {
28549
28874
  verifyConfigSelector: () => verifyConfigSelector
28550
28875
  });
28551
28876
  var init_config = __esm(() => {
28877
+ init_agent_defaults();
28552
28878
  init_bash_approval();
28553
28879
  init_inert_bash_stages();
28554
28880
  init_loader();
@@ -28570,6 +28896,8 @@ var init_config = __esm(() => {
28570
28896
  init_schemas_sandbox();
28571
28897
  init_selectors();
28572
28898
  init_test_strategy();
28899
+ init_unreferenced_agent_models();
28900
+ init_validate();
28573
28901
  });
28574
28902
 
28575
28903
  // src/agents/native/credentials.ts
@@ -28744,6 +29072,32 @@ async function ambientShadows(providerIds) {
28744
29072
  }));
28745
29073
  return checked.filter((id) => id !== undefined);
28746
29074
  }
29075
+ async function providersWithoutCredentials(providerIds) {
29076
+ const unique = [...new Set(providerIds)];
29077
+ let stored;
29078
+ try {
29079
+ stored = new Set((await listStoredProviders()).map((entry) => entry.providerId));
29080
+ } catch {
29081
+ return [];
29082
+ }
29083
+ const sweep = Promise.all(unique.filter((providerId) => !stored.has(providerId)).map(async (providerId) => {
29084
+ try {
29085
+ return await _authDeps.ambientAuthAvailable(providerId) ? undefined : providerId;
29086
+ } catch {
29087
+ return;
29088
+ }
29089
+ })).then((missing) => missing.filter((id) => id !== undefined));
29090
+ let timer;
29091
+ const expiry = new Promise((resolve15) => {
29092
+ timer = setTimeout(() => resolve15([]), AMBIENT_PROBE_TIMEOUT_MS);
29093
+ });
29094
+ try {
29095
+ return await Promise.race([sweep, expiry]);
29096
+ } finally {
29097
+ if (timer !== undefined)
29098
+ clearTimeout(timer);
29099
+ }
29100
+ }
28747
29101
  async function anyAmbientCredential() {
28748
29102
  let timer;
28749
29103
  const sweep = (async () => {
@@ -28764,13 +29118,13 @@ async function anyAmbientCredential() {
28764
29118
  });
28765
29119
  })();
28766
29120
  const expiry = new Promise((resolve15) => {
28767
- timer = setTimeout(() => resolve15(true), AMBIENT_PROBE_TIMEOUT_MS);
29121
+ timer = _authDeps.setTimeout(() => resolve15(true), AMBIENT_PROBE_TIMEOUT_MS);
28768
29122
  });
28769
29123
  try {
28770
29124
  return await Promise.race([sweep, expiry]);
28771
29125
  } finally {
28772
29126
  if (timer !== undefined)
28773
- clearTimeout(timer);
29127
+ _authDeps.clearTimeout(timer);
28774
29128
  }
28775
29129
  }
28776
29130
  var AuthCancelledError, _authDeps, DEFAULT_PI_AUTH_PATH, AMBIENT_PROBE_TIMEOUT_MS = 2000;
@@ -28788,7 +29142,9 @@ var init_auth = __esm(() => {
28788
29142
  _authDeps = {
28789
29143
  login,
28790
29144
  ambientAuthAvailable,
28791
- providerIds: async () => (await defaultProviders2()).map((provider) => provider.id)
29145
+ providerIds: async () => (await defaultProviders2()).map((provider) => provider.id),
29146
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
29147
+ clearTimeout: (id) => clearTimeout(id)
28792
29148
  };
28793
29149
  DEFAULT_PI_AUTH_PATH = join20(homedir3(), ".pi", "agent", "auth.json");
28794
29150
  });
@@ -28874,11 +29230,12 @@ function resolveContextWindow(override, realWindow) {
28874
29230
  }
28875
29231
  return override;
28876
29232
  }
28877
- var NATIVE_AGENT = "native", THINKING_LEVELS;
29233
+ var THINKING_LEVELS;
28878
29234
  var init_models = __esm(() => {
28879
29235
  init_errors();
28880
29236
  init_logger2();
28881
29237
  init_model_spec();
29238
+ init_config();
28882
29239
  THINKING_LEVELS = {
28883
29240
  off: true,
28884
29241
  minimal: true,
@@ -28892,7 +29249,7 @@ var init_models = __esm(() => {
28892
29249
 
28893
29250
  // src/agents/native/client.ts
28894
29251
  import { createClient, defaultProtocols, defaultProviders as defaultProviders3 } from "@nathapp/nax-ai";
28895
- async function buildNativeClient(catalogOverrides = []) {
29252
+ async function buildNativeClient(catalogOverrides = [], options = {}) {
28896
29253
  return createClient({
28897
29254
  providers: await defaultProviders3(),
28898
29255
  protocols: ({ providerOverrides }) => _clientDeps.defaultProtocols({
@@ -28900,7 +29257,8 @@ async function buildNativeClient(catalogOverrides = []) {
28900
29257
  clientApp: NAX_CLIENT_APP,
28901
29258
  providerOverrides
28902
29259
  }),
28903
- ...catalogOverrides.length > 0 ? { providerOverrides: toProviderOverrides(catalogOverrides) } : {}
29260
+ ...catalogOverrides.length > 0 ? { providerOverrides: toProviderOverrides(catalogOverrides) } : {},
29261
+ ...options.transportRetries !== undefined ? { transportRetries: options.transportRetries } : {}
28904
29262
  });
28905
29263
  }
28906
29264
  function canonicalise(value) {
@@ -29358,7 +29716,7 @@ async function openNativeSession(name, opts) {
29358
29716
  });
29359
29717
  return {
29360
29718
  id: name,
29361
- agentName: NATIVE_AGENT,
29719
+ agentName: NATIVE_AGENT_NAME,
29362
29720
  protocolIds: { recordId: nativeSessionId(name), sessionId: nativeSessionId(name) },
29363
29721
  ...opts.modelDef !== undefined ? { modelDef: opts.modelDef } : {},
29364
29722
  ...opts.modelTier !== undefined ? { modelTier: opts.modelTier } : {}
@@ -31030,7 +31388,7 @@ ${previousSummary}`;
31030
31388
 
31031
31389
  class NativeAgentAdapter {
31032
31390
  catalogOverrides;
31033
- name = NATIVE_AGENT;
31391
+ name = NATIVE_AGENT_NAME;
31034
31392
  displayName = "Native (nax-ai)";
31035
31393
  binary = "";
31036
31394
  capabilities;
@@ -31064,7 +31422,7 @@ class NativeAgentAdapter {
31064
31422
  const client = await getNativeClient(this.catalogOverrides);
31065
31423
  const resolved = await client.model(provider, model);
31066
31424
  const controller = new AbortController;
31067
- const timer = options.timeoutMs !== undefined ? setTimeout(() => controller.abort(), options.timeoutMs) : undefined;
31425
+ const timer = options.timeoutMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), options.timeoutMs) : undefined;
31068
31426
  try {
31069
31427
  const sessionId = nativeSessionId(this.oneShotKey);
31070
31428
  const result = await client.complete(resolved, {
@@ -31098,7 +31456,7 @@ class NativeAgentAdapter {
31098
31456
  throw err;
31099
31457
  } finally {
31100
31458
  if (timer !== undefined)
31101
- clearTimeout(timer);
31459
+ _adapterDeps.clearTimeout(timer);
31102
31460
  }
31103
31461
  }
31104
31462
  openSession(name, opts) {
@@ -31142,7 +31500,7 @@ class NativeAgentAdapter {
31142
31500
  });
31143
31501
  const deadlineController = new AbortController;
31144
31502
  const deadlineMs = deadline.remainingMs();
31145
- const deadlineTimer = deadlineMs !== undefined ? setTimeout(() => deadlineController.abort(), deadlineMs) : undefined;
31503
+ const deadlineTimer = deadlineMs !== undefined ? _adapterDeps.setTimeout(() => deadlineController.abort(), deadlineMs) : undefined;
31146
31504
  const turnSignals = [turnController.signal, deadlineController.signal];
31147
31505
  if (opts.signal !== undefined)
31148
31506
  turnSignals.unshift(opts.signal);
@@ -31165,7 +31523,7 @@ class NativeAgentAdapter {
31165
31523
  summarize: async (span, previousSummary) => {
31166
31524
  const remainingMs = deadline.remainingMs();
31167
31525
  const controller = new AbortController;
31168
- const timer = remainingMs !== undefined ? setTimeout(() => controller.abort(), remainingMs) : undefined;
31526
+ const timer = remainingMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), remainingMs) : undefined;
31169
31527
  const signal = AbortSignal.any(opts.signal !== undefined ? [opts.signal, controller.signal, turnController.signal, deadlineController.signal] : [controller.signal, turnController.signal, deadlineController.signal]);
31170
31528
  try {
31171
31529
  const res = await client.complete(resolved, {
@@ -31178,13 +31536,13 @@ class NativeAgentAdapter {
31178
31536
  return { text: res.text, usage: summaryUsage, costUsd, rates: resolvedRates };
31179
31537
  } finally {
31180
31538
  if (timer !== undefined)
31181
- clearTimeout(timer);
31539
+ _adapterDeps.clearTimeout(timer);
31182
31540
  }
31183
31541
  },
31184
31542
  complete: async (messages, tools, requestOptions) => {
31185
31543
  const remainingMs = deadline.remainingMs();
31186
31544
  const controller = new AbortController;
31187
- const timer = remainingMs !== undefined ? setTimeout(() => controller.abort(), remainingMs) : undefined;
31545
+ const timer = remainingMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), remainingMs) : undefined;
31188
31546
  const signal = AbortSignal.any(opts.signal !== undefined ? [opts.signal, controller.signal, turnController.signal, deadlineController.signal] : [controller.signal, turnController.signal, deadlineController.signal]);
31189
31547
  const requestThinking = requestOptions?.thinking === false ? undefined : thinking;
31190
31548
  try {
@@ -31209,7 +31567,7 @@ class NativeAgentAdapter {
31209
31567
  };
31210
31568
  } finally {
31211
31569
  if (timer !== undefined)
31212
- clearTimeout(timer);
31570
+ _adapterDeps.clearTimeout(timer);
31213
31571
  }
31214
31572
  }
31215
31573
  });
@@ -31229,7 +31587,7 @@ class NativeAgentAdapter {
31229
31587
  throw err;
31230
31588
  } finally {
31231
31589
  if (deadlineTimer !== undefined)
31232
- clearTimeout(deadlineTimer);
31590
+ _adapterDeps.clearTimeout(deadlineTimer);
31233
31591
  }
31234
31592
  hooks?.onStreamActivity?.({
31235
31593
  ...eventBase,
@@ -31244,7 +31602,7 @@ class NativeAgentAdapter {
31244
31602
  return closeNativeSession(handle);
31245
31603
  }
31246
31604
  async closePhysicalSession(handle, _workdir, _options) {
31247
- return closeNativeSession({ id: handle, agentName: NATIVE_AGENT });
31605
+ return closeNativeSession({ id: handle, agentName: NATIVE_AGENT_NAME });
31248
31606
  }
31249
31607
  }
31250
31608
  var CONSERVATIVE_CONTEXT_TOKENS = 128000, FALLBACK_TURN_TIMEOUT_SECONDS = 3600, DEFAULT_TIERS, _adapterDeps;
@@ -31262,7 +31620,12 @@ var init_adapter = __esm(() => {
31262
31620
  init_turn_types();
31263
31621
  init_session_affinity();
31264
31622
  DEFAULT_TIERS = ["fast", "balanced", "powerful"];
31265
- _adapterDeps = { listStoredProviders, anyAmbientCredential };
31623
+ _adapterDeps = {
31624
+ listStoredProviders,
31625
+ anyAmbientCredential,
31626
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
31627
+ clearTimeout: (id) => clearTimeout(id)
31628
+ };
31266
31629
  });
31267
31630
 
31268
31631
  // src/agents/native/model-resolver.ts
@@ -31807,7 +32170,7 @@ async function assertPrdCommitted(prdPath, projectRoot) {
31807
32170
  });
31808
32171
  }
31809
32172
  }
31810
- var init_validate = __esm(() => {
32173
+ var init_validate2 = __esm(() => {
31811
32174
  init_errors();
31812
32175
  init_git();
31813
32176
  });
@@ -31906,7 +32269,7 @@ function validateInjectedStory(raw, existingIds) {
31906
32269
  var STORY_ID_PREFIX = "US";
31907
32270
  var init_inject = __esm(() => {
31908
32271
  init_errors();
31909
- init_validate();
32272
+ init_validate2();
31910
32273
  });
31911
32274
 
31912
32275
  // src/prd/modifies-extract.ts
@@ -33002,7 +33365,7 @@ var init_schema_story = __esm(() => {
33002
33365
  init_test_strategy();
33003
33366
  init_errors();
33004
33367
  init_out_of_scope();
33005
- init_validate();
33368
+ init_validate2();
33006
33369
  VALID_COMPLEXITY = ["simple", "medium", "complex", "expert"];
33007
33370
  WORKDIR_SOURCES = ["stated", "derived", "defaulted"];
33008
33371
  STORY_ID_NO_SEPARATOR = /^([A-Za-z]+)(\d+)$/;
@@ -33357,7 +33720,7 @@ var init_prd = __esm(() => {
33357
33720
  init_out_of_scope_extract();
33358
33721
  init_spec_drift();
33359
33722
  init_spec_lint();
33360
- init_validate();
33723
+ init_validate2();
33361
33724
  init_workdir_canonical();
33362
33725
  init_schema2();
33363
33726
  PRD_MAX_FILE_SIZE = 5 * 1024 * 1024;
@@ -40349,23 +40712,6 @@ function lintDiagnosticToFinding(d, workdir, tool) {
40349
40712
  var init_lint = __esm(() => {
40350
40713
  init_path_utils();
40351
40714
  });
40352
- // src/findings/adapters/semantic-review.ts
40353
- function reviewFindingToFinding(f) {
40354
- return {
40355
- source: "semantic-review",
40356
- severity: f.severity,
40357
- category: f.category ?? "",
40358
- rule: f.ruleId,
40359
- file: f.file,
40360
- line: f.line,
40361
- column: f.column,
40362
- endLine: f.endLine,
40363
- endColumn: f.endColumn,
40364
- message: f.message,
40365
- fixTarget: "source"
40366
- };
40367
- }
40368
-
40369
40715
  // src/findings/adapters/test-failure.ts
40370
40716
  function testFailureToFinding(failure) {
40371
40717
  const frames = (failure.stackTrace ?? []).slice(0, MAX_FRAMES_IN_MESSAGE);
@@ -41210,7 +41556,7 @@ function buildBehavioralGuardrailsSection(role, level, _variant, _isolation) {
41210
41556
  if (role === "test-writer") {
41211
41557
  return buildTestWriterGuardrails(level);
41212
41558
  }
41213
- if (role === "single-session" || role === "tdd-simple" || role === "batch") {
41559
+ if (role === "tdd-simple" || role === "batch") {
41214
41560
  return buildCombinedGuardrails(level);
41215
41561
  }
41216
41562
  return buildImplementerGuardrails(level);
@@ -41334,7 +41680,7 @@ ${body}`;
41334
41680
  }
41335
41681
  var HERMETIC_ROLES, LANGUAGE_GUIDANCE;
41336
41682
  var init_hermetic = __esm(() => {
41337
- HERMETIC_ROLES = new Set(["test-writer", "implementer", "tdd-simple", "batch", "single-session"]);
41683
+ HERMETIC_ROLES = new Set(["test-writer", "implementer", "tdd-simple", "batch"]);
41338
41684
  LANGUAGE_GUIDANCE = {
41339
41685
  go: "Define interfaces for external dependencies. Use constructor injection. Test with interface mocks \u2014 no real I/O in tests.",
41340
41686
  rust: "Use trait objects or generics for external deps. Mock with the mockall crate. Use #[cfg(test)] modules.",
@@ -41530,11 +41876,6 @@ isolation scope: Implement source code in src/ to make tests pass. Do not modify
41530
41876
  return `${header}
41531
41877
 
41532
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}`;
41533
- }
41534
- if (role === "single-session") {
41535
- return `${header}
41536
-
41537
- 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}`;
41538
41879
  }
41539
41880
  return `${header}
41540
41881
 
@@ -41575,7 +41916,11 @@ freely. Every other path under \`.nax/\` stays off limits.
41575
41916
  - A test under \`.nax/\` is NOT a reason to skip writing source-tree tests. \`.nax/\` is generated
41576
41917
  scaffolding, not real coverage of the package's code.
41577
41918
  - A source-tree test is NOT a reason to remove a test under \`.nax/\`. The two serve different
41578
- 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.`;
41579
41924
  }
41580
41925
 
41581
41926
  // src/prompts/sections/out-of-scope.ts
@@ -41739,25 +42084,6 @@ Instructions:
41739
42084
  - Do NOT perform semantic acceptance review; semantic/adversarial review stages own acceptance criteria and broad code-quality findings
41740
42085
  - Write a detailed verdict with reasoning
41741
42086
  - Goal: verify story-scoped tests pass and test integrity was preserved`;
41742
- }
41743
- if (role === "single-session") {
41744
- return `# Role: Single-Session
41745
-
41746
- Your task: write tests AND implement the feature in one session.
41747
-
41748
- Workflow:
41749
- 1. Read the acceptance criteria. For each AC, plan one success-path test and one boundary/failure test.
41750
- 2. Create test files in the location the project uses for tests. Cover every AC.
41751
- 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.
41752
- 4. Implement source code in the package's source location to make the tests pass.
41753
- 5. After each meaningful change, re-run only the scoped test files \u2014 never the full suite.
41754
- 6. When all scoped tests pass, stage and commit ALL changed files: \`${gitCommitInstruction(commitMsg)}\`.
41755
-
41756
- Rules:
41757
- - Each test name describes ONE behavior; use AC IDs when available.
41758
- - Assert on observable outputs.
41759
- - ${frameworkHint}
41760
- - Goal: every AC has at least one passing test; all changes committed.`;
41761
42087
  }
41762
42088
  if (role === "batch") {
41763
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";
@@ -41817,6 +42143,11 @@ or is read by another step. A failed run's scratchpad is retained for inspection
41817
42143
  before failing outlive that run, and are cleared at the next run's start. Treat every file as disposable,
41818
42144
  and overwrite freely.
41819
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
+
41820
42151
  \`${dir}\` is the one directory under \`.nax/\` you may write to. Every other path under \`.nax/\`
41821
42152
  must still never be moved, renamed, or deleted.`;
41822
42153
  }
@@ -42047,7 +42378,7 @@ An adversarial reviewer will audit your tests after implementation and BLOCK the
42047
42378
  }
42048
42379
  var AUTHORING_ROLES;
42049
42380
  var init_test_quality = __esm(() => {
42050
- AUTHORING_ROLES = new Set(["test-writer", "single-session", "tdd-simple", "batch"]);
42381
+ AUTHORING_ROLES = new Set(["test-writer", "tdd-simple", "batch"]);
42051
42382
  });
42052
42383
 
42053
42384
  // src/prompts/sections/verdict.ts
@@ -42247,7 +42578,7 @@ ACCEPTANCE TEST FILE: ${p.acceptanceTestPath}
42247
42578
 
42248
42579
  SOURCE FILES (auto-detected from imports, up to ${p.maxFileLines} lines each):
42249
42580
  ${p.sourceFilesSection}
42250
- ${p.verdictSection}
42581
+
42251
42582
  Respond with ONLY a JSON object in this exact format (no markdown, no extra text):
42252
42583
  ${responseSchema}`;
42253
42584
  }
@@ -42326,16 +42657,10 @@ ${f.content}
42326
42657
  \`\`\``).join(`
42327
42658
 
42328
42659
  `) : "(No source files could be resolved from imports)";
42329
- const verdictSection = p.semanticVerdicts && p.semanticVerdicts.length > 0 ? `
42330
- SEMANTIC VERDICTS:
42331
- ${p.semanticVerdicts.map((v) => `- ${v.storyId}: ${v.passed ? "likely test bug (semantic review confirmed AC implementation)" : "unconfirmed"}`).join(`
42332
- `)}
42333
- ` : "";
42334
42660
  return this.buildDiagnosisPromptTemplate({
42335
42661
  truncatedOutput,
42336
42662
  acceptanceTestPath: p.acceptanceTestPath ?? "(path unavailable \u2014 inspect test output for file references)",
42337
42663
  sourceFilesSection,
42338
- verdictSection,
42339
42664
  maxFileLines: MAX_FILE_LINES
42340
42665
  });
42341
42666
  }
@@ -53052,8 +53377,8 @@ var init_version = __esm(() => {
53052
53377
  NAX_AI_VERSION = CATALOG_VERSION;
53053
53378
  NAX_COMMIT = (() => {
53054
53379
  try {
53055
- if (/^[0-9a-f]{6,10}$/.test("a83e5791"))
53056
- return "a83e5791";
53380
+ if (/^[0-9a-f]{6,10}$/.test("7c21771a"))
53381
+ return "7c21771a";
53057
53382
  } catch {}
53058
53383
  try {
53059
53384
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -53612,7 +53937,7 @@ function attachCostSubscriber(bus, aggregator, runId, projectKey) {
53612
53937
  offCompleted();
53613
53938
  };
53614
53939
  }
53615
- var _costSubscriberDeps, COST_ROW_SCHEMA_VERSION = 6;
53940
+ var _costSubscriberDeps, COST_ROW_SCHEMA_VERSION = 7;
53616
53941
  var init_cost2 = __esm(() => {
53617
53942
  init_agents();
53618
53943
  init_version();
@@ -53734,8 +54059,33 @@ function toUsageEntry(event, runId) {
53734
54059
  costUsd: event.costUsd
53735
54060
  };
53736
54061
  }
53737
- function attachUsageAuditSubscriber(bus, auditor, runId) {
53738
- 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) => {
53739
54089
  switch (event.kind) {
53740
54090
  case "agent.usage_update":
53741
54091
  auditor.record(toUsageEntry(event, runId));
@@ -53744,6 +54094,15 @@ function attachUsageAuditSubscriber(bus, auditor, runId) {
53744
54094
  break;
53745
54095
  }
53746
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
+ };
53747
54106
  }
53748
54107
 
53749
54108
  // src/runtime/middleware/index.ts
@@ -53755,15 +54114,255 @@ var init_middleware = __esm(() => {
53755
54114
  init_logging();
53756
54115
  });
53757
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
+
53758
54357
  // src/runtime/packages.ts
53759
- 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";
53760
54359
  function packageWorkdir(view) {
53761
54360
  const { packageDir, repoRoot } = view;
53762
54361
  if (!packageDir)
53763
54362
  return repoRoot;
53764
54363
  if (!repoRoot || isAbsolute15(packageDir))
53765
54364
  return packageDir;
53766
- return join45(repoRoot, packageDir);
54365
+ return join46(repoRoot, packageDir);
53767
54366
  }
53768
54367
  function createPackageView(config2, packageDir, repoRoot, hasOverride, overlay) {
53769
54368
  const memo = new Map;
@@ -53876,7 +54475,7 @@ function storyExecRoot(view) {
53876
54475
  const segments = packageDir.split("/");
53877
54476
  if (segments[0] !== ".nax-wt" || segments.length < 2)
53878
54477
  return repoRoot;
53879
- return join45(repoRoot, segments[0], segments[1]);
54478
+ return join46(repoRoot, segments[0], segments[1]);
53880
54479
  }
53881
54480
  var _packagesDeps;
53882
54481
  var init_packages = __esm(() => {
@@ -54009,9 +54608,9 @@ var init_paths2 = __esm(() => {
54009
54608
  });
54010
54609
 
54011
54610
  // src/runtime/prompt-auditor.ts
54012
- import { appendFileSync as appendFileSync2 } from "fs";
54013
- import { mkdir as mkdir16 } from "fs/promises";
54014
- 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";
54015
54614
  function createNoOpPromptAuditor() {
54016
54615
  return {
54017
54616
  record() {},
@@ -54091,8 +54690,8 @@ class PromptAuditor {
54091
54690
  _featureDir;
54092
54691
  _turnOrdinals = new Map;
54093
54692
  constructor(runId, flushDir, featureName) {
54094
- this._featureDir = join46(flushDir, featureName);
54095
- this._jsonlPath = join46(this._featureDir, `${runId}.jsonl`);
54693
+ this._featureDir = join47(flushDir, featureName);
54694
+ this._jsonlPath = join47(this._featureDir, `${runId}.jsonl`);
54096
54695
  }
54097
54696
  record(entry) {
54098
54697
  this._enqueue(entry.callType === "run" ? { ...entry, turn: this._nextTurn(entry) } : entry);
@@ -54130,7 +54729,7 @@ class PromptAuditor {
54130
54729
  async _writeEntry(entry) {
54131
54730
  if (!this._dirCreated) {
54132
54731
  try {
54133
- await mkdir16(this._featureDir, { recursive: true });
54732
+ await mkdir17(this._featureDir, { recursive: true });
54134
54733
  } catch (err) {
54135
54734
  throw tagAuditError(err, "jsonl");
54136
54735
  }
@@ -54147,7 +54746,7 @@ class PromptAuditor {
54147
54746
  return;
54148
54747
  const filename = deriveTxtFilename(entry);
54149
54748
  try {
54150
- await _promptAuditorDeps.write(join46(this._featureDir, filename), buildTxtContent(safeEntry));
54749
+ await _promptAuditorDeps.write(join47(this._featureDir, filename), buildTxtContent(safeEntry));
54151
54750
  } catch (err) {
54152
54751
  throw tagAuditError(err, "txt");
54153
54752
  }
@@ -54161,80 +54760,10 @@ var init_prompt_auditor = __esm(() => {
54161
54760
  init_logger2();
54162
54761
  _promptAuditorDeps = {
54163
54762
  write: (path7, data) => Bun.write(path7, data),
54164
- appendLine: async (path7, data) => {
54165
- appendFileSync2(path7, data, "utf8");
54166
- }
54167
- };
54168
- });
54169
-
54170
- // src/runtime/usage-auditor.ts
54171
- import { appendFileSync as appendFileSync3 } from "fs";
54172
- import { mkdir as mkdir17 } from "fs/promises";
54173
- import { join as join47 } from "path";
54174
- function createNoOpUsageAuditor() {
54175
- return {
54176
- record() {},
54177
- async flush() {}
54178
- };
54179
- }
54180
- function deriveSessionRole(sessionName) {
54181
- for (const role of ROLES_BY_DESCENDING_LENGTH) {
54182
- if (sessionName === role || sessionName.endsWith(`-${role}`))
54183
- return role;
54184
- }
54185
- return;
54186
- }
54187
-
54188
- class UsageAuditor {
54189
- _queue = Promise.resolve();
54190
- _dirCreated = false;
54191
- _dir;
54192
- _jsonlPath;
54193
- constructor(runId, dir) {
54194
- this._dir = dir;
54195
- this._jsonlPath = join47(dir, `${runId}.jsonl`);
54196
- }
54197
- record(entry) {
54198
- this._queue = this._queue.then(() => this._writeEntry(entry)).catch((err) => {
54199
- const sysErr = err;
54200
- getSafeLogger()?.warn("audit", "usage-audit write failed", {
54201
- path: this._jsonlPath,
54202
- error: errorMessage(err),
54203
- code: sysErr?.code,
54204
- errno: sysErr?.errno,
54205
- syscall: sysErr?.syscall,
54206
- ts: entry.ts,
54207
- storyId: entry.storyId,
54208
- sessionName: entry.sessionName,
54209
- agentName: entry.agentName,
54210
- stage: entry.stage,
54211
- streamCallId: entry.streamCallId
54212
- });
54213
- });
54214
- }
54215
- async _writeEntry(entry) {
54216
- if (!this._dirCreated) {
54217
- await mkdir17(this._dir, { recursive: true });
54218
- this._dirCreated = true;
54219
- }
54220
- const row = { ...entry, sessionRole: deriveSessionRole(entry.sessionName) };
54221
- await _usageAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(row)}
54222
- `);
54223
- }
54224
- async flush() {
54225
- await this._queue;
54226
- }
54227
- }
54228
- var _usageAuditorDeps, ROLES_BY_DESCENDING_LENGTH;
54229
- var init_usage_auditor = __esm(() => {
54230
- init_logger2();
54231
- init_session_role();
54232
- _usageAuditorDeps = {
54233
54763
  appendLine: async (path7, data) => {
54234
54764
  appendFileSync3(path7, data, "utf8");
54235
54765
  }
54236
54766
  };
54237
- ROLES_BY_DESCENDING_LENGTH = [...KNOWN_SESSION_ROLES].sort((a, b) => b.length - a.length);
54238
54767
  });
54239
54768
 
54240
54769
  // src/agents/factory.ts
@@ -65849,7 +66378,7 @@ var init_coding_tool_support = __esm(() => {
65849
66378
 
65850
66379
  // src/agents/tool-preamble.ts
65851
66380
  function promptWithToolPreamble(agentName, options) {
65852
- const base = agentName === NATIVE_AGENT ? options.prompt : buildContextToolPreamble(options);
66381
+ const base = agentName === NATIVE_AGENT_NAME ? options.prompt : buildContextToolPreamble(options);
65853
66382
  const scope = buildAgentScopeSection(options.codingToolRoot, options.codingToolWorkdirLabel);
65854
66383
  return scope === undefined ? base : `${scope}
65855
66384
 
@@ -65857,7 +66386,7 @@ ${base}`;
65857
66386
  }
65858
66387
  function applyDiffAccessForAgentProtocol(agentName, prompt, advertisedTools) {
65859
66388
  return applyProtocolRegions(prompt, {
65860
- protocol: agentName === NATIVE_AGENT ? "native" : "acp",
66389
+ protocol: agentName === NATIVE_AGENT_NAME ? "native" : "acp",
65861
66390
  advertisedTools: new Set(advertisedTools)
65862
66391
  });
65863
66392
  }
@@ -66031,6 +66560,7 @@ __export(exports_runtime, {
66031
66560
  _usageAuditorDeps: () => _usageAuditorDeps,
66032
66561
  attachAgentIdleWatchdog: () => attachAgentIdleWatchdog,
66033
66562
  attachAgentStreamLogging: () => attachAgentStreamLogging,
66563
+ attachInFlightUsageTracker: () => attachInFlightUsageTracker,
66034
66564
  attachUsageAuditSubscriber: () => attachUsageAuditSubscriber,
66035
66565
  claimProjectIdentity: () => claimProjectIdentity,
66036
66566
  createNoOpCostAggregator: () => createNoOpCostAggregator,
@@ -66053,6 +66583,7 @@ __export(exports_runtime, {
66053
66583
  spinTerminalNotice: () => spinTerminalNotice,
66054
66584
  storyExecRoot: () => storyExecRoot,
66055
66585
  storySpendUsd: () => storySpendUsd,
66586
+ toPartialCostEvent: () => toPartialCostEvent,
66056
66587
  totalSpendUsd: () => totalSpendUsd,
66057
66588
  writeProjectIdentity: () => writeProjectIdentity
66058
66589
  });
@@ -66144,9 +66675,10 @@ function createRuntime(config2, workdir, opts) {
66144
66675
  const offCost = attachCostSubscriber(dispatchEvents, costAggregator, runId, getProjectKey(config2, workdir));
66145
66676
  const offAudit = attachAuditSubscriber(dispatchEvents, promptAuditor, runId);
66146
66677
  const offReviewAudit = attachReviewAuditSubscriber(dispatchEvents, reviewAuditor, runId);
66147
- const offUsageAudit = attachUsageAuditSubscriber(agentStreamEvents, usageAuditor, runId);
66678
+ const offUsageAudit = attachUsageAuditSubscriber(agentStreamEvents, dispatchEvents, usageAuditor, runId);
66148
66679
  const offAgentStreamLogging = attachAgentStreamLogging(agentStreamEvents, runId);
66149
66680
  const offWatchdog = attachAgentIdleWatchdog(agentStreamEvents, watchdogControllerRegistry, config2);
66681
+ const { tracker: inFlightTracker, off: offInFlightUsage } = attachInFlightUsageTracker(agentStreamEvents, dispatchEvents);
66150
66682
  const packages = createPackageRegistry(configLoader, workdir);
66151
66683
  const logger = getLogger();
66152
66684
  const quarantineMemo = createQuarantineMemo();
@@ -66214,6 +66746,7 @@ function createRuntime(config2, workdir, opts) {
66214
66746
  offUsageAudit();
66215
66747
  offAgentStreamLogging();
66216
66748
  offWatchdog();
66749
+ offInFlightUsage();
66217
66750
  if (opts?.parentSignal && parentAbortHandler) {
66218
66751
  opts.parentSignal.removeEventListener("abort", parentAbortHandler);
66219
66752
  }
@@ -66223,6 +66756,10 @@ function createRuntime(config2, workdir, opts) {
66223
66756
  sessionManager.close();
66224
66757
  await mcpPool.close();
66225
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);
66226
66763
  const results = await Promise.allSettled([
66227
66764
  promptAuditor.flush(),
66228
66765
  usageAuditor.flush(),
@@ -66242,6 +66779,7 @@ var init_runtime2 = __esm(() => {
66242
66779
  init_agent_stream_events();
66243
66780
  init_cost_aggregator();
66244
66781
  init_dispatch_events();
66782
+ init_in_flight_usage();
66245
66783
  init_middleware();
66246
66784
  init_packages();
66247
66785
  init_paths2();
@@ -66250,6 +66788,7 @@ var init_runtime2 = __esm(() => {
66250
66788
  init_session_role();
66251
66789
  init_spin_breaker();
66252
66790
  init_usage_auditor();
66791
+ init_tools();
66253
66792
  init_factory();
66254
66793
  init_manager2();
66255
66794
  init_config();
@@ -66263,6 +66802,7 @@ var init_runtime2 = __esm(() => {
66263
66802
  init_agent_stream_events();
66264
66803
  init_cost_aggregator();
66265
66804
  init_dispatch_events();
66805
+ init_in_flight_usage();
66266
66806
  init_middleware();
66267
66807
  init_packages();
66268
66808
  init_paths2();
@@ -66674,7 +67214,6 @@ var init_feature_context_filter = __esm(() => {
66674
67214
  implementer: ["all", "implementer"],
66675
67215
  "test-writer": ["all", "test-writer"],
66676
67216
  verifier: ["all", "verifier"],
66677
- "single-session": ["all", "implementer", "test-writer"],
66678
67217
  "tdd-simple": ["all", "implementer", "test-writer"],
66679
67218
  "no-test": ["all", "implementer"],
66680
67219
  batch: ["all", "implementer", "test-writer"]
@@ -67921,8 +68460,7 @@ var init_acceptance_diagnose = __esm(() => {
67921
68460
  testOutput: input.testOutput,
67922
68461
  testFileContent: input.testFileContent,
67923
68462
  acceptanceTestPath: input.acceptanceTestPath,
67924
- sourceFiles: input.sourceFiles,
67925
- semanticVerdicts: input.semanticVerdicts
68463
+ sourceFiles: input.sourceFiles
67926
68464
  });
67927
68465
  return {
67928
68466
  role: { id: "role", content: "", overridable: false },
@@ -76918,7 +77456,7 @@ function buildSessionTurnEvent(input) {
76918
77456
  ...result.pricingSource !== undefined ? { pricingSource: result.pricingSource } : {},
76919
77457
  ...result.rates !== undefined ? { rates: result.rates } : {},
76920
77458
  roundTrips: result.internalRoundTrips ?? 1,
76921
- roundTripUnit: input.agentName === NATIVE_AGENT ? "model-call" : "agent-run",
77459
+ roundTripUnit: input.agentName === NATIVE_AGENT_NAME ? "model-call" : "agent-run",
76922
77460
  protocolIds: {
76923
77461
  sessionId: handle.protocolIds?.sessionId ?? null,
76924
77462
  recordId: handle.protocolIds?.recordId ?? null,
@@ -77475,7 +78013,7 @@ var init_manager_run_fallback = __esm(() => {
77475
78013
 
77476
78014
  // src/agents/registry.ts
77477
78015
  function adapterFor(name) {
77478
- return name === NATIVE_AGENT ? new NativeAgentAdapter : new AcpAgentAdapter(name);
78016
+ return name === NATIVE_AGENT_NAME ? new NativeAgentAdapter : new AcpAgentAdapter(name);
77479
78017
  }
77480
78018
  function buildAdapterList() {
77481
78019
  return [...Array.from(_registryTestAdapters.values()), ...KNOWN_AGENT_NAMES.map(adapterFor)];
@@ -77491,12 +78029,12 @@ async function getInstalledAgents() {
77491
78029
  function createAgentRegistry(config2) {
77492
78030
  const logger = getLogger();
77493
78031
  const adapterCache = new Map;
77494
- const protocol = config2.agent?.protocol ?? "acp";
78032
+ const protocol = config2.agent?.protocol ?? DEFAULT_AGENT_PROTOCOL;
77495
78033
  logger?.info("agents", `Agent protocol: ${protocol}`, { protocol, hasConfig: !!config2.agent });
77496
78034
  function cachedAdapter(name) {
77497
78035
  let adapter = adapterCache.get(name);
77498
78036
  if (adapter === undefined) {
77499
- adapter = name === NATIVE_AGENT ? new NativeAgentAdapter(undefined, config2.agent?.native?.catalogOverrides ?? []) : new AcpAgentAdapter(name);
78037
+ adapter = name === NATIVE_AGENT_NAME ? new NativeAgentAdapter(undefined, config2.agent?.native?.catalogOverrides ?? []) : new AcpAgentAdapter(name);
77500
78038
  adapterCache.set(name, adapter);
77501
78039
  logger?.debug("agents", `Created ${adapter.constructor.name} for ${name}`, { name });
77502
78040
  }
@@ -77530,10 +78068,11 @@ function createAgentRegistry(config2) {
77530
78068
  }
77531
78069
  var KNOWN_AGENT_NAMES, _registryTestAdapters;
77532
78070
  var init_registry4 = __esm(() => {
78071
+ init_config();
77533
78072
  init_logger2();
77534
78073
  init_adapter2();
77535
78074
  init_native();
77536
- KNOWN_AGENT_NAMES = ["claude", "codex", "opencode", "gemini", "aider", "pi", NATIVE_AGENT];
78075
+ KNOWN_AGENT_NAMES = ["claude", "codex", "opencode", "gemini", "aider", "pi", NATIVE_AGENT_NAME];
77537
78076
  _registryTestAdapters = new Map;
77538
78077
  });
77539
78078
 
@@ -77602,7 +78141,7 @@ class AgentManager {
77602
78141
  const fromAgent = this._config.agent?.default;
77603
78142
  if (typeof fromAgent === "string" && fromAgent.length > 0)
77604
78143
  return fromAgent;
77605
- return "claude";
78144
+ return DEFAULT_AGENT_NAME;
77606
78145
  }
77607
78146
  isUnavailable(agent, tier, model) {
77608
78147
  return this._cooldowns.isCooling(agent, tier, this._fallbackIdentity.modelId(agent, tier, model));
@@ -77992,6 +78531,7 @@ class AgentManager {
77992
78531
  }
77993
78532
  var MAX_EMITTER_LISTENERS = 100, _agentManagerDeps;
77994
78533
  var init_manager2 = __esm(() => {
78534
+ init_config();
77995
78535
  init_permissions2();
77996
78536
  init_errors();
77997
78537
  init_logger2();
@@ -78036,7 +78576,7 @@ function toAssignment(p, models, defaultAgent) {
78036
78576
  if (!membership.isTier) {
78037
78577
  return { agent: p.target.agent, agentProfileId: p.id, profileModelPin: p.target.model };
78038
78578
  }
78039
- if (membership.viaDefaultAgentFallback && p.target.agent === NATIVE_AGENT2 !== (defaultAgent === NATIVE_AGENT2)) {
78579
+ if (membership.viaDefaultAgentFallback && p.target.agent === NATIVE_AGENT !== (defaultAgent === NATIVE_AGENT)) {
78040
78580
  getSafeLogger()?.warn("routing", "Profile tier resolves only via the default agent across a protocol boundary", {
78041
78581
  profileId: p.id,
78042
78582
  agent: p.target.agent,
@@ -78046,7 +78586,7 @@ function toAssignment(p, models, defaultAgent) {
78046
78586
  }
78047
78587
  return { agent: p.target.agent, agentProfileId: p.id, profileModelTier: targetModel };
78048
78588
  }
78049
- var NATIVE_AGENT2 = "native";
78589
+ var NATIVE_AGENT = "native";
78050
78590
  var init_agent_profile_resolver = __esm(() => {
78051
78591
  init_config();
78052
78592
  init_logger2();
@@ -78146,7 +78686,11 @@ function resolveDefaultAgent(config2) {
78146
78686
  return fromAgent;
78147
78687
  return FALLBACK_DEFAULT_AGENT;
78148
78688
  }
78149
- var FALLBACK_DEFAULT_AGENT = "claude";
78689
+ var FALLBACK_DEFAULT_AGENT;
78690
+ var init_utils = __esm(() => {
78691
+ init_config();
78692
+ FALLBACK_DEFAULT_AGENT = DEFAULT_AGENT_NAME;
78693
+ });
78150
78694
 
78151
78695
  // src/agents/index.ts
78152
78696
  var init_agents = __esm(() => {
@@ -78161,20 +78705,24 @@ var init_agents = __esm(() => {
78161
78705
  init_shared();
78162
78706
  init_version_detection();
78163
78707
  init_types2();
78708
+ init_utils();
78164
78709
  });
78165
78710
 
78166
78711
  // src/cli/agents.ts
78167
78712
  async function agentsListCommand(config2, _workdir) {
78168
- const adapters = Array.from(ACP_ADAPTER_NAMES).map((name) => new AcpAgentAdapter(name));
78169
- const agentVersions = await Promise.all(adapters.map(async (agent) => ({
78713
+ const acpReachable = (config2.agent?.protocol ?? DEFAULT_AGENT_PROTOCOL) !== "native";
78714
+ const adapters = acpReachable ? Array.from(ACP_ADAPTER_NAMES).map((name) => new AcpAgentAdapter(name)) : [];
78715
+ const defaultAgent = resolveDefaultAgent(config2);
78716
+ const acpVersions = await Promise.all(adapters.map(async (agent) => ({
78170
78717
  name: agent.name,
78171
78718
  displayName: agent.displayName,
78172
78719
  binary: agent.binary,
78173
78720
  version: await _cliAgentsDeps.getAgentVersion(agent.binary),
78174
78721
  installed: await agent.isInstalled(),
78175
78722
  capabilities: agent.capabilities,
78176
- isDefault: resolveDefaultAgent(config2) === agent.name
78723
+ isDefault: defaultAgent === agent.name
78177
78724
  })));
78725
+ const agentVersions = [...nativeListing(config2, defaultAgent), ...acpVersions];
78178
78726
  const rows = agentVersions.map((info) => {
78179
78727
  const status = info.installed ? "installed" : "unavailable";
78180
78728
  const versionStr = info.version || "-";
@@ -78208,6 +78756,22 @@ Available Agents:
78208
78756
  }
78209
78757
  console.log();
78210
78758
  }
78759
+ function nativeListing(config2, defaultAgent) {
78760
+ if ((config2.agent?.protocol ?? DEFAULT_AGENT_PROTOCOL) === "acp")
78761
+ return [];
78762
+ const adapter = new NativeAgentAdapter(Object.keys(config2.models[NATIVE_AGENT_NAME] ?? {}));
78763
+ return [
78764
+ {
78765
+ name: adapter.name,
78766
+ displayName: adapter.displayName,
78767
+ binary: "in-process",
78768
+ version: "",
78769
+ installed: true,
78770
+ capabilities: adapter.capabilities,
78771
+ isDefault: defaultAgent === adapter.name
78772
+ }
78773
+ ];
78774
+ }
78211
78775
  function pad(str, width) {
78212
78776
  return str.padEnd(width);
78213
78777
  }
@@ -78215,7 +78779,9 @@ var _cliAgentsDeps;
78215
78779
  var init_agents2 = __esm(() => {
78216
78780
  init_agents();
78217
78781
  init_acp();
78782
+ init_native();
78218
78783
  init_version_detection();
78784
+ init_config();
78219
78785
  _cliAgentsDeps = { getAgentVersion };
78220
78786
  });
78221
78787
 
@@ -78917,7 +79483,7 @@ var init_config_descriptions = __esm(() => {
78917
79483
  "routing.llm.mode": "Routing mode: one-shot | per-story | hybrid",
78918
79484
  "routing.llm.timeoutMs": "Timeout for LLM routing call in milliseconds",
78919
79485
  execution: "Execution limits and timeouts",
78920
- "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",
78921
79487
  "execution.iterationDelayMs": "Delay between iterations in milliseconds",
78922
79488
  "execution.costLimit": "Max cost in USD before pausing execution (override per run with `nax run --max-cost`)",
78923
79489
  "execution.sessionTimeoutSeconds": "Timeout per agent coding session in seconds",
@@ -79073,7 +79639,6 @@ var init_config_descriptions = __esm(() => {
79073
79639
  "prompts.overrides.test-writer": 'Path to custom test-writer prompt (e.g., ".nax/prompts/test-writer.md")',
79074
79640
  "prompts.overrides.implementer": 'Path to custom implementer prompt (e.g., ".nax/prompts/implementer.md")',
79075
79641
  "prompts.overrides.verifier": 'Path to custom verifier prompt (e.g., ".nax/prompts/verifier.md")',
79076
- "prompts.overrides.single-session": 'Path to custom single-session prompt (e.g., ".nax/prompts/single-session.md")',
79077
79642
  agent: "Agent protocol configuration (ACP-003)",
79078
79643
  "agent.protocol": "Protocol for agent communication: 'acp' (default) | 'native' | 'hybrid'",
79079
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.",
@@ -79376,7 +79941,7 @@ function displayConfigWithDescriptions(obj, path10, sources, indent = 0) {
79376
79941
  if (description) {
79377
79942
  console.log(`${indentStr}# prompts.overrides: ${description}`);
79378
79943
  }
79379
- const roles = ["test-writer", "implementer", "verifier", "single-session"];
79944
+ const roles = ["test-writer", "implementer", "verifier"];
79380
79945
  console.log(`${indentStr}overrides:`);
79381
79946
  for (const role of roles) {
79382
79947
  const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
@@ -79421,7 +79986,7 @@ function displayConfigWithDescriptions(obj, path10, sources, indent = 0) {
79421
79986
  if (description) {
79422
79987
  console.log(`# prompts.overrides: ${description}`);
79423
79988
  }
79424
- const roles = ["test-writer", "implementer", "verifier", "single-session"];
79989
+ const roles = ["test-writer", "implementer", "verifier"];
79425
79990
  console.log("overrides:");
79426
79991
  for (const role of roles) {
79427
79992
  const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
@@ -80450,75 +81015,6 @@ var init_hardening = __esm(() => {
80450
81015
  };
80451
81016
  });
80452
81017
 
80453
- // src/acceptance/semantic-verdict.ts
80454
- import path11 from "path";
80455
- async function persistSemanticVerdict(featureDir2, storyId, verdict) {
80456
- const dir = path11.join(featureDir2, "semantic-verdicts");
80457
- await _semanticVerdictDeps.mkdirp(dir);
80458
- const filePath = path11.join(dir, `${storyId}.json`);
80459
- await _semanticVerdictDeps.writeFile(filePath, JSON.stringify(verdict, null, 2));
80460
- }
80461
- function migrateSemanticVerdict(verdict) {
80462
- if (!verdict.findings?.length)
80463
- return verdict;
80464
- const first = verdict.findings[0];
80465
- if ("source" in first)
80466
- return verdict;
80467
- return {
80468
- ...verdict,
80469
- findings: verdict.findings.map((f) => reviewFindingToFinding(f))
80470
- };
80471
- }
80472
- async function loadSemanticVerdicts(featureDir2) {
80473
- const dir = path11.join(featureDir2, "semantic-verdicts");
80474
- let files;
80475
- try {
80476
- files = await _semanticVerdictDeps.readdir(dir);
80477
- } catch (err) {
80478
- if (err.code === "ENOENT")
80479
- return [];
80480
- throw err;
80481
- }
80482
- const results = [];
80483
- for (const file2 of files) {
80484
- if (!file2.endsWith(".json"))
80485
- continue;
80486
- const filePath = path11.join(dir, file2);
80487
- const content = await _semanticVerdictDeps.readFile(filePath);
80488
- try {
80489
- const parsed = JSON.parse(content);
80490
- results.push(migrateSemanticVerdict(parsed));
80491
- } catch {
80492
- _semanticVerdictDeps.logDebug(`Skipping invalid JSON in semantic-verdicts/${file2}`);
80493
- }
80494
- }
80495
- return results;
80496
- }
80497
- var _semanticVerdictDeps;
80498
- var init_semantic_verdict = __esm(() => {
80499
- init_findings();
80500
- init_logger2();
80501
- _semanticVerdictDeps = {
80502
- mkdirp: async (dir) => {
80503
- const { mkdir: mkdir21 } = await import("fs/promises");
80504
- await mkdir21(dir, { recursive: true });
80505
- },
80506
- writeFile: async (filePath, content) => {
80507
- await Bun.write(filePath, content);
80508
- },
80509
- readdir: async (dir) => {
80510
- const { readdir: readdir7 } = await import("fs/promises");
80511
- return readdir7(dir);
80512
- },
80513
- readFile: async (filePath) => {
80514
- return Bun.file(filePath).text();
80515
- },
80516
- logDebug: (msg) => {
80517
- getLogger()?.debug("semantic-verdict", msg);
80518
- }
80519
- };
80520
- });
80521
-
80522
81018
  // src/acceptance/index.ts
80523
81019
  var exports_acceptance = {};
80524
81020
  __export(exports_acceptance, {
@@ -80531,11 +81027,9 @@ __export(exports_acceptance, {
80531
81027
  groupStoriesByPackage: () => groupStoriesByPackage,
80532
81028
  isStubTestContent: () => isStubTestContent,
80533
81029
  loadAcceptanceTestContent: () => loadAcceptanceTestContent,
80534
- loadSemanticVerdicts: () => loadSemanticVerdicts,
80535
81030
  loadSourceFilesForDiagnosis: () => loadSourceFilesForDiagnosis,
80536
81031
  parseAcceptanceCriteria: () => parseAcceptanceCriteria,
80537
81032
  parseRefinementResponse: () => parseRefinementResponse,
80538
- persistSemanticVerdict: () => persistSemanticVerdict,
80539
81033
  refinementWouldFallback: () => refinementWouldFallback,
80540
81034
  resolveAcceptanceFeatureTestPath: () => resolveAcceptanceFeatureTestPath,
80541
81035
  resolveSuggestedPackageFeatureTestPath: () => resolveSuggestedPackageFeatureTestPath,
@@ -80549,7 +81043,6 @@ var init_acceptance2 = __esm(() => {
80549
81043
  init_generator();
80550
81044
  init_hardening();
80551
81045
  init_refinement();
80552
- init_semantic_verdict();
80553
81046
  init_test_path();
80554
81047
  });
80555
81048
 
@@ -81545,11 +82038,11 @@ var init_generate = __esm(() => {
81545
82038
  // src/cli/init-context.ts
81546
82039
  import { mkdir as mkdir21, readdir as readdir7 } from "fs/promises";
81547
82040
  import { basename as basename11, join as join74, relative as relative23, sep as sep12 } from "path";
81548
- async function bunFileExists(path12) {
81549
- return Bun.file(path12).exists();
82041
+ async function bunFileExists(path11) {
82042
+ return Bun.file(path11).exists();
81550
82043
  }
81551
- async function mkdirp(path12) {
81552
- await mkdir21(path12, { recursive: true });
82044
+ async function mkdirp(path11) {
82045
+ await mkdir21(path11, { recursive: true });
81553
82046
  }
81554
82047
  async function findFiles(dir, maxFiles = 200) {
81555
82048
  const files = [];
@@ -81612,8 +82105,8 @@ async function detectEntryPoints(projectRoot) {
81612
82105
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
81613
82106
  const found = [];
81614
82107
  for (const candidate of candidates) {
81615
- const path12 = join74(projectRoot, candidate);
81616
- if (await bunFileExists(path12)) {
82108
+ const path11 = join74(projectRoot, candidate);
82109
+ if (await bunFileExists(path11)) {
81617
82110
  found.push(candidate);
81618
82111
  }
81619
82112
  }
@@ -81623,8 +82116,8 @@ async function detectConfigFiles(projectRoot) {
81623
82116
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
81624
82117
  const found = [];
81625
82118
  for (const candidate of candidates) {
81626
- const path12 = join74(projectRoot, candidate);
81627
- if (await bunFileExists(path12)) {
82119
+ const path11 = join74(projectRoot, candidate);
82120
+ if (await bunFileExists(path11)) {
81628
82121
  found.push(candidate);
81629
82122
  }
81630
82123
  }
@@ -82453,9 +82946,9 @@ var init_scanner = __esm(() => {
82453
82946
  _scannerDeps = {
82454
82947
  discoverWorkspacePackages: (workdir) => discoverWorkspacePackages2(workdir),
82455
82948
  detectLanguage: (pkgDir) => detectLanguage(pkgDir),
82456
- readPackageJson: async (path12) => {
82949
+ readPackageJson: async (path11) => {
82457
82950
  try {
82458
- const file2 = Bun.file(path12);
82951
+ const file2 = Bun.file(path11);
82459
82952
  if (!await file2.exists())
82460
82953
  return null;
82461
82954
  return await file2.json();
@@ -82476,6 +82969,7 @@ var init_analyze = __esm(() => {
82476
82969
  function createHumanAskLink(opts) {
82477
82970
  let queue = Promise.resolve();
82478
82971
  let activeId;
82972
+ const canRemember = (req) => opts.onRemember !== undefined && req.command !== undefined;
82479
82973
  const deny3 = (decidedBy) => ({
82480
82974
  decision: "deny",
82481
82975
  decidedBy
@@ -82613,7 +83107,7 @@ function createHumanAskLink(opts) {
82613
83107
  `stage: ${req.stage}`
82614
83108
  ].join(`
82615
83109
  `),
82616
- options: OPTIONS,
83110
+ options: canRemember(req) ? [ALLOW_ONCE, ALLOW_REMEMBER, DENY] : [ALLOW_ONCE, DENY],
82617
83111
  timeout: opts.timeoutMs,
82618
83112
  fallback: "abort",
82619
83113
  createdAt: Date.now(),
@@ -82631,7 +83125,7 @@ function createHumanAskLink(opts) {
82631
83125
  if (!PERMITS.has(action)) {
82632
83126
  outcome = deny3("human");
82633
83127
  } else {
82634
- if (action === "allow-remember" && opts.onRemember) {
83128
+ if (action === "allow-remember" && canRemember(req) && opts.onRemember) {
82635
83129
  try {
82636
83130
  await opts.onRemember(req);
82637
83131
  } catch (err) {
@@ -82757,7 +83251,7 @@ function createHumanAskLink(opts) {
82757
83251
  async function cancelPendingAsk(link) {
82758
83252
  await link.cancel();
82759
83253
  }
82760
- 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;
82761
83255
  var init_ask_link = __esm(() => {
82762
83256
  init_permissions();
82763
83257
  init_logger2();
@@ -82766,11 +83260,9 @@ var init_ask_link = __esm(() => {
82766
83260
  clearTimeout: (id) => clearTimeout(id),
82767
83261
  ASK_KEEPALIVE_MS: 60000
82768
83262
  };
82769
- OPTIONS = [
82770
- { key: "allow", label: "Allow once" },
82771
- { key: "allow-remember", label: "Allow + remember" },
82772
- { key: "deny", label: "Deny" }
82773
- ];
83263
+ ALLOW_ONCE = { key: "allow", label: "Allow once" };
83264
+ ALLOW_REMEMBER = { key: "allow-remember", label: "Allow + remember" };
83265
+ DENY = { key: "deny", label: "Deny" };
82774
83266
  PERMITS = new Set(["allow", "allow-remember"]);
82775
83267
  });
82776
83268
 
@@ -82944,10 +83436,10 @@ async function buildDispatchAskWiring(opts, deps = _dispatchAskDeps) {
82944
83436
  stage: req.stage,
82945
83437
  command: req.command ?? "",
82946
83438
  root: req.root ?? opts.repoRoot,
82947
- origin: "escalate",
82948
- matchedRule: null,
83439
+ origin: req.matchedRule !== undefined ? "askRule" : "escalate",
83440
+ matchedRule: req.matchedRule ?? null,
82949
83441
  approvedAt: new Date().toISOString(),
82950
- approvedBy: "telegram",
83442
+ approvedBy: opts.config.interaction?.plugin ?? "unknown",
82951
83443
  naxCommit: NAX_COMMIT
82952
83444
  })
82953
83445
  });
@@ -83025,6 +83517,25 @@ async function collectEffectiveRunStageModes(opts, deps = _dispatchAskDeps) {
83025
83517
  })));
83026
83518
  return collectRunStageModes([opts.rootConfig, ...opts.extraConfigs ?? [], ...packageConfigs]);
83027
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
+ }
83028
83539
  async function buildRunDispatchAskWiring(opts, deps = _dispatchAskDeps) {
83029
83540
  const stageModes = await collectEffectiveRunStageModes({
83030
83541
  projectDir: opts.projectDir,
@@ -83038,6 +83549,7 @@ var DEFAULT_APPROVAL_TIMEOUT_MS = 600000, APPROVAL_AUDIT_DIR = "approval-audit",
83038
83549
  var init_dispatch_ask = __esm(() => {
83039
83550
  init_command_safety();
83040
83551
  init_config();
83552
+ init_logger2();
83041
83553
  init_permissions();
83042
83554
  init_version();
83043
83555
  init_ask_link();
@@ -84723,6 +85235,14 @@ async function checkClaudeCLI() {
84723
85235
  }
84724
85236
  async function checkAgentCLI(config2) {
84725
85237
  const agent = resolveDefaultAgent(config2);
85238
+ if (agent === NATIVE_AGENT_NAME) {
85239
+ return {
85240
+ name: "agent-cli-available",
85241
+ tier: "blocker",
85242
+ passed: true,
85243
+ message: "native agent runs in-process; no CLI binary required"
85244
+ };
85245
+ }
84726
85246
  try {
84727
85247
  const proc = _checkCliDeps.spawn([agent, "--version"], {
84728
85248
  stdout: "pipe",
@@ -84748,6 +85268,7 @@ async function checkAgentCLI(config2) {
84748
85268
  var _checkCliDeps;
84749
85269
  var init_checks_cli = __esm(() => {
84750
85270
  init_agents();
85271
+ init_native();
84751
85272
  init_bun_deps();
84752
85273
  _checkCliDeps = {
84753
85274
  spawn
@@ -88145,6 +88666,11 @@ async function performTeardown(ctx) {
88145
88666
  if (ctx.pidRegistry) {
88146
88667
  await ctx.pidRegistry.killAll();
88147
88668
  }
88669
+ if (ctx.sealApprovals) {
88670
+ await ctx.sealApprovals().catch(() => {
88671
+ return;
88672
+ });
88673
+ }
88148
88674
  }
88149
88675
  function getSignalNumber(signal) {
88150
88676
  const signalMap = {
@@ -88301,7 +88827,7 @@ var init_crash_recovery = __esm(() => {
88301
88827
  });
88302
88828
 
88303
88829
  // src/execution/ensure-package-dirs.ts
88304
- import path12 from "path";
88830
+ import path11 from "path";
88305
88831
  async function ensureStoryPackageDirs(prd, workdir, deps = _ensurePackageDirsDeps) {
88306
88832
  const logger = getSafeLogger();
88307
88833
  const relToStoryId = new Map;
@@ -88314,8 +88840,8 @@ async function ensureStoryPackageDirs(prd, workdir, deps = _ensurePackageDirsDep
88314
88840
  }
88315
88841
  const created = [];
88316
88842
  for (const [rel, storyId] of relToStoryId) {
88317
- const abs = path12.resolve(workdir, rel);
88318
- 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;
88319
88845
  if (abs !== workdir && !abs.startsWith(rootWithSep)) {
88320
88846
  logger?.warn("execution", "Skipping story workdir outside repo root", {
88321
88847
  storyId,
@@ -88466,8 +88992,8 @@ async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
88466
88992
  return false;
88467
88993
  const lines = content.split(`
88468
88994
  `);
88469
- const start = Math.max(0, triple.line - 1 - CONTEXT_LINES);
88470
- 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);
88471
88997
  const window2 = lines.slice(start, end).join(`
88472
88998
  `);
88473
88999
  return normalizeWs2(window2).toLowerCase().includes(normalizeWs2(triple.quote).toLowerCase());
@@ -88492,14 +89018,14 @@ async function verifyEscalationQuotes(reason, workdir, storyId, deps = _quoteInt
88492
89018
  }
88493
89019
  return verified;
88494
89020
  }
88495
- var _quoteIntegrityDeps, CONTEXT_LINES = 3;
89021
+ var _quoteIntegrityDeps, CONTEXT_LINES2 = 3;
88496
89022
  var init_quote_integrity = __esm(() => {
88497
89023
  init_logger2();
88498
89024
  init_path_security2();
88499
89025
  _quoteIntegrityDeps = {
88500
- readFile: async (path13) => {
89026
+ readFile: async (path12) => {
88501
89027
  try {
88502
- return await Bun.file(path13).text();
89028
+ return await Bun.file(path12).text();
88503
89029
  } catch {
88504
89030
  return null;
88505
89031
  }
@@ -88939,7 +89465,7 @@ var init_escalation = __esm(() => {
88939
89465
  import { randomUUID as randomUUID10 } from "crypto";
88940
89466
  import { rename as rename5, unlink as unlink5 } from "fs/promises";
88941
89467
  import { hostname as hostname3 } from "os";
88942
- import path13 from "path";
89468
+ import path12 from "path";
88943
89469
  function getSafeLogger3() {
88944
89470
  try {
88945
89471
  return getLogger();
@@ -89019,7 +89545,7 @@ async function claimReclaimableLock(lockPath, observedContent, lockData) {
89019
89545
  return { action: "discard" };
89020
89546
  }
89021
89547
  async function acquireLock(workdir) {
89022
- const lockPath = path13.join(workdir, "nax.lock");
89548
+ const lockPath = path12.join(workdir, "nax.lock");
89023
89549
  const lockFile = Bun.file(lockPath);
89024
89550
  try {
89025
89551
  const exists2 = await lockFile.exists();
@@ -89072,7 +89598,7 @@ async function acquireLock(workdir) {
89072
89598
  }
89073
89599
  }
89074
89600
  async function releaseLock(workdir) {
89075
- const lockPath = path13.join(workdir, "nax.lock");
89601
+ const lockPath = path12.join(workdir, "nax.lock");
89076
89602
  try {
89077
89603
  await unlink5(lockPath);
89078
89604
  } catch (error48) {
@@ -89097,7 +89623,7 @@ var init_lock2 = __esm(() => {
89097
89623
  // src/execution/feature-lock.ts
89098
89624
  import { mkdir as mkdir24, rename as rename6, unlink as unlink6 } from "fs/promises";
89099
89625
  import { hostname as hostname4 } from "os";
89100
- import path14 from "path";
89626
+ import path13 from "path";
89101
89627
  function validateFeatureId2(featureId) {
89102
89628
  if (!featureId || featureId.length === 0) {
89103
89629
  throw new NaxError("Feature ID cannot be empty", "INVALID_FEATURE_ID", { stage: "feature-lock" });
@@ -89127,7 +89653,7 @@ function getSafeLogger4() {
89127
89653
  }
89128
89654
  function featureLockPath(outputDir, feature) {
89129
89655
  validateFeatureId2(feature);
89130
- return path14.join(outputDir, "features", feature, "nax.lock");
89656
+ return path13.join(outputDir, "features", feature, "nax.lock");
89131
89657
  }
89132
89658
  function lockHost() {
89133
89659
  return _featureLockDeps.host();
@@ -89188,7 +89714,7 @@ function emptyHolder(feature) {
89188
89714
  }
89189
89715
  async function acquireFeatureLock(args) {
89190
89716
  const lockPath = _featureLockDeps.featureLockPath(args.outputDir, args.feature);
89191
- const featureDir2 = path14.dirname(lockPath);
89717
+ const featureDir2 = path13.dirname(lockPath);
89192
89718
  await mkdir24(featureDir2, { recursive: true });
89193
89719
  const lockFile = Bun.file(lockPath);
89194
89720
  if (await lockFile.exists()) {
@@ -90645,7 +91171,7 @@ var init_acceptance3 = __esm(() => {
90645
91171
  });
90646
91172
 
90647
91173
  // src/pipeline/stages/acceptance-setup.ts
90648
- import path15 from "path";
91174
+ import path14 from "path";
90649
91175
  function computeACFingerprint(criteria) {
90650
91176
  const sorted = [...criteria].sort().join(`
90651
91177
  `);
@@ -90655,7 +91181,7 @@ function computeACFingerprint(criteria) {
90655
91181
  }
90656
91182
  function computeAcceptanceLayoutFingerprint(workdir, groups) {
90657
91183
  const layout = groups.map(({ testPath, stories }) => ({
90658
- testPath: path15.relative(workdir, testPath).replaceAll(path15.sep, "/"),
91184
+ testPath: path14.relative(workdir, testPath).replaceAll(path14.sep, "/"),
90659
91185
  storyIds: stories.map((story) => story.id).sort()
90660
91186
  })).sort((a, b) => a.testPath.localeCompare(b.testPath));
90661
91187
  const hasher = new Bun.CryptoHasher("sha256");
@@ -90665,14 +91191,14 @@ function computeAcceptanceLayoutFingerprint(workdir, groups) {
90665
91191
  async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
90666
91192
  const language = ctx.config.project?.language;
90667
91193
  const testPathConfig = ctx.config.acceptance.testPath;
90668
- const metaPath = path15.join(featureDir2, "acceptance-meta.json");
91194
+ const metaPath = path14.join(featureDir2, "acceptance-meta.json");
90669
91195
  const allCriteria = ctx.prd.userStories.filter(isInAcceptanceScope).flatMap((s) => s.acceptanceCriteria);
90670
91196
  const featureName = ctx.prd.feature ?? ctx.prd.featureName;
90671
91197
  const groups = await groupStoriesByPackage(ctx.prd, ctx.workdir, featureName, testPathConfig, language);
90672
91198
  const nonFixStories = groups.flatMap((g) => g.stories);
90673
91199
  const groupConfigs = new Map;
90674
91200
  for (const group of groups) {
90675
- const relativeWorkdir = path15.relative(ctx.projectDir, group.packageDir);
91201
+ const relativeWorkdir = path14.relative(ctx.projectDir, group.packageDir);
90676
91202
  let config2 = ctx.config;
90677
91203
  if (relativeWorkdir && relativeWorkdir !== ".") {
90678
91204
  try {
@@ -90717,7 +91243,6 @@ async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
90717
91243
  await _acceptanceSetupDeps.deleteFile(testPath);
90718
91244
  }
90719
91245
  }
90720
- await _acceptanceSetupDeps.deleteSemanticVerdicts(featureDir2);
90721
91246
  shouldGenerate = true;
90722
91247
  regenerated = true;
90723
91248
  } else {
@@ -90825,7 +91350,7 @@ async function runAcceptanceSetup(ctx, featureDir2, phaseStartTime) {
90825
91350
  testable: c.testable,
90826
91351
  storyId: c.storyId
90827
91352
  })), null, 2);
90828
- await _acceptanceSetupDeps.writeFile(path15.join(featureDir2, "acceptance-refined.json"), refinedJsonContent);
91353
+ await _acceptanceSetupDeps.writeFile(path14.join(featureDir2, "acceptance-refined.json"), refinedJsonContent);
90829
91354
  }
90830
91355
  if (sawDispatchFailure) {
90831
91356
  getSafeLogger()?.warn("acceptance-setup", "generation dispatch failed; not recording acceptance meta so the next run regenerates", { storyId: ctx.story?.id, metaPath });
@@ -90937,21 +91462,6 @@ var init_acceptance_setup = __esm(() => {
90937
91462
  throw err;
90938
91463
  }
90939
91464
  },
90940
- deleteSemanticVerdicts: async (featureDir2) => {
90941
- const dir = `${featureDir2}/semantic-verdicts`;
90942
- const { readdir: readdir8, unlink: unlink7 } = await import("fs/promises");
90943
- let files;
90944
- try {
90945
- files = await readdir8(dir);
90946
- } catch (err) {
90947
- if (err.code === "ENOENT")
90948
- return;
90949
- throw err;
90950
- }
90951
- for (const file2 of files) {
90952
- await unlink7(`${dir}/${file2}`);
90953
- }
90954
- },
90955
91465
  readMeta: async (metaPath) => {
90956
91466
  const f = Bun.file(metaPath);
90957
91467
  if (!await f.exists())
@@ -91156,7 +91666,6 @@ async function getDiffFilePaths(workdir, baseRef) {
91156
91666
  }
91157
91667
  var MAX_DIFF_TEXT_CHARS = 8000, HIGH_MEMORY_TELEMETRY_BYTES, STREAM_DRAIN_DEADLINE_MS = 2000, completionStage, _completionDeps;
91158
91668
  var init_completion = __esm(() => {
91159
- init_acceptance2();
91160
91669
  init_config();
91161
91670
  init_engine();
91162
91671
  init_fragments();
@@ -91276,7 +91785,6 @@ var init_completion = __esm(() => {
91276
91785
  };
91277
91786
  _completionDeps = {
91278
91787
  checkReviewGate,
91279
- persistSemanticVerdict,
91280
91788
  savePRD,
91281
91789
  getDiffText,
91282
91790
  getDiffFilePaths,
@@ -92052,9 +92560,9 @@ var init_prompt2 = __esm(() => {
92052
92560
  });
92053
92561
 
92054
92562
  // src/pipeline/stages/queue-check.ts
92055
- import path16 from "path";
92563
+ import path15 from "path";
92056
92564
  function resolvePrdPath(ctx) {
92057
- return path16.join(ctx.featureDir ?? featureDir(ctx.workdir, "unknown"), "prd.json");
92565
+ return path15.join(ctx.featureDir ?? featureDir(ctx.workdir, "unknown"), "prd.json");
92058
92566
  }
92059
92567
  function logDroppedCommands(logger, ctx, queueCommands, currentIndex) {
92060
92568
  const dropped = queueCommands.slice(currentIndex + 1);
@@ -92105,10 +92613,10 @@ async function processQueueCommands(ctx, logger, queueCommands) {
92105
92613
  }
92106
92614
  if (cmd.type === "INJECT") {
92107
92615
  try {
92108
- if (path16.isAbsolute(cmd.storyFile)) {
92616
+ if (path15.isAbsolute(cmd.storyFile)) {
92109
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 });
92110
92618
  }
92111
- const storyFilePath = validateFilePath(path16.join(ctx.workdir, cmd.storyFile), ctx.workdir);
92619
+ const storyFilePath = validateFilePath(path15.join(ctx.workdir, cmd.storyFile), ctx.workdir);
92112
92620
  const raw = await Bun.file(storyFilePath).json();
92113
92621
  const existingIds = new Set(ctx.prd.userStories.map((s) => s.id));
92114
92622
  const story = validateInjectedStory(raw, existingIds);
@@ -93486,7 +93994,7 @@ var init_hooks = __esm(() => {
93486
93994
  });
93487
93995
 
93488
93996
  // src/execution/lifecycle/acceptance-helpers.ts
93489
- import path17 from "path";
93997
+ import path16 from "path";
93490
93998
  function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2) {
93491
93999
  const matchedEntry = failedPackage ? acceptanceTestPaths?.find((entry) => entry.testPath === failedPackage.testPath || entry.packageDir === failedPackage.packageDir) : undefined;
93492
94000
  const selectedPathEntry = matchedEntry ?? acceptanceTestPaths?.[0];
@@ -93509,10 +94017,7 @@ function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2)
93509
94017
  function isStubTestFile(content) {
93510
94018
  return isStubTestContent(content);
93511
94019
  }
93512
- function isTestLevelFailure(failedACs, totalACs, semanticVerdicts) {
93513
- if (semanticVerdicts && semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
93514
- return true;
93515
- }
94020
+ function isTestLevelFailure(failedACs, totalACs) {
93516
94021
  const failedCount = typeof failedACs === "number" ? failedACs : failedACs.length;
93517
94022
  const hasACError = Array.isArray(failedACs) && failedACs.includes("AC-ERROR");
93518
94023
  if (hasACError)
@@ -93524,7 +94029,7 @@ function isTestLevelFailure(failedACs, totalACs, semanticVerdicts) {
93524
94029
  async function loadSpecContent(featureDir2) {
93525
94030
  if (!featureDir2)
93526
94031
  return "";
93527
- const specPath = path17.join(featureDir2, "spec.md");
94032
+ const specPath = path16.join(featureDir2, "spec.md");
93528
94033
  const specFile = Bun.file(specPath);
93529
94034
  return await specFile.exists() ? await specFile.text() : "";
93530
94035
  }
@@ -93544,7 +94049,7 @@ async function loadAcceptanceTestContent2(featureDir2, testPaths, configuredTest
93544
94049
  }
93545
94050
  if (!configuredTestPath)
93546
94051
  return [];
93547
- const resolvedPath = path17.join(featureDir2, configuredTestPath);
94052
+ const resolvedPath = path16.join(featureDir2, configuredTestPath);
93548
94053
  const testFile = Bun.file(resolvedPath);
93549
94054
  const content = await testFile.exists() ? await testFile.text() : "";
93550
94055
  return [{ content, path: resolvedPath }];
@@ -93574,7 +94079,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
93574
94079
  const { unlink: unlink7 } = await import("fs/promises");
93575
94080
  await unlink7(testPath);
93576
94081
  if (acceptanceContext.featureDir) {
93577
- const metaPath = path17.join(acceptanceContext.featureDir, "acceptance-meta.json");
94082
+ const metaPath = path16.join(acceptanceContext.featureDir, "acceptance-meta.json");
93578
94083
  try {
93579
94084
  await unlink7(metaPath);
93580
94085
  } catch {}
@@ -93590,7 +94095,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
93590
94095
  const diffOutput = await _regenerateDeps.spawnGitDiff(repoRoot, storyGitRef, pathspec);
93591
94096
  const changedFilesRaw = diffOutput.split(`
93592
94097
  `).map((f) => f.trim()).filter((f) => f.length > 0);
93593
- const packageDir = storyPkg && acceptanceContext.projectDir ? path17.join(acceptanceContext.projectDir, storyPkg) : undefined;
94098
+ const packageDir = storyPkg && acceptanceContext.projectDir ? path16.join(acceptanceContext.projectDir, storyPkg) : undefined;
93594
94099
  const ignoreMatchers = acceptanceContext.naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
93595
94100
  const changedFiles2 = filterNaxInternalPaths(changedFilesRaw, ignoreMatchers);
93596
94101
  const MAX_BYTES = 51200;
@@ -93602,7 +94107,7 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
93602
94107
  for (const file2 of changedFiles2) {
93603
94108
  if (totalBytes >= MAX_BYTES)
93604
94109
  break;
93605
- const filePath = path17.join(repoRoot, file2);
94110
+ const filePath = path16.join(repoRoot, file2);
93606
94111
  try {
93607
94112
  const fileContent = await _regenerateDeps.readFile(filePath);
93608
94113
  const remaining = MAX_BYTES - totalBytes;
@@ -93689,7 +94194,7 @@ function fixCallCtx(ctx, packageDir, config2) {
93689
94194
  }
93690
94195
  async function resolveAcceptanceDiagnosis(opts) {
93691
94196
  const logger = getSafeLogger();
93692
- const { ctx, failures, totalACs, strategy, semanticVerdicts, diagnosisOpts } = opts;
94197
+ const { ctx, failures, totalACs, strategy, diagnosisOpts } = opts;
93693
94198
  const storyId = diagnosisOpts.storyId;
93694
94199
  if (strategy === "implement-only") {
93695
94200
  logger?.info("acceptance.diagnosis", "Fast path: implement-only strategy \u2192 source_bug", { storyId });
@@ -93699,19 +94204,6 @@ async function resolveAcceptanceDiagnosis(opts) {
93699
94204
  confidence: 1
93700
94205
  };
93701
94206
  }
93702
- const SENTINELS = ["AC-ERROR", "AC-HOOK"];
93703
- const hasOnlySentinels = failures.failedACs.length > 0 && failures.failedACs.every((ac) => SENTINELS.includes(ac));
93704
- if (!hasOnlySentinels && semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
93705
- logger?.info("acceptance.diagnosis", "Fast path: all semantic verdicts passed \u2192 test_bug", {
93706
- storyId,
93707
- verdictCount: semanticVerdicts.length
93708
- });
93709
- return {
93710
- verdict: "test_bug",
93711
- reasoning: `Semantic review confirmed all ${semanticVerdicts.length} ACs are implemented \u2014 failure is a test generation issue`,
93712
- confidence: 1
93713
- };
93714
- }
93715
94207
  if (isTestLevelFailure(failures.failedACs, totalACs)) {
93716
94208
  logger?.info("acceptance.diagnosis", "Fast path: test-level failure heuristic \u2192 test_bug", {
93717
94209
  storyId,
@@ -93733,8 +94225,7 @@ async function resolveAcceptanceDiagnosis(opts) {
93733
94225
  testOutput: diagnosisOpts.testOutput,
93734
94226
  testFileContent: diagnosisOpts.testFileContent,
93735
94227
  acceptanceTestPath: diagnosisOpts.acceptanceTestPath,
93736
- sourceFiles,
93737
- semanticVerdicts
94228
+ sourceFiles
93738
94229
  });
93739
94230
  }
93740
94231
  var _diagnosisDeps;
@@ -94022,7 +94513,6 @@ async function runAcceptanceLoop(ctx) {
94022
94513
  continue;
94023
94514
  }
94024
94515
  }
94025
- const semanticVerdicts = ctx.featureDir ? await _acceptanceLoopDeps.loadSemanticVerdicts(ctx.featureDir) : [];
94026
94516
  const totalACs = prd.userStories.filter((s) => !isLegacyFixStory(s)).flatMap((s) => s.acceptanceCriteria).length;
94027
94517
  if (!ctx.runtime) {
94028
94518
  logger?.error("acceptance", "Runtime not found for diagnosis", { storyId: firstStory?.id });
@@ -94045,7 +94535,6 @@ async function runAcceptanceLoop(ctx) {
94045
94535
  failures: pkgFailures,
94046
94536
  totalACs,
94047
94537
  strategy,
94048
- semanticVerdicts,
94049
94538
  diagnosisOpts: {
94050
94539
  testOutput: pkg.output,
94051
94540
  testFileContent,
@@ -94091,7 +94580,6 @@ var init_acceptance_loop = __esm(() => {
94091
94580
  init_acceptance_helpers();
94092
94581
  init_acceptance_helpers();
94093
94582
  _acceptanceLoopDeps = {
94094
- loadSemanticVerdicts,
94095
94583
  loadAcceptanceTestContent
94096
94584
  };
94097
94585
  _acceptanceFixCycleDeps = {
@@ -94320,7 +94808,7 @@ var init_scratchpad_wipe = __esm(() => {
94320
94808
  init_logger2();
94321
94809
  init_tools();
94322
94810
  _scratchpadWipeDeps = {
94323
- remove: (path18) => rm7(path18, { recursive: true, force: true })
94811
+ remove: (path17) => rm7(path17, { recursive: true, force: true })
94324
94812
  };
94325
94813
  });
94326
94814
 
@@ -94472,6 +94960,13 @@ async function cleanupRun(options) {
94472
94960
  } catch (error48) {
94473
94961
  logger?.warn("plugins", "Plugin teardown failed", { error: error48 });
94474
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
+ }
94475
94970
  if (interactionChain) {
94476
94971
  try {
94477
94972
  await interactionChain.destroy();
@@ -96909,8 +97404,8 @@ async function defaultRun(cmd, opts) {
96909
97404
  clearTimeout(timer);
96910
97405
  }
96911
97406
  }
96912
- async function defaultReadText(path18) {
96913
- const file2 = Bun.file(path18);
97407
+ async function defaultReadText(path17) {
97408
+ const file2 = Bun.file(path17);
96914
97409
  if (!await file2.exists())
96915
97410
  return null;
96916
97411
  return file2.text();
@@ -97010,10 +97505,10 @@ var init_pr = __esm(() => {
97010
97505
  });
97011
97506
 
97012
97507
  // src/forge/template.ts
97013
- import * as path18 from "path";
97508
+ import * as path17 from "path";
97014
97509
  async function firstExisting(workdir, deps, paths) {
97015
97510
  for (const relPath of paths) {
97016
- const content = await deps.readText(path18.join(workdir, relPath));
97511
+ const content = await deps.readText(path17.join(workdir, relPath));
97017
97512
  if (content !== null)
97018
97513
  return content;
97019
97514
  }
@@ -97228,7 +97723,7 @@ var init_pr_body = __esm(() => {
97228
97723
  });
97229
97724
 
97230
97725
  // src/plugins/builtin/auto-pr/index.ts
97231
- import * as path19 from "path";
97726
+ import * as path18 from "path";
97232
97727
  async function defaultRun2(cmd, opts) {
97233
97728
  const argv = cmd[0] === "git" ? hardenedGitArgv(cmd) : cmd;
97234
97729
  const proc = Bun.spawn(argv, { cwd: opts.cwd, env: gitSpawnEnv(), stdout: "pipe", stderr: "pipe" });
@@ -97254,8 +97749,8 @@ async function defaultRun2(cmd, opts) {
97254
97749
  clearTimeout(timer);
97255
97750
  }
97256
97751
  }
97257
- async function defaultReadText2(path20) {
97258
- const file2 = Bun.file(path20);
97752
+ async function defaultReadText2(path19) {
97753
+ const file2 = Bun.file(path19);
97259
97754
  if (!await file2.exists())
97260
97755
  return null;
97261
97756
  return file2.text();
@@ -97285,8 +97780,8 @@ function getStorySummary(context) {
97285
97780
  function relativePrdPath(workdir, prdPath) {
97286
97781
  if (!prdPath)
97287
97782
  return prdPath;
97288
- const rel = path19.relative(workdir, prdPath);
97289
- 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;
97290
97785
  }
97291
97786
  function toPrBodyContext(context) {
97292
97787
  const summary = getStorySummary(context);
@@ -97575,7 +98070,7 @@ var init_auto_prune = __esm(() => {
97575
98070
  });
97576
98071
 
97577
98072
  // src/plugins/builtin/curator/collect.ts
97578
- import * as path20 from "path";
98073
+ import * as path19 from "path";
97579
98074
  function now() {
97580
98075
  return new Date().toISOString();
97581
98076
  }
@@ -97633,7 +98128,7 @@ function tokenCount(story) {
97633
98128
  }
97634
98129
  async function collectFromMetrics(context) {
97635
98130
  const observations = [];
97636
- const metricsPath = path20.join(context.outputDir, "metrics.json");
98131
+ const metricsPath = path19.join(context.outputDir, "metrics.json");
97637
98132
  try {
97638
98133
  const data = await readJsonFile(metricsPath);
97639
98134
  const runs = Array.isArray(data) ? data : [data];
@@ -97709,11 +98204,11 @@ function findingMessage(finding) {
97709
98204
  }
97710
98205
  async function collectFromReviewAudit(context) {
97711
98206
  const observations = [];
97712
- const auditDir = path20.join(context.outputDir, "review-audit");
98207
+ const auditDir = path19.join(context.outputDir, "review-audit");
97713
98208
  try {
97714
98209
  const glob = new Bun.Glob("**/*.json");
97715
98210
  for await (const file2 of glob.scan({ cwd: auditDir, absolute: false })) {
97716
- const fullPath = path20.join(auditDir, file2);
98211
+ const fullPath = path19.join(auditDir, file2);
97717
98212
  try {
97718
98213
  const audit = asRecord3(await readJsonFile(fullPath));
97719
98214
  if (!audit)
@@ -97764,7 +98259,7 @@ async function collectFromContextManifests(context) {
97764
98259
  try {
97765
98260
  const glob = new Bun.Glob("*/stories/*/context-manifest-*.json");
97766
98261
  for await (const file2 of glob.scan({ cwd: featuresRoot, absolute: false })) {
97767
- const fullPath = path20.join(featuresRoot, file2);
98262
+ const fullPath = path19.join(featuresRoot, file2);
97768
98263
  try {
97769
98264
  const parts = file2.split("/");
97770
98265
  const featureId = parts[0] ?? context.feature;
@@ -98371,9 +98866,9 @@ function renderProposals(proposals, runId, observationCount, provenance) {
98371
98866
 
98372
98867
  // src/plugins/builtin/curator/rollup.ts
98373
98868
  import { appendFile as appendFile7, mkdir as mkdir26, writeFile as writeFile11 } from "fs/promises";
98374
- import * as path21 from "path";
98869
+ import * as path20 from "path";
98375
98870
  async function appendToRollup(observations, rollupPath) {
98376
- const dir = path21.dirname(rollupPath);
98871
+ const dir = path20.dirname(rollupPath);
98377
98872
  await mkdir26(dir, { recursive: true }).catch(() => {});
98378
98873
  const { withPathFileLock: withPathFileLock2 } = await Promise.resolve().then(() => (init_path_file_lock(), exports_path_file_lock));
98379
98874
  await withPathFileLock2(rollupPath, () => appendToRollupUnlocked(observations, rollupPath)).catch(() => {});
@@ -98462,7 +98957,7 @@ var init_rollup2 = __esm(() => {
98462
98957
 
98463
98958
  // src/plugins/builtin/curator/index.ts
98464
98959
  import { mkdir as mkdir27 } from "fs/promises";
98465
- import * as path22 from "path";
98960
+ import * as path21 from "path";
98466
98961
  function getCuratorEnabled(context) {
98467
98962
  const cfg = context.config;
98468
98963
  if (!cfg)
@@ -98538,7 +99033,7 @@ var init_curator = __esm(() => {
98538
99033
  const observations = await collectObservations(curatorContext);
98539
99034
  if (context.outputDir) {
98540
99035
  const { observationsPath, rollupPath } = resolveCuratorOutputs(curatorContext);
98541
- const runDir = path22.dirname(observationsPath);
99036
+ const runDir = path21.dirname(observationsPath);
98542
99037
  await mkdir27(runDir, { recursive: true });
98543
99038
  await Bun.write(observationsPath, observations.map((o) => JSON.stringify(o)).join(`
98544
99039
  `) + (observations.length > 0 ? `
@@ -98576,7 +99071,7 @@ var init_curator = __esm(() => {
98576
99071
  const windowHasObservations = window2.observations.length > 0;
98577
99072
  const provenance = windowHasObservations ? { runCount: window2.runIds.length, observationCount: window2.observations.length } : { runCount: 1, observationCount: observations.length };
98578
99073
  const markdown = renderProposals(proposals, context.runId, observations.length, provenance);
98579
- const proposalsMdPath = path22.join(runDir, "curator-proposals.md");
99074
+ const proposalsMdPath = path21.join(runDir, "curator-proposals.md");
98580
99075
  await Bun.write(proposalsMdPath, markdown);
98581
99076
  }
98582
99077
  return {
@@ -98959,14 +99454,14 @@ var init_validator = __esm(() => {
98959
99454
 
98960
99455
  // src/plugins/loader.ts
98961
99456
  import * as fs from "fs/promises";
98962
- import * as path23 from "path";
99457
+ import * as path22 from "path";
98963
99458
  function getSafeLogger7() {
98964
99459
  return getSafeLogger();
98965
99460
  }
98966
99461
  function extractPluginName(pluginPath) {
98967
- const basename13 = path23.basename(pluginPath);
99462
+ const basename13 = path22.basename(pluginPath);
98968
99463
  if (basename13 === "index.ts" || basename13 === "index.js" || basename13 === "index.mjs") {
98969
- return path23.basename(path23.dirname(pluginPath));
99464
+ return path22.basename(path22.dirname(pluginPath));
98970
99465
  }
98971
99466
  return basename13.replace(/\.(ts|js|mjs)$/, "");
98972
99467
  }
@@ -99113,7 +99608,7 @@ async function discoverPlugins(dir, isTestFileFn) {
99113
99608
  try {
99114
99609
  const entries = await fs.readdir(dir, { withFileTypes: true });
99115
99610
  for (const entry of entries) {
99116
- const fullPath = path23.join(dir, entry.name);
99611
+ const fullPath = path22.join(dir, entry.name);
99117
99612
  if (entry.isFile()) {
99118
99613
  if (isPluginFile(entry.name, isTestFileFn)) {
99119
99614
  discovered.push({ path: fullPath });
@@ -99121,7 +99616,7 @@ async function discoverPlugins(dir, isTestFileFn) {
99121
99616
  } else if (entry.isDirectory()) {
99122
99617
  const indexPaths = ["index.ts", "index.js", "index.mjs"];
99123
99618
  for (const indexFile of indexPaths) {
99124
- const indexPath = path23.join(fullPath, indexFile);
99619
+ const indexPath = path22.join(fullPath, indexFile);
99125
99620
  try {
99126
99621
  await fs.access(indexPath);
99127
99622
  discovered.push({ path: indexPath });
@@ -99146,13 +99641,13 @@ function isPluginFile(filename, isTestFileFn) {
99146
99641
  return !FALLBACK_TEST_FILE_RE.test(filename);
99147
99642
  }
99148
99643
  function resolveModulePath(modulePath, projectRoot) {
99149
- if (path23.isAbsolute(modulePath) || !modulePath.startsWith("./") && !modulePath.startsWith("../")) {
99644
+ if (path22.isAbsolute(modulePath) || !modulePath.startsWith("./") && !modulePath.startsWith("../")) {
99150
99645
  return modulePath;
99151
99646
  }
99152
99647
  if (projectRoot) {
99153
- return path23.resolve(projectRoot, modulePath);
99648
+ return path22.resolve(projectRoot, modulePath);
99154
99649
  }
99155
- return path23.resolve(modulePath);
99650
+ return path22.resolve(modulePath);
99156
99651
  }
99157
99652
  async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots, originalPath) {
99158
99653
  let attemptedPath = initialModulePath;
@@ -99298,8 +99793,26 @@ function warnInertBashStages(config2, logger) {
99298
99793
  logger?.warn("permissions", `bashApproval "${resolved}" on stage "${stage}" grants no Bash (no Bash(...) allow rule) -- the agent is not offered Bash, so nothing can escalate. Add one rule: "allow": ["Bash(ls *, cat *, git status*)"]`, { storyId: "_setup", stage, bashApproval: resolved });
99299
99794
  }
99300
99795
  }
99796
+ function warnUnreferencedAgentModels(prd, config2, logger) {
99797
+ const storyAgents = prd.userStories.flatMap((story) => story.routing?.agent ? [story.routing.agent] : []);
99798
+ const agents = findUnreferencedAgentModels(config2, storyAgents);
99799
+ if (agents.length === 0)
99800
+ return;
99801
+ logger?.warn("config", describeUnreferencedAgentModels(agents, config2), { storyId: "_setup", agents });
99802
+ }
99803
+ async function assertDefaultNativeCredentials(config2) {
99804
+ const { describeMissingNativeCredentials, findMissingNativeCredentials } = await Promise.resolve().then(() => (init_precheck2(), exports_precheck));
99805
+ const missing = await findMissingNativeCredentials(config2);
99806
+ if (missing.length === 0)
99807
+ return;
99808
+ throw new NaxError(describeMissingNativeCredentials(missing), "NATIVE_CREDENTIALS_MISSING", {
99809
+ stage: "setup",
99810
+ providers: missing.map(({ provider: provider2 }) => provider2)
99811
+ });
99812
+ }
99301
99813
  var init_run_setup_warnings = __esm(() => {
99302
99814
  init_config();
99815
+ init_errors();
99303
99816
  });
99304
99817
 
99305
99818
  // src/execution/lifecycle/gitignore-reconcile.ts
@@ -99356,8 +99869,8 @@ var init_language_commands = __esm(() => {
99356
99869
 
99357
99870
  // src/review/scoped-lint.ts
99358
99871
  import { join as join100, relative as relative26 } from "path";
99359
- function normalizePath3(path24) {
99360
- return path24.replaceAll("\\", "/").replace(/^\.\//, "");
99872
+ function normalizePath3(path23) {
99873
+ return path23.replaceAll("\\", "/").replace(/^\.\//, "");
99361
99874
  }
99362
99875
  function isSupportedDerivedScopedCommand(command) {
99363
99876
  const commands = normalizeCommandSpec(command);
@@ -99592,7 +100105,7 @@ var init_scoped_lint = __esm(() => {
99592
100105
  listChangedFiles,
99593
100106
  findPackageDir,
99594
100107
  runLintCommand,
99595
- fileExists: (path24) => Bun.file(path24).exists()
100108
+ fileExists: (path23) => Bun.file(path23).exists()
99596
100109
  };
99597
100110
  });
99598
100111
 
@@ -100053,7 +100566,7 @@ var init_paused_story_prompts = __esm(() => {
100053
100566
  });
100054
100567
 
100055
100568
  // src/execution/lifecycle/run-setup-init.ts
100056
- import path24 from "path";
100569
+ import path23 from "path";
100057
100570
  async function initializeAfterLock(options) {
100058
100571
  const logger = getSafeLogger();
100059
100572
  const { config: config2, workdir, feature, dryRun, runtime, interactionChain, runId, agentGetFn, statusWriter, deps } = options;
@@ -100085,8 +100598,8 @@ async function initializeAfterLock(options) {
100085
100598
  explicit: Object.fromEntries(explicitFields.map((f) => [f, existingProjectConfig[f]])),
100086
100599
  detected: Object.fromEntries(autodetectedFields.map((f) => [f, detectedProfile[f]]))
100087
100600
  });
100088
- const globalPluginsDir = path24.join(globalConfigDir(), "plugins");
100089
- const projectPluginsDir = path24.join(workdir, ".nax", "plugins");
100601
+ const globalPluginsDir = path23.join(globalConfigDir(), "plugins");
100602
+ const projectPluginsDir = path23.join(workdir, ".nax", "plugins");
100090
100603
  const configPlugins = config2.plugins || [];
100091
100604
  const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
100092
100605
  const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
@@ -100116,6 +100629,7 @@ async function initializeAfterLock(options) {
100116
100629
  const prd = initResult.prd;
100117
100630
  statusWriter.setPrd(prd);
100118
100631
  warnProfileMismatch(prd, config2, logger);
100632
+ warnUnreferencedAgentModels(prd, config2, logger);
100119
100633
  let counts = initResult.storyCounts;
100120
100634
  if (counts.paused > 0 && interactionChain !== null) {
100121
100635
  const { promptForPausedStories: promptForPausedStories2 } = await Promise.resolve().then(() => (init_paused_story_prompts(), exports_paused_story_prompts));
@@ -101044,7 +101558,7 @@ __export(exports_migrate, {
101044
101558
  });
101045
101559
  import { existsSync as existsSync30 } from "fs";
101046
101560
  import { mkdir as mkdir28, readdir as readdir10, rename as rename9 } from "fs/promises";
101047
- import path25 from "path";
101561
+ import path24 from "path";
101048
101562
  async function detectGeneratedContent(naxDir) {
101049
101563
  if (!existsSync30(naxDir))
101050
101564
  return [];
@@ -101057,17 +101571,17 @@ async function detectGeneratedContent(naxDir) {
101057
101571
  }
101058
101572
  for (const entry of entries) {
101059
101573
  if (GENERATED_NAMES.has(entry)) {
101060
- candidates.push({ name: entry, srcPath: path25.join(naxDir, entry) });
101574
+ candidates.push({ name: entry, srcPath: path24.join(naxDir, entry) });
101061
101575
  }
101062
101576
  }
101063
- const featuresDir2 = path25.join(naxDir, "features");
101577
+ const featuresDir2 = path24.join(naxDir, "features");
101064
101578
  if (existsSync30(featuresDir2)) {
101065
101579
  let featureDirs = [];
101066
101580
  try {
101067
101581
  featureDirs = await readdir10(featuresDir2);
101068
101582
  } catch {}
101069
101583
  for (const fid of featureDirs) {
101070
- const featureDir2 = path25.join(featuresDir2, fid);
101584
+ const featureDir2 = path24.join(featuresDir2, fid);
101071
101585
  let subEntries = [];
101072
101586
  try {
101073
101587
  subEntries = await readdir10(featureDir2);
@@ -101077,12 +101591,12 @@ async function detectGeneratedContent(naxDir) {
101077
101591
  for (const sub of subEntries) {
101078
101592
  if (GENERATED_FEATURE_SUBNAMES.has(sub)) {
101079
101593
  candidates.push({
101080
- name: path25.join("features", fid, sub),
101081
- srcPath: path25.join(featureDir2, sub)
101594
+ name: path24.join("features", fid, sub),
101595
+ srcPath: path24.join(featureDir2, sub)
101082
101596
  });
101083
101597
  }
101084
101598
  if (sub === "stories") {
101085
- const storiesDir = path25.join(featureDir2, "stories");
101599
+ const storiesDir = path24.join(featureDir2, "stories");
101086
101600
  let storyDirs = [];
101087
101601
  try {
101088
101602
  storyDirs = await readdir10(storiesDir);
@@ -101090,7 +101604,7 @@ async function detectGeneratedContent(naxDir) {
101090
101604
  continue;
101091
101605
  }
101092
101606
  for (const sid of storyDirs) {
101093
- const storyDir = path25.join(storiesDir, sid);
101607
+ const storyDir = path24.join(storiesDir, sid);
101094
101608
  let storyEntries = [];
101095
101609
  try {
101096
101610
  storyEntries = await readdir10(storyDir);
@@ -101100,8 +101614,8 @@ async function detectGeneratedContent(naxDir) {
101100
101614
  for (const se of storyEntries) {
101101
101615
  if (se.startsWith("context-manifest-") && se.endsWith(".json")) {
101102
101616
  candidates.push({
101103
- name: path25.join("features", fid, "stories", sid, se),
101104
- srcPath: path25.join(storyDir, se)
101617
+ name: path24.join("features", fid, "stories", sid, se),
101618
+ srcPath: path24.join(storyDir, se)
101105
101619
  });
101106
101620
  }
101107
101621
  }
@@ -101122,15 +101636,15 @@ async function migrateCommand(options) {
101122
101636
  name: options.reclaim
101123
101637
  });
101124
101638
  }
101125
- const src = path25.join(globalConfigDir(), options.reclaim);
101639
+ const src = path24.join(globalConfigDir(), options.reclaim);
101126
101640
  if (!existsSync30(src)) {
101127
101641
  throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
101128
101642
  stage: "migrate",
101129
101643
  name: options.reclaim
101130
101644
  });
101131
101645
  }
101132
- const archiveBase = path25.join(globalConfigDir(), "_archive");
101133
- 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()}`);
101134
101648
  await mkdir28(archiveBase, { recursive: true });
101135
101649
  await rename9(src, archiveDest);
101136
101650
  logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
@@ -101170,8 +101684,8 @@ async function migrateCommand(options) {
101170
101684
  logger.info("migrate", `Merged: identity for "${options.merge}" updated`, { storyId: "_migrate" });
101171
101685
  return;
101172
101686
  }
101173
- const naxDir = path25.join(options.workdir, ".nax");
101174
- const configPath = path25.join(naxDir, "config.json");
101687
+ const naxDir = path24.join(options.workdir, ".nax");
101688
+ const configPath = path24.join(naxDir, "config.json");
101175
101689
  if (!existsSync30(configPath)) {
101176
101690
  throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
101177
101691
  stage: "migrate",
@@ -101187,7 +101701,7 @@ async function migrateCommand(options) {
101187
101701
  cause: e
101188
101702
  });
101189
101703
  }
101190
- const projectKey = config2.name?.trim() || path25.basename(options.workdir);
101704
+ const projectKey = config2.name?.trim() || path24.basename(options.workdir);
101191
101705
  const destBase = projectOutputDir(projectKey, config2.outputDir);
101192
101706
  const candidates = await detectGeneratedContent(naxDir);
101193
101707
  if (candidates.length === 0) {
@@ -101196,7 +101710,7 @@ async function migrateCommand(options) {
101196
101710
  }
101197
101711
  if (options.dryRun) {
101198
101712
  for (const c of candidates) {
101199
- 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)}`, {
101200
101714
  storyId: "_migrate"
101201
101715
  });
101202
101716
  }
@@ -101205,8 +101719,8 @@ async function migrateCommand(options) {
101205
101719
  await mkdir28(destBase, { recursive: true });
101206
101720
  let moved = 0;
101207
101721
  for (const candidate of candidates) {
101208
- const dest = path25.join(destBase, candidate.name);
101209
- await mkdir28(path25.dirname(dest), { recursive: true });
101722
+ const dest = path24.join(destBase, candidate.name);
101723
+ await mkdir28(path24.dirname(dest), { recursive: true });
101210
101724
  if (existsSync30(dest)) {
101211
101725
  throw new NaxError(`Migration conflict: destination already exists.
101212
101726
  Source: ${candidate.srcPath}
@@ -101236,7 +101750,7 @@ async function migrateCommand(options) {
101236
101750
  moved++;
101237
101751
  logger.info("migrate", `Moved: ${candidate.name}`, { storyId: "_migrate" });
101238
101752
  }
101239
- 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));
101240
101754
  logger.info("migrate", `Migration complete: ${moved} entries moved`, {
101241
101755
  storyId: "_migrate",
101242
101756
  destBase
@@ -101607,10 +102121,10 @@ function renderReport(timeline, options = {}) {
101607
102121
  // src/commands/replay.ts
101608
102122
  import { existsSync as existsSync32 } from "fs";
101609
102123
  import { dirname as dirname20 } from "path";
101610
- async function readJsonlLenient(path26) {
101611
- if (!existsSync32(path26))
102124
+ async function readJsonlLenient(path25) {
102125
+ if (!existsSync32(path25))
101612
102126
  return [];
101613
- const content = await Bun.file(path26).text();
102127
+ const content = await Bun.file(path25).text();
101614
102128
  const lines = content.split(`
101615
102129
  `);
101616
102130
  const entries = [];
@@ -101624,11 +102138,11 @@ async function readJsonlLenient(path26) {
101624
102138
  }
101625
102139
  return entries;
101626
102140
  }
101627
- async function readJsonOrUndefined(path26) {
101628
- if (!existsSync32(path26))
102141
+ async function readJsonOrUndefined(path25) {
102142
+ if (!existsSync32(path25))
101629
102143
  return;
101630
102144
  try {
101631
- return await Bun.file(path26).json();
102145
+ return await Bun.file(path25).json();
101632
102146
  } catch {
101633
102147
  return;
101634
102148
  }
@@ -102341,7 +102855,7 @@ __export(exports_precheck_runner, {
102341
102855
  runPrecheckValidation: () => runPrecheckValidation
102342
102856
  });
102343
102857
  import { mkdirSync as mkdirSync5 } from "fs";
102344
- import path26 from "path";
102858
+ import path25 from "path";
102345
102859
  async function runPrecheckValidation(ctx) {
102346
102860
  const logger = getSafeLogger();
102347
102861
  if (process.env.NAX_PRECHECK !== "1") {
@@ -102350,7 +102864,7 @@ async function runPrecheckValidation(ctx) {
102350
102864
  }
102351
102865
  logger?.info("precheck", "Running precheck validations...");
102352
102866
  const { runPrecheck: runPrecheck3 } = await Promise.resolve().then(() => (init_precheck2(), exports_precheck));
102353
- const projectKey = ctx.config.name?.trim() || path26.basename(ctx.workdir);
102867
+ const projectKey = ctx.config.name?.trim() || path25.basename(ctx.workdir);
102354
102868
  const outputDir = projectOutputDir(projectKey, ctx.config.outputDir);
102355
102869
  const featureLock = ctx.featureName !== undefined ? { outputDir, feature: ctx.featureName } : undefined;
102356
102870
  const precheckResult = await runPrecheck3(ctx.config, ctx.prd, {
@@ -102360,7 +102874,7 @@ async function runPrecheckValidation(ctx) {
102360
102874
  ...featureLock !== undefined ? { featureLock } : {}
102361
102875
  });
102362
102876
  if (ctx.logFilePath) {
102363
- mkdirSync5(path26.dirname(ctx.logFilePath), { recursive: true });
102877
+ mkdirSync5(path25.dirname(ctx.logFilePath), { recursive: true });
102364
102878
  const precheckLog = {
102365
102879
  type: "precheck",
102366
102880
  timestamp: new Date().toISOString(),
@@ -102458,7 +102972,7 @@ __export(exports_run_setup, {
102458
102972
  warnInertBashStages: () => warnInertBashStages,
102459
102973
  warnProfileMismatch: () => warnProfileMismatch
102460
102974
  });
102461
- import path27 from "path";
102975
+ import path26 from "path";
102462
102976
  async function setupRun(options) {
102463
102977
  const logger = getSafeLogger();
102464
102978
  warnFallbackMisconfiguration(options.config, options.agentGetFn, logger);
@@ -102466,6 +102980,9 @@ async function setupRun(options) {
102466
102980
  if (options.agentManager) {
102467
102981
  await options.agentManager.validateCredentials();
102468
102982
  }
102983
+ if (!options.dryRun) {
102984
+ await assertDefaultNativeCredentials(options.config);
102985
+ }
102469
102986
  const {
102470
102987
  prdPath,
102471
102988
  workdir,
@@ -102520,6 +103037,7 @@ async function setupRun(options) {
102520
103037
  }
102521
103038
  await runtime.pidRegistry.cleanupStale();
102522
103039
  let cleanupCrashHandlers;
103040
+ let sealApprovals;
102523
103041
  try {
102524
103042
  cleanupCrashHandlers = _runSetupDeps.installCrashHandlers({
102525
103043
  statusWriter,
@@ -102537,6 +103055,9 @@ async function setupRun(options) {
102537
103055
  emitError: (reason) => {
102538
103056
  pipelineEventBus.emit({ type: "run:errored", reason, feature: options.feature });
102539
103057
  },
103058
+ sealApprovals: async () => {
103059
+ await sealApprovals?.();
103060
+ },
102540
103061
  onShutdown: async (abortSignal) => {
102541
103062
  await closeAllRunSessions(sessionManager, options.agentGetFn, { force: true, signal: abortSignal });
102542
103063
  await runtime.close().catch((err) => {
@@ -102549,7 +103070,7 @@ async function setupRun(options) {
102549
103070
  statusWriter.setPrd(prd);
102550
103071
  {
102551
103072
  const { detectGeneratedContent: detectGeneratedContent2, migrateCommand: migrateCommand2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
102552
- const naxDir = path27.join(workdir, ".nax");
103073
+ const naxDir = path26.join(workdir, ".nax");
102553
103074
  const candidates = await detectGeneratedContent2(naxDir).catch(() => []);
102554
103075
  if (candidates.length > 0) {
102555
103076
  logger?.info("setup", "Found generated content under .nax/ \u2014 migrating to output dir", {
@@ -102576,7 +103097,7 @@ async function setupRun(options) {
102576
103097
  remoteUrl = new TextDecoder().decode(gitResult.stdout).trim() || null;
102577
103098
  }
102578
103099
  } catch {}
102579
- const projectKey = config2.name?.trim() || path27.basename(workdir);
103100
+ const projectKey = config2.name?.trim() || path26.basename(workdir);
102580
103101
  await claimProjectIdentity2(projectKey, workdir, remoteUrl).catch((err) => {
102581
103102
  if (err instanceof NaxError && err.code === "RUN_NAME_COLLISION") {
102582
103103
  throw err;
@@ -102660,6 +103181,19 @@ async function setupRun(options) {
102660
103181
  sweepFeatureTranscripts: _runSetupDeps.sweepFeatureTranscripts
102661
103182
  }
102662
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
+ }
102663
103197
  return {
102664
103198
  statusWriter,
102665
103199
  sessionManager,
@@ -102669,7 +103203,8 @@ async function setupRun(options) {
102669
103203
  storyCounts: initResult.storyCounts,
102670
103204
  interactionChain: initResult.interactionChain,
102671
103205
  shutdownController,
102672
- runtime
103206
+ runtime,
103207
+ sealApprovals
102673
103208
  };
102674
103209
  } catch (error48) {
102675
103210
  cleanupCrashHandlers?.();
@@ -102698,6 +103233,7 @@ var init_run_setup = __esm(() => {
102698
103233
  init_test_runners();
102699
103234
  init_tools();
102700
103235
  init_git_env();
103236
+ init_path_frame();
102701
103237
  init_crash_recovery();
102702
103238
  init_feature_lock();
102703
103239
  init_helpers();
@@ -102710,6 +103246,7 @@ var init_run_setup = __esm(() => {
102710
103246
  createRuntime,
102711
103247
  installCrashHandlers,
102712
103248
  sweepFeatureTranscripts,
103249
+ buildApprovalsSeal,
102713
103250
  acquireLock,
102714
103251
  acquireFeatureLock
102715
103252
  };
@@ -103966,7 +104503,7 @@ var init_queue_file_lock = __esm(() => {
103966
104503
 
103967
104504
  // src/execution/queue-handler.ts
103968
104505
  import { rename as rename10, unlink as unlink11 } from "fs/promises";
103969
- import path28 from "path";
104506
+ import path27 from "path";
103970
104507
  function getSafeLogger8() {
103971
104508
  try {
103972
104509
  return getLogger();
@@ -103998,8 +104535,8 @@ async function claimCommandsLocked(queuePath, processingPath, logger) {
103998
104535
  return result.commands;
103999
104536
  }
104000
104537
  async function readQueueFile(workdir) {
104001
- const queuePath = path28.join(workdir, ".queue.txt");
104002
- 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");
104003
104540
  const logger = getSafeLogger8();
104004
104541
  try {
104005
104542
  return await withQueueFileLock(queuePath, async () => {
@@ -104014,8 +104551,8 @@ async function readQueueFile(workdir) {
104014
104551
  }
104015
104552
  }
104016
104553
  async function processQueueFile(workdir, processor) {
104017
- const queuePath = path28.join(workdir, ".queue.txt");
104018
- 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");
104019
104556
  const logger = getSafeLogger8();
104020
104557
  try {
104021
104558
  return await withQueueFileLock(queuePath, async () => {
@@ -104040,7 +104577,7 @@ async function processQueueFile(workdir, processor) {
104040
104577
  }
104041
104578
  }
104042
104579
  async function drainQueueAtBatchBoundary(workdir, prd) {
104043
- if (!await Bun.file(path28.join(workdir, ".queue.txt")).exists()) {
104580
+ if (!await Bun.file(path27.join(workdir, ".queue.txt")).exists()) {
104044
104581
  return { paused: false };
104045
104582
  }
104046
104583
  const logger = getSafeLogger8();
@@ -104096,10 +104633,10 @@ async function applyBatchBoundaryCommands(workdir, prd, commands, logger) {
104096
104633
  }
104097
104634
  if (cmd.type === "INJECT") {
104098
104635
  try {
104099
- if (path28.isAbsolute(cmd.storyFile)) {
104636
+ if (path27.isAbsolute(cmd.storyFile)) {
104100
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 });
104101
104638
  }
104102
- const storyFilePath = validateFilePath(path28.join(workdir, cmd.storyFile), workdir);
104639
+ const storyFilePath = validateFilePath(path27.join(workdir, cmd.storyFile), workdir);
104103
104640
  const raw = await Bun.file(storyFilePath).json();
104104
104641
  const existingIds = new Set(prd.userStories.map((s) => s.id));
104105
104642
  const story = validateInjectedStory(raw, existingIds);
@@ -104119,8 +104656,8 @@ async function applyBatchBoundaryCommands(workdir, prd, commands, logger) {
104119
104656
  return { paused: false };
104120
104657
  }
104121
104658
  async function clearQueueFile(workdir) {
104122
- const queuePath = path28.join(workdir, ".queue.txt");
104123
- 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");
104124
104661
  const logger = getSafeLogger8();
104125
104662
  try {
104126
104663
  await withQueueFileLock(queuePath, async () => {
@@ -105539,12 +106076,12 @@ var init_body = __esm(() => {
105539
106076
  // src/finish/pr/context.ts
105540
106077
  import { readFile as readFile8 } from "fs/promises";
105541
106078
  import { join as join113 } from "path";
105542
- async function readJson(path29) {
106079
+ async function readJson(path28) {
105543
106080
  let text;
105544
106081
  try {
105545
- text = await _finishPrDeps.readText(path29);
106082
+ text = await _finishPrDeps.readText(path28);
105546
106083
  } catch (error48) {
105547
- _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 });
105548
106085
  return;
105549
106086
  }
105550
106087
  if (text === null)
@@ -105552,7 +106089,7 @@ async function readJson(path29) {
105552
106089
  try {
105553
106090
  return JSON.parse(text);
105554
106091
  } catch (error48) {
105555
- _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 });
105556
106093
  return;
105557
106094
  }
105558
106095
  }
@@ -105636,9 +106173,9 @@ var init_context5 = __esm(() => {
105636
106173
  init_pr_title();
105637
106174
  _finishPrDeps = {
105638
106175
  run: defaultForgeDeps.run,
105639
- readText: async (path29) => {
106176
+ readText: async (path28) => {
105640
106177
  try {
105641
- return await readFile8(path29, "utf8");
106178
+ return await readFile8(path28, "utf8");
105642
106179
  } catch (err) {
105643
106180
  if (err.code === "ENOENT")
105644
106181
  return null;
@@ -106527,7 +107064,7 @@ var init_finish = __esm(() => {
106527
107064
  });
106528
107065
 
106529
107066
  // src/execution/runner-completion.ts
106530
- import path29 from "path";
107067
+ import path28 from "path";
106531
107068
  async function runCompletionPhase(options) {
106532
107069
  const logger = getSafeLogger();
106533
107070
  logger?.debug("execution", "Completion phase started", {
@@ -106548,7 +107085,7 @@ async function runCompletionPhase(options) {
106548
107085
  const acceptanceStartTime = Date.now();
106549
107086
  pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
106550
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) => {
106551
- const relativeWorkdir = path29.relative(options.workdir, g.packageDir);
107088
+ const relativeWorkdir = path28.relative(options.workdir, g.packageDir);
106552
107089
  let groupConfig = options.config;
106553
107090
  if (relativeWorkdir && relativeWorkdir !== ".") {
106554
107091
  try {
@@ -106679,7 +107216,7 @@ async function runCompletionPhase(options) {
106679
107216
  exitReason: options.exitReason,
106680
107217
  runtime: options.runtime,
106681
107218
  abortSignal: options.abortSignal,
106682
- isSequential: options.parallel === undefined,
107219
+ isSequential: options.parallel === undefined || !(options.parallel > 1),
106683
107220
  interactionChain: options.interactionChain
106684
107221
  });
106685
107222
  const { durationMs, runCompletedAt, finalCounts, reportedTotal, pluginGateFailed } = completionResult;
@@ -107285,7 +107822,7 @@ __export(exports_parallel_batch, {
107285
107822
  _parallelBatchDeps: () => _parallelBatchDeps,
107286
107823
  runParallelBatch: () => runParallelBatch
107287
107824
  });
107288
- import path30 from "path";
107825
+ import path29 from "path";
107289
107826
  async function runParallelBatch(options) {
107290
107827
  const { stories, ctx, prd } = options;
107291
107828
  const { workdir, config: config2, maxConcurrency, pipelineContext, eventEmitter, agentGetFn, hooks, pluginRegistry } = ctx;
@@ -107320,7 +107857,7 @@ async function runParallelBatch(options) {
107320
107857
  }
107321
107858
  worktreePaths.set(story.id, storyWorktreePath(workdir, worktreeId));
107322
107859
  }
107323
- const rootConfigPath = path30.join(workdir, ".nax", "config.json");
107860
+ const rootConfigPath = path29.join(workdir, ".nax", "config.json");
107324
107861
  const profileOverride = profileOverrideFromConfig(config2);
107325
107862
  const storyEffectiveConfigs = new Map;
107326
107863
  const configResults = await Promise.allSettled(stories.filter((story) => storyPackageDir(story)).map(async (story) => {
@@ -108303,7 +108840,8 @@ async function run(options) {
108303
108840
  pluginRegistry,
108304
108841
  interactionChain,
108305
108842
  shutdownController,
108306
- runtime
108843
+ runtime,
108844
+ sealApprovals
108307
108845
  } = setupResult;
108308
108846
  prd = setupResult.prd;
108309
108847
  const agentManager = runtime.agentManager;
@@ -108425,7 +108963,8 @@ async function run(options) {
108425
108963
  projectKey: runtime.projectKey,
108426
108964
  curatorRollupPath: runtime.curatorRollupPath,
108427
108965
  logFilePath,
108428
- config: config2
108966
+ config: config2,
108967
+ sealApprovals
108429
108968
  });
108430
108969
  logger?.debug("execution", "Runner finally \u2014 cleanupRun done, run() returning");
108431
108970
  } finally {
@@ -108890,12 +109429,12 @@ async function checkDependenciesInstalled(workdir) {
108890
109429
  { path: "vendor" }
108891
109430
  ];
108892
109431
  const found = [];
108893
- for (const { path: path31 } of depPaths) {
108894
- const fullPath = `${workdir}/${path31}`;
109432
+ for (const { path: path30 } of depPaths) {
109433
+ const fullPath = `${workdir}/${path30}`;
108895
109434
  if (existsSync36(fullPath)) {
108896
109435
  const stats = statSync6(fullPath);
108897
109436
  if (stats.isDirectory()) {
108898
- found.push(path31);
109437
+ found.push(path30);
108899
109438
  }
108900
109439
  }
108901
109440
  }
@@ -109108,7 +109647,7 @@ function collectConfiguredModelPins(config2) {
109108
109647
  }
109109
109648
  }
109110
109649
  const pins = [];
109111
- const defaultAgent = cfg.agent?.default ?? "claude";
109650
+ const defaultAgent = cfg.agent?.default ?? DEFAULT_AGENT_NAME;
109112
109651
  if (cfg.review?.semantic !== undefined) {
109113
109652
  const pin = pinFromConfiguredModel("review.semantic.model", cfg.review.semantic.model, defaultAgent, tierEntries);
109114
109653
  if (pin !== undefined)
@@ -109197,7 +109736,7 @@ var init_checks_model_resolution_walk = __esm(() => {
109197
109736
 
109198
109737
  // src/precheck/checks-model-resolution.ts
109199
109738
  function isNativeAgent(agent) {
109200
- return agent === "native";
109739
+ return agent === NATIVE_AGENT_NAME;
109201
109740
  }
109202
109741
  async function checkModelResolution(config2) {
109203
109742
  const { pins, tierEntries, catalogOverrides } = collectConfiguredModelPins(config2);
@@ -109306,6 +109845,52 @@ var init_checks_model_resolution = __esm(() => {
109306
109845
  };
109307
109846
  });
109308
109847
 
109848
+ // src/precheck/checks-native-credentials.ts
109849
+ function providerOf(id) {
109850
+ const slash = id.indexOf("/");
109851
+ return slash > 0 ? id.slice(0, slash) : undefined;
109852
+ }
109853
+ function providerTiers(config2) {
109854
+ const overridden = new Set((config2.agent?.native?.catalogOverrides ?? []).map((override) => override.provider));
109855
+ const byProvider = new Map;
109856
+ for (const [tier, entry] of Object.entries(config2.models?.[NATIVE_AGENT_NAME] ?? {})) {
109857
+ if (entry === undefined)
109858
+ continue;
109859
+ const provider2 = providerOf(typeof entry === "string" ? entry : entry.model);
109860
+ if (provider2 === undefined || overridden.has(provider2))
109861
+ continue;
109862
+ byProvider.set(provider2, [...byProvider.get(provider2) ?? [], tier]);
109863
+ }
109864
+ return byProvider;
109865
+ }
109866
+ async function findMissingNativeCredentials(config2) {
109867
+ if (resolveDefaultAgent(config2) !== NATIVE_AGENT_NAME)
109868
+ return [];
109869
+ const byProvider = providerTiers(config2);
109870
+ if (byProvider.size === 0)
109871
+ return [];
109872
+ const missing = await _nativeCredentialDeps.providersWithoutCredentials([...byProvider.keys()]);
109873
+ return missing.map((provider2) => ({ provider: provider2, tiers: byProvider.get(provider2) ?? [] }));
109874
+ }
109875
+ function describeMissingNativeCredentials(missing) {
109876
+ const uses = missing.map(({ provider: provider2, tiers }) => `${provider2} (${tiers.map((tier) => `models.native.${tier}`).join(", ")})`).join("; ");
109877
+ const logins = missing.map(({ provider: provider2 }) => `nax auth login ${provider2}`).join(" / ");
109878
+ return `The default native agent needs a credential for ${uses}, but none is stored or in the environment. ` + `Run ${logins}, set the provider's API key environment variable, point models.native at a provider you have ` + `credentials for, or set agent.default "claude" to use an acpx agent.`;
109879
+ }
109880
+ async function checkNativeCredentials(config2) {
109881
+ const missing = await findMissingNativeCredentials(config2);
109882
+ if (missing.length === 0) {
109883
+ return { name: CHECK_NAME, tier: "blocker", passed: true, message: "native default agent credentials found" };
109884
+ }
109885
+ return { name: CHECK_NAME, tier: "blocker", passed: false, message: describeMissingNativeCredentials(missing) };
109886
+ }
109887
+ var CHECK_NAME = "native-credentials", _nativeCredentialDeps;
109888
+ var init_checks_native_credentials = __esm(() => {
109889
+ init_agents();
109890
+ init_native();
109891
+ _nativeCredentialDeps = { providersWithoutCredentials };
109892
+ });
109893
+
109309
109894
  // src/precheck/checks-warnings.ts
109310
109895
  import { existsSync as existsSync37 } from "fs";
109311
109896
  import { isAbsolute as isAbsolute22 } from "path";
@@ -109614,6 +110199,7 @@ var init_checks4 = __esm(() => {
109614
110199
  init_checks_agents();
109615
110200
  init_checks_blockers();
109616
110201
  init_checks_model_resolution();
110202
+ init_checks_native_credentials();
109617
110203
  init_checks_warnings();
109618
110204
  });
109619
110205
 
@@ -109742,6 +110328,7 @@ __export(exports_precheck, {
109742
110328
  EXIT_CODES: () => EXIT_CODES,
109743
110329
  _checkDiskSpaceDeps: () => _checkDiskSpaceDeps,
109744
110330
  _modelResolutionDeps: () => _modelResolutionDeps,
110331
+ _nativeCredentialDeps: () => _nativeCredentialDeps,
109745
110332
  _precheckDeps: () => _precheckDeps,
109746
110333
  checkAgentCLI: () => checkAgentCLI,
109747
110334
  checkBuildCommandInReviewChecks: () => checkBuildCommandInReviewChecks,
@@ -109758,6 +110345,7 @@ __export(exports_precheck, {
109758
110345
  checkLintCommand: () => checkLintCommand,
109759
110346
  checkModelResolution: () => checkModelResolution,
109760
110347
  checkMultiAgentHealth: () => checkMultiAgentHealth,
110348
+ checkNativeCredentials: () => checkNativeCredentials,
109761
110349
  checkOptionalCommands: () => checkOptionalCommands,
109762
110350
  checkPRDValid: () => checkPRDValid,
109763
110351
  checkPendingStories: () => checkPendingStories,
@@ -109766,6 +110354,8 @@ __export(exports_precheck, {
109766
110354
  checkTestCommand: () => checkTestCommand,
109767
110355
  checkTypecheckCommand: () => checkTypecheckCommand,
109768
110356
  checkWorkingTreeClean: () => checkWorkingTreeClean,
110357
+ describeMissingNativeCredentials: () => describeMissingNativeCredentials,
110358
+ findMissingNativeCredentials: () => findMissingNativeCredentials,
109769
110359
  parseDiskSpaceOutput: () => parseDiskSpaceOutput,
109770
110360
  runEnvironmentPrecheck: () => runEnvironmentPrecheck,
109771
110361
  runPrecheck: () => runPrecheck2
@@ -109781,6 +110371,7 @@ function getLateEnvironmentBlockers(config2, workdir) {
109781
110371
  return [
109782
110372
  () => checkAgentCLI(config2),
109783
110373
  () => checkModelResolution(config2),
110374
+ () => checkNativeCredentials(config2),
109784
110375
  () => checkDependenciesInstalled(workdir),
109785
110376
  () => checkTestCommand(config2),
109786
110377
  () => checkLintCommand(config2),
@@ -110170,7 +110761,7 @@ ${repairHint}` : codebaseContext;
110170
110761
  agentRouting: config2.routing?.agents,
110171
110762
  profileName: config2.profile,
110172
110763
  models: config2.models,
110173
- defaultAgent: config2.agent?.default ?? "claude",
110764
+ defaultAgent: config2.agent?.default ?? DEFAULT_AGENT_NAME,
110174
110765
  outputPath: prdPath,
110175
110766
  repoRoot: workdir,
110176
110767
  scope: new Set(subStoriesWithParent.map((s) => s.id)),
@@ -110272,8 +110863,8 @@ var init_plan_runtime = __esm(() => {
110272
110863
  init_git_env();
110273
110864
  init_plan_helpers();
110274
110865
  _planDeps = {
110275
- readFile: (path31) => Bun.file(path31).text(),
110276
- writeFile: (path31, content) => Bun.write(path31, content).then(() => {}),
110866
+ readFile: (path30) => Bun.file(path30).text(),
110867
+ writeFile: (path30, content) => Bun.write(path30, content).then(() => {}),
110277
110868
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
110278
110869
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
110279
110870
  claimProjectIdentity,
@@ -110282,10 +110873,10 @@ var init_plan_runtime = __esm(() => {
110282
110873
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd, ...opts.env ? { env: opts.env } : {} } : {});
110283
110874
  return { stdout: result.stdout, exitCode: result.exitCode };
110284
110875
  },
110285
- mkdirp: (path31) => Bun.spawn(["mkdir", "-p", path31]).exited.then(() => {}),
110286
- existsSync: (path31) => existsSync38(path31),
110876
+ mkdirp: (path30) => Bun.spawn(["mkdir", "-p", path30]).exited.then(() => {}),
110877
+ existsSync: (path30) => existsSync38(path30),
110287
110878
  discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages2(repoRoot),
110288
- readPackageJsonAt: (path31) => Bun.file(path31).json().catch(() => null),
110879
+ readPackageJsonAt: (path30) => Bun.file(path30).json().catch(() => null),
110289
110880
  createInteractionBridge: () => createCliInteractionBridge(),
110290
110881
  initInteractionChain: (cfg, headless) => initInteractionChain(cfg, headless),
110291
110882
  runPrecheck: async (config2, prd, opts) => {
@@ -110306,7 +110897,7 @@ function assertSpecLintClean(specContent, options) {
110306
110897
  return [];
110307
110898
  const findings = lintSpecContent(specContent, {
110308
110899
  maxAcCount: options.maxAcCount,
110309
- fileExists: (path31) => existsSync39(join118(options.workdir, path31))
110900
+ fileExists: (path30) => existsSync39(join118(options.workdir, path30))
110310
110901
  });
110311
110902
  const blocking = findings.filter((finding) => BLOCKING_SPEC_LINT_CODES.has(finding.code));
110312
110903
  if (blocking.length > 0) {
@@ -110366,7 +110957,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
110366
110957
  }));
110367
110958
  const codebaseContext = buildSourceRootsSection(normalizedRoots);
110368
110959
  const relativePackages = [
110369
- ...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))
110370
110961
  ];
110371
110962
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
110372
110963
  const packageJson = await deps.readPackageJsonAt(join119(workdir, relativePath, "package.json"));
@@ -110494,7 +111085,7 @@ async function persistPrd(ctx, prd) {
110494
111085
  agentRouting: ctx.config.routing?.agents,
110495
111086
  profileName: ctx.profileName,
110496
111087
  models: ctx.config.models,
110497
- defaultAgent: ctx.config.agent?.default ?? "claude",
111088
+ defaultAgent: ctx.config.agent?.default ?? DEFAULT_AGENT_NAME,
110498
111089
  outputPath: ctx.outputPath,
110499
111090
  repoRoot: ctx.workdir,
110500
111091
  writeFile: ctx.deps.writeFile
@@ -110502,13 +111093,14 @@ async function persistPrd(ctx, prd) {
110502
111093
  }
110503
111094
  var _persistPrdDeps;
110504
111095
  var init_persist_prd = __esm(() => {
111096
+ init_config();
110505
111097
  init_generator2();
110506
111098
  init_logger2();
110507
111099
  init_operations();
110508
111100
  init_prd();
110509
111101
  init_finalize_routing();
110510
111102
  _persistPrdDeps = {
110511
- existsSync: (path31) => defaultExistsSync(path31),
111103
+ existsSync: (path30) => defaultExistsSync(path30),
110512
111104
  discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages2(repoRoot)
110513
111105
  };
110514
111106
  });
@@ -110771,10 +111363,10 @@ var init_plan2 = __esm(() => {
110771
111363
  });
110772
111364
 
110773
111365
  // src/cli/plugins.ts
110774
- import * as path31 from "path";
111366
+ import * as path30 from "path";
110775
111367
  async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
110776
- const globalPluginsDir = overrideGlobalPluginsDir ?? path31.join(globalConfigDir(), "plugins");
110777
- const projectPluginsDir = path31.join(workdir, ".nax", "plugins");
111368
+ const globalPluginsDir = overrideGlobalPluginsDir ?? path30.join(globalConfigDir(), "plugins");
111369
+ const projectPluginsDir = path30.join(workdir, ".nax", "plugins");
110778
111370
  const configPlugins = config2.plugins || [];
110779
111371
  const registry4 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
110780
111372
  const plugins = registry4.plugins;
@@ -110824,10 +111416,10 @@ function formatSource(type, sourcePath) {
110824
111416
  return `built-in (${sourcePath})`;
110825
111417
  }
110826
111418
  if (type === "global") {
110827
- return `global (${path31.basename(sourcePath)})`;
111419
+ return `global (${path30.basename(sourcePath)})`;
110828
111420
  }
110829
111421
  if (type === "project") {
110830
- return `project (${path31.basename(sourcePath)})`;
111422
+ return `project (${path30.basename(sourcePath)})`;
110831
111423
  }
110832
111424
  return `config (${sourcePath})`;
110833
111425
  }
@@ -110869,7 +111461,7 @@ async function exportPromptCommand(options) {
110869
111461
  var VALID_EXPORT_ROLES;
110870
111462
  var init_prompts_export = __esm(() => {
110871
111463
  init_prompts();
110872
- VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"];
111464
+ VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "tdd-simple"];
110873
111465
  });
110874
111466
 
110875
111467
  // src/cli/prompts-init.ts
@@ -110911,7 +111503,6 @@ async function autoWirePromptsConfig(workdir) {
110911
111503
  "test-writer": ".nax/templates/test-writer.md",
110912
111504
  implementer: ".nax/templates/implementer.md",
110913
111505
  verifier: ".nax/templates/verifier.md",
110914
- "single-session": ".nax/templates/single-session.md",
110915
111506
  "tdd-simple": ".nax/templates/tdd-simple.md"
110916
111507
  }
110917
111508
  }
@@ -110932,7 +111523,6 @@ ${exampleConfig}`);
110932
111523
  "test-writer": ".nax/templates/test-writer.md",
110933
111524
  implementer: ".nax/templates/implementer.md",
110934
111525
  verifier: ".nax/templates/verifier.md",
110935
- "single-session": ".nax/templates/single-session.md",
110936
111526
  "tdd-simple": ".nax/templates/tdd-simple.md"
110937
111527
  };
110938
111528
  prompts.overrides = overrides;
@@ -110991,7 +111581,6 @@ var init_prompts_init = __esm(() => {
110991
111581
  { file: "test-writer.md", role: "test-writer" },
110992
111582
  { file: "implementer.md", role: "implementer", variant: "standard" },
110993
111583
  { file: "verifier.md", role: "verifier" },
110994
- { file: "single-session.md", role: "single-session" },
110995
111584
  { file: "tdd-simple.md", role: "tdd-simple" }
110996
111585
  ];
110997
111586
  });
@@ -111229,8 +111818,8 @@ async function resolveRunProfileOverride(opts) {
111229
111818
  return cliChain;
111230
111819
  if (opts.envProfile)
111231
111820
  return;
111232
- const readJson2 = opts._readJson ?? (async (path32) => {
111233
- const file2 = Bun.file(path32);
111821
+ const readJson2 = opts._readJson ?? (async (path31) => {
111822
+ const file2 = Bun.file(path31);
111234
111823
  if (!await file2.exists())
111235
111824
  return;
111236
111825
  return file2.json();
@@ -111610,11 +112199,11 @@ var init_rules_cli_deps = __esm(() => {
111610
112199
  init_logger2();
111611
112200
  init_rules_lint();
111612
112201
  _rulesCLIDeps = {
111613
- readFile: async (path32) => Bun.file(path32).text(),
111614
- writeFile: async (path32, content) => {
111615
- await Bun.write(path32, content);
112202
+ readFile: async (path31) => Bun.file(path31).text(),
112203
+ writeFile: async (path31, content) => {
112204
+ await Bun.write(path31, content);
111616
112205
  },
111617
- fileExists: async (path32) => Bun.file(path32).exists(),
112206
+ fileExists: async (path31) => Bun.file(path31).exists(),
111618
112207
  globInDir: (dir) => {
111619
112208
  try {
111620
112209
  return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join127(dir, f));
@@ -111622,8 +112211,8 @@ var init_rules_cli_deps = __esm(() => {
111622
112211
  return [];
111623
112212
  }
111624
112213
  },
111625
- mkdir: async (path32) => {
111626
- await mkdir33(path32, { recursive: true });
112214
+ mkdir: async (path31) => {
112215
+ await mkdir33(path31, { recursive: true });
111627
112216
  },
111628
112217
  globCanonicalRuleFiles: (workdir) => _rulesLintDeps.globCanonicalRuleFiles(workdir),
111629
112218
  globHasMatch: (pattern, cwd) => _rulesLintDeps.globHasMatch(pattern, cwd),
@@ -112140,10 +112729,10 @@ var init_setup_analyze = __esm(() => {
112140
112729
  init_detect();
112141
112730
  CANONICAL_SCRIPTS = ["build", "test", "lint", "type-check", "lint:fix"];
112142
112731
  _analyzeRepoDeps = {
112143
- fileExists: async (path32) => Bun.file(path32).exists(),
112144
- readJson: async (path32) => {
112732
+ fileExists: async (path31) => Bun.file(path31).exists(),
112733
+ readJson: async (path31) => {
112145
112734
  try {
112146
- const f = Bun.file(path32);
112735
+ const f = Bun.file(path31);
112147
112736
  if (!await f.exists())
112148
112737
  return null;
112149
112738
  return JSON.parse(await f.text());
@@ -112198,9 +112787,9 @@ async function fillScripts(workdir, analysis) {
112198
112787
  var TYPE_CHECK_KEY = "type-check", TYPE_CHECK_SCRIPT = "tsc --noEmit -p tsconfig.json", TYPE_CHECK_TURBO_PASSTHROUGH = "turbo run type-check", _fillScriptsDeps;
112199
112788
  var init_setup_fill = __esm(() => {
112200
112789
  _fillScriptsDeps = {
112201
- readJson: async (path32) => {
112790
+ readJson: async (path31) => {
112202
112791
  try {
112203
- const f = Bun.file(path32);
112792
+ const f = Bun.file(path31);
112204
112793
  if (!await f.exists())
112205
112794
  return null;
112206
112795
  return JSON.parse(await f.text());
@@ -112208,8 +112797,8 @@ var init_setup_fill = __esm(() => {
112208
112797
  return null;
112209
112798
  }
112210
112799
  },
112211
- writeFile: async (path32, content) => {
112212
- await Bun.write(path32, content);
112800
+ writeFile: async (path31, content) => {
112801
+ await Bun.write(path31, content);
112213
112802
  }
112214
112803
  };
112215
112804
  });
@@ -112267,9 +112856,9 @@ async function writeSetupConfig(workdir, config2, monoConfigs, _opts, deps = _wr
112267
112856
  var _writeSetupDeps;
112268
112857
  var init_setup_write = __esm(() => {
112269
112858
  _writeSetupDeps = {
112270
- writeFile: (path32, content) => Bun.write(path32, content).then(() => {}),
112271
- mkdir: async (path32) => {
112272
- 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]);
112273
112862
  await proc.exited;
112274
112863
  }
112275
112864
  };
@@ -112353,7 +112942,7 @@ var init_setup = __esm(() => {
112353
112942
  },
112354
112943
  generateSetupPlan: (ctx, analysis) => generateSetupPlan(ctx, analysis),
112355
112944
  runGate: (workdir, config2) => runSetupGate(workdir, config2),
112356
- fileExists: (path32) => Bun.file(path32).exists(),
112945
+ fileExists: (path31) => Bun.file(path31).exists(),
112357
112946
  writeSetupConfig: (workdir, config2, monoConfigs, opts) => writeSetupConfig(workdir, config2, monoConfigs, opts),
112358
112947
  stdout: (msg) => {
112359
112948
  process.stdout.write(`${msg}
@@ -112370,7 +112959,7 @@ var init_setup = __esm(() => {
112370
112959
  import { existsSync as existsSync43 } from "fs";
112371
112960
  import { join as join135 } from "path";
112372
112961
  async function resolveSpecPaths(options, deps) {
112373
- const explicit = (options.paths ?? []).filter((path32) => path32.trim().length > 0);
112962
+ const explicit = (options.paths ?? []).filter((path31) => path31.trim().length > 0);
112374
112963
  if (explicit.length > 0)
112375
112964
  return [...explicit];
112376
112965
  if (!options.feature)
@@ -112398,7 +112987,7 @@ async function lintOne(specPath, options, deps) {
112398
112987
  }
112399
112988
  const findings = lintSpecContent(content, {
112400
112989
  maxAcCount: options.maxAcCount,
112401
- fileExists: (path32) => deps.fileExists(join135(options.dir, path32))
112990
+ fileExists: (path31) => deps.fileExists(join135(options.dir, path31))
112402
112991
  });
112403
112992
  return {
112404
112993
  specPath,
@@ -112464,7 +113053,7 @@ var init_spec_lint_command = __esm(() => {
112464
113053
  init_prd();
112465
113054
  init_features_resolve();
112466
113055
  _specLintCommandDeps = {
112467
- readFile: async (path32) => Bun.file(path32).text(),
113056
+ readFile: async (path31) => Bun.file(path31).text(),
112468
113057
  fileExists: existsSync43,
112469
113058
  write: (line) => {
112470
113059
  console.log(line);
@@ -114384,11 +114973,11 @@ var require_react_reconciler_development = __commonJS(function(exports, module)
114384
114973
  fiber = fiber.next, id--;
114385
114974
  return fiber;
114386
114975
  }
114387
- function copyWithSetImpl(obj, path32, index, value) {
114388
- if (index >= path32.length)
114976
+ function copyWithSetImpl(obj, path31, index, value) {
114977
+ if (index >= path31.length)
114389
114978
  return value;
114390
- var key = path32[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
114391
- 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);
114392
114981
  return updated;
114393
114982
  }
114394
114983
  function copyWithRename(obj, oldPath, newPath) {
@@ -114408,11 +114997,11 @@ var require_react_reconciler_development = __commonJS(function(exports, module)
114408
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);
114409
114998
  return updated;
114410
114999
  }
114411
- function copyWithDeleteImpl(obj, path32, index) {
114412
- var key = path32[index], updated = isArrayImpl(obj) ? obj.slice() : assign2({}, obj);
114413
- 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)
114414
115003
  return isArrayImpl(updated) ? updated.splice(key, 1) : delete updated[key], updated;
114415
- updated[key] = copyWithDeleteImpl(obj[key], path32, index + 1);
115004
+ updated[key] = copyWithDeleteImpl(obj[key], path31, index + 1);
114416
115005
  return updated;
114417
115006
  }
114418
115007
  function shouldSuspendImpl() {
@@ -124436,29 +125025,29 @@ Check the top-level render call using <` + componentName2 + ">.");
124436
125025
  var didWarnAboutNestedUpdates = false;
124437
125026
  var didWarnAboutFindNodeInStrictMode = {};
124438
125027
  var overrideHookState = null, overrideHookStateDeletePath = null, overrideHookStateRenamePath = null, overrideProps = null, overridePropsDeletePath = null, overridePropsRenamePath = null, scheduleUpdate = null, scheduleRetry = null, setErrorHandler = null, setSuspenseHandler = null;
124439
- overrideHookState = function(fiber, id, path32, value) {
125028
+ overrideHookState = function(fiber, id, path31, value) {
124440
125029
  id = findHook(fiber, id);
124441
- 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));
124442
125031
  };
124443
- overrideHookStateDeletePath = function(fiber, id, path32) {
125032
+ overrideHookStateDeletePath = function(fiber, id, path31) {
124444
125033
  id = findHook(fiber, id);
124445
- 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));
124446
125035
  };
124447
125036
  overrideHookStateRenamePath = function(fiber, id, oldPath, newPath) {
124448
125037
  id = findHook(fiber, id);
124449
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));
124450
125039
  };
124451
- overrideProps = function(fiber, path32, value) {
124452
- fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path32, 0, value);
125040
+ overrideProps = function(fiber, path31, value) {
125041
+ fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path31, 0, value);
124453
125042
  fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
124454
- path32 = enqueueConcurrentRenderForLane(fiber, 2);
124455
- path32 !== null && scheduleUpdateOnFiber(path32, fiber, 2);
125043
+ path31 = enqueueConcurrentRenderForLane(fiber, 2);
125044
+ path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2);
124456
125045
  };
124457
- overridePropsDeletePath = function(fiber, path32) {
124458
- fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path32, 0);
125046
+ overridePropsDeletePath = function(fiber, path31) {
125047
+ fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path31, 0);
124459
125048
  fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
124460
- path32 = enqueueConcurrentRenderForLane(fiber, 2);
124461
- path32 !== null && scheduleUpdateOnFiber(path32, fiber, 2);
125049
+ path31 = enqueueConcurrentRenderForLane(fiber, 2);
125050
+ path31 !== null && scheduleUpdateOnFiber(path31, fiber, 2);
124462
125051
  };
124463
125052
  overridePropsRenamePath = function(fiber, oldPath, newPath) {
124464
125053
  fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
@@ -126097,11 +126686,38 @@ init_config_profile();
126097
126686
  init_features_resolve();
126098
126687
  init_generate();
126099
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
+
126100
126705
  // src/cli/run-mode.ts
126101
126706
  function resolveUseHeadless(input) {
126102
126707
  return !input.isTTY || input.headlessFlag || input.headlessEnv || input.formatterMode === "json";
126103
126708
  }
126104
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
+
126105
126721
  // bin/nax.ts
126106
126722
  init_status_dispatch();
126107
126723
 
@@ -126120,14 +126736,14 @@ function resolveEffective(detected, configPatterns) {
126120
126736
  return "detected";
126121
126737
  return "none";
126122
126738
  }
126123
- async function loadRawConfig(path32) {
126124
- const f = Bun.file(path32);
126739
+ async function loadRawConfig(path31) {
126740
+ const f = Bun.file(path31);
126125
126741
  if (!await f.exists())
126126
126742
  return {};
126127
126743
  return JSON.parse(await f.text());
126128
126744
  }
126129
- async function writeRawConfig(path32, data) {
126130
- 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)}
126131
126747
  `);
126132
126748
  }
126133
126749
  function deepSet(obj, keyPath, value) {
@@ -131211,8 +131827,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
131211
131827
  }
131212
131828
 
131213
131829
  // node_modules/ink/build/components/ErrorOverview.js
131214
- var cleanupPath = (path32) => {
131215
- return path32?.replace(`file://${cwd()}/`, "");
131830
+ var cleanupPath = (path31) => {
131831
+ return path31?.replace(`file://${cwd()}/`, "");
131216
131832
  };
131217
131833
  var stackUtils = new import_stack_utils.default({
131218
131834
  cwd: cwd(),
@@ -134253,7 +134869,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
134253
134869
  });
134254
134870
  process.exit(exitCode);
134255
134871
  });
134256
- 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) => {
134257
134873
  try {
134258
134874
  validateFeatureName(options.feature);
134259
134875
  } catch (err) {
@@ -134267,6 +134883,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
134267
134883
  console.error(source_default.red(`Invalid directory: ${err.message}`));
134268
134884
  process.exit(1);
134269
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;
134270
134897
  try {
134271
134898
  const { assertCompareAgentExclusive: assertCompareAgentExclusive2 } = await Promise.resolve().then(() => (init_preflight(), exports_preflight));
134272
134899
  assertCompareAgentExclusive2({ compare: options.compare, agent: options.agent });
@@ -134456,12 +135083,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
134456
135083
  config2.agent ??= {};
134457
135084
  config2.agent.default = options.agent;
134458
135085
  }
134459
- const maxIterations = Number.parseInt(options.maxIterations, 10);
134460
- if (!Number.isFinite(maxIterations) || maxIterations < 1) {
134461
- console.error(source_default.red("--max-iterations must be a positive integer"));
134462
- process.exit(1);
134463
- }
134464
- config2.execution.maxIterations = maxIterations;
135086
+ config2 = applyMaxIterationsFlag(config2, maxIterationsFlag.value);
134465
135087
  if (options.maxCost !== undefined) {
134466
135088
  const maxCost = Number(options.maxCost);
134467
135089
  if (!Number.isFinite(maxCost) || maxCost <= 0) {
@@ -134501,15 +135123,6 @@ program2.command("run").description("Run the orchestration loop for a feature").
134501
135123
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
134502
135124
  }
134503
135125
  const statusFilePath = join142(outputDir, "status.json");
134504
- let parallel;
134505
- if (options.parallel !== undefined) {
134506
- parallel = Number.parseInt(options.parallel, 10);
134507
- if (Number.isNaN(parallel) || parallel < 0) {
134508
- tuiInstance?.unmount();
134509
- console.error(source_default.red("--parallel must be a non-negative integer"));
134510
- process.exit(1);
134511
- }
134512
- }
134513
135126
  if (scheduleGate.target) {
134514
135127
  const scheduleController = new AbortController;
134515
135128
  const onSigint = () => scheduleController.abort();
@@ -134941,8 +135554,8 @@ configProfileCmd.command("current").description("Show the currently active profi
134941
135554
  });
134942
135555
  configProfileCmd.command("create <name>").description("Create a new empty profile").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (name, options) => {
134943
135556
  try {
134944
- const path32 = await profileCreateCommand(name, options.dir);
134945
- console.log(`Created profile at: ${path32}`);
135557
+ const path31 = await profileCreateCommand(name, options.dir);
135558
+ console.log(`Created profile at: ${path31}`);
134946
135559
  } catch (err) {
134947
135560
  console.error(source_default.red(`Error: ${err.message}`));
134948
135561
  process.exit(1);