gitnexus 1.1.4 → 1.1.6

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,6 +10,7 @@ interface RepoStats {
10
10
  nodes?: number;
11
11
  edges?: number;
12
12
  communities?: number;
13
+ clusters?: number;
13
14
  processes?: number;
14
15
  }
15
16
  /**
@@ -17,6 +17,7 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
17
17
  * Generate the full GitNexus context content (resources-first approach)
18
18
  */
19
19
  function generateGitNexusContent(projectName, stats) {
20
+ const clusterCount = stats.clusters || stats.communities || 0;
20
21
  return `${GITNEXUS_START_MARKER}
21
22
  # GitNexus MCP
22
23
 
@@ -29,9 +30,11 @@ This project is indexed as **${projectName}** by GitNexus, providing AI agents w
29
30
  | Files | ${stats.files || 0} |
30
31
  | Symbols | ${stats.nodes || 0} |
31
32
  | Relationships | ${stats.edges || 0} |
32
- | Communities | ${stats.communities || 0} |
33
+ | Clusters | ${clusterCount} |
33
34
  | Processes | ${stats.processes || 0} |
34
35
 
36
+ > **Staleness:** If the index is out of date, run \`npx gitnexus analyze\` in the terminal to refresh. The \`gitnexus://repo/${projectName}/context\` resource will warn you when the index is stale.
37
+
35
38
  ## Quick Start
36
39
 
37
40
  \`\`\`
@@ -64,6 +67,8 @@ This project is indexed as **${projectName}** by GitNexus, providing AI agents w
64
67
  | \`impact\` | Blast radius analysis | Before making changes |
65
68
  | \`cypher\` | Raw graph queries | Complex analysis |
66
69
 
70
+ > **Re-indexing:** To refresh a stale index, run \`npx gitnexus analyze\` in the terminal. Use \`--force\` only to rebuild from scratch. This is a CLI command, not an MCP tool.
71
+
67
72
  > **Multi-repo:** When multiple repos are indexed, pass \`repo: "${projectName}"\` to target this project.
68
73
 
69
74
  ## Workflow Examples
@@ -71,17 +76,17 @@ This project is indexed as **${projectName}** by GitNexus, providing AI agents w
71
76
  ### Exploring the Codebase
72
77
  \`\`\`
73
78
  READ gitnexus://repos → Discover repos
74
- READ gitnexus://repo/${projectName}/context → Stats and overview
75
- READ gitnexus://repo/${projectName}/clusters → Find relevant cluster
76
- READ gitnexus://repo/${projectName}/cluster/Auth Explore Auth cluster
77
- gitnexus_explore({name: "validateUser", type: "symbol", repo: "${projectName}"})
79
+ READ gitnexus://repo/${projectName}/context → Stats and overview (check for staleness)
80
+ READ gitnexus://repo/${projectName}/clusters → Find relevant cluster by name
81
+ READ gitnexus://repo/${projectName}/cluster/{name} See members of that cluster
82
+ gitnexus_explore({name: "<symbol_name>", type: "symbol", repo: "${projectName}"})
78
83
  \`\`\`
79
84
 
80
85
  ### Planning a Change
81
86
  \`\`\`
82
- gitnexus_impact({target: "UserService", direction: "upstream", repo: "${projectName}"})
83
- READ gitnexus://repo/${projectName}/processes → Check affected flows
84
- gitnexus_explore({name: "LoginFlow", type: "process", repo: "${projectName}"})
87
+ gitnexus_search({query: "<what you want to change>", repo: "${projectName}"})
88
+ gitnexus_impact({target: "<symbol_name>", direction: "upstream", repo: "${projectName}"})
89
+ READ gitnexus://repo/${projectName}/processes → Check affected execution flows
85
90
  \`\`\`
86
91
 
87
92
  ## Graph Schema
@@ -70,6 +70,7 @@ export const analyzeCommand = async (inputPath, options) => {
70
70
  await createFTSIndex('Function', 'function_fts', ['name', 'content']);
71
71
  await createFTSIndex('Class', 'class_fts', ['name', 'content']);
72
72
  await createFTSIndex('Method', 'method_fts', ['name', 'content']);
73
+ await createFTSIndex('Interface', 'interface_fts', ['name', 'content']);
73
74
  }
74
75
  catch (e) {
75
76
  // FTS index creation may fail if tables are empty (no data for that type)
@@ -103,11 +104,23 @@ export const analyzeCommand = async (inputPath, options) => {
103
104
  await addToGitignore(repoPath);
104
105
  // Generate AI context files
105
106
  const projectName = path.basename(repoPath);
107
+ // Compute aggregated cluster count (grouped by heuristicLabel, >=5 symbols)
108
+ // This matches the aggregation logic in local-backend.ts for tool output consistency.
109
+ let aggregatedClusterCount = 0;
110
+ if (pipelineResult.communityResult?.communities) {
111
+ const groups = new Map();
112
+ for (const c of pipelineResult.communityResult.communities) {
113
+ const label = c.heuristicLabel || c.label || 'Unknown';
114
+ groups.set(label, (groups.get(label) || 0) + c.symbolCount);
115
+ }
116
+ aggregatedClusterCount = Array.from(groups.values()).filter(count => count >= 5).length;
117
+ }
106
118
  const aiContext = await generateAIContextFiles(repoPath, storagePath, projectName, {
107
119
  files: pipelineResult.fileContents.size,
108
120
  nodes: stats.nodes,
109
121
  edges: stats.edges,
110
122
  communities: pipelineResult.communityResult?.stats.totalCommunities,
123
+ clusters: aggregatedClusterCount,
111
124
  processes: pipelineResult.processResult?.stats.totalProcesses,
112
125
  });
113
126
  // Close database
@@ -121,10 +121,16 @@ export const processProcesses = async (knowledgeGraph, memberships, onProgress,
121
121
  },
122
122
  };
123
123
  };
124
+ /**
125
+ * Minimum edge confidence for process tracing.
126
+ * Filters out ambiguous fuzzy-global matches (0.3) that cause
127
+ * traces to jump across unrelated code areas.
128
+ */
129
+ const MIN_TRACE_CONFIDENCE = 0.5;
124
130
  const buildCallsGraph = (graph) => {
125
131
  const adj = new Map();
126
132
  graph.relationships.forEach(rel => {
127
- if (rel.type === 'CALLS') {
133
+ if (rel.type === 'CALLS' && rel.confidence >= MIN_TRACE_CONFIDENCE) {
128
134
  if (!adj.has(rel.sourceId)) {
129
135
  adj.set(rel.sourceId, []);
130
136
  }
@@ -136,7 +142,7 @@ const buildCallsGraph = (graph) => {
136
142
  const buildReverseCallsGraph = (graph) => {
137
143
  const adj = new Map();
138
144
  graph.relationships.forEach(rel => {
139
- if (rel.type === 'CALLS') {
145
+ if (rel.type === 'CALLS' && rel.confidence >= MIN_TRACE_CONFIDENCE) {
140
146
  if (!adj.has(rel.targetId)) {
141
147
  adj.set(rel.targetId, []);
142
148
  }
@@ -44,25 +44,27 @@ async function queryFTSViaExecutor(executor, tableName, indexName, query, limit)
44
44
  * @returns Ranked search results from FTS indexes
45
45
  */
46
46
  export const searchFTSFromKuzu = async (query, limit = 20, repoId) => {
47
- let fileResults, functionResults, classResults, methodResults;
47
+ let fileResults, functionResults, classResults, methodResults, interfaceResults;
48
48
  if (repoId) {
49
49
  // Use MCP connection pool via dynamic import
50
50
  const { executeQuery } = await import('../../mcp/core/kuzu-adapter.js');
51
51
  const executor = (cypher) => executeQuery(repoId, cypher);
52
- [fileResults, functionResults, classResults, methodResults] = await Promise.all([
52
+ [fileResults, functionResults, classResults, methodResults, interfaceResults] = await Promise.all([
53
53
  queryFTSViaExecutor(executor, 'File', 'file_fts', query, limit),
54
54
  queryFTSViaExecutor(executor, 'Function', 'function_fts', query, limit),
55
55
  queryFTSViaExecutor(executor, 'Class', 'class_fts', query, limit),
56
56
  queryFTSViaExecutor(executor, 'Method', 'method_fts', query, limit),
57
+ queryFTSViaExecutor(executor, 'Interface', 'interface_fts', query, limit),
57
58
  ]);
58
59
  }
59
60
  else {
60
61
  // Use core kuzu adapter (CLI / pipeline context)
61
- [fileResults, functionResults, classResults, methodResults] = await Promise.all([
62
+ [fileResults, functionResults, classResults, methodResults, interfaceResults] = await Promise.all([
62
63
  queryFTS('File', 'file_fts', query, limit, false).catch(() => []),
63
64
  queryFTS('Function', 'function_fts', query, limit, false).catch(() => []),
64
65
  queryFTS('Class', 'class_fts', query, limit, false).catch(() => []),
65
66
  queryFTS('Method', 'method_fts', query, limit, false).catch(() => []),
67
+ queryFTS('Interface', 'interface_fts', query, limit, false).catch(() => []),
66
68
  ]);
67
69
  }
68
70
  // Merge results by filePath, summing scores for same file
@@ -82,6 +84,7 @@ export const searchFTSFromKuzu = async (query, limit = 20, repoId) => {
82
84
  addResults(functionResults);
83
85
  addResults(classResults);
84
86
  addResults(methodResults);
87
+ addResults(interfaceResults);
85
88
  // Sort by score descending and add rank
86
89
  const sorted = Array.from(merged.values())
87
90
  .sort((a, b) => b.score - a.score)
@@ -81,7 +81,6 @@ export declare class LocalBackend {
81
81
  private overview;
82
82
  private explore;
83
83
  private impact;
84
- private analyze;
85
84
  disconnect(): Promise<void>;
86
85
  }
87
86
  export {};
@@ -8,9 +8,11 @@
8
8
  import path from 'path';
9
9
  import { initKuzu, executeQuery, closeKuzu, isKuzuReady } from '../core/kuzu-adapter.js';
10
10
  import { embedQuery, getEmbeddingDims, disposeEmbedder } from '../core/embedder.js';
11
- import { isGitRepo, getCurrentCommit } from '../../storage/git.js';
12
- import { getStoragePaths as getRepoStoragePaths, saveMeta as saveRepoMeta, loadMeta as loadRepoMeta, addToGitignore, listRegisteredRepos, registerRepo, } from '../../storage/repo-manager.js';
13
- import { generateAIContextFiles } from '../../cli/ai-context.js';
11
+ // git utilities available if needed
12
+ // import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
13
+ import { listRegisteredRepos, } from '../../storage/repo-manager.js';
14
+ // AI context generation is CLI-only (gitnexus analyze)
15
+ // import { generateAIContextFiles } from '../../cli/ai-context.js';
14
16
  /**
15
17
  * Quick test-file detection for filtering impact results.
16
18
  * Matches common test file patterns across all supported languages.
@@ -123,7 +125,8 @@ export class LocalBackend {
123
125
  }
124
126
  // ─── Lazy KuzuDB Init ────────────────────────────────────────────
125
127
  async ensureInitialized(repoId) {
126
- if (this.initializedRepos.has(repoId))
128
+ // Always check the actual pool — the idle timer may have evicted the connection
129
+ if (this.initializedRepos.has(repoId) && isKuzuReady(repoId))
127
130
  return;
128
131
  const handle = this.repos.get(repoId);
129
132
  if (!handle)
@@ -174,8 +177,6 @@ export class LocalBackend {
174
177
  return this.explore(repo, params);
175
178
  case 'impact':
176
179
  return this.impact(repo, params);
177
- case 'analyze':
178
- return this.analyze(repo, params);
179
180
  default:
180
181
  throw new Error(`Unknown tool: ${method}`);
181
182
  }
@@ -192,11 +193,13 @@ export class LocalBackend {
192
193
  this.semanticSearch(repo, query, limit * 2),
193
194
  ]);
194
195
  // Merge and deduplicate results using reciprocal rank fusion
196
+ // Key by nodeId (symbol-level) so semantic precision is preserved.
197
+ // Fall back to filePath for File-level results that lack a nodeId.
195
198
  const scoreMap = new Map();
196
199
  // BM25 results
197
200
  for (let i = 0; i < bm25Results.length; i++) {
198
201
  const result = bm25Results[i];
199
- const key = result.filePath;
202
+ const key = result.nodeId || result.filePath;
200
203
  const rrfScore = 1 / (60 + i);
201
204
  const existing = scoreMap.get(key);
202
205
  if (existing) {
@@ -210,7 +213,7 @@ export class LocalBackend {
210
213
  // Semantic results
211
214
  for (let i = 0; i < semanticResults.length; i++) {
212
215
  const result = semanticResults[i];
213
- const key = result.filePath;
216
+ const key = result.nodeId || result.filePath;
214
217
  const rrfScore = 1 / (60 + i);
215
218
  const existing = scoreMap.get(key);
216
219
  if (existing) {
@@ -252,10 +255,12 @@ export class LocalBackend {
252
255
  }
253
256
  }
254
257
  // Add relationships if depth is 'full' and we have a node ID
258
+ // Only include connections with actual name/path data (skip MEMBER_OF, STEP_IN_PROCESS noise)
255
259
  if (depth === 'full' && result.nodeId) {
256
260
  try {
257
261
  const relQuery = `
258
262
  MATCH (n {id: '${result.nodeId.replace(/'/g, "''")}'})-[r:CodeRelation]->(m)
263
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'DEFINES', 'EXTENDS', 'IMPLEMENTS']
259
264
  RETURN r.type AS type, m.name AS targetName, m.filePath AS targetPath
260
265
  LIMIT 5
261
266
  `;
@@ -282,11 +287,11 @@ export class LocalBackend {
282
287
  const bm25Results = await searchFTSFromKuzu(query, limit, repo.id);
283
288
  const results = [];
284
289
  for (const bm25Result of bm25Results) {
285
- const fileName = bm25Result.filePath.split('/').pop() || bm25Result.filePath;
290
+ const fullPath = bm25Result.filePath;
286
291
  try {
287
292
  const symbolQuery = `
288
293
  MATCH (n)
289
- WHERE n.filePath CONTAINS '${fileName.replace(/'/g, "''")}'
294
+ WHERE n.filePath = '${fullPath.replace(/'/g, "''")}'
290
295
  RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
291
296
  LIMIT 3
292
297
  `;
@@ -305,6 +310,7 @@ export class LocalBackend {
305
310
  }
306
311
  }
307
312
  else {
313
+ const fileName = fullPath.split('/').pop() || fullPath;
308
314
  results.push({
309
315
  name: fileName,
310
316
  type: 'File',
@@ -314,6 +320,7 @@ export class LocalBackend {
314
320
  }
315
321
  }
316
322
  catch {
323
+ const fileName = fullPath.split('/').pop() || fullPath;
317
324
  results.push({
318
325
  name: fileName,
319
326
  type: 'File',
@@ -485,15 +492,19 @@ export class LocalBackend {
485
492
  await this.ensureInitialized(repo.id);
486
493
  const { name, type } = params;
487
494
  if (type === 'symbol') {
488
- const symbolQuery = `
489
- MATCH (n)
490
- WHERE n.name = '${name.replace(/'/g, "''")}'
491
- RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
492
- LIMIT 1
493
- `;
495
+ // If name contains a path separator or ':', treat it as a qualified lookup
496
+ const isQualified = name.includes('/') || name.includes(':');
497
+ const symbolQuery = isQualified
498
+ ? `MATCH (n) WHERE n.id = '${name.replace(/'/g, "''")}' OR (n.name = '${name.replace(/'/g, "''")}')
499
+ RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
500
+ LIMIT 5`
501
+ : `MATCH (n) WHERE n.name = '${name.replace(/'/g, "''")}'
502
+ RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
503
+ LIMIT 5`;
494
504
  const symbols = await executeQuery(repo.id, symbolQuery);
495
505
  if (symbols.length === 0)
496
506
  return { error: `Symbol '${name}' not found` };
507
+ // Use the first match for detailed exploration
497
508
  const sym = symbols[0];
498
509
  const symId = sym.id || sym[0];
499
510
  const callersQuery = `
@@ -514,7 +525,7 @@ export class LocalBackend {
514
525
  LIMIT 1
515
526
  `;
516
527
  const communities = await executeQuery(repo.id, communityQuery);
517
- return {
528
+ const result = {
518
529
  symbol: {
519
530
  id: symId,
520
531
  name: sym.name || sym[1],
@@ -530,6 +541,16 @@ export class LocalBackend {
530
541
  heuristicLabel: communities[0].heuristicLabel || communities[0][1],
531
542
  } : null,
532
543
  };
544
+ // If multiple symbols share the same name, show alternatives so the agent can disambiguate
545
+ if (symbols.length > 1) {
546
+ result.alternatives = symbols.slice(1).map((s) => ({
547
+ id: s.id || s[0],
548
+ type: s.type || s[2],
549
+ filePath: s.filePath || s[3],
550
+ }));
551
+ result.hint = `Multiple symbols named '${name}' found. Showing details for ${result.symbol.filePath}. Use the full node ID to explore a specific alternative.`;
552
+ }
553
+ return result;
533
554
  }
534
555
  if (type === 'cluster') {
535
556
  const escaped = name.replace(/'/g, "''");
@@ -689,121 +710,6 @@ export class LocalBackend {
689
710
  byDepth: grouped,
690
711
  };
691
712
  }
692
- async analyze(repo, params) {
693
- let repoPath;
694
- if (params.path) {
695
- repoPath = path.resolve(params.path);
696
- }
697
- else {
698
- repoPath = repo.repoPath;
699
- }
700
- if (!isGitRepo(repoPath)) {
701
- return { error: 'Not a git repository' };
702
- }
703
- const { storagePath, kuzuPath } = getRepoStoragePaths(repoPath);
704
- const currentCommit = getCurrentCommit(repoPath);
705
- const existingMeta = await loadRepoMeta(storagePath);
706
- if (existingMeta && !params.force && existingMeta.lastCommit === currentCommit) {
707
- return { status: 'up_to_date', message: 'Repository already up to date.' };
708
- }
709
- // Close this repo's MCP connection before pipeline takes over
710
- await closeKuzu(repo.id);
711
- this.initializedRepos.delete(repo.id);
712
- try {
713
- const { runPipelineFromRepo } = await import('../../core/ingestion/pipeline.js');
714
- const coreKuzu = await import('../../core/kuzu/kuzu-adapter.js');
715
- console.error('GitNexus: Running indexing pipeline...');
716
- const pipelineResult = await runPipelineFromRepo(repoPath, (progress) => {
717
- if (progress.percent % 20 === 0) {
718
- console.error(`GitNexus: ${progress.phase} ${progress.percent}%`);
719
- }
720
- });
721
- console.error('GitNexus: Loading graph into KuzuDB...');
722
- await coreKuzu.initKuzu(kuzuPath);
723
- await coreKuzu.loadGraphToKuzu(pipelineResult.graph, pipelineResult.fileContents, storagePath);
724
- console.error('GitNexus: Creating FTS indexes...');
725
- try {
726
- await coreKuzu.createFTSIndex('File', 'file_fts', ['name', 'content']);
727
- await coreKuzu.createFTSIndex('Function', 'function_fts', ['name', 'content']);
728
- await coreKuzu.createFTSIndex('Class', 'class_fts', ['name', 'content']);
729
- await coreKuzu.createFTSIndex('Method', 'method_fts', ['name', 'content']);
730
- }
731
- catch (e) {
732
- console.error('GitNexus: Some FTS indexes may not have been created:', e.message);
733
- }
734
- if (!params.skipEmbeddings) {
735
- try {
736
- console.error('GitNexus: Generating embeddings...');
737
- const { runEmbeddingPipeline } = await import('../../core/embeddings/embedding-pipeline.js');
738
- await runEmbeddingPipeline(coreKuzu.executeQuery, coreKuzu.executeWithReusedStatement, (progress) => {
739
- if (progress.percent % 25 === 0) {
740
- console.error(`GitNexus: Embeddings ${progress.percent}%`);
741
- }
742
- });
743
- }
744
- catch (e) {
745
- console.error('GitNexus: Embedding generation failed (non-fatal):', e.message);
746
- }
747
- }
748
- const stats = await coreKuzu.getKuzuStats();
749
- const newMeta = {
750
- repoPath,
751
- lastCommit: currentCommit,
752
- indexedAt: new Date().toISOString(),
753
- stats: {
754
- files: pipelineResult.fileContents.size,
755
- nodes: stats.nodes,
756
- edges: stats.edges,
757
- communities: pipelineResult.communityResult?.stats.totalCommunities,
758
- processes: pipelineResult.processResult?.stats.totalProcesses,
759
- },
760
- };
761
- await saveRepoMeta(storagePath, newMeta);
762
- await addToGitignore(repoPath);
763
- // Register in global registry
764
- await registerRepo(repoPath, newMeta);
765
- const projectName = path.basename(repoPath);
766
- await generateAIContextFiles(repoPath, storagePath, projectName, {
767
- files: pipelineResult.fileContents.size,
768
- nodes: stats.nodes,
769
- edges: stats.edges,
770
- communities: pipelineResult.communityResult?.stats.totalCommunities,
771
- processes: pipelineResult.processResult?.stats.totalProcesses,
772
- });
773
- await coreKuzu.closeKuzu();
774
- // Update in-memory state
775
- const handle = {
776
- id: repo.id,
777
- name: projectName,
778
- repoPath,
779
- storagePath,
780
- kuzuPath,
781
- indexedAt: newMeta.indexedAt,
782
- lastCommit: newMeta.lastCommit,
783
- stats: newMeta.stats,
784
- };
785
- this.repos.set(repo.id, handle);
786
- this.contextCache.set(repo.id, {
787
- projectName,
788
- stats: {
789
- fileCount: newMeta.stats.files || 0,
790
- functionCount: newMeta.stats.nodes || 0,
791
- communityCount: newMeta.stats.communities || 0,
792
- processCount: newMeta.stats.processes || 0,
793
- },
794
- });
795
- console.error('GitNexus: Indexing complete!');
796
- return {
797
- status: 'success',
798
- message: `Repository indexed successfully.`,
799
- stats: newMeta.stats,
800
- };
801
- }
802
- catch (e) {
803
- console.error('GitNexus: Indexing failed:', e.message);
804
- return { error: `Indexing failed: ${e.message}` };
805
- }
806
- }
807
713
  async disconnect() {
808
714
  await closeKuzu(); // close all connections
809
715
  await disposeEmbedder();
@@ -132,7 +132,6 @@ function getReposResource(backend) {
132
132
  if (repo.stats) {
133
133
  lines.push(` files: ${repo.stats.files || 0}`);
134
134
  lines.push(` symbols: ${repo.stats.nodes || 0}`);
135
- lines.push(` clusters: ${repo.stats.communities || 0}`);
136
135
  lines.push(` processes: ${repo.stats.processes || 0}`);
137
136
  }
138
137
  }
@@ -158,6 +157,15 @@ async function getContextResource(backend, repoName) {
158
157
  const repoPath = repo.repoPath;
159
158
  const lastCommit = repo.lastCommit || 'HEAD';
160
159
  const staleness = repoPath ? checkStaleness(repoPath, lastCommit) : { isStale: false, commitsBehind: 0 };
160
+ // Get aggregated cluster count (matches what overview/clusters resource shows)
161
+ let clusterCount = context.stats.communityCount;
162
+ try {
163
+ const overview = await backend.callTool('overview', { showClusters: true, showProcesses: false, limit: 100, repo: repoName });
164
+ if (overview.clusters) {
165
+ clusterCount = overview.clusters.length;
166
+ }
167
+ }
168
+ catch { /* fall back to raw count */ }
161
169
  const lines = [
162
170
  `project: ${context.projectName}`,
163
171
  ];
@@ -169,7 +177,7 @@ async function getContextResource(backend, repoName) {
169
177
  lines.push('stats:');
170
178
  lines.push(` files: ${context.stats.fileCount}`);
171
179
  lines.push(` symbols: ${context.stats.functionCount}`);
172
- lines.push(` clusters: ${context.stats.communityCount}`);
180
+ lines.push(` clusters: ${clusterCount}`);
173
181
  lines.push(` processes: ${context.stats.processCount}`);
174
182
  lines.push('');
175
183
  lines.push('tools_available:');
@@ -179,7 +187,8 @@ async function getContextResource(backend, repoName) {
179
187
  lines.push(' - impact: Blast radius analysis');
180
188
  lines.push(' - overview: List all clusters and processes');
181
189
  lines.push(' - cypher: Raw graph queries');
182
- lines.push(' - analyze: Re-index to update stale data');
190
+ lines.push('');
191
+ lines.push('re_index: Run `npx gitnexus analyze` in terminal if data is stale');
183
192
  lines.push('');
184
193
  lines.push('resources_available:');
185
194
  lines.push(' - gitnexus://repos: All indexed repositories');
package/dist/mcp/tools.js CHANGED
@@ -20,37 +20,6 @@ on other tools (search, explore, impact, etc.) to target the correct one.`,
20
20
  required: [],
21
21
  },
22
22
  },
23
- {
24
- name: 'analyze',
25
- description: `Index or re-index a repository. Runs the full pipeline in-process.
26
-
27
- Creates .gitnexus/ in repo root with:
28
- - Knowledge graph (functions, classes, calls, imports)
29
- - Full-text search indexes
30
- - Community detection (Leiden)
31
- - Process tracing
32
- - Embeddings for semantic search
33
-
34
- Also registers the repo in the global registry so the MCP server can serve it.
35
-
36
- Run this when:
37
- - First time using GitNexus on a repo
38
- - After major code changes
39
- - When staleness warning appears in gitnexus://context
40
- - When 'not indexed' error appears
41
-
42
- Note: This may take 30-120 seconds for large repos.`,
43
- inputSchema: {
44
- type: 'object',
45
- properties: {
46
- path: { type: 'string', description: 'Repo path (default: current directory)' },
47
- force: { type: 'boolean', description: 'Re-index even if exists', default: false },
48
- skipEmbeddings: { type: 'boolean', description: 'Skip embedding generation (faster)', default: false },
49
- repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
50
- },
51
- required: [],
52
- },
53
- },
54
23
  {
55
24
  name: 'search',
56
25
  description: `Hybrid search (keyword + semantic) across the codebase.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
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",
@@ -8,7 +8,7 @@ description: Trace bugs through call chains using knowledge graph
8
8
  ## Quick Start
9
9
  ```
10
10
  0. READ gitnexus://repos → Discover indexed repos
11
- 1. If "Index is stale" → gitnexus_analyze({repo: "my-app"})
11
+ 1. If "Index is stale" → run `npx gitnexus analyze` in terminal
12
12
  2. gitnexus_search({query: "...", repo: "my-app"}) → Find code related to error
13
13
  3. gitnexus_explore({name, type: "symbol", repo: "my-app"}) → Get callers and callees
14
14
  4. READ gitnexus://repo/my-app/process/{name} → Trace execution flow
@@ -8,7 +8,7 @@ description: Navigate unfamiliar code using GitNexus knowledge graph
8
8
  ## Quick Start
9
9
  ```
10
10
  0. READ gitnexus://repos → Discover indexed repos (use repo param if multiple)
11
- 1. If "Index is stale" → gitnexus_analyze({repo: "my-app"})
11
+ 1. If "Index is stale" → run `npx gitnexus analyze` in terminal
12
12
  2. READ gitnexus://repo/{name}/context → Get codebase overview (~150 tokens)
13
13
  3. READ gitnexus://repo/{name}/clusters → See all functional clusters
14
14
  4. READ gitnexus://repo/{name}/cluster/{name} → Deep dive on specific cluster
@@ -8,7 +8,7 @@ description: Analyze blast radius before making code changes
8
8
  ## Quick Start
9
9
  ```
10
10
  0. READ gitnexus://repos → Discover indexed repos
11
- 1. If "Index is stale" → gitnexus_analyze({repo: "my-app"})
11
+ 1. If "Index is stale" → run `npx gitnexus analyze` in terminal
12
12
  2. gitnexus_impact({target, direction: "upstream", repo: "my-app"}) → What depends on this
13
13
  3. READ gitnexus://repo/my-app/clusters → Check affected areas
14
14
  4. READ gitnexus://repo/my-app/processes → Affected execution flows
@@ -8,7 +8,7 @@ description: Plan safe refactors using blast radius and dependency mapping
8
8
  ## Quick Start
9
9
  ```
10
10
  0. READ gitnexus://repos → Discover indexed repos
11
- 1. If "Index is stale" → gitnexus_analyze({repo: "my-app"})
11
+ 1. If "Index is stale" → run `npx gitnexus analyze` in terminal
12
12
  2. gitnexus_impact({target, direction: "upstream", repo: "my-app"}) → Map all dependents
13
13
  3. READ gitnexus://repo/my-app/schema → Understand graph structure
14
14
  4. gitnexus_cypher({query: "...", repo: "my-app"}) → Find all references