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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +144 -134
  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
  }
@@ -34509,8 +34529,8 @@ var init_truncation = __esm(() => {
34509
34529
  init_adapter();
34510
34530
  });
34511
34531
 
34512
- // src/operations/semantic-review.ts
34513
- function parseLLMShape(raw) {
34532
+ // src/operations/types.ts
34533
+ function parseLlmReviewShape(raw) {
34514
34534
  if (typeof raw !== "object" || raw === null)
34515
34535
  return null;
34516
34536
  const obj = raw;
@@ -34520,11 +34540,13 @@ function parseLLMShape(raw) {
34520
34540
  return null;
34521
34541
  return { passed: obj.passed, findings: obj.findings };
34522
34542
  }
34543
+
34544
+ // src/operations/semantic-review.ts
34523
34545
  var FAIL_OPEN, semanticReviewHopBody = async (initialPrompt, ctx) => {
34524
34546
  const first = await ctx.send(initialPrompt);
34525
34547
  const isTruncated = looksLikeTruncatedJson(first.output);
34526
34548
  const parsed = tryParseLLMJson(first.output);
34527
- if (!isTruncated && parsed && parseLLMShape(parsed))
34549
+ if (!isTruncated && parsed && parseLlmReviewShape(parsed))
34528
34550
  return first;
34529
34551
  const retryPrompt = isTruncated ? ReviewPromptBuilder.jsonRetryCondensed() : ReviewPromptBuilder.jsonRetry();
34530
34552
  const retry = await ctx.send(retryPrompt);
@@ -34562,7 +34584,7 @@ var init_semantic_review = __esm(() => {
34562
34584
  },
34563
34585
  parse(output, _input, _ctx) {
34564
34586
  const raw = tryParseLLMJson(output);
34565
- const parsed = parseLLMShape(raw);
34587
+ const parsed = parseLlmReviewShape(raw);
34566
34588
  if (parsed)
34567
34589
  return parsed;
34568
34590
  if (/"passed"\s*:\s*false/.test(output))
@@ -34573,21 +34595,11 @@ var init_semantic_review = __esm(() => {
34573
34595
  });
34574
34596
 
34575
34597
  // 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
34598
  var FAIL_OPEN2, adversarialReviewHopBody = async (initialPrompt, ctx) => {
34587
34599
  const first = await ctx.send(initialPrompt);
34588
34600
  const isTruncated = looksLikeTruncatedJson(first.output);
34589
34601
  const parsed = tryParseLLMJson(first.output);
34590
- if (!isTruncated && parsed && parseLLMShape2(parsed))
34602
+ if (!isTruncated && parsed && parseLlmReviewShape(parsed))
34591
34603
  return first;
34592
34604
  const retryPrompt = isTruncated ? ReviewPromptBuilder.jsonRetryCondensed() : ReviewPromptBuilder.jsonRetry();
34593
34605
  const retry = await ctx.send(retryPrompt);
@@ -34627,7 +34639,7 @@ var init_adversarial_review = __esm(() => {
34627
34639
  },
34628
34640
  parse(output, _input, _ctx) {
34629
34641
  const raw = tryParseLLMJson(output);
34630
- const parsed = parseLLMShape2(raw);
34642
+ const parsed = parseLlmReviewShape(raw);
34631
34643
  if (parsed)
34632
34644
  return parsed;
34633
34645
  if (/"passed"\s*:\s*false/.test(output))
@@ -35189,7 +35201,7 @@ var init_cost_aggregator = __esm(() => {
35189
35201
  });
35190
35202
 
35191
35203
  // src/runtime/prompt-auditor.ts
35192
- import { mkdirSync as mkdirSync3 } from "fs";
35204
+ import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
35193
35205
  import { join as join21 } from "path";
35194
35206
  function createNoOpPromptAuditor() {
35195
35207
  return {
@@ -35198,6 +35210,15 @@ function createNoOpPromptAuditor() {
35198
35210
  async flush() {}
35199
35211
  };
35200
35212
  }
35213
+ function deriveTxtFilename(entry) {
35214
+ if (entry.sessionName) {
35215
+ return `${entry.ts}-${entry.sessionName}.txt`;
35216
+ }
35217
+ const parts = [String(entry.ts), entry.callType ?? "call", entry.stage ?? "unknown"];
35218
+ if (entry.storyId)
35219
+ parts.push(entry.storyId);
35220
+ return `${parts.join("-")}.txt`;
35221
+ }
35201
35222
  function buildTxtContent(entry) {
35202
35223
  const ts = new Date(entry.ts).toISOString();
35203
35224
  const lines = [
@@ -35226,77 +35247,51 @@ function buildTxtContent(entry) {
35226
35247
  }
35227
35248
 
35228
35249
  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;
35250
+ _queue = Promise.resolve();
35251
+ _dirCreated = false;
35252
+ _jsonlPath;
35253
+ _featureDir;
35254
+ constructor(runId, flushDir, featureName) {
35255
+ this._featureDir = join21(flushDir, featureName);
35256
+ this._jsonlPath = join21(this._featureDir, `${runId}.jsonl`);
35239
35257
  }
35240
35258
  record(entry) {
35241
- if (this._draining) {
35242
- this._inFlightEntries.push(entry);
35243
- return;
35244
- }
35245
- this._entries.push(entry);
35259
+ this._enqueue(entry);
35246
35260
  }
35247
35261
  recordError(entry) {
35248
- if (this._draining) {
35249
- this._inFlightEntries.push(entry);
35250
- return;
35262
+ this._enqueue(entry);
35263
+ }
35264
+ _enqueue(entry) {
35265
+ this._queue = this._queue.then(() => this._writeEntry(entry)).catch((err) => {
35266
+ getSafeLogger()?.warn("audit", "prompt-audit write failed", {
35267
+ path: this._jsonlPath,
35268
+ error: errorMessage(err)
35269
+ });
35270
+ });
35271
+ }
35272
+ async _writeEntry(entry) {
35273
+ if (!this._dirCreated) {
35274
+ await mkdir3(this._featureDir, { recursive: true });
35275
+ this._dirCreated = true;
35251
35276
  }
35252
- this._entries.push(entry);
35277
+ await _promptAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(entry)}
35278
+ `);
35279
+ if (!("prompt" in entry) || !("response" in entry))
35280
+ return;
35281
+ const auditEntry = entry;
35282
+ const filename = deriveTxtFilename(auditEntry);
35283
+ await _promptAuditorDeps.write(join21(this._featureDir, filename), buildTxtContent(auditEntry));
35253
35284
  }
35254
35285
  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
- }
35286
+ await this._queue;
35294
35287
  }
35295
35288
  }
35296
35289
  var _promptAuditorDeps;
35297
35290
  var init_prompt_auditor = __esm(() => {
35291
+ init_logger2();
35298
35292
  _promptAuditorDeps = {
35299
- write: (path4, data) => Bun.write(path4, data)
35293
+ write: (path4, data) => Bun.write(path4, data),
35294
+ appendLine: (path4, data) => appendFile3(path4, data, "utf8")
35300
35295
  };
35301
35296
  });
35302
35297
 
@@ -35358,7 +35353,7 @@ var init_types5 = __esm(() => {
35358
35353
 
35359
35354
  // src/session/manager.ts
35360
35355
  import { randomUUID as randomUUID2 } from "crypto";
35361
- import { mkdir as mkdir3 } from "fs/promises";
35356
+ import { mkdir as mkdir4 } from "fs/promises";
35362
35357
  import { isAbsolute as isAbsolute8, join as join22, relative as relative8, sep } from "path";
35363
35358
  function resolveProjectDirFromScratchDir(scratchDir) {
35364
35359
  const marker = `${sep}.nax${sep}features${sep}`;
@@ -35678,6 +35673,10 @@ class SessionManager {
35678
35673
  if (this._busySessions.has(handle.id)) {
35679
35674
  throw new NaxError(`Session "${handle.id}" is already processing a prompt (single-flight invariant)`, "SESSION_BUSY", { stage: "session", sessionName: handle.id });
35680
35675
  }
35676
+ const terminalDesc = this._findByName(handle.id);
35677
+ if (terminalDesc && (terminalDesc.state === "COMPLETED" || terminalDesc.state === "FAILED")) {
35678
+ 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 });
35679
+ }
35681
35680
  const adapter = this._getAdapter(handle.agentName);
35682
35681
  if (!adapter) {
35683
35682
  throw new NaxError(`SessionManager.sendPrompt: no adapter found for agent "${handle.agentName}"`, "ADAPTER_NOT_FOUND", { stage: "session", agentName: handle.agentName });
@@ -35842,7 +35841,7 @@ var init_manager2 = __esm(() => {
35842
35841
  uuid: () => randomUUID2(),
35843
35842
  sessionScratchDir: (projectDir, featureName, sessionId) => join22(projectDir, ".nax", "features", featureName, "sessions", sessionId),
35844
35843
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
35845
- await mkdir3(scratchDir, { recursive: true });
35844
+ await mkdir4(scratchDir, { recursive: true });
35846
35845
  const { handle: _handle, ...persistable } = descriptor;
35847
35846
  const derivedProjectDir = projectDir ?? resolveProjectDirFromScratchDir(scratchDir);
35848
35847
  if (derivedProjectDir) {
@@ -38464,6 +38463,7 @@ ${stderr}` };
38464
38463
  `);
38465
38464
  const frameworkOverrideLine = ctx.config.acceptance.testFramework ? `
38466
38465
  [FRAMEWORK OVERRIDE: Use ${ctx.config.acceptance.testFramework} as the test framework regardless of what you detect.]` : "";
38466
+ const groupStoryId = group.stories[0]?.id;
38467
38467
  const genResult = await _acceptanceSetupDeps.callOp(ctx, packageDir, acceptanceGenerateOp, {
38468
38468
  featureName: featureName ?? "",
38469
38469
  criteriaList,
@@ -38471,7 +38471,7 @@ ${stderr}` };
38471
38471
  targetTestFilePath: testPath,
38472
38472
  ..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {},
38473
38473
  ..."previousFailure" in ctx && ctx.previousFailure ? { previousFailure: ctx.previousFailure } : {}
38474
- });
38474
+ }, groupStoryId);
38475
38475
  let testCode = genResult.testCode;
38476
38476
  if (!testCode) {
38477
38477
  const skeletonCriteria = groupRefined.map((c, i) => ({
@@ -39982,7 +39982,7 @@ var init_nax_project_root = __esm(() => {
39982
39982
  });
39983
39983
 
39984
39984
  // src/review/review-audit.ts
39985
- import { mkdir as mkdir4 } from "fs/promises";
39985
+ import { mkdir as mkdir5 } from "fs/promises";
39986
39986
  import { join as join33 } from "path";
39987
39987
  async function writeReviewAudit(entry) {
39988
39988
  try {
@@ -40015,7 +40015,7 @@ var init_review_audit = __esm(() => {
40015
40015
  init_nax_project_root();
40016
40016
  _reviewAuditDeps = {
40017
40017
  async mkdir(path8) {
40018
- await mkdir4(path8, { recursive: true });
40018
+ await mkdir5(path8, { recursive: true });
40019
40019
  },
40020
40020
  async writeFile(path8, content) {
40021
40021
  await Bun.write(path8, content);
@@ -41466,7 +41466,7 @@ var init_runner4 = __esm(() => {
41466
41466
  });
41467
41467
 
41468
41468
  // src/review/verdict-writer.ts
41469
- import { mkdir as mkdir5 } from "fs/promises";
41469
+ import { mkdir as mkdir6 } from "fs/promises";
41470
41470
  import { join as join34 } from "path";
41471
41471
  async function writeReviewVerdict(entry) {
41472
41472
  const logger = getSafeLogger();
@@ -41495,7 +41495,7 @@ var init_verdict_writer = __esm(() => {
41495
41495
  init_nax_project_root();
41496
41496
  _verdictWriterDeps = {
41497
41497
  findNaxProjectRoot,
41498
- mkdir: mkdir5,
41498
+ mkdir: mkdir6,
41499
41499
  writeFile: Bun.write
41500
41500
  };
41501
41501
  });
@@ -42555,8 +42555,8 @@ var init_semantic_verdict = __esm(() => {
42555
42555
  init_logger2();
42556
42556
  _semanticVerdictDeps = {
42557
42557
  mkdirp: async (dir) => {
42558
- const { mkdir: mkdir6 } = await import("fs/promises");
42559
- await mkdir6(dir, { recursive: true });
42558
+ const { mkdir: mkdir7 } = await import("fs/promises");
42559
+ await mkdir7(dir, { recursive: true });
42560
42560
  },
42561
42561
  writeFile: async (filePath, content) => {
42562
42562
  await Bun.write(filePath, content);
@@ -42703,15 +42703,15 @@ var init_effectiveness = __esm(() => {
42703
42703
  });
42704
42704
 
42705
42705
  // src/execution/progress.ts
42706
- import { appendFile as appendFile3, mkdir as mkdir6 } from "fs/promises";
42706
+ import { appendFile as appendFile4, mkdir as mkdir7 } from "fs/promises";
42707
42707
  import { join as join36 } from "path";
42708
42708
  async function appendProgress(featureDir, storyId, status, message) {
42709
- await mkdir6(featureDir, { recursive: true });
42709
+ await mkdir7(featureDir, { recursive: true });
42710
42710
  const progressPath = join36(featureDir, "progress.txt");
42711
42711
  const timestamp = new Date().toISOString();
42712
42712
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
42713
42713
  `;
42714
- await appendFile3(progressPath, entry);
42714
+ await appendFile4(progressPath, entry);
42715
42715
  }
42716
42716
  var init_progress = () => {};
42717
42717
 
@@ -43550,7 +43550,7 @@ async function getStoryChangedFiles(workdir, fromRef) {
43550
43550
  async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, storyFromRef, sessionManager, sessionId, runtime) {
43551
43551
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
43552
43552
  if (!rectificationEnabled)
43553
- return { passed: false, cost: 0 };
43553
+ return { passed: false, cost: 0, fullSuiteGatePassed: false };
43554
43554
  const rectificationConfig = config2.execution.rectification;
43555
43555
  const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
43556
43556
  const { testCommand: resolvedTestCmd } = await _rectificationGateDeps.resolveTestCommands(config2, workdir, story.workdir);
@@ -43564,8 +43564,18 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43564
43564
  if (!fullSuitePassed && fullSuiteResult.output) {
43565
43565
  const testSummary = _rectificationGateDeps.parseTestOutput(fullSuiteResult.output);
43566
43566
  if (testSummary.failed > 0) {
43567
+ if (testSummary.failures.length === 0) {
43568
+ logger.warn("tdd", "Full suite gate found unattributable failures \u2014 deferring to run-level regression", {
43569
+ storyId: story.id,
43570
+ failedTests: testSummary.failed,
43571
+ passedTests: testSummary.passed,
43572
+ outputLength: fullSuiteResult.output.length,
43573
+ outputTail: fullSuiteResult.output.slice(-200)
43574
+ });
43575
+ return { passed: true, cost: 0, fullSuiteGatePassed: false };
43576
+ }
43567
43577
  let filteredFailures = testSummary.failures;
43568
- if (storyFromRef && testSummary.failures.length > 0) {
43578
+ if (storyFromRef) {
43569
43579
  const storyFiles = await getStoryChangedFiles(workdir, storyFromRef);
43570
43580
  if (storyFiles.size > 0) {
43571
43581
  filteredFailures = testSummary.failures.filter((f) => storyFiles.has(f.file));
@@ -43580,7 +43590,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43580
43590
  suppressedTestCount: testSummary.failures.length,
43581
43591
  suppressedFiles: uniqueSuppressedFiles
43582
43592
  });
43583
- return { passed: true, cost: 0 };
43593
+ return { passed: true, cost: 0, fullSuiteGatePassed: true };
43584
43594
  }
43585
43595
  if (wasFiltered) {
43586
43596
  logger.info("tdd", "Full suite gate: suppressed pre-existing failures", {
@@ -43603,7 +43613,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43603
43613
  exitCode: fullSuiteResult.exitCode,
43604
43614
  passedTests: testSummary.passed
43605
43615
  });
43606
- return { passed: true, cost: 0 };
43616
+ return { passed: true, cost: 0, fullSuiteGatePassed: true };
43607
43617
  }
43608
43618
  logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
43609
43619
  storyId: story.id,
@@ -43611,17 +43621,17 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
43611
43621
  outputLength: fullSuiteResult.output.length,
43612
43622
  outputTail: fullSuiteResult.output.slice(-200)
43613
43623
  });
43614
- return { passed: false, cost: 0 };
43624
+ return { passed: false, cost: 0, fullSuiteGatePassed: false };
43615
43625
  }
43616
43626
  if (fullSuitePassed) {
43617
43627
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
43618
- return { passed: true, cost: 0 };
43628
+ return { passed: true, cost: 0, fullSuiteGatePassed: true };
43619
43629
  }
43620
43630
  logger.warn("tdd", "Full suite gate execution failed (no output)", {
43621
43631
  storyId: story.id,
43622
43632
  exitCode: fullSuiteResult.exitCode
43623
43633
  });
43624
- return { passed: false, cost: 0 };
43634
+ return { passed: false, cost: 0, fullSuiteGatePassed: false };
43625
43635
  }
43626
43636
  async function runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, testOutput, featureName, projectDir, sessionManager, sessionId, runtime) {
43627
43637
  logger.warn("tdd", "Full suite gate detected regressions", {
@@ -43761,13 +43771,13 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
43761
43771
  });
43762
43772
  const fixed = outcome.outcome === "fixed";
43763
43773
  if (fixed) {
43764
- return { passed: true, cost: gateCostAccum };
43774
+ return { passed: true, cost: gateCostAccum, fullSuiteGatePassed: true };
43765
43775
  }
43766
43776
  logger.warn("tdd", "[WARN] Full suite gate failed after rectification exhausted", {
43767
43777
  storyId: story.id,
43768
43778
  attempts: outcome.attempts
43769
43779
  });
43770
- return { passed: false, cost: gateCostAccum };
43780
+ return { passed: false, cost: gateCostAccum, fullSuiteGatePassed: false };
43771
43781
  }
43772
43782
  var _rectificationGateDeps;
43773
43783
  var init_rectification_gate = __esm(() => {
@@ -44236,7 +44246,7 @@ async function runThreeSessionTdd(options) {
44236
44246
  };
44237
44247
  }
44238
44248
  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);
44249
+ const { cost: fullSuiteGateCost, fullSuiteGatePassed } = await runFullSuiteGate(story, config2, workdir, wrapAdapterAsManager(agent), implementerTier, lite, logger, featureName, projectDir, initialRef, implementerBinding?.sessionManager, implementerBinding?.sessionId);
44240
44250
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
44241
44251
  const verifierBundle = await getTddContextBundle?.("verifier") ?? tddContextBundles?.verifier;
44242
44252
  const session3 = await runTddSessionOp(verifyTddOp, options, session3Ref, verifierBundle, getTddSessionBinding?.("verifier"));
@@ -46400,7 +46410,7 @@ __export(exports_init_context, {
46400
46410
  _initContextDeps: () => _initContextDeps
46401
46411
  });
46402
46412
  import { existsSync as existsSync21 } from "fs";
46403
- import { mkdir as mkdir7 } from "fs/promises";
46413
+ import { mkdir as mkdir8 } from "fs/promises";
46404
46414
  import { basename as basename5, join as join42 } from "path";
46405
46415
  async function findFiles(dir, maxFiles = 200) {
46406
46416
  try {
@@ -46644,7 +46654,7 @@ async function initPackage(repoRoot, packagePath, force = false) {
46644
46654
  return;
46645
46655
  }
46646
46656
  if (!existsSync21(naxDir)) {
46647
- await mkdir7(naxDir, { recursive: true });
46657
+ await mkdir8(naxDir, { recursive: true });
46648
46658
  }
46649
46659
  const content = generatePackageContextTemplate(packagePath);
46650
46660
  await Bun.write(contextPath, content);
@@ -46659,7 +46669,7 @@ async function initContext(projectRoot, options = {}) {
46659
46669
  return;
46660
46670
  }
46661
46671
  if (!existsSync21(naxDir)) {
46662
- await mkdir7(naxDir, { recursive: true });
46672
+ await mkdir8(naxDir, { recursive: true });
46663
46673
  }
46664
46674
  const scan = await scanProject(projectRoot);
46665
46675
  let content;
@@ -47427,7 +47437,7 @@ var package_default;
47427
47437
  var init_package = __esm(() => {
47428
47438
  package_default = {
47429
47439
  name: "@nathapp/nax",
47430
- version: "0.64.0-canary.2",
47440
+ version: "0.64.0-canary.3",
47431
47441
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
47432
47442
  type: "module",
47433
47443
  bin: {
@@ -47509,8 +47519,8 @@ var init_version = __esm(() => {
47509
47519
  NAX_VERSION = package_default.version;
47510
47520
  NAX_COMMIT = (() => {
47511
47521
  try {
47512
- if (/^[0-9a-f]{6,10}$/.test("c7d0f838"))
47513
- return "c7d0f838";
47522
+ if (/^[0-9a-f]{6,10}$/.test("6caa3726"))
47523
+ return "6caa3726";
47514
47524
  } catch {}
47515
47525
  try {
47516
47526
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -48331,7 +48341,7 @@ var init_acceptance_loop = __esm(() => {
48331
48341
  });
48332
48342
 
48333
48343
  // src/session/scratch-purge.ts
48334
- import { mkdir as mkdir9, rename, rm } from "fs/promises";
48344
+ import { mkdir as mkdir10, rename, rm } from "fs/promises";
48335
48345
  import { dirname as dirname9, join as join60 } from "path";
48336
48346
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
48337
48347
  const sessionsDir = join60(projectDir, ".nax", "features", featureName, "sessions");
@@ -48383,7 +48393,7 @@ var init_scratch_purge = __esm(() => {
48383
48393
  readFile: (path16) => Bun.file(path16).text(),
48384
48394
  remove: (path16) => rm(path16, { recursive: true, force: true }),
48385
48395
  move: async (src, dest) => {
48386
- await mkdir9(dirname9(dest), { recursive: true });
48396
+ await mkdir10(dirname9(dest), { recursive: true });
48387
48397
  await rename(src, dest);
48388
48398
  },
48389
48399
  now: () => Date.now()
@@ -49017,7 +49027,7 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
49017
49027
  var DEFAULT_MAX_BATCH_SIZE = 4;
49018
49028
 
49019
49029
  // src/pipeline/subscribers/events-writer.ts
49020
- import { appendFile as appendFile4, mkdir as mkdir10 } from "fs/promises";
49030
+ import { appendFile as appendFile5, mkdir as mkdir11 } from "fs/promises";
49021
49031
  import { homedir as homedir5 } from "os";
49022
49032
  import { basename as basename9, join as join61 } from "path";
49023
49033
  function wireEventsWriter(bus, feature, runId, workdir) {
@@ -49030,10 +49040,10 @@ function wireEventsWriter(bus, feature, runId, workdir) {
49030
49040
  return (async () => {
49031
49041
  try {
49032
49042
  if (!dirReady) {
49033
- await mkdir10(eventsDir, { recursive: true });
49043
+ await mkdir11(eventsDir, { recursive: true });
49034
49044
  dirReady = true;
49035
49045
  }
49036
- await appendFile4(eventsFile, `${JSON.stringify(line)}
49046
+ await appendFile5(eventsFile, `${JSON.stringify(line)}
49037
49047
  `);
49038
49048
  } catch (err) {
49039
49049
  logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
@@ -49203,7 +49213,7 @@ var init_interaction2 = __esm(() => {
49203
49213
  });
49204
49214
 
49205
49215
  // src/pipeline/subscribers/registry.ts
49206
- import { mkdir as mkdir11, writeFile } from "fs/promises";
49216
+ import { mkdir as mkdir12, writeFile } from "fs/promises";
49207
49217
  import { homedir as homedir6 } from "os";
49208
49218
  import { basename as basename10, join as join62 } from "path";
49209
49219
  function wireRegistry(bus, feature, runId, workdir) {
@@ -49214,7 +49224,7 @@ function wireRegistry(bus, feature, runId, workdir) {
49214
49224
  const unsub = bus.on("run:started", (_ev) => {
49215
49225
  return (async () => {
49216
49226
  try {
49217
- await mkdir11(runDir, { recursive: true });
49227
+ await mkdir12(runDir, { recursive: true });
49218
49228
  const meta3 = {
49219
49229
  runId,
49220
49230
  project,
@@ -49556,7 +49566,7 @@ __export(exports_manager, {
49556
49566
  WorktreeManager: () => WorktreeManager
49557
49567
  });
49558
49568
  import { existsSync as existsSync30, symlinkSync } from "fs";
49559
- import { mkdir as mkdir12 } from "fs/promises";
49569
+ import { mkdir as mkdir13 } from "fs/promises";
49560
49570
  import { join as join64 } from "path";
49561
49571
 
49562
49572
  class WorktreeManager {
@@ -49565,7 +49575,7 @@ class WorktreeManager {
49565
49575
  const infoDir = join64(projectRoot, ".git", "info");
49566
49576
  const excludePath = join64(infoDir, "exclude");
49567
49577
  try {
49568
- await mkdir12(infoDir, { recursive: true });
49578
+ await mkdir13(infoDir, { recursive: true });
49569
49579
  let existing = "";
49570
49580
  if (existsSync30(excludePath)) {
49571
49581
  existing = await Bun.file(excludePath).text();
@@ -52006,7 +52016,7 @@ var exports_precheck_runner = {};
52006
52016
  __export(exports_precheck_runner, {
52007
52017
  runPrecheckValidation: () => runPrecheckValidation
52008
52018
  });
52009
- import { mkdirSync as mkdirSync7 } from "fs";
52019
+ import { mkdirSync as mkdirSync6 } from "fs";
52010
52020
  import path18 from "path";
52011
52021
  async function runPrecheckValidation(ctx) {
52012
52022
  const logger = getSafeLogger();
@@ -52022,7 +52032,7 @@ async function runPrecheckValidation(ctx) {
52022
52032
  silent: true
52023
52033
  });
52024
52034
  if (ctx.logFilePath) {
52025
- mkdirSync7(path18.dirname(ctx.logFilePath), { recursive: true });
52035
+ mkdirSync6(path18.dirname(ctx.logFilePath), { recursive: true });
52026
52036
  const precheckLog = {
52027
52037
  type: "precheck",
52028
52038
  timestamp: new Date().toISOString(),
@@ -83483,7 +83493,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
83483
83493
 
83484
83494
  // bin/nax.ts
83485
83495
  init_source();
83486
- import { existsSync as existsSync33, mkdirSync as mkdirSync8 } from "fs";
83496
+ import { existsSync as existsSync33, mkdirSync as mkdirSync7 } from "fs";
83487
83497
  import { homedir as homedir8 } from "os";
83488
83498
  import { join as join72 } from "path";
83489
83499
 
@@ -85347,7 +85357,7 @@ async function runsShowCommand(options) {
85347
85357
  }
85348
85358
  // src/cli/prompts-main.ts
85349
85359
  init_logger2();
85350
- import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
85360
+ import { existsSync as existsSync19, mkdirSync as mkdirSync3 } from "fs";
85351
85361
  import { join as join40 } from "path";
85352
85362
 
85353
85363
  // src/pipeline/index.ts
@@ -85487,7 +85497,7 @@ async function promptsCommand(options) {
85487
85497
  throw new Error(storyId ? `Story "${storyId}" not found in feature "${feature}"` : `No stories found in feature "${feature}"`);
85488
85498
  }
85489
85499
  if (outputDir) {
85490
- mkdirSync4(outputDir, { recursive: true });
85500
+ mkdirSync3(outputDir, { recursive: true });
85491
85501
  }
85492
85502
  logger.info("cli", "Assembling prompts", {
85493
85503
  feature,
@@ -85569,7 +85579,7 @@ ${"=".repeat(80)}`);
85569
85579
  }
85570
85580
  // src/cli/prompts-init.ts
85571
85581
  init_role_task();
85572
- import { existsSync as existsSync20, mkdirSync as mkdirSync5 } from "fs";
85582
+ import { existsSync as existsSync20, mkdirSync as mkdirSync4 } from "fs";
85573
85583
  import { join as join41 } from "path";
85574
85584
  var TEMPLATE_ROLES = [
85575
85585
  { file: "test-writer.md", role: "test-writer" },
@@ -85595,7 +85605,7 @@ var TEMPLATE_HEADER = `<!--
85595
85605
  async function promptsInitCommand(options) {
85596
85606
  const { workdir, force = false, autoWireConfig = true } = options;
85597
85607
  const templatesDir = join41(workdir, ".nax", "templates");
85598
- mkdirSync5(templatesDir, { recursive: true });
85608
+ mkdirSync4(templatesDir, { recursive: true });
85599
85609
  const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync20(join41(templatesDir, f)));
85600
85610
  if (existingFiles.length > 0 && !force) {
85601
85611
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
@@ -86736,7 +86746,7 @@ function formatValueForTable(value) {
86736
86746
  // src/cli/config-profile.ts
86737
86747
  init_paths();
86738
86748
  init_profile();
86739
- import { mkdirSync as mkdirSync6 } from "fs";
86749
+ import { mkdirSync as mkdirSync5 } from "fs";
86740
86750
  import { readdirSync as readdirSync7 } from "fs";
86741
86751
  import { join as join49 } from "path";
86742
86752
  var _profileCLIDeps = {
@@ -86830,7 +86840,7 @@ async function profileCreateCommand(profileName, startDir) {
86830
86840
  if (await profileFile.exists()) {
86831
86841
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
86832
86842
  }
86833
- mkdirSync6(profilesDir, { recursive: true });
86843
+ mkdirSync5(profilesDir, { recursive: true });
86834
86844
  await Bun.write(profilePath, "{}");
86835
86845
  return profilePath;
86836
86846
  }
@@ -86946,7 +86956,7 @@ async function contextInspectCommand(options) {
86946
86956
  // src/cli/rules.ts
86947
86957
  init_canonical_loader();
86948
86958
  init_errors();
86949
- import { mkdir as mkdir8 } from "fs/promises";
86959
+ import { mkdir as mkdir9 } from "fs/promises";
86950
86960
  import { basename as basename8, join as join50 } from "path";
86951
86961
  var _rulesCLIDeps = {
86952
86962
  readFile: async (path15) => Bun.file(path15).text(),
@@ -86962,7 +86972,7 @@ var _rulesCLIDeps = {
86962
86972
  }
86963
86973
  },
86964
86974
  mkdir: async (path15) => {
86965
- await mkdir8(path15, { recursive: true });
86975
+ await mkdir9(path15, { recursive: true });
86966
86976
  },
86967
86977
  globCanonicalRuleFiles: (workdir) => {
86968
86978
  try {
@@ -95647,8 +95657,8 @@ Next: nax generate --package ${options.package}`));
95647
95657
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
95648
95658
  return;
95649
95659
  }
95650
- mkdirSync8(join72(naxDir, "features"), { recursive: true });
95651
- mkdirSync8(join72(naxDir, "hooks"), { recursive: true });
95660
+ mkdirSync7(join72(naxDir, "features"), { recursive: true });
95661
+ mkdirSync7(join72(naxDir, "hooks"), { recursive: true });
95652
95662
  await Bun.write(join72(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
95653
95663
  await Bun.write(join72(naxDir, "hooks.json"), JSON.stringify({
95654
95664
  hooks: {
@@ -95817,7 +95827,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
95817
95827
  }
95818
95828
  try {
95819
95829
  const planLogDir = join72(featureDir, "plan");
95820
- mkdirSync8(planLogDir, { recursive: true });
95830
+ mkdirSync7(planLogDir, { recursive: true });
95821
95831
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
95822
95832
  const planLogPath = join72(planLogDir, `${planLogId}.jsonl`);
95823
95833
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
@@ -95864,7 +95874,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
95864
95874
  }
95865
95875
  resetLogger();
95866
95876
  const runsDir = join72(featureDir, "runs");
95867
- mkdirSync8(runsDir, { recursive: true });
95877
+ mkdirSync7(runsDir, { recursive: true });
95868
95878
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
95869
95879
  const logFilePath = join72(runsDir, `${runId}.jsonl`);
95870
95880
  const isTTY = process.stdout.isTTY ?? false;
@@ -95971,7 +95981,7 @@ features.command("create <name>").description("Create a new feature").option("-d
95971
95981
  process.exit(1);
95972
95982
  }
95973
95983
  const featureDir = join72(naxDir, "features", name);
95974
- mkdirSync8(featureDir, { recursive: true });
95984
+ mkdirSync7(featureDir, { recursive: true });
95975
95985
  await Bun.write(join72(featureDir, "spec.md"), `# Feature: ${name}
95976
95986
 
95977
95987
  ## Overview
@@ -96082,7 +96092,7 @@ Use: nax plan -f <feature> --from <spec>`));
96082
96092
  }
96083
96093
  const config2 = await loadConfig(workdir, cliOverrides);
96084
96094
  const featureLogDir = join72(naxDir, "features", options.feature, "plan");
96085
- mkdirSync8(featureLogDir, { recursive: true });
96095
+ mkdirSync7(featureLogDir, { recursive: true });
96086
96096
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
96087
96097
  const planLogPath = join72(featureLogDir, `${planLogId}.jsonl`);
96088
96098
  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.3",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {