arcvision 0.2.3 → 0.2.4

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,204 +0,0 @@
1
- /**
2
- * Semantic Analyzer
3
- *
4
- * Builds cross-file symbol resolution and tracks actual usage of imported symbols.
5
- * This enables detection of which files actually USE imported functions/classes,
6
- * not just which files import them.
7
- */
8
-
9
- /**
10
- * Build a global symbol table from all file exports
11
- * @param {Array} nodes - All scanned file nodes with metadata
12
- * @returns {Map} Symbol table mapping symbol names to their source files
13
- */
14
- function buildSymbolTable(nodes) {
15
- const symbolTable = new Map();
16
-
17
- nodes.forEach(node => {
18
- const filePath = node.id;
19
- const metadata = node.metadata;
20
-
21
- if (!metadata) return;
22
-
23
- // Register all exports from this file
24
- if (metadata.exports && Array.isArray(metadata.exports)) {
25
- metadata.exports.forEach(exp => {
26
- const symbolName = exp.name;
27
- if (!symbolTable.has(symbolName)) {
28
- symbolTable.set(symbolName, []);
29
- }
30
- symbolTable.get(symbolName).push({
31
- file: filePath,
32
- type: exp.type,
33
- loc: exp.loc
34
- });
35
- });
36
- }
37
-
38
- // Register all functions
39
- if (metadata.functions && Array.isArray(metadata.functions)) {
40
- metadata.functions.forEach(func => {
41
- const symbolName = func.name;
42
- if (!symbolTable.has(symbolName)) {
43
- symbolTable.set(symbolName, []);
44
- }
45
- symbolTable.get(symbolName).push({
46
- file: filePath,
47
- type: 'function',
48
- loc: func.loc
49
- });
50
- });
51
- }
52
-
53
- // Register all classes
54
- if (metadata.classes && Array.isArray(metadata.classes)) {
55
- metadata.classes.forEach(cls => {
56
- const symbolName = cls.name;
57
- if (!symbolTable.has(symbolName)) {
58
- symbolTable.set(symbolName, []);
59
- }
60
- symbolTable.get(symbolName).push({
61
- file: filePath,
62
- type: 'class',
63
- loc: cls.loc
64
- });
65
- });
66
- }
67
- });
68
-
69
- return symbolTable;
70
- }
71
-
72
- /**
73
- * Resolve symbol usage to their definitions
74
- * @param {Object} node - File node with metadata
75
- * @param {Map} symbolTable - Global symbol table
76
- * @returns {Array} Array of resolved symbol usages
77
- */
78
- function resolveSymbolUsage(node, symbolTable) {
79
- const resolvedUsages = [];
80
- const metadata = node.metadata;
81
-
82
- if (!metadata) return resolvedUsages;
83
-
84
- // Track which symbols this file imports
85
- const importedSymbols = new Map();
86
- if (metadata.imports && Array.isArray(metadata.imports)) {
87
- metadata.imports.forEach(imp => {
88
- if (imp.specifiers && Array.isArray(imp.specifiers)) {
89
- imp.specifiers.forEach(spec => {
90
- const localName = spec.local || spec.imported || spec;
91
- const importedName = spec.imported || spec.local || spec;
92
- importedSymbols.set(localName, {
93
- source: imp.source,
94
- originalName: importedName
95
- });
96
- });
97
- }
98
- });
99
- }
100
-
101
- // Check function calls against imported symbols
102
- if (metadata.functionCalls && Array.isArray(metadata.functionCalls)) {
103
- metadata.functionCalls.forEach(call => {
104
- const callName = call.name;
105
- if (importedSymbols.has(callName)) {
106
- const importInfo = importedSymbols.get(callName);
107
- resolvedUsages.push({
108
- type: 'function_call',
109
- symbol: callName,
110
- source: importInfo.source,
111
- loc: call.loc
112
- });
113
- }
114
- });
115
- }
116
-
117
- // Check constructor calls against imported symbols
118
- if (metadata.constructorCalls && Array.isArray(metadata.constructorCalls)) {
119
- metadata.constructorCalls.forEach(call => {
120
- const className = call.className;
121
- if (importedSymbols.has(className)) {
122
- const importInfo = importedSymbols.get(className);
123
- resolvedUsages.push({
124
- type: 'constructor_call',
125
- symbol: className,
126
- source: importInfo.source,
127
- loc: call.loc
128
- });
129
- }
130
- });
131
- }
132
-
133
- // Check component usage (React/JSX)
134
- if (metadata.componentUsage && Array.isArray(metadata.componentUsage)) {
135
- metadata.componentUsage.forEach(comp => {
136
- const componentName = comp.component;
137
- if (importedSymbols.has(componentName)) {
138
- const importInfo = importedSymbols.get(componentName);
139
- resolvedUsages.push({
140
- type: 'component_usage',
141
- symbol: componentName,
142
- source: importInfo.source,
143
- loc: comp.loc
144
- });
145
- }
146
- });
147
- }
148
-
149
- return resolvedUsages;
150
- }
151
-
152
- /**
153
- * Track cross-file references and build usage edges
154
- * @param {Array} nodes - All scanned file nodes
155
- * @param {Map} symbolTable - Global symbol table
156
- * @returns {Array} Array of usage edges
157
- */
158
- function trackCrossFileReferences(nodes, symbolTable) {
159
- const usageEdges = [];
160
-
161
- nodes.forEach(node => {
162
- const filePath = node.id;
163
- const resolvedUsages = resolveSymbolUsage(node, symbolTable);
164
-
165
- resolvedUsages.forEach(usage => {
166
- // Create an edge from this file to the source of the symbol
167
- usageEdges.push({
168
- source: filePath,
169
- target: usage.source,
170
- type: usage.type,
171
- symbol: usage.symbol,
172
- loc: usage.loc
173
- });
174
- });
175
- });
176
-
177
- return usageEdges;
178
- }
179
-
180
- /**
181
- * Analyze semantic relationships in the codebase
182
- * @param {Array} nodes - All scanned file nodes
183
- * @returns {Object} Semantic analysis results
184
- */
185
- function analyzeSemantics(nodes) {
186
- const symbolTable = buildSymbolTable(nodes);
187
- const usageEdges = trackCrossFileReferences(nodes, symbolTable);
188
-
189
- return {
190
- symbolTable,
191
- usageEdges,
192
- stats: {
193
- totalSymbols: symbolTable.size,
194
- totalUsages: usageEdges.length
195
- }
196
- };
197
- }
198
-
199
- module.exports = {
200
- buildSymbolTable,
201
- resolveSymbolUsage,
202
- trackCrossFileReferences,
203
- analyzeSemantics
204
- };
@@ -1,35 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- /**
5
- * Load and parse tsconfig.json from project root
6
- * @param {string} projectRoot - The root directory of the project
7
- * @returns {Object|null} The compilerOptions from tsconfig.json or null if not found
8
- */
9
- function loadTSConfig(projectRoot) {
10
- const tsconfigPaths = [
11
- path.join(projectRoot, 'tsconfig.json'),
12
- path.join(projectRoot, 'jsconfig.json')
13
- ];
14
-
15
- for (const tsconfigPath of tsconfigPaths) {
16
- if (fs.existsSync(tsconfigPath)) {
17
- try {
18
- let content = fs.readFileSync(tsconfigPath, 'utf-8');
19
- // Remove BOM if present (0xFEFF)
20
- if (content.charCodeAt(0) === 0xFEFF) {
21
- content = content.slice(1);
22
- }
23
- const raw = JSON.parse(content);
24
- return raw.compilerOptions || {};
25
- } catch (error) {
26
- console.warn(`Warning: Could not parse ${tsconfigPath}:`, error.message);
27
- return null;
28
- }
29
- }
30
- }
31
-
32
- return null;
33
- }
34
-
35
- module.exports = { loadTSConfig };
@@ -1,272 +0,0 @@
1
- /**
2
- * Type Dependency Analyzer
3
- *
4
- * Tracks TypeScript type-only dependencies including:
5
- * - Type-only imports
6
- * - Interface extensions
7
- * - Interface implementations
8
- * - Generic type parameters
9
- * - Return type dependencies
10
- */
11
-
12
- const traverse = require('@babel/traverse').default;
13
-
14
- /**
15
- * Extract type-only imports from AST
16
- * @param {Object} ast - Babel AST
17
- * @returns {Array} Array of type imports
18
- */
19
- function extractTypeImports(ast) {
20
- const typeImports = [];
21
-
22
- if (!ast) return typeImports;
23
-
24
- traverse(ast, {
25
- ImportDeclaration(path) {
26
- const node = path.node;
27
-
28
- // Check if it's a type-only import: import type { ... }
29
- if (node.importKind === 'type') {
30
- const specifiers = [];
31
- node.specifiers.forEach(spec => {
32
- if (spec.imported) {
33
- specifiers.push({
34
- imported: spec.imported.name || spec.imported.value,
35
- local: spec.local.name
36
- });
37
- }
38
- });
39
-
40
- typeImports.push({
41
- source: node.source.value,
42
- specifiers,
43
- importKind: 'type',
44
- loc: node.loc
45
- });
46
- }
47
-
48
- // Check for individual type specifiers: import { type User } from '...'
49
- else {
50
- const typeSpecifiers = [];
51
- node.specifiers.forEach(spec => {
52
- if (spec.importKind === 'type' && spec.imported) {
53
- typeSpecifiers.push({
54
- imported: spec.imported.name || spec.imported.value,
55
- local: spec.local.name
56
- });
57
- }
58
- });
59
-
60
- if (typeSpecifiers.length > 0) {
61
- typeImports.push({
62
- source: node.source.value,
63
- specifiers: typeSpecifiers,
64
- importKind: 'mixed',
65
- loc: node.loc
66
- });
67
- }
68
- }
69
- }
70
- });
71
-
72
- return typeImports;
73
- }
74
-
75
- /**
76
- * Extract interface dependencies (extends, implements)
77
- * @param {Object} ast - Babel AST
78
- * @returns {Array} Array of interface dependencies
79
- */
80
- function extractInterfaceDependencies(ast) {
81
- const interfaceDeps = [];
82
-
83
- if (!ast) return interfaceDeps;
84
-
85
- traverse(ast, {
86
- // Interface extends
87
- TSInterfaceDeclaration(path) {
88
- const node = path.node;
89
- const interfaceName = node.id.name;
90
-
91
- if (node.extends && node.extends.length > 0) {
92
- node.extends.forEach(ext => {
93
- const extendedInterface = extractTypeName(ext.expression);
94
- if (extendedInterface) {
95
- interfaceDeps.push({
96
- interface: interfaceName,
97
- extends: extendedInterface,
98
- type: 'interface_extends',
99
- loc: node.loc
100
- });
101
- }
102
- });
103
- }
104
- },
105
-
106
- // Class implements
107
- ClassDeclaration(path) {
108
- const node = path.node;
109
- const className = node.id ? node.id.name : 'Anonymous';
110
-
111
- if (node.implements && node.implements.length > 0) {
112
- node.implements.forEach(impl => {
113
- const implementedInterface = extractTypeName(impl.expression);
114
- if (implementedInterface) {
115
- interfaceDeps.push({
116
- class: className,
117
- implements: implementedInterface,
118
- type: 'class_implements',
119
- loc: node.loc
120
- });
121
- }
122
- });
123
- }
124
- }
125
- });
126
-
127
- return interfaceDeps;
128
- }
129
-
130
- /**
131
- * Extract generic type dependencies
132
- * @param {Object} ast - Babel AST
133
- * @returns {Array} Array of generic type usages
134
- */
135
- function extractGenericDependencies(ast) {
136
- const genericDeps = [];
137
-
138
- if (!ast) return genericDeps;
139
-
140
- traverse(ast, {
141
- // Function return types
142
- FunctionDeclaration(path) {
143
- const node = path.node;
144
- const functionName = node.id ? node.id.name : 'anonymous';
145
-
146
- if (node.returnType) {
147
- const returnTypeName = extractComplexTypeName(node.returnType.typeAnnotation);
148
- if (returnTypeName) {
149
- genericDeps.push({
150
- function: functionName,
151
- returnType: returnTypeName,
152
- type: 'return_type',
153
- loc: node.loc
154
- });
155
- }
156
- }
157
- },
158
-
159
- // Arrow function return types
160
- ArrowFunctionExpression(path) {
161
- const node = path.node;
162
-
163
- if (node.returnType) {
164
- const returnTypeName = extractComplexTypeName(node.returnType.typeAnnotation);
165
- if (returnTypeName) {
166
- genericDeps.push({
167
- function: 'arrow_function',
168
- returnType: returnTypeName,
169
- type: 'return_type',
170
- loc: node.loc
171
- });
172
- }
173
- }
174
- },
175
-
176
- // Variable type annotations
177
- VariableDeclarator(path) {
178
- const node = path.node;
179
- const varName = node.id.name;
180
-
181
- if (node.id.typeAnnotation) {
182
- const typeName = extractComplexTypeName(node.id.typeAnnotation.typeAnnotation);
183
- if (typeName) {
184
- genericDeps.push({
185
- variable: varName,
186
- type: typeName,
187
- kind: 'variable_type',
188
- loc: node.loc
189
- });
190
- }
191
- }
192
- }
193
- });
194
-
195
- return genericDeps;
196
- }
197
-
198
- /**
199
- * Analyze all type dependencies in a file
200
- * @param {Object} ast - Babel AST
201
- * @returns {Object} All type dependencies
202
- */
203
- function analyzeTypeDependencies(ast) {
204
- if (!ast) {
205
- return {
206
- typeImports: [],
207
- interfaceDependencies: [],
208
- genericDependencies: []
209
- };
210
- }
211
-
212
- return {
213
- typeImports: extractTypeImports(ast),
214
- interfaceDependencies: extractInterfaceDependencies(ast),
215
- genericDependencies: extractGenericDependencies(ast)
216
- };
217
- }
218
-
219
- /**
220
- * Helper: Extract type name from TypeScript type node
221
- */
222
- function extractTypeName(node) {
223
- if (!node) return null;
224
-
225
- if (node.type === 'Identifier') {
226
- return node.name;
227
- } else if (node.type === 'TSTypeReference' && node.typeName) {
228
- if (node.typeName.type === 'Identifier') {
229
- return node.typeName.name;
230
- }
231
- }
232
-
233
- return null;
234
- }
235
-
236
- /**
237
- * Helper: Extract complex type names (including generics)
238
- */
239
- function extractComplexTypeName(node) {
240
- if (!node) return null;
241
-
242
- if (node.type === 'TSTypeReference' && node.typeName) {
243
- let baseName = extractTypeName(node);
244
-
245
- // Handle generic parameters
246
- if (node.typeParameters && node.typeParameters.params) {
247
- const params = node.typeParameters.params
248
- .map(p => extractComplexTypeName(p))
249
- .filter(Boolean)
250
- .join(', ');
251
- if (params) {
252
- baseName += `<${params}>`;
253
- }
254
- }
255
-
256
- return baseName;
257
- } else if (node.type === 'Identifier') {
258
- return node.name;
259
- } else if (node.type === 'TSArrayType') {
260
- const elementType = extractComplexTypeName(node.elementType);
261
- return elementType ? `${elementType}[]` : null;
262
- }
263
-
264
- return null;
265
- }
266
-
267
- module.exports = {
268
- extractTypeImports,
269
- extractInterfaceDependencies,
270
- extractGenericDependencies,
271
- analyzeTypeDependencies
272
- };
@@ -1,18 +0,0 @@
1
- const chokidar = require('chokidar');
2
-
3
- function watch(directory, callback) {
4
- const watcher = chokidar.watch(directory, {
5
- ignored: [/(^|[\/\\])\../, '**/node_modules/**'], // ignore dotfiles
6
- persistent: true,
7
- ignoreInitial: true
8
- });
9
-
10
- watcher
11
- .on('add', path => callback('add', path))
12
- .on('change', path => callback('change', path))
13
- .on('unlink', path => callback('unlink', path));
14
-
15
- return watcher;
16
- }
17
-
18
- module.exports = { watch };
@@ -1,153 +0,0 @@
1
- const path = require('path');
2
- const { stableId } = require('./id-generator');
3
-
4
- /**
5
- * Build the context object that conforms to the Arcvision schema
6
- * @param {Array} files - List of files from scanner
7
- * @param {Array} edges - List of edges from scanner
8
- * @param {Object} options - Options including directory path and project name
9
- * @returns {Object} - Context object conforming to schema
10
- */
11
- function buildContext(files, edges, options = {}) {
12
- const {
13
- directory = '.',
14
- projectName = 'arcvision',
15
- language = 'javascript'
16
- } = options;
17
-
18
- // Build nodes from files
19
- const nodes = files.map(file => {
20
- // Use the node's id field which is already the normalized relative path
21
- const normalizedPath = file.id;
22
-
23
- // Determine role based on multiple factors for better accuracy
24
- const hasFunctions = file.metadata.functions && file.metadata.functions.length > 0;
25
- const hasClasses = file.metadata.classes && file.metadata.classes.length > 0;
26
- const hasExports = file.metadata.exports && file.metadata.exports.length > 0;
27
- const hasApiCalls = file.metadata.apiCalls && file.metadata.apiCalls.length > 0;
28
-
29
- let role = 'Structure';
30
- if (hasFunctions || hasClasses || hasApiCalls) {
31
- role = 'Implementation';
32
- } else if (hasExports) {
33
- role = 'Interface';
34
- }
35
-
36
- // Extract dependencies more accurately and remove duplicates
37
- const dependencies = [];
38
- const uniqueDeps = new Set();
39
- if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
40
- file.metadata.imports.forEach(imp => {
41
- if (imp.source && typeof imp.source === 'string') {
42
- // Only add unique dependencies
43
- if (!uniqueDeps.has(imp.source)) {
44
- uniqueDeps.add(imp.source);
45
- dependencies.push(imp.source);
46
- }
47
- }
48
- });
49
- }
50
-
51
- return {
52
- id: stableId(normalizedPath),
53
- type: 'file',
54
- path: normalizedPath,
55
- role: role,
56
- dependencies: dependencies,
57
- blast_radius: file.metadata.blast_radius || 0
58
- };
59
- });
60
-
61
- // Build edges from dependencies
62
- const schemaEdges = [];
63
- for (const edge of edges) {
64
- // Find source and target nodes to get their stable IDs
65
- const sourceNode = nodes.find(n => n.path === edge.source);
66
- const targetNode = nodes.find(n => n.path === edge.target);
67
-
68
- // Include edges where source exists (even if target doesn't exist as a node)
69
- if (sourceNode) {
70
- // Normalize the edge type to fit the schema
71
- let relationType = 'imports';
72
-
73
- // Handle import-related types
74
- if (edge.type === 'imports' || edge.type === 'require' || edge.type === 'export-from' ||
75
- edge.type === 'export-all' || edge.type === 'dynamic-import' || edge.type === 'require-assignment') {
76
- relationType = 'imports';
77
- }
78
- // Handle call-related types (NEW)
79
- else if (edge.type === 'function_call' || edge.type === 'method_call') {
80
- relationType = 'calls';
81
- }
82
- // Handle constructor calls (NEW) - map to calls for schema compliance
83
- else if (edge.type === 'constructor_call') {
84
- relationType = 'calls';
85
- }
86
- // Handle component usage (NEW) - map to imports for schema compliance
87
- else if (edge.type === 'component_usage' || edge.type === 'jsx_component' || edge.type === 'jsx_member_component') {
88
- relationType = 'imports';
89
- }
90
- // Handle existing types
91
- else if (edge.type === 'calls') {
92
- relationType = 'calls';
93
- } else if (edge.type === 'owns') {
94
- relationType = 'owns';
95
- } else if (edge.type === 'depends_on') {
96
- relationType = 'depends_on';
97
- } else {
98
- // Default to 'imports' for any unknown edge types to ensure schema compliance
99
- relationType = 'imports';
100
- }
101
-
102
- if (targetNode) {
103
- // Both source and target nodes exist
104
- schemaEdges.push({
105
- from: sourceNode.id,
106
- to: targetNode.id,
107
- relation: relationType
108
- });
109
- } else {
110
- // Source exists but target doesn't (external dependency like node_modules)
111
- // We'll still include the edge to preserve the relationship
112
- // However, since the schema requires valid node IDs, we'll only add if the target is a known node
113
- // For now, we'll skip edges to non-existent targets to maintain schema compliance
114
- }
115
- }
116
- }
117
-
118
- // Calculate metrics
119
- const metrics = {
120
- total_files: nodes.length,
121
- total_nodes: nodes.length,
122
- total_edges: schemaEdges.length,
123
- total_imports: schemaEdges.filter(edge => edge.relation === 'imports').length,
124
- total_calls: schemaEdges.filter(edge => edge.relation === 'calls').length,
125
- total_owns: schemaEdges.filter(edge => edge.relation === 'owns').length,
126
- total_depends_on: schemaEdges.filter(edge => edge.relation === 'depends_on').length,
127
- files_with_functions: nodes.filter(n => n.role === 'Implementation').length
128
- };
129
-
130
- // Build and return the context object
131
- const context = {
132
- schema_version: '1.0.0',
133
- generated_at: new Date().toISOString(),
134
- system: {
135
- name: projectName,
136
- root_path: path.resolve(directory),
137
- language: language
138
- },
139
- nodes,
140
- edges: schemaEdges,
141
- metrics,
142
- contextSurface: options.contextSurface || {}
143
- };
144
-
145
- // Add additional computed metrics
146
- context.metrics.files_with_functions = nodes.filter(n => n.role === 'Implementation').length;
147
- context.metrics.files_with_high_blast_radius = nodes.filter(n => n.blast_radius > 5).length;
148
- context.metrics.total_dependencies = schemaEdges.length;
149
-
150
- return context;
151
- }
152
-
153
- module.exports = { buildContext };