stackmem 1.0.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 +223 -0
- package/dist/ast-index.js +272 -0
- package/dist/cli.js +718 -0
- package/dist/config.js +2 -0
- package/dist/device.js +30 -0
- package/dist/index.js +324 -0
- package/dist/scoring.js +217 -0
- package/dist/storage.js +53 -0
- package/dist/test-scoring.js +7 -0
- package/dist/tools.js +1 -0
- package/package.json +34 -0
- package/src/ast-index.ts +349 -0
- package/src/cli.ts +814 -0
- package/src/config.ts +3 -0
- package/src/device.ts +34 -0
- package/src/index.ts +436 -0
- package/src/scoring.ts +276 -0
- package/src/storage.ts +57 -0
- package/src/test-scoring.ts +11 -0
- package/src/tools.ts +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# coding-memory
|
|
2
|
+
|
|
3
|
+
AI agents forget everything between sessions. Every new chat re-derives decisions, re-breaks constraints that were already settled, and re-debugs errors that were already fixed last week.
|
|
4
|
+
|
|
5
|
+
coding-memory is a CLI and an MCP server that stores that context in Supabase and hands it back at the start of the next session. A git post-commit hook captures most of it automatically.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
git clone https://github.com/arushlagudu/coding-memory.git
|
|
11
|
+
cd coding-memory
|
|
12
|
+
npm install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Create `.env`:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
19
|
+
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Build and link:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npm run build
|
|
26
|
+
sudo npm link
|
|
27
|
+
sudo npm install -g tsx
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`cm` is now on your PATH. Install the hook in a project:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
$ cd your-project-directory
|
|
34
|
+
$ cm init myapp .
|
|
35
|
+
coding-memory hook installed for project myapp at .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
cm start <project>
|
|
42
|
+
cm save <project> <type> <content> [--force]
|
|
43
|
+
cm fix <project> <problem> <solution>
|
|
44
|
+
cm analyze <project> <path>
|
|
45
|
+
cm search <project> <query>
|
|
46
|
+
cm resolve <project> <memory-id>
|
|
47
|
+
cm delete <project> <memory-id>
|
|
48
|
+
cm context <project> <task>
|
|
49
|
+
cm compress <project>
|
|
50
|
+
cm init <project> <path>
|
|
51
|
+
cm help
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### cm start
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
$ cm start myapp
|
|
58
|
+
Loaded 2 memory(ies) for "myapp".
|
|
59
|
+
|
|
60
|
+
Decisions (1)
|
|
61
|
+
-------------
|
|
62
|
+
[294fba3a-aef3-472a-933f-b6bfd41c7cfa] Use Supabase for persistence layer
|
|
63
|
+
linked: 793a0053-83b6-463a-9e6d-df8b7c2fbed8
|
|
64
|
+
|
|
65
|
+
Constraints (1)
|
|
66
|
+
---------------
|
|
67
|
+
[793a0053-83b6-463a-9e6d-df8b7c2fbed8] Service role key must never be exposed to a client
|
|
68
|
+
linked: 294fba3a-aef3-472a-933f-b6bfd41c7cfa
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### cm save
|
|
72
|
+
|
|
73
|
+
`type` is one of `decision`, `rejection`, `constraint`, `discovery`.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
$ cm save myapp decision "Use ilike search across memories and execution_log"
|
|
77
|
+
Saved decision [294fba3a-aef3-472a-933f-b6bfd41c7cfa] for "myapp".
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
If the new content contradicts something already stored, it asks first.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
$ cm save myapp decision "We switched from Supabase to Postgres directly"
|
|
84
|
+
Conflict detected:
|
|
85
|
+
Old: Use Supabase for persistence layer
|
|
86
|
+
New: We switched from Supabase to Postgres directly
|
|
87
|
+
|
|
88
|
+
Keep old or replace? (k/r): r
|
|
89
|
+
Replaced with new memory [b1a2c3d4-5678-90ab-cdef-1234567890ab].
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`--force` skips the prompt and always replaces. Use it from scripts.
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
$ cm save myapp decision "We switched from Supabase to Postgres directly" --force
|
|
96
|
+
Replaced with new memory [b1a2c3d4-5678-90ab-cdef-1234567890ab].
|
|
97
|
+
Saved decision [b1a2c3d4-5678-90ab-cdef-1234567890ab] for "myapp".
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### cm fix
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
$ cm fix myapp "tsc failed with Node16 module resolution error" "switched moduleResolution to Node16"
|
|
104
|
+
Fix recorded for "myapp".
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### cm analyze
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
$ cm analyze myapp .
|
|
111
|
+
Indexed 7 file(s):
|
|
112
|
+
functions: 24
|
|
113
|
+
classes: 0
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Runs tree-sitter over the project and writes an AST index to `.coding-memory/ast-index.db`.
|
|
117
|
+
|
|
118
|
+
### cm search
|
|
119
|
+
|
|
120
|
+
Checks memories and past fixes in the same query.
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
$ cm search myapp "module resolution"
|
|
124
|
+
Memories (0)
|
|
125
|
+
No results found
|
|
126
|
+
|
|
127
|
+
Past Fixes (1)
|
|
128
|
+
Problem: tsc failed with Node16 module resolution error
|
|
129
|
+
Solution: switched moduleResolution to Node16
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### cm resolve / cm delete
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
$ cm resolve myapp 294fba3a-aef3-472a-933f-b6bfd41c7cfa
|
|
136
|
+
Resolved memory [294fba3a-aef3-472a-933f-b6bfd41c7cfa]
|
|
137
|
+
|
|
138
|
+
$ cm delete myapp 793a0053-83b6-463a-9e6d-df8b7c2fbed8
|
|
139
|
+
Deleted memory [793a0053-83b6-463a-9e6d-df8b7c2fbed8]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`delete` also removes any memory_links pointing at that id.
|
|
143
|
+
|
|
144
|
+
### cm context
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
$ cm context myapp "fix the module resolution error in the build"
|
|
148
|
+
## Project Context: myapp
|
|
149
|
+
### Task: fix the module resolution error in the build
|
|
150
|
+
|
|
151
|
+
**Constraints**
|
|
152
|
+
- Service role key must never be exposed to a client
|
|
153
|
+
|
|
154
|
+
**Discoveries**
|
|
155
|
+
- schema change: migrations/003_add_access_count.sql
|
|
156
|
+
|
|
157
|
+
*(41/2000 tokens used)*
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Top 5 memories relevant to a task, ranked by relevance and decay, capped at 2000 tokens. Feed this to an agent instead of the whole project's memory.
|
|
161
|
+
|
|
162
|
+
### cm compress
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
$ cm compress myapp
|
|
166
|
+
Compressed 4 memories into 1 summary
|
|
167
|
+
Compressed 3 memories into 1 summary
|
|
168
|
+
Compression complete. 2 clusters compressed.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Needs 20+ unresolved memories. Also runs automatically once a project crosses that count.
|
|
172
|
+
|
|
173
|
+
### cm init
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
$ cm init myapp .
|
|
177
|
+
coding-memory hook installed for project myapp at .
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Installs a post-commit hook that calls `dist/cli.js save` after every commit with the commit message, plus whatever the diff turns up: removed imports, new env vars, new auth files, package.json changes, new config files, schema files, TODOs.
|
|
181
|
+
|
|
182
|
+
### cm help
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
$ cm help
|
|
186
|
+
cm — coding memory CLI
|
|
187
|
+
|
|
188
|
+
cm start <project>
|
|
189
|
+
Load all unresolved memories for a project, sorted by decay
|
|
190
|
+
score and capped at a 2000 token context budget.
|
|
191
|
+
|
|
192
|
+
cm save <project> <type> <content> [--force]
|
|
193
|
+
Save a new memory. Prompts before overwriting a contradiction
|
|
194
|
+
unless --force is passed.
|
|
195
|
+
|
|
196
|
+
...
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## How it works
|
|
200
|
+
|
|
201
|
+
Memories are decisions, constraints, discoveries, and rejections, scoped to a project. Fixes are stored separately as a problem string and the solution that closed it, so the same tsc error doesn't cost another 20 minutes next month.
|
|
202
|
+
|
|
203
|
+
Every save checks new content against existing memories with a containment score: shared terms over the size of the smaller set. Above 0.85 with no negation word, it's a duplicate and gets skipped. With a negation word like "switched" or "no longer" and a score above 0.4, it's a contradiction.
|
|
204
|
+
|
|
205
|
+
cm save stops and asks whether to keep the old memory or replace it, unless `--force` is passed. The MCP `save_memory` tool always replaces automatically, since nothing is watching stdin on that path.
|
|
206
|
+
|
|
207
|
+
New memories are also compared against everything else and linked above a lower threshold of 0.15. `cm start` and `cm context` show those links without any manual tagging.
|
|
208
|
+
|
|
209
|
+
Relevance decays over time: `score = (1 + ln(1 + accessCount)) * exp(-0.05 * daysSinceCreated)`. Frequently accessed memories decay slower, but nothing survives forever. The 0.05 constant works out to roughly a 14-day half life.
|
|
210
|
+
|
|
211
|
+
`cm start` and `cm context` cap output at 2000 tokens. They fill from the highest-ranked memory down until the next one would push past that limit.
|
|
212
|
+
|
|
213
|
+
Past 20 unresolved memories, `cm compress` clusters related memories by containment score above 0.2 using union-find. Clusters of 3 or more collapse into a single summary memory.
|
|
214
|
+
|
|
215
|
+
## Research
|
|
216
|
+
|
|
217
|
+
Ideas taken from a few papers, not implementations of them.
|
|
218
|
+
|
|
219
|
+
- Codebase-Memory: Tree-Sitter-Based Knowledge Graphs for LLM Code Exploration via MCP (arXiv:2603.27277)
|
|
220
|
+
- Feedback-Normalized Developer Memory for Reinforcement-Learning Coding Agents (arXiv:2605.01567)
|
|
221
|
+
- A-MEM: Agentic Memory for LLM Agents (arXiv:2502.12110, NeurIPS 2025)
|
|
222
|
+
- SWE-MeM: Learning Adaptive Memory Management for Long-Horizon Coding Agents (arXiv:2606.28434)
|
|
223
|
+
- AtomMem: Learnable Dynamic Agentic Memory with Atomic Memory Operation (arXiv:2601.08323)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import Parser from "tree-sitter";
|
|
5
|
+
import TreeSitterTypeScript from "tree-sitter-typescript";
|
|
6
|
+
import TreeSitterJavaScript from "tree-sitter-javascript";
|
|
7
|
+
const TypeScript = TreeSitterTypeScript.typescript;
|
|
8
|
+
const SKIP_DIRS = new Set([
|
|
9
|
+
"node_modules",
|
|
10
|
+
".git",
|
|
11
|
+
"dist",
|
|
12
|
+
"build",
|
|
13
|
+
"out",
|
|
14
|
+
".next",
|
|
15
|
+
".turbo",
|
|
16
|
+
"coverage",
|
|
17
|
+
".coding-memory",
|
|
18
|
+
]);
|
|
19
|
+
const FUNCTION_NODE_TYPES = new Set([
|
|
20
|
+
"function_declaration",
|
|
21
|
+
"function_expression",
|
|
22
|
+
"generator_function_declaration",
|
|
23
|
+
"generator_function",
|
|
24
|
+
"method_definition",
|
|
25
|
+
]);
|
|
26
|
+
function getDbPath(projectPath) {
|
|
27
|
+
return path.join(projectPath, ".coding-memory", "ast-index.db");
|
|
28
|
+
}
|
|
29
|
+
function openDb(projectPath) {
|
|
30
|
+
const dbDir = path.join(projectPath, ".coding-memory");
|
|
31
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
32
|
+
const db = new Database(getDbPath(projectPath));
|
|
33
|
+
db.pragma("journal_mode = WAL");
|
|
34
|
+
db.exec(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
36
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
+
file_path TEXT NOT NULL UNIQUE,
|
|
38
|
+
file_name TEXT NOT NULL,
|
|
39
|
+
indexed_at TEXT NOT NULL
|
|
40
|
+
);
|
|
41
|
+
CREATE TABLE IF NOT EXISTS functions (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
44
|
+
name TEXT NOT NULL
|
|
45
|
+
);
|
|
46
|
+
CREATE TABLE IF NOT EXISTS classes (
|
|
47
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
48
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
49
|
+
name TEXT NOT NULL
|
|
50
|
+
);
|
|
51
|
+
CREATE TABLE IF NOT EXISTS imports (
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
54
|
+
source TEXT NOT NULL
|
|
55
|
+
);
|
|
56
|
+
`);
|
|
57
|
+
return db;
|
|
58
|
+
}
|
|
59
|
+
function walkProjectFiles(projectPath) {
|
|
60
|
+
const results = [];
|
|
61
|
+
function walk(dir) {
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
if (SKIP_DIRS.has(entry.name))
|
|
72
|
+
continue;
|
|
73
|
+
walk(path.join(dir, entry.name));
|
|
74
|
+
}
|
|
75
|
+
else if (entry.isFile()) {
|
|
76
|
+
if (/\.(ts|js)$/.test(entry.name) && !entry.name.endsWith(".d.ts")) {
|
|
77
|
+
results.push(path.join(dir, entry.name));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
walk(projectPath);
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
function nameOfNode(node) {
|
|
86
|
+
if (!node)
|
|
87
|
+
return null;
|
|
88
|
+
return node.text;
|
|
89
|
+
}
|
|
90
|
+
function extractIdentifierFromLhs(node) {
|
|
91
|
+
// For `const foo = () => {}` / `const foo = function () {}`, the
|
|
92
|
+
// enclosing variable_declarator's `name` field holds the identifier.
|
|
93
|
+
if (node.type === "identifier")
|
|
94
|
+
return node.text;
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function extractFromSource(sourceCode, language) {
|
|
98
|
+
const parser = new Parser();
|
|
99
|
+
parser.setLanguage(language);
|
|
100
|
+
const tree = parser.parse(sourceCode);
|
|
101
|
+
const functions = [];
|
|
102
|
+
const classes = [];
|
|
103
|
+
const imports = [];
|
|
104
|
+
function visit(node) {
|
|
105
|
+
switch (node.type) {
|
|
106
|
+
case "function_declaration":
|
|
107
|
+
case "generator_function_declaration": {
|
|
108
|
+
const name = nameOfNode(node.childForFieldName("name"));
|
|
109
|
+
if (name)
|
|
110
|
+
functions.push(name);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "method_definition": {
|
|
114
|
+
const name = nameOfNode(node.childForFieldName("name"));
|
|
115
|
+
if (name)
|
|
116
|
+
functions.push(name);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "function_expression":
|
|
120
|
+
case "generator_function":
|
|
121
|
+
case "arrow_function": {
|
|
122
|
+
const declaredName = nameOfNode(node.childForFieldName("name"));
|
|
123
|
+
if (declaredName) {
|
|
124
|
+
functions.push(declaredName);
|
|
125
|
+
}
|
|
126
|
+
else if (node.parent?.type === "variable_declarator") {
|
|
127
|
+
const lhs = extractIdentifierFromLhs(node.parent.childForFieldName("name"));
|
|
128
|
+
if (lhs)
|
|
129
|
+
functions.push(lhs);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "class_declaration":
|
|
134
|
+
case "class": {
|
|
135
|
+
const name = nameOfNode(node.childForFieldName("name"));
|
|
136
|
+
if (name) {
|
|
137
|
+
classes.push(name);
|
|
138
|
+
}
|
|
139
|
+
else if (node.parent?.type === "variable_declarator") {
|
|
140
|
+
const lhs = extractIdentifierFromLhs(node.parent.childForFieldName("name"));
|
|
141
|
+
if (lhs)
|
|
142
|
+
classes.push(lhs);
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "import_statement": {
|
|
147
|
+
const source = node.childForFieldName("source");
|
|
148
|
+
if (source)
|
|
149
|
+
imports.push(source.text.replace(/^['"]|['"]$/g, ""));
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "call_expression": {
|
|
153
|
+
const fn = node.childForFieldName("function");
|
|
154
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
155
|
+
const args = node.childForFieldName("arguments");
|
|
156
|
+
const firstArg = args?.namedChild(0);
|
|
157
|
+
if (firstArg?.type === "string") {
|
|
158
|
+
imports.push(firstArg.text.replace(/^['"]|['"]$/g, ""));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const child of node.namedChildren) {
|
|
165
|
+
visit(child);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
visit(tree.rootNode);
|
|
169
|
+
return { functions, classes, imports };
|
|
170
|
+
}
|
|
171
|
+
function extractFile(filePath, projectPath) {
|
|
172
|
+
const sourceCode = fs.readFileSync(filePath, "utf-8");
|
|
173
|
+
const language = filePath.endsWith(".ts") ? TypeScript : TreeSitterJavaScript;
|
|
174
|
+
const { functions, classes, imports } = extractFromSource(sourceCode, language);
|
|
175
|
+
return {
|
|
176
|
+
relativePath: path.relative(projectPath, filePath),
|
|
177
|
+
functions,
|
|
178
|
+
classes,
|
|
179
|
+
imports,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export function analyzeCodebase(projectPath) {
|
|
183
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
184
|
+
const db = openDb(resolvedProjectPath);
|
|
185
|
+
const insertFile = db.prepare("INSERT INTO files (file_path, file_name, indexed_at) VALUES (?, ?, ?) ON CONFLICT(file_path) DO UPDATE SET indexed_at = excluded.indexed_at RETURNING id");
|
|
186
|
+
const deleteChildren = db.prepare("DELETE FROM functions WHERE file_id = ?");
|
|
187
|
+
const deleteClasses = db.prepare("DELETE FROM classes WHERE file_id = ?");
|
|
188
|
+
const deleteImports = db.prepare("DELETE FROM imports WHERE file_id = ?");
|
|
189
|
+
const insertFunction = db.prepare("INSERT INTO functions (file_id, name) VALUES (?, ?)");
|
|
190
|
+
const insertClass = db.prepare("INSERT INTO classes (file_id, name) VALUES (?, ?)");
|
|
191
|
+
const insertImport = db.prepare("INSERT INTO imports (file_id, source) VALUES (?, ?)");
|
|
192
|
+
const files = walkProjectFiles(resolvedProjectPath);
|
|
193
|
+
let totalFunctions = 0;
|
|
194
|
+
let totalClasses = 0;
|
|
195
|
+
const runAll = db.transaction((filePaths) => {
|
|
196
|
+
for (const filePath of filePaths) {
|
|
197
|
+
const extraction = extractFile(filePath, resolvedProjectPath);
|
|
198
|
+
const row = insertFile.get(extraction.relativePath, path.basename(extraction.relativePath), new Date().toISOString());
|
|
199
|
+
const fileId = row.id;
|
|
200
|
+
deleteChildren.run(fileId);
|
|
201
|
+
deleteClasses.run(fileId);
|
|
202
|
+
deleteImports.run(fileId);
|
|
203
|
+
for (const name of extraction.functions) {
|
|
204
|
+
insertFunction.run(fileId, name);
|
|
205
|
+
totalFunctions++;
|
|
206
|
+
}
|
|
207
|
+
for (const name of extraction.classes) {
|
|
208
|
+
insertClass.run(fileId, name);
|
|
209
|
+
totalClasses++;
|
|
210
|
+
}
|
|
211
|
+
for (const source of extraction.imports) {
|
|
212
|
+
insertImport.run(fileId, source);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
runAll(files);
|
|
217
|
+
db.close();
|
|
218
|
+
return {
|
|
219
|
+
files_indexed: files.length,
|
|
220
|
+
functions: totalFunctions,
|
|
221
|
+
classes: totalClasses,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function findFileRow(db, fileName) {
|
|
225
|
+
const exact = db
|
|
226
|
+
.prepare("SELECT id, file_path, file_name FROM files WHERE file_path = ?")
|
|
227
|
+
.get(fileName);
|
|
228
|
+
if (exact)
|
|
229
|
+
return exact;
|
|
230
|
+
return db
|
|
231
|
+
.prepare("SELECT id, file_path, file_name FROM files WHERE file_name = ?")
|
|
232
|
+
.get(path.basename(fileName));
|
|
233
|
+
}
|
|
234
|
+
export function getFileSummary(projectPath, fileName) {
|
|
235
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
236
|
+
const dbPath = getDbPath(resolvedProjectPath);
|
|
237
|
+
if (!fs.existsSync(dbPath)) {
|
|
238
|
+
throw new Error(`No AST index found at ${dbPath}. Run analyze_codebase on this project first.`);
|
|
239
|
+
}
|
|
240
|
+
const db = new Database(dbPath, { readonly: true });
|
|
241
|
+
try {
|
|
242
|
+
const fileRow = findFileRow(db, fileName);
|
|
243
|
+
if (!fileRow) {
|
|
244
|
+
throw new Error(`File "${fileName}" not found in the AST index for ${resolvedProjectPath}.`);
|
|
245
|
+
}
|
|
246
|
+
const functions = db.prepare("SELECT name FROM functions WHERE file_id = ?").all(fileRow.id).map((r) => r.name);
|
|
247
|
+
const classes = db.prepare("SELECT name FROM classes WHERE file_id = ?").all(fileRow.id).map((r) => r.name);
|
|
248
|
+
return { file_path: fileRow.file_path, functions, classes };
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
db.close();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export function findDependencies(projectPath, fileName) {
|
|
255
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
256
|
+
const dbPath = getDbPath(resolvedProjectPath);
|
|
257
|
+
if (!fs.existsSync(dbPath)) {
|
|
258
|
+
throw new Error(`No AST index found at ${dbPath}. Run analyze_codebase on this project first.`);
|
|
259
|
+
}
|
|
260
|
+
const db = new Database(dbPath, { readonly: true });
|
|
261
|
+
try {
|
|
262
|
+
const fileRow = findFileRow(db, fileName);
|
|
263
|
+
if (!fileRow) {
|
|
264
|
+
throw new Error(`File "${fileName}" not found in the AST index for ${resolvedProjectPath}.`);
|
|
265
|
+
}
|
|
266
|
+
const imports = db.prepare("SELECT source FROM imports WHERE file_id = ?").all(fileRow.id).map((r) => r.source);
|
|
267
|
+
return { file_path: fileRow.file_path, imports };
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
db.close();
|
|
271
|
+
}
|
|
272
|
+
}
|