@nathapp/nax 0.64.2-canary.2 → 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 +155 -35
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -29568,7 +29568,43 @@ Fix in priority order. After fixing each priority, re-run the failing check(s) a
29568
29568
  return parts.join(`
29569
29569
  `);
29570
29570
  }
29571
- 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) {
29572
29608
  const scopeConstraint = story.workdir ? `
29573
29609
 
29574
29610
  IMPORTANT: Only modify test files within \`${story.workdir}/\`. Do NOT touch source files.` : `
@@ -29591,10 +29627,14 @@ IMPORTANT: Only modify test files. Do NOT touch source implementation files.`;
29591
29627
  `);
29592
29628
  const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
29593
29629
  `);
29594
- 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:
29595
- 1. Read the flagged test files to verify each finding is a real issue
29596
- 2. Only fix findings that are genuinely incorrect or missing \u2014 do NOT remove tests
29597
- 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.`;
29598
29638
  return `${opener}
29599
29639
 
29600
29640
  Story: ${story.title} (${story.id})
@@ -32448,7 +32488,9 @@ var init_autofix_test_writer = __esm(() => {
32448
32488
  session: { role: "test-writer", lifetime: "fresh" },
32449
32489
  config: autofixConfigSelector,
32450
32490
  build(input, _ctx) {
32451
- const prompt = RectifierPromptBuilder.testWriterRectification(input.failedChecks, input.story);
32491
+ const prompt = RectifierPromptBuilder.testWriterRectification(input.failedChecks, input.story, {
32492
+ mode: input.mode
32493
+ });
32452
32494
  return {
32453
32495
  role: { id: "role", content: "", overridable: false },
32454
32496
  task: { id: "task", content: prompt, overridable: false }
@@ -40082,9 +40124,66 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
40082
40124
  op: strategy.fixOp.name,
40083
40125
  targetFiles: extracted.targetFiles ?? [],
40084
40126
  summary: extracted.summary ?? "",
40127
+ ...extracted.unresolved ? { unresolved: extracted.unresolved } : {},
40085
40128
  costUsd: extracted.costUsd
40086
40129
  });
40087
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
+ }
40088
40187
  let findingsAfter;
40089
40188
  let validatorAttempt = 0;
40090
40189
  for (;; ) {
@@ -41647,7 +41746,13 @@ function collectCurrentFindings(ctx) {
41647
41746
  });
41648
41747
  }
41649
41748
  function collectTestTargetedChecks(ctx) {
41650
- 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);
41651
41756
  }
41652
41757
  function buildAutofixStrategies(ctx, maxAttempts) {
41653
41758
  const implementer = {
@@ -41661,31 +41766,41 @@ function buildAutofixStrategies(ctx, maxAttempts) {
41661
41766
  story: ctx.story
41662
41767
  }),
41663
41768
  extractApplied: (output) => ({
41664
- summary: output.unresolvedReason ?? ""
41769
+ summary: output.unresolvedReason ?? "",
41770
+ unresolved: output.unresolvedReason
41665
41771
  })
41666
41772
  };
41667
41773
  const testWriter = {
41668
41774
  name: "autofix-test-writer",
41669
- appliesTo: (f) => f.fixTarget === "test",
41775
+ appliesTo: (f) => f.fixTarget === "test" || (f.fixTarget ?? "source") === "source" && f.severity === "error" && f.source === "adversarial-review",
41670
41776
  fixOp: testWriterRectifyOp,
41671
41777
  maxAttempts: 1,
41672
41778
  coRun: "co-run-sequential",
41673
- buildInput: (_findings, _prior, _cycleCtx) => ({
41674
- failedChecks: collectTestTargetedChecks(ctx),
41675
- story: ctx.story
41676
- })
41677
- };
41678
- return [implementer, testWriter];
41679
- }
41680
- function findUnresolvedReason(result) {
41681
- for (const iter of result.iterations) {
41682
- for (const fa of iter.fixesApplied) {
41683
- if (fa.strategyName === "autofix-implementer" && fa.summary) {
41684
- 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" };
41685
41783
  }
41784
+ return { failedChecks: collectTestTargetedChecks(ctx), story: ctx.story };
41686
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);
41687
41796
  }
41688
- 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
+ `)}`;
41689
41804
  }
41690
41805
  async function writeShadowReport(ctx, result, initialFindingsCount) {
41691
41806
  const logger = getLogger();
@@ -41739,7 +41854,8 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
41739
41854
  const result = await runFixCycle(cycle, cycleCtx, "autofix-v2");
41740
41855
  ctx.autofixPriorIterations = result.iterations;
41741
41856
  await writeShadowReport(ctx, result, initialFindings.length);
41742
- 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;
41743
41859
  const succeeded = result.exitReason === "resolved" || result.finalFindings.length === 0;
41744
41860
  logger.info("autofix-cycle", "V2 fix cycle complete", {
41745
41861
  storyId,
@@ -41747,9 +41863,15 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
41747
41863
  iterations: result.iterations.length,
41748
41864
  finalFindingsCount: result.finalFindings.length,
41749
41865
  succeeded,
41750
- ...unresolvedReason ? { unresolvedReason } : {}
41866
+ ...unresolvedReason ? { unresolvedReason } : {},
41867
+ ...escalationDigest ? { escalationDigest } : {}
41751
41868
  });
41752
- return { succeeded, cost: 0, ...unresolvedReason ? { unresolvedReason } : {} };
41869
+ return {
41870
+ succeeded,
41871
+ cost: 0,
41872
+ ...unresolvedReason ? { unresolvedReason } : {},
41873
+ ...escalationDigest ? { escalationDigest } : {}
41874
+ };
41753
41875
  }
41754
41876
  var init_autofix_cycle = __esm(() => {
41755
41877
  init_findings();
@@ -43143,10 +43265,7 @@ async function runAdversarialReview(opts) {
43143
43265
  const durationMs2 = Date.now() - startTime;
43144
43266
  logger?.warn("review", `Adversarial review failed: ${blockingFindings.length} blocking findings`, {
43145
43267
  storyId: story.id,
43146
- durationMs: durationMs2
43147
- });
43148
- logger?.debug("review", "Adversarial review findings", {
43149
- storyId: story.id,
43268
+ durationMs: durationMs2,
43150
43269
  findings: blockingFindings.map((f) => ({
43151
43270
  severity: f.severity,
43152
43271
  category: f.category,
@@ -45117,9 +45236,10 @@ var init_autofix = __esm(() => {
45117
45236
  const {
45118
45237
  succeeded: agentFixed,
45119
45238
  cost: agentCost,
45120
- unresolvedReason
45239
+ unresolvedReason,
45240
+ escalationDigest
45121
45241
  } = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, ctx.workdir);
45122
- if (unresolvedReason) {
45242
+ if (!agentFixed && unresolvedReason) {
45123
45243
  if (ctx.mechanicalFailedOnly) {
45124
45244
  logger.warn("autofix", "Mechanical-only failure unfixable \u2014 proceeding (LLM review passed)", {
45125
45245
  storyId: ctx.story.id,
@@ -45166,7 +45286,7 @@ var init_autofix = __esm(() => {
45166
45286
  logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
45167
45287
  return {
45168
45288
  action: "escalate",
45169
- reason: "Autofix exhausted: review still failing after fix attempts",
45289
+ reason: escalationDigest ?? "Autofix exhausted: review still failing after fix attempts",
45170
45290
  cost: agentCost
45171
45291
  };
45172
45292
  }
@@ -50154,7 +50274,7 @@ var package_default;
50154
50274
  var init_package = __esm(() => {
50155
50275
  package_default = {
50156
50276
  name: "@nathapp/nax",
50157
- version: "0.64.2-canary.2",
50277
+ version: "0.64.2",
50158
50278
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
50159
50279
  type: "module",
50160
50280
  bin: {
@@ -50238,8 +50358,8 @@ var init_version = __esm(() => {
50238
50358
  NAX_VERSION = package_default.version;
50239
50359
  NAX_COMMIT = (() => {
50240
50360
  try {
50241
- if (/^[0-9a-f]{6,10}$/.test("6c7fd18c"))
50242
- return "6c7fd18c";
50361
+ if (/^[0-9a-f]{6,10}$/.test("7a4c7325"))
50362
+ return "7a4c7325";
50243
50363
  } catch {}
50244
50364
  try {
50245
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.2",
3
+ "version": "0.64.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {