@nathapp/nax 0.67.12 → 0.67.13

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 +313 -60
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -32839,6 +32839,122 @@ var init_categorization = __esm(() => {
32839
32839
  MECHANICAL_REVIEW_CHECKS = new Set(ORDERED_MECHANICAL_REVIEW_CHECKS);
32840
32840
  LLM_REVIEW_CHECKS = new Set(ORDERED_LLM_REVIEW_CHECKS);
32841
32841
  });
32842
+
32843
+ // src/review/prepare-inputs.ts
32844
+ import { relative as relative8, sep as sep2 } from "path";
32845
+ function derivePackageDirs(workdir, projectDir) {
32846
+ const repoRoot = projectDir ?? workdir;
32847
+ const packageDir = workdir !== repoRoot ? workdir : undefined;
32848
+ let packageDirRelative;
32849
+ if (projectDir && workdir !== projectDir) {
32850
+ const rel = relative8(projectDir, workdir);
32851
+ if (rel !== ".." && !rel.startsWith(`..${sep2}`)) {
32852
+ packageDirRelative = rel && rel !== "." ? rel : undefined;
32853
+ }
32854
+ }
32855
+ return { repoRoot, packageDir, packageDirRelative };
32856
+ }
32857
+ async function prepareSemanticReviewInput(args) {
32858
+ const { workdir, projectDir, storyId, storyGitRef, config: config2, naxIgnoreIndex, semanticConfig } = args;
32859
+ const effectiveRef = await resolveEffectiveRef(workdir, storyGitRef, storyId);
32860
+ if (!effectiveRef) {
32861
+ return {
32862
+ effectiveRef: undefined,
32863
+ stat: "",
32864
+ diff: undefined,
32865
+ excludePatterns: [],
32866
+ skipReason: "no git ref"
32867
+ };
32868
+ }
32869
+ const { packageDir, packageDirRelative } = derivePackageDirs(workdir, projectDir);
32870
+ const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32871
+ const resolved = await resolveTestFilePatterns(config2 ?? reviewConfigSelector.select(DEFAULT_CONFIG), projectDir ?? workdir, packageDirRelative);
32872
+ const excludePatterns = [...resolveReviewExcludePatterns(semanticConfig.excludePatterns, resolved)];
32873
+ const diffMode = semanticConfig.diffMode ?? "ref";
32874
+ if (diffMode === "ref") {
32875
+ if (!stat) {
32876
+ return { effectiveRef, stat: "", diff: undefined, excludePatterns, skipReason: "no changes detected" };
32877
+ }
32878
+ return { effectiveRef, stat, diff: undefined, excludePatterns };
32879
+ }
32880
+ const rawDiff = await collectDiff(workdir, effectiveRef, excludePatterns, { naxIgnoreIndex, packageDir });
32881
+ const diff = truncateDiff(rawDiff, rawDiff.length > DIFF_CAP_BYTES ? stat : undefined);
32882
+ if (!diff) {
32883
+ return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "no production code changes" };
32884
+ }
32885
+ return { effectiveRef, stat, diff, excludePatterns };
32886
+ }
32887
+ async function prepareAdversarialReviewInput(args) {
32888
+ const { workdir, projectDir, storyId, storyGitRef, config: config2, naxIgnoreIndex, adversarialConfig } = args;
32889
+ const effectiveRef = await resolveEffectiveRef(workdir, storyGitRef, storyId);
32890
+ if (!effectiveRef) {
32891
+ return {
32892
+ effectiveRef: undefined,
32893
+ stat: "",
32894
+ diff: undefined,
32895
+ testInventory: undefined,
32896
+ excludePatterns: [],
32897
+ testGlobs: [],
32898
+ refExcludePatterns: [],
32899
+ skipReason: "no git ref"
32900
+ };
32901
+ }
32902
+ const { packageDir, packageDirRelative } = derivePackageDirs(workdir, projectDir);
32903
+ const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32904
+ const effectiveConfig = config2 ?? reviewConfigSelector.select(DEFAULT_CONFIG);
32905
+ const resolved = await resolveTestFilePatterns(effectiveConfig, projectDir ?? workdir, packageDirRelative);
32906
+ const refExcludePatterns = [...resolveReviewExcludePatterns(adversarialConfig.excludePatterns, resolved)];
32907
+ const testGlobs = resolved.globs ?? [];
32908
+ const excludePatterns = [...adversarialConfig.excludePatterns ?? []];
32909
+ const diffMode = adversarialConfig.diffMode ?? "ref";
32910
+ const testFilePatterns = (typeof config2?.execution?.smartTestRunner === "object" ? config2.execution.smartTestRunner?.testFilePatterns : undefined) ?? undefined;
32911
+ if (diffMode === "ref") {
32912
+ if (!stat) {
32913
+ return {
32914
+ effectiveRef,
32915
+ stat: "",
32916
+ diff: undefined,
32917
+ testInventory: undefined,
32918
+ excludePatterns,
32919
+ testGlobs,
32920
+ refExcludePatterns,
32921
+ skipReason: "no changes detected"
32922
+ };
32923
+ }
32924
+ return {
32925
+ effectiveRef,
32926
+ stat,
32927
+ diff: undefined,
32928
+ testInventory: undefined,
32929
+ excludePatterns,
32930
+ testGlobs,
32931
+ refExcludePatterns
32932
+ };
32933
+ }
32934
+ const diff = await collectDiff(workdir, effectiveRef, excludePatterns, { naxIgnoreIndex, packageDir });
32935
+ if (!diff) {
32936
+ return {
32937
+ effectiveRef,
32938
+ stat,
32939
+ diff: undefined,
32940
+ testInventory: undefined,
32941
+ excludePatterns,
32942
+ testGlobs,
32943
+ refExcludePatterns,
32944
+ skipReason: "no code changes"
32945
+ };
32946
+ }
32947
+ const testInventory = await computeTestInventory(workdir, effectiveRef, testFilePatterns, {
32948
+ naxIgnoreIndex,
32949
+ packageDir
32950
+ });
32951
+ return { effectiveRef, stat, diff, testInventory, excludePatterns, testGlobs, refExcludePatterns };
32952
+ }
32953
+ var init_prepare_inputs = __esm(() => {
32954
+ init_config();
32955
+ init_test_runners();
32956
+ init_diff_utils();
32957
+ });
32842
32958
  // src/utils/process-kill.ts
32843
32959
  function killProcessGroup(pid, signal) {
32844
32960
  try {
@@ -33271,7 +33387,7 @@ var init_runners = __esm(() => {
33271
33387
  });
33272
33388
 
33273
33389
  // src/verification/smart-runner.ts
33274
- import { join as join19, relative as relative8 } from "path";
33390
+ import { join as join19, relative as relative9 } from "path";
33275
33391
  function extractPatternSuffix(pattern) {
33276
33392
  const lastStar = pattern.lastIndexOf("*");
33277
33393
  if (lastStar === -1)
@@ -33381,7 +33497,7 @@ async function getChangedNonTestFiles(workdir, baseRef, packagePrefix, testFileR
33381
33497
  let effectivePrefix = packagePrefix;
33382
33498
  if (packagePrefix && repoRoot) {
33383
33499
  const gitRoot = await _gitUtilDeps.getGitRoot(workdir);
33384
- const extraPrefix2 = gitRoot && gitRoot !== repoRoot ? relative8(gitRoot, repoRoot) : "";
33500
+ const extraPrefix2 = gitRoot && gitRoot !== repoRoot ? relative9(gitRoot, repoRoot) : "";
33385
33501
  effectivePrefix = extraPrefix2 ? `${extraPrefix2}/${packagePrefix}` : packagePrefix;
33386
33502
  }
33387
33503
  const scopedRaw = effectivePrefix ? lines.filter((f) => f.startsWith(`${effectivePrefix}/`)) : lines;
@@ -33408,7 +33524,7 @@ async function getChangedTestFiles(workdir, repoRoot, baseRef, packagePrefix, te
33408
33524
  const packageDir = packagePrefix ? join19(repoRoot, packagePrefix) : undefined;
33409
33525
  const ignoreMatchers = naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
33410
33526
  const gitRoot = await _gitUtilDeps.getGitRoot(workdir);
33411
- const extraPrefix = gitRoot && gitRoot !== repoRoot ? relative8(gitRoot, repoRoot) : "";
33527
+ const extraPrefix = gitRoot && gitRoot !== repoRoot ? relative9(gitRoot, repoRoot) : "";
33412
33528
  const effectivePrefix = packagePrefix ? extraPrefix ? `${extraPrefix}/${packagePrefix}` : packagePrefix : undefined;
33413
33529
  const scopedRaw = effectivePrefix ? lines.filter((f) => f.startsWith(`${effectivePrefix}/`)) : lines;
33414
33530
  const scoped2 = filterNaxInternalPaths(scopedRaw, ignoreMatchers);
@@ -33841,12 +33957,12 @@ function acceptanceDiagnoseRawArrayToFindings(raw) {
33841
33957
  }
33842
33958
 
33843
33959
  // src/findings/path-utils.ts
33844
- import { relative as relative9, resolve as resolve11 } from "path";
33960
+ import { relative as relative10, resolve as resolve11 } from "path";
33845
33961
  function rebaseToWorkdir(rawPath, cwd, workdir) {
33846
33962
  if (rawPath.startsWith("/")) {
33847
- return relative9(workdir, rawPath);
33963
+ return relative10(workdir, rawPath);
33848
33964
  }
33849
- return relative9(workdir, resolve11(cwd, rawPath));
33965
+ return relative10(workdir, resolve11(cwd, rawPath));
33850
33966
  }
33851
33967
  var init_path_utils = () => {};
33852
33968
 
@@ -39211,7 +39327,7 @@ var init_lint_parsing = __esm(() => {
39211
39327
  });
39212
39328
 
39213
39329
  // src/review/scoped-lint.ts
39214
- import { join as join24, relative as relative10 } from "path";
39330
+ import { join as join24, relative as relative11 } from "path";
39215
39331
  function shellQuotePath4(path5) {
39216
39332
  return `'${path5.replaceAll("'", "'\\''")}'`;
39217
39333
  }
@@ -39248,7 +39364,7 @@ async function listChangedFiles(workdir, baseRef) {
39248
39364
  function inferActivePackageDir(workdir, projectDir) {
39249
39365
  if (!projectDir)
39250
39366
  return;
39251
- const rel = normalizePath3(relative10(projectDir, workdir));
39367
+ const rel = normalizePath3(relative11(projectDir, workdir));
39252
39368
  if (!rel || rel === "." || rel.startsWith(".."))
39253
39369
  return;
39254
39370
  return rel;
@@ -39733,7 +39849,7 @@ var init_semantic_debate = __esm(() => {
39733
39849
  });
39734
39850
 
39735
39851
  // src/review/semantic.ts
39736
- import { relative as relative11, sep as sep2 } from "path";
39852
+ import { relative as relative12, sep as sep3 } from "path";
39737
39853
  function recordSemanticAudit(opts) {
39738
39854
  opts.runtime?.dispatchEvents.emitReviewDecision({
39739
39855
  kind: "review-decision",
@@ -39799,8 +39915,8 @@ async function runSemanticReview(opts) {
39799
39915
  const packageDir = workdir !== repoRoot ? workdir : undefined;
39800
39916
  const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
39801
39917
  const packageDirRelative = projectDir && workdir !== projectDir ? (() => {
39802
- const rel = relative11(projectDir, workdir);
39803
- if (rel === ".." || rel.startsWith(`..${sep2}`))
39918
+ const rel = relative12(projectDir, workdir);
39919
+ if (rel === ".." || rel.startsWith(`..${sep3}`))
39804
39920
  return;
39805
39921
  return rel && rel !== "." ? rel : undefined;
39806
39922
  })() : undefined;
@@ -40518,6 +40634,7 @@ var init_review = __esm(() => {
40518
40634
  init_semantic_evidence();
40519
40635
  init_categorization();
40520
40636
  init_diff_utils();
40637
+ init_prepare_inputs();
40521
40638
  init_finding_projection();
40522
40639
  init_runner2();
40523
40640
  init_requote_response();
@@ -44078,9 +44195,9 @@ var init_pid_registry = __esm(() => {
44078
44195
  // src/session/manager-deps.ts
44079
44196
  import { randomUUID as randomUUID3 } from "crypto";
44080
44197
  import { mkdir as mkdir5 } from "fs/promises";
44081
- import { isAbsolute as isAbsolute9, join as join27, relative as relative12, sep as sep3 } from "path";
44198
+ import { isAbsolute as isAbsolute9, join as join27, relative as relative13, sep as sep4 } from "path";
44082
44199
  function resolveProjectDirFromScratchDir(scratchDir) {
44083
- const marker = `${sep3}.nax${sep3}features${sep3}`;
44200
+ const marker = `${sep4}.nax${sep4}features${sep4}`;
44084
44201
  const markerIdx = scratchDir.lastIndexOf(marker);
44085
44202
  if (markerIdx > 0)
44086
44203
  return scratchDir.slice(0, markerIdx);
@@ -44090,7 +44207,7 @@ function resolveProjectDirFromScratchDir(scratchDir) {
44090
44207
  return;
44091
44208
  }
44092
44209
  function toProjectRelativePath(projectDir, pathValue) {
44093
- const relativePath = isAbsolute9(pathValue) ? relative12(projectDir, pathValue) : pathValue;
44210
+ const relativePath = isAbsolute9(pathValue) ? relative13(projectDir, pathValue) : pathValue;
44094
44211
  return relativePath === "" ? "." : relativePath;
44095
44212
  }
44096
44213
  var _sessionManagerDeps;
@@ -45399,7 +45516,7 @@ var init_windsurf = __esm(() => {
45399
45516
 
45400
45517
  // src/context/generator.ts
45401
45518
  import { existsSync as existsSync9 } from "fs";
45402
- import { join as join30, relative as relative13 } from "path";
45519
+ import { join as join30, relative as relative14 } from "path";
45403
45520
  async function loadContextContent(options, config2) {
45404
45521
  if (!_generatorDeps.existsSync(options.contextPath)) {
45405
45522
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -45527,7 +45644,7 @@ async function discoverWorkspacePackages2(repoRoot) {
45527
45644
  }
45528
45645
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
45529
45646
  const resolvedRepoRoot = repoRoot ?? packageDir;
45530
- const relativePkgPath = relative13(resolvedRepoRoot, packageDir);
45647
+ const relativePkgPath = relative14(resolvedRepoRoot, packageDir);
45531
45648
  const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
45532
45649
  if (!_generatorDeps.existsSync(contextPath)) {
45533
45650
  return [
@@ -52683,26 +52800,34 @@ function phaseExplicitlyPassed(output) {
52683
52800
  const r = output;
52684
52801
  return r.success === true || r.passed === true;
52685
52802
  }
52686
- function phasePassed(opName, output) {
52803
+ function phasePassed(opName, output, storyId) {
52804
+ const strictVerdictPhase = STRICT_VERDICT_PHASE_NAMES.has(opName);
52687
52805
  if (output === null || output === undefined) {
52688
- getSafeLogger()?.warn("story-orchestrator", "Phase produced no output \u2014 treating as pass", {
52689
- storyId: undefined,
52806
+ getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase produced no output \u2014 treating as fail" : "Phase produced no output \u2014 treating as pass", {
52807
+ storyId,
52690
52808
  phase: opName
52691
52809
  });
52692
- return true;
52810
+ return !strictVerdictPhase;
52811
+ }
52812
+ if (typeof output !== "object") {
52813
+ if (!strictVerdictPhase)
52814
+ return true;
52815
+ getSafeLogger()?.warn("story-orchestrator", "Strict phase produced non-object output \u2014 treating as fail", {
52816
+ storyId,
52817
+ phase: opName
52818
+ });
52819
+ return false;
52693
52820
  }
52694
- if (typeof output !== "object")
52695
- return true;
52696
52821
  const r = output;
52697
52822
  if ("success" in r)
52698
52823
  return r.success !== false;
52699
52824
  if ("passed" in r)
52700
52825
  return r.passed !== false;
52701
- getSafeLogger()?.warn("story-orchestrator", "Phase output has neither 'success' nor 'passed' \u2014 treating as pass", {
52702
- storyId: undefined,
52826
+ getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase output has neither 'success' nor 'passed' \u2014 treating as fail" : "Phase output has neither 'success' nor 'passed' \u2014 treating as pass", {
52827
+ storyId,
52703
52828
  phase: opName
52704
52829
  });
52705
- return true;
52830
+ return !strictVerdictPhase;
52706
52831
  }
52707
52832
  function isFinding(value) {
52708
52833
  return typeof value === "object" && value !== null && typeof value.source === "string" && value.source.length > 0;
@@ -52825,6 +52950,29 @@ function logUnifiedReviewPhaseStart(storyId, opName) {
52825
52950
  logger?.info("review", "Running adversarial check", { storyId });
52826
52951
  }
52827
52952
  }
52953
+ function logDeterministicPhaseOutcome(storyId, opName, output, durationMs, isTddPhase) {
52954
+ if (isTddPhase)
52955
+ return;
52956
+ if (opName === "semantic-review" || opName === "adversarial-review")
52957
+ return;
52958
+ if (output === null || output === undefined || typeof output !== "object")
52959
+ return;
52960
+ const logger = getSafeLogger();
52961
+ const r = output;
52962
+ const success2 = r.success === true || r.passed === true;
52963
+ const findingsCount = Array.isArray(r.findings) ? r.findings.length : undefined;
52964
+ const status = typeof r.status === "string" ? r.status : undefined;
52965
+ const data = { storyId, phase: opName, durationMs };
52966
+ if (findingsCount !== undefined)
52967
+ data.findingsCount = findingsCount;
52968
+ if (status !== undefined)
52969
+ data.status = status;
52970
+ if (success2) {
52971
+ logger?.info("story-orchestrator", `Phase passed: ${opName}`, data);
52972
+ } else {
52973
+ logger?.warn("story-orchestrator", `Phase failed: ${opName}`, data);
52974
+ }
52975
+ }
52828
52976
  function logUnifiedReviewPhaseResult(storyId, opName, output) {
52829
52977
  const logger = getSafeLogger();
52830
52978
  const payload = toReviewDecisionPayload(opName, output);
@@ -52894,6 +53042,7 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
52894
53042
  phaseOutputs[opName] = output;
52895
53043
  emitReviewDecision(ctx, opName, output);
52896
53044
  logUnifiedReviewPhaseResult(ctx.storyId, opName, output);
53045
+ logDeterministicPhaseOutcome(ctx.storyId, opName, output, Date.now() - phaseStartedAt, isTddPhase);
52897
53046
  if (isTddPhase) {
52898
53047
  const durationMs = Date.now() - phaseStartedAt;
52899
53048
  logger?.info("tdd", `Session complete: ${opName}`, {
@@ -52997,8 +53146,22 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52997
53146
  };
52998
53147
  const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
52999
53148
  phaseOutputs.rectification = { iterationCount: cycleResult.iterations.length };
53149
+ const rectLogger = getSafeLogger();
53150
+ const rectSummary = {
53151
+ storyId: ctx.storyId,
53152
+ initialFindingsCount: initialFindings.length,
53153
+ iterationCount: cycleResult.iterations.length,
53154
+ finalFindingsCount: cycleResult.finalFindings.length,
53155
+ exitReason: cycleResult.exitReason,
53156
+ costUsd: cycleResult.costUsd
53157
+ };
53158
+ if (cycleResult.exitReason === "resolved") {
53159
+ rectLogger?.info("story-orchestrator", "Rectification resolved all findings", rectSummary);
53160
+ } else {
53161
+ rectLogger?.warn("story-orchestrator", `Rectification exited: ${cycleResult.exitReason}`, rectSummary);
53162
+ }
53000
53163
  if (cycleResult.exitReason === "validator-error") {
53001
- getSafeLogger()?.warn("story-orchestrator", "rectification cycle aborted \u2014 validator infrastructure error", {
53164
+ rectLogger?.warn("story-orchestrator", "rectification cycle aborted \u2014 validator infrastructure error", {
53002
53165
  storyId: ctx.storyId
53003
53166
  });
53004
53167
  }
@@ -53054,8 +53217,12 @@ class ExecutionPlan {
53054
53217
  });
53055
53218
  throw error48;
53056
53219
  }
53057
- if (!phasePassed(phase.slot.op.name, phaseOutputs[phase.slot.op.name])) {
53220
+ if (!phasePassed(phase.slot.op.name, phaseOutputs[phase.slot.op.name], this.ctx.storyId)) {
53058
53221
  if (!shortCircuitExempt.has(phase.slot.op.name)) {
53222
+ logger?.warn("story-orchestrator", "Short-circuiting on phase failure", {
53223
+ storyId: this.ctx.storyId,
53224
+ phase: phase.slot.op.name
53225
+ });
53059
53226
  break;
53060
53227
  }
53061
53228
  }
@@ -53064,20 +53231,43 @@ class ExecutionPlan {
53064
53231
  const verifierName = this.state.verifier?.slot.op.name;
53065
53232
  const gateName = this.state.fullSuiteGate?.slot.op.name;
53066
53233
  const verifierPassedSsot = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
53067
- if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName])) {
53234
+ if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName], this.ctx.storyId)) {
53068
53235
  logger?.warn("story-orchestrator", "Full-suite gate failed but verifier judged story OK \u2014 treating gate failures as unrelated regressions", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
53069
53236
  }
53070
53237
  const success2 = Object.entries(phaseOutputs).every(([name, output]) => {
53071
53238
  if (verifierPassedSsot && name === gateName)
53072
53239
  return true;
53073
- return phasePassed(name, output);
53240
+ return phasePassed(name, output, this.ctx.storyId);
53074
53241
  });
53075
53242
  const totalCostUsd = Object.values(phaseCosts).reduce((sum, cost) => sum + cost, 0);
53243
+ const durationMs = Date.now() - startedAt;
53244
+ const failedPhases = Object.entries(phaseOutputs).filter(([name, output]) => {
53245
+ if (verifierPassedSsot && name === gateName)
53246
+ return false;
53247
+ return !phasePassed(name, output, this.ctx.storyId);
53248
+ }).map(([name]) => name);
53249
+ const summary = {
53250
+ storyId: this.ctx.storyId,
53251
+ success: success2,
53252
+ totalCostUsd,
53253
+ durationMs,
53254
+ phaseCount: Object.keys(phaseOutputs).length,
53255
+ failedPhases: failedPhases.length > 0 ? failedPhases : undefined
53256
+ };
53257
+ if (rectResult.rectificationExhausted)
53258
+ summary.rectificationExhausted = true;
53259
+ if (rectResult.unfixedFindings)
53260
+ summary.unfixedFindingsCount = rectResult.unfixedFindings.length;
53261
+ if (success2) {
53262
+ logger?.info("story-orchestrator", "Story orchestration complete", summary);
53263
+ } else {
53264
+ logger?.warn("story-orchestrator", "Story orchestration failed", summary);
53265
+ }
53076
53266
  return {
53077
53267
  success: success2,
53078
53268
  phaseCosts,
53079
53269
  totalCostUsd,
53080
- durationMs: Date.now() - startedAt,
53270
+ durationMs,
53081
53271
  phaseOutputs,
53082
53272
  ...rectResult
53083
53273
  };
@@ -53137,7 +53327,7 @@ class StoryOrchestratorBuilder {
53137
53327
  return new ExecutionPlan(ctx, { ...this.state }, opts.isThreeSession ?? false);
53138
53328
  }
53139
53329
  }
53140
- var _storyOrchestratorDeps, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES;
53330
+ var _storyOrchestratorDeps, TDD_OP_NAMES, STRICT_VERDICT_PHASE_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES;
53141
53331
  var init_story_orchestrator = __esm(() => {
53142
53332
  init_errors();
53143
53333
  init_findings();
@@ -53151,6 +53341,13 @@ var init_story_orchestrator = __esm(() => {
53151
53341
  captureGitRef
53152
53342
  };
53153
53343
  TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
53344
+ STRICT_VERDICT_PHASE_NAMES = new Set([
53345
+ fullSuiteGateOp.name,
53346
+ verifyScopedOp.name,
53347
+ lintCheckOp.name,
53348
+ typecheckCheckOp.name,
53349
+ verifierOp.name
53350
+ ]);
53154
53351
  CANONICAL_ORDER = [
53155
53352
  "test-writer",
53156
53353
  "greenfield-gate",
@@ -53392,24 +53589,63 @@ async function assemblePlanInputsFromCtx(ctx) {
53392
53589
  const verifyScopedInput = !_isTdd ? { workdir: ctx.workdir, storyId: story.id } : undefined;
53393
53590
  const lintCheckInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("lint") && ctx.config.quality.commands.lint ? { workdir: ctx.workdir, storyId: story.id } : undefined;
53394
53591
  const typecheckCheckInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("typecheck") && ctx.config.quality.commands.typecheck ? { workdir: ctx.workdir, storyId: story.id } : undefined;
53395
- const semanticReviewInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("semantic") && ctx.config.review.semantic ? {
53396
- workdir: ctx.workdir,
53397
- story,
53398
- semanticConfig: ctx.config.review.semantic,
53399
- mode: ctx.config.review.semantic.diffMode,
53400
- storyGitRef: ctx.storyGitRef,
53401
- featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
53402
- blockingThreshold: ctx.config.review.blockingThreshold
53403
- } : undefined;
53404
- const adversarialReviewInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("adversarial") && ctx.config.review.adversarial ? {
53405
- workdir: ctx.workdir,
53406
- story,
53407
- adversarialConfig: ctx.config.review.adversarial,
53408
- mode: ctx.config.review.adversarial.diffMode,
53409
- storyGitRef: ctx.storyGitRef,
53410
- featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
53411
- blockingThreshold: ctx.config.review.blockingThreshold
53412
- } : undefined;
53592
+ const semanticEnabled = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("semantic") && !!ctx.config.review.semantic;
53593
+ const semanticReviewInput = semanticEnabled ? await (async () => {
53594
+ const prepared = await prepareSemanticReviewInput({
53595
+ workdir: ctx.workdir,
53596
+ projectDir: ctx.projectDir,
53597
+ storyId: story.id,
53598
+ storyGitRef: ctx.storyGitRef,
53599
+ config: ctx.config,
53600
+ naxIgnoreIndex: ctx.naxIgnoreIndex,
53601
+ semanticConfig: ctx.config.review.semantic
53602
+ });
53603
+ if (prepared.skipReason)
53604
+ return;
53605
+ return {
53606
+ workdir: ctx.workdir,
53607
+ story,
53608
+ semanticConfig: ctx.config.review.semantic,
53609
+ mode: ctx.config.review.semantic.diffMode,
53610
+ storyGitRef: prepared.effectiveRef,
53611
+ stat: prepared.stat,
53612
+ diff: prepared.diff,
53613
+ excludePatterns: prepared.excludePatterns,
53614
+ featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
53615
+ priorSemanticIterations: ctx.priorSemanticIterations,
53616
+ blockingThreshold: ctx.config.review.blockingThreshold
53617
+ };
53618
+ })() : undefined;
53619
+ const adversarialEnabled = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("adversarial") && !!ctx.config.review.adversarial;
53620
+ const adversarialReviewInput = adversarialEnabled ? await (async () => {
53621
+ const prepared = await prepareAdversarialReviewInput({
53622
+ workdir: ctx.workdir,
53623
+ projectDir: ctx.projectDir,
53624
+ storyId: story.id,
53625
+ storyGitRef: ctx.storyGitRef,
53626
+ config: ctx.config,
53627
+ naxIgnoreIndex: ctx.naxIgnoreIndex,
53628
+ adversarialConfig: ctx.config.review.adversarial
53629
+ });
53630
+ if (prepared.skipReason)
53631
+ return;
53632
+ return {
53633
+ workdir: ctx.workdir,
53634
+ story,
53635
+ adversarialConfig: ctx.config.review.adversarial,
53636
+ mode: ctx.config.review.adversarial.diffMode,
53637
+ storyGitRef: prepared.effectiveRef,
53638
+ stat: prepared.stat,
53639
+ diff: prepared.diff,
53640
+ testInventory: prepared.testInventory,
53641
+ excludePatterns: prepared.excludePatterns,
53642
+ testGlobs: prepared.testGlobs,
53643
+ refExcludePatterns: prepared.refExcludePatterns,
53644
+ featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
53645
+ priorAdversarialIterations: ctx.priorAdversarialIterations,
53646
+ blockingThreshold: ctx.config.review.blockingThreshold
53647
+ };
53648
+ })() : undefined;
53413
53649
  const rectificationInput = ctx.config.execution?.rectification?.enabled === true ? {
53414
53650
  maxAttempts: ctx.config.execution.rectification.maxAttemptsTotal,
53415
53651
  strategies: [],
@@ -53435,23 +53671,28 @@ var init_plan_inputs = __esm(() => {
53435
53671
  init_context();
53436
53672
  init_errors();
53437
53673
  init_prompts();
53674
+ init_review();
53438
53675
  init_resolver();
53439
53676
  init_build_plan_for_strategy();
53440
53677
  });
53441
53678
 
53442
53679
  // src/pipeline/stages/execution-helpers.ts
53443
- function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
53680
+ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason, failureDetail) {
53681
+ const buildReason = (category) => {
53682
+ const trimmedDetail = failureDetail?.trim();
53683
+ return trimmedDetail ? `TDD ${category}: ${trimmedDetail}` : `TDD ${category}`;
53684
+ };
53444
53685
  if (failureCategory === "isolation-violation") {
53445
53686
  if (!isLiteMode) {
53446
53687
  ctx.retryAsLite = true;
53447
53688
  }
53448
- return { action: "escalate" };
53689
+ return { action: "escalate", reason: buildReason("isolation-violation") };
53449
53690
  }
53450
53691
  if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "full-suite-gate-exhausted" || failureCategory === "verifier-rejected") {
53451
- return { action: "escalate" };
53692
+ return { action: "escalate", reason: buildReason(failureCategory) };
53452
53693
  }
53453
53694
  if (failureCategory === "greenfield-no-tests") {
53454
- return { action: "escalate" };
53695
+ return { action: "escalate", reason: buildReason("greenfield-no-tests") };
53455
53696
  }
53456
53697
  return {
53457
53698
  action: "pause",
@@ -53905,7 +54146,16 @@ Category: ${failureCategory ?? "unknown"}`,
53905
54146
  logger.warn("execution", "Rate limited \u2014 will retry", { storyId: ctx.story.id });
53906
54147
  }
53907
54148
  await cleanupSessionOnFailure(ctx);
53908
- return { action: "escalate" };
54149
+ const failedPhaseNames = Object.keys(failedPhases);
54150
+ const reasonParts = [];
54151
+ reasonParts.push(`agent session failed (exit ${agentResult.exitCode ?? "?"})`);
54152
+ if (failureCategory)
54153
+ reasonParts.push(`category=${failureCategory}`);
54154
+ if (agentResult.rateLimited)
54155
+ reasonParts.push("rate-limited");
54156
+ if (failedPhaseNames.length > 0)
54157
+ reasonParts.push(`phases=${failedPhaseNames.join(",")}`);
54158
+ return { action: "escalate", reason: reasonParts.join("; ") };
53909
54159
  }
53910
54160
  if (!isTdd) {
53911
54161
  await _postRunDeps.autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
@@ -57730,7 +57980,7 @@ var package_default;
57730
57980
  var init_package = __esm(() => {
57731
57981
  package_default = {
57732
57982
  name: "@nathapp/nax",
57733
- version: "0.67.12",
57983
+ version: "0.67.13",
57734
57984
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
57735
57985
  type: "module",
57736
57986
  bin: {
@@ -57825,8 +58075,8 @@ var init_version = __esm(() => {
57825
58075
  NAX_VERSION = package_default.version;
57826
58076
  NAX_COMMIT = (() => {
57827
58077
  try {
57828
- if (/^[0-9a-f]{6,10}$/.test("c747dea2"))
57829
- return "c747dea2";
58078
+ if (/^[0-9a-f]{6,10}$/.test("45ff95d3"))
58079
+ return "45ff95d3";
57830
58080
  } catch {}
57831
58081
  try {
57832
58082
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -60593,13 +60843,16 @@ var init_tier_outcome = __esm(() => {
60593
60843
  });
60594
60844
 
60595
60845
  // src/execution/escalation/tier-escalation.ts
60596
- function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
60846
+ function buildEscalationFailure(story, currentTier, reviewFindings, cost, pipelineReason, failureCategory) {
60597
60847
  const stage = reviewFindings && reviewFindings.length > 0 ? "review" : "escalation";
60848
+ const trimmedReason = pipelineReason?.trim();
60849
+ const categoryPart = failureCategory ? ` [${failureCategory}]` : "";
60850
+ const summary = trimmedReason ? `Tier ${currentTier}${categoryPart}: ${trimmedReason}` : `Tier ${currentTier}${categoryPart} failed \u2014 no pipeline reason recorded`;
60598
60851
  return {
60599
60852
  attempt: (story.attempts ?? 0) + 1,
60600
60853
  modelTier: currentTier,
60601
60854
  stage,
60602
- summary: `Failed with tier ${currentTier}, escalating to next tier`,
60855
+ summary,
60603
60856
  reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
60604
60857
  cost: cost ?? 0,
60605
60858
  timestamp: new Date().toISOString()
@@ -60703,7 +60956,7 @@ async function handleTierEscalation(ctx) {
60703
60956
  const isChangingTier = currentStoryTier !== escalatedTier;
60704
60957
  const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
60705
60958
  const escalationRecord = isChangingTier || shouldSwitchToTestAfter ? buildEscalationRecord(currentStoryTier, shouldSwitchToTestAfter ? currentStoryTier : escalatedTier, ctx.pipelineResult.reason ?? "Escalated to next retry path") : undefined;
60706
- const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
60959
+ const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost, verifiedPipelineReason, escalateFailureCategory);
60707
60960
  return {
60708
60961
  ...s,
60709
60962
  attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.67.12",
3
+ "version": "0.67.13",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {