gitnexus 1.6.10-rc.220 → 1.6.10-rc.221

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.
@@ -3,6 +3,8 @@ import type { Path } from 'path-scurry';
3
3
  export declare const shouldIgnorePath: (filePath: string) => boolean;
4
4
  /** Check if a directory name is in the hardcoded ignore list */
5
5
  export declare const isHardcodedIgnoredDirectory: (name: string) => boolean;
6
+ /** Apply directory ignore rules that depend on repository-relative depth. */
7
+ export declare const isHardcodedIgnoredDirectoryAtPath: (repoRoot: string, directoryPath: string) => boolean;
6
8
  /**
7
9
  * Load .gitignore and .gitnexusignore rules from the repo root.
8
10
  * Returns an `ignore` instance with all patterns, or null if no files found.
@@ -1,4 +1,5 @@
1
1
  import ignore from 'ignore';
2
+ import { existsSync } from 'fs';
2
3
  import fs from 'fs/promises';
3
4
  import nodePath from 'path';
4
5
  import { logger } from '../core/logger.js';
@@ -27,12 +28,15 @@ const DEFAULT_IGNORE_LIST = new Set([
27
28
  // 'packages' removed - commonly used for monorepo source code (lerna, pnpm, yarn workspaces)
28
29
  'venv',
29
30
  '.venv',
30
- 'env',
31
31
  '.env',
32
+ // Bare `env/` can be application source or a Python virtual environment.
33
+ // Path-aware rules below prune it at the root and wherever pyvenv.cfg marks
34
+ // a virtual environment, while preserving ordinary nested source folders.
32
35
  '__pycache__',
33
36
  '.pytest_cache',
34
37
  '.mypy_cache',
35
38
  'site-packages',
39
+ 'dist-packages',
36
40
  '.tox',
37
41
  'eggs',
38
42
  '.eggs',
@@ -78,8 +82,9 @@ const DEFAULT_IGNORE_LIST = new Set([
78
82
  '.temp',
79
83
  // Generated/Compiled
80
84
  '.generated',
81
- 'generated',
82
85
  'auto-generated',
86
+ // Bare `generated/` can contain tracked source-of-truth code. Build output
87
+ // remains covered by .gitignore/.gitnexusignore and the unambiguous names.
83
88
  'monaco-workers', // Monaco editor web-worker bundles generated for browser runtime
84
89
  '.terraform',
85
90
  '.serverless',
@@ -95,6 +100,11 @@ const DEFAULT_IGNORE_LIST = new Set([
95
100
  'snapshots', // Jest snapshots
96
101
  '__snapshots__',
97
102
  ]);
103
+ // Ambiguous names that conventionally denote generated artifacts only at the
104
+ // repository root. Nested directories with these names are frequently source
105
+ // modules (for example apps/web/src/env or packages/api/generated).
106
+ const ROOT_ARTIFACT_DIRECTORIES = new Set(['env', 'generated']);
107
+ const isRootArtifactDirectory = (relativePath, name) => !relativePath.includes('/') && ROOT_ARTIFACT_DIRECTORIES.has(name);
98
108
  const IGNORED_EXTENSIONS = new Set([
99
109
  // Images
100
110
  '.png',
@@ -264,6 +274,9 @@ export const shouldIgnorePath = (filePath) => {
264
274
  const parts = normalizedPath.split('/');
265
275
  const fileName = parts[parts.length - 1];
266
276
  const fileNameLower = fileName.toLowerCase();
277
+ if (parts.length > 0 && isRootArtifactDirectory(parts[0], parts[0])) {
278
+ return true;
279
+ }
267
280
  // Laravel compiles Blade templates into generated PHP cache files under
268
281
  // storage/framework/views. Source templates live in resources/views and are
269
282
  // handled separately; compiled cache should not become source-of-truth. Keep
@@ -299,9 +312,7 @@ export const shouldIgnorePath = (filePath) => {
299
312
  // Ignore files that look like generated/bundled code
300
313
  if (fileNameLower.includes('.bundle.') ||
301
314
  fileNameLower.includes('.chunk.') ||
302
- fileNameLower.includes('.generated.') ||
303
- fileNameLower.endsWith('.d.ts')) {
304
- // TypeScript declaration files
315
+ fileNameLower.includes('.generated.')) {
305
316
  return true;
306
317
  }
307
318
  return false;
@@ -310,6 +321,16 @@ export const shouldIgnorePath = (filePath) => {
310
321
  export const isHardcodedIgnoredDirectory = (name) => {
311
322
  return DEFAULT_IGNORE_LIST.has(name);
312
323
  };
324
+ /** Apply directory ignore rules that depend on repository-relative depth. */
325
+ export const isHardcodedIgnoredDirectoryAtPath = (repoRoot, directoryPath) => {
326
+ const name = nodePath.basename(directoryPath);
327
+ if (isHardcodedIgnoredDirectory(name))
328
+ return true;
329
+ const relative = nodePath.relative(repoRoot, directoryPath).replace(/\\/g, '/');
330
+ if (isRootArtifactDirectory(relative, name))
331
+ return true;
332
+ return name === 'env' && existsSync(nodePath.join(directoryPath, 'pyvenv.cfg'));
333
+ };
313
334
  export const loadIgnoreRules = async (repoPath, options) => {
314
335
  const ig = ignore();
315
336
  let hasRules = false;
@@ -450,9 +471,10 @@ export const createIgnoreFilter = async (repoPath, options) => {
450
471
  // blocks descent into `__tests__/generated/`.
451
472
  if (ig && rel && hasExplicitUnignore(ig, rel) && !ig.ignores(rel + '/'))
452
473
  return false;
453
- // Hardcoded list: block descent into well-known noise directories.
454
- if (DEFAULT_IGNORE_LIST.has(p.name))
474
+ // Hardcoded and path-aware rules prune whole trees before glob walks them.
475
+ if (rel && isHardcodedIgnoredDirectoryAtPath(repoPath, nodePath.join(repoPath, rel))) {
455
476
  return true;
477
+ }
456
478
  // Check against .gitignore / .gitnexusignore patterns.
457
479
  // Since childrenIgnored is only called for directories, always test with
458
480
  // a trailing slash. This ensures directory-only negation patterns (e.g.
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { shouldIgnorePath, loadIgnoreRules } from '../../../config/ignore-service.js';
3
+ import { shouldIgnorePath, loadIgnoreRules, isHardcodedIgnoredDirectoryAtPath, } from '../../../config/ignore-service.js';
4
4
  import { logger } from '../../logger.js';
5
5
  async function parsePythonManifest(repoPath) {
6
6
  const pyprojectPath = path.join(repoPath, 'pyproject.toml');
@@ -124,11 +124,14 @@ async function findPythonFiles(repoPath) {
124
124
  for (const entry of entries) {
125
125
  const childRel = rel ? `${rel}/${entry.name}` : entry.name;
126
126
  if (entry.isDirectory()) {
127
+ const childPath = path.join(dir, entry.name);
127
128
  if (shouldIgnorePath(childRel))
128
129
  continue;
130
+ if (isHardcodedIgnoredDirectoryAtPath(repoPath, childPath))
131
+ continue;
129
132
  if (ig && ig.ignores(childRel + '/'))
130
133
  continue;
131
- await walk(path.join(dir, entry.name), childRel);
134
+ await walk(childPath, childRel);
132
135
  }
133
136
  else if (entry.name.endsWith('.py')) {
134
137
  if (shouldIgnorePath(childRel))
@@ -8,6 +8,20 @@ import { mapConcurrent } from '../../lib/utils.js';
8
8
  import { logger } from '../logger.js';
9
9
  const READ_CONCURRENCY = 32;
10
10
  const ANALYZE_PROGRESS_ACTIVE_ENV = 'GITNEXUS_ANALYZE_PROGRESS_ACTIVE';
11
+ const DECLARATION_COMPANION_SUFFIXES = [
12
+ { declaration: '.d.ts', implementations: ['.ts', '.tsx'] },
13
+ { declaration: '.d.mts', implementations: ['.mts'] },
14
+ { declaration: '.d.cts', implementations: ['.cts'] },
15
+ ];
16
+ const hasImplementationSibling = (declarationPath, scannedPaths) => {
17
+ const companion = DECLARATION_COMPANION_SUFFIXES.find(({ declaration }) => declarationPath.endsWith(declaration));
18
+ if (!companion)
19
+ return false;
20
+ // Keep standalone declarations. Only suppress declaration output that sits
21
+ // beside an implementation with the corresponding module suffix.
22
+ const stem = declarationPath.slice(0, -companion.declaration.length);
23
+ return companion.implementations.some((suffix) => scannedPaths.has(`${stem}${suffix}`));
24
+ };
11
25
  const warnLargeFileSkip = (message) => {
12
26
  if (process.env[ANALYZE_PROGRESS_ACTIVE_ENV] === '1') {
13
27
  // analyze.ts routes console.warn through the progress bar logger while
@@ -61,10 +75,12 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
61
75
  }
62
76
  }
63
77
  }
78
+ const scannedPaths = new Set(entries.map((entry) => entry.path));
79
+ const deduplicatedEntries = entries.filter((entry) => !hasImplementationSibling(entry.path, scannedPaths));
64
80
  // Filesystem/glob traversal order is not stable across filesystems or repeated
65
81
  // scans. Canonicalize once at the scan boundary so every downstream phase sees
66
82
  // the same repository order.
67
- entries.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
83
+ deduplicatedEntries.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
68
84
  if (skippedLarge > 0) {
69
85
  const isDefault = maxFileSizeBytes === DEFAULT_MAX_FILE_SIZE_BYTES;
70
86
  const isOverrideUnset = !process.env.GITNEXUS_MAX_FILE_SIZE;
@@ -93,7 +109,7 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
93
109
  warnLargeFileSkip(` Set GITNEXUS_MAX_FILE_SIZE=<KB> to include files above the default cap.`);
94
110
  }
95
111
  }
96
- return entries;
112
+ return deduplicatedEntries;
97
113
  };
98
114
  /**
99
115
  * Phase 2: Read file contents for a specific set of relative paths.
@@ -17,7 +17,7 @@
17
17
  import fs from 'fs/promises';
18
18
  import path from 'path';
19
19
  import { createRequire } from 'node:module';
20
- import { isHardcodedIgnoredDirectory } from '../../../config/ignore-service.js';
20
+ import { isHardcodedIgnoredDirectoryAtPath } from '../../../config/ignore-service.js';
21
21
  import { logger } from '../../logger.js';
22
22
  import { resolveFile } from '../languages/typescript/file-candidates.js';
23
23
  // `js-yaml` is CJS; the rest of this repository reaches it the same way
@@ -285,10 +285,11 @@ export async function loadNodeWorkspacePackages(repoRoot) {
285
285
  }
286
286
  for (const entry of entries) {
287
287
  if (entry.isDirectory()) {
288
- if (isHardcodedIgnoredDirectory(entry.name))
288
+ const childDir = path.join(dir, entry.name);
289
+ if (isHardcodedIgnoredDirectoryAtPath(repoRoot, childDir))
289
290
  continue;
290
291
  if (depth < SCAN_MAX_DEPTH) {
291
- queue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
292
+ queue.push({ dir: childDir, depth: depth + 1 });
292
293
  }
293
294
  continue;
294
295
  }
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import fs from 'fs/promises';
23
23
  import path from 'path';
24
- import { isHardcodedIgnoredDirectory } from '../../../../config/ignore-service.js';
24
+ import { isHardcodedIgnoredDirectoryAtPath } from '../../../../config/ignore-service.js';
25
25
  import { logger } from '../../../logger.js';
26
26
  const SCAN_MAX_DIRS = 20_000;
27
27
  const SCAN_MAX_DEPTH = 24;
@@ -247,10 +247,11 @@ async function findTsconfigFiles(repoRoot) {
247
247
  }
248
248
  for (const entry of entries) {
249
249
  if (entry.isDirectory()) {
250
- if (isHardcodedIgnoredDirectory(entry.name))
250
+ const childDir = path.join(dir, entry.name);
251
+ if (isHardcodedIgnoredDirectoryAtPath(repoRoot, childDir))
251
252
  continue;
252
253
  if (depth < SCAN_MAX_DEPTH)
253
- queue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
254
+ queue.push({ dir: childDir, depth: depth + 1 });
254
255
  continue;
255
256
  }
256
257
  if (!entry.isFile())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.10-rc.220",
3
+ "version": "1.6.10-rc.221",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",