@creeperhost/modlens-mcp 1.6.0 → 1.6.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 (89) hide show
  1. package/README.md +950 -938
  2. package/dist/cli.js +267 -267
  3. package/dist/generated/sqlite/internal/class.d.ts.map +1 -1
  4. package/dist/generated/sqlite/internal/class.js +4 -3
  5. package/dist/generated/sqlite/internal/class.js.map +1 -1
  6. package/dist/launcher.js +4 -1
  7. package/dist/launcher.js.map +1 -1
  8. package/dist/repositories/embeddings-sqlite.js +21 -21
  9. package/dist/repositories/embeddings.js +28 -28
  10. package/dist/repositories/mod.js +2 -2
  11. package/dist/search-adapter.js +51 -51
  12. package/dist/tools/compat-check.js +9 -9
  13. package/dist/tools/embed-registry.js +9 -9
  14. package/dist/tools/graphify.js +14 -14
  15. package/dist/tools/mc-fts.js +2 -2
  16. package/dist/tools/mixin-scan.d.ts +1 -1
  17. package/dist/tools/mixin-scan.js +16 -16
  18. package/dist/tools/version-diff.js +3 -3
  19. package/package.json +70 -62
  20. package/prisma/backends/schema.sqlite.prisma +215 -215
  21. package/prisma/schema.prisma +303 -303
  22. package/scripts/backup.mjs +151 -151
  23. package/scripts/check-atm10.mjs +27 -27
  24. package/scripts/check-sync.mjs +13 -13
  25. package/scripts/create-fts-index.mjs +50 -50
  26. package/scripts/enable-pgvector.mjs +90 -90
  27. package/scripts/enable-sqlite-vec.mjs +158 -158
  28. package/scripts/migrate-backend.mjs +188 -188
  29. package/scripts/migrate-embed-provenance.mjs +80 -80
  30. package/scripts/migrate-metadata-source.mjs +62 -62
  31. package/scripts/migrate-mod-source-files.mjs +67 -67
  32. package/scripts/post-update.mjs +77 -77
  33. package/scripts/verify-atm10.mjs +63 -63
  34. package/dist/access-flags.test.d.ts +0 -2
  35. package/dist/access-flags.test.d.ts.map +0 -1
  36. package/dist/access-flags.test.js +0 -189
  37. package/dist/access-flags.test.js.map +0 -1
  38. package/dist/fetch-utils.test.d.ts +0 -2
  39. package/dist/fetch-utils.test.d.ts.map +0 -1
  40. package/dist/fetch-utils.test.js +0 -133
  41. package/dist/fetch-utils.test.js.map +0 -1
  42. package/dist/mappings.test.d.ts +0 -2
  43. package/dist/mappings.test.d.ts.map +0 -1
  44. package/dist/mappings.test.js +0 -161
  45. package/dist/mappings.test.js.map +0 -1
  46. package/dist/processor.test.d.ts +0 -2
  47. package/dist/processor.test.d.ts.map +0 -1
  48. package/dist/processor.test.js +0 -629
  49. package/dist/processor.test.js.map +0 -1
  50. package/dist/search.test.d.ts +0 -2
  51. package/dist/search.test.d.ts.map +0 -1
  52. package/dist/search.test.js +0 -71
  53. package/dist/search.test.js.map +0 -1
  54. package/dist/security.test.d.ts +0 -2
  55. package/dist/security.test.d.ts.map +0 -1
  56. package/dist/security.test.js +0 -687
  57. package/dist/security.test.js.map +0 -1
  58. package/dist/tools/compat-check.test.d.ts +0 -2
  59. package/dist/tools/compat-check.test.d.ts.map +0 -1
  60. package/dist/tools/compat-check.test.js +0 -179
  61. package/dist/tools/compat-check.test.js.map +0 -1
  62. package/dist/tools/diagnostics.test.d.ts +0 -2
  63. package/dist/tools/diagnostics.test.d.ts.map +0 -1
  64. package/dist/tools/diagnostics.test.js +0 -168
  65. package/dist/tools/diagnostics.test.js.map +0 -1
  66. package/dist/tools/graphify.test.d.ts +0 -2
  67. package/dist/tools/graphify.test.d.ts.map +0 -1
  68. package/dist/tools/graphify.test.js +0 -39
  69. package/dist/tools/graphify.test.js.map +0 -1
  70. package/dist/tools/ingest.test.d.ts +0 -2
  71. package/dist/tools/ingest.test.d.ts.map +0 -1
  72. package/dist/tools/ingest.test.js +0 -340
  73. package/dist/tools/ingest.test.js.map +0 -1
  74. package/dist/tools/mod-tags.test.d.ts +0 -2
  75. package/dist/tools/mod-tags.test.d.ts.map +0 -1
  76. package/dist/tools/mod-tags.test.js +0 -75
  77. package/dist/tools/mod-tags.test.js.map +0 -1
  78. package/dist/tools/packtools.test.d.ts +0 -2
  79. package/dist/tools/packtools.test.d.ts.map +0 -1
  80. package/dist/tools/packtools.test.js +0 -99
  81. package/dist/tools/packtools.test.js.map +0 -1
  82. package/dist/tools/version-diff.test.d.ts +0 -2
  83. package/dist/tools/version-diff.test.d.ts.map +0 -1
  84. package/dist/tools/version-diff.test.js +0 -102
  85. package/dist/tools/version-diff.test.js.map +0 -1
  86. package/dist/validate.test.d.ts +0 -2
  87. package/dist/validate.test.d.ts.map +0 -1
  88. package/dist/validate.test.js +0 -73
  89. package/dist/validate.test.js.map +0 -1
package/dist/launcher.js CHANGED
@@ -41,7 +41,10 @@ for (const ep of [ENV_FILE, join(PKG_ROOT, ".env")]) {
41
41
  }
42
42
  const args = process.argv.slice(2);
43
43
  const wantSetup = args.includes("--setup");
44
- const isFirstRun = IS_INSTALLED && !existsSync(ENV_FILE);
44
+ // Bootstrap embedded SQLite only when nothing else is configured. A
45
+ // pre-supplied DATABASE_URL (e.g. Postgres via a container env var) opts out, so
46
+ // `docker run -e DATABASE_URL=postgresql://…` works without a stray SQLite db.
47
+ const isFirstRun = IS_INSTALLED && !existsSync(ENV_FILE) && !process.env.DATABASE_URL;
45
48
  // ── Explicit setup wizard (--setup) ─────────────────────────────────────────
46
49
  // Opt-in interactive configuration (Postgres, semantic search, MCP client, …).
47
50
  if (wantSetup) {
@@ -1 +1 @@
1
- {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"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"}
@@ -39,8 +39,8 @@ export async function upsertDocEmbedding(id, vec) {
39
39
  export async function searchDocsByVector(vec, limit = 5) {
40
40
  const db = await getVecDb();
41
41
  try {
42
- const rows = db.prepare(`SELECT rowid AS id, distance FROM vec_doc_entries
43
- WHERE embedding MATCH ? AND k = ?
42
+ const rows = db.prepare(`SELECT rowid AS id, distance FROM vec_doc_entries
43
+ WHERE embedding MATCH ? AND k = ?
44
44
  ORDER BY distance`).all(float32Blob(vec), limit);
45
45
  return rows.map((r) => ({ id: r.id, similarity: 1 - r.distance }));
46
46
  }
@@ -61,8 +61,8 @@ export async function upsertPrimerEmbedding(id, vec) {
61
61
  export async function searchPrimersByVector(vec, limit = 5) {
62
62
  const db = await getVecDb();
63
63
  try {
64
- const rows = db.prepare(`SELECT rowid AS id, distance FROM vec_primers
65
- WHERE embedding MATCH ? AND k = ?
64
+ const rows = db.prepare(`SELECT rowid AS id, distance FROM vec_primers
65
+ WHERE embedding MATCH ? AND k = ?
66
66
  ORDER BY distance`).all(float32Blob(vec), limit);
67
67
  return rows.map((r) => ({ id: r.id, similarity: 1 - r.distance }));
68
68
  }
@@ -84,13 +84,13 @@ export async function searchSourceByVector(vec, mcVersionId, limit = 10, provena
84
84
  const db = await getVecDb();
85
85
  try {
86
86
  const sql = provenance
87
- ? `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mc_source v
88
- JOIN mc_source_files s ON s.id = v.rowid
89
- WHERE v.embedding MATCH ? AND v.k = ? AND s.mc_version_id = ? AND s.embed_source = ?
87
+ ? `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mc_source v
88
+ JOIN mc_source_files s ON s.id = v.rowid
89
+ WHERE v.embedding MATCH ? AND v.k = ? AND s.mc_version_id = ? AND s.embed_source = ?
90
90
  ORDER BY v.distance`
91
- : `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mc_source v
92
- JOIN mc_source_files s ON s.id = v.rowid
93
- WHERE v.embedding MATCH ? AND v.k = ? AND s.mc_version_id = ?
91
+ : `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mc_source v
92
+ JOIN mc_source_files s ON s.id = v.rowid
93
+ WHERE v.embedding MATCH ? AND v.k = ? AND s.mc_version_id = ?
94
94
  ORDER BY v.distance`;
95
95
  const args = provenance
96
96
  ? [float32Blob(vec), limit, mcVersionId, provenance]
@@ -116,13 +116,13 @@ export async function searchModSourceByVector(vec, modId, limit = 10, provenance
116
116
  const db = await getVecDb();
117
117
  try {
118
118
  const sql = provenance
119
- ? `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mod_source v
120
- JOIN mod_source_files s ON s.id = v.rowid
121
- WHERE v.embedding MATCH ? AND v.k = ? AND s.mod_id = ? AND s.embed_source = ?
119
+ ? `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mod_source v
120
+ JOIN mod_source_files s ON s.id = v.rowid
121
+ WHERE v.embedding MATCH ? AND v.k = ? AND s.mod_id = ? AND s.embed_source = ?
122
122
  ORDER BY v.distance`
123
- : `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mod_source v
124
- JOIN mod_source_files s ON s.id = v.rowid
125
- WHERE v.embedding MATCH ? AND v.k = ? AND s.mod_id = ?
123
+ : `SELECT v.rowid AS id, s.class_name, s.embed_source, v.distance FROM vec_mod_source v
124
+ JOIN mod_source_files s ON s.id = v.rowid
125
+ WHERE v.embedding MATCH ? AND v.k = ? AND s.mod_id = ?
126
126
  ORDER BY v.distance`;
127
127
  const args = provenance
128
128
  ? [float32Blob(vec), limit, modId, provenance]
@@ -142,7 +142,7 @@ export async function findSourceIdsByClassNames(classNames, mcVersionId, require
142
142
  const placeholders = classNames.map(() => "?").join(",");
143
143
  const embFilter = requireEmbedding ? " AND embedding IS NOT NULL" : "";
144
144
  try {
145
- const rows = db.prepare(`SELECT id, class_name FROM mc_source_files
145
+ const rows = db.prepare(`SELECT id, class_name FROM mc_source_files
146
146
  WHERE mc_version_id = ? AND class_name IN (${placeholders})${embFilter}`).all(mcVersionId, ...classNames);
147
147
  return new Map(rows.map((r) => [r.class_name, r.id]));
148
148
  }
@@ -157,7 +157,7 @@ export async function findModSourceIdsByClassNames(classNames, modId, requireEmb
157
157
  const placeholders = classNames.map(() => "?").join(",");
158
158
  const embFilter = requireEmbedding ? " AND embedding IS NOT NULL" : "";
159
159
  try {
160
- const rows = db.prepare(`SELECT id, class_name FROM mod_source_files
160
+ const rows = db.prepare(`SELECT id, class_name FROM mod_source_files
161
161
  WHERE mod_id = ? AND class_name IN (${placeholders})${embFilter}`).all(modId, ...classNames);
162
162
  return new Map(rows.map((r) => [r.class_name, r.id]));
163
163
  }
@@ -172,7 +172,7 @@ export async function getEmbedSources(table, ids) {
172
172
  const db = await getVecDb();
173
173
  const placeholders = ids.map(() => "?").join(",");
174
174
  try {
175
- const rows = db.prepare(`SELECT id, embed_source, embed_updated_at FROM ${table}
175
+ const rows = db.prepare(`SELECT id, embed_source, embed_updated_at FROM ${table}
176
176
  WHERE id IN (${placeholders}) AND embedding IS NOT NULL`).all(...ids);
177
177
  return new Map(rows.map((r) => [r.id, {
178
178
  source: r.embed_source,
@@ -188,8 +188,8 @@ export async function countEmbedsBySource(table, scopeColumn, scopeId) {
188
188
  const db = await getVecDb();
189
189
  const result = { local: 0, registry: 0, community: 0, unknown: 0 };
190
190
  try {
191
- const rows = db.prepare(`SELECT embed_source, COUNT(*) AS count FROM ${table}
192
- WHERE ${scopeColumn} = ? AND embedding IS NOT NULL
191
+ const rows = db.prepare(`SELECT embed_source, COUNT(*) AS count FROM ${table}
192
+ WHERE ${scopeColumn} = ? AND embedding IS NOT NULL
193
193
  GROUP BY embed_source`).all(scopeId);
194
194
  for (const r of rows) {
195
195
  if (r.embed_source === "local")
@@ -17,10 +17,10 @@ export async function upsertDocEmbedding(id, vec) {
17
17
  }
18
18
  export async function searchDocsByVector(vec, limit = 5) {
19
19
  const db = await getDb();
20
- return db.$queryRawUnsafe(`SELECT id, (1 - (embedding <=> $1::vector))::float AS similarity
21
- FROM doc_entries
22
- WHERE embedding IS NOT NULL
23
- ORDER BY embedding <=> $1::vector
20
+ return db.$queryRawUnsafe(`SELECT id, (1 - (embedding <=> $1::vector))::float AS similarity
21
+ FROM doc_entries
22
+ WHERE embedding IS NOT NULL
23
+ ORDER BY embedding <=> $1::vector
24
24
  LIMIT $2`, vecLiteral(vec), limit);
25
25
  }
26
26
  // ── primers ───────────────────────────────────────────────────────────────────
@@ -30,10 +30,10 @@ export async function upsertPrimerEmbedding(id, vec) {
30
30
  }
31
31
  export async function searchPrimersByVector(vec, limit = 5) {
32
32
  const db = await getDb();
33
- return db.$queryRawUnsafe(`SELECT id, (1 - (embedding <=> $1::vector))::float AS similarity
34
- FROM primers
35
- WHERE embedding IS NOT NULL
36
- ORDER BY embedding <=> $1::vector
33
+ return db.$queryRawUnsafe(`SELECT id, (1 - (embedding <=> $1::vector))::float AS similarity
34
+ FROM primers
35
+ WHERE embedding IS NOT NULL
36
+ ORDER BY embedding <=> $1::vector
37
37
  LIMIT $2`, vecLiteral(vec), limit);
38
38
  }
39
39
  // ── mc_source_files ───────────────────────────────────────────────────────────
@@ -44,16 +44,16 @@ export async function upsertSourceEmbedding(id, vec, source = "local") {
44
44
  export async function searchSourceByVector(vec, mcVersionId, limit = 10, provenance) {
45
45
  const db = await getDb();
46
46
  if (provenance) {
47
- return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
48
- FROM mc_source_files
49
- WHERE mc_version_id = $3 AND embedding IS NOT NULL AND embed_source = $4
50
- ORDER BY embedding <=> $1::vector
47
+ return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
48
+ FROM mc_source_files
49
+ WHERE mc_version_id = $3 AND embedding IS NOT NULL AND embed_source = $4
50
+ ORDER BY embedding <=> $1::vector
51
51
  LIMIT $2`, vecLiteral(vec), limit, mcVersionId, provenance);
52
52
  }
53
- return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
54
- FROM mc_source_files
55
- WHERE mc_version_id = $3 AND embedding IS NOT NULL
56
- ORDER BY embedding <=> $1::vector
53
+ return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
54
+ FROM mc_source_files
55
+ WHERE mc_version_id = $3 AND embedding IS NOT NULL
56
+ ORDER BY embedding <=> $1::vector
57
57
  LIMIT $2`, vecLiteral(vec), limit, mcVersionId);
58
58
  }
59
59
  /** Count rows with no embedding yet (for backfill progress). */
@@ -74,16 +74,16 @@ export async function upsertModSourceEmbedding(id, vec, source = "local") {
74
74
  export async function searchModSourceByVector(vec, modId, limit = 10, provenance) {
75
75
  const db = await getDb();
76
76
  if (provenance) {
77
- return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
78
- FROM mod_source_files
79
- WHERE mod_id = $3 AND embedding IS NOT NULL AND embed_source = $4
80
- ORDER BY embedding <=> $1::vector
77
+ return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
78
+ FROM mod_source_files
79
+ WHERE mod_id = $3 AND embedding IS NOT NULL AND embed_source = $4
80
+ ORDER BY embedding <=> $1::vector
81
81
  LIMIT $2`, vecLiteral(vec), limit, modId, provenance);
82
82
  }
83
- return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
84
- FROM mod_source_files
85
- WHERE mod_id = $3 AND embedding IS NOT NULL
86
- ORDER BY embedding <=> $1::vector
83
+ return db.$queryRawUnsafe(`SELECT id, class_name, embed_source, (1 - (embedding <=> $1::vector))::float AS similarity
84
+ FROM mod_source_files
85
+ WHERE mod_id = $3 AND embedding IS NOT NULL
86
+ ORDER BY embedding <=> $1::vector
87
87
  LIMIT $2`, vecLiteral(vec), limit, modId);
88
88
  }
89
89
  // ── Class-name → ID lookups (for diff semantic enrichment) ───────────────────
@@ -92,7 +92,7 @@ export async function findSourceIdsByClassNames(classNames, mcVersionId, require
92
92
  return new Map();
93
93
  const db = await getDb();
94
94
  const embFilter = requireEmbedding ? " AND embedding IS NOT NULL" : "";
95
- const rows = await db.$queryRawUnsafe(`SELECT id, class_name FROM mc_source_files
95
+ const rows = await db.$queryRawUnsafe(`SELECT id, class_name FROM mc_source_files
96
96
  WHERE mc_version_id = $1 AND class_name = ANY($2::text[])${embFilter}`, mcVersionId, classNames);
97
97
  return new Map(rows.map((r) => [r.class_name, r.id]));
98
98
  }
@@ -101,7 +101,7 @@ export async function findModSourceIdsByClassNames(classNames, modId, requireEmb
101
101
  return new Map();
102
102
  const db = await getDb();
103
103
  const embFilter = requireEmbedding ? " AND embedding IS NOT NULL" : "";
104
- const rows = await db.$queryRawUnsafe(`SELECT id, class_name FROM mod_source_files
104
+ const rows = await db.$queryRawUnsafe(`SELECT id, class_name FROM mod_source_files
105
105
  WHERE mod_id = $1 AND class_name = ANY($2::text[])${embFilter}`, modId, classNames);
106
106
  return new Map(rows.map((r) => [r.class_name, r.id]));
107
107
  }
@@ -116,8 +116,8 @@ export async function getEmbedSources(table, ids) {
116
116
  /** Count embeddings by source provenance. */
117
117
  export async function countEmbedsBySource(table, scopeColumn, scopeId) {
118
118
  const db = await getDb();
119
- const rows = await db.$queryRawUnsafe(`SELECT embed_source, COUNT(*)::text AS count FROM ${table}
120
- WHERE ${scopeColumn} = $1 AND embedding IS NOT NULL
119
+ const rows = await db.$queryRawUnsafe(`SELECT embed_source, COUNT(*)::text AS count FROM ${table}
120
+ WHERE ${scopeColumn} = $1 AND embedding IS NOT NULL
121
121
  GROUP BY embed_source`, scopeId);
122
122
  const result = { local: 0, registry: 0, community: 0, unknown: 0 };
123
123
  for (const r of rows) {
@@ -327,8 +327,8 @@ export async function countModSourceFiles(modId) {
327
327
  }
328
328
  export async function findModSourceFilesUnembedded(modId, limit, offset) {
329
329
  const db = await getDb();
330
- return db.$queryRawUnsafe(`SELECT id, class_name AS "className", content FROM mod_source_files
331
- WHERE mod_id = $1 AND embedding IS NULL
330
+ return db.$queryRawUnsafe(`SELECT id, class_name AS "className", content FROM mod_source_files
331
+ WHERE mod_id = $1 AND embedding IS NULL
332
332
  ORDER BY id LIMIT $2 OFFSET $3`, modId, limit, offset);
333
333
  }
334
334
  export async function countUnembeddedModSourceFiles(modId) {
@@ -22,10 +22,10 @@ export async function ftsSearchSource(mcVersionId, query, limit) {
22
22
  const Database = (await import("better-sqlite3")).default;
23
23
  const db = new Database(path, { readonly: true });
24
24
  try {
25
- const rows = db.prepare(`SELECT s.id, s.class_name, s.content FROM fts_mc_source f
26
- JOIN mc_source_files s ON s.id = f.rowid
27
- WHERE f.fts_mc_source MATCH ? AND s.mc_version_id = ?
28
- ORDER BY rank
25
+ const rows = db.prepare(`SELECT s.id, s.class_name, s.content FROM fts_mc_source f
26
+ JOIN mc_source_files s ON s.id = f.rowid
27
+ WHERE f.fts_mc_source MATCH ? AND s.mc_version_id = ?
28
+ ORDER BY rank
29
29
  LIMIT ?`).all(query, mcVersionId, limit);
30
30
  return rows.map(r => ({
31
31
  className: r.class_name,
@@ -38,18 +38,18 @@ export async function ftsSearchSource(mcVersionId, query, limit) {
38
38
  }
39
39
  // Postgres / PGlite — existing tsvector query
40
40
  const db = await getDb();
41
- const rows = await db.$queryRaw `
42
- SELECT
43
- class_name,
44
- ts_headline('simple', content,
45
- plainto_tsquery('simple', ${query}),
46
- 'MaxWords=25, MinWords=15, StartSel="", StopSel=""'
47
- ) AS snippet
48
- FROM mc_source_files
49
- WHERE mc_version_id = ${mcVersionId}
50
- AND to_tsvector('simple', content) @@ plainto_tsquery('simple', ${query})
51
- ORDER BY ts_rank(to_tsvector('simple', content), plainto_tsquery('simple', ${query})) DESC
52
- LIMIT ${limit}
41
+ const rows = await db.$queryRaw `
42
+ SELECT
43
+ class_name,
44
+ ts_headline('simple', content,
45
+ plainto_tsquery('simple', ${query}),
46
+ 'MaxWords=25, MinWords=15, StartSel="", StopSel=""'
47
+ ) AS snippet
48
+ FROM mc_source_files
49
+ WHERE mc_version_id = ${mcVersionId}
50
+ AND to_tsvector('simple', content) @@ plainto_tsquery('simple', ${query})
51
+ ORDER BY ts_rank(to_tsvector('simple', content), plainto_tsquery('simple', ${query})) DESC
52
+ LIMIT ${limit}
53
53
  `;
54
54
  return rows.map(r => ({ className: r.class_name, snippet: r.snippet }));
55
55
  }
@@ -68,10 +68,10 @@ export async function ftsSearchModSource(modId, query, limit) {
68
68
  const Database = (await import("better-sqlite3")).default;
69
69
  const db = new Database(path, { readonly: true });
70
70
  try {
71
- const rows = db.prepare(`SELECT s.id, s.class_name, s.content FROM fts_mod_source f
72
- JOIN mod_source_files s ON s.id = f.rowid
73
- WHERE f.fts_mod_source MATCH ? AND s.mod_id = ?
74
- ORDER BY rank
71
+ const rows = db.prepare(`SELECT s.id, s.class_name, s.content FROM fts_mod_source f
72
+ JOIN mod_source_files s ON s.id = f.rowid
73
+ WHERE f.fts_mod_source MATCH ? AND s.mod_id = ?
74
+ ORDER BY rank
75
75
  LIMIT ?`).all(query, modId, limit);
76
76
  return rows.map(r => ({
77
77
  className: r.class_name,
@@ -84,18 +84,18 @@ export async function ftsSearchModSource(modId, query, limit) {
84
84
  }
85
85
  // PostgreSQL — GIN index on to_tsvector('simple', content)
86
86
  const db = await getDb();
87
- const rows = await db.$queryRaw `
88
- SELECT
89
- class_name,
90
- ts_headline('simple', content,
91
- plainto_tsquery('simple', ${query}),
92
- 'MaxWords=25, MinWords=15, StartSel="", StopSel=""'
93
- ) AS snippet
94
- FROM mod_source_files
95
- WHERE mod_id = ${modId}
96
- AND to_tsvector('simple', content) @@ plainto_tsquery('simple', ${query})
97
- ORDER BY ts_rank(to_tsvector('simple', content), plainto_tsquery('simple', ${query})) DESC
98
- LIMIT ${limit}
87
+ const rows = await db.$queryRaw `
88
+ SELECT
89
+ class_name,
90
+ ts_headline('simple', content,
91
+ plainto_tsquery('simple', ${query}),
92
+ 'MaxWords=25, MinWords=15, StartSel="", StopSel=""'
93
+ ) AS snippet
94
+ FROM mod_source_files
95
+ WHERE mod_id = ${modId}
96
+ AND to_tsvector('simple', content) @@ plainto_tsquery('simple', ${query})
97
+ ORDER BY ts_rank(to_tsvector('simple', content), plainto_tsquery('simple', ${query})) DESC
98
+ LIMIT ${limit}
99
99
  `;
100
100
  return rows.map(r => ({ className: r.class_name, snippet: r.snippet }));
101
101
  }
@@ -108,11 +108,11 @@ export async function ftsSearchDocs(query, limit = 20) {
108
108
  const Database = (await import("better-sqlite3")).default;
109
109
  const db = new Database(path, { readonly: true });
110
110
  try {
111
- const rows = db.prepare(`SELECT d.id, d.class_name, d.title, d.summary, d.url, d.category, d.namespace, d.tags
112
- FROM fts_doc_entries f
113
- JOIN doc_entries d ON d.id = f.rowid
114
- WHERE f.fts_doc_entries MATCH ?
115
- ORDER BY rank
111
+ const rows = db.prepare(`SELECT d.id, d.class_name, d.title, d.summary, d.url, d.category, d.namespace, d.tags
112
+ FROM fts_doc_entries f
113
+ JOIN doc_entries d ON d.id = f.rowid
114
+ WHERE f.fts_doc_entries MATCH ?
115
+ ORDER BY rank
116
116
  LIMIT ?`).all(query, limit);
117
117
  return rows.map(r => ({ ...r, tags: JSON.parse(r.tags ?? "[]") }));
118
118
  }
@@ -123,14 +123,14 @@ export async function ftsSearchDocs(query, limit = 20) {
123
123
  // Postgres / PGlite — raw LIKE (case-insensitive via lower())
124
124
  const db = await getDb();
125
125
  const kw = escapeLike(query.toLowerCase());
126
- const rows = await db.$queryRaw `
127
- SELECT id, class_name, title, summary, url, category, namespace, tags
128
- FROM doc_entries
129
- WHERE lower(title) LIKE ${"%" + kw + "%"}
130
- OR lower(summary) LIKE ${"%" + kw + "%"}
131
- OR lower(class_name) LIKE ${"%" + kw + "%"}
132
- ORDER BY id
133
- LIMIT ${limit}
126
+ const rows = await db.$queryRaw `
127
+ SELECT id, class_name, title, summary, url, category, namespace, tags
128
+ FROM doc_entries
129
+ WHERE lower(title) LIKE ${"%" + kw + "%"}
130
+ OR lower(summary) LIKE ${"%" + kw + "%"}
131
+ OR lower(class_name) LIKE ${"%" + kw + "%"}
132
+ ORDER BY id
133
+ LIMIT ${limit}
134
134
  `;
135
135
  return rows;
136
136
  }
@@ -143,12 +143,12 @@ export async function ftsSearchPrimers(query, modloader, limit = 20) {
143
143
  const Database = (await import("better-sqlite3")).default;
144
144
  const db = new Database(path, { readonly: true });
145
145
  try {
146
- const rows = db.prepare(`SELECT p.id, p.title, p.summary, p.from_version, p.to_version, p.modloader, p.url
147
- FROM fts_primers f
148
- JOIN primers p ON p.id = f.rowid
149
- WHERE f.fts_primers MATCH ?
150
- ${modloader ? "AND p.modloader = ?" : ""}
151
- ORDER BY rank
146
+ const rows = db.prepare(`SELECT p.id, p.title, p.summary, p.from_version, p.to_version, p.modloader, p.url
147
+ FROM fts_primers f
148
+ JOIN primers p ON p.id = f.rowid
149
+ WHERE f.fts_primers MATCH ?
150
+ ${modloader ? "AND p.modloader = ?" : ""}
151
+ ORDER BY rank
152
152
  LIMIT ?`).all(...[query, ...(modloader ? [modloader] : []), limit]);
153
153
  return rows.map(r => ({
154
154
  id: r.id, title: r.title, summary: r.summary,
@@ -36,15 +36,15 @@ async function findModsWithMixinTargetsMatching(targets, loader, mcVersion) {
36
36
  }
37
37
  const whereExtra = extra.length ? " AND " + extra.join(" AND ") : "";
38
38
  const db = await getDb();
39
- const rows = await db.$queryRawUnsafe(`
40
- SELECT
41
- m.mod_id,
42
- m.display_name,
43
- ARRAY_AGG(t.cls) FILTER (WHERE t.cls = ANY($1)) AS matched
44
- FROM "mods" m
45
- CROSS JOIN LATERAL jsonb_array_elements_text(m.mixin_targets::jsonb) AS t(cls)
46
- WHERE t.cls = ANY($1) ${whereExtra}
47
- GROUP BY m.mod_id, m.display_name
39
+ const rows = await db.$queryRawUnsafe(`
40
+ SELECT
41
+ m.mod_id,
42
+ m.display_name,
43
+ ARRAY_AGG(t.cls) FILTER (WHERE t.cls = ANY($1)) AS matched
44
+ FROM "mods" m
45
+ CROSS JOIN LATERAL jsonb_array_elements_text(m.mixin_targets::jsonb) AS t(cls)
46
+ WHERE t.cls = ANY($1) ${whereExtra}
47
+ GROUP BY m.mod_id, m.display_name
48
48
  `, ...params);
49
49
  return rows.map((r) => ({
50
50
  modId: r.mod_id,
@@ -49,7 +49,7 @@ export async function exportModEmbeddings(dbId, outputDir) {
49
49
  const chunkSize = parseInt(process.env.OLLAMA_EMBED_CHUNK ?? "1500", 10);
50
50
  // Query all embedded source files
51
51
  const db = await getDb();
52
- const rows = await db.$queryRawUnsafe(`SELECT class_name, embedding::text AS embedding FROM mod_source_files
52
+ const rows = await db.$queryRawUnsafe(`SELECT class_name, embedding::text AS embedding FROM mod_source_files
53
53
  WHERE mod_id = $1 AND embedding IS NOT NULL`, dbId);
54
54
  if (rows.length === 0) {
55
55
  throw new Error(`No embeddings found for mod #${dbId} (${mod.modId}). Run index_semantic first.`);
@@ -96,7 +96,7 @@ export async function exportVanillaEmbeddings(mcVersion, outputDir) {
96
96
  const dims = parseInt(process.env.OLLAMA_EMBED_DIM ?? "768", 10);
97
97
  const chunkSize = parseInt(process.env.OLLAMA_EMBED_CHUNK ?? "1500", 10);
98
98
  const db = await getDb();
99
- const rows = await db.$queryRawUnsafe(`SELECT class_name, embedding::text AS embedding FROM mc_source_files
99
+ const rows = await db.$queryRawUnsafe(`SELECT class_name, embedding::text AS embedding FROM mc_source_files
100
100
  WHERE mc_version_id = $1 AND embedding IS NOT NULL`, versionRow.id);
101
101
  if (rows.length === 0) {
102
102
  throw new Error(`No vanilla embeddings found for MC ${mcVersion}. Run mc_source index_semantic first.`);
@@ -156,7 +156,7 @@ export async function exportAllEmbeddings(outputDir) {
156
156
  const db = await getDb();
157
157
  const model = process.env.OLLAMA_EMBED_MODEL ?? "nomic-embed-text";
158
158
  // Find all mods with at least one embedded source file
159
- const mods = await db.$queryRawUnsafe(`SELECT mod_id, COUNT(*)::text AS count FROM mod_source_files
159
+ const mods = await db.$queryRawUnsafe(`SELECT mod_id, COUNT(*)::text AS count FROM mod_source_files
160
160
  WHERE embedding IS NOT NULL GROUP BY mod_id`);
161
161
  let exported = 0;
162
162
  let skipped = 0;
@@ -497,8 +497,8 @@ export async function getEmbedStatus(target) {
497
497
  if (!mod)
498
498
  throw new Error(`Mod #${target.dbId} not found`);
499
499
  scopeId = target.dbId;
500
- stats = await db.$queryRawUnsafe(`SELECT COUNT(*)::text AS total,
501
- COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END)::text AS embedded
500
+ stats = await db.$queryRawUnsafe(`SELECT COUNT(*)::text AS total,
501
+ COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END)::text AS embedded
502
502
  FROM mod_source_files WHERE mod_id = $1`, target.dbId);
503
503
  }
504
504
  else if (target.targetType === "vanilla") {
@@ -510,8 +510,8 @@ export async function getEmbedStatus(target) {
510
510
  sourceTable = "mc_source_files";
511
511
  scopeColumn = "mc_version_id";
512
512
  scopeId = versionRow.id;
513
- stats = await db.$queryRawUnsafe(`SELECT COUNT(*)::text AS total,
514
- COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END)::text AS embedded
513
+ stats = await db.$queryRawUnsafe(`SELECT COUNT(*)::text AS total,
514
+ COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END)::text AS embedded
515
515
  FROM mc_source_files WHERE mc_version_id = $1`, versionRow.id);
516
516
  }
517
517
  else {
@@ -529,8 +529,8 @@ export async function getEmbedStatus(target) {
529
529
  if (!mod)
530
530
  throw new Error(`Mod #${modDbId} not found`);
531
531
  scopeId = modDbId;
532
- stats = await db.$queryRawUnsafe(`SELECT COUNT(*)::text AS total,
533
- COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END)::text AS embedded
532
+ stats = await db.$queryRawUnsafe(`SELECT COUNT(*)::text AS total,
533
+ COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END)::text AS embedded
534
534
  FROM mod_source_files WHERE mod_id = $1`, modDbId);
535
535
  }
536
536
  const total = parseInt(stats[0].total, 10);
@@ -496,14 +496,14 @@ export async function getGraphReport(dbId) {
496
496
  return content.slice(0, 30_000); // cap at 30KB (reports are typically 2-5KB)
497
497
  }
498
498
  // ── Graph enrichment (Plan 1: Chat-Agent-Driven) ──────────────────────────────
499
- const ENRICHMENT_SCHEMA = `Return JSON with this exact structure:
500
- {
501
- "nodes": [{"id": "ClassName", "type": "class", "src": "path/to/File.java", "loc": "L42", "community": "subsystem_name"}],
502
- "edges": [{"source": "ClassA", "target": "ClassB", "relation": "implements_pattern", "context": "Singleton pattern via lazy init"}]
503
- }
504
-
505
- Valid relation types for semantic edges: implements_pattern, belongs_to_subsystem, delegates_to, adapts, wraps, event_handler_for, config_for, registry_entry, extends_behavior.
506
- Node types: class, interface, enum, annotation, package, subsystem.
499
+ const ENRICHMENT_SCHEMA = `Return JSON with this exact structure:
500
+ {
501
+ "nodes": [{"id": "ClassName", "type": "class", "src": "path/to/File.java", "loc": "L42", "community": "subsystem_name"}],
502
+ "edges": [{"source": "ClassA", "target": "ClassB", "relation": "implements_pattern", "context": "Singleton pattern via lazy init"}]
503
+ }
504
+
505
+ Valid relation types for semantic edges: implements_pattern, belongs_to_subsystem, delegates_to, adapts, wraps, event_handler_for, config_for, registry_entry, extends_behavior.
506
+ Node types: class, interface, enum, annotation, package, subsystem.
507
507
  Focus on design patterns, subsystem groupings, and architectural relationships — NOT call graphs (those are already in the AST layer).`;
508
508
  const CHUNK_SIZE = 20; // files per chunk
509
509
  /**
@@ -542,12 +542,12 @@ export async function enrichNextChunk(dbId) {
542
542
  existingNodes: nodesInChunk.map(n => ({ id: n.id, type: n.type, src: n.src })),
543
543
  existingEdges: edgesInChunk.map(e => ({ source: e.source, target: e.target, relation: e.relation })),
544
544
  };
545
- const prompt = `Analyze these ${chunkFiles.length} source files from mod "${mod.modId}" and identify semantic relationships.
546
-
547
- AST context (structural relationships already captured):
548
- ${JSON.stringify(astContext, null, 2)}
549
-
550
- Add SEMANTIC relationships on top — design patterns, subsystem groupings, architectural intent.
545
+ const prompt = `Analyze these ${chunkFiles.length} source files from mod "${mod.modId}" and identify semantic relationships.
546
+
547
+ AST context (structural relationships already captured):
548
+ ${JSON.stringify(astContext, null, 2)}
549
+
550
+ Add SEMANTIC relationships on top — design patterns, subsystem groupings, architectural intent.
551
551
  Do NOT duplicate existing structural edges (calls, imports, extends, implements).`;
552
552
  return {
553
553
  chunkIndex: cursor,
@@ -128,8 +128,8 @@ export async function indexMcSourceSemantic(version, batchSize = 50) {
128
128
  while (true) {
129
129
  const { getDb } = await import("../db.js");
130
130
  const _db = await getDb();
131
- const batch = await _db.$queryRawUnsafe(`SELECT id, class_name, content FROM mc_source_files
132
- WHERE mc_version_id = $1 AND embedding IS NULL
131
+ const batch = await _db.$queryRawUnsafe(`SELECT id, class_name, content FROM mc_source_files
132
+ WHERE mc_version_id = $1 AND embedding IS NULL
133
133
  ORDER BY id LIMIT $2`, mcVersionId, batchSize);
134
134
  if (!batch.length)
135
135
  break;
@@ -40,7 +40,6 @@ export declare function getMixinTargets(modId: string | number): Promise<{
40
40
  export declare function getMixinConflicts(targetClass: string): Promise<{
41
41
  targetClass: string;
42
42
  conflictingMods: {
43
- tags: string[];
44
43
  sha512: string | null;
45
44
  id: number;
46
45
  version: string;
@@ -67,6 +66,7 @@ export declare function getMixinConflicts(targetClass: string): Promise<{
67
66
  atEntries: import("@prisma/client/runtime/library").JsonValue;
68
67
  awEntries: import("@prisma/client/runtime/library").JsonValue;
69
68
  metadataSource: string | null;
69
+ tags: string[];
70
70
  ingestedAt: Date;
71
71
  updatedAt: Date;
72
72
  }[];
@@ -25,22 +25,22 @@ async function mixinConflictRaw(loader, mcVersion, minConflicts = 2) {
25
25
  }
26
26
  const whereSQL = whereClauses.join(" AND ");
27
27
  const db = await getDb();
28
- const rows = await db.$queryRawUnsafe(`
29
- WITH deduped AS (
30
- SELECT DISTINCT ON (mod_id) id, mod_id, mixin_targets
31
- FROM "mods"
32
- WHERE ${whereSQL}
33
- ORDER BY mod_id, id DESC
34
- )
35
- SELECT
36
- t.class_name,
37
- COUNT(DISTINCT d.mod_id)::int AS mod_count,
38
- ARRAY_AGG(DISTINCT d.id) AS mod_ids
39
- FROM deduped d
40
- CROSS JOIN LATERAL jsonb_array_elements_text(d.mixin_targets::jsonb) AS t(class_name)
41
- GROUP BY t.class_name
42
- HAVING COUNT(DISTINCT d.mod_id) >= $1
43
- ORDER BY mod_count DESC
28
+ const rows = await db.$queryRawUnsafe(`
29
+ WITH deduped AS (
30
+ SELECT DISTINCT ON (mod_id) id, mod_id, mixin_targets
31
+ FROM "mods"
32
+ WHERE ${whereSQL}
33
+ ORDER BY mod_id, id DESC
34
+ )
35
+ SELECT
36
+ t.class_name,
37
+ COUNT(DISTINCT d.mod_id)::int AS mod_count,
38
+ ARRAY_AGG(DISTINCT d.id) AS mod_ids
39
+ FROM deduped d
40
+ CROSS JOIN LATERAL jsonb_array_elements_text(d.mixin_targets::jsonb) AS t(class_name)
41
+ GROUP BY t.class_name
42
+ HAVING COUNT(DISTINCT d.mod_id) >= $1
43
+ ORDER BY mod_count DESC
44
44
  `, ...params);
45
45
  return rows.map((r) => ({
46
46
  className: r.class_name,
@@ -330,9 +330,9 @@ async function batchSimilarities(pairs, backend, table) {
330
330
  const db = await getDb();
331
331
  const aIds = pairs.map((p) => p.aId);
332
332
  const bIds = pairs.map((p) => p.bId);
333
- const rows = await db.$queryRawUnsafe(`SELECT p.a_id, p.b_id, (1 - (a.embedding <=> b.embedding))::float AS sim
334
- FROM unnest($1::int[], $2::int[]) AS p(a_id, b_id)
335
- JOIN ${table} a ON a.id = p.a_id AND a.embedding IS NOT NULL
333
+ const rows = await db.$queryRawUnsafe(`SELECT p.a_id, p.b_id, (1 - (a.embedding <=> b.embedding))::float AS sim
334
+ FROM unnest($1::int[], $2::int[]) AS p(a_id, b_id)
335
+ JOIN ${table} a ON a.id = p.a_id AND a.embedding IS NOT NULL
336
336
  JOIN ${table} b ON b.id = p.b_id AND b.embedding IS NOT NULL`, aIds, bIds);
337
337
  for (const { a_id, b_id, sim } of rows)
338
338
  out.set(`${a_id}:${b_id}`, sim);