@duytransipher/gitnexus 1.4.6-sipher.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.
Files changed (224) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +261 -0
  3. package/dist/cli/ai-context.d.ts +23 -0
  4. package/dist/cli/ai-context.js +265 -0
  5. package/dist/cli/analyze.d.ts +12 -0
  6. package/dist/cli/analyze.js +345 -0
  7. package/dist/cli/augment.d.ts +13 -0
  8. package/dist/cli/augment.js +33 -0
  9. package/dist/cli/clean.d.ts +10 -0
  10. package/dist/cli/clean.js +60 -0
  11. package/dist/cli/eval-server.d.ts +37 -0
  12. package/dist/cli/eval-server.js +389 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +137 -0
  15. package/dist/cli/lazy-action.d.ts +6 -0
  16. package/dist/cli/lazy-action.js +18 -0
  17. package/dist/cli/list.d.ts +6 -0
  18. package/dist/cli/list.js +30 -0
  19. package/dist/cli/mcp.d.ts +8 -0
  20. package/dist/cli/mcp.js +36 -0
  21. package/dist/cli/serve.d.ts +4 -0
  22. package/dist/cli/serve.js +6 -0
  23. package/dist/cli/setup.d.ts +8 -0
  24. package/dist/cli/setup.js +367 -0
  25. package/dist/cli/sipher-patched.d.ts +2 -0
  26. package/dist/cli/sipher-patched.js +77 -0
  27. package/dist/cli/skill-gen.d.ts +26 -0
  28. package/dist/cli/skill-gen.js +549 -0
  29. package/dist/cli/status.d.ts +6 -0
  30. package/dist/cli/status.js +36 -0
  31. package/dist/cli/tool.d.ts +60 -0
  32. package/dist/cli/tool.js +180 -0
  33. package/dist/cli/wiki.d.ts +15 -0
  34. package/dist/cli/wiki.js +365 -0
  35. package/dist/config/ignore-service.d.ts +26 -0
  36. package/dist/config/ignore-service.js +284 -0
  37. package/dist/config/supported-languages.d.ts +15 -0
  38. package/dist/config/supported-languages.js +16 -0
  39. package/dist/core/augmentation/engine.d.ts +26 -0
  40. package/dist/core/augmentation/engine.js +240 -0
  41. package/dist/core/embeddings/embedder.d.ts +60 -0
  42. package/dist/core/embeddings/embedder.js +251 -0
  43. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  44. package/dist/core/embeddings/embedding-pipeline.js +356 -0
  45. package/dist/core/embeddings/index.d.ts +9 -0
  46. package/dist/core/embeddings/index.js +9 -0
  47. package/dist/core/embeddings/text-generator.d.ts +24 -0
  48. package/dist/core/embeddings/text-generator.js +182 -0
  49. package/dist/core/embeddings/types.d.ts +87 -0
  50. package/dist/core/embeddings/types.js +32 -0
  51. package/dist/core/graph/graph.d.ts +2 -0
  52. package/dist/core/graph/graph.js +66 -0
  53. package/dist/core/graph/types.d.ts +66 -0
  54. package/dist/core/graph/types.js +1 -0
  55. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  56. package/dist/core/ingestion/ast-cache.js +35 -0
  57. package/dist/core/ingestion/call-processor.d.ts +23 -0
  58. package/dist/core/ingestion/call-processor.js +793 -0
  59. package/dist/core/ingestion/call-routing.d.ts +68 -0
  60. package/dist/core/ingestion/call-routing.js +129 -0
  61. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  62. package/dist/core/ingestion/cluster-enricher.js +170 -0
  63. package/dist/core/ingestion/community-processor.d.ts +39 -0
  64. package/dist/core/ingestion/community-processor.js +312 -0
  65. package/dist/core/ingestion/constants.d.ts +16 -0
  66. package/dist/core/ingestion/constants.js +16 -0
  67. package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
  68. package/dist/core/ingestion/entry-point-scoring.js +353 -0
  69. package/dist/core/ingestion/export-detection.d.ts +18 -0
  70. package/dist/core/ingestion/export-detection.js +231 -0
  71. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  72. package/dist/core/ingestion/filesystem-walker.js +81 -0
  73. package/dist/core/ingestion/framework-detection.d.ts +54 -0
  74. package/dist/core/ingestion/framework-detection.js +411 -0
  75. package/dist/core/ingestion/heritage-processor.d.ts +28 -0
  76. package/dist/core/ingestion/heritage-processor.js +251 -0
  77. package/dist/core/ingestion/import-processor.d.ts +34 -0
  78. package/dist/core/ingestion/import-processor.js +398 -0
  79. package/dist/core/ingestion/language-config.d.ts +46 -0
  80. package/dist/core/ingestion/language-config.js +167 -0
  81. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  82. package/dist/core/ingestion/mro-processor.js +369 -0
  83. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  84. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  85. package/dist/core/ingestion/parsing-processor.d.ts +19 -0
  86. package/dist/core/ingestion/parsing-processor.js +315 -0
  87. package/dist/core/ingestion/pipeline.d.ts +6 -0
  88. package/dist/core/ingestion/pipeline.js +401 -0
  89. package/dist/core/ingestion/process-processor.d.ts +51 -0
  90. package/dist/core/ingestion/process-processor.js +315 -0
  91. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  92. package/dist/core/ingestion/resolution-context.js +132 -0
  93. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  94. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  95. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  96. package/dist/core/ingestion/resolvers/go.js +42 -0
  97. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  98. package/dist/core/ingestion/resolvers/index.js +13 -0
  99. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  100. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  101. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  102. package/dist/core/ingestion/resolvers/php.js +35 -0
  103. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  104. package/dist/core/ingestion/resolvers/python.js +52 -0
  105. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  106. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  107. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  108. package/dist/core/ingestion/resolvers/rust.js +73 -0
  109. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  110. package/dist/core/ingestion/resolvers/standard.js +123 -0
  111. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  112. package/dist/core/ingestion/resolvers/utils.js +122 -0
  113. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  114. package/dist/core/ingestion/structure-processor.js +36 -0
  115. package/dist/core/ingestion/symbol-table.d.ts +63 -0
  116. package/dist/core/ingestion/symbol-table.js +85 -0
  117. package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
  118. package/dist/core/ingestion/tree-sitter-queries.js +888 -0
  119. package/dist/core/ingestion/type-env.d.ts +49 -0
  120. package/dist/core/ingestion/type-env.js +613 -0
  121. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  123. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  124. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  125. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  126. package/dist/core/ingestion/type-extractors/go.js +467 -0
  127. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  128. package/dist/core/ingestion/type-extractors/index.js +31 -0
  129. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  130. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  131. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  132. package/dist/core/ingestion/type-extractors/php.js +549 -0
  133. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  134. package/dist/core/ingestion/type-extractors/python.js +455 -0
  135. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  136. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  137. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  138. package/dist/core/ingestion/type-extractors/rust.js +456 -0
  139. package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
  140. package/dist/core/ingestion/type-extractors/shared.js +810 -0
  141. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  142. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  143. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  144. package/dist/core/ingestion/type-extractors/types.js +1 -0
  145. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  146. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  147. package/dist/core/ingestion/utils.d.ts +138 -0
  148. package/dist/core/ingestion/utils.js +1290 -0
  149. package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
  150. package/dist/core/ingestion/workers/parse-worker.js +1126 -0
  151. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  152. package/dist/core/ingestion/workers/worker-pool.js +128 -0
  153. package/dist/core/lbug/csv-generator.d.ts +33 -0
  154. package/dist/core/lbug/csv-generator.js +366 -0
  155. package/dist/core/lbug/lbug-adapter.d.ts +103 -0
  156. package/dist/core/lbug/lbug-adapter.js +769 -0
  157. package/dist/core/lbug/schema.d.ts +53 -0
  158. package/dist/core/lbug/schema.js +430 -0
  159. package/dist/core/search/bm25-index.d.ts +23 -0
  160. package/dist/core/search/bm25-index.js +96 -0
  161. package/dist/core/search/hybrid-search.d.ts +49 -0
  162. package/dist/core/search/hybrid-search.js +118 -0
  163. package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
  164. package/dist/core/tree-sitter/parser-loader.js +63 -0
  165. package/dist/core/wiki/generator.d.ts +120 -0
  166. package/dist/core/wiki/generator.js +939 -0
  167. package/dist/core/wiki/graph-queries.d.ts +80 -0
  168. package/dist/core/wiki/graph-queries.js +238 -0
  169. package/dist/core/wiki/html-viewer.d.ts +10 -0
  170. package/dist/core/wiki/html-viewer.js +297 -0
  171. package/dist/core/wiki/llm-client.d.ts +43 -0
  172. package/dist/core/wiki/llm-client.js +186 -0
  173. package/dist/core/wiki/prompts.d.ts +53 -0
  174. package/dist/core/wiki/prompts.js +174 -0
  175. package/dist/lib/utils.d.ts +1 -0
  176. package/dist/lib/utils.js +3 -0
  177. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  178. package/dist/mcp/compatible-stdio-transport.js +200 -0
  179. package/dist/mcp/core/embedder.d.ts +27 -0
  180. package/dist/mcp/core/embedder.js +108 -0
  181. package/dist/mcp/core/lbug-adapter.d.ts +57 -0
  182. package/dist/mcp/core/lbug-adapter.js +455 -0
  183. package/dist/mcp/local/local-backend.d.ts +181 -0
  184. package/dist/mcp/local/local-backend.js +1722 -0
  185. package/dist/mcp/resources.d.ts +31 -0
  186. package/dist/mcp/resources.js +411 -0
  187. package/dist/mcp/server.d.ts +23 -0
  188. package/dist/mcp/server.js +296 -0
  189. package/dist/mcp/staleness.d.ts +15 -0
  190. package/dist/mcp/staleness.js +29 -0
  191. package/dist/mcp/tools.d.ts +24 -0
  192. package/dist/mcp/tools.js +292 -0
  193. package/dist/server/api.d.ts +10 -0
  194. package/dist/server/api.js +344 -0
  195. package/dist/server/mcp-http.d.ts +13 -0
  196. package/dist/server/mcp-http.js +100 -0
  197. package/dist/storage/git.d.ts +6 -0
  198. package/dist/storage/git.js +35 -0
  199. package/dist/storage/repo-manager.d.ts +138 -0
  200. package/dist/storage/repo-manager.js +299 -0
  201. package/dist/types/pipeline.d.ts +32 -0
  202. package/dist/types/pipeline.js +18 -0
  203. package/dist/unreal/bridge.d.ts +4 -0
  204. package/dist/unreal/bridge.js +113 -0
  205. package/dist/unreal/config.d.ts +6 -0
  206. package/dist/unreal/config.js +55 -0
  207. package/dist/unreal/types.d.ts +105 -0
  208. package/dist/unreal/types.js +1 -0
  209. package/hooks/claude/gitnexus-hook.cjs +238 -0
  210. package/hooks/claude/pre-tool-use.sh +79 -0
  211. package/hooks/claude/session-start.sh +42 -0
  212. package/package.json +100 -0
  213. package/scripts/ensure-cli-executable.cjs +21 -0
  214. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  215. package/scripts/setup-unreal-gitnexus.ps1 +191 -0
  216. package/skills/gitnexus-cli.md +82 -0
  217. package/skills/gitnexus-debugging.md +89 -0
  218. package/skills/gitnexus-exploring.md +78 -0
  219. package/skills/gitnexus-guide.md +64 -0
  220. package/skills/gitnexus-impact-analysis.md +97 -0
  221. package/skills/gitnexus-pr-review.md +163 -0
  222. package/skills/gitnexus-refactoring.md +121 -0
  223. package/vendor/leiden/index.cjs +355 -0
  224. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Process Detection Processor
3
+ *
4
+ * Detects execution flows (Processes) in the code graph by:
5
+ * 1. Finding entry points (functions with no internal callers)
6
+ * 2. Tracing forward via CALLS edges (BFS)
7
+ * 3. Grouping and deduplicating similar paths
8
+ * 4. Labeling with heuristic names
9
+ *
10
+ * Processes help agents understand how features work through the codebase.
11
+ */
12
+ import { calculateEntryPointScore, isTestFile } from './entry-point-scoring.js';
13
+ import { SupportedLanguages } from '../../config/supported-languages.js';
14
+ const isDev = process.env.NODE_ENV === 'development';
15
+ const DEFAULT_CONFIG = {
16
+ maxTraceDepth: 10,
17
+ maxBranching: 4,
18
+ maxProcesses: 75,
19
+ minSteps: 3, // 3+ steps = genuine multi-hop flow (2-step is just "A calls B")
20
+ };
21
+ // ============================================================================
22
+ // MAIN PROCESSOR
23
+ // ============================================================================
24
+ /**
25
+ * Detect processes (execution flows) in the knowledge graph
26
+ *
27
+ * This runs AFTER community detection, using CALLS edges to trace flows.
28
+ */
29
+ export const processProcesses = async (knowledgeGraph, memberships, onProgress, config = {}) => {
30
+ const cfg = { ...DEFAULT_CONFIG, ...config };
31
+ onProgress?.('Finding entry points...', 0);
32
+ // Build lookup maps
33
+ const membershipMap = new Map();
34
+ memberships.forEach(m => membershipMap.set(m.nodeId, m.communityId));
35
+ const callsEdges = buildCallsGraph(knowledgeGraph);
36
+ const reverseCallsEdges = buildReverseCallsGraph(knowledgeGraph);
37
+ const nodeMap = new Map();
38
+ for (const n of knowledgeGraph.iterNodes())
39
+ nodeMap.set(n.id, n);
40
+ // Step 1: Find entry points (functions that call others but have few callers)
41
+ const entryPoints = findEntryPoints(knowledgeGraph, reverseCallsEdges, callsEdges);
42
+ onProgress?.(`Found ${entryPoints.length} entry points, tracing flows...`, 20);
43
+ onProgress?.(`Found ${entryPoints.length} entry points, tracing flows...`, 20);
44
+ // Step 2: Trace processes from each entry point
45
+ const allTraces = [];
46
+ for (let i = 0; i < entryPoints.length && allTraces.length < cfg.maxProcesses * 2; i++) {
47
+ const entryId = entryPoints[i];
48
+ const traces = traceFromEntryPoint(entryId, callsEdges, cfg);
49
+ // Filter out traces that are too short
50
+ traces.filter(t => t.length >= cfg.minSteps).forEach(t => allTraces.push(t));
51
+ if (i % 10 === 0) {
52
+ onProgress?.(`Tracing entry point ${i + 1}/${entryPoints.length}...`, 20 + (i / entryPoints.length) * 40);
53
+ }
54
+ }
55
+ onProgress?.(`Found ${allTraces.length} traces, deduplicating...`, 60);
56
+ // Step 3: Deduplicate similar traces (subset removal)
57
+ const uniqueTraces = deduplicateTraces(allTraces);
58
+ // Step 3b: Deduplicate by entry+terminal pair (keep longest path per pair)
59
+ const endpointDeduped = deduplicateByEndpoints(uniqueTraces);
60
+ onProgress?.(`Deduped ${uniqueTraces.length} → ${endpointDeduped.length} unique endpoint pairs`, 70);
61
+ // Step 4: Limit to max processes (prioritize longer traces)
62
+ const limitedTraces = endpointDeduped
63
+ .sort((a, b) => b.length - a.length)
64
+ .slice(0, cfg.maxProcesses);
65
+ onProgress?.(`Creating ${limitedTraces.length} process nodes...`, 80);
66
+ // Step 5: Create process nodes
67
+ const processes = [];
68
+ const steps = [];
69
+ limitedTraces.forEach((trace, idx) => {
70
+ const entryPointId = trace[0];
71
+ const terminalId = trace[trace.length - 1];
72
+ // Get communities touched
73
+ const communitiesSet = new Set();
74
+ trace.forEach(nodeId => {
75
+ const comm = membershipMap.get(nodeId);
76
+ if (comm)
77
+ communitiesSet.add(comm);
78
+ });
79
+ const communities = Array.from(communitiesSet);
80
+ // Determine process type
81
+ const processType = communities.length > 1 ? 'cross_community' : 'intra_community';
82
+ // Generate label
83
+ const entryNode = nodeMap.get(entryPointId);
84
+ const terminalNode = nodeMap.get(terminalId);
85
+ const entryName = entryNode?.properties.name || 'Unknown';
86
+ const terminalName = terminalNode?.properties.name || 'Unknown';
87
+ const heuristicLabel = `${capitalize(entryName)} → ${capitalize(terminalName)}`;
88
+ const processId = `proc_${idx}_${sanitizeId(entryName)}`;
89
+ processes.push({
90
+ id: processId,
91
+ label: heuristicLabel,
92
+ heuristicLabel,
93
+ processType,
94
+ stepCount: trace.length,
95
+ communities,
96
+ entryPointId,
97
+ terminalId,
98
+ trace,
99
+ });
100
+ // Create step relationships
101
+ trace.forEach((nodeId, stepIdx) => {
102
+ steps.push({
103
+ nodeId,
104
+ processId,
105
+ step: stepIdx + 1, // 1-indexed
106
+ });
107
+ });
108
+ });
109
+ onProgress?.('Process detection complete!', 100);
110
+ // Calculate stats
111
+ const crossCommunityCount = processes.filter(p => p.processType === 'cross_community').length;
112
+ const avgStepCount = processes.length > 0
113
+ ? processes.reduce((sum, p) => sum + p.stepCount, 0) / processes.length
114
+ : 0;
115
+ return {
116
+ processes,
117
+ steps,
118
+ stats: {
119
+ totalProcesses: processes.length,
120
+ crossCommunityCount,
121
+ avgStepCount: Math.round(avgStepCount * 10) / 10,
122
+ entryPointsFound: entryPoints.length,
123
+ },
124
+ };
125
+ };
126
+ /**
127
+ * Minimum edge confidence for process tracing.
128
+ * Filters out ambiguous fuzzy-global matches (0.3) that cause
129
+ * traces to jump across unrelated code areas.
130
+ */
131
+ const MIN_TRACE_CONFIDENCE = 0.5;
132
+ const buildCallsGraph = (graph) => {
133
+ const adj = new Map();
134
+ for (const rel of graph.iterRelationships()) {
135
+ if (rel.type === 'CALLS' && rel.confidence >= MIN_TRACE_CONFIDENCE) {
136
+ if (!adj.has(rel.sourceId)) {
137
+ adj.set(rel.sourceId, []);
138
+ }
139
+ adj.get(rel.sourceId).push(rel.targetId);
140
+ }
141
+ }
142
+ return adj;
143
+ };
144
+ const buildReverseCallsGraph = (graph) => {
145
+ const adj = new Map();
146
+ for (const rel of graph.iterRelationships()) {
147
+ if (rel.type === 'CALLS' && rel.confidence >= MIN_TRACE_CONFIDENCE) {
148
+ if (!adj.has(rel.targetId)) {
149
+ adj.set(rel.targetId, []);
150
+ }
151
+ adj.get(rel.targetId).push(rel.sourceId);
152
+ }
153
+ }
154
+ return adj;
155
+ };
156
+ /**
157
+ * Find functions/methods that are good entry points for tracing.
158
+ *
159
+ * Entry points are scored based on:
160
+ * 1. Call ratio (calls many, called by few)
161
+ * 2. Export status (exported/public functions rank higher)
162
+ * 3. Name patterns (handle*, on*, *Controller, etc.)
163
+ *
164
+ * Test files are excluded entirely.
165
+ */
166
+ const findEntryPoints = (graph, reverseCallsEdges, callsEdges) => {
167
+ const symbolTypes = new Set(['Function', 'Method']);
168
+ const entryPointCandidates = [];
169
+ for (const node of graph.iterNodes()) {
170
+ if (!symbolTypes.has(node.label))
171
+ continue;
172
+ const filePath = node.properties.filePath || '';
173
+ // Skip test files entirely
174
+ if (isTestFile(filePath))
175
+ continue;
176
+ const callers = reverseCallsEdges.get(node.id) || [];
177
+ const callees = callsEdges.get(node.id) || [];
178
+ // Must have at least 1 outgoing call to trace forward
179
+ if (callees.length === 0)
180
+ continue;
181
+ // Calculate entry point score using new scoring system
182
+ const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language ?? SupportedLanguages.JavaScript, node.properties.isExported ?? false, callers.length, callees.length, filePath // Pass filePath for framework detection
183
+ );
184
+ let score = baseScore;
185
+ const astFrameworkMultiplier = node.properties.astFrameworkMultiplier ?? 1.0;
186
+ if (astFrameworkMultiplier > 1.0) {
187
+ score *= astFrameworkMultiplier;
188
+ reasons.push(`framework-ast:${node.properties.astFrameworkReason || 'decorator'}`);
189
+ }
190
+ if (score > 0) {
191
+ entryPointCandidates.push({ id: node.id, score, reasons });
192
+ }
193
+ }
194
+ // Sort by score descending and return top candidates
195
+ const sorted = entryPointCandidates.sort((a, b) => b.score - a.score);
196
+ // DEBUG: Log top candidates with new scoring details
197
+ if (sorted.length > 0 && isDev) {
198
+ console.log(`[Process] Top 10 entry point candidates (new scoring):`);
199
+ sorted.slice(0, 10).forEach((c, i) => {
200
+ const node = graph.getNode(c.id);
201
+ const exported = node?.properties.isExported ? '✓' : '✗';
202
+ const shortPath = node?.properties.filePath?.split('/').slice(-2).join('/') || '';
203
+ console.log(` ${i + 1}. ${node?.properties.name} [exported:${exported}] (${shortPath})`);
204
+ console.log(` score: ${c.score.toFixed(2)} = [${c.reasons.join(' × ')}]`);
205
+ });
206
+ }
207
+ return sorted
208
+ .slice(0, 200) // Limit to prevent explosion
209
+ .map(c => c.id);
210
+ };
211
+ // ============================================================================
212
+ // HELPER: Trace from entry point (BFS)
213
+ // ============================================================================
214
+ /**
215
+ * Trace forward from an entry point using BFS.
216
+ * Returns all distinct paths up to maxDepth.
217
+ */
218
+ const traceFromEntryPoint = (entryId, callsEdges, config) => {
219
+ const traces = [];
220
+ // BFS with path tracking
221
+ // Each queue item: [currentNodeId, pathSoFar]
222
+ const queue = [[entryId, [entryId]]];
223
+ while (queue.length > 0 && traces.length < config.maxBranching * 3) {
224
+ const [currentId, path] = queue.shift();
225
+ // Get outgoing calls
226
+ const callees = callsEdges.get(currentId) || [];
227
+ if (callees.length === 0) {
228
+ // Terminal node - this is a complete trace
229
+ if (path.length >= config.minSteps) {
230
+ traces.push([...path]);
231
+ }
232
+ }
233
+ else if (path.length >= config.maxTraceDepth) {
234
+ // Max depth reached - save what we have
235
+ if (path.length >= config.minSteps) {
236
+ traces.push([...path]);
237
+ }
238
+ }
239
+ else {
240
+ // Continue tracing - limit branching
241
+ const limitedCallees = callees.slice(0, config.maxBranching);
242
+ let addedBranch = false;
243
+ for (const calleeId of limitedCallees) {
244
+ // Avoid cycles
245
+ if (!path.includes(calleeId)) {
246
+ queue.push([calleeId, [...path, calleeId]]);
247
+ addedBranch = true;
248
+ }
249
+ }
250
+ // If all branches were cycles, save current path as terminal
251
+ if (!addedBranch && path.length >= config.minSteps) {
252
+ traces.push([...path]);
253
+ }
254
+ }
255
+ }
256
+ return traces;
257
+ };
258
+ // ============================================================================
259
+ // HELPER: Deduplicate traces
260
+ // ============================================================================
261
+ /**
262
+ * Merge traces that are subsets of other traces.
263
+ * Keep longer traces, remove redundant shorter ones.
264
+ */
265
+ const deduplicateTraces = (traces) => {
266
+ if (traces.length === 0)
267
+ return [];
268
+ // Sort by length descending
269
+ const sorted = [...traces].sort((a, b) => b.length - a.length);
270
+ const unique = [];
271
+ for (const trace of sorted) {
272
+ // Check if this trace is a subset of any already-added trace
273
+ const traceKey = trace.join('->');
274
+ const isSubset = unique.some(existing => {
275
+ const existingKey = existing.join('->');
276
+ return existingKey.includes(traceKey);
277
+ });
278
+ if (!isSubset) {
279
+ unique.push(trace);
280
+ }
281
+ }
282
+ return unique;
283
+ };
284
+ // ============================================================================
285
+ // HELPER: Deduplicate by entry+terminal endpoints
286
+ // ============================================================================
287
+ /**
288
+ * Keep only the longest trace per unique entry→terminal pair.
289
+ * Multiple paths between the same two endpoints are redundant for agents.
290
+ */
291
+ const deduplicateByEndpoints = (traces) => {
292
+ if (traces.length === 0)
293
+ return [];
294
+ const byEndpoints = new Map();
295
+ // Sort longest first so the first seen per key is the longest
296
+ const sorted = [...traces].sort((a, b) => b.length - a.length);
297
+ for (const trace of sorted) {
298
+ const key = `${trace[0]}::${trace[trace.length - 1]}`;
299
+ if (!byEndpoints.has(key)) {
300
+ byEndpoints.set(key, trace);
301
+ }
302
+ }
303
+ return Array.from(byEndpoints.values());
304
+ };
305
+ // ============================================================================
306
+ // HELPER: String utilities
307
+ // ============================================================================
308
+ const capitalize = (s) => {
309
+ if (!s)
310
+ return s;
311
+ return s.charAt(0).toUpperCase() + s.slice(1);
312
+ };
313
+ const sanitizeId = (s) => {
314
+ return s.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 20).toLowerCase();
315
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Resolution Context
3
+ *
4
+ * Single implementation of tiered name resolution. Replaces the duplicated
5
+ * tier-selection logic previously split between symbol-resolver.ts and
6
+ * call-processor.ts.
7
+ *
8
+ * Resolution tiers (highest confidence first):
9
+ * 1. Same file (lookupExactFull — authoritative)
10
+ * 2a-named. Named binding chain (walkBindingChain via NamedImportMap)
11
+ * 2a. Import-scoped (lookupFuzzy filtered by ImportMap)
12
+ * 2b. Package-scoped (lookupFuzzy filtered by PackageMap)
13
+ * 3. Global (all candidates — consumers must check candidate count)
14
+ */
15
+ import type { SymbolTable, SymbolDefinition } from './symbol-table.js';
16
+ import type { NamedImportBinding } from './import-processor.js';
17
+ /** Resolution tier for tracking, logging, and test assertions. */
18
+ export type ResolutionTier = 'same-file' | 'import-scoped' | 'global';
19
+ /** Tier-selected candidates with metadata. */
20
+ export interface TieredCandidates {
21
+ readonly candidates: readonly SymbolDefinition[];
22
+ readonly tier: ResolutionTier;
23
+ }
24
+ /** Confidence scores per resolution tier. */
25
+ export declare const TIER_CONFIDENCE: Record<ResolutionTier, number>;
26
+ export type ImportMap = Map<string, Set<string>>;
27
+ export type PackageMap = Map<string, Set<string>>;
28
+ export type NamedImportMap = Map<string, Map<string, NamedImportBinding>>;
29
+ export interface ResolutionContext {
30
+ /**
31
+ * The only resolution API. Returns all candidates at the winning tier.
32
+ *
33
+ * Tier 3 ('global') returns ALL candidates regardless of count —
34
+ * consumers must check candidates.length and refuse ambiguous matches.
35
+ */
36
+ resolve(name: string, fromFile: string): TieredCandidates | null;
37
+ /** Symbol table — used by parsing-processor to populate symbols. */
38
+ readonly symbols: SymbolTable;
39
+ /** Raw maps — used by import-processor to populate import data. */
40
+ readonly importMap: ImportMap;
41
+ readonly packageMap: PackageMap;
42
+ readonly namedImportMap: NamedImportMap;
43
+ enableCache(filePath: string): void;
44
+ clearCache(): void;
45
+ getStats(): {
46
+ fileCount: number;
47
+ globalSymbolCount: number;
48
+ cacheHits: number;
49
+ cacheMisses: number;
50
+ };
51
+ clear(): void;
52
+ }
53
+ export declare const createResolutionContext: () => ResolutionContext;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Resolution Context
3
+ *
4
+ * Single implementation of tiered name resolution. Replaces the duplicated
5
+ * tier-selection logic previously split between symbol-resolver.ts and
6
+ * call-processor.ts.
7
+ *
8
+ * Resolution tiers (highest confidence first):
9
+ * 1. Same file (lookupExactFull — authoritative)
10
+ * 2a-named. Named binding chain (walkBindingChain via NamedImportMap)
11
+ * 2a. Import-scoped (lookupFuzzy filtered by ImportMap)
12
+ * 2b. Package-scoped (lookupFuzzy filtered by PackageMap)
13
+ * 3. Global (all candidates — consumers must check candidate count)
14
+ */
15
+ import { createSymbolTable } from './symbol-table.js';
16
+ import { isFileInPackageDir } from './import-processor.js';
17
+ import { walkBindingChain } from './named-binding-extraction.js';
18
+ /** Confidence scores per resolution tier. */
19
+ export const TIER_CONFIDENCE = {
20
+ 'same-file': 0.95,
21
+ 'import-scoped': 0.9,
22
+ 'global': 0.5,
23
+ };
24
+ export const createResolutionContext = () => {
25
+ const symbols = createSymbolTable();
26
+ const importMap = new Map();
27
+ const packageMap = new Map();
28
+ const namedImportMap = new Map();
29
+ // Per-file cache state
30
+ let cacheFile = null;
31
+ let cache = null;
32
+ let cacheHits = 0;
33
+ let cacheMisses = 0;
34
+ // --- Core resolution (single implementation of tier logic) ---
35
+ const resolveUncached = (name, fromFile) => {
36
+ // Tier 1: Same file — authoritative match
37
+ const localDef = symbols.lookupExactFull(fromFile, name);
38
+ if (localDef) {
39
+ return { candidates: [localDef], tier: 'same-file' };
40
+ }
41
+ // Get all global definitions for subsequent tiers
42
+ const allDefs = symbols.lookupFuzzy(name);
43
+ // Tier 2a-named: Check named bindings BEFORE empty-allDefs early return
44
+ // because aliased imports mean lookupFuzzy('U') returns empty but we
45
+ // can resolve via the exported name.
46
+ const chainResult = walkBindingChain(name, fromFile, symbols, namedImportMap, allDefs);
47
+ if (chainResult && chainResult.length > 0) {
48
+ return { candidates: chainResult, tier: 'import-scoped' };
49
+ }
50
+ if (allDefs.length === 0)
51
+ return null;
52
+ // Tier 2a: Import-scoped — definition in a file imported by fromFile
53
+ const importedFiles = importMap.get(fromFile);
54
+ if (importedFiles) {
55
+ const importedDefs = allDefs.filter(def => importedFiles.has(def.filePath));
56
+ if (importedDefs.length > 0) {
57
+ return { candidates: importedDefs, tier: 'import-scoped' };
58
+ }
59
+ }
60
+ // Tier 2b: Package-scoped — definition in a package dir imported by fromFile
61
+ const importedPackages = packageMap.get(fromFile);
62
+ if (importedPackages) {
63
+ const packageDefs = allDefs.filter(def => {
64
+ for (const dirSuffix of importedPackages) {
65
+ if (isFileInPackageDir(def.filePath, dirSuffix))
66
+ return true;
67
+ }
68
+ return false;
69
+ });
70
+ if (packageDefs.length > 0) {
71
+ return { candidates: packageDefs, tier: 'import-scoped' };
72
+ }
73
+ }
74
+ // Tier 3: Global — pass all candidates through.
75
+ // Consumers must check candidate count and refuse ambiguous matches.
76
+ return { candidates: allDefs, tier: 'global' };
77
+ };
78
+ const resolve = (name, fromFile) => {
79
+ // Check cache (only when enabled AND fromFile matches cached file)
80
+ if (cache && cacheFile === fromFile) {
81
+ if (cache.has(name)) {
82
+ cacheHits++;
83
+ return cache.get(name);
84
+ }
85
+ cacheMisses++;
86
+ }
87
+ const result = resolveUncached(name, fromFile);
88
+ // Store in cache if active and file matches
89
+ if (cache && cacheFile === fromFile) {
90
+ cache.set(name, result);
91
+ }
92
+ return result;
93
+ };
94
+ // --- Cache lifecycle ---
95
+ const enableCache = (filePath) => {
96
+ cacheFile = filePath;
97
+ if (!cache)
98
+ cache = new Map();
99
+ else
100
+ cache.clear();
101
+ };
102
+ const clearCache = () => {
103
+ cacheFile = null;
104
+ // Reuse the Map instance — just clear entries to reduce GC pressure at scale.
105
+ cache?.clear();
106
+ };
107
+ const getStats = () => ({
108
+ ...symbols.getStats(),
109
+ cacheHits,
110
+ cacheMisses,
111
+ });
112
+ const clear = () => {
113
+ symbols.clear();
114
+ importMap.clear();
115
+ packageMap.clear();
116
+ namedImportMap.clear();
117
+ clearCache();
118
+ cacheHits = 0;
119
+ cacheMisses = 0;
120
+ };
121
+ return {
122
+ resolve,
123
+ symbols,
124
+ importMap,
125
+ packageMap,
126
+ namedImportMap,
127
+ enableCache,
128
+ clearCache,
129
+ getStats,
130
+ clear,
131
+ };
132
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * C# namespace import resolution.
3
+ * Handles using-directive resolution via .csproj root namespace stripping.
4
+ */
5
+ import type { SuffixIndex } from './utils.js';
6
+ /** C# project config parsed from .csproj files */
7
+ export interface CSharpProjectConfig {
8
+ /** Root namespace from <RootNamespace> or assembly name (default: project directory name) */
9
+ rootNamespace: string;
10
+ /** Directory containing the .csproj file */
11
+ projectDir: string;
12
+ }
13
+ /**
14
+ * Resolve a C# using-directive import path to matching .cs files.
15
+ * Tries single-file match first, then directory match for namespace imports.
16
+ */
17
+ export declare function resolveCSharpImport(importPath: string, csharpConfigs: CSharpProjectConfig[], normalizedFileList: string[], allFileList: string[], index?: SuffixIndex): string[];
18
+ /**
19
+ * Compute the directory suffix for a C# namespace import (for PackageMap).
20
+ * Returns a suffix like "/ProjectDir/Models/" or null if no config matches.
21
+ */
22
+ export declare function resolveCSharpNamespaceDir(importPath: string, csharpConfigs: CSharpProjectConfig[]): string | null;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * C# namespace import resolution.
3
+ * Handles using-directive resolution via .csproj root namespace stripping.
4
+ */
5
+ import { suffixResolve } from './utils.js';
6
+ /**
7
+ * Resolve a C# using-directive import path to matching .cs files.
8
+ * Tries single-file match first, then directory match for namespace imports.
9
+ */
10
+ export function resolveCSharpImport(importPath, csharpConfigs, normalizedFileList, allFileList, index) {
11
+ const namespacePath = importPath.replace(/\./g, '/');
12
+ const results = [];
13
+ for (const config of csharpConfigs) {
14
+ const nsPath = config.rootNamespace.replace(/\./g, '/');
15
+ let relative;
16
+ if (namespacePath.startsWith(nsPath + '/')) {
17
+ relative = namespacePath.slice(nsPath.length + 1);
18
+ }
19
+ else if (namespacePath === nsPath) {
20
+ // The import IS the root namespace — resolve to all .cs files in project root
21
+ relative = '';
22
+ }
23
+ else {
24
+ continue;
25
+ }
26
+ const dirPrefix = config.projectDir
27
+ ? (relative ? config.projectDir + '/' + relative : config.projectDir)
28
+ : relative;
29
+ // 1. Try as single file: relative.cs (e.g., "Models/DlqMessage.cs")
30
+ if (relative) {
31
+ const candidate = dirPrefix + '.cs';
32
+ if (index) {
33
+ const result = index.get(candidate) || index.getInsensitive(candidate);
34
+ if (result)
35
+ return [result];
36
+ }
37
+ // Also try suffix match
38
+ const suffixResult = index?.get(relative + '.cs') || index?.getInsensitive(relative + '.cs');
39
+ if (suffixResult)
40
+ return [suffixResult];
41
+ }
42
+ // 2. Try as directory: all .cs files directly inside (namespace import)
43
+ if (index) {
44
+ const dirFiles = index.getFilesInDir(dirPrefix, '.cs');
45
+ for (const f of dirFiles) {
46
+ const normalized = f.replace(/\\/g, '/');
47
+ // Check it's a direct child by finding the dirPrefix and ensuring no deeper slashes
48
+ const prefixIdx = normalized.indexOf(dirPrefix + '/');
49
+ if (prefixIdx < 0)
50
+ continue;
51
+ const afterDir = normalized.substring(prefixIdx + dirPrefix.length + 1);
52
+ if (!afterDir.includes('/')) {
53
+ results.push(f);
54
+ }
55
+ }
56
+ if (results.length > 0)
57
+ return results;
58
+ }
59
+ // 3. Linear scan fallback for directory matching
60
+ if (results.length === 0) {
61
+ const dirTrail = dirPrefix + '/';
62
+ for (let i = 0; i < normalizedFileList.length; i++) {
63
+ const normalized = normalizedFileList[i];
64
+ if (!normalized.endsWith('.cs'))
65
+ continue;
66
+ const prefixIdx = normalized.indexOf(dirTrail);
67
+ if (prefixIdx < 0)
68
+ continue;
69
+ const afterDir = normalized.substring(prefixIdx + dirTrail.length);
70
+ if (!afterDir.includes('/')) {
71
+ results.push(allFileList[i]);
72
+ }
73
+ }
74
+ if (results.length > 0)
75
+ return results;
76
+ }
77
+ }
78
+ // Fallback: suffix matching without namespace stripping (single file)
79
+ const pathParts = namespacePath.split('/').filter(Boolean);
80
+ const fallback = suffixResolve(pathParts, normalizedFileList, allFileList, index);
81
+ return fallback ? [fallback] : [];
82
+ }
83
+ /**
84
+ * Compute the directory suffix for a C# namespace import (for PackageMap).
85
+ * Returns a suffix like "/ProjectDir/Models/" or null if no config matches.
86
+ */
87
+ export function resolveCSharpNamespaceDir(importPath, csharpConfigs) {
88
+ const namespacePath = importPath.replace(/\./g, '/');
89
+ for (const config of csharpConfigs) {
90
+ const nsPath = config.rootNamespace.replace(/\./g, '/');
91
+ let relative;
92
+ if (namespacePath.startsWith(nsPath + '/')) {
93
+ relative = namespacePath.slice(nsPath.length + 1);
94
+ }
95
+ else if (namespacePath === nsPath) {
96
+ relative = '';
97
+ }
98
+ else {
99
+ continue;
100
+ }
101
+ const dirPrefix = config.projectDir
102
+ ? (relative ? config.projectDir + '/' + relative : config.projectDir)
103
+ : relative;
104
+ if (!dirPrefix)
105
+ continue;
106
+ return '/' + dirPrefix + '/';
107
+ }
108
+ return null;
109
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Go package import resolution.
3
+ * Handles Go module path-based package imports.
4
+ */
5
+ /** Go module config parsed from go.mod */
6
+ export interface GoModuleConfig {
7
+ /** Module path (e.g., "github.com/user/repo") */
8
+ modulePath: string;
9
+ }
10
+ /**
11
+ * Extract the package directory suffix from a Go import path.
12
+ * Returns the suffix string (e.g., "/internal/auth/") or null if invalid.
13
+ */
14
+ export declare function resolveGoPackageDir(importPath: string, goModule: GoModuleConfig): string | null;
15
+ /**
16
+ * Resolve a Go internal package import to all .go files in the package directory.
17
+ * Returns an array of file paths.
18
+ */
19
+ export declare function resolveGoPackage(importPath: string, goModule: GoModuleConfig, normalizedFileList: string[], allFileList: string[]): string[];