codesummary 1.2.1 → 1.2.2
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/CHANGELOG.md +26 -213
- package/README.md +61 -395
- package/features.md +25 -386
- package/package.json +13 -17
- package/src/ai/errors.js +85 -0
- package/src/ai/featureFlags.js +8 -0
- package/src/ai/promptTemplates.js +337 -0
- package/src/ai/providerClient.js +81 -0
- package/src/ai/providers/ollama.js +92 -0
- package/src/ai/providers/openaiCompatible.js +96 -0
- package/src/analysis/repositorySignals.js +196 -0
- package/src/cli.js +819 -77
- package/src/configManager.js +21 -0
- package/src/graph/adapters/baseAdapter.js +24 -0
- package/src/graph/adapters/javascriptAdapter.js +53 -0
- package/src/graph/adapters/pythonAdapter.js +77 -0
- package/src/graph/graphEngine.js +151 -0
- package/src/graph/graphMetrics.js +79 -0
- package/src/graph/graphSchema.js +30 -0
- package/src/graph/universalExtractor.js +29 -0
- package/src/llmGenerator.js +723 -8
- package/src/pdfGenerator.js +1189 -275
- package/src/renderers/llmSummaryRenderer.js +14 -0
- package/src/renderers/pdfThemeRenderer.js +685 -0
- package/src/scanner.js +115 -8
- package/rag-schema.json +0 -114
- package/src/ragConfig.js +0 -369
- package/src/ragGenerator.js +0 -1740
package/src/configManager.js
CHANGED
|
@@ -175,6 +175,17 @@ export class ConfigManager {
|
|
|
175
175
|
documentTitle: "Project Code Summary",
|
|
176
176
|
maxFilesBeforePrompt: 500,
|
|
177
177
|
},
|
|
178
|
+
ai: {
|
|
179
|
+
enabled: false,
|
|
180
|
+
provider: "openai-compatible",
|
|
181
|
+
baseUrl: "http://localhost:11434/v1",
|
|
182
|
+
apiKey: "",
|
|
183
|
+
model: "llama3.1",
|
|
184
|
+
timeoutMs: 30000,
|
|
185
|
+
maxRetries: 2,
|
|
186
|
+
retryBackoffMs: 500,
|
|
187
|
+
maxBackoffMs: 5000,
|
|
188
|
+
},
|
|
178
189
|
};
|
|
179
190
|
}
|
|
180
191
|
|
|
@@ -874,6 +885,16 @@ export class ConfigManager {
|
|
|
874
885
|
console.log(chalk.gray(` • File warning threshold: ${config.settings?.maxFilesBeforePrompt || 500} files`));
|
|
875
886
|
console.log(chalk.gray(` • Configuration version: ${config.configVersion || 'legacy'}`));
|
|
876
887
|
console.log();
|
|
888
|
+
|
|
889
|
+
// AI Settings
|
|
890
|
+
if (config.ai) {
|
|
891
|
+
console.log(chalk.green("🤖 AI Settings:"));
|
|
892
|
+
console.log(chalk.gray(` • Provider: ${config.ai.provider || 'openai-compatible'}`));
|
|
893
|
+
console.log(chalk.gray(` • Model: ${config.ai.model || 'not set'}`));
|
|
894
|
+
console.log(chalk.gray(` • Base URL: ${config.ai.baseUrl || 'http://localhost:11434/v1'}`));
|
|
895
|
+
console.log(chalk.gray(` • API Key: ${config.ai.apiKey ? '********' : 'not set'}`));
|
|
896
|
+
console.log();
|
|
897
|
+
}
|
|
877
898
|
}
|
|
878
899
|
}
|
|
879
900
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base adapter contract for language-aware extraction.
|
|
3
|
+
*/
|
|
4
|
+
export default class BaseAdapter {
|
|
5
|
+
supports(fileInfo) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
mode() {
|
|
10
|
+
return 'generic';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
extractDependencies(content, fileInfo) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
extractSymbols(content, fileInfo) {
|
|
18
|
+
return {
|
|
19
|
+
entrypoints: [],
|
|
20
|
+
symbols: []
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import BaseAdapter from './baseAdapter.js';
|
|
2
|
+
|
|
3
|
+
export default class JavaScriptAdapter extends BaseAdapter {
|
|
4
|
+
supports(fileInfo) {
|
|
5
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(fileInfo.ext);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
mode() {
|
|
9
|
+
return 'deep';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractDependencies(content) {
|
|
13
|
+
const deps = [];
|
|
14
|
+
const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
|
|
15
|
+
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
16
|
+
const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
17
|
+
let match;
|
|
18
|
+
|
|
19
|
+
while ((match = importRegex.exec(content)) !== null) deps.push(match[1]);
|
|
20
|
+
while ((match = requireRegex.exec(content)) !== null) deps.push(match[1]);
|
|
21
|
+
while ((match = dynamicImportRegex.exec(content)) !== null) deps.push(match[1]);
|
|
22
|
+
|
|
23
|
+
return [...new Set(deps)];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extractSymbols(content, fileInfo) {
|
|
27
|
+
const entrypoints = [];
|
|
28
|
+
const symbols = [];
|
|
29
|
+
|
|
30
|
+
if (/^\s*if\s*\(\s*require\.main\s*===\s*module\s*\)/m.test(content)) {
|
|
31
|
+
entrypoints.push(fileInfo.relativePath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (/^\s*#!/.test(content)) {
|
|
35
|
+
entrypoints.push(fileInfo.relativePath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const classRegex = /^\s*(?:export\s+)?class\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm;
|
|
39
|
+
const fnRegex = /^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm;
|
|
40
|
+
const arrowRegex = /^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/gm;
|
|
41
|
+
let match;
|
|
42
|
+
|
|
43
|
+
while ((match = classRegex.exec(content)) !== null) symbols.push({ name: match[1], kind: 'class' });
|
|
44
|
+
while ((match = fnRegex.exec(content)) !== null) symbols.push({ name: match[1], kind: 'function' });
|
|
45
|
+
while ((match = arrowRegex.exec(content)) !== null) symbols.push({ name: match[1], kind: 'function' });
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
entrypoints: [...new Set(entrypoints)],
|
|
49
|
+
symbols
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import BaseAdapter from './baseAdapter.js';
|
|
2
|
+
|
|
3
|
+
export default class PythonAdapter extends BaseAdapter {
|
|
4
|
+
supports(fileInfo) {
|
|
5
|
+
return fileInfo.ext === '.py';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
mode() {
|
|
9
|
+
return 'deep';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractDependencies(content, fileInfo) {
|
|
13
|
+
const deps = [];
|
|
14
|
+
const fromImportRegex = /^\s*from\s+([A-Za-z0-9_\.]+)\s+import\s+/gm;
|
|
15
|
+
const importRegex = /^\s*import\s+([A-Za-z0-9_\.,\s]+)/gm;
|
|
16
|
+
let match;
|
|
17
|
+
|
|
18
|
+
while ((match = fromImportRegex.exec(content)) !== null) {
|
|
19
|
+
deps.push(match[1]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
23
|
+
const modules = match[1]
|
|
24
|
+
.split(',')
|
|
25
|
+
.map(part => part.trim().split(/\s+as\s+/i)[0])
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
deps.push(...modules);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const relativePrefix = this.getPackagePrefix(fileInfo.relativePath);
|
|
31
|
+
const resolved = deps.map(dep => this.normalizeDependency(dep, relativePrefix));
|
|
32
|
+
return [...new Set(resolved.filter(Boolean))];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
extractSymbols(content, fileInfo) {
|
|
36
|
+
const symbols = [];
|
|
37
|
+
const entrypoints = [];
|
|
38
|
+
|
|
39
|
+
if (/if\s+__name__\s*==\s*['"]__main__['"]\s*:/m.test(content)) {
|
|
40
|
+
entrypoints.push(fileInfo.relativePath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (fileInfo.relativePath.endsWith('__init__.py')) {
|
|
44
|
+
entrypoints.push(fileInfo.relativePath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const classRegex = /^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(|:)/gm;
|
|
48
|
+
const fnRegex = /^\s*def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gm;
|
|
49
|
+
let match;
|
|
50
|
+
|
|
51
|
+
while ((match = classRegex.exec(content)) !== null) symbols.push({ name: match[1], kind: 'class' });
|
|
52
|
+
while ((match = fnRegex.exec(content)) !== null) symbols.push({ name: match[1], kind: 'function' });
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
entrypoints: [...new Set(entrypoints)],
|
|
56
|
+
symbols
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getPackagePrefix(relativePath) {
|
|
61
|
+
const clean = relativePath.replace(/\\/g, '/');
|
|
62
|
+
const parts = clean.split('/');
|
|
63
|
+
parts.pop();
|
|
64
|
+
return parts.join('.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
normalizeDependency(dep, packagePrefix) {
|
|
68
|
+
if (!dep) return null;
|
|
69
|
+
if (dep.startsWith('.')) {
|
|
70
|
+
const trimmed = dep.replace(/^\.+/, '');
|
|
71
|
+
const base = packagePrefix ? `${packagePrefix}.${trimmed}` : trimmed;
|
|
72
|
+
return base.replace(/\.+/g, '.').replace(/^\./, '').replace(/\.$/, '');
|
|
73
|
+
}
|
|
74
|
+
return dep;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createGraph, createNode, createEdge } from './graphSchema.js';
|
|
4
|
+
import UniversalExtractor from './universalExtractor.js';
|
|
5
|
+
import GraphMetrics from './graphMetrics.js';
|
|
6
|
+
import JavaScriptAdapter from './adapters/javascriptAdapter.js';
|
|
7
|
+
import PythonAdapter from './adapters/pythonAdapter.js';
|
|
8
|
+
|
|
9
|
+
export default class GraphEngine {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.universalExtractor = new UniversalExtractor();
|
|
12
|
+
this.metrics = new GraphMetrics();
|
|
13
|
+
this.adapters = [
|
|
14
|
+
new PythonAdapter(),
|
|
15
|
+
new JavaScriptAdapter()
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async analyze(allFiles, fileContentsByPath = null) {
|
|
20
|
+
const graph = createGraph();
|
|
21
|
+
const fileMap = new Map();
|
|
22
|
+
const fileMapNoExt = new Map();
|
|
23
|
+
const packageMap = new Map();
|
|
24
|
+
const localContents = fileContentsByPath || new Map();
|
|
25
|
+
|
|
26
|
+
for (const file of allFiles) {
|
|
27
|
+
const rel = file.relativePath;
|
|
28
|
+
const ext = path.extname(rel).toLowerCase();
|
|
29
|
+
const noExt = rel.slice(0, Math.max(0, rel.length - ext.length));
|
|
30
|
+
const pkg = noExt.replace(/\//g, '.');
|
|
31
|
+
fileMap.set(rel, file);
|
|
32
|
+
fileMapNoExt.set(noExt, rel);
|
|
33
|
+
packageMap.set(pkg, rel);
|
|
34
|
+
graph.nodes.push(createNode(rel, this.languageFromExtension(ext)));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const entrypoints = new Set();
|
|
38
|
+
const symbolsByFile = new Map();
|
|
39
|
+
const adapterModes = {};
|
|
40
|
+
|
|
41
|
+
for (const file of allFiles) {
|
|
42
|
+
const rel = file.relativePath;
|
|
43
|
+
const ext = path.extname(rel).toLowerCase();
|
|
44
|
+
const fileInfo = { ...file, ext };
|
|
45
|
+
const adapter = this.getAdapter(fileInfo);
|
|
46
|
+
|
|
47
|
+
if (!localContents.has(rel)) {
|
|
48
|
+
try {
|
|
49
|
+
localContents.set(rel, await fs.readFile(file.absolutePath, 'utf8'));
|
|
50
|
+
} catch {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const content = localContents.get(rel);
|
|
56
|
+
const deps = adapter
|
|
57
|
+
? adapter.extractDependencies(content, fileInfo)
|
|
58
|
+
: this.universalExtractor.extractDependencies(content, fileInfo);
|
|
59
|
+
const resolvedDeps = deps
|
|
60
|
+
.map(dep => this.resolveDependency(dep, rel, fileMap, fileMapNoExt, packageMap))
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
|
|
63
|
+
for (const dep of [...new Set(resolvedDeps)]) {
|
|
64
|
+
graph.edges.push(createEdge(rel, dep, 'imports'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const symbols = adapter
|
|
68
|
+
? adapter.extractSymbols(content, fileInfo)
|
|
69
|
+
: { entrypoints: [], symbols: [] };
|
|
70
|
+
for (const ep of (symbols.entrypoints || [])) entrypoints.add(ep);
|
|
71
|
+
symbolsByFile.set(rel, symbols.symbols || []);
|
|
72
|
+
|
|
73
|
+
if (adapter) {
|
|
74
|
+
adapterModes[ext] = adapter.mode();
|
|
75
|
+
} else if (!adapterModes[ext]) {
|
|
76
|
+
adapterModes[ext] = 'generic';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
graph.metadata.adapterModes = adapterModes;
|
|
81
|
+
|
|
82
|
+
const metrics = this.metrics.compute(graph);
|
|
83
|
+
const connectedSubmodules = this.metrics.connectedSubmodules(graph);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
graph,
|
|
87
|
+
metrics,
|
|
88
|
+
connectedSubmodules,
|
|
89
|
+
entrypoints: [...entrypoints],
|
|
90
|
+
symbolsByFile,
|
|
91
|
+
fileContentsByPath: localContents
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getAdapter(fileInfo) {
|
|
96
|
+
return this.adapters.find(adapter => adapter.supports(fileInfo)) || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resolveDependency(specifier, sourcePath, fileMap, fileMapNoExt, packageMap) {
|
|
100
|
+
if (!specifier || typeof specifier !== 'string') return null;
|
|
101
|
+
const sourceDir = path.posix.dirname(sourcePath);
|
|
102
|
+
const spec = specifier.replace(/\\/g, '/');
|
|
103
|
+
const candidates = [];
|
|
104
|
+
|
|
105
|
+
if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
106
|
+
const resolved = path.posix.normalize(path.posix.join(sourceDir, spec));
|
|
107
|
+
candidates.push(resolved);
|
|
108
|
+
candidates.push(`${resolved}.js`, `${resolved}.ts`, `${resolved}.jsx`, `${resolved}.tsx`, `${resolved}.py`);
|
|
109
|
+
candidates.push(path.posix.join(resolved, 'index.js'));
|
|
110
|
+
candidates.push(path.posix.join(resolved, 'index.ts'));
|
|
111
|
+
candidates.push(path.posix.join(resolved, '__init__.py'));
|
|
112
|
+
} else if (spec.startsWith('/')) {
|
|
113
|
+
candidates.push(spec.slice(1));
|
|
114
|
+
} else {
|
|
115
|
+
const dotted = spec.replace(/\//g, '.');
|
|
116
|
+
if (packageMap.has(dotted)) {
|
|
117
|
+
return packageMap.get(dotted);
|
|
118
|
+
}
|
|
119
|
+
candidates.push(spec);
|
|
120
|
+
candidates.push(`src/${spec}`);
|
|
121
|
+
candidates.push(`src/${spec}.js`, `src/${spec}.ts`, `src/${spec}.py`);
|
|
122
|
+
candidates.push(`${spec}.js`, `${spec}.ts`, `${spec}.py`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const candidate of candidates) {
|
|
126
|
+
if (fileMap.has(candidate)) return candidate;
|
|
127
|
+
if (fileMapNoExt.has(candidate)) return fileMapNoExt.get(candidate);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
languageFromExtension(ext) {
|
|
134
|
+
const map = {
|
|
135
|
+
'.js': 'JavaScript', '.jsx': 'JavaScript', '.mjs': 'JavaScript', '.cjs': 'JavaScript',
|
|
136
|
+
'.ts': 'TypeScript', '.tsx': 'TypeScript',
|
|
137
|
+
'.py': 'Python',
|
|
138
|
+
'.java': 'Java',
|
|
139
|
+
'.cs': 'C#',
|
|
140
|
+
'.cpp': 'C++', '.c': 'C', '.h': 'C/C++',
|
|
141
|
+
'.php': 'PHP',
|
|
142
|
+
'.rb': 'Ruby',
|
|
143
|
+
'.go': 'Go',
|
|
144
|
+
'.rs': 'Rust',
|
|
145
|
+
'.sh': 'Shell',
|
|
146
|
+
'.bat': 'Batch'
|
|
147
|
+
};
|
|
148
|
+
return map[ext] || 'Unknown';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic graph metrics for repository navigation.
|
|
3
|
+
*/
|
|
4
|
+
export default class GraphMetrics {
|
|
5
|
+
compute(graph) {
|
|
6
|
+
const inDegree = new Map();
|
|
7
|
+
const outDegree = new Map();
|
|
8
|
+
|
|
9
|
+
for (const node of graph.nodes) {
|
|
10
|
+
inDegree.set(node.id, 0);
|
|
11
|
+
outDegree.set(node.id, 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (const edge of graph.edges) {
|
|
15
|
+
outDegree.set(edge.from, (outDegree.get(edge.from) || 0) + 1);
|
|
16
|
+
inDegree.set(edge.to, (inDegree.get(edge.to) || 0) + 1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const hubs = [...outDegree.entries()]
|
|
20
|
+
.sort((a, b) => b[1] - a[1])
|
|
21
|
+
.slice(0, 8)
|
|
22
|
+
.map(([id, degree]) => ({ id, degree }));
|
|
23
|
+
|
|
24
|
+
const centralNodes = [...inDegree.entries()]
|
|
25
|
+
.sort((a, b) => b[1] - a[1])
|
|
26
|
+
.slice(0, 8)
|
|
27
|
+
.map(([id, degree]) => ({ id, degree }));
|
|
28
|
+
|
|
29
|
+
const isolated = graph.nodes
|
|
30
|
+
.filter(node => (inDegree.get(node.id) || 0) === 0 && (outDegree.get(node.id) || 0) === 0)
|
|
31
|
+
.map(node => node.id);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
inDegree,
|
|
35
|
+
outDegree,
|
|
36
|
+
hubs,
|
|
37
|
+
centralNodes,
|
|
38
|
+
isolated
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
connectedSubmodules(graph) {
|
|
43
|
+
const adjacency = new Map();
|
|
44
|
+
for (const node of graph.nodes) adjacency.set(node.id, new Set());
|
|
45
|
+
for (const edge of graph.edges) {
|
|
46
|
+
adjacency.get(edge.from)?.add(edge.to);
|
|
47
|
+
adjacency.get(edge.to)?.add(edge.from);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const visited = new Set();
|
|
51
|
+
const components = [];
|
|
52
|
+
|
|
53
|
+
for (const node of graph.nodes) {
|
|
54
|
+
if (visited.has(node.id)) continue;
|
|
55
|
+
|
|
56
|
+
const stack = [node.id];
|
|
57
|
+
const component = [];
|
|
58
|
+
visited.add(node.id);
|
|
59
|
+
|
|
60
|
+
while (stack.length > 0) {
|
|
61
|
+
const current = stack.pop();
|
|
62
|
+
component.push(current);
|
|
63
|
+
for (const next of (adjacency.get(current) || [])) {
|
|
64
|
+
if (!visited.has(next)) {
|
|
65
|
+
visited.add(next);
|
|
66
|
+
stack.push(next);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
components.push(component.sort((a, b) => a.localeCompare(b)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return components
|
|
75
|
+
.sort((a, b) => b.length - a.length)
|
|
76
|
+
.slice(0, 8);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight schema helpers for dependency graph structures.
|
|
3
|
+
*/
|
|
4
|
+
export function createGraph() {
|
|
5
|
+
return {
|
|
6
|
+
nodes: [],
|
|
7
|
+
edges: [],
|
|
8
|
+
metadata: {
|
|
9
|
+
adapterModes: {},
|
|
10
|
+
generatedAt: new Date().toISOString()
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createNode(path, language = 'Unknown') {
|
|
16
|
+
return {
|
|
17
|
+
id: path,
|
|
18
|
+
path,
|
|
19
|
+
language
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createEdge(from, to, type = 'imports') {
|
|
24
|
+
return {
|
|
25
|
+
from,
|
|
26
|
+
to,
|
|
27
|
+
type
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic dependency extraction fallback for unsupported languages.
|
|
3
|
+
*/
|
|
4
|
+
export default class UniversalExtractor {
|
|
5
|
+
extractDependencies(content, fileInfo) {
|
|
6
|
+
const ext = fileInfo.ext;
|
|
7
|
+
const deps = [];
|
|
8
|
+
let match;
|
|
9
|
+
|
|
10
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext)) {
|
|
11
|
+
const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
|
|
12
|
+
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
13
|
+
while ((match = importRegex.exec(content)) !== null) deps.push(match[1]);
|
|
14
|
+
while ((match = requireRegex.exec(content)) !== null) deps.push(match[1]);
|
|
15
|
+
} else if (ext === '.py') {
|
|
16
|
+
const pyRegex = /(?:from\s+([A-Za-z0-9_\.]+)\s+import|import\s+([A-Za-z0-9_\.]+))/g;
|
|
17
|
+
while ((match = pyRegex.exec(content)) !== null) deps.push(match[1] || match[2]);
|
|
18
|
+
} else if (['.c', '.cpp', '.h', '.hpp', '.cc'].includes(ext)) {
|
|
19
|
+
const includeRegex = /#include\s*[<"]([^>"]+)[>"]/g;
|
|
20
|
+
while ((match = includeRegex.exec(content)) !== null) deps.push(match[1]);
|
|
21
|
+
} else if (['.php'].includes(ext)) {
|
|
22
|
+
const useRegex = /^\s*use\s+([A-Za-z0-9_\\]+)\s*;/gm;
|
|
23
|
+
while ((match = useRegex.exec(content)) !== null) deps.push(match[1]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [...new Set(deps.filter(Boolean))];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|