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 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":[]}