gitnexus 1.6.4-rc.38 → 1.6.4-rc.39

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.
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import path from 'path';
12
12
  import fs from 'fs/promises';
13
- import { getStoragePaths, loadMeta, addToGitignore, registerRepo, } from '../storage/repo-manager.js';
13
+ import { getStoragePaths, loadMeta, ensureGitNexusIgnored, registerRepo, } from '../storage/repo-manager.js';
14
14
  import { getGitRoot, getRemoteUrl, isGitRepo } from '../storage/git.js';
15
15
  export const indexCommand = async (inputPathParts, options) => {
16
16
  console.log('\n GitNexus Index\n');
@@ -96,7 +96,7 @@ export const indexCommand = async (inputPathParts, options) => {
96
96
  meta.remoteUrl = getRemoteUrl(repoPath);
97
97
  }
98
98
  await registerRepo(repoPath, meta);
99
- await addToGitignore(repoPath);
99
+ await ensureGitNexusIgnored(repoPath);
100
100
  const projectName = path.basename(repoPath);
101
101
  const { stats } = meta;
102
102
  console.log(` Repository registered: ${projectName}`);
@@ -1,6 +1,7 @@
1
1
  import * as path from 'node:path';
2
2
  import { glob } from 'glob';
3
3
  import Parser from 'tree-sitter';
4
+ import { createIgnoreFilter } from '../../../config/ignore-service.js';
4
5
  import { readSafe } from './fs-utils.js';
5
6
  import { GRPC_SCAN_GLOB, getPluginForFile, hasProtoPlugin, } from './grpc-patterns/index.js';
6
7
  /**
@@ -185,11 +186,16 @@ function longestSharedSegmentRun(aPath, bPath) {
185
186
  }
186
187
  async function buildProtoContext(repoPath) {
187
188
  const servicesByName = new Map();
189
+ // `.gitnexusignore` / `.gitignore` honoured via the shared IgnoreService —
190
+ // see `filesystem-walker.ts` for the canonical pattern. Replaces a
191
+ // hardcoded `[node_modules, .git, vendor]` array; those names plus the
192
+ // rest of `DEFAULT_IGNORE_LIST` are still excluded by default (#1185).
193
+ const protoIgnoreFilter = await createIgnoreFilter(repoPath);
188
194
  const protoFiles = await glob('**/*.proto', {
189
195
  cwd: repoPath,
190
196
  absolute: false,
191
197
  nodir: true,
192
- ignore: ['**/node_modules/**', '**/.git/**', '**/vendor/**'],
198
+ ignore: protoIgnoreFilter,
193
199
  });
194
200
  const contents = new Map();
195
201
  for (const rel of protoFiles) {
@@ -324,9 +330,14 @@ export class GrpcExtractor {
324
330
  }
325
331
  }
326
332
  // ─── Source files (+ .proto when plugin available) ────────────
333
+ // Honour `.gitnexusignore` / `.gitignore` via the shared IgnoreService —
334
+ // mirrors `filesystem-walker.ts`. Replaces a hardcoded
335
+ // `[node_modules, .git, vendor, dist, build]` array; those names are all
336
+ // in `DEFAULT_IGNORE_LIST`, so default behaviour is preserved (#1185).
337
+ const sourceIgnoreFilter = await createIgnoreFilter(repoPath);
327
338
  const sourceFiles = await glob(GRPC_SCAN_GLOB, {
328
339
  cwd: repoPath,
329
- ignore: ['**/node_modules/**', '**/.git/**', '**/vendor/**', '**/dist/**', '**/build/**'],
340
+ ignore: sourceIgnoreFilter,
330
341
  nodir: true,
331
342
  });
332
343
  const parser = new Parser();
@@ -1,6 +1,7 @@
1
1
  import * as path from 'node:path';
2
2
  import { glob } from 'glob';
3
3
  import Parser from 'tree-sitter';
4
+ import { createIgnoreFilter } from '../../../config/ignore-service.js';
4
5
  import { readSafe } from './fs-utils.js';
5
6
  import { getPluginForFile, HTTP_SCAN_GLOB } from './http-patterns/index.js';
6
7
  /**
@@ -183,9 +184,16 @@ export class HttpRouteExtractor {
183
184
  return [...providers, ...consumers];
184
185
  }
185
186
  async scanFiles(repoPath) {
187
+ // Honour `.gitnexusignore` and `.gitignore` via the shared IgnoreService
188
+ // so contract extraction respects the same exclusion rules as the rest of
189
+ // the ingestion pipeline. Mirrors `filesystem-walker.ts` which uses the
190
+ // same shape. Replaces a hardcoded `[node_modules, .git, dist, build,
191
+ // vendor]` array — those names are still in `DEFAULT_IGNORE_LIST`, so
192
+ // default behaviour is preserved (#1185).
193
+ const ignoreFilter = await createIgnoreFilter(repoPath);
186
194
  return glob(HTTP_SCAN_GLOB, {
187
195
  cwd: repoPath,
188
- ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/vendor/**'],
196
+ ignore: ignoreFilter,
189
197
  nodir: true,
190
198
  });
191
199
  }
@@ -1,5 +1,6 @@
1
1
  import { glob } from 'glob';
2
2
  import Parser from 'tree-sitter';
3
+ import { createIgnoreFilter } from '../../../config/ignore-service.js';
3
4
  import { readSafe } from './fs-utils.js';
4
5
  import { scanFile, unquoteLiteral } from './tree-sitter-scanner.js';
5
6
  import { TOPIC_SCAN_GLOB, getProviderForFile, } from './topic-patterns/index.js';
@@ -40,22 +41,21 @@ export class TopicExtractor {
40
41
  return true;
41
42
  }
42
43
  async extract(_dbExecutor, repoPath, _repo) {
44
+ // Honour `.gitnexusignore` / `.gitignore` via the shared IgnoreService —
45
+ // mirrors `filesystem-walker.ts`. The 5-name hardcoded list
46
+ // (`node_modules, .git, vendor, dist, build`) is preserved because every
47
+ // entry is in `DEFAULT_IGNORE_LIST`, so default behaviour is unchanged
48
+ // (#1185). The Go-specific `**/*_test.go` filter is layered on top via a
49
+ // small wrapper so glob-level pruning is preserved (we never read those
50
+ // files); the wrapper short-circuits before calling the base filter.
51
+ const baseFilter = await createIgnoreFilter(repoPath);
52
+ const ignoreFilter = {
53
+ ignored: (p) => p.relative().endsWith('_test.go') || baseFilter.ignored(p),
54
+ childrenIgnored: (p) => baseFilter.childrenIgnored(p),
55
+ };
43
56
  const files = await glob(TOPIC_SCAN_GLOB, {
44
57
  cwd: repoPath,
45
- ignore: [
46
- '**/node_modules/**',
47
- '**/.git/**',
48
- '**/vendor/**',
49
- '**/dist/**',
50
- '**/build/**',
51
- // Language-level test file conventions. Go test files
52
- // `*_test.go` live next to source; other languages either use
53
- // separate test directories (Python's `tests/`, Java's
54
- // `src/test/`) or are already covered by the dist/build ignores.
55
- // Pushed to the glob level so the orchestrator stays
56
- // language-agnostic.
57
- '**/*_test.go',
58
- ],
58
+ ignore: ignoreFilter,
59
59
  nodir: true,
60
60
  });
61
61
  // One parser reused across files; the scanner calls `setLanguage` per
@@ -13,7 +13,7 @@ import fs from 'fs/promises';
13
13
  import { runPipelineFromRepo } from './ingestion/pipeline.js';
14
14
  import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, } from './lbug/lbug-adapter.js';
15
15
  import { createSearchFTSIndexes } from './search/fts-indexes.js';
16
- import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, cleanupOldKuzuFiles, } from '../storage/repo-manager.js';
16
+ import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, } from '../storage/repo-manager.js';
17
17
  import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName } from '../storage/git.js';
18
18
  import { generateAIContextFiles } from '../cli/ai-context.js';
19
19
  import { EMBEDDING_TABLE_NAME } from './lbug/schema.js';
@@ -69,6 +69,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
69
69
  if (existingMeta && !options.force && existingMeta.lastCommit === currentCommit) {
70
70
  // Non-git folders have currentCommit = '' — always rebuild since we can't detect changes
71
71
  if (currentCommit !== '') {
72
+ await ensureGitNexusIgnored(repoPath);
72
73
  return {
73
74
  repoName: options.registryName ?? getInferredRepoName(repoPath) ?? path.basename(repoPath),
74
75
  repoPath,
@@ -302,10 +303,8 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
302
303
  name: options.registryName,
303
304
  allowDuplicateName: options.allowDuplicateName,
304
305
  });
305
- // Only attempt to update .gitignore when a .git directory is present.
306
- if (hasGitDir(repoPath)) {
307
- await addToGitignore(repoPath);
308
- }
306
+ // Keep generated .gitnexus contents ignored without editing the user's root .gitignore.
307
+ await ensureGitNexusIgnored(repoPath);
309
308
  // ── Generate AI context files (best-effort) ───────────────────────
310
309
  let aggregatedClusterCount = 0;
311
310
  if (pipelineResult.communityResult?.communities) {
@@ -128,9 +128,9 @@ export declare const loadRepo: (repoPath: string) => Promise<IndexedRepo | null>
128
128
  */
129
129
  export declare const findRepo: (startPath: string) => Promise<IndexedRepo | null>;
130
130
  /**
131
- * Add .gitnexus to .gitignore if not already present
131
+ * Keep generated index files ignored without modifying the user's root .gitignore.
132
132
  */
133
- export declare const addToGitignore: (repoPath: string) => Promise<void>;
133
+ export declare const ensureGitNexusIgnored: (repoPath: string) => Promise<void>;
134
134
  /**
135
135
  * Get the path to the global GitNexus directory
136
136
  */
@@ -50,6 +50,7 @@ export const canonicalizePath = (p) => {
50
50
  }
51
51
  };
52
52
  const GITNEXUS_DIR = '.gitnexus';
53
+ const GITNEXUS_EXCLUDE_ENTRY = `${GITNEXUS_DIR}/`;
53
54
  // ─── Local Storage Helpers ─────────────────────────────────────────────
54
55
  /**
55
56
  * Get the .gitnexus storage path for a repository
@@ -185,23 +186,42 @@ export const findRepo = async (startPath) => {
185
186
  return null;
186
187
  };
187
188
  /**
188
- * Add .gitnexus to .gitignore if not already present
189
+ * Keep generated index files ignored without modifying the user's root .gitignore.
189
190
  */
190
- export const addToGitignore = async (repoPath) => {
191
- const gitignorePath = path.join(repoPath, '.gitignore');
191
+ export const ensureGitNexusIgnored = async (repoPath) => {
192
+ const gitignorePath = path.join(getStoragePath(repoPath), '.gitignore');
193
+ await fs.mkdir(path.dirname(gitignorePath), { recursive: true });
194
+ await fs.writeFile(gitignorePath, '*\n', 'utf-8');
195
+ await ensureGitInfoExclude(repoPath);
196
+ };
197
+ const ensureGitInfoExclude = async (repoPath) => {
198
+ const gitDirPath = path.join(path.resolve(repoPath), '.git');
199
+ const excludePath = path.join(gitDirPath, 'info', 'exclude');
192
200
  try {
193
- const content = await fs.readFile(gitignorePath, 'utf-8');
194
- if (content.includes(GITNEXUS_DIR))
201
+ const gitDir = await fs.stat(gitDirPath);
202
+ if (!gitDir.isDirectory())
195
203
  return;
196
- const newContent = content.endsWith('\n')
197
- ? `${content}${GITNEXUS_DIR}\n`
198
- : `${content}\n${GITNEXUS_DIR}\n`;
199
- await fs.writeFile(gitignorePath, newContent, 'utf-8');
200
204
  }
201
205
  catch {
202
- // .gitignore doesn't exist, create it
203
- await fs.writeFile(gitignorePath, `${GITNEXUS_DIR}\n`, 'utf-8');
206
+ return;
204
207
  }
208
+ await fs.mkdir(path.dirname(excludePath), { recursive: true });
209
+ let content = '';
210
+ try {
211
+ content = await fs.readFile(excludePath, 'utf-8');
212
+ }
213
+ catch (err) {
214
+ if (err?.code !== 'ENOENT')
215
+ throw err;
216
+ }
217
+ const excludes = content
218
+ .split(/\r?\n/)
219
+ .map((line) => line.trim())
220
+ .filter((line) => line && !line.startsWith('#'));
221
+ if (excludes.includes(GITNEXUS_DIR) || excludes.includes(GITNEXUS_EXCLUDE_ENTRY))
222
+ return;
223
+ const separator = content.length === 0 || content.endsWith('\n') ? '' : '\n';
224
+ await fs.writeFile(excludePath, `${content}${separator}${GITNEXUS_EXCLUDE_ENTRY}\n`, 'utf-8');
205
225
  };
206
226
  // ─── Global Registry (~/.gitnexus/registry.json) ───────────────────────
207
227
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.4-rc.38",
3
+ "version": "1.6.4-rc.39",
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",