scai 0.1.115 → 0.1.116

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.
@@ -3,120 +3,268 @@ import path from 'path';
3
3
  import { generateEmbedding } from '../../lib/generateEmbedding.js';
4
4
  import { log } from '../../utils/log.js';
5
5
  import { getDbForRepo } from '../client.js';
6
- import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, } from '../sqlTemplates.js';
6
+ import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, insertFunctionTemplate, insertGraphClassTemplate, insertEdgeTemplate, insertGraphEntityTagTemplate, insertGraphTagTemplate, selectGraphTagIdTemplate, } from '../sqlTemplates.js';
7
+ import { kgModule } from '../../pipeline/modules/kgModule.js';
7
8
  export async function extractFromTS(filePath, content, fileId) {
8
9
  const db = getDbForRepo();
10
+ const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
9
11
  try {
10
12
  const project = new Project({ useInMemoryFileSystem: true });
11
13
  const sourceFile = project.createSourceFile(filePath, content);
14
+ // DTO arrays used to store into functions / graph_classes later (FTS index)
12
15
  const functions = [];
13
16
  const classes = [];
14
- const allFuncs = [
17
+ // --- Gather all function AST nodes (so we can both index and analyze calls) ---
18
+ const allFuncNodes = [
15
19
  ...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
16
20
  ...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
17
21
  ...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
18
22
  ];
19
- for (const fn of allFuncs) {
20
- const name = fn.getSymbol()?.getName() ?? `${path.basename(filePath)}:<anon>`;
21
- const start = fn.getStartLineNumber();
22
- const end = fn.getEndLineNumber();
23
- const code = fn.getText();
24
- functions.push({ name, start_line: start, end_line: end, content: code });
25
- }
26
- const allClasses = [
23
+ // --- Gather all class AST nodes ---
24
+ const allClassNodes = [
27
25
  ...sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration),
28
26
  ...sourceFile.getDescendantsOfKind(SyntaxKind.ClassExpression),
29
27
  ];
30
- for (const cls of allClasses) {
31
- const name = cls.getName() ?? `${path.basename(filePath)}:<anon-class>`;
32
- const start = cls.getStartLineNumber();
33
- const end = cls.getEndLineNumber();
34
- const code = cls.getText();
35
- const superClass = cls.getExtends()?.getText() ?? null;
36
- classes.push({
37
- name,
38
- start_line: start,
39
- end_line: end,
40
- content: code,
41
- superClass,
42
- });
43
- }
44
- if (functions.length === 0 && classes.length === 0) {
28
+ // If nothing found, mark as skipped
29
+ if (allFuncNodes.length === 0 && allClassNodes.length === 0) {
45
30
  log(`⚠️ No functions/classes found in TS file: ${filePath}`);
46
31
  db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
47
32
  return false;
48
33
  }
49
- log(`🔍 Found ${functions.length} functions and ${classes.length} classes in ${filePath}`);
50
- // Insert functions
51
- for (const fn of functions) {
52
- const embedding = await generateEmbedding(fn.content);
53
- const result = db
54
- .prepare(`
55
- INSERT INTO functions (
56
- file_id, name, start_line, end_line, content, embedding, lang
57
- ) VALUES (
58
- @file_id, @name, @start_line, @end_line, @content, @embedding, @lang
59
- )
60
- `)
61
- .run({
62
- file_id: fileId,
63
- name: fn.name,
64
- start_line: fn.start_line,
65
- end_line: fn.end_line,
66
- content: fn.content,
67
- embedding: JSON.stringify(embedding),
68
- lang: 'ts',
69
- });
70
- const functionId = result.lastInsertRowid;
71
- // file function edge
72
- db.prepare(`INSERT INTO edges (source_type, source_id, target_type, target_id, relation)
73
- VALUES ('file', @source_id, 'function', @target_id, 'contains')`).run({ source_id: fileId, target_id: functionId });
74
- // Simplified call detection (regex)
75
- const callMatches = fn.content.matchAll(/(\w+)\s*\(/g);
76
- for (const match of callMatches) {
77
- // Store call by name (resolution happens later)
78
- db.prepare(`INSERT INTO function_calls (caller_id, callee_name)
79
- VALUES (@caller_id, @callee_name)`).run({
80
- caller_id: functionId,
81
- callee_name: match[1],
34
+ log(`🔍 Found ${allFuncNodes.length} functions and ${allClassNodes.length} classes in ${filePath}`);
35
+ // --- Optionally ask LLM (kgModule) for tags for the file, persist them and associate with entities if possible ---
36
+ try {
37
+ const kgInput = {
38
+ fileId,
39
+ filepath: filePath,
40
+ summary: undefined,
41
+ };
42
+ const kgResult = await kgModule.run(kgInput, content);
43
+ if (kgResult.entities?.length > 0) {
44
+ const insertTag = db.prepare(insertGraphTagTemplate);
45
+ const getTagId = db.prepare(selectGraphTagIdTemplate);
46
+ const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
47
+ // We'll map entity names from the LLM to unique ids if we can (functions/classes); fallback to name@file
48
+ for (const entity of kgResult.entities) {
49
+ if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length === 0)
50
+ continue;
51
+ for (const tag of entity.tags) {
52
+ if (!tag || typeof tag !== 'string')
53
+ continue;
54
+ try {
55
+ insertTag.run({ name: tag });
56
+ const tagRow = getTagId.get({ name: tag });
57
+ if (!tagRow)
58
+ continue;
59
+ // Try to match to an AST-extracted entity uniqueId, else fallback to name@filepath
60
+ const matchedUniqueId = functions.find(f => f.name === entity.name)?.uniqueId ||
61
+ classes.find(c => c.name === entity.name)?.uniqueId ||
62
+ `${entity.name}@${filePath}`;
63
+ insertEntityTag.run({
64
+ entity_type: entity.type,
65
+ entity_unique_id: matchedUniqueId,
66
+ tag_id: tagRow.id,
67
+ });
68
+ }
69
+ catch (err) {
70
+ console.error('❌ Failed to persist entity/tag', { entity, tag, error: err });
71
+ }
72
+ }
73
+ }
74
+ log(`🏷 Persisted LLM-generated tags for ${filePath}`);
75
+ }
76
+ }
77
+ catch (kgErr) {
78
+ // tagging failure should not stop extraction; log and continue
79
+ console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
80
+ }
81
+ // --- Process functions: build DTOs (for FTS) and emit call edges (KG) using AST nodes ---
82
+ // inside the functions loop
83
+ for (const funcNode of allFuncNodes) {
84
+ // Resolve a reasonable function name
85
+ const symbolName = funcNode.getSymbol()?.getName();
86
+ const name = symbolName ?? '<anon>';
87
+ const start = funcNode.getStartLineNumber();
88
+ const end = funcNode.getEndLineNumber();
89
+ const code = funcNode.getText();
90
+ // --- updated uniqueId: include line number to make anonymous functions unique ---
91
+ const uniqueId = symbolName
92
+ ? `${symbolName}@${normalizedPath}`
93
+ : `${path.basename(filePath)}:<anon>@${normalizedPath}:${start}`;
94
+ // push DTO for later insertion into functions table
95
+ functions.push({ name, start_line: start, end_line: end, content: code, uniqueId });
96
+ // Persist function row
97
+ try {
98
+ const embedding = await generateEmbedding(code);
99
+ db.prepare(insertFunctionTemplate).run({
100
+ file_id: fileId,
101
+ name,
102
+ start_line: start,
103
+ end_line: end,
104
+ content: code,
105
+ embedding: JSON.stringify(embedding),
106
+ lang: 'ts',
107
+ unique_id: uniqueId,
108
+ });
109
+ }
110
+ catch (err) {
111
+ console.error('❌ Failed to insert function row', { filePath, name, error: err });
112
+ }
113
+ // File -> Function 'contains' edge
114
+ try {
115
+ db.prepare(insertEdgeTemplate).run({
116
+ source_type: 'file',
117
+ source_unique_id: normalizedPath,
118
+ target_type: 'function',
119
+ target_unique_id: uniqueId,
120
+ relation: 'contains',
121
+ });
122
+ }
123
+ catch (err) {
124
+ console.error('❌ Failed to persist file->function edge', { filePath, name, error: err });
125
+ }
126
+ // --- AST-based detection of calls inside the function ---
127
+ try {
128
+ const callExprs = funcNode.getDescendantsOfKind(SyntaxKind.CallExpression);
129
+ for (const callExpr of callExprs) {
130
+ let calleeName;
131
+ try {
132
+ const expr = callExpr.getExpression();
133
+ calleeName = expr.getSymbol()?.getName() ?? expr.getText();
134
+ }
135
+ catch {
136
+ calleeName = undefined;
137
+ }
138
+ const targetUniqueId = calleeName ? `${calleeName}@${normalizedPath}` : 'unresolved';
139
+ try {
140
+ db.prepare(insertEdgeTemplate).run({
141
+ source_type: 'function',
142
+ source_unique_id: uniqueId,
143
+ target_type: 'function',
144
+ target_unique_id: targetUniqueId,
145
+ relation: 'calls',
146
+ });
147
+ }
148
+ catch (err) {
149
+ console.error('❌ Failed to persist call edge', { filePath, name, calleeName, error: err });
150
+ }
151
+ }
152
+ }
153
+ catch (err) {
154
+ console.warn(`⚠️ Failed to inspect call expressions for function ${name} in ${filePath}:`, err);
155
+ }
156
+ log(`📌 Indexed TS function: ${name} (uniqueId=${uniqueId})`);
157
+ }
158
+ // --- Process classes (index + contains edge + inherits) ---
159
+ for (const clsNode of allClassNodes) {
160
+ const name = clsNode.getName() ?? `${path.basename(filePath)}:<anon-class>`;
161
+ const start = clsNode.getStartLineNumber();
162
+ const end = clsNode.getEndLineNumber();
163
+ const code = clsNode.getText();
164
+ const superClass = clsNode.getExtends()?.getText() ?? null;
165
+ const uniqueId = `${name}@${normalizedPath}`;
166
+ classes.push({ name, start_line: start, end_line: end, content: code, superClass, uniqueId });
167
+ // persist class row
168
+ try {
169
+ const embedding = await generateEmbedding(code);
170
+ db.prepare(insertGraphClassTemplate).run({
171
+ file_id: fileId,
172
+ name,
173
+ start_line: start,
174
+ end_line: end,
175
+ content: code,
176
+ embedding: JSON.stringify(embedding),
177
+ lang: 'ts',
178
+ unique_id: uniqueId,
179
+ });
180
+ }
181
+ catch (err) {
182
+ console.error('❌ Failed to insert graph class row', { filePath, name, error: err });
183
+ }
184
+ // File -> Class 'contains' edge
185
+ try {
186
+ db.prepare(insertEdgeTemplate).run({
187
+ source_type: 'file',
188
+ source_unique_id: normalizedPath,
189
+ target_type: 'class',
190
+ target_unique_id: uniqueId,
191
+ relation: 'contains',
82
192
  });
83
193
  }
84
- log(`📌 Indexed TS function: ${fn.name}`);
194
+ catch (err) {
195
+ console.error('❌ Failed to persist file->class edge', { filePath, name, error: err });
196
+ }
197
+ // Inheritance edge (may be unresolved if superclass is in another file)
198
+ if (superClass) {
199
+ try {
200
+ db.prepare(insertEdgeTemplate).run({
201
+ source_type: 'class',
202
+ source_unique_id: uniqueId,
203
+ target_type: 'class',
204
+ target_unique_id: `unresolved:${superClass}`,
205
+ relation: 'inherits',
206
+ });
207
+ }
208
+ catch (err) {
209
+ console.error('❌ Failed to persist inherits edge', { filePath, name, superClass, error: err });
210
+ }
211
+ log(`🔗 Class ${name} extends ${superClass} (edge stored for later resolution)`);
212
+ }
213
+ log(`🏷 Indexed TS class: ${name} (unique_id=${uniqueId})`);
214
+ } // end classes loop
215
+ // --- Handle imports (file-level) ---
216
+ try {
217
+ const importDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration);
218
+ for (const imp of importDecls) {
219
+ const moduleSpecifier = imp.getModuleSpecifierValue();
220
+ if (!moduleSpecifier)
221
+ continue;
222
+ const targetUniqueId = `file@${moduleSpecifier}`; // map to file unique id pattern
223
+ try {
224
+ db.prepare(insertEdgeTemplate).run({
225
+ source_type: 'file',
226
+ source_unique_id: normalizedPath,
227
+ target_type: 'file',
228
+ target_unique_id: targetUniqueId,
229
+ relation: 'imports',
230
+ });
231
+ }
232
+ catch (err) {
233
+ console.error('❌ Failed to persist import edge', { filePath, moduleSpecifier, error: err });
234
+ }
235
+ }
236
+ }
237
+ catch (err) {
238
+ console.warn(`⚠️ Import extraction failed for ${filePath}:`, err);
85
239
  }
86
- // Insert classes
87
- for (const cls of classes) {
88
- const embedding = await generateEmbedding(cls.content);
89
- const result = db
90
- .prepare(`
91
- INSERT INTO classes (
92
- file_id, name, start_line, end_line, content, embedding, lang
93
- ) VALUES (
94
- @file_id, @name, @start_line, @end_line, @content, @embedding, @lang
95
- )
96
- `)
97
- .run({
98
- file_id: fileId,
99
- name: cls.name,
100
- start_line: cls.start_line,
101
- end_line: cls.end_line,
102
- content: cls.content,
103
- embedding: JSON.stringify(embedding),
104
- lang: 'ts',
105
- });
106
- const classId = result.lastInsertRowid;
107
- // file → class edge
108
- db.prepare(`INSERT INTO edges (source_type, source_id, target_type, target_id, relation)
109
- VALUES ('file', @source_id, 'class', @target_id, 'contains')`).run({ source_id: fileId, target_id: classId });
110
- // superclass reference → store in helper table for later resolution
111
- if (cls.superClass) {
112
- db.prepare(`INSERT INTO class_inheritance (class_id, super_name)
113
- VALUES (@class_id, @super_name)`).run({ class_id: classId, super_name: cls.superClass });
114
- log(`🔗 Class ${cls.name} extends ${cls.superClass} (edge stored for later resolution)`);
240
+ // --- Handle exports (file-level) ---
241
+ try {
242
+ const exportDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ExportDeclaration);
243
+ for (const exp of exportDecls) {
244
+ const moduleSpecifier = exp.getModuleSpecifierValue();
245
+ if (!moduleSpecifier)
246
+ continue;
247
+ const targetUniqueId = `file@${moduleSpecifier}`;
248
+ try {
249
+ db.prepare(insertEdgeTemplate).run({
250
+ source_type: 'file',
251
+ source_unique_id: normalizedPath,
252
+ target_type: 'file',
253
+ target_unique_id: targetUniqueId,
254
+ relation: 'exports',
255
+ });
256
+ }
257
+ catch (err) {
258
+ console.error('❌ Failed to persist export edge', { filePath, moduleSpecifier, error: err });
259
+ }
115
260
  }
116
- log(`🏷 Indexed TS class: ${cls.name} (id=${classId})`);
117
261
  }
118
- log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
262
+ catch (err) {
263
+ console.warn(`⚠️ Export extraction failed for ${filePath}:`, err);
264
+ }
265
+ // --- Mark file as extracted and finish ---
119
266
  db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
267
+ log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
120
268
  log(`✅ Marked TS functions/classes as extracted for ${filePath}`);
121
269
  return true;
122
270
  }
package/dist/db/schema.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { getDbForRepo } from "./client.js";
2
2
  export function initSchema() {
3
3
  const db = getDbForRepo();
4
- // --- Existing tables ---
4
+ // --- Core tables ---
5
5
  db.exec(`
6
6
  CREATE TABLE IF NOT EXISTS files (
7
7
  id INTEGER PRIMARY KEY AUTOINCREMENT,
8
- path TEXT UNIQUE,
8
+ path TEXT UNIQUE, -- full path
9
9
  filename TEXT,
10
10
  summary TEXT,
11
11
  type TEXT,
@@ -24,6 +24,7 @@ export function initSchema() {
24
24
  id INTEGER PRIMARY KEY AUTOINCREMENT,
25
25
  file_id INTEGER REFERENCES files(id),
26
26
  name TEXT,
27
+ unique_id TEXT UNIQUE, -- e.g. "buildContextualPrompt@cli/src/utils/buildContextualPrompt.ts"
27
28
  start_line INTEGER,
28
29
  end_line INTEGER,
29
30
  content TEXT,
@@ -31,20 +32,16 @@ export function initSchema() {
31
32
  lang TEXT
32
33
  );
33
34
 
34
- CREATE INDEX IF NOT EXISTS idx_file_id ON functions(file_id);
35
-
36
- CREATE TABLE IF NOT EXISTS function_calls (
37
- caller_id INTEGER REFERENCES functions(id),
38
- callee_name TEXT
39
- );
35
+ CREATE INDEX IF NOT EXISTS idx_functions_file_id ON functions(file_id);
36
+ CREATE INDEX IF NOT EXISTS idx_functions_unique_id ON functions(unique_id);
40
37
  `);
41
- // --- KG-specific additions ---
42
- // Classes table
38
+ // --- Graph-specific additions ---
43
39
  db.exec(`
44
- CREATE TABLE IF NOT EXISTS classes (
40
+ CREATE TABLE IF NOT EXISTS graph_classes (
45
41
  id INTEGER PRIMARY KEY AUTOINCREMENT,
46
42
  file_id INTEGER REFERENCES files(id),
47
43
  name TEXT,
44
+ unique_id TEXT UNIQUE, -- e.g. "UserService@src/services/UserService.ts"
48
45
  start_line INTEGER,
49
46
  end_line INTEGER,
50
47
  content TEXT,
@@ -52,40 +49,41 @@ export function initSchema() {
52
49
  lang TEXT
53
50
  );
54
51
 
55
- CREATE INDEX IF NOT EXISTS idx_class_file_id ON classes(file_id);
52
+ CREATE INDEX IF NOT EXISTS idx_graph_classes_file_id ON graph_classes(file_id);
53
+ CREATE INDEX IF NOT EXISTS idx_graph_classes_unique_id ON graph_classes(unique_id);
56
54
  `);
57
- // Edges table (function/class/file relations)
55
+ // Edges table (all relationships live here: calls, inherits, contains, imports, exports, tests, etc.)
58
56
  db.exec(`
59
- CREATE TABLE IF NOT EXISTS edges (
57
+ CREATE TABLE IF NOT EXISTS graph_edges (
60
58
  id INTEGER PRIMARY KEY AUTOINCREMENT,
61
- source_type TEXT NOT NULL, -- 'function' | 'class' | 'file'
62
- source_id INTEGER NOT NULL,
59
+ source_type TEXT NOT NULL, -- 'function' | 'class' | 'file' | 'test'
60
+ source_unique_id TEXT NOT NULL, -- full unique_id (e.g., function@filePath, filePath)
63
61
  target_type TEXT NOT NULL,
64
- target_id INTEGER NOT NULL,
65
- relation TEXT NOT NULL -- e.g., 'calls', 'inherits', 'contains'
62
+ target_unique_id TEXT NOT NULL,
63
+ relation TEXT NOT NULL -- e.g., 'calls', 'imports', 'exports', 'tests'
66
64
  );
67
65
 
68
- CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_type, source_id);
69
- CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_type, target_id);
66
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source_type, source_unique_id);
67
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target_type, target_unique_id);
68
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges(relation);
70
69
  `);
71
- // --- Improved tags setup ---
72
- // Master tag table
70
+ // Tags setup
73
71
  db.exec(`
74
- CREATE TABLE IF NOT EXISTS tags_master (
72
+ CREATE TABLE IF NOT EXISTS graph_tags_master (
75
73
  id INTEGER PRIMARY KEY AUTOINCREMENT,
76
74
  name TEXT UNIQUE NOT NULL
77
75
  );
78
76
 
79
- CREATE TABLE IF NOT EXISTS entity_tags (
77
+ CREATE TABLE IF NOT EXISTS graph_entity_tags (
80
78
  id INTEGER PRIMARY KEY AUTOINCREMENT,
81
79
  entity_type TEXT NOT NULL, -- 'function' | 'class' | 'file'
82
- entity_id INTEGER NOT NULL,
83
- tag_id INTEGER NOT NULL REFERENCES tags_master(id),
84
- UNIQUE(entity_type, entity_id, tag_id)
80
+ entity_unique_id TEXT NOT NULL, -- match against functions.unique_id, classes.unique_id, or files.path
81
+ tag_id INTEGER NOT NULL REFERENCES graph_tags_master(id),
82
+ UNIQUE(entity_type, entity_unique_id, tag_id)
85
83
  );
86
84
 
87
- CREATE INDEX IF NOT EXISTS idx_entity_tags_entity ON entity_tags(entity_type, entity_id);
88
- CREATE INDEX IF NOT EXISTS idx_entity_tags_tag ON entity_tags(tag_id);
85
+ CREATE INDEX IF NOT EXISTS idx_graph_entity_tags_entity ON graph_entity_tags(entity_type, entity_unique_id);
86
+ CREATE INDEX IF NOT EXISTS idx_graph_entity_tags_tag ON graph_entity_tags(tag_id);
89
87
  `);
90
- console.log('KG schema initialized (files, functions, classes, edges, tags)');
88
+ console.log("Graph schema initialized (files, functions, classes, edges, tags with unique_ids)");
91
89
  }
@@ -1,9 +1,10 @@
1
+ // --- Files ---
1
2
  // Upsert file metadata into `files`
2
3
  export const upsertFileTemplate = `
3
4
  INSERT INTO files (path, filename, summary, type, last_modified, indexed_at, embedding)
4
5
  VALUES (:path, :filename, :summary, :type, :lastModified, :indexedAt, :embedding)
5
6
  ON CONFLICT(path) DO UPDATE SET
6
- filename = excluded.filename, -- Update filename when path conflicts
7
+ filename = excluded.filename,
7
8
  summary = CASE
8
9
  WHEN excluded.summary IS NOT NULL AND excluded.summary != files.summary
9
10
  THEN excluded.summary
@@ -18,61 +19,88 @@ export const upsertFileTemplate = `
18
19
  ELSE files.embedding
19
20
  END
20
21
  `;
21
- // 📌 CHANGE 1: Include `filename` in SELECT + weight `filename` highest in bm25
22
- export const fetchBm25ScoresTemplate = `
23
- SELECT f.path, f.filename, f.summary, f.type,
24
- bm25(files_fts, 10.0, 2.0, 1.0) AS bm25Score
25
- FROM files_fts
26
- JOIN files f ON files_fts.rowid = f.id
27
- WHERE files_fts MATCH :query
28
- LIMIT 50
29
- `;
30
- // Fetch embedding vector for a file
31
- export const fetchEmbeddingTemplate = `
32
- SELECT embedding FROM files WHERE path = :path
33
- `;
34
- // 📌 CHANGE 2: Also added weighted `bm25()` with explicit weights here
35
- export const rawQueryTemplate = `
36
- SELECT f.path, f.filename, f.summary, f.type, f.last_modified, f.indexed_at,
37
- bm25(files_fts, 10.0, 2.0, 1.0) AS rank
38
- FROM files_fts
39
- JOIN files f ON files_fts.rowid = f.id
40
- WHERE files_fts MATCH :query
41
- ORDER BY rank
42
- LIMIT :limit
43
- `;
44
- // Insert function metadata
22
+ // Insert or replace into FTS
23
+ export const upsertFileFtsTemplate = `
24
+ INSERT OR REPLACE INTO files_fts (rowid, filename, summary, path)
25
+ VALUES ((SELECT id FROM files WHERE path = :path), :filename, :summary, :path)
26
+ `;
27
+ // Simple file query (no scoring)
28
+ export const queryFilesTemplate = `
29
+ SELECT f.id, f.path, f.filename, f.summary, f.type, f.last_modified, f.indexed_at
30
+ FROM files f
31
+ JOIN files_fts fts ON f.id = fts.rowid
32
+ WHERE fts.files_fts MATCH ?
33
+ LIMIT ?
34
+ `;
35
+ // FTS search with bm25 scoring
36
+ export const searchFilesTemplate = `
37
+ SELECT fts.rowid AS id, f.path, f.filename, f.summary, f.type,
38
+ bm25(files_fts) AS bm25Score, f.embedding
39
+ FROM files f
40
+ JOIN files_fts fts ON f.id = fts.rowid
41
+ WHERE fts.files_fts MATCH ?
42
+ ORDER BY bm25Score ASC
43
+ LIMIT ?
44
+ `;
45
+ // --- Functions ---
45
46
  export const insertFunctionTemplate = `
46
47
  INSERT INTO functions (file_id, name, start_line, end_line, content, embedding, lang)
47
48
  VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
48
49
  `;
49
- // Insert function call edge
50
- export const insertFunctionCallTemplate = `
51
- INSERT INTO function_calls (caller_id, callee_name)
52
- VALUES (:caller_id, :callee_name)
50
+ // --- Graph ---
51
+ export const insertGraphClassTemplate = `
52
+ INSERT INTO graph_classes (file_id, name, start_line, end_line, content, embedding, lang)
53
+ VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
53
54
  `;
54
- // Mark a file as unprocessed
55
- export const markFileAsUnprocessedTemplate = `
56
- UPDATE files
57
- SET processing_status = 'unprocessed',
58
- functions_extracted_at = NULL
59
- WHERE id = :id
55
+ export const insertEdgeTemplate = `
56
+ INSERT INTO graph_edges (
57
+ source_type,
58
+ source_unique_id,
59
+ target_type,
60
+ target_unique_id,
61
+ relation
62
+ )
63
+ VALUES (
64
+ :source_type,
65
+ :source_unique_id,
66
+ :target_type,
67
+ :target_unique_id,
68
+ :relation
69
+ )
70
+ `;
71
+ // --- New graph tag templates ---
72
+ export const insertGraphTagTemplate = `
73
+ INSERT OR IGNORE INTO graph_tags_master (name) VALUES (:name)
60
74
  `;
61
- // Mark a file as extracted
75
+ export const selectGraphTagIdTemplate = `
76
+ SELECT id FROM graph_tags_master WHERE name = :name
77
+ `;
78
+ export const insertGraphEntityTagTemplate = `
79
+ INSERT INTO graph_entity_tags (
80
+ entity_type,
81
+ entity_unique_id,
82
+ tag_id
83
+ )
84
+ VALUES (
85
+ :entity_type,
86
+ :entity_unique_id,
87
+ :tag_id
88
+ )
89
+ ON CONFLICT(entity_type, entity_unique_id, tag_id) DO NOTHING
90
+ `;
91
+ // --- File Processing Status ---
62
92
  export const markFileAsExtractedTemplate = `
63
93
  UPDATE files
64
94
  SET processing_status = 'extracted',
65
95
  functions_extracted_at = CURRENT_TIMESTAMP
66
96
  WHERE id = :id
67
97
  `;
68
- // Mark a file as skipped (not extractable)
69
98
  export const markFileAsSkippedTemplate = `
70
99
  UPDATE files
71
100
  SET processing_status = 'skipped',
72
101
  functions_extracted_at = NULL
73
102
  WHERE id = :id
74
103
  `;
75
- // Mark a file as failed
76
104
  export const markFileAsFailedTemplate = `
77
105
  UPDATE files
78
106
  SET processing_status = 'failed',
@@ -89,7 +117,7 @@ export const selectUnprocessedFiles = `
89
117
  export const markFileAsSkippedByPath = `
90
118
  UPDATE files
91
119
  SET processing_status = 'skipped',
92
- functions_extracted_at = NULL
120
+ functions_extracted_at = NULL
93
121
  WHERE path = @path
94
122
  `;
95
123
  export const updateFileWithSummaryAndEmbedding = `
package/dist/index.js CHANGED
@@ -15,7 +15,6 @@ import { startDaemon } from './commands/DaemonCmd.js';
15
15
  import { runStopDaemonCommand } from "./commands/StopDaemonCmd.js";
16
16
  import { runAskCommand } from './commands/AskCmd.js';
17
17
  import { runBackupCommand } from './commands/BackupCmd.js';
18
- import { runMigrateCommand } from "./commands/MigrateCmd.js";
19
18
  import { runInspectCommand } from "./commands/InspectCmd.js";
20
19
  import { reviewPullRequestCmd } from "./commands/ReviewCmd.js";
21
20
  import { promptForToken } from "./github/token.js";
@@ -276,12 +275,6 @@ db
276
275
  .action(async () => await withContext(async () => {
277
276
  await resetDatabase();
278
277
  }));
279
- db
280
- .command('migrate')
281
- .description('Run DB migration scripts')
282
- .action(async () => await withContext(async () => {
283
- await runMigrateCommand();
284
- }));
285
278
  db
286
279
  .command('inspect')
287
280
  .argument('<filepath>', 'Path to the file to inspect')