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/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
- // 0-3 branches → low complexity
73
+ /** Complexity score is "medium" if there are between 4 and 8 branch points. */
73
74
  medium: 8,
74
- // 4-8 branches → medium complexity
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 lines = code.split("\n");
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 Compiler")} ${dim("v1.0.0")}
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
- node depgraph.js ${cyan("<projectDir>")} ${dim("[options]")}
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
- node depgraph.js ./my-app
1101
+ depgraph ./my-app
761
1102
 
762
1103
  ${dim("# Map with custom output")}
763
- node depgraph.js ./my-app --output ./reports/graph.json
1104
+ depgraph ./my-app --output ./reports/graph.json
764
1105
 
765
1106
  ${dim("# Simulate a change")}
766
- node depgraph.js ./my-app --impact "getUserById" "removing userId param"
1107
+ depgraph ./my-app --impact "getUserById" "removing userId param"
767
1108
 
768
1109
  ${dim("# CI mode")}
769
- node depgraph.js ./src --no-color --output ./ci/depgraph.json
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 Compiler")} ${dim("v1.0.0")}
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.