depgraph-core 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -105
- package/depgraph-output.json +1273 -233
- package/depgraph.js +442 -55
- package/docs/README.md +56 -0
- package/docs/architecture.md +89 -0
- package/docs/data-types.md +218 -0
- package/docs/language-registry.md +205 -0
- package/docs/stage-collector.md +109 -0
- package/docs/stage-graph.md +157 -0
- package/docs/stage-impact.md +154 -0
- package/docs/stage-metrics.md +113 -0
- package/docs/stage-output.md +144 -0
- package/docs/stage-parser.md +144 -0
- package/package.json +5 -1
package/depgraph.js
CHANGED
|
@@ -68,12 +68,12 @@ var require_constants = __commonJS({
|
|
|
68
68
|
exports2.MAX_FILE_SIZE = 3e5;
|
|
69
69
|
exports2.MAX_BFS_DEPTH = 10;
|
|
70
70
|
exports2.COMPLEXITY_THRESHOLDS = {
|
|
71
|
+
/** Complexity score is "low" if there are 3 or fewer branch points. */
|
|
71
72
|
low: 3,
|
|
72
|
-
|
|
73
|
+
/** Complexity score is "medium" if there are between 4 and 8 branch points. */
|
|
73
74
|
medium: 8,
|
|
74
|
-
|
|
75
|
+
/** Complexity score is "high" if there are 9 or more branch points. */
|
|
75
76
|
high: Infinity
|
|
76
|
-
// 9+ → high complexity
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
});
|
|
@@ -83,6 +83,7 @@ var require_javascript = __commonJS({
|
|
|
83
83
|
"dist/languages/javascript.js"(exports2) {
|
|
84
84
|
"use strict";
|
|
85
85
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
86
|
+
exports2.jsEntityPatterns = void 0;
|
|
86
87
|
var registry_1 = require_registry();
|
|
87
88
|
var constants_1 = require_constants();
|
|
88
89
|
function estimateComplexity(code, name) {
|
|
@@ -98,52 +99,51 @@ var require_javascript = __commonJS({
|
|
|
98
99
|
return "medium";
|
|
99
100
|
return "high";
|
|
100
101
|
}
|
|
102
|
+
exports2.jsEntityPatterns = [
|
|
103
|
+
// React components (PascalCase arrow functions)
|
|
104
|
+
{
|
|
105
|
+
regex: /^(?:export\s+)?const\s+([A-Z]\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/gm,
|
|
106
|
+
type: "component"
|
|
107
|
+
},
|
|
108
|
+
// React hooks (camelCase starting with "use")
|
|
109
|
+
{
|
|
110
|
+
regex: /^(?:export\s+)?(?:const\s+)?(use[A-Z]\w+)\s*=/gm,
|
|
111
|
+
type: "hook"
|
|
112
|
+
},
|
|
113
|
+
// regular functions
|
|
114
|
+
{
|
|
115
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
|
|
116
|
+
type: "function"
|
|
117
|
+
},
|
|
118
|
+
// arrow functions assigned to const
|
|
119
|
+
{
|
|
120
|
+
regex: /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
121
|
+
type: "function"
|
|
122
|
+
},
|
|
123
|
+
// classes
|
|
124
|
+
{
|
|
125
|
+
regex: /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm,
|
|
126
|
+
type: "class"
|
|
127
|
+
},
|
|
128
|
+
// TypeScript interfaces
|
|
129
|
+
{
|
|
130
|
+
regex: /^(?:export\s+)?interface\s+(\w+)/gm,
|
|
131
|
+
type: "interface"
|
|
132
|
+
},
|
|
133
|
+
// TypeScript types
|
|
134
|
+
{
|
|
135
|
+
regex: /^(?:export\s+)?type\s+(\w+)\s*=/gm,
|
|
136
|
+
type: "type"
|
|
137
|
+
},
|
|
138
|
+
// Express routes (capture group 1 = method, group 2 = path — skipped in gitdiff context matching)
|
|
139
|
+
{
|
|
140
|
+
regex: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/gm,
|
|
141
|
+
type: "api"
|
|
142
|
+
}
|
|
143
|
+
];
|
|
101
144
|
function extractEntities(code, filePath) {
|
|
102
145
|
const entities = [];
|
|
103
|
-
const
|
|
104
|
-
const patterns = [
|
|
105
|
-
// React components (PascalCase arrow functions)
|
|
106
|
-
{
|
|
107
|
-
regex: /^(?:export\s+)?const\s+([A-Z]\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/gm,
|
|
108
|
-
type: "component"
|
|
109
|
-
},
|
|
110
|
-
// React hooks (camelCase starting with "use")
|
|
111
|
-
{
|
|
112
|
-
regex: /^(?:export\s+)?(?:const\s+)?(use[A-Z]\w+)\s*=/gm,
|
|
113
|
-
type: "hook"
|
|
114
|
-
},
|
|
115
|
-
// regular functions
|
|
116
|
-
{
|
|
117
|
-
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
|
|
118
|
-
type: "function"
|
|
119
|
-
},
|
|
120
|
-
// arrow functions assigned to const
|
|
121
|
-
{
|
|
122
|
-
regex: /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
123
|
-
type: "function"
|
|
124
|
-
},
|
|
125
|
-
// classes
|
|
126
|
-
{
|
|
127
|
-
regex: /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm,
|
|
128
|
-
type: "class"
|
|
129
|
-
},
|
|
130
|
-
// TypeScript interfaces
|
|
131
|
-
{
|
|
132
|
-
regex: /^(?:export\s+)?interface\s+(\w+)/gm,
|
|
133
|
-
type: "interface"
|
|
134
|
-
},
|
|
135
|
-
// TypeScript types
|
|
136
|
-
{
|
|
137
|
-
regex: /^(?:export\s+)?type\s+(\w+)\s*=/gm,
|
|
138
|
-
type: "type"
|
|
139
|
-
},
|
|
140
|
-
// Express routes
|
|
141
|
-
{
|
|
142
|
-
regex: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/gm,
|
|
143
|
-
type: "api"
|
|
144
|
-
}
|
|
145
|
-
];
|
|
146
|
-
for (const { regex, type } of patterns) {
|
|
146
|
+
for (const { regex, type } of exports2.jsEntityPatterns) {
|
|
147
147
|
let match;
|
|
148
148
|
regex.lastIndex = 0;
|
|
149
149
|
while ((match = regex.exec(code)) !== null) {
|
|
@@ -222,12 +222,193 @@ var require_javascript = __commonJS({
|
|
|
222
222
|
extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"],
|
|
223
223
|
extractEntities,
|
|
224
224
|
extractImports,
|
|
225
|
-
extractExports
|
|
225
|
+
extractExports,
|
|
226
|
+
entityPatterns: exports2.jsEntityPatterns
|
|
226
227
|
};
|
|
227
228
|
(0, registry_1.registerParser)(JavaScriptParser);
|
|
228
229
|
}
|
|
229
230
|
});
|
|
230
231
|
|
|
232
|
+
// dist/languages/python.js
|
|
233
|
+
var require_python = __commonJS({
|
|
234
|
+
"dist/languages/python.js"(exports2) {
|
|
235
|
+
"use strict";
|
|
236
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
237
|
+
exports2.pyEntityPatterns = void 0;
|
|
238
|
+
var registry_1 = require_registry();
|
|
239
|
+
var constants_1 = require_constants();
|
|
240
|
+
function estimateComplexity(code, name) {
|
|
241
|
+
const lines = code.split("\n");
|
|
242
|
+
const defLine = lines.findIndex((l) => l.match(new RegExp(`def\\s+${name}\\s*\\(`)));
|
|
243
|
+
if (defLine === -1)
|
|
244
|
+
return "low";
|
|
245
|
+
const bodyLines = [];
|
|
246
|
+
for (let i = defLine + 1; i < lines.length; i++) {
|
|
247
|
+
const line = lines[i];
|
|
248
|
+
if (line.trim() === "")
|
|
249
|
+
continue;
|
|
250
|
+
if (!line.match(/^\s+/))
|
|
251
|
+
break;
|
|
252
|
+
bodyLines.push(line);
|
|
253
|
+
}
|
|
254
|
+
const body = bodyLines.join("\n");
|
|
255
|
+
const branches = (body.match(/\b(if|elif|else|for|while|except|and|or)\b/g) || []).length;
|
|
256
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
257
|
+
return "low";
|
|
258
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
259
|
+
return "medium";
|
|
260
|
+
return "high";
|
|
261
|
+
}
|
|
262
|
+
exports2.pyEntityPatterns = [
|
|
263
|
+
// regular functions
|
|
264
|
+
{
|
|
265
|
+
regex: /^(?:async\s+)?def\s+(\w+)\s*\(/gm,
|
|
266
|
+
type: "function"
|
|
267
|
+
},
|
|
268
|
+
// classes
|
|
269
|
+
{
|
|
270
|
+
regex: /^class\s+(\w+)(?:\s*\([^)]*\))?\s*:/gm,
|
|
271
|
+
type: "class"
|
|
272
|
+
}
|
|
273
|
+
];
|
|
274
|
+
function extractEntities(code, filePath) {
|
|
275
|
+
const entities = [];
|
|
276
|
+
for (const { regex, type } of exports2.pyEntityPatterns) {
|
|
277
|
+
regex.lastIndex = 0;
|
|
278
|
+
let match;
|
|
279
|
+
while ((match = regex.exec(code)) !== null) {
|
|
280
|
+
const name = match[1];
|
|
281
|
+
if (type === "function" && name.startsWith("__") && name.endsWith("__"))
|
|
282
|
+
continue;
|
|
283
|
+
if (entities.some((e) => e.name === name))
|
|
284
|
+
continue;
|
|
285
|
+
const upToMatch = code.slice(0, match.index);
|
|
286
|
+
const line = upToMatch.split("\n").length;
|
|
287
|
+
entities.push({
|
|
288
|
+
name,
|
|
289
|
+
type,
|
|
290
|
+
line,
|
|
291
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return entities;
|
|
296
|
+
}
|
|
297
|
+
function extractImports(code) {
|
|
298
|
+
const imports = [];
|
|
299
|
+
const fromPattern = /^from\s+([\w.]+)\s+import\s+(.+)$/gm;
|
|
300
|
+
let match;
|
|
301
|
+
while ((match = fromPattern.exec(code)) !== null) {
|
|
302
|
+
const source = match[1];
|
|
303
|
+
const names = match[2].split(",").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
304
|
+
const isLocal = source.startsWith(".");
|
|
305
|
+
imports.push({ source, names, isLocal });
|
|
306
|
+
}
|
|
307
|
+
const importPattern = /^import\s+([\w.]+)/gm;
|
|
308
|
+
while ((match = importPattern.exec(code)) !== null) {
|
|
309
|
+
const source = match[1];
|
|
310
|
+
imports.push({
|
|
311
|
+
source,
|
|
312
|
+
names: [source],
|
|
313
|
+
isLocal: false
|
|
314
|
+
// bare imports are always external
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return imports;
|
|
318
|
+
}
|
|
319
|
+
function extractExports(code) {
|
|
320
|
+
const allMatch = code.match(/__all__\s*=\s*\[([^\]]+)\]/);
|
|
321
|
+
if (!allMatch)
|
|
322
|
+
return [];
|
|
323
|
+
return allMatch[1].split(",").map((n) => n.trim().replace(/['"]/g, "")).filter((n) => n.length > 0);
|
|
324
|
+
}
|
|
325
|
+
var PythonParser = {
|
|
326
|
+
lang: "py",
|
|
327
|
+
extensions: [".py"],
|
|
328
|
+
extractEntities,
|
|
329
|
+
extractImports,
|
|
330
|
+
extractExports,
|
|
331
|
+
entityPatterns: exports2.pyEntityPatterns
|
|
332
|
+
};
|
|
333
|
+
(0, registry_1.registerParser)(PythonParser);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// dist/languages/go.js
|
|
338
|
+
var require_go = __commonJS({
|
|
339
|
+
"dist/languages/go.js"(exports2) {
|
|
340
|
+
"use strict";
|
|
341
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
342
|
+
exports2.goEntityPatterns = void 0;
|
|
343
|
+
var registry_1 = require_registry();
|
|
344
|
+
exports2.goEntityPatterns = [
|
|
345
|
+
// functions (including methods: func (r *Receiver) Name(...))
|
|
346
|
+
{
|
|
347
|
+
regex: /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/gm,
|
|
348
|
+
type: "function"
|
|
349
|
+
},
|
|
350
|
+
// type declarations (structs, interfaces, type aliases)
|
|
351
|
+
{
|
|
352
|
+
regex: /^type\s+(\w+)\s+(?:struct|interface)/gm,
|
|
353
|
+
type: "class"
|
|
354
|
+
}
|
|
355
|
+
];
|
|
356
|
+
function extractEntities(code, filePath) {
|
|
357
|
+
const entities = [];
|
|
358
|
+
for (const { regex, type } of exports2.goEntityPatterns) {
|
|
359
|
+
regex.lastIndex = 0;
|
|
360
|
+
let match;
|
|
361
|
+
while ((match = regex.exec(code)) !== null) {
|
|
362
|
+
const name = match[1];
|
|
363
|
+
if (entities.some((e) => e.name === name))
|
|
364
|
+
continue;
|
|
365
|
+
const upToMatch = code.slice(0, match.index);
|
|
366
|
+
const line = upToMatch.split("\n").length;
|
|
367
|
+
entities.push({ name, type, line, complexity: "low" });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return entities;
|
|
371
|
+
}
|
|
372
|
+
function extractImports(code) {
|
|
373
|
+
const imports = [];
|
|
374
|
+
const singlePattern = /^import\s+(?:\w+\s+)?["']([^"']+)["']/gm;
|
|
375
|
+
let match;
|
|
376
|
+
while ((match = singlePattern.exec(code)) !== null) {
|
|
377
|
+
imports.push({ source: match[1], names: [match[1]], isLocal: match[1].startsWith(".") });
|
|
378
|
+
}
|
|
379
|
+
const blockPattern = /import\s+\(([^)]+)\)/gs;
|
|
380
|
+
while ((match = blockPattern.exec(code)) !== null) {
|
|
381
|
+
const lines = match[1].split("\n");
|
|
382
|
+
for (const line of lines) {
|
|
383
|
+
const pkgMatch = line.match(/(?:\w+\s+)?["']([^"']+)["']/);
|
|
384
|
+
if (pkgMatch) {
|
|
385
|
+
imports.push({ source: pkgMatch[1], names: [pkgMatch[1]], isLocal: pkgMatch[1].startsWith(".") });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return imports;
|
|
390
|
+
}
|
|
391
|
+
function extractExports(code) {
|
|
392
|
+
const exports3 = [];
|
|
393
|
+
const pattern = /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?([A-Z]\w*)\s*\(/gm;
|
|
394
|
+
let match;
|
|
395
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
396
|
+
exports3.push(match[1]);
|
|
397
|
+
}
|
|
398
|
+
return [...new Set(exports3)];
|
|
399
|
+
}
|
|
400
|
+
var GoParser = {
|
|
401
|
+
lang: "go",
|
|
402
|
+
extensions: [".go"],
|
|
403
|
+
extractEntities,
|
|
404
|
+
extractImports,
|
|
405
|
+
extractExports,
|
|
406
|
+
entityPatterns: exports2.goEntityPatterns
|
|
407
|
+
};
|
|
408
|
+
(0, registry_1.registerParser)(GoParser);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
231
412
|
// dist/stages/collector.js
|
|
232
413
|
var require_collector = __commonJS({
|
|
233
414
|
"dist/stages/collector.js"(exports2) {
|
|
@@ -709,12 +890,165 @@ var require_output = __commonJS({
|
|
|
709
890
|
}
|
|
710
891
|
});
|
|
711
892
|
|
|
893
|
+
// dist/stages/gitdiff.js
|
|
894
|
+
var require_gitdiff = __commonJS({
|
|
895
|
+
"dist/stages/gitdiff.js"(exports2) {
|
|
896
|
+
"use strict";
|
|
897
|
+
var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
|
|
898
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
899
|
+
};
|
|
900
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
901
|
+
exports2.getChangedEntities = getChangedEntities;
|
|
902
|
+
var child_process_1 = require("child_process");
|
|
903
|
+
var path_1 = __importDefault2(require("path"));
|
|
904
|
+
var registry_1 = require_registry();
|
|
905
|
+
function getChangedEntities(options) {
|
|
906
|
+
const diff = runGitDiff(options);
|
|
907
|
+
if (!diff)
|
|
908
|
+
return [];
|
|
909
|
+
return parseDiff(diff, options.projectDir);
|
|
910
|
+
}
|
|
911
|
+
function runGitDiff(options) {
|
|
912
|
+
const { projectDir: projectDir2, mode, commit, from, to } = options;
|
|
913
|
+
let command;
|
|
914
|
+
if (mode === "uncommitted") {
|
|
915
|
+
command = "git diff HEAD";
|
|
916
|
+
} else if (mode === "last-commit") {
|
|
917
|
+
if (commit) {
|
|
918
|
+
command = `git diff ${commit}~1 ${commit}`;
|
|
919
|
+
} else {
|
|
920
|
+
command = "git diff HEAD~1 HEAD";
|
|
921
|
+
}
|
|
922
|
+
} else if (mode === "branches") {
|
|
923
|
+
if (!from || !to) {
|
|
924
|
+
console.warn("\u26A0 --from and --to are required for branch comparison");
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
command = `git diff ${from}...${to}`;
|
|
928
|
+
} else {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
const result = (0, child_process_1.execSync)(command, {
|
|
933
|
+
cwd: projectDir2,
|
|
934
|
+
encoding: "utf-8",
|
|
935
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
936
|
+
});
|
|
937
|
+
return result || null;
|
|
938
|
+
} catch (err) {
|
|
939
|
+
console.warn(`\u26A0 Git command failed: ${command}`);
|
|
940
|
+
console.warn(` Make sure ${projectDir2} is a git repository`);
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function parseDiff(diff, projectDir2) {
|
|
945
|
+
const entities = [];
|
|
946
|
+
const lines = diff.split("\n");
|
|
947
|
+
let currentFile = "";
|
|
948
|
+
let changeType = "modified";
|
|
949
|
+
for (let i = 0; i < lines.length; i++) {
|
|
950
|
+
const line = lines[i];
|
|
951
|
+
if (line.startsWith("diff --git")) {
|
|
952
|
+
const fileMatch = line.match(/b\/(.+)$/);
|
|
953
|
+
if (fileMatch) {
|
|
954
|
+
currentFile = fileMatch[1];
|
|
955
|
+
}
|
|
956
|
+
changeType = "modified";
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (line.startsWith("new file mode")) {
|
|
960
|
+
changeType = "added";
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (line.startsWith("deleted file mode")) {
|
|
964
|
+
changeType = "deleted";
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (line.startsWith("index ")) {
|
|
968
|
+
if (changeType === "modified")
|
|
969
|
+
changeType = "modified";
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
if (line.startsWith("@@")) {
|
|
973
|
+
const contextMatch = line.match(/@@[^@]*@@\s*(.+)$/);
|
|
974
|
+
if (contextMatch) {
|
|
975
|
+
const context = contextMatch[1].trim();
|
|
976
|
+
const entity = extractEntityFromContext(context, currentFile);
|
|
977
|
+
if (entity) {
|
|
978
|
+
const description = buildDescription(lines, i, entity.name);
|
|
979
|
+
const alreadyFound = entities.some((e) => e.name === entity.name && e.file === currentFile);
|
|
980
|
+
if (!alreadyFound) {
|
|
981
|
+
entities.push({
|
|
982
|
+
name: entity.name,
|
|
983
|
+
file: currentFile,
|
|
984
|
+
changeType,
|
|
985
|
+
description
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return entities;
|
|
994
|
+
}
|
|
995
|
+
function extractEntityFromContext(context, file) {
|
|
996
|
+
const ext = path_1.default.extname(file).toLowerCase();
|
|
997
|
+
const parser = (0, registry_1.getLanguageParser)(ext);
|
|
998
|
+
if (parser?.entityPatterns) {
|
|
999
|
+
for (const { regex, type } of parser.entityPatterns) {
|
|
1000
|
+
if (type === "api")
|
|
1001
|
+
continue;
|
|
1002
|
+
const singleLineRegex = new RegExp(regex.source, regex.flags.replace("g", ""));
|
|
1003
|
+
const m = singleLineRegex.exec(context);
|
|
1004
|
+
if (m?.[1])
|
|
1005
|
+
return { name: m[1], type };
|
|
1006
|
+
}
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
if ([".java", ".cs"].includes(ext)) {
|
|
1010
|
+
const m = context.match(/(?:public|private|protected|static|override|async|virtual)\s+\S+\s+(\w+)\s*\(/);
|
|
1011
|
+
if (m)
|
|
1012
|
+
return { name: m[1], type: "method" };
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
function buildDescription(lines, contextIdx, entityName) {
|
|
1017
|
+
const added = [];
|
|
1018
|
+
const removed = [];
|
|
1019
|
+
for (let i = contextIdx + 1; i < Math.min(contextIdx + 20, lines.length); i++) {
|
|
1020
|
+
const line = lines[i];
|
|
1021
|
+
if (line.startsWith("@@") || line.startsWith("diff"))
|
|
1022
|
+
break;
|
|
1023
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1024
|
+
added.push(line.slice(1).trim());
|
|
1025
|
+
}
|
|
1026
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
1027
|
+
removed.push(line.slice(1).trim());
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (added.length === 0 && removed.length > 0) {
|
|
1031
|
+
return `${entityName}: ${removed.length} line(s) removed`;
|
|
1032
|
+
}
|
|
1033
|
+
if (added.length > 0 && removed.length === 0) {
|
|
1034
|
+
return `${entityName}: ${added.length} line(s) added`;
|
|
1035
|
+
}
|
|
1036
|
+
if (added.length > 0 && removed.length > 0) {
|
|
1037
|
+
return `${entityName}: ${removed.length} line(s) changed to ${added.length} new line(s)`;
|
|
1038
|
+
}
|
|
1039
|
+
return `${entityName}: modified`;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
712
1044
|
// dist/main.js
|
|
713
1045
|
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
714
1046
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
715
1047
|
};
|
|
716
1048
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
717
1049
|
require_javascript();
|
|
1050
|
+
require_python();
|
|
1051
|
+
require_go();
|
|
718
1052
|
var fs_1 = __importDefault(require("fs"));
|
|
719
1053
|
var collector_1 = require_collector();
|
|
720
1054
|
var parser_1 = require_parser();
|
|
@@ -722,6 +1056,7 @@ var graph_1 = require_graph();
|
|
|
722
1056
|
var metrics_1 = require_metrics();
|
|
723
1057
|
var impact_1 = require_impact();
|
|
724
1058
|
var output_1 = require_output();
|
|
1059
|
+
var gitdiff_1 = require_gitdiff();
|
|
725
1060
|
var args = process.argv.slice(2);
|
|
726
1061
|
var noColor = args.includes("--no-color");
|
|
727
1062
|
var verbose = args.includes("--verbose");
|
|
@@ -742,11 +1077,11 @@ function getFlag(flag) {
|
|
|
742
1077
|
}
|
|
743
1078
|
function printHelp() {
|
|
744
1079
|
console.log(`
|
|
745
|
-
${bold("DepGraph
|
|
1080
|
+
${bold("DepGraph")} ${dim("v1.0.2")}
|
|
746
1081
|
${dim("Dependency mapping \xB7 Impact simulation \xB7 Developer intelligence")}
|
|
747
1082
|
|
|
748
1083
|
${bold("USAGE")}
|
|
749
|
-
|
|
1084
|
+
depgraph ${cyan("<projectDir>")} ${dim("[options]")}
|
|
750
1085
|
|
|
751
1086
|
${bold("OPTIONS")}
|
|
752
1087
|
${cyan("--output")} ${dim("<file>")} Output path ${dim("(default: ./depgraph-output.json)")}
|
|
@@ -755,24 +1090,43 @@ ${bold("OPTIONS")}
|
|
|
755
1090
|
${cyan("--no-color")} Disable colors ${dim("(for CI)")}
|
|
756
1091
|
${cyan("--help, -h")} Show this help message
|
|
757
1092
|
|
|
1093
|
+
${bold("GIT FLAGS")}
|
|
1094
|
+
${cyan("--git-impact")} Auto-detect changes from git diff
|
|
1095
|
+
${cyan("--commit")} ${dim("<sha>")} Analyze a specific commit
|
|
1096
|
+
${cyan("--from")} ${dim("<branch>")} Compare from this branch
|
|
1097
|
+
${cyan("--to")} ${dim("<branch>")} Compare to this branch
|
|
1098
|
+
|
|
758
1099
|
${bold("EXAMPLES")}
|
|
759
1100
|
${dim("# Map a project")}
|
|
760
|
-
|
|
1101
|
+
depgraph ./my-app
|
|
761
1102
|
|
|
762
1103
|
${dim("# Map with custom output")}
|
|
763
|
-
|
|
1104
|
+
depgraph ./my-app --output ./reports/graph.json
|
|
764
1105
|
|
|
765
1106
|
${dim("# Simulate a change")}
|
|
766
|
-
|
|
1107
|
+
depgraph ./my-app --impact "getUserById" "removing userId param"
|
|
767
1108
|
|
|
768
1109
|
${dim("# CI mode")}
|
|
769
|
-
|
|
1110
|
+
depgraph ./src --no-color --output ./ci/depgraph.json
|
|
1111
|
+
|
|
1112
|
+
${bold("GIT EXAMPLES")}
|
|
1113
|
+
${dim("# Analyze uncommitted changes")}
|
|
1114
|
+
depgraph ./src --git-impact
|
|
1115
|
+
|
|
1116
|
+
${dim("# Analyze last commit")}
|
|
1117
|
+
depgraph ./src --git-impact --commit HEAD
|
|
1118
|
+
|
|
1119
|
+
${dim("# Compare two branches")}
|
|
1120
|
+
depgraph ./src --git-impact --from main --to feature/my-branch
|
|
1121
|
+
|
|
1122
|
+
${dim("# Specific commit")}
|
|
1123
|
+
depgraph ./src --git-impact --commit abc1234
|
|
770
1124
|
`);
|
|
771
1125
|
}
|
|
772
1126
|
function printBanner() {
|
|
773
1127
|
console.log(`
|
|
774
1128
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
775
|
-
${bold(" DepGraph
|
|
1129
|
+
${bold(" DepGraph")} ${dim("v1.0.0")}
|
|
776
1130
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
777
1131
|
`);
|
|
778
1132
|
}
|
|
@@ -832,6 +1186,11 @@ var projectDir = args[0];
|
|
|
832
1186
|
var outputPath = getFlag("--output") ?? "./depgraph-output.json";
|
|
833
1187
|
var impactTarget = getFlag("--impact");
|
|
834
1188
|
var impactDesc = impactTarget ? args[args.indexOf("--impact") + 2] ?? "no description provided" : void 0;
|
|
1189
|
+
var gitImpact = args.includes("--git-impact");
|
|
1190
|
+
var gitCommit = getFlag("--commit");
|
|
1191
|
+
var gitFrom = getFlag("--from");
|
|
1192
|
+
var gitTo = getFlag("--to");
|
|
1193
|
+
var gitMode = gitFrom && gitTo ? "branches" : gitCommit ? "last-commit" : "uncommitted";
|
|
835
1194
|
printBanner();
|
|
836
1195
|
console.log(`${bold("\u{1F50D} Scanning")} ${cyan(projectDir)}
|
|
837
1196
|
`);
|
|
@@ -855,6 +1214,34 @@ try {
|
|
|
855
1214
|
if (impactTarget) {
|
|
856
1215
|
impact = (0, impact_1.simulateImpact)(metrics, impactTarget, impactDesc ?? "");
|
|
857
1216
|
printImpact(impact);
|
|
1217
|
+
} else if (gitImpact) {
|
|
1218
|
+
console.log(`
|
|
1219
|
+
${bold("\u{1F50D} Reading git diff...")}`);
|
|
1220
|
+
const changed = (0, gitdiff_1.getChangedEntities)({
|
|
1221
|
+
projectDir,
|
|
1222
|
+
mode: gitMode,
|
|
1223
|
+
commit: gitCommit,
|
|
1224
|
+
from: gitFrom,
|
|
1225
|
+
to: gitTo
|
|
1226
|
+
});
|
|
1227
|
+
if (changed.length === 0) {
|
|
1228
|
+
console.log(`
|
|
1229
|
+
${green("\u2713")} No changed entities found in diff`);
|
|
1230
|
+
} else {
|
|
1231
|
+
console.log(`
|
|
1232
|
+
${bold(`Found ${changed.length} changed entity(s):`)}`);
|
|
1233
|
+
for (const entity of changed) {
|
|
1234
|
+
console.log(` ${dim("\u2192")} ${entity.name} ${dim(`(${entity.file})`)}`);
|
|
1235
|
+
}
|
|
1236
|
+
console.log(`
|
|
1237
|
+
${bold("Running impact simulation...")}`);
|
|
1238
|
+
for (const entity of changed) {
|
|
1239
|
+
console.log(`
|
|
1240
|
+
${dim("\u2500".repeat(42))}`);
|
|
1241
|
+
const result = (0, impact_1.simulateImpact)(metrics, entity.name, entity.description);
|
|
1242
|
+
printImpact(result);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
858
1245
|
}
|
|
859
1246
|
(0, output_1.writeOutput)(metrics, parsed, outputPath, impact);
|
|
860
1247
|
} catch (err) {
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# DepGraph — Developer Documentation
|
|
2
|
+
|
|
3
|
+
Welcome to the internal documentation for **DepGraph Core**.
|
|
4
|
+
|
|
5
|
+
This folder explains how every moving part of the compiler works so you can contribute confidently, extend it with new languages, or debug an unexpected result.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
| File | What it covers |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [architecture.md](./architecture.md) | Big-picture overview — how the pipeline fits together |
|
|
14
|
+
| [stage-collector.md](./stage-collector.md) | Stage 1 — File collection (scanning the project directory) |
|
|
15
|
+
| [stage-parser.md](./stage-parser.md) | Stage 2 — Source file parsing (entities, imports, exports) |
|
|
16
|
+
| [stage-graph.md](./stage-graph.md) | Stage 3 — Dependency graph construction |
|
|
17
|
+
| [stage-metrics.md](./stage-metrics.md) | Stage 4 — Metrics computation (centrality, degrees) |
|
|
18
|
+
| [stage-impact.md](./stage-impact.md) | Stage 5 — Impact simulation (BFS + risk scoring) |
|
|
19
|
+
| [stage-output.md](./stage-output.md) | Stage 6 — JSON report generation |
|
|
20
|
+
| [language-registry.md](./language-registry.md) | The language plugin system — how to add a new language |
|
|
21
|
+
| [data-types.md](./data-types.md) | All shared TypeScript interfaces, explained |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick Mental Model
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Your project folder
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
┌─────────────┐
|
|
32
|
+
│ Collector │ ← finds every eligible source file
|
|
33
|
+
└──────┬──────┘
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────┐
|
|
36
|
+
│ Parser │ ← reads each file; extracts entities, imports, exports
|
|
37
|
+
└──────┬──────┘
|
|
38
|
+
▼
|
|
39
|
+
┌─────────────┐
|
|
40
|
+
│ Graph │ ← links entities together through their imports
|
|
41
|
+
└──────┬──────┘
|
|
42
|
+
▼
|
|
43
|
+
┌─────────────┐
|
|
44
|
+
│ Metrics │ ← computes in/out-degree and centrality for every node
|
|
45
|
+
└──────┬──────┘
|
|
46
|
+
▼
|
|
47
|
+
┌─────────────┐
|
|
48
|
+
│ Impact │ ← (optional) BFS from a target node → risk report
|
|
49
|
+
└──────┬──────┘
|
|
50
|
+
▼
|
|
51
|
+
┌─────────────┐
|
|
52
|
+
│ Output │ ← serialises everything to depgraph-output.json
|
|
53
|
+
└─────────────┘
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Each stage is self-contained. Data flows **forward only** — no stage reaches back to an earlier one.
|