arcvision 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,12 +35,15 @@ Scan the current directory and generate architecture map.
35
35
  Options:
36
36
  - `-u, --upload`: Upload to database
37
37
 
38
+ The scan command will also output a blast radius analysis showing the file with the highest blast radius and its potential impact on your codebase.
39
+
38
40
  ## Features
39
41
 
40
42
  - **Modern Import Resolution**: Handles path aliases (`@/components/*`), barrel files (`./utils` → `./utils/index.ts`), and directory imports
41
43
  - **AST-based Parsing**: Uses Babel parser for accurate import detection
42
44
  - **Multi-framework Support**: Works with React, Next.js, and other modern JavaScript frameworks
43
45
  - **Dependency Graph Generation**: Creates comprehensive node-edge relationships
46
+ - **Blast Radius Analysis**: Identifies high-risk files that are imported by many other files
44
47
  - **Cloud Integration**: Uploads results to ArcVision dashboard for visualization
45
48
 
46
49
  ## Supported Import Patterns
package/dist/index.js CHANGED
@@ -56514,6 +56514,58 @@ var require_path_resolver = __commonJS({
56514
56514
  }
56515
56515
  });
56516
56516
 
56517
+ // src/core/blastRadius.js
56518
+ var require_blastRadius = __commonJS({
56519
+ "src/core/blastRadius.js"(exports2, module2) {
56520
+ function buildReverseDependencyGraph(architectureMap) {
56521
+ const reverseGraph = {};
56522
+ architectureMap.nodes.forEach((node) => {
56523
+ const filePath = node.id;
56524
+ if (!reverseGraph[filePath]) {
56525
+ reverseGraph[filePath] = [];
56526
+ }
56527
+ });
56528
+ architectureMap.edges.forEach((edge) => {
56529
+ const importingFile = edge.source;
56530
+ const importedFile = edge.target;
56531
+ if (!reverseGraph[importedFile]) {
56532
+ reverseGraph[importedFile] = [];
56533
+ }
56534
+ if (!reverseGraph[importedFile].includes(importingFile)) {
56535
+ reverseGraph[importedFile].push(importingFile);
56536
+ }
56537
+ });
56538
+ return reverseGraph;
56539
+ }
56540
+ function computeBlastRadius(reverseGraph) {
56541
+ const blastRadiusMap = {};
56542
+ for (const [filePath, importingFiles] of Object.entries(reverseGraph)) {
56543
+ blastRadiusMap[filePath] = importingFiles.length;
56544
+ }
56545
+ return blastRadiusMap;
56546
+ }
56547
+ function findHighestBlastRadius2(blastRadiusMap) {
56548
+ if (Object.keys(blastRadiusMap).length === 0) {
56549
+ return null;
56550
+ }
56551
+ let maxFile = null;
56552
+ let maxRadius = -1;
56553
+ for (const [filePath, radius] of Object.entries(blastRadiusMap)) {
56554
+ if (radius > maxRadius) {
56555
+ maxRadius = radius;
56556
+ maxFile = { file: filePath, blast_radius: radius };
56557
+ }
56558
+ }
56559
+ return maxFile;
56560
+ }
56561
+ module2.exports = {
56562
+ buildReverseDependencyGraph,
56563
+ computeBlastRadius,
56564
+ findHighestBlastRadius: findHighestBlastRadius2
56565
+ };
56566
+ }
56567
+ });
56568
+
56517
56569
  // src/core/scanner.js
56518
56570
  var require_scanner = __commonJS({
56519
56571
  "src/core/scanner.js"(exports2, module2) {
@@ -56523,6 +56575,7 @@ var require_scanner = __commonJS({
56523
56575
  var pluginManager = require_plugin_manager();
56524
56576
  var { loadTSConfig } = require_tsconfig_utils();
56525
56577
  var { resolveImport } = require_path_resolver();
56578
+ var { buildReverseDependencyGraph, computeBlastRadius } = require_blastRadius();
56526
56579
  async function scan(directory) {
56527
56580
  const normalize = (p) => p.replace(/\\/g, "/");
56528
56581
  const options = {
@@ -56580,6 +56633,11 @@ var require_scanner = __commonJS({
56580
56633
  }
56581
56634
  });
56582
56635
  });
56636
+ const reverseGraph = buildReverseDependencyGraph(architectureMap);
56637
+ const blastRadiusMap = computeBlastRadius(reverseGraph);
56638
+ architectureMap.nodes.forEach((node) => {
56639
+ node.metadata.blast_radius = blastRadiusMap[node.id] || 0;
56640
+ });
56583
56641
  return architectureMap;
56584
56642
  } catch (err) {
56585
56643
  throw err;
@@ -56606,6 +56664,20 @@ try {
56606
56664
  }
56607
56665
  var CONFIG_FILE = path.join(os.homedir(), ".arcvisionrc");
56608
56666
  var API_URL = process.env.ARCVISION_API_URL || "https://arcvisiondev.vercel.app";
56667
+ var { findHighestBlastRadius } = require_blastRadius();
56668
+ function analyzeBlastRadius(architectureMap) {
56669
+ const blastRadiusData = [];
56670
+ architectureMap.nodes.forEach((node) => {
56671
+ blastRadiusData.push({
56672
+ file: node.id,
56673
+ blast_radius: node.metadata.blast_radius || 0
56674
+ });
56675
+ });
56676
+ return findHighestBlastRadius(blastRadiusData.reduce((acc, item) => {
56677
+ acc[item.file] = item.blast_radius;
56678
+ return acc;
56679
+ }, {}));
56680
+ }
56609
56681
  function saveToken(token) {
56610
56682
  try {
56611
56683
  fs.writeFileSync(CONFIG_FILE, JSON.stringify({ token }));
@@ -56643,7 +56715,7 @@ async function uploadToDatabase(jsonData) {
56643
56715
  try {
56644
56716
  console.log(chalk.blue(`Uploading to ${API_URL}/api/upload...`));
56645
56717
  const controller = new AbortController();
56646
- const timeoutId = setTimeout(() => controller.abort(), 3e4);
56718
+ const timeoutId = setTimeout(() => controller.abort(), 6e4);
56647
56719
  const response = await fetch(`${API_URL}/api/upload`, {
56648
56720
  method: "POST",
56649
56721
  headers: {
@@ -56732,6 +56804,13 @@ program.command("scan").description("Scan the current directory and generate arc
56732
56804
  try {
56733
56805
  const map = await scanner.scan(targetDir);
56734
56806
  console.log(chalk.green("Scan complete!"));
56807
+ const blastRadiusInfo = analyzeBlastRadius(map);
56808
+ if (blastRadiusInfo && blastRadiusInfo.blast_radius > 0) {
56809
+ console.log(`
56810
+ \u26A0\uFE0F ${blastRadiusInfo.file} has the highest blast radius (${blastRadiusInfo.blast_radius}). Changes here may affect many parts of the system.`);
56811
+ } else {
56812
+ console.log("\nNo high-risk files detected based on import dependencies.");
56813
+ }
56735
56814
  if (options.upload) {
56736
56815
  await uploadToDatabase(map);
56737
56816
  } else {
@@ -0,0 +1,76 @@
1
+ # Blast Radius Implementation
2
+
3
+ ## Architecture
4
+
5
+ ### Files Added/Modified
6
+ 1. `src/core/blastRadius.js` - Core blast radius calculation logic
7
+ 2. `src/core/scanner.js` - Modified to include blast radius in output
8
+ 3. `src/index.js` - Modified to display blast radius insight
9
+
10
+ ### Core Functions
11
+
12
+ #### `buildReverseDependencyGraph(architectureMap)`
13
+ - Creates a reverse dependency map where keys are imported files and values are arrays of files that import them
14
+ - Takes the architecture map (with nodes and edges) as input
15
+ - Returns a map: `{ [importedFile]: [importingFile1, importingFile2, ...] }`
16
+
17
+ #### `computeBlastRadius(reverseGraph)`
18
+ - Calculates the blast radius for each file as the count of files that import it
19
+ - Takes the reverse dependency graph as input
20
+ - Returns a map: `{ [filePath]: blastRadiusNumber }`
21
+
22
+ #### `findHighestBlastRadius(blastRadiusMap)`
23
+ - Finds the file with the highest blast radius value
24
+ - Takes the blast radius map as input
25
+ - Returns an object: `{ file: filePath, blast_radius: radius }` or null
26
+
27
+ ## Data Flow
28
+
29
+ 1. **Scanning Phase**: The scanner builds the architecture map with nodes and edges as before
30
+ 2. **Blast Radius Calculation Phase**: After the architecture map is built:
31
+ - Reverse dependency graph is built from the edges
32
+ - Blast radius is computed for each file
33
+ - Blast radius values are added to each node's metadata
34
+ 3. **Output Phase**: The CLI prints the architecture map as JSON and then shows the blast radius insight
35
+
36
+ ## Data Structure Changes
37
+
38
+ ### Node Metadata
39
+ Each node in the architecture map now includes:
40
+ ```javascript
41
+ {
42
+ id: "relative/file/path.js",
43
+ type: "file",
44
+ metadata: {
45
+ imports: [...],
46
+ exports: [...],
47
+ functions: [...],
48
+ apiCalls: [...],
49
+ blast_radius: 5 // NEW FIELD
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### CLI Output
55
+ The CLI now prints an additional message after the scan:
56
+ ```
57
+ ⚠️ src/core/utils.js has the highest blast radius (5). Changes here may affect many parts of the system.
58
+ ```
59
+
60
+ ## Algorithm Complexity
61
+ - **Time Complexity**: O(V + E) where V is the number of files and E is the number of import relationships
62
+ - **Space Complexity**: O(V + E) for storing the reverse dependency graph
63
+
64
+ ## Error Handling
65
+ - Handles empty repositories gracefully
66
+ - Handles files with no imports or no dependents
67
+ - Maintains backward compatibility with existing functionality
68
+ - Preserves all existing fields in the architecture map
69
+
70
+ ## Testing Considerations
71
+ The implementation should be tested with:
72
+ - Empty repositories
73
+ - Repositories with no dependencies
74
+ - Repositories with complex dependency chains
75
+ - Repositories with circular dependencies
76
+ - Large repositories with many files
@@ -0,0 +1,44 @@
1
+ # Blast Radius Feature
2
+
3
+ ## Overview
4
+ The Blast Radius feature analyzes your codebase to identify files that are most critical to your application. It calculates how many other files depend on each file, helping you identify high-risk areas where changes could have wide-ranging impacts.
5
+
6
+ ## How It Works
7
+ - **Blast Radius Score**: For each file, the blast radius is calculated as the number of files that import it (direct dependencies only).
8
+ - **Reverse Dependency Graph**: The system builds a reverse dependency graph to track which files import each file.
9
+ - **Risk Assessment**: Files with higher blast radius scores are considered higher risk because changes to them could affect many other parts of the system.
10
+
11
+ ## Output
12
+ The blast radius score is added to each file's metadata in the architecture map:
13
+
14
+ ```json
15
+ {
16
+ "nodes": [
17
+ {
18
+ "id": "src/auth/session.ts",
19
+ "type": "file",
20
+ "metadata": {
21
+ "imports": ["src/db/client.ts", "src/utils/logger.ts"],
22
+ "exports": [],
23
+ "functions": [],
24
+ "apiCalls": [],
25
+ "blast_radius": 2
26
+ }
27
+ }
28
+ ],
29
+ "edges": [...]
30
+ }
31
+ ```
32
+
33
+ ## CLI Output
34
+ After scanning, the CLI will display a warning for the file with the highest blast radius:
35
+
36
+ ```
37
+ ⚠️ src/auth/session.ts has the highest blast radius (2). Changes here may affect many parts of the system.
38
+ ```
39
+
40
+ ## Use Cases
41
+ - Identify critical files that require extra care during refactoring
42
+ - Understand the potential impact of code changes
43
+ - Prioritize code review efforts for high-risk files
44
+ - Analyze architectural dependencies in your codebase
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcvision",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Architecture scanner for modern codebases",
5
5
  "bin": {
6
6
  "arcvision": "./dist/index.js"
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Blast Radius Analysis
3
+ *
4
+ * This module computes the blast radius for each file in the architecture map.
5
+ * Blast radius is defined as the number of files that import a given file.
6
+ */
7
+
8
+ /**
9
+ * Builds a reverse dependency graph from the architecture map
10
+ * @param {Object} architectureMap - The architecture map with nodes and edges
11
+ * @returns {Object} Reverse dependency map where key is imported file and value is array of importing files
12
+ */
13
+ function buildReverseDependencyGraph(architectureMap) {
14
+ const reverseGraph = {};
15
+
16
+ // Initialize all files in the reverse graph
17
+ architectureMap.nodes.forEach(node => {
18
+ const filePath = node.id;
19
+ if (!reverseGraph[filePath]) {
20
+ reverseGraph[filePath] = [];
21
+ }
22
+ });
23
+
24
+ // Build reverse dependencies from edges
25
+ architectureMap.edges.forEach(edge => {
26
+ const importingFile = edge.source; // file that imports
27
+ const importedFile = edge.target; // file that is imported
28
+
29
+ if (!reverseGraph[importedFile]) {
30
+ reverseGraph[importedFile] = [];
31
+ }
32
+
33
+ // Add the importing file to the list of files that depend on importedFile
34
+ if (!reverseGraph[importedFile].includes(importingFile)) {
35
+ reverseGraph[importedFile].push(importingFile);
36
+ }
37
+ });
38
+
39
+ return reverseGraph;
40
+ }
41
+
42
+ /**
43
+ * Computes blast radius for each file
44
+ * @param {Object} reverseGraph - The reverse dependency graph
45
+ * @returns {Object} Map of file paths to their blast radius (number of files that import them)
46
+ */
47
+ function computeBlastRadius(reverseGraph) {
48
+ const blastRadiusMap = {};
49
+
50
+ for (const [filePath, importingFiles] of Object.entries(reverseGraph)) {
51
+ blastRadiusMap[filePath] = importingFiles.length;
52
+ }
53
+
54
+ return blastRadiusMap;
55
+ }
56
+
57
+ /**
58
+ * Finds the file with the highest blast radius
59
+ * @param {Object} blastRadiusMap - Map of file paths to their blast radius
60
+ * @returns {Object|null} Object containing the file path and blast radius, or null if no files
61
+ */
62
+ function findHighestBlastRadius(blastRadiusMap) {
63
+ if (Object.keys(blastRadiusMap).length === 0) {
64
+ return null;
65
+ }
66
+
67
+ let maxFile = null;
68
+ let maxRadius = -1;
69
+
70
+ for (const [filePath, radius] of Object.entries(blastRadiusMap)) {
71
+ if (radius > maxRadius) {
72
+ maxRadius = radius;
73
+ maxFile = { file: filePath, blast_radius: radius };
74
+ }
75
+ }
76
+
77
+ return maxFile;
78
+ }
79
+
80
+ module.exports = {
81
+ buildReverseDependencyGraph,
82
+ computeBlastRadius,
83
+ findHighestBlastRadius
84
+ };
@@ -4,6 +4,7 @@ const parser = require('./parser');
4
4
  const pluginManager = require('../plugins/plugin-manager');
5
5
  const { loadTSConfig } = require('./tsconfig-utils');
6
6
  const { resolveImport } = require('./path-resolver');
7
+ const { buildReverseDependencyGraph, computeBlastRadius } = require('./blastRadius');
7
8
 
8
9
  async function scan(directory) {
9
10
  // Normalize helper
@@ -87,6 +88,15 @@ async function scan(directory) {
87
88
  });
88
89
  });
89
90
 
91
+ // Calculate blast radius for each file
92
+ const reverseGraph = buildReverseDependencyGraph(architectureMap);
93
+ const blastRadiusMap = computeBlastRadius(reverseGraph);
94
+
95
+ // Add blast_radius to each node
96
+ architectureMap.nodes.forEach(node => {
97
+ node.metadata.blast_radius = blastRadiusMap[node.id] || 0;
98
+ });
99
+
90
100
  return architectureMap;
91
101
 
92
102
  } catch (err) {
package/src/index.js CHANGED
@@ -24,6 +24,27 @@ try {
24
24
  const CONFIG_FILE = path.join(os.homedir(), '.arcvisionrc');
25
25
  const API_URL = process.env.ARCVISION_API_URL || 'https://arcvisiondev.vercel.app';
26
26
 
27
+ // Blast radius analysis
28
+ const { findHighestBlastRadius } = require('./core/blastRadius');
29
+
30
+ function analyzeBlastRadius(architectureMap) {
31
+ // Extract blast radius information from nodes
32
+ const blastRadiusData = [];
33
+
34
+ architectureMap.nodes.forEach(node => {
35
+ blastRadiusData.push({
36
+ file: node.id,
37
+ blast_radius: node.metadata.blast_radius || 0
38
+ });
39
+ });
40
+
41
+ // Find the file with the highest blast radius
42
+ return findHighestBlastRadius(blastRadiusData.reduce((acc, item) => {
43
+ acc[item.file] = item.blast_radius;
44
+ return acc;
45
+ }, {}));
46
+ }
47
+
27
48
  function saveToken(token) {
28
49
  try {
29
50
  fs.writeFileSync(CONFIG_FILE, JSON.stringify({ token }));
@@ -67,7 +88,7 @@ async function uploadToDatabase(jsonData) {
67
88
 
68
89
  // Add timeout to fetch request
69
90
  const controller = new AbortController();
70
- const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
91
+ const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
71
92
 
72
93
  const response = await fetch(`${API_URL}/api/upload`, {
73
94
  method: 'POST',
@@ -181,6 +202,14 @@ program
181
202
  const map = await scanner.scan(targetDir);
182
203
  console.log(chalk.green('Scan complete!'));
183
204
 
205
+ // Analyze and print blast radius insight
206
+ const blastRadiusInfo = analyzeBlastRadius(map);
207
+ if (blastRadiusInfo && blastRadiusInfo.blast_radius > 0) {
208
+ console.log(`\n⚠️ ${blastRadiusInfo.file} has the highest blast radius (${blastRadiusInfo.blast_radius}). Changes here may affect many parts of the system.`);
209
+ } else {
210
+ console.log('\nNo high-risk files detected based on import dependencies.');
211
+ }
212
+
184
213
  // Upload to database if requested
185
214
  if (options.upload) {
186
215
  await uploadToDatabase(map);