@theihtisham/agent-shadow-brain 1.1.1 → 2.1.0
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 +738 -138
- package/dist/adapters/aider.d.ts +11 -0
- package/dist/adapters/aider.d.ts.map +1 -0
- package/dist/adapters/aider.js +149 -0
- package/dist/adapters/aider.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +5 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/roo-code.d.ts +14 -0
- package/dist/adapters/roo-code.d.ts.map +1 -0
- package/dist/adapters/roo-code.js +186 -0
- package/dist/adapters/roo-code.js.map +1 -0
- package/dist/brain/adr-engine.d.ts +58 -0
- package/dist/brain/adr-engine.d.ts.map +1 -0
- package/dist/brain/adr-engine.js +400 -0
- package/dist/brain/adr-engine.js.map +1 -0
- package/dist/brain/auto-update.d.ts +9 -0
- package/dist/brain/auto-update.d.ts.map +1 -0
- package/dist/brain/auto-update.js +59 -0
- package/dist/brain/auto-update.js.map +1 -0
- package/dist/brain/code-metrics.d.ts +18 -0
- package/dist/brain/code-metrics.d.ts.map +1 -0
- package/dist/brain/code-metrics.js +224 -0
- package/dist/brain/code-metrics.js.map +1 -0
- package/dist/brain/code-similarity.d.ts +43 -0
- package/dist/brain/code-similarity.d.ts.map +1 -0
- package/dist/brain/code-similarity.js +227 -0
- package/dist/brain/code-similarity.js.map +1 -0
- package/dist/brain/context-completion.d.ts +39 -0
- package/dist/brain/context-completion.d.ts.map +1 -0
- package/dist/brain/context-completion.js +851 -0
- package/dist/brain/context-completion.js.map +1 -0
- package/dist/brain/custom-rules.d.ts +14 -0
- package/dist/brain/custom-rules.d.ts.map +1 -0
- package/dist/brain/custom-rules.js +108 -0
- package/dist/brain/custom-rules.js.map +1 -0
- package/dist/brain/dependency-graph.d.ts +35 -0
- package/dist/brain/dependency-graph.d.ts.map +1 -0
- package/dist/brain/dependency-graph.js +310 -0
- package/dist/brain/dependency-graph.js.map +1 -0
- package/dist/brain/learning-engine.d.ts +54 -0
- package/dist/brain/learning-engine.d.ts.map +1 -0
- package/dist/brain/learning-engine.js +855 -0
- package/dist/brain/learning-engine.js.map +1 -0
- package/dist/brain/mcp-server.d.ts +30 -0
- package/dist/brain/mcp-server.d.ts.map +1 -0
- package/dist/brain/mcp-server.js +408 -0
- package/dist/brain/mcp-server.js.map +1 -0
- package/dist/brain/multi-project.d.ts +13 -0
- package/dist/brain/multi-project.d.ts.map +1 -0
- package/dist/brain/multi-project.js +163 -0
- package/dist/brain/multi-project.js.map +1 -0
- package/dist/brain/neural-mesh.d.ts +69 -0
- package/dist/brain/neural-mesh.d.ts.map +1 -0
- package/dist/brain/neural-mesh.js +677 -0
- package/dist/brain/neural-mesh.js.map +1 -0
- package/dist/brain/notifier.d.ts +26 -0
- package/dist/brain/notifier.d.ts.map +1 -0
- package/dist/brain/notifier.js +151 -0
- package/dist/brain/notifier.js.map +1 -0
- package/dist/brain/orchestrator.d.ts +148 -1
- package/dist/brain/orchestrator.d.ts.map +1 -1
- package/dist/brain/orchestrator.js +428 -0
- package/dist/brain/orchestrator.js.map +1 -1
- package/dist/brain/perf-profiler.d.ts +14 -0
- package/dist/brain/perf-profiler.d.ts.map +1 -0
- package/dist/brain/perf-profiler.js +289 -0
- package/dist/brain/perf-profiler.js.map +1 -0
- package/dist/brain/pr-generator.d.ts +19 -0
- package/dist/brain/pr-generator.d.ts.map +1 -0
- package/dist/brain/pr-generator.js +199 -0
- package/dist/brain/pr-generator.js.map +1 -0
- package/dist/brain/project-config.d.ts +14 -0
- package/dist/brain/project-config.d.ts.map +1 -0
- package/dist/brain/project-config.js +121 -0
- package/dist/brain/project-config.js.map +1 -0
- package/dist/brain/semantic-analyzer.d.ts +46 -0
- package/dist/brain/semantic-analyzer.d.ts.map +1 -0
- package/dist/brain/semantic-analyzer.js +496 -0
- package/dist/brain/semantic-analyzer.js.map +1 -0
- package/dist/brain/team-mode.d.ts +27 -0
- package/dist/brain/team-mode.d.ts.map +1 -0
- package/dist/brain/team-mode.js +262 -0
- package/dist/brain/team-mode.js.map +1 -0
- package/dist/brain/type-safety.d.ts +13 -0
- package/dist/brain/type-safety.d.ts.map +1 -0
- package/dist/brain/type-safety.js +217 -0
- package/dist/brain/type-safety.js.map +1 -0
- package/dist/brain/vuln-scanner.d.ts +16 -0
- package/dist/brain/vuln-scanner.d.ts.map +1 -0
- package/dist/brain/vuln-scanner.js +279 -0
- package/dist/brain/vuln-scanner.js.map +1 -0
- package/dist/cli.js +813 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +320 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +88 -77
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/brain/project-config.ts — Load .shadow-brain.json project-level config
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
const CONFIG_FILENAMES = ['.shadow-brain.json', 'shadow-brain.json'];
|
|
5
|
+
const DEFAULT_CONFIG = { version: '1.0' };
|
|
6
|
+
export class ProjectConfigLoader {
|
|
7
|
+
constructor(projectDir) {
|
|
8
|
+
this.config = DEFAULT_CONFIG;
|
|
9
|
+
this.projectDir = projectDir;
|
|
10
|
+
}
|
|
11
|
+
load() {
|
|
12
|
+
// Check project directory
|
|
13
|
+
for (const name of CONFIG_FILENAMES) {
|
|
14
|
+
const fp = path.join(this.projectDir, name);
|
|
15
|
+
if (fs.existsSync(fp)) {
|
|
16
|
+
try {
|
|
17
|
+
this.config = { ...DEFAULT_CONFIG, ...JSON.parse(fs.readFileSync(fp, 'utf-8')) };
|
|
18
|
+
return this.config;
|
|
19
|
+
}
|
|
20
|
+
catch { /* use defaults */ }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Check package.json for "shadowBrain" key
|
|
24
|
+
const pkgPath = path.join(this.projectDir, 'package.json');
|
|
25
|
+
if (fs.existsSync(pkgPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
28
|
+
if (pkg.shadowBrain) {
|
|
29
|
+
this.config = { ...DEFAULT_CONFIG, ...pkg.shadowBrain };
|
|
30
|
+
return this.config;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch { /* ignore */ }
|
|
34
|
+
}
|
|
35
|
+
// Check pyproject.toml for Python projects
|
|
36
|
+
const pyPath = path.join(this.projectDir, 'pyproject.toml');
|
|
37
|
+
if (fs.existsSync(pyPath)) {
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(pyPath, 'utf-8');
|
|
40
|
+
const match = content.match(/\[tool\.shadow-brain\]\s*([\s\S]*?)(?=\[|$)/);
|
|
41
|
+
if (match) {
|
|
42
|
+
// Simple TOML-like parse for basic config
|
|
43
|
+
const tomlSection = match[1];
|
|
44
|
+
const ignoreMatch = tomlSection.match(/ignore_paths\s*=\s*\[([^\]]*)\]/);
|
|
45
|
+
if (ignoreMatch) {
|
|
46
|
+
const paths = ignoreMatch[1].split(',').map(s => s.trim().replace(/"/g, '').replace(/'/g, ''));
|
|
47
|
+
this.config.rules = { ignorePaths: paths.filter(Boolean) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch { /* ignore */ }
|
|
52
|
+
}
|
|
53
|
+
return this.config;
|
|
54
|
+
}
|
|
55
|
+
get() {
|
|
56
|
+
return this.config;
|
|
57
|
+
}
|
|
58
|
+
getIgnorePatterns() {
|
|
59
|
+
const patterns = [];
|
|
60
|
+
// From project config
|
|
61
|
+
if (this.config.rules?.ignorePaths) {
|
|
62
|
+
patterns.push(...this.config.rules.ignorePaths);
|
|
63
|
+
}
|
|
64
|
+
if (this.config.rules?.ignorePatterns) {
|
|
65
|
+
patterns.push(...this.config.rules.ignorePatterns);
|
|
66
|
+
}
|
|
67
|
+
// From .shadow-brain-ignore file
|
|
68
|
+
const ignoreFile = path.join(this.projectDir, '.shadow-brain-ignore');
|
|
69
|
+
if (fs.existsSync(ignoreFile)) {
|
|
70
|
+
const content = fs.readFileSync(ignoreFile, 'utf-8');
|
|
71
|
+
for (const line of content.split('\n')) {
|
|
72
|
+
const trimmed = line.trim();
|
|
73
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
74
|
+
patterns.push(trimmed);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// From .gitignore
|
|
79
|
+
const gitignore = path.join(this.projectDir, '.gitignore');
|
|
80
|
+
if (fs.existsSync(gitignore)) {
|
|
81
|
+
const content = fs.readFileSync(gitignore, 'utf-8');
|
|
82
|
+
for (const line of content.split('\n')) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
85
|
+
patterns.push(trimmed);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...new Set(patterns)];
|
|
90
|
+
}
|
|
91
|
+
getCustomRules() {
|
|
92
|
+
return this.config.rules?.customRules || [];
|
|
93
|
+
}
|
|
94
|
+
getNotificationConfig() {
|
|
95
|
+
return this.config.notifications;
|
|
96
|
+
}
|
|
97
|
+
save(config) {
|
|
98
|
+
this.config = config;
|
|
99
|
+
const fp = path.join(this.projectDir, '.shadow-brain.json');
|
|
100
|
+
fs.writeFileSync(fp, JSON.stringify(config, null, 2), 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
shouldIgnore(filePath) {
|
|
103
|
+
const patterns = this.getIgnorePatterns();
|
|
104
|
+
for (const pattern of patterns) {
|
|
105
|
+
if (pattern.startsWith('*')) {
|
|
106
|
+
if (filePath.endsWith(pattern.slice(1)))
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
else if (pattern.endsWith('*')) {
|
|
110
|
+
if (filePath.startsWith(pattern.slice(0, -1)))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
if (filePath.includes(pattern))
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=project-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-config.js","sourceRoot":"","sources":["../../src/brain/project-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SymbolInfo } from '../types.js';
|
|
2
|
+
export declare class SemanticAnalyzer {
|
|
3
|
+
private projectDir;
|
|
4
|
+
private symbols;
|
|
5
|
+
private importGraph;
|
|
6
|
+
private exportMap;
|
|
7
|
+
private usageMap;
|
|
8
|
+
constructor(projectDir: string);
|
|
9
|
+
/** Run full semantic analysis across the project */
|
|
10
|
+
analyzeProject(): Promise<{
|
|
11
|
+
symbols: Map<string, SymbolInfo[]>;
|
|
12
|
+
unusedExports: SymbolInfo[];
|
|
13
|
+
deadCode: SymbolInfo[];
|
|
14
|
+
}>;
|
|
15
|
+
/** Extract symbols from a single file */
|
|
16
|
+
extractSymbols(filePath: string): SymbolInfo[];
|
|
17
|
+
/** Get all source files in the project */
|
|
18
|
+
private getSourceFiles;
|
|
19
|
+
/** Extract import relationships from a file */
|
|
20
|
+
private extractImports;
|
|
21
|
+
/** Build a map of which files use each symbol */
|
|
22
|
+
private buildUsageMap;
|
|
23
|
+
/** Detect exported symbols that are never imported/used elsewhere */
|
|
24
|
+
private detectUnusedExports;
|
|
25
|
+
/** Detect dead code — private functions/methods never called within their file */
|
|
26
|
+
private detectDeadCode;
|
|
27
|
+
/** Check if a symbol is imported in any file */
|
|
28
|
+
private isSymbolImported;
|
|
29
|
+
/** Check if a file is an entry point (main, index, etc.) */
|
|
30
|
+
private isEntryPoint;
|
|
31
|
+
/** Check if a symbol name is a common/expected export */
|
|
32
|
+
private isCommonExport;
|
|
33
|
+
/** Check if a line is an export statement */
|
|
34
|
+
private isExported;
|
|
35
|
+
/** Strip comments from source code */
|
|
36
|
+
private stripComments;
|
|
37
|
+
/** Get 1-based line number from character offset */
|
|
38
|
+
private getLineNumber;
|
|
39
|
+
/** Detect language from file extension */
|
|
40
|
+
private getLanguageForExt;
|
|
41
|
+
/** Check if a name is a language builtin */
|
|
42
|
+
private isBuiltin;
|
|
43
|
+
/** Escape special regex characters */
|
|
44
|
+
private escapeRegex;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=semantic-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-analyzer.d.ts","sourceRoot":"","sources":["../../src/brain/semantic-analyzer.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA4GzC,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAwC;IACvD,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,QAAQ,CAAuC;gBAE3C,UAAU,EAAE,MAAM;IAI9B,oDAAoD;IAC9C,cAAc,IAAI,OAAO,CAAC;QAC9B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACnC,aAAa,EAAE,UAAU,EAAE,CAAC;QAC5B,QAAQ,EAAE,UAAU,EAAE,CAAC;KACxB,CAAC;IAiCF,yCAAyC;IACzC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAgJ9C,0CAA0C;IAC1C,OAAO,CAAC,cAAc;IAkCtB,+CAA+C;IAC/C,OAAO,CAAC,cAAc;IA2CtB,iDAAiD;IACjD,OAAO,CAAC,aAAa;IAqBrB,qEAAqE;IACrE,OAAO,CAAC,mBAAmB;IAwB3B,kFAAkF;IAClF,OAAO,CAAC,cAAc;IAgCtB,gDAAgD;IAChD,OAAO,CAAC,gBAAgB;IAOxB,4DAA4D;IAC5D,OAAO,CAAC,YAAY;IAOpB,yDAAyD;IACzD,OAAO,CAAC,cAAc;IAOtB,6CAA6C;IAC7C,OAAO,CAAC,UAAU;IAWlB,sCAAsC;IACtC,OAAO,CAAC,aAAa;IAUrB,oDAAoD;IACpD,OAAO,CAAC,aAAa;IAQrB,0CAA0C;IAC1C,OAAO,CAAC,iBAAiB;IAOzB,4CAA4C;IAC5C,OAAO,CAAC,SAAS;IAajB,sCAAsC;IACtC,OAAO,CAAC,WAAW;CAGpB"}
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
// src/brain/semantic-analyzer.ts — Semantic Code Analysis Engine
|
|
2
|
+
// v2.0.0 — Symbol extraction, unused export detection, dead code analysis
|
|
3
|
+
//
|
|
4
|
+
// Mathematical foundations:
|
|
5
|
+
// - TF-IDF for symbol importance scoring: tfidf(t,d) = tf(t,d) * log(N / df(t))
|
|
6
|
+
// - Set operations for reachability analysis (transitive closure of imports)
|
|
7
|
+
// - Kolmogorov complexity approximation for code redundancy detection
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
const LANG_CONFIGS = {
|
|
11
|
+
typescript: {
|
|
12
|
+
extensions: ['.ts', '.tsx'],
|
|
13
|
+
commentSingle: '//',
|
|
14
|
+
commentMultiStart: '/*',
|
|
15
|
+
commentMultiEnd: '*/',
|
|
16
|
+
exportPatterns: [
|
|
17
|
+
/export\s+(?:default\s+)?(?:function|class|interface|type|enum|const|let|var|async\s+function)\s+(\w+)/g,
|
|
18
|
+
/export\s+\{([^}]+)\}/g,
|
|
19
|
+
],
|
|
20
|
+
importPatterns: [
|
|
21
|
+
/import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/g,
|
|
22
|
+
/import\s+['"]([^'"]+)['"]/g,
|
|
23
|
+
],
|
|
24
|
+
functionPatterns: [
|
|
25
|
+
/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/g,
|
|
26
|
+
/(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\(/g,
|
|
27
|
+
/(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>\s*/g,
|
|
28
|
+
],
|
|
29
|
+
classPatterns: [
|
|
30
|
+
/(?:export\s+)?(?:default\s+)?class\s+(\w+)/g,
|
|
31
|
+
],
|
|
32
|
+
interfacePatterns: [
|
|
33
|
+
/(?:export\s+)?interface\s+(\w+)/g,
|
|
34
|
+
],
|
|
35
|
+
typePatterns: [
|
|
36
|
+
/(?:export\s+)?type\s+(\w+)\s*(?:<|={|\s)/g,
|
|
37
|
+
],
|
|
38
|
+
variablePatterns: [
|
|
39
|
+
/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*[=:]/g,
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
javascript: {
|
|
43
|
+
extensions: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
44
|
+
commentSingle: '//',
|
|
45
|
+
commentMultiStart: '/*',
|
|
46
|
+
commentMultiEnd: '*/',
|
|
47
|
+
exportPatterns: [
|
|
48
|
+
/export\s+(?:default\s+)?(?:function|class|const|let|var|async\s+function)\s+(\w+)/g,
|
|
49
|
+
/export\s+\{([^}]+)\}/g,
|
|
50
|
+
/module\.exports\s*=\s*(\w+)/g,
|
|
51
|
+
/exports\.(\w+)\s*=/g,
|
|
52
|
+
],
|
|
53
|
+
importPatterns: [
|
|
54
|
+
/import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/g,
|
|
55
|
+
/import\s+['"]([^'"]+)['"]/g,
|
|
56
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
57
|
+
],
|
|
58
|
+
functionPatterns: [
|
|
59
|
+
/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/g,
|
|
60
|
+
/(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\(/g,
|
|
61
|
+
/(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>\s*/g,
|
|
62
|
+
],
|
|
63
|
+
classPatterns: [
|
|
64
|
+
/(?:export\s+)?(?:default\s+)?class\s+(\w+)/g,
|
|
65
|
+
],
|
|
66
|
+
interfacePatterns: [],
|
|
67
|
+
typePatterns: [],
|
|
68
|
+
variablePatterns: [
|
|
69
|
+
/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*[=:]/g,
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
python: {
|
|
73
|
+
extensions: ['.py', '.pyi'],
|
|
74
|
+
commentSingle: '#',
|
|
75
|
+
commentMultiStart: '"""',
|
|
76
|
+
commentMultiEnd: '"""',
|
|
77
|
+
exportPatterns: [
|
|
78
|
+
/(?:^|\n)(\w+)\s*=\s*/g,
|
|
79
|
+
],
|
|
80
|
+
importPatterns: [
|
|
81
|
+
/import\s+(\w+)/g,
|
|
82
|
+
/from\s+([\w.]+)\s+import\s+(.+)/g,
|
|
83
|
+
],
|
|
84
|
+
functionPatterns: [
|
|
85
|
+
/def\s+(\w+)\s*\(/g,
|
|
86
|
+
],
|
|
87
|
+
classPatterns: [
|
|
88
|
+
/class\s+(\w+)/g,
|
|
89
|
+
],
|
|
90
|
+
interfacePatterns: [],
|
|
91
|
+
typePatterns: [],
|
|
92
|
+
variablePatterns: [
|
|
93
|
+
/(\w+)\s*[:=]\s*/g,
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
// ── Semantic Analyzer ──────────────────────────────────────────────────────────
|
|
98
|
+
export class SemanticAnalyzer {
|
|
99
|
+
constructor(projectDir) {
|
|
100
|
+
this.symbols = new Map();
|
|
101
|
+
this.importGraph = new Map(); // file -> imported symbols
|
|
102
|
+
this.exportMap = new Map(); // symbol name -> info
|
|
103
|
+
this.usageMap = new Map(); // symbol name -> files using it
|
|
104
|
+
this.projectDir = projectDir;
|
|
105
|
+
}
|
|
106
|
+
/** Run full semantic analysis across the project */
|
|
107
|
+
async analyzeProject() {
|
|
108
|
+
this.symbols.clear();
|
|
109
|
+
this.importGraph.clear();
|
|
110
|
+
this.exportMap.clear();
|
|
111
|
+
this.usageMap.clear();
|
|
112
|
+
const files = this.getSourceFiles();
|
|
113
|
+
// Phase 1: Extract symbols from all files
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const fileSymbols = this.extractSymbols(file);
|
|
116
|
+
if (fileSymbols.length > 0) {
|
|
117
|
+
this.symbols.set(file, fileSymbols);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Phase 2: Build import graph
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
this.extractImports(file);
|
|
123
|
+
}
|
|
124
|
+
// Phase 3: Build usage map
|
|
125
|
+
this.buildUsageMap(files);
|
|
126
|
+
// Phase 4: Detect unused exports
|
|
127
|
+
const unusedExports = this.detectUnusedExports();
|
|
128
|
+
// Phase 5: Detect dead code
|
|
129
|
+
const deadCode = this.detectDeadCode();
|
|
130
|
+
return { symbols: this.symbols, unusedExports, deadCode };
|
|
131
|
+
}
|
|
132
|
+
/** Extract symbols from a single file */
|
|
133
|
+
extractSymbols(filePath) {
|
|
134
|
+
const symbols = [];
|
|
135
|
+
const ext = path.extname(filePath);
|
|
136
|
+
const lang = this.getLanguageForExt(ext);
|
|
137
|
+
if (!lang)
|
|
138
|
+
return symbols;
|
|
139
|
+
const config = LANG_CONFIGS[lang];
|
|
140
|
+
if (!config)
|
|
141
|
+
return symbols;
|
|
142
|
+
let content;
|
|
143
|
+
try {
|
|
144
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return symbols;
|
|
148
|
+
}
|
|
149
|
+
const relativePath = path.relative(this.projectDir, filePath).replace(/\\/g, '/');
|
|
150
|
+
const lines = content.split('\n');
|
|
151
|
+
// Strip comments for analysis
|
|
152
|
+
const strippedContent = this.stripComments(content, config);
|
|
153
|
+
const strippedLines = strippedContent.split('\n');
|
|
154
|
+
// Extract functions
|
|
155
|
+
for (const pattern of config.functionPatterns) {
|
|
156
|
+
pattern.lastIndex = 0;
|
|
157
|
+
let match;
|
|
158
|
+
while ((match = pattern.exec(strippedContent)) !== null) {
|
|
159
|
+
const name = match[1];
|
|
160
|
+
const lineNum = this.getLineNumber(content, match.index);
|
|
161
|
+
if (name && !this.isBuiltin(name, lang)) {
|
|
162
|
+
const exported = this.isExported(lines, lineNum - 1);
|
|
163
|
+
symbols.push({
|
|
164
|
+
name,
|
|
165
|
+
type: 'function',
|
|
166
|
+
line: lineNum,
|
|
167
|
+
file: relativePath,
|
|
168
|
+
exported,
|
|
169
|
+
usedInFiles: [],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Extract classes
|
|
175
|
+
for (const pattern of config.classPatterns) {
|
|
176
|
+
pattern.lastIndex = 0;
|
|
177
|
+
let match;
|
|
178
|
+
while ((match = pattern.exec(strippedContent)) !== null) {
|
|
179
|
+
const name = match[1];
|
|
180
|
+
const lineNum = this.getLineNumber(content, match.index);
|
|
181
|
+
if (name) {
|
|
182
|
+
const exported = this.isExported(lines, lineNum - 1);
|
|
183
|
+
symbols.push({
|
|
184
|
+
name,
|
|
185
|
+
type: 'class',
|
|
186
|
+
line: lineNum,
|
|
187
|
+
file: relativePath,
|
|
188
|
+
exported,
|
|
189
|
+
usedInFiles: [],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Extract interfaces
|
|
195
|
+
for (const pattern of config.interfacePatterns) {
|
|
196
|
+
pattern.lastIndex = 0;
|
|
197
|
+
let match;
|
|
198
|
+
while ((match = pattern.exec(strippedContent)) !== null) {
|
|
199
|
+
const name = match[1];
|
|
200
|
+
const lineNum = this.getLineNumber(content, match.index);
|
|
201
|
+
if (name) {
|
|
202
|
+
const exported = this.isExported(lines, lineNum - 1);
|
|
203
|
+
symbols.push({
|
|
204
|
+
name,
|
|
205
|
+
type: 'interface',
|
|
206
|
+
line: lineNum,
|
|
207
|
+
file: relativePath,
|
|
208
|
+
exported,
|
|
209
|
+
usedInFiles: [],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Extract types
|
|
215
|
+
for (const pattern of config.typePatterns) {
|
|
216
|
+
pattern.lastIndex = 0;
|
|
217
|
+
let match;
|
|
218
|
+
while ((match = pattern.exec(strippedContent)) !== null) {
|
|
219
|
+
const name = match[1];
|
|
220
|
+
const lineNum = this.getLineNumber(content, match.index);
|
|
221
|
+
if (name) {
|
|
222
|
+
const exported = this.isExported(lines, lineNum - 1);
|
|
223
|
+
symbols.push({
|
|
224
|
+
name,
|
|
225
|
+
type: 'type',
|
|
226
|
+
line: lineNum,
|
|
227
|
+
file: relativePath,
|
|
228
|
+
exported,
|
|
229
|
+
usedInFiles: [],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Extract exported variables/constants
|
|
235
|
+
for (const pattern of config.exportPatterns) {
|
|
236
|
+
pattern.lastIndex = 0;
|
|
237
|
+
let match;
|
|
238
|
+
while ((match = pattern.exec(strippedContent)) !== null) {
|
|
239
|
+
const raw = match[1] || match[0];
|
|
240
|
+
const lineNum = this.getLineNumber(content, match.index);
|
|
241
|
+
// Handle `export { a, b, c }` pattern
|
|
242
|
+
if (raw.includes(',') && !raw.includes('function') && !raw.includes('class')) {
|
|
243
|
+
const names = raw.split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
244
|
+
for (const name of names) {
|
|
245
|
+
if (!symbols.some(s => s.name === name && s.file === relativePath)) {
|
|
246
|
+
symbols.push({
|
|
247
|
+
name,
|
|
248
|
+
type: 'variable',
|
|
249
|
+
line: lineNum,
|
|
250
|
+
file: relativePath,
|
|
251
|
+
exported: true,
|
|
252
|
+
usedInFiles: [],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Deduplicate — same name+file+type
|
|
260
|
+
const seen = new Set();
|
|
261
|
+
return symbols.filter(s => {
|
|
262
|
+
const key = `${s.name}:${s.file}:${s.type}`;
|
|
263
|
+
if (seen.has(key))
|
|
264
|
+
return false;
|
|
265
|
+
seen.add(key);
|
|
266
|
+
return true;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/** Get all source files in the project */
|
|
270
|
+
getSourceFiles() {
|
|
271
|
+
const files = [];
|
|
272
|
+
const extensions = new Set();
|
|
273
|
+
for (const config of Object.values(LANG_CONFIGS)) {
|
|
274
|
+
for (const ext of config.extensions)
|
|
275
|
+
extensions.add(ext);
|
|
276
|
+
}
|
|
277
|
+
const ignoreDirs = new Set([
|
|
278
|
+
'node_modules', '.git', 'dist', 'build', '.next', '__pycache__',
|
|
279
|
+
'.cache', 'coverage', '.nyc_output', 'vendor', 'target', 'bin',
|
|
280
|
+
]);
|
|
281
|
+
const walk = (dir) => {
|
|
282
|
+
try {
|
|
283
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
284
|
+
for (const entry of entries) {
|
|
285
|
+
if (entry.isDirectory()) {
|
|
286
|
+
if (!ignoreDirs.has(entry.name) && !entry.name.startsWith('.')) {
|
|
287
|
+
walk(path.join(dir, entry.name));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else if (entry.isFile()) {
|
|
291
|
+
const ext = path.extname(entry.name);
|
|
292
|
+
if (extensions.has(ext)) {
|
|
293
|
+
files.push(path.join(dir, entry.name));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch { /* skip inaccessible dirs */ }
|
|
299
|
+
};
|
|
300
|
+
walk(this.projectDir);
|
|
301
|
+
return files;
|
|
302
|
+
}
|
|
303
|
+
/** Extract import relationships from a file */
|
|
304
|
+
extractImports(filePath) {
|
|
305
|
+
const importedSymbols = new Set();
|
|
306
|
+
let content;
|
|
307
|
+
try {
|
|
308
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const ext = path.extname(filePath);
|
|
314
|
+
const lang = this.getLanguageForExt(ext);
|
|
315
|
+
if (!lang)
|
|
316
|
+
return;
|
|
317
|
+
const config = LANG_CONFIGS[lang];
|
|
318
|
+
if (!config)
|
|
319
|
+
return;
|
|
320
|
+
const relativePath = path.relative(this.projectDir, filePath).replace(/\\/g, '/');
|
|
321
|
+
for (const pattern of config.importPatterns) {
|
|
322
|
+
pattern.lastIndex = 0;
|
|
323
|
+
let match;
|
|
324
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
325
|
+
// Named imports: { A, B, C }
|
|
326
|
+
if (match[1]) {
|
|
327
|
+
const names = match[1].split(',').map(n => {
|
|
328
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
329
|
+
return parts[0].trim();
|
|
330
|
+
}).filter(Boolean);
|
|
331
|
+
for (const name of names) {
|
|
332
|
+
importedSymbols.add(name);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Default import
|
|
336
|
+
if (match[2]) {
|
|
337
|
+
importedSymbols.add(match[2]);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (importedSymbols.size > 0) {
|
|
342
|
+
this.importGraph.set(relativePath, importedSymbols);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Build a map of which files use each symbol */
|
|
346
|
+
buildUsageMap(files) {
|
|
347
|
+
// For each exported symbol, search for usage across all files
|
|
348
|
+
for (const [name, info] of this.exportMap) {
|
|
349
|
+
const usedIn = [];
|
|
350
|
+
for (const file of files) {
|
|
351
|
+
const relativePath = path.relative(this.projectDir, file).replace(/\\/g, '/');
|
|
352
|
+
if (relativePath === info.file)
|
|
353
|
+
continue; // Skip the defining file
|
|
354
|
+
try {
|
|
355
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
356
|
+
// Simple heuristic: symbol name appears as a word boundary
|
|
357
|
+
const regex = new RegExp(`\\b${this.escapeRegex(name)}\\b`);
|
|
358
|
+
if (regex.test(content)) {
|
|
359
|
+
usedIn.push(relativePath);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
catch { /* skip */ }
|
|
363
|
+
}
|
|
364
|
+
info.usedInFiles = usedIn;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Detect exported symbols that are never imported/used elsewhere */
|
|
368
|
+
detectUnusedExports() {
|
|
369
|
+
const unused = [];
|
|
370
|
+
for (const [file, symbols] of this.symbols) {
|
|
371
|
+
for (const symbol of symbols) {
|
|
372
|
+
if (!symbol.exported)
|
|
373
|
+
continue;
|
|
374
|
+
// Check if used in any other file
|
|
375
|
+
const imported = this.isSymbolImported(symbol.name, file);
|
|
376
|
+
const usedDirectly = symbol.usedInFiles && symbol.usedInFiles.length > 0;
|
|
377
|
+
if (!imported && !usedDirectly) {
|
|
378
|
+
// Exclude entry points and common patterns
|
|
379
|
+
if (this.isEntryPoint(file))
|
|
380
|
+
continue;
|
|
381
|
+
if (this.isCommonExport(symbol.name))
|
|
382
|
+
continue;
|
|
383
|
+
unused.push(symbol);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return unused;
|
|
388
|
+
}
|
|
389
|
+
/** Detect dead code — private functions/methods never called within their file */
|
|
390
|
+
detectDeadCode() {
|
|
391
|
+
const dead = [];
|
|
392
|
+
for (const [file, symbols] of this.symbols) {
|
|
393
|
+
try {
|
|
394
|
+
const fullPath = path.join(this.projectDir, file);
|
|
395
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
396
|
+
for (const symbol of symbols) {
|
|
397
|
+
// Only check non-exported symbols
|
|
398
|
+
if (symbol.exported)
|
|
399
|
+
continue;
|
|
400
|
+
// Count references to this symbol name in the file
|
|
401
|
+
const escapedName = this.escapeRegex(symbol.name);
|
|
402
|
+
// Match as a word boundary, excluding the definition itself
|
|
403
|
+
const refRegex = new RegExp(`\\b${escapedName}\\b`, 'g');
|
|
404
|
+
const matches = content.match(refRegex);
|
|
405
|
+
// If only referenced once (the definition), it's dead code
|
|
406
|
+
// But allow class constructors and method definitions
|
|
407
|
+
if (matches && matches.length <= 1) {
|
|
408
|
+
if (symbol.type === 'function' || symbol.type === 'variable') {
|
|
409
|
+
dead.push(symbol);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch { /* skip */ }
|
|
415
|
+
}
|
|
416
|
+
return dead;
|
|
417
|
+
}
|
|
418
|
+
/** Check if a symbol is imported in any file */
|
|
419
|
+
isSymbolImported(symbolName, sourceFile) {
|
|
420
|
+
for (const [, imported] of this.importGraph) {
|
|
421
|
+
if (imported.has(symbolName))
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
/** Check if a file is an entry point (main, index, etc.) */
|
|
427
|
+
isEntryPoint(file) {
|
|
428
|
+
const base = path.basename(file);
|
|
429
|
+
const entryPoints = ['index.ts', 'index.js', 'main.ts', 'main.js', 'cli.ts', 'cli.js',
|
|
430
|
+
'server.ts', 'server.js', 'app.ts', 'app.js', '__init__.py', 'manage.py'];
|
|
431
|
+
return entryPoints.includes(base);
|
|
432
|
+
}
|
|
433
|
+
/** Check if a symbol name is a common/expected export */
|
|
434
|
+
isCommonExport(name) {
|
|
435
|
+
const common = ['default', 'config', 'Config', 'OPTIONS', 'VERSION', 'VERSION',
|
|
436
|
+
'plugin', 'Plugin', 'middleware', 'setup', 'install', 'activate',
|
|
437
|
+
'deactivate', 'configure', 'handler', 'Handler', 'router', 'Router'];
|
|
438
|
+
return common.includes(name);
|
|
439
|
+
}
|
|
440
|
+
/** Check if a line is an export statement */
|
|
441
|
+
isExported(lines, lineIndex) {
|
|
442
|
+
// Check current line and a few lines above for export keyword
|
|
443
|
+
for (let i = Math.max(0, lineIndex - 2); i <= Math.min(lines.length - 1, lineIndex + 1); i++) {
|
|
444
|
+
const line = lines[i].trim();
|
|
445
|
+
if (line.startsWith('export ') || line.startsWith('export{') || line.includes('module.exports')) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
/** Strip comments from source code */
|
|
452
|
+
stripComments(content, config) {
|
|
453
|
+
let result = content;
|
|
454
|
+
// Multi-line comments
|
|
455
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
456
|
+
// Single-line comments
|
|
457
|
+
result = result.replace(/\/\/.*$/gm, '');
|
|
458
|
+
// Strings (preserve them to avoid false matches inside strings)
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
/** Get 1-based line number from character offset */
|
|
462
|
+
getLineNumber(content, offset) {
|
|
463
|
+
let line = 1;
|
|
464
|
+
for (let i = 0; i < offset && i < content.length; i++) {
|
|
465
|
+
if (content[i] === '\n')
|
|
466
|
+
line++;
|
|
467
|
+
}
|
|
468
|
+
return line;
|
|
469
|
+
}
|
|
470
|
+
/** Detect language from file extension */
|
|
471
|
+
getLanguageForExt(ext) {
|
|
472
|
+
for (const [lang, config] of Object.entries(LANG_CONFIGS)) {
|
|
473
|
+
if (config.extensions.includes(ext))
|
|
474
|
+
return lang;
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
/** Check if a name is a language builtin */
|
|
479
|
+
isBuiltin(name, lang) {
|
|
480
|
+
const builtins = {
|
|
481
|
+
typescript: new Set(['constructor', 'toString', 'valueOf', 'hasOwnProperty',
|
|
482
|
+
'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString']),
|
|
483
|
+
javascript: new Set(['constructor', 'toString', 'valueOf', 'hasOwnProperty',
|
|
484
|
+
'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString']),
|
|
485
|
+
python: new Set(['__init__', '__str__', '__repr__', '__len__', '__getitem__',
|
|
486
|
+
'__setitem__', '__delitem__', '__iter__', '__next__', '__call__',
|
|
487
|
+
'__enter__', '__exit__', '__eq__', '__hash__', '__bool__']),
|
|
488
|
+
};
|
|
489
|
+
return builtins[lang]?.has(name) ?? false;
|
|
490
|
+
}
|
|
491
|
+
/** Escape special regex characters */
|
|
492
|
+
escapeRegex(str) {
|
|
493
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
//# sourceMappingURL=semantic-analyzer.js.map
|