@nathapp/nax 0.64.2-canary.1 → 0.64.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +181 -48
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -5304,7 +5304,11 @@ class AgentManager {
5304
5304
  const sessionRole = handle.role ?? opts.sessionRole ?? "main";
5305
5305
  const start = Date.now();
5306
5306
  try {
5307
- const result = await this._sendPrompt(handle, prompt, opts);
5307
+ const rawResult = await this._sendPrompt(handle, prompt, opts);
5308
+ const result = {
5309
+ ...rawResult,
5310
+ protocolIds: rawResult.protocolIds ?? handle.protocolIds
5311
+ };
5308
5312
  const event = {
5309
5313
  kind: "session-turn",
5310
5314
  sessionName: handle.id,
@@ -29564,7 +29568,43 @@ Fix in priority order. After fixing each priority, re-run the failing check(s) a
29564
29568
  return parts.join(`
29565
29569
  `);
29566
29570
  }
29567
- static testWriterRectification(testFileFindings, story) {
29571
+ static testWriterRectification(findings, story, options) {
29572
+ if (options?.mode === "write-failing-test") {
29573
+ return RectifierPromptBuilder._testWriterWriteFailingTest(findings, story);
29574
+ }
29575
+ return RectifierPromptBuilder._testWriterFixTestFiles(findings, story);
29576
+ }
29577
+ static _testWriterWriteFailingTest(findings, story) {
29578
+ const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
29579
+ `);
29580
+ const findingLines = findings.flatMap((c) => (c.findings ?? []).map((f) => `- [${f.severity}] ${f.file ?? "unknown"}:${f.line ?? "?"} \u2014 ${f.message}`)).join(`
29581
+ `);
29582
+ const scopeConstraint = story.workdir ? `
29583
+
29584
+ IMPORTANT: Only create or modify test files within \`${story.workdir}/\`. Do NOT touch source files.` : `
29585
+
29586
+ IMPORTANT: Only create or modify test files. Do NOT touch source implementation files.`;
29587
+ return `You are writing a failing test that documents spec-correct behavior.
29588
+
29589
+ Story: ${story.title} (${story.id})
29590
+
29591
+ ### Acceptance Criteria
29592
+ ${acList}
29593
+
29594
+ ### Source Bugs Found by Adversarial Review
29595
+ ${findingLines}
29596
+
29597
+ **Task:** For each bug above, write a NEW failing test that asserts the spec-correct behavior described in the finding. The test should FAIL with the current (buggy) implementation and PASS once the implementer fixes the source.
29598
+
29599
+ Rules:
29600
+ 1. Write the test against the SPECIFICATION, not the current behavior.
29601
+ 2. Do NOT fix the source files \u2014 only write test files.
29602
+ 3. Do NOT modify existing passing tests.
29603
+ 4. The test must fail with the current code.
29604
+
29605
+ Commit your new tests when done.${scopeConstraint}`;
29606
+ }
29607
+ static _testWriterFixTestFiles(testFileFindings, story) {
29568
29608
  const scopeConstraint = story.workdir ? `
29569
29609
 
29570
29610
  IMPORTANT: Only modify test files within \`${story.workdir}/\`. Do NOT touch source files.` : `
@@ -29587,10 +29627,14 @@ IMPORTANT: Only modify test files. Do NOT touch source implementation files.`;
29587
29627
  `);
29588
29628
  const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
29589
29629
  `);
29590
- const importantNote = isLintOnly ? "**Important:** Fix the lint errors in the test files listed above. Do NOT modify source implementation files." : `**Important:** These findings are in test files. Before making any changes:
29591
- 1. Read the flagged test files to verify each finding is a real issue
29592
- 2. Only fix findings that are genuinely incorrect or missing \u2014 do NOT remove tests
29593
- 3. Do NOT modify source implementation files`;
29630
+ const importantNote = isLintOnly ? "**Important:** Fix the lint errors in the test files listed above. Do NOT modify source implementation files." : `**Important:** You are encoding the SPECIFICATION, not the current behavior.
29631
+
29632
+ Before making any changes:
29633
+ 1. Read the flagged test files to verify each finding is a real issue.
29634
+ 2. Do NOT loosen assertions to match current implementation behavior. If a test is failing because the source is wrong, the source is the suspect \u2014 not the test.
29635
+ 3. Do NOT delete a failing test because the implementation makes it hard to assert on. Refactor the test structure if needed; never silently drop coverage.
29636
+ 4. If the current behavior disagrees with the acceptance criteria, write the test against the spec and let the implementer fix the source.
29637
+ 5. Do NOT modify source implementation files.`;
29594
29638
  return `${opener}
29595
29639
 
29596
29640
  Story: ${story.title} (${story.id})
@@ -30355,7 +30399,9 @@ function turnResultToAgentResult(r) {
30355
30399
  durationMs: 0,
30356
30400
  estimatedCostUsd: r.estimatedCostUsd ?? 0,
30357
30401
  exactCostUsd: r.exactCostUsd,
30358
- tokenUsage: r.tokenUsage
30402
+ tokenUsage: r.tokenUsage,
30403
+ protocolIds: r.protocolIds,
30404
+ internalRoundTrips: r.internalRoundTrips
30359
30405
  };
30360
30406
  }
30361
30407
  function buildHopCallback(ctx, sessionId, _initialOptions) {
@@ -32442,7 +32488,9 @@ var init_autofix_test_writer = __esm(() => {
32442
32488
  session: { role: "test-writer", lifetime: "fresh" },
32443
32489
  config: autofixConfigSelector,
32444
32490
  build(input, _ctx) {
32445
- const prompt = RectifierPromptBuilder.testWriterRectification(input.failedChecks, input.story);
32491
+ const prompt = RectifierPromptBuilder.testWriterRectification(input.failedChecks, input.story, {
32492
+ mode: input.mode
32493
+ });
32446
32494
  return {
32447
32495
  role: { id: "role", content: "", overridable: false },
32448
32496
  task: { id: "task", content: prompt, overridable: false }
@@ -36840,9 +36888,9 @@ async function runTrackedSession(state, id, runner, request) {
36840
36888
  ...request,
36841
36889
  runOptions: {
36842
36890
  ...request.runOptions,
36843
- onSessionEstablished: (protocolIds, sessionName2) => {
36891
+ onSessionEstablished: (protocolIds2, sessionName2) => {
36844
36892
  try {
36845
- state.bindHandle(id, sessionName2, protocolIds);
36893
+ state.bindHandle(id, sessionName2, protocolIds2);
36846
36894
  } catch (err) {
36847
36895
  getLogger().warn("session", "bindHandle via onSessionEstablished failed", {
36848
36896
  storyId: state.sessions.get(id)?.storyId,
@@ -36850,7 +36898,7 @@ async function runTrackedSession(state, id, runner, request) {
36850
36898
  error: err instanceof Error ? err.message : String(err)
36851
36899
  });
36852
36900
  }
36853
- callerCallback?.(protocolIds, sessionName2);
36901
+ callerCallback?.(protocolIds2, sessionName2);
36854
36902
  }
36855
36903
  }
36856
36904
  };
@@ -36925,6 +36973,8 @@ async function runTrackedSession(state, id, runner, request) {
36925
36973
  if (current?.state === "RUNNING") {
36926
36974
  state.transition(id, result.success ? "COMPLETED" : "FAILED");
36927
36975
  }
36976
+ const protocolIds = result.protocolIds ?? current?.protocolIds;
36977
+ const turn = Math.max(result.internalRoundTrips ?? 1, 1);
36928
36978
  const sessionName = state.nameFor({
36929
36979
  workdir: pre.workdir,
36930
36980
  featureName: pre.featureName,
@@ -36943,10 +36993,10 @@ async function runTrackedSession(state, id, runner, request) {
36943
36993
  featureName: pre.featureName,
36944
36994
  workdir: pre.workdir,
36945
36995
  resolvedPermissions,
36946
- turn: result.internalRoundTrips ?? 0,
36996
+ turn,
36947
36997
  protocolIds: {
36948
- sessionId: result.protocolIds?.sessionId ?? null,
36949
- recordId: result.protocolIds?.recordId ?? null
36998
+ sessionId: protocolIds?.sessionId ?? null,
36999
+ recordId: protocolIds?.recordId ?? null
36950
37000
  },
36951
37001
  origin: "runTrackedSession",
36952
37002
  tokenUsage: result.tokenUsage,
@@ -37336,11 +37386,12 @@ class SessionManager {
37336
37386
  }
37337
37387
  this._busySessions.add(handle.id);
37338
37388
  try {
37339
- return await adapter.sendTurn(handle, prompt, {
37389
+ const result = await adapter.sendTurn(handle, prompt, {
37340
37390
  interactionHandler: opts?.interactionHandler ?? NO_OP_INTERACTION_HANDLER,
37341
37391
  signal: opts?.signal,
37342
37392
  maxTurns: opts?.maxTurns
37343
37393
  });
37394
+ return { ...result, protocolIds: result.protocolIds ?? handle.protocolIds };
37344
37395
  } catch (err) {
37345
37396
  if (opts?.signal?.aborted || err instanceof Error && err.name === "AbortError") {
37346
37397
  this._cancelledSessions.add(handle.id);
@@ -37648,7 +37699,9 @@ function createSessionRunHop(sessionManager) {
37648
37699
  durationMs: Date.now() - startMs,
37649
37700
  estimatedCostUsd: turnResult.estimatedCostUsd ?? 0,
37650
37701
  exactCostUsd: turnResult.exactCostUsd,
37651
- tokenUsage: turnResult.tokenUsage
37702
+ tokenUsage: turnResult.tokenUsage,
37703
+ protocolIds: handle.protocolIds,
37704
+ internalRoundTrips: turnResult.internalRoundTrips
37652
37705
  }
37653
37706
  };
37654
37707
  } catch (err) {
@@ -40071,9 +40124,66 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
40071
40124
  op: strategy.fixOp.name,
40072
40125
  targetFiles: extracted.targetFiles ?? [],
40073
40126
  summary: extracted.summary ?? "",
40127
+ ...extracted.unresolved ? { unresolved: extracted.unresolved } : {},
40074
40128
  costUsd: extracted.costUsd
40075
40129
  });
40076
40130
  }
40131
+ const unresolvedFa = fixesApplied.find((fa) => fa.unresolved);
40132
+ if (unresolvedFa) {
40133
+ const finishedAt2 = now();
40134
+ cycle.iterations.push({
40135
+ iterationNum: cycle.iterations.length + 1,
40136
+ findingsBefore,
40137
+ fixesApplied,
40138
+ findingsAfter: cycle.findings,
40139
+ outcome: "unchanged",
40140
+ startedAt,
40141
+ finishedAt: finishedAt2
40142
+ });
40143
+ logger?.info("findings.cycle", "cycle exited \u2014 agent gave up", {
40144
+ storyId,
40145
+ packageDir,
40146
+ cycleName,
40147
+ reason: "agent-gave-up",
40148
+ strategyName: unresolvedFa.strategyName,
40149
+ unresolvedDetail: unresolvedFa.unresolved
40150
+ });
40151
+ return {
40152
+ iterations: cycle.iterations,
40153
+ finalFindings: cycle.findings,
40154
+ exitReason: "agent-gave-up",
40155
+ unresolvedDetail: unresolvedFa.unresolved,
40156
+ costUsd: totalCostUsd
40157
+ };
40158
+ }
40159
+ const provisionalIterations = [...cycle.iterations, { fixesApplied }];
40160
+ const allExhausted = group.every((s) => countStrategyAttempts(provisionalIterations, s.name) >= s.maxAttempts);
40161
+ if (allExhausted) {
40162
+ const finishedAt2 = now();
40163
+ cycle.iterations.push({
40164
+ iterationNum: cycle.iterations.length + 1,
40165
+ findingsBefore,
40166
+ fixesApplied,
40167
+ findingsAfter: cycle.findings,
40168
+ outcome: "unchanged",
40169
+ startedAt,
40170
+ finishedAt: finishedAt2
40171
+ });
40172
+ logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (skipped final validate)", {
40173
+ storyId,
40174
+ packageDir,
40175
+ cycleName,
40176
+ reason: "max-attempts-per-strategy",
40177
+ exhaustedStrategy: group[0]?.name
40178
+ });
40179
+ return {
40180
+ iterations: cycle.iterations,
40181
+ finalFindings: cycle.findings,
40182
+ exitReason: "max-attempts-per-strategy",
40183
+ exhaustedStrategy: group[0]?.name,
40184
+ costUsd: totalCostUsd
40185
+ };
40186
+ }
40077
40187
  let findingsAfter;
40078
40188
  let validatorAttempt = 0;
40079
40189
  for (;; ) {
@@ -41636,7 +41746,13 @@ function collectCurrentFindings(ctx) {
41636
41746
  });
41637
41747
  }
41638
41748
  function collectTestTargetedChecks(ctx) {
41639
- return collectFailedChecks(ctx).filter((c) => c.findings?.some((f) => f.fixTarget === "test"));
41749
+ return collectFailedChecks(ctx).map((c) => ({ ...c, findings: (c.findings ?? []).filter((f) => f.fixTarget === "test") })).filter((c) => c.findings.length > 0);
41750
+ }
41751
+ function collectAdversarialSourceChecks(ctx) {
41752
+ return collectFailedChecks(ctx).map((c) => ({
41753
+ ...c,
41754
+ findings: (c.findings ?? []).filter((f) => (f.fixTarget ?? "source") === "source" && f.severity === "error" && f.source === "adversarial-review")
41755
+ })).filter((c) => c.check === "adversarial" && c.findings.length > 0);
41640
41756
  }
41641
41757
  function buildAutofixStrategies(ctx, maxAttempts) {
41642
41758
  const implementer = {
@@ -41650,31 +41766,41 @@ function buildAutofixStrategies(ctx, maxAttempts) {
41650
41766
  story: ctx.story
41651
41767
  }),
41652
41768
  extractApplied: (output) => ({
41653
- summary: output.unresolvedReason ?? ""
41769
+ summary: output.unresolvedReason ?? "",
41770
+ unresolved: output.unresolvedReason
41654
41771
  })
41655
41772
  };
41656
41773
  const testWriter = {
41657
41774
  name: "autofix-test-writer",
41658
- appliesTo: (f) => f.fixTarget === "test",
41775
+ appliesTo: (f) => f.fixTarget === "test" || (f.fixTarget ?? "source") === "source" && f.severity === "error" && f.source === "adversarial-review",
41659
41776
  fixOp: testWriterRectifyOp,
41660
41777
  maxAttempts: 1,
41661
41778
  coRun: "co-run-sequential",
41662
- buildInput: (_findings, _prior, _cycleCtx) => ({
41663
- failedChecks: collectTestTargetedChecks(ctx),
41664
- story: ctx.story
41665
- })
41666
- };
41667
- return [implementer, testWriter];
41668
- }
41669
- function findUnresolvedReason(result) {
41670
- for (const iter of result.iterations) {
41671
- for (const fa of iter.fixesApplied) {
41672
- if (fa.strategyName === "autofix-implementer" && fa.summary) {
41673
- return fa.summary;
41779
+ buildInput: (findings, _prior, _cycleCtx) => {
41780
+ const hasSourceBug = findings.some((f) => (f.fixTarget ?? "source") === "source" && f.source === "adversarial-review");
41781
+ if (hasSourceBug) {
41782
+ return { failedChecks: collectAdversarialSourceChecks(ctx), story: ctx.story, mode: "write-failing-test" };
41674
41783
  }
41784
+ return { failedChecks: collectTestTargetedChecks(ctx), story: ctx.story };
41675
41785
  }
41786
+ };
41787
+ return [testWriter, implementer];
41788
+ }
41789
+ function buildEscalationDigest(findings) {
41790
+ const byFile = new Map;
41791
+ for (const f of findings) {
41792
+ const file3 = f.file ?? "unknown";
41793
+ const list = byFile.get(file3) ?? [];
41794
+ list.push(f);
41795
+ byFile.set(file3, list);
41676
41796
  }
41677
- return;
41797
+ const lines = [...byFile.entries()].map(([file3, fs]) => {
41798
+ const categories = fs.map((f) => f.category ?? f.source).join(", ");
41799
+ return ` - ${categories} in ${file3}`;
41800
+ });
41801
+ return `Autofix exhausted: ${findings.length} finding${findings.length !== 1 ? "s" : ""} remain
41802
+ ${lines.join(`
41803
+ `)}`;
41678
41804
  }
41679
41805
  async function writeShadowReport(ctx, result, initialFindingsCount) {
41680
41806
  const logger = getLogger();
@@ -41728,7 +41854,8 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
41728
41854
  const result = await runFixCycle(cycle, cycleCtx, "autofix-v2");
41729
41855
  ctx.autofixPriorIterations = result.iterations;
41730
41856
  await writeShadowReport(ctx, result, initialFindings.length);
41731
- const unresolvedReason = findUnresolvedReason(result);
41857
+ const unresolvedReason = result.exitReason === "agent-gave-up" ? result.unresolvedDetail : undefined;
41858
+ const escalationDigest = result.exitReason === "max-attempts-per-strategy" && result.finalFindings.length > 0 ? buildEscalationDigest(result.finalFindings) : undefined;
41732
41859
  const succeeded = result.exitReason === "resolved" || result.finalFindings.length === 0;
41733
41860
  logger.info("autofix-cycle", "V2 fix cycle complete", {
41734
41861
  storyId,
@@ -41736,9 +41863,15 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
41736
41863
  iterations: result.iterations.length,
41737
41864
  finalFindingsCount: result.finalFindings.length,
41738
41865
  succeeded,
41739
- ...unresolvedReason ? { unresolvedReason } : {}
41866
+ ...unresolvedReason ? { unresolvedReason } : {},
41867
+ ...escalationDigest ? { escalationDigest } : {}
41740
41868
  });
41741
- return { succeeded, cost: 0, ...unresolvedReason ? { unresolvedReason } : {} };
41869
+ return {
41870
+ succeeded,
41871
+ cost: 0,
41872
+ ...unresolvedReason ? { unresolvedReason } : {},
41873
+ ...escalationDigest ? { escalationDigest } : {}
41874
+ };
41742
41875
  }
41743
41876
  var init_autofix_cycle = __esm(() => {
41744
41877
  init_findings();
@@ -43132,10 +43265,7 @@ async function runAdversarialReview(opts) {
43132
43265
  const durationMs2 = Date.now() - startTime;
43133
43266
  logger?.warn("review", `Adversarial review failed: ${blockingFindings.length} blocking findings`, {
43134
43267
  storyId: story.id,
43135
- durationMs: durationMs2
43136
- });
43137
- logger?.debug("review", "Adversarial review findings", {
43138
- storyId: story.id,
43268
+ durationMs: durationMs2,
43139
43269
  findings: blockingFindings.map((f) => ({
43140
43270
  severity: f.severity,
43141
43271
  category: f.category,
@@ -45106,9 +45236,10 @@ var init_autofix = __esm(() => {
45106
45236
  const {
45107
45237
  succeeded: agentFixed,
45108
45238
  cost: agentCost,
45109
- unresolvedReason
45239
+ unresolvedReason,
45240
+ escalationDigest
45110
45241
  } = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, ctx.workdir);
45111
- if (unresolvedReason) {
45242
+ if (!agentFixed && unresolvedReason) {
45112
45243
  if (ctx.mechanicalFailedOnly) {
45113
45244
  logger.warn("autofix", "Mechanical-only failure unfixable \u2014 proceeding (LLM review passed)", {
45114
45245
  storyId: ctx.story.id,
@@ -45155,7 +45286,7 @@ var init_autofix = __esm(() => {
45155
45286
  logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
45156
45287
  return {
45157
45288
  action: "escalate",
45158
- reason: "Autofix exhausted: review still failing after fix attempts",
45289
+ reason: escalationDigest ?? "Autofix exhausted: review still failing after fix attempts",
45159
45290
  cost: agentCost
45160
45291
  };
45161
45292
  }
@@ -46763,7 +46894,8 @@ async function runThreeSessionTdd(options) {
46763
46894
  lite = false,
46764
46895
  _recursionDepth = 0,
46765
46896
  projectDir,
46766
- agentManager
46897
+ agentManager,
46898
+ runtime
46767
46899
  } = options;
46768
46900
  const logger = getLogger();
46769
46901
  const MAX_RECURSION_DEPTH = 2;
@@ -46906,7 +47038,7 @@ async function runThreeSessionTdd(options) {
46906
47038
  };
46907
47039
  }
46908
47040
  const implementerBinding = getTddSessionBinding?.("implementer");
46909
- const { cost: fullSuiteGateCost, fullSuiteGatePassed } = await runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, implementerBinding?.sessionManager, implementerBinding?.sessionId);
47041
+ const { cost: fullSuiteGateCost, fullSuiteGatePassed } = await runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, implementerBinding?.sessionManager, implementerBinding?.sessionId, runtime);
46910
47042
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
46911
47043
  const verifierBundle = await getTddContextBundle?.("verifier") ?? tddContextBundles?.verifier;
46912
47044
  const session3 = await runTddSessionOp(verifyTddOp, options, session3Ref, verifierBundle, getTddSessionBinding?.("verifier"));
@@ -47162,7 +47294,8 @@ async function runThreeSessionTddFromCtx(ctx, opts) {
47162
47294
  interactionChain: ctx.interaction,
47163
47295
  projectDir: ctx.projectDir,
47164
47296
  abortSignal: ctx.abortSignal,
47165
- agentManager: ctx.agentManager
47297
+ agentManager: ctx.agentManager,
47298
+ runtime: ctx.runtime
47166
47299
  });
47167
47300
  }
47168
47301
  var init_orchestrator_ctx = __esm(() => {
@@ -50141,7 +50274,7 @@ var package_default;
50141
50274
  var init_package = __esm(() => {
50142
50275
  package_default = {
50143
50276
  name: "@nathapp/nax",
50144
- version: "0.64.2-canary.1",
50277
+ version: "0.64.2",
50145
50278
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
50146
50279
  type: "module",
50147
50280
  bin: {
@@ -50225,8 +50358,8 @@ var init_version = __esm(() => {
50225
50358
  NAX_VERSION = package_default.version;
50226
50359
  NAX_COMMIT = (() => {
50227
50360
  try {
50228
- if (/^[0-9a-f]{6,10}$/.test("b8feb3bf"))
50229
- return "b8feb3bf";
50361
+ if (/^[0-9a-f]{6,10}$/.test("7a4c7325"))
50362
+ return "7a4c7325";
50230
50363
  } catch {}
50231
50364
  try {
50232
50365
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.64.2-canary.1",
3
+ "version": "0.64.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {