@nathapp/nax 0.67.1 → 0.67.3

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 +86 -15
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -35108,6 +35108,24 @@ async function getChangedFiles(workdir, fromRef = "HEAD") {
35108
35108
  return output.trim().split(`
35109
35109
  `).filter(Boolean);
35110
35110
  }
35111
+ async function getAddedLinesPerFile(workdir, fromRef = "HEAD") {
35112
+ const proc = _isolationDeps.spawn(["git", "diff", "--numstat", fromRef], {
35113
+ cwd: workdir,
35114
+ stdout: "pipe",
35115
+ stderr: "pipe"
35116
+ });
35117
+ const output = await Bun.readableStreamToText(proc.stdout);
35118
+ await proc.exited;
35119
+ const result = new Map;
35120
+ for (const line of output.trim().split(`
35121
+ `).filter(Boolean)) {
35122
+ const [addedStr, _deletedStr, path4] = line.split("\t");
35123
+ const added = Number.parseInt(addedStr ?? "", 10);
35124
+ if (path4 && Number.isFinite(added))
35125
+ result.set(path4, added);
35126
+ }
35127
+ return result;
35128
+ }
35111
35129
  function matchesAllowedPath(filePath, allowedPaths) {
35112
35130
  return allowedPaths.some((pattern) => {
35113
35131
  const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
@@ -35115,17 +35133,25 @@ function matchesAllowedPath(filePath, allowedPaths) {
35115
35133
  return regex.test(filePath);
35116
35134
  });
35117
35135
  }
35118
- async function verifyTestWriterIsolation(workdir, beforeRef, allowedPaths = ["src/index.ts", "src/**/index.ts"], testFilePatterns = DEFAULT_TEST_FILE_PATTERNS) {
35136
+ async function verifyTestWriterIsolation(workdir, beforeRef, allowedPaths = ["src/index.ts", "src/**/index.ts"], testFilePatterns = DEFAULT_TEST_FILE_PATTERNS, mode = "strict") {
35119
35137
  const changed = await getChangedFiles(workdir, beforeRef);
35120
35138
  const sourceFiles = changed.filter((f) => isSourceFile(f) && !isTestFileByPatterns(f, testFilePatterns));
35139
+ const addedLines = mode === "lite" && sourceFiles.length > 0 ? await getAddedLinesPerFile(workdir, beforeRef) : null;
35121
35140
  const softViolations = [];
35122
35141
  const violations = [];
35123
35142
  for (const file3 of sourceFiles) {
35124
35143
  if (matchesAllowedPath(file3, allowedPaths)) {
35125
35144
  softViolations.push(file3);
35126
- } else {
35127
- violations.push(file3);
35145
+ continue;
35128
35146
  }
35147
+ if (addedLines) {
35148
+ const added = addedLines.get(file3) ?? Number.POSITIVE_INFINITY;
35149
+ if (added <= LITE_STUB_ADDED_LINES_CEILING) {
35150
+ softViolations.push(file3);
35151
+ continue;
35152
+ }
35153
+ }
35154
+ violations.push(file3);
35129
35155
  }
35130
35156
  return {
35131
35157
  passed: violations.length === 0,
@@ -35151,7 +35177,7 @@ async function verifyImplementerIsolation(workdir, beforeRef, testFilePatterns =
35151
35177
  description: "Implementer should not modify test files"
35152
35178
  };
35153
35179
  }
35154
- var _isolationDeps, SRC_PATTERNS;
35180
+ var _isolationDeps, SRC_PATTERNS, LITE_STUB_ADDED_LINES_CEILING = 20;
35155
35181
  var init_isolation = __esm(() => {
35156
35182
  init_test_runners();
35157
35183
  init_bun_deps();
@@ -35235,7 +35261,7 @@ var init_write_test = __esm(() => {
35235
35261
  return parsed;
35236
35262
  const allowedPaths = ctx.config.tdd?.testWriterAllowedPaths ?? ["src/index.ts", "src/**/index.ts"];
35237
35263
  const testFilePatterns = typeof ctx.packageView.config.execution?.smartTestRunner === "object" && ctx.packageView.config.execution.smartTestRunner !== null ? ctx.packageView.config.execution.smartTestRunner.testFilePatterns : undefined;
35238
- const isolation = await verifyTestWriterIsolation(ctx.packageView.packageDir, input.beforeRef, allowedPaths, testFilePatterns);
35264
+ const isolation = await verifyTestWriterIsolation(ctx.packageView.packageDir, input.beforeRef, allowedPaths, testFilePatterns, input.lite ? "lite" : "strict");
35239
35265
  return { ...parsed, isolation };
35240
35266
  }
35241
35267
  };
@@ -51779,12 +51805,12 @@ async function assertionSiteDiffCheck(workdir, beforeRef, files) {
51779
51805
  }
51780
51806
  return { violated: false };
51781
51807
  }
51782
- async function runIsolationGuard(workdir, beforeRef, config2, packageDir) {
51808
+ async function runIsolationGuard(workdir, beforeRef, config2, packageDir, mode = "strict") {
51783
51809
  if (config2.quality.autofix?.enforceTestWriterIsolation === false) {
51784
51810
  return { violated: false, skipped: true };
51785
51811
  }
51786
51812
  const resolved = await resolveTestFilePatterns(config2, workdir, packageDir);
51787
- const result = await _guardDeps.verifyTestWriterIsolation(workdir, beforeRef, config2.tdd?.testWriterAllowedPaths, resolved.globs);
51813
+ const result = await _guardDeps.verifyTestWriterIsolation(workdir, beforeRef, config2.tdd?.testWriterAllowedPaths, resolved.globs, mode);
51788
51814
  if (!result.passed) {
51789
51815
  return { violated: true, files: result.violations ?? [] };
51790
51816
  }
@@ -52129,7 +52155,7 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
52129
52155
  });
52130
52156
  return { unresolved: `assertion_weakening:${assertionResult.file}:${assertionResult.line}` };
52131
52157
  }
52132
- const isolationResult = await _autofixCycleGuardDeps.runIsolationGuard(ctx.workdir, beforeRef, ctx.config, ctx.story.workdir || undefined);
52158
+ const isolationResult = await _autofixCycleGuardDeps.runIsolationGuard(ctx.workdir, beforeRef, ctx.config, ctx.story.workdir || undefined, ctx.routing?.testStrategy === "three-session-tdd-lite" ? "lite" : "strict");
52133
52159
  if (isolationResult.violated) {
52134
52160
  await _autofixCycleGuardDeps.revertDiff(ctx.workdir, isolationResult.files);
52135
52161
  logger.info("autofix-cycle", "test-writer isolation guard violated \u2014 reverted", {
@@ -54185,6 +54211,12 @@ function collectOrderedPhases(state) {
54185
54211
  return [];
54186
54212
  });
54187
54213
  }
54214
+ function phaseExplicitlyPassed(output) {
54215
+ if (output === null || output === undefined || typeof output !== "object")
54216
+ return false;
54217
+ const r = output;
54218
+ return r.success === true || r.passed === true;
54219
+ }
54188
54220
  function phasePassed(opName, output) {
54189
54221
  if (output === null || output === undefined) {
54190
54222
  getSafeLogger()?.warn("story-orchestrator", "Phase produced no output \u2014 treating as pass", {
@@ -54368,10 +54400,13 @@ class ExecutionPlan {
54368
54400
  const phaseOutputs = {};
54369
54401
  const startedAt = Date.now();
54370
54402
  const logger = getSafeLogger();
54371
- const shortCircuitExempt = this.state.rectification ? new Set([
54403
+ const verifierPresent = this.state.verifier !== undefined;
54404
+ const rectificationExempt = this.state.rectification ? [
54372
54405
  ...this.state.fullSuiteGate ? [this.state.fullSuiteGate.slot.op.name] : [],
54373
54406
  ...this.state.verifier ? [this.state.verifier.slot.op.name] : []
54374
- ]) : new Set;
54407
+ ] : [];
54408
+ const verifierExempt = verifierPresent && this.state.fullSuiteGate ? [this.state.fullSuiteGate.slot.op.name] : [];
54409
+ const shortCircuitExempt = new Set([...rectificationExempt, ...verifierExempt]);
54375
54410
  for (const phase of collectOrderedPhases(this.state)) {
54376
54411
  try {
54377
54412
  await runPhase(this.ctx, phase.slot, phaseCosts, phaseOutputs, this.isThreeSession);
@@ -54390,7 +54425,17 @@ class ExecutionPlan {
54390
54425
  }
54391
54426
  }
54392
54427
  await runRectification(this.ctx, this.state, phaseCosts, phaseOutputs);
54393
- const success2 = Object.entries(phaseOutputs).every(([name, output]) => phasePassed(name, output));
54428
+ const verifierName = this.state.verifier?.slot.op.name;
54429
+ const gateName = this.state.fullSuiteGate?.slot.op.name;
54430
+ const verifierPassedSsot = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
54431
+ if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName])) {
54432
+ 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 });
54433
+ }
54434
+ const success2 = Object.entries(phaseOutputs).every(([name, output]) => {
54435
+ if (verifierPassedSsot && name === gateName)
54436
+ return true;
54437
+ return phasePassed(name, output);
54438
+ });
54394
54439
  const totalCostUsd = Object.values(phaseCosts).reduce((sum, cost) => sum + cost, 0);
54395
54440
  return {
54396
54441
  success: success2,
@@ -54599,7 +54644,8 @@ async function assemblePlanInputsFromCtx(ctx) {
54599
54644
  story,
54600
54645
  promptMarkdown: testWriterPrompt,
54601
54646
  featureContextMarkdown: ctx.featureContextMarkdown,
54602
- constitution: ctx.constitution?.content
54647
+ constitution: ctx.constitution?.content,
54648
+ lite: isLite
54603
54649
  } : undefined;
54604
54650
  const greenfieldGateInput = _isTdd && _isFreshRun && resolvedTestPatterns ? { story, workdir: ctx.workdir, resolvedTestPatterns } : undefined;
54605
54651
  const implementerInput = {
@@ -54800,6 +54846,13 @@ function deriveTddFailureCategory(phaseOutputs) {
54800
54846
  }
54801
54847
  return "tests-failing";
54802
54848
  }
54849
+ const verifierPassed = verifierOutput?.success === true;
54850
+ if (!verifierPassed) {
54851
+ const gateOutput = phaseOutputs[fullSuiteGateOp.name];
54852
+ if (gateOutput && (gateOutput.success === false || gateOutput.passed === false)) {
54853
+ return "tests-failing";
54854
+ }
54855
+ }
54803
54856
  const implOutput = phaseOutputs[implementerOp.name];
54804
54857
  if (implOutput?.success === false) {
54805
54858
  return "session-failure";
@@ -54898,6 +54951,24 @@ async function applyPostRunInspection(ctx, planResult, opts) {
54898
54951
  }
54899
54952
  const pauseReason = extractPauseReason(planResult.phaseOutputs);
54900
54953
  const failureCategory = isTdd && !planResult.success ? deriveTddFailureCategory(planResult.phaseOutputs) : undefined;
54954
+ if (isTdd && !planResult.success && !failureCategory) {
54955
+ const phaseSignals = {};
54956
+ for (const [name, output] of Object.entries(planResult.phaseOutputs)) {
54957
+ if (output && typeof output === "object") {
54958
+ const r = output;
54959
+ const signal = {};
54960
+ if (typeof r.success === "boolean")
54961
+ signal.success = r.success;
54962
+ if (typeof r.passed === "boolean")
54963
+ signal.passed = r.passed;
54964
+ phaseSignals[name] = signal;
54965
+ }
54966
+ }
54967
+ logger.warn("execution", "TDD plan failed but no failure category derived \u2014 defaulting to pause", {
54968
+ storyId: ctx.story.id,
54969
+ phaseSignals
54970
+ });
54971
+ }
54901
54972
  const tddIsolations = {};
54902
54973
  for (const opName of ["test-writer", "implementer", "verifier"]) {
54903
54974
  const phaseOut = planResult.phaseOutputs[opName];
@@ -59182,7 +59253,7 @@ var package_default;
59182
59253
  var init_package = __esm(() => {
59183
59254
  package_default = {
59184
59255
  name: "@nathapp/nax",
59185
- version: "0.67.1",
59256
+ version: "0.67.3",
59186
59257
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
59187
59258
  type: "module",
59188
59259
  bin: {
@@ -59277,8 +59348,8 @@ var init_version = __esm(() => {
59277
59348
  NAX_VERSION = package_default.version;
59278
59349
  NAX_COMMIT = (() => {
59279
59350
  try {
59280
- if (/^[0-9a-f]{6,10}$/.test("be0f8521"))
59281
- return "be0f8521";
59351
+ if (/^[0-9a-f]{6,10}$/.test("bcfe96ad"))
59352
+ return "bcfe96ad";
59282
59353
  } catch {}
59283
59354
  try {
59284
59355
  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.1",
3
+ "version": "0.67.3",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {