@nathapp/nax 0.63.0-canary.13 → 0.63.0-canary.14

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 +152 -20
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -33735,7 +33735,7 @@ function packChunks(chunks, budgetTokens, availableBudgetTokens) {
33735
33735
  }
33736
33736
  var FLOOR_KINDS;
33737
33737
  var init_packing = __esm(() => {
33738
- FLOOR_KINDS = ["static", "feature"];
33738
+ FLOOR_KINDS = ["static", "feature", "test-coverage"];
33739
33739
  });
33740
33740
 
33741
33741
  // src/utils/path-security.ts
@@ -34517,6 +34517,7 @@ var init_scoring = __esm(() => {
34517
34517
  KIND_WEIGHTS = {
34518
34518
  static: 1,
34519
34519
  feature: 1,
34520
+ "test-coverage": 1,
34520
34521
  session: 0.9,
34521
34522
  history: 0.8,
34522
34523
  neighbor: 0.75,
@@ -34561,8 +34562,8 @@ var init_stage_config = __esm(() => {
34561
34562
  PHASE_0_PROVIDERS = ["static-rules", "feature-context"];
34562
34563
  PHASE_1_PROVIDERS = ["static-rules", "feature-context", "session-scratch"];
34563
34564
  PHASE_3_TDD_TEST_WRITER = [...PHASE_1_PROVIDERS, "code-neighbor"];
34564
- PHASE_3_TDD_IMPLEMENTER = [...PHASE_1_PROVIDERS, "git-history", "code-neighbor"];
34565
- PHASE_3_EXECUTION = [...PHASE_1_PROVIDERS, "git-history", "code-neighbor"];
34565
+ PHASE_3_TDD_IMPLEMENTER = [...PHASE_1_PROVIDERS, "git-history", "code-neighbor", "test-coverage"];
34566
+ PHASE_3_EXECUTION = [...PHASE_1_PROVIDERS, "git-history", "code-neighbor", "test-coverage"];
34566
34567
  PHASE_3_RECTIFY = [...PHASE_1_PROVIDERS, "code-neighbor"];
34567
34568
  DEFAULT_STAGE_CONFIG = {
34568
34569
  role: "implementer",
@@ -34870,8 +34871,8 @@ class ContextOrchestrator {
34870
34871
  return matches ? { ...c, roleFiltered: false } : { ...c, roleFiltered: true };
34871
34872
  });
34872
34873
  const roleFiltered = postRoleFilter.filter((c) => c.roleFiltered);
34873
- const belowMin = postRoleFilter.filter((c) => !c.roleFiltered && c.belowMinScore);
34874
- const kept = postRoleFilter.filter((c) => !c.roleFiltered && !c.belowMinScore);
34874
+ const belowMin = postRoleFilter.filter((c) => !c.roleFiltered && c.belowMinScore && !FLOOR_KINDS.includes(c.kind));
34875
+ const kept = postRoleFilter.filter((c) => !c.roleFiltered && (!c.belowMinScore || FLOOR_KINDS.includes(c.kind)));
34875
34876
  const { packed, budgetExcludedIds, usedTokens, floorPackedIds, floorOverageIds } = packChunks(kept, effectiveBudgetTokens, request.availableBudgetTokens);
34876
34877
  const pushMarkdown = renderChunks(packed, {
34877
34878
  priorStageDigest: request.priorStageDigest
@@ -35814,6 +35815,82 @@ var init_static_rules = __esm(() => {
35814
35815
  LEGACY_CANDIDATE_FILES = ["CLAUDE.md", ".cursorrules", "AGENTS.md"];
35815
35816
  });
35816
35817
 
35818
+ // src/context/engine/providers/test-coverage.ts
35819
+ import { createHash as createHash8 } from "crypto";
35820
+ function contentHash86(content) {
35821
+ return createHash8("sha256").update(content).digest("hex").slice(0, 8);
35822
+ }
35823
+
35824
+ class TestCoverageProvider {
35825
+ story;
35826
+ config;
35827
+ id = "test-coverage";
35828
+ kind = "test-coverage";
35829
+ constructor(story, config2) {
35830
+ this.story = story;
35831
+ this.config = config2;
35832
+ }
35833
+ async fetch(request) {
35834
+ const tcConfig = this.config.context?.testCoverage;
35835
+ if (tcConfig?.enabled === false) {
35836
+ return { chunks: [], pullTools: [] };
35837
+ }
35838
+ if (!request.packageDir) {
35839
+ return { chunks: [], pullTools: [] };
35840
+ }
35841
+ try {
35842
+ const resolved = await _testCoverageProviderDeps.resolveTestFilePatterns(this.config, request.repoRoot, request.packageDir);
35843
+ const contextFiles = _testCoverageProviderDeps.getContextFiles(this.story);
35844
+ const globs = resolved.patterns ?? resolved.globs;
35845
+ const scanOptions = {
35846
+ workdir: request.packageDir,
35847
+ testDir: tcConfig.testDir,
35848
+ maxTokens: tcConfig.maxTokens ?? 500,
35849
+ detail: tcConfig.detail ?? "names-and-counts",
35850
+ scopeToStory: tcConfig.scopeToStory ?? true,
35851
+ contextFiles,
35852
+ resolvedTestGlobs: globs
35853
+ };
35854
+ const result = await _testCoverageProviderDeps.generateTestCoverageSummary(scanOptions);
35855
+ if (!result.summary) {
35856
+ return { chunks: [], pullTools: [] };
35857
+ }
35858
+ const hash2 = contentHash86(result.summary);
35859
+ const chunk = {
35860
+ id: `test-coverage:${hash2}`,
35861
+ kind: "test-coverage",
35862
+ scope: "story",
35863
+ role: ["implementer", "tdd"],
35864
+ content: result.summary,
35865
+ tokens: result.tokens,
35866
+ rawScore: 0.85
35867
+ };
35868
+ return { chunks: [chunk], pullTools: [] };
35869
+ } catch (err) {
35870
+ const logger = _testCoverageProviderDeps.getLogger();
35871
+ logger.warn("test-coverage", "Scanner failed \u2014 returning empty chunks", {
35872
+ storyId: this.story.id,
35873
+ packageDir: request.packageDir,
35874
+ error: errorMessage(err)
35875
+ });
35876
+ return { chunks: [], pullTools: [] };
35877
+ }
35878
+ }
35879
+ }
35880
+ var _testCoverageProviderDeps;
35881
+ var init_test_coverage = __esm(() => {
35882
+ init_logger2();
35883
+ init_prd();
35884
+ init_resolver();
35885
+ init_test_scanner();
35886
+ _testCoverageProviderDeps = {
35887
+ generateTestCoverageSummary,
35888
+ resolveTestFilePatterns,
35889
+ getLogger: () => getLogger(),
35890
+ getContextFiles
35891
+ };
35892
+ });
35893
+
35817
35894
  // src/context/engine/orchestrator-factory.ts
35818
35895
  function createDefaultOrchestrator(story, config2, _storyScratchDirs, additionalProviders = []) {
35819
35896
  const allowLegacyClaudeMd = config2.context?.v2?.rules?.allowLegacyClaudeMd ?? false;
@@ -35822,6 +35899,7 @@ function createDefaultOrchestrator(story, config2, _storyScratchDirs, additional
35822
35899
  new StaticRulesProvider({ allowLegacyClaudeMd, budgetTokens: rulesBudgetTokens }),
35823
35900
  new FeatureContextProviderV2(story, config2)
35824
35901
  ];
35902
+ providers.push(new TestCoverageProvider(story, config2));
35825
35903
  providers.push(new SessionScratchProvider);
35826
35904
  const providerConfig = config2.context?.v2?.providers;
35827
35905
  providers.push(new GitHistoryProvider({ historyScope: providerConfig?.historyScope ?? "package" }));
@@ -35840,6 +35918,7 @@ var init_orchestrator_factory = __esm(() => {
35840
35918
  init_git_history();
35841
35919
  init_session_scratch();
35842
35920
  init_static_rules();
35921
+ init_test_coverage();
35843
35922
  });
35844
35923
 
35845
35924
  // src/context/engine/providers/plugin-loader.ts
@@ -38306,6 +38385,9 @@ async function recheckReview(ctx) {
38306
38385
  function collectFailedChecks(ctx) {
38307
38386
  return (ctx.reviewResult?.checks ?? []).filter((c) => !c.success);
38308
38387
  }
38388
+ function getCheckSignature(checks3) {
38389
+ return [...new Set(checks3.map((check2) => check2.check))].sort().join("|");
38390
+ }
38309
38391
  function buildAutofixEscalationPreamble(attempt, maxAttempts, rethinkAtAttempt, urgencyAtAttempt) {
38310
38392
  return buildProgressivePromptPreamble({
38311
38393
  attempt,
@@ -38397,6 +38479,8 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
38397
38479
  const loopState = {
38398
38480
  attempt: 0,
38399
38481
  failedChecks: implementerChecks,
38482
+ checkSignature: getCheckSignature(implementerChecks),
38483
+ checkSignatureChanged: false,
38400
38484
  consecutiveNoOps: 0,
38401
38485
  lastWasNoOp: false
38402
38486
  };
@@ -38430,6 +38514,10 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
38430
38514
  }
38431
38515
  const isSessionContinuation = attempt > 1 && sessionConfirmedOpen;
38432
38516
  if (isSessionContinuation) {
38517
+ if (state.checkSignatureChanged) {
38518
+ const attemptsRemaining = Math.max(1, maxAttempts - attempt + 1);
38519
+ return RectifierPromptBuilder.firstAttemptDelta(state.failedChecks, attemptsRemaining);
38520
+ }
38433
38521
  return RectifierPromptBuilder.continuation(state.failedChecks, attempt, Math.min(rethinkAtAttempt, maxAttempts), Math.min(urgencyAtAttempt, maxAttempts));
38434
38522
  }
38435
38523
  let prompt = RectifierPromptBuilder.reviewRectification(state.failedChecks, ctx.story);
@@ -38587,7 +38675,12 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
38587
38675
  }
38588
38676
  }
38589
38677
  if (updatedFailed.length > 0) {
38590
- state.failedChecks.splice(0, state.failedChecks.length, ...collectFailedChecks(ctx));
38678
+ const updatedCheckSignature = getCheckSignature(updatedFailed);
38679
+ state.checkSignatureChanged = updatedCheckSignature !== state.checkSignature;
38680
+ state.checkSignature = updatedCheckSignature;
38681
+ state.failedChecks.splice(0, state.failedChecks.length, ...updatedFailed);
38682
+ } else {
38683
+ state.checkSignatureChanged = false;
38591
38684
  }
38592
38685
  return false;
38593
38686
  },
@@ -40094,7 +40187,14 @@ var init_isolation = __esm(() => {
40094
40187
  });
40095
40188
 
40096
40189
  // src/tdd/rectification-gate.ts
40097
- async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir) {
40190
+ async function getStoryChangedFiles(workdir, fromRef) {
40191
+ const result = await _rectificationGateDeps.executeWithTimeout(`git diff --name-only ${fromRef} HEAD`, 15, undefined, { cwd: workdir });
40192
+ if (!result.output)
40193
+ return new Set;
40194
+ return new Set(result.output.split(`
40195
+ `).map((l) => l.trim()).filter(Boolean));
40196
+ }
40197
+ async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, storyFromRef) {
40098
40198
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
40099
40199
  if (!rectificationEnabled)
40100
40200
  return { passed: false, cost: 0 };
@@ -40111,7 +40211,36 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
40111
40211
  if (!fullSuitePassed && fullSuiteResult.output) {
40112
40212
  const testSummary = _rectificationGateDeps.parseTestOutput(fullSuiteResult.output);
40113
40213
  if (testSummary.failed > 0) {
40114
- return await runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, effectiveTestCmd, fullSuiteTimeout, featureName, projectDir);
40214
+ let filteredFailures = testSummary.failures;
40215
+ if (storyFromRef && testSummary.failures.length > 0) {
40216
+ const storyFiles = await getStoryChangedFiles(workdir, storyFromRef);
40217
+ if (storyFiles.size > 0) {
40218
+ filteredFailures = testSummary.failures.filter((f) => storyFiles.has(f.file));
40219
+ }
40220
+ }
40221
+ const wasFiltered = filteredFailures.length < testSummary.failures.length;
40222
+ if (wasFiltered && filteredFailures.length === 0) {
40223
+ logger.info("tdd", "Full suite gate: all failures are pre-existing \u2014 accepting as pass", {
40224
+ storyId: story.id,
40225
+ suppressedCount: testSummary.failures.length,
40226
+ suppressedFiles: testSummary.failures.map((f) => f.file)
40227
+ });
40228
+ return { passed: true, cost: 0 };
40229
+ }
40230
+ if (wasFiltered) {
40231
+ logger.info("tdd", "Full suite gate: suppressed pre-existing failures", {
40232
+ storyId: story.id,
40233
+ total: testSummary.failures.length,
40234
+ suppressed: testSummary.failures.length - filteredFailures.length,
40235
+ remaining: filteredFailures.length
40236
+ });
40237
+ }
40238
+ const filteredSummary = {
40239
+ ...testSummary,
40240
+ failures: filteredFailures,
40241
+ failed: wasFiltered ? filteredFailures.length : testSummary.failed
40242
+ };
40243
+ return await runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, filteredSummary, rectificationConfig, effectiveTestCmd, fullSuiteTimeout, featureName, projectDir);
40115
40244
  }
40116
40245
  if (testSummary.passed > 0) {
40117
40246
  logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
@@ -40958,7 +41087,7 @@ async function runThreeSessionTdd(options) {
40958
41087
  lite
40959
41088
  };
40960
41089
  }
40961
- const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, wrapAdapterAsManager(agent), implementerTier, lite, logger, featureName, projectDir);
41090
+ const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, wrapAdapterAsManager(agent), implementerTier, lite, logger, featureName, projectDir, initialRef);
40962
41091
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
40963
41092
  const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
40964
41093
  const verifierBundle = await getTddContextBundle?.("verifier") ?? tddContextBundles?.verifier;
@@ -43370,7 +43499,7 @@ function coerceSmartTestRunner(val) {
43370
43499
  return DEFAULT_SMART_RUNNER_CONFIG2;
43371
43500
  if (val === false)
43372
43501
  return { ...DEFAULT_SMART_RUNNER_CONFIG2, enabled: false };
43373
- return val;
43502
+ return { ...DEFAULT_SMART_RUNNER_CONFIG2, ...val };
43374
43503
  }
43375
43504
  function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
43376
43505
  if (testScopedTemplate) {
@@ -43427,6 +43556,10 @@ var init_verify = __esm(() => {
43427
43556
  command: effectiveCommand
43428
43557
  });
43429
43558
  }
43559
+ } else if (!smartRunnerConfig.enabled) {
43560
+ logger.info("verify", "[smart-runner] Disabled by config", {
43561
+ storyId: ctx.story.id
43562
+ });
43430
43563
  } else if (smartRunnerConfig.enabled) {
43431
43564
  const repoRoot = ctx.projectDir ?? ctx.workdir;
43432
43565
  const resolvedPatterns = await _verifyDeps.resolveTestFilePatterns(ctx.config, repoRoot, ctx.story.workdir);
@@ -43463,15 +43596,13 @@ var init_verify = __esm(() => {
43463
43596
  }
43464
43597
  }
43465
43598
  if (isFullSuite && regressionMode === "deferred") {
43466
- logger.info("verify", "[smart-runner] No mapped tests \u2014 deferring full suite to run-end (mode: deferred)", {
43467
- storyId: ctx.story.id
43468
- });
43599
+ const message = !isMonorepoOrchestrator && !smartRunnerConfig.enabled ? "[smart-runner] Disabled by config \u2014 deferring full suite to run-end (mode: deferred)" : "[smart-runner] No mapped tests \u2014 deferring full suite to run-end (mode: deferred)";
43600
+ logger.info("verify", message, { storyId: ctx.story.id });
43469
43601
  return { action: "continue" };
43470
43602
  }
43471
43603
  if (isFullSuite) {
43472
- logger.info("verify", "[smart-runner] No mapped tests \u2014 falling back to full suite", {
43473
- storyId: ctx.story.id
43474
- });
43604
+ const message = !isMonorepoOrchestrator && !smartRunnerConfig.enabled ? "[smart-runner] Disabled by config \u2014 running full suite" : "[smart-runner] No mapped tests \u2014 falling back to full suite";
43605
+ logger.info("verify", message, { storyId: ctx.story.id });
43475
43606
  }
43476
43607
  logger.info("verify", isFullSuite ? "Running full suite" : "Running scoped tests", {
43477
43608
  storyId: ctx.story.id,
@@ -44687,7 +44818,7 @@ var package_default;
44687
44818
  var init_package = __esm(() => {
44688
44819
  package_default = {
44689
44820
  name: "@nathapp/nax",
44690
- version: "0.63.0-canary.13",
44821
+ version: "0.63.0-canary.14",
44691
44822
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
44692
44823
  type: "module",
44693
44824
  bin: {
@@ -44768,8 +44899,8 @@ var init_version = __esm(() => {
44768
44899
  NAX_VERSION = package_default.version;
44769
44900
  NAX_COMMIT = (() => {
44770
44901
  try {
44771
- if (/^[0-9a-f]{6,10}$/.test("69a5495a"))
44772
- return "69a5495a";
44902
+ if (/^[0-9a-f]{6,10}$/.test("374c8fbc"))
44903
+ return "374c8fbc";
44773
44904
  } catch {}
44774
44905
  try {
44775
44906
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -83991,7 +84122,8 @@ async function generateCommand(options) {
83991
84122
  const suffix = dryRun ? " (dry run)" : "";
83992
84123
  console.log(source_default.green(`\u2713 ${agent} \u2192 ${result.outputFile} (${result.content.length} bytes${suffix})`));
83993
84124
  } else {
83994
- let configAgents = config2?.generate?.agents;
84125
+ const projectNaxDir = findProjectDir(workdir);
84126
+ let configAgents = projectNaxDir ? config2?.generate?.agents : null;
83995
84127
  const misplacedAgents = config2?.autoMode?.generate;
83996
84128
  if (!configAgents && misplacedAgents?.agents && misplacedAgents.agents.length > 0) {
83997
84129
  console.warn(source_default.yellow('\u26A0 Warning: "generate.agents" is nested under "autoMode" in your config \u2014 it should be at the top level.'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.63.0-canary.13",
3
+ "version": "0.63.0-canary.14",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {