@nathapp/nax 0.53.0-canary.2 → 0.53.0

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 +339 -229
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -3258,6 +3258,21 @@ async function withProcessTimeout(proc, timeoutMs, opts) {
3258
3258
  return { exitCode, timedOut };
3259
3259
  }
3260
3260
 
3261
+ // src/utils/bun-deps.ts
3262
+ function typedSpawn(cmd, opts) {
3263
+ return Bun.spawn(cmd, opts);
3264
+ }
3265
+ function which(name) {
3266
+ return Bun.which(name);
3267
+ }
3268
+ function sleep(ms) {
3269
+ return Bun.sleep(ms);
3270
+ }
3271
+ var spawn;
3272
+ var init_bun_deps = __esm(() => {
3273
+ spawn = Bun.spawn;
3274
+ });
3275
+
3261
3276
  // src/config/test-strategy.ts
3262
3277
  function resolveTestStrategy(raw) {
3263
3278
  if (!raw)
@@ -3479,11 +3494,10 @@ async function executeComplete(binary, prompt, options) {
3479
3494
  }
3480
3495
  var _completeDeps;
3481
3496
  var init_complete = __esm(() => {
3497
+ init_bun_deps();
3482
3498
  init_types2();
3483
3499
  _completeDeps = {
3484
- spawn(cmd, opts) {
3485
- return Bun.spawn(cmd, opts);
3486
- }
3500
+ spawn: typedSpawn
3487
3501
  };
3488
3502
  });
3489
3503
 
@@ -3772,6 +3786,7 @@ async function executeOnce(binary, options, pidRegistry) {
3772
3786
  var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_PERIOD_MS = 5000, _runOnceDeps;
3773
3787
  var init_execution = __esm(() => {
3774
3788
  init_logger2();
3789
+ init_bun_deps();
3775
3790
  init_cost2();
3776
3791
  _runOnceDeps = {
3777
3792
  killProc(proc, signal) {
@@ -3781,9 +3796,7 @@ var init_execution = __esm(() => {
3781
3796
  return buildCommand(binary, options);
3782
3797
  },
3783
3798
  withProcessTimeout,
3784
- spawn(cmd, opts) {
3785
- return Bun.spawn(cmd, opts);
3786
- }
3799
+ spawn: typedSpawn
3787
3800
  };
3788
3801
  });
3789
3802
 
@@ -17793,7 +17806,7 @@ var init_schemas3 = __esm(() => {
17793
17806
  lintCommand: exports_external.string().nullable().optional(),
17794
17807
  typecheckCommand: exports_external.string().nullable().optional(),
17795
17808
  dangerouslySkipPermissions: exports_external.boolean().default(true),
17796
- permissionProfile: exports_external.enum(["unrestricted", "safe", "scoped"]).optional(),
17809
+ permissionProfile: exports_external.enum(["unrestricted", "safe", "scoped"]).default("unrestricted"),
17797
17810
  permissions: exports_external.record(exports_external.string(), exports_external.object({
17798
17811
  mode: exports_external.enum(["approve-all", "approve-reads", "scoped"]),
17799
17812
  allowedTools: exports_external.array(exports_external.string()).optional(),
@@ -17811,7 +17824,8 @@ var init_schemas3 = __esm(() => {
17811
17824
  test: exports_external.string().optional(),
17812
17825
  testScoped: exports_external.string().optional(),
17813
17826
  lintFix: exports_external.string().optional(),
17814
- formatFix: exports_external.string().optional()
17827
+ formatFix: exports_external.string().optional(),
17828
+ build: exports_external.string().optional()
17815
17829
  }),
17816
17830
  forceExit: exports_external.boolean().default(false),
17817
17831
  detectOpenHandles: exports_external.boolean().default(true),
@@ -17850,7 +17864,6 @@ var init_schemas3 = __esm(() => {
17850
17864
  "SENTRY_AUTH_TOKEN",
17851
17865
  "DATADOG_API_KEY"
17852
17866
  ]),
17853
- environmentalEscalationDivisor: exports_external.number().min(1).max(10).default(2),
17854
17867
  testing: exports_external.object({
17855
17868
  hermetic: exports_external.boolean().default(true),
17856
17869
  externalBoundaries: exports_external.array(exports_external.string()).optional(),
@@ -17885,11 +17898,12 @@ var init_schemas3 = __esm(() => {
17885
17898
  });
17886
17899
  ReviewConfigSchema = exports_external.object({
17887
17900
  enabled: exports_external.boolean(),
17888
- checks: exports_external.array(exports_external.enum(["typecheck", "lint", "test"])),
17901
+ checks: exports_external.array(exports_external.enum(["typecheck", "lint", "test", "build"])),
17889
17902
  commands: exports_external.object({
17890
17903
  typecheck: exports_external.string().optional(),
17891
17904
  lint: exports_external.string().optional(),
17892
- test: exports_external.string().optional()
17905
+ test: exports_external.string().optional(),
17906
+ build: exports_external.string().optional()
17893
17907
  }),
17894
17908
  pluginMode: exports_external.enum(["per-story", "deferred"]).default("per-story")
17895
17909
  });
@@ -18038,7 +18052,7 @@ var init_defaults = __esm(() => {
18038
18052
  autoMode: {
18039
18053
  enabled: true,
18040
18054
  defaultAgent: "claude",
18041
- fallbackOrder: ["claude", "codex", "opencode", "gemini"],
18055
+ fallbackOrder: ["claude"],
18042
18056
  complexityRouting: {
18043
18057
  simple: "fast",
18044
18058
  medium: "balanced",
@@ -18062,26 +18076,26 @@ var init_defaults = __esm(() => {
18062
18076
  fallbackToKeywords: true,
18063
18077
  cacheDecisions: true,
18064
18078
  mode: "hybrid",
18065
- timeoutMs: 15000
18079
+ timeoutMs: 30000
18066
18080
  }
18067
18081
  },
18068
18082
  execution: {
18069
18083
  maxIterations: 10,
18070
18084
  iterationDelayMs: 2000,
18071
- costLimit: 5,
18072
- sessionTimeoutSeconds: 600,
18073
- verificationTimeoutSeconds: 300,
18085
+ costLimit: 30,
18086
+ sessionTimeoutSeconds: 3600,
18087
+ verificationTimeoutSeconds: 600,
18074
18088
  maxStoriesPerFeature: 500,
18075
18089
  rectification: {
18076
18090
  enabled: true,
18077
18091
  maxRetries: 2,
18078
- fullSuiteTimeoutSeconds: 120,
18092
+ fullSuiteTimeoutSeconds: 300,
18079
18093
  maxFailureSummaryChars: 2000,
18080
18094
  abortOnIncreasingFailures: true
18081
18095
  },
18082
18096
  regressionGate: {
18083
18097
  enabled: true,
18084
- timeoutSeconds: 120,
18098
+ timeoutSeconds: 300,
18085
18099
  acceptOnTimeout: true,
18086
18100
  maxRectificationAttempts: 2
18087
18101
  },
@@ -18130,7 +18144,6 @@ var init_defaults = __esm(() => {
18130
18144
  "SENTRY_AUTH_TOKEN",
18131
18145
  "DATADOG_API_KEY"
18132
18146
  ],
18133
- environmentalEscalationDivisor: 2,
18134
18147
  testing: {
18135
18148
  hermetic: true
18136
18149
  }
@@ -18215,6 +18228,9 @@ var init_defaults = __esm(() => {
18215
18228
  }
18216
18229
  },
18217
18230
  prompts: {},
18231
+ agent: {
18232
+ protocol: "acp"
18233
+ },
18218
18234
  decompose: {
18219
18235
  trigger: "auto",
18220
18236
  maxAcceptanceCriteria: 6,
@@ -18539,19 +18555,18 @@ var _decomposeDeps, _claudeAdapterDeps;
18539
18555
  var init_adapter = __esm(() => {
18540
18556
  init_pid_registry();
18541
18557
  init_logger2();
18558
+ init_bun_deps();
18542
18559
  init_decompose();
18543
18560
  init_complete();
18544
18561
  init_execution();
18545
18562
  init_interactive();
18546
18563
  init_plan();
18547
18564
  _decomposeDeps = {
18548
- spawn(cmd, opts) {
18549
- return Bun.spawn(cmd, opts);
18550
- }
18565
+ spawn: typedSpawn
18551
18566
  };
18552
18567
  _claudeAdapterDeps = {
18553
- sleep: (ms) => Bun.sleep(ms),
18554
- spawn: Bun.spawn
18568
+ sleep,
18569
+ spawn: typedSpawn
18555
18570
  };
18556
18571
  });
18557
18572
 
@@ -18728,6 +18743,18 @@ __export(exports_generator, {
18728
18743
  _generatorPRDDeps: () => _generatorPRDDeps
18729
18744
  });
18730
18745
  import { join as join2 } from "path";
18746
+ function skeletonImportLine(testFramework) {
18747
+ if (!testFramework)
18748
+ return `import { describe, test, expect } from "bun:test";`;
18749
+ const fw = testFramework.toLowerCase();
18750
+ if (fw === "jest" || fw === "@jest/globals") {
18751
+ return `import { describe, test, expect } from "@jest/globals";`;
18752
+ }
18753
+ if (fw === "vitest") {
18754
+ return `import { describe, test, expect } from "vitest";`;
18755
+ }
18756
+ return `import { describe, test, expect } from "bun:test";`;
18757
+ }
18731
18758
  async function generateFromPRD(_stories, refinedCriteria, options) {
18732
18759
  const logger = getLogger();
18733
18760
  const criteria = refinedCriteria.map((c, i) => ({
@@ -18775,7 +18802,7 @@ Rules:
18775
18802
  - **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
18776
18803
  - Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
18777
18804
  - Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
18778
- - **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/nax/features/${options.featureName}/acceptance.test.ts\` and will ALWAYS run from the repo root via \`bun test <absolute-path>\`. The repo root is exactly 3 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..')\`. Never use 4 or more \`../\` \u2014 that would escape the repo. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
18805
+ - **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/.nax/features/${options.featureName}/acceptance.test.ts\` and will ALWAYS run from the repo root. The repo root is exactly 4 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..', '..')\`. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
18779
18806
  const prompt = basePrompt;
18780
18807
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
18781
18808
  const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
@@ -18784,7 +18811,18 @@ Rules:
18784
18811
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
18785
18812
  workdir: options.workdir
18786
18813
  });
18787
- const testCode = extractTestCode(rawOutput);
18814
+ let testCode = extractTestCode(rawOutput);
18815
+ if (!testCode) {
18816
+ const targetPath = join2(options.featureDir, "acceptance.test.ts");
18817
+ try {
18818
+ const existing = await Bun.file(targetPath).text();
18819
+ const recovered = extractTestCode(existing);
18820
+ if (recovered) {
18821
+ logger.info("acceptance", "Recovered acceptance test written to disk by ACP adapter", { targetPath });
18822
+ testCode = recovered;
18823
+ }
18824
+ } catch {}
18825
+ }
18788
18826
  if (!testCode) {
18789
18827
  logger.warn("acceptance", "LLM returned non-code output for acceptance tests \u2014 falling back to skeleton", {
18790
18828
  outputPreview: rawOutput.slice(0, 200)
@@ -18794,7 +18832,10 @@ Rules:
18794
18832
  text: c.refined,
18795
18833
  lineNumber: i + 1
18796
18834
  }));
18797
- return { testCode: generateSkeletonTests(options.featureName, skeletonCriteria), criteria: skeletonCriteria };
18835
+ return {
18836
+ testCode: generateSkeletonTests(options.featureName, skeletonCriteria, options.testFramework),
18837
+ criteria: skeletonCriteria
18838
+ };
18798
18839
  }
18799
18840
  const refinedJsonContent = JSON.stringify(refinedCriteria.map((c, i) => ({
18800
18841
  acId: `AC-${i + 1}`,
@@ -18861,7 +18902,7 @@ Rules:
18861
18902
  - **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
18862
18903
  - Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
18863
18904
  - Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
18864
- - **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/nax/features/${featureName}/acceptance.test.ts\` and will ALWAYS run from the repo root via \`bun test <absolute-path>\`. The repo root is exactly 3 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..')\`. Never use 4 or more \`../\` \u2014 that would escape the repo. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
18905
+ - **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/.nax/features/${featureName}/acceptance.test.ts\` and will ALWAYS run from the repo root. The repo root is exactly 4 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..', '..')\`. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
18865
18906
  }
18866
18907
  async function generateAcceptanceTests(adapter, options) {
18867
18908
  const logger = getLogger();
@@ -18869,7 +18910,7 @@ async function generateAcceptanceTests(adapter, options) {
18869
18910
  if (criteria.length === 0) {
18870
18911
  logger.warn("acceptance", "\u26A0 No acceptance criteria found in spec.md");
18871
18912
  return {
18872
- testCode: generateSkeletonTests(options.featureName, []),
18913
+ testCode: generateSkeletonTests(options.featureName, [], options.testFramework),
18873
18914
  criteria: []
18874
18915
  };
18875
18916
  }
@@ -18888,7 +18929,7 @@ async function generateAcceptanceTests(adapter, options) {
18888
18929
  outputPreview: output.slice(0, 200)
18889
18930
  });
18890
18931
  return {
18891
- testCode: generateSkeletonTests(options.featureName, criteria),
18932
+ testCode: generateSkeletonTests(options.featureName, criteria, options.testFramework),
18892
18933
  criteria
18893
18934
  };
18894
18935
  }
@@ -18899,7 +18940,7 @@ async function generateAcceptanceTests(adapter, options) {
18899
18940
  } catch (error48) {
18900
18941
  logger.warn("acceptance", "\u26A0 Agent test generation error", { error: error48.message });
18901
18942
  return {
18902
- testCode: generateSkeletonTests(options.featureName, criteria),
18943
+ testCode: generateSkeletonTests(options.featureName, criteria, options.testFramework),
18903
18944
  criteria
18904
18945
  };
18905
18946
  }
@@ -18930,7 +18971,7 @@ function extractTestCode(output) {
18930
18971
  }
18931
18972
  return code;
18932
18973
  }
18933
- function generateSkeletonTests(featureName, criteria) {
18974
+ function generateSkeletonTests(featureName, criteria, testFramework) {
18934
18975
  const tests = criteria.map((ac) => {
18935
18976
  return ` test("${ac.id}: ${ac.text}", async () => {
18936
18977
  // TODO: Implement acceptance test for ${ac.id}
@@ -18940,7 +18981,7 @@ function generateSkeletonTests(featureName, criteria) {
18940
18981
  }).join(`
18941
18982
 
18942
18983
  `);
18943
- return `import { describe, test, expect } from "bun:test";
18984
+ return `${skeletonImportLine(testFramework)}
18944
18985
 
18945
18986
  describe("${featureName} - Acceptance Tests", () => {
18946
18987
  ${tests || " // No acceptance criteria found"}
@@ -19308,8 +19349,8 @@ class SpawnAcpSession {
19308
19349
  const processPid = proc.pid;
19309
19350
  await this.pidRegistry?.register(processPid);
19310
19351
  try {
19311
- proc.stdin.write(text);
19312
- proc.stdin.end();
19352
+ proc.stdin?.write(text);
19353
+ proc.stdin?.end();
19313
19354
  const exitCode = await proc.exited;
19314
19355
  const stdout = await new Response(proc.stdout).text();
19315
19356
  const stderr = await new Response(proc.stderr).text();
@@ -19444,10 +19485,9 @@ function createSpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry) {
19444
19485
  var _spawnClientDeps;
19445
19486
  var init_spawn_client = __esm(() => {
19446
19487
  init_logger2();
19488
+ init_bun_deps();
19447
19489
  _spawnClientDeps = {
19448
- spawn(cmd, opts) {
19449
- return Bun.spawn(cmd, opts);
19450
- }
19490
+ spawn: typedSpawn
19451
19491
  };
19452
19492
  });
19453
19493
 
@@ -19994,6 +20034,7 @@ class AcpAgentAdapter {
19994
20034
  var MAX_AGENT_OUTPUT_CHARS2 = 5000, MAX_RATE_LIMIT_RETRIES = 3, INTERACTION_TIMEOUT_MS, AGENT_REGISTRY, DEFAULT_ENTRY, _acpAdapterDeps, MAX_SESSION_AGE_MS;
19995
20035
  var init_adapter2 = __esm(() => {
19996
20036
  init_logger2();
20037
+ init_bun_deps();
19997
20038
  init_decompose();
19998
20039
  init_spawn_client();
19999
20040
  init_types2();
@@ -20026,12 +20067,8 @@ var init_adapter2 = __esm(() => {
20026
20067
  maxContextTokens: 128000
20027
20068
  };
20028
20069
  _acpAdapterDeps = {
20029
- which(name) {
20030
- return Bun.which(name);
20031
- },
20032
- async sleep(ms) {
20033
- await Bun.sleep(ms);
20034
- },
20070
+ which,
20071
+ sleep,
20035
20072
  createClient(cmdStr, cwd, timeoutSeconds, pidRegistry) {
20036
20073
  return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry);
20037
20074
  }
@@ -20105,14 +20142,11 @@ class AiderAdapter {
20105
20142
  }
20106
20143
  var _aiderCompleteDeps, MAX_AGENT_OUTPUT_CHARS3 = 5000;
20107
20144
  var init_adapter3 = __esm(() => {
20145
+ init_bun_deps();
20108
20146
  init_types2();
20109
20147
  _aiderCompleteDeps = {
20110
- which(name) {
20111
- return Bun.which(name);
20112
- },
20113
- spawn(cmd, opts) {
20114
- return Bun.spawn(cmd, opts);
20115
- }
20148
+ which,
20149
+ spawn: typedSpawn
20116
20150
  };
20117
20151
  });
20118
20152
 
@@ -20180,19 +20214,14 @@ class CodexAdapter {
20180
20214
  }
20181
20215
  var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS4 = 5000;
20182
20216
  var init_adapter4 = __esm(() => {
20217
+ init_bun_deps();
20183
20218
  init_types2();
20184
20219
  _codexRunDeps = {
20185
- which(name) {
20186
- return Bun.which(name);
20187
- },
20188
- spawn(cmd, opts) {
20189
- return Bun.spawn(cmd, opts);
20190
- }
20220
+ which,
20221
+ spawn: typedSpawn
20191
20222
  };
20192
20223
  _codexCompleteDeps = {
20193
- spawn(cmd, opts) {
20194
- return Bun.spawn(cmd, opts);
20195
- }
20224
+ spawn: typedSpawn
20196
20225
  };
20197
20226
  });
20198
20227
 
@@ -20280,19 +20309,14 @@ class GeminiAdapter {
20280
20309
  }
20281
20310
  var _geminiRunDeps, _geminiCompleteDeps, MAX_AGENT_OUTPUT_CHARS5 = 5000;
20282
20311
  var init_adapter5 = __esm(() => {
20312
+ init_bun_deps();
20283
20313
  init_types2();
20284
20314
  _geminiRunDeps = {
20285
- which(name) {
20286
- return Bun.which(name);
20287
- },
20288
- spawn(cmd, opts) {
20289
- return Bun.spawn(cmd, opts);
20290
- }
20315
+ which,
20316
+ spawn: typedSpawn
20291
20317
  };
20292
20318
  _geminiCompleteDeps = {
20293
- spawn(cmd, opts) {
20294
- return Bun.spawn(cmd, opts);
20295
- }
20319
+ spawn: typedSpawn
20296
20320
  };
20297
20321
  });
20298
20322
 
@@ -20342,14 +20366,11 @@ class OpenCodeAdapter {
20342
20366
  }
20343
20367
  var _opencodeCompleteDeps;
20344
20368
  var init_adapter6 = __esm(() => {
20369
+ init_bun_deps();
20345
20370
  init_types2();
20346
20371
  _opencodeCompleteDeps = {
20347
- which(name) {
20348
- return Bun.which(name);
20349
- },
20350
- spawn(cmd, opts) {
20351
- return Bun.spawn(cmd, opts);
20352
- }
20372
+ which,
20373
+ spawn: typedSpawn
20353
20374
  };
20354
20375
  });
20355
20376
 
@@ -20384,7 +20405,7 @@ async function checkAgentHealth() {
20384
20405
  })));
20385
20406
  }
20386
20407
  function createAgentRegistry(config2) {
20387
- const protocol = config2.agent?.protocol ?? "cli";
20408
+ const protocol = config2.agent?.protocol ?? "acp";
20388
20409
  const logger = getLogger();
20389
20410
  const acpCache = new Map;
20390
20411
  logger?.info("agents", `Agent protocol: ${protocol}`, { protocol, hasConfig: !!config2.agent });
@@ -20634,6 +20655,9 @@ function mergePackageConfig(root, packageOverride) {
20634
20655
  ...packageOverride.quality?.commands?.test !== undefined && {
20635
20656
  test: packageOverride.quality.commands.test
20636
20657
  },
20658
+ ...packageOverride.quality?.commands?.build !== undefined && {
20659
+ build: packageOverride.quality.commands.build
20660
+ },
20637
20661
  ...packageOverride.review?.commands
20638
20662
  }
20639
20663
  },
@@ -21089,7 +21113,7 @@ __export(exports_llm, {
21089
21113
  classifyWithLlm: () => classifyWithLlm,
21090
21114
  buildRoutingPrompt: () => buildRoutingPrompt,
21091
21115
  buildBatchPrompt: () => buildBatchRoutingPrompt,
21092
- _deps: () => _deps
21116
+ _llmStrategyDeps: () => _llmStrategyDeps
21093
21117
  });
21094
21118
  function clearCache() {
21095
21119
  cachedDecisions.clear();
@@ -21160,7 +21184,7 @@ async function routeBatch(stories, context) {
21160
21184
  if (!llmConfig) {
21161
21185
  throw new Error("LLM routing config not found");
21162
21186
  }
21163
- const adapter = context.adapter ?? _deps.adapter;
21187
+ const adapter = context.adapter ?? _llmStrategyDeps.adapter;
21164
21188
  if (!adapter) {
21165
21189
  throw new Error("No agent adapter available for batch routing (AA-003)");
21166
21190
  }
@@ -21206,7 +21230,7 @@ async function classifyWithLlm(story, config2, adapter) {
21206
21230
  logger2.info("routing", "One-shot mode cache miss, falling back to keyword", { storyId: story.id });
21207
21231
  return null;
21208
21232
  }
21209
- const effectiveAdapter = adapter ?? _deps.adapter;
21233
+ const effectiveAdapter = adapter ?? _llmStrategyDeps.adapter;
21210
21234
  if (!effectiveAdapter) {
21211
21235
  throw new Error("No agent adapter available for LLM routing (AA-003)");
21212
21236
  }
@@ -21237,16 +21261,17 @@ async function classifyWithLlm(story, config2, adapter) {
21237
21261
  });
21238
21262
  return decision;
21239
21263
  }
21240
- var cachedDecisions, MAX_CACHE_SIZE = 100, _deps;
21264
+ var cachedDecisions, MAX_CACHE_SIZE = 100, _llmStrategyDeps;
21241
21265
  var init_llm = __esm(() => {
21242
21266
  init_config();
21243
21267
  init_logger2();
21268
+ init_bun_deps();
21244
21269
  init_router();
21245
21270
  init_llm_prompts();
21246
21271
  init_llm_prompts();
21247
21272
  cachedDecisions = new Map;
21248
- _deps = {
21249
- spawn: (cmd, opts) => Bun.spawn(cmd, opts),
21273
+ _llmStrategyDeps = {
21274
+ spawn: typedSpawn,
21250
21275
  adapter: undefined
21251
21276
  };
21252
21277
  });
@@ -21380,11 +21405,11 @@ function routeTask(title, description, acceptanceCriteria, tags, config2) {
21380
21405
  reasoning: reasons.length > 0 ? `${prefix}: ${reasons.join(", ")}` : `test-after: simple task (${complexity})`
21381
21406
  };
21382
21407
  }
21383
- async function tryLlmBatchRoute(config2, stories, label = "routing", _deps2 = _tryLlmBatchRouteDeps) {
21408
+ async function tryLlmBatchRoute(config2, stories, label = "routing", _deps = _tryLlmBatchRouteDeps) {
21384
21409
  const mode = config2.routing.llm?.mode ?? "hybrid";
21385
21410
  if (config2.routing.strategy !== "llm" || mode === "per-story" || stories.length === 0)
21386
21411
  return;
21387
- const resolvedAdapter = _deps2.getAgent(config2.execution?.agent ?? "claude");
21412
+ const resolvedAdapter = _deps.getAgent(config2.execution?.agent ?? "claude");
21388
21413
  if (!resolvedAdapter)
21389
21414
  return;
21390
21415
  const logger = getSafeLogger();
@@ -22077,7 +22102,7 @@ var package_default;
22077
22102
  var init_package = __esm(() => {
22078
22103
  package_default = {
22079
22104
  name: "@nathapp/nax",
22080
- version: "0.53.0-canary.2",
22105
+ version: "0.53.0",
22081
22106
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22082
22107
  type: "module",
22083
22108
  bin: {
@@ -22154,8 +22179,8 @@ var init_version = __esm(() => {
22154
22179
  NAX_VERSION = package_default.version;
22155
22180
  NAX_COMMIT = (() => {
22156
22181
  try {
22157
- if (/^[0-9a-f]{6,10}$/.test("490600a"))
22158
- return "490600a";
22182
+ if (/^[0-9a-f]{6,10}$/.test("18532ac"))
22183
+ return "18532ac";
22159
22184
  } catch {}
22160
22185
  try {
22161
22186
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23313,6 +23338,7 @@ class WebhookInteractionPlugin {
23313
23338
  var WebhookConfigSchema, InteractionResponseSchema;
23314
23339
  var init_webhook = __esm(() => {
23315
23340
  init_zod();
23341
+ init_bun_deps();
23316
23342
  WebhookConfigSchema = exports_external.object({
23317
23343
  url: exports_external.string().url().optional(),
23318
23344
  callbackPort: exports_external.number().int().min(1024).max(65535).optional(),
@@ -23351,8 +23377,8 @@ class AutoInteractionPlugin {
23351
23377
  return;
23352
23378
  }
23353
23379
  try {
23354
- if (_deps3.callLlm) {
23355
- const decision2 = await _deps3.callLlm(request);
23380
+ if (_autoPluginDeps.callLlm) {
23381
+ const decision2 = await _autoPluginDeps.callLlm(request);
23356
23382
  if (decision2.confidence < (this.config.confidenceThreshold ?? 0.7)) {
23357
23383
  return;
23358
23384
  }
@@ -23381,9 +23407,9 @@ class AutoInteractionPlugin {
23381
23407
  }
23382
23408
  async callLlm(request) {
23383
23409
  const prompt = this.buildPrompt(request);
23384
- const adapter = _deps3.adapter;
23410
+ const adapter = _autoPluginDeps.adapter;
23385
23411
  if (!adapter) {
23386
- throw new Error("Auto plugin requires adapter to be injected via _deps.adapter");
23412
+ throw new Error("Auto plugin requires adapter to be injected via _autoPluginDeps.adapter");
23387
23413
  }
23388
23414
  let modelArg;
23389
23415
  if (this.config.naxConfig) {
@@ -23470,7 +23496,7 @@ Respond with ONLY this JSON (no markdown, no explanation):
23470
23496
  return parsed;
23471
23497
  }
23472
23498
  }
23473
- var AutoConfigSchema, _deps3;
23499
+ var AutoConfigSchema, _autoPluginDeps;
23474
23500
  var init_auto = __esm(() => {
23475
23501
  init_zod();
23476
23502
  init_config();
@@ -23480,7 +23506,7 @@ var init_auto = __esm(() => {
23480
23506
  maxCostPerDecision: exports_external.number().positive().optional(),
23481
23507
  naxConfig: exports_external.any().optional()
23482
23508
  });
23483
- _deps3 = {
23509
+ _autoPluginDeps = {
23484
23510
  adapter: null,
23485
23511
  callLlm: null
23486
23512
  };
@@ -23849,7 +23875,9 @@ var init_acceptance2 = __esm(() => {
23849
23875
  });
23850
23876
  return { action: "continue" };
23851
23877
  }
23852
- const proc = Bun.spawn(["bun", "test", testPath], {
23878
+ const configuredTestCmd = ctx.config.quality?.commands?.test;
23879
+ const testCmdParts = configuredTestCmd ? [...configuredTestCmd.trim().split(/\s+/), testPath] : ["bun", "test", testPath];
23880
+ const proc = Bun.spawn(testCmdParts, {
23853
23881
  cwd: ctx.workdir,
23854
23882
  stdout: "pipe",
23855
23883
  stderr: "pipe"
@@ -23963,11 +23991,10 @@ async function getAgentVersions() {
23963
23991
  }
23964
23992
  var _versionDetectionDeps;
23965
23993
  var init_version_detection = __esm(() => {
23994
+ init_bun_deps();
23966
23995
  init_registry();
23967
23996
  _versionDetectionDeps = {
23968
- spawn(cmd, opts) {
23969
- return Bun.spawn(cmd, opts);
23970
- }
23997
+ spawn: typedSpawn
23971
23998
  };
23972
23999
  });
23973
24000
 
@@ -24051,8 +24078,15 @@ var init_acceptance_setup = __esm(() => {
24051
24078
  writeMeta: async (metaPath, meta3) => {
24052
24079
  await Bun.write(metaPath, JSON.stringify(meta3, null, 2));
24053
24080
  },
24054
- runTest: async (_testPath, _workdir) => {
24055
- const proc = Bun.spawn(["bun", "test", _testPath], {
24081
+ runTest: async (_testPath, _workdir, _testCmd) => {
24082
+ let cmd;
24083
+ if (_testCmd) {
24084
+ const parts = _testCmd.trim().split(/\s+/);
24085
+ cmd = [...parts, _testPath];
24086
+ } else {
24087
+ cmd = ["bun", "test", _testPath];
24088
+ }
24089
+ const proc = Bun.spawn(cmd, {
24056
24090
  cwd: _workdir,
24057
24091
  stdout: "pipe",
24058
24092
  stderr: "pipe"
@@ -24147,7 +24181,8 @@ ${stderr}` };
24147
24181
  ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
24148
24182
  return { action: "continue" };
24149
24183
  }
24150
- const { exitCode } = await _acceptanceSetupDeps.runTest(testPath, ctx.workdir);
24184
+ const testCmd = ctx.config.quality?.commands?.test;
24185
+ const { exitCode } = await _acceptanceSetupDeps.runTest(testPath, ctx.workdir, testCmd);
24151
24186
  if (exitCode === 0) {
24152
24187
  ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
24153
24188
  return {
@@ -24345,11 +24380,12 @@ async function captureOutputFiles(workdir, baseRef, scopePrefix) {
24345
24380
  var _gitDeps, GIT_TIMEOUT_MS = 1e4;
24346
24381
  var init_git = __esm(() => {
24347
24382
  init_logger2();
24348
- _gitDeps = { spawn: Bun.spawn };
24383
+ init_bun_deps();
24384
+ _gitDeps = { spawn };
24349
24385
  });
24350
24386
 
24351
24387
  // src/review/runner.ts
24352
- var {spawn } = globalThis.Bun;
24388
+ var {spawn: spawn2 } = globalThis.Bun;
24353
24389
  async function loadPackageJson(workdir) {
24354
24390
  try {
24355
24391
  const file2 = _reviewRunnerDeps.file(`${workdir}/package.json`);
@@ -24383,9 +24419,11 @@ async function resolveCommand(check2, config2, executionConfig, workdir, quality
24383
24419
  if (qualityCmd) {
24384
24420
  return qualityCmd;
24385
24421
  }
24386
- const packageJson = await loadPackageJson(workdir);
24387
- if (hasScript(packageJson, check2)) {
24388
- return `bun run ${check2}`;
24422
+ if (check2 !== "build") {
24423
+ const packageJson = await loadPackageJson(workdir);
24424
+ if (hasScript(packageJson, check2)) {
24425
+ return `bun run ${check2}`;
24426
+ }
24389
24427
  }
24390
24428
  return null;
24391
24429
  }
@@ -24486,7 +24524,7 @@ async function runReview(config2, workdir, executionConfig, qualityCommands, sto
24486
24524
  const checks3 = [];
24487
24525
  let firstFailure;
24488
24526
  await autoCommitIfDirty(workdir, "review", "agent", storyId ?? "review");
24489
- const allUncommittedFiles = await _deps4.getUncommittedFiles(workdir);
24527
+ const allUncommittedFiles = await _reviewGitDeps.getUncommittedFiles(workdir);
24490
24528
  const NAX_RUNTIME_PATTERNS = [
24491
24529
  /nax\.lock$/,
24492
24530
  /nax\/metrics\.json$/,
@@ -24541,18 +24579,18 @@ Stage and commit these files before running review.`
24541
24579
  failureReason: firstFailure
24542
24580
  };
24543
24581
  }
24544
- var _reviewRunnerDeps, REVIEW_CHECK_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS2 = 5000, _deps4;
24582
+ var _reviewRunnerDeps, REVIEW_CHECK_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS2 = 5000, _reviewGitDeps;
24545
24583
  var init_runner2 = __esm(() => {
24546
24584
  init_logger2();
24547
24585
  init_git();
24548
- _reviewRunnerDeps = { spawn, file: Bun.file };
24549
- _deps4 = {
24586
+ _reviewRunnerDeps = { spawn: spawn2, file: Bun.file };
24587
+ _reviewGitDeps = {
24550
24588
  getUncommittedFiles: getUncommittedFilesImpl
24551
24589
  };
24552
24590
  });
24553
24591
 
24554
24592
  // src/review/orchestrator.ts
24555
- var {spawn: spawn2 } = globalThis.Bun;
24593
+ var {spawn: spawn3 } = globalThis.Bun;
24556
24594
  async function getChangedFiles(workdir, baseRef) {
24557
24595
  try {
24558
24596
  const diffArgs = ["diff", "--name-only"];
@@ -24650,7 +24688,7 @@ var _orchestratorDeps, reviewOrchestrator;
24650
24688
  var init_orchestrator = __esm(() => {
24651
24689
  init_logger2();
24652
24690
  init_runner2();
24653
- _orchestratorDeps = { spawn: spawn2 };
24691
+ _orchestratorDeps = { spawn: spawn3 };
24654
24692
  reviewOrchestrator = new ReviewOrchestrator;
24655
24693
  });
24656
24694
 
@@ -25802,7 +25840,7 @@ async function addFileElements(elements, storyContext, story) {
25802
25840
  if (contextFiles.length === 0 && storyContext.config?.context?.autoDetect?.enabled !== false && storyContext.workdir) {
25803
25841
  const autoDetectConfig = storyContext.config?.context?.autoDetect;
25804
25842
  try {
25805
- const detected = await _deps5.autoDetectContextFiles({
25843
+ const detected = await _contextBuilderDeps.autoDetectContextFiles({
25806
25844
  workdir: storyContext.workdir,
25807
25845
  storyTitle: story.title,
25808
25846
  maxFiles: autoDetectConfig?.maxFiles ?? 5,
@@ -25860,7 +25898,7 @@ ${content}
25860
25898
  }
25861
25899
  }
25862
25900
  }
25863
- var _deps5;
25901
+ var _contextBuilderDeps;
25864
25902
  var init_builder2 = __esm(() => {
25865
25903
  init_logger2();
25866
25904
  init_prd();
@@ -25869,7 +25907,7 @@ var init_builder2 = __esm(() => {
25869
25907
  init_parent_context();
25870
25908
  init_test_scanner();
25871
25909
  init_elements();
25872
- _deps5 = {
25910
+ _contextBuilderDeps = {
25873
25911
  autoDetectContextFiles
25874
25912
  };
25875
25913
  });
@@ -26186,7 +26224,8 @@ async function verifyImplementerIsolation(workdir, beforeRef) {
26186
26224
  }
26187
26225
  var _isolationDeps, TEST_PATTERNS, SRC_PATTERNS;
26188
26226
  var init_isolation = __esm(() => {
26189
- _isolationDeps = { spawn: Bun.spawn };
26227
+ init_bun_deps();
26228
+ _isolationDeps = { spawn };
26190
26229
  TEST_PATTERNS = [/^test\//, /^tests\//, /^__tests__\//, /\.spec\.\w+$/, /\.test\.\w+$/, /\.e2e-spec\.\w+$/];
26191
26230
  SRC_PATTERNS = [/^src\//, /^lib\//, /^packages\//];
26192
26231
  });
@@ -26374,12 +26413,69 @@ function buildTestCommand(baseCommand, options) {
26374
26413
  }
26375
26414
  var _executorDeps, DEFAULT_STRIP_ENV_VARS;
26376
26415
  var init_executor = __esm(() => {
26377
- _executorDeps = { spawn: Bun.spawn };
26416
+ init_bun_deps();
26417
+ _executorDeps = { spawn };
26378
26418
  DEFAULT_STRIP_ENV_VARS = ["CLAUDECODE", "REPL_ID", "AGENT"];
26379
26419
  });
26380
26420
 
26381
26421
  // src/verification/parser.ts
26382
26422
  function parseBunTestOutput(output) {
26423
+ if (isJestLikeOutput(output)) {
26424
+ return parseJestOutput(output);
26425
+ }
26426
+ return parseBunOutput(output);
26427
+ }
26428
+ function isJestLikeOutput(output) {
26429
+ return /^\s*Tests:\s+\d+/m.test(output) || /^\s*Test Files\s+\d+/m.test(output);
26430
+ }
26431
+ function parseJestOutput(output) {
26432
+ const failures = [];
26433
+ let passed = 0;
26434
+ let failed = 0;
26435
+ const summaryMatches = Array.from(output.matchAll(/^\s*Tests:\s+(.*)/gm));
26436
+ if (summaryMatches.length > 0) {
26437
+ const summaryLine = summaryMatches[summaryMatches.length - 1][1];
26438
+ const failedMatch = summaryLine.match(/(\d+)\s+failed/);
26439
+ const passedMatch = summaryLine.match(/(\d+)\s+passed/);
26440
+ if (failedMatch)
26441
+ failed = Number.parseInt(failedMatch[1], 10);
26442
+ if (passedMatch)
26443
+ passed = Number.parseInt(passedMatch[1], 10);
26444
+ }
26445
+ let currentFile = "unknown";
26446
+ const lines = output.split(`
26447
+ `);
26448
+ for (let i = 0;i < lines.length; i++) {
26449
+ const line = lines[i];
26450
+ const fileMatch = line.match(/^\s*(?:FAIL|PASS)\s+(\S+\.[jt]sx?)/);
26451
+ if (fileMatch) {
26452
+ currentFile = fileMatch[1];
26453
+ continue;
26454
+ }
26455
+ const bulletMatch = line.match(/^\s+\u25CF\s+(.+)$/);
26456
+ if (bulletMatch) {
26457
+ const testName = bulletMatch[1].trim();
26458
+ let error48 = "";
26459
+ for (let j = i + 1;j < lines.length && j < i + 10; j++) {
26460
+ const next = lines[j].trim();
26461
+ if (!next)
26462
+ continue;
26463
+ if (next.startsWith("\u25CF") || /^(?:FAIL|PASS)\s/.test(next))
26464
+ break;
26465
+ error48 = next;
26466
+ break;
26467
+ }
26468
+ failures.push({
26469
+ file: currentFile,
26470
+ testName,
26471
+ error: error48 || "Unknown error",
26472
+ stackTrace: []
26473
+ });
26474
+ }
26475
+ }
26476
+ return { passed, failed, failures };
26477
+ }
26478
+ function parseBunOutput(output) {
26383
26479
  const lines = output.split(`
26384
26480
  `);
26385
26481
  const failures = [];
@@ -26596,9 +26692,10 @@ async function regression(options) {
26596
26692
  }
26597
26693
  var _regressionRunnerDeps;
26598
26694
  var init_runners = __esm(() => {
26695
+ init_bun_deps();
26599
26696
  init_executor();
26600
26697
  _regressionRunnerDeps = {
26601
- sleep: (ms) => Bun.sleep(ms)
26698
+ sleep
26602
26699
  };
26603
26700
  });
26604
26701
 
@@ -26722,9 +26819,10 @@ async function cleanupProcessTree(pid, gracePeriodMs = 3000) {
26722
26819
  var _cleanupDeps;
26723
26820
  var init_cleanup = __esm(() => {
26724
26821
  init_logger2();
26822
+ init_bun_deps();
26725
26823
  _cleanupDeps = {
26726
- spawn: Bun.spawn,
26727
- sleep: Bun.sleep,
26824
+ spawn,
26825
+ sleep,
26728
26826
  kill: process.kill.bind(process)
26729
26827
  };
26730
26828
  });
@@ -29145,7 +29243,8 @@ ${stderr}`;
29145
29243
  var _acceptanceDeps;
29146
29244
  var init_acceptance3 = __esm(() => {
29147
29245
  init_logger2();
29148
- _acceptanceDeps = { spawn: Bun.spawn };
29246
+ init_bun_deps();
29247
+ _acceptanceDeps = { spawn };
29149
29248
  });
29150
29249
 
29151
29250
  // src/verification/strategies/regression.ts
@@ -29986,7 +30085,7 @@ __export(exports_init_context, {
29986
30085
  initContext: () => initContext,
29987
30086
  generatePackageContextTemplate: () => generatePackageContextTemplate,
29988
30087
  generateContextTemplate: () => generateContextTemplate,
29989
- _deps: () => _deps6
30088
+ _initContextDeps: () => _initContextDeps
29990
30089
  });
29991
30090
  import { existsSync as existsSync20 } from "fs";
29992
30091
  import { mkdir } from "fs/promises";
@@ -30195,7 +30294,7 @@ The context.md should include:
30195
30294
  Keep it under 2000 tokens. Use markdown formatting. Be specific to the detected stack and structure.
30196
30295
  `;
30197
30296
  try {
30198
- const result = await _deps6.callLLM(prompt);
30297
+ const result = await _initContextDeps.callLLM(prompt);
30199
30298
  logger.info("init", "Generated context.md with LLM");
30200
30299
  return result;
30201
30300
  } catch (err) {
@@ -30260,10 +30359,10 @@ async function initContext(projectRoot, options = {}) {
30260
30359
  await Bun.write(contextPath, content);
30261
30360
  logger.info("init", "Generated .nax/context.md template from project scan", { path: contextPath });
30262
30361
  }
30263
- var _deps6;
30362
+ var _initContextDeps;
30264
30363
  var init_init_context = __esm(() => {
30265
30364
  init_logger2();
30266
- _deps6 = {
30365
+ _initContextDeps = {
30267
30366
  callLLM: async (_prompt) => {
30268
30367
  throw new Error("callLLM not implemented");
30269
30368
  }
@@ -30979,7 +31078,7 @@ var init_checks_config = () => {};
30979
31078
  async function checkAgentCLI(config2) {
30980
31079
  const agent = config2.execution?.agent || "claude";
30981
31080
  try {
30982
- const proc = _deps8.spawn([agent, "--version"], {
31081
+ const proc = _checkCliDeps.spawn([agent, "--version"], {
30983
31082
  stdout: "pipe",
30984
31083
  stderr: "pipe"
30985
31084
  });
@@ -31000,10 +31099,11 @@ async function checkAgentCLI(config2) {
31000
31099
  };
31001
31100
  }
31002
31101
  }
31003
- var _deps8;
31102
+ var _checkCliDeps;
31004
31103
  var init_checks_cli = __esm(() => {
31005
- _deps8 = {
31006
- spawn: Bun.spawn
31104
+ init_bun_deps();
31105
+ _checkCliDeps = {
31106
+ spawn
31007
31107
  };
31008
31108
  });
31009
31109
 
@@ -31596,19 +31696,19 @@ var init_precheck = __esm(() => {
31596
31696
  });
31597
31697
 
31598
31698
  // src/hooks/runner.ts
31599
- import { join as join44 } from "path";
31699
+ import { join as join45 } from "path";
31600
31700
  async function loadHooksConfig(projectDir, globalDir) {
31601
31701
  let globalHooks = { hooks: {} };
31602
31702
  let projectHooks = { hooks: {} };
31603
31703
  let skipGlobal = false;
31604
- const projectPath = join44(projectDir, "hooks.json");
31704
+ const projectPath = join45(projectDir, "hooks.json");
31605
31705
  const projectData = await loadJsonFile(projectPath, "hooks");
31606
31706
  if (projectData) {
31607
31707
  projectHooks = projectData;
31608
31708
  skipGlobal = projectData.skipGlobal ?? false;
31609
31709
  }
31610
31710
  if (!skipGlobal && globalDir) {
31611
- const globalPath = join44(globalDir, "hooks.json");
31711
+ const globalPath = join45(globalDir, "hooks.json");
31612
31712
  const globalData = await loadJsonFile(globalPath, "hooks");
31613
31713
  if (globalData) {
31614
31714
  globalHooks = globalData;
@@ -32049,7 +32149,7 @@ __export(exports_acceptance_loop, {
32049
32149
  isStubTestFile: () => isStubTestFile,
32050
32150
  _acceptanceLoopDeps: () => _acceptanceLoopDeps
32051
32151
  });
32052
- import path14, { join as join45 } from "path";
32152
+ import path14, { join as join46 } from "path";
32053
32153
  function isStubTestFile(content) {
32054
32154
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
32055
32155
  }
@@ -32111,7 +32211,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
32111
32211
  agent: ctx.config.autoMode.defaultAgent,
32112
32212
  iteration: iterations
32113
32213
  }), ctx.workdir);
32114
- const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join45(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
32214
+ const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
32115
32215
  const fixContext = {
32116
32216
  config: ctx.config,
32117
32217
  effectiveConfig: fixEffectiveConfig,
@@ -32714,12 +32814,12 @@ __export(exports_manager, {
32714
32814
  WorktreeManager: () => WorktreeManager
32715
32815
  });
32716
32816
  import { existsSync as existsSync32, symlinkSync } from "fs";
32717
- import { join as join46 } from "path";
32817
+ import { join as join47 } from "path";
32718
32818
 
32719
32819
  class WorktreeManager {
32720
32820
  async create(projectRoot, storyId) {
32721
32821
  validateStoryId(storyId);
32722
- const worktreePath = join46(projectRoot, ".nax-wt", storyId);
32822
+ const worktreePath = join47(projectRoot, ".nax-wt", storyId);
32723
32823
  const branchName = `nax/${storyId}`;
32724
32824
  try {
32725
32825
  const proc = _managerDeps.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
@@ -32744,9 +32844,9 @@ class WorktreeManager {
32744
32844
  }
32745
32845
  throw new Error(`Failed to create worktree: ${String(error48)}`);
32746
32846
  }
32747
- const nodeModulesSource = join46(projectRoot, "node_modules");
32847
+ const nodeModulesSource = join47(projectRoot, "node_modules");
32748
32848
  if (existsSync32(nodeModulesSource)) {
32749
- const nodeModulesTarget = join46(worktreePath, "node_modules");
32849
+ const nodeModulesTarget = join47(worktreePath, "node_modules");
32750
32850
  try {
32751
32851
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
32752
32852
  } catch (error48) {
@@ -32754,9 +32854,9 @@ class WorktreeManager {
32754
32854
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
32755
32855
  }
32756
32856
  }
32757
- const envSource = join46(projectRoot, ".env");
32857
+ const envSource = join47(projectRoot, ".env");
32758
32858
  if (existsSync32(envSource)) {
32759
- const envTarget = join46(worktreePath, ".env");
32859
+ const envTarget = join47(worktreePath, ".env");
32760
32860
  try {
32761
32861
  symlinkSync(envSource, envTarget, "file");
32762
32862
  } catch (error48) {
@@ -32767,7 +32867,7 @@ class WorktreeManager {
32767
32867
  }
32768
32868
  async remove(projectRoot, storyId) {
32769
32869
  validateStoryId(storyId);
32770
- const worktreePath = join46(projectRoot, ".nax-wt", storyId);
32870
+ const worktreePath = join47(projectRoot, ".nax-wt", storyId);
32771
32871
  const branchName = `nax/${storyId}`;
32772
32872
  try {
32773
32873
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -32857,8 +32957,9 @@ class WorktreeManager {
32857
32957
  var _managerDeps;
32858
32958
  var init_manager = __esm(() => {
32859
32959
  init_logger2();
32960
+ init_bun_deps();
32860
32961
  _managerDeps = {
32861
- spawn: Bun.spawn
32962
+ spawn
32862
32963
  };
32863
32964
  });
32864
32965
 
@@ -33076,8 +33177,9 @@ ${stderr}`;
33076
33177
  var _mergeDeps;
33077
33178
  var init_merge = __esm(() => {
33078
33179
  init_logger2();
33180
+ init_bun_deps();
33079
33181
  _mergeDeps = {
33080
- spawn: Bun.spawn
33182
+ spawn
33081
33183
  };
33082
33184
  });
33083
33185
 
@@ -33168,7 +33270,7 @@ var init_parallel_worker = __esm(() => {
33168
33270
 
33169
33271
  // src/execution/parallel-coordinator.ts
33170
33272
  import os3 from "os";
33171
- import { join as join47 } from "path";
33273
+ import { join as join48 } from "path";
33172
33274
  function groupStoriesByDependencies(stories) {
33173
33275
  const batches = [];
33174
33276
  const processed = new Set;
@@ -33247,7 +33349,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
33247
33349
  };
33248
33350
  const worktreePaths = new Map;
33249
33351
  for (const story of batch) {
33250
- const worktreePath = join47(projectRoot, ".nax-wt", story.id);
33352
+ const worktreePath = join48(projectRoot, ".nax-wt", story.id);
33251
33353
  try {
33252
33354
  await worktreeManager.create(projectRoot, story.id);
33253
33355
  worktreePaths.set(story.id, worktreePath);
@@ -33296,7 +33398,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
33296
33398
  });
33297
33399
  logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
33298
33400
  storyId: mergeResult.storyId,
33299
- worktreePath: join47(projectRoot, ".nax-wt", mergeResult.storyId)
33401
+ worktreePath: join48(projectRoot, ".nax-wt", mergeResult.storyId)
33300
33402
  });
33301
33403
  }
33302
33404
  }
@@ -33755,13 +33857,13 @@ var init_parallel_executor = __esm(() => {
33755
33857
 
33756
33858
  // src/pipeline/subscribers/events-writer.ts
33757
33859
  import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
33758
- import { homedir as homedir7 } from "os";
33759
- import { basename as basename5, join as join48 } from "path";
33860
+ import { homedir as homedir6 } from "os";
33861
+ import { basename as basename5, join as join49 } from "path";
33760
33862
  function wireEventsWriter(bus, feature, runId, workdir) {
33761
33863
  const logger = getSafeLogger();
33762
33864
  const project = basename5(workdir);
33763
- const eventsDir = join48(homedir7(), ".nax", "events", project);
33764
- const eventsFile = join48(eventsDir, "events.jsonl");
33865
+ const eventsDir = join49(homedir6(), ".nax", "events", project);
33866
+ const eventsFile = join49(eventsDir, "events.jsonl");
33765
33867
  let dirReady = false;
33766
33868
  const write = (line) => {
33767
33869
  (async () => {
@@ -33934,13 +34036,13 @@ var init_interaction2 = __esm(() => {
33934
34036
 
33935
34037
  // src/pipeline/subscribers/registry.ts
33936
34038
  import { mkdir as mkdir3, writeFile } from "fs/promises";
33937
- import { homedir as homedir8 } from "os";
33938
- import { basename as basename6, join as join49 } from "path";
34039
+ import { homedir as homedir7 } from "os";
34040
+ import { basename as basename6, join as join50 } from "path";
33939
34041
  function wireRegistry(bus, feature, runId, workdir) {
33940
34042
  const logger = getSafeLogger();
33941
34043
  const project = basename6(workdir);
33942
- const runDir = join49(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33943
- const metaFile = join49(runDir, "meta.json");
34044
+ const runDir = join50(homedir7(), ".nax", "runs", `${project}-${feature}-${runId}`);
34045
+ const metaFile = join50(runDir, "meta.json");
33944
34046
  const unsub = bus.on("run:started", (_ev) => {
33945
34047
  (async () => {
33946
34048
  try {
@@ -33950,8 +34052,8 @@ function wireRegistry(bus, feature, runId, workdir) {
33950
34052
  project,
33951
34053
  feature,
33952
34054
  workdir,
33953
- statusPath: join49(workdir, ".nax", "features", feature, "status.json"),
33954
- eventsDir: join49(workdir, ".nax", "features", feature, "runs"),
34055
+ statusPath: join50(workdir, ".nax", "features", feature, "status.json"),
34056
+ eventsDir: join50(workdir, ".nax", "features", feature, "runs"),
33955
34057
  registeredAt: new Date().toISOString()
33956
34058
  };
33957
34059
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -34095,7 +34197,7 @@ var init_reporters = __esm(() => {
34095
34197
  });
34096
34198
 
34097
34199
  // src/execution/deferred-review.ts
34098
- var {spawn: spawn3 } = globalThis.Bun;
34200
+ var {spawn: spawn4 } = globalThis.Bun;
34099
34201
  async function captureRunStartRef(workdir) {
34100
34202
  try {
34101
34203
  const proc = _deferredReviewDeps.spawn({
@@ -34163,7 +34265,7 @@ async function runDeferredReview(workdir, reviewConfig, plugins, runStartRef) {
34163
34265
  }
34164
34266
  var _deferredReviewDeps;
34165
34267
  var init_deferred_review = __esm(() => {
34166
- _deferredReviewDeps = { spawn: spawn3 };
34268
+ _deferredReviewDeps = { spawn: spawn4 };
34167
34269
  });
34168
34270
 
34169
34271
  // src/execution/dry-run.ts
@@ -34594,7 +34696,7 @@ var init_pipeline_result_handler = __esm(() => {
34594
34696
  });
34595
34697
 
34596
34698
  // src/execution/iteration-runner.ts
34597
- import { join as join50 } from "path";
34699
+ import { join as join51 } from "path";
34598
34700
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
34599
34701
  const logger = getSafeLogger();
34600
34702
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -34620,7 +34722,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34620
34722
  const storyStartTime = Date.now();
34621
34723
  const storyGitRef = await captureGitRef(ctx.workdir);
34622
34724
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
34623
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join50(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
34725
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join51(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
34624
34726
  const pipelineContext = {
34625
34727
  config: ctx.config,
34626
34728
  effectiveConfig,
@@ -34991,7 +35093,7 @@ async function writeStatusFile(filePath, status) {
34991
35093
  var init_status_file = () => {};
34992
35094
 
34993
35095
  // src/execution/status-writer.ts
34994
- import { join as join51 } from "path";
35096
+ import { join as join52 } from "path";
34995
35097
 
34996
35098
  class StatusWriter {
34997
35099
  statusFile;
@@ -35059,7 +35161,7 @@ class StatusWriter {
35059
35161
  if (!this._prd)
35060
35162
  return;
35061
35163
  const safeLogger = getSafeLogger();
35062
- const featureStatusPath = join51(featureDir, "status.json");
35164
+ const featureStatusPath = join52(featureDir, "status.json");
35063
35165
  try {
35064
35166
  const base = this.getSnapshot(totalCost, iterations);
35065
35167
  if (!base) {
@@ -35267,7 +35369,7 @@ __export(exports_run_initialization, {
35267
35369
  initializeRun: () => initializeRun,
35268
35370
  _reconcileDeps: () => _reconcileDeps
35269
35371
  });
35270
- import { join as join52 } from "path";
35372
+ import { join as join53 } from "path";
35271
35373
  async function reconcileState(prd, prdPath, workdir, config2) {
35272
35374
  const logger = getSafeLogger();
35273
35375
  let reconciledCount = 0;
@@ -35285,7 +35387,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
35285
35387
  });
35286
35388
  continue;
35287
35389
  }
35288
- const effectiveWorkdir = story.workdir ? join52(workdir, story.workdir) : workdir;
35390
+ const effectiveWorkdir = story.workdir ? join53(workdir, story.workdir) : workdir;
35289
35391
  try {
35290
35392
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
35291
35393
  if (!reviewResult.success) {
@@ -66419,8 +66521,8 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
66419
66521
  // bin/nax.ts
66420
66522
  init_source();
66421
66523
  import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
66422
- import { homedir as homedir10 } from "os";
66423
- import { join as join54 } from "path";
66524
+ import { homedir as homedir9 } from "os";
66525
+ import { join as join55 } from "path";
66424
66526
 
66425
66527
  // node_modules/commander/esm.mjs
66426
66528
  var import__ = __toESM(require_commander(), 1);
@@ -67617,7 +67719,7 @@ function validatePlanOutput(raw, feature, branch) {
67617
67719
  }
67618
67720
 
67619
67721
  // src/cli/plan.ts
67620
- var _deps2 = {
67722
+ var _planDeps = {
67621
67723
  readFile: (path) => Bun.file(path).text(),
67622
67724
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
67623
67725
  scanCodebase: (workdir) => scanCodebase(workdir),
@@ -67640,30 +67742,30 @@ async function planCommand(workdir, config2, options) {
67640
67742
  }
67641
67743
  const logger = getLogger();
67642
67744
  logger?.info("plan", "Reading spec", { from: options.from });
67643
- const specContent = await _deps2.readFile(options.from);
67745
+ const specContent = await _planDeps.readFile(options.from);
67644
67746
  logger?.info("plan", "Scanning codebase...");
67645
67747
  const [scan, discoveredPackages, pkg] = await Promise.all([
67646
- _deps2.scanCodebase(workdir),
67647
- _deps2.discoverWorkspacePackages(workdir),
67648
- _deps2.readPackageJson(workdir)
67748
+ _planDeps.scanCodebase(workdir),
67749
+ _planDeps.discoverWorkspacePackages(workdir),
67750
+ _planDeps.readPackageJson(workdir)
67649
67751
  ]);
67650
67752
  const codebaseContext = buildCodebaseContext2(scan);
67651
67753
  const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
67652
67754
  const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
67653
- const pkgJson = await _deps2.readPackageJsonAt(join11(workdir, rel, "package.json"));
67755
+ const pkgJson = await _planDeps.readPackageJsonAt(join11(workdir, rel, "package.json"));
67654
67756
  return buildPackageSummary(rel, pkgJson);
67655
67757
  })) : [];
67656
67758
  const projectName = detectProjectName(workdir, pkg);
67657
67759
  const branchName = options.branch ?? `feat/${options.feature}`;
67658
67760
  const outputDir = join11(naxDir, "features", options.feature);
67659
67761
  const outputPath = join11(outputDir, "prd.json");
67660
- await _deps2.mkdirp(outputDir);
67762
+ await _planDeps.mkdirp(outputDir);
67661
67763
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
67662
67764
  const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
67663
67765
  let rawResponse;
67664
67766
  if (options.auto) {
67665
67767
  const prompt = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails);
67666
- const cliAdapter = _deps2.getAgent(agentName);
67768
+ const cliAdapter = _planDeps.getAgent(agentName);
67667
67769
  if (!cliAdapter)
67668
67770
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
67669
67771
  let autoModel;
@@ -67684,10 +67786,10 @@ async function planCommand(workdir, config2, options) {
67684
67786
  } catch {}
67685
67787
  } else {
67686
67788
  const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails);
67687
- const adapter = _deps2.getAgent(agentName, config2);
67789
+ const adapter = _planDeps.getAgent(agentName, config2);
67688
67790
  if (!adapter)
67689
67791
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
67690
- const interactionBridge = _deps2.createInteractionBridge();
67792
+ const interactionBridge = _planDeps.createInteractionBridge();
67691
67793
  const pidRegistry = new PidRegistry(workdir);
67692
67794
  const resolvedPerm = resolvePermissions(config2, "plan");
67693
67795
  const resolvedModel = config2?.plan?.model ?? "balanced";
@@ -67718,14 +67820,14 @@ async function planCommand(workdir, config2, options) {
67718
67820
  await pidRegistry.killAll().catch(() => {});
67719
67821
  logger?.info("plan", "Interactive session ended", { durationMs: Date.now() - planStartTime });
67720
67822
  }
67721
- if (!_deps2.existsSync(outputPath)) {
67823
+ if (!_planDeps.existsSync(outputPath)) {
67722
67824
  throw new Error(`[plan] Agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
67723
67825
  }
67724
- rawResponse = await _deps2.readFile(outputPath);
67826
+ rawResponse = await _planDeps.readFile(outputPath);
67725
67827
  }
67726
67828
  const finalPrd = validatePlanOutput(rawResponse, options.feature, branchName);
67727
67829
  finalPrd.project = projectName;
67728
- await _deps2.writeFile(outputPath, JSON.stringify(finalPrd, null, 2));
67830
+ await _planDeps.writeFile(outputPath, JSON.stringify(finalPrd, null, 2));
67729
67831
  logger?.info("plan", "[OK] PRD written", { outputPath });
67730
67832
  return outputPath;
67731
67833
  }
@@ -67756,7 +67858,7 @@ function detectProjectName(workdir, pkg) {
67756
67858
  if (pkg?.name && typeof pkg.name === "string") {
67757
67859
  return pkg.name;
67758
67860
  }
67759
- const result = _deps2.spawnSync(["git", "remote", "get-url", "origin"], { cwd: workdir });
67861
+ const result = _planDeps.spawnSync(["git", "remote", "get-url", "origin"], { cwd: workdir });
67760
67862
  if (result.exitCode === 0) {
67761
67863
  const url2 = result.stdout.toString().trim();
67762
67864
  const match = url2.match(/\/([^/]+?)(?:\.git)?$/);
@@ -69527,6 +69629,7 @@ var FIELD_DESCRIPTIONS = {
69527
69629
  "quality.commands.typecheck": "Custom typecheck command",
69528
69630
  "quality.commands.lint": "Custom lint command",
69529
69631
  "quality.commands.test": "Custom test command",
69632
+ "quality.commands.build": "Custom build command",
69530
69633
  "quality.forceExit": "Append --forceExit to test command (prevents hangs)",
69531
69634
  "quality.detectOpenHandles": "Append --detectOpenHandles on timeout",
69532
69635
  "quality.detectOpenHandlesRetries": "Max retries with --detectOpenHandles",
@@ -69534,7 +69637,6 @@ var FIELD_DESCRIPTIONS = {
69534
69637
  "quality.drainTimeoutMs": "Deadline in ms to drain stdout/stderr after kill",
69535
69638
  "quality.shell": "Shell to use for verification commands",
69536
69639
  "quality.stripEnvVars": "Environment variables to strip during verification",
69537
- "quality.environmentalEscalationDivisor": "Divisor for environmental failure early escalation",
69538
69640
  tdd: "Test-driven development configuration",
69539
69641
  "tdd.maxRetries": "Max retries per TDD session before escalating",
69540
69642
  "tdd.autoVerifyIsolation": "Auto-verify test isolation between sessions",
@@ -69559,11 +69661,12 @@ var FIELD_DESCRIPTIONS = {
69559
69661
  "analyze.maxCodebaseSummaryTokens": "Max tokens for codebase summary",
69560
69662
  review: "Review phase configuration",
69561
69663
  "review.enabled": "Enable review phase",
69562
- "review.checks": "List of checks to run (typecheck, lint, test)",
69664
+ "review.checks": "List of checks to run (typecheck, lint, test, build)",
69563
69665
  "review.commands": "Custom commands per check",
69564
69666
  "review.commands.typecheck": "Custom typecheck command for review",
69565
69667
  "review.commands.lint": "Custom lint command for review",
69566
69668
  "review.commands.test": "Custom test command for review",
69669
+ "review.commands.build": "Custom build command for review",
69567
69670
  plan: "Planning phase configuration",
69568
69671
  "plan.model": "Model tier for planning",
69569
69672
  "plan.outputPath": "Output path for generated spec (relative to nax/)",
@@ -69940,24 +70043,32 @@ async function diagnose(options) {
69940
70043
 
69941
70044
  // src/commands/logs.ts
69942
70045
  import { existsSync as existsSync26 } from "fs";
69943
- import { join as join40 } from "path";
70046
+ import { join as join41 } from "path";
69944
70047
 
69945
70048
  // src/commands/logs-formatter.ts
69946
70049
  init_source();
69947
70050
  init_formatter();
69948
70051
  import { readdirSync as readdirSync7 } from "fs";
69949
- import { join as join39 } from "path";
70052
+ import { join as join40 } from "path";
69950
70053
 
69951
70054
  // src/commands/logs-reader.ts
69952
70055
  import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
69953
70056
  import { readdir as readdir3 } from "fs/promises";
70057
+ import { join as join39 } from "path";
70058
+
70059
+ // src/utils/paths.ts
69954
70060
  import { homedir as homedir5 } from "os";
69955
70061
  import { join as join38 } from "path";
69956
- var _deps7 = {
69957
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
70062
+ function getRunsDir() {
70063
+ return process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs");
70064
+ }
70065
+
70066
+ // src/commands/logs-reader.ts
70067
+ var _logsReaderDeps = {
70068
+ getRunsDir
69958
70069
  };
69959
70070
  async function resolveRunFileFromRegistry(runId) {
69960
- const runsDir = _deps7.getRunsDir();
70071
+ const runsDir = _logsReaderDeps.getRunsDir();
69961
70072
  let entries;
69962
70073
  try {
69963
70074
  entries = await readdir3(runsDir);
@@ -69966,7 +70077,7 @@ async function resolveRunFileFromRegistry(runId) {
69966
70077
  }
69967
70078
  let matched = null;
69968
70079
  for (const entry of entries) {
69969
- const metaPath = join38(runsDir, entry, "meta.json");
70080
+ const metaPath = join39(runsDir, entry, "meta.json");
69970
70081
  try {
69971
70082
  const meta3 = await Bun.file(metaPath).json();
69972
70083
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -69988,14 +70099,14 @@ async function resolveRunFileFromRegistry(runId) {
69988
70099
  return null;
69989
70100
  }
69990
70101
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
69991
- return join38(matched.eventsDir, specificFile ?? files[0]);
70102
+ return join39(matched.eventsDir, specificFile ?? files[0]);
69992
70103
  }
69993
70104
  async function selectRunFile(runsDir) {
69994
70105
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
69995
70106
  if (files.length === 0) {
69996
70107
  return null;
69997
70108
  }
69998
- return join38(runsDir, files[0]);
70109
+ return join39(runsDir, files[0]);
69999
70110
  }
70000
70111
  async function extractRunSummary(filePath) {
70001
70112
  const file2 = Bun.file(filePath);
@@ -70080,7 +70191,7 @@ Runs:
70080
70191
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
70081
70192
  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"));
70082
70193
  for (const file2 of files) {
70083
- const filePath = join39(runsDir, file2);
70194
+ const filePath = join40(runsDir, file2);
70084
70195
  const summary = await extractRunSummary(filePath);
70085
70196
  const timestamp = file2.replace(".jsonl", "");
70086
70197
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -70205,7 +70316,7 @@ async function logsCommand(options) {
70205
70316
  return;
70206
70317
  }
70207
70318
  const resolved = resolveProject({ dir: options.dir });
70208
- const naxDir = join40(resolved.projectDir, ".nax");
70319
+ const naxDir = join41(resolved.projectDir, ".nax");
70209
70320
  const configPath = resolved.configPath;
70210
70321
  const configFile = Bun.file(configPath);
70211
70322
  const config2 = await configFile.json();
@@ -70213,8 +70324,8 @@ async function logsCommand(options) {
70213
70324
  if (!featureName) {
70214
70325
  throw new Error("No feature specified in config.json");
70215
70326
  }
70216
- const featureDir = join40(naxDir, "features", featureName);
70217
- const runsDir = join40(featureDir, "runs");
70327
+ const featureDir = join41(naxDir, "features", featureName);
70328
+ const runsDir = join41(featureDir, "runs");
70218
70329
  if (!existsSync26(runsDir)) {
70219
70330
  throw new Error(`No runs directory found for feature: ${featureName}`);
70220
70331
  }
@@ -70239,7 +70350,7 @@ init_config();
70239
70350
  init_prd();
70240
70351
  init_precheck();
70241
70352
  import { existsSync as existsSync31 } from "fs";
70242
- import { join as join41 } from "path";
70353
+ import { join as join42 } from "path";
70243
70354
  async function precheckCommand(options) {
70244
70355
  const resolved = resolveProject({
70245
70356
  dir: options.dir,
@@ -70255,9 +70366,9 @@ async function precheckCommand(options) {
70255
70366
  process.exit(1);
70256
70367
  }
70257
70368
  }
70258
- const naxDir = join41(resolved.projectDir, ".nax");
70259
- const featureDir = join41(naxDir, "features", featureName);
70260
- const prdPath = join41(featureDir, "prd.json");
70369
+ const naxDir = join42(resolved.projectDir, ".nax");
70370
+ const featureDir = join42(naxDir, "features", featureName);
70371
+ const prdPath = join42(featureDir, "prd.json");
70261
70372
  if (!existsSync31(featureDir)) {
70262
70373
  console.error(source_default.red(`Feature not found: ${featureName}`));
70263
70374
  process.exit(1);
@@ -70280,11 +70391,10 @@ async function precheckCommand(options) {
70280
70391
  // src/commands/runs.ts
70281
70392
  init_source();
70282
70393
  import { readdir as readdir4 } from "fs/promises";
70283
- import { homedir as homedir6 } from "os";
70284
- import { join as join42 } from "path";
70394
+ import { join as join43 } from "path";
70285
70395
  var DEFAULT_LIMIT = 20;
70286
- var _deps9 = {
70287
- getRunsDir: () => join42(homedir6(), ".nax", "runs")
70396
+ var _runsCmdDeps = {
70397
+ getRunsDir
70288
70398
  };
70289
70399
  function formatDuration3(ms) {
70290
70400
  if (ms <= 0)
@@ -70326,7 +70436,7 @@ function pad3(str, width) {
70326
70436
  return str + " ".repeat(padding);
70327
70437
  }
70328
70438
  async function runsCommand(options = {}) {
70329
- const runsDir = _deps9.getRunsDir();
70439
+ const runsDir = _runsCmdDeps.getRunsDir();
70330
70440
  let entries;
70331
70441
  try {
70332
70442
  entries = await readdir4(runsDir);
@@ -70336,7 +70446,7 @@ async function runsCommand(options = {}) {
70336
70446
  }
70337
70447
  const rows = [];
70338
70448
  for (const entry of entries) {
70339
- const metaPath = join42(runsDir, entry, "meta.json");
70449
+ const metaPath = join43(runsDir, entry, "meta.json");
70340
70450
  let meta3;
70341
70451
  try {
70342
70452
  meta3 = await Bun.file(metaPath).json();
@@ -70413,7 +70523,7 @@ async function runsCommand(options = {}) {
70413
70523
 
70414
70524
  // src/commands/unlock.ts
70415
70525
  init_source();
70416
- import { join as join43 } from "path";
70526
+ import { join as join44 } from "path";
70417
70527
  function isProcessAlive3(pid) {
70418
70528
  try {
70419
70529
  process.kill(pid, 0);
@@ -70428,7 +70538,7 @@ function formatLockAge(ageMs) {
70428
70538
  }
70429
70539
  async function unlockCommand(options) {
70430
70540
  const workdir = options.dir ?? process.cwd();
70431
- const lockPath = join43(workdir, "nax.lock");
70541
+ const lockPath = join44(workdir, "nax.lock");
70432
70542
  const lockFile = Bun.file(lockPath);
70433
70543
  const exists = await lockFile.exists();
70434
70544
  if (!exists) {
@@ -78267,15 +78377,15 @@ Next: nax generate --package ${options.package}`));
78267
78377
  }
78268
78378
  return;
78269
78379
  }
78270
- const naxDir = join54(workdir, "nax");
78380
+ const naxDir = join55(workdir, "nax");
78271
78381
  if (existsSync34(naxDir) && !options.force) {
78272
78382
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
78273
78383
  return;
78274
78384
  }
78275
- mkdirSync6(join54(naxDir, "features"), { recursive: true });
78276
- mkdirSync6(join54(naxDir, "hooks"), { recursive: true });
78277
- await Bun.write(join54(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
78278
- await Bun.write(join54(naxDir, "hooks.json"), JSON.stringify({
78385
+ mkdirSync6(join55(naxDir, "features"), { recursive: true });
78386
+ mkdirSync6(join55(naxDir, "hooks"), { recursive: true });
78387
+ await Bun.write(join55(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
78388
+ await Bun.write(join55(naxDir, "hooks.json"), JSON.stringify({
78279
78389
  hooks: {
78280
78390
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
78281
78391
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -78283,12 +78393,12 @@ Next: nax generate --package ${options.package}`));
78283
78393
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
78284
78394
  }
78285
78395
  }, null, 2));
78286
- await Bun.write(join54(naxDir, ".gitignore"), `# nax temp files
78396
+ await Bun.write(join55(naxDir, ".gitignore"), `# nax temp files
78287
78397
  *.tmp
78288
78398
  .paused.json
78289
78399
  .nax-verifier-verdict.json
78290
78400
  `);
78291
- await Bun.write(join54(naxDir, "context.md"), `# Project Context
78401
+ await Bun.write(join55(naxDir, "context.md"), `# Project Context
78292
78402
 
78293
78403
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
78294
78404
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -78414,8 +78524,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
78414
78524
  console.error(source_default.red("nax not initialized. Run: nax init"));
78415
78525
  process.exit(1);
78416
78526
  }
78417
- const featureDir = join54(naxDir, "features", options.feature);
78418
- const prdPath = join54(featureDir, "prd.json");
78527
+ const featureDir = join55(naxDir, "features", options.feature);
78528
+ const prdPath = join55(featureDir, "prd.json");
78419
78529
  if (options.plan && options.from) {
78420
78530
  if (existsSync34(prdPath) && !options.force) {
78421
78531
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -78437,10 +78547,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
78437
78547
  }
78438
78548
  }
78439
78549
  try {
78440
- const planLogDir = join54(featureDir, "plan");
78550
+ const planLogDir = join55(featureDir, "plan");
78441
78551
  mkdirSync6(planLogDir, { recursive: true });
78442
78552
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78443
- const planLogPath = join54(planLogDir, `${planLogId}.jsonl`);
78553
+ const planLogPath = join55(planLogDir, `${planLogId}.jsonl`);
78444
78554
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
78445
78555
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
78446
78556
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -78478,10 +78588,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
78478
78588
  process.exit(1);
78479
78589
  }
78480
78590
  resetLogger();
78481
- const runsDir = join54(featureDir, "runs");
78591
+ const runsDir = join55(featureDir, "runs");
78482
78592
  mkdirSync6(runsDir, { recursive: true });
78483
78593
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78484
- const logFilePath = join54(runsDir, `${runId}.jsonl`);
78594
+ const logFilePath = join55(runsDir, `${runId}.jsonl`);
78485
78595
  const isTTY = process.stdout.isTTY ?? false;
78486
78596
  const headlessFlag = options.headless ?? false;
78487
78597
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -78497,7 +78607,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78497
78607
  config2.autoMode.defaultAgent = options.agent;
78498
78608
  }
78499
78609
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
78500
- const globalNaxDir = join54(homedir10(), ".nax");
78610
+ const globalNaxDir = join55(homedir9(), ".nax");
78501
78611
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
78502
78612
  const eventEmitter = new PipelineEventEmitter;
78503
78613
  let tuiInstance;
@@ -78520,7 +78630,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78520
78630
  } else {
78521
78631
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
78522
78632
  }
78523
- const statusFilePath = join54(workdir, "nax", "status.json");
78633
+ const statusFilePath = join55(workdir, "nax", "status.json");
78524
78634
  let parallel;
78525
78635
  if (options.parallel !== undefined) {
78526
78636
  parallel = Number.parseInt(options.parallel, 10);
@@ -78546,7 +78656,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78546
78656
  headless: useHeadless,
78547
78657
  skipPrecheck: options.skipPrecheck ?? false
78548
78658
  });
78549
- const latestSymlink = join54(runsDir, "latest.jsonl");
78659
+ const latestSymlink = join55(runsDir, "latest.jsonl");
78550
78660
  try {
78551
78661
  if (existsSync34(latestSymlink)) {
78552
78662
  Bun.spawnSync(["rm", latestSymlink]);
@@ -78584,9 +78694,9 @@ features.command("create <name>").description("Create a new feature").option("-d
78584
78694
  console.error(source_default.red("nax not initialized. Run: nax init"));
78585
78695
  process.exit(1);
78586
78696
  }
78587
- const featureDir = join54(naxDir, "features", name);
78697
+ const featureDir = join55(naxDir, "features", name);
78588
78698
  mkdirSync6(featureDir, { recursive: true });
78589
- await Bun.write(join54(featureDir, "spec.md"), `# Feature: ${name}
78699
+ await Bun.write(join55(featureDir, "spec.md"), `# Feature: ${name}
78590
78700
 
78591
78701
  ## Overview
78592
78702
 
@@ -78619,7 +78729,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78619
78729
 
78620
78730
  <!-- What this feature explicitly does NOT cover. -->
78621
78731
  `);
78622
- await Bun.write(join54(featureDir, "progress.txt"), `# Progress: ${name}
78732
+ await Bun.write(join55(featureDir, "progress.txt"), `# Progress: ${name}
78623
78733
 
78624
78734
  Created: ${new Date().toISOString()}
78625
78735
 
@@ -78645,7 +78755,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78645
78755
  console.error(source_default.red("nax not initialized."));
78646
78756
  process.exit(1);
78647
78757
  }
78648
- const featuresDir = join54(naxDir, "features");
78758
+ const featuresDir = join55(naxDir, "features");
78649
78759
  if (!existsSync34(featuresDir)) {
78650
78760
  console.log(source_default.dim("No features yet."));
78651
78761
  return;
@@ -78660,7 +78770,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78660
78770
  Features:
78661
78771
  `));
78662
78772
  for (const name of entries) {
78663
- const prdPath = join54(featuresDir, name, "prd.json");
78773
+ const prdPath = join55(featuresDir, name, "prd.json");
78664
78774
  if (existsSync34(prdPath)) {
78665
78775
  const prd = await loadPRD(prdPath);
78666
78776
  const c = countStories(prd);
@@ -78691,10 +78801,10 @@ Use: nax plan -f <feature> --from <spec>`));
78691
78801
  process.exit(1);
78692
78802
  }
78693
78803
  const config2 = await loadConfig(workdir);
78694
- const featureLogDir = join54(naxDir, "features", options.feature, "plan");
78804
+ const featureLogDir = join55(naxDir, "features", options.feature, "plan");
78695
78805
  mkdirSync6(featureLogDir, { recursive: true });
78696
78806
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78697
- const planLogPath = join54(featureLogDir, `${planLogId}.jsonl`);
78807
+ const planLogPath = join55(featureLogDir, `${planLogId}.jsonl`);
78698
78808
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
78699
78809
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
78700
78810
  try {
@@ -78731,7 +78841,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78731
78841
  console.error(source_default.red("nax not initialized. Run: nax init"));
78732
78842
  process.exit(1);
78733
78843
  }
78734
- const featureDir = join54(naxDir, "features", options.feature);
78844
+ const featureDir = join55(naxDir, "features", options.feature);
78735
78845
  if (!existsSync34(featureDir)) {
78736
78846
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
78737
78847
  process.exit(1);
@@ -78747,7 +78857,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78747
78857
  specPath: options.from,
78748
78858
  reclassify: options.reclassify
78749
78859
  });
78750
- const prdPath = join54(featureDir, "prd.json");
78860
+ const prdPath = join55(featureDir, "prd.json");
78751
78861
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
78752
78862
  const c = countStories(prd);
78753
78863
  console.log(source_default.green(`