codemap-ai 0.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 +181 -0
- package/dist/chunk-5ONPBEWJ.js +350 -0
- package/dist/chunk-5ONPBEWJ.js.map +1 -0
- package/dist/chunk-FLUWKIEM.js +347 -0
- package/dist/chunk-FLUWKIEM.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1302 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +331 -0
- package/dist/index.js +1163 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +365 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/server-ACGCJ3GE.js +87 -0
- package/dist/server-ACGCJ3GE.js.map +1 -0
- package/dist/server-TBIVIIUJ.js +367 -0
- package/dist/server-TBIVIIUJ.js.map +1 -0
- package/package.json +70 -0
- package/web/index.html +639 -0
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# CodeMap
|
|
2
|
+
|
|
3
|
+
AI-powered codebase knowledge graph generator with Claude Code integration.
|
|
4
|
+
|
|
5
|
+
**CodeMap** scans your codebase, builds a knowledge graph of all functions, classes, imports, and their relationships, then generates Claude Code skills that help the AI understand your project.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Multi-language support**: Python, TypeScript, JavaScript (more coming)
|
|
10
|
+
- **Knowledge graph**: SQLite-based graph of code relationships
|
|
11
|
+
- **Auto-generated skills**: Creates Claude Code skills from your codebase
|
|
12
|
+
- **Impact analysis**: See what files are affected by changes
|
|
13
|
+
- **Web visualization**: Interactive D3 force-directed graph
|
|
14
|
+
- **MCP server**: Direct integration with Claude Code
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Scan your project
|
|
20
|
+
npx codemap-ai scan /path/to/project
|
|
21
|
+
|
|
22
|
+
# Generate Claude Code skills
|
|
23
|
+
npx codemap-ai generate-skills /path/to/project
|
|
24
|
+
|
|
25
|
+
# Start visualization server
|
|
26
|
+
npx codemap-ai serve /path/to/project
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
### `codemap scan [path]`
|
|
32
|
+
|
|
33
|
+
Scan a codebase and build the knowledge graph.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
codemap scan .
|
|
37
|
+
codemap scan /path/to/project --clean
|
|
38
|
+
codemap scan . --include "src/**/*.ts" --exclude "**/*.test.ts"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
- `-o, --output <dir>` - Output directory (default: `.codemap`)
|
|
43
|
+
- `--include <patterns...>` - Glob patterns to include
|
|
44
|
+
- `--exclude <patterns...>` - Glob patterns to exclude
|
|
45
|
+
- `--clean` - Clear existing data before scanning
|
|
46
|
+
|
|
47
|
+
### `codemap generate-skills [path]`
|
|
48
|
+
|
|
49
|
+
Generate Claude Code skills from the knowledge graph.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
codemap generate-skills .
|
|
53
|
+
codemap generate-skills . --name "My Project"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This creates skills in `.claude/skills/codemap/`:
|
|
57
|
+
- `/architecture` - Project structure overview
|
|
58
|
+
- `/patterns` - Common code patterns
|
|
59
|
+
- `/dependencies` - Dependency explorer
|
|
60
|
+
- `/impact` - Change impact analysis
|
|
61
|
+
- `/navigate` - Code navigation helper
|
|
62
|
+
|
|
63
|
+
### `codemap affected <target>`
|
|
64
|
+
|
|
65
|
+
Show files that would be affected by changes.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
codemap affected src/auth/login.ts
|
|
69
|
+
codemap affected UserService
|
|
70
|
+
codemap affected handleSubmit --depth 3
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `codemap stats`
|
|
74
|
+
|
|
75
|
+
Show statistics about the analyzed codebase.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
codemap stats
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `codemap serve [path]`
|
|
82
|
+
|
|
83
|
+
Start the web visualization server.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
codemap serve .
|
|
87
|
+
codemap serve . --port 8080
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Opens an interactive graph at `http://localhost:3333`.
|
|
91
|
+
|
|
92
|
+
### `codemap mcp-server`
|
|
93
|
+
|
|
94
|
+
Start the MCP server for Claude Code integration.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Add to Claude Code
|
|
98
|
+
claude mcp add codemap -- npx codemap-ai mcp-server --path /path/to/project
|
|
99
|
+
|
|
100
|
+
# Or run directly
|
|
101
|
+
codemap mcp-server --path .
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## MCP Tools
|
|
105
|
+
|
|
106
|
+
When connected to Claude Code, these tools become available:
|
|
107
|
+
|
|
108
|
+
- `codemap_search` - Search for functions, classes, files
|
|
109
|
+
- `codemap_callers` - Find all callers of a function
|
|
110
|
+
- `codemap_dependencies` - Get import/dependency info
|
|
111
|
+
- `codemap_impact` - Analyze change impact
|
|
112
|
+
- `codemap_stats` - Get codebase statistics
|
|
113
|
+
- `codemap_file_contents` - List contents of a file
|
|
114
|
+
|
|
115
|
+
## Generated Skills
|
|
116
|
+
|
|
117
|
+
After running `generate-skills`, Claude Code can use these commands:
|
|
118
|
+
|
|
119
|
+
### `/architecture`
|
|
120
|
+
Shows project structure, statistics, and key directories.
|
|
121
|
+
|
|
122
|
+
### `/patterns`
|
|
123
|
+
Explains common patterns, key classes, and coding conventions.
|
|
124
|
+
|
|
125
|
+
### `/dependencies [module]`
|
|
126
|
+
Explores what a module imports and what imports it.
|
|
127
|
+
|
|
128
|
+
### `/impact [file or function]`
|
|
129
|
+
Analyzes what would be affected by changes.
|
|
130
|
+
|
|
131
|
+
### `/navigate [query]`
|
|
132
|
+
Finds and navigates to specific code.
|
|
133
|
+
|
|
134
|
+
## Programmatic Usage
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { scan, generateSkills, scanAndGenerate } from 'codemap-ai';
|
|
138
|
+
|
|
139
|
+
// Scan only
|
|
140
|
+
const { analysis, dbPath } = await scan('/path/to/project');
|
|
141
|
+
|
|
142
|
+
// Generate skills from existing scan
|
|
143
|
+
const skills = generateSkills('/path/to/project', 'My Project');
|
|
144
|
+
|
|
145
|
+
// Scan and generate in one step
|
|
146
|
+
const result = await scanAndGenerate('/path/to/project', 'My Project');
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## How It Works
|
|
150
|
+
|
|
151
|
+
1. **Parsing**: Uses Tree-sitter to parse source files into ASTs
|
|
152
|
+
2. **Graph Building**: Extracts nodes (files, functions, classes) and edges (imports, calls, extends)
|
|
153
|
+
3. **Storage**: Stores everything in a SQLite database for fast queries
|
|
154
|
+
4. **Skill Generation**: Analyzes the graph to create helpful Claude Code skills
|
|
155
|
+
5. **MCP Server**: Exposes graph queries as MCP tools
|
|
156
|
+
|
|
157
|
+
## Supported Languages
|
|
158
|
+
|
|
159
|
+
| Language | Parsing | Imports | Calls | Classes |
|
|
160
|
+
|----------|---------|---------|-------|---------|
|
|
161
|
+
| TypeScript | ✅ | ✅ | ✅ | ✅ |
|
|
162
|
+
| JavaScript | ✅ | ✅ | ✅ | ✅ |
|
|
163
|
+
| Python | ✅ | ✅ | ✅ | ✅ |
|
|
164
|
+
| Go | 🚧 | 🚧 | 🚧 | 🚧 |
|
|
165
|
+
| Rust | 🚧 | 🚧 | 🚧 | 🚧 |
|
|
166
|
+
|
|
167
|
+
## Configuration
|
|
168
|
+
|
|
169
|
+
Create a `codemap.config.json` in your project root:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"include": ["src/**/*.ts", "lib/**/*.py"],
|
|
174
|
+
"exclude": ["**/*.test.ts", "**/node_modules/**"],
|
|
175
|
+
"languages": ["typescript", "python"]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/graph/storage.ts
|
|
5
|
+
import Database from "better-sqlite3";
|
|
6
|
+
var GraphStorage = class {
|
|
7
|
+
db;
|
|
8
|
+
constructor(dbPath) {
|
|
9
|
+
this.db = new Database(dbPath);
|
|
10
|
+
this.initSchema();
|
|
11
|
+
}
|
|
12
|
+
initSchema() {
|
|
13
|
+
this.db.exec(`
|
|
14
|
+
-- Nodes table
|
|
15
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
type TEXT NOT NULL,
|
|
18
|
+
name TEXT NOT NULL,
|
|
19
|
+
file_path TEXT NOT NULL,
|
|
20
|
+
start_line INTEGER NOT NULL,
|
|
21
|
+
end_line INTEGER NOT NULL,
|
|
22
|
+
language TEXT NOT NULL,
|
|
23
|
+
metadata TEXT
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
-- Edges table
|
|
27
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
type TEXT NOT NULL,
|
|
30
|
+
source_id TEXT NOT NULL,
|
|
31
|
+
target_id TEXT NOT NULL,
|
|
32
|
+
metadata TEXT
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
-- Files table for tracking analyzed files
|
|
36
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
37
|
+
path TEXT PRIMARY KEY,
|
|
38
|
+
language TEXT NOT NULL,
|
|
39
|
+
last_modified INTEGER,
|
|
40
|
+
analyzed_at TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
-- Imports table
|
|
44
|
+
CREATE TABLE IF NOT EXISTS imports (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
file_path TEXT NOT NULL,
|
|
47
|
+
source TEXT NOT NULL,
|
|
48
|
+
specifiers TEXT NOT NULL,
|
|
49
|
+
is_default INTEGER NOT NULL,
|
|
50
|
+
is_namespace INTEGER NOT NULL,
|
|
51
|
+
line INTEGER NOT NULL
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
-- Exports table
|
|
55
|
+
CREATE TABLE IF NOT EXISTS exports (
|
|
56
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
57
|
+
file_path TEXT NOT NULL,
|
|
58
|
+
name TEXT NOT NULL,
|
|
59
|
+
is_default INTEGER NOT NULL,
|
|
60
|
+
line INTEGER NOT NULL
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- Project metadata
|
|
64
|
+
CREATE TABLE IF NOT EXISTS project_meta (
|
|
65
|
+
key TEXT PRIMARY KEY,
|
|
66
|
+
value TEXT NOT NULL
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
-- Indexes for faster queries
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file_path);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_imports_file ON imports(file_path);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_imports_source ON imports(source);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_exports_file ON exports(file_path);
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
// ============ Node Operations ============
|
|
82
|
+
insertNode(node) {
|
|
83
|
+
const stmt = this.db.prepare(`
|
|
84
|
+
INSERT OR REPLACE INTO nodes (id, type, name, file_path, start_line, end_line, language, metadata)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
86
|
+
`);
|
|
87
|
+
stmt.run(
|
|
88
|
+
node.id,
|
|
89
|
+
node.type,
|
|
90
|
+
node.name,
|
|
91
|
+
node.filePath,
|
|
92
|
+
node.startLine,
|
|
93
|
+
node.endLine,
|
|
94
|
+
node.language,
|
|
95
|
+
node.metadata ? JSON.stringify(node.metadata) : null
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
getNode(id) {
|
|
99
|
+
const stmt = this.db.prepare("SELECT * FROM nodes WHERE id = ?");
|
|
100
|
+
const row = stmt.get(id);
|
|
101
|
+
if (!row) return null;
|
|
102
|
+
return this.rowToNode(row);
|
|
103
|
+
}
|
|
104
|
+
getNodesByFile(filePath) {
|
|
105
|
+
const stmt = this.db.prepare("SELECT * FROM nodes WHERE file_path = ?");
|
|
106
|
+
const rows = stmt.all(filePath);
|
|
107
|
+
return rows.map(this.rowToNode);
|
|
108
|
+
}
|
|
109
|
+
getNodesByType(type) {
|
|
110
|
+
const stmt = this.db.prepare("SELECT * FROM nodes WHERE type = ?");
|
|
111
|
+
const rows = stmt.all(type);
|
|
112
|
+
return rows.map(this.rowToNode);
|
|
113
|
+
}
|
|
114
|
+
searchNodes(query) {
|
|
115
|
+
const stmt = this.db.prepare(
|
|
116
|
+
"SELECT * FROM nodes WHERE name LIKE ? OR file_path LIKE ?"
|
|
117
|
+
);
|
|
118
|
+
const pattern = `%${query}%`;
|
|
119
|
+
const rows = stmt.all(pattern, pattern);
|
|
120
|
+
return rows.map(this.rowToNode);
|
|
121
|
+
}
|
|
122
|
+
getAllNodes() {
|
|
123
|
+
const stmt = this.db.prepare("SELECT * FROM nodes");
|
|
124
|
+
const rows = stmt.all();
|
|
125
|
+
return rows.map(this.rowToNode);
|
|
126
|
+
}
|
|
127
|
+
rowToNode(row) {
|
|
128
|
+
return {
|
|
129
|
+
id: row.id,
|
|
130
|
+
type: row.type,
|
|
131
|
+
name: row.name,
|
|
132
|
+
filePath: row.file_path,
|
|
133
|
+
startLine: row.start_line,
|
|
134
|
+
endLine: row.end_line,
|
|
135
|
+
language: row.language,
|
|
136
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// ============ Edge Operations ============
|
|
140
|
+
insertEdge(edge) {
|
|
141
|
+
const stmt = this.db.prepare(`
|
|
142
|
+
INSERT OR REPLACE INTO edges (id, type, source_id, target_id, metadata)
|
|
143
|
+
VALUES (?, ?, ?, ?, ?)
|
|
144
|
+
`);
|
|
145
|
+
stmt.run(
|
|
146
|
+
edge.id,
|
|
147
|
+
edge.type,
|
|
148
|
+
edge.sourceId,
|
|
149
|
+
edge.targetId,
|
|
150
|
+
edge.metadata ? JSON.stringify(edge.metadata) : null
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
getEdgesFrom(sourceId) {
|
|
154
|
+
const stmt = this.db.prepare("SELECT * FROM edges WHERE source_id = ?");
|
|
155
|
+
const rows = stmt.all(sourceId);
|
|
156
|
+
return rows.map(this.rowToEdge);
|
|
157
|
+
}
|
|
158
|
+
getEdgesTo(targetId) {
|
|
159
|
+
const stmt = this.db.prepare("SELECT * FROM edges WHERE target_id = ?");
|
|
160
|
+
const rows = stmt.all(targetId);
|
|
161
|
+
return rows.map(this.rowToEdge);
|
|
162
|
+
}
|
|
163
|
+
getEdgesByType(type) {
|
|
164
|
+
const stmt = this.db.prepare("SELECT * FROM edges WHERE type = ?");
|
|
165
|
+
const rows = stmt.all(type);
|
|
166
|
+
return rows.map(this.rowToEdge);
|
|
167
|
+
}
|
|
168
|
+
getAllEdges() {
|
|
169
|
+
const stmt = this.db.prepare("SELECT * FROM edges");
|
|
170
|
+
const rows = stmt.all();
|
|
171
|
+
return rows.map(this.rowToEdge);
|
|
172
|
+
}
|
|
173
|
+
rowToEdge(row) {
|
|
174
|
+
return {
|
|
175
|
+
id: row.id,
|
|
176
|
+
type: row.type,
|
|
177
|
+
sourceId: row.source_id,
|
|
178
|
+
targetId: row.target_id,
|
|
179
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// ============ File Analysis Operations ============
|
|
183
|
+
insertFileAnalysis(analysis) {
|
|
184
|
+
const transaction = this.db.transaction(() => {
|
|
185
|
+
this.deleteFileData(analysis.filePath);
|
|
186
|
+
const fileStmt = this.db.prepare(`
|
|
187
|
+
INSERT OR REPLACE INTO files (path, language, analyzed_at)
|
|
188
|
+
VALUES (?, ?, ?)
|
|
189
|
+
`);
|
|
190
|
+
fileStmt.run(analysis.filePath, analysis.language, (/* @__PURE__ */ new Date()).toISOString());
|
|
191
|
+
for (const node of analysis.nodes) {
|
|
192
|
+
this.insertNode(node);
|
|
193
|
+
}
|
|
194
|
+
for (const edge of analysis.edges) {
|
|
195
|
+
this.insertEdge(edge);
|
|
196
|
+
}
|
|
197
|
+
const importStmt = this.db.prepare(`
|
|
198
|
+
INSERT INTO imports (file_path, source, specifiers, is_default, is_namespace, line)
|
|
199
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
200
|
+
`);
|
|
201
|
+
for (const imp of analysis.imports) {
|
|
202
|
+
importStmt.run(
|
|
203
|
+
analysis.filePath,
|
|
204
|
+
imp.source,
|
|
205
|
+
JSON.stringify(imp.specifiers),
|
|
206
|
+
imp.isDefault ? 1 : 0,
|
|
207
|
+
imp.isNamespace ? 1 : 0,
|
|
208
|
+
imp.line
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const exportStmt = this.db.prepare(`
|
|
212
|
+
INSERT INTO exports (file_path, name, is_default, line)
|
|
213
|
+
VALUES (?, ?, ?, ?)
|
|
214
|
+
`);
|
|
215
|
+
for (const exp of analysis.exports) {
|
|
216
|
+
exportStmt.run(analysis.filePath, exp.name, exp.isDefault ? 1 : 0, exp.line);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
transaction();
|
|
220
|
+
}
|
|
221
|
+
deleteFileData(filePath) {
|
|
222
|
+
this.db.prepare("DELETE FROM nodes WHERE file_path = ?").run(filePath);
|
|
223
|
+
this.db.prepare("DELETE FROM imports WHERE file_path = ?").run(filePath);
|
|
224
|
+
this.db.prepare("DELETE FROM exports WHERE file_path = ?").run(filePath);
|
|
225
|
+
this.db.prepare("DELETE FROM files WHERE path = ?").run(filePath);
|
|
226
|
+
}
|
|
227
|
+
// ============ Query Operations ============
|
|
228
|
+
/**
|
|
229
|
+
* Get all files that import from a given file/module
|
|
230
|
+
*/
|
|
231
|
+
getFilesThatImport(modulePath) {
|
|
232
|
+
const stmt = this.db.prepare(`
|
|
233
|
+
SELECT DISTINCT file_path FROM imports
|
|
234
|
+
WHERE source LIKE ? OR source LIKE ?
|
|
235
|
+
`);
|
|
236
|
+
const rows = stmt.all(`%${modulePath}%`, modulePath);
|
|
237
|
+
return rows.map((r) => r.file_path);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get all callers of a function/method
|
|
241
|
+
*/
|
|
242
|
+
getCallers(nodeName) {
|
|
243
|
+
const stmt = this.db.prepare(`
|
|
244
|
+
SELECT DISTINCT n.* FROM nodes n
|
|
245
|
+
JOIN edges e ON n.id = e.source_id
|
|
246
|
+
WHERE e.type = 'calls' AND e.target_id LIKE ?
|
|
247
|
+
`);
|
|
248
|
+
const rows = stmt.all(`%${nodeName}%`);
|
|
249
|
+
return rows.map(this.rowToNode);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get all functions/methods called by a node
|
|
253
|
+
*/
|
|
254
|
+
getCallees(nodeId) {
|
|
255
|
+
const stmt = this.db.prepare(`
|
|
256
|
+
SELECT target_id, metadata FROM edges
|
|
257
|
+
WHERE source_id = ? AND type = 'calls'
|
|
258
|
+
`);
|
|
259
|
+
const rows = stmt.all(nodeId);
|
|
260
|
+
return rows.map((r) => ({
|
|
261
|
+
name: r.target_id.replace("ref:", ""),
|
|
262
|
+
line: r.metadata ? JSON.parse(r.metadata).line : void 0
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get the dependency tree for a file
|
|
267
|
+
*/
|
|
268
|
+
getFileDependencies(filePath) {
|
|
269
|
+
const importsStmt = this.db.prepare(
|
|
270
|
+
"SELECT DISTINCT source FROM imports WHERE file_path = ?"
|
|
271
|
+
);
|
|
272
|
+
const imports = importsStmt.all(filePath).map((r) => r.source);
|
|
273
|
+
const importedBy = this.getFilesThatImport(
|
|
274
|
+
filePath.replace(/\.(ts|tsx|js|jsx|py)$/, "")
|
|
275
|
+
);
|
|
276
|
+
return { imports, importedBy };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get statistics about the graph
|
|
280
|
+
*/
|
|
281
|
+
getStats() {
|
|
282
|
+
const totalFiles = this.db.prepare("SELECT COUNT(*) as count FROM files").get().count;
|
|
283
|
+
const totalNodes = this.db.prepare("SELECT COUNT(*) as count FROM nodes").get().count;
|
|
284
|
+
const totalEdges = this.db.prepare("SELECT COUNT(*) as count FROM edges").get().count;
|
|
285
|
+
const nodesByType = {};
|
|
286
|
+
const nodeTypeRows = this.db.prepare("SELECT type, COUNT(*) as count FROM nodes GROUP BY type").all();
|
|
287
|
+
for (const row of nodeTypeRows) {
|
|
288
|
+
nodesByType[row.type] = row.count;
|
|
289
|
+
}
|
|
290
|
+
const edgesByType = {};
|
|
291
|
+
const edgeTypeRows = this.db.prepare("SELECT type, COUNT(*) as count FROM edges GROUP BY type").all();
|
|
292
|
+
for (const row of edgeTypeRows) {
|
|
293
|
+
edgesByType[row.type] = row.count;
|
|
294
|
+
}
|
|
295
|
+
const languages = {};
|
|
296
|
+
const langRows = this.db.prepare("SELECT language, COUNT(*) as count FROM files GROUP BY language").all();
|
|
297
|
+
for (const row of langRows) {
|
|
298
|
+
languages[row.language] = row.count;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
totalFiles,
|
|
302
|
+
totalNodes,
|
|
303
|
+
totalEdges,
|
|
304
|
+
nodesByType,
|
|
305
|
+
edgesByType,
|
|
306
|
+
languages
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// ============ Graph Export ============
|
|
310
|
+
exportForVisualization() {
|
|
311
|
+
const nodes = this.getAllNodes().map((n) => ({
|
|
312
|
+
id: n.id,
|
|
313
|
+
label: n.name,
|
|
314
|
+
type: n.type,
|
|
315
|
+
file: n.filePath
|
|
316
|
+
}));
|
|
317
|
+
const edges = this.getAllEdges().filter((e) => !e.targetId.startsWith("ref:")).map((e) => ({
|
|
318
|
+
source: e.sourceId,
|
|
319
|
+
target: e.targetId,
|
|
320
|
+
type: e.type
|
|
321
|
+
}));
|
|
322
|
+
return { nodes, edges };
|
|
323
|
+
}
|
|
324
|
+
// ============ Cleanup ============
|
|
325
|
+
clear() {
|
|
326
|
+
this.db.exec(`
|
|
327
|
+
DELETE FROM nodes;
|
|
328
|
+
DELETE FROM edges;
|
|
329
|
+
DELETE FROM files;
|
|
330
|
+
DELETE FROM imports;
|
|
331
|
+
DELETE FROM exports;
|
|
332
|
+
`);
|
|
333
|
+
}
|
|
334
|
+
close() {
|
|
335
|
+
this.db.close();
|
|
336
|
+
}
|
|
337
|
+
// ============ Metadata ============
|
|
338
|
+
setMeta(key, value) {
|
|
339
|
+
this.db.prepare("INSERT OR REPLACE INTO project_meta (key, value) VALUES (?, ?)").run(key, value);
|
|
340
|
+
}
|
|
341
|
+
getMeta(key) {
|
|
342
|
+
const row = this.db.prepare("SELECT value FROM project_meta WHERE key = ?").get(key);
|
|
343
|
+
return row?.value || null;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export {
|
|
348
|
+
GraphStorage
|
|
349
|
+
};
|
|
350
|
+
//# sourceMappingURL=chunk-5ONPBEWJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph/storage.ts"],"sourcesContent":["/**\n * SQLite-based graph storage\n */\n\nimport Database from \"better-sqlite3\";\nimport type { GraphNode, GraphEdge, FileAnalysis, ProjectAnalysis } from \"../types.js\";\n\nexport class GraphStorage {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.initSchema();\n }\n\n private initSchema(): void {\n this.db.exec(`\n -- Nodes table\n CREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n file_path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n language TEXT NOT NULL,\n metadata TEXT\n );\n\n -- Edges table\n CREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n metadata TEXT\n );\n\n -- Files table for tracking analyzed files\n CREATE TABLE IF NOT EXISTS files (\n path TEXT PRIMARY KEY,\n language TEXT NOT NULL,\n last_modified INTEGER,\n analyzed_at TEXT NOT NULL\n );\n\n -- Imports table\n CREATE TABLE IF NOT EXISTS imports (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n file_path TEXT NOT NULL,\n source TEXT NOT NULL,\n specifiers TEXT NOT NULL,\n is_default INTEGER NOT NULL,\n is_namespace INTEGER NOT NULL,\n line INTEGER NOT NULL\n );\n\n -- Exports table\n CREATE TABLE IF NOT EXISTS exports (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n file_path TEXT NOT NULL,\n name TEXT NOT NULL,\n is_default INTEGER NOT NULL,\n line INTEGER NOT NULL\n );\n\n -- Project metadata\n CREATE TABLE IF NOT EXISTS project_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n -- Indexes for faster queries\n CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file_path);\n CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);\n CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\n CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);\n CREATE INDEX IF NOT EXISTS idx_imports_file ON imports(file_path);\n CREATE INDEX IF NOT EXISTS idx_imports_source ON imports(source);\n CREATE INDEX IF NOT EXISTS idx_exports_file ON exports(file_path);\n `);\n }\n\n // ============ Node Operations ============\n\n insertNode(node: GraphNode): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO nodes (id, type, name, file_path, start_line, end_line, language, metadata)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n stmt.run(\n node.id,\n node.type,\n node.name,\n node.filePath,\n node.startLine,\n node.endLine,\n node.language,\n node.metadata ? JSON.stringify(node.metadata) : null\n );\n }\n\n getNode(id: string): GraphNode | null {\n const stmt = this.db.prepare(\"SELECT * FROM nodes WHERE id = ?\");\n const row = stmt.get(id) as any;\n if (!row) return null;\n return this.rowToNode(row);\n }\n\n getNodesByFile(filePath: string): GraphNode[] {\n const stmt = this.db.prepare(\"SELECT * FROM nodes WHERE file_path = ?\");\n const rows = stmt.all(filePath) as any[];\n return rows.map(this.rowToNode);\n }\n\n getNodesByType(type: string): GraphNode[] {\n const stmt = this.db.prepare(\"SELECT * FROM nodes WHERE type = ?\");\n const rows = stmt.all(type) as any[];\n return rows.map(this.rowToNode);\n }\n\n searchNodes(query: string): GraphNode[] {\n const stmt = this.db.prepare(\n \"SELECT * FROM nodes WHERE name LIKE ? OR file_path LIKE ?\"\n );\n const pattern = `%${query}%`;\n const rows = stmt.all(pattern, pattern) as any[];\n return rows.map(this.rowToNode);\n }\n\n getAllNodes(): GraphNode[] {\n const stmt = this.db.prepare(\"SELECT * FROM nodes\");\n const rows = stmt.all() as any[];\n return rows.map(this.rowToNode);\n }\n\n private rowToNode(row: any): GraphNode {\n return {\n id: row.id,\n type: row.type,\n name: row.name,\n filePath: row.file_path,\n startLine: row.start_line,\n endLine: row.end_line,\n language: row.language,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n };\n }\n\n // ============ Edge Operations ============\n\n insertEdge(edge: GraphEdge): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO edges (id, type, source_id, target_id, metadata)\n VALUES (?, ?, ?, ?, ?)\n `);\n stmt.run(\n edge.id,\n edge.type,\n edge.sourceId,\n edge.targetId,\n edge.metadata ? JSON.stringify(edge.metadata) : null\n );\n }\n\n getEdgesFrom(sourceId: string): GraphEdge[] {\n const stmt = this.db.prepare(\"SELECT * FROM edges WHERE source_id = ?\");\n const rows = stmt.all(sourceId) as any[];\n return rows.map(this.rowToEdge);\n }\n\n getEdgesTo(targetId: string): GraphEdge[] {\n const stmt = this.db.prepare(\"SELECT * FROM edges WHERE target_id = ?\");\n const rows = stmt.all(targetId) as any[];\n return rows.map(this.rowToEdge);\n }\n\n getEdgesByType(type: string): GraphEdge[] {\n const stmt = this.db.prepare(\"SELECT * FROM edges WHERE type = ?\");\n const rows = stmt.all(type) as any[];\n return rows.map(this.rowToEdge);\n }\n\n getAllEdges(): GraphEdge[] {\n const stmt = this.db.prepare(\"SELECT * FROM edges\");\n const rows = stmt.all() as any[];\n return rows.map(this.rowToEdge);\n }\n\n private rowToEdge(row: any): GraphEdge {\n return {\n id: row.id,\n type: row.type,\n sourceId: row.source_id,\n targetId: row.target_id,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n };\n }\n\n // ============ File Analysis Operations ============\n\n insertFileAnalysis(analysis: FileAnalysis): void {\n const transaction = this.db.transaction(() => {\n // Delete old data for this file\n this.deleteFileData(analysis.filePath);\n\n // Insert file record\n const fileStmt = this.db.prepare(`\n INSERT OR REPLACE INTO files (path, language, analyzed_at)\n VALUES (?, ?, ?)\n `);\n fileStmt.run(analysis.filePath, analysis.language, new Date().toISOString());\n\n // Insert nodes\n for (const node of analysis.nodes) {\n this.insertNode(node);\n }\n\n // Insert edges\n for (const edge of analysis.edges) {\n this.insertEdge(edge);\n }\n\n // Insert imports\n const importStmt = this.db.prepare(`\n INSERT INTO imports (file_path, source, specifiers, is_default, is_namespace, line)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n for (const imp of analysis.imports) {\n importStmt.run(\n analysis.filePath,\n imp.source,\n JSON.stringify(imp.specifiers),\n imp.isDefault ? 1 : 0,\n imp.isNamespace ? 1 : 0,\n imp.line\n );\n }\n\n // Insert exports\n const exportStmt = this.db.prepare(`\n INSERT INTO exports (file_path, name, is_default, line)\n VALUES (?, ?, ?, ?)\n `);\n for (const exp of analysis.exports) {\n exportStmt.run(analysis.filePath, exp.name, exp.isDefault ? 1 : 0, exp.line);\n }\n });\n\n transaction();\n }\n\n deleteFileData(filePath: string): void {\n this.db.prepare(\"DELETE FROM nodes WHERE file_path = ?\").run(filePath);\n this.db.prepare(\"DELETE FROM imports WHERE file_path = ?\").run(filePath);\n this.db.prepare(\"DELETE FROM exports WHERE file_path = ?\").run(filePath);\n this.db.prepare(\"DELETE FROM files WHERE path = ?\").run(filePath);\n // Note: Edges are more complex, we keep them and clean up later\n }\n\n // ============ Query Operations ============\n\n /**\n * Get all files that import from a given file/module\n */\n getFilesThatImport(modulePath: string): string[] {\n const stmt = this.db.prepare(`\n SELECT DISTINCT file_path FROM imports\n WHERE source LIKE ? OR source LIKE ?\n `);\n // Match both relative and absolute imports\n const rows = stmt.all(`%${modulePath}%`, modulePath) as any[];\n return rows.map((r) => r.file_path);\n }\n\n /**\n * Get all callers of a function/method\n */\n getCallers(nodeName: string): GraphNode[] {\n const stmt = this.db.prepare(`\n SELECT DISTINCT n.* FROM nodes n\n JOIN edges e ON n.id = e.source_id\n WHERE e.type = 'calls' AND e.target_id LIKE ?\n `);\n const rows = stmt.all(`%${nodeName}%`) as any[];\n return rows.map(this.rowToNode);\n }\n\n /**\n * Get all functions/methods called by a node\n */\n getCallees(nodeId: string): { name: string; line?: number }[] {\n const stmt = this.db.prepare(`\n SELECT target_id, metadata FROM edges\n WHERE source_id = ? AND type = 'calls'\n `);\n const rows = stmt.all(nodeId) as any[];\n return rows.map((r) => ({\n name: r.target_id.replace(\"ref:\", \"\"),\n line: r.metadata ? JSON.parse(r.metadata).line : undefined,\n }));\n }\n\n /**\n * Get the dependency tree for a file\n */\n getFileDependencies(filePath: string): { imports: string[]; importedBy: string[] } {\n const importsStmt = this.db.prepare(\n \"SELECT DISTINCT source FROM imports WHERE file_path = ?\"\n );\n const imports = (importsStmt.all(filePath) as any[]).map((r) => r.source);\n\n const importedBy = this.getFilesThatImport(\n filePath.replace(/\\.(ts|tsx|js|jsx|py)$/, \"\")\n );\n\n return { imports, importedBy };\n }\n\n /**\n * Get statistics about the graph\n */\n getStats(): {\n totalFiles: number;\n totalNodes: number;\n totalEdges: number;\n nodesByType: Record<string, number>;\n edgesByType: Record<string, number>;\n languages: Record<string, number>;\n } {\n const totalFiles =\n (this.db.prepare(\"SELECT COUNT(*) as count FROM files\").get() as any).count;\n const totalNodes =\n (this.db.prepare(\"SELECT COUNT(*) as count FROM nodes\").get() as any).count;\n const totalEdges =\n (this.db.prepare(\"SELECT COUNT(*) as count FROM edges\").get() as any).count;\n\n const nodesByType: Record<string, number> = {};\n const nodeTypeRows = this.db\n .prepare(\"SELECT type, COUNT(*) as count FROM nodes GROUP BY type\")\n .all() as any[];\n for (const row of nodeTypeRows) {\n nodesByType[row.type] = row.count;\n }\n\n const edgesByType: Record<string, number> = {};\n const edgeTypeRows = this.db\n .prepare(\"SELECT type, COUNT(*) as count FROM edges GROUP BY type\")\n .all() as any[];\n for (const row of edgeTypeRows) {\n edgesByType[row.type] = row.count;\n }\n\n const languages: Record<string, number> = {};\n const langRows = this.db\n .prepare(\"SELECT language, COUNT(*) as count FROM files GROUP BY language\")\n .all() as any[];\n for (const row of langRows) {\n languages[row.language] = row.count;\n }\n\n return {\n totalFiles,\n totalNodes,\n totalEdges,\n nodesByType,\n edgesByType,\n languages,\n };\n }\n\n // ============ Graph Export ============\n\n exportForVisualization(): {\n nodes: Array<{ id: string; label: string; type: string; file: string }>;\n edges: Array<{ source: string; target: string; type: string }>;\n } {\n const nodes = this.getAllNodes().map((n) => ({\n id: n.id,\n label: n.name,\n type: n.type,\n file: n.filePath,\n }));\n\n const edges = this.getAllEdges()\n .filter((e) => !e.targetId.startsWith(\"ref:\")) // Only resolved edges\n .map((e) => ({\n source: e.sourceId,\n target: e.targetId,\n type: e.type,\n }));\n\n return { nodes, edges };\n }\n\n // ============ Cleanup ============\n\n clear(): void {\n this.db.exec(`\n DELETE FROM nodes;\n DELETE FROM edges;\n DELETE FROM files;\n DELETE FROM imports;\n DELETE FROM exports;\n `);\n }\n\n close(): void {\n this.db.close();\n }\n\n // ============ Metadata ============\n\n setMeta(key: string, value: string): void {\n this.db.prepare(\"INSERT OR REPLACE INTO project_meta (key, value) VALUES (?, ?)\").run(key, value);\n }\n\n getMeta(key: string): string | null {\n const row = this.db.prepare(\"SELECT value FROM project_meta WHERE key = ?\").get(key) as any;\n return row?.value || null;\n }\n}\n"],"mappings":"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","names":[]}
|