@nathapp/nax 0.46.0 → 0.46.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nax.js CHANGED
@@ -3458,7 +3458,46 @@ var init_complete = __esm(() => {
3458
3458
  };
3459
3459
  });
3460
3460
 
3461
- // src/agents/claude/cost.ts
3461
+ // src/agents/cost/pricing.ts
3462
+ var COST_RATES, MODEL_PRICING;
3463
+ var init_pricing = __esm(() => {
3464
+ COST_RATES = {
3465
+ fast: {
3466
+ inputPer1M: 0.8,
3467
+ outputPer1M: 4
3468
+ },
3469
+ balanced: {
3470
+ inputPer1M: 3,
3471
+ outputPer1M: 15
3472
+ },
3473
+ powerful: {
3474
+ inputPer1M: 15,
3475
+ outputPer1M: 75
3476
+ }
3477
+ };
3478
+ MODEL_PRICING = {
3479
+ sonnet: { input: 3, output: 15 },
3480
+ haiku: { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3481
+ opus: { input: 15, output: 75 },
3482
+ "claude-sonnet-4": { input: 3, output: 15 },
3483
+ "claude-sonnet-4-5": { input: 3, output: 15 },
3484
+ "claude-sonnet-4-6": { input: 3, output: 15 },
3485
+ "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3486
+ "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3487
+ "claude-opus": { input: 15, output: 75 },
3488
+ "claude-opus-4": { input: 15, output: 75 },
3489
+ "claude-opus-4-6": { input: 15, output: 75 },
3490
+ "gpt-4.1": { input: 10, output: 30 },
3491
+ "gpt-4": { input: 30, output: 60 },
3492
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3493
+ "gemini-2.5-pro": { input: 0.075, output: 0.3 },
3494
+ "gemini-2-pro": { input: 0.075, output: 0.3 },
3495
+ codex: { input: 0.02, output: 0.06 },
3496
+ "code-davinci-002": { input: 0.02, output: 0.06 }
3497
+ };
3498
+ });
3499
+
3500
+ // src/agents/cost/parse.ts
3462
3501
  function parseTokenUsage(output) {
3463
3502
  try {
3464
3503
  const jsonMatch = output.match(/\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/);
@@ -3502,6 +3541,8 @@ function parseTokenUsage(output) {
3502
3541
  }
3503
3542
  return null;
3504
3543
  }
3544
+
3545
+ // src/agents/cost/calculate.ts
3505
3546
  function estimateCost(modelTier, inputTokens, outputTokens, customRates) {
3506
3547
  const rates = customRates ?? COST_RATES[modelTier];
3507
3548
  const inputCost = inputTokens / 1e6 * rates.inputPer1M;
@@ -3543,25 +3584,45 @@ function formatCostWithConfidence(estimate) {
3543
3584
  return `~${formattedCost} (duration-based)`;
3544
3585
  }
3545
3586
  }
3546
- var COST_RATES;
3587
+ function estimateCostFromTokenUsage(usage, model) {
3588
+ const pricing = MODEL_PRICING[model];
3589
+ if (!pricing) {
3590
+ const fallbackInputRate = 3 / 1e6;
3591
+ const fallbackOutputRate = 15 / 1e6;
3592
+ const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
3593
+ const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
3594
+ const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
3595
+ const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
3596
+ return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
3597
+ }
3598
+ const inputRate = pricing.input / 1e6;
3599
+ const outputRate = pricing.output / 1e6;
3600
+ const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
3601
+ const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
3602
+ const inputCost = (usage.input_tokens ?? 0) * inputRate;
3603
+ const outputCost = (usage.output_tokens ?? 0) * outputRate;
3604
+ const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
3605
+ const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
3606
+ return inputCost + outputCost + cacheReadCost + cacheCreationCost;
3607
+ }
3608
+ var init_calculate = __esm(() => {
3609
+ init_pricing();
3610
+ });
3611
+
3612
+ // src/agents/cost/index.ts
3547
3613
  var init_cost = __esm(() => {
3548
- COST_RATES = {
3549
- fast: {
3550
- inputPer1M: 0.8,
3551
- outputPer1M: 4
3552
- },
3553
- balanced: {
3554
- inputPer1M: 3,
3555
- outputPer1M: 15
3556
- },
3557
- powerful: {
3558
- inputPer1M: 15,
3559
- outputPer1M: 75
3560
- }
3561
- };
3614
+ init_pricing();
3615
+ init_calculate();
3616
+ });
3617
+
3618
+ // src/agents/claude/cost.ts
3619
+ var init_cost2 = __esm(() => {
3620
+ init_cost();
3562
3621
  });
3563
3622
 
3564
3623
  // src/agents/claude/execution.ts
3624
+ import { homedir } from "os";
3625
+ import { isAbsolute } from "path";
3565
3626
  function buildCommand(binary, options) {
3566
3627
  const model = options.modelDef.model;
3567
3628
  const { skipPermissions } = resolvePermissions(options.config, options.pipelineStage ?? "run");
@@ -3570,12 +3631,19 @@ function buildCommand(binary, options) {
3570
3631
  }
3571
3632
  function buildAllowedEnv(options) {
3572
3633
  const allowed = {};
3573
- const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3634
+ const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3574
3635
  for (const varName of essentialVars) {
3575
3636
  if (process.env[varName]) {
3576
3637
  allowed[varName] = process.env[varName];
3577
3638
  }
3578
3639
  }
3640
+ const rawHome = process.env.HOME ?? "";
3641
+ const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
3642
+ if (rawHome !== safeHome) {
3643
+ const logger = getLogger();
3644
+ logger.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
3645
+ }
3646
+ allowed.HOME = safeHome;
3579
3647
  const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
3580
3648
  for (const varName of apiKeyVars) {
3581
3649
  if (process.env[varName]) {
@@ -3665,7 +3733,7 @@ async function executeOnce(binary, options, pidRegistry) {
3665
3733
  var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_PERIOD_MS = 5000, _runOnceDeps;
3666
3734
  var init_execution = __esm(() => {
3667
3735
  init_logger2();
3668
- init_cost();
3736
+ init_cost2();
3669
3737
  _runOnceDeps = {
3670
3738
  killProc(proc, signal) {
3671
3739
  proc.kill(signal);
@@ -18671,7 +18739,7 @@ Respond with ONLY the TypeScript test code (no markdown code fences, no explanat
18671
18739
  testable: c.testable,
18672
18740
  storyId: c.storyId
18673
18741
  })), null, 2);
18674
- await _generatorPRDDeps.writeFile(join2(options.workdir, "acceptance-refined.json"), refinedJsonContent);
18742
+ await _generatorPRDDeps.writeFile(join2(options.featureDir, "acceptance-refined.json"), refinedJsonContent);
18675
18743
  return { testCode, criteria };
18676
18744
  }
18677
18745
  function buildStrategyInstructions(strategy, framework) {
@@ -19047,13 +19115,21 @@ function parseAcpxJsonOutput(rawOutput) {
19047
19115
  }
19048
19116
 
19049
19117
  // src/agents/acp/spawn-client.ts
19118
+ import { homedir as homedir2 } from "os";
19119
+ import { isAbsolute as isAbsolute2 } from "path";
19050
19120
  function buildAllowedEnv2(extraEnv) {
19051
19121
  const allowed = {};
19052
- const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
19122
+ const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
19053
19123
  for (const varName of essentialVars) {
19054
19124
  if (process.env[varName])
19055
19125
  allowed[varName] = process.env[varName];
19056
19126
  }
19127
+ const rawHome = process.env.HOME ?? "";
19128
+ const safeHome = rawHome && isAbsolute2(rawHome) ? rawHome : homedir2();
19129
+ if (rawHome !== safeHome) {
19130
+ getSafeLogger()?.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
19131
+ }
19132
+ allowed.HOME = safeHome;
19057
19133
  const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
19058
19134
  for (const varName of apiKeyVars) {
19059
19135
  if (process.env[varName])
@@ -19270,44 +19346,8 @@ var init_spawn_client = __esm(() => {
19270
19346
  });
19271
19347
 
19272
19348
  // src/agents/acp/cost.ts
19273
- function estimateCostFromTokenUsage(usage, model) {
19274
- const pricing = MODEL_PRICING[model];
19275
- if (!pricing) {
19276
- const fallbackInputRate = 3 / 1e6;
19277
- const fallbackOutputRate = 15 / 1e6;
19278
- const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
19279
- const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
19280
- const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
19281
- const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
19282
- return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
19283
- }
19284
- const inputRate = pricing.input / 1e6;
19285
- const outputRate = pricing.output / 1e6;
19286
- const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
19287
- const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
19288
- const inputCost = (usage.input_tokens ?? 0) * inputRate;
19289
- const outputCost = (usage.output_tokens ?? 0) * outputRate;
19290
- const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
19291
- const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
19292
- return inputCost + outputCost + cacheReadCost + cacheCreationCost;
19293
- }
19294
- var MODEL_PRICING;
19295
- var init_cost2 = __esm(() => {
19296
- MODEL_PRICING = {
19297
- "claude-sonnet-4": { input: 3, output: 15 },
19298
- "claude-sonnet-4-5": { input: 3, output: 15 },
19299
- "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
19300
- "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
19301
- "claude-opus": { input: 15, output: 75 },
19302
- "claude-opus-4": { input: 15, output: 75 },
19303
- "gpt-4.1": { input: 10, output: 30 },
19304
- "gpt-4": { input: 30, output: 60 },
19305
- "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
19306
- "gemini-2.5-pro": { input: 0.075, output: 0.3 },
19307
- "gemini-2-pro": { input: 0.075, output: 0.3 },
19308
- codex: { input: 0.02, output: 0.06 },
19309
- "code-davinci-002": { input: 0.02, output: 0.06 }
19310
- };
19349
+ var init_cost3 = __esm(() => {
19350
+ init_cost();
19311
19351
  });
19312
19352
 
19313
19353
  // src/agents/acp/adapter.ts
@@ -19828,7 +19868,7 @@ var init_adapter2 = __esm(() => {
19828
19868
  init_decompose();
19829
19869
  init_spawn_client();
19830
19870
  init_types2();
19831
- init_cost2();
19871
+ init_cost3();
19832
19872
  INTERACTION_TIMEOUT_MS = 5 * 60 * 1000;
19833
19873
  AGENT_REGISTRY = {
19834
19874
  claude: {
@@ -20427,7 +20467,7 @@ var init_chain = __esm(() => {
20427
20467
 
20428
20468
  // src/utils/path-security.ts
20429
20469
  import { realpathSync } from "fs";
20430
- import { dirname, isAbsolute, join as join5, normalize, resolve } from "path";
20470
+ import { dirname, isAbsolute as isAbsolute3, join as join5, normalize, resolve } from "path";
20431
20471
  function safeRealpath(p) {
20432
20472
  try {
20433
20473
  return realpathSync(p);
@@ -20445,7 +20485,7 @@ function validateModulePath(modulePath, allowedRoots) {
20445
20485
  return { valid: false, error: "Module path is empty" };
20446
20486
  }
20447
20487
  const normalizedRoots = allowedRoots.map((r) => safeRealpath(resolve(r)));
20448
- if (isAbsolute(modulePath)) {
20488
+ if (isAbsolute3(modulePath)) {
20449
20489
  const absoluteTarget = safeRealpath(normalize(modulePath));
20450
20490
  const isWithin = normalizedRoots.some((root) => {
20451
20491
  return absoluteTarget.startsWith(`${root}/`) || absoluteTarget === root;
@@ -20736,7 +20776,7 @@ function isPlainObject2(value) {
20736
20776
 
20737
20777
  // src/config/path-security.ts
20738
20778
  import { existsSync as existsSync4, lstatSync, realpathSync as realpathSync2 } from "fs";
20739
- import { isAbsolute as isAbsolute2, normalize as normalize2, resolve as resolve3 } from "path";
20779
+ import { isAbsolute as isAbsolute4, normalize as normalize2, resolve as resolve3 } from "path";
20740
20780
  function validateDirectory(dirPath, baseDir) {
20741
20781
  const resolved = resolve3(dirPath);
20742
20782
  if (!existsSync4(resolved)) {
@@ -20768,7 +20808,7 @@ function validateDirectory(dirPath, baseDir) {
20768
20808
  function isWithinDirectory(targetPath, basePath) {
20769
20809
  const normalizedTarget = normalize2(targetPath);
20770
20810
  const normalizedBase = normalize2(basePath);
20771
- if (!isAbsolute2(normalizedTarget) || !isAbsolute2(normalizedBase)) {
20811
+ if (!isAbsolute4(normalizedTarget) || !isAbsolute4(normalizedBase)) {
20772
20812
  return false;
20773
20813
  }
20774
20814
  const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
@@ -20804,10 +20844,10 @@ var MAX_DIRECTORY_DEPTH = 10;
20804
20844
  var init_path_security2 = () => {};
20805
20845
 
20806
20846
  // src/config/paths.ts
20807
- import { homedir } from "os";
20847
+ import { homedir as homedir3 } from "os";
20808
20848
  import { join as join6, resolve as resolve4 } from "path";
20809
20849
  function globalConfigDir() {
20810
- return join6(homedir(), ".nax");
20850
+ return join6(homedir3(), ".nax");
20811
20851
  }
20812
20852
  var init_paths = () => {};
20813
20853
 
@@ -22138,7 +22178,7 @@ var package_default;
22138
22178
  var init_package = __esm(() => {
22139
22179
  package_default = {
22140
22180
  name: "@nathapp/nax",
22141
- version: "0.46.0",
22181
+ version: "0.46.2",
22142
22182
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22143
22183
  type: "module",
22144
22184
  bin: {
@@ -22211,8 +22251,8 @@ var init_version = __esm(() => {
22211
22251
  NAX_VERSION = package_default.version;
22212
22252
  NAX_COMMIT = (() => {
22213
22253
  try {
22214
- if (/^[0-9a-f]{6,10}$/.test("6a485b9"))
22215
- return "6a485b9";
22254
+ if (/^[0-9a-f]{6,10}$/.test("506ad27"))
22255
+ return "506ad27";
22216
22256
  } catch {}
22217
22257
  try {
22218
22258
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -24018,6 +24058,7 @@ ${stderr}` };
24018
24058
  const result = await _acceptanceSetupDeps.generate(ctx.prd.userStories, refinedCriteria, {
24019
24059
  featureName: ctx.prd.feature,
24020
24060
  workdir: ctx.workdir,
24061
+ featureDir: ctx.featureDir,
24021
24062
  codebaseContext: "",
24022
24063
  modelTier: ctx.config.acceptance.model ?? "fast",
24023
24064
  modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
@@ -24045,6 +24086,99 @@ ${stderr}` };
24045
24086
  };
24046
24087
  });
24047
24088
 
24089
+ // src/agents/shared/validation.ts
24090
+ function validateAgentForTier(agent, tier) {
24091
+ return agent.capabilities.supportedTiers.includes(tier);
24092
+ }
24093
+ function validateAgentFeature(agent, feature) {
24094
+ return agent.capabilities.features.has(feature);
24095
+ }
24096
+ function describeAgentCapabilities(agent) {
24097
+ const tiers = agent.capabilities.supportedTiers.join(",");
24098
+ const features = Array.from(agent.capabilities.features).join(",");
24099
+ const maxTokens = agent.capabilities.maxContextTokens;
24100
+ return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
24101
+ }
24102
+
24103
+ // src/agents/shared/version-detection.ts
24104
+ async function getAgentVersion(binaryName) {
24105
+ try {
24106
+ const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
24107
+ stdout: "pipe",
24108
+ stderr: "pipe"
24109
+ });
24110
+ const exitCode = await proc.exited;
24111
+ if (exitCode !== 0) {
24112
+ return null;
24113
+ }
24114
+ const stdout = await new Response(proc.stdout).text();
24115
+ const versionLine = stdout.trim().split(`
24116
+ `)[0];
24117
+ const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
24118
+ if (versionMatch) {
24119
+ return versionMatch[0];
24120
+ }
24121
+ return versionLine || null;
24122
+ } catch {
24123
+ return null;
24124
+ }
24125
+ }
24126
+ async function getAgentVersions() {
24127
+ const agents = await getInstalledAgents();
24128
+ const agentsByName = new Map(agents.map((a) => [a.name, a]));
24129
+ const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
24130
+ const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
24131
+ const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
24132
+ return {
24133
+ name: agent.name,
24134
+ displayName: agent.displayName,
24135
+ version: version2,
24136
+ installed: agentsByName.has(agent.name)
24137
+ };
24138
+ }));
24139
+ return versions2;
24140
+ }
24141
+ var _versionDetectionDeps;
24142
+ var init_version_detection = __esm(() => {
24143
+ init_registry();
24144
+ _versionDetectionDeps = {
24145
+ spawn(cmd, opts) {
24146
+ return Bun.spawn(cmd, opts);
24147
+ }
24148
+ };
24149
+ });
24150
+
24151
+ // src/agents/index.ts
24152
+ var exports_agents = {};
24153
+ __export(exports_agents, {
24154
+ validateAgentForTier: () => validateAgentForTier,
24155
+ validateAgentFeature: () => validateAgentFeature,
24156
+ parseTokenUsage: () => parseTokenUsage,
24157
+ getInstalledAgents: () => getInstalledAgents,
24158
+ getAllAgentNames: () => getAllAgentNames,
24159
+ getAgentVersions: () => getAgentVersions,
24160
+ getAgentVersion: () => getAgentVersion,
24161
+ getAgent: () => getAgent,
24162
+ formatCostWithConfidence: () => formatCostWithConfidence,
24163
+ estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
24164
+ estimateCostFromOutput: () => estimateCostFromOutput,
24165
+ estimateCostByDuration: () => estimateCostByDuration,
24166
+ estimateCost: () => estimateCost,
24167
+ describeAgentCapabilities: () => describeAgentCapabilities,
24168
+ checkAgentHealth: () => checkAgentHealth,
24169
+ MODEL_PRICING: () => MODEL_PRICING,
24170
+ CompleteError: () => CompleteError,
24171
+ ClaudeCodeAdapter: () => ClaudeCodeAdapter,
24172
+ COST_RATES: () => COST_RATES
24173
+ });
24174
+ var init_agents = __esm(() => {
24175
+ init_types2();
24176
+ init_claude();
24177
+ init_registry();
24178
+ init_cost();
24179
+ init_version_detection();
24180
+ });
24181
+
24048
24182
  // src/pipeline/event-bus.ts
24049
24183
  class PipelineEventBus {
24050
24184
  subscribers = new Map;
@@ -24455,8 +24589,87 @@ async function recheckReview(ctx) {
24455
24589
  const result = await reviewStage2.execute(ctx);
24456
24590
  return result.action === "continue";
24457
24591
  }
24592
+ function collectFailedChecks(ctx) {
24593
+ return (ctx.reviewResult?.checks ?? []).filter((c) => !c.success);
24594
+ }
24595
+ function buildReviewRectificationPrompt(failedChecks, story) {
24596
+ const errors3 = failedChecks.map((c) => `## ${c.check} errors (exit code ${c.exitCode})
24597
+ \`\`\`
24598
+ ${c.output}
24599
+ \`\`\``).join(`
24600
+
24601
+ `);
24602
+ return `You are fixing lint/typecheck errors from a code review.
24603
+
24604
+ Story: ${story.title} (${story.id})
24605
+
24606
+ The following quality checks failed after implementation:
24607
+
24608
+ ${errors3}
24609
+
24610
+ Fix ALL errors listed above. Do NOT change test files or test behavior.
24611
+ Do NOT add new features \u2014 only fix the quality check errors.
24612
+ Commit your fixes when done.`;
24613
+ }
24614
+ async function runAgentRectification(ctx) {
24615
+ const logger = getLogger();
24616
+ const maxAttempts = ctx.config.quality.autofix?.maxAttempts ?? 2;
24617
+ const failedChecks = collectFailedChecks(ctx);
24618
+ if (failedChecks.length === 0) {
24619
+ logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
24620
+ return false;
24621
+ }
24622
+ logger.info("autofix", "Starting agent rectification for review failures", {
24623
+ storyId: ctx.story.id,
24624
+ failedChecks: failedChecks.map((c) => c.check),
24625
+ maxAttempts
24626
+ });
24627
+ const agentGetFn = ctx.agentGetFn ?? getAgent;
24628
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
24629
+ logger.info("autofix", `Agent rectification attempt ${attempt}/${maxAttempts}`, { storyId: ctx.story.id });
24630
+ const agent = agentGetFn(ctx.config.autoMode.defaultAgent);
24631
+ if (!agent) {
24632
+ logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
24633
+ return false;
24634
+ }
24635
+ const prompt = buildReviewRectificationPrompt(failedChecks, ctx.story);
24636
+ const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
24637
+ const modelDef = resolveModel(ctx.config.models[modelTier]);
24638
+ await agent.run({
24639
+ prompt,
24640
+ workdir: ctx.workdir,
24641
+ modelTier,
24642
+ modelDef,
24643
+ timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
24644
+ dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
24645
+ pipelineStage: "rectification",
24646
+ config: ctx.config,
24647
+ maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
24648
+ storyId: ctx.story.id,
24649
+ sessionRole: "implementer"
24650
+ });
24651
+ const passed = await _autofixDeps.recheckReview(ctx);
24652
+ if (passed) {
24653
+ logger.info("autofix", `[OK] Agent rectification succeeded on attempt ${attempt}`, {
24654
+ storyId: ctx.story.id
24655
+ });
24656
+ return true;
24657
+ }
24658
+ const updatedFailed = collectFailedChecks(ctx);
24659
+ if (updatedFailed.length > 0) {
24660
+ failedChecks.splice(0, failedChecks.length, ...updatedFailed);
24661
+ }
24662
+ logger.warn("autofix", `Agent rectification still failing after attempt ${attempt}`, {
24663
+ storyId: ctx.story.id
24664
+ });
24665
+ }
24666
+ logger.warn("autofix", "Agent rectification exhausted", { storyId: ctx.story.id });
24667
+ return false;
24668
+ }
24458
24669
  var autofixStage, _autofixDeps;
24459
24670
  var init_autofix = __esm(() => {
24671
+ init_agents();
24672
+ init_config();
24460
24673
  init_logger2();
24461
24674
  init_event_bus();
24462
24675
  autofixStage = {
@@ -24482,14 +24695,7 @@ var init_autofix = __esm(() => {
24482
24695
  }
24483
24696
  const lintFixCmd = ctx.config.quality.commands.lintFix;
24484
24697
  const formatFixCmd = ctx.config.quality.commands.formatFix;
24485
- if (!lintFixCmd && !formatFixCmd) {
24486
- logger.debug("autofix", "No fix commands configured \u2014 skipping autofix", { storyId: ctx.story.id });
24487
- return { action: "escalate", reason: "Review failed and no autofix commands configured" };
24488
- }
24489
- const maxAttempts = ctx.config.quality.autofix?.maxAttempts ?? 2;
24490
- let fixed = false;
24491
- for (let attempt = 1;attempt <= maxAttempts; attempt++) {
24492
- logger.info("autofix", `Autofix attempt ${attempt}/${maxAttempts}`, { storyId: ctx.story.id });
24698
+ if (lintFixCmd || formatFixCmd) {
24493
24699
  if (lintFixCmd) {
24494
24700
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
24495
24701
  const lintResult = await _autofixDeps.runCommand(lintFixCmd, ctx.workdir);
@@ -24515,22 +24721,24 @@ var init_autofix = __esm(() => {
24515
24721
  const recheckPassed = await _autofixDeps.recheckReview(ctx);
24516
24722
  pipelineEventBus.emit({ type: "autofix:completed", storyId: ctx.story.id, fixed: recheckPassed });
24517
24723
  if (recheckPassed) {
24518
- if (ctx.reviewResult) {
24724
+ if (ctx.reviewResult)
24519
24725
  ctx.reviewResult = { ...ctx.reviewResult, success: true };
24520
- }
24521
- fixed = true;
24522
- break;
24726
+ logger.info("autofix", "Mechanical autofix succeeded \u2014 retrying review", { storyId: ctx.story.id });
24727
+ return { action: "retry", fromStage: "review" };
24523
24728
  }
24524
24729
  }
24525
- if (fixed) {
24526
- logger.info("autofix", "Autofix succeeded \u2014 retrying review", { storyId: ctx.story.id });
24730
+ const agentFixed = await _autofixDeps.runAgentRectification(ctx);
24731
+ if (agentFixed) {
24732
+ if (ctx.reviewResult)
24733
+ ctx.reviewResult = { ...ctx.reviewResult, success: true };
24734
+ logger.info("autofix", "Agent rectification succeeded \u2014 retrying review", { storyId: ctx.story.id });
24527
24735
  return { action: "retry", fromStage: "review" };
24528
24736
  }
24529
24737
  logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
24530
24738
  return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
24531
24739
  }
24532
24740
  };
24533
- _autofixDeps = { runCommand, recheckReview };
24741
+ _autofixDeps = { runCommand, recheckReview, runAgentRectification };
24534
24742
  });
24535
24743
 
24536
24744
  // src/execution/progress.ts
@@ -25678,97 +25886,6 @@ ${pluginMarkdown}` : pluginMarkdown;
25678
25886
  };
25679
25887
  });
25680
25888
 
25681
- // src/agents/shared/validation.ts
25682
- function validateAgentForTier(agent, tier) {
25683
- return agent.capabilities.supportedTiers.includes(tier);
25684
- }
25685
- function validateAgentFeature(agent, feature) {
25686
- return agent.capabilities.features.has(feature);
25687
- }
25688
- function describeAgentCapabilities(agent) {
25689
- const tiers = agent.capabilities.supportedTiers.join(",");
25690
- const features = Array.from(agent.capabilities.features).join(",");
25691
- const maxTokens = agent.capabilities.maxContextTokens;
25692
- return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
25693
- }
25694
-
25695
- // src/agents/shared/version-detection.ts
25696
- async function getAgentVersion(binaryName) {
25697
- try {
25698
- const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
25699
- stdout: "pipe",
25700
- stderr: "pipe"
25701
- });
25702
- const exitCode = await proc.exited;
25703
- if (exitCode !== 0) {
25704
- return null;
25705
- }
25706
- const stdout = await new Response(proc.stdout).text();
25707
- const versionLine = stdout.trim().split(`
25708
- `)[0];
25709
- const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
25710
- if (versionMatch) {
25711
- return versionMatch[0];
25712
- }
25713
- return versionLine || null;
25714
- } catch {
25715
- return null;
25716
- }
25717
- }
25718
- async function getAgentVersions() {
25719
- const agents = await getInstalledAgents();
25720
- const agentsByName = new Map(agents.map((a) => [a.name, a]));
25721
- const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
25722
- const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
25723
- const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
25724
- return {
25725
- name: agent.name,
25726
- displayName: agent.displayName,
25727
- version: version2,
25728
- installed: agentsByName.has(agent.name)
25729
- };
25730
- }));
25731
- return versions2;
25732
- }
25733
- var _versionDetectionDeps;
25734
- var init_version_detection = __esm(() => {
25735
- init_registry();
25736
- _versionDetectionDeps = {
25737
- spawn(cmd, opts) {
25738
- return Bun.spawn(cmd, opts);
25739
- }
25740
- };
25741
- });
25742
-
25743
- // src/agents/index.ts
25744
- var exports_agents = {};
25745
- __export(exports_agents, {
25746
- validateAgentForTier: () => validateAgentForTier,
25747
- validateAgentFeature: () => validateAgentFeature,
25748
- parseTokenUsage: () => parseTokenUsage,
25749
- getInstalledAgents: () => getInstalledAgents,
25750
- getAllAgentNames: () => getAllAgentNames,
25751
- getAgentVersions: () => getAgentVersions,
25752
- getAgentVersion: () => getAgentVersion,
25753
- getAgent: () => getAgent,
25754
- formatCostWithConfidence: () => formatCostWithConfidence,
25755
- estimateCostFromOutput: () => estimateCostFromOutput,
25756
- estimateCostByDuration: () => estimateCostByDuration,
25757
- estimateCost: () => estimateCost,
25758
- describeAgentCapabilities: () => describeAgentCapabilities,
25759
- checkAgentHealth: () => checkAgentHealth,
25760
- CompleteError: () => CompleteError,
25761
- ClaudeCodeAdapter: () => ClaudeCodeAdapter,
25762
- COST_RATES: () => COST_RATES
25763
- });
25764
- var init_agents = __esm(() => {
25765
- init_types2();
25766
- init_claude();
25767
- init_registry();
25768
- init_cost();
25769
- init_version_detection();
25770
- });
25771
-
25772
25889
  // src/tdd/isolation.ts
25773
25890
  function isTestFile(filePath) {
25774
25891
  return TEST_PATTERNS.some((pattern) => pattern.test(filePath));
@@ -30069,12 +30186,15 @@ async function checkWorkingTreeClean(workdir) {
30069
30186
  });
30070
30187
  const output = await new Response(proc.stdout).text();
30071
30188
  const exitCode = await proc.exited;
30072
- const passed = exitCode === 0 && output.trim() === "";
30189
+ const lines = output.trim() === "" ? [] : output.split(`
30190
+ `).filter(Boolean);
30191
+ const nonNaxDirtyFiles = lines.filter((line) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(line)));
30192
+ const passed = exitCode === 0 && nonNaxDirtyFiles.length === 0;
30073
30193
  return {
30074
30194
  name: "working-tree-clean",
30075
30195
  tier: "blocker",
30076
30196
  passed,
30077
- message: passed ? "Working tree is clean" : "Uncommitted changes detected"
30197
+ message: passed ? "Working tree is clean" : `Uncommitted changes detected: ${nonNaxDirtyFiles.map((l) => l.slice(3)).join(", ")}`
30078
30198
  };
30079
30199
  }
30080
30200
  async function checkGitUserConfigured(workdir) {
@@ -30099,7 +30219,23 @@ async function checkGitUserConfigured(workdir) {
30099
30219
  message: passed ? "Git user is configured" : !hasName && !hasEmail ? "Git user.name and user.email not configured" : !hasName ? "Git user.name not configured" : "Git user.email not configured"
30100
30220
  };
30101
30221
  }
30102
- var init_checks_git = () => {};
30222
+ var NAX_RUNTIME_PATTERNS;
30223
+ var init_checks_git = __esm(() => {
30224
+ NAX_RUNTIME_PATTERNS = [
30225
+ /^.{2} nax\.lock$/,
30226
+ /^.{2} nax\/metrics\.json$/,
30227
+ /^.{2} nax\/features\/[^/]+\/status\.json$/,
30228
+ /^.{2} nax\/features\/[^/]+\/runs\//,
30229
+ /^.{2} nax\/features\/[^/]+\/plan\//,
30230
+ /^.{2} nax\/features\/[^/]+\/acp-sessions\.json$/,
30231
+ /^.{2} nax\/features\/[^/]+\/interactions\//,
30232
+ /^.{2} nax\/features\/[^/]+\/progress\.txt$/,
30233
+ /^.{2} nax\/features\/[^/]+\/acceptance-refined\.json$/,
30234
+ /^.{2} \.nax-verifier-verdict\.json$/,
30235
+ /^.{2} \.nax-pids$/,
30236
+ /^.{2} \.nax-wt\//
30237
+ ];
30238
+ });
30103
30239
 
30104
30240
  // src/precheck/checks-config.ts
30105
30241
  import { existsSync as existsSync26, statSync as statSync3 } from "fs";
@@ -30360,6 +30496,7 @@ var init_checks_blockers = __esm(() => {
30360
30496
 
30361
30497
  // src/precheck/checks-warnings.ts
30362
30498
  import { existsSync as existsSync28 } from "fs";
30499
+ import { isAbsolute as isAbsolute6 } from "path";
30363
30500
  async function checkClaudeMdExists(workdir) {
30364
30501
  const claudeMdPath = `${workdir}/CLAUDE.md`;
30365
30502
  const passed = existsSync28(claudeMdPath);
@@ -30453,7 +30590,14 @@ async function checkGitignoreCoversNax(workdir) {
30453
30590
  }
30454
30591
  const file2 = Bun.file(gitignorePath);
30455
30592
  const content = await file2.text();
30456
- const patterns = ["nax.lock", "runs/", "test/tmp/"];
30593
+ const patterns = [
30594
+ "nax.lock",
30595
+ "nax/**/runs/",
30596
+ "nax/metrics.json",
30597
+ "nax/features/*/status.json",
30598
+ ".nax-pids",
30599
+ ".nax-wt/"
30600
+ ];
30457
30601
  const missing = patterns.filter((pattern) => !content.includes(pattern));
30458
30602
  const passed = missing.length === 0;
30459
30603
  return {
@@ -30482,6 +30626,16 @@ async function checkPromptOverrideFiles(config2, workdir) {
30482
30626
  }
30483
30627
  return checks3;
30484
30628
  }
30629
+ async function checkHomeEnvValid() {
30630
+ const home = process.env.HOME ?? "";
30631
+ const passed = home !== "" && isAbsolute6(home);
30632
+ return {
30633
+ name: "home-env-valid",
30634
+ tier: "warning",
30635
+ passed,
30636
+ message: passed ? `HOME env is valid: ${home}` : home === "" ? "HOME env is not set \u2014 agent may write files to unexpected locations" : `HOME env is not an absolute path ("${home}") \u2014 may cause literal "~" directories in repo`
30637
+ };
30638
+ }
30485
30639
  var init_checks_warnings = () => {};
30486
30640
 
30487
30641
  // src/precheck/checks-agents.ts
@@ -30662,6 +30816,7 @@ function getEnvironmentWarnings(config2, workdir) {
30662
30816
  () => checkDiskSpace(),
30663
30817
  () => checkOptionalCommands(config2, workdir),
30664
30818
  () => checkGitignoreCoversNax(workdir),
30819
+ () => checkHomeEnvValid(),
30665
30820
  () => checkPromptOverrideFiles(config2, workdir),
30666
30821
  () => checkMultiAgentHealth()
30667
30822
  ];
@@ -32902,12 +33057,12 @@ var init_parallel_executor = __esm(() => {
32902
33057
 
32903
33058
  // src/pipeline/subscribers/events-writer.ts
32904
33059
  import { appendFile as appendFile2, mkdir } from "fs/promises";
32905
- import { homedir as homedir5 } from "os";
33060
+ import { homedir as homedir7 } from "os";
32906
33061
  import { basename as basename3, join as join40 } from "path";
32907
33062
  function wireEventsWriter(bus, feature, runId, workdir) {
32908
33063
  const logger = getSafeLogger();
32909
33064
  const project = basename3(workdir);
32910
- const eventsDir = join40(homedir5(), ".nax", "events", project);
33065
+ const eventsDir = join40(homedir7(), ".nax", "events", project);
32911
33066
  const eventsFile = join40(eventsDir, "events.jsonl");
32912
33067
  let dirReady = false;
32913
33068
  const write = (line) => {
@@ -33067,12 +33222,12 @@ var init_interaction2 = __esm(() => {
33067
33222
 
33068
33223
  // src/pipeline/subscribers/registry.ts
33069
33224
  import { mkdir as mkdir2, writeFile } from "fs/promises";
33070
- import { homedir as homedir6 } from "os";
33225
+ import { homedir as homedir8 } from "os";
33071
33226
  import { basename as basename4, join as join41 } from "path";
33072
33227
  function wireRegistry(bus, feature, runId, workdir) {
33073
33228
  const logger = getSafeLogger();
33074
33229
  const project = basename4(workdir);
33075
- const runDir = join41(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
33230
+ const runDir = join41(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33076
33231
  const metaFile = join41(runDir, "meta.json");
33077
33232
  const unsub = bus.on("run:started", (_ev) => {
33078
33233
  (async () => {
@@ -65472,7 +65627,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
65472
65627
  // bin/nax.ts
65473
65628
  init_source();
65474
65629
  import { existsSync as existsSync32, mkdirSync as mkdirSync6 } from "fs";
65475
- import { homedir as homedir8 } from "os";
65630
+ import { homedir as homedir10 } from "os";
65476
65631
  import { join as join43 } from "path";
65477
65632
 
65478
65633
  // node_modules/commander/esm.mjs
@@ -68628,10 +68783,10 @@ import { join as join32 } from "path";
68628
68783
  // src/commands/logs-reader.ts
68629
68784
  import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
68630
68785
  import { readdir as readdir3 } from "fs/promises";
68631
- import { homedir as homedir3 } from "os";
68786
+ import { homedir as homedir5 } from "os";
68632
68787
  import { join as join31 } from "path";
68633
68788
  var _deps6 = {
68634
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir3(), ".nax", "runs")
68789
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir5(), ".nax", "runs")
68635
68790
  };
68636
68791
  async function resolveRunFileFromRegistry(runId) {
68637
68792
  const runsDir = _deps6.getRunsDir();
@@ -68957,11 +69112,11 @@ async function precheckCommand(options) {
68957
69112
  // src/commands/runs.ts
68958
69113
  init_source();
68959
69114
  import { readdir as readdir4 } from "fs/promises";
68960
- import { homedir as homedir4 } from "os";
69115
+ import { homedir as homedir6 } from "os";
68961
69116
  import { join as join35 } from "path";
68962
69117
  var DEFAULT_LIMIT = 20;
68963
69118
  var _deps8 = {
68964
- getRunsDir: () => join35(homedir4(), ".nax", "runs")
69119
+ getRunsDir: () => join35(homedir6(), ".nax", "runs")
68965
69120
  };
68966
69121
  function formatDuration3(ms) {
68967
69122
  if (ms <= 0)
@@ -77155,7 +77310,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77155
77310
  config2.autoMode.defaultAgent = options.agent;
77156
77311
  }
77157
77312
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
77158
- const globalNaxDir = join43(homedir8(), ".nax");
77313
+ const globalNaxDir = join43(homedir10(), ".nax");
77159
77314
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
77160
77315
  const eventEmitter = new PipelineEventEmitter;
77161
77316
  let tuiInstance;