@nathapp/nax 0.67.7 → 0.67.9

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 +236 -46
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -21312,7 +21312,7 @@ class AgentManager {
21312
21312
  return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
21313
21313
  }
21314
21314
  const isFailStale = result.adapterFailure?.outcome === "fail-stale";
21315
- const maxStaleRetries = this._config.agent?.idleWatchdog?.maxRetryAttempts ?? 1;
21315
+ const maxStaleRetries = this._config.agent?.idleWatchdog?.maxRetryAttempts ?? 3;
21316
21316
  if (isFailStale && result.adapterFailure?.retriable && staleRetryAttempts < maxStaleRetries) {
21317
21317
  staleRetryAttempts++;
21318
21318
  const retryHop = {
@@ -21330,7 +21330,8 @@ class AgentManager {
21330
21330
  logger?.info("agent-manager", "fail-stale: immediate same-agent retry", {
21331
21331
  storyId: request.runOptions.storyId,
21332
21332
  attempt: staleRetryAttempts,
21333
- agent: currentAgent
21333
+ agent: currentAgent,
21334
+ reason: result.adapterFailure?.reason
21334
21335
  });
21335
21336
  currentHopKind = { kind: "stale-retry", attempt: staleRetryAttempts };
21336
21337
  continue;
@@ -21461,6 +21462,8 @@ class AgentManager {
21461
21462
  const primaryAgent = primaryAgentOverride ?? this.getDefault();
21462
21463
  let currentAgent = primaryAgent;
21463
21464
  let hopsSoFar = 0;
21465
+ let staleRetryAttempts = 0;
21466
+ const maxStaleRetries = this._config.agent?.idleWatchdog?.maxRetryAttempts ?? 3;
21464
21467
  const _opStartMs = Date.now();
21465
21468
  const _agentChain = [primaryAgent];
21466
21469
  let _finalStatus = "error";
@@ -21497,10 +21500,45 @@ class AgentManager {
21497
21500
  };
21498
21501
  }
21499
21502
  _totalCostUsd += result.estimatedCostUsd;
21503
+ if (!result.adapterFailure && !result.output?.trim()) {
21504
+ result = {
21505
+ ...result,
21506
+ adapterFailure: {
21507
+ outcome: "fail-stale",
21508
+ category: "availability",
21509
+ retriable: true,
21510
+ message: "[completeWithFallback] agent returned no output",
21511
+ reason: "empty-output"
21512
+ }
21513
+ };
21514
+ }
21500
21515
  if (!result.adapterFailure) {
21501
21516
  _finalStatus = "ok";
21502
21517
  return { result, fallbacks };
21503
21518
  }
21519
+ const isFailStale = result.adapterFailure.outcome === "fail-stale";
21520
+ if (isFailStale && result.adapterFailure.retriable && staleRetryAttempts < maxStaleRetries) {
21521
+ staleRetryAttempts++;
21522
+ const retryHop = {
21523
+ storyId: options.storyId,
21524
+ priorAgent: currentAgent,
21525
+ newAgent: currentAgent,
21526
+ hop: staleRetryAttempts,
21527
+ outcome: result.adapterFailure.outcome,
21528
+ category: result.adapterFailure.category,
21529
+ timestamp: new Date().toISOString(),
21530
+ costUsd: result.estimatedCostUsd
21531
+ };
21532
+ fallbacks.push(retryHop);
21533
+ this._emitter.emit("onSwapAttempt", retryHop);
21534
+ logger?.info("agent-manager", "completeWithFallback: fail-stale same-agent retry", {
21535
+ storyId: options.storyId,
21536
+ attempt: staleRetryAttempts,
21537
+ agent: currentAgent,
21538
+ reason: result.adapterFailure.reason
21539
+ });
21540
+ continue;
21541
+ }
21504
21542
  if (!this.shouldSwap(result.adapterFailure, hopsSoFar, true)) {
21505
21543
  _finalStatus = hopsSoFar > 0 ? "exhausted" : "error";
21506
21544
  return { result, fallbacks };
@@ -30343,18 +30381,19 @@ Schema: {"passed": boolean, "findings": [{"severity": string, "category": string
30343
30381
  const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
30344
30382
  return `Your previous verifiedBy.observed value did not match the referenced file on disk.
30345
30383
 
30384
+ You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
30385
+
30346
30386
  Return ONLY this JSON object:
30347
30387
  {"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
30348
30388
 
30349
30389
  Finding issue: ${opts.finding.issue}
30350
30390
  Referenced file: ${file3}
30351
30391
  Referenced line: ${line}
30352
- Previous observed: ${opts.previousObserved}
30353
30392
 
30354
30393
  Rules:
30355
- - Copy observed verbatim from the file. Do not paraphrase.
30356
- - observed must be a 1-3 line excerpt that proves the claim.
30357
- - If you cannot quote proof exactly, set observed to "".
30394
+ - Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
30395
+ - observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
30396
+ - If after reading the file you cannot find anything that proves the claim, set observed to "".
30358
30397
  - Do not return a full review. Do not include markdown fences or explanation.`;
30359
30398
  }
30360
30399
  }
@@ -31813,7 +31852,20 @@ async function checkFindingEvidence(opts) {
31813
31852
  const contents = await readSafeFile(opts.workdir, file3);
31814
31853
  if (contents === null)
31815
31854
  return { status: "unreadable", file: file3, line, observed };
31816
- return normalizedIncludes(contents, observed) ? { status: "matched", file: file3, line, observed } : { status: "unmatched", file: file3, line, observed };
31855
+ return matchesEvidence(contents, observed, line) ? { status: "matched", file: file3, line, observed } : { status: "unmatched", file: file3, line, observed };
31856
+ }
31857
+ function matchesEvidence(contents, observed, line) {
31858
+ if (!line || line <= 0) {
31859
+ return normalizedIncludes(contents, observed);
31860
+ }
31861
+ const lines = contents.split(`
31862
+ `);
31863
+ const cited = Math.min(Math.max(0, line - 1), lines.length - 1);
31864
+ const start = Math.max(0, cited - EVIDENCE_LINE_WINDOW);
31865
+ const end = Math.min(lines.length, cited + EVIDENCE_LINE_WINDOW + 1);
31866
+ const windowText = lines.slice(start, end).join(`
31867
+ `);
31868
+ return normalizedIncludes(windowText, observed);
31817
31869
  }
31818
31870
  function downgradeUnsubstantiatedFinding(opts) {
31819
31871
  _evidenceDeps.getLogger()?.warn("review", "Downgraded unsubstantiated review finding", {
@@ -31861,7 +31913,7 @@ function stripWrappingQuotes(text) {
31861
31913
  function isMatchingWrapper(first, last) {
31862
31914
  return first === "`" && last === "`" || first === `"` && last === `"` || first === "'" && last === "'";
31863
31915
  }
31864
- var OBSERVED_PREVIEW_CHARS = 160, ISSUE_PREVIEW_CHARS = 200, SEMANTIC_FINDING_DOWNGRADED_EVENT = "review.semantic.finding.downgraded", ADVERSARIAL_FINDING_DOWNGRADED_EVENT = "review.adversarial.finding.downgraded", _evidenceDeps;
31916
+ var OBSERVED_PREVIEW_CHARS = 160, ISSUE_PREVIEW_CHARS = 200, EVIDENCE_LINE_WINDOW = 10, SEMANTIC_FINDING_DOWNGRADED_EVENT = "review.semantic.finding.downgraded", ADVERSARIAL_FINDING_DOWNGRADED_EVENT = "review.adversarial.finding.downgraded", _evidenceDeps;
31865
31917
  var init_semantic_evidence = __esm(() => {
31866
31918
  init_logger2();
31867
31919
  init_path_security2();
@@ -34951,6 +35003,53 @@ var init_acceptance_fix = __esm(() => {
34951
35003
  };
34952
35004
  });
34953
35005
 
35006
+ // src/review/requote-response.ts
35007
+ function parseRequoteResponse(output) {
35008
+ const parsed = tryParseLLMJson(output);
35009
+ if (!isRecord(parsed))
35010
+ return null;
35011
+ const canonical = extractCanonical(parsed);
35012
+ if (canonical)
35013
+ return canonical;
35014
+ const findings = parsed.findings;
35015
+ if (!Array.isArray(findings) || findings.length !== 1)
35016
+ return null;
35017
+ const finding = findings[0];
35018
+ if (!isRecord(finding))
35019
+ return null;
35020
+ return extractCanonical(finding.verifiedBy) ?? extractCanonical(finding);
35021
+ }
35022
+ function extractCanonical(value) {
35023
+ if (!isRecord(value))
35024
+ return null;
35025
+ if (typeof value.file !== "string" || typeof value.observed !== "string")
35026
+ return null;
35027
+ const file3 = value.file.trim();
35028
+ if (!file3)
35029
+ return null;
35030
+ const line = coerceLine(value.line);
35031
+ if (line === null)
35032
+ return null;
35033
+ return {
35034
+ file: file3,
35035
+ line: line === undefined ? undefined : line,
35036
+ observed: value.observed
35037
+ };
35038
+ }
35039
+ function coerceLine(value) {
35040
+ if (value == null)
35041
+ return;
35042
+ if (typeof value === "number")
35043
+ return value;
35044
+ if (typeof value === "string" && /^\d+$/.test(value))
35045
+ return Number.parseInt(value, 10);
35046
+ return null;
35047
+ }
35048
+ function isRecord(value) {
35049
+ return typeof value === "object" && value !== null && !Array.isArray(value);
35050
+ }
35051
+ var init_requote_response = () => {};
35052
+
34954
35053
  // src/operations/semantic-review.ts
34955
35054
  async function requoteBlockingFindings(findings, ctx) {
34956
35055
  const threshold = ctx.input.blockingThreshold ?? "error";
@@ -34972,7 +35071,7 @@ async function requoteBlockingFindings(findings, ctx) {
34972
35071
  if (used >= maxRequotes)
34973
35072
  break;
34974
35073
  used += 1;
34975
- const retry = await ctx.send(ReviewPromptBuilder.requoteVerbatim({ finding, previousObserved: initialEvidence.observed ?? "" }));
35074
+ const retry = await ctx.send(ReviewPromptBuilder.requoteVerbatim({ finding }));
34976
35075
  extraCostUsd += retry.estimatedCostUsd ?? 0;
34977
35076
  const requote = parseRequoteResponse(retry.output);
34978
35077
  if (!requote) {
@@ -35021,18 +35120,6 @@ async function requoteBlockingFindings(findings, ctx) {
35021
35120
  }
35022
35121
  return { findings: next, changed, extraCostUsd };
35023
35122
  }
35024
- function parseRequoteResponse(output) {
35025
- const parsed = tryParseLLMJson(output);
35026
- if (!parsed || typeof parsed.file !== "string" || typeof parsed.observed !== "string")
35027
- return null;
35028
- if (parsed.line != null && typeof parsed.line !== "number")
35029
- return null;
35030
- return {
35031
- file: parsed.file,
35032
- line: typeof parsed.line === "number" ? parsed.line : undefined,
35033
- observed: parsed.observed
35034
- };
35035
- }
35036
35123
  var FAIL_OPEN2, SEMANTIC_REQUOTE_RECOVERED_EVENT = "review.semantic.finding.requote_recovered", SEMANTIC_REQUOTE_FAILED_EVENT = "review.semantic.finding.requote_failed", DEFAULT_MAX_REQUOTES = 5, semanticReviewHopBody = async (initialPrompt, ctx) => {
35037
35124
  const turn = await ctx.sendWithParseRetry(initialPrompt);
35038
35125
  const parsed = validateLLMShape(tryParseLLMJson(turn.output));
@@ -35041,9 +35128,10 @@ var FAIL_OPEN2, SEMANTIC_REQUOTE_RECOVERED_EVENT = "review.semantic.finding.requ
35041
35128
  const requoted = await requoteBlockingFindings(parsed.findings, ctx);
35042
35129
  if (!requoted.changed)
35043
35130
  return turn;
35131
+ const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
35044
35132
  return {
35045
35133
  ...turn,
35046
- output: JSON.stringify({ passed: parsed.passed, findings: requoted.findings }),
35134
+ output: JSON.stringify({ passed, findings: requoted.findings }),
35047
35135
  estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
35048
35136
  };
35049
35137
  }, semanticReviewOp;
@@ -35052,6 +35140,7 @@ var init_semantic_review = __esm(() => {
35052
35140
  init_config();
35053
35141
  init_logger2();
35054
35142
  init_prompts();
35143
+ init_requote_response();
35055
35144
  init_semantic_evidence();
35056
35145
  init_semantic_helpers();
35057
35146
  FAIL_OPEN2 = { passed: true, findings: [], failOpen: true };
@@ -37259,14 +37348,45 @@ var init_full_suite_rectify = __esm(() => {
37259
37348
  init_implement();
37260
37349
  });
37261
37350
 
37351
+ // src/operations/_finding-to-check.ts
37352
+ function findingsToFailedChecks(findings) {
37353
+ const grouped = new Map;
37354
+ for (const finding of findings) {
37355
+ const check2 = SOURCE_TO_CHECK[finding.source];
37356
+ if (!check2)
37357
+ continue;
37358
+ const bucket = grouped.get(check2) ?? [];
37359
+ bucket.push(finding);
37360
+ grouped.set(check2, bucket);
37361
+ }
37362
+ return [...grouped.entries()].map(([check2, grpFindings]) => ({
37363
+ check: check2,
37364
+ success: false,
37365
+ command: "",
37366
+ exitCode: 1,
37367
+ output: "",
37368
+ durationMs: 0,
37369
+ findings: grpFindings
37370
+ }));
37371
+ }
37372
+ var SOURCE_TO_CHECK;
37373
+ var init__finding_to_check = __esm(() => {
37374
+ SOURCE_TO_CHECK = {
37375
+ "semantic-review": "semantic",
37376
+ "adversarial-review": "adversarial",
37377
+ lint: "lint",
37378
+ typecheck: "typecheck"
37379
+ };
37380
+ });
37381
+
37262
37382
  // src/operations/autofix-implementer-strategy.ts
37263
37383
  function makeAutofixImplementerStrategy(story) {
37264
37384
  return {
37265
37385
  name: "autofix-implementer",
37266
37386
  appliesTo: (f) => f.fixTarget === "source" && IMPLEMENTER_SOURCES.has(f.source),
37267
37387
  fixOp: implementerRectifyOp,
37268
- buildInput: (_findings, _prior, _cycleCtx) => ({
37269
- failedChecks: [],
37388
+ buildInput: (findings, _prior, _cycleCtx) => ({
37389
+ failedChecks: findingsToFailedChecks(findings),
37270
37390
  story
37271
37391
  }),
37272
37392
  extractApplied: (output) => ({
@@ -37279,6 +37399,7 @@ function makeAutofixImplementerStrategy(story) {
37279
37399
  }
37280
37400
  var IMPLEMENTER_SOURCES;
37281
37401
  var init_autofix_implementer_strategy = __esm(() => {
37402
+ init__finding_to_check();
37282
37403
  init_autofix_implementer();
37283
37404
  IMPLEMENTER_SOURCES = new Set(["lint", "typecheck", "semantic-review"]);
37284
37405
  });
@@ -37289,8 +37410,8 @@ function makeAutofixTestWriterStrategy(story, config2) {
37289
37410
  name: "autofix-test-writer",
37290
37411
  appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review",
37291
37412
  fixOp: testWriterRectifyOp,
37292
- buildInput: (_findings, _prior, _cycleCtx) => ({
37293
- failedChecks: [],
37413
+ buildInput: (findings, _prior, _cycleCtx) => ({
37414
+ failedChecks: findingsToFailedChecks(findings),
37294
37415
  story,
37295
37416
  blockingThreshold: config2.review?.blockingThreshold
37296
37417
  }),
@@ -37299,6 +37420,7 @@ function makeAutofixTestWriterStrategy(story, config2) {
37299
37420
  };
37300
37421
  }
37301
37422
  var init_autofix_test_writer_strategy = __esm(() => {
37423
+ init__finding_to_check();
37302
37424
  init_autofix_test_writer();
37303
37425
  });
37304
37426
 
@@ -37782,6 +37904,7 @@ var init_operations = __esm(() => {
37782
37904
  init_full_suite_rectify();
37783
37905
  init_autofix_implementer_strategy();
37784
37906
  init_autofix_test_writer_strategy();
37907
+ init__finding_to_check();
37785
37908
  init_mechanical_lintfix_strategy();
37786
37909
  init_mechanical_formatfix_strategy();
37787
37910
  init_lint_check();
@@ -37987,7 +38110,10 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
37987
38110
  if (allExhausted) {
37988
38111
  let liteFindingsAfter;
37989
38112
  try {
37990
- liteFindingsAfter = await cycle.validate(ctx, { mode: "lite" });
38113
+ liteFindingsAfter = await cycle.validate(ctx, {
38114
+ mode: "lite",
38115
+ strategiesRun: group.map((s) => s.name)
38116
+ });
37991
38117
  } catch (err) {
37992
38118
  const finishedAt3 = now();
37993
38119
  cycle.iterations.push({
@@ -38059,7 +38185,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
38059
38185
  let validatorAttempt = 0;
38060
38186
  for (;; ) {
38061
38187
  try {
38062
- findingsAfter = await cycle.validate(ctx, { mode: "full" });
38188
+ findingsAfter = await cycle.validate(ctx, { mode: "full", strategiesRun: group.map((s) => s.name) });
38063
38189
  break;
38064
38190
  } catch (err) {
38065
38191
  if (validatorAttempt >= cycle.config.validatorRetries) {
@@ -39743,6 +39869,7 @@ var init_review = __esm(() => {
39743
39869
  init_diff_utils();
39744
39870
  init_finding_projection();
39745
39871
  init_runner2();
39872
+ init_requote_response();
39746
39873
  init_severity();
39747
39874
  });
39748
39875
 
@@ -41251,8 +41378,8 @@ var init_prompts = __esm(() => {
41251
41378
  // src/operations/build-hop-callback.ts
41252
41379
  function turnResultToAgentResult(r) {
41253
41380
  return {
41254
- success: true,
41255
- exitCode: 0,
41381
+ success: !r.adapterFailure,
41382
+ exitCode: r.adapterFailure ? 1 : 0,
41256
41383
  output: r.output,
41257
41384
  rateLimited: false,
41258
41385
  durationMs: 0,
@@ -41260,7 +41387,8 @@ function turnResultToAgentResult(r) {
41260
41387
  exactCostUsd: r.exactCostUsd,
41261
41388
  tokenUsage: r.tokenUsage,
41262
41389
  protocolIds: r.protocolIds,
41263
- internalRoundTrips: r.internalRoundTrips
41390
+ internalRoundTrips: r.internalRoundTrips,
41391
+ ...r.adapterFailure ? { adapterFailure: r.adapterFailure } : {}
41264
41392
  };
41265
41393
  }
41266
41394
  function buildHopCallback(ctx, sessionId, _initialOptions) {
@@ -41629,13 +41757,26 @@ async function callOp(ctx, op, input) {
41629
41757
  let lastRetryTurn;
41630
41758
  const sendWithFileOutput = async (promptText, bodyCtx) => {
41631
41759
  const turn = await bodyCtx.send(promptText);
41632
- if (!fileOutputPath)
41633
- return turn;
41634
- const fileContent = await _callOpDeps.readFileOutput(fileOutputPath);
41635
- if (fileContent === null) {
41636
- return turn;
41760
+ let effective = turn;
41761
+ if (fileOutputPath) {
41762
+ const fileContent = await _callOpDeps.readFileOutput(fileOutputPath);
41763
+ if (fileContent !== null) {
41764
+ effective = { ...turn, output: fileContent };
41765
+ }
41637
41766
  }
41638
- return { ...turn, output: fileContent };
41767
+ if (!effective.output?.trim() && !effective.adapterFailure) {
41768
+ return {
41769
+ ...effective,
41770
+ adapterFailure: {
41771
+ outcome: "fail-stale",
41772
+ category: "availability",
41773
+ retriable: true,
41774
+ message: `[${op.name}] agent returned no output`,
41775
+ reason: "empty-output"
41776
+ }
41777
+ };
41778
+ }
41779
+ return effective;
41639
41780
  };
41640
41781
  const sendWithParseRetry = async (initialPrompt, bodyCtx) => {
41641
41782
  retryFallback = undefined;
@@ -43828,7 +43969,8 @@ class SessionManager {
43828
43969
  category: "availability",
43829
43970
  outcome: "fail-stale",
43830
43971
  retriable: true,
43831
- message: "idle watchdog cancelled session \u2014 no stream activity"
43972
+ message: "idle watchdog cancelled session \u2014 no stream activity",
43973
+ reason: "idle-watchdog"
43832
43974
  });
43833
43975
  }
43834
43976
  }
@@ -51884,9 +52026,19 @@ function extractPhaseFindings(output) {
51884
52026
  const success2 = "success" in record2 ? record2.success === true : ("passed" in record2) ? record2.passed === true : findings.length === 0;
51885
52027
  return success2 ? [] : findings;
51886
52028
  }
51887
- function gatherRectificationFindings(phaseOutputs, phases) {
52029
+ function shouldSkipPhaseForRectification(phase, state, phaseOutputs) {
52030
+ if (phase.kind !== "full-suite-gate")
52031
+ return false;
52032
+ const verifierName = state.verifier?.slot.op.name;
52033
+ if (!verifierName)
52034
+ return false;
52035
+ return phaseExplicitlyPassed(phaseOutputs[verifierName]);
52036
+ }
52037
+ function gatherRectificationFindings(phaseOutputs, phases, state) {
51888
52038
  const findings = [];
51889
52039
  for (const phase of phases) {
52040
+ if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
52041
+ continue;
51890
52042
  findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
51891
52043
  }
51892
52044
  return findings;
@@ -51902,6 +52054,21 @@ function collectRectificationPhases(state) {
51902
52054
  state.adversarialReview
51903
52055
  ].filter((phase) => phase !== undefined);
51904
52056
  }
52057
+ function phasesToRevalidate(strategiesRun, allPhases) {
52058
+ const sourceFiltered = allPhases.filter((p) => p.kind !== "verifier");
52059
+ if (!strategiesRun || strategiesRun.length === 0)
52060
+ return sourceFiltered;
52061
+ const unknown2 = strategiesRun.some((name) => STRATEGY_TO_REVALIDATION_PHASES[name] === undefined);
52062
+ if (unknown2)
52063
+ return sourceFiltered;
52064
+ const needed = new Set;
52065
+ for (const name of strategiesRun) {
52066
+ for (const kind of STRATEGY_TO_REVALIDATION_PHASES[name] ?? []) {
52067
+ needed.add(kind);
52068
+ }
52069
+ }
52070
+ return sourceFiltered.filter((p) => needed.has(p.kind));
52071
+ }
51905
52072
  function toReviewDecisionPayload(opName, output) {
51906
52073
  if (output === null || output === undefined || typeof output !== "object")
51907
52074
  return null;
@@ -52057,7 +52224,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52057
52224
  if (ctx.runtime.signal?.aborted) {
52058
52225
  return {};
52059
52226
  }
52060
- const initialFindings = gatherRectificationFindings(phaseOutputs, validationPhases);
52227
+ const initialFindings = gatherRectificationFindings(phaseOutputs, validationPhases, state);
52061
52228
  if (initialFindings.length === 0) {
52062
52229
  return {};
52063
52230
  }
@@ -52077,13 +52244,22 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52077
52244
  validate: async (_validateCtx, opts) => {
52078
52245
  if (ctx.runtime.signal?.aborted)
52079
52246
  return [];
52080
- const lite = opts?.mode === "lite";
52247
+ const lite = (opts?.mode ?? "full") === "lite";
52248
+ const phases = phasesToRevalidate(opts?.strategiesRun, validationPhases);
52249
+ getSafeLogger()?.debug("story-orchestrator", "rectification validate scope", {
52250
+ storyId: ctx.storyId,
52251
+ mode: opts?.mode ?? "full",
52252
+ strategiesRun: opts?.strategiesRun,
52253
+ phasesSelected: phases.map((p) => p.kind)
52254
+ });
52081
52255
  const findings = [];
52082
- for (const phase of validationPhases) {
52256
+ for (const phase of phases) {
52083
52257
  if (lite && phase.kind === "full-suite-gate") {
52084
52258
  continue;
52085
52259
  }
52086
52260
  await runPhase(ctx, phase.slot, phaseCosts, phaseOutputs);
52261
+ if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
52262
+ continue;
52087
52263
  findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
52088
52264
  }
52089
52265
  return findings;
@@ -52230,7 +52406,7 @@ class StoryOrchestratorBuilder {
52230
52406
  return new ExecutionPlan(ctx, { ...this.state }, opts.isThreeSession ?? false);
52231
52407
  }
52232
52408
  }
52233
- var _storyOrchestratorDeps, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY;
52409
+ var _storyOrchestratorDeps, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES;
52234
52410
  var init_story_orchestrator = __esm(() => {
52235
52411
  init_errors();
52236
52412
  init_findings();
@@ -52268,6 +52444,20 @@ var init_story_orchestrator = __esm(() => {
52268
52444
  "semantic-review": "semanticReview",
52269
52445
  "adversarial-review": "adversarialReview"
52270
52446
  };
52447
+ STRATEGY_TO_REVALIDATION_PHASES = {
52448
+ "mechanical-lintfix": ["lint-check"],
52449
+ "mechanical-formatfix": ["lint-check"],
52450
+ "autofix-implementer": [
52451
+ "lint-check",
52452
+ "typecheck-check",
52453
+ "full-suite-gate",
52454
+ "verify-scoped",
52455
+ "semantic-review",
52456
+ "adversarial-review"
52457
+ ],
52458
+ "autofix-test-writer": ["lint-check", "typecheck-check", "full-suite-gate", "verify-scoped", "adversarial-review"],
52459
+ "full-suite-rectify": ["lint-check", "typecheck-check", "full-suite-gate", "verify-scoped", "semantic-review"]
52460
+ };
52271
52461
  });
52272
52462
 
52273
52463
  // src/execution/build-plan-for-strategy.ts
@@ -56753,7 +56943,7 @@ var package_default;
56753
56943
  var init_package = __esm(() => {
56754
56944
  package_default = {
56755
56945
  name: "@nathapp/nax",
56756
- version: "0.67.7",
56946
+ version: "0.67.9",
56757
56947
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
56758
56948
  type: "module",
56759
56949
  bin: {
@@ -56848,8 +57038,8 @@ var init_version = __esm(() => {
56848
57038
  NAX_VERSION = package_default.version;
56849
57039
  NAX_COMMIT = (() => {
56850
57040
  try {
56851
- if (/^[0-9a-f]{6,10}$/.test("1c26c38d"))
56852
- return "1c26c38d";
57041
+ if (/^[0-9a-f]{6,10}$/.test("ab2db4bb"))
57042
+ return "ab2db4bb";
56853
57043
  } catch {}
56854
57044
  try {
56855
57045
  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.67.7",
3
+ "version": "0.67.9",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {