feat(gitnexus): v1.2.9 — impact enrichment, cypher markdown, Windows setup fix

- Impact tool now returns risk score, affected processes/modules, and summary
- Cypher tool formats results as markdown tables for LLM readability
- Context tool includes module (functional area) field
- Semantic search skips model init when embeddings are disabled
- Setup: wrap npx in cmd /c on Windows for .cmd script compatibility
- Embedder: silence stderr during ONNX model load to protect MCP stdio
- API: use executeCypher directly to avoid double formatting
- Add community integrations section to READMEs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
abhigyanpatwari 2026-02-25 17:10:51 +05:30
parent 6be54ce9d3
commit 91289404c2
11 changed files with 166 additions and 25 deletions

View file

@ -3,7 +3,7 @@
<!-- gitnexus:start -->
# GitNexus MCP
This project is indexed by GitNexus as **GitnexusV2** (1309 symbols, 3350 relationships, 101 execution flows).
This project is indexed by GitNexus as **GitnexusV2** (1348 symbols, 3469 relationships, 104 execution flows).
GitNexus provides a knowledge graph over this codebase — call chains, blast radius, execution flows, and semantic search.

View file

@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus MCP
This project is indexed by GitNexus as **GitnexusV2** (1309 symbols, 3350 relationships, 101 execution flows).
This project is indexed by GitNexus as **GitnexusV2** (1348 symbols, 3469 relationships, 104 execution flows).
GitNexus provides a knowledge graph over this codebase — call chains, blast radius, execution flows, and semantic search.

View file

@ -72,6 +72,12 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
> **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context.
### Community Integrations
| Agent | Install | Source |
|-------|---------|--------|
| [pi](https://pi.dev) | `pi install npm:pi-gitnexus` | [pi-gitnexus](https://github.com/tintinweb/pi-gitnexus) |
If you prefer manual configuration:
**Claude Code** (full support — MCP + skills + hooks):

View file

@ -39,6 +39,12 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
> **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context.
### Community Integrations
| Agent | Install | Source |
|-------|---------|--------|
| [pi](https://pi.dev) | `pi install npm:pi-gitnexus` | [pi-gitnexus](https://github.com/tintinweb/pi-gitnexus) |
## MCP Setup (manual)
If you prefer to configure manually instead of using `gitnexus setup`:

View file

@ -1,12 +1,12 @@
{
"name": "gitnexus",
"version": "1.1.9",
"version": "1.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gitnexus",
"version": "1.1.9",
"version": "1.2.9",
"license": "PolyForm-Noncommercial-1.0.0",
"dependencies": {
"@huggingface/transformers": "^3.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "gitnexus",
"version": "1.2.8",
"version": "1.2.9",
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
"author": "Abhigyan Patwari",
"license": "PolyForm-Noncommercial-1.0.0",

View file

@ -22,9 +22,16 @@ interface SetupResult {
}
/**
* The MCP server entry for all editors
* The MCP server entry for all editors.
* On Windows, npx must be invoked via cmd /c since it's a .cmd script.
*/
function getMcpEntry() {
if (process.platform === 'win32') {
return {
command: 'cmd',
args: ['/c', 'npx', '-y', 'gitnexus@latest', 'mcp'],
};
}
return {
command: 'npx',
args: ['-y', 'gitnexus@latest', 'mcp'],

View file

@ -43,10 +43,13 @@ export const initEmbedder = async (): Promise<FeatureExtractionPipeline> => {
for (const device of devicesToTry) {
try {
// Silence stdout during model load — ONNX Runtime and transformers.js
// may write progress/init messages to stdout which corrupts MCP stdio protocol.
const origWrite = process.stdout.write;
// Silence stdout and stderr during model load — ONNX Runtime and transformers.js
// may write progress/init messages that corrupt MCP stdio protocol or produce
// noisy warnings (e.g. node assignment to execution providers).
const origStdout = process.stdout.write;
const origStderr = process.stderr.write;
process.stdout.write = (() => true) as any;
process.stderr.write = (() => true) as any;
try {
embedderInstance = await (pipeline as any)(
'feature-extraction',
@ -57,7 +60,8 @@ export const initEmbedder = async (): Promise<FeatureExtractionPipeline> => {
}
);
} finally {
process.stdout.write = origWrite;
process.stdout.write = origStdout;
process.stderr.write = origStderr;
}
console.error(`GitNexus: Embedding model loaded (${device})`);
return embedderInstance!;

View file

@ -279,8 +279,10 @@ export class LocalBackend {
switch (method) {
case 'query':
return this.query(repo, params);
case 'cypher':
return this.cypher(repo, params);
case 'cypher': {
const raw = await this.cypher(repo, params);
return this.formatCypherAsMarkdown(raw);
}
case 'context':
return this.context(repo, params);
case 'impact':
@ -395,16 +397,18 @@ export class LocalBackend {
`);
} catch { /* symbol might not be in any process */ }
// Get cluster cohesion as internal ranking signal (never exposed)
// Get cluster membership + cohesion (cohesion used as internal ranking signal)
let cohesion = 0;
let module: string | undefined;
try {
const cohesionRows = await executeQuery(repo.id, `
MATCH (n {id: '${escaped}'})-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
RETURN c.cohesion AS cohesion
RETURN c.cohesion AS cohesion, c.heuristicLabel AS module
LIMIT 1
`);
if (cohesionRows.length > 0) {
cohesion = (cohesionRows[0].cohesion ?? cohesionRows[0][0]) || 0;
module = cohesionRows[0].module ?? cohesionRows[0][1];
}
} catch { /* no cluster info */ }
@ -429,6 +433,7 @@ export class LocalBackend {
filePath: sym.filePath,
startLine: sym.startLine,
endLine: sym.endLine,
...(module ? { module } : {}),
...(includeContent && content ? { content } : {}),
};
@ -577,6 +582,10 @@ export class LocalBackend {
*/
private async semanticSearch(repo: RepoHandle, query: string, limit: number): Promise<any[]> {
try {
// Check if embedding table exists before loading the model (avoids heavy model init when embeddings are off)
const tableCheck = await executeQuery(repo.id, `MATCH (e:CodeEmbedding) RETURN COUNT(*) AS cnt LIMIT 1`);
if (!tableCheck.length || (tableCheck[0].cnt ?? tableCheck[0][0]) === 0) return [];
const queryVec = await embedQuery(query);
const dims = getEmbeddingDims();
const queryVecStr = `[${queryVec.join(',')}]`;
@ -630,8 +639,8 @@ export class LocalBackend {
}
return results;
} catch (err: any) {
console.error('GitNexus: Semantic search unavailable -', err.message);
} catch {
// Expected when embeddings are disabled — silently fall back to BM25-only
return [];
}
}
@ -643,11 +652,11 @@ export class LocalBackend {
private async cypher(repo: RepoHandle, params: { query: string }): Promise<any> {
await this.ensureInitialized(repo.id);
if (!isKuzuReady(repo.id)) {
return { error: 'KuzuDB not ready. Index may be corrupted.' };
}
try {
const result = await executeQuery(repo.id, params.query);
return result;
@ -656,6 +665,36 @@ export class LocalBackend {
}
}
/**
* Format raw Cypher result rows as a markdown table for LLM readability.
* Falls back to raw result if rows aren't tabular objects.
*/
private formatCypherAsMarkdown(result: any): any {
if (!Array.isArray(result) || result.length === 0) return result;
const firstRow = result[0];
if (typeof firstRow !== 'object' || firstRow === null) return result;
const keys = Object.keys(firstRow);
if (keys.length === 0) return result;
const header = '| ' + keys.join(' | ') + ' |';
const separator = '| ' + keys.map(() => '---').join(' | ') + ' |';
const dataRows = result.map((row: any) =>
'| ' + keys.map(k => {
const v = row[k];
if (v === null || v === undefined) return '';
if (typeof v === 'object') return JSON.stringify(v);
return String(v);
}).join(' | ') + ' |'
);
return {
markdown: [header, separator, ...dataRows].join('\n'),
row_count: result.length,
};
}
/**
* Aggregate same-named clusters: group by heuristicLabel, sum symbols,
* weighted-average cohesion, filter out tiny clusters (<5 symbols).
@ -1318,7 +1357,69 @@ export class LocalBackend {
if (!grouped[item.depth]) grouped[item.depth] = [];
grouped[item.depth].push(item);
}
// ── Enrichment: affected processes, modules, risk ──────────────
const directCount = (grouped[1] || []).length;
let affectedProcesses: any[] = [];
let affectedModules: any[] = [];
if (impacted.length > 0) {
const allIds = impacted.map(i => `'${i.id.replace(/'/g, "''")}'`).join(', ');
const d1Ids = (grouped[1] || []).map((i: any) => `'${i.id.replace(/'/g, "''")}'`).join(', ');
// Affected processes: which execution flows are broken and at which step
const [processRows, moduleRows, directModuleRows] = await Promise.all([
executeQuery(repo.id, `
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
WHERE s.id IN [${allIds}]
RETURN p.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits, MIN(r.step) AS minStep, p.stepCount AS stepCount
ORDER BY hits DESC
LIMIT 20
`).catch(() => []),
executeQuery(repo.id, `
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
WHERE s.id IN [${allIds}]
RETURN c.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits
ORDER BY hits DESC
LIMIT 20
`).catch(() => []),
d1Ids ? executeQuery(repo.id, `
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
WHERE s.id IN [${d1Ids}]
RETURN DISTINCT c.heuristicLabel AS name
`).catch(() => []) : Promise.resolve([]),
]);
affectedProcesses = processRows.map((r: any) => ({
name: r.name || r[0],
hits: r.hits || r[1],
broken_at_step: r.minStep ?? r[2],
step_count: r.stepCount ?? r[3],
}));
const directModuleSet = new Set(directModuleRows.map((r: any) => r.name || r[0]));
affectedModules = moduleRows.map((r: any) => {
const name = r.name || r[0];
return {
name,
hits: r.hits || r[1],
impact: directModuleSet.has(name) ? 'direct' : 'indirect',
};
});
}
// Risk scoring
const processCount = affectedProcesses.length;
const moduleCount = affectedModules.length;
let risk = 'LOW';
if (directCount >= 30 || processCount >= 5 || moduleCount >= 5 || impacted.length >= 200) {
risk = 'CRITICAL';
} else if (directCount >= 15 || processCount >= 3 || moduleCount >= 3 || impacted.length >= 100) {
risk = 'HIGH';
} else if (directCount >= 5 || impacted.length >= 30) {
risk = 'MEDIUM';
}
return {
target: {
id: symId,
@ -1328,6 +1429,14 @@ export class LocalBackend {
},
direction,
impactedCount: impacted.length,
risk,
summary: {
direct: directCount,
processes_affected: processCount,
modules_affected: moduleCount,
},
affected_processes: affectedProcesses,
affected_modules: affectedModules,
byDepth: grouped,
};
}

View file

@ -49,7 +49,7 @@ AFTER THIS: Use context() on a specific symbol for 360-degree view (callers, cal
Returns results grouped by process (execution flow):
- processes: ranked execution flows with relevance priority
- process_symbols: all symbols in those flows with file locations
- process_symbols: all symbols in those flows with file locations and module (functional area)
- definitions: standalone types/interfaces not in any process
Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.`,
@ -91,6 +91,8 @@ EXAMPLES:
• Trace a process:
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process) WHERE p.heuristicLabel = "UserLogin" RETURN s.name, r.step ORDER BY r.step
OUTPUT: Returns { markdown, row_count } — results formatted as a Markdown table for easy reading.
TIPS:
- All relationships use single CodeRelation table — filter with {type: 'CALLS'} etc.
- Community = auto-detected functional area (Leiden algorithm)
@ -172,10 +174,17 @@ Each edit is tagged with confidence:
{
name: 'impact',
description: `Analyze the blast radius of changing a code symbol.
Returns all symbols affected by modifying the target, grouped by depth with edge types and confidence.
Returns affected symbols grouped by depth, plus risk assessment, affected execution flows, and affected modules.
WHEN TO USE: Before making code changes — especially refactoring, renaming, or modifying shared code. Shows what would break.
AFTER THIS: Review d=1 items (WILL BREAK). READ gitnexus://repo/{name}/processes to check affected execution flows.
AFTER THIS: Review d=1 items (WILL BREAK). Use context() on high-risk symbols.
Output includes:
- risk: LOW / MEDIUM / HIGH / CRITICAL
- summary: direct callers, processes affected, modules affected
- affected_processes: which execution flows break and at which step
- affected_modules: which functional areas are hit (direct vs indirect)
- byDepth: all affected symbols grouped by traversal depth
Depth groups:
- d=1: WILL BREAK (direct callers/importers)

View file

@ -191,9 +191,9 @@ export const createServer = async (port: number) => {
return;
}
const result = await backend.callTool('cypher', { repo: repoName, query: cypher });
if (result && !Array.isArray(result) && result.error) {
res.status(500).json({ error: result.error });
const result = await backend.executeCypher(repoName, cypher);
if (result && !Array.isArray(result) && (result as any).error) {
res.status(500).json({ error: (result as any).error });
return;
}
res.json({ result });