@nathapp/nax 0.56.1 → 0.56.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +222 -110
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -3663,7 +3663,18 @@ var init_env = __esm(() => {
3663
3663
  init_logger2();
3664
3664
  ESSENTIAL_VARS = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3665
3665
  API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
3666
- ALLOWED_PREFIXES = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_", "ANTHROPIC_"];
3666
+ ALLOWED_PREFIXES = [
3667
+ "CLAUDE_",
3668
+ "NAX_",
3669
+ "CLAW_",
3670
+ "TURBO_",
3671
+ "ACPX_",
3672
+ "CODEX_",
3673
+ "GEMINI_",
3674
+ "ANTHROPIC_",
3675
+ "OPENCODE_",
3676
+ "MINIMAX_"
3677
+ ];
3667
3678
  });
3668
3679
 
3669
3680
  // src/agents/cost/pricing.ts
@@ -22056,7 +22067,7 @@ var package_default;
22056
22067
  var init_package = __esm(() => {
22057
22068
  package_default = {
22058
22069
  name: "@nathapp/nax",
22059
- version: "0.56.1",
22070
+ version: "0.56.3",
22060
22071
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22061
22072
  type: "module",
22062
22073
  bin: {
@@ -22135,8 +22146,8 @@ var init_version = __esm(() => {
22135
22146
  NAX_VERSION = package_default.version;
22136
22147
  NAX_COMMIT = (() => {
22137
22148
  try {
22138
- if (/^[0-9a-f]{6,10}$/.test("bfe07f9f"))
22139
- return "bfe07f9f";
22149
+ if (/^[0-9a-f]{6,10}$/.test("52dcc35f"))
22150
+ return "52dcc35f";
22140
22151
  } catch {}
22141
22152
  try {
22142
22153
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -22389,6 +22400,18 @@ var DEFAULT_FALLBACK_AGENT = "claude";
22389
22400
  var init_resolvers = () => {};
22390
22401
 
22391
22402
  // src/debate/session.ts
22403
+ function resolveDebaterModel(debater, config2) {
22404
+ const tier = debater.model ?? "fast";
22405
+ if (!config2?.models)
22406
+ return debater.model;
22407
+ try {
22408
+ const defaultAgent = config2.autoMode?.defaultAgent ?? debater.agent;
22409
+ const modelDef = resolveModelForAgent(config2.models, debater.agent, tier, defaultAgent);
22410
+ return modelDef.model;
22411
+ } catch {
22412
+ return debater.model;
22413
+ }
22414
+ }
22392
22415
  function buildFailedResult(storyId, stage, stageConfig, totalCostUsd = 0) {
22393
22416
  return {
22394
22417
  storyId,
@@ -22411,10 +22434,12 @@ class DebateSession {
22411
22434
  storyId;
22412
22435
  stage;
22413
22436
  stageConfig;
22437
+ config;
22414
22438
  constructor(opts) {
22415
22439
  this.storyId = opts.storyId;
22416
22440
  this.stage = opts.stage;
22417
22441
  this.stageConfig = opts.stageConfig;
22442
+ this.config = opts.config;
22418
22443
  }
22419
22444
  async run(prompt) {
22420
22445
  const sessionMode = this.stageConfig.sessionMode ?? "one-shot";
@@ -22437,11 +22462,17 @@ class DebateSession {
22437
22462
  }
22438
22463
  resolved.push({ debater, adapter });
22439
22464
  }
22465
+ logger?.info("debate", "debate:start", {
22466
+ storyId: this.storyId,
22467
+ stage: this.stage,
22468
+ debaters: resolved.map((r) => r.debater.agent)
22469
+ });
22440
22470
  const sessions = [];
22441
22471
  try {
22442
22472
  for (let i = 0;i < resolved.length; i++) {
22443
22473
  const { debater, adapter } = resolved[i];
22444
- const cmdStr = `acpx --model ${debater.model} ${debater.agent}`;
22474
+ const resolvedModel = resolveDebaterModel(debater, this.config);
22475
+ const cmdStr = resolvedModel ? `acpx --model ${resolvedModel} ${debater.agent}` : `acpx ${debater.agent}`;
22445
22476
  const client = _debateSessionDeps.createSpawnAcpClient(cmdStr);
22446
22477
  const sessionName = `nax-debate-${this.storyId}-${i}`;
22447
22478
  try {
@@ -22457,9 +22488,19 @@ class DebateSession {
22457
22488
  }
22458
22489
  if (sessions.length < 2) {
22459
22490
  if (sessions.length === 1) {
22491
+ logger?.warn("debate", "debate:fallback", {
22492
+ storyId: this.storyId,
22493
+ stage: this.stage,
22494
+ reason: "only 1 session created"
22495
+ });
22460
22496
  const solo = sessions[0];
22461
22497
  const response = await solo.session.prompt(prompt);
22462
22498
  const output = extractSessionOutput(response);
22499
+ logger?.info("debate", "debate:result", {
22500
+ storyId: this.storyId,
22501
+ stage: this.stage,
22502
+ outcome: "passed"
22503
+ });
22463
22504
  return {
22464
22505
  storyId: this.storyId,
22465
22506
  stage: this.stage,
@@ -22471,6 +22512,11 @@ class DebateSession {
22471
22512
  totalCostUsd
22472
22513
  };
22473
22514
  }
22515
+ logger?.warn("debate", "debate:fallback", {
22516
+ storyId: this.storyId,
22517
+ stage: this.stage,
22518
+ reason: "no sessions created"
22519
+ });
22474
22520
  return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22475
22521
  }
22476
22522
  const proposalSettled = await Promise.allSettled(sessions.map(({ session }) => session.prompt(prompt)));
@@ -22486,8 +22532,22 @@ class DebateSession {
22486
22532
  }
22487
22533
  }
22488
22534
  if (successfulSessions.length < 2) {
22535
+ logger?.warn("debate", "debate:fallback", {
22536
+ storyId: this.storyId,
22537
+ stage: this.stage,
22538
+ reason: "fewer than 2 proposal rounds succeeded"
22539
+ });
22489
22540
  return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22490
22541
  }
22542
+ for (let i = 0;i < successfulSessions.length; i++) {
22543
+ const s = successfulSessions[i];
22544
+ logger?.info("debate", "debate:proposal", {
22545
+ storyId: this.storyId,
22546
+ stage: this.stage,
22547
+ debaterIndex: i,
22548
+ agent: s.entry.debater.agent
22549
+ });
22550
+ }
22491
22551
  let critiqueOutputs = [];
22492
22552
  if (config2.rounds > 1) {
22493
22553
  const proposalOutputs2 = successfulSessions.map((s) => s.output);
@@ -22506,6 +22566,11 @@ class DebateSession {
22506
22566
  debater: s.entry.debater,
22507
22567
  output: s.output
22508
22568
  }));
22569
+ logger?.info("debate", "debate:result", {
22570
+ storyId: this.storyId,
22571
+ stage: this.stage,
22572
+ outcome
22573
+ });
22509
22574
  return {
22510
22575
  storyId: this.storyId,
22511
22576
  stage: this.stage,
@@ -22534,16 +22599,40 @@ class DebateSession {
22534
22599
  }
22535
22600
  resolved.push({ debater, adapter });
22536
22601
  }
22537
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: debater.model }).then((output) => ({ debater, adapter, output, cost: 0 }))));
22602
+ logger?.info("debate", "debate:start", {
22603
+ storyId: this.storyId,
22604
+ stage: this.stage,
22605
+ debaters: resolved.map((r) => r.debater.agent)
22606
+ });
22607
+ const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: resolveDebaterModel(debater, this.config) }).then((output) => ({ debater, adapter, output, cost: 0 }))));
22538
22608
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22539
22609
  for (const r of proposalSettled) {
22540
22610
  if (r.status === "fulfilled") {
22541
22611
  totalCostUsd += r.value.cost;
22542
22612
  }
22543
22613
  }
22614
+ for (let i = 0;i < successful.length; i++) {
22615
+ logger?.info("debate", "debate:proposal", {
22616
+ storyId: this.storyId,
22617
+ stage: this.stage,
22618
+ debaterIndex: i,
22619
+ agent: successful[i].debater.agent,
22620
+ model: resolveDebaterModel(successful[i].debater, this.config)
22621
+ });
22622
+ }
22544
22623
  if (successful.length < 2) {
22545
22624
  if (successful.length === 1) {
22625
+ logger?.warn("debate", "debate:fallback", {
22626
+ storyId: this.storyId,
22627
+ stage: this.stage,
22628
+ reason: "only 1 debater succeeded"
22629
+ });
22546
22630
  const solo = successful[0];
22631
+ logger?.info("debate", "debate:result", {
22632
+ storyId: this.storyId,
22633
+ stage: this.stage,
22634
+ outcome: "passed"
22635
+ });
22547
22636
  return {
22548
22637
  storyId: this.storyId,
22549
22638
  stage: this.stage,
@@ -22557,8 +22646,20 @@ class DebateSession {
22557
22646
  }
22558
22647
  if (resolved.length > 0) {
22559
22648
  const { adapter: fallbackAdapter, debater: fallbackDebater } = resolved[0];
22649
+ logger?.warn("debate", "debate:fallback", {
22650
+ storyId: this.storyId,
22651
+ stage: this.stage,
22652
+ reason: "all debaters failed \u2014 retrying with first adapter"
22653
+ });
22560
22654
  try {
22561
- const fallbackOutput = await fallbackAdapter.complete(prompt, { model: fallbackDebater.model });
22655
+ const fallbackOutput = await fallbackAdapter.complete(prompt, {
22656
+ model: resolveDebaterModel(fallbackDebater, this.config)
22657
+ });
22658
+ logger?.info("debate", "debate:result", {
22659
+ storyId: this.storyId,
22660
+ stage: this.stage,
22661
+ outcome: "passed"
22662
+ });
22562
22663
  return {
22563
22664
  storyId: this.storyId,
22564
22665
  stage: this.stage,
@@ -22577,7 +22678,7 @@ class DebateSession {
22577
22678
  if (config2.rounds > 1) {
22578
22679
  const proposalOutputs2 = successful.map((p) => p.output);
22579
22680
  const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => adapter.complete(buildCritiquePrompt(prompt, proposalOutputs2, i), {
22580
- model: debater.model
22681
+ model: resolveDebaterModel(debater, this.config)
22581
22682
  })));
22582
22683
  for (const r of critiqueSettled) {
22583
22684
  if (r.status === "fulfilled") {
@@ -22593,6 +22694,11 @@ class DebateSession {
22593
22694
  debater: p.debater,
22594
22695
  output: p.output
22595
22696
  }));
22697
+ logger?.info("debate", "debate:result", {
22698
+ storyId: this.storyId,
22699
+ stage: this.stage,
22700
+ outcome
22701
+ });
22596
22702
  return {
22597
22703
  storyId: this.storyId,
22598
22704
  stage: this.stage,
@@ -22604,7 +22710,7 @@ class DebateSession {
22604
22710
  totalCostUsd
22605
22711
  };
22606
22712
  }
22607
- async resolve(proposalOutputs, critiqueOutputs, successful) {
22713
+ async resolve(proposalOutputs, critiqueOutputs, _successful) {
22608
22714
  const resolverConfig = this.stageConfig.resolver;
22609
22715
  if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
22610
22716
  return majorityResolver(proposalOutputs, resolverConfig.type === "majority-fail-open");
@@ -22631,6 +22737,7 @@ var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
22631
22737
  var init_session = __esm(() => {
22632
22738
  init_spawn_client();
22633
22739
  init_registry();
22740
+ init_config();
22634
22741
  init_logger2();
22635
22742
  init_resolvers();
22636
22743
  _debateSessionDeps = {
@@ -26276,7 +26383,8 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
26276
26383
  const debateSession = _semanticDeps.createDebateSession({
26277
26384
  storyId: story.id,
26278
26385
  stage: "review",
26279
- stageConfig: reviewStageConfig
26386
+ stageConfig: reviewStageConfig,
26387
+ config: naxConfig
26280
26388
  });
26281
26389
  const debateResult = await debateSession.run(prompt);
26282
26390
  let passCount = 0;
@@ -27030,17 +27138,15 @@ var init_autofix = __esm(() => {
27030
27138
  });
27031
27139
 
27032
27140
  // src/execution/progress.ts
27033
- import { mkdirSync as mkdirSync2 } from "fs";
27141
+ import { appendFile as appendFile2, mkdir } from "fs/promises";
27034
27142
  import { join as join18 } from "path";
27035
27143
  async function appendProgress(featureDir, storyId, status, message) {
27036
- mkdirSync2(featureDir, { recursive: true });
27144
+ await mkdir(featureDir, { recursive: true });
27037
27145
  const progressPath = join18(featureDir, "progress.txt");
27038
27146
  const timestamp = new Date().toISOString();
27039
27147
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
27040
27148
  `;
27041
- const file3 = Bun.file(progressPath);
27042
- const existing = await file3.exists() ? await file3.text() : "";
27043
- await Bun.write(progressPath, existing + entry);
27149
+ await appendFile2(progressPath, entry);
27044
27150
  }
27045
27151
  var init_progress = () => {};
27046
27152
 
@@ -32384,7 +32490,7 @@ __export(exports_init_context, {
32384
32490
  _initContextDeps: () => _initContextDeps
32385
32491
  });
32386
32492
  import { existsSync as existsSync24 } from "fs";
32387
- import { mkdir } from "fs/promises";
32493
+ import { mkdir as mkdir2 } from "fs/promises";
32388
32494
  import { basename as basename3, join as join30 } from "path";
32389
32495
  async function findFiles(dir, maxFiles = 200) {
32390
32496
  try {
@@ -32628,7 +32734,7 @@ async function initPackage(repoRoot, packagePath, force = false) {
32628
32734
  return;
32629
32735
  }
32630
32736
  if (!existsSync24(naxDir)) {
32631
- await mkdir(naxDir, { recursive: true });
32737
+ await mkdir2(naxDir, { recursive: true });
32632
32738
  }
32633
32739
  const content = generatePackageContextTemplate(packagePath);
32634
32740
  await Bun.write(contextPath, content);
@@ -32643,7 +32749,7 @@ async function initContext(projectRoot, options = {}) {
32643
32749
  return;
32644
32750
  }
32645
32751
  if (!existsSync24(naxDir)) {
32646
- await mkdir(naxDir, { recursive: true });
32752
+ await mkdir2(naxDir, { recursive: true });
32647
32753
  }
32648
32754
  const scan = await scanProject(projectRoot);
32649
32755
  let content;
@@ -34346,7 +34452,7 @@ var init_headless_formatter = __esm(() => {
34346
34452
  });
34347
34453
 
34348
34454
  // src/pipeline/subscribers/events-writer.ts
34349
- import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
34455
+ import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
34350
34456
  import { homedir as homedir5 } from "os";
34351
34457
  import { basename as basename6, join as join47 } from "path";
34352
34458
  function wireEventsWriter(bus, feature, runId, workdir) {
@@ -34359,10 +34465,10 @@ function wireEventsWriter(bus, feature, runId, workdir) {
34359
34465
  return (async () => {
34360
34466
  try {
34361
34467
  if (!dirReady) {
34362
- await mkdir2(eventsDir, { recursive: true });
34468
+ await mkdir3(eventsDir, { recursive: true });
34363
34469
  dirReady = true;
34364
34470
  }
34365
- await appendFile2(eventsFile, `${JSON.stringify(line)}
34471
+ await appendFile3(eventsFile, `${JSON.stringify(line)}
34366
34472
  `);
34367
34473
  } catch (err) {
34368
34474
  logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
@@ -34532,7 +34638,7 @@ var init_interaction2 = __esm(() => {
34532
34638
  });
34533
34639
 
34534
34640
  // src/pipeline/subscribers/registry.ts
34535
- import { mkdir as mkdir3, writeFile } from "fs/promises";
34641
+ import { mkdir as mkdir4, writeFile } from "fs/promises";
34536
34642
  import { homedir as homedir6 } from "os";
34537
34643
  import { basename as basename7, join as join48 } from "path";
34538
34644
  function wireRegistry(bus, feature, runId, workdir) {
@@ -34543,7 +34649,7 @@ function wireRegistry(bus, feature, runId, workdir) {
34543
34649
  const unsub = bus.on("run:started", (_ev) => {
34544
34650
  return (async () => {
34545
34651
  try {
34546
- await mkdir3(runDir, { recursive: true });
34652
+ await mkdir4(runDir, { recursive: true });
34547
34653
  const meta3 = {
34548
34654
  runId,
34549
34655
  project,
@@ -35495,7 +35601,7 @@ __export(exports_manager, {
35495
35601
  WorktreeManager: () => WorktreeManager
35496
35602
  });
35497
35603
  import { existsSync as existsSync32, symlinkSync } from "fs";
35498
- import { mkdir as mkdir4 } from "fs/promises";
35604
+ import { mkdir as mkdir5 } from "fs/promises";
35499
35605
  import { join as join50 } from "path";
35500
35606
 
35501
35607
  class WorktreeManager {
@@ -35504,7 +35610,7 @@ class WorktreeManager {
35504
35610
  const infoDir = join50(projectRoot, ".git", "info");
35505
35611
  const excludePath = join50(infoDir, "exclude");
35506
35612
  try {
35507
- await mkdir4(infoDir, { recursive: true });
35613
+ await mkdir5(infoDir, { recursive: true });
35508
35614
  let existing = "";
35509
35615
  if (existsSync32(excludePath)) {
35510
35616
  existing = await Bun.file(excludePath).text();
@@ -36685,6 +36791,7 @@ class StatusWriter {
36685
36791
  _prd = null;
36686
36792
  _currentStory = null;
36687
36793
  _consecutiveWriteFailures = 0;
36794
+ _mutex = Promise.resolve();
36688
36795
  constructor(statusFile, config2, ctx) {
36689
36796
  this.statusFile = statusFile;
36690
36797
  this.costLimit = config2.execution.costLimit === Number.POSITIVE_INFINITY ? null : config2.execution.costLimit;
@@ -36720,6 +36827,11 @@ class StatusWriter {
36720
36827
  async update(totalCost, iterations, overrides = {}) {
36721
36828
  if (!this._prd)
36722
36829
  return;
36830
+ const write = this._doUpdate(totalCost, iterations, overrides);
36831
+ this._mutex = this._mutex.then(() => write).catch(() => write);
36832
+ return this._mutex;
36833
+ }
36834
+ async _doUpdate(totalCost, iterations, overrides) {
36723
36835
  const safeLogger = getSafeLogger();
36724
36836
  try {
36725
36837
  const base = this.getSnapshot(totalCost, iterations);
@@ -36744,20 +36856,23 @@ class StatusWriter {
36744
36856
  return;
36745
36857
  const safeLogger = getSafeLogger();
36746
36858
  const featureStatusPath = join52(featureDir, "status.json");
36747
- try {
36748
- const base = this.getSnapshot(totalCost, iterations);
36749
- if (!base) {
36750
- throw new Error("Failed to get snapshot");
36859
+ const write = async () => {
36860
+ try {
36861
+ const base = this.getSnapshot(totalCost, iterations);
36862
+ if (!base)
36863
+ throw new Error("Failed to get snapshot");
36864
+ const state = { ...base, ...overrides };
36865
+ await writeStatusFile(featureStatusPath, buildStatusSnapshot(state));
36866
+ safeLogger?.debug("status-file", "Feature status written", { path: featureStatusPath });
36867
+ } catch (err) {
36868
+ safeLogger?.warn("status-file", "Failed to write feature status file (non-fatal)", {
36869
+ path: featureStatusPath,
36870
+ error: err.message
36871
+ });
36751
36872
  }
36752
- const state = { ...base, ...overrides };
36753
- await writeStatusFile(featureStatusPath, buildStatusSnapshot(state));
36754
- safeLogger?.debug("status-file", "Feature status written", { path: featureStatusPath });
36755
- } catch (err) {
36756
- safeLogger?.warn("status-file", "Failed to write feature status file (non-fatal)", {
36757
- path: featureStatusPath,
36758
- error: err.message
36759
- });
36760
- }
36873
+ };
36874
+ this._mutex = this._mutex.then(write).catch(() => write());
36875
+ return this._mutex;
36761
36876
  }
36762
36877
  }
36763
36878
  var init_status_writer = __esm(() => {
@@ -36856,7 +36971,7 @@ var exports_precheck_runner = {};
36856
36971
  __export(exports_precheck_runner, {
36857
36972
  runPrecheckValidation: () => runPrecheckValidation
36858
36973
  });
36859
- import { mkdirSync as mkdirSync5 } from "fs";
36974
+ import { mkdirSync as mkdirSync4 } from "fs";
36860
36975
  import path17 from "path";
36861
36976
  async function runPrecheckValidation(ctx) {
36862
36977
  const logger = getSafeLogger();
@@ -36871,7 +36986,7 @@ async function runPrecheckValidation(ctx) {
36871
36986
  format: "human"
36872
36987
  });
36873
36988
  if (ctx.logFilePath) {
36874
- mkdirSync5(path17.dirname(ctx.logFilePath), { recursive: true });
36989
+ mkdirSync4(path17.dirname(ctx.logFilePath), { recursive: true });
36875
36990
  const precheckLog = {
36876
36991
  type: "precheck",
36877
36992
  timestamp: new Date().toISOString(),
@@ -68177,7 +68292,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
68177
68292
 
68178
68293
  // bin/nax.ts
68179
68294
  init_source();
68180
- import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
68295
+ import { existsSync as existsSync34, mkdirSync as mkdirSync5 } from "fs";
68181
68296
  import { homedir as homedir8 } from "os";
68182
68297
  import { join as join55 } from "path";
68183
68298
 
@@ -69453,78 +69568,75 @@ async function planCommand(workdir, config2, options) {
69453
69568
  autoModel = resolveModelForAgent2(config2.models, defaultAgent, planTier, defaultAgent).model;
69454
69569
  }
69455
69570
  } catch {}
69456
- if (isAcp) {
69457
- logger?.info("plan", "Starting ACP auto planning session", {
69458
- agent: agentName,
69459
- model: autoModel ?? config2?.plan?.model ?? "balanced",
69460
- workdir,
69461
- feature: options.feature,
69462
- timeoutSeconds
69463
- });
69464
- const pidRegistry = new PidRegistry(workdir);
69465
- try {
69466
- await adapter.plan({
69467
- prompt,
69571
+ const runSingleAgentPlan = async () => {
69572
+ if (isAcp) {
69573
+ logger?.info("plan", "Starting ACP auto planning session", {
69574
+ agent: agentName,
69575
+ model: autoModel ?? config2?.plan?.model ?? "balanced",
69468
69576
  workdir,
69469
- interactive: false,
69470
- timeoutSeconds,
69471
- config: config2,
69472
- modelTier: config2?.plan?.model ?? "balanced",
69473
- dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69474
- maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69475
- featureName: options.feature,
69476
- pidRegistry,
69477
- sessionRole: "plan"
69478
- });
69479
- } finally {
69480
- await pidRegistry.killAll().catch(() => {});
69481
- }
69482
- if (!_planDeps.existsSync(outputPath)) {
69483
- throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69484
- }
69485
- rawResponse = await _planDeps.readFile(outputPath);
69486
- } else {
69487
- const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69488
- if (debateEnabled) {
69489
- const planStageConfig = config2.debate?.stages.plan;
69490
- const debateSession = _planDeps.createDebateSession({
69491
- storyId: options.feature,
69492
- stage: "plan",
69493
- stageConfig: planStageConfig
69577
+ feature: options.feature,
69578
+ timeoutSeconds
69494
69579
  });
69495
- const debateResult = await debateSession.run(prompt);
69496
- if (debateResult.outcome !== "failed" && debateResult.output) {
69497
- rawResponse = debateResult.output;
69498
- } else {
69499
- logger?.warn("debate", "All debaters failed \u2014 falling back to single agent", {
69500
- stage: "debate",
69501
- event: "fallback"
69502
- });
69503
- rawResponse = await adapter.complete(prompt, {
69504
- model: autoModel,
69505
- jsonMode: true,
69580
+ const pidRegistry = new PidRegistry(workdir);
69581
+ try {
69582
+ await adapter.plan({
69583
+ prompt,
69506
69584
  workdir,
69585
+ interactive: false,
69586
+ timeoutSeconds,
69507
69587
  config: config2,
69588
+ modelTier: config2?.plan?.model ?? "balanced",
69589
+ dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69590
+ maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69508
69591
  featureName: options.feature,
69592
+ pidRegistry,
69509
69593
  sessionRole: "plan"
69510
69594
  });
69595
+ } finally {
69596
+ await pidRegistry.killAll().catch(() => {});
69511
69597
  }
69512
- } else {
69513
- rawResponse = await adapter.complete(prompt, {
69514
- model: autoModel,
69515
- jsonMode: true,
69516
- workdir,
69517
- config: config2,
69518
- featureName: options.feature,
69519
- sessionRole: "plan"
69520
- });
69598
+ if (!_planDeps.existsSync(outputPath)) {
69599
+ throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69600
+ }
69601
+ return await _planDeps.readFile(outputPath);
69521
69602
  }
69603
+ let result = await adapter.complete(prompt, {
69604
+ model: autoModel,
69605
+ jsonMode: true,
69606
+ workdir,
69607
+ config: config2,
69608
+ featureName: options.feature,
69609
+ sessionRole: "plan"
69610
+ });
69522
69611
  try {
69523
- const envelope = JSON.parse(rawResponse);
69612
+ const envelope = JSON.parse(result);
69524
69613
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
69525
- rawResponse = envelope.result;
69614
+ result = envelope.result;
69526
69615
  }
69527
69616
  } catch {}
69617
+ return result;
69618
+ };
69619
+ const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69620
+ if (debateEnabled) {
69621
+ const planStageConfig = config2?.debate?.stages.plan;
69622
+ const debateSession = _planDeps.createDebateSession({
69623
+ storyId: options.feature,
69624
+ stage: "plan",
69625
+ stageConfig: planStageConfig,
69626
+ config: config2
69627
+ });
69628
+ const debateResult = await debateSession.run(prompt);
69629
+ if (debateResult.outcome !== "failed" && debateResult.output) {
69630
+ rawResponse = debateResult.output;
69631
+ } else {
69632
+ logger?.warn("debate", "All debaters failed \u2014 falling back to single agent", {
69633
+ stage: "debate",
69634
+ event: "fallback"
69635
+ });
69636
+ rawResponse = await runSingleAgentPlan();
69637
+ }
69638
+ } else {
69639
+ rawResponse = await runSingleAgentPlan();
69528
69640
  }
69529
69641
  } else {
69530
69642
  const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
@@ -70589,7 +70701,7 @@ async function runsShowCommand(options) {
70589
70701
  }
70590
70702
  // src/cli/prompts-main.ts
70591
70703
  init_logger2();
70592
- import { existsSync as existsSync22, mkdirSync as mkdirSync3 } from "fs";
70704
+ import { existsSync as existsSync22, mkdirSync as mkdirSync2 } from "fs";
70593
70705
  import { join as join28 } from "path";
70594
70706
 
70595
70707
  // src/pipeline/index.ts
@@ -70729,7 +70841,7 @@ async function promptsCommand(options) {
70729
70841
  throw new Error(storyId ? `Story "${storyId}" not found in feature "${feature}"` : `No stories found in feature "${feature}"`);
70730
70842
  }
70731
70843
  if (outputDir) {
70732
- mkdirSync3(outputDir, { recursive: true });
70844
+ mkdirSync2(outputDir, { recursive: true });
70733
70845
  }
70734
70846
  logger.info("cli", "Assembling prompts", {
70735
70847
  feature,
@@ -70809,7 +70921,7 @@ ${"=".repeat(80)}`);
70809
70921
  return processedStories;
70810
70922
  }
70811
70923
  // src/cli/prompts-init.ts
70812
- import { existsSync as existsSync23, mkdirSync as mkdirSync4 } from "fs";
70924
+ import { existsSync as existsSync23, mkdirSync as mkdirSync3 } from "fs";
70813
70925
  import { join as join29 } from "path";
70814
70926
  var TEMPLATE_ROLES = [
70815
70927
  { file: "test-writer.md", role: "test-writer" },
@@ -70835,7 +70947,7 @@ var TEMPLATE_HEADER = `<!--
70835
70947
  async function promptsInitCommand(options) {
70836
70948
  const { workdir, force = false, autoWireConfig = true } = options;
70837
70949
  const templatesDir = join29(workdir, ".nax", "templates");
70838
- mkdirSync4(templatesDir, { recursive: true });
70950
+ mkdirSync3(templatesDir, { recursive: true });
70839
70951
  const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join29(templatesDir, f)));
70840
70952
  if (existingFiles.length > 0 && !force) {
70841
70953
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
@@ -80304,8 +80416,8 @@ Next: nax generate --package ${options.package}`));
80304
80416
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
80305
80417
  return;
80306
80418
  }
80307
- mkdirSync6(join55(naxDir, "features"), { recursive: true });
80308
- mkdirSync6(join55(naxDir, "hooks"), { recursive: true });
80419
+ mkdirSync5(join55(naxDir, "features"), { recursive: true });
80420
+ mkdirSync5(join55(naxDir, "hooks"), { recursive: true });
80309
80421
  await Bun.write(join55(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
80310
80422
  await Bun.write(join55(naxDir, "hooks.json"), JSON.stringify({
80311
80423
  hooks: {
@@ -80470,7 +80582,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80470
80582
  }
80471
80583
  try {
80472
80584
  const planLogDir = join55(featureDir, "plan");
80473
- mkdirSync6(planLogDir, { recursive: true });
80585
+ mkdirSync5(planLogDir, { recursive: true });
80474
80586
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80475
80587
  const planLogPath = join55(planLogDir, `${planLogId}.jsonl`);
80476
80588
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
@@ -80517,7 +80629,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80517
80629
  }
80518
80630
  resetLogger();
80519
80631
  const runsDir = join55(featureDir, "runs");
80520
- mkdirSync6(runsDir, { recursive: true });
80632
+ mkdirSync5(runsDir, { recursive: true });
80521
80633
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80522
80634
  const logFilePath = join55(runsDir, `${runId}.jsonl`);
80523
80635
  const isTTY = process.stdout.isTTY ?? false;
@@ -80623,7 +80735,7 @@ features.command("create <name>").description("Create a new feature").option("-d
80623
80735
  process.exit(1);
80624
80736
  }
80625
80737
  const featureDir = join55(naxDir, "features", name);
80626
- mkdirSync6(featureDir, { recursive: true });
80738
+ mkdirSync5(featureDir, { recursive: true });
80627
80739
  await Bun.write(join55(featureDir, "spec.md"), `# Feature: ${name}
80628
80740
 
80629
80741
  ## Overview
@@ -80730,7 +80842,7 @@ Use: nax plan -f <feature> --from <spec>`));
80730
80842
  }
80731
80843
  const config2 = await loadConfig(workdir);
80732
80844
  const featureLogDir = join55(naxDir, "features", options.feature, "plan");
80733
- mkdirSync6(featureLogDir, { recursive: true });
80845
+ mkdirSync5(featureLogDir, { recursive: true });
80734
80846
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80735
80847
  const planLogPath = join55(featureLogDir, `${planLogId}.jsonl`);
80736
80848
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.56.1",
3
+ "version": "0.56.3",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {