@kanchingyg/repomcp 0.0.0-stage → 0.1.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -2
  3. package/SKILL.md +10 -0
  4. package/dist/adapters/api-routes.d.ts +31 -0
  5. package/dist/adapters/api-routes.d.ts.map +1 -0
  6. package/dist/adapters/api-routes.js +680 -0
  7. package/dist/adapters/api-routes.js.map +1 -0
  8. package/dist/adapters/cli-crawler.d.ts +28 -0
  9. package/dist/adapters/cli-crawler.d.ts.map +1 -0
  10. package/dist/adapters/cli-crawler.js +467 -0
  11. package/dist/adapters/cli-crawler.js.map +1 -0
  12. package/dist/adapters/data-sources.d.ts +6 -0
  13. package/dist/adapters/data-sources.d.ts.map +1 -0
  14. package/dist/adapters/data-sources.js +672 -0
  15. package/dist/adapters/data-sources.js.map +1 -0
  16. package/dist/adapters/docs-bm25.d.ts +50 -0
  17. package/dist/adapters/docs-bm25.d.ts.map +1 -0
  18. package/dist/adapters/docs-bm25.js +484 -0
  19. package/dist/adapters/docs-bm25.js.map +1 -0
  20. package/dist/adapters/index.d.ts +18 -0
  21. package/dist/adapters/index.d.ts.map +1 -0
  22. package/dist/adapters/index.js +66 -0
  23. package/dist/adapters/index.js.map +1 -0
  24. package/dist/adapters/polyglot-code.d.ts +65 -0
  25. package/dist/adapters/polyglot-code.d.ts.map +1 -0
  26. package/dist/adapters/polyglot-code.js +691 -0
  27. package/dist/adapters/polyglot-code.js.map +1 -0
  28. package/dist/adapters/remote-target.d.ts +30 -0
  29. package/dist/adapters/remote-target.d.ts.map +1 -0
  30. package/dist/adapters/remote-target.js +559 -0
  31. package/dist/adapters/remote-target.js.map +1 -0
  32. package/dist/adapters/task-runners.d.ts +8 -0
  33. package/dist/adapters/task-runners.d.ts.map +1 -0
  34. package/dist/adapters/task-runners.js +468 -0
  35. package/dist/adapters/task-runners.js.map +1 -0
  36. package/dist/adapters/types.d.ts +9 -0
  37. package/dist/adapters/types.d.ts.map +1 -0
  38. package/dist/adapters/types.js +21 -0
  39. package/dist/adapters/types.js.map +1 -0
  40. package/dist/bridge.d.ts +136 -0
  41. package/dist/bridge.d.ts.map +1 -0
  42. package/dist/bridge.js +488 -0
  43. package/dist/bridge.js.map +1 -0
  44. package/dist/checkpoints.d.ts +41 -0
  45. package/dist/checkpoints.d.ts.map +1 -0
  46. package/dist/checkpoints.js +208 -0
  47. package/dist/checkpoints.js.map +1 -0
  48. package/dist/cli.d.ts +3 -0
  49. package/dist/cli.d.ts.map +1 -0
  50. package/dist/cli.js +124 -0
  51. package/dist/cli.js.map +1 -0
  52. package/dist/dirty-parser.d.ts +10 -0
  53. package/dist/dirty-parser.d.ts.map +1 -0
  54. package/dist/dirty-parser.js +220 -0
  55. package/dist/dirty-parser.js.map +1 -0
  56. package/dist/harnesses.d.ts +74 -0
  57. package/dist/harnesses.d.ts.map +1 -0
  58. package/dist/harnesses.js +204 -0
  59. package/dist/harnesses.js.map +1 -0
  60. package/dist/introspect.d.ts +18 -0
  61. package/dist/introspect.d.ts.map +1 -0
  62. package/dist/introspect.js +125 -0
  63. package/dist/introspect.js.map +1 -0
  64. package/dist/path-containment.d.ts +2 -0
  65. package/dist/path-containment.d.ts.map +1 -0
  66. package/dist/path-containment.js +20 -0
  67. package/dist/path-containment.js.map +1 -0
  68. package/dist/providers.d.ts +17 -0
  69. package/dist/providers.d.ts.map +1 -0
  70. package/dist/providers.js +127 -0
  71. package/dist/providers.js.map +1 -0
  72. package/dist/types.d.ts +58 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/dist/types.js +2 -0
  75. package/dist/types.js.map +1 -0
  76. package/jsr.json +24 -0
  77. package/package.json +57 -4
@@ -0,0 +1,672 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
3
+ import { join, resolve, relative } from "node:path";
4
+ import { resolvePathUnderRepoRoot } from "../path-containment.js";
5
+ import { emptyDiscovery } from "./types.js";
6
+ const IGNORE_DIRS = new Set([
7
+ "node_modules",
8
+ ".git",
9
+ "dist",
10
+ "build",
11
+ ".next",
12
+ "coverage",
13
+ ".turbo",
14
+ ".cache",
15
+ ]);
16
+ const MAX_SCAN_FILES = 800;
17
+ const MUTATING_SQL_RE = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|REPLACE|ATTACH|VACUUM|REINDEX)\b/i;
18
+ const READ_ONLY_PRAGMA_RE = /^\s*PRAGMA\s+(?:table_info|database_list|index_info|index_list|index_xinfo|compile_options|encoding|freelist_count|integrity_check|quick_check|foreign_key_list|foreign_key_check|page_count|page_size|schema_version)\s*(?:\(|;|$)/i;
19
+ const WRITE_PRAGMA_RE = /^\s*PRAGMA\s+/i;
20
+ function walkDataFiles(repoRoot) {
21
+ const inv = {
22
+ sqlite: [],
23
+ sql: [],
24
+ prisma: [],
25
+ drizzle: [],
26
+ csv: [],
27
+ jsonl: [],
28
+ };
29
+ let scanned = 0;
30
+ const visit = (dir) => {
31
+ if (scanned >= MAX_SCAN_FILES || !existsSync(dir))
32
+ return;
33
+ let entries;
34
+ try {
35
+ entries = readdirSync(dir);
36
+ }
37
+ catch {
38
+ return;
39
+ }
40
+ for (const name of entries) {
41
+ if (scanned >= MAX_SCAN_FILES)
42
+ break;
43
+ const abs = join(dir, name);
44
+ if (name.startsWith(".") && name !== ".") {
45
+ const stDot = safeStat(abs);
46
+ if (stDot?.isDirectory())
47
+ continue;
48
+ }
49
+ const st = safeStat(abs);
50
+ if (!st)
51
+ continue;
52
+ if (st.isDirectory()) {
53
+ if (IGNORE_DIRS.has(name))
54
+ continue;
55
+ visit(abs);
56
+ continue;
57
+ }
58
+ if (!st.isFile())
59
+ continue;
60
+ scanned += 1;
61
+ const rel = toRepoRel(repoRoot, abs);
62
+ const lower = name.toLowerCase();
63
+ if (/\.(db|sqlite|sqlite3)$/.test(lower)) {
64
+ inv.sqlite.push(rel);
65
+ }
66
+ else if (lower.endsWith(".sql")) {
67
+ inv.sql.push(rel);
68
+ }
69
+ else if (lower === "schema.prisma" || lower.endsWith(".prisma")) {
70
+ inv.prisma.push(rel);
71
+ }
72
+ else if (lower.endsWith(".csv")) {
73
+ inv.csv.push(rel);
74
+ }
75
+ else if (lower.endsWith(".jsonl") || lower.endsWith(".ndjson")) {
76
+ inv.jsonl.push(rel);
77
+ }
78
+ else if (/schema.*\.(ts|mts|cts|js|mjs|cjs)$/.test(lower)) {
79
+ if (looksLikeDrizzleSchema(abs))
80
+ inv.drizzle.push(rel);
81
+ }
82
+ }
83
+ };
84
+ visit(repoRoot);
85
+ return inv;
86
+ }
87
+ function safeStat(p) {
88
+ try {
89
+ return statSync(p);
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ function toRepoRel(repoRoot, abs) {
96
+ const rel = relative(repoRoot, abs);
97
+ return rel.split("\\").join("/");
98
+ }
99
+ function looksLikeDrizzleSchema(absPath) {
100
+ try {
101
+ const head = readFileSync(absPath, "utf8").slice(0, 12_000);
102
+ return (/from\s+['"]drizzle-orm/.test(head) &&
103
+ /\b(sqliteTable|pgTable|mysqlTable)\s*\(/.test(head));
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ function resolveRepoPath(repoRoot, input) {
110
+ try {
111
+ return resolvePathUnderRepoRoot(repoRoot, input);
112
+ }
113
+ catch (err) {
114
+ const msg = err instanceof Error ? err.message : String(err);
115
+ if (msg.includes("escapes repo root"))
116
+ throw err;
117
+ throw new Error(`File not found: ${input}`);
118
+ }
119
+ }
120
+ function stripSqlComments(sql) {
121
+ let out = "";
122
+ let i = 0;
123
+ while (i < sql.length) {
124
+ const ch = sql[i];
125
+ const next = sql[i + 1];
126
+ if (ch === "-" && next === "-") {
127
+ while (i < sql.length && sql[i] !== "\n")
128
+ i += 1;
129
+ continue;
130
+ }
131
+ if (ch === "/" && next === "*") {
132
+ i += 2;
133
+ while (i < sql.length - 1 && !(sql[i] === "*" && sql[i + 1] === "/"))
134
+ i += 1;
135
+ i += 2;
136
+ continue;
137
+ }
138
+ out += ch;
139
+ i += 1;
140
+ }
141
+ return out;
142
+ }
143
+ function isWritePragmaSql(sql) {
144
+ const trimmed = stripSqlComments(sql).trim();
145
+ if (!WRITE_PRAGMA_RE.test(trimmed))
146
+ return false;
147
+ if (READ_ONLY_PRAGMA_RE.test(trimmed))
148
+ return false;
149
+ return true;
150
+ }
151
+ export function isMutatingSql(sql) {
152
+ const trimmed = stripSqlComments(sql).trim();
153
+ if (!trimmed)
154
+ return false;
155
+ const withoutTrailing = trimmed.replace(/;+\s*$/, "");
156
+ if (withoutTrailing.includes(";"))
157
+ return true;
158
+ if (isWritePragmaSql(withoutTrailing))
159
+ return true;
160
+ return MUTATING_SQL_RE.test(withoutTrailing);
161
+ }
162
+ let sqliteModuleLoaded;
163
+ function loadSqliteModule() {
164
+ if (sqliteModuleLoaded !== undefined)
165
+ return sqliteModuleLoaded;
166
+ try {
167
+ const req = createRequire(import.meta.url);
168
+ const mod = req("node:sqlite");
169
+ if (mod?.DatabaseSync) {
170
+ sqliteModuleLoaded = mod;
171
+ return sqliteModuleLoaded;
172
+ }
173
+ }
174
+ catch {
175
+ // node:sqlite unavailable (Node < 22 or build without sqlite)
176
+ }
177
+ sqliteModuleLoaded = null;
178
+ return null;
179
+ }
180
+ function openSqlite(absPath, allowWrite) {
181
+ const mod = loadSqliteModule();
182
+ if (!mod) {
183
+ return {
184
+ ok: false,
185
+ message: "node:sqlite is not available (requires Node 22+). Use schema from .sql or schema.prisma, or upgrade Node.",
186
+ };
187
+ }
188
+ try {
189
+ const db = new mod.DatabaseSync(absPath, { readOnly: !allowWrite });
190
+ if (!allowWrite) {
191
+ db.prepare("PRAGMA query_only=ON").get();
192
+ }
193
+ return { ok: true, db };
194
+ }
195
+ catch (err) {
196
+ const msg = err instanceof Error ? err.message : String(err);
197
+ return {
198
+ ok: false,
199
+ message: `Failed to open SQLite database at ${absPath}: ${msg}`,
200
+ };
201
+ }
202
+ }
203
+ function openSqliteReadOnly(absPath) {
204
+ return openSqlite(absPath, false);
205
+ }
206
+ function sqliteSchema(db) {
207
+ const tables = db
208
+ .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`)
209
+ .all();
210
+ const out = {};
211
+ for (const { name } of tables) {
212
+ const cols = db.prepare(`PRAGMA table_info(${quoteSqlIdent(name)})`).all();
213
+ out[name] = { columns: cols };
214
+ }
215
+ return out;
216
+ }
217
+ function quoteSqlIdent(name) {
218
+ return `"${name.replace(/"/g, '""')}"`;
219
+ }
220
+ function parseCreateTablesFromSql(text) {
221
+ const out = {};
222
+ const re = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|'([^']+)'|`([^`]+)`|(\w+))\s*\(([\s\S]*?)\)\s*;/gi;
223
+ let m;
224
+ while ((m = re.exec(text)) !== null) {
225
+ const table = m[1] ?? m[2] ?? m[3] ?? m[4];
226
+ if (!table)
227
+ continue;
228
+ const body = m[5];
229
+ const columns = body
230
+ .split(",")
231
+ .map((line) => line.trim())
232
+ .filter((line) => line && !/^PRIMARY\s+KEY/i.test(line) && !/^CONSTRAINT\s/i.test(line) && !/^UNIQUE\s/i.test(line) && !/^FOREIGN\s+KEY/i.test(line) && !/^CHECK\s/i.test(line))
233
+ .map((line) => line.split(/\s+/)[0]?.replace(/^["'`]|["'`]$/g, "") ?? "")
234
+ .filter(Boolean);
235
+ out[table] = { columns };
236
+ }
237
+ return out;
238
+ }
239
+ function parsePrismaModels(text) {
240
+ const out = {};
241
+ const blocks = text.split(/\nmodel\s+/);
242
+ for (let i = 1; i < blocks.length; i++) {
243
+ const block = blocks[i];
244
+ const nameMatch = /^(\w+)\s*\{/.exec(block);
245
+ if (!nameMatch)
246
+ continue;
247
+ const modelName = nameMatch[1];
248
+ const body = block.slice(nameMatch[0].length);
249
+ const end = body.indexOf("}");
250
+ const inner = end >= 0 ? body.slice(0, end) : body;
251
+ const fields = [];
252
+ for (const line of inner.split("\n")) {
253
+ const trimmed = line.trim();
254
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("@@"))
255
+ continue;
256
+ const field = /^(\w+)\s+/.exec(trimmed);
257
+ if (field)
258
+ fields.push(field[1]);
259
+ }
260
+ out[modelName] = { fields };
261
+ }
262
+ return out;
263
+ }
264
+ function parseDrizzleTables(text) {
265
+ const out = {};
266
+ const tableRe = /\b(sqliteTable|pgTable|mysqlTable)\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{([\s\S]*?)\}\s*(?:,\s*\(|\))/g;
267
+ let m;
268
+ while ((m = tableRe.exec(text)) !== null) {
269
+ const tableName = m[2];
270
+ const body = m[3];
271
+ const columns = [];
272
+ const colRe = /^\s*(\w+)\s*:/gm;
273
+ let cm;
274
+ while ((cm = colRe.exec(body)) !== null) {
275
+ columns.push(cm[1]);
276
+ }
277
+ out[tableName] = { columns };
278
+ }
279
+ return out;
280
+ }
281
+ function classifyPath(inv, relPath) {
282
+ if (inv.sqlite.includes(relPath))
283
+ return "sqlite";
284
+ if (inv.sql.includes(relPath))
285
+ return "sql";
286
+ if (inv.prisma.includes(relPath))
287
+ return "prisma";
288
+ if (inv.drizzle.includes(relPath))
289
+ return "drizzle";
290
+ if (inv.csv.includes(relPath))
291
+ return "csv";
292
+ if (inv.jsonl.includes(relPath))
293
+ return "jsonl";
294
+ const lower = relPath.toLowerCase();
295
+ if (/\.(db|sqlite|sqlite3)$/.test(lower))
296
+ return "sqlite";
297
+ if (lower.endsWith(".sql"))
298
+ return "sql";
299
+ if (lower.endsWith(".prisma"))
300
+ return "prisma";
301
+ if (lower.endsWith(".csv"))
302
+ return "csv";
303
+ if (lower.endsWith(".jsonl") || lower.endsWith(".ndjson"))
304
+ return "jsonl";
305
+ return null;
306
+ }
307
+ function schemaForFile(repoRoot, relPath, inv) {
308
+ const abs = resolveRepoPath(repoRoot, relPath);
309
+ const kind = classifyPath(inv, relPath.replace(/\\/g, "/"));
310
+ const text = readFileSync(abs, "utf8");
311
+ switch (kind) {
312
+ case "sqlite": {
313
+ const opened = openSqliteReadOnly(abs);
314
+ if (!opened.ok) {
315
+ return {
316
+ path: relPath,
317
+ kind: "sqlite",
318
+ error: opened.message,
319
+ hint: "Schema may still be inferable from companion .sql or schema.prisma files.",
320
+ };
321
+ }
322
+ try {
323
+ return { path: relPath, kind: "sqlite", tables: sqliteSchema(opened.db) };
324
+ }
325
+ finally {
326
+ opened.db.close();
327
+ }
328
+ }
329
+ case "sql":
330
+ return { path: relPath, kind: "sql", tables: parseCreateTablesFromSql(text) };
331
+ case "prisma":
332
+ return { path: relPath, kind: "prisma", models: parsePrismaModels(text) };
333
+ case "drizzle":
334
+ return { path: relPath, kind: "drizzle", tables: parseDrizzleTables(text) };
335
+ default:
336
+ return { path: relPath, error: "Unsupported schema path for db_schema" };
337
+ }
338
+ }
339
+ function inventorySummary(inv) {
340
+ return {
341
+ sqlite: inv.sqlite,
342
+ sql: inv.sql,
343
+ prisma: inv.prisma,
344
+ drizzle: inv.drizzle,
345
+ csv: inv.csv,
346
+ jsonl: inv.jsonl,
347
+ };
348
+ }
349
+ function parseCsvLine(line) {
350
+ const fields = [];
351
+ let cur = "";
352
+ let inQuotes = false;
353
+ for (let i = 0; i < line.length; i++) {
354
+ const ch = line[i];
355
+ if (inQuotes) {
356
+ if (ch === '"') {
357
+ if (line[i + 1] === '"') {
358
+ cur += '"';
359
+ i += 1;
360
+ }
361
+ else {
362
+ inQuotes = false;
363
+ }
364
+ }
365
+ else {
366
+ cur += ch;
367
+ }
368
+ }
369
+ else if (ch === '"') {
370
+ inQuotes = true;
371
+ }
372
+ else if (ch === ",") {
373
+ fields.push(cur);
374
+ cur = "";
375
+ }
376
+ else {
377
+ cur += ch;
378
+ }
379
+ }
380
+ fields.push(cur);
381
+ return fields;
382
+ }
383
+ function parseCsv(text) {
384
+ const lines = text.split(/\r?\n/).filter((l) => l.length > 0);
385
+ if (lines.length === 0)
386
+ return { headers: [], rows: [] };
387
+ const headers = parseCsvLine(lines[0]);
388
+ const rows = lines.slice(1).map(parseCsvLine);
389
+ return { headers, rows };
390
+ }
391
+ function sampleJsonl(absPath, limit) {
392
+ const text = readFileSync(absPath, "utf8");
393
+ const out = [];
394
+ for (const line of text.split(/\r?\n/)) {
395
+ if (!line.trim())
396
+ continue;
397
+ if (out.length >= limit)
398
+ break;
399
+ out.push(JSON.parse(line));
400
+ }
401
+ return out;
402
+ }
403
+ function sampleCsv(absPath, limit) {
404
+ const { headers, rows } = parseCsv(readFileSync(absPath, "utf8"));
405
+ return { headers, rows: rows.slice(0, limit) };
406
+ }
407
+ function sampleSqliteTable(absPath, table, limit) {
408
+ const opened = openSqliteReadOnly(absPath);
409
+ if (!opened.ok) {
410
+ return { error: opened.message };
411
+ }
412
+ try {
413
+ const safeTable = table.replace(/"/g, '""');
414
+ const rows = opened.db
415
+ .prepare(`SELECT * FROM "${safeTable}" LIMIT ?`)
416
+ .all(limit);
417
+ return { table, rows };
418
+ }
419
+ finally {
420
+ opened.db.close();
421
+ }
422
+ }
423
+ function filterCsv(absPath, column, value, mode, limit) {
424
+ const { headers, rows } = parseCsv(readFileSync(absPath, "utf8"));
425
+ const idx = headers.indexOf(column);
426
+ if (idx < 0) {
427
+ throw new Error(`Column not found: ${column}. Available: ${headers.join(", ")}`);
428
+ }
429
+ const filtered = [];
430
+ for (const row of rows) {
431
+ const cell = row[idx] ?? "";
432
+ const match = mode === "exact" ? cell === value : cell.toLowerCase().includes(value.toLowerCase());
433
+ if (match) {
434
+ filtered.push(row);
435
+ if (filtered.length >= limit)
436
+ break;
437
+ }
438
+ }
439
+ return { headers, rows: filtered };
440
+ }
441
+ function asString(v, field) {
442
+ if (typeof v !== "string" || !v.trim()) {
443
+ throw new Error(`Missing or invalid string argument: ${field}`);
444
+ }
445
+ return v;
446
+ }
447
+ function asOptionalNumber(v, fallback) {
448
+ if (v === undefined || v === null)
449
+ return fallback;
450
+ const n = typeof v === "number" ? v : Number(v);
451
+ if (!Number.isFinite(n) || n < 0) {
452
+ throw new Error("limit must be a non-negative number");
453
+ }
454
+ return Math.floor(n);
455
+ }
456
+ const READ_ONLY_ANNOTATIONS = {
457
+ readOnlyHint: true,
458
+ destructiveHint: false,
459
+ idempotentHint: true,
460
+ openWorldHint: false,
461
+ };
462
+ const MUTATING_ANNOTATIONS = {
463
+ readOnlyHint: false,
464
+ destructiveHint: true,
465
+ idempotentHint: false,
466
+ openWorldHint: false,
467
+ };
468
+ function buildTools(inv, allowWrite) {
469
+ const hasSchema = inv.sqlite.length > 0 ||
470
+ inv.sql.length > 0 ||
471
+ inv.prisma.length > 0 ||
472
+ inv.drizzle.length > 0;
473
+ const hasSqlite = inv.sqlite.length > 0;
474
+ const hasTabular = hasSqlite || inv.csv.length > 0 || inv.jsonl.length > 0;
475
+ const hasCsv = inv.csv.length > 0;
476
+ const tools = [];
477
+ const summary = JSON.stringify(inventorySummary(inv));
478
+ if (hasSchema) {
479
+ tools.push({
480
+ name: "db_schema",
481
+ description: `Inspect database schema (SQLite, SQL, Prisma, Drizzle). Discovered: ${summary}`,
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ path: {
486
+ type: "string",
487
+ description: "Repo-relative file path (.db, .sql, schema.prisma, drizzle schema). Omit to list discovered sources.",
488
+ },
489
+ },
490
+ additionalProperties: false,
491
+ },
492
+ mutates: false,
493
+ annotations: READ_ONLY_ANNOTATIONS,
494
+ });
495
+ }
496
+ if (hasSqlite) {
497
+ tools.push({
498
+ name: "db_query_readonly",
499
+ description: `Run SQL against a repo SQLite file${allowWrite ? " (writes allowed)" : " (read-only)"}. Discovered DBs: ${inv.sqlite.join(", ")}`,
500
+ inputSchema: {
501
+ type: "object",
502
+ properties: {
503
+ path: { type: "string", description: "Repo-relative .db/.sqlite path" },
504
+ sql: { type: "string", description: "SQL query (SELECT / PRAGMA / WITH …)" },
505
+ },
506
+ required: ["path", "sql"],
507
+ additionalProperties: false,
508
+ },
509
+ mutates: allowWrite,
510
+ annotations: allowWrite ? MUTATING_ANNOTATIONS : READ_ONLY_ANNOTATIONS,
511
+ });
512
+ }
513
+ if (hasTabular) {
514
+ tools.push({
515
+ name: "data_sample",
516
+ description: `Sample rows from CSV, JSONL, or a SQLite table. Discovered: csv=${inv.csv.length}, jsonl=${inv.jsonl.length}, sqlite=${inv.sqlite.length}`,
517
+ inputSchema: {
518
+ type: "object",
519
+ properties: {
520
+ path: { type: "string", description: "Repo-relative data file path" },
521
+ limit: { type: "number", description: "Max rows/lines (default 10)" },
522
+ table: { type: "string", description: "SQLite table name (required for .db files)" },
523
+ },
524
+ required: ["path"],
525
+ additionalProperties: false,
526
+ },
527
+ mutates: false,
528
+ annotations: READ_ONLY_ANNOTATIONS,
529
+ });
530
+ }
531
+ if (hasCsv) {
532
+ tools.push({
533
+ name: "csv_filter",
534
+ description: `Filter CSV rows by column value. Discovered CSVs: ${inv.csv.join(", ")}`,
535
+ inputSchema: {
536
+ type: "object",
537
+ properties: {
538
+ path: { type: "string", description: "Repo-relative .csv path" },
539
+ column: { type: "string", description: "Header column name" },
540
+ value: { type: "string", description: "Value to match" },
541
+ mode: {
542
+ type: "string",
543
+ description: "exact (default) or contains",
544
+ },
545
+ limit: { type: "number", description: "Max matching rows (default 50)" },
546
+ },
547
+ required: ["path", "column", "value"],
548
+ additionalProperties: false,
549
+ },
550
+ mutates: false,
551
+ annotations: READ_ONLY_ANNOTATIONS,
552
+ });
553
+ }
554
+ return tools;
555
+ }
556
+ function makeHandlers(repoRoot, inv, allowWrite) {
557
+ const handlers = {};
558
+ if (inv.sqlite.length > 0 ||
559
+ inv.sql.length > 0 ||
560
+ inv.prisma.length > 0 ||
561
+ inv.drizzle.length > 0) {
562
+ handlers.db_schema = async (args) => {
563
+ const pathArg = args.path;
564
+ if (pathArg === undefined || pathArg === null || pathArg === "") {
565
+ return { discovered: inventorySummary(inv) };
566
+ }
567
+ const rel = asString(pathArg, "path");
568
+ return schemaForFile(repoRoot, rel, inv);
569
+ };
570
+ }
571
+ if (inv.sqlite.length > 0) {
572
+ handlers.db_query_readonly = async (args) => {
573
+ const rel = asString(args.path, "path");
574
+ const sql = asString(args.sql, "sql");
575
+ if (isMutatingSql(sql) && !allowWrite) {
576
+ return {
577
+ error: "Mutating SQL blocked (INSERT/UPDATE/DELETE/DROP/ALTER/CREATE/ATTACH/REPLACE/VACUUM/REINDEX or write PRAGMA). Pass --allow-write to permit writes.",
578
+ };
579
+ }
580
+ const abs = resolveRepoPath(repoRoot, rel);
581
+ const opened = openSqlite(abs, allowWrite);
582
+ if (!opened.ok) {
583
+ return { error: opened.message };
584
+ }
585
+ try {
586
+ const stmt = opened.db.prepare(sql);
587
+ const trimmed = stripSqlComments(sql).trim().toUpperCase();
588
+ const readShape = trimmed.startsWith("SELECT") ||
589
+ trimmed.startsWith("WITH") ||
590
+ trimmed.startsWith("EXPLAIN") ||
591
+ (trimmed.startsWith("PRAGMA") && !isWritePragmaSql(sql));
592
+ if (readShape) {
593
+ const rows = stmt.all();
594
+ return { path: rel, rows };
595
+ }
596
+ if (allowWrite) {
597
+ const info = stmt.get();
598
+ return { path: rel, result: info, wrote: true };
599
+ }
600
+ return { error: "Only read-only SQL allowed without --allow-write." };
601
+ }
602
+ catch (err) {
603
+ const msg = err instanceof Error ? err.message : String(err);
604
+ return { error: msg };
605
+ }
606
+ finally {
607
+ opened.db.close();
608
+ }
609
+ };
610
+ }
611
+ if (inv.sqlite.length > 0 || inv.csv.length > 0 || inv.jsonl.length > 0) {
612
+ handlers.data_sample = async (args) => {
613
+ const rel = asString(args.path, "path");
614
+ const limit = asOptionalNumber(args.limit, 10);
615
+ const abs = resolveRepoPath(repoRoot, rel);
616
+ const norm = rel.replace(/\\/g, "/");
617
+ const kind = classifyPath(inv, norm);
618
+ if (kind === "csv" || norm.toLowerCase().endsWith(".csv")) {
619
+ return { path: rel, ...sampleCsv(abs, limit) };
620
+ }
621
+ if (kind === "jsonl" || norm.toLowerCase().endsWith(".jsonl") || norm.toLowerCase().endsWith(".ndjson")) {
622
+ return { path: rel, lines: sampleJsonl(abs, limit) };
623
+ }
624
+ if (kind === "sqlite" || /\.(db|sqlite|sqlite3)$/i.test(norm)) {
625
+ const table = asString(args.table, "table");
626
+ const sampled = sampleSqliteTable(abs, table, limit);
627
+ return { path: rel, ...sampled };
628
+ }
629
+ return { error: `Unsupported data_sample path: ${rel}` };
630
+ };
631
+ }
632
+ if (inv.csv.length > 0) {
633
+ handlers.csv_filter = async (args) => {
634
+ const rel = asString(args.path, "path");
635
+ const column = asString(args.column, "column");
636
+ const value = asString(args.value, "value");
637
+ const modeRaw = args.mode;
638
+ let mode = "exact";
639
+ if (modeRaw === "contains") {
640
+ mode = "contains";
641
+ }
642
+ else if (modeRaw !== undefined && modeRaw !== "exact") {
643
+ throw new Error('mode must be "exact" or "contains"');
644
+ }
645
+ const limit = asOptionalNumber(args.limit, 50);
646
+ const abs = resolveRepoPath(repoRoot, rel);
647
+ return { path: rel, ...filterCsv(abs, column, value, mode, limit) };
648
+ };
649
+ }
650
+ return handlers;
651
+ }
652
+ export function discoverDataSources(repoRoot, opts) {
653
+ const root = resolve(repoRoot);
654
+ if (!existsSync(root)) {
655
+ return emptyDiscovery();
656
+ }
657
+ const inv = walkDataFiles(root);
658
+ const total = inv.sqlite.length +
659
+ inv.sql.length +
660
+ inv.prisma.length +
661
+ inv.drizzle.length +
662
+ inv.csv.length +
663
+ inv.jsonl.length;
664
+ if (total === 0) {
665
+ return emptyDiscovery();
666
+ }
667
+ const allowWrite = Boolean(opts?.allowWrite);
668
+ const tools = buildTools(inv, allowWrite);
669
+ const handlers = makeHandlers(root, inv, allowWrite);
670
+ return { tools, handlers };
671
+ }
672
+ //# sourceMappingURL=data-sources.js.map