@nathapp/nax 0.42.2 → 0.42.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.
package/dist/nax.js CHANGED
@@ -18054,6 +18054,13 @@ var init_defaults = __esm(() => {
18054
18054
  });
18055
18055
 
18056
18056
  // src/config/schema.ts
18057
+ var exports_schema = {};
18058
+ __export(exports_schema, {
18059
+ resolveModel: () => resolveModel,
18060
+ NaxConfigSchema: () => NaxConfigSchema,
18061
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
18062
+ AcceptanceConfigSchema: () => AcceptanceConfigSchema
18063
+ });
18057
18064
  var init_schema = __esm(() => {
18058
18065
  init_types3();
18059
18066
  init_schemas3();
@@ -18968,8 +18975,6 @@ class SpawnAcpSession {
18968
18975
  "--cwd",
18969
18976
  this.cwd,
18970
18977
  ...this.permissionMode === "approve-all" ? ["--approve-all"] : [],
18971
- "--format",
18972
- "json",
18973
18978
  "--model",
18974
18979
  this.model,
18975
18980
  "--timeout",
@@ -19500,10 +19505,15 @@ class AcpAgentAdapter {
19500
19505
  async plan(options) {
19501
19506
  let modelDef = options.modelDef;
19502
19507
  if (!modelDef && options.config?.models) {
19503
- const { resolveBalancedModelDef: resolveBalancedModelDef2 } = await Promise.resolve().then(() => (init_model_resolution(), exports_model_resolution));
19504
- try {
19505
- modelDef = resolveBalancedModelDef2(options.config);
19506
- } catch {}
19508
+ const tier = options.modelTier ?? "balanced";
19509
+ const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
19510
+ const models = options.config.models;
19511
+ const entry = models[tier] ?? models.balanced;
19512
+ if (entry) {
19513
+ try {
19514
+ modelDef = resolveModel2(entry);
19515
+ } catch {}
19516
+ }
19507
19517
  }
19508
19518
  modelDef ??= { provider: "anthropic", model: "claude-sonnet-4-5-20250514" };
19509
19519
  const timeoutSeconds = options.timeoutSeconds ?? options.config?.execution?.sessionTimeoutSeconds ?? 600;
@@ -21849,7 +21859,7 @@ var package_default;
21849
21859
  var init_package = __esm(() => {
21850
21860
  package_default = {
21851
21861
  name: "@nathapp/nax",
21852
- version: "0.42.2",
21862
+ version: "0.42.3",
21853
21863
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
21854
21864
  type: "module",
21855
21865
  bin: {
@@ -21922,8 +21932,8 @@ var init_version = __esm(() => {
21922
21932
  NAX_VERSION = package_default.version;
21923
21933
  NAX_COMMIT = (() => {
21924
21934
  try {
21925
- if (/^[0-9a-f]{6,10}$/.test("9c1f716"))
21926
- return "9c1f716";
21935
+ if (/^[0-9a-f]{6,10}$/.test("b051dcb"))
21936
+ return "b051dcb";
21927
21937
  } catch {}
21928
21938
  try {
21929
21939
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -65743,7 +65753,8 @@ var _deps2 = {
65743
65753
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
65744
65754
  return { stdout: result.stdout, exitCode: result.exitCode };
65745
65755
  },
65746
- mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {})
65756
+ mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {}),
65757
+ existsSync: (path) => existsSync9(path)
65747
65758
  };
65748
65759
  async function planCommand(workdir, config2, options) {
65749
65760
  const naxDir = join10(workdir, "nax");
@@ -65759,11 +65770,14 @@ async function planCommand(workdir, config2, options) {
65759
65770
  const pkg = await _deps2.readPackageJson(workdir);
65760
65771
  const projectName = detectProjectName(workdir, pkg);
65761
65772
  const branchName = options.branch ?? `feat/${options.feature}`;
65762
- const prompt = buildPlanningPrompt(specContent, codebaseContext);
65773
+ const outputDir = join10(naxDir, "features", options.feature);
65774
+ const outputPath = join10(outputDir, "prd.json");
65775
+ await _deps2.mkdirp(outputDir);
65763
65776
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
65764
65777
  const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
65765
65778
  let rawResponse;
65766
65779
  if (options.auto) {
65780
+ const prompt = buildPlanningPrompt(specContent, codebaseContext);
65767
65781
  const cliAdapter = _deps2.getAgent(agentName);
65768
65782
  if (!cliAdapter)
65769
65783
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
@@ -65775,33 +65789,34 @@ async function planCommand(workdir, config2, options) {
65775
65789
  }
65776
65790
  } catch {}
65777
65791
  } else {
65792
+ const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath);
65778
65793
  const adapter = _deps2.getAgent(agentName, config2);
65779
65794
  if (!adapter)
65780
65795
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
65781
65796
  const interactionBridge = createCliInteractionBridge();
65782
65797
  logger?.info("plan", "Starting interactive planning session...", { agent: agentName });
65783
65798
  try {
65784
- const result = await adapter.plan({
65799
+ await adapter.plan({
65785
65800
  prompt,
65786
65801
  workdir,
65787
65802
  interactive: true,
65788
65803
  timeoutSeconds,
65789
65804
  interactionBridge,
65790
65805
  config: config2,
65791
- modelTier: "balanced",
65806
+ modelTier: config2?.plan?.model ?? "balanced",
65792
65807
  dangerouslySkipPermissions: config2?.execution?.dangerouslySkipPermissions ?? false,
65793
65808
  maxInteractionTurns: config2?.agent?.maxInteractionTurns
65794
65809
  });
65795
- rawResponse = result.specContent;
65796
65810
  } finally {
65797
65811
  logger?.info("plan", "Interactive session ended");
65798
65812
  }
65813
+ if (!_deps2.existsSync(outputPath)) {
65814
+ throw new Error(`[plan] Agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
65815
+ }
65816
+ rawResponse = await _deps2.readFile(outputPath);
65799
65817
  }
65800
65818
  const finalPrd = validatePlanOutput(rawResponse, options.feature, branchName);
65801
65819
  finalPrd.project = projectName;
65802
- const outputDir = join10(naxDir, "features", options.feature);
65803
- const outputPath = join10(outputDir, "prd.json");
65804
- await _deps2.mkdirp(outputDir);
65805
65820
  await _deps2.writeFile(outputPath, JSON.stringify(finalPrd, null, 2));
65806
65821
  logger?.info("plan", "[OK] PRD written", { outputPath });
65807
65822
  return outputPath;
@@ -65868,7 +65883,7 @@ function buildCodebaseContext2(scan) {
65868
65883
  return sections.join(`
65869
65884
  `);
65870
65885
  }
65871
- function buildPlanningPrompt(specContent, codebaseContext) {
65886
+ function buildPlanningPrompt(specContent, codebaseContext, outputFilePath) {
65872
65887
  return `You are a senior software architect generating a product requirements document (PRD) as JSON.
65873
65888
 
65874
65889
  ## Spec
@@ -65924,7 +65939,8 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
65924
65939
  - three-session-tdd: Complex stories. Full TDD cycle with separate test-writer and implementer sessions.
65925
65940
  - three-session-tdd-lite: Expert/high-risk stories. Full TDD with additional verifier session.
65926
65941
 
65927
- Output ONLY the JSON object. Do not wrap in markdown code blocks.`;
65942
+ ${outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
65943
+ Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.` : "Output ONLY the JSON object. Do not wrap in markdown code blocks."}`;
65928
65944
  }
65929
65945
  // src/cli/accept.ts
65930
65946
  init_config();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.42.2",
3
+ "version": "0.42.3",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {
@@ -636,14 +636,19 @@ export class AcpAgentAdapter implements AgentAdapter {
636
636
  }
637
637
 
638
638
  async plan(options: PlanOptions): Promise<PlanResult> {
639
- // Resolve model: explicit > config.models.balanced > fallback
639
+ // Resolve model: explicit > config.models[tier] > config.models.balanced > fallback
640
640
  let modelDef = options.modelDef;
641
641
  if (!modelDef && options.config?.models) {
642
- const { resolveBalancedModelDef } = await import("../model-resolution");
643
- try {
644
- modelDef = resolveBalancedModelDef(options.config as import("../../config").NaxConfig);
645
- } catch {
646
- // resolveBalancedModelDef can throw if models.balanced missing
642
+ const tier = options.modelTier ?? "balanced";
643
+ const { resolveModel } = await import("../../config/schema");
644
+ const models = options.config.models as Record<string, unknown>;
645
+ const entry = models[tier] ?? models.balanced;
646
+ if (entry) {
647
+ try {
648
+ modelDef = resolveModel(entry as Parameters<typeof resolveModel>[0]);
649
+ } catch {
650
+ // resolveModel can throw on malformed entries
651
+ }
647
652
  }
648
653
  }
649
654
  modelDef ??= { provider: "anthropic", model: "claude-sonnet-4-5-20250514" };
@@ -121,8 +121,6 @@ class SpawnAcpSession implements AcpSession {
121
121
  "--cwd",
122
122
  this.cwd,
123
123
  ...(this.permissionMode === "approve-all" ? ["--approve-all"] : []),
124
- "--format",
125
- "json",
126
124
  "--model",
127
125
  this.model,
128
126
  "--timeout",
package/src/cli/plan.ts CHANGED
@@ -37,6 +37,7 @@ export const _deps = {
37
37
  return { stdout: result.stdout as Buffer, exitCode: result.exitCode };
38
38
  },
39
39
  mkdirp: (path: string): Promise<void> => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {}),
40
+ existsSync: (path: string): boolean => existsSync(path),
40
41
  };
41
42
 
42
43
  // ─────────────────────────────────────────────────────────────────────────────
@@ -88,9 +89,11 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
88
89
  const pkg = await _deps.readPackageJson(workdir);
89
90
  const projectName = detectProjectName(workdir, pkg);
90
91
 
91
- // Build prompt
92
+ // Compute output path early — needed for interactive file-write prompt
92
93
  const branchName = options.branch ?? `feat/${options.feature}`;
93
- const prompt = buildPlanningPrompt(specContent, codebaseContext);
94
+ const outputDir = join(naxDir, "features", options.feature);
95
+ const outputPath = join(outputDir, "prd.json");
96
+ await _deps.mkdirp(outputDir);
94
97
 
95
98
  const agentName = config?.autoMode?.defaultAgent ?? "claude";
96
99
 
@@ -101,6 +104,7 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
101
104
  let rawResponse: string;
102
105
  if (options.auto) {
103
106
  // One-shot: use CLI adapter directly — simple completion doesn't need ACP session overhead
107
+ const prompt = buildPlanningPrompt(specContent, codebaseContext);
104
108
  const cliAdapter = _deps.getAgent(agentName);
105
109
  if (!cliAdapter) throw new Error(`[plan] No agent adapter found for '${agentName}'`);
106
110
  rawResponse = await cliAdapter.complete(prompt, { jsonMode: true });
@@ -114,27 +118,32 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
114
118
  // Not an envelope — use rawResponse as-is
115
119
  }
116
120
  } else {
117
- // Interactive: use protocol-aware adapter (ACP when configured)
121
+ // Interactive: agent writes PRD JSON directly to outputPath (avoids output truncation)
122
+ const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath);
118
123
  const adapter = _deps.getAgent(agentName, config);
119
124
  if (!adapter) throw new Error(`[plan] No agent adapter found for '${agentName}'`);
120
125
  const interactionBridge = createCliInteractionBridge();
121
126
  logger?.info("plan", "Starting interactive planning session...", { agent: agentName });
122
127
  try {
123
- const result = await adapter.plan({
128
+ await adapter.plan({
124
129
  prompt,
125
130
  workdir,
126
131
  interactive: true,
127
132
  timeoutSeconds,
128
133
  interactionBridge,
129
134
  config,
130
- modelTier: "balanced",
135
+ modelTier: config?.plan?.model ?? "balanced",
131
136
  dangerouslySkipPermissions: config?.execution?.dangerouslySkipPermissions ?? false,
132
137
  maxInteractionTurns: config?.agent?.maxInteractionTurns,
133
138
  });
134
- rawResponse = result.specContent;
135
139
  } finally {
136
140
  logger?.info("plan", "Interactive session ended");
137
141
  }
142
+ // Read back from file written by agent
143
+ if (!_deps.existsSync(outputPath)) {
144
+ throw new Error(`[plan] Agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
145
+ }
146
+ rawResponse = await _deps.readFile(outputPath);
138
147
  }
139
148
 
140
149
  // Validate and normalize: handles markdown extraction, trailing commas, LLM quirks,
@@ -144,10 +153,7 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
144
153
  // Override project with auto-detected name (validatePlanOutput fills feature/branchName already)
145
154
  finalPrd.project = projectName;
146
155
 
147
- // Write output
148
- const outputDir = join(naxDir, "features", options.feature);
149
- const outputPath = join(outputDir, "prd.json");
150
- await _deps.mkdirp(outputDir);
156
+ // Write normalized PRD (overwrites agent-written file with validated/normalized version)
151
157
  await _deps.writeFile(outputPath, JSON.stringify(finalPrd, null, 2));
152
158
 
153
159
  logger?.info("plan", "[OK] PRD written", { outputPath });
@@ -256,7 +262,7 @@ function buildCodebaseContext(scan: CodebaseScan): string {
256
262
  * - Complexity classification guide
257
263
  * - Test strategy guide
258
264
  */
259
- function buildPlanningPrompt(specContent: string, codebaseContext: string): string {
265
+ function buildPlanningPrompt(specContent: string, codebaseContext: string, outputFilePath?: string): string {
260
266
  return `You are a senior software architect generating a product requirements document (PRD) as JSON.
261
267
 
262
268
  ## Spec
@@ -312,5 +318,9 @@ Generate a JSON object with this exact structure (no markdown, no explanation
312
318
  - three-session-tdd: Complex stories. Full TDD cycle with separate test-writer and implementer sessions.
313
319
  - three-session-tdd-lite: Expert/high-risk stories. Full TDD with additional verifier session.
314
320
 
315
- Output ONLY the JSON object. Do not wrap in markdown code blocks.`;
321
+ ${
322
+ outputFilePath
323
+ ? `Write the PRD JSON directly to this file path: ${outputFilePath}\nDo NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.`
324
+ : "Output ONLY the JSON object. Do not wrap in markdown code blocks."
325
+ }`;
316
326
  }