@zuvia-software-solutions/code-mapper 1.4.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/README.md +215 -0
- package/dist/cli/ai-context.d.ts +19 -0
- package/dist/cli/ai-context.js +168 -0
- package/dist/cli/analyze.d.ts +7 -0
- package/dist/cli/analyze.js +325 -0
- package/dist/cli/augment.d.ts +7 -0
- package/dist/cli/augment.js +27 -0
- package/dist/cli/clean.d.ts +5 -0
- package/dist/cli/clean.js +56 -0
- package/dist/cli/eval-server.d.ts +25 -0
- package/dist/cli/eval-server.js +365 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +102 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +19 -0
- package/dist/cli/list.d.ts +2 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/refresh.d.ts +12 -0
- package/dist/cli/refresh.js +165 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +8 -0
- package/dist/cli/setup.d.ts +6 -0
- package/dist/cli/setup.js +218 -0
- package/dist/cli/status.d.ts +2 -0
- package/dist/cli/status.js +33 -0
- package/dist/cli/tool.d.ts +28 -0
- package/dist/cli/tool.js +87 -0
- package/dist/config/ignore-service.d.ts +32 -0
- package/dist/config/ignore-service.js +282 -0
- package/dist/config/supported-languages.d.ts +23 -0
- package/dist/config/supported-languages.js +52 -0
- package/dist/core/augmentation/engine.d.ts +22 -0
- package/dist/core/augmentation/engine.js +232 -0
- package/dist/core/embeddings/embedder.d.ts +35 -0
- package/dist/core/embeddings/embedder.js +171 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
- package/dist/core/embeddings/embedding-pipeline.js +402 -0
- package/dist/core/embeddings/index.d.ts +5 -0
- package/dist/core/embeddings/index.js +6 -0
- package/dist/core/embeddings/text-generator.d.ts +20 -0
- package/dist/core/embeddings/text-generator.js +159 -0
- package/dist/core/embeddings/types.d.ts +60 -0
- package/dist/core/embeddings/types.js +23 -0
- package/dist/core/graph/graph.d.ts +4 -0
- package/dist/core/graph/graph.js +65 -0
- package/dist/core/graph/types.d.ts +69 -0
- package/dist/core/graph/types.js +3 -0
- package/dist/core/incremental/child-process.d.ts +8 -0
- package/dist/core/incremental/child-process.js +649 -0
- package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
- package/dist/core/incremental/refresh-coordinator.js +147 -0
- package/dist/core/incremental/types.d.ts +78 -0
- package/dist/core/incremental/types.js +153 -0
- package/dist/core/incremental/watcher.d.ts +63 -0
- package/dist/core/incremental/watcher.js +338 -0
- package/dist/core/ingestion/ast-cache.d.ts +12 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +34 -0
- package/dist/core/ingestion/call-processor.js +937 -0
- package/dist/core/ingestion/call-routing.d.ts +40 -0
- package/dist/core/ingestion/call-routing.js +97 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
- package/dist/core/ingestion/cluster-enricher.js +151 -0
- package/dist/core/ingestion/community-processor.d.ts +26 -0
- package/dist/core/ingestion/community-processor.js +272 -0
- package/dist/core/ingestion/constants.d.ts +5 -0
- package/dist/core/ingestion/constants.js +8 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
- package/dist/core/ingestion/entry-point-scoring.js +317 -0
- package/dist/core/ingestion/export-detection.d.ts +11 -0
- package/dist/core/ingestion/export-detection.js +203 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
- package/dist/core/ingestion/filesystem-walker.js +64 -0
- package/dist/core/ingestion/framework-detection.d.ts +42 -0
- package/dist/core/ingestion/framework-detection.js +405 -0
- package/dist/core/ingestion/heritage-processor.d.ts +15 -0
- package/dist/core/ingestion/heritage-processor.js +237 -0
- package/dist/core/ingestion/import-processor.d.ts +31 -0
- package/dist/core/ingestion/import-processor.js +416 -0
- package/dist/core/ingestion/language-config.d.ts +32 -0
- package/dist/core/ingestion/language-config.js +161 -0
- package/dist/core/ingestion/mro-processor.d.ts +32 -0
- package/dist/core/ingestion/mro-processor.js +343 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
- package/dist/core/ingestion/named-binding-extraction.js +343 -0
- package/dist/core/ingestion/parsing-processor.d.ts +20 -0
- package/dist/core/ingestion/parsing-processor.js +282 -0
- package/dist/core/ingestion/pipeline.d.ts +3 -0
- package/dist/core/ingestion/pipeline.js +416 -0
- package/dist/core/ingestion/process-processor.d.ts +42 -0
- package/dist/core/ingestion/process-processor.js +357 -0
- package/dist/core/ingestion/resolution-context.d.ts +40 -0
- package/dist/core/ingestion/resolution-context.js +171 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
- package/dist/core/ingestion/resolvers/csharp.js +101 -0
- package/dist/core/ingestion/resolvers/go.d.ts +8 -0
- package/dist/core/ingestion/resolvers/go.js +33 -0
- package/dist/core/ingestion/resolvers/index.d.ts +14 -0
- package/dist/core/ingestion/resolvers/index.js +10 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
- package/dist/core/ingestion/resolvers/jvm.js +74 -0
- package/dist/core/ingestion/resolvers/php.d.ts +7 -0
- package/dist/core/ingestion/resolvers/php.js +30 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
- package/dist/core/ingestion/resolvers/ruby.js +13 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
- package/dist/core/ingestion/resolvers/rust.js +62 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
- package/dist/core/ingestion/resolvers/standard.js +144 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
- package/dist/core/ingestion/resolvers/utils.js +113 -0
- package/dist/core/ingestion/structure-processor.d.ts +4 -0
- package/dist/core/ingestion/structure-processor.js +39 -0
- package/dist/core/ingestion/symbol-table.d.ts +34 -0
- package/dist/core/ingestion/symbol-table.js +48 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
- package/dist/core/ingestion/tree-sitter-queries.js +691 -0
- package/dist/core/ingestion/type-env.d.ts +52 -0
- package/dist/core/ingestion/type-env.js +349 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/csharp.js +224 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/go.js +261 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
- package/dist/core/ingestion/type-extractors/index.js +30 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/jvm.js +386 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/php.js +280 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/python.js +175 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
- package/dist/core/ingestion/type-extractors/ruby.js +218 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/rust.js +290 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
- package/dist/core/ingestion/type-extractors/shared.js +322 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/swift.js +140 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
- package/dist/core/ingestion/type-extractors/types.js +4 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/typescript.js +227 -0
- package/dist/core/ingestion/utils.d.ts +73 -0
- package/dist/core/ingestion/utils.js +992 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
- package/dist/core/ingestion/workers/parse-worker.js +1055 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +123 -0
- package/dist/core/lbug/csv-generator.d.ts +28 -0
- package/dist/core/lbug/csv-generator.js +355 -0
- package/dist/core/lbug/lbug-adapter.d.ts +96 -0
- package/dist/core/lbug/lbug-adapter.js +753 -0
- package/dist/core/lbug/schema.d.ts +46 -0
- package/dist/core/lbug/schema.js +402 -0
- package/dist/core/search/bm25-index.d.ts +20 -0
- package/dist/core/search/bm25-index.js +123 -0
- package/dist/core/search/hybrid-search.d.ts +32 -0
- package/dist/core/search/hybrid-search.js +131 -0
- package/dist/core/search/query-cache.d.ts +18 -0
- package/dist/core/search/query-cache.js +47 -0
- package/dist/core/search/query-expansion.d.ts +19 -0
- package/dist/core/search/query-expansion.js +75 -0
- package/dist/core/search/reranker.d.ts +29 -0
- package/dist/core/search/reranker.js +122 -0
- package/dist/core/search/types.d.ts +154 -0
- package/dist/core/search/types.js +51 -0
- package/dist/core/semantic/tsgo-service.d.ts +67 -0
- package/dist/core/semantic/tsgo-service.js +355 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
- package/dist/core/tree-sitter/parser-loader.js +71 -0
- package/dist/lib/memory-guard.d.ts +35 -0
- package/dist/lib/memory-guard.js +70 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +6 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
- package/dist/mcp/compatible-stdio-transport.js +209 -0
- package/dist/mcp/core/embedder.d.ts +24 -0
- package/dist/mcp/core/embedder.js +168 -0
- package/dist/mcp/core/lbug-adapter.d.ts +29 -0
- package/dist/mcp/core/lbug-adapter.js +330 -0
- package/dist/mcp/local/local-backend.d.ts +188 -0
- package/dist/mcp/local/local-backend.js +2759 -0
- package/dist/mcp/resources.d.ts +22 -0
- package/dist/mcp/resources.js +379 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +217 -0
- package/dist/mcp/staleness.d.ts +10 -0
- package/dist/mcp/staleness.js +25 -0
- package/dist/mcp/tools.d.ts +21 -0
- package/dist/mcp/tools.js +202 -0
- package/dist/server/api.d.ts +5 -0
- package/dist/server/api.js +340 -0
- package/dist/server/mcp-http.d.ts +7 -0
- package/dist/server/mcp-http.js +95 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +87 -0
- package/dist/storage/repo-manager.js +249 -0
- package/dist/types/pipeline.d.ts +35 -0
- package/dist/types/pipeline.js +20 -0
- package/hooks/claude/code-mapper-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/models/mlx-embedder.py +185 -0
- package/package.json +100 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/process-processor.ts
|
|
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, isTestFile } from './entry-point-scoring.js';
|
|
4
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
5
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
maxTraceDepth: 20,
|
|
8
|
+
maxProcesses: 120,
|
|
9
|
+
minSteps: 2,
|
|
10
|
+
minEdgeConfidence: 0.5,
|
|
11
|
+
minPathConfidence: 0.1,
|
|
12
|
+
maxBeamSize: 5000,
|
|
13
|
+
};
|
|
14
|
+
/** Detect processes (execution flows) in the knowledge graph, runs after community detection */
|
|
15
|
+
export const processProcesses = async (knowledgeGraph, memberships, onProgress, config = {}) => {
|
|
16
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
17
|
+
onProgress?.('Finding entry points...', 0);
|
|
18
|
+
// Build lookup maps
|
|
19
|
+
const membershipMap = new Map();
|
|
20
|
+
memberships.forEach(m => membershipMap.set(m.nodeId, m.communityId));
|
|
21
|
+
const callsEdges = buildCallsGraph(knowledgeGraph, cfg.minEdgeConfidence);
|
|
22
|
+
const reverseCallsEdges = buildReverseCallsGraph(knowledgeGraph, cfg.minEdgeConfidence);
|
|
23
|
+
const nodeMap = new Map();
|
|
24
|
+
for (const n of knowledgeGraph.iterNodes())
|
|
25
|
+
nodeMap.set(n.id, n);
|
|
26
|
+
// Find entry points (functions that call others but have few callers)
|
|
27
|
+
const entryPoints = findEntryPoints(knowledgeGraph, reverseCallsEdges, callsEdges);
|
|
28
|
+
onProgress?.(`Found ${entryPoints.length} entry points, tracing flows...`, 20);
|
|
29
|
+
// Trace processes from each entry point
|
|
30
|
+
const allTraces = [];
|
|
31
|
+
for (let i = 0; i < entryPoints.length && allTraces.length < cfg.maxProcesses * 2; i++) {
|
|
32
|
+
const entryId = entryPoints[i];
|
|
33
|
+
const traces = traceFromEntryPoint(entryId, callsEdges, cfg);
|
|
34
|
+
// Filter out traces that are too short
|
|
35
|
+
traces.filter(t => t.length >= cfg.minSteps).forEach(t => allTraces.push(t));
|
|
36
|
+
if (i % 10 === 0) {
|
|
37
|
+
onProgress?.(`Tracing entry point ${i + 1}/${entryPoints.length}...`, 20 + (i / entryPoints.length) * 40);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
onProgress?.(`Found ${allTraces.length} traces, deduplicating...`, 60);
|
|
41
|
+
// Deduplicate similar traces (subset removal)
|
|
42
|
+
const uniqueTraces = deduplicateTraces(allTraces);
|
|
43
|
+
// Deduplicate by entry+terminal pair (keep longest path per pair)
|
|
44
|
+
const endpointDeduped = deduplicateByEndpoints(uniqueTraces);
|
|
45
|
+
// D4: Merge traces that share ≥ 80% of their steps (near-duplicate paths)
|
|
46
|
+
// Two flows like [A,B,C,D,E] and [A,B,C,D,F] share 4/5 = 80% — keep the longer one
|
|
47
|
+
const prefixMerged = mergeSimilarTraces(endpointDeduped);
|
|
48
|
+
onProgress?.(`Deduped ${uniqueTraces.length} → ${prefixMerged.length} unique flows`, 70);
|
|
49
|
+
// Limit to max processes (prioritize longer traces)
|
|
50
|
+
const limitedTraces = prefixMerged
|
|
51
|
+
.sort((a, b) => b.length - a.length)
|
|
52
|
+
.slice(0, cfg.maxProcesses);
|
|
53
|
+
onProgress?.(`Creating ${limitedTraces.length} process nodes...`, 80);
|
|
54
|
+
// Create process nodes
|
|
55
|
+
const processes = [];
|
|
56
|
+
const steps = [];
|
|
57
|
+
// Build communityId -> heuristicLabel map from Community nodes in the graph
|
|
58
|
+
const communityLabels = new Map();
|
|
59
|
+
for (const n of knowledgeGraph.iterNodes()) {
|
|
60
|
+
if (n.label === 'Community') {
|
|
61
|
+
communityLabels.set(n.id, n.properties.heuristicLabel || n.properties.name || 'Unknown');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
limitedTraces.forEach((trace, idx) => {
|
|
65
|
+
const entryPointId = trace[0];
|
|
66
|
+
const terminalId = trace[trace.length - 1];
|
|
67
|
+
// Get communities touched
|
|
68
|
+
const communitiesSet = new Set();
|
|
69
|
+
trace.forEach(nodeId => {
|
|
70
|
+
const comm = membershipMap.get(nodeId);
|
|
71
|
+
if (comm)
|
|
72
|
+
communitiesSet.add(comm);
|
|
73
|
+
});
|
|
74
|
+
const communities = Array.from(communitiesSet);
|
|
75
|
+
// Determine process type
|
|
76
|
+
const processType = communities.length > 1 ? 'cross_community' : 'intra_community';
|
|
77
|
+
// Generate label with module context from entry point's community
|
|
78
|
+
const entryNode = nodeMap.get(entryPointId);
|
|
79
|
+
const terminalNode = nodeMap.get(terminalId);
|
|
80
|
+
const entryName = entryNode?.properties.name || 'Unknown';
|
|
81
|
+
const terminalName = terminalNode?.properties.name || 'Unknown';
|
|
82
|
+
// Find the community for the entry point
|
|
83
|
+
const entryMembership = memberships.find(m => m.nodeId === entryPointId);
|
|
84
|
+
const entryModule = entryMembership ? communityLabels.get(entryMembership.communityId) : undefined;
|
|
85
|
+
const modulePrefix = entryModule && entryModule !== 'Unknown' ? `${entryModule}: ` : '';
|
|
86
|
+
const heuristicLabel = `${modulePrefix}${capitalize(entryName)} → ${capitalize(terminalName)}`;
|
|
87
|
+
const processId = `proc_${idx}_${sanitizeId(entryName)}`;
|
|
88
|
+
processes.push({
|
|
89
|
+
id: processId,
|
|
90
|
+
label: heuristicLabel,
|
|
91
|
+
heuristicLabel,
|
|
92
|
+
processType,
|
|
93
|
+
stepCount: trace.length,
|
|
94
|
+
communities,
|
|
95
|
+
entryPointId,
|
|
96
|
+
terminalId,
|
|
97
|
+
trace,
|
|
98
|
+
});
|
|
99
|
+
// Create step relationships
|
|
100
|
+
trace.forEach((nodeId, stepIdx) => {
|
|
101
|
+
steps.push({
|
|
102
|
+
nodeId,
|
|
103
|
+
processId,
|
|
104
|
+
step: stepIdx + 1,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
onProgress?.('Process detection complete!', 100);
|
|
109
|
+
// Calculate stats
|
|
110
|
+
const crossCommunityCount = processes.filter(p => p.processType === 'cross_community').length;
|
|
111
|
+
const avgStepCount = processes.length > 0
|
|
112
|
+
? processes.reduce((sum, p) => sum + p.stepCount, 0) / processes.length
|
|
113
|
+
: 0;
|
|
114
|
+
return {
|
|
115
|
+
processes,
|
|
116
|
+
steps,
|
|
117
|
+
stats: {
|
|
118
|
+
totalProcesses: processes.length,
|
|
119
|
+
crossCommunityCount,
|
|
120
|
+
avgStepCount: Math.round(avgStepCount * 10) / 10,
|
|
121
|
+
entryPointsFound: entryPoints.length,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
const buildCallsGraph = (graph, minEdgeConfidence) => {
|
|
126
|
+
const adj = new Map();
|
|
127
|
+
for (const rel of graph.iterRelationships()) {
|
|
128
|
+
if (rel.type === 'CALLS' && rel.confidence >= minEdgeConfidence) {
|
|
129
|
+
if (!adj.has(rel.sourceId)) {
|
|
130
|
+
adj.set(rel.sourceId, []);
|
|
131
|
+
}
|
|
132
|
+
adj.get(rel.sourceId).push({ targetId: rel.targetId, confidence: rel.confidence });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Sort each caller's callees by confidence descending — beam search explores
|
|
136
|
+
// highest-confidence edges first within each expansion
|
|
137
|
+
for (const [, edges] of adj) {
|
|
138
|
+
edges.sort((a, b) => b.confidence - a.confidence);
|
|
139
|
+
}
|
|
140
|
+
return adj;
|
|
141
|
+
};
|
|
142
|
+
const buildReverseCallsGraph = (graph, minEdgeConfidence) => {
|
|
143
|
+
const adj = new Map();
|
|
144
|
+
for (const rel of graph.iterRelationships()) {
|
|
145
|
+
if (rel.type === 'CALLS' && rel.confidence >= minEdgeConfidence) {
|
|
146
|
+
if (!adj.has(rel.targetId)) {
|
|
147
|
+
adj.set(rel.targetId, []);
|
|
148
|
+
}
|
|
149
|
+
adj.get(rel.targetId).push(rel.sourceId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return adj;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Find functions/methods that are good entry points for tracing
|
|
156
|
+
*
|
|
157
|
+
* Scored by call ratio, export status, and name patterns; test files excluded
|
|
158
|
+
*/
|
|
159
|
+
const findEntryPoints = (graph, reverseCallsEdges, callsEdges) => {
|
|
160
|
+
const symbolTypes = new Set(['Function', 'Method']);
|
|
161
|
+
const entryPointCandidates = [];
|
|
162
|
+
for (const node of graph.iterNodes()) {
|
|
163
|
+
if (!symbolTypes.has(node.label))
|
|
164
|
+
continue;
|
|
165
|
+
const filePath = node.properties.filePath || '';
|
|
166
|
+
// Skip test files entirely
|
|
167
|
+
if (isTestFile(filePath))
|
|
168
|
+
continue;
|
|
169
|
+
const callers = reverseCallsEdges.get(node.id) || [];
|
|
170
|
+
const calleeEdges = callsEdges.get(node.id) || [];
|
|
171
|
+
// Must have at least 1 outgoing call to trace forward
|
|
172
|
+
if (calleeEdges.length === 0)
|
|
173
|
+
continue;
|
|
174
|
+
// Calculate entry point score
|
|
175
|
+
const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language ?? SupportedLanguages.JavaScript, node.properties.isExported ?? false, callers.length, calleeEdges.length, filePath);
|
|
176
|
+
let score = baseScore;
|
|
177
|
+
const astFrameworkMultiplier = node.properties.astFrameworkMultiplier ?? 1.0;
|
|
178
|
+
if (astFrameworkMultiplier > 1.0) {
|
|
179
|
+
score *= astFrameworkMultiplier;
|
|
180
|
+
reasons.push(`framework-ast:${node.properties.astFrameworkReason || 'decorator'}`);
|
|
181
|
+
}
|
|
182
|
+
if (score > 0) {
|
|
183
|
+
entryPointCandidates.push({ id: node.id, score, reasons });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Sort by score descending and return top candidates
|
|
187
|
+
const sorted = entryPointCandidates.sort((a, b) => b.score - a.score);
|
|
188
|
+
if (sorted.length > 0 && isDev) {
|
|
189
|
+
console.log(`[Process] Top 10 entry point candidates:`);
|
|
190
|
+
sorted.slice(0, 10).forEach((c, i) => {
|
|
191
|
+
const node = graph.getNode(c.id);
|
|
192
|
+
const exported = node?.properties.isExported ? '✓' : '✗';
|
|
193
|
+
const shortPath = node?.properties.filePath?.split('/').slice(-2).join('/') || '';
|
|
194
|
+
console.log(` ${i + 1}. ${node?.properties.name} [exported:${exported}] (${shortPath})`);
|
|
195
|
+
console.log(` score: ${c.score.toFixed(2)} = [${c.reasons.join(' × ')}]`);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return sorted.map(c => c.id);
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Trace forward from an entry point using confidence-gated beam search.
|
|
202
|
+
*
|
|
203
|
+
* Instead of a static per-node branching limit, this follows ALL callees whose
|
|
204
|
+
* edge confidence meets the threshold, then prunes paths whose cumulative
|
|
205
|
+
* confidence (product of edge confidences along the path) drops below a floor.
|
|
206
|
+
*
|
|
207
|
+
* A global beam size caps the number of active paths in memory, ensuring bounded
|
|
208
|
+
* resource usage regardless of graph fan-out. Paths are explored in order of
|
|
209
|
+
* cumulative confidence (highest first), so well-resolved execution flows are
|
|
210
|
+
* discovered before speculative ones.
|
|
211
|
+
*/
|
|
212
|
+
const traceFromEntryPoint = (entryId, callsEdges, config) => {
|
|
213
|
+
const traces = [];
|
|
214
|
+
// Priority queue sorted by cumulative confidence descending
|
|
215
|
+
const beam = [{
|
|
216
|
+
cumulativeConf: 1.0,
|
|
217
|
+
currentId: entryId,
|
|
218
|
+
path: [entryId],
|
|
219
|
+
pathSet: new Set([entryId]),
|
|
220
|
+
}];
|
|
221
|
+
while (beam.length > 0) {
|
|
222
|
+
// Pop highest cumulative confidence
|
|
223
|
+
const entry = beam.shift();
|
|
224
|
+
const edges = callsEdges.get(entry.currentId) || [];
|
|
225
|
+
// Filter edges by per-edge confidence gate
|
|
226
|
+
const viable = edges.filter(e => e.confidence >= config.minEdgeConfidence);
|
|
227
|
+
if (viable.length === 0 || entry.path.length >= config.maxTraceDepth) {
|
|
228
|
+
// Terminal — record if long enough
|
|
229
|
+
if (entry.path.length >= config.minSteps) {
|
|
230
|
+
traces.push(entry.path);
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
let addedBranch = false;
|
|
235
|
+
for (const edge of viable) {
|
|
236
|
+
// O(1) cycle detection
|
|
237
|
+
if (entry.pathSet.has(edge.targetId))
|
|
238
|
+
continue;
|
|
239
|
+
const childConf = entry.cumulativeConf * edge.confidence;
|
|
240
|
+
// Prune paths whose cumulative confidence is too low
|
|
241
|
+
if (childConf < config.minPathConfidence)
|
|
242
|
+
continue;
|
|
243
|
+
const newPath = [...entry.path, edge.targetId];
|
|
244
|
+
const newSet = new Set(entry.pathSet);
|
|
245
|
+
newSet.add(edge.targetId);
|
|
246
|
+
const child = {
|
|
247
|
+
cumulativeConf: childConf,
|
|
248
|
+
currentId: edge.targetId,
|
|
249
|
+
path: newPath,
|
|
250
|
+
pathSet: newSet,
|
|
251
|
+
};
|
|
252
|
+
// Binary search for sorted insert (descending by cumulativeConf)
|
|
253
|
+
let lo = 0;
|
|
254
|
+
let hi = beam.length;
|
|
255
|
+
while (lo < hi) {
|
|
256
|
+
const mid = (lo + hi) >>> 1;
|
|
257
|
+
if (beam[mid].cumulativeConf > childConf)
|
|
258
|
+
lo = mid + 1;
|
|
259
|
+
else
|
|
260
|
+
hi = mid;
|
|
261
|
+
}
|
|
262
|
+
beam.splice(lo, 0, child);
|
|
263
|
+
addedBranch = true;
|
|
264
|
+
}
|
|
265
|
+
// Trim beam if it exceeds max size (drop lowest-confidence tails)
|
|
266
|
+
if (beam.length > config.maxBeamSize) {
|
|
267
|
+
beam.length = config.maxBeamSize;
|
|
268
|
+
}
|
|
269
|
+
if (!addedBranch && entry.path.length >= config.minSteps) {
|
|
270
|
+
traces.push(entry.path);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return traces;
|
|
274
|
+
};
|
|
275
|
+
/** Merge traces that are subsets of other traces, keeping longer ones */
|
|
276
|
+
const deduplicateTraces = (traces) => {
|
|
277
|
+
if (traces.length === 0)
|
|
278
|
+
return [];
|
|
279
|
+
const sorted = [...traces].sort((a, b) => b.length - a.length);
|
|
280
|
+
const unique = [];
|
|
281
|
+
for (const trace of sorted) {
|
|
282
|
+
// Check if this trace is an ordered subsequence of any already-added trace.
|
|
283
|
+
// Fixed: previous string .includes() had false positives (e.g. "A->B" inside "A->B->D").
|
|
284
|
+
const isSubset = unique.some(existing => isOrderedSubsequence(trace, existing));
|
|
285
|
+
if (!isSubset) {
|
|
286
|
+
unique.push(trace);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return unique;
|
|
290
|
+
};
|
|
291
|
+
/** Check if every element of shorter appears in longer in the same order */
|
|
292
|
+
function isOrderedSubsequence(shorter, longer) {
|
|
293
|
+
if (shorter.length >= longer.length)
|
|
294
|
+
return false;
|
|
295
|
+
let j = 0;
|
|
296
|
+
for (let i = 0; i < longer.length && j < shorter.length; i++) {
|
|
297
|
+
if (longer[i] === shorter[j])
|
|
298
|
+
j++;
|
|
299
|
+
}
|
|
300
|
+
return j === shorter.length;
|
|
301
|
+
}
|
|
302
|
+
/** Keep only the longest trace per unique entry->terminal pair */
|
|
303
|
+
const deduplicateByEndpoints = (traces) => {
|
|
304
|
+
if (traces.length === 0)
|
|
305
|
+
return [];
|
|
306
|
+
const byEndpoints = new Map();
|
|
307
|
+
// Sort longest first so the first seen per key is the longest
|
|
308
|
+
const sorted = [...traces].sort((a, b) => b.length - a.length);
|
|
309
|
+
for (const trace of sorted) {
|
|
310
|
+
const key = `${trace[0]}::${trace[trace.length - 1]}`;
|
|
311
|
+
if (!byEndpoints.has(key)) {
|
|
312
|
+
byEndpoints.set(key, trace);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return Array.from(byEndpoints.values());
|
|
316
|
+
};
|
|
317
|
+
/** D4: Merge traces that share ≥ 80% of steps — keeps the longer one.
|
|
318
|
+
* Reduces near-duplicate flows like [A,B,C,D,E] and [A,B,C,D,F] to just the longer. */
|
|
319
|
+
const mergeSimilarTraces = (traces) => {
|
|
320
|
+
if (traces.length <= 1)
|
|
321
|
+
return traces;
|
|
322
|
+
const sorted = [...traces].sort((a, b) => b.length - a.length);
|
|
323
|
+
const kept = [];
|
|
324
|
+
const removed = new Set();
|
|
325
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
326
|
+
if (removed.has(i))
|
|
327
|
+
continue;
|
|
328
|
+
kept.push(sorted[i]);
|
|
329
|
+
// Check remaining traces for high overlap with this one
|
|
330
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
331
|
+
if (removed.has(j))
|
|
332
|
+
continue;
|
|
333
|
+
const shorter = sorted[j];
|
|
334
|
+
const longer = sorted[i];
|
|
335
|
+
// Count shared steps
|
|
336
|
+
const longerSet = new Set(longer);
|
|
337
|
+
let shared = 0;
|
|
338
|
+
for (const step of shorter) {
|
|
339
|
+
if (longerSet.has(step))
|
|
340
|
+
shared++;
|
|
341
|
+
}
|
|
342
|
+
// If ≥ 80% of the shorter trace's steps are in the longer trace, merge (remove shorter)
|
|
343
|
+
if (shared >= shorter.length * 0.8) {
|
|
344
|
+
removed.add(j);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return kept;
|
|
349
|
+
};
|
|
350
|
+
const capitalize = (s) => {
|
|
351
|
+
if (!s)
|
|
352
|
+
return s;
|
|
353
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
354
|
+
};
|
|
355
|
+
const sanitizeId = (s) => {
|
|
356
|
+
return s.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 20).toLowerCase();
|
|
357
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file resolution-context.ts
|
|
3
|
+
* @description Single implementation of tiered name resolution
|
|
4
|
+
*
|
|
5
|
+
* Resolution tiers (highest confidence first):
|
|
6
|
+
* 1 Same file (lookupExactFull — authoritative)
|
|
7
|
+
* 2a Named binding chain (walkBindingChain via NamedImportMap)
|
|
8
|
+
* 2a Import-scoped (lookupFuzzy filtered by ImportMap)
|
|
9
|
+
* 2b Package-scoped (lookupFuzzy filtered by PackageMap)
|
|
10
|
+
* 3 Global (all candidates — consumers must check candidate count)
|
|
11
|
+
*/
|
|
12
|
+
import type { SymbolTable, SymbolDefinition } from './symbol-table.js';
|
|
13
|
+
import type { NamedImportBinding } from './import-processor.js';
|
|
14
|
+
export type ResolutionTier = 'same-file' | 'import-scoped' | 'global';
|
|
15
|
+
export interface TieredCandidates {
|
|
16
|
+
readonly candidates: readonly SymbolDefinition[];
|
|
17
|
+
readonly tier: ResolutionTier;
|
|
18
|
+
}
|
|
19
|
+
export declare const TIER_CONFIDENCE: Record<ResolutionTier, number>;
|
|
20
|
+
export type ImportMap = Map<string, Set<string>>;
|
|
21
|
+
export type PackageMap = Map<string, Set<string>>;
|
|
22
|
+
export type NamedImportMap = Map<string, Map<string, NamedImportBinding>>;
|
|
23
|
+
export interface ResolutionContext {
|
|
24
|
+
/** Resolve a name from a file, returning candidates at the winning tier */
|
|
25
|
+
resolve(name: string, fromFile: string): TieredCandidates | null;
|
|
26
|
+
readonly symbols: SymbolTable;
|
|
27
|
+
readonly importMap: ImportMap;
|
|
28
|
+
readonly packageMap: PackageMap;
|
|
29
|
+
readonly namedImportMap: NamedImportMap;
|
|
30
|
+
enableCache(filePath: string): void;
|
|
31
|
+
clearCache(): void;
|
|
32
|
+
getStats(): {
|
|
33
|
+
fileCount: number;
|
|
34
|
+
globalSymbolCount: number;
|
|
35
|
+
cacheHits: number;
|
|
36
|
+
cacheMisses: number;
|
|
37
|
+
};
|
|
38
|
+
clear(): void;
|
|
39
|
+
}
|
|
40
|
+
export declare const createResolutionContext: () => ResolutionContext;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/resolution-context.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file resolution-context.ts
|
|
4
|
+
* @description Single implementation of tiered name resolution
|
|
5
|
+
*
|
|
6
|
+
* Resolution tiers (highest confidence first):
|
|
7
|
+
* 1 Same file (lookupExactFull — authoritative)
|
|
8
|
+
* 2a Named binding chain (walkBindingChain via NamedImportMap)
|
|
9
|
+
* 2a Import-scoped (lookupFuzzy filtered by ImportMap)
|
|
10
|
+
* 2b Package-scoped (lookupFuzzy filtered by PackageMap)
|
|
11
|
+
* 3 Global (all candidates — consumers must check candidate count)
|
|
12
|
+
*/
|
|
13
|
+
import { createSymbolTable } from './symbol-table.js';
|
|
14
|
+
import { isFileInPackageDir } from './import-processor.js';
|
|
15
|
+
import { walkBindingChain } from './named-binding-extraction.js';
|
|
16
|
+
// Confidence scores per resolution tier
|
|
17
|
+
export const TIER_CONFIDENCE = {
|
|
18
|
+
'same-file': 0.95,
|
|
19
|
+
'import-scoped': 0.9,
|
|
20
|
+
'global': 0.5,
|
|
21
|
+
};
|
|
22
|
+
export const createResolutionContext = () => {
|
|
23
|
+
const symbols = createSymbolTable();
|
|
24
|
+
const importMap = new Map();
|
|
25
|
+
const packageMap = new Map();
|
|
26
|
+
const namedImportMap = new Map();
|
|
27
|
+
// Per-file cache state
|
|
28
|
+
let cacheFile = null;
|
|
29
|
+
let cache = null;
|
|
30
|
+
let cacheHits = 0;
|
|
31
|
+
let cacheMisses = 0;
|
|
32
|
+
// Core resolution (single implementation of tier logic)
|
|
33
|
+
const resolveUncached = (name, fromFile) => {
|
|
34
|
+
// Tier 1: Same file — authoritative match
|
|
35
|
+
const localDef = symbols.lookupExactFull(fromFile, name);
|
|
36
|
+
if (localDef) {
|
|
37
|
+
return { candidates: [localDef], tier: 'same-file' };
|
|
38
|
+
}
|
|
39
|
+
// Get all global definitions for subsequent tiers
|
|
40
|
+
const allDefs = symbols.lookupFuzzy(name);
|
|
41
|
+
// Tier 2a-named: Check named bindings BEFORE empty-allDefs early return
|
|
42
|
+
// because aliased imports mean lookupFuzzy('U') returns empty
|
|
43
|
+
const chainResult = walkBindingChain(name, fromFile, symbols, namedImportMap, allDefs);
|
|
44
|
+
if (chainResult && chainResult.length > 0) {
|
|
45
|
+
return { candidates: chainResult, tier: 'import-scoped' };
|
|
46
|
+
}
|
|
47
|
+
if (allDefs.length === 0) {
|
|
48
|
+
// Don't exit early — still check import map. The symbol may be imported from another file.
|
|
49
|
+
// First try lookupExactFull with the exact imported file path
|
|
50
|
+
const importedFiles = importMap.get(fromFile);
|
|
51
|
+
if (importedFiles) {
|
|
52
|
+
for (const importedFile of importedFiles) {
|
|
53
|
+
const imported = symbols.lookupExactFull(importedFile, name);
|
|
54
|
+
if (imported)
|
|
55
|
+
return { candidates: [imported], tier: 'import-scoped' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Fallback: try fuzzy lookup — the symbol may exist globally but path normalization differs
|
|
59
|
+
const fuzzyDefs = symbols.lookupFuzzy(name);
|
|
60
|
+
if (fuzzyDefs.length > 0) {
|
|
61
|
+
if (importedFiles) {
|
|
62
|
+
const stripExt = (p) => p.replace(/\.[^.]+$/, '').replace(/\\/g, '/');
|
|
63
|
+
const importedArr = Array.from(importedFiles).map(stripExt);
|
|
64
|
+
const importedDefs = fuzzyDefs.filter(def => {
|
|
65
|
+
const defStripped = stripExt(def.filePath);
|
|
66
|
+
return importedArr.some(imp => defStripped.endsWith(imp) || imp.endsWith(defStripped)
|
|
67
|
+
|| defStripped.includes(imp) || imp.includes(defStripped));
|
|
68
|
+
});
|
|
69
|
+
if (importedDefs.length > 0) {
|
|
70
|
+
return { candidates: importedDefs, tier: 'import-scoped' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Last resort: if there's exactly one global definition and it's a Function, use it
|
|
74
|
+
// This handles cross-file calls where the import map fails but the symbol is unique
|
|
75
|
+
if (fuzzyDefs.length === 1 && (fuzzyDefs[0].type === 'Function' || fuzzyDefs[0].type === 'Method')) {
|
|
76
|
+
return { candidates: fuzzyDefs, tier: 'global' };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
// Tier 2a: Import-scoped — definition in a file imported by fromFile
|
|
82
|
+
const importedFiles = importMap.get(fromFile);
|
|
83
|
+
if (importedFiles) {
|
|
84
|
+
// First try exact match
|
|
85
|
+
let importedDefs = allDefs.filter(def => importedFiles.has(def.filePath));
|
|
86
|
+
// Fallback: suffix matching for path normalization + extension differences (.ts vs .js)
|
|
87
|
+
if (importedDefs.length === 0) {
|
|
88
|
+
const stripExt = (p) => p.replace(/\.[^.]+$/, '').replace(/\\/g, '/');
|
|
89
|
+
const importedArr = Array.from(importedFiles).map(stripExt);
|
|
90
|
+
importedDefs = allDefs.filter(def => {
|
|
91
|
+
const defStripped = stripExt(def.filePath);
|
|
92
|
+
return importedArr.some(imp => defStripped.endsWith(imp) || imp.endsWith(defStripped)
|
|
93
|
+
|| defStripped.includes(imp) || imp.includes(defStripped));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (importedDefs.length > 0) {
|
|
97
|
+
return { candidates: importedDefs, tier: 'import-scoped' };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Tier 2b: Package-scoped — definition in a package dir imported by fromFile
|
|
101
|
+
const importedPackages = packageMap.get(fromFile);
|
|
102
|
+
if (importedPackages) {
|
|
103
|
+
const packageDefs = allDefs.filter(def => {
|
|
104
|
+
for (const dirSuffix of importedPackages) {
|
|
105
|
+
if (isFileInPackageDir(def.filePath, dirSuffix))
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
if (packageDefs.length > 0) {
|
|
111
|
+
return { candidates: packageDefs, tier: 'import-scoped' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Tier 3: Global — consumers must check candidate count for ambiguity
|
|
115
|
+
return { candidates: allDefs, tier: 'global' };
|
|
116
|
+
};
|
|
117
|
+
const resolve = (name, fromFile) => {
|
|
118
|
+
// Check cache (only when enabled AND fromFile matches cached file)
|
|
119
|
+
if (cache && cacheFile === fromFile) {
|
|
120
|
+
if (cache.has(name)) {
|
|
121
|
+
cacheHits++;
|
|
122
|
+
return cache.get(name);
|
|
123
|
+
}
|
|
124
|
+
cacheMisses++;
|
|
125
|
+
}
|
|
126
|
+
const result = resolveUncached(name, fromFile);
|
|
127
|
+
// Store in cache if active and file matches
|
|
128
|
+
if (cache && cacheFile === fromFile) {
|
|
129
|
+
cache.set(name, result);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
// Cache lifecycle
|
|
134
|
+
const enableCache = (filePath) => {
|
|
135
|
+
cacheFile = filePath;
|
|
136
|
+
if (!cache)
|
|
137
|
+
cache = new Map();
|
|
138
|
+
else
|
|
139
|
+
cache.clear();
|
|
140
|
+
};
|
|
141
|
+
const clearCache = () => {
|
|
142
|
+
cacheFile = null;
|
|
143
|
+
// Reuse the Map instance to reduce GC pressure
|
|
144
|
+
cache?.clear();
|
|
145
|
+
};
|
|
146
|
+
const getStats = () => ({
|
|
147
|
+
...symbols.getStats(),
|
|
148
|
+
cacheHits,
|
|
149
|
+
cacheMisses,
|
|
150
|
+
});
|
|
151
|
+
const clear = () => {
|
|
152
|
+
symbols.clear();
|
|
153
|
+
importMap.clear();
|
|
154
|
+
packageMap.clear();
|
|
155
|
+
namedImportMap.clear();
|
|
156
|
+
clearCache();
|
|
157
|
+
cacheHits = 0;
|
|
158
|
+
cacheMisses = 0;
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
resolve,
|
|
162
|
+
symbols,
|
|
163
|
+
importMap,
|
|
164
|
+
packageMap,
|
|
165
|
+
namedImportMap,
|
|
166
|
+
enableCache,
|
|
167
|
+
clearCache,
|
|
168
|
+
getStats,
|
|
169
|
+
clear,
|
|
170
|
+
};
|
|
171
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @file C# namespace import resolution via .csproj root namespace stripping */
|
|
2
|
+
import type { SuffixIndex } from './utils.js';
|
|
3
|
+
export interface CSharpProjectConfig {
|
|
4
|
+
rootNamespace: string;
|
|
5
|
+
projectDir: string;
|
|
6
|
+
}
|
|
7
|
+
/** Resolve a C# using-directive to matching .cs files (single-file or directory match) */
|
|
8
|
+
export declare function resolveCSharpImport(importPath: string, csharpConfigs: CSharpProjectConfig[], normalizedFileList: string[], allFileList: string[], index?: SuffixIndex): string[];
|
|
9
|
+
/** Compute the directory suffix for a C# namespace import (e.g., "/ProjectDir/Models/") */
|
|
10
|
+
export declare function resolveCSharpNamespaceDir(importPath: string, csharpConfigs: CSharpProjectConfig[]): string | null;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/resolvers/csharp.ts
|
|
2
|
+
/** @file C# namespace import resolution via .csproj root namespace stripping */
|
|
3
|
+
import { suffixResolve } from './utils.js';
|
|
4
|
+
/** Resolve a C# using-directive to matching .cs files (single-file or directory match) */
|
|
5
|
+
export function resolveCSharpImport(importPath, csharpConfigs, normalizedFileList, allFileList, index) {
|
|
6
|
+
const namespacePath = importPath.replace(/\./g, '/');
|
|
7
|
+
const results = [];
|
|
8
|
+
for (const config of csharpConfigs) {
|
|
9
|
+
const nsPath = config.rootNamespace.replace(/\./g, '/');
|
|
10
|
+
let relative;
|
|
11
|
+
if (namespacePath.startsWith(nsPath + '/')) {
|
|
12
|
+
relative = namespacePath.slice(nsPath.length + 1);
|
|
13
|
+
}
|
|
14
|
+
else if (namespacePath === nsPath) {
|
|
15
|
+
// Import IS the root namespace — resolve to all .cs files in project root
|
|
16
|
+
relative = '';
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const dirPrefix = config.projectDir
|
|
22
|
+
? (relative ? config.projectDir + '/' + relative : config.projectDir)
|
|
23
|
+
: relative;
|
|
24
|
+
// 1. Try as single file (e.g., "Models/DlqMessage.cs")
|
|
25
|
+
if (relative) {
|
|
26
|
+
const candidate = dirPrefix + '.cs';
|
|
27
|
+
if (index) {
|
|
28
|
+
const result = index.get(candidate) || index.getInsensitive(candidate);
|
|
29
|
+
if (result)
|
|
30
|
+
return [result];
|
|
31
|
+
}
|
|
32
|
+
// Also try suffix match
|
|
33
|
+
const suffixResult = index?.get(relative + '.cs') || index?.getInsensitive(relative + '.cs');
|
|
34
|
+
if (suffixResult)
|
|
35
|
+
return [suffixResult];
|
|
36
|
+
}
|
|
37
|
+
// 2. Try as directory: all direct .cs children (namespace import)
|
|
38
|
+
if (index) {
|
|
39
|
+
const dirFiles = index.getFilesInDir(dirPrefix, '.cs');
|
|
40
|
+
for (const f of dirFiles) {
|
|
41
|
+
const normalized = f.replace(/\\/g, '/');
|
|
42
|
+
// Ensure file is a direct child (no deeper slashes)
|
|
43
|
+
const prefixIdx = normalized.indexOf(dirPrefix + '/');
|
|
44
|
+
if (prefixIdx < 0)
|
|
45
|
+
continue;
|
|
46
|
+
const afterDir = normalized.substring(prefixIdx + dirPrefix.length + 1);
|
|
47
|
+
if (!afterDir.includes('/')) {
|
|
48
|
+
results.push(f);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (results.length > 0)
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
// 3. Linear scan fallback for directory matching
|
|
55
|
+
if (results.length === 0) {
|
|
56
|
+
const dirTrail = dirPrefix + '/';
|
|
57
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
58
|
+
const normalized = normalizedFileList[i];
|
|
59
|
+
if (!normalized.endsWith('.cs'))
|
|
60
|
+
continue;
|
|
61
|
+
const prefixIdx = normalized.indexOf(dirTrail);
|
|
62
|
+
if (prefixIdx < 0)
|
|
63
|
+
continue;
|
|
64
|
+
const afterDir = normalized.substring(prefixIdx + dirTrail.length);
|
|
65
|
+
if (!afterDir.includes('/')) {
|
|
66
|
+
results.push(allFileList[i]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (results.length > 0)
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Fallback: suffix matching without namespace stripping
|
|
74
|
+
const pathParts = namespacePath.split('/').filter(Boolean);
|
|
75
|
+
const fallback = suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
76
|
+
return fallback ? [fallback] : [];
|
|
77
|
+
}
|
|
78
|
+
/** Compute the directory suffix for a C# namespace import (e.g., "/ProjectDir/Models/") */
|
|
79
|
+
export function resolveCSharpNamespaceDir(importPath, csharpConfigs) {
|
|
80
|
+
const namespacePath = importPath.replace(/\./g, '/');
|
|
81
|
+
for (const config of csharpConfigs) {
|
|
82
|
+
const nsPath = config.rootNamespace.replace(/\./g, '/');
|
|
83
|
+
let relative;
|
|
84
|
+
if (namespacePath.startsWith(nsPath + '/')) {
|
|
85
|
+
relative = namespacePath.slice(nsPath.length + 1);
|
|
86
|
+
}
|
|
87
|
+
else if (namespacePath === nsPath) {
|
|
88
|
+
relative = '';
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const dirPrefix = config.projectDir
|
|
94
|
+
? (relative ? config.projectDir + '/' + relative : config.projectDir)
|
|
95
|
+
: relative;
|
|
96
|
+
if (!dirPrefix)
|
|
97
|
+
continue;
|
|
98
|
+
return '/' + dirPrefix + '/';
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|