project-librarian 0.3.0 → 0.4.1

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.
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.evidenceCoverage = evidenceCoverage;
37
+ exports.workspaceSummary = workspaceSummary;
38
+ exports.workspaceDependencyGraph = workspaceDependencyGraph;
39
+ exports.validCodeReportSections = validCodeReportSections;
40
+ exports.resolveCodeReportSection = resolveCodeReportSection;
41
+ exports.invalidCodeReportSectionMessage = invalidCodeReportSectionMessage;
42
+ exports.codeReportMetadata = codeReportMetadata;
43
+ exports.codeReportForRequestedSection = codeReportForRequestedSection;
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const workspace_1 = require("../workspace");
47
+ const ownership_1 = require("./ownership");
48
+ const schema_1 = require("./schema");
49
+ function evidenceCoverage(database) {
50
+ const rows = database.prepare(`
51
+ SELECT 'files' AS table_name, count(*) AS rows FROM files
52
+ UNION ALL SELECT 'symbols', count(*) FROM symbols
53
+ UNION ALL SELECT 'imports', count(*) FROM imports
54
+ UNION ALL SELECT 'routes', count(*) FROM routes
55
+ UNION ALL SELECT 'configs', count(*) FROM configs
56
+ UNION ALL SELECT 'edges', count(*) FROM edges
57
+ `).all();
58
+ return Object.fromEntries(rows.map((row) => [String(row.table_name), Number(row.rows ?? 0)]));
59
+ }
60
+ function incrementOwnerField(owners, context, filePath, field, increment = 1) {
61
+ const info = (0, ownership_1.ownershipInfo)(filePath, context);
62
+ const key = info.owner;
63
+ const current = owners.get(key) ?? {
64
+ bytes: 0,
65
+ codeowners: info.codeowners,
66
+ configs: 0,
67
+ file_count: 0,
68
+ imports: 0,
69
+ languages: "",
70
+ lines: 0,
71
+ owner: key,
72
+ owner_source: info.owner_source,
73
+ routes: 0,
74
+ symbols: 0,
75
+ };
76
+ if (info.codeowners && !current.codeowners.split(", ").includes(info.codeowners))
77
+ current.codeowners = current.codeowners ? `${current.codeowners}; ${info.codeowners}` : info.codeowners;
78
+ current[field] += increment;
79
+ owners.set(key, current);
80
+ }
81
+ function ownershipSummary(database) {
82
+ const files = database.prepare("SELECT path, language, profile, lines, bytes FROM files ORDER BY path").all();
83
+ const context = (0, ownership_1.ownershipContext)();
84
+ const owners = new Map();
85
+ const ownerLanguages = new Map();
86
+ for (const row of files) {
87
+ const filePath = String(row.path);
88
+ const key = (0, ownership_1.ownershipInfo)(filePath, context).owner;
89
+ incrementOwnerField(owners, context, filePath, "file_count");
90
+ incrementOwnerField(owners, context, filePath, "lines", Number(row.lines ?? 0));
91
+ incrementOwnerField(owners, context, filePath, "bytes", Number(row.bytes ?? 0));
92
+ const languages = ownerLanguages.get(key) ?? new Set();
93
+ languages.add(String(row.language));
94
+ ownerLanguages.set(key, languages);
95
+ }
96
+ for (const row of database.prepare("SELECT file_path, count(*) AS count FROM symbols GROUP BY file_path").all())
97
+ incrementOwnerField(owners, context, String(row.file_path), "symbols", Number(row.count ?? 0));
98
+ for (const row of database.prepare("SELECT file_path, count(*) AS count FROM routes GROUP BY file_path").all())
99
+ incrementOwnerField(owners, context, String(row.file_path), "routes", Number(row.count ?? 0));
100
+ for (const row of database.prepare("SELECT from_file, count(*) AS count FROM imports GROUP BY from_file").all())
101
+ incrementOwnerField(owners, context, String(row.from_file), "imports", Number(row.count ?? 0));
102
+ for (const row of database.prepare("SELECT file_path, count(*) AS count FROM configs GROUP BY file_path").all())
103
+ incrementOwnerField(owners, context, String(row.file_path), "configs", Number(row.count ?? 0));
104
+ return Array.from(owners.values()).map((owner) => ({
105
+ ...owner,
106
+ languages: Array.from(ownerLanguages.get(String(owner.owner)) ?? []).sort().join(", "),
107
+ })).sort((left, right) => right.file_count - left.file_count || left.owner.localeCompare(right.owner)).slice(0, 25);
108
+ }
109
+ function languageProfileSummary(database) {
110
+ return database.prepare("SELECT language, profile, count(*) AS files, sum(lines) AS lines, sum(bytes) AS bytes FROM files GROUP BY language, profile ORDER BY files DESC, language").all();
111
+ }
112
+ function parserBackendSummary(database, runtime) {
113
+ return database.prepare("SELECT language, profile, count(*) AS files, sum(lines) AS lines, sum(bytes) AS bytes FROM files GROUP BY language, profile ORDER BY files DESC, language").all().map((row) => {
114
+ const profile = String(row.profile);
115
+ const backend = runtime.parserBackendForProfile(profile);
116
+ return {
117
+ language: row.language,
118
+ profile,
119
+ backend: backend.id,
120
+ label: backend.label,
121
+ extraction_strength: backend.strength,
122
+ files: row.files,
123
+ lines: row.lines,
124
+ bytes: row.bytes,
125
+ };
126
+ });
127
+ }
128
+ function workspaceSummary(database) {
129
+ const context = (0, ownership_1.ownershipContext)();
130
+ const counts = new Map();
131
+ for (const workspace of context.workspaces) {
132
+ counts.set(workspace.root, { ...workspace, bytes: 0, files: 0, lines: 0 });
133
+ }
134
+ for (const row of database.prepare("SELECT path, lines, bytes FROM files ORDER BY path").all()) {
135
+ const workspace = (0, ownership_1.matchingWorkspace)(String(row.path), context.workspaces);
136
+ if (!workspace)
137
+ continue;
138
+ const current = counts.get(workspace.root) ?? { ...workspace, bytes: 0, files: 0, lines: 0 };
139
+ current.files += 1;
140
+ current.lines += Number(row.lines ?? 0);
141
+ current.bytes += Number(row.bytes ?? 0);
142
+ counts.set(workspace.root, current);
143
+ }
144
+ return {
145
+ workspace_packages: Array.from(counts.values()).sort((left, right) => left.root.localeCompare(right.root)),
146
+ codeowners: context.codeownerRules.map((rule) => ({
147
+ file_path: rule.file_path,
148
+ line: rule.line,
149
+ pattern: rule.pattern,
150
+ owners: rule.owners.join(", "),
151
+ })),
152
+ };
153
+ }
154
+ function packageManagerFromLockfile(filePath) {
155
+ const base = path.basename(filePath);
156
+ if (base === "package-lock.json" || base === "npm-shrinkwrap.json")
157
+ return "npm";
158
+ if (base === "pnpm-lock.yaml")
159
+ return "pnpm";
160
+ if (base === "yarn.lock")
161
+ return "yarn";
162
+ if (base === "bun.lockb" || base === "bun.lock")
163
+ return "bun";
164
+ return "unknown";
165
+ }
166
+ function workspaceDependencyGraph() {
167
+ const workspaces = (0, ownership_1.workspacePackages)();
168
+ const byName = new Map(workspaces.map((workspace) => [workspace.name, workspace]));
169
+ const lockfiles = ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"]
170
+ .filter((filePath) => fs.existsSync((0, workspace_1.abs)(filePath)))
171
+ .map((filePath) => ({ file_path: filePath, package_manager: packageManagerFromLockfile(filePath), scope: "root" }));
172
+ const workspaceRows = [];
173
+ const internalEdges = [];
174
+ const externalDependencies = new Map();
175
+ for (const workspace of workspaces) {
176
+ const packageJsonPath = (0, workspace_1.normalizePath)(path.join(workspace.root, "package.json"));
177
+ const packageJson = (0, ownership_1.readJsonObject)(packageJsonPath);
178
+ const dependencyFields = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
179
+ const dependencyCounts = {};
180
+ const workspaceInternalEdges = [];
181
+ for (const field of dependencyFields) {
182
+ const dependencies = packageJson?.[field];
183
+ if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies))
184
+ continue;
185
+ for (const [dependencyName, version] of Object.entries(dependencies)) {
186
+ dependencyCounts[field] = (dependencyCounts[field] ?? 0) + 1;
187
+ const target = byName.get(dependencyName);
188
+ if (target) {
189
+ const edge = {
190
+ from_workspace: workspace.root,
191
+ from_package: workspace.name,
192
+ to_workspace: target.root,
193
+ to_package: target.name,
194
+ dependency_type: field,
195
+ version: typeof version === "string" ? version : String(version),
196
+ };
197
+ internalEdges.push(edge);
198
+ workspaceInternalEdges.push(edge);
199
+ }
200
+ else {
201
+ const key = `${dependencyName}\0${field}`;
202
+ const current = externalDependencies.get(key) ?? { dependency: dependencyName, dependency_type: field, workspaces: new Set() };
203
+ current.workspaces.add(workspace.root);
204
+ externalDependencies.set(key, current);
205
+ }
206
+ }
207
+ }
208
+ for (const lockfileName of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"]) {
209
+ const lockfilePath = (0, workspace_1.normalizePath)(path.join(workspace.root, lockfileName));
210
+ if (fs.existsSync((0, workspace_1.abs)(lockfilePath))) {
211
+ lockfiles.push({ file_path: lockfilePath, package_manager: packageManagerFromLockfile(lockfilePath), scope: workspace.root });
212
+ }
213
+ }
214
+ workspaceRows.push({
215
+ name: workspace.name,
216
+ root: workspace.root,
217
+ dependency_counts: dependencyCounts,
218
+ internal_dependency_count: workspaceInternalEdges.length,
219
+ });
220
+ }
221
+ return {
222
+ workspace_count: workspaces.length,
223
+ package_managers: Array.from(new Set(lockfiles.map((lockfile) => lockfile.package_manager))).sort(),
224
+ lockfiles,
225
+ workspaces: workspaceRows.sort((left, right) => String(left.root).localeCompare(String(right.root))),
226
+ internal_dependencies: internalEdges.sort((left, right) => String(left.from_workspace).localeCompare(String(right.from_workspace)) || String(left.to_workspace).localeCompare(String(right.to_workspace))),
227
+ external_dependency_hotspots: Array.from(externalDependencies.values()).map((entry) => ({
228
+ dependency: entry.dependency,
229
+ dependency_type: entry.dependency_type,
230
+ workspace_count: entry.workspaces.size,
231
+ workspaces: Array.from(entry.workspaces).sort().join(", "),
232
+ })).sort((left, right) => right.workspace_count - left.workspace_count || left.dependency.localeCompare(right.dependency)).slice(0, 100),
233
+ };
234
+ }
235
+ function routeInventory(database) {
236
+ return database.prepare("SELECT method, route, file_path, line, handler FROM routes ORDER BY file_path, line LIMIT 100").all();
237
+ }
238
+ function dependencyHotspots(database) {
239
+ return {
240
+ imports: database.prepare("SELECT to_ref, count(DISTINCT from_file) AS importing_files, count(*) AS reference_count FROM imports GROUP BY to_ref ORDER BY importing_files DESC, reference_count DESC, to_ref LIMIT 50").all(),
241
+ package_dependencies: database.prepare("SELECT substr(key, 12) AS package, value AS version, file_path FROM configs WHERE key LIKE 'dependency:%' ORDER BY file_path, package LIMIT 100").all(),
242
+ };
243
+ }
244
+ function configInventory(database) {
245
+ return database.prepare("SELECT key, value, file_path, line FROM configs WHERE key LIKE 'script:%' OR key LIKE 'dependency:%' OR key LIKE 'devDependency:%' ORDER BY file_path, key LIMIT 150").all();
246
+ }
247
+ function edgeSummary(database) {
248
+ return {
249
+ by_kind: database.prepare("SELECT kind, count(*) AS edges FROM edges GROUP BY kind ORDER BY edges DESC, kind").all(),
250
+ fanout: database.prepare("SELECT source_kind, source, kind, count(DISTINCT target) AS targets, file_path FROM edges GROUP BY source_kind, source, kind, file_path ORDER BY targets DESC, source LIMIT 50").all(),
251
+ };
252
+ }
253
+ const reportSectionDefinitions = [
254
+ { key: "coverage", outputKey: "evidence_coverage", aliases: ["coverage", "evidence", "evidence_coverage"], render: (database) => evidenceCoverage(database) },
255
+ { key: "ownership", outputKey: "ownership_summary", aliases: ["ownership", "ownership_summary"], render: (database) => ownershipSummary(database) },
256
+ { key: "languages", outputKey: "language_profile_summary", aliases: ["language", "language_profile_summary", "languages"], render: (database) => languageProfileSummary(database) },
257
+ { key: "parsers", outputKey: "parser_backend_summary", aliases: ["parser", "parser_backend_summary", "parser_backends", "parsers"], render: (database, runtime) => parserBackendSummary(database, runtime) },
258
+ { key: "workspaces", outputKey: "workspace_summary", aliases: ["workspace", "workspace_summary", "workspaces"], render: (database) => workspaceSummary(database) },
259
+ { key: "workspace-graph", outputKey: "workspace_dependency_graph", aliases: ["workspace_graph", "workspace-graph", "workspacegraph", "monorepo", "monorepo_graph"], render: () => workspaceDependencyGraph() },
260
+ { key: "routes", outputKey: "route_inventory", aliases: ["route", "route_inventory", "routes"], render: (database) => routeInventory(database) },
261
+ { key: "hotspots", outputKey: "dependency_hotspots", aliases: ["dependencies", "dependency", "dependency_hotspots", "hotspot", "hotspots"], render: (database) => dependencyHotspots(database) },
262
+ { key: "configs", outputKey: "config_inventory", aliases: ["config", "configs"], render: (database) => configInventory(database) },
263
+ { key: "edges", outputKey: "edge_summary", aliases: ["edge", "edge_summary", "edges"], render: (database) => edgeSummary(database) },
264
+ ];
265
+ const reportSectionByKey = new Map(reportSectionDefinitions.map((section) => [section.key, section]));
266
+ const reportSectionByAlias = new Map(reportSectionDefinitions.flatMap((section) => section.aliases.map((alias) => [alias, section])));
267
+ function validCodeReportSections() {
268
+ return reportSectionDefinitions.map((section) => section.key);
269
+ }
270
+ function resolveCodeReportSection(requestedSection) {
271
+ const requested = requestedSection.trim().toLowerCase();
272
+ if (!requested || requested === "all" || requested === "full")
273
+ return "";
274
+ return reportSectionByAlias.get(requested)?.key;
275
+ }
276
+ function invalidCodeReportSectionMessage(requestedSection) {
277
+ return `invalid --code-report-section: ${requestedSection}; expected one of: ${validCodeReportSections().join(", ")}`;
278
+ }
279
+ function codeReportMetadata(database, runtime) {
280
+ const staleness = runtime.staleness;
281
+ return {
282
+ schema_version: 1,
283
+ generated_at: new Date().toISOString(),
284
+ database: runtime.databaseRelativePath,
285
+ scopes: (0, schema_1.indexedScopes)(database),
286
+ parser_mode: (0, schema_1.indexedParserMode)(database),
287
+ stale: {
288
+ files: staleness.added + staleness.changed + staleness.deleted,
289
+ changed: staleness.changed,
290
+ added: staleness.added,
291
+ deleted: staleness.deleted,
292
+ },
293
+ };
294
+ }
295
+ function codeReportSectionData(database, section, runtime) {
296
+ const definition = reportSectionByKey.get(section);
297
+ if (!definition)
298
+ return undefined;
299
+ return definition.render(database, runtime);
300
+ }
301
+ function codeReport(database, runtime) {
302
+ const report = {
303
+ ...codeReportMetadata(database, runtime),
304
+ report_sections: reportSectionDefinitions.map((section) => section.outputKey),
305
+ };
306
+ for (const section of reportSectionDefinitions) {
307
+ report[section.outputKey] = section.render(database, runtime);
308
+ }
309
+ return report;
310
+ }
311
+ function codeReportForRequestedSection(database, requestedSection, runtime) {
312
+ const section = resolveCodeReportSection(requestedSection);
313
+ if (section === undefined)
314
+ return undefined;
315
+ if (!section)
316
+ return codeReport(database, runtime);
317
+ return {
318
+ ...codeReportMetadata(database, runtime),
319
+ section,
320
+ data: codeReportSectionData(database, section, runtime),
321
+ };
322
+ }
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.codeIndexSchemaVersion = void 0;
4
+ exports.setupDatabase = setupDatabase;
5
+ exports.createIndexStatements = createIndexStatements;
6
+ exports.removeIndexedFile = removeIndexedFile;
7
+ exports.writeIndexMetadata = writeIndexMetadata;
8
+ exports.readMetaValue = readMetaValue;
9
+ exports.indexedScopes = indexedScopes;
10
+ exports.indexedParserMode = indexedParserMode;
11
+ exports.incrementalCompatibility = incrementalCompatibility;
12
+ exports.codeIndexSnapshot = codeIndexSnapshot;
13
+ const workspace_1 = require("../workspace");
14
+ exports.codeIndexSchemaVersion = "4";
15
+ function setupDatabase(database) {
16
+ database.exec(`
17
+ PRAGMA journal_mode = WAL;
18
+ CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
19
+ CREATE TABLE files (
20
+ path TEXT PRIMARY KEY,
21
+ language TEXT NOT NULL,
22
+ profile TEXT NOT NULL,
23
+ kind TEXT NOT NULL,
24
+ bytes INTEGER NOT NULL,
25
+ lines INTEGER NOT NULL,
26
+ hash TEXT NOT NULL,
27
+ mtime_ms REAL NOT NULL,
28
+ size INTEGER NOT NULL
29
+ );
30
+ CREATE TABLE symbols (
31
+ id INTEGER PRIMARY KEY,
32
+ name TEXT NOT NULL,
33
+ kind TEXT NOT NULL,
34
+ file_path TEXT NOT NULL,
35
+ line INTEGER NOT NULL,
36
+ signature TEXT NOT NULL
37
+ );
38
+ CREATE TABLE imports (
39
+ id INTEGER PRIMARY KEY,
40
+ from_file TEXT NOT NULL,
41
+ to_ref TEXT NOT NULL,
42
+ imported TEXT NOT NULL,
43
+ line INTEGER NOT NULL,
44
+ raw TEXT NOT NULL
45
+ );
46
+ CREATE TABLE routes (
47
+ id INTEGER PRIMARY KEY,
48
+ method TEXT NOT NULL,
49
+ route TEXT NOT NULL,
50
+ file_path TEXT NOT NULL,
51
+ line INTEGER NOT NULL,
52
+ handler TEXT NOT NULL
53
+ );
54
+ CREATE TABLE configs (
55
+ id INTEGER PRIMARY KEY,
56
+ key TEXT NOT NULL,
57
+ value TEXT NOT NULL,
58
+ file_path TEXT NOT NULL,
59
+ line INTEGER NOT NULL
60
+ );
61
+ CREATE TABLE edges (
62
+ id INTEGER PRIMARY KEY,
63
+ kind TEXT NOT NULL,
64
+ source_kind TEXT NOT NULL,
65
+ source TEXT NOT NULL,
66
+ target_kind TEXT NOT NULL,
67
+ target TEXT NOT NULL,
68
+ file_path TEXT NOT NULL,
69
+ line INTEGER NOT NULL,
70
+ evidence TEXT NOT NULL
71
+ );
72
+ CREATE VIRTUAL TABLE files_fts USING fts5(path, language, profile, content);
73
+ CREATE VIRTUAL TABLE symbols_fts USING fts5(name, kind, file_path, signature);
74
+ CREATE INDEX idx_symbols_file ON symbols(file_path);
75
+ CREATE INDEX idx_symbols_name ON symbols(name);
76
+ CREATE INDEX idx_imports_from ON imports(from_file);
77
+ CREATE INDEX idx_routes_path ON routes(route);
78
+ CREATE INDEX idx_configs_file ON configs(file_path);
79
+ CREATE INDEX idx_edges_source ON edges(source_kind, source);
80
+ CREATE INDEX idx_edges_target ON edges(target_kind, target);
81
+ CREATE INDEX idx_edges_kind ON edges(kind);
82
+ `);
83
+ }
84
+ function createIndexStatements(database) {
85
+ return {
86
+ deleteConfig: database.prepare("DELETE FROM configs WHERE file_path = ?"),
87
+ deleteEdge: database.prepare("DELETE FROM edges WHERE file_path = ?"),
88
+ deleteFile: database.prepare("DELETE FROM files WHERE path = ?"),
89
+ deleteFileFts: database.prepare("DELETE FROM files_fts WHERE path = ?"),
90
+ deleteImport: database.prepare("DELETE FROM imports WHERE from_file = ?"),
91
+ deleteRoute: database.prepare("DELETE FROM routes WHERE file_path = ?"),
92
+ deleteSymbol: database.prepare("DELETE FROM symbols WHERE file_path = ?"),
93
+ deleteSymbolFts: database.prepare("DELETE FROM symbols_fts WHERE file_path = ?"),
94
+ insertConfig: database.prepare("INSERT INTO configs (key, value, file_path, line) VALUES (?, ?, ?, ?)"),
95
+ insertEdge: database.prepare("INSERT INTO edges (kind, source_kind, source, target_kind, target, file_path, line, evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"),
96
+ insertFile: database.prepare("INSERT INTO files (path, language, profile, kind, bytes, lines, hash, mtime_ms, size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
97
+ insertFileFts: database.prepare("INSERT INTO files_fts (path, language, profile, content) VALUES (?, ?, ?, ?)"),
98
+ insertImport: database.prepare("INSERT INTO imports (from_file, to_ref, imported, line, raw) VALUES (?, ?, ?, ?, ?)"),
99
+ insertMeta: database.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)"),
100
+ insertRoute: database.prepare("INSERT INTO routes (method, route, file_path, line, handler) VALUES (?, ?, ?, ?, ?)"),
101
+ insertSymbol: database.prepare("INSERT INTO symbols (name, kind, file_path, line, signature) VALUES (?, ?, ?, ?, ?)"),
102
+ insertSymbolFts: database.prepare("INSERT INTO symbols_fts (name, kind, file_path, signature) VALUES (?, ?, ?, ?)"),
103
+ };
104
+ }
105
+ function removeIndexedFile(filePath, statements) {
106
+ statements.deleteConfig.run(filePath);
107
+ statements.deleteEdge.run(filePath);
108
+ statements.deleteImport.run(filePath);
109
+ statements.deleteRoute.run(filePath);
110
+ statements.deleteSymbol.run(filePath);
111
+ statements.deleteSymbolFts.run(filePath);
112
+ statements.deleteFileFts.run(filePath);
113
+ statements.deleteFile.run(filePath);
114
+ }
115
+ function writeIndexMetadata(scopes, parserMode, statements) {
116
+ statements.insertMeta.run("schema_version", exports.codeIndexSchemaVersion);
117
+ statements.insertMeta.run("updated_at", new Date().toISOString());
118
+ statements.insertMeta.run("root", workspace_1.root);
119
+ statements.insertMeta.run("scopes", scopes.join(", "));
120
+ statements.insertMeta.run("scopes_json", JSON.stringify(scopes));
121
+ statements.insertMeta.run("parser_mode", parserMode);
122
+ statements.insertMeta.run("terminology", "code evidence index");
123
+ }
124
+ function readMetaValue(database, key) {
125
+ const rows = database.prepare("SELECT value FROM meta WHERE key = ?").all(key);
126
+ const value = rows[0]?.value;
127
+ return typeof value === "string" ? value : "";
128
+ }
129
+ function indexedScopes(database) {
130
+ const scopesJson = readMetaValue(database, "scopes_json");
131
+ if (scopesJson) {
132
+ try {
133
+ const parsed = JSON.parse(scopesJson);
134
+ if (Array.isArray(parsed) && parsed.every((scope) => typeof scope === "string"))
135
+ return parsed;
136
+ }
137
+ catch {
138
+ // Fall back to the legacy comma-separated scope metadata below.
139
+ }
140
+ }
141
+ return readMetaValue(database, "scopes")
142
+ .split(",")
143
+ .map((scope) => scope.trim())
144
+ .filter(Boolean);
145
+ }
146
+ function indexedParserMode(database) {
147
+ const mode = readMetaValue(database, "parser_mode");
148
+ return mode === "tree-sitter" ? "tree-sitter" : "default";
149
+ }
150
+ function scopesMatch(left, right) {
151
+ return left.length === right.length && left.every((scope, index) => scope === right[index]);
152
+ }
153
+ function incrementalCompatibility(database, scopes, parserMode) {
154
+ const existingSchemaVersion = readMetaValue(database, "schema_version");
155
+ if (existingSchemaVersion !== exports.codeIndexSchemaVersion) {
156
+ return {
157
+ compatible: false,
158
+ reason: `existing schema version ${existingSchemaVersion || "(missing)"} does not match ${exports.codeIndexSchemaVersion}`,
159
+ };
160
+ }
161
+ const existingScopes = indexedScopes(database);
162
+ if (!scopesMatch(existingScopes, scopes)) {
163
+ return {
164
+ compatible: false,
165
+ reason: `indexed scopes do not match requested scopes: indexed [${existingScopes.join(", ")}], requested [${scopes.join(", ")}]`,
166
+ };
167
+ }
168
+ const existingParserMode = indexedParserMode(database);
169
+ if (existingParserMode !== parserMode) {
170
+ return {
171
+ compatible: false,
172
+ reason: `indexed parser mode ${existingParserMode} does not match requested parser mode ${parserMode}`,
173
+ };
174
+ }
175
+ return { compatible: true, reason: "" };
176
+ }
177
+ function snapshotRows(database, sql) {
178
+ return database.prepare(sql).all().map((row) => {
179
+ const normalized = {};
180
+ for (const key of Object.keys(row).sort()) {
181
+ const value = row[key];
182
+ normalized[key] = typeof value === "string" || typeof value === "number" || value === null ? value : String(value);
183
+ }
184
+ return normalized;
185
+ });
186
+ }
187
+ function codeIndexSnapshot(database) {
188
+ return {
189
+ configs: snapshotRows(database, "SELECT file_path, line, key, value FROM configs ORDER BY file_path, line, key, value"),
190
+ edges: snapshotRows(database, "SELECT file_path, line, kind, source_kind, source, target_kind, target, evidence FROM edges ORDER BY file_path, line, kind, source, target, evidence"),
191
+ files: snapshotRows(database, "SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path"),
192
+ imports: snapshotRows(database, "SELECT from_file, line, to_ref, imported, raw FROM imports ORDER BY from_file, line, to_ref, imported, raw"),
193
+ routes: snapshotRows(database, "SELECT file_path, line, method, route, handler FROM routes ORDER BY file_path, line, method, route, handler"),
194
+ symbols: snapshotRows(database, "SELECT file_path, line, kind, name, signature FROM symbols ORDER BY file_path, line, kind, name, signature"),
195
+ };
196
+ }
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.containsLikePattern = containsLikePattern;
4
+ exports.shouldUseFtsSearchForScale = shouldUseFtsSearchForScale;
5
+ exports.searchFiles = searchFiles;
6
+ exports.searchSymbols = searchSymbols;
7
+ const code_index_file_policy_1 = require("../code-index-file-policy");
8
+ function escapeLikeTerm(term) {
9
+ return term.replace(/[\\%_]/g, (match) => `\\${match}`);
10
+ }
11
+ function containsLikePattern(term) {
12
+ return `%${escapeLikeTerm(term)}%`;
13
+ }
14
+ function prefixLikePattern(term) {
15
+ return `${escapeLikeTerm(term)}%`;
16
+ }
17
+ function ftsTokens(term) {
18
+ return Array.from(new Set(term.match(/[\p{L}\p{N}_]+/gu) ?? []));
19
+ }
20
+ function ftsPrefixQuery(term) {
21
+ const tokens = ftsTokens(term);
22
+ return tokens.slice(0, 8).map((token) => `"${token.replace(/"/g, "\"\"")}"*`).join(" AND ");
23
+ }
24
+ function indexedFileCount(database) {
25
+ const row = database.prepare("SELECT count(*) AS count FROM files").all()[0] ?? {};
26
+ return Number(row.count ?? 0);
27
+ }
28
+ function shouldUseFtsSearchForScale(term, fileCount) {
29
+ const tokens = Array.from(new Set(term.match(/[\p{L}\p{N}_]+/gu) ?? []));
30
+ if (tokens.length === 0)
31
+ return false;
32
+ if (tokens.length > 1)
33
+ return true;
34
+ return fileCount >= code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD;
35
+ }
36
+ function shouldUseFtsSearch(database, term) {
37
+ const tokens = ftsTokens(term);
38
+ if (tokens.length === 0)
39
+ return false;
40
+ if (tokens.length > 1)
41
+ return true;
42
+ return indexedFileCount(database) >= code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD;
43
+ }
44
+ function stringValue(row, key) {
45
+ const value = row[key];
46
+ return typeof value === "string" || typeof value === "number" ? String(value) : "";
47
+ }
48
+ function addRankedRow(rowsByKey, row, key, score) {
49
+ const current = rowsByKey.get(key);
50
+ if (!current || score > current.score)
51
+ rowsByKey.set(key, { row, score });
52
+ }
53
+ function rankedRows(rowsByKey, limit, stableKeys) {
54
+ return Array.from(rowsByKey.values())
55
+ .sort((left, right) => {
56
+ const scoreDelta = right.score - left.score;
57
+ if (scoreDelta !== 0)
58
+ return scoreDelta;
59
+ for (const key of stableKeys) {
60
+ const compared = stringValue(left.row, key).localeCompare(stringValue(right.row, key));
61
+ if (compared !== 0)
62
+ return compared;
63
+ }
64
+ return 0;
65
+ })
66
+ .slice(0, limit)
67
+ .map((ranked) => ranked.row);
68
+ }
69
+ function searchFiles(database, term, limit = 25) {
70
+ const normalized = term.trim();
71
+ if (!normalized)
72
+ return [];
73
+ const contains = containsLikePattern(normalized);
74
+ const prefix = prefixLikePattern(normalized);
75
+ const rowsByKey = new Map();
76
+ const exactRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path = ? ORDER BY path LIMIT ?").all(normalized, limit);
77
+ exactRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 900));
78
+ const prefixRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT ?").all(prefix, limit);
79
+ prefixRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 750));
80
+ const ftsQuery = shouldUseFtsSearch(database, normalized) ? ftsPrefixQuery(normalized) : "";
81
+ if (ftsQuery) {
82
+ const ftsRows = database.prepare(`
83
+ SELECT files.path, files.language, files.profile, files.lines, files.bytes
84
+ FROM files_fts
85
+ JOIN files ON files.path = files_fts.path
86
+ WHERE files_fts MATCH ?
87
+ ORDER BY bm25(files_fts, 8.0, 1.0, 1.0, 0.25), files.path
88
+ LIMIT ?
89
+ `).all(ftsQuery, limit);
90
+ ftsRows.forEach((row, index) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 650 - index));
91
+ }
92
+ const containsRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT ?").all(contains, limit);
93
+ containsRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 500));
94
+ return rankedRows(rowsByKey, limit, ["path"]);
95
+ }
96
+ function symbolKey(row) {
97
+ return [
98
+ stringValue(row, "file_path"),
99
+ stringValue(row, "line"),
100
+ stringValue(row, "kind"),
101
+ stringValue(row, "name"),
102
+ stringValue(row, "signature"),
103
+ ].join("\u0000");
104
+ }
105
+ function searchSymbols(database, term, limit = 50) {
106
+ const normalized = term.trim();
107
+ if (!normalized)
108
+ return [];
109
+ const contains = containsLikePattern(normalized);
110
+ const prefix = prefixLikePattern(normalized);
111
+ const rowsByKey = new Map();
112
+ const exactRows = database.prepare(`
113
+ SELECT name, kind, file_path, line, signature
114
+ FROM symbols
115
+ WHERE name = ? OR signature = ?
116
+ ORDER BY file_path, line
117
+ LIMIT ?
118
+ `).all(normalized, normalized, limit);
119
+ exactRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 1000));
120
+ const prefixRows = database.prepare(`
121
+ SELECT name, kind, file_path, line, signature
122
+ FROM symbols
123
+ WHERE name LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\'
124
+ ORDER BY file_path, line
125
+ LIMIT ?
126
+ `).all(prefix, prefix, limit);
127
+ prefixRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 850));
128
+ const ftsQuery = shouldUseFtsSearch(database, normalized) ? ftsPrefixQuery(normalized) : "";
129
+ if (ftsQuery) {
130
+ const ftsRows = database.prepare(`
131
+ SELECT symbols.name, symbols.kind, symbols.file_path, symbols.line, symbols.signature
132
+ FROM symbols_fts
133
+ JOIN symbols
134
+ ON symbols.name = symbols_fts.name
135
+ AND symbols.kind = symbols_fts.kind
136
+ AND symbols.file_path = symbols_fts.file_path
137
+ AND symbols.signature = symbols_fts.signature
138
+ WHERE symbols_fts MATCH ?
139
+ ORDER BY bm25(symbols_fts, 8.0, 1.0, 4.0, 2.0), symbols.file_path, symbols.line
140
+ LIMIT ?
141
+ `).all(ftsQuery, limit);
142
+ ftsRows.forEach((row, index) => addRankedRow(rowsByKey, row, symbolKey(row), 700 - index));
143
+ }
144
+ const containsRows = database.prepare(`
145
+ SELECT name, kind, file_path, line, signature
146
+ FROM symbols
147
+ WHERE name LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\'
148
+ ORDER BY file_path, line
149
+ LIMIT ?
150
+ `).all(contains, contains, contains, limit);
151
+ containsRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 500));
152
+ return rankedRows(rowsByKey, limit, ["file_path", "line", "kind", "name", "signature"]);
153
+ }