kotadb 2.0.1-next.20260203165716 → 2.0.1-next.20260203174555

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kotadb",
3
- "version": "2.0.1-next.20260203165716",
3
+ "version": "2.0.1-next.20260203174555",
4
4
  "description": "Local-only code intelligence tool for CLI agents. SQLite-backed repository indexing and code search via MCP.",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Database queries for Dynamic Expertise feature
3
+ *
4
+ * Provides domain-specific file discovery based on dependency graph analysis.
5
+ * Key files are identified by how many other files depend on them (dependents).
6
+ *
7
+ * @module @api/expertise-queries
8
+ */
9
+
10
+ import { getGlobalDatabase, type KotaDatabase } from "@db/sqlite/index.js";
11
+ import { createLogger } from "@logging/logger.js";
12
+
13
+ const logger = createLogger({ module: "expertise-queries" });
14
+
15
+ /**
16
+ * Domain to path pattern mappings for expertise routing.
17
+ *
18
+ * Each domain maps to an array of SQL LIKE patterns that identify
19
+ * files belonging to that domain within the repository.
20
+ */
21
+ export const DOMAIN_PATH_PATTERNS: Record<string, string[]> = {
22
+ "database": [
23
+ "src/db/%",
24
+ "app/src/db/%",
25
+ ],
26
+ "api": [
27
+ "src/api/%",
28
+ "src/mcp/%",
29
+ "app/src/api/%",
30
+ "app/src/mcp/%",
31
+ ],
32
+ "indexer": [
33
+ "src/indexer/%",
34
+ "app/src/indexer/%",
35
+ ],
36
+ "testing": [
37
+ "tests/%",
38
+ "app/tests/%",
39
+ "__tests__/%",
40
+ ],
41
+ "claude-config": [
42
+ ".claude/%",
43
+ ],
44
+ "agent-authoring": [
45
+ ".claude/agents/%",
46
+ ],
47
+ "automation": [
48
+ ".claude/commands/automation/%",
49
+ ],
50
+ "github": [
51
+ ".github/%",
52
+ ],
53
+ "documentation": [
54
+ "web/docs/%",
55
+ "docs/%",
56
+ ],
57
+ };
58
+
59
+ /**
60
+ * Result type for domain key files query
61
+ */
62
+ export interface DomainKeyFile {
63
+ /** File path relative to repository root */
64
+ path: string;
65
+ /** Number of files that depend on this file */
66
+ dependentCount: number;
67
+ /** Repository ID the file belongs to */
68
+ repositoryId: string;
69
+ }
70
+
71
+ /**
72
+ * Get the most-depended-on files for a domain (key files).
73
+ *
74
+ * Key files are identified by counting how many other files import them.
75
+ * This helps identify the core infrastructure files for each domain.
76
+ *
77
+ * @param domain - Domain name (e.g., "database", "api", "indexer")
78
+ * @param limit - Maximum number of files to return (default: 10)
79
+ * @param repositoryId - Optional repository filter
80
+ * @returns Array of key files sorted by dependent count (descending)
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const keyFiles = getDomainKeyFiles("database", 5);
85
+ * // Returns top 5 most-imported files from src/db/
86
+ * ```
87
+ */
88
+ export function getDomainKeyFiles(
89
+ domain: string,
90
+ limit: number = 10,
91
+ repositoryId?: string,
92
+ ): DomainKeyFile[] {
93
+ const db = getGlobalDatabase();
94
+ return getDomainKeyFilesInternal(db, domain, limit, repositoryId);
95
+ }
96
+
97
+ /**
98
+ * Internal implementation that accepts a database parameter.
99
+ * Used for testing with injected database instances.
100
+ */
101
+ function getDomainKeyFilesInternal(
102
+ db: KotaDatabase,
103
+ domain: string,
104
+ limit: number = 10,
105
+ repositoryId?: string,
106
+ ): DomainKeyFile[] {
107
+ const patterns = DOMAIN_PATH_PATTERNS[domain];
108
+
109
+ if (!patterns || patterns.length === 0) {
110
+ logger.warn("Unknown domain or no patterns defined", { domain });
111
+ return [];
112
+ }
113
+
114
+ // Build WHERE clause for path patterns
115
+ const pathConditions = patterns.map(() => "f.path LIKE ?").join(" OR ");
116
+
117
+ // Build repository filter condition
118
+ const repoCondition = repositoryId ? "AND f.repository_id = ?" : "";
119
+
120
+ const sql = `
121
+ SELECT
122
+ f.path,
123
+ f.repository_id,
124
+ COUNT(DISTINCT r.file_id) AS dependent_count
125
+ FROM indexed_files f
126
+ JOIN indexed_references r ON f.path = r.target_file_path
127
+ WHERE r.reference_type = 'import'
128
+ AND (${pathConditions})
129
+ ${repoCondition}
130
+ GROUP BY f.id
131
+ ORDER BY dependent_count DESC
132
+ LIMIT ?
133
+ `;
134
+
135
+ // Build params array
136
+ const params: (string | number)[] = [...patterns];
137
+ if (repositoryId) {
138
+ params.push(repositoryId);
139
+ }
140
+ params.push(limit);
141
+
142
+ const rows = db.query<{
143
+ path: string;
144
+ repository_id: string;
145
+ dependent_count: number;
146
+ }>(sql, params);
147
+
148
+ logger.debug("Retrieved domain key files", {
149
+ domain,
150
+ count: rows.length,
151
+ limit,
152
+ repositoryId,
153
+ });
154
+
155
+ return rows.map(row => ({
156
+ path: row.path,
157
+ dependentCount: row.dependent_count,
158
+ repositoryId: row.repository_id,
159
+ }));
160
+ }
161
+
162
+ /**
163
+ * Get all files for a domain (without dependency ranking).
164
+ *
165
+ * Returns all indexed files matching the domain's path patterns.
166
+ * Useful when you need the full list rather than just key files.
167
+ *
168
+ * @param domain - Domain name
169
+ * @param limit - Maximum number of files (default: 100)
170
+ * @param repositoryId - Optional repository filter
171
+ * @returns Array of file paths
172
+ */
173
+ export function getDomainFiles(
174
+ domain: string,
175
+ limit: number = 100,
176
+ repositoryId?: string,
177
+ ): string[] {
178
+ const db = getGlobalDatabase();
179
+ return getDomainFilesInternal(db, domain, limit, repositoryId);
180
+ }
181
+
182
+ /**
183
+ * Internal implementation for getDomainFiles.
184
+ */
185
+ function getDomainFilesInternal(
186
+ db: KotaDatabase,
187
+ domain: string,
188
+ limit: number = 100,
189
+ repositoryId?: string,
190
+ ): string[] {
191
+ const patterns = DOMAIN_PATH_PATTERNS[domain];
192
+
193
+ if (!patterns || patterns.length === 0) {
194
+ logger.warn("Unknown domain or no patterns defined", { domain });
195
+ return [];
196
+ }
197
+
198
+ const pathConditions = patterns.map(() => "path LIKE ?").join(" OR ");
199
+ const repoCondition = repositoryId ? "AND repository_id = ?" : "";
200
+
201
+ const sql = `
202
+ SELECT path
203
+ FROM indexed_files
204
+ WHERE (${pathConditions})
205
+ ${repoCondition}
206
+ ORDER BY indexed_at DESC
207
+ LIMIT ?
208
+ `;
209
+
210
+ const params: (string | number)[] = [...patterns];
211
+ if (repositoryId) {
212
+ params.push(repositoryId);
213
+ }
214
+ params.push(limit);
215
+
216
+ const rows = db.query<{ path: string }>(sql, params);
217
+
218
+ return rows.map(row => row.path);
219
+ }
220
+
221
+ /**
222
+ * Get available domains that have indexed files.
223
+ *
224
+ * Checks which domains have at least one file matching their patterns.
225
+ * Useful for determining which expertise areas are available.
226
+ *
227
+ * @param repositoryId - Optional repository filter
228
+ * @returns Array of domain names that have files
229
+ */
230
+ export function getAvailableDomains(repositoryId?: string): string[] {
231
+ const db = getGlobalDatabase();
232
+ const availableDomains: string[] = [];
233
+
234
+ for (const domain of Object.keys(DOMAIN_PATH_PATTERNS)) {
235
+ const files = getDomainFilesInternal(db, domain, 1, repositoryId);
236
+ if (files.length > 0) {
237
+ availableDomains.push(domain);
238
+ }
239
+ }
240
+
241
+ logger.debug("Retrieved available domains", {
242
+ count: availableDomains.length,
243
+ domains: availableDomains,
244
+ repositoryId,
245
+ });
246
+
247
+ return availableDomains;
248
+ }
249
+
250
+ /**
251
+ * @deprecated Use getDomainKeyFiles() directly
252
+ * Backward-compatible alias that accepts db parameter for testing.
253
+ */
254
+ export function getDomainKeyFilesLocal(
255
+ db: KotaDatabase,
256
+ domain: string,
257
+ limit: number = 10,
258
+ repositoryId?: string,
259
+ ): DomainKeyFile[] {
260
+ return getDomainKeyFilesInternal(db, domain, limit, repositoryId);
261
+ }
262
+
263
+ /**
264
+ * @deprecated Use getDomainFiles() directly
265
+ * Backward-compatible alias that accepts db parameter for testing.
266
+ */
267
+ export function getDomainFilesLocal(
268
+ db: KotaDatabase,
269
+ domain: string,
270
+ limit: number = 100,
271
+ repositoryId?: string,
272
+ ): string[] {
273
+ return getDomainFilesInternal(db, domain, limit, repositoryId);
274
+ }