@nathapp/nax 0.65.0-canary.1 → 0.65.0-canary.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 (2) hide show
  1. package/dist/nax.js +699 -510
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -4925,6 +4925,24 @@ function formatSessionName(req) {
4925
4925
  }
4926
4926
  var init_session_name = () => {};
4927
4927
 
4928
+ // src/agents/retry/default-strategy.ts
4929
+ var MAX_RETRIES = 3, defaultRetryStrategy;
4930
+ var init_default_strategy = __esm(() => {
4931
+ defaultRetryStrategy = {
4932
+ shouldRetry(failure, attempt, _ctx) {
4933
+ if (attempt >= MAX_RETRIES)
4934
+ return { retry: false };
4935
+ if (failure instanceof Error)
4936
+ return { retry: false };
4937
+ const af = failure;
4938
+ if (af.outcome !== "fail-rate-limit")
4939
+ return { retry: false };
4940
+ const delayMs = 2 ** (attempt + 1) * 1000;
4941
+ return { retry: true, delayMs };
4942
+ }
4943
+ };
4944
+ });
4945
+
4928
4946
  // src/agents/manager.ts
4929
4947
  import { EventEmitter } from "events";
4930
4948
 
@@ -4941,6 +4959,7 @@ class AgentManager {
4941
4959
  _runHop;
4942
4960
  _dispatchEvents;
4943
4961
  _pidRegistry;
4962
+ _retryStrategy;
4944
4963
  events;
4945
4964
  constructor(config, registry, opts) {
4946
4965
  this._config = config;
@@ -4951,6 +4970,7 @@ class AgentManager {
4951
4970
  this._sendPrompt = opts?.sendPrompt;
4952
4971
  this._runHop = opts?.runHop;
4953
4972
  this._dispatchEvents = opts?.dispatchEvents ?? new DispatchEventBus;
4973
+ this._retryStrategy = opts?.retryStrategy ?? defaultRetryStrategy;
4954
4974
  this.events = {
4955
4975
  on: (event, listener) => {
4956
4976
  this._emitter.on(event, listener);
@@ -5049,7 +5069,6 @@ class AgentManager {
5049
5069
  const primaryAgent = primaryAgentOverride ?? this.getDefault();
5050
5070
  let currentAgent = primaryAgent;
5051
5071
  let hopsSoFar = 0;
5052
- const MAX_RATE_LIMIT_RETRIES = 3;
5053
5072
  let rateLimitRetry = 0;
5054
5073
  let currentBundle = request.bundle;
5055
5074
  let currentFailure;
@@ -5095,27 +5114,35 @@ class AgentManager {
5095
5114
  return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
5096
5115
  }
5097
5116
  if (!this.shouldSwap(result.adapterFailure, hopsSoFar, !!bundleForSwapCheck)) {
5098
- if (result.adapterFailure?.outcome === "fail-rate-limit" && rateLimitRetry < MAX_RATE_LIMIT_RETRIES) {
5099
- if (request.signal?.aborted) {
5100
- logger?.info("agent-manager", "Rate-limited backoff aborted \u2014 shutdown in progress", {
5101
- storyId: request.runOptions.storyId
5117
+ if (result.adapterFailure) {
5118
+ const retryCtx = {
5119
+ site: "run",
5120
+ agentName: currentAgent,
5121
+ stage: request.runOptions.pipelineStage ?? "run",
5122
+ storyId: request.runOptions.storyId
5123
+ };
5124
+ const decision = this._retryStrategy.shouldRetry(result.adapterFailure, rateLimitRetry, retryCtx);
5125
+ if (decision.retry) {
5126
+ if (request.signal?.aborted) {
5127
+ logger?.info("agent-manager", "Rate-limited backoff aborted \u2014 shutdown in progress", {
5128
+ storyId: request.runOptions.storyId
5129
+ });
5130
+ _finalStatus = "cancelled";
5131
+ return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
5132
+ }
5133
+ rateLimitRetry += 1;
5134
+ logger?.info("agent-manager", "Rate-limited with no swap candidate \u2014 backing off", {
5135
+ storyId: request.runOptions.storyId,
5136
+ attempt: rateLimitRetry,
5137
+ backoffMs: decision.delayMs
5102
5138
  });
5103
- _finalStatus = "cancelled";
5104
- return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
5105
- }
5106
- rateLimitRetry += 1;
5107
- const backoffMs = 2 ** rateLimitRetry * 1000;
5108
- logger?.info("agent-manager", "Rate-limited with no swap candidate \u2014 backing off", {
5109
- storyId: request.runOptions.storyId,
5110
- attempt: rateLimitRetry,
5111
- backoffMs
5112
- });
5113
- await _agentManagerDeps.sleep(backoffMs, request.signal);
5114
- if (request.signal?.aborted) {
5115
- _finalStatus = "cancelled";
5116
- return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
5139
+ await _agentManagerDeps.sleep(decision.delayMs, request.signal);
5140
+ if (request.signal?.aborted) {
5141
+ _finalStatus = "cancelled";
5142
+ return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
5143
+ }
5144
+ continue;
5117
5145
  }
5118
- continue;
5119
5146
  }
5120
5147
  if (hopsSoFar > 0) {
5121
5148
  this._emitter.emit("onSwapExhausted", { storyId: request.runOptions.storyId, hops: hopsSoFar });
@@ -5435,6 +5462,7 @@ var init_manager = __esm(() => {
5435
5462
  init_session_name();
5436
5463
  init_bun_deps();
5437
5464
  init_registry();
5465
+ init_default_strategy();
5438
5466
  _agentManagerDeps = {
5439
5467
  sleep: (ms, signal) => cancellableDelay(ms, signal)
5440
5468
  };
@@ -5492,6 +5520,30 @@ var init_bridge_builder = __esm(() => {
5492
5520
  QUESTION_PATTERNS = [/\?\s*$/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
5493
5521
  });
5494
5522
 
5523
+ // src/agents/retry/presets.ts
5524
+ function resolveRetryPreset(preset) {
5525
+ return {
5526
+ shouldRetry(failure, attempt, _ctx) {
5527
+ if (attempt >= preset.maxAttempts - 1)
5528
+ return { retry: false };
5529
+ if (preset.preset === "transient-network") {
5530
+ if (failure instanceof Error)
5531
+ return { retry: true, delayMs: preset.baseDelayMs };
5532
+ const af = failure;
5533
+ if (af.retriable)
5534
+ return { retry: true, delayMs: preset.baseDelayMs };
5535
+ return { retry: false };
5536
+ }
5537
+ return { retry: false };
5538
+ }
5539
+ };
5540
+ }
5541
+
5542
+ // src/agents/retry/index.ts
5543
+ var init_retry = __esm(() => {
5544
+ init_default_strategy();
5545
+ });
5546
+
5495
5547
  // src/config/schema-types.ts
5496
5548
  function isBuiltinModelTier(value) {
5497
5549
  return value === "fast" || value === "balanced" || value === "powerful";
@@ -19478,8 +19530,10 @@ var init_schemas_context = __esm(() => {
19478
19530
  providers: exports_external.object({
19479
19531
  historyScope: exports_external.enum(["repo", "package"]).default("package"),
19480
19532
  neighborScope: exports_external.enum(["repo", "package"]).default("package"),
19481
- crossPackageDepth: exports_external.number().int().min(0).default(1)
19482
- }).default({ historyScope: "package", neighborScope: "package", crossPackageDepth: 1 }),
19533
+ crossPackageDepth: exports_external.number().int().min(0).default(1),
19534
+ sourceGlob: exports_external.string().optional(),
19535
+ maxGlobFiles: exports_external.number().int().min(1).default(500)
19536
+ }).default({ historyScope: "package", neighborScope: "package", crossPackageDepth: 1, maxGlobFiles: 500 }),
19483
19537
  staleness: exports_external.object({
19484
19538
  enabled: exports_external.boolean().default(true),
19485
19539
  maxStoryAge: exports_external.number().int().min(1).default(10),
@@ -19495,7 +19549,12 @@ var init_schemas_context = __esm(() => {
19495
19549
  deterministic: false,
19496
19550
  session: { retentionDays: 7, archiveOnFeatureArchive: true },
19497
19551
  staleness: { enabled: true, maxStoryAge: 10, scoreMultiplier: 0.4 },
19498
- providers: { historyScope: "package", neighborScope: "package", crossPackageDepth: 1 }
19552
+ providers: {
19553
+ historyScope: "package",
19554
+ neighborScope: "package",
19555
+ crossPackageDepth: 1,
19556
+ maxGlobFiles: 500
19557
+ }
19499
19558
  }));
19500
19559
  ContextConfigSchema = exports_external.object({
19501
19560
  testCoverage: TestCoverageConfigSchema,
@@ -20265,7 +20324,7 @@ var init_schemas3 = __esm(() => {
20265
20324
  deterministic: false,
20266
20325
  session: { retentionDays: 7, archiveOnFeatureArchive: true },
20267
20326
  staleness: { enabled: true, maxStoryAge: 10, scoreMultiplier: 0.4 },
20268
- providers: { historyScope: "package", neighborScope: "package", crossPackageDepth: 1 }
20327
+ providers: { historyScope: "package", neighborScope: "package", crossPackageDepth: 1, maxGlobFiles: 500 }
20269
20328
  }
20270
20329
  }),
20271
20330
  optimizer: OptimizerConfigSchema.optional(),
@@ -20950,6 +21009,23 @@ function applyBatchModeCompat(conf) {
20950
21009
  }
20951
21010
  return conf;
20952
21011
  }
21012
+ function applyRoutingRetryDeprecationWarning(conf, warn = (msg) => {
21013
+ try {
21014
+ getLogger().warn("config", msg);
21015
+ } catch {}
21016
+ }) {
21017
+ const routing = conf.routing;
21018
+ const llm = routing?.llm;
21019
+ if (!llm)
21020
+ return conf;
21021
+ if ("retries" in llm) {
21022
+ warn("routing.llm.retries is deprecated (issue #856). " + "This value is still applied but will be removed in v1.0. " + "Retry policy is now declared on each operation \u2014 remove this key from your config.");
21023
+ }
21024
+ if ("retryDelayMs" in llm) {
21025
+ warn("routing.llm.retryDelayMs is deprecated (issue #856). " + "This value is still applied but will be removed in v1.0. " + "Retry policy is now declared on each operation \u2014 remove this key from your config.");
21026
+ }
21027
+ return conf;
21028
+ }
20953
21029
  async function loadConfig(startDir, cliOverrides) {
20954
21030
  let rawConfig = structuredClone(DEFAULT_CONFIG);
20955
21031
  const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
@@ -20962,14 +21038,14 @@ async function loadConfig(startDir, cliOverrides) {
20962
21038
  } catch {}
20963
21039
  if (globalConfRaw) {
20964
21040
  const { profile: _gProfile, ...globalConfStripped } = globalConfRaw;
20965
- const globalConf = applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(globalConfStripped, logger), logger)));
21041
+ const globalConf = applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(globalConfStripped, logger), logger))));
20966
21042
  rawConfig = deepMergeConfig(rawConfig, globalConf);
20967
21043
  }
20968
21044
  if (projDir) {
20969
21045
  const projConf = await loadJsonFile(join3(projDir, "config.json"), "config");
20970
21046
  if (projConf) {
20971
21047
  const { profile: _pProfile, ...projConfStripped } = projConf;
20972
- const resolvedProjConf = applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(projConfStripped, logger), logger)));
21048
+ const resolvedProjConf = applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(projConfStripped, logger), logger))));
20973
21049
  rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
20974
21050
  }
20975
21051
  }
@@ -21541,6 +21617,126 @@ var init_packing = __esm(() => {
21541
21617
  FLOOR_KINDS = ["static", "feature", "test-coverage"];
21542
21618
  });
21543
21619
 
21620
+ // src/project/detector.ts
21621
+ import { join as join5 } from "path";
21622
+ async function _detectLanguageImpl(workdir, pkg) {
21623
+ const deps = _detectorDeps;
21624
+ if (await deps.fileExists(join5(workdir, "go.mod")))
21625
+ return "go";
21626
+ if (await deps.fileExists(join5(workdir, "Cargo.toml")))
21627
+ return "rust";
21628
+ if (await deps.fileExists(join5(workdir, "pyproject.toml")))
21629
+ return "python";
21630
+ if (await deps.fileExists(join5(workdir, "requirements.txt")))
21631
+ return "python";
21632
+ if (pkg != null) {
21633
+ const allDeps = {
21634
+ ...pkg.dependencies,
21635
+ ...pkg.devDependencies
21636
+ };
21637
+ if ("typescript" in allDeps)
21638
+ return "typescript";
21639
+ return "javascript";
21640
+ }
21641
+ return;
21642
+ }
21643
+ function detectType(pkg) {
21644
+ if (pkg == null)
21645
+ return;
21646
+ if (pkg.workspaces != null)
21647
+ return "monorepo";
21648
+ const allDeps = {
21649
+ ...pkg.dependencies,
21650
+ ...pkg.devDependencies
21651
+ };
21652
+ for (const dep of WEB_DEPS) {
21653
+ if (dep in allDeps)
21654
+ return "web";
21655
+ }
21656
+ if ("ink" in allDeps)
21657
+ return "tui";
21658
+ for (const dep of API_DEPS) {
21659
+ if (dep in allDeps)
21660
+ return "api";
21661
+ }
21662
+ if (pkg.bin != null)
21663
+ return "cli";
21664
+ return;
21665
+ }
21666
+ async function detectTestFramework(_workdir, language, pkg) {
21667
+ if (language === "go")
21668
+ return "go-test";
21669
+ if (language === "rust")
21670
+ return "cargo-test";
21671
+ if (language === "python")
21672
+ return "pytest";
21673
+ if (pkg != null) {
21674
+ const devDeps = pkg.devDependencies ?? {};
21675
+ if ("vitest" in devDeps)
21676
+ return "vitest";
21677
+ if ("jest" in devDeps)
21678
+ return "jest";
21679
+ }
21680
+ return;
21681
+ }
21682
+ async function detectLanguage(packageDir) {
21683
+ const pkg = await _detectorDeps.readJson(join5(packageDir, "package.json"));
21684
+ return _detectLanguageImpl(packageDir, pkg);
21685
+ }
21686
+ async function detectLintTool(workdir, language) {
21687
+ if (language === "go")
21688
+ return "golangci-lint";
21689
+ if (language === "rust")
21690
+ return "clippy";
21691
+ if (language === "python")
21692
+ return "ruff";
21693
+ const deps = _detectorDeps;
21694
+ if (await deps.fileExists(join5(workdir, "biome.json")))
21695
+ return "biome";
21696
+ if (await deps.fileExists(join5(workdir, ".eslintrc")))
21697
+ return "eslint";
21698
+ if (await deps.fileExists(join5(workdir, ".eslintrc.js")))
21699
+ return "eslint";
21700
+ if (await deps.fileExists(join5(workdir, ".eslintrc.json")))
21701
+ return "eslint";
21702
+ return;
21703
+ }
21704
+ async function detectProjectProfile(workdir, existing) {
21705
+ const pkg = await _detectorDeps.readJson(join5(workdir, "package.json"));
21706
+ const language = existing.language !== undefined ? existing.language : await _detectLanguageImpl(workdir, pkg);
21707
+ const type = existing.type !== undefined ? existing.type : detectType(pkg);
21708
+ const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
21709
+ const lintTool = existing.lintTool !== undefined ? existing.lintTool : await detectLintTool(workdir, language);
21710
+ return { language, type, testFramework, lintTool };
21711
+ }
21712
+ var _detectorDeps, WEB_DEPS, API_DEPS;
21713
+ var init_detector = __esm(() => {
21714
+ _detectorDeps = {
21715
+ async fileExists(path) {
21716
+ const file3 = Bun.file(path);
21717
+ return file3.exists();
21718
+ },
21719
+ async readJson(path) {
21720
+ try {
21721
+ const file3 = Bun.file(path);
21722
+ if (!await file3.exists())
21723
+ return null;
21724
+ const text = await file3.text();
21725
+ return JSON.parse(text);
21726
+ } catch {
21727
+ return null;
21728
+ }
21729
+ }
21730
+ };
21731
+ WEB_DEPS = new Set(["react", "next", "vue", "nuxt"]);
21732
+ API_DEPS = new Set(["express", "fastify", "hono"]);
21733
+ });
21734
+
21735
+ // src/project/index.ts
21736
+ var init_project = __esm(() => {
21737
+ init_detector();
21738
+ });
21739
+
21544
21740
  // src/test-runners/detect/workspace.ts
21545
21741
  async function expandWorkspaceGlob(workdir, pattern) {
21546
21742
  const dirs = [];
@@ -21663,7 +21859,7 @@ var init_workspace = __esm(() => {
21663
21859
 
21664
21860
  // src/utils/path-security.ts
21665
21861
  import { realpathSync as realpathSync2 } from "fs";
21666
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join5, normalize as normalize2, resolve as resolve4 } from "path";
21862
+ import { dirname as dirname2, isAbsolute as isAbsolute3, join as join6, normalize as normalize2, resolve as resolve4 } from "path";
21667
21863
  function safeRealpathForComparison(p) {
21668
21864
  try {
21669
21865
  return realpathSync2(p);
@@ -21672,7 +21868,7 @@ function safeRealpathForComparison(p) {
21672
21868
  if (parent === p)
21673
21869
  return normalize2(p);
21674
21870
  const resolvedParent = safeRealpathForComparison(parent);
21675
- return join5(resolvedParent, p.split("/").pop() ?? "");
21871
+ return join6(resolvedParent, p.split("/").pop() ?? "");
21676
21872
  }
21677
21873
  }
21678
21874
  function isRelativeAndSafe(filePath) {
@@ -21700,7 +21896,7 @@ function validateModulePath(modulePath, allowedRoots) {
21700
21896
  } else {
21701
21897
  for (let i = 0;i < allowedRoots.length; i++) {
21702
21898
  const originalRoot = resolve4(allowedRoots[i]);
21703
- const absoluteInput = resolve4(join5(originalRoot, modulePath));
21899
+ const absoluteInput = resolve4(join6(originalRoot, modulePath));
21704
21900
  const resolved = safeRealpathForComparison(absoluteInput);
21705
21901
  const resolvedRoot = resolvedRoots[i];
21706
21902
  if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
@@ -21717,7 +21913,7 @@ var init_path_security2 = () => {};
21717
21913
 
21718
21914
  // src/context/engine/providers/code-neighbor.ts
21719
21915
  import { createHash as createHash3 } from "crypto";
21720
- import { join as join6, relative, resolve as resolve5 } from "path";
21916
+ import { join as join7, relative, resolve as resolve5 } from "path";
21721
21917
  function isExcludedPath(file3, ignoreMatchers) {
21722
21918
  for (const prefix of EXCLUDED_DIR_PREFIXES) {
21723
21919
  if (file3.startsWith(prefix) || file3.includes(`/${prefix}`))
@@ -21829,10 +22025,17 @@ function stripExt(s) {
21829
22025
  function isTestFile(filePath, regex) {
21830
22026
  return regex.some((re) => re.test(filePath));
21831
22027
  }
21832
- async function collectNeighbors(filePath, workdir, extraGlobWorkdirs, siblingTestContext, ignoreMatchers) {
22028
+ async function resolveSourceGlob(override, packageDir) {
22029
+ if (override)
22030
+ return override;
22031
+ const language = await _codeNeighborDeps.detectLanguage(packageDir);
22032
+ return (language && SOURCE_GLOB_BY_LANGUAGE[language]) ?? FALLBACK_SOURCE_GLOB;
22033
+ }
22034
+ async function collectNeighbors(filePath, workdir, sourceGlob, maxGlobFiles, extraGlobWorkdirs, siblingTestContext, ignoreMatchers, globCtx) {
21833
22035
  const neighbors = new Set;
21834
- if (await _codeNeighborDeps.fileExists(join6(workdir, filePath))) {
21835
- const content = await _codeNeighborDeps.readFile(join6(workdir, filePath));
22036
+ let anyTruncated = false;
22037
+ if (await _codeNeighborDeps.fileExists(join7(workdir, filePath))) {
22038
+ const content = await _codeNeighborDeps.readFile(join7(workdir, filePath));
21836
22039
  for (const spec of parseImportSpecifiers(content)) {
21837
22040
  const resolved = resolveImport(spec, filePath, workdir);
21838
22041
  if (resolved && resolved !== filePath)
@@ -21842,14 +22045,16 @@ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs, siblingTes
21842
22045
  const fileBaseName = (filePath.split("/").pop() ?? filePath).replace(/\.[^.]+$/, "");
21843
22046
  const fileNoExt = filePath.replace(/\.[^.]+$/, "");
21844
22047
  const scanForReverseDeps = async (scanWorkdir) => {
21845
- const srcFiles = _codeNeighborDeps.glob(SOURCE_GLOB, scanWorkdir, ignoreMatchers);
22048
+ const { files: srcFiles, truncated } = _codeNeighborDeps.glob(sourceGlob, scanWorkdir, ignoreMatchers, maxGlobFiles, globCtx);
22049
+ if (truncated)
22050
+ anyTruncated = true;
21846
22051
  for (const srcFile of srcFiles) {
21847
22052
  if (neighbors.size >= MAX_NEIGHBORS_PER_FILE)
21848
22053
  break;
21849
22054
  if (srcFile === filePath)
21850
22055
  continue;
21851
22056
  try {
21852
- const content = await _codeNeighborDeps.readFile(join6(scanWorkdir, srcFile));
22057
+ const content = await _codeNeighborDeps.readFile(join7(scanWorkdir, srcFile));
21853
22058
  if (content.includes(fileBaseName)) {
21854
22059
  for (const spec of parseImportSpecifiers(content)) {
21855
22060
  const resolved = resolveImport(spec, srcFile, scanWorkdir);
@@ -21874,7 +22079,7 @@ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs, siblingTes
21874
22079
  const candidates = deriveSiblingTestCandidates(filePath, siblingTestContext.globs);
21875
22080
  let chosen = null;
21876
22081
  for (const candidate of candidates) {
21877
- if (await _codeNeighborDeps.fileExists(join6(workdir, candidate))) {
22082
+ if (await _codeNeighborDeps.fileExists(join7(workdir, candidate))) {
21878
22083
  chosen = candidate;
21879
22084
  break;
21880
22085
  }
@@ -21888,7 +22093,7 @@ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs, siblingTes
21888
22093
  if (chosen !== null && chosen !== filePath)
21889
22094
  neighbors.add(chosen);
21890
22095
  }
21891
- return [...neighbors].slice(0, MAX_NEIGHBORS_PER_FILE);
22096
+ return { neighbors: [...neighbors].slice(0, MAX_NEIGHBORS_PER_FILE), truncated: anyTruncated };
21892
22097
  }
21893
22098
  async function resolveExtraGlobWorkdirs(neighborScope, crossPackageDepth, repoRoot, packageDir) {
21894
22099
  if (neighborScope !== "package" || crossPackageDepth <= 0 || packageDir === repoRoot) {
@@ -21898,7 +22103,7 @@ async function resolveExtraGlobWorkdirs(neighborScope, crossPackageDepth, repoRo
21898
22103
  const relPkgDirs = await _codeNeighborDeps.discoverWorkspacePackages(repoRoot);
21899
22104
  if (relPkgDirs.length === 0)
21900
22105
  return [repoRoot];
21901
- return relPkgDirs.map((rel) => join6(repoRoot, rel)).filter((abs) => abs !== packageDir);
22106
+ return relPkgDirs.map((rel) => join7(repoRoot, rel)).filter((abs) => abs !== packageDir);
21902
22107
  } catch {
21903
22108
  return [repoRoot];
21904
22109
  }
@@ -21909,9 +22114,13 @@ class CodeNeighborProvider {
21909
22114
  kind = "neighbor";
21910
22115
  neighborScope;
21911
22116
  crossPackageDepth;
22117
+ sourceGlobOverride;
22118
+ maxGlobFiles;
21912
22119
  constructor(options = {}) {
21913
22120
  this.neighborScope = options.neighborScope ?? "package";
21914
22121
  this.crossPackageDepth = options.crossPackageDepth ?? 1;
22122
+ this.sourceGlobOverride = options.sourceGlob;
22123
+ this.maxGlobFiles = options.maxGlobFiles ?? MAX_GLOB_FILES_DEFAULT;
21915
22124
  }
21916
22125
  async fetch(request) {
21917
22126
  const { touchedFiles } = request;
@@ -21926,9 +22135,14 @@ class CodeNeighborProvider {
21926
22135
  regex: request.resolvedTestPatterns.regex
21927
22136
  } : undefined;
21928
22137
  const ignoreMatchers = request.naxIgnoreIndex?.getMatchers(workdir);
22138
+ const sourceGlob = await resolveSourceGlob(this.sourceGlobOverride, request.packageDir);
22139
+ const globCtx = { storyId: request.storyId, packageDir: request.packageDir };
21929
22140
  const sections = [];
22141
+ let anyTruncated = false;
21930
22142
  for (const file3 of filesToProcess) {
21931
- const neighbors = await collectNeighbors(file3, workdir, extraGlobWorkdirs, siblingTestContext, ignoreMatchers);
22143
+ const { neighbors, truncated } = await collectNeighbors(file3, workdir, sourceGlob, this.maxGlobFiles, extraGlobWorkdirs, siblingTestContext, ignoreMatchers, globCtx);
22144
+ if (truncated)
22145
+ anyTruncated = true;
21932
22146
  if (neighbors.length > 0) {
21933
22147
  sections.push(`### ${file3}
21934
22148
  ${neighbors.map((n) => `- ${n}`).join(`
@@ -21947,7 +22161,12 @@ ${sections.join(`
21947
22161
 
21948
22162
  `)}`;
21949
22163
  const maxChars = MAX_CHUNK_TOKENS * 4;
21950
- const content = rawContent.length > maxChars ? rawContent.slice(0, maxChars) : rawContent;
22164
+ const body = rawContent.length > maxChars ? rawContent.slice(0, maxChars) : rawContent;
22165
+ const truncationNote = anyTruncated ? `
22166
+
22167
+ > Note: reverse-dep scan capped at ${this.maxGlobFiles} files; some neighbors may be missing.
22168
+ > Increase \`context.v2.providers.maxGlobFiles\` or set \`sourceGlob\` to a narrower pattern (e.g. \`**/*.go\`) to reduce the scan footprint.` : "";
22169
+ const content = body + truncationNote;
21951
22170
  const tokens = Math.ceil(content.length / 4);
21952
22171
  const chunk = {
21953
22172
  id: `code-neighbor:${contentHash8(content)}`,
@@ -21961,11 +22180,19 @@ ${sections.join(`
21961
22180
  return { chunks: [chunk], pullTools: [] };
21962
22181
  }
21963
22182
  }
21964
- var MAX_FILES = 10, MAX_NEIGHBORS_PER_FILE = 8, MAX_GLOB_FILES = 200, MAX_CHUNK_TOKENS = 500, SOURCE_GLOB = "**/*.{ts,tsx,js,jsx,py,go,rs,java,rb,php,cs,cpp,c,h}", EXCLUDED_DIR_PREFIXES, _codeNeighborDeps, FROM_PATTERN, REQUIRE_PATTERN, IMPORT_SIDE_EFFECT_PATTERN;
22183
+ var MAX_FILES = 10, MAX_NEIGHBORS_PER_FILE = 8, MAX_GLOB_FILES_DEFAULT = 500, MAX_CHUNK_TOKENS = 500, SOURCE_GLOB_BY_LANGUAGE, FALLBACK_SOURCE_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,rb,php,cs,cpp,c,h}", EXCLUDED_DIR_PREFIXES, _codeNeighborDeps, FROM_PATTERN, REQUIRE_PATTERN, IMPORT_SIDE_EFFECT_PATTERN;
21965
22184
  var init_code_neighbor = __esm(() => {
21966
22185
  init_logger2();
22186
+ init_project();
21967
22187
  init_workspace();
21968
22188
  init_path_security2();
22189
+ SOURCE_GLOB_BY_LANGUAGE = {
22190
+ typescript: "**/*.{ts,tsx,js,jsx,mjs,cjs}",
22191
+ javascript: "**/*.{js,jsx,mjs,cjs}",
22192
+ go: "**/*.go",
22193
+ python: "**/*.py",
22194
+ rust: "**/*.rs"
22195
+ };
21969
22196
  EXCLUDED_DIR_PREFIXES = [
21970
22197
  "node_modules/",
21971
22198
  ".git/",
@@ -21980,8 +22207,9 @@ var init_code_neighbor = __esm(() => {
21980
22207
  fileExists: (path) => Bun.file(path).exists(),
21981
22208
  readFile: (path) => Bun.file(path).text(),
21982
22209
  discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages(repoRoot),
22210
+ detectLanguage: (packageDir) => detectLanguage(packageDir),
21983
22211
  getLogger,
21984
- glob: (pattern, cwd, ignoreMatchers = []) => {
22212
+ glob: (pattern, cwd, ignoreMatchers = [], cap = MAX_GLOB_FILES_DEFAULT, ctx) => {
21985
22213
  const g = new Bun.Glob(pattern);
21986
22214
  const results = [];
21987
22215
  let count = 0;
@@ -21989,7 +22217,7 @@ var init_code_neighbor = __esm(() => {
21989
22217
  for (const file3 of g.scanSync({ cwd, absolute: false })) {
21990
22218
  if (isExcludedPath(file3, ignoreMatchers))
21991
22219
  continue;
21992
- if (count >= MAX_GLOB_FILES) {
22220
+ if (count >= cap) {
21993
22221
  truncated = true;
21994
22222
  break;
21995
22223
  }
@@ -21997,13 +22225,16 @@ var init_code_neighbor = __esm(() => {
21997
22225
  count++;
21998
22226
  }
21999
22227
  if (truncated) {
22000
- _codeNeighborDeps.getLogger().debug("context-v2", "Glob cap reached \u2014 results truncated", {
22228
+ _codeNeighborDeps.getLogger().warn("context-v2", "Reverse-dep glob cap reached \u2014 results truncated", {
22229
+ storyId: ctx?.storyId,
22230
+ packageDir: ctx?.packageDir,
22001
22231
  pattern,
22002
22232
  cwd,
22003
- cap: MAX_GLOB_FILES
22233
+ cap,
22234
+ hint: "Increase context.v2.providers.maxGlobFiles or narrow context.v2.providers.sourceGlob"
22004
22235
  });
22005
22236
  }
22006
- return results;
22237
+ return { files: results, truncated };
22007
22238
  }
22008
22239
  };
22009
22240
  FROM_PATTERN = /from\s+['"]([^'"]+)['"]/g;
@@ -22465,11 +22696,11 @@ class PullToolBudget {
22465
22696
  return this.sessionCalls;
22466
22697
  }
22467
22698
  }
22468
- async function handleQueryNeighbor(input, repoRoot, budget, maxTokensPerCall = DEFAULT_MAX_TOKENS_PER_CALL, resolvedTestPatterns, storyId) {
22699
+ async function handleQueryNeighbor(input, repoRoot, budget, maxTokensPerCall = DEFAULT_MAX_TOKENS_PER_CALL, resolvedTestPatterns, storyId, providerOptions) {
22469
22700
  budget.consume();
22470
- const provider = new CodeNeighborProvider;
22701
+ const provider = new CodeNeighborProvider(providerOptions ?? {});
22471
22702
  const request = {
22472
- storyId: "_pull-tool",
22703
+ storyId: storyId ?? "_pull-tool",
22473
22704
  repoRoot,
22474
22705
  packageDir: repoRoot,
22475
22706
  stage: "pull-tool",
@@ -23150,7 +23381,7 @@ var init_orchestrator = __esm(() => {
23150
23381
  });
23151
23382
 
23152
23383
  // src/context/rules/canonical-loader.ts
23153
- import { basename as basename3, join as join7 } from "path";
23384
+ import { basename as basename3, join as join8 } from "path";
23154
23385
  function parseRuleAllowMarker(line) {
23155
23386
  const allowed = new Set;
23156
23387
  RULE_ALLOW_MARKER.lastIndex = 0;
@@ -23285,7 +23516,7 @@ function applyCanonicalRulesBudget(rules, budgetTokens) {
23285
23516
  }
23286
23517
  async function loadCanonicalRules(workdir, options = {}) {
23287
23518
  const logger = _canonicalLoaderDeps.getLogger();
23288
- const rulesDir = join7(workdir, CANONICAL_RULES_DIR);
23519
+ const rulesDir = join8(workdir, CANONICAL_RULES_DIR);
23289
23520
  const allFilePaths = _canonicalLoaderDeps.globInDir(rulesDir);
23290
23521
  const filePaths = allFilePaths.filter((filePath) => {
23291
23522
  const normalized = filePath.replaceAll("\\", "/");
@@ -23386,7 +23617,7 @@ var init_canonical_loader = __esm(() => {
23386
23617
  for (const rel of files) {
23387
23618
  const depth = rel.split("/").length - 1;
23388
23619
  if (depth <= 1) {
23389
- kept.push(join7(dir, rel));
23620
+ kept.push(join8(dir, rel));
23390
23621
  } else {
23391
23622
  ignored.push(rel);
23392
23623
  }
@@ -23748,7 +23979,7 @@ var init_scratch_writer = __esm(() => {
23748
23979
  });
23749
23980
 
23750
23981
  // src/utils/path-filters.ts
23751
- import { join as join8, relative as relative2 } from "path";
23982
+ import { join as join9, relative as relative2 } from "path";
23752
23983
  function basename4(path) {
23753
23984
  const stripped = path.startsWith("./") ? path.slice(2) : path;
23754
23985
  const idx = stripped.lastIndexOf("/");
@@ -23838,8 +24069,8 @@ async function resolveNaxIgnorePatterns(repoRoot, packageDir) {
23838
24069
  const normalizedRepoRoot = normalizePath(repoRoot);
23839
24070
  const normalizedPackageDir = packageDir ? normalizePath(packageDir) : normalizedRepoRoot;
23840
24071
  const packagePrefix = normalizedPackageDir !== normalizedRepoRoot ? normalizePath(relative2(repoRoot, packageDir ?? repoRoot)) : null;
23841
- const rootFile = join8(repoRoot, NAX_IGNORE_FILENAME);
23842
- const packageFile = join8(packageDir ?? repoRoot, NAX_IGNORE_FILENAME);
24072
+ const rootFile = join9(repoRoot, NAX_IGNORE_FILENAME);
24073
+ const packageFile = join9(packageDir ?? repoRoot, NAX_IGNORE_FILENAME);
23843
24074
  const rootPatterns = await readIgnorePatterns(rootFile);
23844
24075
  const packagePatterns = packageDir && packageDir !== repoRoot ? await readIgnorePatterns(packageFile) : [];
23845
24076
  return [
@@ -24014,7 +24245,7 @@ var init_session_scratch = __esm(() => {
24014
24245
 
24015
24246
  // src/context/engine/providers/static-rules.ts
24016
24247
  import { createHash as createHash8 } from "crypto";
24017
- import { join as join9, relative as relative3 } from "path";
24248
+ import { join as join10, relative as relative3 } from "path";
24018
24249
  function contentHash85(content) {
24019
24250
  return createHash8("sha256").update(content).digest("hex").slice(0, 8);
24020
24251
  }
@@ -24222,7 +24453,7 @@ ${rule.content}`,
24222
24453
  const existingCandidates = [];
24223
24454
  for (const fileName of LEGACY_CANDIDATE_FILES) {
24224
24455
  try {
24225
- if (await _staticRulesDeps.fileExists(join9(rootDir, fileName))) {
24456
+ if (await _staticRulesDeps.fileExists(join10(rootDir, fileName))) {
24226
24457
  existingCandidates.push(fileName);
24227
24458
  }
24228
24459
  } catch {}
@@ -24238,11 +24469,11 @@ ${rule.content}`,
24238
24469
  for (const fileName of LEGACY_CANDIDATE_FILES) {
24239
24470
  legacySources.push({
24240
24471
  sourceId: fileName,
24241
- filePath: join9(rootDir, fileName),
24472
+ filePath: join10(rootDir, fileName),
24242
24473
  heading: fileName
24243
24474
  });
24244
24475
  }
24245
- const rulesDir = join9(rootDir, LEGACY_RULES_DIR);
24476
+ const rulesDir = join10(rootDir, LEGACY_RULES_DIR);
24246
24477
  const nestedRulePaths = _staticRulesDeps.globInDir(rulesDir);
24247
24478
  for (const filePath of nestedRulePaths) {
24248
24479
  const normalized = normalizePath2(filePath);
@@ -24301,7 +24532,7 @@ var init_static_rules = __esm(() => {
24301
24532
  fileExists: async (path) => Bun.file(path).exists(),
24302
24533
  globInDir: (dir) => {
24303
24534
  try {
24304
- return [...new Bun.Glob("**/*.md").scanSync({ cwd: dir, absolute: false })].sort().map((f) => join9(dir, f));
24535
+ return [...new Bun.Glob("**/*.md").scanSync({ cwd: dir, absolute: false })].sort().map((f) => join10(dir, f));
24305
24536
  } catch {
24306
24537
  return [];
24307
24538
  }
@@ -25507,7 +25738,7 @@ var init_detect2 = __esm(() => {
25507
25738
  });
25508
25739
 
25509
25740
  // src/test-runners/resolver.ts
25510
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join10, relative as relative4, resolve as resolve6 } from "path";
25741
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join11, relative as relative4, resolve as resolve6 } from "path";
25511
25742
  function buildResolved(globs, resolution) {
25512
25743
  return {
25513
25744
  globs,
@@ -25559,7 +25790,7 @@ async function resolveTestFilePatterns(config2, workdir, packageDir, options) {
25559
25790
  validateGlobs(rootPatterns, "resolver");
25560
25791
  return buildResolved(rootPatterns, "root-config");
25561
25792
  }
25562
- const detectionWorkdir = packageDir ? join10(workdir, packageDir) : workdir;
25793
+ const detectionWorkdir = packageDir ? join11(workdir, packageDir) : workdir;
25563
25794
  const detected = await _resolverDeps2.detectTestFilePatterns(detectionWorkdir);
25564
25795
  if (detected.confidence !== "empty" && detected.patterns.length > 0) {
25565
25796
  getSafeLogger()?.info("resolver", "Test patterns auto-detected", {
@@ -25917,7 +26148,9 @@ function createDefaultOrchestrator(story, config2, _storyScratchDirs, additional
25917
26148
  providers.push(new GitHistoryProvider({ historyScope: providerConfig?.historyScope ?? "package" }));
25918
26149
  providers.push(new CodeNeighborProvider({
25919
26150
  neighborScope: providerConfig?.neighborScope ?? "package",
25920
- crossPackageDepth: providerConfig?.crossPackageDepth ?? 1
26151
+ crossPackageDepth: providerConfig?.crossPackageDepth ?? 1,
26152
+ sourceGlob: providerConfig?.sourceGlob,
26153
+ maxGlobFiles: providerConfig?.maxGlobFiles
25921
26154
  }));
25922
26155
  providers.push(...additionalProviders);
25923
26156
  return new ContextOrchestrator(providers);
@@ -25934,7 +26167,7 @@ var init_orchestrator_factory = __esm(() => {
25934
26167
  });
25935
26168
 
25936
26169
  // src/context/engine/providers/plugin-loader.ts
25937
- import { isAbsolute as isAbsolute5, join as join11, resolve as resolve7 } from "path";
26170
+ import { isAbsolute as isAbsolute5, join as join12, resolve as resolve7 } from "path";
25938
26171
  function isInitialisable(p) {
25939
26172
  return typeof p.init === "function";
25940
26173
  }
@@ -25962,7 +26195,7 @@ function resolveModuleSpecifier(specifier, workdir) {
25962
26195
  }
25963
26196
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
25964
26197
  const resolvedWorkdir = resolve7(workdir);
25965
- const resolved = resolve7(join11(workdir, specifier));
26198
+ const resolved = resolve7(join12(workdir, specifier));
25966
26199
  if (resolved !== resolvedWorkdir && !resolved.startsWith(`${resolvedWorkdir}/`)) {
25967
26200
  throw new Error(`Plugin module path escapes project workdir: "${specifier}" resolves to "${resolved}" (workdir: "${resolvedWorkdir}")`);
25968
26201
  }
@@ -26108,15 +26341,15 @@ var init_available_budget = __esm(() => {
26108
26341
 
26109
26342
  // src/context/engine/manifest-store.ts
26110
26343
  import { mkdir as mkdir2 } from "fs/promises";
26111
- import { dirname as dirname5, isAbsolute as isAbsolute6, join as join12, relative as relative6, resolve as resolve8 } from "path";
26344
+ import { dirname as dirname5, isAbsolute as isAbsolute6, join as join13, relative as relative6, resolve as resolve8 } from "path";
26112
26345
  function contextStoryDir(projectDir, featureId, storyId) {
26113
- return join12(projectDir, ".nax", "features", featureId, "stories", storyId);
26346
+ return join13(projectDir, ".nax", "features", featureId, "stories", storyId);
26114
26347
  }
26115
26348
  function contextManifestPath(projectDir, featureId, storyId, stage) {
26116
- return join12(contextStoryDir(projectDir, featureId, storyId), `context-manifest-${stage}.json`);
26349
+ return join13(contextStoryDir(projectDir, featureId, storyId), `context-manifest-${stage}.json`);
26117
26350
  }
26118
26351
  function rebuildManifestPath(projectDir, featureId, storyId) {
26119
- return join12(contextStoryDir(projectDir, featureId, storyId), "rebuild-manifest.json");
26352
+ return join13(contextStoryDir(projectDir, featureId, storyId), "rebuild-manifest.json");
26120
26353
  }
26121
26354
  function toStoredPath(projectDir, pathValue) {
26122
26355
  const relativePath = isAbsolute6(pathValue) ? relative6(projectDir, pathValue) : pathValue;
@@ -26172,7 +26405,7 @@ async function loadContextManifests(projectDir, storyId, featureId) {
26172
26405
  const storyDir = contextStoryDir(projectDir, feature, storyId);
26173
26406
  const manifestFiles = await _manifestStoreDeps.listManifestFiles(storyDir);
26174
26407
  for (const fileName of manifestFiles) {
26175
- const fullPath = join12(storyDir, fileName);
26408
+ const fullPath = join13(storyDir, fileName);
26176
26409
  if (!await _manifestStoreDeps.fileExists(fullPath))
26177
26410
  continue;
26178
26411
  try {
@@ -26197,7 +26430,7 @@ var init_manifest_store = __esm(() => {
26197
26430
  fileExists: (path2) => Bun.file(path2).exists(),
26198
26431
  readFile: (path2) => Bun.file(path2).text(),
26199
26432
  listFeatureDirs: async (projectDir) => {
26200
- const baseDir = join12(projectDir, ".nax", "features");
26433
+ const baseDir = join13(projectDir, ".nax", "features");
26201
26434
  try {
26202
26435
  const dirs = [];
26203
26436
  for await (const entry of new Bun.Glob("*").scan({ cwd: baseDir, absolute: false })) {
@@ -26224,7 +26457,7 @@ var init_manifest_store = __esm(() => {
26224
26457
 
26225
26458
  // src/context/engine/stage-assembler.ts
26226
26459
  import { readdir } from "fs/promises";
26227
- import { isAbsolute as isAbsolute7, join as join13, resolve as resolve9 } from "path";
26460
+ import { isAbsolute as isAbsolute7, join as join14, resolve as resolve9 } from "path";
26228
26461
  function dedupeScratchDirs(dirs) {
26229
26462
  return [...new Set(dirs.filter((dir) => Boolean(dir)))];
26230
26463
  }
@@ -26233,7 +26466,7 @@ function toAbsolutePath2(projectDir, pathValue) {
26233
26466
  }
26234
26467
  async function discoverSessionScratchDirsOnDisk(projectDir, featureName, storyId, ttlMs) {
26235
26468
  const logger = getLogger();
26236
- const sessionsRoot = join13(projectDir, ".nax", "features", featureName, "sessions");
26469
+ const sessionsRoot = join14(projectDir, ".nax", "features", featureName, "sessions");
26237
26470
  let entries;
26238
26471
  try {
26239
26472
  entries = await _stageAssemblerDeps.readdir(sessionsRoot);
@@ -26243,7 +26476,7 @@ async function discoverSessionScratchDirsOnDisk(projectDir, featureName, storyId
26243
26476
  const cutoff = _stageAssemblerDeps.now() - ttlMs;
26244
26477
  const found = [];
26245
26478
  for (const entry of entries) {
26246
- const descriptorPath = join13(sessionsRoot, entry, "descriptor.json");
26479
+ const descriptorPath = join14(sessionsRoot, entry, "descriptor.json");
26247
26480
  try {
26248
26481
  const parsed = await _stageAssemblerDeps.readDescriptor(descriptorPath);
26249
26482
  if (!parsed || parsed.storyId !== storyId || !parsed.scratchDir)
@@ -26394,7 +26627,7 @@ function createContextToolRuntime(options) {
26394
26627
  switch (name) {
26395
26628
  case "query_neighbor": {
26396
26629
  const patterns = await getResolvedTestPatterns();
26397
- return handleQueryNeighbor(input, repoRoot, getBudget(tool), tool.maxTokensPerCall, patterns);
26630
+ return handleQueryNeighbor(input, repoRoot, getBudget(tool), tool.maxTokensPerCall, patterns, story.id, config2.context?.v2?.providers);
26398
26631
  }
26399
26632
  case "query_feature_context":
26400
26633
  return handleQueryFeatureContext(input, story, config2, repoRoot, getBudget(tool), tool.maxTokensPerCall);
@@ -26665,7 +26898,7 @@ function detectFramework(output) {
26665
26898
  return "unknown";
26666
26899
  }
26667
26900
  var TEST_FILE_PATTERNS;
26668
- var init_detector = __esm(() => {
26901
+ var init_detector2 = __esm(() => {
26669
26902
  init_conventions();
26670
26903
  TEST_FILE_PATTERNS = [
26671
26904
  /(?:^|\/)(?:test|tests|__tests__|specs?)(?:\/|$)/,
@@ -26927,7 +27160,7 @@ function analyzeTestExitCode(output, exitCode) {
26927
27160
  return result;
26928
27161
  }
26929
27162
  var init_parser = __esm(() => {
26930
- init_detector();
27163
+ init_detector2();
26931
27164
  });
26932
27165
 
26933
27166
  // src/test-runners/ac-parser.ts
@@ -26986,14 +27219,14 @@ function parseTestFailures(output) {
26986
27219
  return failedACs;
26987
27220
  }
26988
27221
  var init_ac_parser = __esm(() => {
26989
- init_detector();
27222
+ init_detector2();
26990
27223
  });
26991
27224
 
26992
27225
  // src/test-runners/index.ts
26993
27226
  var init_test_runners = __esm(() => {
26994
27227
  init_conventions();
26995
27228
  init_detect2();
26996
- init_detector();
27229
+ init_detector2();
26997
27230
  init_resolver();
26998
27231
  init_parser();
26999
27232
  init_ac_parser();
@@ -28120,13 +28353,13 @@ var exports_loader = {};
28120
28353
  __export(exports_loader, {
28121
28354
  loadOverride: () => loadOverride
28122
28355
  });
28123
- import { join as join14 } from "path";
28356
+ import { join as join15 } from "path";
28124
28357
  async function loadOverride(role, workdir, config2) {
28125
28358
  const overridePath = config2.prompts?.overrides?.[role];
28126
28359
  if (!overridePath) {
28127
28360
  return null;
28128
28361
  }
28129
- const absolutePath = join14(workdir, overridePath);
28362
+ const absolutePath = join15(workdir, overridePath);
28130
28363
  const file3 = Bun.file(absolutePath);
28131
28364
  if (!await file3.exists()) {
28132
28365
  return null;
@@ -30703,6 +30936,17 @@ function resolveTimeoutMs(op, input, buildCtx) {
30703
30936
  }
30704
30937
  return timeoutMs;
30705
30938
  }
30939
+ function resolveOpRetry(op, input, buildCtx) {
30940
+ if (!op.retry)
30941
+ return null;
30942
+ if (typeof op.retry === "function") {
30943
+ const preset = op.retry(input, buildCtx);
30944
+ return preset ? resolveRetryPreset(preset) : null;
30945
+ }
30946
+ if ("shouldRetry" in op.retry)
30947
+ return op.retry;
30948
+ return resolveRetryPreset(op.retry);
30949
+ }
30706
30950
  function synthesizeStory(storyId) {
30707
30951
  return {
30708
30952
  id: storyId ?? "",
@@ -30733,7 +30977,7 @@ async function callOp(ctx, op, input) {
30733
30977
  if (op.kind === "complete") {
30734
30978
  const completeOp = op;
30735
30979
  const sessionRole2 = ctx.sessionOverride?.role;
30736
- const raw = await ctx.runtime.agentManager.completeAs(dispatchAgent, prompt, {
30980
+ const completeOptions = {
30737
30981
  modelDef: resolved.modelDef,
30738
30982
  jsonMode: completeOp.jsonMode ?? false,
30739
30983
  pipelineStage: op.stage,
@@ -30742,9 +30986,40 @@ async function callOp(ctx, op, input) {
30742
30986
  featureName: ctx.featureName,
30743
30987
  ...sessionRole2 !== undefined ? { sessionRole: sessionRole2 } : {},
30744
30988
  ...timeoutMs !== undefined ? { timeoutMs } : {}
30745
- });
30746
- const parsedComplete = op.parse(raw.output, input, buildCtx);
30747
- return runPostParse(op, parsedComplete, input, buildCtx);
30989
+ };
30990
+ const retryStrategy = resolveOpRetry(completeOp, input, buildCtx);
30991
+ let attempt = 0;
30992
+ while (attempt <= MAX_COMPLETE_RETRY_ATTEMPTS) {
30993
+ try {
30994
+ const raw = await ctx.runtime.agentManager.completeAs(dispatchAgent, prompt, completeOptions);
30995
+ const parsedComplete = op.parse(raw.output, input, buildCtx);
30996
+ return await runPostParse(op, parsedComplete, input, buildCtx);
30997
+ } catch (err) {
30998
+ if (!retryStrategy)
30999
+ throw err;
31000
+ const decision = retryStrategy.shouldRetry(err, attempt, {
31001
+ site: "complete",
31002
+ agentName: dispatchAgent,
31003
+ stage: op.stage,
31004
+ storyId: ctx.storyId
31005
+ });
31006
+ if (!decision.retry)
31007
+ throw err;
31008
+ if (ctx.runtime.signal?.aborted)
31009
+ throw err;
31010
+ getSafeLogger()?.warn("call-op", `LLM call failed (attempt ${attempt + 1}), retrying in ${decision.delayMs}ms`, {
31011
+ storyId: ctx.storyId,
31012
+ op: op.name,
31013
+ attempt,
31014
+ delayMs: decision.delayMs
31015
+ });
31016
+ await _callOpDeps.sleep(decision.delayMs, ctx.runtime.signal);
31017
+ if (ctx.runtime.signal?.aborted)
31018
+ throw err;
31019
+ attempt++;
31020
+ }
31021
+ }
31022
+ throw new NaxError(`callOp[${op.name}]: exceeded MAX_COMPLETE_RETRY_ATTEMPTS (${MAX_COMPLETE_RETRY_ATTEMPTS})`, "CALL_OP_MAX_RETRIES", { stage: op.stage, storyId: ctx.storyId });
30748
31023
  }
30749
31024
  const runOp = op;
30750
31025
  const story = ctx.story ?? synthesizeStory(ctx.storyId);
@@ -30819,11 +31094,18 @@ async function runPostParse(op, parsed, input, buildCtx) {
30819
31094
  }
30820
31095
  return final ?? parsed;
30821
31096
  }
31097
+ var _callOpDeps, MAX_COMPLETE_RETRY_ATTEMPTS = 20;
30822
31098
  var init_call = __esm(() => {
31099
+ init_retry();
30823
31100
  init_config();
30824
31101
  init_errors();
31102
+ init_logger2();
30825
31103
  init_compose();
31104
+ init_bun_deps();
30826
31105
  init_build_hop_callback();
31106
+ _callOpDeps = {
31107
+ sleep: (ms, signal) => cancellableDelay(ms, signal)
31108
+ };
30827
31109
  });
30828
31110
 
30829
31111
  // src/prd/validate.ts
@@ -31507,24 +31789,6 @@ function keywordRoute(story, config2) {
31507
31789
  const reasoning = reasons.length > 0 ? `${prefix}: ${reasons.join(", ")}` : `${prefix}: ${complexity} task`;
31508
31790
  return { complexity, modelTier, testStrategy, reasoning };
31509
31791
  }
31510
- async function classifyWithRetry(ctx, op, input, opts) {
31511
- let lastErr;
31512
- for (let i = 0;i <= opts.retries; i++) {
31513
- try {
31514
- return await callOp(ctx, op, input);
31515
- } catch (err) {
31516
- lastErr = err;
31517
- if (i < opts.retries) {
31518
- const logger = getSafeLogger();
31519
- logger?.warn("routing", `LLM call failed (attempt ${i + 1}/${opts.retries + 1}), retrying in ${opts.retryDelayMs}ms`, {
31520
- error: lastErr.message
31521
- });
31522
- await Bun.sleep(opts.retryDelayMs);
31523
- }
31524
- }
31525
- }
31526
- throw lastErr ?? new Error("classifyWithRetry: unknown failure");
31527
- }
31528
31792
  async function resolveRouting(story, config2, plugins, dispatchContext) {
31529
31793
  const logger = getSafeLogger();
31530
31794
  const runtimeContext = dispatchContext;
@@ -31584,14 +31848,12 @@ async function resolveRouting(story, config2, plugins, dispatchContext) {
31584
31848
  agentName: resolveDefaultAgent(runtime.configLoader.current()),
31585
31849
  storyId: story.id
31586
31850
  };
31587
- const retries = llmConfig.retries ?? 1;
31588
- const retryDelayMs = llmConfig.retryDelayMs ?? 1000;
31589
- const decision = await classifyWithRetry(ctx, classifyRouteOp, {
31851
+ const decision = await callOp(ctx, classifyRouteOp, {
31590
31852
  title: story.title,
31591
31853
  description: story.description,
31592
31854
  acceptanceCriteria: story.acceptanceCriteria,
31593
31855
  tags: story.tags
31594
- }, { retries, retryDelayMs });
31856
+ });
31595
31857
  if (llmConfig.cacheDecisions) {
31596
31858
  if (cachedDecisions.size >= MAX_CACHE_SIZE)
31597
31859
  evictOldest();
@@ -31678,9 +31940,7 @@ async function tryLlmBatchRoute(config2, stories, label = "routing", _deps = _tr
31678
31940
  packageDir: runtime.workdir,
31679
31941
  agentName: resolveDefaultAgent(runtime.configLoader.current())
31680
31942
  };
31681
- const retries = llmConfig.retries ?? 1;
31682
- const retryDelayMs = llmConfig.retryDelayMs ?? 1000;
31683
- const decisions = await classifyWithRetry(ctx, classifyRouteBatchOp, needsRouting, { retries, retryDelayMs });
31943
+ const decisions = await callOp(ctx, classifyRouteBatchOp, needsRouting);
31684
31944
  if (llmConfig.cacheDecisions) {
31685
31945
  for (const [storyId, decision] of decisions.entries()) {
31686
31946
  if (cachedDecisions.size >= MAX_CACHE_SIZE)
@@ -31771,6 +32031,11 @@ var init_classify_route = __esm(() => {
31771
32031
  jsonMode: true,
31772
32032
  config: routingConfigSelector,
31773
32033
  model: (_input, ctx) => ctx.config.routing.llm?.model ?? "balanced",
32034
+ retry: (_input, ctx) => ({
32035
+ preset: "transient-network",
32036
+ maxAttempts: (ctx.config.routing.llm?.retries ?? 1) + 1,
32037
+ baseDelayMs: ctx.config.routing.llm?.retryDelayMs ?? 1000
32038
+ }),
31774
32039
  build(input, _ctx) {
31775
32040
  const criteria = input.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join(`
31776
32041
  `);
@@ -31803,6 +32068,11 @@ ${storyBody}`, overridable: false }
31803
32068
  jsonMode: true,
31804
32069
  config: routingConfigSelector,
31805
32070
  model: (_input, ctx) => ctx.config.routing.llm?.model ?? "balanced",
32071
+ retry: (_input, ctx) => ({
32072
+ preset: "transient-network",
32073
+ maxAttempts: (ctx.config.routing.llm?.retries ?? 1) + 1,
32074
+ baseDelayMs: ctx.config.routing.llm?.retryDelayMs ?? 1000
32075
+ }),
31806
32076
  build(input, _ctx) {
31807
32077
  const storyBlocks = input.map((story, idx) => {
31808
32078
  const criteria = story.acceptanceCriteria.map((c, i) => ` ${i + 1}. ${c}`).join(`
@@ -33760,10 +34030,10 @@ var init_plan_helpers = __esm(() => {
33760
34030
 
33761
34031
  // src/analyze/scanner.ts
33762
34032
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
33763
- import { join as join15 } from "path";
34033
+ import { join as join16 } from "path";
33764
34034
  async function scanCodebase(workdir) {
33765
- const srcPath = join15(workdir, "src");
33766
- const packageJsonPath = join15(workdir, "package.json");
34035
+ const srcPath = join16(workdir, "src");
34036
+ const packageJsonPath = join16(workdir, "package.json");
33767
34037
  const fileTree = existsSync5(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
33768
34038
  let dependencies = {};
33769
34039
  let devDependencies = {};
@@ -33804,7 +34074,7 @@ async function generateFileTree(dir, maxDepth, currentDepth = 0, prefix = "") {
33804
34074
  const isDir = dirent.isDirectory();
33805
34075
  entries.push(`${prefix}${connector}${dirent.name}${isDir ? "/" : ""}`);
33806
34076
  if (isDir) {
33807
- const subtree = await generateFileTree(join15(dir, dirent.name), maxDepth, currentDepth + 1, prefix + childPrefix);
34077
+ const subtree = await generateFileTree(join16(dir, dirent.name), maxDepth, currentDepth + 1, prefix + childPrefix);
33808
34078
  if (subtree) {
33809
34079
  entries.push(subtree);
33810
34080
  }
@@ -33828,16 +34098,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
33828
34098
  } else {
33829
34099
  patterns.push("Test framework: likely bun:test (no framework dependency)");
33830
34100
  }
33831
- if (existsSync5(join15(workdir, "test"))) {
34101
+ if (existsSync5(join16(workdir, "test"))) {
33832
34102
  patterns.push("Test directory: test/");
33833
34103
  }
33834
- if (existsSync5(join15(workdir, "__tests__"))) {
34104
+ if (existsSync5(join16(workdir, "__tests__"))) {
33835
34105
  patterns.push("Test directory: __tests__/");
33836
34106
  }
33837
- if (existsSync5(join15(workdir, "tests"))) {
34107
+ if (existsSync5(join16(workdir, "tests"))) {
33838
34108
  patterns.push("Test directory: tests/");
33839
34109
  }
33840
- const hasTestFiles = existsSync5(join15(workdir, "test")) || existsSync5(join15(workdir, "src"));
34110
+ const hasTestFiles = existsSync5(join16(workdir, "test")) || existsSync5(join16(workdir, "src"));
33841
34111
  if (hasTestFiles) {
33842
34112
  patterns.push("Test files: *.test.ts, *.spec.ts");
33843
34113
  }
@@ -33847,9 +34117,9 @@ var init_scanner = () => {};
33847
34117
 
33848
34118
  // src/context/injector.ts
33849
34119
  import { existsSync as existsSync6 } from "fs";
33850
- import { join as join16 } from "path";
34120
+ import { join as join17 } from "path";
33851
34121
  async function detectNode(workdir) {
33852
- const pkgPath = join16(workdir, "package.json");
34122
+ const pkgPath = join17(workdir, "package.json");
33853
34123
  if (!existsSync6(pkgPath))
33854
34124
  return null;
33855
34125
  try {
@@ -33866,7 +34136,7 @@ async function detectNode(workdir) {
33866
34136
  }
33867
34137
  }
33868
34138
  async function detectGo(workdir) {
33869
- const goMod = join16(workdir, "go.mod");
34139
+ const goMod = join17(workdir, "go.mod");
33870
34140
  if (!existsSync6(goMod))
33871
34141
  return null;
33872
34142
  try {
@@ -33890,7 +34160,7 @@ async function detectGo(workdir) {
33890
34160
  }
33891
34161
  }
33892
34162
  async function detectRust(workdir) {
33893
- const cargoPath = join16(workdir, "Cargo.toml");
34163
+ const cargoPath = join17(workdir, "Cargo.toml");
33894
34164
  if (!existsSync6(cargoPath))
33895
34165
  return null;
33896
34166
  try {
@@ -33906,8 +34176,8 @@ async function detectRust(workdir) {
33906
34176
  }
33907
34177
  }
33908
34178
  async function detectPython(workdir) {
33909
- const pyproject = join16(workdir, "pyproject.toml");
33910
- const requirements = join16(workdir, "requirements.txt");
34179
+ const pyproject = join17(workdir, "pyproject.toml");
34180
+ const requirements = join17(workdir, "requirements.txt");
33911
34181
  if (!existsSync6(pyproject) && !existsSync6(requirements))
33912
34182
  return null;
33913
34183
  try {
@@ -33926,7 +34196,7 @@ async function detectPython(workdir) {
33926
34196
  }
33927
34197
  }
33928
34198
  async function detectPhp(workdir) {
33929
- const composerPath = join16(workdir, "composer.json");
34199
+ const composerPath = join17(workdir, "composer.json");
33930
34200
  if (!existsSync6(composerPath))
33931
34201
  return null;
33932
34202
  try {
@@ -33939,7 +34209,7 @@ async function detectPhp(workdir) {
33939
34209
  }
33940
34210
  }
33941
34211
  async function detectRuby(workdir) {
33942
- const gemfile = join16(workdir, "Gemfile");
34212
+ const gemfile = join17(workdir, "Gemfile");
33943
34213
  if (!existsSync6(gemfile))
33944
34214
  return null;
33945
34215
  try {
@@ -33951,9 +34221,9 @@ async function detectRuby(workdir) {
33951
34221
  }
33952
34222
  }
33953
34223
  async function detectJvm(workdir) {
33954
- const pom = join16(workdir, "pom.xml");
33955
- const gradle = join16(workdir, "build.gradle");
33956
- const gradleKts = join16(workdir, "build.gradle.kts");
34224
+ const pom = join17(workdir, "pom.xml");
34225
+ const gradle = join17(workdir, "build.gradle");
34226
+ const gradleKts = join17(workdir, "build.gradle.kts");
33957
34227
  if (!existsSync6(pom) && !existsSync6(gradle) && !existsSync6(gradleKts))
33958
34228
  return null;
33959
34229
  try {
@@ -33961,7 +34231,7 @@ async function detectJvm(workdir) {
33961
34231
  const content2 = await Bun.file(pom).text();
33962
34232
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
33963
34233
  const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
33964
- const lang2 = existsSync6(join16(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
34234
+ const lang2 = existsSync6(join17(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
33965
34235
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
33966
34236
  }
33967
34237
  const gradleFile = existsSync6(gradleKts) ? gradleKts : gradle;
@@ -34215,7 +34485,7 @@ var init_windsurf = __esm(() => {
34215
34485
 
34216
34486
  // src/context/generator.ts
34217
34487
  import { existsSync as existsSync7 } from "fs";
34218
- import { join as join17, relative as relative8 } from "path";
34488
+ import { join as join18, relative as relative8 } from "path";
34219
34489
  async function loadContextContent(options, config2) {
34220
34490
  if (!_generatorDeps.existsSync(options.contextPath)) {
34221
34491
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -34233,7 +34503,7 @@ async function generateFor(agent, options, config2) {
34233
34503
  try {
34234
34504
  const context = await loadContextContent(options, config2);
34235
34505
  const content = generator.generate(context);
34236
- const outputPath = join17(options.outputDir, generator.outputFile);
34506
+ const outputPath = join18(options.outputDir, generator.outputFile);
34237
34507
  validateFilePath(outputPath, options.outputDir);
34238
34508
  if (!options.dryRun) {
34239
34509
  await _generatorDeps.writeFile(outputPath, content);
@@ -34251,7 +34521,7 @@ async function generateAll(options, config2, agentFilter) {
34251
34521
  for (const [agentKey, generator] of entries) {
34252
34522
  try {
34253
34523
  const content = generator.generate(context);
34254
- const outputPath = join17(options.outputDir, generator.outputFile);
34524
+ const outputPath = join18(options.outputDir, generator.outputFile);
34255
34525
  validateFilePath(outputPath, options.outputDir);
34256
34526
  if (!options.dryRun) {
34257
34527
  await _generatorDeps.writeFile(outputPath, content);
@@ -34271,7 +34541,7 @@ async function discoverPackages(repoRoot) {
34271
34541
  const glob = new Bun.Glob(pattern);
34272
34542
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
34273
34543
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
34274
- const pkgAbsolute = join17(repoRoot, pkgRelative);
34544
+ const pkgAbsolute = join18(repoRoot, pkgRelative);
34275
34545
  if (!seen.has(pkgAbsolute)) {
34276
34546
  seen.add(pkgAbsolute);
34277
34547
  packages.push(pkgAbsolute);
@@ -34303,14 +34573,14 @@ async function discoverWorkspacePackages2(repoRoot) {
34303
34573
  }
34304
34574
  }
34305
34575
  }
34306
- const turboPath = join17(repoRoot, "turbo.json");
34576
+ const turboPath = join18(repoRoot, "turbo.json");
34307
34577
  try {
34308
34578
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
34309
34579
  if (Array.isArray(turbo.packages)) {
34310
34580
  await resolveGlobs(turbo.packages);
34311
34581
  }
34312
34582
  } catch {}
34313
- const pkgPath = join17(repoRoot, "package.json");
34583
+ const pkgPath = join18(repoRoot, "package.json");
34314
34584
  try {
34315
34585
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
34316
34586
  const ws = pkg.workspaces;
@@ -34318,7 +34588,7 @@ async function discoverWorkspacePackages2(repoRoot) {
34318
34588
  if (patterns.length > 0)
34319
34589
  await resolveGlobs(patterns);
34320
34590
  } catch {}
34321
- const pnpmPath = join17(repoRoot, "pnpm-workspace.yaml");
34591
+ const pnpmPath = join18(repoRoot, "pnpm-workspace.yaml");
34322
34592
  try {
34323
34593
  const raw = await _generatorDeps.readTextFile(pnpmPath);
34324
34594
  const lines = raw.split(`
@@ -34344,7 +34614,7 @@ async function discoverWorkspacePackages2(repoRoot) {
34344
34614
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
34345
34615
  const resolvedRepoRoot = repoRoot ?? packageDir;
34346
34616
  const relativePkgPath = relative8(resolvedRepoRoot, packageDir);
34347
- const contextPath = join17(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
34617
+ const contextPath = join18(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
34348
34618
  if (!_generatorDeps.existsSync(contextPath)) {
34349
34619
  return [
34350
34620
  {
@@ -34602,8 +34872,8 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
34602
34872
  const agentName = resolverConfig.agent ?? RESOLVER_FALLBACK_AGENT;
34603
34873
  const manager = agentManager ?? _debateSessionDeps.agentManager;
34604
34874
  if (manager !== undefined && manager.getAgent(agentName) !== undefined) {
34605
- const configModels = config2?.models ?? DEFAULT_CONFIG.models;
34606
- const configDefaultAgent = resolveDefaultAgent(config2 ?? DEFAULT_CONFIG);
34875
+ const configModels = config2.models ?? DEFAULT_CONFIG.models;
34876
+ const configDefaultAgent = resolveDefaultAgent(config2);
34607
34877
  const synthesisSessionName = workdir !== undefined ? formatSessionName({ workdir, featureName, storyId, role: "synthesis" }) : undefined;
34608
34878
  const resolverDebater = { agent: agentName, model: resolverConfig.model };
34609
34879
  const resolverSelection = { agent: agentName, model: resolverConfig.model ?? "fast" };
@@ -34646,8 +34916,8 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
34646
34916
  if (!manager) {
34647
34917
  return { outcome: "passed", resolverCostUsd: 0 };
34648
34918
  }
34649
- const configModels = config2?.models ?? DEFAULT_CONFIG.models;
34650
- const configDefaultAgent = resolveDefaultAgent(config2 ?? DEFAULT_CONFIG);
34919
+ const configModels = config2.models ?? DEFAULT_CONFIG.models;
34920
+ const configDefaultAgent = resolveDefaultAgent(config2);
34651
34921
  const judgeSessionName = workdir !== undefined ? formatSessionName({ workdir, featureName, storyId, role: "judge" }) : undefined;
34652
34922
  const resolverDebater = { agent: agentName, model: resolverConfig.model };
34653
34923
  const resolverSelection = { agent: agentName, model: resolverConfig.model ?? "fast" };
@@ -34879,7 +35149,7 @@ async function runStateful(ctx, prompt) {
34879
35149
  output: s.output
34880
35150
  }))
34881
35151
  } : undefined;
34882
- const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.completeConfig, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successfulProposals.map((s) => s.debater), agentManager);
35152
+ const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successfulProposals.map((s) => s.debater), agentManager);
34883
35153
  totalCostUsd += outcome.resolverCostUsd;
34884
35154
  const proposals = successfulProposals.map((s) => ({
34885
35155
  debater: s.debater,
@@ -35128,7 +35398,7 @@ async function runHybrid(ctx, prompt) {
35128
35398
  output: s.output
35129
35399
  }))
35130
35400
  } : undefined;
35131
- const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.completeConfig, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successfulProposals.map((s) => s.debater), agentManager);
35401
+ const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successfulProposals.map((s) => s.debater), agentManager);
35132
35402
  totalCostUsd += resolveResult.resolverCostUsd;
35133
35403
  return {
35134
35404
  storyId: ctx.storyId,
@@ -35160,7 +35430,7 @@ var init_runner_hybrid = __esm(() => {
35160
35430
  });
35161
35431
 
35162
35432
  // src/debate/runner-plan.ts
35163
- import { join as join18 } from "path";
35433
+ import { join as join19 } from "path";
35164
35434
  async function runPlan(ctx, taskContext, outputFormat, opts) {
35165
35435
  const logger = _debateSessionDeps.getSafeLogger();
35166
35436
  const config2 = ctx.stageConfig;
@@ -35188,7 +35458,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
35188
35458
  const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
35189
35459
  const proposalBuilder = new DebatePromptBuilder({ taskContext, outputFormat, stage: "plan" }, { debaters: resolved.map((r) => r.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
35190
35460
  const settled = await allSettledBounded(resolved.map(({ debater: rd, agentName }, i) => async () => {
35191
- const tempOutputPath = join18(opts.outputDir, `prd-debate-${i}.json`);
35461
+ const tempOutputPath = join19(opts.outputDir, `prd-debate-${i}.json`);
35192
35462
  const debaterPrompt = `${proposalBuilder.buildProposalPrompt(i)}
35193
35463
 
35194
35464
  Write the PRD JSON directly to this file path: ${tempOutputPath}
@@ -35287,7 +35557,6 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
35287
35557
  stage: ctx.stage,
35288
35558
  stageConfig: ctx.stageConfig,
35289
35559
  config: ctx.config,
35290
- completeConfig: ctx.completeConfig,
35291
35560
  workdir: opts.workdir,
35292
35561
  featureName: opts.feature,
35293
35562
  timeoutSeconds: opts.timeoutSeconds ?? 600,
@@ -35328,7 +35597,7 @@ The spec above is the authoritative source for acceptance criteria.
35328
35597
  - Preserve the spec's AC wording. You may refine for clarity but must not change semantics.
35329
35598
  - Preserve each story's \`routing\` object unchanged \u2014 especially \`routing.complexity\` and \`routing.testStrategy\`. These are required by the schema and must not be dropped or modified during synthesis.` : "";
35330
35599
  const planSynthesisSuffix = `IMPORTANT: Your response must be a single valid JSON object in PRD format (with project, feature, branchName, userStories array, etc.). Do NOT wrap it in markdown fences. Output raw JSON only.${specAnchor}`;
35331
- const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.completeConfig, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, undefined, undefined, planSynthesisSuffix, successful.map((p) => p.debater), agentManager);
35600
+ const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, undefined, undefined, planSynthesisSuffix, successful.map((p) => p.debater), agentManager);
35332
35601
  const winningOutput = outcome.output ?? successful[0].output;
35333
35602
  const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
35334
35603
  logger?.info("debate", "debate:result", {
@@ -35364,7 +35633,6 @@ class DebateRunner {
35364
35633
  stage;
35365
35634
  stageConfig;
35366
35635
  config;
35367
- completeConfig;
35368
35636
  workdir;
35369
35637
  featureName;
35370
35638
  timeoutSeconds;
@@ -35376,7 +35644,6 @@ class DebateRunner {
35376
35644
  this.stage = opts.stage;
35377
35645
  this.stageConfig = opts.stageConfig;
35378
35646
  this.config = opts.config ?? debateConfigSelector.select(DEFAULT_CONFIG);
35379
- this.completeConfig = opts.config;
35380
35647
  this.workdir = opts.workdir ?? opts.ctx.packageDir;
35381
35648
  this.featureName = opts.featureName ?? opts.stage;
35382
35649
  this.timeoutSeconds = opts.timeoutSeconds ?? opts.stageConfig.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
@@ -35518,7 +35785,7 @@ class DebateRunner {
35518
35785
  output: s.output
35519
35786
  }))
35520
35787
  } : undefined;
35521
- const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, this.stageConfig, this.completeConfig, this.ctx.storyId ?? "", this.timeoutSeconds * 1000, this.workdir, this.featureName, this.reviewerSession, fullResolverContext, undefined, successful.map((s) => s.debater), agentManager);
35788
+ const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, this.stageConfig, this.config, this.ctx.storyId ?? "", this.timeoutSeconds * 1000, this.workdir, this.featureName, this.reviewerSession, fullResolverContext, undefined, successful.map((s) => s.debater), agentManager);
35522
35789
  totalCostUsd += outcome.resolverCostUsd;
35523
35790
  const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
35524
35791
  logger?.info("debate", "debate:result", { storyId: this.ctx.storyId, stage: this.stage, outcome: outcome.outcome });
@@ -35539,7 +35806,6 @@ class DebateRunner {
35539
35806
  stage: this.stage,
35540
35807
  stageConfig: this.stageConfig,
35541
35808
  config: this.config,
35542
- completeConfig: this.completeConfig,
35543
35809
  workdir: this.workdir,
35544
35810
  featureName: this.featureName,
35545
35811
  timeoutSeconds: this.timeoutSeconds,
@@ -35557,7 +35823,6 @@ class DebateRunner {
35557
35823
  stage: this.stage,
35558
35824
  stageConfig: this.stageConfig,
35559
35825
  config: this.config,
35560
- completeConfig: this.completeConfig,
35561
35826
  agentManager: this.ctx.runtime.agentManager,
35562
35827
  sessionManager: this.sessionManager ?? this.ctx.runtime.sessionManager,
35563
35828
  runtime: this.ctx.runtime,
@@ -36824,7 +37089,7 @@ var init_init = __esm(() => {
36824
37089
 
36825
37090
  // src/runtime/cost-aggregator.ts
36826
37091
  import { mkdirSync as mkdirSync2 } from "fs";
36827
- import { join as join19 } from "path";
37092
+ import { join as join20 } from "path";
36828
37093
  function createNoOpCostAggregator() {
36829
37094
  return {
36830
37095
  record() {},
@@ -36939,7 +37204,7 @@ class CostAggregator {
36939
37204
  if (events.length === 0 && errors3.length === 0)
36940
37205
  return;
36941
37206
  mkdirSync2(this._drainDir, { recursive: true });
36942
- const path4 = join19(this._drainDir, `${this._runId}.jsonl`);
37207
+ const path4 = join20(this._drainDir, `${this._runId}.jsonl`);
36943
37208
  const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
36944
37209
  await _costAggDeps.write(path4, `${sorted.map((e) => JSON.stringify(e)).join(`
36945
37210
  `)}
@@ -36975,7 +37240,7 @@ var init_cost_aggregator = __esm(() => {
36975
37240
  // src/runtime/prompt-auditor.ts
36976
37241
  import { appendFileSync } from "fs";
36977
37242
  import { mkdir as mkdir3 } from "fs/promises";
36978
- import { join as join20 } from "path";
37243
+ import { join as join21 } from "path";
36979
37244
  function createNoOpPromptAuditor() {
36980
37245
  return {
36981
37246
  record() {},
@@ -37041,8 +37306,8 @@ class PromptAuditor {
37041
37306
  _jsonlPath;
37042
37307
  _featureDir;
37043
37308
  constructor(runId, flushDir, featureName) {
37044
- this._featureDir = join20(flushDir, featureName);
37045
- this._jsonlPath = join20(this._featureDir, `${runId}.jsonl`);
37309
+ this._featureDir = join21(flushDir, featureName);
37310
+ this._jsonlPath = join21(this._featureDir, `${runId}.jsonl`);
37046
37311
  }
37047
37312
  record(entry) {
37048
37313
  this._enqueue(entry);
@@ -37091,7 +37356,7 @@ class PromptAuditor {
37091
37356
  const auditEntry = entry;
37092
37357
  const filename = deriveTxtFilename(auditEntry);
37093
37358
  try {
37094
- await _promptAuditorDeps.write(join20(this._featureDir, filename), buildTxtContent(auditEntry));
37359
+ await _promptAuditorDeps.write(join21(this._featureDir, filename), buildTxtContent(auditEntry));
37095
37360
  } catch (err) {
37096
37361
  throw tagAuditError(err, "txt");
37097
37362
  }
@@ -37112,11 +37377,11 @@ var init_prompt_auditor = __esm(() => {
37112
37377
  });
37113
37378
 
37114
37379
  // src/utils/nax-project-root.ts
37115
- import { dirname as dirname6, join as join21, resolve as resolve11 } from "path";
37380
+ import { dirname as dirname6, join as join22, resolve as resolve11 } from "path";
37116
37381
  async function findNaxProjectRoot(startDir) {
37117
37382
  let dir = resolve11(startDir);
37118
37383
  for (let depth = 0;depth < MAX_NAX_WALK_DEPTH; depth++) {
37119
- if (await _naxProjectRootDeps.exists(join21(dir, ".nax", "config.json"))) {
37384
+ if (await _naxProjectRootDeps.exists(join22(dir, ".nax", "config.json"))) {
37120
37385
  return dir;
37121
37386
  }
37122
37387
  const parent = dirname6(dir);
@@ -37137,7 +37402,7 @@ var init_nax_project_root = __esm(() => {
37137
37402
 
37138
37403
  // src/review/review-audit.ts
37139
37404
  import { mkdir as mkdir4 } from "fs/promises";
37140
- import { join as join22 } from "path";
37405
+ import { join as join23 } from "path";
37141
37406
  function auditKey(reviewer, storyId) {
37142
37407
  return `${reviewer}:${storyId ?? "_feature"}`;
37143
37408
  }
@@ -37167,15 +37432,15 @@ function toPersistedEntry(entry, epochMs) {
37167
37432
  async function persistReviewAudit(entry) {
37168
37433
  let resolvedDir;
37169
37434
  if (entry.outputDir) {
37170
- resolvedDir = join22(entry.outputDir, "review-audit", entry.featureName ?? "_unknown");
37435
+ resolvedDir = join23(entry.outputDir, "review-audit", entry.featureName ?? "_unknown");
37171
37436
  } else {
37172
37437
  const projectRoot = entry.projectDir ?? await _reviewAuditDeps.findNaxProjectRoot(entry.workdir);
37173
- resolvedDir = join22(projectRoot, ".nax", "review-audit", entry.featureName ?? "_unknown");
37438
+ resolvedDir = join23(projectRoot, ".nax", "review-audit", entry.featureName ?? "_unknown");
37174
37439
  }
37175
37440
  await _reviewAuditDeps.mkdir(resolvedDir);
37176
37441
  const epochMs = _reviewAuditDeps.now();
37177
37442
  const filename = `${epochMs}-${entry.sessionName}.json`;
37178
- await _reviewAuditDeps.writeFile(join22(resolvedDir, filename), toPersistedEntry(entry, epochMs));
37443
+ await _reviewAuditDeps.writeFile(join23(resolvedDir, filename), toPersistedEntry(entry, epochMs));
37179
37444
  }
37180
37445
  function createNoOpReviewAuditor() {
37181
37446
  return {
@@ -37568,7 +37833,7 @@ var init_pid_registry = __esm(() => {
37568
37833
  // src/session/manager-deps.ts
37569
37834
  import { randomUUID as randomUUID2 } from "crypto";
37570
37835
  import { mkdir as mkdir5 } from "fs/promises";
37571
- import { isAbsolute as isAbsolute8, join as join23, relative as relative9, sep } from "path";
37836
+ import { isAbsolute as isAbsolute8, join as join24, relative as relative9, sep } from "path";
37572
37837
  function resolveProjectDirFromScratchDir(scratchDir) {
37573
37838
  const marker = `${sep}.nax${sep}features${sep}`;
37574
37839
  const markerIdx = scratchDir.lastIndexOf(marker);
@@ -37589,7 +37854,7 @@ var init_manager_deps = __esm(() => {
37589
37854
  now: () => new Date().toISOString(),
37590
37855
  nowMs: () => Date.now(),
37591
37856
  uuid: () => randomUUID2(),
37592
- sessionScratchDir: (projectDir, featureName, sessionId) => join23(projectDir, ".nax", "features", featureName, "sessions", sessionId),
37857
+ sessionScratchDir: (projectDir, featureName, sessionId) => join24(projectDir, ".nax", "features", featureName, "sessions", sessionId),
37593
37858
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
37594
37859
  await mkdir5(scratchDir, { recursive: true });
37595
37860
  const { handle: _handle, ...persistable } = descriptor;
@@ -37600,7 +37865,7 @@ var init_manager_deps = __esm(() => {
37600
37865
  persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
37601
37866
  }
37602
37867
  }
37603
- await Bun.write(join23(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
37868
+ await Bun.write(join24(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
37604
37869
  }
37605
37870
  };
37606
37871
  });
@@ -38521,7 +38786,7 @@ __export(exports_runtime, {
38521
38786
  DispatchEventBus: () => DispatchEventBus,
38522
38787
  CostAggregator: () => CostAggregator
38523
38788
  });
38524
- import { basename as basename5, join as join24 } from "path";
38789
+ import { basename as basename5, join as join25 } from "path";
38525
38790
  function createRuntime(config2, workdir, opts) {
38526
38791
  const runId = crypto.randomUUID();
38527
38792
  const controller = new AbortController;
@@ -38534,10 +38799,10 @@ function createRuntime(config2, workdir, opts) {
38534
38799
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
38535
38800
  const globalDir = globalOutputDir();
38536
38801
  const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
38537
- const costDir = join24(outputDir, "cost");
38802
+ const costDir = join25(outputDir, "cost");
38538
38803
  const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
38539
38804
  const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
38540
- const auditDir = config2.agent?.promptAudit?.dir ?? join24(outputDir, "prompt-audit");
38805
+ const auditDir = config2.agent?.promptAudit?.dir ?? join25(outputDir, "prompt-audit");
38541
38806
  let promptAuditor;
38542
38807
  if (opts?.promptAuditor) {
38543
38808
  promptAuditor = opts.promptAuditor;
@@ -39651,9 +39916,9 @@ __export(exports_plan_decompose, {
39651
39916
  runReplanLoop: () => runReplanLoop,
39652
39917
  planDecomposeCommand: () => planDecomposeCommand
39653
39918
  });
39654
- import { join as join25 } from "path";
39919
+ import { join as join26 } from "path";
39655
39920
  async function planDecomposeCommand(workdir, config2, options) {
39656
- const prdPath = join25(workdir, ".nax", "features", options.feature, "prd.json");
39921
+ const prdPath = join26(workdir, ".nax", "features", options.feature, "prd.json");
39657
39922
  if (!_planDeps.existsSync(prdPath)) {
39658
39923
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
39659
39924
  stage: "decompose",
@@ -39827,7 +40092,7 @@ var init_plan_decompose = __esm(() => {
39827
40092
 
39828
40093
  // src/cli/plan-runtime.ts
39829
40094
  import { existsSync as existsSync13 } from "fs";
39830
- import { join as join26 } from "path";
40095
+ import { join as join27 } from "path";
39831
40096
  function isRuntimeWithAgentManager(value) {
39832
40097
  return typeof value === "object" && value !== null && "agentManager" in value;
39833
40098
  }
@@ -39867,7 +40132,7 @@ var init_plan_runtime = __esm(() => {
39867
40132
  writeFile: (path5, content) => Bun.write(path5, content).then(() => {}),
39868
40133
  scanCodebase: (workdir) => scanCodebase(workdir),
39869
40134
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
39870
- readPackageJson: (workdir) => Bun.file(join26(workdir, "package.json")).json().catch(() => null),
40135
+ readPackageJson: (workdir) => Bun.file(join27(workdir, "package.json")).json().catch(() => null),
39871
40136
  spawnSync: (cmd, opts) => {
39872
40137
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
39873
40138
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -40255,7 +40520,7 @@ var init_metrics = __esm(() => {
40255
40520
 
40256
40521
  // src/commands/common.ts
40257
40522
  import { existsSync as existsSync15, readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
40258
- import { join as join28, resolve as resolve12 } from "path";
40523
+ import { join as join29, resolve as resolve12 } from "path";
40259
40524
  function resolveProject(options = {}) {
40260
40525
  const { dir, feature } = options;
40261
40526
  let projectRoot;
@@ -40263,12 +40528,12 @@ function resolveProject(options = {}) {
40263
40528
  let configPath;
40264
40529
  if (dir) {
40265
40530
  projectRoot = realpathSync3(resolve12(dir));
40266
- naxDir = join28(projectRoot, ".nax");
40531
+ naxDir = join29(projectRoot, ".nax");
40267
40532
  if (!existsSync15(naxDir)) {
40268
40533
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
40269
40534
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
40270
40535
  }
40271
- configPath = join28(naxDir, "config.json");
40536
+ configPath = join29(naxDir, "config.json");
40272
40537
  if (!existsSync15(configPath)) {
40273
40538
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
40274
40539
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -40276,22 +40541,22 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
40276
40541
  } else {
40277
40542
  const found = findProjectRoot(process.cwd());
40278
40543
  if (!found) {
40279
- const cwdNaxDir = join28(process.cwd(), ".nax");
40544
+ const cwdNaxDir = join29(process.cwd(), ".nax");
40280
40545
  if (existsSync15(cwdNaxDir)) {
40281
- const cwdConfigPath = join28(cwdNaxDir, "config.json");
40546
+ const cwdConfigPath = join29(cwdNaxDir, "config.json");
40282
40547
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
40283
40548
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
40284
40549
  }
40285
40550
  throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
40286
40551
  }
40287
40552
  projectRoot = found;
40288
- naxDir = join28(projectRoot, ".nax");
40289
- configPath = join28(naxDir, "config.json");
40553
+ naxDir = join29(projectRoot, ".nax");
40554
+ configPath = join29(naxDir, "config.json");
40290
40555
  }
40291
40556
  let featureDir;
40292
40557
  if (feature) {
40293
- const featuresDir = join28(naxDir, "features");
40294
- featureDir = join28(featuresDir, feature);
40558
+ const featuresDir = join29(naxDir, "features");
40559
+ featureDir = join29(featuresDir, feature);
40295
40560
  if (!existsSync15(featureDir)) {
40296
40561
  const availableFeatures = existsSync15(featuresDir) ? readdirSync3(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
40297
40562
  const availableMsg = availableFeatures.length > 0 ? `
@@ -40318,12 +40583,12 @@ function findProjectRoot(startDir) {
40318
40583
  let current = resolve12(startDir);
40319
40584
  let depth = 0;
40320
40585
  while (depth < MAX_DIRECTORY_DEPTH) {
40321
- const naxDir = join28(current, ".nax");
40322
- const configPath = join28(naxDir, "config.json");
40586
+ const naxDir = join29(current, ".nax");
40587
+ const configPath = join29(naxDir, "config.json");
40323
40588
  if (existsSync15(configPath)) {
40324
40589
  return realpathSync3(current);
40325
40590
  }
40326
- const parent = join28(current, "..");
40591
+ const parent = join29(current, "..");
40327
40592
  if (parent === current) {
40328
40593
  break;
40329
40594
  }
@@ -41678,13 +41943,13 @@ var init_executor = __esm(() => {
41678
41943
 
41679
41944
  // src/verification/runners.ts
41680
41945
  import { existsSync as existsSync18 } from "fs";
41681
- import { join as join32 } from "path";
41946
+ import { join as join33 } from "path";
41682
41947
  async function verifyAssets(workingDirectory, expectedFiles) {
41683
41948
  if (!expectedFiles || expectedFiles.length === 0)
41684
41949
  return { success: true, missingFiles: [] };
41685
41950
  const missingFiles = [];
41686
41951
  for (const file3 of expectedFiles) {
41687
- if (!existsSync18(join32(workingDirectory, file3)))
41952
+ if (!existsSync18(join33(workingDirectory, file3)))
41688
41953
  missingFiles.push(file3);
41689
41954
  }
41690
41955
  if (missingFiles.length > 0) {
@@ -41776,7 +42041,7 @@ var init_runners = __esm(() => {
41776
42041
  });
41777
42042
 
41778
42043
  // src/verification/smart-runner.ts
41779
- import { join as join33, relative as relative10 } from "path";
42044
+ import { join as join34, relative as relative10 } from "path";
41780
42045
  function extractPatternSuffix(pattern) {
41781
42046
  const lastStar = pattern.lastIndexOf("*");
41782
42047
  if (lastStar === -1)
@@ -41879,7 +42144,7 @@ async function getChangedNonTestFiles(workdir, baseRef, packagePrefix, testFileR
41879
42144
  const lines = stdout.trim().split(`
41880
42145
  `).filter(Boolean);
41881
42146
  const effectiveRepoRoot = repoRoot ?? workdir;
41882
- const packageDir = packagePrefix ? join33(effectiveRepoRoot, packagePrefix) : undefined;
42147
+ const packageDir = packagePrefix ? join34(effectiveRepoRoot, packagePrefix) : undefined;
41883
42148
  const ignoreMatchers = naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(effectiveRepoRoot, packageDir);
41884
42149
  let effectivePrefix = packagePrefix;
41885
42150
  if (packagePrefix && repoRoot) {
@@ -41908,7 +42173,7 @@ async function getChangedTestFiles(workdir, repoRoot, baseRef, packagePrefix, te
41908
42173
  return [];
41909
42174
  const lines = stdout.trim().split(`
41910
42175
  `).filter(Boolean);
41911
- const packageDir = packagePrefix ? join33(repoRoot, packagePrefix) : undefined;
42176
+ const packageDir = packagePrefix ? join34(repoRoot, packagePrefix) : undefined;
41912
42177
  const ignoreMatchers = naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
41913
42178
  const gitRoot = await _gitUtilDeps.getGitRoot(workdir);
41914
42179
  const extraPrefix = gitRoot && gitRoot !== repoRoot ? relative10(gitRoot, repoRoot) : "";
@@ -41916,7 +42181,7 @@ async function getChangedTestFiles(workdir, repoRoot, baseRef, packagePrefix, te
41916
42181
  const scopedRaw = effectivePrefix ? lines.filter((f) => f.startsWith(`${effectivePrefix}/`)) : lines;
41917
42182
  const scoped = filterNaxInternalPaths(scopedRaw, ignoreMatchers);
41918
42183
  const stripped = extraPrefix ? scoped.map((f) => f.slice(`${extraPrefix}/`.length)) : scoped;
41919
- return stripped.filter((f) => testFileRegex.some((re) => re.test(f))).map((f) => join33(repoRoot, f));
42184
+ return stripped.filter((f) => testFileRegex.some((re) => re.test(f))).map((f) => join34(repoRoot, f));
41920
42185
  } catch {
41921
42186
  return [];
41922
42187
  }
@@ -42081,7 +42346,7 @@ var init_scoped = __esm(() => {
42081
42346
  });
42082
42347
 
42083
42348
  // src/quality/command-resolver.ts
42084
- import { join as join34 } from "path";
42349
+ import { join as join35 } from "path";
42085
42350
  async function resolveQualityTestCommands(config2, workdir, storyWorkdir) {
42086
42351
  const rawTestCommand = config2.review?.commands?.test ?? config2.quality?.commands?.test;
42087
42352
  const rawScopedTemplate = config2.quality?.commands?.testScoped;
@@ -42108,7 +42373,7 @@ var init_command_resolver = __esm(() => {
42108
42373
  _commandResolverDeps = {
42109
42374
  readPackageName: async (dir) => {
42110
42375
  try {
42111
- const content = await Bun.file(join34(dir, "package.json")).json();
42376
+ const content = await Bun.file(join35(dir, "package.json")).json();
42112
42377
  return typeof content.name === "string" ? content.name : null;
42113
42378
  } catch {
42114
42379
  return null;
@@ -42197,7 +42462,7 @@ var init_event_bus = __esm(() => {
42197
42462
  });
42198
42463
 
42199
42464
  // src/pipeline/stages/autofix-cycle.ts
42200
- import { join as join35 } from "path";
42465
+ import { join as join36 } from "path";
42201
42466
  function fixCallCtx(ctx) {
42202
42467
  const packageView = ctx.packageView ?? ctx.runtime.packages.repo();
42203
42468
  return {
@@ -42299,7 +42564,7 @@ ${lines.join(`
42299
42564
  }
42300
42565
  async function writeShadowReport(ctx, result, initialFindingsCount) {
42301
42566
  const logger = getLogger();
42302
- const shadowDir = join35(ctx.runtime.outputDir, "cycle-shadow", ctx.story.id);
42567
+ const shadowDir = join36(ctx.runtime.outputDir, "cycle-shadow", ctx.story.id);
42303
42568
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
42304
42569
  const report = {
42305
42570
  storyId: ctx.story.id,
@@ -42311,7 +42576,7 @@ async function writeShadowReport(ctx, result, initialFindingsCount) {
42311
42576
  ...result.exhaustedStrategy ? { exhaustedStrategy: result.exhaustedStrategy } : {}
42312
42577
  };
42313
42578
  try {
42314
- const file3 = join35(shadowDir, `${timestamp}.json`);
42579
+ const file3 = join36(shadowDir, `${timestamp}.json`);
42315
42580
  await Bun.write(file3, JSON.stringify(report, null, 2));
42316
42581
  } catch (err) {
42317
42582
  logger.debug("autofix-cycle", "Shadow report write failed (non-fatal)", {
@@ -44966,16 +45231,16 @@ var init_runner4 = __esm(() => {
44966
45231
 
44967
45232
  // src/review/verdict-writer.ts
44968
45233
  import { mkdir as mkdir6 } from "fs/promises";
44969
- import { join as join36 } from "path";
45234
+ import { join as join37 } from "path";
44970
45235
  async function writeReviewVerdict(entry) {
44971
45236
  const logger = getSafeLogger();
44972
45237
  try {
44973
- const projectDir = await _verdictWriterDeps.findNaxProjectRoot(entry.featureName ? join36(entry.featureName) : ".");
45238
+ const projectDir = await _verdictWriterDeps.findNaxProjectRoot(entry.featureName ? join37(entry.featureName) : ".");
44974
45239
  const baseDir = projectDir ?? ".";
44975
- const verdictDir = entry.featureName ? join36(baseDir, ".nax", "review-verdicts", entry.featureName) : join36(baseDir, ".nax", "review-verdicts", "_unknown");
45240
+ const verdictDir = entry.featureName ? join37(baseDir, ".nax", "review-verdicts", entry.featureName) : join37(baseDir, ".nax", "review-verdicts", "_unknown");
44976
45241
  await _verdictWriterDeps.mkdir(verdictDir, { recursive: true });
44977
45242
  const fileName = `${entry.storyId}.json`;
44978
- const filePath = join36(verdictDir, fileName);
45243
+ const filePath = join37(verdictDir, fileName);
44979
45244
  await _verdictWriterDeps.writeFile(filePath, JSON.stringify(entry, null, 2));
44980
45245
  logger?.debug("review", "Review verdict written", {
44981
45246
  storyId: entry.storyId,
@@ -45000,7 +45265,7 @@ var init_verdict_writer = __esm(() => {
45000
45265
  });
45001
45266
 
45002
45267
  // src/review/orchestrator.ts
45003
- import { join as join37 } from "path";
45268
+ import { join as join38 } from "path";
45004
45269
  var {spawn: spawn4 } = globalThis.Bun;
45005
45270
  async function getChangedFiles2(workdir, baseRef) {
45006
45271
  try {
@@ -45323,7 +45588,7 @@ class ReviewOrchestrator {
45323
45588
  const baseRef = storyGitRef ?? executionConfig?.storyGitRef;
45324
45589
  const changedFiles = await getChangedFiles2(workdir, baseRef);
45325
45590
  const repoRoot = projectDir ?? workdir;
45326
- const packageDir = scopePrefix ? join37(repoRoot, scopePrefix) : undefined;
45591
+ const packageDir = scopePrefix ? join38(repoRoot, scopePrefix) : undefined;
45327
45592
  const ignoreMatchers = naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
45328
45593
  const visibleChangedFiles = filterNaxInternalPaths(changedFiles, ignoreMatchers);
45329
45594
  const scopedFiles = scopePrefix ? visibleChangedFiles.filter((f) => f === scopePrefix || f.startsWith(`${scopePrefix}/`)) : visibleChangedFiles;
@@ -46042,10 +46307,10 @@ var init_effectiveness = __esm(() => {
46042
46307
 
46043
46308
  // src/execution/progress.ts
46044
46309
  import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
46045
- import { join as join38 } from "path";
46310
+ import { join as join39 } from "path";
46046
46311
  async function appendProgress(featureDir, storyId, status, message) {
46047
46312
  await mkdir7(featureDir, { recursive: true });
46048
- const progressPath = join38(featureDir, "progress.txt");
46313
+ const progressPath = join39(featureDir, "progress.txt");
46049
46314
  const timestamp = new Date().toISOString();
46050
46315
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
46051
46316
  `;
@@ -46177,7 +46442,7 @@ var init_completion = __esm(() => {
46177
46442
 
46178
46443
  // src/constitution/loader.ts
46179
46444
  import { existsSync as existsSync19 } from "fs";
46180
- import { join as join39 } from "path";
46445
+ import { join as join40 } from "path";
46181
46446
  function truncateToTokens(text, maxTokens) {
46182
46447
  const maxChars = maxTokens * 3;
46183
46448
  if (text.length <= maxChars) {
@@ -46199,7 +46464,7 @@ async function loadConstitution(projectDir, config2) {
46199
46464
  }
46200
46465
  let combinedContent = "";
46201
46466
  if (!config2.skipGlobal) {
46202
- const globalPath = join39(globalConfigDir(), config2.path);
46467
+ const globalPath = join40(globalConfigDir(), config2.path);
46203
46468
  if (existsSync19(globalPath)) {
46204
46469
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
46205
46470
  const globalFile = Bun.file(validatedPath);
@@ -46209,7 +46474,7 @@ async function loadConstitution(projectDir, config2) {
46209
46474
  }
46210
46475
  }
46211
46476
  }
46212
- const projectPath = join39(projectDir, config2.path);
46477
+ const projectPath = join40(projectDir, config2.path);
46213
46478
  if (existsSync19(projectPath)) {
46214
46479
  const validatedPath = validateFilePath(projectPath, projectDir);
46215
46480
  const projectFile = Bun.file(validatedPath);
@@ -46767,14 +47032,14 @@ async function closeAllRunSessions(sessionManager, agentGetFn) {
46767
47032
 
46768
47033
  // src/context/greenfield.ts
46769
47034
  import { readdir as readdir2 } from "fs/promises";
46770
- import { join as join40 } from "path";
47035
+ import { join as join41 } from "path";
46771
47036
  async function scanForTestFiles(dir, testPatterns, isRootCall = true) {
46772
47037
  const results = [];
46773
47038
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
46774
47039
  try {
46775
47040
  const entries = await readdir2(dir, { withFileTypes: true });
46776
47041
  for (const entry of entries) {
46777
- const fullPath = join40(dir, entry.name);
47042
+ const fullPath = join41(dir, entry.name);
46778
47043
  if (entry.isDirectory()) {
46779
47044
  if (ignoreDirs.has(entry.name))
46780
47045
  continue;
@@ -46900,8 +47165,9 @@ var UNMAPPED_FAILURE_OUTPUT_MAX_LINES = 200, UNMAPPED_FAILURE_OUTPUT_MAX_CHARS =
46900
47165
  // src/tdd/rectification-gate.ts
46901
47166
  async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, sessionManager, sessionId, runtime) {
46902
47167
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
46903
- if (!rectificationEnabled)
46904
- return { passed: false, cost: 0, fullSuiteGatePassed: false };
47168
+ if (!rectificationEnabled) {
47169
+ return { passed: false, cost: 0, fullSuiteGatePassed: false, status: "disabled" };
47170
+ }
46905
47171
  const rectificationConfig = config2.execution.rectification;
46906
47172
  const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
46907
47173
  const { testCommand: resolvedTestCmd } = await _rectificationGateDeps.resolveTestCommands(config2, workdir, story.workdir);
@@ -46923,7 +47189,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
46923
47189
  outputLength: fullSuiteResult.output.length,
46924
47190
  outputTail: fullSuiteResult.output.slice(-200)
46925
47191
  });
46926
- return { passed: true, cost: 0, fullSuiteGatePassed: false };
47192
+ return { passed: true, cost: 0, fullSuiteGatePassed: false, status: "deferred-unattributable" };
46927
47193
  }
46928
47194
  return await runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, effectiveTestCmd, fullSuiteTimeout, fullSuiteResult.output, featureName, projectDir, sessionManager, sessionId, runtime);
46929
47195
  }
@@ -46933,7 +47199,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
46933
47199
  exitCode: fullSuiteResult.exitCode,
46934
47200
  passedTests: testSummary.passed
46935
47201
  });
46936
- return { passed: true, cost: 0, fullSuiteGatePassed: true };
47202
+ return { passed: true, cost: 0, fullSuiteGatePassed: true, status: "passed-with-nonzero-exit" };
46937
47203
  }
46938
47204
  logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
46939
47205
  storyId: story.id,
@@ -46941,17 +47207,17 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
46941
47207
  outputLength: fullSuiteResult.output.length,
46942
47208
  outputTail: fullSuiteResult.output.slice(-200)
46943
47209
  });
46944
- return { passed: false, cost: 0, fullSuiteGatePassed: false };
47210
+ return { passed: false, cost: 0, fullSuiteGatePassed: false, status: "inconclusive" };
46945
47211
  }
46946
47212
  if (fullSuitePassed) {
46947
47213
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
46948
- return { passed: true, cost: 0, fullSuiteGatePassed: true };
47214
+ return { passed: true, cost: 0, fullSuiteGatePassed: true, status: "passed" };
46949
47215
  }
46950
47216
  logger.warn("tdd", "Full suite gate execution failed (no output)", {
46951
47217
  storyId: story.id,
46952
47218
  exitCode: fullSuiteResult.exitCode
46953
47219
  });
46954
- return { passed: false, cost: 0, fullSuiteGatePassed: false };
47220
+ return { passed: false, cost: 0, fullSuiteGatePassed: false, status: "execution-failed" };
46955
47221
  }
46956
47222
  async function runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, testOutput, featureName, projectDir, sessionManager, sessionId, runtime) {
46957
47223
  logger.warn("tdd", "Full suite gate detected regressions", {
@@ -47125,13 +47391,19 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
47125
47391
  });
47126
47392
  const fixed = outcome.outcome === "fixed";
47127
47393
  if (fixed) {
47128
- return { passed: true, cost: gateCostAccum, fullSuiteGatePassed: true };
47394
+ return { passed: true, cost: gateCostAccum, fullSuiteGatePassed: true, status: "passed" };
47129
47395
  }
47130
47396
  logger.warn("tdd", "[WARN] Full suite gate failed after rectification exhausted", {
47131
47397
  storyId: story.id,
47132
47398
  attempts: outcome.attempts
47133
47399
  });
47134
- return { passed: false, cost: gateCostAccum, fullSuiteGatePassed: false };
47400
+ return {
47401
+ passed: false,
47402
+ cost: gateCostAccum,
47403
+ fullSuiteGatePassed: false,
47404
+ status: "rectification-exhausted",
47405
+ attempts: outcome.attempts
47406
+ };
47135
47407
  }
47136
47408
  var _rectificationGateDeps;
47137
47409
  var init_rectification_gate = __esm(() => {
@@ -47422,6 +47694,24 @@ var init_verdict = __esm(() => {
47422
47694
  });
47423
47695
 
47424
47696
  // src/tdd/orchestrator.ts
47697
+ async function rollbackTddFailureIfNeeded(shouldRollback, workdir, initialRef, storyId, failureCategory) {
47698
+ if (!shouldRollback) {
47699
+ return;
47700
+ }
47701
+ const logger = getLogger();
47702
+ try {
47703
+ await rollbackToRef(workdir, initialRef);
47704
+ logger.info("tdd", "Rolled back git changes due to TDD failure", {
47705
+ storyId,
47706
+ failureCategory
47707
+ });
47708
+ } catch (error48) {
47709
+ logger.error("tdd", "Failed to rollback git changes after TDD failure", {
47710
+ storyId,
47711
+ error: errorMessage(error48)
47712
+ });
47713
+ }
47714
+ }
47425
47715
  async function runThreeSessionTdd(options) {
47426
47716
  const {
47427
47717
  agent,
@@ -47582,7 +47872,33 @@ async function runThreeSessionTdd(options) {
47582
47872
  };
47583
47873
  }
47584
47874
  const implementerBinding = getTddSessionBinding?.("implementer");
47585
- const { cost: fullSuiteGateCost, fullSuiteGatePassed } = await runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, implementerBinding?.sessionManager, implementerBinding?.sessionId, runtime);
47875
+ const fullSuiteGate = await runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, implementerBinding?.sessionManager, implementerBinding?.sessionId, runtime);
47876
+ const { cost: fullSuiteGateCost, fullSuiteGatePassed } = fullSuiteGate;
47877
+ if (fullSuiteGate.status === "rectification-exhausted") {
47878
+ const failureCategory = "full-suite-gate-exhausted";
47879
+ const totalCost2 = sessions.reduce((sum, s) => sum + s.estimatedCostUsd, 0) + fullSuiteGateCost;
47880
+ const totalDurationMs2 = sessions.reduce((sum, s) => sum + s.durationMs, 0);
47881
+ const totalTokenUsage2 = sumTddTokenUsage(sessions);
47882
+ const terminalReviewReason = "Full suite gate failed after rectification exhausted";
47883
+ logger.warn("tdd", "Stopping before verifier because full-suite gate rectification exhausted", {
47884
+ storyId: story.id,
47885
+ attempts: fullSuiteGate.attempts,
47886
+ failureCategory
47887
+ });
47888
+ await rollbackTddFailureIfNeeded(shouldRollbackOnFailure, workdir, initialRef, story.id, failureCategory);
47889
+ return {
47890
+ success: false,
47891
+ sessions,
47892
+ needsHumanReview: true,
47893
+ reviewReason: terminalReviewReason,
47894
+ failureCategory,
47895
+ totalCost: totalCost2,
47896
+ totalDurationMs: totalDurationMs2,
47897
+ ...totalTokenUsage2 && { totalTokenUsage: totalTokenUsage2 },
47898
+ lite,
47899
+ fullSuiteGatePassed
47900
+ };
47901
+ }
47586
47902
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
47587
47903
  const verifierBundle = await getTddContextBundle?.("verifier") ?? tddContextBundles?.verifier;
47588
47904
  const session3 = await runTddSessionOp(verifyTddOp, options, session3Ref, verifierBundle, getTddSessionBinding?.("verifier"));
@@ -47658,20 +47974,7 @@ async function runThreeSessionTdd(options) {
47658
47974
  lite,
47659
47975
  verdictAvailable: verdict !== null
47660
47976
  });
47661
- if (!allSuccessful && shouldRollbackOnFailure) {
47662
- try {
47663
- await rollbackToRef(workdir, initialRef);
47664
- logger.info("tdd", "Rolled back git changes due to TDD failure", {
47665
- storyId: story.id,
47666
- failureCategory: finalFailureCategory
47667
- });
47668
- } catch (error48) {
47669
- logger.error("tdd", "Failed to rollback git changes after TDD failure", {
47670
- storyId: story.id,
47671
- error: errorMessage(error48)
47672
- });
47673
- }
47674
- }
47977
+ await rollbackTddFailureIfNeeded(shouldRollbackOnFailure && !allSuccessful, workdir, initialRef, story.id, finalFailureCategory);
47675
47978
  return {
47676
47979
  success: allSuccessful,
47677
47980
  sessions,
@@ -47881,7 +48184,7 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
47881
48184
  }
47882
48185
  return { action: "escalate" };
47883
48186
  }
47884
- if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
48187
+ if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "full-suite-gate-exhausted" || failureCategory === "verifier-rejected") {
47885
48188
  return { action: "escalate" };
47886
48189
  }
47887
48190
  if (failureCategory === "greenfield-no-tests") {
@@ -49804,7 +50107,7 @@ function buildFrontmatter(story, ctx, role) {
49804
50107
  }
49805
50108
 
49806
50109
  // src/cli/prompts-tdd.ts
49807
- import { join as join41 } from "path";
50110
+ import { join as join42 } from "path";
49808
50111
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
49809
50112
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
49810
50113
  TddPromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
@@ -49823,7 +50126,7 @@ ${frontmatter}---
49823
50126
 
49824
50127
  ${session.prompt}`;
49825
50128
  if (outputDir) {
49826
- const promptFile = join41(outputDir, `${story.id}.${session.role}.md`);
50129
+ const promptFile = join42(outputDir, `${story.id}.${session.role}.md`);
49827
50130
  await Bun.write(promptFile, fullOutput);
49828
50131
  logger.info("cli", "Written TDD prompt file", {
49829
50132
  storyId: story.id,
@@ -49839,7 +50142,7 @@ ${"=".repeat(80)}`);
49839
50142
  }
49840
50143
  }
49841
50144
  if (outputDir && ctx.contextMarkdown) {
49842
- const contextFile = join41(outputDir, `${story.id}.context.md`);
50145
+ const contextFile = join42(outputDir, `${story.id}.context.md`);
49843
50146
  const frontmatter = buildFrontmatter(story, ctx);
49844
50147
  const contextOutput = `---
49845
50148
  ${frontmatter}---
@@ -49854,16 +50157,16 @@ var init_prompts_tdd = __esm(() => {
49854
50157
 
49855
50158
  // src/cli/prompts-main.ts
49856
50159
  import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
49857
- import { join as join42 } from "path";
50160
+ import { join as join43 } from "path";
49858
50161
  async function promptsCommand(options) {
49859
50162
  const logger = getLogger();
49860
50163
  const { feature, workdir, config: config2, storyId, outputDir } = options;
49861
- const naxDir = join42(workdir, ".nax");
50164
+ const naxDir = join43(workdir, ".nax");
49862
50165
  if (!existsSync20(naxDir)) {
49863
50166
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
49864
50167
  }
49865
- const featureDir = join42(naxDir, "features", feature);
49866
- const prdPath = join42(featureDir, "prd.json");
50168
+ const featureDir = join43(naxDir, "features", feature);
50169
+ const prdPath = join43(featureDir, "prd.json");
49867
50170
  if (!existsSync20(prdPath)) {
49868
50171
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
49869
50172
  }
@@ -49930,10 +50233,10 @@ ${frontmatter}---
49930
50233
 
49931
50234
  ${ctx.prompt}`;
49932
50235
  if (outputDir) {
49933
- const promptFile = join42(outputDir, `${story.id}.prompt.md`);
50236
+ const promptFile = join43(outputDir, `${story.id}.prompt.md`);
49934
50237
  await Bun.write(promptFile, fullOutput);
49935
50238
  if (ctx.contextMarkdown) {
49936
- const contextFile = join42(outputDir, `${story.id}.context.md`);
50239
+ const contextFile = join43(outputDir, `${story.id}.context.md`);
49937
50240
  const contextOutput = `---
49938
50241
  ${frontmatter}---
49939
50242
 
@@ -49969,12 +50272,12 @@ var init_prompts_main = __esm(() => {
49969
50272
 
49970
50273
  // src/cli/prompts-init.ts
49971
50274
  import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
49972
- import { join as join43 } from "path";
50275
+ import { join as join44 } from "path";
49973
50276
  async function promptsInitCommand(options) {
49974
50277
  const { workdir, force = false, autoWireConfig = true } = options;
49975
- const templatesDir = join43(workdir, ".nax", "templates");
50278
+ const templatesDir = join44(workdir, ".nax", "templates");
49976
50279
  mkdirSync4(templatesDir, { recursive: true });
49977
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join43(templatesDir, f)));
50280
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join44(templatesDir, f)));
49978
50281
  if (existingFiles.length > 0 && !force) {
49979
50282
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
49980
50283
  Pass --force to overwrite existing templates.`);
@@ -49982,7 +50285,7 @@ async function promptsInitCommand(options) {
49982
50285
  }
49983
50286
  const written = [];
49984
50287
  for (const template of TEMPLATE_ROLES) {
49985
- const filePath = join43(templatesDir, template.file);
50288
+ const filePath = join44(templatesDir, template.file);
49986
50289
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
49987
50290
  const content = TEMPLATE_HEADER + roleBody;
49988
50291
  await Bun.write(filePath, content);
@@ -49998,7 +50301,7 @@ async function promptsInitCommand(options) {
49998
50301
  return written;
49999
50302
  }
50000
50303
  async function autoWirePromptsConfig(workdir) {
50001
- const configPath = join43(workdir, "nax.config.json");
50304
+ const configPath = join44(workdir, "nax.config.json");
50002
50305
  if (!existsSync21(configPath)) {
50003
50306
  const exampleConfig = JSON.stringify({
50004
50307
  prompts: {
@@ -50162,7 +50465,7 @@ __export(exports_init_context, {
50162
50465
  });
50163
50466
  import { existsSync as existsSync22 } from "fs";
50164
50467
  import { mkdir as mkdir8 } from "fs/promises";
50165
- import { basename as basename9, join as join44 } from "path";
50468
+ import { basename as basename9, join as join45 } from "path";
50166
50469
  async function findFiles(dir, maxFiles = 200) {
50167
50470
  try {
50168
50471
  const proc = Bun.spawnSync([
@@ -50190,7 +50493,7 @@ async function findFiles(dir, maxFiles = 200) {
50190
50493
  return [];
50191
50494
  }
50192
50495
  async function readPackageManifest(projectRoot) {
50193
- const packageJsonPath = join44(projectRoot, "package.json");
50496
+ const packageJsonPath = join45(projectRoot, "package.json");
50194
50497
  if (!existsSync22(packageJsonPath)) {
50195
50498
  return null;
50196
50499
  }
@@ -50208,7 +50511,7 @@ async function readPackageManifest(projectRoot) {
50208
50511
  }
50209
50512
  }
50210
50513
  async function readReadmeSnippet(projectRoot) {
50211
- const readmePath = join44(projectRoot, "README.md");
50514
+ const readmePath = join45(projectRoot, "README.md");
50212
50515
  if (!existsSync22(readmePath)) {
50213
50516
  return null;
50214
50517
  }
@@ -50226,7 +50529,7 @@ async function detectEntryPoints(projectRoot) {
50226
50529
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
50227
50530
  const found = [];
50228
50531
  for (const candidate of candidates) {
50229
- const path14 = join44(projectRoot, candidate);
50532
+ const path14 = join45(projectRoot, candidate);
50230
50533
  if (existsSync22(path14)) {
50231
50534
  found.push(candidate);
50232
50535
  }
@@ -50237,7 +50540,7 @@ async function detectConfigFiles(projectRoot) {
50237
50540
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
50238
50541
  const found = [];
50239
50542
  for (const candidate of candidates) {
50240
- const path14 = join44(projectRoot, candidate);
50543
+ const path14 = join45(projectRoot, candidate);
50241
50544
  if (existsSync22(path14)) {
50242
50545
  found.push(candidate);
50243
50546
  }
@@ -50398,8 +50701,8 @@ function generatePackageContextTemplate(packagePath) {
50398
50701
  }
50399
50702
  async function initPackage(repoRoot, packagePath, force = false) {
50400
50703
  const logger = getLogger();
50401
- const naxDir = join44(repoRoot, ".nax", "mono", packagePath);
50402
- const contextPath = join44(naxDir, "context.md");
50704
+ const naxDir = join45(repoRoot, ".nax", "mono", packagePath);
50705
+ const contextPath = join45(naxDir, "context.md");
50403
50706
  if (existsSync22(contextPath) && !force) {
50404
50707
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
50405
50708
  return;
@@ -50413,8 +50716,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
50413
50716
  }
50414
50717
  async function initContext(projectRoot, options = {}) {
50415
50718
  const logger = getLogger();
50416
- const naxDir = join44(projectRoot, ".nax");
50417
- const contextPath = join44(naxDir, "context.md");
50719
+ const naxDir = join45(projectRoot, ".nax");
50720
+ const contextPath = join45(naxDir, "context.md");
50418
50721
  if (existsSync22(contextPath) && !options.force) {
50419
50722
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
50420
50723
  return;
@@ -50444,9 +50747,9 @@ var init_init_context = __esm(() => {
50444
50747
 
50445
50748
  // src/cli/init-detect.ts
50446
50749
  import { existsSync as existsSync23, readFileSync } from "fs";
50447
- import { join as join45 } from "path";
50750
+ import { join as join46 } from "path";
50448
50751
  function readPackageJson(projectRoot) {
50449
- const pkgPath = join45(projectRoot, "package.json");
50752
+ const pkgPath = join46(projectRoot, "package.json");
50450
50753
  if (!existsSync23(pkgPath))
50451
50754
  return;
50452
50755
  try {
@@ -50489,41 +50792,41 @@ function detectStack(projectRoot) {
50489
50792
  };
50490
50793
  }
50491
50794
  function detectRuntime(projectRoot) {
50492
- if (existsSync23(join45(projectRoot, "bun.lockb")) || existsSync23(join45(projectRoot, "bunfig.toml"))) {
50795
+ if (existsSync23(join46(projectRoot, "bun.lockb")) || existsSync23(join46(projectRoot, "bunfig.toml"))) {
50493
50796
  return "bun";
50494
50797
  }
50495
- if (existsSync23(join45(projectRoot, "package-lock.json")) || existsSync23(join45(projectRoot, "yarn.lock")) || existsSync23(join45(projectRoot, "pnpm-lock.yaml"))) {
50798
+ if (existsSync23(join46(projectRoot, "package-lock.json")) || existsSync23(join46(projectRoot, "yarn.lock")) || existsSync23(join46(projectRoot, "pnpm-lock.yaml"))) {
50496
50799
  return "node";
50497
50800
  }
50498
50801
  return "unknown";
50499
50802
  }
50500
- function detectLanguage(projectRoot) {
50501
- if (existsSync23(join45(projectRoot, "tsconfig.json")))
50803
+ function detectLanguage2(projectRoot) {
50804
+ if (existsSync23(join46(projectRoot, "tsconfig.json")))
50502
50805
  return "typescript";
50503
- if (existsSync23(join45(projectRoot, "pyproject.toml")) || existsSync23(join45(projectRoot, "setup.py"))) {
50806
+ if (existsSync23(join46(projectRoot, "pyproject.toml")) || existsSync23(join46(projectRoot, "setup.py"))) {
50504
50807
  return "python";
50505
50808
  }
50506
- if (existsSync23(join45(projectRoot, "Cargo.toml")))
50809
+ if (existsSync23(join46(projectRoot, "Cargo.toml")))
50507
50810
  return "rust";
50508
- if (existsSync23(join45(projectRoot, "go.mod")))
50811
+ if (existsSync23(join46(projectRoot, "go.mod")))
50509
50812
  return "go";
50510
50813
  return "unknown";
50511
50814
  }
50512
50815
  function detectLinter(projectRoot) {
50513
- if (existsSync23(join45(projectRoot, "biome.json")) || existsSync23(join45(projectRoot, "biome.jsonc"))) {
50816
+ if (existsSync23(join46(projectRoot, "biome.json")) || existsSync23(join46(projectRoot, "biome.jsonc"))) {
50514
50817
  return "biome";
50515
50818
  }
50516
- if (existsSync23(join45(projectRoot, ".eslintrc.json")) || existsSync23(join45(projectRoot, ".eslintrc.js")) || existsSync23(join45(projectRoot, "eslint.config.js"))) {
50819
+ if (existsSync23(join46(projectRoot, ".eslintrc.json")) || existsSync23(join46(projectRoot, ".eslintrc.js")) || existsSync23(join46(projectRoot, "eslint.config.js"))) {
50517
50820
  return "eslint";
50518
50821
  }
50519
50822
  return "unknown";
50520
50823
  }
50521
50824
  function detectMonorepo(projectRoot) {
50522
- if (existsSync23(join45(projectRoot, "turbo.json")))
50825
+ if (existsSync23(join46(projectRoot, "turbo.json")))
50523
50826
  return "turborepo";
50524
- if (existsSync23(join45(projectRoot, "nx.json")))
50827
+ if (existsSync23(join46(projectRoot, "nx.json")))
50525
50828
  return "nx";
50526
- if (existsSync23(join45(projectRoot, "pnpm-workspace.yaml")))
50829
+ if (existsSync23(join46(projectRoot, "pnpm-workspace.yaml")))
50527
50830
  return "pnpm-workspaces";
50528
50831
  const pkg = readPackageJson(projectRoot);
50529
50832
  if (pkg?.workspaces)
@@ -50533,7 +50836,7 @@ function detectMonorepo(projectRoot) {
50533
50836
  function detectProjectStack(projectRoot) {
50534
50837
  return {
50535
50838
  runtime: detectRuntime(projectRoot),
50536
- language: detectLanguage(projectRoot),
50839
+ language: detectLanguage2(projectRoot),
50537
50840
  linter: detectLinter(projectRoot),
50538
50841
  monorepo: detectMonorepo(projectRoot)
50539
50842
  };
@@ -50666,7 +50969,7 @@ __export(exports_init, {
50666
50969
  });
50667
50970
  import { existsSync as existsSync24 } from "fs";
50668
50971
  import { mkdir as mkdir9 } from "fs/promises";
50669
- import { join as join46 } from "path";
50972
+ import { join as join47 } from "path";
50670
50973
  function validateProjectName(name) {
50671
50974
  if (!name)
50672
50975
  return { valid: false, error: "name must be non-empty" };
@@ -50702,7 +51005,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
50702
51005
  }
50703
51006
  async function updateGitignore(projectRoot) {
50704
51007
  const logger = getLogger();
50705
- const gitignorePath = join46(projectRoot, ".gitignore");
51008
+ const gitignorePath = join47(projectRoot, ".gitignore");
50706
51009
  let existing = "";
50707
51010
  if (existsSync24(gitignorePath)) {
50708
51011
  existing = await Bun.file(gitignorePath).text();
@@ -50788,7 +51091,7 @@ async function initGlobal() {
50788
51091
  await mkdir9(globalDir, { recursive: true });
50789
51092
  logger.info("init", "Created global config directory", { path: globalDir });
50790
51093
  }
50791
- const configPath = join46(globalDir, "config.json");
51094
+ const configPath = join47(globalDir, "config.json");
50792
51095
  if (!existsSync24(configPath)) {
50793
51096
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
50794
51097
  `);
@@ -50796,14 +51099,14 @@ async function initGlobal() {
50796
51099
  } else {
50797
51100
  logger.info("init", "Global config already exists", { path: configPath });
50798
51101
  }
50799
- const constitutionPath = join46(globalDir, "constitution.md");
51102
+ const constitutionPath = join47(globalDir, "constitution.md");
50800
51103
  if (!existsSync24(constitutionPath)) {
50801
51104
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
50802
51105
  logger.info("init", "Created global constitution", { path: constitutionPath });
50803
51106
  } else {
50804
51107
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
50805
51108
  }
50806
- const hooksDir = join46(globalDir, "hooks");
51109
+ const hooksDir = join47(globalDir, "hooks");
50807
51110
  if (!existsSync24(hooksDir)) {
50808
51111
  await mkdir9(hooksDir, { recursive: true });
50809
51112
  logger.info("init", "Created global hooks directory", { path: hooksDir });
@@ -50836,7 +51139,7 @@ async function initProject(projectRoot, options) {
50836
51139
  if (detectedName && !options?.force) {
50837
51140
  const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
50838
51141
  if (collision.collision && collision.existing) {
50839
- const configPath2 = join46(projectDir, "config.json");
51142
+ const configPath2 = join47(projectDir, "config.json");
50840
51143
  throw new NaxError([
50841
51144
  `Project name collision: "${detectedName}"`,
50842
51145
  ` This project: ${projectRoot}`,
@@ -50864,7 +51167,7 @@ async function initProject(projectRoot, options) {
50864
51167
  linter: stack.linter,
50865
51168
  monorepo: stack.monorepo
50866
51169
  });
50867
- const configPath = join46(projectDir, "config.json");
51170
+ const configPath = join47(projectDir, "config.json");
50868
51171
  if (!existsSync24(configPath)) {
50869
51172
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
50870
51173
  `);
@@ -50873,14 +51176,14 @@ async function initProject(projectRoot, options) {
50873
51176
  logger.info("init", "Project config already exists", { path: configPath });
50874
51177
  }
50875
51178
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
50876
- const constitutionPath = join46(projectDir, "constitution.md");
51179
+ const constitutionPath = join47(projectDir, "constitution.md");
50877
51180
  if (!existsSync24(constitutionPath) || options?.force) {
50878
51181
  await Bun.write(constitutionPath, buildConstitution(stack));
50879
51182
  logger.info("init", "Created project constitution", { path: constitutionPath });
50880
51183
  } else {
50881
51184
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
50882
51185
  }
50883
- const hooksDir = join46(projectDir, "hooks");
51186
+ const hooksDir = join47(projectDir, "hooks");
50884
51187
  if (!existsSync24(hooksDir)) {
50885
51188
  await mkdir9(hooksDir, { recursive: true });
50886
51189
  logger.info("init", "Created project hooks directory", { path: hooksDir });
@@ -52342,19 +52645,19 @@ var init_command_argv = __esm(() => {
52342
52645
  });
52343
52646
 
52344
52647
  // src/hooks/runner.ts
52345
- import { join as join65 } from "path";
52648
+ import { join as join66 } from "path";
52346
52649
  async function loadHooksConfig(projectDir, globalDir) {
52347
52650
  let globalHooks = { hooks: {} };
52348
52651
  let projectHooks = { hooks: {} };
52349
52652
  let skipGlobal = false;
52350
- const projectPath = join65(projectDir, "hooks.json");
52653
+ const projectPath = join66(projectDir, "hooks.json");
52351
52654
  const projectData = await loadJsonFile(projectPath, "hooks");
52352
52655
  if (projectData) {
52353
52656
  projectHooks = projectData;
52354
52657
  skipGlobal = projectData.skipGlobal ?? false;
52355
52658
  }
52356
52659
  if (!skipGlobal && globalDir) {
52357
- const globalPath = join65(globalDir, "hooks.json");
52660
+ const globalPath = join66(globalDir, "hooks.json");
52358
52661
  const globalData = await loadJsonFile(globalPath, "hooks");
52359
52662
  if (globalData) {
52360
52663
  globalHooks = globalData;
@@ -52509,7 +52812,7 @@ var package_default;
52509
52812
  var init_package = __esm(() => {
52510
52813
  package_default = {
52511
52814
  name: "@nathapp/nax",
52512
- version: "0.65.0-canary.1",
52815
+ version: "0.65.0-canary.2",
52513
52816
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
52514
52817
  type: "module",
52515
52818
  bin: {
@@ -52594,8 +52897,8 @@ var init_version = __esm(() => {
52594
52897
  NAX_VERSION = package_default.version;
52595
52898
  NAX_COMMIT = (() => {
52596
52899
  try {
52597
- if (/^[0-9a-f]{6,10}$/.test("ed680ba9"))
52598
- return "ed680ba9";
52900
+ if (/^[0-9a-f]{6,10}$/.test("32aecf38"))
52901
+ return "32aecf38";
52599
52902
  } catch {}
52600
52903
  try {
52601
52904
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -53470,15 +53773,15 @@ var init_acceptance_loop = __esm(() => {
53470
53773
 
53471
53774
  // src/session/scratch-purge.ts
53472
53775
  import { mkdir as mkdir13, rename, rm } from "fs/promises";
53473
- import { dirname as dirname11, join as join66 } from "path";
53776
+ import { dirname as dirname11, join as join67 } from "path";
53474
53777
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
53475
- const sessionsDir = join66(projectDir, ".nax", "features", featureName, "sessions");
53778
+ const sessionsDir = join67(projectDir, ".nax", "features", featureName, "sessions");
53476
53779
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
53477
53780
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
53478
53781
  let purged = 0;
53479
53782
  for (const sessionId of sessionIds) {
53480
- const sessionDir = join66(sessionsDir, sessionId);
53481
- const descriptorPath = join66(sessionDir, "descriptor.json");
53783
+ const sessionDir = join67(sessionsDir, sessionId);
53784
+ const descriptorPath = join67(sessionDir, "descriptor.json");
53482
53785
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
53483
53786
  continue;
53484
53787
  let lastActivityAt;
@@ -53494,7 +53797,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
53494
53797
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
53495
53798
  continue;
53496
53799
  if (archiveInsteadOfDelete) {
53497
- const archiveDest = join66(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
53800
+ const archiveDest = join67(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
53498
53801
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
53499
53802
  } else {
53500
53803
  await _scratchPurgeDeps.remove(sessionDir);
@@ -54205,12 +54508,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
54205
54508
  // src/pipeline/subscribers/events-writer.ts
54206
54509
  import { appendFile as appendFile5, mkdir as mkdir14 } from "fs/promises";
54207
54510
  import { homedir as homedir5 } from "os";
54208
- import { basename as basename14, join as join67 } from "path";
54511
+ import { basename as basename14, join as join68 } from "path";
54209
54512
  function wireEventsWriter(bus, feature, runId, workdir) {
54210
54513
  const logger = getSafeLogger();
54211
54514
  const project = basename14(workdir);
54212
- const eventsDir = join67(homedir5(), ".nax", "events", project);
54213
- const eventsFile = join67(eventsDir, "events.jsonl");
54515
+ const eventsDir = join68(homedir5(), ".nax", "events", project);
54516
+ const eventsFile = join68(eventsDir, "events.jsonl");
54214
54517
  let dirReady = false;
54215
54518
  const write = (line) => {
54216
54519
  return (async () => {
@@ -54391,12 +54694,12 @@ var init_interaction2 = __esm(() => {
54391
54694
  // src/pipeline/subscribers/registry.ts
54392
54695
  import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
54393
54696
  import { homedir as homedir6 } from "os";
54394
- import { basename as basename15, join as join68 } from "path";
54697
+ import { basename as basename15, join as join69 } from "path";
54395
54698
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
54396
54699
  const logger = getSafeLogger();
54397
54700
  const project = basename15(workdir);
54398
- const runDir = join68(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
54399
- const metaFile = join68(runDir, "meta.json");
54701
+ const runDir = join69(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
54702
+ const metaFile = join69(runDir, "meta.json");
54400
54703
  const unsub = bus.on("run:started", (_ev) => {
54401
54704
  return (async () => {
54402
54705
  try {
@@ -54406,8 +54709,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
54406
54709
  project,
54407
54710
  feature,
54408
54711
  workdir,
54409
- statusPath: join68(outputDir, "features", feature, "status.json"),
54410
- eventsDir: join68(outputDir, "features", feature, "runs"),
54712
+ statusPath: join69(outputDir, "features", feature, "status.json"),
54713
+ eventsDir: join69(outputDir, "features", feature, "runs"),
54411
54714
  registeredAt: new Date().toISOString()
54412
54715
  };
54413
54716
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -54655,7 +54958,7 @@ var init_types8 = __esm(() => {
54655
54958
 
54656
54959
  // src/worktree/dependencies.ts
54657
54960
  import { existsSync as existsSync32 } from "fs";
54658
- import { join as join69 } from "path";
54961
+ import { join as join70 } from "path";
54659
54962
  async function prepareWorktreeDependencies(options) {
54660
54963
  const mode = options.config.execution.worktreeDependencies.mode;
54661
54964
  const resolvedCwd = resolveDependencyCwd(options);
@@ -54669,7 +54972,7 @@ async function prepareWorktreeDependencies(options) {
54669
54972
  }
54670
54973
  }
54671
54974
  function resolveDependencyCwd(options) {
54672
- return options.storyWorkdir ? join69(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
54975
+ return options.storyWorkdir ? join70(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
54673
54976
  }
54674
54977
  function resolveInheritedDependencies(options, resolvedCwd) {
54675
54978
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -54679,7 +54982,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
54679
54982
  }
54680
54983
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
54681
54984
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
54682
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join69(directory, filename))));
54985
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join70(directory, filename))));
54683
54986
  }
54684
54987
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
54685
54988
  const setupCommand = config2.execution.worktreeDependencies.setupCommand;
@@ -54743,13 +55046,13 @@ __export(exports_manager, {
54743
55046
  });
54744
55047
  import { existsSync as existsSync33, symlinkSync } from "fs";
54745
55048
  import { mkdir as mkdir16 } from "fs/promises";
54746
- import { join as join70 } from "path";
55049
+ import { join as join71 } from "path";
54747
55050
 
54748
55051
  class WorktreeManager {
54749
55052
  async ensureGitExcludes(projectRoot) {
54750
55053
  const logger = getSafeLogger();
54751
- const infoDir = join70(projectRoot, ".git", "info");
54752
- const excludePath = join70(infoDir, "exclude");
55054
+ const infoDir = join71(projectRoot, ".git", "info");
55055
+ const excludePath = join71(infoDir, "exclude");
54753
55056
  try {
54754
55057
  await mkdir16(infoDir, { recursive: true });
54755
55058
  let existing = "";
@@ -54776,7 +55079,7 @@ ${missing.join(`
54776
55079
  }
54777
55080
  async create(projectRoot, storyId) {
54778
55081
  validateStoryId(storyId);
54779
- const worktreePath = join70(projectRoot, ".nax-wt", storyId);
55082
+ const worktreePath = join71(projectRoot, ".nax-wt", storyId);
54780
55083
  const branchName = `nax/${storyId}`;
54781
55084
  try {
54782
55085
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -54817,9 +55120,9 @@ ${missing.join(`
54817
55120
  }
54818
55121
  throw new Error(`Failed to create worktree: ${String(error48)}`);
54819
55122
  }
54820
- const envSource = join70(projectRoot, ".env");
55123
+ const envSource = join71(projectRoot, ".env");
54821
55124
  if (existsSync33(envSource)) {
54822
- const envTarget = join70(worktreePath, ".env");
55125
+ const envTarget = join71(worktreePath, ".env");
54823
55126
  try {
54824
55127
  symlinkSync(envSource, envTarget, "file");
54825
55128
  } catch (error48) {
@@ -54830,7 +55133,7 @@ ${missing.join(`
54830
55133
  }
54831
55134
  async remove(projectRoot, storyId) {
54832
55135
  validateStoryId(storyId);
54833
- const worktreePath = join70(projectRoot, ".nax-wt", storyId);
55136
+ const worktreePath = join71(projectRoot, ".nax-wt", storyId);
54834
55137
  const branchName = `nax/${storyId}`;
54835
55138
  try {
54836
55139
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -55321,6 +55624,7 @@ function resolveMaxAttemptsOutcome(failureCategory) {
55321
55624
  return "pause";
55322
55625
  case "session-failure":
55323
55626
  case "tests-failing":
55627
+ case "full-suite-gate-exhausted":
55324
55628
  case "dependency-prep":
55325
55629
  return "fail";
55326
55630
  default:
@@ -55542,10 +55846,10 @@ var init_merge_conflict_rectify = __esm(() => {
55542
55846
  });
55543
55847
 
55544
55848
  // src/execution/pipeline-result-handler.ts
55545
- import { join as join71 } from "path";
55849
+ import { join as join72 } from "path";
55546
55850
  async function removeWorktreeDirectory(projectRoot, storyId) {
55547
55851
  const logger = getSafeLogger();
55548
- const worktreePath = join71(projectRoot, ".nax-wt", storyId);
55852
+ const worktreePath = join72(projectRoot, ".nax-wt", storyId);
55549
55853
  try {
55550
55854
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
55551
55855
  cwd: projectRoot,
@@ -55756,7 +56060,7 @@ var init_pipeline_result_handler = __esm(() => {
55756
56060
 
55757
56061
  // src/execution/iteration-runner.ts
55758
56062
  import { existsSync as existsSync34 } from "fs";
55759
- import { join as join72 } from "path";
56063
+ import { join as join73 } from "path";
55760
56064
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
55761
56065
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
55762
56066
  if (ctx.dryRun) {
@@ -55781,7 +56085,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
55781
56085
  const storyStartTime = Date.now();
55782
56086
  let effectiveWorkdir = ctx.workdir;
55783
56087
  if (ctx.config.execution.storyIsolation === "worktree") {
55784
- const worktreePath = join72(ctx.workdir, ".nax-wt", story.id);
56088
+ const worktreePath = join73(ctx.workdir, ".nax-wt", story.id);
55785
56089
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
55786
56090
  if (!worktreeExists) {
55787
56091
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -55801,7 +56105,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
55801
56105
  }
55802
56106
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
55803
56107
  const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
55804
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join72(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
56108
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join73(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
55805
56109
  let dependencyContext;
55806
56110
  if (ctx.config.execution.storyIsolation === "worktree") {
55807
56111
  try {
@@ -55828,7 +56132,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
55828
56132
  };
55829
56133
  }
55830
56134
  }
55831
- const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join72(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join72(ctx.workdir, story.workdir) : ctx.workdir;
56135
+ const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join73(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join73(ctx.workdir, story.workdir) : ctx.workdir;
55832
56136
  const pipelineContext = {
55833
56137
  config: effectiveConfig,
55834
56138
  rootConfig: ctx.config,
@@ -56025,7 +56329,7 @@ __export(exports_parallel_worker, {
56025
56329
  executeParallelBatch: () => executeParallelBatch,
56026
56330
  _parallelWorkerDeps: () => _parallelWorkerDeps
56027
56331
  });
56028
- import { join as join73 } from "path";
56332
+ import { join as join74 } from "path";
56029
56333
  async function executeStoryInWorktree(story, worktreePath, dependencyContext, context, routing, eventEmitter) {
56030
56334
  const logger = getSafeLogger();
56031
56335
  try {
@@ -56045,7 +56349,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
56045
56349
  story,
56046
56350
  stories: [story],
56047
56351
  projectDir: context.projectDir,
56048
- workdir: dependencyContext.cwd ?? (story.workdir ? join73(worktreePath, story.workdir) : worktreePath),
56352
+ workdir: dependencyContext.cwd ?? (story.workdir ? join74(worktreePath, story.workdir) : worktreePath),
56049
56353
  worktreeDependencyContext: dependencyContext,
56050
56354
  routing,
56051
56355
  storyGitRef: storyGitRef ?? undefined
@@ -56813,122 +57117,6 @@ var init_unified_executor = __esm(() => {
56813
57117
  };
56814
57118
  });
56815
57119
 
56816
- // src/project/detector.ts
56817
- import { join as join74 } from "path";
56818
- async function detectLanguage2(workdir, pkg) {
56819
- const deps = _detectorDeps;
56820
- if (await deps.fileExists(join74(workdir, "go.mod")))
56821
- return "go";
56822
- if (await deps.fileExists(join74(workdir, "Cargo.toml")))
56823
- return "rust";
56824
- if (await deps.fileExists(join74(workdir, "pyproject.toml")))
56825
- return "python";
56826
- if (await deps.fileExists(join74(workdir, "requirements.txt")))
56827
- return "python";
56828
- if (pkg != null) {
56829
- const allDeps2 = {
56830
- ...pkg.dependencies,
56831
- ...pkg.devDependencies
56832
- };
56833
- if ("typescript" in allDeps2)
56834
- return "typescript";
56835
- return "javascript";
56836
- }
56837
- return;
56838
- }
56839
- function detectType(pkg) {
56840
- if (pkg == null)
56841
- return;
56842
- if (pkg.workspaces != null)
56843
- return "monorepo";
56844
- const allDeps2 = {
56845
- ...pkg.dependencies,
56846
- ...pkg.devDependencies
56847
- };
56848
- for (const dep of WEB_DEPS) {
56849
- if (dep in allDeps2)
56850
- return "web";
56851
- }
56852
- if ("ink" in allDeps2)
56853
- return "tui";
56854
- for (const dep of API_DEPS) {
56855
- if (dep in allDeps2)
56856
- return "api";
56857
- }
56858
- if (pkg.bin != null)
56859
- return "cli";
56860
- return;
56861
- }
56862
- async function detectTestFramework(_workdir, language, pkg) {
56863
- if (language === "go")
56864
- return "go-test";
56865
- if (language === "rust")
56866
- return "cargo-test";
56867
- if (language === "python")
56868
- return "pytest";
56869
- if (pkg != null) {
56870
- const devDeps = pkg.devDependencies ?? {};
56871
- if ("vitest" in devDeps)
56872
- return "vitest";
56873
- if ("jest" in devDeps)
56874
- return "jest";
56875
- }
56876
- return;
56877
- }
56878
- async function detectLintTool(workdir, language) {
56879
- if (language === "go")
56880
- return "golangci-lint";
56881
- if (language === "rust")
56882
- return "clippy";
56883
- if (language === "python")
56884
- return "ruff";
56885
- const deps = _detectorDeps;
56886
- if (await deps.fileExists(join74(workdir, "biome.json")))
56887
- return "biome";
56888
- if (await deps.fileExists(join74(workdir, ".eslintrc")))
56889
- return "eslint";
56890
- if (await deps.fileExists(join74(workdir, ".eslintrc.js")))
56891
- return "eslint";
56892
- if (await deps.fileExists(join74(workdir, ".eslintrc.json")))
56893
- return "eslint";
56894
- return;
56895
- }
56896
- async function detectProjectProfile(workdir, existing) {
56897
- const pkg = await _detectorDeps.readJson(join74(workdir, "package.json"));
56898
- const language = existing.language !== undefined ? existing.language : await detectLanguage2(workdir, pkg);
56899
- const type = existing.type !== undefined ? existing.type : detectType(pkg);
56900
- const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
56901
- const lintTool = existing.lintTool !== undefined ? existing.lintTool : await detectLintTool(workdir, language);
56902
- return { language, type, testFramework, lintTool };
56903
- }
56904
- var _detectorDeps, WEB_DEPS, API_DEPS;
56905
- var init_detector2 = __esm(() => {
56906
- _detectorDeps = {
56907
- async fileExists(path22) {
56908
- const file3 = Bun.file(path22);
56909
- return file3.exists();
56910
- },
56911
- async readJson(path22) {
56912
- try {
56913
- const file3 = Bun.file(path22);
56914
- if (!await file3.exists())
56915
- return null;
56916
- const text = await file3.text();
56917
- return JSON.parse(text);
56918
- } catch {
56919
- return null;
56920
- }
56921
- }
56922
- };
56923
- WEB_DEPS = new Set(["react", "next", "vue", "nuxt"]);
56924
- API_DEPS = new Set(["express", "fastify", "hono"]);
56925
- });
56926
-
56927
- // src/project/index.ts
56928
- var init_project = __esm(() => {
56929
- init_detector2();
56930
- });
56931
-
56932
57120
  // src/execution/status-file.ts
56933
57121
  import { rename as rename2, unlink as unlink3 } from "fs/promises";
56934
57122
  import { resolve as resolve16 } from "path";
@@ -89328,9 +89516,9 @@ init_plan_runtime();
89328
89516
  init_plan_runtime();
89329
89517
  init_plan_decompose();
89330
89518
  import { existsSync as existsSync14 } from "fs";
89331
- import { join as join27 } from "path";
89519
+ import { join as join28 } from "path";
89332
89520
  async function planCommand(workdir, config2, options) {
89333
- const naxDir = join27(workdir, ".nax");
89521
+ const naxDir = join28(workdir, ".nax");
89334
89522
  if (!existsSync14(naxDir)) {
89335
89523
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
89336
89524
  }
@@ -89346,13 +89534,13 @@ async function planCommand(workdir, config2, options) {
89346
89534
  const codebaseContext = buildCodebaseContext(scan);
89347
89535
  const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
89348
89536
  const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
89349
- const pkgJson = await _planDeps.readPackageJsonAt(join27(workdir, rel, "package.json"));
89537
+ const pkgJson = await _planDeps.readPackageJsonAt(join28(workdir, rel, "package.json"));
89350
89538
  return buildPackageSummary(rel, pkgJson);
89351
89539
  })) : [];
89352
89540
  const projectName = detectProjectName(workdir, pkg);
89353
89541
  const branchName = options.branch ?? `feat/${options.feature}`;
89354
- const outputDir = join27(naxDir, "features", options.feature);
89355
- const outputPath = join27(outputDir, "prd.json");
89542
+ const outputDir = join28(naxDir, "features", options.feature);
89543
+ const outputPath = join28(outputDir, "prd.json");
89356
89544
  await _planDeps.mkdirp(outputDir);
89357
89545
  const defaultAgentName = resolveDefaultAgent(config2);
89358
89546
  const timeoutSeconds = config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
@@ -89716,7 +89904,7 @@ init_interaction();
89716
89904
  init_prd();
89717
89905
  init_runtime();
89718
89906
  import { existsSync as existsSync16, readdirSync as readdirSync4 } from "fs";
89719
- import { basename as basename7, join as join30, resolve as resolve14 } from "path";
89907
+ import { basename as basename7, join as join31, resolve as resolve14 } from "path";
89720
89908
  var _statusFeaturesDeps = {
89721
89909
  projectOutputDir,
89722
89910
  loadConfig
@@ -89730,7 +89918,7 @@ function isPidAlive(pid) {
89730
89918
  }
89731
89919
  }
89732
89920
  async function loadStatusFile(featureDir) {
89733
- const statusPath = join30(featureDir, "status.json");
89921
+ const statusPath = join31(featureDir, "status.json");
89734
89922
  if (!existsSync16(statusPath)) {
89735
89923
  return null;
89736
89924
  }
@@ -89745,7 +89933,7 @@ async function loadProjectStatusFile(projectDir) {
89745
89933
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
89746
89934
  const projectKey = config2?.name?.trim() || basename7(projectDir);
89747
89935
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
89748
- const statusPath = join30(outputDir, "status.json");
89936
+ const statusPath = join31(outputDir, "status.json");
89749
89937
  if (!existsSync16(statusPath)) {
89750
89938
  return null;
89751
89939
  }
@@ -89757,7 +89945,7 @@ async function loadProjectStatusFile(projectDir) {
89757
89945
  }
89758
89946
  }
89759
89947
  async function getFeatureSummary(featureName, featureDir) {
89760
- const prdPath = join30(featureDir, "prd.json");
89948
+ const prdPath = join31(featureDir, "prd.json");
89761
89949
  if (!existsSync16(prdPath)) {
89762
89950
  return {
89763
89951
  name: featureName,
@@ -89800,7 +89988,7 @@ async function getFeatureSummary(featureName, featureDir) {
89800
89988
  };
89801
89989
  }
89802
89990
  }
89803
- const runsDir = join30(featureDir, "runs");
89991
+ const runsDir = join31(featureDir, "runs");
89804
89992
  if (existsSync16(runsDir)) {
89805
89993
  const runs = readdirSync4(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
89806
89994
  if (runs.length > 0) {
@@ -89814,7 +90002,7 @@ async function displayAllFeatures(projectDir) {
89814
90002
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
89815
90003
  const projectKey = config2?.name?.trim() || basename7(projectDir);
89816
90004
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
89817
- const featuresDir = join30(outputDir, "features");
90005
+ const featuresDir = join31(outputDir, "features");
89818
90006
  if (!existsSync16(featuresDir)) {
89819
90007
  console.log(source_default.dim("No features found."));
89820
90008
  return;
@@ -89855,7 +90043,7 @@ async function displayAllFeatures(projectDir) {
89855
90043
  console.log();
89856
90044
  }
89857
90045
  }
89858
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join30(featuresDir, name))));
90046
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join31(featuresDir, name))));
89859
90047
  console.log(source_default.bold(`\uD83D\uDCCA Features
89860
90048
  `));
89861
90049
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -89881,7 +90069,7 @@ async function displayAllFeatures(projectDir) {
89881
90069
  console.log();
89882
90070
  }
89883
90071
  async function displayFeatureDetails(featureName, featureDir) {
89884
- const prdPath = join30(featureDir, "prd.json");
90072
+ const prdPath = join31(featureDir, "prd.json");
89885
90073
  if (!existsSync16(prdPath)) {
89886
90074
  console.log(source_default.bold(`
89887
90075
  \uD83D\uDCCA ${featureName}
@@ -90027,7 +90215,7 @@ async function displayFeatureStatus(options = {}) {
90027
90215
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
90028
90216
  const projectKey = config2?.name?.trim() || basename7(projectDir);
90029
90217
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
90030
- featureDir = join30(outputDir, "features", options.feature);
90218
+ featureDir = join31(outputDir, "features", options.feature);
90031
90219
  } else {
90032
90220
  const resolved = resolveProject({ feature: options.feature });
90033
90221
  if (!resolved.featureDir) {
@@ -90047,7 +90235,7 @@ init_errors();
90047
90235
  init_logger2();
90048
90236
  init_runtime();
90049
90237
  import { existsSync as existsSync17, readdirSync as readdirSync5 } from "fs";
90050
- import { basename as basename8, join as join31 } from "path";
90238
+ import { basename as basename8, join as join32 } from "path";
90051
90239
  async function resolveOutputDir2(workdir, override) {
90052
90240
  if (override)
90053
90241
  return override;
@@ -90071,7 +90259,7 @@ async function runsListCommand(options) {
90071
90259
  const logger = getLogger();
90072
90260
  const { feature, workdir } = options;
90073
90261
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
90074
- const runsDir = join31(outputDir, "features", feature, "runs");
90262
+ const runsDir = join32(outputDir, "features", feature, "runs");
90075
90263
  if (!existsSync17(runsDir)) {
90076
90264
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
90077
90265
  return;
@@ -90083,7 +90271,7 @@ async function runsListCommand(options) {
90083
90271
  }
90084
90272
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
90085
90273
  for (const file3 of files.sort().reverse()) {
90086
- const logPath = join31(runsDir, file3);
90274
+ const logPath = join32(runsDir, file3);
90087
90275
  const entries = await parseRunLog(logPath);
90088
90276
  const startEvent = entries.find((e) => e.message === "run.start");
90089
90277
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -90110,7 +90298,7 @@ async function runsShowCommand(options) {
90110
90298
  const logger = getLogger();
90111
90299
  const { runId, feature, workdir } = options;
90112
90300
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
90113
- const logPath = join31(outputDir, "features", feature, "runs", `${runId}.jsonl`);
90301
+ const logPath = join32(outputDir, "features", feature, "runs", `${runId}.jsonl`);
90114
90302
  if (!existsSync17(logPath)) {
90115
90303
  logger.error("cli", "Run not found", { runId, feature, logPath });
90116
90304
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -90223,7 +90411,7 @@ init_logger2();
90223
90411
  init_prd();
90224
90412
  init_runtime();
90225
90413
  import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
90226
- import { basename as basename12, join as join51 } from "path";
90414
+ import { basename as basename12, join as join52 } from "path";
90227
90415
 
90228
90416
  // src/cli/diagnose-analysis.ts
90229
90417
  function detectFailurePattern(story, _prd, status) {
@@ -90426,7 +90614,7 @@ function isProcessAlive2(pid) {
90426
90614
  }
90427
90615
  }
90428
90616
  async function loadStatusFile2(outputDir) {
90429
- const statusPath = join51(outputDir, "status.json");
90617
+ const statusPath = join52(outputDir, "status.json");
90430
90618
  if (!existsSync25(statusPath))
90431
90619
  return null;
90432
90620
  try {
@@ -90454,7 +90642,7 @@ async function countCommitsSince(workdir, since) {
90454
90642
  }
90455
90643
  }
90456
90644
  async function checkLock(workdir) {
90457
- const lockFile = Bun.file(join51(workdir, "nax.lock"));
90645
+ const lockFile = Bun.file(join52(workdir, "nax.lock"));
90458
90646
  if (!await lockFile.exists())
90459
90647
  return { lockPresent: false };
90460
90648
  try {
@@ -90472,8 +90660,8 @@ async function diagnoseCommand(options = {}) {
90472
90660
  const logger = getLogger();
90473
90661
  const workdir = options.workdir ?? process.cwd();
90474
90662
  const naxSubdir = findProjectDir(workdir);
90475
- let projectDir = naxSubdir ? join51(naxSubdir, "..") : null;
90476
- if (!projectDir && existsSync25(join51(workdir, ".nax"))) {
90663
+ let projectDir = naxSubdir ? join52(naxSubdir, "..") : null;
90664
+ if (!projectDir && existsSync25(join52(workdir, ".nax"))) {
90477
90665
  projectDir = workdir;
90478
90666
  }
90479
90667
  if (!projectDir)
@@ -90487,7 +90675,7 @@ async function diagnoseCommand(options = {}) {
90487
90675
  if (status2) {
90488
90676
  feature = status2.run.feature;
90489
90677
  } else {
90490
- const featuresDir = join51(outputDir, "features");
90678
+ const featuresDir = join52(outputDir, "features");
90491
90679
  if (!existsSync25(featuresDir))
90492
90680
  throw new Error("No features found in project");
90493
90681
  const features = readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -90497,8 +90685,8 @@ async function diagnoseCommand(options = {}) {
90497
90685
  logger.info("diagnose", "No feature specified, using first found", { feature });
90498
90686
  }
90499
90687
  }
90500
- const featureDir = join51(outputDir, "features", feature);
90501
- const prdPath = join51(featureDir, "prd.json");
90688
+ const featureDir = join52(outputDir, "features", feature);
90689
+ const prdPath = join52(featureDir, "prd.json");
90502
90690
  if (!existsSync25(prdPath))
90503
90691
  throw new Error(`Feature not found: ${feature}`);
90504
90692
  const prd = await loadPRD(prdPath);
@@ -90543,7 +90731,7 @@ init_source();
90543
90731
  init_loader();
90544
90732
  init_generator2();
90545
90733
  import { existsSync as existsSync26 } from "fs";
90546
- import { join as join52 } from "path";
90734
+ import { join as join53 } from "path";
90547
90735
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
90548
90736
  async function generateCommand(options) {
90549
90737
  const workdir = options.dir ?? process.cwd();
@@ -90586,7 +90774,7 @@ async function generateCommand(options) {
90586
90774
  return;
90587
90775
  }
90588
90776
  if (options.package) {
90589
- const packageDir = join52(workdir, options.package);
90777
+ const packageDir = join53(workdir, options.package);
90590
90778
  if (dryRun) {
90591
90779
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
90592
90780
  }
@@ -90606,8 +90794,8 @@ async function generateCommand(options) {
90606
90794
  process.exit(1);
90607
90795
  return;
90608
90796
  }
90609
- const contextPath = options.context ? join52(workdir, options.context) : join52(workdir, ".nax/context.md");
90610
- const outputDir = options.output ? join52(workdir, options.output) : workdir;
90797
+ const contextPath = options.context ? join53(workdir, options.context) : join53(workdir, ".nax/context.md");
90798
+ const outputDir = options.output ? join53(workdir, options.output) : workdir;
90611
90799
  const autoInject = !options.noAutoInject;
90612
90800
  if (!existsSync26(contextPath)) {
90613
90801
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -90713,7 +90901,7 @@ async function generateCommand(options) {
90713
90901
  // src/cli/config-display.ts
90714
90902
  init_loader();
90715
90903
  import { existsSync as existsSync28 } from "fs";
90716
- import { join as join54 } from "path";
90904
+ import { join as join55 } from "path";
90717
90905
 
90718
90906
  // src/cli/config-descriptions.ts
90719
90907
  var FIELD_DESCRIPTIONS = {
@@ -90954,7 +91142,7 @@ function deepEqual(a, b) {
90954
91142
  init_defaults();
90955
91143
  init_loader();
90956
91144
  import { existsSync as existsSync27 } from "fs";
90957
- import { join as join53 } from "path";
91145
+ import { join as join54 } from "path";
90958
91146
  async function loadConfigFile(path19) {
90959
91147
  if (!existsSync27(path19))
90960
91148
  return null;
@@ -90976,7 +91164,7 @@ async function loadProjectConfig() {
90976
91164
  const projectDir = findProjectDir();
90977
91165
  if (!projectDir)
90978
91166
  return null;
90979
- const projectPath = join53(projectDir, "config.json");
91167
+ const projectPath = join54(projectDir, "config.json");
90980
91168
  return await loadConfigFile(projectPath);
90981
91169
  }
90982
91170
 
@@ -91036,7 +91224,7 @@ async function configCommand(config2, options = {}) {
91036
91224
  function determineConfigSources() {
91037
91225
  const globalPath = globalConfigPath();
91038
91226
  const projectDir = findProjectDir();
91039
- const projectPath = projectDir ? join54(projectDir, "config.json") : null;
91227
+ const projectPath = projectDir ? join55(projectDir, "config.json") : null;
91040
91228
  return {
91041
91229
  global: fileExists(globalPath) ? globalPath : null,
91042
91230
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -91185,15 +91373,15 @@ init_paths();
91185
91373
  init_profile();
91186
91374
  import { mkdirSync as mkdirSync5 } from "fs";
91187
91375
  import { readdirSync as readdirSync7 } from "fs";
91188
- import { join as join55 } from "path";
91376
+ import { join as join56 } from "path";
91189
91377
  var _profileCLIDeps = {
91190
91378
  env: process.env
91191
91379
  };
91192
91380
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
91193
91381
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
91194
91382
  async function profileListCommand(startDir) {
91195
- const globalProfilesDir = join55(globalConfigDir(), "profiles");
91196
- const projectProfilesDir = join55(projectConfigDir(startDir), "profiles");
91383
+ const globalProfilesDir = join56(globalConfigDir(), "profiles");
91384
+ const projectProfilesDir = join56(projectConfigDir(startDir), "profiles");
91197
91385
  const globalProfiles = scanProfileDir(globalProfilesDir);
91198
91386
  const projectProfiles = scanProfileDir(projectProfilesDir);
91199
91387
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -91252,7 +91440,7 @@ function maskProfileValues(obj) {
91252
91440
  return result;
91253
91441
  }
91254
91442
  async function profileUseCommand(profileName, startDir) {
91255
- const configPath = join55(projectConfigDir(startDir), "config.json");
91443
+ const configPath = join56(projectConfigDir(startDir), "config.json");
91256
91444
  const configFile = Bun.file(configPath);
91257
91445
  let existing = {};
91258
91446
  if (await configFile.exists()) {
@@ -91271,8 +91459,8 @@ async function profileCurrentCommand(startDir) {
91271
91459
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
91272
91460
  }
91273
91461
  async function profileCreateCommand(profileName, startDir) {
91274
- const profilesDir = join55(projectConfigDir(startDir), "profiles");
91275
- const profilePath = join55(profilesDir, `${profileName}.json`);
91462
+ const profilesDir = join56(projectConfigDir(startDir), "profiles");
91463
+ const profilePath = join56(profilesDir, `${profileName}.json`);
91276
91464
  const profileFile = Bun.file(profilePath);
91277
91465
  if (await profileFile.exists()) {
91278
91466
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -91394,7 +91582,7 @@ async function contextInspectCommand(options) {
91394
91582
  init_canonical_loader();
91395
91583
  init_errors();
91396
91584
  import { mkdir as mkdir12 } from "fs/promises";
91397
- import { basename as basename13, join as join56 } from "path";
91585
+ import { basename as basename13, join as join57 } from "path";
91398
91586
  var _rulesCLIDeps = {
91399
91587
  readFile: async (path19) => Bun.file(path19).text(),
91400
91588
  writeFile: async (path19, content) => {
@@ -91403,7 +91591,7 @@ var _rulesCLIDeps = {
91403
91591
  fileExists: async (path19) => Bun.file(path19).exists(),
91404
91592
  globInDir: (dir) => {
91405
91593
  try {
91406
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join56(dir, f));
91594
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join57(dir, f));
91407
91595
  } catch {
91408
91596
  return [];
91409
91597
  }
@@ -91452,7 +91640,7 @@ ${r.content}`).join(`
91452
91640
  `);
91453
91641
  const shimContent = `${header + body}
91454
91642
  `;
91455
- const shimPath = join56(workdir, shimFileName);
91643
+ const shimPath = join57(workdir, shimFileName);
91456
91644
  if (options.dryRun) {
91457
91645
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
91458
91646
  return;
@@ -91481,14 +91669,14 @@ function neutralizeContent(content) {
91481
91669
  }
91482
91670
  async function collectMigrationSources(workdir) {
91483
91671
  const sources = [];
91484
- const claudeMdPath = join56(workdir, "CLAUDE.md");
91672
+ const claudeMdPath = join57(workdir, "CLAUDE.md");
91485
91673
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
91486
91674
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
91487
91675
  if (content.trim()) {
91488
91676
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
91489
91677
  }
91490
91678
  }
91491
- const rulesDir = join56(workdir, ".claude", "rules");
91679
+ const rulesDir = join57(workdir, ".claude", "rules");
91492
91680
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
91493
91681
  for (const filePath of ruleFiles) {
91494
91682
  try {
@@ -91508,7 +91696,7 @@ async function rulesMigrateCommand(options) {
91508
91696
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
91509
91697
  return;
91510
91698
  }
91511
- const targetDir = join56(workdir, CANONICAL_RULES_DIR);
91699
+ const targetDir = join57(workdir, CANONICAL_RULES_DIR);
91512
91700
  if (!options.dryRun) {
91513
91701
  try {
91514
91702
  await _rulesCLIDeps.mkdir(targetDir);
@@ -91519,7 +91707,7 @@ async function rulesMigrateCommand(options) {
91519
91707
  let written = 0;
91520
91708
  let skipped = 0;
91521
91709
  for (const { sourcePath, targetFileName, content } of sources) {
91522
- const targetPath = join56(targetDir, targetFileName);
91710
+ const targetPath = join57(targetDir, targetFileName);
91523
91711
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
91524
91712
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
91525
91713
  skipped++;
@@ -91558,7 +91746,7 @@ function collectCanonicalRuleRoots(workdir) {
91558
91746
  const packageRel = normalized.slice(0, idx);
91559
91747
  if (!packageRel)
91560
91748
  continue;
91561
- roots.add(join56(workdir, packageRel));
91749
+ roots.add(join57(workdir, packageRel));
91562
91750
  }
91563
91751
  return [...roots].sort();
91564
91752
  }
@@ -91580,7 +91768,7 @@ init_logger2();
91580
91768
  init_detect2();
91581
91769
  init_workspace();
91582
91770
  init_common();
91583
- import { join as join57 } from "path";
91771
+ import { join as join58 } from "path";
91584
91772
  function resolveEffective(detected, configPatterns) {
91585
91773
  if (configPatterns !== undefined)
91586
91774
  return "config";
@@ -91665,7 +91853,7 @@ async function detectCommand(options) {
91665
91853
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
91666
91854
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
91667
91855
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
91668
- const pkgConfigPath = join57(workdir, ".nax", "mono", dir, "config.json");
91856
+ const pkgConfigPath = join58(workdir, ".nax", "mono", dir, "config.json");
91669
91857
  const pkgRaw = await loadRawConfig(pkgConfigPath);
91670
91858
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
91671
91859
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -91719,13 +91907,13 @@ async function detectCommand(options) {
91719
91907
  if (rootDetected.confidence === "empty") {
91720
91908
  console.log(source_default.yellow(" root: skipped (empty detection)"));
91721
91909
  } else {
91722
- const rootConfigPath = join57(workdir, ".nax", "config.json");
91910
+ const rootConfigPath = join58(workdir, ".nax", "config.json");
91723
91911
  try {
91724
91912
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
91725
91913
  if (status === "skipped") {
91726
91914
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
91727
91915
  } else {
91728
- console.log(source_default.green(` root: ${status} \u2192 ${join57(".nax", "config.json")}`));
91916
+ console.log(source_default.green(` root: ${status} \u2192 ${join58(".nax", "config.json")}`));
91729
91917
  }
91730
91918
  } catch (err) {
91731
91919
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -91738,13 +91926,13 @@ async function detectCommand(options) {
91738
91926
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
91739
91927
  continue;
91740
91928
  }
91741
- const pkgConfigPath = join57(workdir, ".nax", "mono", dir, "config.json");
91929
+ const pkgConfigPath = join58(workdir, ".nax", "mono", dir, "config.json");
91742
91930
  try {
91743
91931
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
91744
91932
  if (status === "skipped") {
91745
91933
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
91746
91934
  } else {
91747
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join57(".nax", "mono", dir, "config.json")}`));
91935
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join58(".nax", "mono", dir, "config.json")}`));
91748
91936
  }
91749
91937
  } catch (err) {
91750
91938
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -91767,24 +91955,24 @@ async function diagnose(options) {
91767
91955
  // src/commands/logs.ts
91768
91956
  init_common();
91769
91957
  import { existsSync as existsSync30 } from "fs";
91770
- import { join as join61 } from "path";
91958
+ import { join as join62 } from "path";
91771
91959
 
91772
91960
  // src/commands/logs-formatter.ts
91773
91961
  init_source();
91774
91962
  init_formatter();
91775
91963
  import { readdirSync as readdirSync9 } from "fs";
91776
- import { join as join60 } from "path";
91964
+ import { join as join61 } from "path";
91777
91965
 
91778
91966
  // src/commands/logs-reader.ts
91779
91967
  import { existsSync as existsSync29, readdirSync as readdirSync8 } from "fs";
91780
91968
  import { readdir as readdir4 } from "fs/promises";
91781
- import { join as join59 } from "path";
91969
+ import { join as join60 } from "path";
91782
91970
 
91783
91971
  // src/utils/paths.ts
91784
91972
  import { homedir as homedir4 } from "os";
91785
- import { join as join58 } from "path";
91973
+ import { join as join59 } from "path";
91786
91974
  function getRunsDir() {
91787
- return process.env.NAX_RUNS_DIR ?? join58(homedir4(), ".nax", "runs");
91975
+ return process.env.NAX_RUNS_DIR ?? join59(homedir4(), ".nax", "runs");
91788
91976
  }
91789
91977
 
91790
91978
  // src/commands/logs-reader.ts
@@ -91801,7 +91989,7 @@ async function resolveRunFileFromRegistry(runId) {
91801
91989
  }
91802
91990
  let matched = null;
91803
91991
  for (const entry of entries) {
91804
- const metaPath = join59(runsDir, entry, "meta.json");
91992
+ const metaPath = join60(runsDir, entry, "meta.json");
91805
91993
  try {
91806
91994
  const meta3 = await Bun.file(metaPath).json();
91807
91995
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -91823,14 +92011,14 @@ async function resolveRunFileFromRegistry(runId) {
91823
92011
  return null;
91824
92012
  }
91825
92013
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
91826
- return join59(matched.eventsDir, specificFile ?? files[0]);
92014
+ return join60(matched.eventsDir, specificFile ?? files[0]);
91827
92015
  }
91828
92016
  async function selectRunFile(runsDir) {
91829
92017
  const files = readdirSync8(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
91830
92018
  if (files.length === 0) {
91831
92019
  return null;
91832
92020
  }
91833
- return join59(runsDir, files[0]);
92021
+ return join60(runsDir, files[0]);
91834
92022
  }
91835
92023
  async function extractRunSummary(filePath) {
91836
92024
  const file3 = Bun.file(filePath);
@@ -91915,7 +92103,7 @@ Runs:
91915
92103
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
91916
92104
  console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
91917
92105
  for (const file3 of files) {
91918
- const filePath = join60(runsDir, file3);
92106
+ const filePath = join61(runsDir, file3);
91919
92107
  const summary = await extractRunSummary(filePath);
91920
92108
  const timestamp = file3.replace(".jsonl", "");
91921
92109
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -92029,7 +92217,7 @@ async function logsCommand(options) {
92029
92217
  return;
92030
92218
  }
92031
92219
  const resolved = resolveProject({ dir: options.dir });
92032
- const naxDir = join61(resolved.projectDir, ".nax");
92220
+ const naxDir = join62(resolved.projectDir, ".nax");
92033
92221
  const configPath = resolved.configPath;
92034
92222
  const configFile = Bun.file(configPath);
92035
92223
  const config2 = await configFile.json();
@@ -92037,8 +92225,8 @@ async function logsCommand(options) {
92037
92225
  if (!featureName) {
92038
92226
  throw new Error("No feature specified in config.json");
92039
92227
  }
92040
- const featureDir = join61(naxDir, "features", featureName);
92041
- const runsDir = join61(featureDir, "runs");
92228
+ const featureDir = join62(naxDir, "features", featureName);
92229
+ const runsDir = join62(featureDir, "runs");
92042
92230
  if (!existsSync30(runsDir)) {
92043
92231
  throw new Error(`No runs directory found for feature: ${featureName}`);
92044
92232
  }
@@ -92064,7 +92252,7 @@ init_prd();
92064
92252
  init_precheck();
92065
92253
  init_common();
92066
92254
  import { existsSync as existsSync31 } from "fs";
92067
- import { join as join62 } from "path";
92255
+ import { join as join63 } from "path";
92068
92256
  async function precheckCommand(options) {
92069
92257
  const resolved = resolveProject({
92070
92258
  dir: options.dir,
@@ -92086,9 +92274,9 @@ async function precheckCommand(options) {
92086
92274
  process.exit(1);
92087
92275
  }
92088
92276
  }
92089
- const naxDir = join62(resolved.projectDir, ".nax");
92090
- const featureDir = join62(naxDir, "features", featureName);
92091
- const prdPath = join62(featureDir, "prd.json");
92277
+ const naxDir = join63(resolved.projectDir, ".nax");
92278
+ const featureDir = join63(naxDir, "features", featureName);
92279
+ const prdPath = join63(featureDir, "prd.json");
92092
92280
  if (!existsSync31(featureDir)) {
92093
92281
  console.error(source_default.red(`Feature not found: ${featureName}`));
92094
92282
  process.exit(1);
@@ -92110,7 +92298,7 @@ async function precheckCommand(options) {
92110
92298
  // src/commands/runs.ts
92111
92299
  init_source();
92112
92300
  import { readdir as readdir5 } from "fs/promises";
92113
- import { join as join63 } from "path";
92301
+ import { join as join64 } from "path";
92114
92302
  var DEFAULT_LIMIT = 20;
92115
92303
  var _runsCmdDeps = {
92116
92304
  getRunsDir
@@ -92165,7 +92353,7 @@ async function runsCommand(options = {}) {
92165
92353
  }
92166
92354
  const rows = [];
92167
92355
  for (const entry of entries) {
92168
- const metaPath = join63(runsDir, entry, "meta.json");
92356
+ const metaPath = join64(runsDir, entry, "meta.json");
92169
92357
  let meta3;
92170
92358
  try {
92171
92359
  meta3 = await Bun.file(metaPath).json();
@@ -92242,7 +92430,7 @@ async function runsCommand(options = {}) {
92242
92430
 
92243
92431
  // src/commands/unlock.ts
92244
92432
  init_source();
92245
- import { join as join64 } from "path";
92433
+ import { join as join65 } from "path";
92246
92434
  function isProcessAlive3(pid) {
92247
92435
  try {
92248
92436
  process.kill(pid, 0);
@@ -92257,7 +92445,7 @@ function formatLockAge(ageMs) {
92257
92445
  }
92258
92446
  async function unlockCommand(options) {
92259
92447
  const workdir = options.dir ?? process.cwd();
92260
- const lockPath = join64(workdir, "nax.lock");
92448
+ const lockPath = join65(workdir, "nax.lock");
92261
92449
  const lockFile = Bun.file(lockPath);
92262
92450
  const exists = await lockFile.exists();
92263
92451
  if (!exists) {
@@ -100472,6 +100660,7 @@ program2.command("migrate").description("Migrate generated content from .nax/ to
100472
100660
  return;
100473
100661
  }
100474
100662
  const { migrateCommand: migrateCommand2 } = await Promise.resolve().then(() => (init_migrate(), exports_migrate));
100663
+ initLogger({ level: "info", useChalk: true });
100475
100664
  try {
100476
100665
  await migrateCommand2({
100477
100666
  workdir,