@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.
- package/LICENSE +194 -0
- package/README.md +488 -0
- package/VERSION +1 -0
- package/dist/agents/generator.d.ts +74 -0
- package/dist/agents/generator.js +375 -0
- package/dist/agents/templates.d.ts +29 -0
- package/dist/agents/templates.js +459 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +1835 -0
- package/dist/core/cluster-engine.d.ts +32 -0
- package/dist/core/cluster-engine.js +314 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.js +178 -0
- package/dist/core/context-builder.d.ts +61 -0
- package/dist/core/context-builder.js +252 -0
- package/dist/core/flow-tracer.d.ts +63 -0
- package/dist/core/flow-tracer.js +366 -0
- package/dist/core/git-tracker.d.ts +20 -0
- package/dist/core/git-tracker.js +159 -0
- package/dist/core/graph.d.ts +42 -0
- package/dist/core/graph.js +186 -0
- package/dist/core/metrics.d.ts +24 -0
- package/dist/core/metrics.js +87 -0
- package/dist/core/scanner.d.ts +53 -0
- package/dist/core/scanner.js +949 -0
- package/dist/core/store-bun.d.ts +13 -0
- package/dist/core/store-bun.js +34 -0
- package/dist/core/store-interface.d.ts +15 -0
- package/dist/core/store-interface.js +7 -0
- package/dist/core/store-node.d.ts +13 -0
- package/dist/core/store-node.js +35 -0
- package/dist/core/store.d.ts +132 -0
- package/dist/core/store.js +614 -0
- package/dist/core/workspace-manager.d.ts +9 -0
- package/dist/core/workspace-manager.js +64 -0
- package/dist/exporters/dot-exporter.d.ts +16 -0
- package/dist/exporters/dot-exporter.js +179 -0
- package/dist/exporters/graph-exporter.d.ts +14 -0
- package/dist/exporters/graph-exporter.js +85 -0
- package/dist/exporters/index.d.ts +9 -0
- package/dist/exporters/index.js +12 -0
- package/dist/exporters/llm-exporter.d.ts +18 -0
- package/dist/exporters/llm-exporter.js +224 -0
- package/dist/exporters/svg-exporter.d.ts +19 -0
- package/dist/exporters/svg-exporter.js +319 -0
- package/dist/exporters/toon-exporter.d.ts +16 -0
- package/dist/exporters/toon-exporter.js +246 -0
- package/dist/frameworks/detectors/aspnet.d.ts +11 -0
- package/dist/frameworks/detectors/aspnet.js +52 -0
- package/dist/frameworks/detectors/django.d.ts +14 -0
- package/dist/frameworks/detectors/django.js +135 -0
- package/dist/frameworks/detectors/drupal.d.ts +13 -0
- package/dist/frameworks/detectors/drupal.js +94 -0
- package/dist/frameworks/detectors/express.d.ts +12 -0
- package/dist/frameworks/detectors/express.js +234 -0
- package/dist/frameworks/detectors/fastapi.d.ts +12 -0
- package/dist/frameworks/detectors/fastapi.js +203 -0
- package/dist/frameworks/detectors/flask.d.ts +12 -0
- package/dist/frameworks/detectors/flask.js +244 -0
- package/dist/frameworks/detectors/go.d.ts +11 -0
- package/dist/frameworks/detectors/go.js +75 -0
- package/dist/frameworks/detectors/laravel.d.ts +11 -0
- package/dist/frameworks/detectors/laravel.js +462 -0
- package/dist/frameworks/detectors/nestjs.d.ts +12 -0
- package/dist/frameworks/detectors/nestjs.js +155 -0
- package/dist/frameworks/detectors/nextjs.d.ts +11 -0
- package/dist/frameworks/detectors/nextjs.js +118 -0
- package/dist/frameworks/detectors/rails.d.ts +12 -0
- package/dist/frameworks/detectors/rails.js +76 -0
- package/dist/frameworks/detectors/react-router.d.ts +11 -0
- package/dist/frameworks/detectors/react-router.js +115 -0
- package/dist/frameworks/detectors/rust.d.ts +11 -0
- package/dist/frameworks/detectors/rust.js +59 -0
- package/dist/frameworks/detectors/spring.d.ts +11 -0
- package/dist/frameworks/detectors/spring.js +56 -0
- package/dist/frameworks/detectors/sveltekit.d.ts +11 -0
- package/dist/frameworks/detectors/sveltekit.js +154 -0
- package/dist/frameworks/detectors/symfony.d.ts +13 -0
- package/dist/frameworks/detectors/symfony.js +175 -0
- package/dist/frameworks/detectors/tanstack-router.d.ts +12 -0
- package/dist/frameworks/detectors/tanstack-router.js +80 -0
- package/dist/frameworks/detectors/vapor.d.ts +11 -0
- package/dist/frameworks/detectors/vapor.js +52 -0
- package/dist/frameworks/detectors/vue-router.d.ts +12 -0
- package/dist/frameworks/detectors/vue-router.js +237 -0
- package/dist/frameworks/detectors/wordpress.d.ts +13 -0
- package/dist/frameworks/detectors/wordpress.js +141 -0
- package/dist/frameworks/detectors/yii.d.ts +11 -0
- package/dist/frameworks/detectors/yii.js +131 -0
- package/dist/frameworks/framework-registry.d.ts +13 -0
- package/dist/frameworks/framework-registry.js +77 -0
- package/dist/frameworks/route-registry.d.ts +26 -0
- package/dist/frameworks/route-registry.js +102 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +30 -0
- package/dist/languages/index.d.ts +2 -0
- package/dist/languages/index.js +7 -0
- package/dist/languages/installer.d.ts +13 -0
- package/dist/languages/installer.js +103 -0
- package/dist/languages/registry.d.ts +19 -0
- package/dist/languages/registry.js +427 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +20 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +1699 -0
- package/dist/parsers/common-methods.d.ts +3 -0
- package/dist/parsers/common-methods.js +33 -0
- package/dist/parsers/fallback-parser.d.ts +10 -0
- package/dist/parsers/fallback-parser.js +18 -0
- package/dist/parsers/generic-wasm-parser.d.ts +23 -0
- package/dist/parsers/generic-wasm-parser.js +168 -0
- package/dist/parsers/ignored-symbols.d.ts +26 -0
- package/dist/parsers/ignored-symbols.js +77 -0
- package/dist/parsers/index.d.ts +9 -0
- package/dist/parsers/index.js +13 -0
- package/dist/parsers/languages/javascript.d.ts +11 -0
- package/dist/parsers/languages/javascript.js +28 -0
- package/dist/parsers/languages/php.d.ts +15 -0
- package/dist/parsers/languages/php.js +648 -0
- package/dist/parsers/languages/typescript.d.ts +10 -0
- package/dist/parsers/languages/typescript.js +9 -0
- package/dist/parsers/languages/vue.d.ts +13 -0
- package/dist/parsers/languages/vue.js +63 -0
- package/dist/parsers/parse-worker.d.ts +2 -0
- package/dist/parsers/parse-worker.js +185 -0
- package/dist/parsers/parser-interface.d.ts +9 -0
- package/dist/parsers/parser-interface.js +0 -0
- package/dist/parsers/parser-registry.d.ts +8 -0
- package/dist/parsers/parser-registry.js +52 -0
- package/dist/parsers/wasm-parser.d.ts +16 -0
- package/dist/parsers/wasm-parser.js +110 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.js +0 -0
- package/dist/ui/index.html +270 -0
- package/dist/ui/main.js +581 -0
- package/dist/ui/main.js.map +7 -0
- package/dist/ui/styles.css +573 -0
- package/dist/ui-events.d.ts +36 -0
- package/dist/ui-events.js +61 -0
- package/dist/ui-server.d.ts +12 -0
- package/dist/ui-server.js +504 -0
- package/package.json +179 -0
- package/queries/bash/references.scm +22 -0
- package/queries/bash/symbols.scm +15 -0
- package/queries/c/references.scm +14 -0
- package/queries/c/symbols.scm +30 -0
- package/queries/c-sharp/references.scm +26 -0
- package/queries/c-sharp/symbols.scm +57 -0
- package/queries/cpp/references.scm +21 -0
- package/queries/cpp/symbols.scm +44 -0
- package/queries/dart/references.scm +33 -0
- package/queries/dart/symbols.scm +38 -0
- package/queries/elixir/references.scm +45 -0
- package/queries/elixir/symbols.scm +41 -0
- package/queries/go/references.scm +22 -0
- package/queries/go/symbols.scm +53 -0
- package/queries/java/references.scm +32 -0
- package/queries/java/symbols.scm +41 -0
- package/queries/javascript/references.scm +14 -0
- package/queries/javascript/symbols.scm +23 -0
- package/queries/kotlin/references.scm +31 -0
- package/queries/kotlin/symbols.scm +24 -0
- package/queries/lua/references.scm +19 -0
- package/queries/lua/symbols.scm +29 -0
- package/queries/pascal/references.scm +29 -0
- package/queries/pascal/symbols.scm +45 -0
- package/queries/php/references.scm +109 -0
- package/queries/php/symbols.scm +33 -0
- package/queries/python/references.scm +50 -0
- package/queries/python/symbols.scm +21 -0
- package/queries/ruby/references.scm +48 -0
- package/queries/ruby/symbols.scm +24 -0
- package/queries/rust/references.scm +31 -0
- package/queries/rust/symbols.scm +35 -0
- package/queries/scala/references.scm +30 -0
- package/queries/scala/symbols.scm +35 -0
- package/queries/svelte/references.scm +20 -0
- package/queries/svelte/symbols.scm +30 -0
- package/queries/swift/references.scm +22 -0
- package/queries/swift/symbols.scm +37 -0
- package/queries/typescript/references.scm +25 -0
- package/queries/typescript/symbols.scm +35 -0
- package/queries/vue/references.scm +20 -0
- package/queries/vue/symbols.scm +28 -0
- package/queries/zig/references.scm +20 -0
- package/queries/zig/symbols.scm +22 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-dart.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-kotlin.wasm +0 -0
- package/wasm/tree-sitter-php.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-scala.wasm +0 -0
- package/wasm/tree-sitter-swift.wasm +0 -0
- package/wasm/tree-sitter-tsx.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- 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 };
|