@zuvia-software-solutions/code-mapper 1.4.0 → 2.0.0
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.
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +1 -0
- package/dist/cli/analyze.js +73 -82
- package/dist/cli/augment.js +0 -2
- package/dist/cli/eval-server.d.ts +2 -2
- package/dist/cli/eval-server.js +6 -6
- package/dist/cli/index.js +6 -10
- package/dist/cli/mcp.d.ts +1 -3
- package/dist/cli/mcp.js +3 -3
- package/dist/cli/refresh.d.ts +2 -2
- package/dist/cli/refresh.js +24 -29
- package/dist/cli/status.js +4 -13
- package/dist/cli/tool.d.ts +5 -4
- package/dist/cli/tool.js +8 -10
- package/dist/config/ignore-service.js +14 -34
- package/dist/core/augmentation/engine.js +53 -83
- package/dist/core/db/adapter.d.ts +99 -0
- package/dist/core/db/adapter.js +402 -0
- package/dist/core/db/graph-loader.d.ts +27 -0
- package/dist/core/db/graph-loader.js +148 -0
- package/dist/core/db/queries.d.ts +160 -0
- package/dist/core/db/queries.js +441 -0
- package/dist/core/db/schema.d.ts +108 -0
- package/dist/core/db/schema.js +136 -0
- package/dist/core/embeddings/embedder.d.ts +21 -12
- package/dist/core/embeddings/embedder.js +104 -50
- package/dist/core/embeddings/embedding-pipeline.d.ts +48 -22
- package/dist/core/embeddings/embedding-pipeline.js +220 -262
- package/dist/core/embeddings/text-generator.js +4 -19
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/graph.d.ts +1 -1
- package/dist/core/graph/graph.js +1 -0
- package/dist/core/graph/types.d.ts +11 -9
- package/dist/core/graph/types.js +4 -1
- package/dist/core/incremental/refresh.d.ts +46 -0
- package/dist/core/incremental/refresh.js +464 -0
- package/dist/core/incremental/types.d.ts +2 -1
- package/dist/core/incremental/types.js +42 -44
- package/dist/core/ingestion/ast-cache.js +1 -0
- package/dist/core/ingestion/call-processor.d.ts +15 -3
- package/dist/core/ingestion/call-processor.js +448 -60
- package/dist/core/ingestion/cluster-enricher.d.ts +1 -1
- package/dist/core/ingestion/cluster-enricher.js +2 -0
- package/dist/core/ingestion/community-processor.d.ts +1 -1
- package/dist/core/ingestion/community-processor.js +8 -3
- package/dist/core/ingestion/export-detection.d.ts +1 -1
- package/dist/core/ingestion/export-detection.js +1 -1
- package/dist/core/ingestion/filesystem-walker.js +1 -1
- package/dist/core/ingestion/heritage-processor.d.ts +2 -2
- package/dist/core/ingestion/heritage-processor.js +22 -11
- package/dist/core/ingestion/import-processor.d.ts +2 -2
- package/dist/core/ingestion/import-processor.js +24 -9
- package/dist/core/ingestion/language-config.js +7 -4
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +23 -11
- package/dist/core/ingestion/named-binding-extraction.js +5 -5
- package/dist/core/ingestion/parsing-processor.d.ts +4 -4
- package/dist/core/ingestion/parsing-processor.js +26 -18
- package/dist/core/ingestion/pipeline.d.ts +4 -2
- package/dist/core/ingestion/pipeline.js +50 -20
- package/dist/core/ingestion/process-processor.d.ts +2 -2
- package/dist/core/ingestion/process-processor.js +28 -14
- package/dist/core/ingestion/resolution-context.d.ts +1 -1
- package/dist/core/ingestion/resolution-context.js +14 -4
- package/dist/core/ingestion/resolvers/csharp.js +4 -3
- package/dist/core/ingestion/resolvers/go.js +3 -1
- package/dist/core/ingestion/resolvers/jvm.js +13 -4
- package/dist/core/ingestion/resolvers/standard.js +2 -2
- package/dist/core/ingestion/resolvers/utils.js +6 -2
- package/dist/core/ingestion/route-stitcher.d.ts +15 -0
- package/dist/core/ingestion/route-stitcher.js +92 -0
- package/dist/core/ingestion/structure-processor.d.ts +1 -1
- package/dist/core/ingestion/structure-processor.js +3 -2
- package/dist/core/ingestion/symbol-table.d.ts +2 -0
- package/dist/core/ingestion/symbol-table.js +5 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +177 -0
- package/dist/core/ingestion/type-env.js +20 -0
- package/dist/core/ingestion/type-extractors/csharp.js +4 -3
- package/dist/core/ingestion/type-extractors/go.js +23 -12
- package/dist/core/ingestion/type-extractors/php.js +18 -10
- package/dist/core/ingestion/type-extractors/ruby.js +15 -3
- package/dist/core/ingestion/type-extractors/rust.js +3 -2
- package/dist/core/ingestion/type-extractors/shared.js +3 -2
- package/dist/core/ingestion/type-extractors/typescript.js +11 -5
- package/dist/core/ingestion/utils.d.ts +27 -4
- package/dist/core/ingestion/utils.js +145 -100
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +97 -29
- package/dist/core/ingestion/workers/worker-pool.js +3 -0
- package/dist/core/search/bm25-index.d.ts +15 -8
- package/dist/core/search/bm25-index.js +48 -98
- package/dist/core/search/hybrid-search.d.ts +9 -3
- package/dist/core/search/hybrid-search.js +30 -25
- package/dist/core/search/reranker.js +9 -7
- package/dist/core/search/types.d.ts +0 -4
- package/dist/core/semantic/tsgo-service.d.ts +5 -1
- package/dist/core/semantic/tsgo-service.js +161 -66
- package/dist/lib/tsgo-test.d.ts +2 -0
- package/dist/lib/tsgo-test.js +6 -0
- package/dist/lib/type-utils.d.ts +25 -0
- package/dist/lib/type-utils.js +22 -0
- package/dist/lib/utils.d.ts +3 -2
- package/dist/lib/utils.js +3 -2
- package/dist/mcp/compatible-stdio-transport.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +29 -56
- package/dist/mcp/local/local-backend.js +808 -1118
- package/dist/mcp/resources.js +35 -25
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +5 -5
- package/dist/mcp/tools.js +24 -25
- package/dist/storage/repo-manager.d.ts +2 -12
- package/dist/storage/repo-manager.js +1 -47
- package/dist/types/pipeline.d.ts +8 -5
- package/dist/types/pipeline.js +5 -0
- package/package.json +18 -11
- package/dist/cli/serve.d.ts +0 -5
- package/dist/cli/serve.js +0 -8
- package/dist/core/incremental/child-process.d.ts +0 -8
- package/dist/core/incremental/child-process.js +0 -649
- package/dist/core/incremental/refresh-coordinator.d.ts +0 -32
- package/dist/core/incremental/refresh-coordinator.js +0 -147
- package/dist/core/lbug/csv-generator.d.ts +0 -28
- package/dist/core/lbug/csv-generator.js +0 -355
- package/dist/core/lbug/lbug-adapter.d.ts +0 -96
- package/dist/core/lbug/lbug-adapter.js +0 -753
- package/dist/core/lbug/schema.d.ts +0 -46
- package/dist/core/lbug/schema.js +0 -402
- package/dist/mcp/core/embedder.d.ts +0 -24
- package/dist/mcp/core/embedder.js +0 -168
- package/dist/mcp/core/lbug-adapter.d.ts +0 -29
- package/dist/mcp/core/lbug-adapter.js +0 -330
- package/dist/server/api.d.ts +0 -5
- package/dist/server/api.js +0 -340
- package/dist/server/mcp-http.d.ts +0 -7
- package/dist/server/mcp-http.js +0 -95
- package/models/mlx-embedder.py +0 -185
|
@@ -4,7 +4,7 @@ import { createKnowledgeGraph } from '../graph/graph.js';
|
|
|
4
4
|
import { processStructure } from './structure-processor.js';
|
|
5
5
|
import { processParsing } from './parsing-processor.js';
|
|
6
6
|
import { processImports, processImportsFromExtracted, buildImportResolutionContext } from './import-processor.js';
|
|
7
|
-
import { processCalls, processCallsFromExtracted, processRoutesFromExtracted, createDependsOnEdges, createProvidesEdges } from './call-processor.js';
|
|
7
|
+
import { processCalls, processCallsFromExtracted, processRoutesFromExtracted, createDependsOnEdges, createProvidesEdges, resolveInterfaceDispatches } from './call-processor.js';
|
|
8
8
|
import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
|
|
9
9
|
import { computeMRO } from './mro-processor.js';
|
|
10
10
|
import { processCommunities } from './community-processor.js';
|
|
@@ -19,7 +19,9 @@ import fs from 'node:fs';
|
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
21
|
import { memoryGuard } from '../../lib/memory-guard.js';
|
|
22
|
-
|
|
22
|
+
import { toNodeId, toEdgeId } from '../db/schema.js';
|
|
23
|
+
import { getTsgoService } from '../semantic/tsgo-service.js';
|
|
24
|
+
const isDev = process.env['NODE_ENV'] === 'development';
|
|
23
25
|
// Default chunk budget — used when memory is plentiful.
|
|
24
26
|
// Under memory pressure, adaptiveBatchSize() shrinks this automatically.
|
|
25
27
|
const DEFAULT_CHUNK_BYTE_BUDGET = 50 * 1024 * 1024;
|
|
@@ -28,7 +30,7 @@ const DEFAULT_CHUNK_BYTE_BUDGET = 50 * 1024 * 1024;
|
|
|
28
30
|
const WORKING_MEMORY_MULTIPLIER = 20;
|
|
29
31
|
// Max AST trees to keep in LRU cache
|
|
30
32
|
const AST_CACHE_CAP = 50;
|
|
31
|
-
export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
33
|
+
export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
|
|
32
34
|
const graph = createKnowledgeGraph();
|
|
33
35
|
const ctx = createResolutionContext();
|
|
34
36
|
const symbolTable = ctx.symbols;
|
|
@@ -167,6 +169,8 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
167
169
|
try {
|
|
168
170
|
for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
|
|
169
171
|
const chunkPaths = chunks[chunkIdx];
|
|
172
|
+
if (!chunkPaths)
|
|
173
|
+
continue;
|
|
170
174
|
// Read content for this chunk
|
|
171
175
|
const chunkContents = await readFileContents(repoPath, chunkPaths);
|
|
172
176
|
const chunkFiles = chunkPaths
|
|
@@ -256,6 +260,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
256
260
|
}
|
|
257
261
|
astCache.clear();
|
|
258
262
|
}
|
|
263
|
+
let tsgoWasUsed = false;
|
|
259
264
|
// Phase B: Resolve ALL deferred calls now that every symbol is registered
|
|
260
265
|
if (allExtractedCalls.length > 0) {
|
|
261
266
|
onProgress({
|
|
@@ -264,14 +269,34 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
264
269
|
message: `Resolving ${allExtractedCalls.length} calls across all files...`,
|
|
265
270
|
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
|
|
266
271
|
});
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
// Start tsgo for high-confidence TS/JS resolution (optional — graceful fallback)
|
|
273
|
+
let tsgoService = null;
|
|
274
|
+
if (opts?.tsgo !== false) {
|
|
275
|
+
try {
|
|
276
|
+
const service = getTsgoService(repoPath);
|
|
277
|
+
if (await service.start()) {
|
|
278
|
+
tsgoService = service;
|
|
279
|
+
tsgoWasUsed = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// tsgo is optional — if @typescript/native-preview isn't installed, skip silently
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
await processCallsFromExtracted(graph, allExtractedCalls, ctx, (current, total) => {
|
|
288
|
+
onProgress({
|
|
289
|
+
phase: 'parsing',
|
|
290
|
+
percent: 82,
|
|
291
|
+
message: `Resolving calls: ${current}/${total} files...`,
|
|
292
|
+
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
|
|
293
|
+
});
|
|
294
|
+
}, allConstructorBindings.length > 0 ? allConstructorBindings : undefined, tsgoService, repoPath);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
// Stop tsgo after call resolution completes
|
|
298
|
+
tsgoService?.stop();
|
|
299
|
+
}
|
|
275
300
|
}
|
|
276
301
|
// Log resolution cache stats in dev mode
|
|
277
302
|
if (isDev) {
|
|
@@ -293,6 +318,11 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
293
318
|
if (isDev && (diEdgeCount > 0 || providesEdgeCount > 0)) {
|
|
294
319
|
console.log(`💉 DI: ${diEdgeCount} DEPENDS_ON edges, ${providesEdgeCount} PROVIDES edges`);
|
|
295
320
|
}
|
|
321
|
+
// Phase 4.5a2: Interface dispatch — connect callers of interfaces to implementations
|
|
322
|
+
const ifaceEdges = await resolveInterfaceDispatches(graph, ctx);
|
|
323
|
+
if (isDev && ifaceEdges > 0) {
|
|
324
|
+
console.log(`🔌 Interface dispatch: ${ifaceEdges} implementation edges`);
|
|
325
|
+
}
|
|
296
326
|
// Phase 4.5b: Method Resolution Order
|
|
297
327
|
onProgress({
|
|
298
328
|
phase: 'parsing',
|
|
@@ -325,7 +355,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
325
355
|
}
|
|
326
356
|
communityResult.communities.forEach(comm => {
|
|
327
357
|
graph.addNode({
|
|
328
|
-
id: comm.id,
|
|
358
|
+
id: toNodeId(comm.id),
|
|
329
359
|
label: 'Community',
|
|
330
360
|
properties: {
|
|
331
361
|
name: comm.label,
|
|
@@ -338,10 +368,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
338
368
|
});
|
|
339
369
|
communityResult.memberships.forEach(membership => {
|
|
340
370
|
graph.addRelationship({
|
|
341
|
-
id: `${membership.nodeId}_member_of_${membership.communityId}
|
|
371
|
+
id: toEdgeId(`${membership.nodeId}_member_of_${membership.communityId}`),
|
|
342
372
|
type: 'MEMBER_OF',
|
|
343
|
-
sourceId: membership.nodeId,
|
|
344
|
-
targetId: membership.communityId,
|
|
373
|
+
sourceId: toNodeId(membership.nodeId),
|
|
374
|
+
targetId: toNodeId(membership.communityId),
|
|
345
375
|
confidence: 1.0,
|
|
346
376
|
reason: 'leiden-algorithm',
|
|
347
377
|
});
|
|
@@ -371,7 +401,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
371
401
|
}
|
|
372
402
|
processResult.processes.forEach(proc => {
|
|
373
403
|
graph.addNode({
|
|
374
|
-
id: proc.id,
|
|
404
|
+
id: toNodeId(proc.id),
|
|
375
405
|
label: 'Process',
|
|
376
406
|
properties: {
|
|
377
407
|
name: proc.label,
|
|
@@ -387,10 +417,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
387
417
|
});
|
|
388
418
|
processResult.steps.forEach(step => {
|
|
389
419
|
graph.addRelationship({
|
|
390
|
-
id: `${step.nodeId}_step_${step.step}_${step.processId}
|
|
420
|
+
id: toEdgeId(`${step.nodeId}_step_${step.step}_${step.processId}`),
|
|
391
421
|
type: 'STEP_IN_PROCESS',
|
|
392
|
-
sourceId: step.nodeId,
|
|
393
|
-
targetId: step.processId,
|
|
422
|
+
sourceId: toNodeId(step.nodeId),
|
|
423
|
+
targetId: toNodeId(step.processId),
|
|
394
424
|
confidence: 1.0,
|
|
395
425
|
reason: 'trace-detection',
|
|
396
426
|
step: step.step,
|
|
@@ -407,7 +437,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
407
437
|
},
|
|
408
438
|
});
|
|
409
439
|
astCache.clear();
|
|
410
|
-
return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult };
|
|
440
|
+
return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult, tsgoEnabled: tsgoWasUsed };
|
|
411
441
|
}
|
|
412
442
|
catch (error) {
|
|
413
443
|
cleanup();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @file process-processor.ts @description Detects execution flows (Processes) by finding entry points, tracing forward via confidence-gated beam search, deduplicating similar paths, and labeling with heuristic names */
|
|
2
|
-
import { KnowledgeGraph } from '../graph/types.js';
|
|
3
|
-
import { CommunityMembership } from './community-processor.js';
|
|
2
|
+
import type { KnowledgeGraph } from '../graph/types.js';
|
|
3
|
+
import type { CommunityMembership } from './community-processor.js';
|
|
4
4
|
export interface ProcessDetectionConfig {
|
|
5
5
|
maxTraceDepth: number;
|
|
6
6
|
maxProcesses: number;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// code-mapper/src/core/ingestion/process-processor.ts
|
|
2
2
|
/** @file process-processor.ts @description Detects execution flows (Processes) by finding entry points, tracing forward via confidence-gated beam search, deduplicating similar paths, and labeling with heuristic names */
|
|
3
|
-
import { calculateEntryPointScore
|
|
3
|
+
import { calculateEntryPointScore } from './entry-point-scoring.js';
|
|
4
4
|
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
5
|
-
const isDev = process.env
|
|
5
|
+
const isDev = process.env['NODE_ENV'] === 'development';
|
|
6
6
|
const DEFAULT_CONFIG = {
|
|
7
7
|
maxTraceDepth: 20,
|
|
8
8
|
maxProcesses: 120,
|
|
@@ -30,6 +30,8 @@ export const processProcesses = async (knowledgeGraph, memberships, onProgress,
|
|
|
30
30
|
const allTraces = [];
|
|
31
31
|
for (let i = 0; i < entryPoints.length && allTraces.length < cfg.maxProcesses * 2; i++) {
|
|
32
32
|
const entryId = entryPoints[i];
|
|
33
|
+
if (entryId === undefined)
|
|
34
|
+
continue;
|
|
33
35
|
const traces = traceFromEntryPoint(entryId, callsEdges, cfg);
|
|
34
36
|
// Filter out traces that are too short
|
|
35
37
|
traces.filter(t => t.length >= cfg.minSteps).forEach(t => allTraces.push(t));
|
|
@@ -64,6 +66,8 @@ export const processProcesses = async (knowledgeGraph, memberships, onProgress,
|
|
|
64
66
|
limitedTraces.forEach((trace, idx) => {
|
|
65
67
|
const entryPointId = trace[0];
|
|
66
68
|
const terminalId = trace[trace.length - 1];
|
|
69
|
+
if (entryPointId === undefined || terminalId === undefined)
|
|
70
|
+
return;
|
|
67
71
|
// Get communities touched
|
|
68
72
|
const communitiesSet = new Set();
|
|
69
73
|
trace.forEach(nodeId => {
|
|
@@ -126,10 +130,12 @@ const buildCallsGraph = (graph, minEdgeConfidence) => {
|
|
|
126
130
|
const adj = new Map();
|
|
127
131
|
for (const rel of graph.iterRelationships()) {
|
|
128
132
|
if (rel.type === 'CALLS' && rel.confidence >= minEdgeConfidence) {
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
let edges = adj.get(rel.sourceId);
|
|
134
|
+
if (edges === undefined) {
|
|
135
|
+
edges = [];
|
|
136
|
+
adj.set(rel.sourceId, edges);
|
|
131
137
|
}
|
|
132
|
-
|
|
138
|
+
edges.push({ targetId: rel.targetId, confidence: rel.confidence });
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
// Sort each caller's callees by confidence descending — beam search explores
|
|
@@ -143,10 +149,12 @@ const buildReverseCallsGraph = (graph, minEdgeConfidence) => {
|
|
|
143
149
|
const adj = new Map();
|
|
144
150
|
for (const rel of graph.iterRelationships()) {
|
|
145
151
|
if (rel.type === 'CALLS' && rel.confidence >= minEdgeConfidence) {
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
let callers = adj.get(rel.targetId);
|
|
153
|
+
if (callers === undefined) {
|
|
154
|
+
callers = [];
|
|
155
|
+
adj.set(rel.targetId, callers);
|
|
148
156
|
}
|
|
149
|
-
|
|
157
|
+
callers.push(rel.sourceId);
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
return adj;
|
|
@@ -163,9 +171,6 @@ const findEntryPoints = (graph, reverseCallsEdges, callsEdges) => {
|
|
|
163
171
|
if (!symbolTypes.has(node.label))
|
|
164
172
|
continue;
|
|
165
173
|
const filePath = node.properties.filePath || '';
|
|
166
|
-
// Skip test files entirely
|
|
167
|
-
if (isTestFile(filePath))
|
|
168
|
-
continue;
|
|
169
174
|
const callers = reverseCallsEdges.get(node.id) || [];
|
|
170
175
|
const calleeEdges = callsEdges.get(node.id) || [];
|
|
171
176
|
// Must have at least 1 outgoing call to trace forward
|
|
@@ -221,6 +226,8 @@ const traceFromEntryPoint = (entryId, callsEdges, config) => {
|
|
|
221
226
|
while (beam.length > 0) {
|
|
222
227
|
// Pop highest cumulative confidence
|
|
223
228
|
const entry = beam.shift();
|
|
229
|
+
if (entry === undefined)
|
|
230
|
+
break;
|
|
224
231
|
const edges = callsEdges.get(entry.currentId) || [];
|
|
225
232
|
// Filter edges by per-edge confidence gate
|
|
226
233
|
const viable = edges.filter(e => e.confidence >= config.minEdgeConfidence);
|
|
@@ -254,7 +261,10 @@ const traceFromEntryPoint = (entryId, callsEdges, config) => {
|
|
|
254
261
|
let hi = beam.length;
|
|
255
262
|
while (lo < hi) {
|
|
256
263
|
const mid = (lo + hi) >>> 1;
|
|
257
|
-
|
|
264
|
+
const midEntry = beam[mid];
|
|
265
|
+
if (midEntry === undefined)
|
|
266
|
+
break;
|
|
267
|
+
if (midEntry.cumulativeConf > childConf)
|
|
258
268
|
lo = mid + 1;
|
|
259
269
|
else
|
|
260
270
|
hi = mid;
|
|
@@ -325,13 +335,17 @@ const mergeSimilarTraces = (traces) => {
|
|
|
325
335
|
for (let i = 0; i < sorted.length; i++) {
|
|
326
336
|
if (removed.has(i))
|
|
327
337
|
continue;
|
|
328
|
-
|
|
338
|
+
const longer = sorted[i];
|
|
339
|
+
if (longer === undefined)
|
|
340
|
+
continue;
|
|
341
|
+
kept.push(longer);
|
|
329
342
|
// Check remaining traces for high overlap with this one
|
|
330
343
|
for (let j = i + 1; j < sorted.length; j++) {
|
|
331
344
|
if (removed.has(j))
|
|
332
345
|
continue;
|
|
333
346
|
const shorter = sorted[j];
|
|
334
|
-
|
|
347
|
+
if (shorter === undefined)
|
|
348
|
+
continue;
|
|
335
349
|
// Count shared steps
|
|
336
350
|
const longerSet = new Set(longer);
|
|
337
351
|
let shared = 0;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { SymbolTable, SymbolDefinition } from './symbol-table.js';
|
|
13
13
|
import type { NamedImportBinding } from './import-processor.js';
|
|
14
|
-
export type ResolutionTier = 'same-file' | 'import-scoped' | 'global';
|
|
14
|
+
export type ResolutionTier = 'tsgo-resolved' | 'same-file' | 'import-scoped' | 'global';
|
|
15
15
|
export interface TieredCandidates {
|
|
16
16
|
readonly candidates: readonly SymbolDefinition[];
|
|
17
17
|
readonly tier: ResolutionTier;
|
|
@@ -15,6 +15,7 @@ import { isFileInPackageDir } from './import-processor.js';
|
|
|
15
15
|
import { walkBindingChain } from './named-binding-extraction.js';
|
|
16
16
|
// Confidence scores per resolution tier
|
|
17
17
|
export const TIER_CONFIDENCE = {
|
|
18
|
+
'tsgo-resolved': 0.99,
|
|
18
19
|
'same-file': 0.95,
|
|
19
20
|
'import-scoped': 0.9,
|
|
20
21
|
'global': 0.5,
|
|
@@ -70,10 +71,19 @@ export const createResolutionContext = () => {
|
|
|
70
71
|
return { candidates: importedDefs, tier: 'import-scoped' };
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
|
-
// Last resort: if there
|
|
74
|
-
// This handles cross-file calls where the import map fails but the symbol is
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
// Last resort: if there are few global definitions that are callable, use them
|
|
75
|
+
// This handles cross-file calls where the import map fails but the symbol is resolvable
|
|
76
|
+
const callableDefs = fuzzyDefs.filter(d => d.type === 'Function' || d.type === 'Method' || d.type === 'Class' ||
|
|
77
|
+
d.type === 'Constructor' || d.type === 'Interface');
|
|
78
|
+
if (callableDefs.length === 1) {
|
|
79
|
+
return { candidates: callableDefs, tier: 'global' };
|
|
80
|
+
}
|
|
81
|
+
// If all callable defs point to the same nodeId (duplicates from re-exports), deduplicate
|
|
82
|
+
if (callableDefs.length > 1) {
|
|
83
|
+
const uniqueIds = new Set(callableDefs.map(d => d.nodeId));
|
|
84
|
+
if (uniqueIds.size === 1) {
|
|
85
|
+
return { candidates: [callableDefs[0]], tier: 'global' };
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
return null;
|
|
@@ -56,14 +56,15 @@ export function resolveCSharpImport(importPath, csharpConfigs, normalizedFileLis
|
|
|
56
56
|
const dirTrail = dirPrefix + '/';
|
|
57
57
|
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
58
58
|
const normalized = normalizedFileList[i];
|
|
59
|
-
if (!normalized.endsWith('.cs'))
|
|
59
|
+
if (!normalized || !normalized.endsWith('.cs'))
|
|
60
60
|
continue;
|
|
61
61
|
const prefixIdx = normalized.indexOf(dirTrail);
|
|
62
62
|
if (prefixIdx < 0)
|
|
63
63
|
continue;
|
|
64
64
|
const afterDir = normalized.substring(prefixIdx + dirTrail.length);
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const original = allFileList[i];
|
|
66
|
+
if (!afterDir.includes('/') && original) {
|
|
67
|
+
results.push(original);
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
if (results.length > 0)
|
|
@@ -25,7 +25,9 @@ export function resolveGoPackage(importPath, goModule, normalizedFileList, allFi
|
|
|
25
25
|
if (normalized.includes(pkgSuffix) && normalized.endsWith('.go') && !normalized.endsWith('_test.go')) {
|
|
26
26
|
const afterPkg = normalized.substring(normalized.indexOf(pkgSuffix) + pkgSuffix.length);
|
|
27
27
|
if (!afterPkg.includes('/')) {
|
|
28
|
-
|
|
28
|
+
const original = allFileList[i];
|
|
29
|
+
if (original !== undefined)
|
|
30
|
+
matches.push(original);
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -33,11 +33,15 @@ export function resolveJvmWildcard(importPath, normalizedFileList, allFileList,
|
|
|
33
33
|
const matches = [];
|
|
34
34
|
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
35
35
|
const normalized = normalizedFileList[i];
|
|
36
|
+
if (normalized === undefined)
|
|
37
|
+
continue;
|
|
36
38
|
if (normalized.includes(packageSuffix) &&
|
|
37
39
|
extensions.some(ext => normalized.endsWith(ext))) {
|
|
38
40
|
const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
|
|
39
41
|
if (!afterPackage.includes('/')) {
|
|
40
|
-
|
|
42
|
+
const original = allFileList[i];
|
|
43
|
+
if (original !== undefined)
|
|
44
|
+
matches.push(original);
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -50,6 +54,8 @@ export function resolveJvmMemberImport(importPath, normalizedFileList, allFileLi
|
|
|
50
54
|
if (segments.length < 3)
|
|
51
55
|
return null;
|
|
52
56
|
const lastSeg = segments[segments.length - 1];
|
|
57
|
+
if (lastSeg === undefined)
|
|
58
|
+
return null;
|
|
53
59
|
if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
|
|
54
60
|
const classPath = segments.slice(0, -1).join('/');
|
|
55
61
|
for (const ext of extensions) {
|
|
@@ -62,9 +68,12 @@ export function resolveJvmMemberImport(importPath, normalizedFileList, allFileLi
|
|
|
62
68
|
else {
|
|
63
69
|
const fullSuffix = '/' + classSuffix;
|
|
64
70
|
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
const normalized = normalizedFileList[i];
|
|
72
|
+
if (normalized === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
if (normalized.endsWith(fullSuffix) ||
|
|
75
|
+
normalized.toLowerCase().endsWith(fullSuffix.toLowerCase())) {
|
|
76
|
+
return allFileList[i] ?? null;
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
}
|
|
@@ -82,8 +82,8 @@ export const resolveImportPath = (currentFile, importPath, allFiles, allFileList
|
|
|
82
82
|
if (language === SupportedLanguages.Python && importPath.startsWith('.')) {
|
|
83
83
|
const dotMatch = importPath.match(/^(\.+)(.*)/);
|
|
84
84
|
if (dotMatch) {
|
|
85
|
-
const dotCount = dotMatch[1]
|
|
86
|
-
const modulePart = dotMatch[2]; // e.g., "models" from ".models"
|
|
85
|
+
const dotCount = dotMatch[1]?.length ?? 0;
|
|
86
|
+
const modulePart = dotMatch[2] ?? ''; // e.g., "models" from ".models"
|
|
87
87
|
const dirParts = currentFile.split('/').slice(0, -1);
|
|
88
88
|
// 1 dot = same package, each additional dot goes up one level
|
|
89
89
|
for (let i = 1; i < dotCount; i++) {
|
|
@@ -43,6 +43,8 @@ export function buildSuffixIndex(normalizedFileList, allFileList) {
|
|
|
43
43
|
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
44
44
|
const normalized = normalizedFileList[i];
|
|
45
45
|
const original = allFileList[i];
|
|
46
|
+
if (normalized === undefined || original === undefined)
|
|
47
|
+
continue;
|
|
46
48
|
const parts = normalized.split('/');
|
|
47
49
|
// Index all suffixes: "a/b/c.java" -> "c.java", "b/c.java", "a/b/c.java"
|
|
48
50
|
for (let j = parts.length - 1; j >= 0; j--) {
|
|
@@ -61,7 +63,9 @@ export function buildSuffixIndex(normalizedFileList, allFileList) {
|
|
|
61
63
|
if (lastSlash >= 0) {
|
|
62
64
|
// Build all directory suffix variants
|
|
63
65
|
const dirParts = parts.slice(0, -1);
|
|
64
|
-
const fileName = parts
|
|
66
|
+
const fileName = parts.at(-1);
|
|
67
|
+
if (fileName === undefined)
|
|
68
|
+
continue;
|
|
65
69
|
const ext = fileName.substring(fileName.lastIndexOf('.'));
|
|
66
70
|
for (let j = dirParts.length - 1; j >= 0; j--) {
|
|
67
71
|
const dirSuffix = dirParts.slice(j).join('/');
|
|
@@ -105,7 +109,7 @@ export function suffixResolve(pathParts, normalizedFileList, allFileList, index)
|
|
|
105
109
|
const suffixPattern = '/' + suffixWithExt;
|
|
106
110
|
const matchIdx = normalizedFileList.findIndex(filePath => filePath.endsWith(suffixPattern) || filePath.toLowerCase().endsWith(suffixPattern.toLowerCase()));
|
|
107
111
|
if (matchIdx !== -1) {
|
|
108
|
-
return allFileList[matchIdx];
|
|
112
|
+
return allFileList[matchIdx] ?? null;
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file HTTP route stitching — connects client-side URL builders to server-side route handlers
|
|
3
|
+
*
|
|
4
|
+
* Runs AFTER DB loading (needs content field populated by graph-loader).
|
|
5
|
+
* Queries the DB directly for node content with route patterns.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: "meet in the middle" via canonical URL patterns.
|
|
8
|
+
* Works for Express, Fastify, Koa, Hono, or any framework using router.method(path, handler)
|
|
9
|
+
*/
|
|
10
|
+
import type Database from 'better-sqlite3';
|
|
11
|
+
/**
|
|
12
|
+
* Stitch client-side API path functions to server-side route handlers.
|
|
13
|
+
* Runs after loadGraphToDb — queries SQLite content field directly.
|
|
14
|
+
*/
|
|
15
|
+
export declare function stitchRoutes(db: Database.Database): number;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/route-stitcher.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file HTTP route stitching — connects client-side URL builders to server-side route handlers
|
|
4
|
+
*
|
|
5
|
+
* Runs AFTER DB loading (needs content field populated by graph-loader).
|
|
6
|
+
* Queries the DB directly for node content with route patterns.
|
|
7
|
+
*
|
|
8
|
+
* Strategy: "meet in the middle" via canonical URL patterns.
|
|
9
|
+
* Works for Express, Fastify, Koa, Hono, or any framework using router.method(path, handler)
|
|
10
|
+
*/
|
|
11
|
+
import { toEdgeId } from '../db/schema.js';
|
|
12
|
+
import { insertEdgesBatch } from '../db/adapter.js';
|
|
13
|
+
/** Normalize a URL pattern to canonical form for matching */
|
|
14
|
+
function canonicalizeRoute(pattern) {
|
|
15
|
+
return pattern
|
|
16
|
+
.replace(/['"``]/g, '')
|
|
17
|
+
.replace(/:[a-zA-Z_]\w*/g, ':p') // Express params :paramName → :p
|
|
18
|
+
.replace(/\$\{[^}]+\}/g, ':p') // Template literals ${var} → :p
|
|
19
|
+
.replace(/\/+$/, '') // Strip trailing slashes
|
|
20
|
+
.replace(/\/+/g, '/') // Collapse double slashes
|
|
21
|
+
.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Stitch client-side API path functions to server-side route handlers.
|
|
25
|
+
* Runs after loadGraphToDb — queries SQLite content field directly.
|
|
26
|
+
*/
|
|
27
|
+
export function stitchRoutes(db) {
|
|
28
|
+
const serverRoutes = [];
|
|
29
|
+
const clientRoutes = [];
|
|
30
|
+
// Regex for route registrations in server File nodes
|
|
31
|
+
const ROUTE_REG = /\.(get|post|put|patch|delete|options)\s*\(\s*['"`](\/[^'"`\n]+)['"`]/gi;
|
|
32
|
+
// Regex for template literal URLs in client Function nodes
|
|
33
|
+
const TEMPLATE_URL = /`(\/(?:api|v\d+)\/[^`\n]+)`/g;
|
|
34
|
+
// String literal URLs (fetch('/api/...'))
|
|
35
|
+
const STRING_URL = /['"](\/(api|v\d+)\/[^'"]+)['"]/g;
|
|
36
|
+
// Server: scan File nodes for route registrations
|
|
37
|
+
const serverNodes = db.prepare(`SELECT id, name, filePath, content FROM nodes WHERE label = 'File' AND content LIKE '%/api/%'`).all();
|
|
38
|
+
for (const node of serverNodes) {
|
|
39
|
+
let m;
|
|
40
|
+
ROUTE_REG.lastIndex = 0;
|
|
41
|
+
while ((m = ROUTE_REG.exec(node.content)) !== null) {
|
|
42
|
+
const routePattern = canonicalizeRoute(m[2]);
|
|
43
|
+
if (routePattern.includes('/api/') || routePattern.includes('/v1/') || routePattern.includes('/v2/')) {
|
|
44
|
+
serverRoutes.push({ pattern: routePattern, nodeId: node.id, filePath: node.filePath, name: node.name, side: 'server' });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Client: scan Function nodes for template URLs
|
|
49
|
+
const clientNodes = db.prepare(`SELECT id, name, filePath, content FROM nodes WHERE label = 'Function' AND content LIKE '%/api/%'`).all();
|
|
50
|
+
for (const node of clientNodes) {
|
|
51
|
+
let m;
|
|
52
|
+
TEMPLATE_URL.lastIndex = 0;
|
|
53
|
+
while ((m = TEMPLATE_URL.exec(node.content)) !== null) {
|
|
54
|
+
const routePattern = canonicalizeRoute(m[1]);
|
|
55
|
+
if (routePattern.length > 3) {
|
|
56
|
+
clientRoutes.push({ pattern: routePattern, nodeId: node.id, filePath: node.filePath, name: node.name, side: 'client' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
STRING_URL.lastIndex = 0;
|
|
60
|
+
while ((m = STRING_URL.exec(node.content)) !== null) {
|
|
61
|
+
const routePattern = canonicalizeRoute(m[1]);
|
|
62
|
+
if (routePattern.length > 3) {
|
|
63
|
+
clientRoutes.push({ pattern: routePattern, nodeId: node.id, filePath: node.filePath, name: node.name, side: 'client' });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Match client patterns to server patterns
|
|
68
|
+
const edges = [];
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
for (const client of clientRoutes) {
|
|
71
|
+
for (const server of serverRoutes) {
|
|
72
|
+
if (client.pattern === server.pattern && client.filePath !== server.filePath) {
|
|
73
|
+
const edgeKey = `${client.nodeId}->${server.nodeId}`;
|
|
74
|
+
if (seen.has(edgeKey))
|
|
75
|
+
continue;
|
|
76
|
+
seen.add(edgeKey);
|
|
77
|
+
edges.push({
|
|
78
|
+
id: toEdgeId(`CALLS:route-stitch:${edgeKey}`),
|
|
79
|
+
sourceId: client.nodeId,
|
|
80
|
+
targetId: server.nodeId,
|
|
81
|
+
type: 'CALLS',
|
|
82
|
+
confidence: 0.85,
|
|
83
|
+
reason: 'http-route-match',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (edges.length > 0) {
|
|
89
|
+
insertEdgesBatch(db, edges);
|
|
90
|
+
}
|
|
91
|
+
return edges.length;
|
|
92
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** @file structure-processor.ts @description Builds the file/folder tree structure in the knowledge graph by creating File and Folder nodes with CONTAINS relationships */
|
|
2
|
-
import { KnowledgeGraph } from "../graph/types.js";
|
|
2
|
+
import type { KnowledgeGraph } from "../graph/types.js";
|
|
3
3
|
/** Build File/Folder nodes and CONTAINS edges from a list of file paths */
|
|
4
4
|
export declare const processStructure: (graph: KnowledgeGraph, paths: string[]) => void;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// code-mapper/src/core/ingestion/structure-processor.ts
|
|
2
2
|
/** @file structure-processor.ts @description Builds the file/folder tree structure in the knowledge graph by creating File and Folder nodes with CONTAINS relationships */
|
|
3
3
|
import { generateId } from "../../lib/utils.js";
|
|
4
|
+
import { toEdgeId } from "../db/schema.js";
|
|
4
5
|
/** Build File/Folder nodes and CONTAINS edges from a list of file paths */
|
|
5
6
|
export const processStructure = (graph, paths) => {
|
|
6
7
|
paths.forEach(path => {
|
|
7
8
|
const parts = path.split('/');
|
|
8
9
|
let currentPath = '';
|
|
9
|
-
let parentId =
|
|
10
|
+
let parentId = null;
|
|
10
11
|
parts.forEach((part, index) => {
|
|
11
12
|
const isFile = index === parts.length - 1;
|
|
12
13
|
const label = isFile ? 'File' : 'Folder';
|
|
@@ -22,7 +23,7 @@ export const processStructure = (graph, paths) => {
|
|
|
22
23
|
};
|
|
23
24
|
graph.addNode(node);
|
|
24
25
|
if (parentId) {
|
|
25
|
-
const relId =
|
|
26
|
+
const relId = toEdgeId(`CONTAINS:${parentId}->${nodeId}`);
|
|
26
27
|
const relationship = {
|
|
27
28
|
id: relId,
|
|
28
29
|
type: 'CONTAINS',
|
|
@@ -22,6 +22,8 @@ export interface SymbolTable {
|
|
|
22
22
|
lookupExactFull: (filePath: string, name: string) => SymbolDefinition | undefined;
|
|
23
23
|
/** Look up a symbol anywhere in the project (low confidence, used for missing imports) */
|
|
24
24
|
lookupFuzzy: (name: string) => SymbolDefinition[];
|
|
25
|
+
/** Return all symbol definitions registered for a given file */
|
|
26
|
+
lookupAllInFile: (filePath: string) => SymbolDefinition[];
|
|
25
27
|
/** Return file count and global symbol count for debugging */
|
|
26
28
|
getStats: () => {
|
|
27
29
|
fileCount: number;
|
|
@@ -36,6 +36,10 @@ export const createSymbolTable = () => {
|
|
|
36
36
|
const lookupFuzzy = (name) => {
|
|
37
37
|
return globalIndex.get(name) || [];
|
|
38
38
|
};
|
|
39
|
+
const lookupAllInFile = (filePath) => {
|
|
40
|
+
const fileMap = fileIndex.get(filePath);
|
|
41
|
+
return fileMap ? Array.from(fileMap.values()) : [];
|
|
42
|
+
};
|
|
39
43
|
const getStats = () => ({
|
|
40
44
|
fileCount: fileIndex.size,
|
|
41
45
|
globalSymbolCount: globalIndex.size
|
|
@@ -44,5 +48,5 @@ export const createSymbolTable = () => {
|
|
|
44
48
|
fileIndex.clear();
|
|
45
49
|
globalIndex.clear();
|
|
46
50
|
};
|
|
47
|
-
return { add, lookupExact, lookupExactFull, lookupFuzzy, getStats, clear };
|
|
51
|
+
return { add, lookupExact, lookupExactFull, lookupFuzzy, lookupAllInFile, getStats, clear };
|
|
48
52
|
};
|