depgraph-core 1.0.0-beta

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 ADDED
@@ -0,0 +1,864 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __commonJS = (cb, mod) => function __require() {
5
+ try {
6
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7
+ } catch (e) {
8
+ throw mod = 0, e;
9
+ }
10
+ };
11
+
12
+ // dist/languages/registry.js
13
+ var require_registry = __commonJS({
14
+ "dist/languages/registry.js"(exports2) {
15
+ "use strict";
16
+ Object.defineProperty(exports2, "__esModule", { value: true });
17
+ exports2.registerParser = registerParser;
18
+ exports2.getLanguageParser = getLanguageParser;
19
+ var parsers = [];
20
+ function registerParser(parser) {
21
+ parsers.push(parser);
22
+ }
23
+ function getLanguageParser(ext) {
24
+ return parsers.find((p) => p.extensions.includes(ext)) ?? null;
25
+ }
26
+ }
27
+ });
28
+
29
+ // dist/constants.js
30
+ var require_constants = __commonJS({
31
+ "dist/constants.js"(exports2) {
32
+ "use strict";
33
+ Object.defineProperty(exports2, "__esModule", { value: true });
34
+ exports2.COMPLEXITY_THRESHOLDS = exports2.MAX_BFS_DEPTH = exports2.MAX_FILE_SIZE = exports2.SUPPORTED_EXTS = exports2.IGNORE_DIRS = exports2.VERSION = void 0;
35
+ exports2.VERSION = "1.0.0";
36
+ exports2.IGNORE_DIRS = /* @__PURE__ */ new Set([
37
+ "node_modules",
38
+ ".git",
39
+ "dist",
40
+ "build",
41
+ ".next",
42
+ "__pycache__",
43
+ "vendor",
44
+ "venv",
45
+ "target",
46
+ "out",
47
+ "coverage",
48
+ ".cache"
49
+ ]);
50
+ exports2.SUPPORTED_EXTS = /* @__PURE__ */ new Set([
51
+ ".ts",
52
+ ".tsx",
53
+ ".js",
54
+ ".jsx",
55
+ ".mjs",
56
+ ".cjs",
57
+ ".py",
58
+ ".go",
59
+ ".java",
60
+ ".cs",
61
+ ".rb",
62
+ ".php",
63
+ ".swift",
64
+ ".kt",
65
+ ".vue",
66
+ ".svelte"
67
+ ]);
68
+ exports2.MAX_FILE_SIZE = 3e5;
69
+ exports2.MAX_BFS_DEPTH = 10;
70
+ exports2.COMPLEXITY_THRESHOLDS = {
71
+ low: 3,
72
+ // 0-3 branches → low complexity
73
+ medium: 8,
74
+ // 4-8 branches → medium complexity
75
+ high: Infinity
76
+ // 9+ → high complexity
77
+ };
78
+ }
79
+ });
80
+
81
+ // dist/languages/javascript.js
82
+ var require_javascript = __commonJS({
83
+ "dist/languages/javascript.js"(exports2) {
84
+ "use strict";
85
+ Object.defineProperty(exports2, "__esModule", { value: true });
86
+ var registry_1 = require_registry();
87
+ var constants_1 = require_constants();
88
+ function estimateComplexity(code, name) {
89
+ const bodyMatch = code.match(new RegExp(`function\\s+${name}[^{]*{([\\s\\S]*?)
90
+ }`, "m"));
91
+ if (!bodyMatch)
92
+ return "low";
93
+ const body = bodyMatch[1];
94
+ const branches = (body.match(/\b(if|else|for|while|switch|catch|&&|\|\|)\b/g) || []).length;
95
+ if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
96
+ return "low";
97
+ if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
98
+ return "medium";
99
+ return "high";
100
+ }
101
+ function extractEntities(code, filePath) {
102
+ 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) {
147
+ let match;
148
+ regex.lastIndex = 0;
149
+ while ((match = regex.exec(code)) !== null) {
150
+ const upToMatch = code.slice(0, match.index);
151
+ const line = upToMatch.split("\n").length;
152
+ if (type === "api") {
153
+ entities.push({
154
+ name: `${match[1].toUpperCase()} ${match[2]}`,
155
+ type: "api",
156
+ line,
157
+ complexity: "low"
158
+ });
159
+ } else {
160
+ const name = match[1];
161
+ if (entities.some((e) => e.name === name))
162
+ continue;
163
+ entities.push({
164
+ name,
165
+ type,
166
+ line,
167
+ complexity: estimateComplexity(code, name)
168
+ });
169
+ }
170
+ }
171
+ }
172
+ return entities;
173
+ }
174
+ function extractImports(code) {
175
+ const imports = [];
176
+ const namedPattern = /^import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/gm;
177
+ let match;
178
+ while ((match = namedPattern.exec(code)) !== null) {
179
+ const names = match[1].split(",").map((n) => n.trim().replace(/\s+as\s+\w+/, ""));
180
+ const source = match[2];
181
+ imports.push({
182
+ source,
183
+ names,
184
+ isLocal: source.startsWith(".")
185
+ });
186
+ }
187
+ const defaultPattern = /^import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/gm;
188
+ while ((match = defaultPattern.exec(code)) !== null) {
189
+ imports.push({
190
+ source: match[2],
191
+ names: [match[1]],
192
+ isLocal: match[2].startsWith(".")
193
+ });
194
+ }
195
+ const requirePattern = /(?:const|let|var)\s+\{?([^}=]+)\}?\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/gm;
196
+ while ((match = requirePattern.exec(code)) !== null) {
197
+ const names = match[1].split(",").map((n) => n.trim());
198
+ imports.push({
199
+ source: match[2],
200
+ names,
201
+ isLocal: match[2].startsWith(".")
202
+ });
203
+ }
204
+ return imports;
205
+ }
206
+ function extractExports(code) {
207
+ const exports3 = [];
208
+ const namedPattern = /^export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|type|interface)\s+(\w+)/gm;
209
+ let match;
210
+ while ((match = namedPattern.exec(code)) !== null) {
211
+ exports3.push(match[1]);
212
+ }
213
+ const listPattern = /^export\s+\{([^}]+)\}/gm;
214
+ while ((match = listPattern.exec(code)) !== null) {
215
+ const names = match[1].split(",").map((n) => n.trim());
216
+ exports3.push(...names);
217
+ }
218
+ return [...new Set(exports3)];
219
+ }
220
+ var JavaScriptParser = {
221
+ lang: "js",
222
+ extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"],
223
+ extractEntities,
224
+ extractImports,
225
+ extractExports
226
+ };
227
+ (0, registry_1.registerParser)(JavaScriptParser);
228
+ }
229
+ });
230
+
231
+ // dist/stages/collector.js
232
+ var require_collector = __commonJS({
233
+ "dist/stages/collector.js"(exports2) {
234
+ "use strict";
235
+ var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
236
+ return mod && mod.__esModule ? mod : { "default": mod };
237
+ };
238
+ Object.defineProperty(exports2, "__esModule", { value: true });
239
+ exports2.collectFiles = collectFiles;
240
+ var fs_12 = __importDefault2(require("fs"));
241
+ var path_1 = __importDefault2(require("path"));
242
+ var constants_1 = require_constants();
243
+ function collectFiles(dir) {
244
+ const results = [];
245
+ function walk(currentDir) {
246
+ let entries;
247
+ try {
248
+ entries = fs_12.default.readdirSync(currentDir);
249
+ } catch (err) {
250
+ console.warn(` >> ====== > Cannot read directory: ${currentDir}`);
251
+ return;
252
+ }
253
+ for (const entry of entries) {
254
+ const fullPath = path_1.default.join(currentDir, entry);
255
+ let stat;
256
+ try {
257
+ stat = fs_12.default.statSync(fullPath);
258
+ } catch (err) {
259
+ console.warn(` >> ====== > Cannot stat: ${fullPath}`);
260
+ continue;
261
+ }
262
+ if (stat.isDirectory()) {
263
+ if (!constants_1.IGNORE_DIRS.has(entry)) {
264
+ walk(fullPath);
265
+ }
266
+ continue;
267
+ }
268
+ const ext = path_1.default.extname(entry);
269
+ if (constants_1.SUPPORTED_EXTS.has(ext) && stat.size < constants_1.MAX_FILE_SIZE) {
270
+ results.push(fullPath);
271
+ }
272
+ }
273
+ }
274
+ walk(dir);
275
+ return results;
276
+ }
277
+ }
278
+ });
279
+
280
+ // dist/stages/parser.js
281
+ var require_parser = __commonJS({
282
+ "dist/stages/parser.js"(exports2) {
283
+ "use strict";
284
+ var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
285
+ return mod && mod.__esModule ? mod : { "default": mod };
286
+ };
287
+ Object.defineProperty(exports2, "__esModule", { value: true });
288
+ exports2.parseFile = parseFile;
289
+ exports2.parseFiles = parseFiles;
290
+ var fs_12 = __importDefault2(require("fs"));
291
+ var path_1 = __importDefault2(require("path"));
292
+ var registry_1 = require_registry();
293
+ function parseFile(filePath) {
294
+ let code;
295
+ try {
296
+ code = fs_12.default.readFileSync(filePath, "utf-8");
297
+ } catch {
298
+ console.warn(`\u26A0 Cannot read file: ${filePath}`);
299
+ return null;
300
+ }
301
+ const ext = path_1.default.extname(filePath).toLowerCase();
302
+ const parser = (0, registry_1.getLanguageParser)(ext);
303
+ if (!parser)
304
+ return null;
305
+ const cleanCode = code.split("\n").map((line) => {
306
+ const commentIndex = line.indexOf("//");
307
+ if (commentIndex === -1)
308
+ return line;
309
+ const before = line.slice(0, commentIndex);
310
+ const inString = (before.match(/"/g) || []).length % 2 !== 0 || (before.match(/'/g) || []).length % 2 !== 0;
311
+ return inString ? line : line.slice(0, commentIndex);
312
+ }).join("\n");
313
+ const lines = code.split("\n").length;
314
+ const entities = parser.extractEntities(cleanCode, filePath);
315
+ const imports = parser.extractImports(cleanCode);
316
+ const exports3 = parser.extractExports(cleanCode);
317
+ return { filePath, lang: parser.lang, lines, entities, imports, exports: exports3 };
318
+ }
319
+ function parseFiles(filePaths) {
320
+ const results = [];
321
+ for (const filePath of filePaths) {
322
+ const parsed = parseFile(filePath);
323
+ if (parsed) {
324
+ results.push(parsed);
325
+ }
326
+ }
327
+ return results;
328
+ }
329
+ }
330
+ });
331
+
332
+ // dist/stages/graph.js
333
+ var require_graph = __commonJS({
334
+ "dist/stages/graph.js"(exports2) {
335
+ "use strict";
336
+ var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
337
+ return mod && mod.__esModule ? mod : { "default": mod };
338
+ };
339
+ Object.defineProperty(exports2, "__esModule", { value: true });
340
+ exports2.buildGraph = buildGraph;
341
+ var path_1 = __importDefault2(require("path"));
342
+ function buildGraph(parsedFiles) {
343
+ const nodes = /* @__PURE__ */ new Map();
344
+ const edges = [];
345
+ for (const file of parsedFiles) {
346
+ const fileBase = path_1.default.basename(file.filePath, path_1.default.extname(file.filePath));
347
+ for (const entity of file.entities) {
348
+ const id = makeId(entity.name, fileBase);
349
+ if (nodes.has(id))
350
+ continue;
351
+ nodes.set(id, {
352
+ id,
353
+ name: entity.name,
354
+ type: entity.type,
355
+ file: file.filePath,
356
+ line: entity.line,
357
+ lang: file.lang,
358
+ complexity: entity.complexity,
359
+ inDegree: 0,
360
+ outDegree: 0,
361
+ centralityScore: 0,
362
+ connections: []
363
+ });
364
+ }
365
+ }
366
+ const fileMap = /* @__PURE__ */ new Map();
367
+ for (const file of parsedFiles) {
368
+ fileMap.set(file.filePath, file);
369
+ }
370
+ for (const file of parsedFiles) {
371
+ const fileBase = path_1.default.basename(file.filePath, path_1.default.extname(file.filePath));
372
+ for (const imp of file.imports) {
373
+ if (!imp.isLocal)
374
+ continue;
375
+ const resolvedPath = resolvePath(file.filePath, imp.source, parsedFiles);
376
+ if (!resolvedPath)
377
+ continue;
378
+ const targetFile = fileMap.get(resolvedPath);
379
+ if (!targetFile)
380
+ continue;
381
+ for (const importedName of imp.names) {
382
+ const targetEntity = targetFile.entities.find((e) => e.name === importedName);
383
+ if (!targetEntity)
384
+ continue;
385
+ const targetBase = path_1.default.basename(resolvedPath, path_1.default.extname(resolvedPath));
386
+ const toId = makeId(importedName, targetBase);
387
+ if (!nodes.has(toId))
388
+ continue;
389
+ const fromEntities = file.entities.length > 0 ? file.entities : [{ name: fileBase, type: "file", line: 0, complexity: "low" }];
390
+ for (const fromEntity of fromEntities) {
391
+ const fromId = makeId(fromEntity.name, fileBase);
392
+ if (!nodes.has(fromId))
393
+ continue;
394
+ if (fromId === toId)
395
+ continue;
396
+ const alreadyExists = edges.some((e) => e.from === fromId && e.to === toId && e.type === "imports");
397
+ if (alreadyExists)
398
+ continue;
399
+ edges.push({
400
+ from: fromId,
401
+ to: toId,
402
+ type: "imports",
403
+ description: `${fromEntity.name} imports ${importedName} from ${path_1.default.basename(resolvedPath)}`
404
+ });
405
+ const fromNode = nodes.get(fromId);
406
+ const toNode = nodes.get(toId);
407
+ if (fromNode && !fromNode.connections.includes(toId)) {
408
+ fromNode.connections.push(toId);
409
+ }
410
+ if (toNode && !toNode.connections.includes(fromId)) {
411
+ toNode.connections.push(fromId);
412
+ }
413
+ }
414
+ }
415
+ }
416
+ }
417
+ return { nodes, edges };
418
+ }
419
+ function makeId(name, fileBase) {
420
+ const cleanName = name.replace(/[^a-zA-Z0-9]/g, "_");
421
+ const cleanBase = fileBase.replace(/[^a-zA-Z0-9]/g, "_");
422
+ return `${cleanName}__${cleanBase}`;
423
+ }
424
+ function resolvePath(fromFile, importSource, allFiles) {
425
+ const fromDir = path_1.default.dirname(fromFile);
426
+ const base = path_1.default.join(fromDir, importSource);
427
+ const candidates = [
428
+ base,
429
+ `${base}.ts`,
430
+ `${base}.tsx`,
431
+ `${base}.js`,
432
+ `${base}.jsx`,
433
+ `${base}/index.ts`,
434
+ `${base}/index.js`
435
+ ];
436
+ for (const candidate of candidates) {
437
+ const normalized = candidate.replace(/\\/g, "/");
438
+ const found = allFiles.find((f) => f.filePath.replace(/\\/g, "/") === normalized);
439
+ if (found)
440
+ return found.filePath;
441
+ }
442
+ return null;
443
+ }
444
+ }
445
+ });
446
+
447
+ // dist/stages/metrics.js
448
+ var require_metrics = __commonJS({
449
+ "dist/stages/metrics.js"(exports2) {
450
+ "use strict";
451
+ Object.defineProperty(exports2, "__esModule", { value: true });
452
+ exports2.computeMetrics = computeMetrics;
453
+ exports2.getEntryPoints = getEntryPoints;
454
+ exports2.getLeafNodes = getLeafNodes;
455
+ exports2.getIsolatedNodes = getIsolatedNodes;
456
+ exports2.getCriticalNodes = getCriticalNodes;
457
+ function computeMetrics(graph) {
458
+ for (const edge of graph.edges) {
459
+ const fromNode = graph.nodes.get(edge.from);
460
+ const toNode = graph.nodes.get(edge.to);
461
+ if (fromNode)
462
+ fromNode.outDegree += 1;
463
+ if (toNode)
464
+ toNode.inDegree += 1;
465
+ }
466
+ for (const [, node] of graph.nodes) {
467
+ node.centralityScore = node.inDegree * 2 + node.outDegree;
468
+ }
469
+ return graph;
470
+ }
471
+ function getEntryPoints(graph) {
472
+ return [...graph.nodes.values()].filter((n) => n.inDegree === 0 && n.outDegree > 0).map((n) => n.id);
473
+ }
474
+ function getLeafNodes(graph) {
475
+ return [...graph.nodes.values()].filter((n) => n.outDegree === 0 && n.inDegree > 0).map((n) => n.id);
476
+ }
477
+ function getIsolatedNodes(graph) {
478
+ return [...graph.nodes.values()].filter((n) => n.inDegree === 0 && n.outDegree === 0).map((n) => n.id);
479
+ }
480
+ function getCriticalNodes(graph) {
481
+ return [...graph.nodes.values()].filter((n) => n.centralityScore > 20).map((n) => n.id);
482
+ }
483
+ }
484
+ });
485
+
486
+ // dist/stages/impact.js
487
+ var require_impact = __commonJS({
488
+ "dist/stages/impact.js"(exports2) {
489
+ "use strict";
490
+ Object.defineProperty(exports2, "__esModule", { value: true });
491
+ exports2.simulateImpact = simulateImpact;
492
+ var constants_1 = require_constants();
493
+ function simulateImpact(graph, targetName, changeDescription) {
494
+ const targetNode = [...graph.nodes.values()].find((n) => n.name === targetName);
495
+ if (!targetNode) {
496
+ return emptyReport(targetName, changeDescription, `Node "${targetName}" not found in graph`);
497
+ }
498
+ const affected = [];
499
+ const visited = /* @__PURE__ */ new Set();
500
+ const queue = [];
501
+ const directDependents = getDirectDependents(graph, targetNode.id);
502
+ for (const depId of directDependents) {
503
+ queue.push({ id: depId, depth: 1 });
504
+ }
505
+ while (queue.length > 0) {
506
+ const { id, depth } = queue.shift();
507
+ if (visited.has(id))
508
+ continue;
509
+ if (depth > constants_1.MAX_BFS_DEPTH)
510
+ continue;
511
+ visited.add(id);
512
+ const node = graph.nodes.get(id);
513
+ if (!node)
514
+ continue;
515
+ const impact = getImpactLevel(depth);
516
+ affected.push({
517
+ nodeId: id,
518
+ name: node.name,
519
+ file: node.file,
520
+ depth,
521
+ impact,
522
+ reason: getReason(node.name, targetName, depth),
523
+ changeRequired: getChangeRequired(node.name, targetName, impact),
524
+ breakingChange: depth <= 2
525
+ });
526
+ const nextDependents = getDirectDependents(graph, id);
527
+ for (const nextId of nextDependents) {
528
+ if (!visited.has(nextId)) {
529
+ queue.push({ id: nextId, depth: depth + 1 });
530
+ }
531
+ }
532
+ }
533
+ const riskScore = computeRiskScore(affected, targetNode.inDegree);
534
+ const riskLevel = getRiskLevel(riskScore);
535
+ const breakingChanges = affected.filter((n) => n.breakingChange);
536
+ const testingPlan = buildTestingPlan(targetName, affected);
537
+ const recommendations = buildRecommendations(riskScore, breakingChanges.length);
538
+ return {
539
+ targetNode: targetNode.id,
540
+ changeDescription,
541
+ riskScore,
542
+ riskLevel,
543
+ affectedNodes: affected,
544
+ breakingChanges,
545
+ testingPlan,
546
+ recommendations
547
+ };
548
+ }
549
+ function getDirectDependents(graph, nodeId) {
550
+ return graph.edges.filter((e) => e.to === nodeId).map((e) => e.from);
551
+ }
552
+ function getImpactLevel(depth) {
553
+ if (depth === 1)
554
+ return "critical";
555
+ if (depth === 2)
556
+ return "high";
557
+ if (depth <= 4)
558
+ return "medium";
559
+ return "low";
560
+ }
561
+ function computeRiskScore(affected, inDegree) {
562
+ const C = affected.filter((n) => n.impact === "critical").length;
563
+ const H = affected.filter((n) => n.impact === "high").length;
564
+ const M = affected.filter((n) => n.impact === "medium").length;
565
+ const L = affected.filter((n) => n.impact === "low").length;
566
+ const score = C * 30 + H * 15 + M * 7 + L * 2 + inDegree * 3;
567
+ return Math.min(100, score);
568
+ }
569
+ function getRiskLevel(score) {
570
+ if (score >= 75)
571
+ return "CRITICAL";
572
+ if (score >= 50)
573
+ return "HIGH";
574
+ if (score >= 25)
575
+ return "MEDIUM";
576
+ return "LOW";
577
+ }
578
+ function getReason(nodeName, targetName, depth) {
579
+ if (depth === 1)
580
+ return `${nodeName} directly imports ${targetName}`;
581
+ if (depth === 2)
582
+ return `${nodeName} depends on something that uses ${targetName}`;
583
+ return `${nodeName} is transitively affected by changes to ${targetName}`;
584
+ }
585
+ function getChangeRequired(name, targetName, impact) {
586
+ if (impact === "critical")
587
+ return `Update ${name} to handle the new interface of ${targetName}`;
588
+ if (impact === "high")
589
+ return `Review ${name} for compatibility with changed ${targetName}`;
590
+ if (impact === "medium")
591
+ return `Test ${name} after deploying changes to ${targetName}`;
592
+ return `Monitor ${name} for unexpected behavior after ${targetName} changes`;
593
+ }
594
+ function buildTestingPlan(targetName, affected) {
595
+ const plan = [];
596
+ plan.push(`Test ${targetName} directly after making changes`);
597
+ const critical = affected.filter((n) => n.impact === "critical");
598
+ const high = affected.filter((n) => n.impact === "high");
599
+ for (const node of critical) {
600
+ plan.push(`Regression test ${node.name} \u2014 direct dependent`);
601
+ }
602
+ for (const node of high) {
603
+ plan.push(`Integration test ${node.name} \u2014 indirect dependent`);
604
+ }
605
+ if (affected.length > 5) {
606
+ plan.push(`Run full test suite \u2014 ${affected.length} nodes affected`);
607
+ }
608
+ return plan;
609
+ }
610
+ function buildRecommendations(riskScore, breakingCount) {
611
+ const rec = [];
612
+ if (riskScore >= 75) {
613
+ rec.push("Full team review required before merging");
614
+ rec.push("Consider a phased rollout");
615
+ rec.push("Run full regression test suite");
616
+ } else if (riskScore >= 50) {
617
+ rec.push("Tech lead review recommended");
618
+ rec.push("Feature flag this change");
619
+ rec.push("Test all breaking changes before deploying");
620
+ } else if (riskScore >= 25) {
621
+ rec.push("Code review required");
622
+ rec.push("Test all affected modules");
623
+ } else {
624
+ rec.push("Standard PR process is sufficient");
625
+ rec.push("Unit tests for the changed node are enough");
626
+ }
627
+ if (breakingCount > 0) {
628
+ rec.push(`${breakingCount} breaking change(s) must be updated before deploying`);
629
+ }
630
+ return rec;
631
+ }
632
+ function emptyReport(targetName, changeDescription, reason) {
633
+ return {
634
+ targetNode: targetName,
635
+ changeDescription,
636
+ riskScore: 0,
637
+ riskLevel: "LOW",
638
+ affectedNodes: [],
639
+ breakingChanges: [],
640
+ testingPlan: [`Could not simulate: ${reason}`],
641
+ recommendations: ["Verify the node name and try again"]
642
+ };
643
+ }
644
+ }
645
+ });
646
+
647
+ // dist/stages/output.js
648
+ var require_output = __commonJS({
649
+ "dist/stages/output.js"(exports2) {
650
+ "use strict";
651
+ var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
652
+ return mod && mod.__esModule ? mod : { "default": mod };
653
+ };
654
+ Object.defineProperty(exports2, "__esModule", { value: true });
655
+ exports2.writeOutput = writeOutput;
656
+ var fs_12 = __importDefault2(require("fs"));
657
+ var path_1 = __importDefault2(require("path"));
658
+ var metrics_12 = require_metrics();
659
+ function writeOutput(graph, parsed, outputPath2, impact) {
660
+ const totalLines = parsed.reduce((sum, f) => sum + f.lines, 0);
661
+ const summary = {
662
+ totalNodes: graph.nodes.size,
663
+ totalEdges: graph.edges.length,
664
+ entryPoints: (0, metrics_12.getEntryPoints)(graph),
665
+ leafNodes: (0, metrics_12.getLeafNodes)(graph),
666
+ isolatedNodes: (0, metrics_12.getIsolatedNodes)(graph),
667
+ criticalNodes: (0, metrics_12.getCriticalNodes)(graph)
668
+ };
669
+ const output = {
670
+ meta: {
671
+ version: "1.0.0",
672
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
673
+ totalFiles: parsed.length,
674
+ totalLines
675
+ },
676
+ summary,
677
+ nodes: [...graph.nodes.values()],
678
+ // Map → Array
679
+ edges: graph.edges,
680
+ files: parsed,
681
+ impact
682
+ // optional, only if --impact was used
683
+ };
684
+ const dir = path_1.default.dirname(outputPath2);
685
+ if (!fs_12.default.existsSync(dir)) {
686
+ fs_12.default.mkdirSync(dir, { recursive: true });
687
+ }
688
+ fs_12.default.writeFileSync(
689
+ outputPath2,
690
+ JSON.stringify(output, null, 2),
691
+ // 2 = pretty print with 2 spaces
692
+ "utf-8"
693
+ );
694
+ console.log(`
695
+ \u2705 Output written to ${outputPath2}`);
696
+ console.log(` ${output.meta.totalFiles} files`);
697
+ console.log(` ${summary.totalNodes} nodes`);
698
+ console.log(` ${summary.totalEdges} edges`);
699
+ console.log(` ${totalLines} total lines of code`);
700
+ if (impact) {
701
+ console.log(`
702
+ \u{1F4A5} Impact Report included`);
703
+ console.log(` Target : ${impact.targetNode}`);
704
+ console.log(` Risk Level : ${impact.riskLevel}`);
705
+ console.log(` Risk Score : ${impact.riskScore}`);
706
+ console.log(` Affected : ${impact.affectedNodes.length} nodes`);
707
+ }
708
+ }
709
+ }
710
+ });
711
+
712
+ // dist/main.js
713
+ var __importDefault = exports && exports.__importDefault || function(mod) {
714
+ return mod && mod.__esModule ? mod : { "default": mod };
715
+ };
716
+ Object.defineProperty(exports, "__esModule", { value: true });
717
+ require_javascript();
718
+ var fs_1 = __importDefault(require("fs"));
719
+ var collector_1 = require_collector();
720
+ var parser_1 = require_parser();
721
+ var graph_1 = require_graph();
722
+ var metrics_1 = require_metrics();
723
+ var impact_1 = require_impact();
724
+ var output_1 = require_output();
725
+ var args = process.argv.slice(2);
726
+ var noColor = args.includes("--no-color");
727
+ var verbose = args.includes("--verbose");
728
+ function color(text, code) {
729
+ if (noColor)
730
+ return text;
731
+ return `\x1B[${code}m${text}\x1B[0m`;
732
+ }
733
+ var dim = (t) => color(t, "2");
734
+ var bold = (t) => color(t, "1");
735
+ var green = (t) => color(t, "32");
736
+ var yellow = (t) => color(t, "33");
737
+ var red = (t) => color(t, "31");
738
+ var cyan = (t) => color(t, "36");
739
+ function getFlag(flag) {
740
+ const idx = args.indexOf(flag);
741
+ return idx !== -1 ? args[idx + 1] : void 0;
742
+ }
743
+ function printHelp() {
744
+ console.log(`
745
+ ${bold("DepGraph Compiler")} ${dim("v1.0.0")}
746
+ ${dim("Dependency mapping \xB7 Impact simulation \xB7 Developer intelligence")}
747
+
748
+ ${bold("USAGE")}
749
+ node depgraph.js ${cyan("<projectDir>")} ${dim("[options]")}
750
+
751
+ ${bold("OPTIONS")}
752
+ ${cyan("--output")} ${dim("<file>")} Output path ${dim("(default: ./depgraph-output.json)")}
753
+ ${cyan("--impact")} ${dim("<name> <desc>")} Simulate changing a node
754
+ ${cyan("--verbose")} Show per-file parsing details
755
+ ${cyan("--no-color")} Disable colors ${dim("(for CI)")}
756
+ ${cyan("--help, -h")} Show this help message
757
+
758
+ ${bold("EXAMPLES")}
759
+ ${dim("# Map a project")}
760
+ node depgraph.js ./my-app
761
+
762
+ ${dim("# Map with custom output")}
763
+ node depgraph.js ./my-app --output ./reports/graph.json
764
+
765
+ ${dim("# Simulate a change")}
766
+ node depgraph.js ./my-app --impact "getUserById" "removing userId param"
767
+
768
+ ${dim("# CI mode")}
769
+ node depgraph.js ./src --no-color --output ./ci/depgraph.json
770
+ `);
771
+ }
772
+ function printBanner() {
773
+ console.log(`
774
+ ${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")}
776
+ ${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
+ `);
778
+ }
779
+ function printSummary(fileCount, nodeCount, edgeCount, criticalNodes) {
780
+ console.log(bold("\u{1F4CA} Graph Summary"));
781
+ console.log(` ${dim("Files :")} ${green(String(fileCount))}`);
782
+ console.log(` ${dim("Nodes :")} ${green(String(nodeCount))}`);
783
+ console.log(` ${dim("Edges :")} ${green(String(edgeCount))}`);
784
+ if (criticalNodes.length > 0) {
785
+ console.log(`
786
+ ${bold("\u{1F534} Critical Nodes")} ${dim("(change carefully)")}`);
787
+ for (const id of criticalNodes) {
788
+ console.log(` ${red("\u25CF")} ${id}`);
789
+ }
790
+ }
791
+ }
792
+ function printImpact(impact) {
793
+ const levelColor = impact.riskLevel === "CRITICAL" ? red : impact.riskLevel === "HIGH" ? yellow : impact.riskLevel === "MEDIUM" ? cyan : green;
794
+ console.log(`
795
+ ${bold("\u{1F4A5} Impact Simulation")}`);
796
+ console.log(` ${dim("Target :")} ${bold(impact.targetNode)}`);
797
+ console.log(` ${dim("Change :")} ${impact.changeDescription}`);
798
+ console.log(` ${dim("Risk Score :")} ${levelColor(String(impact.riskScore))}`);
799
+ console.log(` ${dim("Risk Level :")} ${bold(levelColor(impact.riskLevel))}`);
800
+ if (impact.affectedNodes.length === 0) {
801
+ console.log(`
802
+ ${green("\u2713")} No affected nodes found`);
803
+ } else {
804
+ console.log(`
805
+ ${bold(`\u{1F4CB} Affected Nodes (${impact.affectedNodes.length})`)}`);
806
+ for (const node of impact.affectedNodes) {
807
+ const impColor = node.impact === "critical" ? red : node.impact === "high" ? yellow : node.impact === "medium" ? cyan : green;
808
+ console.log(`
809
+ ${impColor(`[${node.impact.toUpperCase().padEnd(8)}]`)} ${bold(node.name)}`);
810
+ console.log(` ${dim("file :")} ${node.file}`);
811
+ console.log(` ${dim("reason :")} ${node.reason}`);
812
+ console.log(` ${dim("action :")} ${node.changeRequired}`);
813
+ console.log(` ${dim("breaking:")} ${node.breakingChange ? red("YES") : green("no")}`);
814
+ }
815
+ }
816
+ console.log(`
817
+ ${bold("\u{1F9EA} Testing Plan")}`);
818
+ for (const item of impact.testingPlan) {
819
+ console.log(` ${dim("\u2192")} ${item}`);
820
+ }
821
+ console.log(`
822
+ ${bold("\u{1F4A1} Recommendations")}`);
823
+ for (const rec of impact.recommendations) {
824
+ console.log(` ${dim("\u2192")} ${rec}`);
825
+ }
826
+ }
827
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
828
+ printHelp();
829
+ process.exit(0);
830
+ }
831
+ var projectDir = args[0];
832
+ var outputPath = getFlag("--output") ?? "./depgraph-output.json";
833
+ var impactTarget = getFlag("--impact");
834
+ var impactDesc = impactTarget ? args[args.indexOf("--impact") + 2] ?? "no description provided" : void 0;
835
+ printBanner();
836
+ console.log(`${bold("\u{1F50D} Scanning")} ${cyan(projectDir)}
837
+ `);
838
+ if (!fs_1.default.existsSync(projectDir)) {
839
+ console.error(red(`\u2717 Directory not found: ${projectDir}`));
840
+ process.exit(1);
841
+ }
842
+ try {
843
+ const files = (0, collector_1.collectFiles)(projectDir);
844
+ if (verbose) {
845
+ files.forEach((f) => console.log(dim(` ${f}`)));
846
+ }
847
+ const parsed = (0, parser_1.parseFiles)(files);
848
+ if (verbose) {
849
+ parsed.forEach((f) => console.log(dim(` parsed: ${f.filePath} \u2192 ${f.entities.length} entities`)));
850
+ }
851
+ const graph = (0, graph_1.buildGraph)(parsed);
852
+ const metrics = (0, metrics_1.computeMetrics)(graph);
853
+ printSummary(files.length, metrics.nodes.size, metrics.edges.length, (0, metrics_1.getCriticalNodes)(metrics));
854
+ let impact = void 0;
855
+ if (impactTarget) {
856
+ impact = (0, impact_1.simulateImpact)(metrics, impactTarget, impactDesc ?? "");
857
+ printImpact(impact);
858
+ }
859
+ (0, output_1.writeOutput)(metrics, parsed, outputPath, impact);
860
+ } catch (err) {
861
+ console.error(red(`
862
+ \u2717 Error: ${err.message}`));
863
+ process.exit(1);
864
+ }