arcvision 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,119 +1,99 @@
1
1
  {
2
2
  "schema_version": "1.0.0",
3
- "generated_at": "2026-01-06T04:33:19.544Z",
3
+ "generated_at": "2026-01-06T06:57:00.243Z",
4
4
  "system": {
5
- "name": "engine",
6
- "root_path": "C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\engine",
5
+ "name": "plugins",
6
+ "root_path": "C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\plugins",
7
7
  "language": "javascript"
8
8
  },
9
9
  "nodes": [
10
10
  {
11
- "id": "18f3b408c9f517b4",
11
+ "id": "9788ca3218e16ddf",
12
12
  "type": "file",
13
- "path": "id-generator.js",
14
- "role": "Implementation",
13
+ "path": "express-plugin.js",
14
+ "role": "Structure",
15
15
  "dependencies": [
16
- "crypto"
16
+ "@babel/parser",
17
+ "@babel/traverse",
18
+ "fs"
17
19
  ],
18
- "blast_radius": 1
19
- },
20
- {
21
- "id": "906fe6a206ee8ed6",
22
- "type": "file",
23
- "path": "context_sorter.js",
24
- "role": "Implementation",
25
- "dependencies": [],
26
20
  "blast_radius": 0
27
21
  },
28
22
  {
29
- "id": "a90facc61fceac47",
23
+ "id": "d3f4923fd01ce58d",
30
24
  "type": "file",
31
- "path": "context_builder.js",
32
- "role": "Implementation",
25
+ "path": "react-plugin.js",
26
+ "role": "Structure",
33
27
  "dependencies": [
34
- "path",
35
- "./id-generator"
28
+ "@babel/parser",
29
+ "@babel/traverse",
30
+ "fs"
36
31
  ],
37
32
  "blast_radius": 0
38
33
  },
39
34
  {
40
- "id": "e23459b69864dc40",
35
+ "id": "d6515f66bafa0b42",
41
36
  "type": "file",
42
- "path": "context_validator.js",
37
+ "path": "plugin-manager.js",
43
38
  "role": "Implementation",
44
39
  "dependencies": [
45
40
  "fs",
46
- "path",
47
- "ajv"
41
+ "path"
48
42
  ],
49
43
  "blast_radius": 0
50
44
  }
51
45
  ],
52
- "edges": [
53
- {
54
- "from": "a90facc61fceac47",
55
- "to": "18f3b408c9f517b4",
56
- "relation": "imports"
57
- }
58
- ],
46
+ "edges": [],
59
47
  "metrics": {
60
- "files_with_functions": 4,
48
+ "files_with_functions": 1,
61
49
  "files_with_high_blast_radius": 0,
62
- "total_dependencies": 1,
63
- "total_edges": 1,
64
- "total_files": 4,
65
- "total_imports": 1,
66
- "total_nodes": 4
50
+ "total_dependencies": 0,
51
+ "total_edges": 0,
52
+ "total_files": 3,
53
+ "total_imports": 0,
54
+ "total_nodes": 3
67
55
  },
68
56
  "contextSurface": {
69
57
  "topBlastRadiusFiles": [
70
58
  {
71
- "file": "id-generator.js",
72
- "blastRadius": 1,
73
- "percentOfGraph": 25
59
+ "file": "express-plugin.js",
60
+ "blastRadius": 0,
61
+ "percentOfGraph": 0
74
62
  },
75
63
  {
76
- "file": "context_builder.js",
64
+ "file": "plugin-manager.js",
77
65
  "blastRadius": 0,
78
66
  "percentOfGraph": 0
79
67
  },
80
68
  {
81
- "file": "context_sorter.js",
69
+ "file": "react-plugin.js",
82
70
  "blastRadius": 0,
83
71
  "percentOfGraph": 0
84
72
  }
85
73
  ],
86
74
  "criticalityAnalysis": [
87
75
  {
88
- "file": "id-generator.js",
89
- "blastRadius": 1,
90
- "dependencies": 1,
91
- "dependencyStrength": 0,
92
- "criticalityScore": 2,
93
- "isHub": false
94
- },
95
- {
96
- "file": "context_validator.js",
76
+ "file": "react-plugin.js",
97
77
  "blastRadius": 0,
98
78
  "dependencies": 0,
99
79
  "dependencyStrength": 0,
100
- "criticalityScore": 1,
80
+ "criticalityScore": 0,
101
81
  "isHub": false
102
82
  },
103
83
  {
104
- "file": "context_sorter.js",
84
+ "file": "plugin-manager.js",
105
85
  "blastRadius": 0,
106
86
  "dependencies": 0,
107
87
  "dependencyStrength": 0,
108
- "criticalityScore": 1,
88
+ "criticalityScore": 0,
109
89
  "isHub": false
110
90
  },
111
91
  {
112
- "file": "context_builder.js",
92
+ "file": "express-plugin.js",
113
93
  "blastRadius": 0,
114
94
  "dependencies": 0,
115
95
  "dependencyStrength": 0,
116
- "criticalityScore": 1,
96
+ "criticalityScore": 0,
117
97
  "isHub": false
118
98
  }
119
99
  ]
package/dist/index.js CHANGED
@@ -56407,6 +56407,16 @@ var require_parser = __commonJS({
56407
56407
  });
56408
56408
  }
56409
56409
  });
56410
+ } else if (decl.id.type === "ArrayPattern") {
56411
+ decl.id.elements.forEach((element) => {
56412
+ if (element && element.name) {
56413
+ metadata.exports.push({
56414
+ name: element.name,
56415
+ type: "variable",
56416
+ loc: element.loc
56417
+ });
56418
+ }
56419
+ });
56410
56420
  }
56411
56421
  });
56412
56422
  }
@@ -56530,6 +56540,27 @@ var require_parser = __commonJS({
56530
56540
  loc: node.loc
56531
56541
  });
56532
56542
  }
56543
+ if (node.callee.type === "Import" && node.arguments.length > 0) {
56544
+ const sourceArg = node.arguments[0];
56545
+ if (sourceArg && sourceArg.type === "StringLiteral") {
56546
+ metadata.imports.push({
56547
+ source: sourceArg.value,
56548
+ specifiers: [],
56549
+ type: "dynamic-import"
56550
+ });
56551
+ }
56552
+ }
56553
+ },
56554
+ VariableDeclarator({ node }) {
56555
+ if (node.init && node.init.type === "CallExpression" && node.init.callee.name === "require" && node.init.arguments.length > 0 && node.init.arguments[0].type === "StringLiteral") {
56556
+ metadata.imports.push({
56557
+ source: node.init.arguments[0].value,
56558
+ specifiers: [],
56559
+ type: "require-assignment"
56560
+ });
56561
+ }
56562
+ },
56563
+ MemberExpression({ node }) {
56533
56564
  }
56534
56565
  });
56535
56566
  return metadata;
@@ -56635,13 +56666,13 @@ var require_path_resolver = __commonJS({
56635
56666
  }
56636
56667
  if (importPath.startsWith(".")) {
56637
56668
  let resolvedPath = path2.resolve(path2.dirname(importerPath), importPath);
56638
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
56669
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
56639
56670
  for (const ext of extensions) {
56640
56671
  if (fs2.existsSync(resolvedPath + ext)) {
56641
56672
  return resolvedPath + ext;
56642
56673
  }
56643
56674
  }
56644
- const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
56675
+ const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
56645
56676
  for (const idxFile of indexFiles) {
56646
56677
  const indexPath = path2.join(resolvedPath, idxFile);
56647
56678
  if (fs2.existsSync(indexPath)) {
@@ -56665,13 +56696,13 @@ var require_path_resolver = __commonJS({
56665
56696
  for (const target of aliasTargets) {
56666
56697
  const targetBase = target.replace(/\*.*$/, "");
56667
56698
  const targetPath = path2.resolve(projectRoot, targetBase) + suffix;
56668
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
56699
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
56669
56700
  for (const ext of extensions) {
56670
56701
  if (fs2.existsSync(targetPath + ext)) {
56671
56702
  return targetPath + ext;
56672
56703
  }
56673
56704
  }
56674
- const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
56705
+ const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
56675
56706
  for (const idxFile of indexFiles) {
56676
56707
  const indexPath = path2.join(targetPath, idxFile);
56677
56708
  if (fs2.existsSync(indexPath)) {
@@ -56691,13 +56722,13 @@ var require_path_resolver = __commonJS({
56691
56722
  const remainingPath = importPath.substring(aliasPattern.length);
56692
56723
  targetPath = path2.join(targetPath, remainingPath);
56693
56724
  }
56694
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
56725
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
56695
56726
  for (const ext of extensions) {
56696
56727
  if (fs2.existsSync(targetPath + ext)) {
56697
56728
  return targetPath + ext;
56698
56729
  }
56699
56730
  }
56700
- const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
56731
+ const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
56701
56732
  for (const idxFile of indexFiles) {
56702
56733
  const indexPath = path2.join(targetPath, idxFile);
56703
56734
  if (fs2.existsSync(indexPath)) {
@@ -56714,13 +56745,13 @@ var require_path_resolver = __commonJS({
56714
56745
  }
56715
56746
  if (tsconfig && tsconfig.baseUrl && !importPath.startsWith(".")) {
56716
56747
  let resolvedPath = path2.resolve(projectRoot, tsconfig.baseUrl, importPath);
56717
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
56748
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
56718
56749
  for (const ext of extensions) {
56719
56750
  if (fs2.existsSync(resolvedPath + ext)) {
56720
56751
  return resolvedPath + ext;
56721
56752
  }
56722
56753
  }
56723
- const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
56754
+ const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
56724
56755
  for (const idxFile of indexFiles) {
56725
56756
  const indexPath = path2.join(resolvedPath, idxFile);
56726
56757
  if (fs2.existsSync(indexPath)) {
@@ -56927,10 +56958,14 @@ var require_context_builder = __commonJS({
56927
56958
  role = "Interface";
56928
56959
  }
56929
56960
  const dependencies = [];
56961
+ const uniqueDeps = /* @__PURE__ */ new Set();
56930
56962
  if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
56931
56963
  file.metadata.imports.forEach((imp) => {
56932
56964
  if (imp.source && typeof imp.source === "string") {
56933
- dependencies.push(imp.source);
56965
+ if (!uniqueDeps.has(imp.source)) {
56966
+ uniqueDeps.add(imp.source);
56967
+ dependencies.push(imp.source);
56968
+ }
56934
56969
  }
56935
56970
  });
56936
56971
  }
@@ -56948,10 +56983,20 @@ var require_context_builder = __commonJS({
56948
56983
  const sourceNode = nodes.find((n) => n.path === edge.source);
56949
56984
  const targetNode = nodes.find((n) => n.path === edge.target);
56950
56985
  if (sourceNode && targetNode) {
56986
+ let relationType = "imports";
56987
+ if (edge.type === "imports" || edge.type === "require" || edge.type === "export-from" || edge.type === "export-all" || edge.type === "dynamic-import" || edge.type === "require-assignment") {
56988
+ relationType = "imports";
56989
+ } else if (edge.type === "calls") {
56990
+ relationType = "calls";
56991
+ } else if (edge.type === "owns") {
56992
+ relationType = "owns";
56993
+ } else if (edge.type === "depends_on") {
56994
+ relationType = "depends_on";
56995
+ }
56951
56996
  schemaEdges.push({
56952
56997
  from: sourceNode.id,
56953
56998
  to: targetNode.id,
56954
- relation: "imports"
56999
+ relation: relationType
56955
57000
  });
56956
57001
  }
56957
57002
  }
@@ -63599,34 +63644,59 @@ var require_scanner = __commonJS({
63599
63644
  }
63600
63645
  }
63601
63646
  const tsconfig = loadTSConfig(directory);
63602
- const normalizedFileMap = /* @__PURE__ */ new Map();
63603
- architectureMap.nodes.forEach((n) => normalizedFileMap.set(normalize(n.id), n.id));
63647
+ const pathToNodeIdMap = /* @__PURE__ */ new Map();
63604
63648
  architectureMap.nodes.forEach((node) => {
63605
- node.metadata.imports.forEach((imp) => {
63606
- const resolvedPath = resolveImport(
63607
- imp.source,
63608
- path2.join(directory, node.id),
63609
- directory,
63610
- tsconfig
63611
- );
63612
- if (resolvedPath) {
63613
- const relativeResolvedPath = path2.relative(directory, resolvedPath);
63614
- const normalizedResolvedPath = normalize(relativeResolvedPath);
63615
- if (normalizedFileMap.has(normalizedResolvedPath)) {
63616
- architectureMap.edges.push({
63617
- source: normalize(node.id),
63618
- target: normalizedResolvedPath,
63619
- type: "import"
63620
- });
63649
+ const normalizedPath = normalize(node.id);
63650
+ pathToNodeIdMap.set(normalizedPath, node.id);
63651
+ });
63652
+ architectureMap.nodes.forEach((node) => {
63653
+ if (node.metadata.imports && Array.isArray(node.metadata.imports)) {
63654
+ node.metadata.imports.forEach((imp) => {
63655
+ if (imp.source && typeof imp.source === "string") {
63656
+ const resolvedPath = resolveImport(
63657
+ imp.source,
63658
+ path2.join(directory, node.id),
63659
+ directory,
63660
+ tsconfig
63661
+ );
63662
+ if (resolvedPath) {
63663
+ const relativeResolvedPath = path2.relative(directory, resolvedPath);
63664
+ const normalizedResolvedPath = normalize(relativeResolvedPath);
63665
+ if (pathToNodeIdMap.has(normalizedResolvedPath)) {
63666
+ architectureMap.edges.push({
63667
+ source: normalize(node.id),
63668
+ target: normalizedResolvedPath,
63669
+ type: imp.type || "import"
63670
+ });
63671
+ }
63672
+ }
63621
63673
  }
63622
- }
63623
- });
63674
+ });
63675
+ }
63624
63676
  });
63625
63677
  const reverseGraph = buildReverseDependencyGraph(architectureMap);
63626
63678
  const blastRadiusMap = computeBlastRadius(reverseGraph);
63627
63679
  architectureMap.nodes.forEach((node) => {
63628
63680
  node.metadata.blast_radius = blastRadiusMap[node.id] || 0;
63629
63681
  });
63682
+ const validEdges = [];
63683
+ const edgeSet = /* @__PURE__ */ new Set();
63684
+ architectureMap.edges.forEach((edge) => {
63685
+ const normalizedSource = normalize(edge.source);
63686
+ const normalizedTarget = normalize(edge.target);
63687
+ const edgeKey = `${normalizedSource}\u2192${normalizedTarget}`;
63688
+ const sourceNodeExists = architectureMap.nodes.some((node) => node.id === normalizedSource);
63689
+ const targetNodeExists = architectureMap.nodes.some((node) => node.id === normalizedTarget);
63690
+ if (sourceNodeExists && targetNodeExists && !edgeSet.has(edgeKey)) {
63691
+ edgeSet.add(edgeKey);
63692
+ validEdges.push({
63693
+ source: normalizedSource,
63694
+ target: normalizedTarget,
63695
+ type: edge.type
63696
+ });
63697
+ }
63698
+ });
63699
+ architectureMap.edges = validEdges;
63630
63700
  const totalFiles = architectureMap.nodes.length;
63631
63701
  const { computeBlastRadiusWithPercentage: computeBlastRadiusWithPercentage2, analyzeCriticality } = require_blastRadius();
63632
63702
  const allFilesWithPercentage = computeBlastRadiusWithPercentage2(blastRadiusMap, totalFiles);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcvision",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Architecture scanner for modern codebases",
5
5
  "bin": {
6
6
  "arcvision": "./dist/index.js"
@@ -159,6 +159,17 @@ function parseFile(filePath) {
159
159
  });
160
160
  }
161
161
  });
162
+ } else if (decl.id.type === 'ArrayPattern') {
163
+ // Handle array destructuring exports: export const [a, b] = arr
164
+ decl.id.elements.forEach(element => {
165
+ if (element && element.name) {
166
+ metadata.exports.push({
167
+ name: element.name,
168
+ type: 'variable',
169
+ loc: element.loc
170
+ });
171
+ }
172
+ });
162
173
  }
163
174
  });
164
175
  }
@@ -293,6 +304,36 @@ function parseFile(filePath) {
293
304
  loc: node.loc
294
305
  });
295
306
  }
307
+
308
+ // Handle other common import patterns like import() in function calls
309
+ if (node.callee.type === 'Import' && node.arguments.length > 0) {
310
+ const sourceArg = node.arguments[0];
311
+ if (sourceArg && sourceArg.type === 'StringLiteral') {
312
+ metadata.imports.push({
313
+ source: sourceArg.value,
314
+ specifiers: [],
315
+ type: 'dynamic-import'
316
+ });
317
+ }
318
+ }
319
+ },
320
+
321
+ VariableDeclarator({ node }) {
322
+ // Handle import assignments like: const { something } = require('module')
323
+ if (node.init && node.init.type === 'CallExpression' &&
324
+ node.init.callee.name === 'require' &&
325
+ node.init.arguments.length > 0 &&
326
+ node.init.arguments[0].type === 'StringLiteral') {
327
+ metadata.imports.push({
328
+ source: node.init.arguments[0].value,
329
+ specifiers: [],
330
+ type: 'require-assignment'
331
+ });
332
+ }
333
+ },
334
+
335
+ MemberExpression({ node }) {
336
+ // Additional member expression handling for complex import patterns
296
337
  }
297
338
  });
298
339
 
@@ -23,7 +23,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
23
23
  let resolvedPath = path.resolve(path.dirname(importerPath), importPath);
24
24
 
25
25
  // Try with various extensions in order of preference
26
- const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
26
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
27
27
  for (const ext of extensions) {
28
28
  if (fs.existsSync(resolvedPath + ext)) {
29
29
  return resolvedPath + ext;
@@ -31,7 +31,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
31
31
  }
32
32
 
33
33
  // Try as directory with index files
34
- const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
34
+ const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
35
35
  for (const idxFile of indexFiles) {
36
36
  const indexPath = path.join(resolvedPath, idxFile);
37
37
  if (fs.existsSync(indexPath)) {
@@ -69,7 +69,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
69
69
  const targetPath = path.resolve(projectRoot, targetBase) + suffix;
70
70
 
71
71
  // Try with extensions
72
- const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
72
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
73
73
  for (const ext of extensions) {
74
74
  if (fs.existsSync(targetPath + ext)) {
75
75
  return targetPath + ext;
@@ -77,7 +77,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
77
77
  }
78
78
 
79
79
  // Try as directory with index files
80
- const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
80
+ const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
81
81
  for (const idxFile of indexFiles) {
82
82
  const indexPath = path.join(targetPath, idxFile);
83
83
  if (fs.existsSync(indexPath)) {
@@ -104,7 +104,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
104
104
  }
105
105
 
106
106
  // Try with extensions
107
- const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
107
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
108
108
  for (const ext of extensions) {
109
109
  if (fs.existsSync(targetPath + ext)) {
110
110
  return targetPath + ext;
@@ -112,7 +112,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
112
112
  }
113
113
 
114
114
  // Try as directory with index files
115
- const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
115
+ const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
116
116
  for (const idxFile of indexFiles) {
117
117
  const indexPath = path.join(targetPath, idxFile);
118
118
  if (fs.existsSync(indexPath)) {
@@ -135,7 +135,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
135
135
  let resolvedPath = path.resolve(projectRoot, tsconfig.baseUrl, importPath);
136
136
 
137
137
  // Try with extensions
138
- const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
138
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
139
139
  for (const ext of extensions) {
140
140
  if (fs.existsSync(resolvedPath + ext)) {
141
141
  return resolvedPath + ext;
@@ -143,7 +143,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
143
143
  }
144
144
 
145
145
  // Try as directory with index files
146
- const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
146
+ const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
147
147
  for (const idxFile of indexFiles) {
148
148
  const indexPath = path.join(resolvedPath, idxFile);
149
149
  if (fs.existsSync(indexPath)) {
@@ -88,34 +88,43 @@ async function scan(directory) {
88
88
  // Load tsconfig for path resolution
89
89
  const tsconfig = loadTSConfig(directory);
90
90
 
91
- const normalizedFileMap = new Map();
92
- architectureMap.nodes.forEach(n => normalizedFileMap.set(normalize(n.id), n.id));
91
+ // Create a map of normalized relative paths to actual node IDs
92
+ const pathToNodeIdMap = new Map();
93
+ architectureMap.nodes.forEach(node => {
94
+ const normalizedPath = normalize(node.id);
95
+ pathToNodeIdMap.set(normalizedPath, node.id);
96
+ });
93
97
 
98
+ // Process imports to create edges
94
99
  architectureMap.nodes.forEach(node => {
95
- node.metadata.imports.forEach(imp => {
96
- // Resolve the import path using the new resolver
97
- const resolvedPath = resolveImport(
98
- imp.source,
99
- path.join(directory, node.id),
100
- directory,
101
- tsconfig
102
- );
103
-
104
- if (resolvedPath) {
105
- // Convert resolved absolute path back to relative path
106
- const relativeResolvedPath = path.relative(directory, resolvedPath);
107
- const normalizedResolvedPath = normalize(relativeResolvedPath);
108
-
109
- // Check if the resolved file exists in our scanned files
110
- if (normalizedFileMap.has(normalizedResolvedPath)) {
111
- architectureMap.edges.push({
112
- source: normalize(node.id),
113
- target: normalizedResolvedPath,
114
- type: 'import'
115
- });
100
+ if (node.metadata.imports && Array.isArray(node.metadata.imports)) {
101
+ node.metadata.imports.forEach(imp => {
102
+ if (imp.source && typeof imp.source === 'string') {
103
+ // Resolve the import path using the new resolver
104
+ const resolvedPath = resolveImport(
105
+ imp.source,
106
+ path.join(directory, node.id),
107
+ directory,
108
+ tsconfig
109
+ );
110
+
111
+ if (resolvedPath) {
112
+ // Convert resolved absolute path back to relative path
113
+ const relativeResolvedPath = path.relative(directory, resolvedPath);
114
+ const normalizedResolvedPath = normalize(relativeResolvedPath);
115
+
116
+ // Check if the resolved file exists in our scanned files
117
+ if (pathToNodeIdMap.has(normalizedResolvedPath)) {
118
+ architectureMap.edges.push({
119
+ source: normalize(node.id),
120
+ target: normalizedResolvedPath,
121
+ type: imp.type || 'import'
122
+ });
123
+ }
124
+ }
116
125
  }
117
- }
118
- });
126
+ });
127
+ }
119
128
  });
120
129
 
121
130
  // Calculate blast radius for each file
@@ -127,6 +136,35 @@ async function scan(directory) {
127
136
  node.metadata.blast_radius = blastRadiusMap[node.id] || 0;
128
137
  });
129
138
 
139
+ // Process edges to ensure they match existing nodes and remove duplicates
140
+ const validEdges = [];
141
+ const edgeSet = new Set(); // To track unique edges
142
+
143
+ architectureMap.edges.forEach(edge => {
144
+ // Normalize both source and target paths for consistent matching
145
+ const normalizedSource = normalize(edge.source);
146
+ const normalizedTarget = normalize(edge.target);
147
+
148
+ // Create a unique key for the edge to avoid duplicates
149
+ const edgeKey = `${normalizedSource}→${normalizedTarget}`;
150
+
151
+ // Check if both source and target nodes exist in our architecture map
152
+ const sourceNodeExists = architectureMap.nodes.some(node => node.id === normalizedSource);
153
+ const targetNodeExists = architectureMap.nodes.some(node => node.id === normalizedTarget);
154
+
155
+ if (sourceNodeExists && targetNodeExists && !edgeSet.has(edgeKey)) {
156
+ edgeSet.add(edgeKey);
157
+ validEdges.push({
158
+ source: normalizedSource,
159
+ target: normalizedTarget,
160
+ type: edge.type
161
+ });
162
+ }
163
+ });
164
+
165
+ // Replace the edges with only valid ones
166
+ architectureMap.edges = validEdges;
167
+
130
168
  // Add top blast radius files to the architecture map
131
169
  const totalFiles = architectureMap.nodes.length;
132
170
  const { computeBlastRadiusWithPercentage, analyzeCriticality } = require('./blastRadius');
@@ -33,12 +33,17 @@ function buildContext(files, edges, options = {}) {
33
33
  role = 'Interface';
34
34
  }
35
35
 
36
- // Extract dependencies more accurately
36
+ // Extract dependencies more accurately and remove duplicates
37
37
  const dependencies = [];
38
+ const uniqueDeps = new Set();
38
39
  if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
39
40
  file.metadata.imports.forEach(imp => {
40
41
  if (imp.source && typeof imp.source === 'string') {
41
- dependencies.push(imp.source);
42
+ // Only add unique dependencies
43
+ if (!uniqueDeps.has(imp.source)) {
44
+ uniqueDeps.add(imp.source);
45
+ dependencies.push(imp.source);
46
+ }
42
47
  }
43
48
  });
44
49
  }
@@ -62,10 +67,22 @@ function buildContext(files, edges, options = {}) {
62
67
 
63
68
  // Only include edges where both source and target nodes exist
64
69
  if (sourceNode && targetNode) {
70
+ // Normalize the edge type to fit the schema
71
+ let relationType = 'imports';
72
+ if (edge.type === 'imports' || edge.type === 'require' || edge.type === 'export-from' || edge.type === 'export-all' || edge.type === 'dynamic-import' || edge.type === 'require-assignment') {
73
+ relationType = 'imports';
74
+ } else if (edge.type === 'calls') {
75
+ relationType = 'calls';
76
+ } else if (edge.type === 'owns') {
77
+ relationType = 'owns';
78
+ } else if (edge.type === 'depends_on') {
79
+ relationType = 'depends_on';
80
+ }
81
+
65
82
  schemaEdges.push({
66
83
  from: sourceNode.id,
67
84
  to: targetNode.id,
68
- relation: 'imports'
85
+ relation: relationType
69
86
  });
70
87
  }
71
88
  }