@nathapp/nax 0.63.0-canary.2 → 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 +139 -18
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -32984,14 +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
+ return null;
32991
+ const suffix = pattern.slice(lastStar + 1);
32992
+ if (suffix.length === 0)
32990
32993
  return null;
32991
- const ext = srcMatch[2] ?? "ts";
32992
- return `test/unit/${srcMatch[1]}.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;
33054
+ }
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));
32993
33061
  }
32994
- async function collectNeighbors(filePath, workdir, extraGlobWorkdirs) {
33062
+ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs, siblingTestContext) {
32995
33063
  const neighbors = new Set;
32996
33064
  if (await _codeNeighborDeps.fileExists(join20(workdir, filePath))) {
32997
33065
  const content = await _codeNeighborDeps.readFile(join20(workdir, filePath));
@@ -33032,9 +33100,24 @@ async function collectNeighbors(filePath, workdir, extraGlobWorkdirs) {
33032
33100
  await scanForReverseDeps(extraDir);
33033
33101
  }
33034
33102
  }
33035
- const testPath = siblingTestPath(filePath);
33036
- if (testPath)
33037
- 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
+ }
33038
33121
  return [...neighbors].slice(0, MAX_NEIGHBORS_PER_FILE);
33039
33122
  }
33040
33123
  async function resolveExtraGlobWorkdirs(neighborScope, crossPackageDepth, repoRoot, packageDir) {
@@ -33068,9 +33151,13 @@ class CodeNeighborProvider {
33068
33151
  return { chunks: [], pullTools: [] };
33069
33152
  }
33070
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;
33071
33158
  const sections2 = [];
33072
33159
  for (const file3 of filesToProcess) {
33073
- const neighbors = await collectNeighbors(file3, workdir, extraGlobWorkdirs);
33160
+ const neighbors = await collectNeighbors(file3, workdir, extraGlobWorkdirs, siblingTestContext);
33074
33161
  if (neighbors.length > 0) {
33075
33162
  sections2.push(`### ${file3}
33076
33163
  ${neighbors.map((n) => `- ${n}`).join(`
@@ -33434,7 +33521,7 @@ class PullToolBudget {
33434
33521
  return this.sessionCalls;
33435
33522
  }
33436
33523
  }
33437
- 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) {
33438
33525
  budget.consume();
33439
33526
  const provider = new CodeNeighborProvider;
33440
33527
  const request = {
@@ -33444,7 +33531,8 @@ async function handleQueryNeighbor(input, repoRoot, budget, maxTokensPerCall = D
33444
33531
  stage: "pull-tool",
33445
33532
  role: "implementer",
33446
33533
  budgetTokens: maxTokensPerCall,
33447
- touchedFiles: [input.filePath]
33534
+ touchedFiles: [input.filePath],
33535
+ ...resolvedTestPatterns && { resolvedTestPatterns }
33448
33536
  };
33449
33537
  const result = await provider.fetch(request);
33450
33538
  const content = result.chunks.map((c) => c.content).join(`
@@ -35110,6 +35198,21 @@ function createContextToolRuntime(options) {
35110
35198
  const budgets = new Map;
35111
35199
  const runCounter = options.runCounter ?? createRunCallCounter();
35112
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
+ }
35113
35216
  function getBudget(tool) {
35114
35217
  const existing = budgets.get(tool.name);
35115
35218
  if (existing)
@@ -35125,8 +35228,10 @@ function createContextToolRuntime(options) {
35125
35228
  throw new Error(`Unknown context tool: ${name}`);
35126
35229
  }
35127
35230
  switch (name) {
35128
- case "query_neighbor":
35129
- 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
+ }
35130
35235
  case "query_feature_context":
35131
35236
  return handleQueryFeatureContext(input, story, config2, repoRoot, getBudget(tool), tool.maxTokensPerCall);
35132
35237
  default:
@@ -35136,6 +35241,8 @@ function createContextToolRuntime(options) {
35136
35241
  };
35137
35242
  }
35138
35243
  var init_tool_runtime = __esm(() => {
35244
+ init_logger2();
35245
+ init_resolver();
35139
35246
  init_pull_tools();
35140
35247
  });
35141
35248
 
@@ -38367,6 +38474,17 @@ async function runV2Path(ctx) {
38367
38474
  }
38368
38475
  }
38369
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
+ }
38370
38488
  const request = {
38371
38489
  storyId: ctx.story.id,
38372
38490
  featureId: ctx.featureDir?.replace(/\/$/, "").split("/").pop(),
@@ -38388,7 +38506,8 @@ async function runV2Path(ctx) {
38388
38506
  agentId: agentName,
38389
38507
  availableBudgetTokens: estimateAvailableBudgetTokens(agentName, ctx.prompt),
38390
38508
  deterministic: ctx.config.context.v2.deterministic,
38391
- planDigestBoost: getStageContextConfig(ctx.routing?.testStrategy ?? "").planDigestBoost
38509
+ planDigestBoost: getStageContextConfig(ctx.routing?.testStrategy ?? "").planDigestBoost,
38510
+ ...resolvedTestPatterns && { resolvedTestPatterns }
38392
38511
  };
38393
38512
  const pluginConfigs = ctx.config.context.v2.pluginProviders ?? [];
38394
38513
  const pluginProviders = pluginConfigs.length > 0 ? await _contextStageDeps.loadPlugins(pluginConfigs, ctx.projectDir ?? ctx.workdir) : [];
@@ -38507,6 +38626,7 @@ var init_context2 = __esm(() => {
38507
38626
  init_logger2();
38508
38627
  init_prd();
38509
38628
  init_scratch_writer();
38629
+ init_resolver();
38510
38630
  _contextStageDeps = {
38511
38631
  createOrchestrator: createDefaultOrchestrator,
38512
38632
  loadPlugins: loadPluginProviders,
@@ -43430,7 +43550,7 @@ var package_default;
43430
43550
  var init_package = __esm(() => {
43431
43551
  package_default = {
43432
43552
  name: "@nathapp/nax",
43433
- version: "0.63.0-canary.2",
43553
+ version: "0.63.0-canary.4",
43434
43554
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
43435
43555
  type: "module",
43436
43556
  bin: {
@@ -43510,8 +43630,8 @@ var init_version = __esm(() => {
43510
43630
  NAX_VERSION = package_default.version;
43511
43631
  NAX_COMMIT = (() => {
43512
43632
  try {
43513
- if (/^[0-9a-f]{6,10}$/.test("acd83bd2"))
43514
- return "acd83bd2";
43633
+ if (/^[0-9a-f]{6,10}$/.test("543ca776"))
43634
+ return "543ca776";
43515
43635
  } catch {}
43516
43636
  try {
43517
43637
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -45198,6 +45318,7 @@ class SessionManager {
45198
45318
  continue;
45199
45319
  const updated = { ...session, state: "COMPLETED", lastActivityAt: now };
45200
45320
  this._sessions.set(id, updated);
45321
+ this._persistDescriptor(updated);
45201
45322
  closed.push({ ...updated });
45202
45323
  getLogger().debug("session", "Session closed by closeStory", {
45203
45324
  storyId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.63.0-canary.2",
3
+ "version": "0.63.0-canary.4",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {