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 +1 -1
- package/src/api/expertise-queries.ts +274 -0
- package/src/cli/expertise.ts +495 -0
- package/src/cli.ts +14 -0
- package/src/mcp/server.ts +48 -0
- package/src/mcp/tools.ts +550 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kotadb",
|
|
3
|
-
"version": "2.0.1-next.
|
|
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
|
+
}
|