scai 0.1.115 → 0.1.117

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,258 @@ 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';
8
+ import { BUILTINS } from '../../fileRules/builtins.js';
9
+ import { getUniqueId } from '../../utils/sharedUtils.js';
7
10
  export async function extractFromTS(filePath, content, fileId) {
8
11
  const db = getDbForRepo();
12
+ const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
9
13
  try {
10
14
  const project = new Project({ useInMemoryFileSystem: true });
11
15
  const sourceFile = project.createSourceFile(filePath, content);
16
+ // DTO arrays used to store into functions / graph_classes later (FTS index)
12
17
  const functions = [];
13
18
  const classes = [];
14
- const allFuncs = [
19
+ // --- Gather all function AST nodes ---
20
+ const allFuncNodes = [
15
21
  ...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
16
22
  ...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
17
23
  ...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
18
24
  ];
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 = [
25
+ // --- Gather all class AST nodes ---
26
+ const allClassNodes = [
27
27
  ...sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration),
28
28
  ...sourceFile.getDescendantsOfKind(SyntaxKind.ClassExpression),
29
29
  ];
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) {
30
+ if (allFuncNodes.length === 0 && allClassNodes.length === 0) {
45
31
  log(`⚠️ No functions/classes found in TS file: ${filePath}`);
46
32
  db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
47
33
  return false;
48
34
  }
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],
35
+ log(`🔍 Found ${allFuncNodes.length} functions and ${allClassNodes.length} classes in ${filePath}`);
36
+ // --- LLM tagging ---
37
+ try {
38
+ const kgInput = { fileId, filepath: filePath, summary: undefined };
39
+ const kgResult = await kgModule.run(kgInput, content);
40
+ if (kgResult.entities?.length > 0) {
41
+ const insertTag = db.prepare(insertGraphTagTemplate);
42
+ const getTagId = db.prepare(selectGraphTagIdTemplate);
43
+ const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
44
+ for (const entity of kgResult.entities) {
45
+ if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length === 0)
46
+ continue;
47
+ for (const tag of entity.tags) {
48
+ if (!tag || typeof tag !== 'string')
49
+ continue;
50
+ try {
51
+ insertTag.run({ name: tag });
52
+ const tagRow = getTagId.get({ name: tag });
53
+ if (!tagRow)
54
+ continue;
55
+ const matchedUniqueId = functions.find(f => f.name === entity.name)?.unique_id ||
56
+ classes.find(c => c.name === entity.name)?.unique_id ||
57
+ `${entity.name}@${filePath}`;
58
+ insertEntityTag.run({
59
+ entity_type: entity.type,
60
+ entity_unique_id: matchedUniqueId,
61
+ tag_id: tagRow.id,
62
+ });
63
+ }
64
+ catch (err) {
65
+ console.error('❌ Failed to persist entity/tag', { entity, tag, error: err });
66
+ }
67
+ }
68
+ }
69
+ log(`🏷 Persisted LLM-generated tags for ${filePath}`);
70
+ }
71
+ }
72
+ catch (kgErr) {
73
+ console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
74
+ }
75
+ // --- Process functions ---
76
+ for (const funcNode of allFuncNodes) {
77
+ const symbolName = funcNode.getSymbol()?.getName();
78
+ const name = symbolName ?? '<anon>';
79
+ const start = funcNode.getStartLineNumber();
80
+ const end = funcNode.getEndLineNumber();
81
+ const code = funcNode.getText();
82
+ const unique_id = getUniqueId(name, filePath, start, funcNode.getStart(), code);
83
+ functions.push({ name, start_line: start, end_line: end, content: code, unique_id });
84
+ try {
85
+ const embedding = await generateEmbedding(code);
86
+ db.prepare(insertFunctionTemplate).run({
87
+ file_id: fileId,
88
+ name,
89
+ start_line: start,
90
+ end_line: end,
91
+ content: code,
92
+ embedding: JSON.stringify(embedding),
93
+ lang: 'ts',
94
+ unique_id,
82
95
  });
83
96
  }
84
- log(`📌 Indexed TS function: ${fn.name}`);
97
+ catch (err) {
98
+ console.error('❌ Failed to insert function row', { filePath, name, error: err });
99
+ }
100
+ // File -> Function 'contains' edge
101
+ try {
102
+ db.prepare(insertEdgeTemplate).run({
103
+ source_type: 'file',
104
+ source_unique_id: normalizedPath,
105
+ target_type: 'function',
106
+ target_unique_id: unique_id,
107
+ relation: 'contains',
108
+ });
109
+ }
110
+ catch (err) {
111
+ console.error('❌ Failed to persist file->function edge', { filePath, name, error: err });
112
+ }
113
+ // AST-based calls
114
+ try {
115
+ const callExprs = funcNode.getDescendantsOfKind(SyntaxKind.CallExpression);
116
+ const edgeSet = new Set();
117
+ for (const callExpr of callExprs) {
118
+ let calleeName;
119
+ try {
120
+ const expr = callExpr.getExpression();
121
+ calleeName = expr.getSymbol()?.getName() ?? expr.getText();
122
+ }
123
+ catch {
124
+ calleeName = undefined;
125
+ }
126
+ if (!calleeName || BUILTINS.has(calleeName))
127
+ continue;
128
+ const targetUniqueId = `${calleeName}@${normalizedPath}`;
129
+ const edgeKey = `${unique_id}->${targetUniqueId}`;
130
+ if (!edgeSet.has(edgeKey)) {
131
+ edgeSet.add(edgeKey);
132
+ try {
133
+ db.prepare(insertEdgeTemplate).run({
134
+ source_type: 'function',
135
+ source_unique_id: unique_id,
136
+ target_type: 'function',
137
+ target_unique_id: targetUniqueId,
138
+ relation: 'calls',
139
+ });
140
+ }
141
+ catch (err) {
142
+ console.error('❌ Failed to persist call edge', { filePath, name, calleeName, error: err });
143
+ }
144
+ }
145
+ }
146
+ }
147
+ catch (err) {
148
+ console.warn(`⚠️ Failed to inspect call expressions for function ${name} in ${filePath}:`, err);
149
+ }
150
+ log(`📌 Indexed TS function: ${name} (unique_id=${unique_id})`);
85
151
  }
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)`);
152
+ // --- Process classes ---
153
+ for (const clsNode of allClassNodes) {
154
+ const name = clsNode.getName() ?? `${path.basename(filePath)}:<anon-class>`;
155
+ const start = clsNode.getStartLineNumber();
156
+ const end = clsNode.getEndLineNumber();
157
+ const code = clsNode.getText();
158
+ const superClass = clsNode.getExtends()?.getText() ?? null;
159
+ const unique_id = getUniqueId(name, filePath, start, clsNode.getStart(), code);
160
+ classes.push({ name, start_line: start, end_line: end, content: code, superClass, unique_id });
161
+ try {
162
+ const embedding = await generateEmbedding(code);
163
+ db.prepare(insertGraphClassTemplate).run({
164
+ file_id: fileId,
165
+ name,
166
+ start_line: start,
167
+ end_line: end,
168
+ content: code,
169
+ embedding: JSON.stringify(embedding),
170
+ lang: 'ts',
171
+ unique_id,
172
+ });
173
+ }
174
+ catch (err) {
175
+ console.error(' Failed to insert graph class row', { filePath, name, error: err });
115
176
  }
116
- log(`🏷 Indexed TS class: ${cls.name} (id=${classId})`);
177
+ try {
178
+ db.prepare(insertEdgeTemplate).run({
179
+ source_type: 'file',
180
+ source_unique_id: normalizedPath,
181
+ target_type: 'class',
182
+ target_unique_id: unique_id,
183
+ relation: 'contains',
184
+ });
185
+ }
186
+ catch (err) {
187
+ console.error('❌ Failed to persist file->class edge', { filePath, name, error: err });
188
+ }
189
+ if (superClass) {
190
+ try {
191
+ db.prepare(insertEdgeTemplate).run({
192
+ source_type: 'class',
193
+ source_unique_id: unique_id,
194
+ target_type: 'class',
195
+ target_unique_id: `unresolved:${superClass}`,
196
+ relation: 'inherits',
197
+ });
198
+ }
199
+ catch (err) {
200
+ console.error('❌ Failed to persist inherits edge', { filePath, name, superClass, error: err });
201
+ }
202
+ log(`🔗 Class ${name} extends ${superClass} (edge stored for later resolution)`);
203
+ }
204
+ log(`🏷 Indexed TS class: ${name} (unique_id=${unique_id})`);
205
+ }
206
+ // --- Imports ---
207
+ try {
208
+ const importDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration);
209
+ for (const imp of importDecls) {
210
+ const moduleSpecifier = imp.getModuleSpecifierValue();
211
+ if (!moduleSpecifier)
212
+ continue;
213
+ const targetUniqueId = `file@${moduleSpecifier}`;
214
+ try {
215
+ db.prepare(insertEdgeTemplate).run({
216
+ source_type: 'file',
217
+ source_unique_id: normalizedPath,
218
+ target_type: 'file',
219
+ target_unique_id: targetUniqueId,
220
+ relation: 'imports',
221
+ });
222
+ }
223
+ catch (err) {
224
+ console.error('❌ Failed to persist import edge', { filePath, moduleSpecifier, error: err });
225
+ }
226
+ }
227
+ }
228
+ catch (err) {
229
+ console.warn(`⚠️ Import extraction failed for ${filePath}:`, err);
230
+ }
231
+ // --- Exports ---
232
+ try {
233
+ const exportDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ExportDeclaration);
234
+ for (const exp of exportDecls) {
235
+ const moduleSpecifier = exp.getModuleSpecifierValue();
236
+ if (!moduleSpecifier)
237
+ continue;
238
+ const targetUniqueId = `file@${moduleSpecifier}`;
239
+ try {
240
+ db.prepare(insertEdgeTemplate).run({
241
+ source_type: 'file',
242
+ source_unique_id: normalizedPath,
243
+ target_type: 'file',
244
+ target_unique_id: targetUniqueId,
245
+ relation: 'exports',
246
+ });
247
+ }
248
+ catch (err) {
249
+ console.error('❌ Failed to persist export edge', { filePath, moduleSpecifier, error: err });
250
+ }
251
+ }
252
+ }
253
+ catch (err) {
254
+ console.warn(`⚠️ Export extraction failed for ${filePath}:`, err);
117
255
  }
118
- log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
119
256
  db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
257
+ log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
120
258
  log(`✅ Marked TS functions/classes as extracted for ${filePath}`);
121
259
  return true;
122
260
  }
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
- INSERT INTO functions (file_id, name, start_line, end_line, content, embedding, lang)
47
+ INSERT INTO functions (file_id, name, start_line, end_line, content, embedding, lang, unique_id)
48
+ VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang, :unique_id)
49
+ `;
50
+ // --- Graph ---
51
+ export const insertGraphClassTemplate = `
52
+ INSERT INTO graph_classes (file_id, name, start_line, end_line, content, embedding, lang)
47
53
  VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
48
54
  `;
49
- // Insert function call edge
50
- export const insertFunctionCallTemplate = `
51
- INSERT INTO function_calls (caller_id, callee_name)
52
- VALUES (:caller_id, :callee_name)
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
+ )
53
70
  `;
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
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 = `
@@ -0,0 +1,14 @@
1
+ export const BUILTINS = new Set([
2
+ // String methods
3
+ "trim", "toLowerCase", "toUpperCase", "includes", "startsWith", "endsWith", "replace", "match", "split",
4
+ // Array methods
5
+ "map", "forEach", "filter", "reduce", "some", "every", "find", "findIndex", "push", "pop", "shift", "unshift", "slice", "splice",
6
+ // Object methods
7
+ "keys", "values", "entries", "assign",
8
+ // Number/Math
9
+ "parseInt", "parseFloat", "isNaN", "isFinite", "floor", "ceil", "round", "abs", "max", "min", "random",
10
+ // JSON / Promise / RegExp
11
+ "stringify", "parse", "then", "catch", "finally", "test",
12
+ // Console
13
+ "log", "error", "warn", "info",
14
+ ]);
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')