depwire-cli 0.8.0 → 0.9.1
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.
|
@@ -29,7 +29,9 @@ function scanDirectory(rootDir, baseDir = rootDir) {
|
|
|
29
29
|
const isJavaScript = entry.endsWith(".js") || entry.endsWith(".jsx") || entry.endsWith(".mjs") || entry.endsWith(".cjs");
|
|
30
30
|
const isPython = entry.endsWith(".py");
|
|
31
31
|
const isGo = entry.endsWith(".go") && !entry.endsWith("_test.go");
|
|
32
|
-
|
|
32
|
+
const isRust = entry.endsWith(".rs");
|
|
33
|
+
const isC = entry.endsWith(".c") || entry.endsWith(".h");
|
|
34
|
+
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC) {
|
|
33
35
|
files.push(relative(rootDir, fullPath));
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -54,10 +56,18 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
54
56
|
// TypeScript
|
|
55
57
|
"go.mod",
|
|
56
58
|
// Go
|
|
59
|
+
"Cargo.toml",
|
|
60
|
+
// Rust
|
|
57
61
|
"pyproject.toml",
|
|
58
62
|
// Python (modern)
|
|
59
63
|
"setup.py",
|
|
60
64
|
// Python (legacy)
|
|
65
|
+
"Makefile",
|
|
66
|
+
// C/C++ (make-based)
|
|
67
|
+
"CMakeLists.txt",
|
|
68
|
+
// C/C++ (cmake-based)
|
|
69
|
+
"configure.ac",
|
|
70
|
+
// C/C++ (autotools)
|
|
61
71
|
".git"
|
|
62
72
|
// Any git repo
|
|
63
73
|
];
|
|
@@ -80,8 +90,8 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
// src/parser/index.ts
|
|
83
|
-
import { readFileSync as
|
|
84
|
-
import { join as
|
|
93
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
94
|
+
import { join as join8 } from "path";
|
|
85
95
|
|
|
86
96
|
// src/parser/detect.ts
|
|
87
97
|
import { extname as extname3 } from "path";
|
|
@@ -109,7 +119,9 @@ async function initParser() {
|
|
|
109
119
|
"tsx": "tree-sitter-tsx.wasm",
|
|
110
120
|
"javascript": "tree-sitter-javascript.wasm",
|
|
111
121
|
"python": "tree-sitter-python.wasm",
|
|
112
|
-
"go": "tree-sitter-go.wasm"
|
|
122
|
+
"go": "tree-sitter-go.wasm",
|
|
123
|
+
"rust": "tree-sitter-rust.wasm",
|
|
124
|
+
"c": "tree-sitter-c.wasm"
|
|
113
125
|
};
|
|
114
126
|
for (const [name, file] of Object.entries(grammarFiles)) {
|
|
115
127
|
const wasmPath = path.join(grammarsDir, file);
|
|
@@ -1972,12 +1984,799 @@ var goParser = {
|
|
|
1972
1984
|
parseFile: parseGoFile
|
|
1973
1985
|
};
|
|
1974
1986
|
|
|
1987
|
+
// src/parser/rust.ts
|
|
1988
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1989
|
+
import { join as join6, dirname as dirname5, relative as relative3 } from "path";
|
|
1990
|
+
function parseRustFile(filePath, sourceCode, projectRoot) {
|
|
1991
|
+
const parser = getParser("rust");
|
|
1992
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
1993
|
+
const context = {
|
|
1994
|
+
filePath,
|
|
1995
|
+
projectRoot,
|
|
1996
|
+
sourceCode,
|
|
1997
|
+
symbols: [],
|
|
1998
|
+
edges: [],
|
|
1999
|
+
currentScope: [],
|
|
2000
|
+
currentModule: []
|
|
2001
|
+
};
|
|
2002
|
+
walkNode5(tree.rootNode, context);
|
|
2003
|
+
return {
|
|
2004
|
+
filePath,
|
|
2005
|
+
symbols: context.symbols,
|
|
2006
|
+
edges: context.edges
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
function walkNode5(node, context) {
|
|
2010
|
+
processNode5(node, context);
|
|
2011
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2012
|
+
const child = node.child(i);
|
|
2013
|
+
if (child) {
|
|
2014
|
+
walkNode5(child, context);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
function processNode5(node, context) {
|
|
2019
|
+
const type = node.type;
|
|
2020
|
+
switch (type) {
|
|
2021
|
+
case "function_item":
|
|
2022
|
+
processFunctionItem(node, context);
|
|
2023
|
+
break;
|
|
2024
|
+
case "struct_item":
|
|
2025
|
+
processStructItem(node, context);
|
|
2026
|
+
break;
|
|
2027
|
+
case "enum_item":
|
|
2028
|
+
processEnumItem(node, context);
|
|
2029
|
+
break;
|
|
2030
|
+
case "trait_item":
|
|
2031
|
+
processTraitItem(node, context);
|
|
2032
|
+
break;
|
|
2033
|
+
case "impl_item":
|
|
2034
|
+
processImplItem(node, context);
|
|
2035
|
+
break;
|
|
2036
|
+
case "const_item":
|
|
2037
|
+
processConstItem(node, context);
|
|
2038
|
+
break;
|
|
2039
|
+
case "type_item":
|
|
2040
|
+
processTypeItem(node, context);
|
|
2041
|
+
break;
|
|
2042
|
+
case "use_declaration":
|
|
2043
|
+
processUseDeclaration(node, context);
|
|
2044
|
+
break;
|
|
2045
|
+
case "mod_item":
|
|
2046
|
+
processModItem(node, context);
|
|
2047
|
+
break;
|
|
2048
|
+
case "call_expression":
|
|
2049
|
+
processCallExpression5(node, context);
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function processFunctionItem(node, context) {
|
|
2054
|
+
const nameNode = node.childForFieldName("name");
|
|
2055
|
+
if (!nameNode) return;
|
|
2056
|
+
const name = nodeText4(nameNode, context);
|
|
2057
|
+
const exported = hasVisibility(node, "pub");
|
|
2058
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2059
|
+
context.symbols.push({
|
|
2060
|
+
id: symbolId,
|
|
2061
|
+
name,
|
|
2062
|
+
kind: "function",
|
|
2063
|
+
filePath: context.filePath,
|
|
2064
|
+
startLine: node.startPosition.row + 1,
|
|
2065
|
+
endLine: node.endPosition.row + 1,
|
|
2066
|
+
exported
|
|
2067
|
+
});
|
|
2068
|
+
context.currentScope.push(name);
|
|
2069
|
+
const body = node.childForFieldName("body");
|
|
2070
|
+
if (body) {
|
|
2071
|
+
walkNode5(body, context);
|
|
2072
|
+
}
|
|
2073
|
+
context.currentScope.pop();
|
|
2074
|
+
}
|
|
2075
|
+
function processStructItem(node, context) {
|
|
2076
|
+
const nameNode = node.childForFieldName("name");
|
|
2077
|
+
if (!nameNode) return;
|
|
2078
|
+
const name = nodeText4(nameNode, context);
|
|
2079
|
+
const exported = hasVisibility(node, "pub");
|
|
2080
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2081
|
+
context.symbols.push({
|
|
2082
|
+
id: symbolId,
|
|
2083
|
+
name,
|
|
2084
|
+
kind: "class",
|
|
2085
|
+
// Consistent with other parsers
|
|
2086
|
+
filePath: context.filePath,
|
|
2087
|
+
startLine: node.startPosition.row + 1,
|
|
2088
|
+
endLine: node.endPosition.row + 1,
|
|
2089
|
+
exported
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
function processEnumItem(node, context) {
|
|
2093
|
+
const nameNode = node.childForFieldName("name");
|
|
2094
|
+
if (!nameNode) return;
|
|
2095
|
+
const name = nodeText4(nameNode, context);
|
|
2096
|
+
const exported = hasVisibility(node, "pub");
|
|
2097
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2098
|
+
context.symbols.push({
|
|
2099
|
+
id: symbolId,
|
|
2100
|
+
name,
|
|
2101
|
+
kind: "enum",
|
|
2102
|
+
filePath: context.filePath,
|
|
2103
|
+
startLine: node.startPosition.row + 1,
|
|
2104
|
+
endLine: node.endPosition.row + 1,
|
|
2105
|
+
exported
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
function processTraitItem(node, context) {
|
|
2109
|
+
const nameNode = node.childForFieldName("name");
|
|
2110
|
+
if (!nameNode) return;
|
|
2111
|
+
const name = nodeText4(nameNode, context);
|
|
2112
|
+
const exported = hasVisibility(node, "pub");
|
|
2113
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2114
|
+
context.symbols.push({
|
|
2115
|
+
id: symbolId,
|
|
2116
|
+
name,
|
|
2117
|
+
kind: "interface",
|
|
2118
|
+
filePath: context.filePath,
|
|
2119
|
+
startLine: node.startPosition.row + 1,
|
|
2120
|
+
endLine: node.endPosition.row + 1,
|
|
2121
|
+
exported
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
function processImplItem(node, context) {
|
|
2125
|
+
const typeNode = node.childForFieldName("type");
|
|
2126
|
+
if (!typeNode) return;
|
|
2127
|
+
const typeName = extractTypeName2(typeNode, context);
|
|
2128
|
+
if (!typeName) return;
|
|
2129
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2130
|
+
const child = node.child(i);
|
|
2131
|
+
if (child && child.type === "function_item") {
|
|
2132
|
+
const nameNode = child.childForFieldName("name");
|
|
2133
|
+
if (!nameNode) continue;
|
|
2134
|
+
const name = nodeText4(nameNode, context);
|
|
2135
|
+
const exported = hasVisibility(child, "pub");
|
|
2136
|
+
const symbolId = `${context.filePath}::${typeName}.${name}`;
|
|
2137
|
+
context.symbols.push({
|
|
2138
|
+
id: symbolId,
|
|
2139
|
+
name,
|
|
2140
|
+
kind: "method",
|
|
2141
|
+
filePath: context.filePath,
|
|
2142
|
+
startLine: child.startPosition.row + 1,
|
|
2143
|
+
endLine: child.endPosition.row + 1,
|
|
2144
|
+
exported,
|
|
2145
|
+
scope: typeName
|
|
2146
|
+
});
|
|
2147
|
+
context.currentScope.push(`${typeName}.${name}`);
|
|
2148
|
+
const body = child.childForFieldName("body");
|
|
2149
|
+
if (body) {
|
|
2150
|
+
walkNode5(body, context);
|
|
2151
|
+
}
|
|
2152
|
+
context.currentScope.pop();
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
function processConstItem(node, context) {
|
|
2157
|
+
const nameNode = node.childForFieldName("name");
|
|
2158
|
+
if (!nameNode) return;
|
|
2159
|
+
const name = nodeText4(nameNode, context);
|
|
2160
|
+
const exported = hasVisibility(node, "pub");
|
|
2161
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2162
|
+
context.symbols.push({
|
|
2163
|
+
id: symbolId,
|
|
2164
|
+
name,
|
|
2165
|
+
kind: "constant",
|
|
2166
|
+
filePath: context.filePath,
|
|
2167
|
+
startLine: node.startPosition.row + 1,
|
|
2168
|
+
endLine: node.endPosition.row + 1,
|
|
2169
|
+
exported
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
function processTypeItem(node, context) {
|
|
2173
|
+
const nameNode = node.childForFieldName("name");
|
|
2174
|
+
if (!nameNode) return;
|
|
2175
|
+
const name = nodeText4(nameNode, context);
|
|
2176
|
+
const exported = hasVisibility(node, "pub");
|
|
2177
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2178
|
+
context.symbols.push({
|
|
2179
|
+
id: symbolId,
|
|
2180
|
+
name,
|
|
2181
|
+
kind: "type_alias",
|
|
2182
|
+
filePath: context.filePath,
|
|
2183
|
+
startLine: node.startPosition.row + 1,
|
|
2184
|
+
endLine: node.endPosition.row + 1,
|
|
2185
|
+
exported
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
function processUseDeclaration(node, context) {
|
|
2189
|
+
let pathNode = findChildByType5(node, "scoped_identifier");
|
|
2190
|
+
if (!pathNode) {
|
|
2191
|
+
pathNode = findChildByType5(node, "identifier");
|
|
2192
|
+
}
|
|
2193
|
+
if (!pathNode) {
|
|
2194
|
+
pathNode = findChildByType5(node, "use_as_clause");
|
|
2195
|
+
if (pathNode) {
|
|
2196
|
+
pathNode = pathNode.childForFieldName("path");
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
if (!pathNode) {
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
let pathText = nodeText4(pathNode, context);
|
|
2203
|
+
if (!pathText.startsWith("crate::") && !pathText.startsWith("super::") && !pathText.startsWith("self::")) {
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
const segments = pathText.split("::");
|
|
2207
|
+
if (segments.length > 1) {
|
|
2208
|
+
segments.pop();
|
|
2209
|
+
pathText = segments.join("::");
|
|
2210
|
+
}
|
|
2211
|
+
const resolvedFiles = resolveRustImport(pathText, context);
|
|
2212
|
+
if (resolvedFiles.length === 0) return;
|
|
2213
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
2214
|
+
for (const targetFile of resolvedFiles) {
|
|
2215
|
+
const targetId = `${targetFile}::__file__`;
|
|
2216
|
+
context.edges.push({
|
|
2217
|
+
source: sourceId,
|
|
2218
|
+
target: targetId,
|
|
2219
|
+
kind: "imports",
|
|
2220
|
+
filePath: context.filePath,
|
|
2221
|
+
line: node.startPosition.row + 1
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function processModItem(node, context) {
|
|
2226
|
+
const nameNode = node.childForFieldName("name");
|
|
2227
|
+
if (!nameNode) return;
|
|
2228
|
+
const name = nodeText4(nameNode, context);
|
|
2229
|
+
const body = node.childForFieldName("body");
|
|
2230
|
+
if (!body) {
|
|
2231
|
+
const resolvedFiles = resolveModuleFile(name, context);
|
|
2232
|
+
if (resolvedFiles.length > 0) {
|
|
2233
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
2234
|
+
for (const targetFile of resolvedFiles) {
|
|
2235
|
+
const targetId = `${targetFile}::__file__`;
|
|
2236
|
+
context.edges.push({
|
|
2237
|
+
source: sourceId,
|
|
2238
|
+
target: targetId,
|
|
2239
|
+
kind: "imports",
|
|
2240
|
+
filePath: context.filePath,
|
|
2241
|
+
line: node.startPosition.row + 1
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
const exported = hasVisibility(node, "pub");
|
|
2247
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2248
|
+
context.symbols.push({
|
|
2249
|
+
id: symbolId,
|
|
2250
|
+
name,
|
|
2251
|
+
kind: "module",
|
|
2252
|
+
filePath: context.filePath,
|
|
2253
|
+
startLine: node.startPosition.row + 1,
|
|
2254
|
+
endLine: node.endPosition.row + 1,
|
|
2255
|
+
exported
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
function processCallExpression5(node, context) {
|
|
2259
|
+
const functionNode = node.childForFieldName("function");
|
|
2260
|
+
if (!functionNode) return;
|
|
2261
|
+
const calleeName = extractCalleeNameFromNode(functionNode, context);
|
|
2262
|
+
if (!calleeName) return;
|
|
2263
|
+
const builtins = ["println!", "print!", "eprintln!", "eprint!", "format!", "panic!", "assert!", "assert_eq!", "assert_ne!", "vec!"];
|
|
2264
|
+
if (builtins.includes(calleeName)) return;
|
|
2265
|
+
const callerId = getCurrentSymbolId5(context);
|
|
2266
|
+
if (!callerId) return;
|
|
2267
|
+
const calleeId = resolveSymbol4(calleeName, context);
|
|
2268
|
+
if (!calleeId) return;
|
|
2269
|
+
context.edges.push({
|
|
2270
|
+
source: callerId,
|
|
2271
|
+
target: calleeId,
|
|
2272
|
+
kind: "calls",
|
|
2273
|
+
filePath: context.filePath,
|
|
2274
|
+
line: node.startPosition.row + 1
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
function resolveRustImport(importPath, context) {
|
|
2278
|
+
if (importPath.startsWith("crate::")) {
|
|
2279
|
+
const relativePath = importPath.replace("crate::", "").replace(/::/g, "/");
|
|
2280
|
+
const possibleFiles = [
|
|
2281
|
+
join6(context.projectRoot, "src", `${relativePath}.rs`),
|
|
2282
|
+
join6(context.projectRoot, "src", relativePath, "mod.rs")
|
|
2283
|
+
];
|
|
2284
|
+
return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
|
|
2285
|
+
}
|
|
2286
|
+
if (importPath.startsWith("super::")) {
|
|
2287
|
+
const currentFileAbs = join6(context.projectRoot, context.filePath);
|
|
2288
|
+
const currentDir = dirname5(currentFileAbs);
|
|
2289
|
+
const parentDir = dirname5(currentDir);
|
|
2290
|
+
const relativePath = importPath.replace("super::", "").replace(/::/g, "/");
|
|
2291
|
+
const possibleFiles = [
|
|
2292
|
+
join6(parentDir, `${relativePath}.rs`),
|
|
2293
|
+
join6(parentDir, relativePath, "mod.rs")
|
|
2294
|
+
];
|
|
2295
|
+
return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
|
|
2296
|
+
}
|
|
2297
|
+
if (importPath.startsWith("self::")) {
|
|
2298
|
+
const currentFileAbs = join6(context.projectRoot, context.filePath);
|
|
2299
|
+
const currentDir = dirname5(currentFileAbs);
|
|
2300
|
+
const relativePath = importPath.replace("self::", "").replace(/::/g, "/");
|
|
2301
|
+
const possibleFiles = [
|
|
2302
|
+
join6(currentDir, `${relativePath}.rs`),
|
|
2303
|
+
join6(currentDir, relativePath, "mod.rs")
|
|
2304
|
+
];
|
|
2305
|
+
return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
|
|
2306
|
+
}
|
|
2307
|
+
return [];
|
|
2308
|
+
}
|
|
2309
|
+
function resolveModuleFile(moduleName, context) {
|
|
2310
|
+
const currentFileAbs = join6(context.projectRoot, context.filePath);
|
|
2311
|
+
const currentDir = dirname5(currentFileAbs);
|
|
2312
|
+
const possibleFiles = [
|
|
2313
|
+
join6(currentDir, `${moduleName}.rs`),
|
|
2314
|
+
join6(currentDir, moduleName, "mod.rs")
|
|
2315
|
+
];
|
|
2316
|
+
return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
|
|
2317
|
+
}
|
|
2318
|
+
function hasVisibility(node, visibility) {
|
|
2319
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2320
|
+
const child = node.child(i);
|
|
2321
|
+
if (child && child.type === "visibility_modifier") {
|
|
2322
|
+
const text = nodeText4(child, { sourceCode: node.text });
|
|
2323
|
+
return text === visibility;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
function extractTypeName2(typeNode, context) {
|
|
2329
|
+
if (typeNode.type === "type_identifier") {
|
|
2330
|
+
return nodeText4(typeNode, context);
|
|
2331
|
+
}
|
|
2332
|
+
if (typeNode.type === "generic_type") {
|
|
2333
|
+
const typeId = findChildByType5(typeNode, "type_identifier");
|
|
2334
|
+
if (typeId) {
|
|
2335
|
+
return nodeText4(typeId, context);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
for (let i = 0; i < typeNode.childCount; i++) {
|
|
2339
|
+
const child = typeNode.child(i);
|
|
2340
|
+
if (child && child.type === "type_identifier") {
|
|
2341
|
+
return nodeText4(child, context);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2346
|
+
function extractCalleeNameFromNode(functionNode, context) {
|
|
2347
|
+
if (functionNode.type === "identifier") {
|
|
2348
|
+
return nodeText4(functionNode, context);
|
|
2349
|
+
}
|
|
2350
|
+
if (functionNode.type === "field_expression") {
|
|
2351
|
+
const field = functionNode.childForFieldName("field");
|
|
2352
|
+
if (field) {
|
|
2353
|
+
return nodeText4(field, context);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (functionNode.type === "scoped_identifier") {
|
|
2357
|
+
const name = functionNode.childForFieldName("name");
|
|
2358
|
+
if (name) {
|
|
2359
|
+
return nodeText4(name, context);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return null;
|
|
2363
|
+
}
|
|
2364
|
+
function resolveSymbol4(name, context) {
|
|
2365
|
+
const currentFileId = context.filePath;
|
|
2366
|
+
const symbol = context.symbols.find((s) => s.name === name && s.filePath === currentFileId);
|
|
2367
|
+
if (symbol) {
|
|
2368
|
+
return symbol.id;
|
|
2369
|
+
}
|
|
2370
|
+
if (context.currentScope.length > 0) {
|
|
2371
|
+
for (let i = context.currentScope.length - 1; i >= 0; i--) {
|
|
2372
|
+
const scopedId = `${currentFileId}::${context.currentScope[i]}.${name}`;
|
|
2373
|
+
const scopedSymbol = context.symbols.find((s) => s.id === scopedId);
|
|
2374
|
+
if (scopedSymbol) {
|
|
2375
|
+
return scopedId;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
function findChildByType5(node, type) {
|
|
2382
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2383
|
+
const child = node.child(i);
|
|
2384
|
+
if (child && child.type === type) {
|
|
2385
|
+
return child;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
function nodeText4(node, context) {
|
|
2391
|
+
return context.sourceCode.slice(node.startIndex, node.endIndex);
|
|
2392
|
+
}
|
|
2393
|
+
function getCurrentSymbolId5(context) {
|
|
2394
|
+
if (context.currentScope.length === 0) return null;
|
|
2395
|
+
return `${context.filePath}::${context.currentScope.join(".")}`;
|
|
2396
|
+
}
|
|
2397
|
+
var rustParser = {
|
|
2398
|
+
language: "rust",
|
|
2399
|
+
extensions: [".rs"],
|
|
2400
|
+
parseFile: parseRustFile
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
// src/parser/c.ts
|
|
2404
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2405
|
+
import { join as join7, dirname as dirname6, relative as relative4 } from "path";
|
|
2406
|
+
function parseCFile(filePath, sourceCode, projectRoot) {
|
|
2407
|
+
const parser = getParser("c");
|
|
2408
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
2409
|
+
const context = {
|
|
2410
|
+
filePath,
|
|
2411
|
+
projectRoot,
|
|
2412
|
+
sourceCode,
|
|
2413
|
+
symbols: [],
|
|
2414
|
+
edges: [],
|
|
2415
|
+
currentScope: []
|
|
2416
|
+
};
|
|
2417
|
+
walkNode6(tree.rootNode, context);
|
|
2418
|
+
return {
|
|
2419
|
+
filePath,
|
|
2420
|
+
symbols: context.symbols,
|
|
2421
|
+
edges: context.edges
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
function walkNode6(node, context) {
|
|
2425
|
+
processNode6(node, context);
|
|
2426
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2427
|
+
const child = node.child(i);
|
|
2428
|
+
if (child) {
|
|
2429
|
+
walkNode6(child, context);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
function processNode6(node, context) {
|
|
2434
|
+
const type = node.type;
|
|
2435
|
+
switch (type) {
|
|
2436
|
+
case "function_definition":
|
|
2437
|
+
processFunctionDefinition2(node, context);
|
|
2438
|
+
break;
|
|
2439
|
+
case "struct_specifier":
|
|
2440
|
+
processStructSpecifier(node, context);
|
|
2441
|
+
break;
|
|
2442
|
+
case "enum_specifier":
|
|
2443
|
+
processEnumSpecifier(node, context);
|
|
2444
|
+
break;
|
|
2445
|
+
case "type_definition":
|
|
2446
|
+
processTypeDefinition(node, context);
|
|
2447
|
+
break;
|
|
2448
|
+
case "declaration":
|
|
2449
|
+
processDeclaration(node, context);
|
|
2450
|
+
break;
|
|
2451
|
+
case "preproc_def":
|
|
2452
|
+
case "preproc_function_def":
|
|
2453
|
+
processMacroDefinition(node, context);
|
|
2454
|
+
break;
|
|
2455
|
+
case "preproc_include":
|
|
2456
|
+
processIncludeDirective(node, context);
|
|
2457
|
+
break;
|
|
2458
|
+
case "call_expression":
|
|
2459
|
+
processCallExpression6(node, context);
|
|
2460
|
+
break;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
function processFunctionDefinition2(node, context) {
|
|
2464
|
+
const declarator = node.childForFieldName("declarator");
|
|
2465
|
+
if (!declarator) return;
|
|
2466
|
+
const nameNode = extractFunctionName(declarator);
|
|
2467
|
+
if (!nameNode) return;
|
|
2468
|
+
const name = nodeText5(nameNode, context);
|
|
2469
|
+
const exported = !hasStorageClass(node, "static", context);
|
|
2470
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2471
|
+
context.symbols.push({
|
|
2472
|
+
id: symbolId,
|
|
2473
|
+
name,
|
|
2474
|
+
kind: "function",
|
|
2475
|
+
filePath: context.filePath,
|
|
2476
|
+
startLine: node.startPosition.row + 1,
|
|
2477
|
+
endLine: node.endPosition.row + 1,
|
|
2478
|
+
exported
|
|
2479
|
+
});
|
|
2480
|
+
context.currentScope.push(name);
|
|
2481
|
+
const body = node.childForFieldName("body");
|
|
2482
|
+
if (body) {
|
|
2483
|
+
walkNode6(body, context);
|
|
2484
|
+
}
|
|
2485
|
+
context.currentScope.pop();
|
|
2486
|
+
}
|
|
2487
|
+
function processStructSpecifier(node, context) {
|
|
2488
|
+
const parent = node.parent;
|
|
2489
|
+
let name = null;
|
|
2490
|
+
if (parent && parent.type === "type_definition") {
|
|
2491
|
+
const typedefName = parent.childForFieldName("declarator");
|
|
2492
|
+
if (typedefName) {
|
|
2493
|
+
name = extractIdentifierFromDeclarator(typedefName, context);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
if (!name) {
|
|
2497
|
+
const nameNode = node.childForFieldName("name");
|
|
2498
|
+
if (nameNode) {
|
|
2499
|
+
name = nodeText5(nameNode, context);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
if (!name) return;
|
|
2503
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2504
|
+
context.symbols.push({
|
|
2505
|
+
id: symbolId,
|
|
2506
|
+
name,
|
|
2507
|
+
kind: "class",
|
|
2508
|
+
filePath: context.filePath,
|
|
2509
|
+
startLine: node.startPosition.row + 1,
|
|
2510
|
+
endLine: node.endPosition.row + 1,
|
|
2511
|
+
exported: true
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
function processEnumSpecifier(node, context) {
|
|
2515
|
+
const parent = node.parent;
|
|
2516
|
+
let name = null;
|
|
2517
|
+
if (parent && parent.type === "type_definition") {
|
|
2518
|
+
const typedefName = parent.childForFieldName("declarator");
|
|
2519
|
+
if (typedefName) {
|
|
2520
|
+
name = extractIdentifierFromDeclarator(typedefName, context);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
if (!name) {
|
|
2524
|
+
const nameNode = node.childForFieldName("name");
|
|
2525
|
+
if (nameNode) {
|
|
2526
|
+
name = nodeText5(nameNode, context);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
if (!name) return;
|
|
2530
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2531
|
+
context.symbols.push({
|
|
2532
|
+
id: symbolId,
|
|
2533
|
+
name,
|
|
2534
|
+
kind: "enum",
|
|
2535
|
+
filePath: context.filePath,
|
|
2536
|
+
startLine: node.startPosition.row + 1,
|
|
2537
|
+
endLine: node.endPosition.row + 1,
|
|
2538
|
+
exported: true
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
function processTypeDefinition(node, context) {
|
|
2542
|
+
const typeNode = node.childForFieldName("type");
|
|
2543
|
+
if (!typeNode) return;
|
|
2544
|
+
if (typeNode.type === "struct_specifier" || typeNode.type === "enum_specifier") {
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const declarator = node.childForFieldName("declarator");
|
|
2548
|
+
if (!declarator) return;
|
|
2549
|
+
const name = extractIdentifierFromDeclarator(declarator, context);
|
|
2550
|
+
if (!name) return;
|
|
2551
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2552
|
+
context.symbols.push({
|
|
2553
|
+
id: symbolId,
|
|
2554
|
+
name,
|
|
2555
|
+
kind: "type_alias",
|
|
2556
|
+
filePath: context.filePath,
|
|
2557
|
+
startLine: node.startPosition.row + 1,
|
|
2558
|
+
endLine: node.endPosition.row + 1,
|
|
2559
|
+
exported: true
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
function processDeclaration(node, context) {
|
|
2563
|
+
if (context.currentScope.length > 0) {
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
const parent = node.parent;
|
|
2567
|
+
if (!parent || parent.type !== "translation_unit") {
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
const hasStatic = hasStorageClass(node, "static", context);
|
|
2571
|
+
const declarator = node.childForFieldName("declarator");
|
|
2572
|
+
if (!declarator) return;
|
|
2573
|
+
const name = extractIdentifierFromDeclarator(declarator, context);
|
|
2574
|
+
if (!name) return;
|
|
2575
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2576
|
+
context.symbols.push({
|
|
2577
|
+
id: symbolId,
|
|
2578
|
+
name,
|
|
2579
|
+
kind: "variable",
|
|
2580
|
+
filePath: context.filePath,
|
|
2581
|
+
startLine: node.startPosition.row + 1,
|
|
2582
|
+
endLine: node.endPosition.row + 1,
|
|
2583
|
+
exported: !hasStatic
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
function processMacroDefinition(node, context) {
|
|
2587
|
+
const nameNode = node.childForFieldName("name");
|
|
2588
|
+
if (!nameNode) return;
|
|
2589
|
+
const name = nodeText5(nameNode, context);
|
|
2590
|
+
const kind = node.type === "preproc_function_def" ? "function" : "constant";
|
|
2591
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
2592
|
+
context.symbols.push({
|
|
2593
|
+
id: symbolId,
|
|
2594
|
+
name,
|
|
2595
|
+
kind,
|
|
2596
|
+
filePath: context.filePath,
|
|
2597
|
+
startLine: node.startPosition.row + 1,
|
|
2598
|
+
endLine: node.endPosition.row + 1,
|
|
2599
|
+
exported: true
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
function processIncludeDirective(node, context) {
|
|
2603
|
+
const pathNode = node.childForFieldName("path");
|
|
2604
|
+
if (!pathNode) return;
|
|
2605
|
+
const pathText = nodeText5(pathNode, context);
|
|
2606
|
+
const isLocalInclude = pathText.startsWith('"') && pathText.endsWith('"');
|
|
2607
|
+
if (!isLocalInclude) {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const includePath = pathText.slice(1, -1);
|
|
2611
|
+
const resolvedFiles = resolveIncludePath(includePath, context.filePath, context.projectRoot);
|
|
2612
|
+
if (resolvedFiles.length === 0) return;
|
|
2613
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
2614
|
+
for (const targetPath of resolvedFiles) {
|
|
2615
|
+
const targetId = `${targetPath}::__file__`;
|
|
2616
|
+
context.edges.push({
|
|
2617
|
+
source: sourceId,
|
|
2618
|
+
target: targetId,
|
|
2619
|
+
kind: "imports",
|
|
2620
|
+
filePath: context.filePath,
|
|
2621
|
+
line: node.startPosition.row + 1
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function processCallExpression6(node, context) {
|
|
2626
|
+
if (context.currentScope.length === 0) return;
|
|
2627
|
+
const functionNode = node.childForFieldName("function");
|
|
2628
|
+
if (!functionNode) return;
|
|
2629
|
+
const calleeName = nodeText5(functionNode, context);
|
|
2630
|
+
const builtins = /* @__PURE__ */ new Set(["printf", "scanf", "malloc", "free", "memcpy", "strlen", "strcmp", "strcpy", "strcat"]);
|
|
2631
|
+
if (builtins.has(calleeName)) return;
|
|
2632
|
+
const callerId = getCurrentSymbolId6(context);
|
|
2633
|
+
if (!callerId) return;
|
|
2634
|
+
const calleeId = resolveSymbol5(calleeName, context);
|
|
2635
|
+
if (!calleeId) return;
|
|
2636
|
+
context.edges.push({
|
|
2637
|
+
source: callerId,
|
|
2638
|
+
target: calleeId,
|
|
2639
|
+
kind: "calls",
|
|
2640
|
+
filePath: context.filePath,
|
|
2641
|
+
line: node.startPosition.row + 1
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
function resolveIncludePath(includePath, currentFile, projectRoot) {
|
|
2645
|
+
const currentFileAbs = join7(projectRoot, currentFile);
|
|
2646
|
+
const currentDir = dirname6(currentFileAbs);
|
|
2647
|
+
const possibleFiles = [
|
|
2648
|
+
join7(currentDir, includePath),
|
|
2649
|
+
join7(projectRoot, includePath)
|
|
2650
|
+
];
|
|
2651
|
+
const resolvedFiles = [];
|
|
2652
|
+
for (const absPath of possibleFiles) {
|
|
2653
|
+
if (existsSync7(absPath)) {
|
|
2654
|
+
const relPath = relative4(projectRoot, absPath);
|
|
2655
|
+
resolvedFiles.push(relPath);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return resolvedFiles;
|
|
2659
|
+
}
|
|
2660
|
+
function hasStorageClass(node, className, context) {
|
|
2661
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2662
|
+
const child = node.child(i);
|
|
2663
|
+
if (child && child.type === "storage_class_specifier") {
|
|
2664
|
+
const text = nodeText5(child, context);
|
|
2665
|
+
if (text === className) {
|
|
2666
|
+
return true;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
let parent = node.parent;
|
|
2671
|
+
while (parent) {
|
|
2672
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
2673
|
+
const child = parent.child(i);
|
|
2674
|
+
if (child && child.type === "storage_class_specifier") {
|
|
2675
|
+
const text = nodeText5(child, context);
|
|
2676
|
+
if (text === className) {
|
|
2677
|
+
return true;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
parent = parent.parent;
|
|
2682
|
+
}
|
|
2683
|
+
return false;
|
|
2684
|
+
}
|
|
2685
|
+
function extractFunctionName(declarator) {
|
|
2686
|
+
if (declarator.type === "identifier") {
|
|
2687
|
+
return declarator;
|
|
2688
|
+
}
|
|
2689
|
+
if (declarator.type === "function_declarator") {
|
|
2690
|
+
const innerDeclarator = declarator.childForFieldName("declarator");
|
|
2691
|
+
if (innerDeclarator) {
|
|
2692
|
+
return extractFunctionName(innerDeclarator);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
if (declarator.type === "pointer_declarator") {
|
|
2696
|
+
const innerDeclarator = declarator.childForFieldName("declarator");
|
|
2697
|
+
if (innerDeclarator) {
|
|
2698
|
+
return extractFunctionName(innerDeclarator);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
for (let i = 0; i < declarator.childCount; i++) {
|
|
2702
|
+
const child = declarator.child(i);
|
|
2703
|
+
if (child && child.type === "identifier") {
|
|
2704
|
+
return child;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
function extractIdentifierFromDeclarator(declarator, context) {
|
|
2710
|
+
if (declarator.type === "identifier") {
|
|
2711
|
+
return nodeText5(declarator, context);
|
|
2712
|
+
}
|
|
2713
|
+
if (declarator.type === "type_identifier") {
|
|
2714
|
+
return nodeText5(declarator, context);
|
|
2715
|
+
}
|
|
2716
|
+
const identifierNode = findChildByType6(declarator, "identifier");
|
|
2717
|
+
if (identifierNode) {
|
|
2718
|
+
return nodeText5(identifierNode, context);
|
|
2719
|
+
}
|
|
2720
|
+
const typeIdNode = findChildByType6(declarator, "type_identifier");
|
|
2721
|
+
if (typeIdNode) {
|
|
2722
|
+
return nodeText5(typeIdNode, context);
|
|
2723
|
+
}
|
|
2724
|
+
for (let i = 0; i < declarator.childCount; i++) {
|
|
2725
|
+
const child = declarator.child(i);
|
|
2726
|
+
if (child) {
|
|
2727
|
+
const name = extractIdentifierFromDeclarator(child, context);
|
|
2728
|
+
if (name) return name;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
function resolveSymbol5(name, context) {
|
|
2734
|
+
const currentFileId = `${context.filePath}::__file__`;
|
|
2735
|
+
const symbol = context.symbols.find(
|
|
2736
|
+
(s) => s.name === name && (s.filePath === context.filePath || s.exported)
|
|
2737
|
+
);
|
|
2738
|
+
if (symbol) {
|
|
2739
|
+
return symbol.id;
|
|
2740
|
+
}
|
|
2741
|
+
for (let i = context.currentScope.length - 1; i >= 0; i--) {
|
|
2742
|
+
const scopedId = `${context.filePath}::${context.currentScope.slice(0, i + 1).join("::")}::${name}`;
|
|
2743
|
+
const scopedSymbol = context.symbols.find((s) => s.id === scopedId);
|
|
2744
|
+
if (scopedSymbol) {
|
|
2745
|
+
return scopedSymbol.id;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
return null;
|
|
2749
|
+
}
|
|
2750
|
+
function findChildByType6(node, type) {
|
|
2751
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2752
|
+
const child = node.child(i);
|
|
2753
|
+
if (child && child.type === type) {
|
|
2754
|
+
return child;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
return null;
|
|
2758
|
+
}
|
|
2759
|
+
function nodeText5(node, context) {
|
|
2760
|
+
return context.sourceCode.slice(node.startIndex, node.endIndex);
|
|
2761
|
+
}
|
|
2762
|
+
function getCurrentSymbolId6(context) {
|
|
2763
|
+
if (context.currentScope.length === 0) return null;
|
|
2764
|
+
return `${context.filePath}::${context.currentScope.join("::")}`;
|
|
2765
|
+
}
|
|
2766
|
+
var cParser = {
|
|
2767
|
+
name: "c",
|
|
2768
|
+
extensions: [".c", ".h"],
|
|
2769
|
+
parseFile: parseCFile
|
|
2770
|
+
};
|
|
2771
|
+
|
|
1975
2772
|
// src/parser/detect.ts
|
|
1976
2773
|
var parsers = [
|
|
1977
2774
|
typescriptParser,
|
|
1978
2775
|
pythonParser,
|
|
1979
2776
|
javascriptParser,
|
|
1980
|
-
goParser
|
|
2777
|
+
goParser,
|
|
2778
|
+
rustParser,
|
|
2779
|
+
cParser
|
|
1981
2780
|
];
|
|
1982
2781
|
function getParserForFile(filePath) {
|
|
1983
2782
|
const ext = extname3(filePath).toLowerCase();
|
|
@@ -2007,7 +2806,7 @@ async function parseProject(projectRoot, options) {
|
|
|
2007
2806
|
let errorFiles = 0;
|
|
2008
2807
|
for (const file of files) {
|
|
2009
2808
|
try {
|
|
2010
|
-
const fullPath =
|
|
2809
|
+
const fullPath = join8(projectRoot, file);
|
|
2011
2810
|
if (options?.exclude) {
|
|
2012
2811
|
const shouldExclude2 = options.exclude.some(
|
|
2013
2812
|
(pattern) => minimatch(file, pattern, { matchBase: true })
|
|
@@ -2033,7 +2832,7 @@ async function parseProject(projectRoot, options) {
|
|
|
2033
2832
|
skippedFiles++;
|
|
2034
2833
|
continue;
|
|
2035
2834
|
}
|
|
2036
|
-
const sourceCode =
|
|
2835
|
+
const sourceCode = readFileSync5(fullPath, "utf-8");
|
|
2037
2836
|
const parsed = parser.parseFile(file, sourceCode, projectRoot);
|
|
2038
2837
|
parsedFiles.push(parsed);
|
|
2039
2838
|
} catch (err) {
|
|
@@ -2089,6 +2888,18 @@ function buildGraph(parsedFiles) {
|
|
|
2089
2888
|
exported: false
|
|
2090
2889
|
});
|
|
2091
2890
|
}
|
|
2891
|
+
if (edge.target.endsWith("::__file__") && !fileNodes.has(edge.target)) {
|
|
2892
|
+
fileNodes.add(edge.target);
|
|
2893
|
+
const filePath = edge.target.replace("::__file__", "");
|
|
2894
|
+
graph.addNode(edge.target, {
|
|
2895
|
+
name: "__file__",
|
|
2896
|
+
kind: "import",
|
|
2897
|
+
filePath,
|
|
2898
|
+
startLine: 1,
|
|
2899
|
+
endLine: 1,
|
|
2900
|
+
exported: false
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2092
2903
|
}
|
|
2093
2904
|
}
|
|
2094
2905
|
for (const file of parsedFiles) {
|
|
@@ -2414,7 +3225,7 @@ function watchProject(projectRoot, callbacks) {
|
|
|
2414
3225
|
const watcher = chokidar.watch(projectRoot, watcherOptions);
|
|
2415
3226
|
console.error("[Watcher] Attaching event listeners...");
|
|
2416
3227
|
watcher.on("change", (absolutePath) => {
|
|
2417
|
-
const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
|
|
3228
|
+
const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".c", ".h"];
|
|
2418
3229
|
if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
|
|
2419
3230
|
if (absolutePath.endsWith("_test.go")) return;
|
|
2420
3231
|
const relativePath = absolutePath.replace(projectRoot + "/", "");
|
|
@@ -2422,7 +3233,7 @@ function watchProject(projectRoot, callbacks) {
|
|
|
2422
3233
|
callbacks.onFileChanged(relativePath);
|
|
2423
3234
|
});
|
|
2424
3235
|
watcher.on("add", (absolutePath) => {
|
|
2425
|
-
const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
|
|
3236
|
+
const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".c", ".h"];
|
|
2426
3237
|
if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
|
|
2427
3238
|
if (absolutePath.endsWith("_test.go")) return;
|
|
2428
3239
|
const relativePath = absolutePath.replace(projectRoot + "/", "");
|
|
@@ -2430,7 +3241,7 @@ function watchProject(projectRoot, callbacks) {
|
|
|
2430
3241
|
callbacks.onFileAdded(relativePath);
|
|
2431
3242
|
});
|
|
2432
3243
|
watcher.on("unlink", (absolutePath) => {
|
|
2433
|
-
const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
|
|
3244
|
+
const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".c", ".h"];
|
|
2434
3245
|
if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
|
|
2435
3246
|
if (absolutePath.endsWith("_test.go")) return;
|
|
2436
3247
|
const relativePath = absolutePath.replace(projectRoot + "/", "");
|
|
@@ -2448,10 +3259,10 @@ function watchProject(projectRoot, callbacks) {
|
|
|
2448
3259
|
for (const dir of dirs) {
|
|
2449
3260
|
const files = watched[dir];
|
|
2450
3261
|
fileCount += files.filter(
|
|
2451
|
-
(f) => f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx") || f.endsWith(".mjs") || f.endsWith(".cjs") || f.endsWith(".py") || f.endsWith(".go") && !f.endsWith("_test.go")
|
|
3262
|
+
(f) => f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx") || f.endsWith(".mjs") || f.endsWith(".cjs") || f.endsWith(".py") || f.endsWith(".go") && !f.endsWith("_test.go") || f.endsWith(".rs") || f.endsWith(".c") || f.endsWith(".h")
|
|
2452
3263
|
).length;
|
|
2453
3264
|
}
|
|
2454
|
-
console.error(`[Watcher] Watching ${fileCount} TypeScript/JavaScript/Python/Go files in ${dirs.length} directories`);
|
|
3265
|
+
console.error(`[Watcher] Watching ${fileCount} TypeScript/JavaScript/Python/Go/Rust/C files in ${dirs.length} directories`);
|
|
2455
3266
|
});
|
|
2456
3267
|
return watcher;
|
|
2457
3268
|
}
|
|
@@ -2460,10 +3271,10 @@ function watchProject(projectRoot, callbacks) {
|
|
|
2460
3271
|
import express from "express";
|
|
2461
3272
|
import open from "open";
|
|
2462
3273
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2463
|
-
import { dirname as
|
|
3274
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
2464
3275
|
import { WebSocketServer } from "ws";
|
|
2465
3276
|
var __filename = fileURLToPath2(import.meta.url);
|
|
2466
|
-
var __dirname2 =
|
|
3277
|
+
var __dirname2 = dirname7(__filename);
|
|
2467
3278
|
var activeServer = null;
|
|
2468
3279
|
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
2469
3280
|
const net = await import("net");
|
|
@@ -2501,7 +3312,7 @@ async function startVizServer(initialVizData, graph, projectRoot, port = 3333, s
|
|
|
2501
3312
|
const availablePort = await findAvailablePort(port);
|
|
2502
3313
|
const app = express();
|
|
2503
3314
|
let vizData = initialVizData;
|
|
2504
|
-
const publicDir =
|
|
3315
|
+
const publicDir = join9(__dirname2, "viz", "public");
|
|
2505
3316
|
app.use(express.static(publicDir));
|
|
2506
3317
|
app.get("/api/graph", (req, res) => {
|
|
2507
3318
|
res.json(vizData);
|
|
@@ -2617,7 +3428,7 @@ function isProjectLoaded(state) {
|
|
|
2617
3428
|
}
|
|
2618
3429
|
|
|
2619
3430
|
// src/graph/updater.ts
|
|
2620
|
-
import { join as
|
|
3431
|
+
import { join as join10 } from "path";
|
|
2621
3432
|
function removeFileFromGraph(graph, filePath) {
|
|
2622
3433
|
const nodesToRemove = [];
|
|
2623
3434
|
graph.forEachNode((node, attrs) => {
|
|
@@ -2661,7 +3472,7 @@ function addFileToGraph(graph, parsedFile) {
|
|
|
2661
3472
|
}
|
|
2662
3473
|
async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
|
|
2663
3474
|
removeFileFromGraph(graph, relativeFilePath);
|
|
2664
|
-
const absolutePath =
|
|
3475
|
+
const absolutePath = join10(projectRoot, relativeFilePath);
|
|
2665
3476
|
try {
|
|
2666
3477
|
const parsedFile = parseTypeScriptFile(absolutePath, relativeFilePath);
|
|
2667
3478
|
addFileToGraph(graph, parsedFile);
|
|
@@ -2671,7 +3482,7 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
|
|
|
2671
3482
|
}
|
|
2672
3483
|
|
|
2673
3484
|
// src/health/metrics.ts
|
|
2674
|
-
import { dirname as
|
|
3485
|
+
import { dirname as dirname8 } from "path";
|
|
2675
3486
|
function scoreToGrade(score) {
|
|
2676
3487
|
if (score >= 90) return "A";
|
|
2677
3488
|
if (score >= 80) return "B";
|
|
@@ -2704,8 +3515,8 @@ function calculateCouplingScore(graph) {
|
|
|
2704
3515
|
totalEdges++;
|
|
2705
3516
|
fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
|
|
2706
3517
|
fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
|
|
2707
|
-
const sourceDir =
|
|
2708
|
-
const targetDir =
|
|
3518
|
+
const sourceDir = dirname8(sourceAttrs.filePath).split("/")[0];
|
|
3519
|
+
const targetDir = dirname8(targetAttrs.filePath).split("/")[0];
|
|
2709
3520
|
if (sourceDir !== targetDir) {
|
|
2710
3521
|
crossDirEdges++;
|
|
2711
3522
|
}
|
|
@@ -2752,8 +3563,8 @@ function calculateCohesionScore(graph) {
|
|
|
2752
3563
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
2753
3564
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
2754
3565
|
if (sourceAttrs.filePath !== targetAttrs.filePath) {
|
|
2755
|
-
const sourceDir =
|
|
2756
|
-
const targetDir =
|
|
3566
|
+
const sourceDir = dirname8(sourceAttrs.filePath);
|
|
3567
|
+
const targetDir = dirname8(targetAttrs.filePath);
|
|
2757
3568
|
if (!dirEdges.has(sourceDir)) {
|
|
2758
3569
|
dirEdges.set(sourceDir, { internal: 0, total: 0 });
|
|
2759
3570
|
}
|
|
@@ -3035,8 +3846,8 @@ function calculateDepthScore(graph) {
|
|
|
3035
3846
|
}
|
|
3036
3847
|
|
|
3037
3848
|
// src/health/index.ts
|
|
3038
|
-
import { readFileSync as
|
|
3039
|
-
import { join as
|
|
3849
|
+
import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
|
|
3850
|
+
import { join as join11, dirname as dirname9 } from "path";
|
|
3040
3851
|
function calculateHealthScore(graph, projectRoot) {
|
|
3041
3852
|
const coupling = calculateCouplingScore(graph);
|
|
3042
3853
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -3135,7 +3946,7 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
3135
3946
|
}
|
|
3136
3947
|
}
|
|
3137
3948
|
function saveHealthHistory(projectRoot, report) {
|
|
3138
|
-
const historyFile =
|
|
3949
|
+
const historyFile = join11(projectRoot, ".depwire", "health-history.json");
|
|
3139
3950
|
const entry = {
|
|
3140
3951
|
timestamp: report.timestamp,
|
|
3141
3952
|
score: report.overall,
|
|
@@ -3147,9 +3958,9 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
3147
3958
|
}))
|
|
3148
3959
|
};
|
|
3149
3960
|
let history = [];
|
|
3150
|
-
if (
|
|
3961
|
+
if (existsSync8(historyFile)) {
|
|
3151
3962
|
try {
|
|
3152
|
-
const content =
|
|
3963
|
+
const content = readFileSync6(historyFile, "utf-8");
|
|
3153
3964
|
history = JSON.parse(content);
|
|
3154
3965
|
} catch {
|
|
3155
3966
|
}
|
|
@@ -3158,16 +3969,16 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
3158
3969
|
if (history.length > 50) {
|
|
3159
3970
|
history = history.slice(-50);
|
|
3160
3971
|
}
|
|
3161
|
-
mkdirSync(
|
|
3972
|
+
mkdirSync(dirname9(historyFile), { recursive: true });
|
|
3162
3973
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
3163
3974
|
}
|
|
3164
3975
|
function loadHealthHistory(projectRoot) {
|
|
3165
|
-
const historyFile =
|
|
3166
|
-
if (!
|
|
3976
|
+
const historyFile = join11(projectRoot, ".depwire", "health-history.json");
|
|
3977
|
+
if (!existsSync8(historyFile)) {
|
|
3167
3978
|
return [];
|
|
3168
3979
|
}
|
|
3169
3980
|
try {
|
|
3170
|
-
const content =
|
|
3981
|
+
const content = readFileSync6(historyFile, "utf-8");
|
|
3171
3982
|
return JSON.parse(content);
|
|
3172
3983
|
} catch {
|
|
3173
3984
|
return [];
|
|
@@ -3473,11 +4284,11 @@ function filterByConfidence(symbols, minConfidence) {
|
|
|
3473
4284
|
}
|
|
3474
4285
|
|
|
3475
4286
|
// src/docs/generator.ts
|
|
3476
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as
|
|
3477
|
-
import { join as
|
|
4287
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync11 } from "fs";
|
|
4288
|
+
import { join as join14 } from "path";
|
|
3478
4289
|
|
|
3479
4290
|
// src/docs/architecture.ts
|
|
3480
|
-
import { dirname as
|
|
4291
|
+
import { dirname as dirname10 } from "path";
|
|
3481
4292
|
|
|
3482
4293
|
// src/docs/templates.ts
|
|
3483
4294
|
function header(text, level = 1) {
|
|
@@ -3628,7 +4439,7 @@ function generateModuleStructure(graph) {
|
|
|
3628
4439
|
function getDirectoryStats(graph) {
|
|
3629
4440
|
const dirMap = /* @__PURE__ */ new Map();
|
|
3630
4441
|
graph.forEachNode((node, attrs) => {
|
|
3631
|
-
const dir =
|
|
4442
|
+
const dir = dirname10(attrs.filePath);
|
|
3632
4443
|
if (dir === ".") return;
|
|
3633
4444
|
if (!dirMap.has(dir)) {
|
|
3634
4445
|
dirMap.set(dir, {
|
|
@@ -3653,7 +4464,7 @@ function getDirectoryStats(graph) {
|
|
|
3653
4464
|
});
|
|
3654
4465
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
3655
4466
|
graph.forEachNode((node, attrs) => {
|
|
3656
|
-
const dir =
|
|
4467
|
+
const dir = dirname10(attrs.filePath);
|
|
3657
4468
|
if (!filesPerDir.has(dir)) {
|
|
3658
4469
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
3659
4470
|
}
|
|
@@ -3668,8 +4479,8 @@ function getDirectoryStats(graph) {
|
|
|
3668
4479
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
3669
4480
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
3670
4481
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
3671
|
-
const sourceDir =
|
|
3672
|
-
const targetDir =
|
|
4482
|
+
const sourceDir = dirname10(sourceAttrs.filePath);
|
|
4483
|
+
const targetDir = dirname10(targetAttrs.filePath);
|
|
3673
4484
|
if (sourceDir !== targetDir) {
|
|
3674
4485
|
if (!dirEdges.has(sourceDir)) {
|
|
3675
4486
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -4650,7 +5461,7 @@ function detectCyclesDetailed(graph) {
|
|
|
4650
5461
|
}
|
|
4651
5462
|
|
|
4652
5463
|
// src/docs/onboarding.ts
|
|
4653
|
-
import { dirname as
|
|
5464
|
+
import { dirname as dirname11 } from "path";
|
|
4654
5465
|
function generateOnboarding(graph, projectRoot, version) {
|
|
4655
5466
|
let output = "";
|
|
4656
5467
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -4709,7 +5520,7 @@ function generateQuickOrientation(graph) {
|
|
|
4709
5520
|
const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
|
|
4710
5521
|
const dirs = /* @__PURE__ */ new Set();
|
|
4711
5522
|
graph.forEachNode((node, attrs) => {
|
|
4712
|
-
const dir =
|
|
5523
|
+
const dir = dirname11(attrs.filePath);
|
|
4713
5524
|
if (dir !== ".") {
|
|
4714
5525
|
const topLevel = dir.split("/")[0];
|
|
4715
5526
|
dirs.add(topLevel);
|
|
@@ -4813,7 +5624,7 @@ function generateModuleMap(graph) {
|
|
|
4813
5624
|
function getDirectoryStats2(graph) {
|
|
4814
5625
|
const dirMap = /* @__PURE__ */ new Map();
|
|
4815
5626
|
graph.forEachNode((node, attrs) => {
|
|
4816
|
-
const dir =
|
|
5627
|
+
const dir = dirname11(attrs.filePath);
|
|
4817
5628
|
if (dir === ".") return;
|
|
4818
5629
|
if (!dirMap.has(dir)) {
|
|
4819
5630
|
dirMap.set(dir, {
|
|
@@ -4828,7 +5639,7 @@ function getDirectoryStats2(graph) {
|
|
|
4828
5639
|
});
|
|
4829
5640
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
4830
5641
|
graph.forEachNode((node, attrs) => {
|
|
4831
|
-
const dir =
|
|
5642
|
+
const dir = dirname11(attrs.filePath);
|
|
4832
5643
|
if (!filesPerDir.has(dir)) {
|
|
4833
5644
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
4834
5645
|
}
|
|
@@ -4843,8 +5654,8 @@ function getDirectoryStats2(graph) {
|
|
|
4843
5654
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
4844
5655
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
4845
5656
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
4846
|
-
const sourceDir =
|
|
4847
|
-
const targetDir =
|
|
5657
|
+
const sourceDir = dirname11(sourceAttrs.filePath);
|
|
5658
|
+
const targetDir = dirname11(targetAttrs.filePath);
|
|
4848
5659
|
if (sourceDir !== targetDir) {
|
|
4849
5660
|
if (!dirEdges.has(sourceDir)) {
|
|
4850
5661
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -4925,7 +5736,7 @@ function detectClusters(graph) {
|
|
|
4925
5736
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
4926
5737
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
4927
5738
|
graph.forEachNode((node, attrs) => {
|
|
4928
|
-
const dir =
|
|
5739
|
+
const dir = dirname11(attrs.filePath);
|
|
4929
5740
|
if (!dirFiles.has(dir)) {
|
|
4930
5741
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
4931
5742
|
}
|
|
@@ -4979,8 +5790,8 @@ function inferClusterName(files) {
|
|
|
4979
5790
|
if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
|
|
4980
5791
|
return capitalizeFirst2(sortedWords[0][0]);
|
|
4981
5792
|
}
|
|
4982
|
-
const commonDir =
|
|
4983
|
-
if (files.every((f) =>
|
|
5793
|
+
const commonDir = dirname11(files[0]);
|
|
5794
|
+
if (files.every((f) => dirname11(f) === commonDir)) {
|
|
4984
5795
|
return capitalizeFirst2(commonDir.split("/").pop() || "Core");
|
|
4985
5796
|
}
|
|
4986
5797
|
return "Core";
|
|
@@ -5038,7 +5849,7 @@ function generateDepwireUsage(projectRoot) {
|
|
|
5038
5849
|
}
|
|
5039
5850
|
|
|
5040
5851
|
// src/docs/files.ts
|
|
5041
|
-
import { dirname as
|
|
5852
|
+
import { dirname as dirname12, basename as basename3 } from "path";
|
|
5042
5853
|
function generateFiles(graph, projectRoot, version) {
|
|
5043
5854
|
let output = "";
|
|
5044
5855
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -5142,7 +5953,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
5142
5953
|
const fileStats = getFileStats2(graph);
|
|
5143
5954
|
const dirMap = /* @__PURE__ */ new Map();
|
|
5144
5955
|
for (const file of fileStats) {
|
|
5145
|
-
const dir =
|
|
5956
|
+
const dir = dirname12(file.filePath);
|
|
5146
5957
|
const topDir = dir === "." ? "." : dir.split("/")[0];
|
|
5147
5958
|
if (!dirMap.has(topDir)) {
|
|
5148
5959
|
dirMap.set(topDir, {
|
|
@@ -5785,7 +6596,7 @@ function generateRecommendations(graph) {
|
|
|
5785
6596
|
}
|
|
5786
6597
|
|
|
5787
6598
|
// src/docs/tests.ts
|
|
5788
|
-
import { basename as basename4, dirname as
|
|
6599
|
+
import { basename as basename4, dirname as dirname13 } from "path";
|
|
5789
6600
|
function generateTests(graph, projectRoot, version) {
|
|
5790
6601
|
let output = "";
|
|
5791
6602
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -5814,7 +6625,7 @@ function getFileCount8(graph) {
|
|
|
5814
6625
|
}
|
|
5815
6626
|
function isTestFile3(filePath) {
|
|
5816
6627
|
const fileName = basename4(filePath).toLowerCase();
|
|
5817
|
-
const dirPath =
|
|
6628
|
+
const dirPath = dirname13(filePath).toLowerCase();
|
|
5818
6629
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
5819
6630
|
return true;
|
|
5820
6631
|
}
|
|
@@ -5873,12 +6684,12 @@ function generateTestFileInventory(graph) {
|
|
|
5873
6684
|
}
|
|
5874
6685
|
function matchTestToSource(testFile) {
|
|
5875
6686
|
const testFileName = basename4(testFile);
|
|
5876
|
-
const testDir =
|
|
6687
|
+
const testDir = dirname13(testFile);
|
|
5877
6688
|
let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
|
|
5878
6689
|
const possiblePaths = [];
|
|
5879
6690
|
possiblePaths.push(testDir + "/" + sourceFileName);
|
|
5880
6691
|
if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
|
|
5881
|
-
const parentDir =
|
|
6692
|
+
const parentDir = dirname13(testDir);
|
|
5882
6693
|
possiblePaths.push(parentDir + "/" + sourceFileName);
|
|
5883
6694
|
}
|
|
5884
6695
|
if (testDir.includes("test")) {
|
|
@@ -6075,7 +6886,7 @@ function generateTestStatistics(graph) {
|
|
|
6075
6886
|
`;
|
|
6076
6887
|
const dirTestCoverage = /* @__PURE__ */ new Map();
|
|
6077
6888
|
for (const sourceFile of sourceFiles) {
|
|
6078
|
-
const dir =
|
|
6889
|
+
const dir = dirname13(sourceFile).split("/")[0];
|
|
6079
6890
|
if (!dirTestCoverage.has(dir)) {
|
|
6080
6891
|
dirTestCoverage.set(dir, { total: 0, tested: 0 });
|
|
6081
6892
|
}
|
|
@@ -6098,7 +6909,7 @@ function generateTestStatistics(graph) {
|
|
|
6098
6909
|
}
|
|
6099
6910
|
|
|
6100
6911
|
// src/docs/history.ts
|
|
6101
|
-
import { dirname as
|
|
6912
|
+
import { dirname as dirname14 } from "path";
|
|
6102
6913
|
import { execSync } from "child_process";
|
|
6103
6914
|
function generateHistory(graph, projectRoot, version) {
|
|
6104
6915
|
let output = "";
|
|
@@ -6379,7 +7190,7 @@ function generateFeatureClusters(graph) {
|
|
|
6379
7190
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
6380
7191
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
6381
7192
|
graph.forEachNode((node, attrs) => {
|
|
6382
|
-
const dir =
|
|
7193
|
+
const dir = dirname14(attrs.filePath);
|
|
6383
7194
|
if (!dirFiles.has(dir)) {
|
|
6384
7195
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
6385
7196
|
}
|
|
@@ -6461,7 +7272,7 @@ function capitalizeFirst3(str) {
|
|
|
6461
7272
|
}
|
|
6462
7273
|
|
|
6463
7274
|
// src/docs/current.ts
|
|
6464
|
-
import { dirname as
|
|
7275
|
+
import { dirname as dirname15 } from "path";
|
|
6465
7276
|
function generateCurrent(graph, projectRoot, version) {
|
|
6466
7277
|
let output = "";
|
|
6467
7278
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -6599,7 +7410,7 @@ function generateCompleteFileIndex(graph) {
|
|
|
6599
7410
|
fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
6600
7411
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
6601
7412
|
for (const info of fileInfos) {
|
|
6602
|
-
const dir =
|
|
7413
|
+
const dir = dirname15(info.filePath);
|
|
6603
7414
|
const topDir = dir === "." ? "root" : dir.split("/")[0];
|
|
6604
7415
|
if (!dirGroups.has(topDir)) {
|
|
6605
7416
|
dirGroups.set(topDir, []);
|
|
@@ -6810,8 +7621,8 @@ function getTopLevelDir2(filePath) {
|
|
|
6810
7621
|
}
|
|
6811
7622
|
|
|
6812
7623
|
// src/docs/status.ts
|
|
6813
|
-
import { readFileSync as
|
|
6814
|
-
import { join as
|
|
7624
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
7625
|
+
import { join as join12 } from "path";
|
|
6815
7626
|
function generateStatus(graph, projectRoot, version) {
|
|
6816
7627
|
let output = "";
|
|
6817
7628
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -6844,12 +7655,12 @@ function getFileCount11(graph) {
|
|
|
6844
7655
|
}
|
|
6845
7656
|
function extractComments(projectRoot, filePath) {
|
|
6846
7657
|
const comments = [];
|
|
6847
|
-
const fullPath =
|
|
6848
|
-
if (!
|
|
7658
|
+
const fullPath = join12(projectRoot, filePath);
|
|
7659
|
+
if (!existsSync9(fullPath)) {
|
|
6849
7660
|
return comments;
|
|
6850
7661
|
}
|
|
6851
7662
|
try {
|
|
6852
|
-
const content =
|
|
7663
|
+
const content = readFileSync7(fullPath, "utf-8");
|
|
6853
7664
|
const lines = content.split("\n");
|
|
6854
7665
|
const patterns = [
|
|
6855
7666
|
{ type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
|
|
@@ -7411,15 +8222,15 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
7411
8222
|
}
|
|
7412
8223
|
|
|
7413
8224
|
// src/docs/metadata.ts
|
|
7414
|
-
import { existsSync as
|
|
7415
|
-
import { join as
|
|
8225
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
8226
|
+
import { join as join13 } from "path";
|
|
7416
8227
|
function loadMetadata(outputDir) {
|
|
7417
|
-
const metadataPath =
|
|
7418
|
-
if (!
|
|
8228
|
+
const metadataPath = join13(outputDir, "metadata.json");
|
|
8229
|
+
if (!existsSync10(metadataPath)) {
|
|
7419
8230
|
return null;
|
|
7420
8231
|
}
|
|
7421
8232
|
try {
|
|
7422
|
-
const content =
|
|
8233
|
+
const content = readFileSync8(metadataPath, "utf-8");
|
|
7423
8234
|
return JSON.parse(content);
|
|
7424
8235
|
} catch (err) {
|
|
7425
8236
|
console.error("Failed to load metadata:", err);
|
|
@@ -7427,7 +8238,7 @@ function loadMetadata(outputDir) {
|
|
|
7427
8238
|
}
|
|
7428
8239
|
}
|
|
7429
8240
|
function saveMetadata(outputDir, metadata) {
|
|
7430
|
-
const metadataPath =
|
|
8241
|
+
const metadataPath = join13(outputDir, "metadata.json");
|
|
7431
8242
|
writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
7432
8243
|
}
|
|
7433
8244
|
function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
|
|
@@ -7470,7 +8281,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7470
8281
|
const generated = [];
|
|
7471
8282
|
const errors = [];
|
|
7472
8283
|
try {
|
|
7473
|
-
if (!
|
|
8284
|
+
if (!existsSync11(options.outputDir)) {
|
|
7474
8285
|
mkdirSync2(options.outputDir, { recursive: true });
|
|
7475
8286
|
if (options.verbose) {
|
|
7476
8287
|
console.log(`Created output directory: ${options.outputDir}`);
|
|
@@ -7509,7 +8320,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7509
8320
|
try {
|
|
7510
8321
|
if (options.verbose) console.log("Generating ARCHITECTURE.md...");
|
|
7511
8322
|
const content = generateArchitecture(graph, projectRoot, version, parseTime);
|
|
7512
|
-
const filePath =
|
|
8323
|
+
const filePath = join14(options.outputDir, "ARCHITECTURE.md");
|
|
7513
8324
|
writeFileSync3(filePath, content, "utf-8");
|
|
7514
8325
|
generated.push("ARCHITECTURE.md");
|
|
7515
8326
|
} catch (err) {
|
|
@@ -7520,7 +8331,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7520
8331
|
try {
|
|
7521
8332
|
if (options.verbose) console.log("Generating CONVENTIONS.md...");
|
|
7522
8333
|
const content = generateConventions(graph, projectRoot, version);
|
|
7523
|
-
const filePath =
|
|
8334
|
+
const filePath = join14(options.outputDir, "CONVENTIONS.md");
|
|
7524
8335
|
writeFileSync3(filePath, content, "utf-8");
|
|
7525
8336
|
generated.push("CONVENTIONS.md");
|
|
7526
8337
|
} catch (err) {
|
|
@@ -7531,7 +8342,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7531
8342
|
try {
|
|
7532
8343
|
if (options.verbose) console.log("Generating DEPENDENCIES.md...");
|
|
7533
8344
|
const content = generateDependencies(graph, projectRoot, version);
|
|
7534
|
-
const filePath =
|
|
8345
|
+
const filePath = join14(options.outputDir, "DEPENDENCIES.md");
|
|
7535
8346
|
writeFileSync3(filePath, content, "utf-8");
|
|
7536
8347
|
generated.push("DEPENDENCIES.md");
|
|
7537
8348
|
} catch (err) {
|
|
@@ -7542,7 +8353,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7542
8353
|
try {
|
|
7543
8354
|
if (options.verbose) console.log("Generating ONBOARDING.md...");
|
|
7544
8355
|
const content = generateOnboarding(graph, projectRoot, version);
|
|
7545
|
-
const filePath =
|
|
8356
|
+
const filePath = join14(options.outputDir, "ONBOARDING.md");
|
|
7546
8357
|
writeFileSync3(filePath, content, "utf-8");
|
|
7547
8358
|
generated.push("ONBOARDING.md");
|
|
7548
8359
|
} catch (err) {
|
|
@@ -7553,7 +8364,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7553
8364
|
try {
|
|
7554
8365
|
if (options.verbose) console.log("Generating FILES.md...");
|
|
7555
8366
|
const content = generateFiles(graph, projectRoot, version);
|
|
7556
|
-
const filePath =
|
|
8367
|
+
const filePath = join14(options.outputDir, "FILES.md");
|
|
7557
8368
|
writeFileSync3(filePath, content, "utf-8");
|
|
7558
8369
|
generated.push("FILES.md");
|
|
7559
8370
|
} catch (err) {
|
|
@@ -7564,7 +8375,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7564
8375
|
try {
|
|
7565
8376
|
if (options.verbose) console.log("Generating API_SURFACE.md...");
|
|
7566
8377
|
const content = generateApiSurface(graph, projectRoot, version);
|
|
7567
|
-
const filePath =
|
|
8378
|
+
const filePath = join14(options.outputDir, "API_SURFACE.md");
|
|
7568
8379
|
writeFileSync3(filePath, content, "utf-8");
|
|
7569
8380
|
generated.push("API_SURFACE.md");
|
|
7570
8381
|
} catch (err) {
|
|
@@ -7575,7 +8386,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7575
8386
|
try {
|
|
7576
8387
|
if (options.verbose) console.log("Generating ERRORS.md...");
|
|
7577
8388
|
const content = generateErrors(graph, projectRoot, version);
|
|
7578
|
-
const filePath =
|
|
8389
|
+
const filePath = join14(options.outputDir, "ERRORS.md");
|
|
7579
8390
|
writeFileSync3(filePath, content, "utf-8");
|
|
7580
8391
|
generated.push("ERRORS.md");
|
|
7581
8392
|
} catch (err) {
|
|
@@ -7586,7 +8397,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7586
8397
|
try {
|
|
7587
8398
|
if (options.verbose) console.log("Generating TESTS.md...");
|
|
7588
8399
|
const content = generateTests(graph, projectRoot, version);
|
|
7589
|
-
const filePath =
|
|
8400
|
+
const filePath = join14(options.outputDir, "TESTS.md");
|
|
7590
8401
|
writeFileSync3(filePath, content, "utf-8");
|
|
7591
8402
|
generated.push("TESTS.md");
|
|
7592
8403
|
} catch (err) {
|
|
@@ -7597,7 +8408,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7597
8408
|
try {
|
|
7598
8409
|
if (options.verbose) console.log("Generating HISTORY.md...");
|
|
7599
8410
|
const content = generateHistory(graph, projectRoot, version);
|
|
7600
|
-
const filePath =
|
|
8411
|
+
const filePath = join14(options.outputDir, "HISTORY.md");
|
|
7601
8412
|
writeFileSync3(filePath, content, "utf-8");
|
|
7602
8413
|
generated.push("HISTORY.md");
|
|
7603
8414
|
} catch (err) {
|
|
@@ -7608,7 +8419,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7608
8419
|
try {
|
|
7609
8420
|
if (options.verbose) console.log("Generating CURRENT.md...");
|
|
7610
8421
|
const content = generateCurrent(graph, projectRoot, version);
|
|
7611
|
-
const filePath =
|
|
8422
|
+
const filePath = join14(options.outputDir, "CURRENT.md");
|
|
7612
8423
|
writeFileSync3(filePath, content, "utf-8");
|
|
7613
8424
|
generated.push("CURRENT.md");
|
|
7614
8425
|
} catch (err) {
|
|
@@ -7619,7 +8430,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7619
8430
|
try {
|
|
7620
8431
|
if (options.verbose) console.log("Generating STATUS.md...");
|
|
7621
8432
|
const content = generateStatus(graph, projectRoot, version);
|
|
7622
|
-
const filePath =
|
|
8433
|
+
const filePath = join14(options.outputDir, "STATUS.md");
|
|
7623
8434
|
writeFileSync3(filePath, content, "utf-8");
|
|
7624
8435
|
generated.push("STATUS.md");
|
|
7625
8436
|
} catch (err) {
|
|
@@ -7630,7 +8441,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7630
8441
|
try {
|
|
7631
8442
|
if (options.verbose) console.log("Generating HEALTH.md...");
|
|
7632
8443
|
const content = generateHealth(graph, projectRoot, version);
|
|
7633
|
-
const filePath =
|
|
8444
|
+
const filePath = join14(options.outputDir, "HEALTH.md");
|
|
7634
8445
|
writeFileSync3(filePath, content, "utf-8");
|
|
7635
8446
|
generated.push("HEALTH.md");
|
|
7636
8447
|
} catch (err) {
|
|
@@ -7641,7 +8452,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7641
8452
|
try {
|
|
7642
8453
|
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
7643
8454
|
const content = generateDeadCode(graph, projectRoot, version);
|
|
7644
|
-
const filePath =
|
|
8455
|
+
const filePath = join14(options.outputDir, "DEAD_CODE.md");
|
|
7645
8456
|
writeFileSync3(filePath, content, "utf-8");
|
|
7646
8457
|
generated.push("DEAD_CODE.md");
|
|
7647
8458
|
} catch (err) {
|
|
@@ -7689,13 +8500,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7689
8500
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7690
8501
|
|
|
7691
8502
|
// src/mcp/tools.ts
|
|
7692
|
-
import { dirname as
|
|
7693
|
-
import { existsSync as
|
|
8503
|
+
import { dirname as dirname16, join as join17 } from "path";
|
|
8504
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
|
|
7694
8505
|
|
|
7695
8506
|
// src/mcp/connect.ts
|
|
7696
8507
|
import simpleGit from "simple-git";
|
|
7697
|
-
import { existsSync as
|
|
7698
|
-
import { join as
|
|
8508
|
+
import { existsSync as existsSync12 } from "fs";
|
|
8509
|
+
import { join as join15, basename as basename5, resolve as resolve2 } from "path";
|
|
7699
8510
|
import { tmpdir, homedir } from "os";
|
|
7700
8511
|
function validateProjectPath(source) {
|
|
7701
8512
|
const resolved = resolve2(source);
|
|
@@ -7708,11 +8519,11 @@ function validateProjectPath(source) {
|
|
|
7708
8519
|
"/boot",
|
|
7709
8520
|
"/proc",
|
|
7710
8521
|
"/sys",
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
8522
|
+
join15(homedir(), ".ssh"),
|
|
8523
|
+
join15(homedir(), ".gnupg"),
|
|
8524
|
+
join15(homedir(), ".aws"),
|
|
8525
|
+
join15(homedir(), ".config"),
|
|
8526
|
+
join15(homedir(), ".env")
|
|
7716
8527
|
];
|
|
7717
8528
|
for (const blocked of blockedPaths) {
|
|
7718
8529
|
if (resolved.startsWith(blocked)) {
|
|
@@ -7735,11 +8546,11 @@ async function connectToRepo(source, subdirectory, state) {
|
|
|
7735
8546
|
};
|
|
7736
8547
|
}
|
|
7737
8548
|
projectName = match[1];
|
|
7738
|
-
const reposDir =
|
|
7739
|
-
const cloneDir =
|
|
8549
|
+
const reposDir = join15(tmpdir(), "depwire-repos");
|
|
8550
|
+
const cloneDir = join15(reposDir, projectName);
|
|
7740
8551
|
console.error(`Connecting to GitHub repo: ${source}`);
|
|
7741
8552
|
const git = simpleGit();
|
|
7742
|
-
if (
|
|
8553
|
+
if (existsSync12(cloneDir)) {
|
|
7743
8554
|
console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
|
|
7744
8555
|
try {
|
|
7745
8556
|
await git.cwd(cloneDir).pull();
|
|
@@ -7757,7 +8568,7 @@ async function connectToRepo(source, subdirectory, state) {
|
|
|
7757
8568
|
};
|
|
7758
8569
|
}
|
|
7759
8570
|
}
|
|
7760
|
-
projectRoot = subdirectory ?
|
|
8571
|
+
projectRoot = subdirectory ? join15(cloneDir, subdirectory) : cloneDir;
|
|
7761
8572
|
} else {
|
|
7762
8573
|
const validation2 = validateProjectPath(source);
|
|
7763
8574
|
if (!validation2.valid) {
|
|
@@ -7766,13 +8577,13 @@ async function connectToRepo(source, subdirectory, state) {
|
|
|
7766
8577
|
message: validation2.error
|
|
7767
8578
|
};
|
|
7768
8579
|
}
|
|
7769
|
-
if (!
|
|
8580
|
+
if (!existsSync12(source)) {
|
|
7770
8581
|
return {
|
|
7771
8582
|
error: "Directory not found",
|
|
7772
8583
|
message: `Directory does not exist: ${source}`
|
|
7773
8584
|
};
|
|
7774
8585
|
}
|
|
7775
|
-
projectRoot = subdirectory ?
|
|
8586
|
+
projectRoot = subdirectory ? join15(source, subdirectory) : source;
|
|
7776
8587
|
projectName = basename5(projectRoot);
|
|
7777
8588
|
}
|
|
7778
8589
|
const validation = validateProjectPath(projectRoot);
|
|
@@ -7782,7 +8593,7 @@ async function connectToRepo(source, subdirectory, state) {
|
|
|
7782
8593
|
message: validation.error
|
|
7783
8594
|
};
|
|
7784
8595
|
}
|
|
7785
|
-
if (!
|
|
8596
|
+
if (!existsSync12(projectRoot)) {
|
|
7786
8597
|
return {
|
|
7787
8598
|
error: "Project root not found",
|
|
7788
8599
|
message: `Directory does not exist: ${projectRoot}`
|
|
@@ -8063,24 +8874,24 @@ function getWeekNumber(date) {
|
|
|
8063
8874
|
}
|
|
8064
8875
|
|
|
8065
8876
|
// src/temporal/snapshots.ts
|
|
8066
|
-
import { writeFileSync as writeFileSync4, readFileSync as
|
|
8067
|
-
import { join as
|
|
8877
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync9, mkdirSync as mkdirSync3, existsSync as existsSync13, readdirSync as readdirSync5 } from "fs";
|
|
8878
|
+
import { join as join16 } from "path";
|
|
8068
8879
|
function saveSnapshot(snapshot, outputDir) {
|
|
8069
|
-
if (!
|
|
8880
|
+
if (!existsSync13(outputDir)) {
|
|
8070
8881
|
mkdirSync3(outputDir, { recursive: true });
|
|
8071
8882
|
}
|
|
8072
8883
|
const filename = `${snapshot.commitHash.substring(0, 8)}.json`;
|
|
8073
|
-
const filepath =
|
|
8884
|
+
const filepath = join16(outputDir, filename);
|
|
8074
8885
|
writeFileSync4(filepath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
8075
8886
|
}
|
|
8076
8887
|
function loadSnapshot(commitHash, outputDir) {
|
|
8077
8888
|
const shortHash = commitHash.substring(0, 8);
|
|
8078
|
-
const filepath =
|
|
8079
|
-
if (!
|
|
8889
|
+
const filepath = join16(outputDir, `${shortHash}.json`);
|
|
8890
|
+
if (!existsSync13(filepath)) {
|
|
8080
8891
|
return null;
|
|
8081
8892
|
}
|
|
8082
8893
|
try {
|
|
8083
|
-
const content =
|
|
8894
|
+
const content = readFileSync9(filepath, "utf-8");
|
|
8084
8895
|
return JSON.parse(content);
|
|
8085
8896
|
} catch {
|
|
8086
8897
|
return null;
|
|
@@ -8767,7 +9578,7 @@ function handleGetArchitectureSummary(graph) {
|
|
|
8767
9578
|
const dirMap = /* @__PURE__ */ new Map();
|
|
8768
9579
|
const languageBreakdown = {};
|
|
8769
9580
|
fileSummary.forEach((f) => {
|
|
8770
|
-
const dir = f.filePath.includes("/") ?
|
|
9581
|
+
const dir = f.filePath.includes("/") ? dirname16(f.filePath) : ".";
|
|
8771
9582
|
if (!dirMap.has(dir)) {
|
|
8772
9583
|
dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
|
|
8773
9584
|
}
|
|
@@ -8854,8 +9665,8 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
|
|
|
8854
9665
|
};
|
|
8855
9666
|
}
|
|
8856
9667
|
async function handleGetProjectDocs(docType, state) {
|
|
8857
|
-
const docsDir =
|
|
8858
|
-
if (!
|
|
9668
|
+
const docsDir = join17(state.projectRoot, ".depwire");
|
|
9669
|
+
if (!existsSync14(docsDir)) {
|
|
8859
9670
|
const errorMessage = `Project documentation has not been generated yet.
|
|
8860
9671
|
|
|
8861
9672
|
Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
|
|
@@ -8885,12 +9696,12 @@ Available document types:
|
|
|
8885
9696
|
missing.push(doc);
|
|
8886
9697
|
continue;
|
|
8887
9698
|
}
|
|
8888
|
-
const filePath =
|
|
8889
|
-
if (!
|
|
9699
|
+
const filePath = join17(docsDir, metadata.documents[doc].file);
|
|
9700
|
+
if (!existsSync14(filePath)) {
|
|
8890
9701
|
missing.push(doc);
|
|
8891
9702
|
continue;
|
|
8892
9703
|
}
|
|
8893
|
-
const content =
|
|
9704
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
8894
9705
|
if (docsToReturn.length > 1) {
|
|
8895
9706
|
output += `
|
|
8896
9707
|
|
|
@@ -8915,16 +9726,16 @@ Available document types:
|
|
|
8915
9726
|
}
|
|
8916
9727
|
async function handleUpdateProjectDocs(docType, state) {
|
|
8917
9728
|
const startTime = Date.now();
|
|
8918
|
-
const docsDir =
|
|
9729
|
+
const docsDir = join17(state.projectRoot, ".depwire");
|
|
8919
9730
|
console.error("Regenerating project documentation...");
|
|
8920
9731
|
const parsedFiles = await parseProject(state.projectRoot);
|
|
8921
9732
|
const graph = buildGraph(parsedFiles);
|
|
8922
9733
|
const parseTime = (Date.now() - startTime) / 1e3;
|
|
8923
9734
|
state.graph = graph;
|
|
8924
|
-
const packageJsonPath =
|
|
8925
|
-
const packageJson = JSON.parse(
|
|
9735
|
+
const packageJsonPath = join17(__dirname, "../../package.json");
|
|
9736
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
8926
9737
|
const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
|
|
8927
|
-
const docsExist =
|
|
9738
|
+
const docsExist = existsSync14(docsDir);
|
|
8928
9739
|
const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
|
|
8929
9740
|
outputDir: docsDir,
|
|
8930
9741
|
format: "markdown",
|
|
@@ -8983,7 +9794,7 @@ async function handleGetTemporalGraph(state, commits, strategy) {
|
|
|
8983
9794
|
}
|
|
8984
9795
|
const sampledCommits = sampleCommits(allCommits, commits, strategy);
|
|
8985
9796
|
const snapshots = [];
|
|
8986
|
-
const outputDir =
|
|
9797
|
+
const outputDir = join17(projectRoot, ".depwire", "temporal");
|
|
8987
9798
|
for (const commit of sampledCommits) {
|
|
8988
9799
|
const existing = loadSnapshot(commit.hash, outputDir);
|
|
8989
9800
|
if (existing) {
|