mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
Release v3.8.42 — full CHANGELOG in CHANGELOG.md. CI: 103 checks green incl. CodeQL (all languages), Semgrep, all 8 unit shards, coverage, Node 24 compat, and integration tests. Full unit suite validated locally: 19437 pass / 0 fail. The 3 red checks are advisory and do not gate main (no required status checks): SonarCloud/SonarQube new-code coverage gate, and PR Test Policy (test-masking detector flagging the legitimate dead-Phind provider removal in #5530 — reviewed, correct). Includes cycle-close reconciliation + repair of inherited base-red tests from #5480/#5527/#5427/#5521 that the PR->release fast-path did not exercise.
230 lines
6.5 KiB
TypeScript
230 lines
6.5 KiB
TypeScript
/**
|
|
* Copilot CodeGraph Knowledge Module
|
|
*
|
|
* Provides the Copilot with read-only access to the project's CodeGraph index.
|
|
* Queries the `.codegraph/codegraph.db` SQLite database to find symbols,
|
|
* explore relationships, list files, and search documentation.
|
|
*
|
|
* Falls back gracefully if the CodeGraph DB does not exist (e.g., production installs).
|
|
*/
|
|
|
|
import { existsSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface CodeGraphNode {
|
|
id: string;
|
|
kind: string;
|
|
name: string;
|
|
qualifiedName: string;
|
|
filePath: string;
|
|
language: string;
|
|
startLine: number;
|
|
endLine: number;
|
|
signature?: string;
|
|
docstring?: string;
|
|
isExported: boolean;
|
|
visibility?: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Database access (lazy loaded)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
let _db: unknown = null;
|
|
|
|
function getDbPath(): string | null {
|
|
// Try project root first (dev), then cwd, then DATA_DIR
|
|
const candidates = [
|
|
join(process.cwd(), ".codegraph", "codegraph.db"),
|
|
join(process.cwd(), "..", ".codegraph", "codegraph.db"),
|
|
];
|
|
|
|
// Try to resolve from the project root
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
// Walk up to find .codegraph/
|
|
let dir = __dirname;
|
|
for (let i = 0; i < 10; i++) {
|
|
const candidate = join(dir, ".codegraph", "codegraph.db");
|
|
if (existsSync(candidate)) return candidate;
|
|
const parent = join(dir, "..");
|
|
if (parent === dir) break;
|
|
dir = parent;
|
|
}
|
|
|
|
for (const c of candidates) {
|
|
if (existsSync(c)) return c;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export interface CodeGraphQueryResult {
|
|
success: boolean;
|
|
data: unknown;
|
|
error?: string;
|
|
engine: "sqlite" | "cli" | "none";
|
|
}
|
|
|
|
function queryDb(query: string, params: unknown[] = []): CodeGraphQueryResult {
|
|
try {
|
|
if (!_db) {
|
|
const dbPath = getDbPath();
|
|
if (!dbPath) {
|
|
return { success: false, data: null, error: "CodeGraph DB not found", engine: "none" };
|
|
}
|
|
// Dynamic import to avoid hard dependency on better-sqlite3
|
|
_db = null;
|
|
|
|
// Use better-sqlite3 if available
|
|
try {
|
|
const Database = require("better-sqlite3");
|
|
_db = new Database(dbPath, { readonly: true });
|
|
} catch {
|
|
return {
|
|
success: false,
|
|
data: null,
|
|
error: "better-sqlite3 not available",
|
|
engine: "none",
|
|
};
|
|
}
|
|
}
|
|
|
|
const stmt = (
|
|
_db as { prepare: (sql: string) => { all: (params: unknown[]) => unknown[] } }
|
|
).prepare(query);
|
|
const rows = stmt.all(params);
|
|
return { success: true, data: rows, engine: "sqlite" };
|
|
} catch (err) {
|
|
return {
|
|
success: false,
|
|
data: null,
|
|
error: err instanceof Error ? err.message : "Unknown error",
|
|
engine: "none",
|
|
};
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Search operations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Search symbols by name (exact or partial match via FTS).
|
|
*/
|
|
export function searchSymbols(query: string, limit = 20): CodeGraphQueryResult {
|
|
const sql = `
|
|
SELECT n.*
|
|
FROM nodes n
|
|
JOIN nodes_fts fts ON n.id = fts.id
|
|
WHERE nodes_fts MATCH ?
|
|
ORDER BY rank
|
|
LIMIT ?
|
|
`;
|
|
|
|
// Escape FTS special chars and create prefix query
|
|
const sanitized = query.replace(/[^a-zA-Z0-9_]/g, " ").trim();
|
|
if (!sanitized) {
|
|
// Fallback to LIKE if query is empty after sanitization
|
|
return queryDb(`SELECT * FROM nodes WHERE lower(name) LIKE ? ORDER BY kind, name LIMIT ?`, [
|
|
`%${query.toLowerCase()}%`,
|
|
limit,
|
|
]);
|
|
}
|
|
|
|
const ftsQuery = sanitized
|
|
.split(/\s+/)
|
|
.map((w) => `"${w}"*`)
|
|
.join(" AND ");
|
|
|
|
return queryDb(sql, [ftsQuery, limit]);
|
|
}
|
|
|
|
/**
|
|
* Find callers of a symbol (edges where target matches).
|
|
*/
|
|
export function findCallers(symbolName: string, limit = 20): CodeGraphQueryResult {
|
|
return queryDb(
|
|
`SELECT e.id as edgeId, e.kind as edgeKind, e.line, e.col,
|
|
s.id as sourceId, s.name as sourceName, s.kind as sourceKind,
|
|
s.file_path as sourceFile, s.start_line as sourceLine,
|
|
t.name as targetName, t.file_path as targetFile
|
|
FROM edges e
|
|
JOIN nodes s ON e.source = s.id
|
|
JOIN nodes t ON e.target = t.id
|
|
WHERE t.name = ?
|
|
ORDER BY e.kind
|
|
LIMIT ?`,
|
|
[symbolName, limit]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Find callees of a symbol (edges where source matches).
|
|
*/
|
|
export function findCallees(symbolName: string, limit = 20): CodeGraphQueryResult {
|
|
return queryDb(
|
|
`SELECT e.id as edgeId, e.kind as edgeKind, e.line, e.col,
|
|
s.name as sourceName,
|
|
t.id as targetId, t.name as targetName, t.kind as targetKind,
|
|
t.file_path as targetFile, t.start_line as targetLine
|
|
FROM edges e
|
|
JOIN nodes s ON e.source = s.id
|
|
JOIN nodes t ON e.target = t.id
|
|
WHERE s.name = ?
|
|
ORDER BY e.kind
|
|
LIMIT ?`,
|
|
[symbolName, limit]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get context for a file: all symbols defined in it.
|
|
*/
|
|
export function getFileContext(filePath: string): CodeGraphQueryResult {
|
|
// Try matching on suffix of file_path (many nodes store paths relative to root)
|
|
return queryDb(
|
|
`SELECT * FROM nodes
|
|
WHERE file_path LIKE ? OR file_path = ?
|
|
ORDER BY start_line
|
|
LIMIT 100`,
|
|
[`%${filePath}`, filePath]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* List all indexed files, optionally filtered by language.
|
|
*/
|
|
export function listFiles(language?: string, limit = 50): CodeGraphQueryResult {
|
|
if (language) {
|
|
return queryDb(`SELECT * FROM files WHERE language = ? ORDER BY path LIMIT ?`, [
|
|
language,
|
|
limit,
|
|
]);
|
|
}
|
|
return queryDb(`SELECT * FROM files ORDER BY path LIMIT ?`, [limit]);
|
|
}
|
|
|
|
/**
|
|
* Check if CodeGraph DB is available.
|
|
*/
|
|
export function isCodeGraphAvailable(): boolean {
|
|
return getDbPath() !== null;
|
|
}
|
|
|
|
/**
|
|
* Get summary stats from the index.
|
|
*/
|
|
export function getCodeGraphStats(): CodeGraphQueryResult {
|
|
return queryDb(`SELECT 'total_nodes' as key, COUNT(*) as value FROM nodes UNION ALL
|
|
SELECT 'total_edges', COUNT(*) FROM edges UNION ALL
|
|
SELECT 'total_files', COUNT(*) FROM files UNION ALL
|
|
SELECT 'languages', GROUP_CONCAT(DISTINCT language) FROM files UNION ALL
|
|
SELECT 'node_kinds', GROUP_CONCAT(DISTINCT kind) FROM nodes`);
|
|
}
|