@nathapp/nax 0.64.0-canary.2 → 0.64.0-canary.4

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 +234 -146
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -3660,7 +3660,8 @@ class SpawnAcpSession {
3660
3660
  return {
3661
3661
  messages: [{ role: "assistant", content: errorContent }],
3662
3662
  stopReason: "error",
3663
- retryable: parsedOnError.retryable
3663
+ retryable: parsedOnError.retryable,
3664
+ exitCode
3664
3665
  };
3665
3666
  }
3666
3667
  try {
@@ -18937,6 +18938,7 @@ class AcpSessionHandleImpl {
18937
18938
  _resumed;
18938
18939
  _timeoutSeconds;
18939
18940
  _modelDef;
18941
+ _permissionMode;
18940
18942
  constructor(opts) {
18941
18943
  this.id = opts.id;
18942
18944
  this.agentName = opts.agentName;
@@ -18947,6 +18949,7 @@ class AcpSessionHandleImpl {
18947
18949
  this._resumed = opts.resumed;
18948
18950
  this._timeoutSeconds = opts.timeoutSeconds;
18949
18951
  this._modelDef = opts.modelDef;
18952
+ this._permissionMode = opts.permissionMode;
18950
18953
  }
18951
18954
  }
18952
18955
  function extractOutput(response) {
@@ -19288,7 +19291,8 @@ class AcpAgentAdapter {
19288
19291
  sessionName: name,
19289
19292
  resumed: ensured.resumed,
19290
19293
  timeoutSeconds,
19291
- modelDef
19294
+ modelDef,
19295
+ permissionMode: resolvedPermissions.mode
19292
19296
  });
19293
19297
  } catch (error48) {
19294
19298
  if (session) {
@@ -19300,7 +19304,8 @@ class AcpAgentAdapter {
19300
19304
  }
19301
19305
  async sendTurn(handle, prompt, opts) {
19302
19306
  const impl = handle;
19303
- const { _session: session, _sessionName: sessionName, _timeoutSeconds: timeoutSeconds, _modelDef: modelDef } = impl;
19307
+ const { _sessionName: sessionName, _timeoutSeconds: timeoutSeconds, _modelDef: modelDef } = impl;
19308
+ let sessionRecreated = false;
19304
19309
  const { interactionHandler, signal } = opts;
19305
19310
  const MAX_TURNS = opts.maxTurns ?? 10;
19306
19311
  let totalTokenUsage = { inputTokens: 0, outputTokens: 0 };
@@ -19321,7 +19326,7 @@ class AcpAgentAdapter {
19321
19326
  while (turnCount < MAX_TURNS) {
19322
19327
  turnCount++;
19323
19328
  getSafeLogger()?.debug("acp-adapter", `Session turn ${turnCount}/${MAX_TURNS}`, { sessionName });
19324
- const turnResult = await runSessionPrompt(session, currentPrompt, timeoutSeconds * 1000, signal);
19329
+ const turnResult = await runSessionPrompt(impl._session, currentPrompt, timeoutSeconds * 1000, signal);
19325
19330
  if (turnResult.timedOut) {
19326
19331
  timedOut = true;
19327
19332
  break;
@@ -19333,6 +19338,21 @@ class AcpAgentAdapter {
19333
19338
  lastResponse = turnResult.response;
19334
19339
  if (!lastResponse)
19335
19340
  break;
19341
+ if (lastResponse.exitCode === 4 && !sessionRecreated) {
19342
+ sessionRecreated = true;
19343
+ getSafeLogger()?.info("acp-adapter", "NO_SESSION detected \u2014 re-establishing session", { sessionName });
19344
+ try {
19345
+ const ensured = await ensureAcpSession(impl._client, impl._sessionName, impl.agentName, impl._permissionMode);
19346
+ impl._session = ensured.session;
19347
+ turnCount--;
19348
+ continue;
19349
+ } catch (err) {
19350
+ getSafeLogger()?.warn("acp-adapter", "Session re-establishment failed after NO_SESSION", {
19351
+ sessionName,
19352
+ error: err instanceof Error ? err.message : String(err)
19353
+ });
19354
+ }
19355
+ }
19336
19356
  if (lastResponse.cumulative_token_usage) {
19337
19357
  totalTokenUsage = addTokenUsage(totalTokenUsage, this._mapper.toInternal(lastResponse.cumulative_token_usage));
19338
19358
  }
@@ -19973,6 +19993,7 @@ class AgentManager {
19973
19993
  agentName,
19974
19994
  kind: "complete",
19975
19995
  request: null,
19996
+ completeOptions: augmented,
19976
19997
  prompt,
19977
19998
  config: this._config,
19978
19999
  signal: options.signal,
@@ -30190,6 +30211,7 @@ function turnResultToAgentResult(r) {
30190
30211
  rateLimited: false,
30191
30212
  durationMs: 0,
30192
30213
  estimatedCostUsd: r.estimatedCostUsd ?? 0,
30214
+ exactCostUsd: r.exactCostUsd,
30193
30215
  tokenUsage: r.tokenUsage
30194
30216
  };
30195
30217
  }
@@ -30362,7 +30384,8 @@ async function callOp(ctx, op, input) {
30362
30384
  jsonMode: completeOp.jsonMode ?? false,
30363
30385
  pipelineStage: op.stage,
30364
30386
  storyId: ctx.storyId,
30365
- workdir: ctx.packageDir
30387
+ workdir: ctx.packageDir,
30388
+ featureName: ctx.featureName
30366
30389
  });
30367
30390
  return op.parse(raw.output, input, buildCtx);
30368
30391
  }
@@ -34509,8 +34532,8 @@ var init_truncation = __esm(() => {
34509
34532
  init_adapter();
34510
34533
  });
34511
34534
 
34512
- // src/operations/semantic-review.ts
34513
- function parseLLMShape(raw) {
34535
+ // src/operations/types.ts
34536
+ function parseLlmReviewShape(raw) {
34514
34537
  if (typeof raw !== "object" || raw === null)
34515
34538
  return null;
34516
34539
  const obj = raw;
@@ -34520,11 +34543,13 @@ function parseLLMShape(raw) {
34520
34543
  return null;
34521
34544
  return { passed: obj.passed, findings: obj.findings };
34522
34545
  }
34546
+
34547
+ // src/operations/semantic-review.ts
34523
34548
  var FAIL_OPEN, semanticReviewHopBody = async (initialPrompt, ctx) => {
34524
34549
  const first = await ctx.send(initialPrompt);
34525
34550
  const isTruncated = looksLikeTruncatedJson(first.output);
34526
34551
  const parsed = tryParseLLMJson(first.output);
34527
- if (!isTruncated && parsed && parseLLMShape(parsed))
34552
+ if (!isTruncated && parsed && parseLlmReviewShape(parsed))
34528
34553
  return first;
34529
34554
  const retryPrompt = isTruncated ? ReviewPromptBuilder.jsonRetryCondensed() : ReviewPromptBuilder.jsonRetry();
34530
34555
  const retry = await ctx.send(retryPrompt);
@@ -34562,7 +34587,7 @@ var init_semantic_review = __esm(() => {
34562
34587
  },
34563
34588
  parse(output, _input, _ctx) {
34564
34589
  const raw = tryParseLLMJson(output);
34565
- const parsed = parseLLMShape(raw);
34590
+ const parsed = parseLlmReviewShape(raw);
34566
34591
  if (parsed)
34567
34592
  return parsed;
34568
34593
  if (/"passed"\s*:\s*false/.test(output))
@@ -34573,21 +34598,11 @@ var init_semantic_review = __esm(() => {
34573
34598
  });
34574
34599
 
34575
34600
  // src/operations/adversarial-review.ts
34576
- function parseLLMShape2(raw) {
34577
- if (typeof raw !== "object" || raw === null)
34578
- return null;
34579
- const obj = raw;
34580
- if (typeof obj.passed !== "boolean")
34581
- return null;
34582
- if (!Array.isArray(obj.findings))
34583
- return null;
34584
- return { passed: obj.passed, findings: obj.findings };
34585
- }
34586
34601
  var FAIL_OPEN2, adversarialReviewHopBody = async (initialPrompt, ctx) => {
34587
34602
  const first = await ctx.send(initialPrompt);
34588
34603
  const isTruncated = looksLikeTruncatedJson(first.output);
34589
34604
  const parsed = tryParseLLMJson(first.output);
34590
- if (!isTruncated && parsed && parseLLMShape2(parsed))
34605
+ if (!isTruncated && parsed && parseLlmReviewShape(parsed))
34591
34606
  return first;
34592
34607
  const retryPrompt = isTruncated ? ReviewPromptBuilder.jsonRetryCondensed() : ReviewPromptBuilder.jsonRetry();
34593
34608
  const retry = await ctx.send(retryPrompt);
@@ -34627,7 +34642,7 @@ var init_adversarial_review = __esm(() => {
34627
34642
  },
34628
34643
  parse(output, _input, _ctx) {
34629
34644
  const raw = tryParseLLMJson(output);
34630
- const parsed = parseLLMShape2(raw);
34645
+ const parsed = parseLlmReviewShape(raw);
34631
34646
  if (parsed)
34632
34647
  return parsed;
34633
34648
  if (/"passed"\s*:\s*false/.test(output))
@@ -35189,7 +35204,7 @@ var init_cost_aggregator = __esm(() => {
35189
35204
  });
35190
35205
 
35191
35206
  // src/runtime/prompt-auditor.ts
35192
- import { mkdirSync as mkdirSync3 } from "fs";
35207
+ import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
35193
35208
  import { join as join21 } from "path";
35194
35209
  function createNoOpPromptAuditor() {
35195
35210
  return {
@@ -35198,6 +35213,25 @@ function createNoOpPromptAuditor() {
35198
35213
  async flush() {}
35199
35214
  };
35200
35215
  }
35216
+ function deriveTxtFilename(entry) {
35217
+ if (entry.sessionName) {
35218
+ const suffix = deriveAuditSuffix(entry);
35219
+ return `${entry.ts}-${entry.sessionName}${suffix ? `-${suffix}` : ""}.txt`;
35220
+ }
35221
+ const parts = [String(entry.ts), entry.callType ?? "call", entry.stage ?? "unknown"];
35222
+ if (entry.storyId)
35223
+ parts.push(entry.storyId);
35224
+ return `${parts.join("-")}.txt`;
35225
+ }
35226
+ function deriveAuditSuffix(entry) {
35227
+ if (entry.callType === "run" && entry.turn !== undefined) {
35228
+ const stage = entry.stage ?? "run";
35229
+ return `${stage}-t${String(entry.turn).padStart(2, "0")}`;
35230
+ }
35231
+ if (entry.callType === "complete")
35232
+ return "complete";
35233
+ return entry.stage ?? entry.callType;
35234
+ }
35201
35235
  function buildTxtContent(entry) {
35202
35236
  const ts = new Date(entry.ts).toISOString();
35203
35237
  const lines = [
@@ -35226,77 +35260,51 @@ function buildTxtContent(entry) {
35226
35260
  }
35227
35261
 
35228
35262
  class PromptAuditor {
35229
- _runId;
35230
- _flushDir;
35231
- _featureName;
35232
- _entries = [];
35233
- _draining = false;
35234
- _inFlightEntries = [];
35235
- constructor(_runId, _flushDir, _featureName) {
35236
- this._runId = _runId;
35237
- this._flushDir = _flushDir;
35238
- this._featureName = _featureName;
35263
+ _queue = Promise.resolve();
35264
+ _dirCreated = false;
35265
+ _jsonlPath;
35266
+ _featureDir;
35267
+ constructor(runId, flushDir, featureName) {
35268
+ this._featureDir = join21(flushDir, featureName);
35269
+ this._jsonlPath = join21(this._featureDir, `${runId}.jsonl`);
35239
35270
  }
35240
35271
  record(entry) {
35241
- if (this._draining) {
35242
- this._inFlightEntries.push(entry);
35243
- return;
35244
- }
35245
- this._entries.push(entry);
35272
+ this._enqueue(entry);
35246
35273
  }
35247
35274
  recordError(entry) {
35248
- if (this._draining) {
35249
- this._inFlightEntries.push(entry);
35250
- return;
35275
+ this._enqueue(entry);
35276
+ }
35277
+ _enqueue(entry) {
35278
+ this._queue = this._queue.then(() => this._writeEntry(entry)).catch((err) => {
35279
+ getSafeLogger()?.warn("audit", "prompt-audit write failed", {
35280
+ path: this._jsonlPath,
35281
+ error: errorMessage(err)
35282
+ });
35283
+ });
35284
+ }
35285
+ async _writeEntry(entry) {
35286
+ if (!this._dirCreated) {
35287
+ await mkdir3(this._featureDir, { recursive: true });
35288
+ this._dirCreated = true;
35251
35289
  }
35252
- this._entries.push(entry);
35290
+ await _promptAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(entry)}
35291
+ `);
35292
+ if (!("prompt" in entry) || !("response" in entry))
35293
+ return;
35294
+ const auditEntry = entry;
35295
+ const filename = deriveTxtFilename(auditEntry);
35296
+ await _promptAuditorDeps.write(join21(this._featureDir, filename), buildTxtContent(auditEntry));
35253
35297
  }
35254
35298
  async flush() {
35255
- this._draining = true;
35256
- try {
35257
- const entries = this._entries.splice(0);
35258
- if (entries.length === 0)
35259
- return;
35260
- const featureDir = join21(this._flushDir, this._featureName);
35261
- mkdirSync3(featureDir, { recursive: true });
35262
- const jsonlPath = join21(featureDir, `${this._runId}.jsonl`);
35263
- await _promptAuditorDeps.write(jsonlPath, `${entries.map((e) => JSON.stringify(e)).join(`
35264
- `)}
35265
- `);
35266
- for (const entry of entries) {
35267
- if (!("prompt" in entry) || !("response" in entry))
35268
- continue;
35269
- const auditEntry = entry;
35270
- if (!auditEntry.sessionName)
35271
- continue;
35272
- const filename = `${auditEntry.ts}-${auditEntry.sessionName}.txt`;
35273
- await _promptAuditorDeps.write(join21(featureDir, filename), buildTxtContent(auditEntry));
35274
- }
35275
- const lateEntries = this._inFlightEntries.splice(0);
35276
- if (lateEntries.length > 0) {
35277
- const allEntries = [...entries, ...lateEntries];
35278
- await _promptAuditorDeps.write(jsonlPath, `${allEntries.map((e) => JSON.stringify(e)).join(`
35279
- `)}
35280
- `);
35281
- for (const entry of lateEntries) {
35282
- if (!("prompt" in entry) || !("response" in entry))
35283
- continue;
35284
- const auditEntry = entry;
35285
- if (!auditEntry.sessionName)
35286
- continue;
35287
- const filename = `${auditEntry.ts}-${auditEntry.sessionName}.txt`;
35288
- await _promptAuditorDeps.write(join21(featureDir, filename), buildTxtContent(auditEntry));
35289
- }
35290
- }
35291
- } finally {
35292
- this._draining = false;
35293
- }
35299
+ await this._queue;
35294
35300
  }
35295
35301
  }
35296
35302
  var _promptAuditorDeps;
35297
35303
  var init_prompt_auditor = __esm(() => {
35304
+ init_logger2();
35298
35305
  _promptAuditorDeps = {
35299
- write: (path4, data) => Bun.write(path4, data)
35306
+ write: (path4, data) => Bun.write(path4, data),
35307
+ appendLine: (path4, data) => appendFile3(path4, data, "utf8")
35300
35308
  };
35301
35309
  });
35302
35310
 
@@ -35358,7 +35366,7 @@ var init_types5 = __esm(() => {
35358
35366
 
35359
35367
  // src/session/manager.ts
35360
35368
  import { randomUUID as randomUUID2 } from "crypto";
35361
- import { mkdir as mkdir3 } from "fs/promises";
35369
+ import { mkdir as mkdir4 } from "fs/promises";
35362
35370
  import { isAbsolute as isAbsolute8, join as join22, relative as relative8, sep } from "path";
35363
35371
  function resolveProjectDirFromScratchDir(scratchDir) {
35364
35372
  const marker = `${sep}.nax${sep}features${sep}`;
@@ -35678,6 +35686,10 @@ class SessionManager {
35678
35686
  if (this._busySessions.has(handle.id)) {
35679
35687
  throw new NaxError(`Session "${handle.id}" is already processing a prompt (single-flight invariant)`, "SESSION_BUSY", { stage: "session", sessionName: handle.id });
35680
35688
  }
35689
+ const terminalDesc = this._findByName(handle.id);
35690
+ if (terminalDesc && (terminalDesc.state === "COMPLETED" || terminalDesc.state === "FAILED")) {
35691
+ throw new NaxError(`Session "${handle.id}" is in terminal state ${terminalDesc.state} \u2014 call openSession first to resume`, "SESSION_TERMINAL_STATE", { stage: "session", sessionName: handle.id, state: terminalDesc.state });
35692
+ }
35681
35693
  const adapter = this._getAdapter(handle.agentName);
35682
35694
  if (!adapter) {
35683
35695
  throw new NaxError(`SessionManager.sendPrompt: no adapter found for agent "${handle.agentName}"`, "ADAPTER_NOT_FOUND", { stage: "session", agentName: handle.agentName });
@@ -35842,7 +35854,7 @@ var init_manager2 = __esm(() => {
35842
35854
  uuid: () => randomUUID2(),
35843
35855
  sessionScratchDir: (projectDir, featureName, sessionId) => join22(projectDir, ".nax", "features", featureName, "sessions", sessionId),
35844
35856
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
35845
- await mkdir3(scratchDir, { recursive: true });
35857
+ await mkdir4(scratchDir, { recursive: true });
35846
35858
  const { handle: _handle, ...persistable } = descriptor;
35847
35859
  const derivedProjectDir = projectDir ?? resolveProjectDirFromScratchDir(scratchDir);
35848
35860
  if (derivedProjectDir) {
@@ -35955,9 +35967,11 @@ function extractCosts(result) {
35955
35967
  if (!result || typeof result !== "object")
35956
35968
  return null;
35957
35969
  const r = result;
35970
+ const hasEstimatedCost = "estimatedCostUsd" in r;
35971
+ const hasCost = "costUsd" in r;
35958
35972
  const estimatedCostUsd = r.estimatedCostUsd ?? r.costUsd ?? 0;
35959
35973
  const exactCostUsd = r.exactCostUsd;
35960
- if (estimatedCostUsd === 0 && exactCostUsd == null)
35974
+ if (!hasEstimatedCost && !hasCost && exactCostUsd == null)
35961
35975
  return null;
35962
35976
  return { estimatedCostUsd, exactCostUsd };
35963
35977
  }
@@ -35965,6 +35979,8 @@ function costMiddleware(aggregator, runId) {
35965
35979
  return {
35966
35980
  name: "cost",
35967
35981
  async after(ctx, result, durationMs) {
35982
+ if (ctx.kind === "run" && ctx.sessionHandle === undefined && ctx.request?.executeHop)
35983
+ return;
35968
35984
  const tokens = extractTokens(result);
35969
35985
  const costs = extractCosts(result);
35970
35986
  if (!tokens && !costs)
@@ -36013,17 +36029,35 @@ function extractOutput2(result) {
36013
36029
  return "";
36014
36030
  return result.output ?? "";
36015
36031
  }
36032
+ function sessionNameFromCompleteOptions(ctx) {
36033
+ const opts = ctx.completeOptions;
36034
+ if (!opts)
36035
+ return;
36036
+ if (opts.sessionName)
36037
+ return opts.sessionName;
36038
+ if (!opts.workdir || !opts.featureName)
36039
+ return;
36040
+ return formatSessionName({
36041
+ workdir: opts.workdir,
36042
+ featureName: opts.featureName,
36043
+ storyId: opts.storyId,
36044
+ role: opts.sessionRole,
36045
+ pipelineStage: opts.pipelineStage
36046
+ });
36047
+ }
36016
36048
  function auditMiddleware(auditor, runId) {
36017
36049
  return {
36018
36050
  name: "audit",
36019
36051
  async after(ctx, result, durationMs) {
36052
+ if (ctx.kind === "run" && ctx.sessionHandle === undefined && ctx.request?.executeHop)
36053
+ return;
36020
36054
  const runOpts = ctx.request?.runOptions;
36021
36055
  const prompt = ctx.prompt ?? runOpts?.prompt;
36022
36056
  if (!prompt)
36023
36057
  return;
36024
36058
  const agentResult = result ?? {};
36025
36059
  const { protocolIds } = agentResult;
36026
- const sessionName = ctx.sessionHandle?.id;
36060
+ const sessionName = ctx.sessionHandle?.id ?? sessionNameFromCompleteOptions(ctx);
36027
36061
  const internalRoundTrips = result && typeof result === "object" && "internalRoundTrips" in result ? result.internalRoundTrips : undefined;
36028
36062
  const entry = {
36029
36063
  ts: Date.now(),
@@ -36036,9 +36070,9 @@ function auditMiddleware(auditor, runId) {
36036
36070
  response: extractOutput2(result),
36037
36071
  durationMs,
36038
36072
  callType: ctx.kind,
36039
- workdir: runOpts?.workdir,
36073
+ workdir: runOpts?.workdir ?? ctx.completeOptions?.workdir,
36040
36074
  projectDir: runOpts?.projectDir,
36041
- featureName: runOpts?.featureName,
36075
+ featureName: runOpts?.featureName ?? ctx.completeOptions?.featureName,
36042
36076
  ...sessionName !== undefined && { sessionName },
36043
36077
  ...protocolIds?.recordId !== undefined && { recordId: protocolIds.recordId },
36044
36078
  ...protocolIds?.sessionId !== undefined && { sessionId: protocolIds.sessionId },
@@ -36072,6 +36106,7 @@ function auditMiddleware(auditor, runId) {
36072
36106
  }
36073
36107
  var init_audit = __esm(() => {
36074
36108
  init_errors();
36109
+ init_session_name();
36075
36110
  });
36076
36111
 
36077
36112
  // src/runtime/middleware/index.ts
@@ -38274,6 +38309,9 @@ __export(exports_acceptance_setup, {
38274
38309
  _acceptanceSetupDeps: () => _acceptanceSetupDeps
38275
38310
  });
38276
38311
  import path7 from "path";
38312
+ function hasLikelyTestContent2(content) {
38313
+ return /\b(?:describe|test|it|expect)\s*\(/.test(content) || /func\s+Test\w+\s*\(/.test(content) || /def\s+test_\w+/.test(content) || /#\[test\]/.test(content);
38314
+ }
38277
38315
  function computeACFingerprint(criteria) {
38278
38316
  const sorted = [...criteria].sort().join(`
38279
38317
  `);
@@ -38344,6 +38382,9 @@ var init_acceptance_setup = __esm(() => {
38344
38382
  writeMeta: async (metaPath, meta3) => {
38345
38383
  await Bun.write(metaPath, JSON.stringify(meta3, null, 2));
38346
38384
  },
38385
+ readFile: async (filePath) => {
38386
+ return Bun.file(filePath).text();
38387
+ },
38347
38388
  autoCommitIfDirty,
38348
38389
  loadGroupConfig: async (projectDir, relativeWorkdir) => {
38349
38390
  return loadConfigForWorkdir(path7.join(projectDir, ".nax", "config.json"), relativeWorkdir || undefined);
@@ -38464,6 +38505,7 @@ ${stderr}` };
38464
38505
  `);
38465
38506
  const frameworkOverrideLine = ctx.config.acceptance.testFramework ? `
38466
38507
  [FRAMEWORK OVERRIDE: Use ${ctx.config.acceptance.testFramework} as the test framework regardless of what you detect.]` : "";
38508
+ const groupStoryId = group.stories[0]?.id;
38467
38509
  const genResult = await _acceptanceSetupDeps.callOp(ctx, packageDir, acceptanceGenerateOp, {
38468
38510
  featureName: featureName ?? "",
38469
38511
  criteriaList,
@@ -38471,17 +38513,53 @@ ${stderr}` };
38471
38513
  targetTestFilePath: testPath,
38472
38514
  ..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {},
38473
38515
  ..."previousFailure" in ctx && ctx.previousFailure ? { previousFailure: ctx.previousFailure } : {}
38474
- });
38516
+ }, groupStoryId);
38475
38517
  let testCode = genResult.testCode;
38476
38518
  if (!testCode) {
38477
- const skeletonCriteria = groupRefined.map((c, i) => ({
38478
- id: `AC-${i + 1}`,
38479
- text: c.refined,
38480
- lineNumber: i + 1
38481
- }));
38482
- testCode = generateSkeletonTests(featureName, skeletonCriteria, ctx.config.acceptance.testFramework, language);
38519
+ const backupPath = `${testPath}.llm-recovery.bak`;
38520
+ if (await _acceptanceSetupDeps.fileExists(testPath)) {
38521
+ try {
38522
+ const existing = await _acceptanceSetupDeps.readFile(testPath);
38523
+ const extracted = extractTestCode(existing);
38524
+ if (extracted) {
38525
+ testCode = extracted;
38526
+ getSafeLogger()?.info("acceptance-setup", "Agent wrote fenced code block to disk \u2014 using extracted code", { storyId: groupStoryId, testPath });
38527
+ } else if (existing.trim().length > 0 && hasLikelyTestContent2(existing)) {
38528
+ let backupCreated = false;
38529
+ try {
38530
+ await _acceptanceSetupDeps.writeFile(backupPath, existing);
38531
+ backupCreated = true;
38532
+ } catch (backupErr) {
38533
+ getSafeLogger()?.warn("acceptance-setup", "Failed to create .llm-recovery.bak \u2014 preserving agent file anyway", { storyId: groupStoryId, testPath, backupPath, error: errorMessage(backupErr) });
38534
+ }
38535
+ testCode = existing;
38536
+ getSafeLogger()?.info("acceptance-setup", "Agent wrote acceptance test directly \u2014 preserving file with backup", { storyId: groupStoryId, testPath, backupPath, backupCreated });
38537
+ } else {
38538
+ if (existing.trim().length > 0) {
38539
+ try {
38540
+ await _acceptanceSetupDeps.writeFile(backupPath, existing);
38541
+ } catch (backupErr) {
38542
+ getSafeLogger()?.warn("acceptance-setup", "Failed to create .llm-recovery.bak for unrecognised file", { storyId: groupStoryId, testPath, backupPath, error: errorMessage(backupErr) });
38543
+ }
38544
+ }
38545
+ getSafeLogger()?.error("acceptance-setup", "Agent-written file not recognised as test code \u2014 falling back to skeleton", { storyId: groupStoryId, testPath, backupPath, fileSize: existing.length });
38546
+ }
38547
+ } catch (err) {
38548
+ getSafeLogger()?.warn("acceptance-setup", "Failed to read agent-written file \u2014 falling back to skeleton", { storyId: groupStoryId, testPath, error: errorMessage(err) });
38549
+ }
38550
+ }
38551
+ if (!testCode) {
38552
+ const skeletonCriteria = groupRefined.map((c, i) => ({
38553
+ id: `AC-${i + 1}`,
38554
+ text: c.refined,
38555
+ lineNumber: i + 1
38556
+ }));
38557
+ testCode = generateSkeletonTests(featureName, skeletonCriteria, ctx.config.acceptance.testFramework, language);
38558
+ }
38559
+ }
38560
+ if (testCode) {
38561
+ await _acceptanceSetupDeps.writeFile(testPath, testCode);
38483
38562
  }
38484
- await _acceptanceSetupDeps.writeFile(testPath, testCode);
38485
38563
  }
38486
38564
  if (allRefinedCriteria.length > 0) {
38487
38565
  const refinedJsonContent = JSON.stringify(allRefinedCriteria.map((c, i) => ({
@@ -39982,7 +40060,7 @@ var init_nax_project_root = __esm(() => {
39982
40060
  });
39983
40061
 
39984
40062
  // src/review/review-audit.ts
39985
- import { mkdir as mkdir4 } from "fs/promises";
40063
+ import { mkdir as mkdir5 } from "fs/promises";
39986
40064
  import { join as join33 } from "path";
39987
40065
  async function writeReviewAudit(entry) {
39988
40066
  try {
@@ -40015,7 +40093,7 @@ var init_review_audit = __esm(() => {
40015
40093
  init_nax_project_root();
40016
40094
  _reviewAuditDeps = {
40017
40095
  async mkdir(path8) {
40018
- await mkdir4(path8, { recursive: true });
40096
+ await mkdir5(path8, { recursive: true });
40019
40097
  },
40020
40098
  async writeFile(path8, content) {
40021
40099
  await Bun.write(path8, content);
@@ -41466,7 +41544,7 @@ var init_runner4 = __esm(() => {
41466
41544
  });
41467
41545
 
41468
41546
  // src/review/verdict-writer.ts
41469
- import { mkdir as mkdir5 } from "fs/promises";
41547
+ import { mkdir as mkdir6 } from "fs/promises";
41470
41548
  import { join as join34 } from "path";
41471
41549
  async function writeReviewVerdict(entry) {
41472
41550
  const logger = getSafeLogger();
@@ -41495,7 +41573,7 @@ var init_verdict_writer = __esm(() => {
41495
41573
  init_nax_project_root();
41496
41574
  _verdictWriterDeps = {
41497
41575
  findNaxProjectRoot,
41498
- mkdir: mkdir5,
41576
+ mkdir: mkdir6,
41499
41577
  writeFile: Bun.write
41500
41578
  };
41501
41579
  });
@@ -42555,8 +42633,8 @@ var init_semantic_verdict = __esm(() => {
42555
42633
  init_logger2();
42556
42634
  _semanticVerdictDeps = {
42557
42635
  mkdirp: async (dir) => {
42558
- const { mkdir: mkdir6 } = await import("fs/promises");
42559
- await mkdir6(dir, { recursive: true });
42636
+ const { mkdir: mkdir7 } = await import("fs/promises");
42637
+ await mkdir7(dir, { recursive: true });
42560
42638
  },
42561
42639
  writeFile: async (filePath, content) => {
42562
42640
  await Bun.write(filePath, content);
@@ -42703,15 +42781,15 @@ var init_effectiveness = __esm(() => {
42703
42781
  });
42704
42782
 
42705
42783
  // src/execution/progress.ts
42706
- import { appendFile as appendFile3, mkdir as mkdir6 } from "fs/promises";
42784
+ import { appendFile as appendFile4, mkdir as mkdir7 } from "fs/promises";
42707
42785
  import { join as join36 } from "path";
42708
42786
  async function appendProgress(featureDir, storyId, status, message) {
42709
- await mkdir6(featureDir, { recursive: true });
42787
+ await mkdir7(featureDir, { recursive: true });
42710
42788
  const progressPath = join36(featureDir, "progress.txt");
42711
42789
  const timestamp = new Date().toISOString();
42712
42790
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
42713
42791
  `;
42714
- await appendFile3(progressPath, entry);
42792
+ await appendFile4(progressPath, entry);
42715
42793
  }
42716
42794
  var init_progress = () => {};
42717
42795
 
@@ -43550,7 +43628,7 @@ async function getStoryChangedFiles(workdir, fromRef) {
43550
43628
  async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, storyFromRef, sessionManager, sessionId, runtime) {
43551
43629
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
43552
43630
  if (!rectificationEnabled)
43553
- return { passed: false, cost: 0 };
43631
+ return { passed: false, cost: 0, fullSuiteGatePassed: false };
43554
43632
  const rectificationConfig = config2.execution.rectification;
43555
43633
  const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
43556
43634
  const { testCommand: resolvedTestCmd } = await _rectificationGateDeps.resolveTestCommands(config2, workdir, story.workdir);
@@ -43564,8 +43642,18 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43564
43642
  if (!fullSuitePassed && fullSuiteResult.output) {
43565
43643
  const testSummary = _rectificationGateDeps.parseTestOutput(fullSuiteResult.output);
43566
43644
  if (testSummary.failed > 0) {
43645
+ if (testSummary.failures.length === 0) {
43646
+ logger.warn("tdd", "Full suite gate found unattributable failures \u2014 deferring to run-level regression", {
43647
+ storyId: story.id,
43648
+ failedTests: testSummary.failed,
43649
+ passedTests: testSummary.passed,
43650
+ outputLength: fullSuiteResult.output.length,
43651
+ outputTail: fullSuiteResult.output.slice(-200)
43652
+ });
43653
+ return { passed: true, cost: 0, fullSuiteGatePassed: false };
43654
+ }
43567
43655
  let filteredFailures = testSummary.failures;
43568
- if (storyFromRef && testSummary.failures.length > 0) {
43656
+ if (storyFromRef) {
43569
43657
  const storyFiles = await getStoryChangedFiles(workdir, storyFromRef);
43570
43658
  if (storyFiles.size > 0) {
43571
43659
  filteredFailures = testSummary.failures.filter((f) => storyFiles.has(f.file));
@@ -43580,7 +43668,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43580
43668
  suppressedTestCount: testSummary.failures.length,
43581
43669
  suppressedFiles: uniqueSuppressedFiles
43582
43670
  });
43583
- return { passed: true, cost: 0 };
43671
+ return { passed: true, cost: 0, fullSuiteGatePassed: true };
43584
43672
  }
43585
43673
  if (wasFiltered) {
43586
43674
  logger.info("tdd", "Full suite gate: suppressed pre-existing failures", {
@@ -43603,7 +43691,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43603
43691
  exitCode: fullSuiteResult.exitCode,
43604
43692
  passedTests: testSummary.passed
43605
43693
  });
43606
- return { passed: true, cost: 0 };
43694
+ return { passed: true, cost: 0, fullSuiteGatePassed: true };
43607
43695
  }
43608
43696
  logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
43609
43697
  storyId: story.id,
@@ -43611,17 +43699,17 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43611
43699
  outputLength: fullSuiteResult.output.length,
43612
43700
  outputTail: fullSuiteResult.output.slice(-200)
43613
43701
  });
43614
- return { passed: false, cost: 0 };
43702
+ return { passed: false, cost: 0, fullSuiteGatePassed: false };
43615
43703
  }
43616
43704
  if (fullSuitePassed) {
43617
43705
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
43618
- return { passed: true, cost: 0 };
43706
+ return { passed: true, cost: 0, fullSuiteGatePassed: true };
43619
43707
  }
43620
43708
  logger.warn("tdd", "Full suite gate execution failed (no output)", {
43621
43709
  storyId: story.id,
43622
43710
  exitCode: fullSuiteResult.exitCode
43623
43711
  });
43624
- return { passed: false, cost: 0 };
43712
+ return { passed: false, cost: 0, fullSuiteGatePassed: false };
43625
43713
  }
43626
43714
  async function runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, testOutput, featureName, projectDir, sessionManager, sessionId, runtime) {
43627
43715
  logger.warn("tdd", "Full suite gate detected regressions", {
@@ -43761,13 +43849,13 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
43761
43849
  });
43762
43850
  const fixed = outcome.outcome === "fixed";
43763
43851
  if (fixed) {
43764
- return { passed: true, cost: gateCostAccum };
43852
+ return { passed: true, cost: gateCostAccum, fullSuiteGatePassed: true };
43765
43853
  }
43766
43854
  logger.warn("tdd", "[WARN] Full suite gate failed after rectification exhausted", {
43767
43855
  storyId: story.id,
43768
43856
  attempts: outcome.attempts
43769
43857
  });
43770
- return { passed: false, cost: gateCostAccum };
43858
+ return { passed: false, cost: gateCostAccum, fullSuiteGatePassed: false };
43771
43859
  }
43772
43860
  var _rectificationGateDeps;
43773
43861
  var init_rectification_gate = __esm(() => {
@@ -44236,7 +44324,7 @@ async function runThreeSessionTdd(options) {
44236
44324
  };
44237
44325
  }
44238
44326
  const implementerBinding = getTddSessionBinding?.("implementer");
44239
- const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, wrapAdapterAsManager(agent), implementerTier, lite, logger, featureName, projectDir, initialRef, implementerBinding?.sessionManager, implementerBinding?.sessionId);
44327
+ const { cost: fullSuiteGateCost, fullSuiteGatePassed } = await runFullSuiteGate(story, config2, workdir, wrapAdapterAsManager(agent), implementerTier, lite, logger, featureName, projectDir, initialRef, implementerBinding?.sessionManager, implementerBinding?.sessionId);
44240
44328
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
44241
44329
  const verifierBundle = await getTddContextBundle?.("verifier") ?? tddContextBundles?.verifier;
44242
44330
  const session3 = await runTddSessionOp(verifyTddOp, options, session3Ref, verifierBundle, getTddSessionBinding?.("verifier"));
@@ -46400,7 +46488,7 @@ __export(exports_init_context, {
46400
46488
  _initContextDeps: () => _initContextDeps
46401
46489
  });
46402
46490
  import { existsSync as existsSync21 } from "fs";
46403
- import { mkdir as mkdir7 } from "fs/promises";
46491
+ import { mkdir as mkdir8 } from "fs/promises";
46404
46492
  import { basename as basename5, join as join42 } from "path";
46405
46493
  async function findFiles(dir, maxFiles = 200) {
46406
46494
  try {
@@ -46644,7 +46732,7 @@ async function initPackage(repoRoot, packagePath, force = false) {
46644
46732
  return;
46645
46733
  }
46646
46734
  if (!existsSync21(naxDir)) {
46647
- await mkdir7(naxDir, { recursive: true });
46735
+ await mkdir8(naxDir, { recursive: true });
46648
46736
  }
46649
46737
  const content = generatePackageContextTemplate(packagePath);
46650
46738
  await Bun.write(contextPath, content);
@@ -46659,7 +46747,7 @@ async function initContext(projectRoot, options = {}) {
46659
46747
  return;
46660
46748
  }
46661
46749
  if (!existsSync21(naxDir)) {
46662
- await mkdir7(naxDir, { recursive: true });
46750
+ await mkdir8(naxDir, { recursive: true });
46663
46751
  }
46664
46752
  const scan = await scanProject(projectRoot);
46665
46753
  let content;
@@ -47427,7 +47515,7 @@ var package_default;
47427
47515
  var init_package = __esm(() => {
47428
47516
  package_default = {
47429
47517
  name: "@nathapp/nax",
47430
- version: "0.64.0-canary.2",
47518
+ version: "0.64.0-canary.4",
47431
47519
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
47432
47520
  type: "module",
47433
47521
  bin: {
@@ -47509,8 +47597,8 @@ var init_version = __esm(() => {
47509
47597
  NAX_VERSION = package_default.version;
47510
47598
  NAX_COMMIT = (() => {
47511
47599
  try {
47512
- if (/^[0-9a-f]{6,10}$/.test("c7d0f838"))
47513
- return "c7d0f838";
47600
+ if (/^[0-9a-f]{6,10}$/.test("c0ad2048"))
47601
+ return "c0ad2048";
47514
47602
  } catch {}
47515
47603
  try {
47516
47604
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -48331,7 +48419,7 @@ var init_acceptance_loop = __esm(() => {
48331
48419
  });
48332
48420
 
48333
48421
  // src/session/scratch-purge.ts
48334
- import { mkdir as mkdir9, rename, rm } from "fs/promises";
48422
+ import { mkdir as mkdir10, rename, rm } from "fs/promises";
48335
48423
  import { dirname as dirname9, join as join60 } from "path";
48336
48424
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
48337
48425
  const sessionsDir = join60(projectDir, ".nax", "features", featureName, "sessions");
@@ -48383,7 +48471,7 @@ var init_scratch_purge = __esm(() => {
48383
48471
  readFile: (path16) => Bun.file(path16).text(),
48384
48472
  remove: (path16) => rm(path16, { recursive: true, force: true }),
48385
48473
  move: async (src, dest) => {
48386
- await mkdir9(dirname9(dest), { recursive: true });
48474
+ await mkdir10(dirname9(dest), { recursive: true });
48387
48475
  await rename(src, dest);
48388
48476
  },
48389
48477
  now: () => Date.now()
@@ -49017,7 +49105,7 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
49017
49105
  var DEFAULT_MAX_BATCH_SIZE = 4;
49018
49106
 
49019
49107
  // src/pipeline/subscribers/events-writer.ts
49020
- import { appendFile as appendFile4, mkdir as mkdir10 } from "fs/promises";
49108
+ import { appendFile as appendFile5, mkdir as mkdir11 } from "fs/promises";
49021
49109
  import { homedir as homedir5 } from "os";
49022
49110
  import { basename as basename9, join as join61 } from "path";
49023
49111
  function wireEventsWriter(bus, feature, runId, workdir) {
@@ -49030,10 +49118,10 @@ function wireEventsWriter(bus, feature, runId, workdir) {
49030
49118
  return (async () => {
49031
49119
  try {
49032
49120
  if (!dirReady) {
49033
- await mkdir10(eventsDir, { recursive: true });
49121
+ await mkdir11(eventsDir, { recursive: true });
49034
49122
  dirReady = true;
49035
49123
  }
49036
- await appendFile4(eventsFile, `${JSON.stringify(line)}
49124
+ await appendFile5(eventsFile, `${JSON.stringify(line)}
49037
49125
  `);
49038
49126
  } catch (err) {
49039
49127
  logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
@@ -49203,7 +49291,7 @@ var init_interaction2 = __esm(() => {
49203
49291
  });
49204
49292
 
49205
49293
  // src/pipeline/subscribers/registry.ts
49206
- import { mkdir as mkdir11, writeFile } from "fs/promises";
49294
+ import { mkdir as mkdir12, writeFile } from "fs/promises";
49207
49295
  import { homedir as homedir6 } from "os";
49208
49296
  import { basename as basename10, join as join62 } from "path";
49209
49297
  function wireRegistry(bus, feature, runId, workdir) {
@@ -49214,7 +49302,7 @@ function wireRegistry(bus, feature, runId, workdir) {
49214
49302
  const unsub = bus.on("run:started", (_ev) => {
49215
49303
  return (async () => {
49216
49304
  try {
49217
- await mkdir11(runDir, { recursive: true });
49305
+ await mkdir12(runDir, { recursive: true });
49218
49306
  const meta3 = {
49219
49307
  runId,
49220
49308
  project,
@@ -49556,7 +49644,7 @@ __export(exports_manager, {
49556
49644
  WorktreeManager: () => WorktreeManager
49557
49645
  });
49558
49646
  import { existsSync as existsSync30, symlinkSync } from "fs";
49559
- import { mkdir as mkdir12 } from "fs/promises";
49647
+ import { mkdir as mkdir13 } from "fs/promises";
49560
49648
  import { join as join64 } from "path";
49561
49649
 
49562
49650
  class WorktreeManager {
@@ -49565,7 +49653,7 @@ class WorktreeManager {
49565
49653
  const infoDir = join64(projectRoot, ".git", "info");
49566
49654
  const excludePath = join64(infoDir, "exclude");
49567
49655
  try {
49568
- await mkdir12(infoDir, { recursive: true });
49656
+ await mkdir13(infoDir, { recursive: true });
49569
49657
  let existing = "";
49570
49658
  if (existsSync30(excludePath)) {
49571
49659
  existing = await Bun.file(excludePath).text();
@@ -52006,7 +52094,7 @@ var exports_precheck_runner = {};
52006
52094
  __export(exports_precheck_runner, {
52007
52095
  runPrecheckValidation: () => runPrecheckValidation
52008
52096
  });
52009
- import { mkdirSync as mkdirSync7 } from "fs";
52097
+ import { mkdirSync as mkdirSync6 } from "fs";
52010
52098
  import path18 from "path";
52011
52099
  async function runPrecheckValidation(ctx) {
52012
52100
  const logger = getSafeLogger();
@@ -52022,7 +52110,7 @@ async function runPrecheckValidation(ctx) {
52022
52110
  silent: true
52023
52111
  });
52024
52112
  if (ctx.logFilePath) {
52025
- mkdirSync7(path18.dirname(ctx.logFilePath), { recursive: true });
52113
+ mkdirSync6(path18.dirname(ctx.logFilePath), { recursive: true });
52026
52114
  const precheckLog = {
52027
52115
  type: "precheck",
52028
52116
  timestamp: new Date().toISOString(),
@@ -83483,7 +83571,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
83483
83571
 
83484
83572
  // bin/nax.ts
83485
83573
  init_source();
83486
- import { existsSync as existsSync33, mkdirSync as mkdirSync8 } from "fs";
83574
+ import { existsSync as existsSync33, mkdirSync as mkdirSync7 } from "fs";
83487
83575
  import { homedir as homedir8 } from "os";
83488
83576
  import { join as join72 } from "path";
83489
83577
 
@@ -85347,7 +85435,7 @@ async function runsShowCommand(options) {
85347
85435
  }
85348
85436
  // src/cli/prompts-main.ts
85349
85437
  init_logger2();
85350
- import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
85438
+ import { existsSync as existsSync19, mkdirSync as mkdirSync3 } from "fs";
85351
85439
  import { join as join40 } from "path";
85352
85440
 
85353
85441
  // src/pipeline/index.ts
@@ -85487,7 +85575,7 @@ async function promptsCommand(options) {
85487
85575
  throw new Error(storyId ? `Story "${storyId}" not found in feature "${feature}"` : `No stories found in feature "${feature}"`);
85488
85576
  }
85489
85577
  if (outputDir) {
85490
- mkdirSync4(outputDir, { recursive: true });
85578
+ mkdirSync3(outputDir, { recursive: true });
85491
85579
  }
85492
85580
  logger.info("cli", "Assembling prompts", {
85493
85581
  feature,
@@ -85569,7 +85657,7 @@ ${"=".repeat(80)}`);
85569
85657
  }
85570
85658
  // src/cli/prompts-init.ts
85571
85659
  init_role_task();
85572
- import { existsSync as existsSync20, mkdirSync as mkdirSync5 } from "fs";
85660
+ import { existsSync as existsSync20, mkdirSync as mkdirSync4 } from "fs";
85573
85661
  import { join as join41 } from "path";
85574
85662
  var TEMPLATE_ROLES = [
85575
85663
  { file: "test-writer.md", role: "test-writer" },
@@ -85595,7 +85683,7 @@ var TEMPLATE_HEADER = `<!--
85595
85683
  async function promptsInitCommand(options) {
85596
85684
  const { workdir, force = false, autoWireConfig = true } = options;
85597
85685
  const templatesDir = join41(workdir, ".nax", "templates");
85598
- mkdirSync5(templatesDir, { recursive: true });
85686
+ mkdirSync4(templatesDir, { recursive: true });
85599
85687
  const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync20(join41(templatesDir, f)));
85600
85688
  if (existingFiles.length > 0 && !force) {
85601
85689
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
@@ -86736,7 +86824,7 @@ function formatValueForTable(value) {
86736
86824
  // src/cli/config-profile.ts
86737
86825
  init_paths();
86738
86826
  init_profile();
86739
- import { mkdirSync as mkdirSync6 } from "fs";
86827
+ import { mkdirSync as mkdirSync5 } from "fs";
86740
86828
  import { readdirSync as readdirSync7 } from "fs";
86741
86829
  import { join as join49 } from "path";
86742
86830
  var _profileCLIDeps = {
@@ -86830,7 +86918,7 @@ async function profileCreateCommand(profileName, startDir) {
86830
86918
  if (await profileFile.exists()) {
86831
86919
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
86832
86920
  }
86833
- mkdirSync6(profilesDir, { recursive: true });
86921
+ mkdirSync5(profilesDir, { recursive: true });
86834
86922
  await Bun.write(profilePath, "{}");
86835
86923
  return profilePath;
86836
86924
  }
@@ -86946,7 +87034,7 @@ async function contextInspectCommand(options) {
86946
87034
  // src/cli/rules.ts
86947
87035
  init_canonical_loader();
86948
87036
  init_errors();
86949
- import { mkdir as mkdir8 } from "fs/promises";
87037
+ import { mkdir as mkdir9 } from "fs/promises";
86950
87038
  import { basename as basename8, join as join50 } from "path";
86951
87039
  var _rulesCLIDeps = {
86952
87040
  readFile: async (path15) => Bun.file(path15).text(),
@@ -86962,7 +87050,7 @@ var _rulesCLIDeps = {
86962
87050
  }
86963
87051
  },
86964
87052
  mkdir: async (path15) => {
86965
- await mkdir8(path15, { recursive: true });
87053
+ await mkdir9(path15, { recursive: true });
86966
87054
  },
86967
87055
  globCanonicalRuleFiles: (workdir) => {
86968
87056
  try {
@@ -95647,8 +95735,8 @@ Next: nax generate --package ${options.package}`));
95647
95735
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
95648
95736
  return;
95649
95737
  }
95650
- mkdirSync8(join72(naxDir, "features"), { recursive: true });
95651
- mkdirSync8(join72(naxDir, "hooks"), { recursive: true });
95738
+ mkdirSync7(join72(naxDir, "features"), { recursive: true });
95739
+ mkdirSync7(join72(naxDir, "hooks"), { recursive: true });
95652
95740
  await Bun.write(join72(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
95653
95741
  await Bun.write(join72(naxDir, "hooks.json"), JSON.stringify({
95654
95742
  hooks: {
@@ -95817,7 +95905,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
95817
95905
  }
95818
95906
  try {
95819
95907
  const planLogDir = join72(featureDir, "plan");
95820
- mkdirSync8(planLogDir, { recursive: true });
95908
+ mkdirSync7(planLogDir, { recursive: true });
95821
95909
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
95822
95910
  const planLogPath = join72(planLogDir, `${planLogId}.jsonl`);
95823
95911
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
@@ -95864,7 +95952,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
95864
95952
  }
95865
95953
  resetLogger();
95866
95954
  const runsDir = join72(featureDir, "runs");
95867
- mkdirSync8(runsDir, { recursive: true });
95955
+ mkdirSync7(runsDir, { recursive: true });
95868
95956
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
95869
95957
  const logFilePath = join72(runsDir, `${runId}.jsonl`);
95870
95958
  const isTTY = process.stdout.isTTY ?? false;
@@ -95971,7 +96059,7 @@ features.command("create <name>").description("Create a new feature").option("-d
95971
96059
  process.exit(1);
95972
96060
  }
95973
96061
  const featureDir = join72(naxDir, "features", name);
95974
- mkdirSync8(featureDir, { recursive: true });
96062
+ mkdirSync7(featureDir, { recursive: true });
95975
96063
  await Bun.write(join72(featureDir, "spec.md"), `# Feature: ${name}
95976
96064
 
95977
96065
  ## Overview
@@ -96082,7 +96170,7 @@ Use: nax plan -f <feature> --from <spec>`));
96082
96170
  }
96083
96171
  const config2 = await loadConfig(workdir, cliOverrides);
96084
96172
  const featureLogDir = join72(naxDir, "features", options.feature, "plan");
96085
- mkdirSync8(featureLogDir, { recursive: true });
96173
+ mkdirSync7(featureLogDir, { recursive: true });
96086
96174
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
96087
96175
  const planLogPath = join72(featureLogDir, `${planLogId}.jsonl`);
96088
96176
  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.64.0-canary.2",
3
+ "version": "0.64.0-canary.4",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {