@mgamil/mapx 0.2.4

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 (203) hide show
  1. package/LICENSE +194 -0
  2. package/README.md +488 -0
  3. package/VERSION +1 -0
  4. package/dist/agents/generator.d.ts +74 -0
  5. package/dist/agents/generator.js +375 -0
  6. package/dist/agents/templates.d.ts +29 -0
  7. package/dist/agents/templates.js +459 -0
  8. package/dist/cli.d.ts +16 -0
  9. package/dist/cli.js +1835 -0
  10. package/dist/core/cluster-engine.d.ts +32 -0
  11. package/dist/core/cluster-engine.js +314 -0
  12. package/dist/core/config.d.ts +29 -0
  13. package/dist/core/config.js +178 -0
  14. package/dist/core/context-builder.d.ts +61 -0
  15. package/dist/core/context-builder.js +252 -0
  16. package/dist/core/flow-tracer.d.ts +63 -0
  17. package/dist/core/flow-tracer.js +366 -0
  18. package/dist/core/git-tracker.d.ts +20 -0
  19. package/dist/core/git-tracker.js +159 -0
  20. package/dist/core/graph.d.ts +42 -0
  21. package/dist/core/graph.js +186 -0
  22. package/dist/core/metrics.d.ts +24 -0
  23. package/dist/core/metrics.js +87 -0
  24. package/dist/core/scanner.d.ts +53 -0
  25. package/dist/core/scanner.js +949 -0
  26. package/dist/core/store-bun.d.ts +13 -0
  27. package/dist/core/store-bun.js +34 -0
  28. package/dist/core/store-interface.d.ts +15 -0
  29. package/dist/core/store-interface.js +7 -0
  30. package/dist/core/store-node.d.ts +13 -0
  31. package/dist/core/store-node.js +35 -0
  32. package/dist/core/store.d.ts +132 -0
  33. package/dist/core/store.js +614 -0
  34. package/dist/core/workspace-manager.d.ts +9 -0
  35. package/dist/core/workspace-manager.js +64 -0
  36. package/dist/exporters/dot-exporter.d.ts +16 -0
  37. package/dist/exporters/dot-exporter.js +179 -0
  38. package/dist/exporters/graph-exporter.d.ts +14 -0
  39. package/dist/exporters/graph-exporter.js +85 -0
  40. package/dist/exporters/index.d.ts +9 -0
  41. package/dist/exporters/index.js +12 -0
  42. package/dist/exporters/llm-exporter.d.ts +18 -0
  43. package/dist/exporters/llm-exporter.js +224 -0
  44. package/dist/exporters/svg-exporter.d.ts +19 -0
  45. package/dist/exporters/svg-exporter.js +319 -0
  46. package/dist/exporters/toon-exporter.d.ts +16 -0
  47. package/dist/exporters/toon-exporter.js +246 -0
  48. package/dist/frameworks/detectors/aspnet.d.ts +11 -0
  49. package/dist/frameworks/detectors/aspnet.js +52 -0
  50. package/dist/frameworks/detectors/django.d.ts +14 -0
  51. package/dist/frameworks/detectors/django.js +135 -0
  52. package/dist/frameworks/detectors/drupal.d.ts +13 -0
  53. package/dist/frameworks/detectors/drupal.js +94 -0
  54. package/dist/frameworks/detectors/express.d.ts +12 -0
  55. package/dist/frameworks/detectors/express.js +234 -0
  56. package/dist/frameworks/detectors/fastapi.d.ts +12 -0
  57. package/dist/frameworks/detectors/fastapi.js +203 -0
  58. package/dist/frameworks/detectors/flask.d.ts +12 -0
  59. package/dist/frameworks/detectors/flask.js +244 -0
  60. package/dist/frameworks/detectors/go.d.ts +11 -0
  61. package/dist/frameworks/detectors/go.js +75 -0
  62. package/dist/frameworks/detectors/laravel.d.ts +11 -0
  63. package/dist/frameworks/detectors/laravel.js +462 -0
  64. package/dist/frameworks/detectors/nestjs.d.ts +12 -0
  65. package/dist/frameworks/detectors/nestjs.js +155 -0
  66. package/dist/frameworks/detectors/nextjs.d.ts +11 -0
  67. package/dist/frameworks/detectors/nextjs.js +118 -0
  68. package/dist/frameworks/detectors/rails.d.ts +12 -0
  69. package/dist/frameworks/detectors/rails.js +76 -0
  70. package/dist/frameworks/detectors/react-router.d.ts +11 -0
  71. package/dist/frameworks/detectors/react-router.js +115 -0
  72. package/dist/frameworks/detectors/rust.d.ts +11 -0
  73. package/dist/frameworks/detectors/rust.js +59 -0
  74. package/dist/frameworks/detectors/spring.d.ts +11 -0
  75. package/dist/frameworks/detectors/spring.js +56 -0
  76. package/dist/frameworks/detectors/sveltekit.d.ts +11 -0
  77. package/dist/frameworks/detectors/sveltekit.js +154 -0
  78. package/dist/frameworks/detectors/symfony.d.ts +13 -0
  79. package/dist/frameworks/detectors/symfony.js +175 -0
  80. package/dist/frameworks/detectors/tanstack-router.d.ts +12 -0
  81. package/dist/frameworks/detectors/tanstack-router.js +80 -0
  82. package/dist/frameworks/detectors/vapor.d.ts +11 -0
  83. package/dist/frameworks/detectors/vapor.js +52 -0
  84. package/dist/frameworks/detectors/vue-router.d.ts +12 -0
  85. package/dist/frameworks/detectors/vue-router.js +237 -0
  86. package/dist/frameworks/detectors/wordpress.d.ts +13 -0
  87. package/dist/frameworks/detectors/wordpress.js +141 -0
  88. package/dist/frameworks/detectors/yii.d.ts +11 -0
  89. package/dist/frameworks/detectors/yii.js +131 -0
  90. package/dist/frameworks/framework-registry.d.ts +13 -0
  91. package/dist/frameworks/framework-registry.js +77 -0
  92. package/dist/frameworks/route-registry.d.ts +26 -0
  93. package/dist/frameworks/route-registry.js +102 -0
  94. package/dist/index.d.ts +19 -0
  95. package/dist/index.js +30 -0
  96. package/dist/languages/index.d.ts +2 -0
  97. package/dist/languages/index.js +7 -0
  98. package/dist/languages/installer.d.ts +13 -0
  99. package/dist/languages/installer.js +103 -0
  100. package/dist/languages/registry.d.ts +19 -0
  101. package/dist/languages/registry.js +427 -0
  102. package/dist/main.d.ts +2 -0
  103. package/dist/main.js +20 -0
  104. package/dist/mcp.d.ts +11 -0
  105. package/dist/mcp.js +1699 -0
  106. package/dist/parsers/common-methods.d.ts +3 -0
  107. package/dist/parsers/common-methods.js +33 -0
  108. package/dist/parsers/fallback-parser.d.ts +10 -0
  109. package/dist/parsers/fallback-parser.js +18 -0
  110. package/dist/parsers/generic-wasm-parser.d.ts +23 -0
  111. package/dist/parsers/generic-wasm-parser.js +168 -0
  112. package/dist/parsers/ignored-symbols.d.ts +26 -0
  113. package/dist/parsers/ignored-symbols.js +77 -0
  114. package/dist/parsers/index.d.ts +9 -0
  115. package/dist/parsers/index.js +13 -0
  116. package/dist/parsers/languages/javascript.d.ts +11 -0
  117. package/dist/parsers/languages/javascript.js +28 -0
  118. package/dist/parsers/languages/php.d.ts +15 -0
  119. package/dist/parsers/languages/php.js +648 -0
  120. package/dist/parsers/languages/typescript.d.ts +10 -0
  121. package/dist/parsers/languages/typescript.js +9 -0
  122. package/dist/parsers/languages/vue.d.ts +13 -0
  123. package/dist/parsers/languages/vue.js +63 -0
  124. package/dist/parsers/parse-worker.d.ts +2 -0
  125. package/dist/parsers/parse-worker.js +185 -0
  126. package/dist/parsers/parser-interface.d.ts +9 -0
  127. package/dist/parsers/parser-interface.js +0 -0
  128. package/dist/parsers/parser-registry.d.ts +8 -0
  129. package/dist/parsers/parser-registry.js +52 -0
  130. package/dist/parsers/wasm-parser.d.ts +16 -0
  131. package/dist/parsers/wasm-parser.js +110 -0
  132. package/dist/types.d.ts +172 -0
  133. package/dist/types.js +0 -0
  134. package/dist/ui/index.html +270 -0
  135. package/dist/ui/main.js +581 -0
  136. package/dist/ui/main.js.map +7 -0
  137. package/dist/ui/styles.css +573 -0
  138. package/dist/ui-events.d.ts +36 -0
  139. package/dist/ui-events.js +61 -0
  140. package/dist/ui-server.d.ts +12 -0
  141. package/dist/ui-server.js +504 -0
  142. package/package.json +179 -0
  143. package/queries/bash/references.scm +22 -0
  144. package/queries/bash/symbols.scm +15 -0
  145. package/queries/c/references.scm +14 -0
  146. package/queries/c/symbols.scm +30 -0
  147. package/queries/c-sharp/references.scm +26 -0
  148. package/queries/c-sharp/symbols.scm +57 -0
  149. package/queries/cpp/references.scm +21 -0
  150. package/queries/cpp/symbols.scm +44 -0
  151. package/queries/dart/references.scm +33 -0
  152. package/queries/dart/symbols.scm +38 -0
  153. package/queries/elixir/references.scm +45 -0
  154. package/queries/elixir/symbols.scm +41 -0
  155. package/queries/go/references.scm +22 -0
  156. package/queries/go/symbols.scm +53 -0
  157. package/queries/java/references.scm +32 -0
  158. package/queries/java/symbols.scm +41 -0
  159. package/queries/javascript/references.scm +14 -0
  160. package/queries/javascript/symbols.scm +23 -0
  161. package/queries/kotlin/references.scm +31 -0
  162. package/queries/kotlin/symbols.scm +24 -0
  163. package/queries/lua/references.scm +19 -0
  164. package/queries/lua/symbols.scm +29 -0
  165. package/queries/pascal/references.scm +29 -0
  166. package/queries/pascal/symbols.scm +45 -0
  167. package/queries/php/references.scm +109 -0
  168. package/queries/php/symbols.scm +33 -0
  169. package/queries/python/references.scm +50 -0
  170. package/queries/python/symbols.scm +21 -0
  171. package/queries/ruby/references.scm +48 -0
  172. package/queries/ruby/symbols.scm +24 -0
  173. package/queries/rust/references.scm +31 -0
  174. package/queries/rust/symbols.scm +35 -0
  175. package/queries/scala/references.scm +30 -0
  176. package/queries/scala/symbols.scm +35 -0
  177. package/queries/svelte/references.scm +20 -0
  178. package/queries/svelte/symbols.scm +30 -0
  179. package/queries/swift/references.scm +22 -0
  180. package/queries/swift/symbols.scm +37 -0
  181. package/queries/typescript/references.scm +25 -0
  182. package/queries/typescript/symbols.scm +35 -0
  183. package/queries/vue/references.scm +20 -0
  184. package/queries/vue/symbols.scm +28 -0
  185. package/queries/zig/references.scm +20 -0
  186. package/queries/zig/symbols.scm +22 -0
  187. package/wasm/tree-sitter-c.wasm +0 -0
  188. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  189. package/wasm/tree-sitter-cpp.wasm +0 -0
  190. package/wasm/tree-sitter-dart.wasm +0 -0
  191. package/wasm/tree-sitter-go.wasm +0 -0
  192. package/wasm/tree-sitter-java.wasm +0 -0
  193. package/wasm/tree-sitter-javascript.wasm +0 -0
  194. package/wasm/tree-sitter-kotlin.wasm +0 -0
  195. package/wasm/tree-sitter-php.wasm +0 -0
  196. package/wasm/tree-sitter-python.wasm +0 -0
  197. package/wasm/tree-sitter-ruby.wasm +0 -0
  198. package/wasm/tree-sitter-rust.wasm +0 -0
  199. package/wasm/tree-sitter-scala.wasm +0 -0
  200. package/wasm/tree-sitter-swift.wasm +0 -0
  201. package/wasm/tree-sitter-tsx.wasm +0 -0
  202. package/wasm/tree-sitter-typescript.wasm +0 -0
  203. package/wasm/tree-sitter-vue.wasm +0 -0
@@ -0,0 +1,252 @@
1
+ const STOP_WORDS = /* @__PURE__ */ new Set([
2
+ "the",
3
+ "and",
4
+ "for",
5
+ "this",
6
+ "that",
7
+ "with",
8
+ "from",
9
+ "test",
10
+ "task",
11
+ "implement",
12
+ "add",
13
+ "fix",
14
+ "bug",
15
+ "issue",
16
+ "update",
17
+ "delete",
18
+ "remove",
19
+ "create",
20
+ "make",
21
+ "get",
22
+ "set",
23
+ "run",
24
+ "code",
25
+ "file",
26
+ "project",
27
+ "class",
28
+ "function",
29
+ "method",
30
+ "interface",
31
+ "type",
32
+ "import",
33
+ "export",
34
+ "require",
35
+ "include",
36
+ "exclude"
37
+ ]);
38
+ const SUFFIXES = /* @__PURE__ */ new Set(["controller", "service", "repository", "manager", "handler", "helper", "provider", "model"]);
39
+ class ContextBuilder {
40
+ store;
41
+ graph;
42
+ constructor(store, graph) {
43
+ this.store = store;
44
+ this.graph = graph;
45
+ }
46
+ static extractKeywords(text) {
47
+ const withSpaces = text.replace(/([a-z])([A-Z])/g, "$1 $2");
48
+ const words = withSpaces.toLowerCase().split(/[^a-z0-9]+/);
49
+ const keywords = [];
50
+ for (const word of words) {
51
+ if (word.length >= 3 && !STOP_WORDS.has(word)) {
52
+ keywords.push(word);
53
+ for (const suffix of SUFFIXES) {
54
+ if (word.endsWith(suffix) && word.length > suffix.length) {
55
+ keywords.push(word.slice(0, -suffix.length));
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return Array.from(new Set(keywords));
61
+ }
62
+ async buildContext(options) {
63
+ const budget = options.tokens ?? 8192;
64
+ const maxDepth = options.depth ?? 2;
65
+ const repo = options.repo;
66
+ const matchedSymbols = [];
67
+ const seedFiles = /* @__PURE__ */ new Set();
68
+ if (options.seeds) {
69
+ for (const seed of options.seeds) {
70
+ if (seed.includes(".") || seed.includes("/")) {
71
+ if (this.store.getFile(seed)) {
72
+ seedFiles.add(seed);
73
+ }
74
+ } else {
75
+ const sym = this.store.getSymbolByName(seed, repo);
76
+ if (sym) {
77
+ seedFiles.add(sym.file_path);
78
+ matchedSymbols.push({
79
+ name: sym.name,
80
+ kind: sym.kind,
81
+ filePath: sym.file_path
82
+ });
83
+ }
84
+ }
85
+ }
86
+ }
87
+ const keywords = ContextBuilder.extractKeywords(options.task);
88
+ for (const kw of keywords) {
89
+ const syms = this.store.searchSymbolsFiltered({ term: kw, repo, limit: 10 });
90
+ for (const sym of syms) {
91
+ seedFiles.add(sym.file_path);
92
+ if (!matchedSymbols.some((s) => s.name === sym.name && s.filePath === sym.file_path)) {
93
+ matchedSymbols.push({
94
+ name: sym.name,
95
+ kind: sym.kind,
96
+ filePath: sym.file_path
97
+ });
98
+ }
99
+ }
100
+ }
101
+ const allFiles = this.store.getAllFiles(repo);
102
+ for (const kw of keywords) {
103
+ const kwLower = kw.toLowerCase();
104
+ for (const f of allFiles) {
105
+ if (f.path.toLowerCase().includes(kwLower)) {
106
+ seedFiles.add(f.path);
107
+ }
108
+ }
109
+ }
110
+ if (seedFiles.size === 0) {
111
+ const topFiles = this.graph.getRankedFiles().slice(0, 5);
112
+ for (const tf of topFiles) {
113
+ seedFiles.add(tf.path);
114
+ }
115
+ }
116
+ const distances = /* @__PURE__ */ new Map();
117
+ for (const sf of seedFiles) {
118
+ distances.set(sf, 0);
119
+ }
120
+ const queue = Array.from(seedFiles);
121
+ let head = 0;
122
+ while (head < queue.length) {
123
+ const file = queue[head++];
124
+ const currentDepth = distances.get(file);
125
+ if (currentDepth >= maxDepth) continue;
126
+ const neighbors = [
127
+ ...this.graph.getDependencies(file).map((d) => d.target),
128
+ ...this.graph.getReverseDependencies(file).map((r) => r.source)
129
+ ];
130
+ for (const neighbor of neighbors) {
131
+ if (!distances.has(neighbor)) {
132
+ distances.set(neighbor, currentDepth + 1);
133
+ queue.push(neighbor);
134
+ }
135
+ }
136
+ }
137
+ const rankedAll = this.graph.getRankedFiles();
138
+ const rankedMap = /* @__PURE__ */ new Map();
139
+ for (const f of rankedAll) {
140
+ rankedMap.set(f.path, f.pagerank);
141
+ }
142
+ const candidates = Array.from(distances.keys());
143
+ const candidateScores = candidates.map((path) => {
144
+ const depth = distances.get(path);
145
+ const dbFile = this.store.getFile(path);
146
+ const language = dbFile ? dbFile.language : "unknown";
147
+ let score = 0;
148
+ if (depth === 0) {
149
+ score += 1e3;
150
+ } else if (depth === 1) {
151
+ score += 100;
152
+ } else if (depth === 2) {
153
+ score += 10;
154
+ } else {
155
+ score += 1;
156
+ }
157
+ const pathLower = path.toLowerCase();
158
+ let keywordPathMatches = 0;
159
+ for (const kw of keywords) {
160
+ if (pathLower.includes(kw.toLowerCase())) {
161
+ keywordPathMatches++;
162
+ }
163
+ }
164
+ score += keywordPathMatches * 200;
165
+ const fileSyms = this.store.getSymbolsForFile(path);
166
+ let keywordSymbolMatches = 0;
167
+ for (const sym of fileSyms) {
168
+ const symNameLower = sym.name.toLowerCase();
169
+ for (const kw of keywords) {
170
+ if (symNameLower.includes(kw.toLowerCase())) {
171
+ keywordSymbolMatches++;
172
+ }
173
+ }
174
+ }
175
+ score += keywordSymbolMatches * 50;
176
+ const pr = rankedMap.get(path) || 0;
177
+ score += pr * 20;
178
+ return {
179
+ path,
180
+ language,
181
+ score,
182
+ depth
183
+ };
184
+ });
185
+ candidateScores.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
186
+ const includedFiles = [];
187
+ const excludedFiles = [];
188
+ let currentTokens = 0;
189
+ for (const cand of candidateScores) {
190
+ const dbFile = this.store.getFile(cand.path);
191
+ if (!dbFile) continue;
192
+ const syms = this.store.getSymbolsForFile(cand.path);
193
+ const symbolCount = syms.length;
194
+ let fileTokens = 150;
195
+ if (symbolCount > 3) {
196
+ fileTokens += (symbolCount - 3) * 20;
197
+ }
198
+ if (currentTokens + fileTokens <= budget) {
199
+ includedFiles.push({
200
+ path: cand.path,
201
+ language: cand.language,
202
+ lineCount: dbFile.lines || 0,
203
+ sizeBytes: dbFile.size_bytes || 0,
204
+ symbols: syms.map((s) => ({
205
+ name: s.name,
206
+ kind: s.kind,
207
+ scope: s.scope,
208
+ startLine: s.start_line,
209
+ endLine: s.end_line
210
+ }))
211
+ });
212
+ currentTokens += fileTokens;
213
+ } else {
214
+ excludedFiles.push(cand.path);
215
+ }
216
+ }
217
+ const includedPaths = new Set(includedFiles.map((f) => f.path));
218
+ const edges = [];
219
+ for (const path of includedPaths) {
220
+ const fileEdges = this.store.getEdgesForFile(path);
221
+ for (const edge of fileEdges) {
222
+ if (includedPaths.has(edge.target_file)) {
223
+ edges.push({
224
+ sourceFile: edge.source_file,
225
+ targetFile: edge.target_file,
226
+ sourceSymbol: edge.source_symbol,
227
+ targetSymbol: edge.target_symbol,
228
+ edgeType: edge.edge_type
229
+ });
230
+ }
231
+ }
232
+ }
233
+ const simpleFiles = includedFiles.map((f) => ({
234
+ path: f.path,
235
+ language: f.language,
236
+ lineCount: f.lineCount,
237
+ sizeBytes: f.sizeBytes
238
+ }));
239
+ return {
240
+ includedFiles,
241
+ excludedFiles,
242
+ edges,
243
+ estimatedTokens: currentTokens,
244
+ matchedSymbols,
245
+ files: simpleFiles,
246
+ symbols: matchedSymbols
247
+ };
248
+ }
249
+ }
250
+ export {
251
+ ContextBuilder
252
+ };
@@ -0,0 +1,63 @@
1
+ import { Store } from './store.js';
2
+ import { ReferenceType } from '../types.js';
3
+ import './store-interface.js';
4
+ import './graph.js';
5
+
6
+ type TraceDirection = 'down' | 'up' | 'both';
7
+ interface TraceOptions {
8
+ startSymbol?: string;
9
+ startFile?: string;
10
+ direction: TraceDirection;
11
+ maxDepth: number;
12
+ edgeTypes?: ReferenceType[];
13
+ includeStructural: boolean;
14
+ repo?: string;
15
+ }
16
+ interface TraceNode {
17
+ file: string;
18
+ symbol: string | null;
19
+ depth: number;
20
+ incomingEdgeType: ReferenceType | 'start';
21
+ }
22
+ interface TracePath {
23
+ nodes: TraceNode[];
24
+ cycles: CyclicEdge[];
25
+ }
26
+ interface CyclicEdge {
27
+ fromFile: string;
28
+ fromSymbol: string | null;
29
+ toFile: string;
30
+ toSymbol: string | null;
31
+ edgeType: ReferenceType;
32
+ cycleLength: number;
33
+ }
34
+ interface TraceResult {
35
+ start: {
36
+ file: string;
37
+ symbol: string | null;
38
+ };
39
+ direction: TraceDirection;
40
+ paths: TracePath[];
41
+ sources: TraceNode[];
42
+ sinks: TraceNode[];
43
+ cycles: CyclicEdge[];
44
+ nodeCount: number;
45
+ edgeCount: number;
46
+ maxDepthReached: boolean;
47
+ }
48
+ declare class FlowTracer {
49
+ private store;
50
+ constructor(store: Store);
51
+ trace(options: TraceOptions): TraceResult;
52
+ resolveStart(input: string, repo?: string): {
53
+ file: string;
54
+ symbol: string | null;
55
+ } | null;
56
+ findSources(repo?: string): TraceNode[];
57
+ findSinks(repo?: string): TraceNode[];
58
+ findCriticalPath(from: string, to: string, repo?: string): TracePath | null;
59
+ private getEdgesForNode;
60
+ private getScopedSymbolName;
61
+ }
62
+
63
+ export { type CyclicEdge, FlowTracer, type TraceDirection, type TraceNode, type TraceOptions, type TracePath, type TraceResult };
@@ -0,0 +1,366 @@
1
+ const DATA_BEARING_EDGES = [
2
+ "call",
3
+ "instantiation",
4
+ "param_type",
5
+ "return_type",
6
+ "relation",
7
+ "dispatch",
8
+ "notify",
9
+ "route"
10
+ ];
11
+ const STRUCTURAL_EDGES = [
12
+ "import",
13
+ "require",
14
+ "extends",
15
+ "implements",
16
+ "binding",
17
+ "middleware"
18
+ ];
19
+ function matchesSymbol(nodeSymbol, edgeSymbol) {
20
+ if (!nodeSymbol) return true;
21
+ if (!edgeSymbol) return true;
22
+ if (nodeSymbol === edgeSymbol) return true;
23
+ if (nodeSymbol.includes("::")) {
24
+ const [, name] = nodeSymbol.split("::");
25
+ if (edgeSymbol === name) return true;
26
+ }
27
+ return false;
28
+ }
29
+ class FlowTracer {
30
+ constructor(store) {
31
+ this.store = store;
32
+ }
33
+ store;
34
+ trace(options) {
35
+ const repo = options.repo;
36
+ const direction = options.direction;
37
+ const maxDepth = options.maxDepth ?? 3;
38
+ let startFile = options.startFile;
39
+ let startSymbol = options.startSymbol;
40
+ if (!startFile && startSymbol) {
41
+ const resolved = this.resolveStart(startSymbol, repo);
42
+ if (resolved) {
43
+ startFile = resolved.file;
44
+ startSymbol = resolved.symbol ?? void 0;
45
+ }
46
+ } else if (startFile && !startSymbol) {
47
+ const resolved = this.resolveStart(startFile, repo);
48
+ if (resolved) {
49
+ startFile = resolved.file;
50
+ startSymbol = resolved.symbol ?? void 0;
51
+ }
52
+ }
53
+ if (!startFile) {
54
+ throw new Error(`Could not resolve starting file or symbol: ${options.startFile || options.startSymbol}`);
55
+ }
56
+ const startNode = {
57
+ file: startFile,
58
+ symbol: startSymbol || null,
59
+ depth: 0,
60
+ incomingEdgeType: "start"
61
+ };
62
+ if (direction === "both") {
63
+ const downRes = this.trace({ ...options, direction: "down", startFile, startSymbol });
64
+ const upRes = this.trace({ ...options, direction: "up", startFile, startSymbol });
65
+ const uniqueNodesMap = /* @__PURE__ */ new Map();
66
+ const addNode = (n) => {
67
+ const key = `${n.file}::${n.symbol || ""}`;
68
+ if (!uniqueNodesMap.has(key) || uniqueNodesMap.get(key).depth > n.depth) {
69
+ uniqueNodesMap.set(key, n);
70
+ }
71
+ };
72
+ for (const p of [...downRes.paths, ...upRes.paths]) {
73
+ for (const n of p.nodes) addNode(n);
74
+ }
75
+ const mergedCycles = [...downRes.cycles, ...upRes.cycles];
76
+ const mergedSources = [...downRes.sources, ...upRes.sources];
77
+ const mergedSinks = [...downRes.sinks, ...upRes.sinks];
78
+ return {
79
+ start: { file: startFile, symbol: startSymbol || null },
80
+ direction: "both",
81
+ paths: [...downRes.paths, ...upRes.paths],
82
+ sources: Array.from(new Map(mergedSources.map((n) => [`${n.file}::${n.symbol || ""}`, n])).values()),
83
+ sinks: Array.from(new Map(mergedSinks.map((n) => [`${n.file}::${n.symbol || ""}`, n])).values()),
84
+ cycles: Array.from(new Map(mergedCycles.map((c) => [`${c.fromFile}->${c.toFile}`, c])).values()),
85
+ nodeCount: uniqueNodesMap.size,
86
+ edgeCount: downRes.edgeCount + upRes.edgeCount,
87
+ maxDepthReached: downRes.maxDepthReached || upRes.maxDepthReached
88
+ };
89
+ }
90
+ const allPaths = [];
91
+ const detectedCycles = [];
92
+ const stack = [startNode];
93
+ const stackKeys = /* @__PURE__ */ new Set([`${startFile}::${startSymbol || ""}`]);
94
+ const uniqueVisited = /* @__PURE__ */ new Set([`${startFile}::${startSymbol || ""}`]);
95
+ let maxDepthReached = false;
96
+ const getPathKey = (file, symbol) => `${file}::${symbol || ""}`;
97
+ const dfs = (current) => {
98
+ const edges = this.getEdgesForNode(current, direction, options.includeStructural, repo);
99
+ let hasValidSteps = false;
100
+ const validNextEdges = [];
101
+ for (const edge of edges) {
102
+ const nextFile = direction === "up" ? edge.source_file : edge.target_file;
103
+ const nextSymbolName = direction === "up" ? edge.source_symbol : edge.target_symbol;
104
+ const nextSymbol = this.getScopedSymbolName(nextFile, nextSymbolName, repo);
105
+ const nextKey = getPathKey(nextFile, nextSymbol);
106
+ if (stackKeys.has(nextKey)) {
107
+ const ancestorIndex = stack.findIndex((n) => getPathKey(n.file, n.symbol) === nextKey);
108
+ const cycleLength = stack.length - ancestorIndex;
109
+ detectedCycles.push({
110
+ fromFile: current.file,
111
+ fromSymbol: current.symbol,
112
+ toFile: nextFile,
113
+ toSymbol: nextSymbol,
114
+ edgeType: edge.edge_type,
115
+ cycleLength
116
+ });
117
+ } else {
118
+ hasValidSteps = true;
119
+ validNextEdges.push({ edge, nextFile, nextSymbol, nextKey });
120
+ }
121
+ }
122
+ if (!hasValidSteps || current.depth >= maxDepth) {
123
+ allPaths.push({
124
+ nodes: [...stack],
125
+ cycles: []
126
+ });
127
+ if (current.depth >= maxDepth && hasValidSteps) {
128
+ maxDepthReached = true;
129
+ }
130
+ return;
131
+ }
132
+ for (const next of validNextEdges) {
133
+ if (uniqueVisited.has(next.nextKey)) {
134
+ continue;
135
+ }
136
+ const nextNode = {
137
+ file: next.nextFile,
138
+ symbol: next.nextSymbol,
139
+ depth: current.depth + 1,
140
+ incomingEdgeType: next.edge.edge_type
141
+ };
142
+ stack.push(nextNode);
143
+ stackKeys.add(next.nextKey);
144
+ uniqueVisited.add(next.nextKey);
145
+ dfs(nextNode);
146
+ stack.pop();
147
+ stackKeys.delete(next.nextKey);
148
+ }
149
+ };
150
+ dfs(startNode);
151
+ const uniqueNodes = /* @__PURE__ */ new Set();
152
+ const traceEdges = /* @__PURE__ */ new Set();
153
+ for (const p of allPaths) {
154
+ for (let i = 0; i < p.nodes.length; i++) {
155
+ uniqueNodes.add(`${p.nodes[i].file}::${p.nodes[i].symbol || ""}`);
156
+ if (i > 0) {
157
+ const from = direction === "up" ? p.nodes[i].file : p.nodes[i - 1].file;
158
+ const to = direction === "up" ? p.nodes[i - 1].file : p.nodes[i].file;
159
+ traceEdges.add(`${from}->${to}`);
160
+ }
161
+ }
162
+ }
163
+ const traceSources = [];
164
+ const traceSinks = [];
165
+ for (const key of uniqueNodes) {
166
+ const [file, symbol] = key.split("::");
167
+ const nodeSym = symbol || null;
168
+ const node = { file, symbol: nodeSym, depth: 0, incomingEdgeType: "start" };
169
+ const outEdges = this.getEdgesForNode(node, "down", options.includeStructural, repo);
170
+ const inEdges = this.getEdgesForNode(node, "up", options.includeStructural, repo);
171
+ if (inEdges.length === 0) {
172
+ traceSources.push(node);
173
+ }
174
+ if (outEdges.length === 0) {
175
+ traceSinks.push(node);
176
+ }
177
+ }
178
+ return {
179
+ start: { file: startFile, symbol: startSymbol || null },
180
+ direction,
181
+ paths: allPaths,
182
+ sources: traceSources,
183
+ sinks: traceSinks,
184
+ cycles: detectedCycles,
185
+ nodeCount: uniqueNodes.size,
186
+ edgeCount: traceEdges.size,
187
+ maxDepthReached
188
+ };
189
+ }
190
+ resolveStart(input, repo) {
191
+ const allFiles = this.store.getAllFiles(repo);
192
+ const matchedFile = allFiles.find((f) => f.path === input || f.path.endsWith(input));
193
+ if (matchedFile) {
194
+ return { file: matchedFile.path, symbol: null };
195
+ }
196
+ let scope = null;
197
+ let name = input;
198
+ if (input.includes("::")) {
199
+ const parts = input.split("::");
200
+ scope = parts[0];
201
+ name = parts[1];
202
+ }
203
+ const allSymbols = this.store.getAllSymbols(repo);
204
+ let matchedSymbol = null;
205
+ if (scope) {
206
+ matchedSymbol = allSymbols.find((s) => s.name === name && s.scope === scope);
207
+ } else {
208
+ matchedSymbol = allSymbols.find((s) => s.name === name || s.scope === name);
209
+ }
210
+ if (matchedSymbol) {
211
+ return {
212
+ file: matchedSymbol.file_path,
213
+ symbol: matchedSymbol.scope ? `${matchedSymbol.scope}::${matchedSymbol.name}` : matchedSymbol.name
214
+ };
215
+ }
216
+ return null;
217
+ }
218
+ findSources(repo) {
219
+ const files = this.store.getAllFiles(repo);
220
+ const edges = this.store.getAllEdges(repo);
221
+ const filePaths = new Set(files.map((f) => f.path));
222
+ for (const e of edges) {
223
+ filePaths.add(e.source_file);
224
+ filePaths.add(e.target_file);
225
+ }
226
+ const dataEdges = edges.filter((e) => DATA_BEARING_EDGES.includes(e.edge_type));
227
+ const incomingCount = /* @__PURE__ */ new Map();
228
+ const outgoingCount = /* @__PURE__ */ new Map();
229
+ for (const p of filePaths) {
230
+ incomingCount.set(p, 0);
231
+ outgoingCount.set(p, 0);
232
+ }
233
+ for (const e of dataEdges) {
234
+ const src = e.source_file;
235
+ const tgt = e.target_file;
236
+ outgoingCount.set(src, (outgoingCount.get(src) || 0) + 1);
237
+ incomingCount.set(tgt, (incomingCount.get(tgt) || 0) + 1);
238
+ }
239
+ const sources = [];
240
+ for (const p of filePaths) {
241
+ const inc = incomingCount.get(p) || 0;
242
+ const out = outgoingCount.get(p) || 0;
243
+ if (inc === 0 && out > 0) {
244
+ sources.push({
245
+ file: p,
246
+ symbol: null,
247
+ depth: 0,
248
+ incomingEdgeType: "start"
249
+ });
250
+ }
251
+ }
252
+ return sources;
253
+ }
254
+ findSinks(repo) {
255
+ const files = this.store.getAllFiles(repo);
256
+ const edges = this.store.getAllEdges(repo);
257
+ const filePaths = new Set(files.map((f) => f.path));
258
+ for (const e of edges) {
259
+ filePaths.add(e.source_file);
260
+ filePaths.add(e.target_file);
261
+ }
262
+ const dataEdges = edges.filter((e) => DATA_BEARING_EDGES.includes(e.edge_type));
263
+ const incomingCount = /* @__PURE__ */ new Map();
264
+ const outgoingCount = /* @__PURE__ */ new Map();
265
+ for (const p of filePaths) {
266
+ incomingCount.set(p, 0);
267
+ outgoingCount.set(p, 0);
268
+ }
269
+ for (const e of dataEdges) {
270
+ const src = e.source_file;
271
+ const tgt = e.target_file;
272
+ outgoingCount.set(src, (outgoingCount.get(src) || 0) + 1);
273
+ incomingCount.set(tgt, (incomingCount.get(tgt) || 0) + 1);
274
+ }
275
+ const sinks = [];
276
+ for (const p of filePaths) {
277
+ const inc = incomingCount.get(p) || 0;
278
+ const out = outgoingCount.get(p) || 0;
279
+ if (out === 0 && inc > 0) {
280
+ sinks.push({
281
+ file: p,
282
+ symbol: null,
283
+ depth: 0,
284
+ incomingEdgeType: "start"
285
+ });
286
+ }
287
+ }
288
+ return sinks;
289
+ }
290
+ findCriticalPath(from, to, repo) {
291
+ const startNode = this.resolveStart(from, repo);
292
+ if (!startNode) return null;
293
+ const targetNode = this.resolveStart(to, repo);
294
+ if (!targetNode) return null;
295
+ const getPathKey = (file, symbol) => `${file}::${symbol || ""}`;
296
+ const targetKey = getPathKey(targetNode.file, targetNode.symbol);
297
+ const queue = [
298
+ { node: { file: startNode.file, symbol: startNode.symbol, depth: 0, incomingEdgeType: "start" }, path: [{ file: startNode.file, symbol: startNode.symbol, depth: 0, incomingEdgeType: "start" }] }
299
+ ];
300
+ const visited = /* @__PURE__ */ new Set([getPathKey(startNode.file, startNode.symbol)]);
301
+ while (queue.length > 0) {
302
+ const { node, path } = queue.shift();
303
+ const currentKey = getPathKey(node.file, node.symbol);
304
+ if (currentKey === targetKey) {
305
+ return {
306
+ nodes: path,
307
+ cycles: []
308
+ };
309
+ }
310
+ const edges = this.getEdgesForNode(node, "down", false, repo);
311
+ for (const edge of edges) {
312
+ const nextFile = edge.target_file;
313
+ const nextSymbolName = edge.target_symbol;
314
+ const nextSymbol = this.getScopedSymbolName(nextFile, nextSymbolName, repo);
315
+ const nextKey = getPathKey(nextFile, nextSymbol);
316
+ if (!visited.has(nextKey)) {
317
+ visited.add(nextKey);
318
+ const nextNode = {
319
+ file: nextFile,
320
+ symbol: nextSymbol,
321
+ depth: node.depth + 1,
322
+ incomingEdgeType: edge.edge_type
323
+ };
324
+ queue.push({
325
+ node: nextNode,
326
+ path: [...path, nextNode]
327
+ });
328
+ }
329
+ }
330
+ }
331
+ return null;
332
+ }
333
+ getEdgesForNode(node, direction, includeStructural, repo) {
334
+ const edges = direction === "up" ? this.store.getReverseEdges(node.file) : this.store.getEdgesForFile(node.file);
335
+ let filtered = edges;
336
+ if (repo) {
337
+ filtered = filtered.filter((e) => e.repo === repo);
338
+ }
339
+ const isDataBearing = (type) => DATA_BEARING_EDGES.includes(type);
340
+ const isStructural = (type) => STRUCTURAL_EDGES.includes(type);
341
+ filtered = filtered.filter((e) => {
342
+ const type = e.edge_type;
343
+ if (includeStructural) {
344
+ return isDataBearing(type) || isStructural(type);
345
+ }
346
+ return isDataBearing(type);
347
+ });
348
+ filtered = filtered.filter((e) => {
349
+ const edgeSym = direction === "up" ? e.target_symbol : e.source_symbol;
350
+ return matchesSymbol(node.symbol, edgeSym);
351
+ });
352
+ return filtered;
353
+ }
354
+ getScopedSymbolName(file, name, repo) {
355
+ if (!name) return null;
356
+ const symbols = this.store.getSymbolsForFile(file);
357
+ const found = symbols.find((s) => s.name === name);
358
+ if (found && found.scope) {
359
+ return `${found.scope}::${name}`;
360
+ }
361
+ return name;
362
+ }
363
+ }
364
+ export {
365
+ FlowTracer
366
+ };
@@ -0,0 +1,20 @@
1
+ import { SubmoduleInfo } from '../types.js';
2
+
3
+ interface GitFileStatus {
4
+ path: string;
5
+ status: 'added' | 'modified' | 'removed' | 'renamed' | 'unchanged';
6
+ }
7
+ interface GitBlobHash {
8
+ path: string;
9
+ hash: string;
10
+ }
11
+ declare function getGitBlobHashes(repoRoot: string): Map<string, string>;
12
+ declare function getChangedFiles(repoRoot: string, since?: string): GitFileStatus[];
13
+ declare function getCurrentCommitSha(repoRoot: string): string | null;
14
+ declare function getPreviousCommitSha(repoRoot: string): string | null;
15
+ declare function isGitRepo(dir: string): boolean;
16
+ declare function getRepoName(repoRoot: string): string;
17
+
18
+ declare function discoverSubmodules(repoRoot: string): SubmoduleInfo[];
19
+
20
+ export { type GitBlobHash, type GitFileStatus, discoverSubmodules, getChangedFiles, getCurrentCommitSha, getGitBlobHashes, getPreviousCommitSha, getRepoName, isGitRepo };