@nathapp/nax 0.70.0-canary.7 → 0.70.1

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 +148 -54
  2. package/package.json +2 -1
package/dist/nax.js CHANGED
@@ -17277,9 +17277,13 @@ var init_schemas_review = __esm(() => {
17277
17277
  }).optional(),
17278
17278
  nonBlockingFix: exports_external.object({
17279
17279
  enabled: exports_external.boolean().default(false),
17280
- scope: exports_external.enum(["source", "both"]).default("both"),
17280
+ scope: exports_external.enum(["source", "both", "triage"]).default("both"),
17281
17281
  regressionAttempts: exports_external.number().int().min(0).default(1),
17282
- verifierGuard: exports_external.boolean().default(true)
17282
+ verifierGuard: exports_external.boolean().default(true),
17283
+ sourceDiffCap: exports_external.object({
17284
+ maxFiles: exports_external.number().int().min(0).default(10),
17285
+ maxLines: exports_external.number().int().min(0).default(500)
17286
+ }).optional().default({ maxFiles: 10, maxLines: 500 })
17283
17287
  }).optional()
17284
17288
  });
17285
17289
  ReviewConfigSchema = exports_external.object({
@@ -32581,6 +32585,14 @@ var init_ac_structural_counterfactual = __esm(() => {
32581
32585
  BLOCKING_CATEGORIES = new Set(["input", "error-path", "abandonment", "assumption"]);
32582
32586
  });
32583
32587
 
32588
+ // src/review/category-fix-target.ts
32589
+ function categoryToFixTarget(category) {
32590
+ return category != null && BLOCKING_CATEGORIES.has(category) ? "source" : "test";
32591
+ }
32592
+ var init_category_fix_target = __esm(() => {
32593
+ init_ac_structural_counterfactual();
32594
+ });
32595
+
32584
32596
  // src/review/severity.ts
32585
32597
  function isBlockingSeverity(sev, threshold = "error") {
32586
32598
  return (SEVERITY_RANK[sev] ?? 0) >= SEVERITY_RANK[threshold];
@@ -32637,12 +32649,13 @@ function toAdversarialReviewFindings(findings) {
32637
32649
  line: f.line,
32638
32650
  message: f.issue,
32639
32651
  suggestion: f.suggestion,
32640
- fixTarget: f.category === "test-gap" ? "test" : undefined,
32652
+ fixTarget: categoryToFixTarget(f.category),
32641
32653
  meta: Object.keys(metaExtras).length > 0 ? metaExtras : undefined
32642
32654
  };
32643
32655
  });
32644
32656
  }
32645
32657
  var init_adversarial_helpers = __esm(() => {
32658
+ init_category_fix_target();
32646
32659
  init_severity();
32647
32660
  });
32648
32661
 
@@ -33448,9 +33461,16 @@ function buildMeta(f, originalSeverity) {
33448
33461
  function findingCategory(f) {
33449
33462
  return "category" in f && f.category ? f.category : undefined;
33450
33463
  }
33464
+ function deriveFixTargetForReviewFinding(category, source) {
33465
+ if (source === "semantic-review" || source === "semantic-debate-review") {
33466
+ return "source";
33467
+ }
33468
+ return categoryToFixTarget(category);
33469
+ }
33451
33470
  function llmFindingToReviewFinding(f, opts = {}) {
33452
33471
  const category = findingCategory(f);
33453
33472
  const narrowed = narrowSeverity(f.severity);
33473
+ const source = opts.source;
33454
33474
  const result = {
33455
33475
  ruleId: deriveRuleId(category, f.issue),
33456
33476
  severity: narrowed,
@@ -33460,8 +33480,9 @@ function llmFindingToReviewFinding(f, opts = {}) {
33460
33480
  };
33461
33481
  if (category)
33462
33482
  result.category = category;
33463
- if (opts.source)
33464
- result.source = opts.source;
33483
+ if (source)
33484
+ result.source = source;
33485
+ result.fixTarget = deriveFixTargetForReviewFinding(category, source);
33465
33486
  const meta3 = buildMeta(f, f.severity !== narrowed ? f.severity : undefined);
33466
33487
  if (meta3)
33467
33488
  result.meta = meta3;
@@ -33472,6 +33493,7 @@ function llmFindingsToReviewFindings(findings, opts = {}) {
33472
33493
  }
33473
33494
  var SEVERITY_MAP, RULE_ID_SLUG_TOKENS = 6;
33474
33495
  var init_finding_projection = __esm(() => {
33496
+ init_category_fix_target();
33475
33497
  SEVERITY_MAP = {
33476
33498
  critical: "critical",
33477
33499
  error: "error",
@@ -39375,9 +39397,10 @@ var init__finding_to_check = __esm(() => {
39375
39397
  // src/operations/autofix-implementer-strategy.ts
39376
39398
  function makeAutofixImplementerStrategy(story, config2, sink, opts = {}) {
39377
39399
  const claimsAdversarial = opts.includeAdversarialReview === true;
39400
+ const adversarialReviewByFixTarget = opts.adversarialReviewByFixTarget;
39378
39401
  return {
39379
39402
  name: "autofix-implementer",
39380
- appliesTo: (f) => (f.fixTarget === "source" || f.fixTarget == null) && IMPLEMENTER_SOURCES.has(f.source) || claimsAdversarial && f.source === "adversarial-review",
39403
+ appliesTo: (f) => (f.fixTarget === "source" || f.fixTarget == null) && IMPLEMENTER_SOURCES.has(f.source) || adversarialReviewByFixTarget !== undefined && f.source === "adversarial-review" && f.fixTarget === adversarialReviewByFixTarget || adversarialReviewByFixTarget === undefined && claimsAdversarial && f.source === "adversarial-review",
39381
39404
  fixOp: implementerRectifyOp,
39382
39405
  buildInput: (findings, _prior, _cycleCtx) => ({
39383
39406
  failedChecks: findingsToFailedChecks(findings),
@@ -39409,10 +39432,11 @@ var init_autofix_implementer_strategy = __esm(() => {
39409
39432
  });
39410
39433
 
39411
39434
  // src/operations/autofix-test-writer-strategy.ts
39412
- function makeAutofixTestWriterStrategy(story, config2, sink) {
39435
+ function makeAutofixTestWriterStrategy(story, config2, sink, opts = {}) {
39436
+ const includeAdversarial = opts.includeAdversarialReview !== false;
39413
39437
  return {
39414
39438
  name: "autofix-test-writer",
39415
- appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review" || sink.mockHandoffs.length > 0,
39439
+ appliesTo: (f) => f.fixTarget === "test" || includeAdversarial && f.source === "adversarial-review" || sink.mockHandoffs.length > 0,
39416
39440
  fixOp: testWriterRectifyOp,
39417
39441
  buildInput: (findings, _prior, _cycleCtx) => {
39418
39442
  if (sink.mockHandoffs.length > 0) {
@@ -42307,6 +42331,7 @@ var init_runner2 = __esm(() => {
42307
42331
  // src/review/index.ts
42308
42332
  var init_review = __esm(() => {
42309
42333
  init_semantic_helpers();
42334
+ init_category_fix_target();
42310
42335
  init_ac_quote_validator();
42311
42336
  init_ac_structural_counterfactual();
42312
42337
  init_adversarial();
@@ -54815,6 +54840,26 @@ var init_rollback = __esm(() => {
54815
54840
  };
54816
54841
  });
54817
54842
 
54843
+ // src/utils/paths.ts
54844
+ import { join as join47, relative as relative13, sep as sep4 } from "path";
54845
+ function packageDirRelative(projectDir, workdir) {
54846
+ if (!projectDir || !workdir || workdir === projectDir)
54847
+ return;
54848
+ const rel = relative13(projectDir, workdir);
54849
+ if (rel === ".." || rel.startsWith(`..${sep4}`))
54850
+ return;
54851
+ return rel && rel !== "." ? rel : undefined;
54852
+ }
54853
+ function getRunsDir() {
54854
+ return process.env.NAX_RUNS_DIR ?? join47(globalConfigDir(), "runs");
54855
+ }
54856
+ function getEventsRootDir() {
54857
+ return join47(globalConfigDir(), "events");
54858
+ }
54859
+ var init_paths3 = __esm(() => {
54860
+ init_paths();
54861
+ });
54862
+
54818
54863
  // src/execution/non-blocking-fix.ts
54819
54864
  function shouldRunNonBlockingFix(cfg, advisoryCount) {
54820
54865
  return cfg?.enabled === true && advisoryCount > 0;
@@ -54823,9 +54868,42 @@ function nonBlockingExcludePhases() {
54823
54868
  return REVIEW_PHASE_KINDS;
54824
54869
  }
54825
54870
  function nonBlockingExtraPhases(cfg) {
54826
- return cfg.scope === "both" && cfg.verifierGuard ? ["verifier"] : [];
54871
+ return (cfg.scope === "both" || cfg.scope === "triage") && cfg.verifierGuard ? ["verifier"] : [];
54872
+ }
54873
+ function createMeasureSourceDiff(args) {
54874
+ const packageDirRel = packageDirRelative(args.projectDir, args.packageDir);
54875
+ return async (workdir, fromRef) => {
54876
+ const resolved = await _nonBlockingFixDeps.resolveTestFilePatterns(args.config, args.projectDir, packageDirRel);
54877
+ const isTestFile3 = createTestFileClassifier(resolved);
54878
+ const proc = _nonBlockingFixDeps.spawn(["git", "diff", "--numstat", fromRef], {
54879
+ cwd: workdir,
54880
+ stdout: "pipe",
54881
+ stderr: "pipe"
54882
+ });
54883
+ const stdout = await Bun.readableStreamToText(proc.stdout);
54884
+ const stderr = await Bun.readableStreamToText(proc.stderr);
54885
+ const exitCode = await proc.exited;
54886
+ if (exitCode !== 0) {
54887
+ const detail = stderr.trim() || `exit ${exitCode}`;
54888
+ throw new Error(`[non-blocking-fix] git diff --numstat failed: ${detail}`);
54889
+ }
54890
+ let fileCount = 0;
54891
+ let sourceLineCount = 0;
54892
+ for (const line of stdout.trim().split(`
54893
+ `).filter(Boolean)) {
54894
+ const [addedStr, _deletedStr, filePath] = line.split("\t");
54895
+ if (!filePath || isTestFile3(filePath))
54896
+ continue;
54897
+ fileCount += 1;
54898
+ const added = Number.parseInt(addedStr ?? "", 10);
54899
+ if (Number.isFinite(added))
54900
+ sourceLineCount += added;
54901
+ }
54902
+ return { fileCount, sourceLineCount };
54903
+ };
54827
54904
  }
54828
- async function runNonBlockingFix(args, _deps = DEFAULT_DEPS) {
54905
+ async function runNonBlockingFix(args, overrides = {}) {
54906
+ const _deps = { ...DEFAULT_DEPS, ...overrides };
54829
54907
  const logger = getSafeLogger();
54830
54908
  if (!shouldRunNonBlockingFix(args.cfg, args.advisoryFindings.length)) {
54831
54909
  return { ran: false, kept: false, restored: false };
@@ -54845,9 +54923,34 @@ async function runNonBlockingFix(args, _deps = DEFAULT_DEPS) {
54845
54923
  exhausted = true;
54846
54924
  }
54847
54925
  if (!exhausted) {
54926
+ const cap = args.cfg.sourceDiffCap;
54927
+ if (cap) {
54928
+ let metrics;
54929
+ try {
54930
+ metrics = await _deps.measureSourceDiff(args.workdir, restoreRef);
54931
+ } catch (err) {
54932
+ logger?.warn("non-blocking-fix", "source-diff measurement threw \u2014 restoring", {
54933
+ storyId: args.storyId,
54934
+ error: err instanceof Error ? err.message : String(err)
54935
+ });
54936
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, logger);
54937
+ }
54938
+ if (metrics.fileCount > cap.maxFiles || metrics.sourceLineCount > cap.maxLines) {
54939
+ logger?.info("non-blocking-fix", "source diff exceeded cap \u2014 restoring", {
54940
+ storyId: args.storyId,
54941
+ fileCount: metrics.fileCount,
54942
+ sourceLineCount: metrics.sourceLineCount,
54943
+ cap
54944
+ });
54945
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, logger);
54946
+ }
54947
+ }
54848
54948
  logger?.info("non-blocking-fix", "best-effort fix kept", { storyId: args.storyId });
54849
54949
  return { ran: true, kept: true, restored: false };
54850
54950
  }
54951
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, logger);
54952
+ }
54953
+ async function restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, logger) {
54851
54954
  await _deps.rollbackToRef(args.workdir, restoreRef);
54852
54955
  for (const key of Object.keys(args.phaseOutputs))
54853
54956
  delete args.phaseOutputs[key];
@@ -54857,12 +54960,23 @@ async function runNonBlockingFix(args, _deps = DEFAULT_DEPS) {
54857
54960
  });
54858
54961
  return { ran: true, kept: false, restored: true };
54859
54962
  }
54860
- var REVIEW_PHASE_KINDS, DEFAULT_DEPS;
54963
+ var REVIEW_PHASE_KINDS, _nonBlockingFixDeps, DEFAULT_DEPS;
54861
54964
  var init_non_blocking_fix = __esm(() => {
54862
54965
  init_logger2();
54863
54966
  init_rollback();
54967
+ init_test_runners();
54968
+ init_bun_deps();
54969
+ init_paths3();
54864
54970
  REVIEW_PHASE_KINDS = ["semantic-review", "adversarial-review"];
54865
- DEFAULT_DEPS = { captureSnapshotRef, rollbackToRef };
54971
+ _nonBlockingFixDeps = {
54972
+ spawn: typedSpawn,
54973
+ resolveTestFilePatterns
54974
+ };
54975
+ DEFAULT_DEPS = {
54976
+ captureSnapshotRef,
54977
+ rollbackToRef,
54978
+ measureSourceDiff: async () => ({ fileCount: 0, sourceLineCount: 0 })
54979
+ };
54866
54980
  });
54867
54981
 
54868
54982
  // src/execution/story-orchestrator-logging.ts
@@ -55542,7 +55656,7 @@ class ExecutionPlan {
55542
55656
  const advisoryOut = phaseOutputs["adversarial-review"];
55543
55657
  const advisoryFindings = advisoryOut?.advisoryFindings ?? [];
55544
55658
  if (advCfg && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
55545
- await runNonBlockingFix({
55659
+ await _storyOrchestratorDeps.runNonBlockingFix({
55546
55660
  workdir: this.ctx.packageDir,
55547
55661
  storyId: this.ctx.storyId,
55548
55662
  advisoryFindings,
@@ -55556,6 +55670,12 @@ class ExecutionPlan {
55556
55670
  maxAttempts,
55557
55671
  postValidate: this.state.nonBlockingFixPostValidate
55558
55672
  })
55673
+ }, {
55674
+ measureSourceDiff: createMeasureSourceDiff({
55675
+ config: this.ctx.runtime.configLoader.current(),
55676
+ projectDir: this.ctx.runtime.projectDir,
55677
+ packageDir: this.ctx.packageDir
55678
+ })
55559
55679
  });
55560
55680
  }
55561
55681
  const verifierName = this.state.verifier?.slot.op.name;
@@ -55684,7 +55804,8 @@ var init_story_orchestrator = __esm(() => {
55684
55804
  runFixCycle,
55685
55805
  captureGitRef,
55686
55806
  prepareSemanticReviewInput,
55687
- prepareAdversarialReviewInput
55807
+ prepareAdversarialReviewInput,
55808
+ runNonBlockingFix
55688
55809
  };
55689
55810
  EXHAUSTED_EXIT_REASONS = new Set([
55690
55811
  "max-attempts-total",
@@ -55743,7 +55864,7 @@ var init_story_orchestrator = __esm(() => {
55743
55864
  });
55744
55865
 
55745
55866
  // src/execution/build-plan-for-strategy.ts
55746
- import { join as join47 } from "path";
55867
+ import { join as join48 } from "path";
55747
55868
  function requiresInitialRefCapture(strategy) {
55748
55869
  return isThreeSessionStrategy(strategy);
55749
55870
  }
@@ -55787,7 +55908,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
55787
55908
  if (inputs.adversarialReview) {
55788
55909
  builder.addAdversarialReview(inputs.adversarialReview);
55789
55910
  }
55790
- const packageDir = join47(ctx.packageDir, story.workdir ?? "");
55911
+ const packageDir = join48(ctx.packageDir, story.workdir ?? "");
55791
55912
  const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
55792
55913
  if (shouldRunRectification(config2) && inputs.rectification) {
55793
55914
  const sink = makeDeclarationSink();
@@ -55842,6 +55963,12 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
55842
55963
  nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
55843
55964
  includeAdversarialReview: true
55844
55965
  }));
55966
+ } else if (nbf.scope === "triage") {
55967
+ nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
55968
+ adversarialReviewByFixTarget: "source"
55969
+ }), makeAutofixTestWriterStrategy(story, config2, nbSink, {
55970
+ includeAdversarialReview: false
55971
+ }));
55845
55972
  } else {
55846
55973
  nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
55847
55974
  includeAdversarialReview: false
@@ -55876,26 +56003,6 @@ var init_build_plan_for_strategy = __esm(() => {
55876
56003
  init_story_orchestrator();
55877
56004
  });
55878
56005
 
55879
- // src/utils/paths.ts
55880
- import { join as join48, relative as relative13, sep as sep4 } from "path";
55881
- function packageDirRelative(projectDir, workdir) {
55882
- if (!projectDir || !workdir || workdir === projectDir)
55883
- return;
55884
- const rel = relative13(projectDir, workdir);
55885
- if (rel === ".." || rel.startsWith(`..${sep4}`))
55886
- return;
55887
- return rel && rel !== "." ? rel : undefined;
55888
- }
55889
- function getRunsDir() {
55890
- return process.env.NAX_RUNS_DIR ?? join48(globalConfigDir(), "runs");
55891
- }
55892
- function getEventsRootDir() {
55893
- return join48(globalConfigDir(), "events");
55894
- }
55895
- var init_paths3 = __esm(() => {
55896
- init_paths();
55897
- });
55898
-
55899
56006
  // src/execution/plan-inputs.ts
55900
56007
  function validatePlanInputs(story, config2) {
55901
56008
  if (!story.id || story.id.trim() === "") {
@@ -60469,7 +60576,7 @@ var package_default;
60469
60576
  var init_package = __esm(() => {
60470
60577
  package_default = {
60471
60578
  name: "@nathapp/nax",
60472
- version: "0.70.0-canary.7",
60579
+ version: "0.70.1",
60473
60580
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
60474
60581
  type: "module",
60475
60582
  bin: {
@@ -60501,6 +60608,7 @@ var init_package = __esm(() => {
60501
60608
  "test:unit": "bun test ./test/unit/ --timeout=60000",
60502
60609
  "test:integration": "bun test ./test/integration/ --timeout=60000",
60503
60610
  "test:ui": "bun test ./test/ui/ --timeout=60000",
60611
+ "test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
60504
60612
  "check-test-overlap": "bun run scripts/check-test-overlap.ts",
60505
60613
  "check-dead-tests": "bun run scripts/check-dead-tests.ts",
60506
60614
  "check:test-sizes": "bun run scripts/check-test-sizes.ts",
@@ -60564,8 +60672,8 @@ var init_version = __esm(() => {
60564
60672
  NAX_VERSION = package_default.version;
60565
60673
  NAX_COMMIT = (() => {
60566
60674
  try {
60567
- if (/^[0-9a-f]{6,10}$/.test("08567b02"))
60568
- return "08567b02";
60675
+ if (/^[0-9a-f]{6,10}$/.test("c53be9bf"))
60676
+ return "c53be9bf";
60569
60677
  } catch {}
60570
60678
  try {
60571
60679
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -61546,20 +61654,6 @@ async function runDeferredRegression(options) {
61546
61654
  storyOutcomes: {}
61547
61655
  };
61548
61656
  }
61549
- if (regressionMode !== "deferred") {
61550
- logger?.info("regression", "Regression gate mode is not deferred, skipping");
61551
- return {
61552
- success: true,
61553
- failedTests: 0,
61554
- failedTestFiles: [],
61555
- passedTests: 0,
61556
- rectificationAttempts: 0,
61557
- affectedStories: [],
61558
- storyCosts: {},
61559
- storyDurations: {},
61560
- storyOutcomes: {}
61561
- };
61562
- }
61563
61657
  const testCommand = config2.quality.commands.test ?? "bun test";
61564
61658
  const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
61565
61659
  const maxRectificationAttempts = config2.execution.rectification.maxAttemptsTotal;
@@ -61867,7 +61961,7 @@ async function handleRunCompletion(options) {
61867
61961
  hooksConfig
61868
61962
  } = options;
61869
61963
  const regressionMode = config2.execution.regressionGate?.mode;
61870
- if (options.skipRegression) {} else if (regressionMode === "deferred" && config2.quality.commands.test) {
61964
+ if (options.skipRegression) {} else if ((regressionMode === "deferred" || regressionMode === "per-story") && config2.quality.commands.test) {
61871
61965
  statusWriter.setPostRunPhase("regression", { status: "running" });
61872
61966
  pipelineEventBus.emit({ type: "postrun:phase:started", phase: "regression" });
61873
61967
  const regressionResult = await _runCompletionDeps.runDeferredRegression({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.70.0-canary.7",
3
+ "version": "0.70.1",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "test:unit": "bun test ./test/unit/ --timeout=60000",
33
33
  "test:integration": "bun test ./test/integration/ --timeout=60000",
34
34
  "test:ui": "bun test ./test/ui/ --timeout=60000",
35
+ "test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
35
36
  "check-test-overlap": "bun run scripts/check-test-overlap.ts",
36
37
  "check-dead-tests": "bun run scripts/check-dead-tests.ts",
37
38
  "check:test-sizes": "bun run scripts/check-test-sizes.ts",