mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
151 lines
5 KiB
TypeScript
151 lines
5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { createKnowledgeGraph } from '../../src/core/graph/graph.js';
|
|
import { processesPhase } from '../../src/core/ingestion/pipeline-phases/processes.js';
|
|
import { toolsPhase } from '../../src/core/ingestion/pipeline-phases/tools.js';
|
|
import type {
|
|
PhaseResult,
|
|
PipelineContext,
|
|
} from '../../src/core/ingestion/pipeline-phases/types.js';
|
|
import type { KnowledgeGraph } from '../../src/core/graph/types.js';
|
|
import type { GraphNode, GraphRelationship, NodeLabel } from 'gitnexus-shared';
|
|
|
|
function makeCtx(graph: KnowledgeGraph, repoPath = 'D:/tmp/repo'): PipelineContext {
|
|
return {
|
|
repoPath,
|
|
graph,
|
|
onProgress: () => {},
|
|
pipelineStart: 0,
|
|
};
|
|
}
|
|
|
|
function phaseResult<T>(phaseName: string, output: T): PhaseResult<T> {
|
|
return { phaseName, output, durationMs: 0 };
|
|
}
|
|
|
|
function addNode(
|
|
graph: KnowledgeGraph,
|
|
id: string,
|
|
label: NodeLabel,
|
|
name: string,
|
|
filePath: string,
|
|
) {
|
|
graph.addNode({
|
|
id,
|
|
label,
|
|
properties: {
|
|
name,
|
|
filePath,
|
|
startLine: 1,
|
|
endLine: 1,
|
|
isExported: true,
|
|
content: '',
|
|
},
|
|
} satisfies GraphNode);
|
|
}
|
|
|
|
function addCall(graph: KnowledgeGraph, sourceId: string, targetId: string) {
|
|
graph.addRelationship({
|
|
id: `${sourceId}->${targetId}`,
|
|
sourceId,
|
|
targetId,
|
|
type: 'CALLS',
|
|
confidence: 1,
|
|
reason: 'direct',
|
|
} satisfies GraphRelationship);
|
|
}
|
|
|
|
describe('Tool handler and process linking phases', () => {
|
|
it('falls back to the file node when a parsed tool handler is missing from the graph', async () => {
|
|
const graph = createKnowledgeGraph();
|
|
addNode(graph, 'File:src/tools.py', 'File', 'tools.py', 'src/tools.py');
|
|
|
|
const output = await toolsPhase.execute(
|
|
makeCtx(graph),
|
|
new Map([
|
|
[
|
|
'parse',
|
|
phaseResult('parse', {
|
|
allToolDefs: [
|
|
{
|
|
filePath: 'src/tools.py',
|
|
toolName: 'stale_tool',
|
|
description: 'Stale handler',
|
|
lineNumber: 1,
|
|
handlerNodeId: 'Function:src/tools.py:missing',
|
|
},
|
|
],
|
|
allPaths: [],
|
|
}),
|
|
],
|
|
]),
|
|
);
|
|
|
|
expect(output.toolDefs).toEqual([
|
|
{ name: 'stale_tool', filePath: 'src/tools.py', description: 'Stale handler' },
|
|
]);
|
|
|
|
const edge = graph.relationships.find((rel) => rel.type === 'HANDLES_TOOL');
|
|
expect(edge).toMatchObject({
|
|
sourceId: 'File:src/tools.py',
|
|
targetId: 'Tool:stale_tool',
|
|
});
|
|
});
|
|
|
|
it('does not attach file-level fallback tools to handler-specific processes', async () => {
|
|
const graph = createKnowledgeGraph();
|
|
const filePath = 'src/tools.ts';
|
|
const alpha = 'Function:src/tools.ts:alpha';
|
|
const alphaHelper = 'Function:src/tools.ts:alphaHelper';
|
|
const alphaLeaf = 'Function:src/tools.ts:alphaLeaf';
|
|
const fileEntry = 'Function:src/tools.ts:fileEntry';
|
|
const fileHelper = 'Function:src/tools.ts:fileHelper';
|
|
const fileLeaf = 'Function:src/tools.ts:fileLeaf';
|
|
|
|
addNode(graph, 'File:src/tools.ts', 'File', 'tools.ts', filePath);
|
|
addNode(graph, alpha, 'Function', 'alpha', filePath);
|
|
addNode(graph, alphaHelper, 'Function', 'alphaHelper', filePath);
|
|
addNode(graph, alphaLeaf, 'Function', 'alphaLeaf', filePath);
|
|
addNode(graph, fileEntry, 'Function', 'fileEntry', filePath);
|
|
addNode(graph, fileHelper, 'Function', 'fileHelper', filePath);
|
|
addNode(graph, fileLeaf, 'Function', 'fileLeaf', filePath);
|
|
addNode(graph, 'Tool:alpha', 'Tool', 'alpha', filePath);
|
|
addNode(graph, 'Tool:fallback_tool', 'Tool', 'fallback_tool', filePath);
|
|
addCall(graph, alpha, alphaHelper);
|
|
addCall(graph, alphaHelper, alphaLeaf);
|
|
addCall(graph, fileEntry, fileHelper);
|
|
addCall(graph, fileHelper, fileLeaf);
|
|
|
|
await processesPhase.execute(
|
|
makeCtx(graph),
|
|
new Map([
|
|
['structure', phaseResult('structure', { totalFiles: 1 })],
|
|
['communities', phaseResult('communities', { communityResult: { memberships: [] } })],
|
|
['routes', phaseResult('routes', { routeRegistry: new Map() })],
|
|
[
|
|
'tools',
|
|
phaseResult('tools', {
|
|
toolDefs: [
|
|
{ name: 'alpha', filePath, description: '', handlerNodeId: alpha },
|
|
{ name: 'fallback_tool', filePath, description: '' },
|
|
],
|
|
}),
|
|
],
|
|
]),
|
|
);
|
|
|
|
const processEntryById = new Map(
|
|
graph.nodes
|
|
.filter((node) => node.label === 'Process')
|
|
.map((node) => [node.id, node.properties.entryPointId]),
|
|
);
|
|
const linkedEntriesByTool = new Map<string, unknown[]>();
|
|
for (const rel of graph.relationships.filter((edge) => edge.type === 'ENTRY_POINT_OF')) {
|
|
const list = linkedEntriesByTool.get(rel.sourceId) ?? [];
|
|
list.push(processEntryById.get(rel.targetId));
|
|
linkedEntriesByTool.set(rel.sourceId, list);
|
|
}
|
|
|
|
expect(linkedEntriesByTool.get('Tool:alpha')).toEqual([alpha]);
|
|
expect(linkedEntriesByTool.get('Tool:fallback_tool')).toEqual([fileEntry]);
|
|
});
|
|
});
|