@nathapp/nax 0.63.0-canary.3 → 0.63.0-canary.4

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 +137 -20
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -32984,17 +32984,82 @@ function resolveImport(specifier, fromFile, workdir) {
32984
32984
  }
32985
32985
  return null;
32986
32986
  }
32987
- function siblingTestPath(filePath) {
32988
- const srcMatch = filePath.match(/^src\/(.+)\.(ts|tsx|js|jsx)$/);
32989
- if (!srcMatch)
32987
+ function decomposeTestGlob(pattern) {
32988
+ const lastStar = pattern.lastIndexOf("*");
32989
+ if (lastStar === -1)
32990
32990
  return null;
32991
- const base = srcMatch[1] ?? "";
32992
- if (base.endsWith(".test") || base.endsWith(".spec"))
32991
+ const suffix = pattern.slice(lastStar + 1);
32992
+ if (suffix.length === 0)
32993
32993
  return null;
32994
- const ext = srcMatch[2] ?? "ts";
32995
- return `test/unit/${base}.test.${ext}`;
32994
+ const firstStar = pattern.indexOf("*");
32995
+ let prefix = pattern.slice(0, firstStar);
32996
+ if (prefix.endsWith("/"))
32997
+ prefix = prefix.slice(0, -1);
32998
+ return { prefix, suffix };
32999
+ }
33000
+ function deriveSiblingTestCandidates(filePath, patterns) {
33001
+ const srcExtMatch = filePath.match(/\.[^.]+$/);
33002
+ if (!srcExtMatch)
33003
+ return [];
33004
+ const srcExt = srcExtMatch[0];
33005
+ const stemWithPath = filePath.slice(0, -srcExt.length);
33006
+ for (const pattern of patterns) {
33007
+ const decomposed = decomposeTestGlob(pattern);
33008
+ if (decomposed && filePath.endsWith(decomposed.suffix))
33009
+ return [];
33010
+ if (decomposed) {
33011
+ const markerFromSuffix = stripExt(decomposed.suffix);
33012
+ if (markerFromSuffix.length > 0 && stemWithPath.endsWith(markerFromSuffix))
33013
+ return [];
33014
+ }
33015
+ }
33016
+ if (stemWithPath.endsWith(".test") || stemWithPath.endsWith(".spec"))
33017
+ return [];
33018
+ const srcPrefixed = stemWithPath.startsWith("src/");
33019
+ const srcInMiddleIdx = stemWithPath.indexOf("/src/");
33020
+ let innerStem = null;
33021
+ let pkgPrefix = "";
33022
+ if (srcPrefixed) {
33023
+ innerStem = stemWithPath.slice("src/".length);
33024
+ } else if (srcInMiddleIdx >= 0) {
33025
+ pkgPrefix = `${stemWithPath.slice(0, srcInMiddleIdx)}/`;
33026
+ innerStem = stemWithPath.slice(srcInMiddleIdx + "/src/".length);
33027
+ }
33028
+ const candidates = [];
33029
+ const seen = new Set;
33030
+ const push = (path8) => {
33031
+ if (path8 === filePath)
33032
+ return;
33033
+ if (!seen.has(path8)) {
33034
+ seen.add(path8);
33035
+ candidates.push(path8);
33036
+ }
33037
+ };
33038
+ for (const pattern of patterns) {
33039
+ const decomposed = decomposeTestGlob(pattern);
33040
+ if (!decomposed)
33041
+ continue;
33042
+ const { prefix, suffix } = decomposed;
33043
+ const suffixExt = (suffix.match(/\.[^.]+$/) ?? [""])[0];
33044
+ const marker = suffixExt ? suffix.slice(0, -suffixExt.length) : suffix;
33045
+ if (marker.length === 0)
33046
+ continue;
33047
+ const effectiveSuffix = `${marker}${srcExt}`;
33048
+ push(`${stemWithPath}${effectiveSuffix}`);
33049
+ if (innerStem !== null && prefix.length > 0) {
33050
+ push(`${pkgPrefix}${prefix}/${innerStem}${effectiveSuffix}`);
33051
+ }
33052
+ }
33053
+ return candidates;
32996
33054
  }
32997
- async function collectNeighbors(filePath, workdir, extraGlobWorkdirs) {
33055
+ function stripExt(s) {
33056
+ const m = s.match(/\.[^.]+$/);
33057
+ return m ? s.slice(0, -m[0].length) : s;
33058
+ }
33059
+ function isTestFile2(filePath, regex) {
33060
+ return regex.some((re) => re.test(filePath));
33061
+ }
33062
+ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs, siblingTestContext) {
32998
33063
  const neighbors = new Set;
32999
33064
  if (await _codeNeighborDeps.fileExists(join20(workdir, filePath))) {
33000
33065
  const content = await _codeNeighborDeps.readFile(join20(workdir, filePath));
@@ -33035,9 +33100,24 @@ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs) {
33035
33100
  await scanForReverseDeps(extraDir);
33036
33101
  }
33037
33102
  }
33038
- const testPath = siblingTestPath(filePath);
33039
- if (testPath)
33040
- neighbors.add(testPath);
33103
+ if (siblingTestContext && !isTestFile2(filePath, siblingTestContext.regex)) {
33104
+ const candidates = deriveSiblingTestCandidates(filePath, siblingTestContext.globs);
33105
+ let chosen = null;
33106
+ for (const candidate of candidates) {
33107
+ if (await _codeNeighborDeps.fileExists(join20(workdir, candidate))) {
33108
+ chosen = candidate;
33109
+ break;
33110
+ }
33111
+ }
33112
+ if (chosen === null) {
33113
+ const colocated = candidates[0];
33114
+ const mirrored = candidates.find((c, i) => i > 0 && c !== colocated);
33115
+ if (mirrored)
33116
+ chosen = mirrored;
33117
+ }
33118
+ if (chosen !== null && chosen !== filePath)
33119
+ neighbors.add(chosen);
33120
+ }
33041
33121
  return [...neighbors].slice(0, MAX_NEIGHBORS_PER_FILE);
33042
33122
  }
33043
33123
  async function resolveExtraGlobWorkdirs(neighborScope, crossPackageDepth, repoRoot, packageDir) {
@@ -33071,9 +33151,13 @@ class CodeNeighborProvider {
33071
33151
  return { chunks: [], pullTools: [] };
33072
33152
  }
33073
33153
  const filesToProcess = touchedFiles.filter(isRelativeAndSafe).slice(0, MAX_FILES);
33154
+ const siblingTestContext = request.resolvedTestPatterns ? {
33155
+ globs: request.resolvedTestPatterns.globs,
33156
+ regex: request.resolvedTestPatterns.regex
33157
+ } : undefined;
33074
33158
  const sections2 = [];
33075
33159
  for (const file3 of filesToProcess) {
33076
- const neighbors = await collectNeighbors(file3, workdir, extraGlobWorkdirs);
33160
+ const neighbors = await collectNeighbors(file3, workdir, extraGlobWorkdirs, siblingTestContext);
33077
33161
  if (neighbors.length > 0) {
33078
33162
  sections2.push(`### ${file3}
33079
33163
  ${neighbors.map((n) => `- ${n}`).join(`
@@ -33437,7 +33521,7 @@ class PullToolBudget {
33437
33521
  return this.sessionCalls;
33438
33522
  }
33439
33523
  }
33440
- async function handleQueryNeighbor(input, repoRoot, budget, maxTokensPerCall = DEFAULT_MAX_TOKENS_PER_CALL) {
33524
+ async function handleQueryNeighbor(input, repoRoot, budget, maxTokensPerCall = DEFAULT_MAX_TOKENS_PER_CALL, resolvedTestPatterns) {
33441
33525
  budget.consume();
33442
33526
  const provider = new CodeNeighborProvider;
33443
33527
  const request = {
@@ -33447,7 +33531,8 @@ async function handleQueryNeighbor(input, repoRoot, budget, maxTokensPerCall = D
33447
33531
  stage: "pull-tool",
33448
33532
  role: "implementer",
33449
33533
  budgetTokens: maxTokensPerCall,
33450
- touchedFiles: [input.filePath]
33534
+ touchedFiles: [input.filePath],
33535
+ ...resolvedTestPatterns && { resolvedTestPatterns }
33451
33536
  };
33452
33537
  const result = await provider.fetch(request);
33453
33538
  const content = result.chunks.map((c) => c.content).join(`
@@ -35113,6 +35198,21 @@ function createContextToolRuntime(options) {
35113
35198
  const budgets = new Map;
35114
35199
  const runCounter = options.runCounter ?? createRunCallCounter();
35115
35200
  const maxCallsPerRun = config2.context?.v2?.pull?.maxCallsPerRun ?? 50;
35201
+ let resolvedTestPatternsPromise = null;
35202
+ async function getResolvedTestPatterns() {
35203
+ if (resolvedTestPatternsPromise === null) {
35204
+ resolvedTestPatternsPromise = resolveTestFilePatterns(config2, repoRoot, story.workdir || undefined, {
35205
+ storyId: story.id
35206
+ }).catch((err) => {
35207
+ getLogger().warn("context", "Pull-tool runtime: failed to resolve test patterns", {
35208
+ storyId: story.id,
35209
+ error: errorMessage(err)
35210
+ });
35211
+ return;
35212
+ });
35213
+ }
35214
+ return resolvedTestPatternsPromise;
35215
+ }
35116
35216
  function getBudget(tool) {
35117
35217
  const existing = budgets.get(tool.name);
35118
35218
  if (existing)
@@ -35128,8 +35228,10 @@ function createContextToolRuntime(options) {
35128
35228
  throw new Error(`Unknown context tool: ${name}`);
35129
35229
  }
35130
35230
  switch (name) {
35131
- case "query_neighbor":
35132
- return handleQueryNeighbor(input, repoRoot, getBudget(tool), tool.maxTokensPerCall);
35231
+ case "query_neighbor": {
35232
+ const patterns = await getResolvedTestPatterns();
35233
+ return handleQueryNeighbor(input, repoRoot, getBudget(tool), tool.maxTokensPerCall, patterns);
35234
+ }
35133
35235
  case "query_feature_context":
35134
35236
  return handleQueryFeatureContext(input, story, config2, repoRoot, getBudget(tool), tool.maxTokensPerCall);
35135
35237
  default:
@@ -35139,6 +35241,8 @@ function createContextToolRuntime(options) {
35139
35241
  };
35140
35242
  }
35141
35243
  var init_tool_runtime = __esm(() => {
35244
+ init_logger2();
35245
+ init_resolver();
35142
35246
  init_pull_tools();
35143
35247
  });
35144
35248
 
@@ -38370,6 +38474,17 @@ async function runV2Path(ctx) {
38370
38474
  }
38371
38475
  }
38372
38476
  const touchedFiles = getContextFiles(ctx.story);
38477
+ let resolvedTestPatterns;
38478
+ try {
38479
+ resolvedTestPatterns = await resolveTestFilePatterns(ctx.config, ctx.workdir, ctx.story.workdir || undefined, {
38480
+ storyId: ctx.story.id
38481
+ });
38482
+ } catch (err) {
38483
+ logger.warn("context", "Failed to resolve test-file patterns \u2014 providers will skip sibling-test hints", {
38484
+ storyId: ctx.story.id,
38485
+ error: errorMessage(err)
38486
+ });
38487
+ }
38373
38488
  const request = {
38374
38489
  storyId: ctx.story.id,
38375
38490
  featureId: ctx.featureDir?.replace(/\/$/, "").split("/").pop(),
@@ -38391,7 +38506,8 @@ async function runV2Path(ctx) {
38391
38506
  agentId: agentName,
38392
38507
  availableBudgetTokens: estimateAvailableBudgetTokens(agentName, ctx.prompt),
38393
38508
  deterministic: ctx.config.context.v2.deterministic,
38394
- planDigestBoost: getStageContextConfig(ctx.routing?.testStrategy ?? "").planDigestBoost
38509
+ planDigestBoost: getStageContextConfig(ctx.routing?.testStrategy ?? "").planDigestBoost,
38510
+ ...resolvedTestPatterns && { resolvedTestPatterns }
38395
38511
  };
38396
38512
  const pluginConfigs = ctx.config.context.v2.pluginProviders ?? [];
38397
38513
  const pluginProviders = pluginConfigs.length > 0 ? await _contextStageDeps.loadPlugins(pluginConfigs, ctx.projectDir ?? ctx.workdir) : [];
@@ -38510,6 +38626,7 @@ var init_context2 = __esm(() => {
38510
38626
  init_logger2();
38511
38627
  init_prd();
38512
38628
  init_scratch_writer();
38629
+ init_resolver();
38513
38630
  _contextStageDeps = {
38514
38631
  createOrchestrator: createDefaultOrchestrator,
38515
38632
  loadPlugins: loadPluginProviders,
@@ -43433,7 +43550,7 @@ var package_default;
43433
43550
  var init_package = __esm(() => {
43434
43551
  package_default = {
43435
43552
  name: "@nathapp/nax",
43436
- version: "0.63.0-canary.3",
43553
+ version: "0.63.0-canary.4",
43437
43554
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
43438
43555
  type: "module",
43439
43556
  bin: {
@@ -43513,8 +43630,8 @@ var init_version = __esm(() => {
43513
43630
  NAX_VERSION = package_default.version;
43514
43631
  NAX_COMMIT = (() => {
43515
43632
  try {
43516
- if (/^[0-9a-f]{6,10}$/.test("d2af3ceb"))
43517
- return "d2af3ceb";
43633
+ if (/^[0-9a-f]{6,10}$/.test("543ca776"))
43634
+ return "543ca776";
43518
43635
  } catch {}
43519
43636
  try {
43520
43637
  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.63.0-canary.3",
3
+ "version": "0.63.0-canary.4",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {