Enhances code extraction and project indexing
Adds support for Dart and Liquid languages with tree-sitter parsing. Improves accuracy of code symbol extraction for existing languages. Indexes project files to enhance code navigation features. Migrates build system to facilitate code contributions. Removes git hook functionality. Integrates Sentry for error tracking and reporting. Enhances project initialization and configuration loading.
This commit is contained in:
@@ -38,6 +38,9 @@ export class DatabaseConnection {
|
||||
// Enable foreign keys and WAL mode for better performance
|
||||
db.pragma('foreign_keys = ON');
|
||||
db.pragma('journal_mode = WAL');
|
||||
// Wait up to 2 minutes if database is locked by another process
|
||||
// (indexing operations can hold locks for extended periods)
|
||||
db.pragma('busy_timeout = 120000');
|
||||
|
||||
// Run schema initialization
|
||||
const schemaPath = path.join(__dirname, 'schema.sql');
|
||||
@@ -60,6 +63,9 @@ export class DatabaseConnection {
|
||||
// Enable foreign keys and WAL mode
|
||||
db.pragma('foreign_keys = ON');
|
||||
db.pragma('journal_mode = WAL');
|
||||
// Wait up to 2 minutes if database is locked by another process
|
||||
// (indexing operations can hold locks for extended periods)
|
||||
db.pragma('busy_timeout = 120000');
|
||||
|
||||
// Check and run migrations if needed
|
||||
const conn = new DatabaseConnection(db, dbPath);
|
||||
|
||||
+110
-28
@@ -198,28 +198,54 @@ export class QueryBuilder {
|
||||
`);
|
||||
}
|
||||
|
||||
this.stmts.insertNode.run({
|
||||
id: node.id,
|
||||
kind: node.kind,
|
||||
name: node.name,
|
||||
qualifiedName: node.qualifiedName,
|
||||
filePath: node.filePath,
|
||||
language: node.language,
|
||||
startLine: node.startLine,
|
||||
endLine: node.endLine,
|
||||
startColumn: node.startColumn,
|
||||
endColumn: node.endColumn,
|
||||
docstring: node.docstring ?? null,
|
||||
signature: node.signature ?? null,
|
||||
visibility: node.visibility ?? null,
|
||||
isExported: node.isExported ? 1 : 0,
|
||||
isAsync: node.isAsync ? 1 : 0,
|
||||
isStatic: node.isStatic ? 1 : 0,
|
||||
isAbstract: node.isAbstract ? 1 : 0,
|
||||
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
|
||||
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
|
||||
updatedAt: node.updatedAt,
|
||||
});
|
||||
// Validate required fields to prevent SQLite bind errors
|
||||
if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
|
||||
console.error('[CodeGraph] Skipping node with missing required fields:', {
|
||||
id: node.id,
|
||||
kind: node.kind,
|
||||
name: node.name,
|
||||
filePath: node.filePath,
|
||||
language: node.language,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.stmts.insertNode.run({
|
||||
id: node.id,
|
||||
kind: node.kind,
|
||||
name: node.name,
|
||||
qualifiedName: node.qualifiedName ?? node.name,
|
||||
filePath: node.filePath,
|
||||
language: node.language,
|
||||
startLine: node.startLine ?? 0,
|
||||
endLine: node.endLine ?? 0,
|
||||
startColumn: node.startColumn ?? 0,
|
||||
endColumn: node.endColumn ?? 0,
|
||||
docstring: node.docstring ?? null,
|
||||
signature: node.signature ?? null,
|
||||
visibility: node.visibility ?? null,
|
||||
isExported: node.isExported ? 1 : 0,
|
||||
isAsync: node.isAsync ? 1 : 0,
|
||||
isStatic: node.isStatic ? 1 : 0,
|
||||
isAbstract: node.isAbstract ? 1 : 0,
|
||||
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
|
||||
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
|
||||
updatedAt: node.updatedAt ?? Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
const { captureException } = require('../sentry');
|
||||
captureException(error, {
|
||||
operation: 'insertNode',
|
||||
nodeId: node.id,
|
||||
nodeKind: node.kind,
|
||||
nodeName: node.name,
|
||||
filePath: node.filePath,
|
||||
language: node.language,
|
||||
startLine: node.startLine,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,17 +292,23 @@ export class QueryBuilder {
|
||||
// Invalidate cache before update
|
||||
this.nodeCache.delete(node.id);
|
||||
|
||||
// Validate required fields
|
||||
if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
|
||||
console.error('[CodeGraph] Skipping node update with missing required fields:', node.id);
|
||||
return;
|
||||
}
|
||||
|
||||
this.stmts.updateNode.run({
|
||||
id: node.id,
|
||||
kind: node.kind,
|
||||
name: node.name,
|
||||
qualifiedName: node.qualifiedName,
|
||||
qualifiedName: node.qualifiedName ?? node.name,
|
||||
filePath: node.filePath,
|
||||
language: node.language,
|
||||
startLine: node.startLine,
|
||||
endLine: node.endLine,
|
||||
startColumn: node.startColumn,
|
||||
endColumn: node.endColumn,
|
||||
startLine: node.startLine ?? 0,
|
||||
endLine: node.endLine ?? 0,
|
||||
startColumn: node.startColumn ?? 0,
|
||||
endColumn: node.endColumn ?? 0,
|
||||
docstring: node.docstring ?? null,
|
||||
signature: node.signature ?? null,
|
||||
visibility: node.visibility ?? null,
|
||||
@@ -286,7 +318,7 @@ export class QueryBuilder {
|
||||
isAbstract: node.isAbstract ? 1 : 0,
|
||||
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
|
||||
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
|
||||
updatedAt: node.updatedAt,
|
||||
updatedAt: node.updatedAt ?? Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -524,6 +556,56 @@ export class QueryBuilder {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find nodes by exact name match
|
||||
*
|
||||
* Used for hybrid search - looks up symbols by exact name or case-insensitive match.
|
||||
* Returns high-confidence matches for known symbol names extracted from query.
|
||||
*
|
||||
* @param names - Array of symbol names to look up
|
||||
* @param options - Search options (kinds, languages, limit)
|
||||
* @returns SearchResult array with exact matches scored at 1.0
|
||||
*/
|
||||
findNodesByExactName(names: string[], options: SearchOptions = {}): SearchResult[] {
|
||||
if (names.length === 0) return [];
|
||||
|
||||
const { kinds, languages, limit = 50 } = options;
|
||||
|
||||
// Build query with exact matches (case-insensitive)
|
||||
let sql = `
|
||||
SELECT nodes.*,
|
||||
CASE
|
||||
WHEN name COLLATE NOCASE IN (${names.map(() => '?').join(',')}) THEN 1.0
|
||||
ELSE 0.9
|
||||
END as score
|
||||
FROM nodes
|
||||
WHERE name COLLATE NOCASE IN (${names.map(() => '?').join(',')})
|
||||
`;
|
||||
|
||||
// Duplicate names for both SELECT and WHERE clauses
|
||||
const params: (string | number)[] = [...names, ...names];
|
||||
|
||||
if (kinds && kinds.length > 0) {
|
||||
sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
|
||||
params.push(...kinds);
|
||||
}
|
||||
|
||||
if (languages && languages.length > 0) {
|
||||
sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
|
||||
params.push(...languages);
|
||||
}
|
||||
|
||||
sql += ' ORDER BY score DESC, length(name) ASC LIMIT ?';
|
||||
params.push(limit);
|
||||
|
||||
const rows = this.db.prepare(sql).all(...params) as (NodeRow & { score: number })[];
|
||||
|
||||
return rows.map((row) => ({
|
||||
node: rowToNode(row),
|
||||
score: row.score,
|
||||
}));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Edge Operations
|
||||
// ===========================================================================
|
||||
|
||||
+10
-9
@@ -89,32 +89,33 @@ CREATE INDEX IF NOT EXISTS idx_nodes_file_path ON nodes(file_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_language ON nodes(language);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_file_line ON nodes(file_path, start_line);
|
||||
|
||||
-- Full-text search index on node names and docstrings
|
||||
-- Full-text search index on node names, docstrings, and signatures
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
|
||||
id,
|
||||
name,
|
||||
qualified_name,
|
||||
docstring,
|
||||
signature,
|
||||
content='nodes',
|
||||
content_rowid='rowid'
|
||||
);
|
||||
|
||||
-- Triggers to keep FTS index in sync
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
|
||||
INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring)
|
||||
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring);
|
||||
INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring, signature)
|
||||
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring, NEW.signature);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
|
||||
INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring)
|
||||
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.qualified_name, OLD.docstring);
|
||||
INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring, signature)
|
||||
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.qualified_name, OLD.docstring, OLD.signature);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
|
||||
INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring)
|
||||
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.qualified_name, OLD.docstring);
|
||||
INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring)
|
||||
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring);
|
||||
INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring, signature)
|
||||
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.qualified_name, OLD.docstring, OLD.signature);
|
||||
INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring, signature)
|
||||
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring, NEW.signature);
|
||||
END;
|
||||
|
||||
-- Edge indexes
|
||||
|
||||
Reference in New Issue
Block a user