@nathapp/nax 0.54.2 → 0.54.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 +191 -108
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -18786,7 +18786,7 @@ async function refineAcceptanceCriteria(criteria, context) {
18786
18786
  if (criteria.length === 0) {
18787
18787
  return [];
18788
18788
  }
18789
- const { storyId, codebaseContext, config: config2, testStrategy, testFramework } = context;
18789
+ const { storyId, featureName, workdir, codebaseContext, config: config2, testStrategy, testFramework } = context;
18790
18790
  const logger = getLogger();
18791
18791
  const modelTier = config2.acceptance?.model ?? "fast";
18792
18792
  const modelEntry = config2.models[modelTier] ?? config2.models.fast;
@@ -18801,7 +18801,11 @@ async function refineAcceptanceCriteria(criteria, context) {
18801
18801
  jsonMode: true,
18802
18802
  maxTokens: 4096,
18803
18803
  model: modelDef.model,
18804
- config: config2
18804
+ config: config2,
18805
+ featureName,
18806
+ storyId,
18807
+ workdir,
18808
+ sessionRole: "refine"
18805
18809
  });
18806
18810
  } catch (error48) {
18807
18811
  const reason = errorMessage(error48);
@@ -18940,27 +18944,58 @@ Rules:
18940
18944
  - **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
18941
18945
  - Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
18942
18946
  - **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
18943
- - Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
18944
- - **Path anchor (CRITICAL)**: This test file lives at \`<package-root>/${acceptanceTestFilename(options.language)}\` and runs from the package root. Import from package sources using relative paths like \`./src/...\`. No deep \`../../../../\` traversal needed.`;
18947
+ - **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
18948
+ - **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${options.featureDir}/${acceptanceTestFilename(options.language)}\`. Import from package sources using relative paths like \`./src/...\`. No deep \`../../../../\` traversal needed.`;
18945
18949
  const prompt = basePrompt;
18946
18950
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
18947
18951
  const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
18948
18952
  model: options.modelDef.model,
18949
18953
  config: options.config,
18950
18954
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
18951
- workdir: options.workdir
18955
+ workdir: options.workdir,
18956
+ featureName: options.featureName,
18957
+ sessionRole: "acceptance-gen"
18952
18958
  });
18953
18959
  let testCode = extractTestCode(rawOutput);
18960
+ logger.debug("acceptance", "Received raw output from LLM", {
18961
+ hasCode: testCode !== null,
18962
+ outputLength: rawOutput.length,
18963
+ outputPreview: rawOutput.slice(0, 300)
18964
+ });
18954
18965
  if (!testCode) {
18955
18966
  const targetPath = join2(options.featureDir, acceptanceTestFilename(options.language));
18967
+ let recoveryFailed = false;
18968
+ logger.debug("acceptance", "BUG-076 recovery: checking for agent-written file", { targetPath });
18956
18969
  try {
18957
18970
  const existing = await Bun.file(targetPath).text();
18958
18971
  const recovered = extractTestCode(existing);
18972
+ logger.debug("acceptance", "BUG-076 recovery: file check result", {
18973
+ fileSize: existing.length,
18974
+ extractedCode: recovered !== null,
18975
+ filePreview: existing.slice(0, 300)
18976
+ });
18959
18977
  if (recovered) {
18960
18978
  logger.info("acceptance", "Acceptance test written directly by agent \u2014 using existing file", { targetPath });
18961
18979
  testCode = recovered;
18980
+ } else {
18981
+ recoveryFailed = true;
18982
+ logger.error("acceptance", "BUG-076: ACP adapter wrote file but no code extractable \u2014 falling back to skeleton", {
18983
+ targetPath,
18984
+ filePreview: existing.slice(0, 300)
18985
+ });
18962
18986
  }
18963
- } catch {}
18987
+ } catch {
18988
+ recoveryFailed = true;
18989
+ logger.debug("acceptance", "BUG-076 recovery: no file written by agent, falling back to skeleton", {
18990
+ targetPath,
18991
+ rawOutputPreview: rawOutput.slice(0, 500)
18992
+ });
18993
+ }
18994
+ if (recoveryFailed) {
18995
+ logger.error("acceptance", "BUG-076: LLM returned non-code output and no file was written by agent \u2014 falling back to skeleton", {
18996
+ rawOutputPreview: rawOutput.slice(0, 500)
18997
+ });
18998
+ }
18964
18999
  }
18965
19000
  if (!testCode) {
18966
19001
  logger.warn("acceptance", "LLM returned non-code output for acceptance tests \u2014 falling back to skeleton", {
@@ -19061,7 +19096,9 @@ async function generateAcceptanceTests(adapter, options) {
19061
19096
  model: options.modelDef.model,
19062
19097
  config: options.config,
19063
19098
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
19064
- workdir: options.workdir
19099
+ workdir: options.workdir,
19100
+ featureName: options.featureName,
19101
+ sessionRole: "acceptance-gen"
19065
19102
  });
19066
19103
  const testCode = extractTestCode(output);
19067
19104
  if (!testCode) {
@@ -19312,7 +19349,10 @@ async function generateFixStories(adapter, options) {
19312
19349
  try {
19313
19350
  const fixDescription = await adapter.complete(prompt, {
19314
19351
  model: modelDef.model,
19315
- config: options.config
19352
+ config: options.config,
19353
+ featureName: options.prd.feature,
19354
+ workdir: options.workdir,
19355
+ sessionRole: "fix-gen"
19316
19356
  });
19317
19357
  fixStories.push({
19318
19358
  id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
@@ -20101,10 +20141,11 @@ class AcpAgentAdapter {
20101
20141
  let session = null;
20102
20142
  let hadError = false;
20103
20143
  try {
20144
+ const completeSessionName = _options?.sessionName ?? buildSessionName(workdir ?? process.cwd(), _options?.featureName, _options?.storyId, _options?.sessionRole);
20104
20145
  session = await client.createSession({
20105
20146
  agentName: this.name,
20106
20147
  permissionMode,
20107
- sessionName: _options?.sessionName
20148
+ sessionName: completeSessionName
20108
20149
  });
20109
20150
  let timeoutId;
20110
20151
  const timeoutPromise = new Promise((_, reject) => {
@@ -20210,7 +20251,9 @@ class AcpAgentAdapter {
20210
20251
  output = await this.complete(prompt, {
20211
20252
  model,
20212
20253
  jsonMode: true,
20213
- config: options.config
20254
+ config: options.config,
20255
+ workdir: options.workdir,
20256
+ sessionRole: "decompose"
20214
20257
  });
20215
20258
  } catch (err) {
20216
20259
  const msg = err instanceof Error ? err.message : String(err);
@@ -22305,7 +22348,7 @@ var package_default;
22305
22348
  var init_package = __esm(() => {
22306
22349
  package_default = {
22307
22350
  name: "@nathapp/nax",
22308
- version: "0.54.2",
22351
+ version: "0.54.3",
22309
22352
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22310
22353
  type: "module",
22311
22354
  bin: {
@@ -22382,8 +22425,8 @@ var init_version = __esm(() => {
22382
22425
  NAX_VERSION = package_default.version;
22383
22426
  NAX_COMMIT = (() => {
22384
22427
  try {
22385
- if (/^[0-9a-f]{6,10}$/.test("18dd8fc"))
22386
- return "18dd8fc";
22428
+ if (/^[0-9a-f]{6,10}$/.test("5acee1f"))
22429
+ return "5acee1f";
22387
22430
  } catch {}
22388
22431
  try {
22389
22432
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23627,7 +23670,10 @@ class AutoInteractionPlugin {
23627
23670
  const output = await adapter.complete(prompt, {
23628
23671
  ...modelArg && { model: modelArg },
23629
23672
  jsonMode: true,
23630
- ...this.config.naxConfig && { config: this.config.naxConfig }
23673
+ ...this.config.naxConfig && { config: this.config.naxConfig },
23674
+ featureName: request.featureName,
23675
+ storyId: request.storyId,
23676
+ sessionRole: "auto"
23631
23677
  });
23632
23678
  return this.parseResponse(output);
23633
23679
  }
@@ -24368,31 +24414,33 @@ ${stderr}` };
24368
24414
  }
24369
24415
  let totalCriteria = 0;
24370
24416
  let testableCount = 0;
24371
- const existsResults = await Promise.all(testPaths.map(({ testPath }) => _acceptanceSetupDeps.fileExists(testPath)));
24372
- const anyFileMissing = existsResults.some((exists) => !exists);
24373
- let shouldGenerate = anyFileMissing;
24374
- if (!anyFileMissing) {
24375
- const fingerprint = computeACFingerprint(allCriteria);
24376
- const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
24377
- getSafeLogger()?.debug("acceptance-setup", "Fingerprint check", {
24378
- currentFingerprint: fingerprint,
24379
- storedFingerprint: meta3?.acFingerprint ?? "none",
24380
- match: meta3?.acFingerprint === fingerprint
24381
- });
24382
- if (!meta3 || meta3.acFingerprint !== fingerprint) {
24417
+ const fingerprint = computeACFingerprint(allCriteria);
24418
+ const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
24419
+ getSafeLogger()?.debug("acceptance-setup", "Fingerprint check", {
24420
+ currentFingerprint: fingerprint,
24421
+ storedFingerprint: meta3?.acFingerprint ?? "none",
24422
+ match: meta3?.acFingerprint === fingerprint
24423
+ });
24424
+ let shouldGenerate = false;
24425
+ if (!meta3 || meta3.acFingerprint !== fingerprint) {
24426
+ if (!meta3) {
24427
+ getSafeLogger()?.info("acceptance-setup", "No acceptance meta \u2014 generating acceptance tests");
24428
+ } else {
24383
24429
  getSafeLogger()?.info("acceptance-setup", "ACs changed \u2014 regenerating acceptance tests", {
24384
- reason: !meta3 ? "no meta file" : "fingerprint mismatch"
24430
+ reason: "fingerprint mismatch",
24431
+ currentFingerprint: fingerprint,
24432
+ storedFingerprint: meta3.acFingerprint
24385
24433
  });
24386
- for (const { testPath } of testPaths) {
24387
- if (await _acceptanceSetupDeps.fileExists(testPath)) {
24388
- await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
24389
- await _acceptanceSetupDeps.deleteFile(testPath);
24390
- }
24434
+ }
24435
+ for (const { testPath } of testPaths) {
24436
+ if (await _acceptanceSetupDeps.fileExists(testPath)) {
24437
+ await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
24438
+ await _acceptanceSetupDeps.deleteFile(testPath);
24391
24439
  }
24392
- shouldGenerate = true;
24393
- } else {
24394
- getSafeLogger()?.info("acceptance-setup", "Reusing existing acceptance tests (fingerprint match)");
24395
24440
  }
24441
+ shouldGenerate = true;
24442
+ } else {
24443
+ getSafeLogger()?.info("acceptance-setup", "Reusing existing acceptance tests (fingerprint match)");
24396
24444
  }
24397
24445
  if (shouldGenerate) {
24398
24446
  totalCriteria = allCriteria.length;
@@ -24404,6 +24452,8 @@ ${stderr}` };
24404
24452
  for (const story of nonFixStories) {
24405
24453
  const storyRefined = await _acceptanceSetupDeps.refine(story.acceptanceCriteria, {
24406
24454
  storyId: story.id,
24455
+ featureName: ctx.prd.feature,
24456
+ workdir: ctx.workdir,
24407
24457
  codebaseContext: "",
24408
24458
  config: ctx.config,
24409
24459
  testStrategy: ctx.config.acceptance.testStrategy,
@@ -24439,10 +24489,10 @@ ${stderr}` };
24439
24489
  });
24440
24490
  await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
24441
24491
  }
24442
- const fingerprint = computeACFingerprint(allCriteria);
24492
+ const fingerprint2 = computeACFingerprint(allCriteria);
24443
24493
  await _acceptanceSetupDeps.writeMeta(metaPath, {
24444
24494
  generatedAt: new Date().toISOString(),
24445
- acFingerprint: fingerprint,
24495
+ acFingerprint: fingerprint2,
24446
24496
  storyCount: ctx.prd.userStories.length,
24447
24497
  acCount: totalCriteria,
24448
24498
  generator: "nax"
@@ -24907,6 +24957,16 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
24907
24957
  storyId: story.id,
24908
24958
  durationMs: durationMs2
24909
24959
  });
24960
+ logger?.debug("review", "Semantic review findings", {
24961
+ storyId: story.id,
24962
+ findings: parsed.findings.map((f) => ({
24963
+ severity: f.severity,
24964
+ file: f.file,
24965
+ line: f.line,
24966
+ issue: f.issue,
24967
+ suggestion: f.suggestion
24968
+ }))
24969
+ });
24910
24970
  const output = `Semantic review failed:
24911
24971
 
24912
24972
  ${formatFindings(parsed.findings)}`;
@@ -32828,36 +32888,32 @@ import { appendFileSync as appendFileSync2 } from "fs";
32828
32888
  function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
32829
32889
  const logger = getSafeLogger();
32830
32890
  stopHeartbeat();
32831
- heartbeatTimer = setInterval(async () => {
32832
- logger?.debug("crash-recovery", "Heartbeat");
32833
- if (jsonlFilePath) {
32891
+ heartbeatTimer = setInterval(() => {
32892
+ (async () => {
32834
32893
  try {
32835
- const heartbeatEntry = {
32836
- timestamp: new Date().toISOString(),
32837
- level: "debug",
32838
- stage: "heartbeat",
32839
- message: "Process alive",
32840
- data: {
32841
- pid: process.pid,
32842
- memoryUsageMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
32843
- }
32844
- };
32845
- const line = `${JSON.stringify(heartbeatEntry)}
32894
+ logger?.debug("crash-recovery", "Heartbeat");
32895
+ if (jsonlFilePath) {
32896
+ const heartbeatEntry = {
32897
+ timestamp: new Date().toISOString(),
32898
+ level: "debug",
32899
+ stage: "heartbeat",
32900
+ message: "Process alive",
32901
+ data: {
32902
+ pid: process.pid,
32903
+ memoryUsageMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
32904
+ }
32905
+ };
32906
+ const line = `${JSON.stringify(heartbeatEntry)}
32846
32907
  `;
32847
- appendFileSync2(jsonlFilePath, line);
32908
+ appendFileSync2(jsonlFilePath, line);
32909
+ }
32910
+ await statusWriter.update(getTotalCost(), getIterations(), {
32911
+ lastHeartbeat: new Date().toISOString()
32912
+ });
32848
32913
  } catch (err) {
32849
- logger?.warn("crash-recovery", "Failed to write heartbeat", { error: err.message });
32914
+ logger?.warn("crash-recovery", "Failed during heartbeat", { error: err.message });
32850
32915
  }
32851
- }
32852
- try {
32853
- await statusWriter.update(getTotalCost(), getIterations(), {
32854
- lastHeartbeat: new Date().toISOString()
32855
- });
32856
- } catch (err) {
32857
- logger?.warn("crash-recovery", "Failed to update status during heartbeat", {
32858
- error: err.message
32859
- });
32860
- }
32916
+ })().catch(() => {});
32861
32917
  }, 60000);
32862
32918
  logger?.debug("crash-recovery", "Heartbeat started (60s interval)");
32863
32919
  }
@@ -33016,6 +33072,10 @@ function createSignalHandler(ctx) {
33016
33072
  }
33017
33073
  function createUncaughtExceptionHandler(ctx) {
33018
33074
  return async (error48) => {
33075
+ process.stderr.write(`
33076
+ [nax crash] Uncaught exception: ${error48.message}
33077
+ ${error48.stack ?? ""}
33078
+ `);
33019
33079
  const logger = getSafeLogger();
33020
33080
  logger?.error("crash-recovery", "Uncaught exception", {
33021
33081
  error: error48.message,
@@ -33036,6 +33096,10 @@ function createUncaughtExceptionHandler(ctx) {
33036
33096
  function createUnhandledRejectionHandler(ctx) {
33037
33097
  return async (reason) => {
33038
33098
  const error48 = reason instanceof Error ? reason : new Error(String(reason));
33099
+ process.stderr.write(`
33100
+ [nax crash] Unhandled rejection: ${error48.message}
33101
+ ${error48.stack ?? ""}
33102
+ `);
33039
33103
  const logger = getSafeLogger();
33040
33104
  logger?.error("crash-recovery", "Unhandled promise rejection", {
33041
33105
  error: error48.message,
@@ -34829,7 +34893,7 @@ function wireEventsWriter(bus, feature, runId, workdir) {
34829
34893
  const eventsFile = join49(eventsDir, "events.jsonl");
34830
34894
  let dirReady = false;
34831
34895
  const write = (line) => {
34832
- (async () => {
34896
+ return (async () => {
34833
34897
  try {
34834
34898
  if (!dirReady) {
34835
34899
  await mkdir2(eventsDir, { recursive: true });
@@ -34847,16 +34911,30 @@ function wireEventsWriter(bus, feature, runId, workdir) {
34847
34911
  };
34848
34912
  const unsubs = [];
34849
34913
  unsubs.push(bus.on("run:started", (_ev) => {
34850
- write({ ts: new Date().toISOString(), event: "run:started", runId, feature, project });
34914
+ return write({ ts: new Date().toISOString(), event: "run:started", runId, feature, project });
34851
34915
  }));
34852
34916
  unsubs.push(bus.on("story:started", (ev) => {
34853
- write({ ts: new Date().toISOString(), event: "story:started", runId, feature, project, storyId: ev.storyId });
34917
+ return write({
34918
+ ts: new Date().toISOString(),
34919
+ event: "story:started",
34920
+ runId,
34921
+ feature,
34922
+ project,
34923
+ storyId: ev.storyId
34924
+ });
34854
34925
  }));
34855
34926
  unsubs.push(bus.on("story:completed", (ev) => {
34856
- write({ ts: new Date().toISOString(), event: "story:completed", runId, feature, project, storyId: ev.storyId });
34927
+ return write({
34928
+ ts: new Date().toISOString(),
34929
+ event: "story:completed",
34930
+ runId,
34931
+ feature,
34932
+ project,
34933
+ storyId: ev.storyId
34934
+ });
34857
34935
  }));
34858
34936
  unsubs.push(bus.on("story:decomposed", (ev) => {
34859
- write({
34937
+ return write({
34860
34938
  ts: new Date().toISOString(),
34861
34939
  event: "story:decomposed",
34862
34940
  runId,
@@ -34867,13 +34945,20 @@ function wireEventsWriter(bus, feature, runId, workdir) {
34867
34945
  });
34868
34946
  }));
34869
34947
  unsubs.push(bus.on("story:failed", (ev) => {
34870
- write({ ts: new Date().toISOString(), event: "story:failed", runId, feature, project, storyId: ev.storyId });
34948
+ return write({
34949
+ ts: new Date().toISOString(),
34950
+ event: "story:failed",
34951
+ runId,
34952
+ feature,
34953
+ project,
34954
+ storyId: ev.storyId
34955
+ });
34871
34956
  }));
34872
34957
  unsubs.push(bus.on("run:completed", (_ev) => {
34873
- write({ ts: new Date().toISOString(), event: "on-complete", runId, feature, project });
34958
+ return write({ ts: new Date().toISOString(), event: "on-complete", runId, feature, project });
34874
34959
  }));
34875
34960
  unsubs.push(bus.on("run:paused", (ev) => {
34876
- write({
34961
+ return write({
34877
34962
  ts: new Date().toISOString(),
34878
34963
  event: "run:paused",
34879
34964
  runId,
@@ -34895,44 +34980,44 @@ var init_events_writer = __esm(() => {
34895
34980
  function wireHooks(bus, hooks, workdir, feature) {
34896
34981
  const logger = getSafeLogger();
34897
34982
  const safe = (name, fn) => {
34898
- fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) }));
34983
+ return fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) })).catch(() => {});
34899
34984
  };
34900
34985
  const unsubs = [];
34901
34986
  unsubs.push(bus.on("run:started", (ev) => {
34902
- safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
34987
+ return safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
34903
34988
  }));
34904
34989
  unsubs.push(bus.on("story:started", (ev) => {
34905
- safe("on-story-start", () => fireHook(hooks, "on-story-start", hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }), workdir));
34990
+ return safe("on-story-start", () => fireHook(hooks, "on-story-start", hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }), workdir));
34906
34991
  }));
34907
34992
  unsubs.push(bus.on("story:completed", (ev) => {
34908
- safe("on-story-complete", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }), workdir));
34993
+ return safe("on-story-complete", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }), workdir));
34909
34994
  }));
34910
34995
  unsubs.push(bus.on("story:decomposed", (ev) => {
34911
- safe("on-story-complete (decomposed)", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "decomposed", subStoryCount: ev.subStoryCount }), workdir));
34996
+ return safe("on-story-complete (decomposed)", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "decomposed", subStoryCount: ev.subStoryCount }), workdir));
34912
34997
  }));
34913
34998
  unsubs.push(bus.on("story:failed", (ev) => {
34914
- safe("on-story-fail", () => fireHook(hooks, "on-story-fail", hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }), workdir));
34999
+ return safe("on-story-fail", () => fireHook(hooks, "on-story-fail", hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }), workdir));
34915
35000
  }));
34916
35001
  unsubs.push(bus.on("story:paused", (ev) => {
34917
- safe("on-pause (story)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
35002
+ return safe("on-pause (story)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
34918
35003
  }));
34919
35004
  unsubs.push(bus.on("run:paused", (ev) => {
34920
- safe("on-pause (run)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
35005
+ return safe("on-pause (run)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
34921
35006
  }));
34922
35007
  unsubs.push(bus.on("run:completed", (ev) => {
34923
- safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
35008
+ return safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
34924
35009
  }));
34925
35010
  unsubs.push(bus.on("run:resumed", (ev) => {
34926
- safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
35011
+ return safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
34927
35012
  }));
34928
35013
  unsubs.push(bus.on("story:completed", (ev) => {
34929
- safe("on-session-end (completed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "passed" }), workdir));
35014
+ return safe("on-session-end (completed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "passed" }), workdir));
34930
35015
  }));
34931
35016
  unsubs.push(bus.on("story:failed", (ev) => {
34932
- safe("on-session-end (failed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "failed" }), workdir));
35017
+ return safe("on-session-end (failed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "failed" }), workdir));
34933
35018
  }));
34934
35019
  unsubs.push(bus.on("run:errored", (ev) => {
34935
- safe("on-error", () => fireHook(hooks, "on-error", hookCtx(feature, { reason: ev.reason }), workdir));
35020
+ return safe("on-error", () => fireHook(hooks, "on-error", hookCtx(feature, { reason: ev.reason }), workdir));
34936
35021
  }));
34937
35022
  return () => {
34938
35023
  for (const u of unsubs)
@@ -35007,7 +35092,7 @@ function wireRegistry(bus, feature, runId, workdir) {
35007
35092
  const runDir = join50(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
35008
35093
  const metaFile = join50(runDir, "meta.json");
35009
35094
  const unsub = bus.on("run:started", (_ev) => {
35010
- (async () => {
35095
+ return (async () => {
35011
35096
  try {
35012
35097
  await mkdir3(runDir, { recursive: true });
35013
35098
  const meta3 = {
@@ -35038,11 +35123,11 @@ var init_registry3 = __esm(() => {
35038
35123
  function wireReporters(bus, pluginRegistry, runId, startTime) {
35039
35124
  const logger = getSafeLogger();
35040
35125
  const safe = (name, fn) => {
35041
- fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) }));
35126
+ return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
35042
35127
  };
35043
35128
  const unsubs = [];
35044
35129
  unsubs.push(bus.on("run:started", (ev) => {
35045
- safe("onRunStart", async () => {
35130
+ return safe("onRunStart", async () => {
35046
35131
  const reporters = pluginRegistry.getReporters();
35047
35132
  for (const r of reporters) {
35048
35133
  if (r.onRunStart) {
@@ -35061,7 +35146,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
35061
35146
  });
35062
35147
  }));
35063
35148
  unsubs.push(bus.on("story:completed", (ev) => {
35064
- safe("onStoryComplete(completed)", async () => {
35149
+ return safe("onStoryComplete(completed)", async () => {
35065
35150
  const reporters = pluginRegistry.getReporters();
35066
35151
  for (const r of reporters) {
35067
35152
  if (r.onStoryComplete) {
@@ -35083,7 +35168,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
35083
35168
  });
35084
35169
  }));
35085
35170
  unsubs.push(bus.on("story:failed", (ev) => {
35086
- safe("onStoryComplete(failed)", async () => {
35171
+ return safe("onStoryComplete(failed)", async () => {
35087
35172
  const reporters = pluginRegistry.getReporters();
35088
35173
  for (const r of reporters) {
35089
35174
  if (r.onStoryComplete) {
@@ -35105,7 +35190,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
35105
35190
  });
35106
35191
  }));
35107
35192
  unsubs.push(bus.on("story:paused", (ev) => {
35108
- safe("onStoryComplete(paused)", async () => {
35193
+ return safe("onStoryComplete(paused)", async () => {
35109
35194
  const reporters = pluginRegistry.getReporters();
35110
35195
  for (const r of reporters) {
35111
35196
  if (r.onStoryComplete) {
@@ -35127,7 +35212,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
35127
35212
  });
35128
35213
  }));
35129
35214
  unsubs.push(bus.on("run:completed", (ev) => {
35130
- safe("onRunEnd", async () => {
35215
+ return safe("onRunEnd", async () => {
35131
35216
  const reporters = pluginRegistry.getReporters();
35132
35217
  for (const r of reporters) {
35133
35218
  if (r.onRunEnd) {
@@ -68924,7 +69009,14 @@ async function planCommand(workdir, config2, options) {
68924
69009
  if (entry)
68925
69010
  autoModel = resolveModel2(entry).model;
68926
69011
  } catch {}
68927
- rawResponse = await cliAdapter.complete(prompt, { model: autoModel, jsonMode: true, workdir, config: config2 });
69012
+ rawResponse = await cliAdapter.complete(prompt, {
69013
+ model: autoModel,
69014
+ jsonMode: true,
69015
+ workdir,
69016
+ config: config2,
69017
+ featureName: options.feature,
69018
+ sessionRole: "plan"
69019
+ });
68928
69020
  try {
68929
69021
  const envelope = JSON.parse(rawResponse);
68930
69022
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
@@ -71349,7 +71441,7 @@ Runs:
71349
71441
  const summary = await extractRunSummary(filePath);
71350
71442
  const timestamp = file2.replace(".jsonl", "");
71351
71443
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
71352
- const duration3 = summary ? formatDuration2(summary.durationMs) : "?";
71444
+ const duration3 = summary ? formatDuration(summary.durationMs) : "?";
71353
71445
  const cost = summary ? `$${summary.totalCost.toFixed(4)}` : "$?.????";
71354
71446
  const status = summary ? summary.failed === 0 ? source_default.green("\u2713") : source_default.red("\u2717") : "?";
71355
71447
  console.log(` ${timestamp} ${stories.padEnd(7)} ${duration3.padEnd(8)} ${cost.padEnd(8)} ${status}`);
@@ -71443,17 +71535,6 @@ function shouldDisplayEntry(entry, options) {
71443
71535
  }
71444
71536
  return true;
71445
71537
  }
71446
- function formatDuration2(ms) {
71447
- if (ms < 1000) {
71448
- return `${ms}ms`;
71449
- }
71450
- if (ms < 60000) {
71451
- return `${(ms / 1000).toFixed(1)}s`;
71452
- }
71453
- const minutes = Math.floor(ms / 60000);
71454
- const seconds = Math.floor(ms % 60000 / 1000);
71455
- return `${minutes}m${seconds}s`;
71456
- }
71457
71538
 
71458
71539
  // src/commands/logs.ts
71459
71540
  async function logsCommand(options) {
@@ -71555,7 +71636,7 @@ var DEFAULT_LIMIT = 20;
71555
71636
  var _runsCmdDeps = {
71556
71637
  getRunsDir
71557
71638
  };
71558
- function formatDuration3(ms) {
71639
+ function formatDuration2(ms) {
71559
71640
  if (ms <= 0)
71560
71641
  return "-";
71561
71642
  const minutes = Math.floor(ms / 60000);
@@ -71668,7 +71749,7 @@ async function runsCommand(options = {}) {
71668
71749
  pad3(row.feature, COL.feature),
71669
71750
  pad3(colored, COL.status + (colored.length - visibleLength(colored))),
71670
71751
  pad3(`${row.passed}/${row.total}`, COL.stories),
71671
- pad3(formatDuration3(row.durationMs), COL.duration),
71752
+ pad3(formatDuration2(row.durationMs), COL.duration),
71672
71753
  formatDate(row.registeredAt)
71673
71754
  ].join(" ");
71674
71755
  console.log(line);
@@ -71991,7 +72072,8 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
71991
72072
  startTime: options.startTime,
71992
72073
  batchPlan,
71993
72074
  agentGetFn: options.agentGetFn,
71994
- pidRegistry: options.pidRegistry
72075
+ pidRegistry: options.pidRegistry,
72076
+ interactionChain: options.interactionChain
71995
72077
  }, prd);
71996
72078
  prd = sequentialResult.prd;
71997
72079
  iterations = sequentialResult.iterations;
@@ -72117,7 +72199,8 @@ async function run(options) {
72117
72199
  parallel,
72118
72200
  runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
72119
72201
  agentGetFn,
72120
- pidRegistry
72202
+ pidRegistry,
72203
+ interactionChain
72121
72204
  }, prd, pluginRegistry);
72122
72205
  prd = executionResult.prd;
72123
72206
  iterations = executionResult.iterations;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.54.2",
3
+ "version": "0.54.3",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {