@nathapp/nax 0.77.1 → 0.77.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 +139 -18
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -16865,7 +16865,10 @@ var init_schemas_execution = __esm(() => {
16865
16865
  maxFailureSummaryChars: exports_external.number().int().min(500).max(1e4).default(2000),
16866
16866
  abortOnIncreasingFailures: exports_external.boolean().default(true),
16867
16867
  consecutiveIncreasesToBail: exports_external.number().int().min(1).max(10).default(2),
16868
+ abortOnNoProgress: exports_external.boolean().default(true),
16869
+ consecutiveNoProgressToBail: exports_external.number().int().min(1).max(10).default(3),
16868
16870
  escalateOnExhaustion: exports_external.boolean().optional().default(true),
16871
+ storyScopedFixBudget: exports_external.boolean().default(true),
16869
16872
  rethinkAtAttempt: exports_external.number().int().min(1).default(2),
16870
16873
  urgencyAtAttempt: exports_external.number().int().min(1).default(3)
16871
16874
  });
@@ -17531,7 +17534,10 @@ var init_schemas3 = __esm(() => {
17531
17534
  maxFailureSummaryChars: 2000,
17532
17535
  abortOnIncreasingFailures: true,
17533
17536
  consecutiveIncreasesToBail: 2,
17537
+ abortOnNoProgress: true,
17538
+ consecutiveNoProgressToBail: 3,
17534
17539
  escalateOnExhaustion: true,
17540
+ storyScopedFixBudget: true,
17535
17541
  rethinkAtAttempt: 2,
17536
17542
  urgencyAtAttempt: 3
17537
17543
  },
@@ -43779,8 +43785,8 @@ var init_cycle_iteration_log = __esm(() => {
43779
43785
  });
43780
43786
 
43781
43787
  // src/findings/cycle-retirement.ts
43782
- function createDeclineLedger() {
43783
- const declinedByStrategy = new Map;
43788
+ function createDeclineLedger(backing) {
43789
+ const declinedByStrategy = backing ?? new Map;
43784
43790
  const hasDeclined = (strategyName, finding) => declinedByStrategy.get(strategyName)?.has(findingKey(finding)) === true;
43785
43791
  const isRetiredFor = (strategy, findings) => {
43786
43792
  const claimed = findings.filter((f) => strategy.appliesTo(f));
@@ -43872,13 +43878,14 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
43872
43878
  const storyId = ctx.storyId;
43873
43879
  const packageDir = ctx.packageDir;
43874
43880
  let totalCostUsd = 0;
43875
- const declines = createDeclineLedger();
43881
+ const declines = createDeclineLedger(_deps.declineBacking);
43876
43882
  let unresolvedDetail;
43877
43883
  const finish = (result) => unresolvedDetail !== undefined && result.unresolvedDetail === undefined ? { ...result, unresolvedDetail } : result;
43878
43884
  for (;; ) {
43879
43885
  if (cycle.findings.length === 0 && cycle.verdict === undefined) {
43880
43886
  return { iterations: cycle.iterations, finalFindings: [], exitReason: "resolved", costUsd: totalCostUsd };
43881
43887
  }
43888
+ const history = cycle.priorIterations ? [...cycle.priorIterations, ...cycle.iterations] : cycle.iterations;
43882
43889
  const selectable = cycle.strategies.filter((s) => !declines.isRetiredFor(s, cycle.findings));
43883
43890
  const active = selectActiveStrategies(selectable, cycle.findings, cycle.verdict);
43884
43891
  if (active.length === 0) {
@@ -43900,9 +43907,9 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
43900
43907
  costUsd: totalCostUsd
43901
43908
  });
43902
43909
  }
43903
- const uncappedActive = active.filter((s) => countStrategyAttempts(cycle.iterations, s.name) < s.maxAttempts);
43910
+ const uncappedActive = active.filter((s) => countStrategyAttempts(history, s.name) < s.maxAttempts);
43904
43911
  if (uncappedActive.length === 0) {
43905
- const exhaustedStrategy = active.find((s) => countStrategyAttempts(cycle.iterations, s.name) >= s.maxAttempts);
43912
+ const exhaustedStrategy = active.find((s) => countStrategyAttempts(history, s.name) >= s.maxAttempts);
43906
43913
  logger?.info("findings.cycle", "cycle exited \u2014 all active strategies exhausted", {
43907
43914
  storyId,
43908
43915
  packageDir,
@@ -43918,7 +43925,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
43918
43925
  costUsd: totalCostUsd
43919
43926
  });
43920
43927
  }
43921
- const totalAttempts = countTotalAttempts(cycle.iterations);
43928
+ const totalAttempts = countTotalAttempts(history);
43922
43929
  if (totalAttempts >= cycle.config.maxAttemptsTotal) {
43923
43930
  logger?.info("findings.cycle", "cycle exited \u2014 total attempt cap reached", {
43924
43931
  storyId,
@@ -43936,7 +43943,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
43936
43943
  });
43937
43944
  }
43938
43945
  for (const strategy of uncappedActive) {
43939
- const bailReason = strategy.bailWhen?.(cycle.iterations) ?? null;
43946
+ const bailReason = strategy.bailWhen?.(history) ?? null;
43940
43947
  if (bailReason !== null) {
43941
43948
  logger?.info("findings.cycle", "cycle exited \u2014 bail predicate fired", {
43942
43949
  storyId,
@@ -44024,7 +44031,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
44024
44031
  });
44025
44032
  }
44026
44033
  const allExhausted = group.every((s) => {
44027
- const prior = countStrategyAttempts(cycle.iterations, s.name);
44034
+ const prior = countStrategyAttempts(history, s.name);
44028
44035
  const current = fixesApplied.filter((fa) => fa.strategyName === s.name).length;
44029
44036
  return prior + current >= s.maxAttempts;
44030
44037
  });
@@ -44192,6 +44199,55 @@ var init_cycle = __esm(() => {
44192
44199
  };
44193
44200
  });
44194
44201
 
44202
+ // src/findings/bail-marker.ts
44203
+ function markNaxBailWrapper(predicate) {
44204
+ Object.assign(predicate, { [NAX_BAIL_WRAPPER]: true });
44205
+ return predicate;
44206
+ }
44207
+ function isNaxBailWrapper(predicate) {
44208
+ if (!predicate)
44209
+ return false;
44210
+ return predicate[NAX_BAIL_WRAPPER] === true;
44211
+ }
44212
+ var NAX_BAIL_WRAPPER = "__naxBailWrapper";
44213
+
44214
+ // src/findings/story-fix-history.ts
44215
+ function createStoryFixHistory() {
44216
+ return new Map;
44217
+ }
44218
+ function storyFixKey(storyId, tier) {
44219
+ return `${storyId}::${tier ?? "default"}`;
44220
+ }
44221
+ function getStoryFixState(store, key) {
44222
+ let existing = store.get(key);
44223
+ if (!existing) {
44224
+ existing = { iterations: [], declines: new Map };
44225
+ store.set(key, existing);
44226
+ }
44227
+ return existing;
44228
+ }
44229
+ function appendStoryFixIterations(store, key, iterations) {
44230
+ const existing = store.get(key);
44231
+ if (existing) {
44232
+ store.set(key, {
44233
+ iterations: [...existing.iterations, ...iterations],
44234
+ declines: existing.declines
44235
+ });
44236
+ } else {
44237
+ store.set(key, {
44238
+ iterations: [...iterations],
44239
+ declines: new Map
44240
+ });
44241
+ }
44242
+ }
44243
+ function mergeStoryFixDeclines(store, key, declines) {
44244
+ const existing = store.get(key);
44245
+ store.set(key, {
44246
+ iterations: existing?.iterations ?? [],
44247
+ declines: new Map([...declines].map(([name, keys]) => [name, new Set(keys)]))
44248
+ });
44249
+ }
44250
+
44195
44251
  // src/findings/index.ts
44196
44252
  var init_findings = __esm(() => {
44197
44253
  init_types6();
@@ -44199,6 +44255,7 @@ var init_findings = __esm(() => {
44199
44255
  init_path_utils();
44200
44256
  init_cycle();
44201
44257
  init_cycle_iteration_log();
44258
+ init_cycle_retirement();
44202
44259
  });
44203
44260
 
44204
44261
  // src/review/review-iteration-store.ts
@@ -44685,7 +44742,7 @@ var package_default;
44685
44742
  var init_package = __esm(() => {
44686
44743
  package_default = {
44687
44744
  name: "@nathapp/nax",
44688
- version: "0.77.1",
44745
+ version: "0.77.2",
44689
44746
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
44690
44747
  type: "module",
44691
44748
  bin: {
@@ -44789,8 +44846,8 @@ var init_version = __esm(() => {
44789
44846
  NAX_VERSION = package_default.version;
44790
44847
  NAX_COMMIT = (() => {
44791
44848
  try {
44792
- if (/^[0-9a-f]{6,10}$/.test("a50be624"))
44793
- return "a50be624";
44849
+ if (/^[0-9a-f]{6,10}$/.test("888b55c1"))
44850
+ return "888b55c1";
44794
44851
  } catch {}
44795
44852
  try {
44796
44853
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -51912,6 +51969,7 @@ function createRuntime(config2, workdir, opts) {
51912
51969
  const adversarialIterations = new Map;
51913
51970
  const semanticIterations = new Map;
51914
51971
  const rectificationOscillations = new Map;
51972
+ const storyFixHistory = createStoryFixHistory();
51915
51973
  const mutationSummaries = new Map;
51916
51974
  const dirtyWorktrees = new Set;
51917
51975
  let closed = false;
@@ -51938,6 +51996,7 @@ function createRuntime(config2, workdir, opts) {
51938
51996
  adversarialIterations,
51939
51997
  semanticIterations,
51940
51998
  rectificationOscillations,
51999
+ storyFixHistory,
51941
52000
  mutationSummaries,
51942
52001
  dirtyWorktrees,
51943
52002
  get signal() {
@@ -51985,6 +52044,7 @@ var init_runtime = __esm(() => {
51985
52044
  init_config();
51986
52045
  init_errors();
51987
52046
  init_pid_registry();
52047
+ init_findings();
51988
52048
  init_logger2();
51989
52049
  init_review_audit();
51990
52050
  init_session();
@@ -61274,6 +61334,45 @@ function countOscillationOutcomes(iterations) {
61274
61334
  return count;
61275
61335
  }
61276
61336
 
61337
+ // src/execution/story-orchestrator/no-progress-bail.ts
61338
+ function madeNoProgress(iteration) {
61339
+ if (iteration.findingsBefore.length === 0)
61340
+ return false;
61341
+ const after = new Set(iteration.findingsAfter.map(findingKey));
61342
+ return iteration.findingsBefore.every((finding) => after.has(findingKey(finding)));
61343
+ }
61344
+ function withNoProgressBail(strategies, enabled, consecutiveNoProgress) {
61345
+ if (!enabled)
61346
+ return strategies;
61347
+ const threshold = Math.max(1, consecutiveNoProgress);
61348
+ return strategies.map((strategy) => {
61349
+ const innerBail = strategy.bailWhen;
61350
+ const isUserBail = innerBail !== undefined && !isNaxBailWrapper(innerBail);
61351
+ return {
61352
+ ...strategy,
61353
+ bailWhen: markNaxBailWrapper((iterations) => {
61354
+ if (isUserBail) {
61355
+ const userReason = innerBail(iterations);
61356
+ if (userReason !== null)
61357
+ return userReason;
61358
+ }
61359
+ if (iterations.length >= threshold) {
61360
+ const trailing = iterations.slice(-threshold);
61361
+ if (trailing.every(madeNoProgress)) {
61362
+ return `no finding resolved for ${threshold} consecutive iteration(s); ${trailing.at(-1)?.findingsBefore.length ?? 0} finding(s) persisted`;
61363
+ }
61364
+ }
61365
+ if (!isUserBail && innerBail)
61366
+ return innerBail(iterations);
61367
+ return null;
61368
+ })
61369
+ };
61370
+ });
61371
+ }
61372
+ var init_no_progress_bail = __esm(() => {
61373
+ init_findings();
61374
+ });
61375
+
61277
61376
  // src/execution/checkpoint/resume-plan.ts
61278
61377
  function buildResumePlan(cp, current) {
61279
61378
  if (!cp) {
@@ -61806,7 +61905,7 @@ function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
61806
61905
  const threshold = Math.max(1, consecutiveIncreases);
61807
61906
  return strategies.map((strategy) => ({
61808
61907
  ...strategy,
61809
- bailWhen: (iterations) => {
61908
+ bailWhen: markNaxBailWrapper((iterations) => {
61810
61909
  const userReason = strategy.bailWhen?.(iterations) ?? null;
61811
61910
  if (userReason !== null)
61812
61911
  return userReason;
@@ -61820,7 +61919,7 @@ function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
61820
61919
  return `failure count increased for ${threshold} consecutive iteration(s): ${first.findingsBefore.length} -> ${last.findingsAfter.length}`;
61821
61920
  }
61822
61921
  return null;
61823
- }
61922
+ })
61824
61923
  }));
61825
61924
  }
61826
61925
  var _storyOrchestratorDeps, ALL_FINDING_SEVERITIES;
@@ -61986,6 +62085,12 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
61986
62085
  if (!ctx.storyId) {
61987
62086
  return {};
61988
62087
  }
62088
+ const storyFixBudgetEnabled = !nbfPath && ctx.runtime.configLoader.current().execution?.rectification?.storyScopedFixBudget === true;
62089
+ const store = ctx.runtime.storyFixHistory;
62090
+ const fixKey = storyFixBudgetEnabled ? storyFixKey(ctx.storyId, ctx.phaseTelemetry?.tier) : undefined;
62091
+ const fixState = fixKey !== undefined && store ? getStoryFixState(store, fixKey) : undefined;
62092
+ const priorIterationCount = fixState?.iterations.length ?? 0;
62093
+ const declineSnapshot = fixState ? new Map([...fixState.declines].map(([name, keys]) => [name, new Set(keys)])) : undefined;
61989
62094
  const fixOpPhaseOutputs = {};
61990
62095
  const wrappedCallOp = async (cycleCtx, op, input) => {
61991
62096
  const slot = { op, input };
@@ -61994,7 +62099,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
61994
62099
  const cycle = {
61995
62100
  findings: [...initialFindings],
61996
62101
  iterations: [],
61997
- strategies: withIncreasingFailuresBail(overrides?.strategies ?? rectification2.strategies, rectification2.abortOnIncreasingFailures, rectification2.consecutiveIncreasesToBail ?? 1),
62102
+ priorIterations: fixState?.iterations,
62103
+ strategies: withNoProgressBail(withIncreasingFailuresBail(overrides?.strategies ?? rectification2.strategies, rectification2.abortOnIncreasingFailures, rectification2.consecutiveIncreasesToBail ?? 1), rectification2.abortOnNoProgress ?? true, rectification2.consecutiveNoProgressToBail ?? 3),
61998
62104
  config: { maxAttemptsTotal: overrides?.maxAttempts ?? rectification2.maxAttempts, validatorRetries: 1 },
61999
62105
  validate: async (_validateCtx, opts) => {
62000
62106
  if (ctx.runtime.signal?.aborted)
@@ -62055,15 +62161,22 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
62055
62161
  return { findings: validated, shortCircuited };
62056
62162
  }
62057
62163
  };
62058
- const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
62164
+ const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp, declineBacking: declineSnapshot });
62165
+ if (fixKey !== undefined && store && cycleResult.iterations.length > 0) {
62166
+ appendStoryFixIterations(store, fixKey, cycleResult.iterations);
62167
+ }
62168
+ if (fixKey !== undefined && store && declineSnapshot) {
62169
+ mergeStoryFixDeclines(store, fixKey, declineSnapshot);
62170
+ }
62059
62171
  const oscillationCount = countOscillationOutcomes(cycleResult.iterations);
62060
62172
  if (oscillationCount > 0) {
62061
62173
  recordOscillations(ctx.runtime.rectificationOscillations, ctx.storyId, oscillationCount);
62062
62174
  }
62175
+ const reportedExitReason = cycleResult.exitReason === "validate-short-circuit" && priorIterationCount > 0 ? "max-attempts-per-strategy" : cycleResult.exitReason;
62063
62176
  phaseOutputs.rectification = {
62064
62177
  success: cycleResult.exitReason === "resolved",
62065
62178
  iterationCount: cycleResult.iterations.length,
62066
- exitReason: cycleResult.exitReason,
62179
+ exitReason: reportedExitReason,
62067
62180
  finalFindingsCount: cycleResult.finalFindings.length
62068
62181
  };
62069
62182
  const rectLogger = getSafeLogger();
@@ -62072,7 +62185,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
62072
62185
  initialFindingsCount: initialFindings.length,
62073
62186
  iterationCount: cycleResult.iterations.length,
62074
62187
  finalFindingsCount: cycleResult.finalFindings.length,
62075
- exitReason: cycleResult.exitReason,
62188
+ exitReason: reportedExitReason,
62076
62189
  costUsd: cycleResult.costUsd
62077
62190
  };
62078
62191
  if (cycleResult.exitReason === "resolved") {
@@ -62098,8 +62211,10 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
62098
62211
  return {};
62099
62212
  }
62100
62213
  var init_rectification = __esm(() => {
62214
+ init_findings();
62101
62215
  init_logger2();
62102
62216
  init_nbf_flake_triage();
62217
+ init_no_progress_bail();
62103
62218
  init_phase_eval();
62104
62219
  init_phase_eval();
62105
62220
  init_run_phase();
@@ -62453,6 +62568,7 @@ var init_story_orchestrator = __esm(() => {
62453
62568
  init_phase_eval();
62454
62569
  init_rectification();
62455
62570
  init_nbf_flake_triage();
62571
+ init_no_progress_bail();
62456
62572
  init_run_phase();
62457
62573
  init_review_decision();
62458
62574
  init_types9();
@@ -62873,7 +62989,9 @@ async function assemblePlanInputsFromCtx(ctx) {
62873
62989
  maxAttempts: ctx.config.execution.rectification.maxAttemptsTotal,
62874
62990
  strategies: [],
62875
62991
  abortOnIncreasingFailures: ctx.config.execution.rectification.abortOnIncreasingFailures,
62876
- consecutiveIncreasesToBail: ctx.config.execution.rectification.consecutiveIncreasesToBail
62992
+ consecutiveIncreasesToBail: ctx.config.execution.rectification.consecutiveIncreasesToBail,
62993
+ abortOnNoProgress: ctx.config.execution.rectification.abortOnNoProgress,
62994
+ consecutiveNoProgressToBail: ctx.config.execution.rectification.consecutiveNoProgressToBail
62877
62995
  } : undefined;
62878
62996
  const mutationCheckInput = ctx.config.execution?.mutationCheck?.enabled === true ? {
62879
62997
  story,
@@ -76417,6 +76535,7 @@ var init_lifecycle = __esm(() => {
76417
76535
  var exports_execution = {};
76418
76536
  __export(exports_execution, {
76419
76537
  writeExitSummary: () => writeExitSummary,
76538
+ withNoProgressBail: () => withNoProgressBail,
76420
76539
  withIncreasingFailuresBail: () => withIncreasingFailuresBail,
76421
76540
  toReviewDecisionPayload: () => toReviewDecisionPayload,
76422
76541
  synthesizeBackfillMetric: () => synthesizeBackfillMetric,
@@ -107607,6 +107726,8 @@ var FIELD_DESCRIPTIONS = {
107607
107726
  "execution.rectification.maxFailureSummaryChars": "Max characters in failure summary",
107608
107727
  "execution.rectification.abortOnIncreasingFailures": "Abort if failure count increases",
107609
107728
  "execution.rectification.consecutiveIncreasesToBail": "Consecutive regressing iterations required before abortOnIncreasingFailures bails (default: 2; 1 = legacy behaviour)",
107729
+ "execution.rectification.abortOnNoProgress": "Abort rectification when no progress is made for several consecutive iterations (default: true)",
107730
+ "execution.rectification.consecutiveNoProgressToBail": "Consecutive no-progress iterations required before abortOnNoProgress bails (default: 3; one higher than the count bail's 2 because the no-progress predicate fires on a wider shape)",
107610
107731
  "execution.rectification.escalateOnExhaustion": "Enable model tier escalation when attempts are exhausted with remaining failures",
107611
107732
  "execution.rectification.rethinkAtAttempt": "Attempt number at which 'rethink your approach' language is injected into the prompt (default: 2)",
107612
107733
  "execution.rectification.urgencyAtAttempt": "Attempt number at which 'final chance before escalation' urgency is added (default: 3)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.77.1",
3
+ "version": "0.77.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {