depgraph-core 1.0.2 → 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
@@ -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,7 +222,8 @@ 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
  }
@@ -233,6 +234,7 @@ var require_python = __commonJS({
233
234
  "dist/languages/python.js"(exports2) {
234
235
  "use strict";
235
236
  Object.defineProperty(exports2, "__esModule", { value: true });
237
+ exports2.pyEntityPatterns = void 0;
236
238
  var registry_1 = require_registry();
237
239
  var constants_1 = require_constants();
238
240
  function estimateComplexity(code, name) {
@@ -257,21 +259,21 @@ var require_python = __commonJS({
257
259
  return "medium";
258
260
  return "high";
259
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
+ ];
260
274
  function extractEntities(code, filePath) {
261
275
  const entities = [];
262
- const patterns = [
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
- for (const { regex, type } of patterns) {
276
+ for (const { regex, type } of exports2.pyEntityPatterns) {
275
277
  regex.lastIndex = 0;
276
278
  let match;
277
279
  while ((match = regex.exec(code)) !== null) {
@@ -325,12 +327,88 @@ var require_python = __commonJS({
325
327
  extensions: [".py"],
326
328
  extractEntities,
327
329
  extractImports,
328
- extractExports
330
+ extractExports,
331
+ entityPatterns: exports2.pyEntityPatterns
329
332
  };
330
333
  (0, registry_1.registerParser)(PythonParser);
331
334
  }
332
335
  });
333
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
+
334
412
  // dist/stages/collector.js
335
413
  var require_collector = __commonJS({
336
414
  "dist/stages/collector.js"(exports2) {
@@ -812,6 +890,157 @@ var require_output = __commonJS({
812
890
  }
813
891
  });
814
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
+
815
1044
  // dist/main.js
816
1045
  var __importDefault = exports && exports.__importDefault || function(mod) {
817
1046
  return mod && mod.__esModule ? mod : { "default": mod };
@@ -819,6 +1048,7 @@ var __importDefault = exports && exports.__importDefault || function(mod) {
819
1048
  Object.defineProperty(exports, "__esModule", { value: true });
820
1049
  require_javascript();
821
1050
  require_python();
1051
+ require_go();
822
1052
  var fs_1 = __importDefault(require("fs"));
823
1053
  var collector_1 = require_collector();
824
1054
  var parser_1 = require_parser();
@@ -826,6 +1056,7 @@ var graph_1 = require_graph();
826
1056
  var metrics_1 = require_metrics();
827
1057
  var impact_1 = require_impact();
828
1058
  var output_1 = require_output();
1059
+ var gitdiff_1 = require_gitdiff();
829
1060
  var args = process.argv.slice(2);
830
1061
  var noColor = args.includes("--no-color");
831
1062
  var verbose = args.includes("--verbose");
@@ -846,11 +1077,11 @@ function getFlag(flag) {
846
1077
  }
847
1078
  function printHelp() {
848
1079
  console.log(`
849
- ${bold("DepGraph Compiler")} ${dim("v1.0.2")}
1080
+ ${bold("DepGraph")} ${dim("v1.0.2")}
850
1081
  ${dim("Dependency mapping \xB7 Impact simulation \xB7 Developer intelligence")}
851
1082
 
852
1083
  ${bold("USAGE")}
853
- node depgraph.js ${cyan("<projectDir>")} ${dim("[options]")}
1084
+ depgraph ${cyan("<projectDir>")} ${dim("[options]")}
854
1085
 
855
1086
  ${bold("OPTIONS")}
856
1087
  ${cyan("--output")} ${dim("<file>")} Output path ${dim("(default: ./depgraph-output.json)")}
@@ -859,24 +1090,43 @@ ${bold("OPTIONS")}
859
1090
  ${cyan("--no-color")} Disable colors ${dim("(for CI)")}
860
1091
  ${cyan("--help, -h")} Show this help message
861
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
+
862
1099
  ${bold("EXAMPLES")}
863
1100
  ${dim("# Map a project")}
864
- node depgraph.js ./my-app
1101
+ depgraph ./my-app
865
1102
 
866
1103
  ${dim("# Map with custom output")}
867
- node depgraph.js ./my-app --output ./reports/graph.json
1104
+ depgraph ./my-app --output ./reports/graph.json
868
1105
 
869
1106
  ${dim("# Simulate a change")}
870
- node depgraph.js ./my-app --impact "getUserById" "removing userId param"
1107
+ depgraph ./my-app --impact "getUserById" "removing userId param"
871
1108
 
872
1109
  ${dim("# CI mode")}
873
- 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
874
1124
  `);
875
1125
  }
876
1126
  function printBanner() {
877
1127
  console.log(`
878
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")}
879
- ${bold(" DepGraph Compiler")} ${dim("v1.0.0")}
1129
+ ${bold(" DepGraph")} ${dim("v1.0.0")}
880
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")}
881
1131
  `);
882
1132
  }
@@ -936,6 +1186,11 @@ var projectDir = args[0];
936
1186
  var outputPath = getFlag("--output") ?? "./depgraph-output.json";
937
1187
  var impactTarget = getFlag("--impact");
938
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";
939
1194
  printBanner();
940
1195
  console.log(`${bold("\u{1F50D} Scanning")} ${cyan(projectDir)}
941
1196
  `);
@@ -959,6 +1214,34 @@ try {
959
1214
  if (impactTarget) {
960
1215
  impact = (0, impact_1.simulateImpact)(metrics, impactTarget, impactDesc ?? "");
961
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
+ }
962
1245
  }
963
1246
  (0, output_1.writeOutput)(metrics, parsed, outputPath, impact);
964
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.
@@ -0,0 +1,89 @@
1
+ # Architecture Overview
2
+
3
+ > **File**: `src/main.ts` — the orchestration entry point
4
+ > **Role**: Wires together all six stages in sequential order and handles CLI argument parsing.
5
+
6
+ ---
7
+
8
+ ## The Pipeline at a Glance
9
+
10
+ DepGraph is a **linear, stage-based compiler**. There is no framework magic — it's a plain chain of function calls where the output of each stage becomes the input of the next.
11
+
12
+ ```
13
+ collectFiles()
14
+ │ string[] (file paths)
15
+
16
+ parseFiles()
17
+ │ ParsedFile[] (entities, imports, exports per file)
18
+
19
+ buildGraph()
20
+ │ DepGraph (nodes + edges map)
21
+
22
+ computeMetrics()
23
+ │ DepGraph (same graph, nodes now have inDegree / outDegree / centralityScore)
24
+
25
+ simulateImpact() ← only runs when --impact flag is provided
26
+ │ ImpactReport
27
+
28
+ writeOutput()
29
+ depgraph-output.json
30
+ ```
31
+
32
+ Every stage lives in its own file under `src/stages/`. They are pure functions — given the same input they always produce the same output, and they never touch the file system except for the collector (reading) and output (writing).
33
+
34
+ ---
35
+
36
+ ## Entry Point — `src/main.ts`
37
+
38
+ `main.ts` does four things and nothing else:
39
+
40
+ 1. **Parses CLI flags** — `--output`, `--impact`, `--verbose`, `--no-color`, `--help`
41
+ 2. **Runs the pipeline** inside a single `try/catch` block
42
+ 3. **Prints progress** to stdout (banner, summary, impact table)
43
+ 4. **Exits with code 1** on any unhandled error
44
+
45
+ ### Key variables wired at startup
46
+
47
+ ```ts
48
+ const projectDir = args[0]; // the path to scan
49
+ const outputPath = getFlag('--output'); // where to write the JSON
50
+ const impactTarget = getFlag('--impact'); // entity name to simulate
51
+ ```
52
+
53
+ ### Execution order in the `try` block
54
+
55
+ ```ts
56
+ const files = collectFiles(projectDir); // Stage 1
57
+ const parsed = parseFiles(files); // Stage 2
58
+ const graph = buildGraph(parsed); // Stage 3
59
+ const metrics = computeMetrics(graph); // Stage 4
60
+ // Stage 5 — optional
61
+ const impact = impactTarget
62
+ ? simulateImpact(metrics, impactTarget, impactDesc)
63
+ : undefined;
64
+ writeOutput(metrics, parsed, outputPath, impact); // Stage 6
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Design Principles
70
+
71
+ | Principle | How it's applied |
72
+ |---|---|
73
+ | **Single responsibility** | Each stage file exports exactly one primary function |
74
+ | **No hidden state** | All data passed explicitly between stages |
75
+ | **Fail loudly** | `process.exit(1)` on unrecoverable errors — no silent swallowing |
76
+ | **Zero runtime dependencies** | Only Node.js built-ins (`fs`, `path`) plus TypeScript |
77
+ | **Plugin language support** | Languages register themselves via the registry (see [language-registry.md](./language-registry.md)) |
78
+
79
+ ---
80
+
81
+ ## Adding a New Stage
82
+
83
+ If you need to insert a new processing step (e.g. a linting stage), follow this pattern:
84
+
85
+ 1. Create `src/stages/my-stage.ts` and export a pure function.
86
+ 2. Import and call it in `main.ts` after the stage it depends on.
87
+ 3. Thread its output into downstream stages.
88
+
89
+ No registration, no magic — just a function call.