@wrongstack/tools 0.296.2 → 0.296.3

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.
@@ -255,11 +255,18 @@ async function parseSymbols(opts) {
255
255
  } else if (ts.isHeritageClause(node)) {
256
256
  for (const t of node.types) {
257
257
  const name = getTypeName(t.expression);
258
- if (name) refs.push({ fromId: 0, toName: name, callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement", line: lineNum });
258
+ if (name)
259
+ refs.push({
260
+ fromId: 0,
261
+ toName: name,
262
+ callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
263
+ line: lineNum
264
+ });
259
265
  }
260
266
  } else if (ts.isImportDeclaration(node)) {
261
- const moduleName = getModuleName(node);
262
- if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
267
+ emitImportSpecifierRefs(node, refs, lineNum);
268
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
269
+ emitExportSpecifierRefs(node, refs, lineNum);
263
270
  }
264
271
  const scopeIdx = scopeParts.length;
265
272
  pushScopeName(node, scopeParts);
@@ -275,11 +282,6 @@ function getTypeName(name) {
275
282
  if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
276
283
  return "";
277
284
  }
278
- function getModuleName(node) {
279
- const moduleSpecifier = node.moduleSpecifier;
280
- if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
281
- return "";
282
- }
283
285
  function deduplicateRefs(refs) {
284
286
  const seen = /* @__PURE__ */ new Set();
285
287
  return refs.filter((r) => {
@@ -289,6 +291,44 @@ function deduplicateRefs(refs) {
289
291
  return true;
290
292
  });
291
293
  }
294
+ function getImportSpecifierName(spec) {
295
+ return spec.propertyName?.text ?? spec.name.text;
296
+ }
297
+ function emitImportSpecifierRefs(node, refs, lineNum) {
298
+ const clause = node.importClause;
299
+ if (!clause) return;
300
+ if (clause.name) {
301
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
302
+ }
303
+ const bindings = clause.namedBindings;
304
+ if (!bindings) return;
305
+ if (ts.isNamedImports(bindings)) {
306
+ for (const element of bindings.elements) {
307
+ refs.push({
308
+ fromId: 0,
309
+ toName: getImportSpecifierName(element),
310
+ callType: "import",
311
+ line: lineNum
312
+ });
313
+ }
314
+ } else if (ts.isNamespaceImport(bindings)) {
315
+ refs.push({ fromId: 0, toName: bindings.name.text, callType: "import", line: lineNum });
316
+ }
317
+ }
318
+ function emitExportSpecifierRefs(node, refs, lineNum) {
319
+ const clause = node.exportClause;
320
+ if (clause && ts.isNamespaceExport(clause)) {
321
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
322
+ return;
323
+ }
324
+ if (clause && ts.isNamedExports(clause)) {
325
+ for (const element of clause.elements) {
326
+ const originalName = element.propertyName?.text ?? element.name.text;
327
+ refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
328
+ }
329
+ return;
330
+ }
331
+ }
292
332
  var ts, tsLoad, kindMapCache;
293
333
  var init_ts_parser = __esm({
294
334
  "src/codebase-index/ts-parser.ts"() {
@@ -2130,71 +2170,6 @@ import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
2130
2170
  import * as fs7 from "node:fs";
2131
2171
  import * as path10 from "node:path";
2132
2172
 
2133
- // src/codebase-index/schema.ts
2134
- var SCHEMA_VERSION = 3;
2135
-
2136
- // src/codebase-index/lsp-kind.ts
2137
- function lspKindToInternalKind(k) {
2138
- switch (k) {
2139
- case 5 /* Class */:
2140
- return "class";
2141
- case 6 /* Method */:
2142
- return "method";
2143
- case 7 /* Property */:
2144
- case 8 /* Field */:
2145
- return "property";
2146
- case 9 /* Constructor */:
2147
- return "class";
2148
- case 10 /* Enum */:
2149
- return "enum";
2150
- case 11 /* Interface */:
2151
- return "interface";
2152
- case 12 /* Function */:
2153
- return "function";
2154
- case 13 /* Variable */:
2155
- return "var";
2156
- case 14 /* Constant */:
2157
- return "const";
2158
- case 22 /* EnumMember */:
2159
- return "enum";
2160
- case 26 /* TypeParameter */:
2161
- return "type";
2162
- case 3 /* Namespace */:
2163
- return "namespace";
2164
- default:
2165
- return null;
2166
- }
2167
- }
2168
- function internalKindToLspKind(k) {
2169
- switch (k) {
2170
- case "class":
2171
- return 5 /* Class */;
2172
- case "method":
2173
- return 6 /* Method */;
2174
- case "property":
2175
- return 7 /* Property */;
2176
- case "function":
2177
- return 12 /* Function */;
2178
- case "var":
2179
- return 13 /* Variable */;
2180
- case "const":
2181
- return 14 /* Constant */;
2182
- case "let":
2183
- return 13 /* Variable */;
2184
- case "enum":
2185
- return 10 /* Enum */;
2186
- case "interface":
2187
- return 11 /* Interface */;
2188
- case "namespace":
2189
- return 3 /* Namespace */;
2190
- case "type":
2191
- return 26 /* TypeParameter */;
2192
- // parameter and other internal-only kinds have no LSP equivalent
2193
- default:
2194
- return null;
2195
- }
2196
- }
2197
-
2198
2173
  // src/codebase-index/bm25.ts
2199
2174
  var K1 = 1.5;
2200
2175
  var B = 0.75;
@@ -2284,6 +2259,71 @@ var Bm25Index = class {
2284
2259
  }
2285
2260
  };
2286
2261
 
2262
+ // src/codebase-index/lsp-kind.ts
2263
+ function lspKindToInternalKind(k) {
2264
+ switch (k) {
2265
+ case 5 /* Class */:
2266
+ return "class";
2267
+ case 6 /* Method */:
2268
+ return "method";
2269
+ case 7 /* Property */:
2270
+ case 8 /* Field */:
2271
+ return "property";
2272
+ case 9 /* Constructor */:
2273
+ return "class";
2274
+ case 10 /* Enum */:
2275
+ return "enum";
2276
+ case 11 /* Interface */:
2277
+ return "interface";
2278
+ case 12 /* Function */:
2279
+ return "function";
2280
+ case 13 /* Variable */:
2281
+ return "var";
2282
+ case 14 /* Constant */:
2283
+ return "const";
2284
+ case 22 /* EnumMember */:
2285
+ return "enum";
2286
+ case 26 /* TypeParameter */:
2287
+ return "type";
2288
+ case 3 /* Namespace */:
2289
+ return "namespace";
2290
+ default:
2291
+ return null;
2292
+ }
2293
+ }
2294
+ function internalKindToLspKind(k) {
2295
+ switch (k) {
2296
+ case "class":
2297
+ return 5 /* Class */;
2298
+ case "method":
2299
+ return 6 /* Method */;
2300
+ case "property":
2301
+ return 7 /* Property */;
2302
+ case "function":
2303
+ return 12 /* Function */;
2304
+ case "var":
2305
+ return 13 /* Variable */;
2306
+ case "const":
2307
+ return 14 /* Constant */;
2308
+ case "let":
2309
+ return 13 /* Variable */;
2310
+ case "enum":
2311
+ return 10 /* Enum */;
2312
+ case "interface":
2313
+ return 11 /* Interface */;
2314
+ case "namespace":
2315
+ return 3 /* Namespace */;
2316
+ case "type":
2317
+ return 26 /* TypeParameter */;
2318
+ // parameter and other internal-only kinds have no LSP equivalent
2319
+ default:
2320
+ return null;
2321
+ }
2322
+ }
2323
+
2324
+ // src/codebase-index/schema.ts
2325
+ var SCHEMA_VERSION = 3;
2326
+
2287
2327
  // src/codebase-index/sqlite-runtime.ts
2288
2328
  import { createRequire } from "node:module";
2289
2329
  import { toErrorMessage } from "@wrongstack/core/utils";
@@ -2351,96 +2391,6 @@ function runSqliteWithRetry(fn) {
2351
2391
  throw lastError;
2352
2392
  }
2353
2393
 
2354
- // src/codebase-index/writer-helpers.ts
2355
- import { resolveWstackPaths } from "@wrongstack/core/utils";
2356
- function escapeLike(value) {
2357
- return value.replace(/[\\%_]/g, (char) => `\\${char}`);
2358
- }
2359
- function assignRefsToSymbols(refs, symbols) {
2360
- if (refs.length === 0 || symbols.length === 0) return [];
2361
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
2362
- const seen = /* @__PURE__ */ new Set();
2363
- const assigned = [];
2364
- for (const ref of refs) {
2365
- let owner;
2366
- for (const symbol of ordered) {
2367
- if (symbol.line > ref.line) break;
2368
- owner = symbol;
2369
- }
2370
- if (!owner && ref.callType === "import") owner = ordered[0];
2371
- if (!owner || owner.id <= 0) continue;
2372
- const key = `${owner.id}:${ref.toName}:${ref.callType}`;
2373
- if (seen.has(key)) continue;
2374
- seen.add(key);
2375
- assigned.push({ ...ref, fromId: owner.id });
2376
- }
2377
- return assigned;
2378
- }
2379
- function resolveIndexDir(projectRoot, override) {
2380
- return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
2381
- }
2382
- function codebaseIndexDirOverride(ctx) {
2383
- const v = ctx.meta?.["codebaseIndexDir"];
2384
- return typeof v === "string" ? v : void 0;
2385
- }
2386
-
2387
- // src/codebase-index/writer-schema.ts
2388
- var METADATA_TABLE_SQL = `
2389
- CREATE TABLE IF NOT EXISTS metadata (
2390
- key TEXT PRIMARY KEY,
2391
- value TEXT NOT NULL
2392
- );
2393
- `;
2394
- var CORE_TABLES_SQL = `
2395
- CREATE TABLE IF NOT EXISTS files (
2396
- file TEXT PRIMARY KEY,
2397
- lang TEXT NOT NULL,
2398
- mtime_ms INTEGER NOT NULL,
2399
- symbol_count INTEGER NOT NULL DEFAULT 0,
2400
- last_indexed INTEGER NOT NULL
2401
- );
2402
- CREATE TABLE IF NOT EXISTS symbols (
2403
- id INTEGER PRIMARY KEY,
2404
- lang TEXT NOT NULL,
2405
- kind TEXT NOT NULL,
2406
- name TEXT NOT NULL,
2407
- file TEXT NOT NULL,
2408
- line INTEGER NOT NULL,
2409
- col INTEGER NOT NULL,
2410
- signature TEXT NOT NULL DEFAULT '',
2411
- doc_comment TEXT NOT NULL DEFAULT '',
2412
- scope TEXT NOT NULL DEFAULT '',
2413
- text TEXT NOT NULL DEFAULT '',
2414
- file_fk TEXT NOT NULL
2415
- );
2416
- `;
2417
- var SYMBOL_INDEX_SQL = [
2418
- "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
2419
- "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
2420
- "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
2421
- "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
2422
- "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
2423
- "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
2424
- "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
2425
- ];
2426
- var REFS_TABLE_SQL = `
2427
- CREATE TABLE IF NOT EXISTS refs (
2428
- id INTEGER PRIMARY KEY,
2429
- from_id INTEGER NOT NULL,
2430
- to_name TEXT NOT NULL,
2431
- to_id INTEGER,
2432
- call_type TEXT NOT NULL,
2433
- line INTEGER NOT NULL
2434
- );
2435
- `;
2436
- var REFS_INDEX_SQL = [
2437
- "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
2438
- "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
2439
- "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
2440
- "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
2441
- ];
2442
- var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
2443
-
2444
2394
  // src/codebase-index/writer-admin.ts
2445
2395
  import * as fs6 from "node:fs";
2446
2396
  import * as path8 from "node:path";
@@ -2913,6 +2863,39 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
2913
2863
  return { nodes, edges };
2914
2864
  }
2915
2865
 
2866
+ // src/codebase-index/writer-helpers.ts
2867
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
2868
+ function escapeLike(value) {
2869
+ return value.replace(/[\\%_]/g, (char) => `\\${char}`);
2870
+ }
2871
+ function assignRefsToSymbols(refs, symbols) {
2872
+ if (refs.length === 0 || symbols.length === 0) return [];
2873
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
2874
+ const seen = /* @__PURE__ */ new Set();
2875
+ const assigned = [];
2876
+ for (const ref of refs) {
2877
+ let owner;
2878
+ for (const symbol of ordered) {
2879
+ if (symbol.line > ref.line) break;
2880
+ owner = symbol;
2881
+ }
2882
+ if (!owner && ref.callType === "import") owner = ordered[0];
2883
+ if (!owner || owner.id <= 0) continue;
2884
+ const key = `${owner.id}:${ref.toName}:${ref.callType}`;
2885
+ if (seen.has(key)) continue;
2886
+ seen.add(key);
2887
+ assigned.push({ ...ref, fromId: owner.id });
2888
+ }
2889
+ return assigned;
2890
+ }
2891
+ function resolveIndexDir(projectRoot, override) {
2892
+ return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
2893
+ }
2894
+ function codebaseIndexDirOverride(ctx) {
2895
+ const v = ctx.meta?.["codebaseIndexDir"];
2896
+ return typeof v === "string" ? v : void 0;
2897
+ }
2898
+
2916
2899
  // src/codebase-index/writer-pragmas.ts
2917
2900
  import { sqliteCachePragmas } from "@wrongstack/core/utils";
2918
2901
  function applyIndexStorePragmas(db) {
@@ -2931,6 +2914,63 @@ function applyIndexStorePragmas(db) {
2931
2914
  }
2932
2915
  }
2933
2916
 
2917
+ // src/codebase-index/writer-schema.ts
2918
+ var METADATA_TABLE_SQL = `
2919
+ CREATE TABLE IF NOT EXISTS metadata (
2920
+ key TEXT PRIMARY KEY,
2921
+ value TEXT NOT NULL
2922
+ );
2923
+ `;
2924
+ var CORE_TABLES_SQL = `
2925
+ CREATE TABLE IF NOT EXISTS files (
2926
+ file TEXT PRIMARY KEY,
2927
+ lang TEXT NOT NULL,
2928
+ mtime_ms INTEGER NOT NULL,
2929
+ symbol_count INTEGER NOT NULL DEFAULT 0,
2930
+ last_indexed INTEGER NOT NULL
2931
+ );
2932
+ CREATE TABLE IF NOT EXISTS symbols (
2933
+ id INTEGER PRIMARY KEY,
2934
+ lang TEXT NOT NULL,
2935
+ kind TEXT NOT NULL,
2936
+ name TEXT NOT NULL,
2937
+ file TEXT NOT NULL,
2938
+ line INTEGER NOT NULL,
2939
+ col INTEGER NOT NULL,
2940
+ signature TEXT NOT NULL DEFAULT '',
2941
+ doc_comment TEXT NOT NULL DEFAULT '',
2942
+ scope TEXT NOT NULL DEFAULT '',
2943
+ text TEXT NOT NULL DEFAULT '',
2944
+ file_fk TEXT NOT NULL
2945
+ );
2946
+ `;
2947
+ var SYMBOL_INDEX_SQL = [
2948
+ "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
2949
+ "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
2950
+ "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
2951
+ "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
2952
+ "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
2953
+ "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
2954
+ "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
2955
+ ];
2956
+ var REFS_TABLE_SQL = `
2957
+ CREATE TABLE IF NOT EXISTS refs (
2958
+ id INTEGER PRIMARY KEY,
2959
+ from_id INTEGER NOT NULL,
2960
+ to_name TEXT NOT NULL,
2961
+ to_id INTEGER,
2962
+ call_type TEXT NOT NULL,
2963
+ line INTEGER NOT NULL
2964
+ );
2965
+ `;
2966
+ var REFS_INDEX_SQL = [
2967
+ "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
2968
+ "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
2969
+ "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
2970
+ "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
2971
+ ];
2972
+ var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
2973
+
2934
2974
  // src/codebase-index/writer-search-helpers.ts
2935
2975
  function normalizeSearchLimit(limit) {
2936
2976
  return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
@@ -3139,9 +3179,15 @@ var IndexStore = class _IndexStore {
3139
3179
  DROP TABLE IF EXISTS refs;
3140
3180
  `);
3141
3181
  this.db.exec("DROP TABLE IF EXISTS symbols_fts");
3142
- this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
3182
+ this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
3183
+ String(SCHEMA_VERSION),
3184
+ "version"
3185
+ );
3143
3186
  } else if (storedVersion === null) {
3144
- this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
3187
+ this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run(
3188
+ "version",
3189
+ String(SCHEMA_VERSION)
3190
+ );
3145
3191
  }
3146
3192
  this.db.exec(CORE_TABLES_SQL);
3147
3193
  for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
@@ -3158,7 +3204,9 @@ var IndexStore = class _IndexStore {
3158
3204
  );
3159
3205
  if (symbolCount !== ftsCount) {
3160
3206
  this.db.exec("DELETE FROM symbols_fts");
3161
- const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
3207
+ const rows = this.stmt(
3208
+ "SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
3209
+ ).all();
3162
3210
  bulkInsertFtsWithStatement(
3163
3211
  (sql) => this.stmt(sql),
3164
3212
  _IndexStore.MAX_SQL_VARS,
@@ -3380,7 +3428,9 @@ var IndexStore = class _IndexStore {
3380
3428
  const limitSql = limit !== void 0 ? " LIMIT ?" : "";
3381
3429
  const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
3382
3430
  const binds = limit !== void 0 ? [...values, limit] : values;
3383
- const rows = this.stmt(sql).all(...binds);
3431
+ const rows = this.stmt(sql).all(
3432
+ ...binds
3433
+ );
3384
3434
  return rows.map((row) => mapWriterSearchRow(row, filter?.lspKind));
3385
3435
  }
3386
3436
  /** Shared WHERE builder for {@link search} / empty-query ranked totals. */
@@ -3556,7 +3606,9 @@ var IndexStore = class _IndexStore {
3556
3606
  }
3557
3607
  setLastIndexed(ts2) {
3558
3608
  this.runWithRetry(() => {
3559
- this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
3609
+ this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
3610
+ String(ts2)
3611
+ );
3560
3612
  });
3561
3613
  }
3562
3614
  getMetadata(key) {
@@ -3667,7 +3719,9 @@ var IndexStore = class _IndexStore {
3667
3719
  this.stmt(
3668
3720
  `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
3669
3721
  ).run(...options.deleteForFiles);
3670
- this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
3722
+ this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(
3723
+ ...options.deleteForFiles
3724
+ );
3671
3725
  }
3672
3726
  const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
3673
3727
  let nextId = this.allocateSymbolIds(totalSymbols);
@@ -3711,11 +3765,7 @@ var IndexStore = class _IndexStore {
3711
3765
  this.ftsAvailable,
3712
3766
  ftsRows
3713
3767
  );
3714
- bulkInsertRefsWithStatement(
3715
- (sql) => this.stmt(sql),
3716
- _IndexStore.MAX_SQL_VARS,
3717
- refsToInsert
3718
- );
3768
+ bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
3719
3769
  const upsertStmt = this.stmt(
3720
3770
  `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
3721
3771
  VALUES (?, ?, ?, ?, ?)
@@ -3744,7 +3794,9 @@ var IndexStore = class _IndexStore {
3744
3794
  */
3745
3795
  deleteRefsForFile(file) {
3746
3796
  this.runWithRetry(() => {
3747
- this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
3797
+ this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
3798
+ file
3799
+ );
3748
3800
  });
3749
3801
  }
3750
3802
  /**
@@ -3899,9 +3951,7 @@ var IndexStore = class _IndexStore {
3899
3951
  * build the full symbol universe for the reachability scan.
3900
3952
  */
3901
3953
  getAllSymbols() {
3902
- return this.stmt(
3903
- "SELECT id, name, file, kind, line FROM symbols ORDER BY id"
3904
- ).all().map((r) => ({ ...r, kind: r.kind }));
3954
+ return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
3905
3955
  }
3906
3956
  /**
3907
3957
  * Returns every resolved reference (to_id IS NOT NULL). Used by
@@ -3913,6 +3963,25 @@ var IndexStore = class _IndexStore {
3913
3963
  "SELECT from_id AS fromId, to_id AS toId, call_type AS callType FROM refs WHERE to_id IS NOT NULL"
3914
3964
  ).all();
3915
3965
  }
3966
+ /**
3967
+ * Returns ALL import refs (including unresolved) with their source-file
3968
+ * path and resolved target id. Used by the dead-code scan's file-level
3969
+ * graph traversal to handle barrel-only entry points where no symbol
3970
+ * carries the ref.
3971
+ *
3972
+ * Refs whose `from_id` doesn't match a known symbol (e.g. pure-barrel
3973
+ * files with no declarations) will have `sourceFile === null`.
3974
+ */
3975
+ getAllImportRefs() {
3976
+ return this.stmt(
3977
+ `SELECT s.file AS sourceFile, r.to_name AS toName, r.to_id AS toId,
3978
+ r.call_type AS callType, r.line
3979
+ FROM refs r
3980
+ LEFT JOIN symbols s ON r.from_id = s.id
3981
+ WHERE r.call_type = 'import'
3982
+ ORDER BY r.line`
3983
+ ).all();
3984
+ }
3916
3985
  close() {
3917
3986
  this.stmtCache.clear();
3918
3987
  this.bm25Dirty = true;
@@ -5808,6 +5877,7 @@ var codebaseStatsTool = {
5808
5877
  };
5809
5878
 
5810
5879
  // src/codebase-index/dead-code-scan.ts
5880
+ init_languages();
5811
5881
  import * as fs12 from "node:fs";
5812
5882
  import * as path13 from "node:path";
5813
5883
  var deadCodeScanTool = {
@@ -5876,7 +5946,15 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
5876
5946
  if (rootPkg) {
5877
5947
  addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
5878
5948
  }
5879
- const workspaces = rootPkg ? extractWorkspaceGlobs(rootPkg, projectRoot) : [];
5949
+ let workspaces;
5950
+ if (rootPkg) {
5951
+ workspaces = extractWorkspaceGlobs(rootPkg, projectRoot);
5952
+ if (workspaces.length === 0) {
5953
+ workspaces = extractPnpmWorkspaceDirs(projectRoot);
5954
+ }
5955
+ } else {
5956
+ workspaces = [];
5957
+ }
5880
5958
  for (const wsDir of workspaces) {
5881
5959
  const pkgJsonPath = path13.join(wsDir, "package.json");
5882
5960
  const pkg = tryReadJson(pkgJsonPath);
@@ -5898,77 +5976,189 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
5898
5976
  }
5899
5977
  return [...entries];
5900
5978
  }
5979
+ var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
5980
+ var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path13.sep}${d}${path13.sep}`);
5981
+ function trySourceEquivalent(resolved) {
5982
+ resolved = resolved.replace(/[/\\]/g, path13.sep);
5983
+ for (const marker of BUILD_OUTPUT_DIR_NAMES) {
5984
+ const idx = resolved.indexOf(marker);
5985
+ if (idx === -1) continue;
5986
+ const base = resolved.replace(marker, `${path13.sep}src${path13.sep}`);
5987
+ const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
5988
+ if (candidate !== base && fs12.existsSync(candidate)) {
5989
+ return candidate;
5990
+ }
5991
+ const dtsStripped = base.replace(/\.d\.ts$/, "");
5992
+ const candidateDts = dtsStripped + ".ts";
5993
+ if (candidateDts !== base && candidateDts !== candidate && fs12.existsSync(candidateDts)) {
5994
+ return candidateDts;
5995
+ }
5996
+ const candidateNoExt = base + ".ts";
5997
+ if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs12.existsSync(candidateNoExt)) {
5998
+ return candidateNoExt;
5999
+ }
6000
+ }
6001
+ return null;
6002
+ }
6003
+ function tryAddEntryPath(pkgDir, rawPath, entries) {
6004
+ const resolved = resolveAgainst(pkgDir, rawPath);
6005
+ if (fs12.existsSync(resolved)) entries.add(resolved);
6006
+ const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
6007
+ if (tsResolved !== resolved && fs12.existsSync(tsResolved)) {
6008
+ entries.add(tsResolved);
6009
+ }
6010
+ const srcAlt = trySourceEquivalent(resolved);
6011
+ if (srcAlt) entries.add(srcAlt);
6012
+ }
5901
6013
  function addPkgJsonEntryPoints(pkgDir, pkg, entries) {
5902
6014
  if (typeof pkg.main === "string") {
5903
- const resolved = resolveAgainst(pkgDir, pkg.main);
5904
- if (fs12.existsSync(resolved)) entries.add(resolved);
5905
- const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
5906
- if (tsResolved !== resolved && fs12.existsSync(tsResolved)) {
5907
- entries.add(tsResolved);
5908
- }
6015
+ tryAddEntryPath(pkgDir, pkg.main, entries);
5909
6016
  }
5910
6017
  const bin = pkg.bin;
5911
6018
  if (typeof bin === "string") {
5912
- const resolved = resolveAgainst(pkgDir, bin);
5913
- if (fs12.existsSync(resolved)) entries.add(resolved);
6019
+ tryAddEntryPath(pkgDir, bin, entries);
5914
6020
  } else if (bin && typeof bin === "object") {
5915
6021
  for (const value of Object.values(bin)) {
5916
6022
  if (typeof value === "string") {
5917
- const resolved = resolveAgainst(pkgDir, value);
5918
- if (fs12.existsSync(resolved)) entries.add(resolved);
6023
+ tryAddEntryPath(pkgDir, value, entries);
5919
6024
  }
5920
6025
  }
5921
6026
  }
5922
6027
  for (const key of ["types", "typings"]) {
5923
6028
  if (typeof pkg[key] === "string") {
5924
- const resolved = resolveAgainst(pkgDir, pkg[key]);
5925
- if (fs12.existsSync(resolved)) entries.add(resolved);
6029
+ tryAddEntryPath(pkgDir, pkg[key], entries);
5926
6030
  }
5927
6031
  }
5928
6032
  const exports_ = pkg.exports;
5929
6033
  if (exports_ && typeof exports_ === "object") {
5930
6034
  for (const value of Object.values(exports_)) {
5931
6035
  if (typeof value === "string") {
5932
- const resolved = resolveAgainst(pkgDir, value);
5933
- if (fs12.existsSync(resolved)) entries.add(resolved);
6036
+ tryAddEntryPath(pkgDir, value, entries);
5934
6037
  } else if (value && typeof value === "object") {
5935
- for (const nested of Object.values(
5936
- value
5937
- )) {
6038
+ for (const nested of Object.values(value)) {
5938
6039
  if (typeof nested === "string") {
5939
- const resolved = resolveAgainst(pkgDir, nested);
5940
- if (fs12.existsSync(resolved)) entries.add(resolved);
6040
+ tryAddEntryPath(pkgDir, nested, entries);
5941
6041
  }
5942
6042
  }
5943
6043
  }
5944
6044
  }
5945
6045
  }
5946
6046
  }
6047
+ function expandGlobPattern(entry, projectRoot) {
6048
+ const dirs = [];
6049
+ if (entry.includes("*")) {
6050
+ const base = entry.replace(/\/\*+$/, "");
6051
+ const baseDir = path13.resolve(projectRoot, base);
6052
+ try {
6053
+ const children = fs12.readdirSync(baseDir, { withFileTypes: true });
6054
+ for (const child of children) {
6055
+ if (child.isDirectory()) {
6056
+ dirs.push(path13.join(baseDir, child.name));
6057
+ }
6058
+ }
6059
+ } catch {
6060
+ }
6061
+ } else {
6062
+ dirs.push(path13.resolve(projectRoot, entry));
6063
+ }
6064
+ return dirs;
6065
+ }
5947
6066
  function extractWorkspaceGlobs(pkg, projectRoot) {
5948
6067
  const dirs = [];
5949
6068
  const workspaces = pkg.workspaces;
5950
6069
  if (Array.isArray(workspaces)) {
5951
6070
  for (const entry of workspaces) {
5952
6071
  if (typeof entry === "string") {
5953
- if (entry.includes("*")) {
5954
- const base = entry.replace(/\/\*+$/, "");
5955
- const baseDir = path13.resolve(projectRoot, base);
5956
- try {
5957
- const children = fs12.readdirSync(baseDir, { withFileTypes: true });
5958
- for (const child of children) {
5959
- if (child.isDirectory()) {
5960
- dirs.push(path13.join(baseDir, child.name));
5961
- }
5962
- }
5963
- } catch {
6072
+ dirs.push(...expandGlobPattern(entry, projectRoot));
6073
+ }
6074
+ }
6075
+ }
6076
+ return dirs;
6077
+ }
6078
+ function extractPnpmWorkspaceDirs(projectRoot) {
6079
+ const yamlPath = path13.join(projectRoot, "pnpm-workspace.yaml");
6080
+ if (!fs12.existsSync(yamlPath)) return [];
6081
+ try {
6082
+ const content = fs12.readFileSync(yamlPath, "utf8");
6083
+ const dirs = [];
6084
+ let inPackages = false;
6085
+ const lines = content.split("\n");
6086
+ const itemRe = /^\s+-\s+"([^"]+)"|^\s+-\s+'([^']+)'|^\s+-\s+(\S+)/;
6087
+ for (const line of lines) {
6088
+ const trimmed = line.trim();
6089
+ if (/^packages\s*:\s*$/.test(trimmed)) {
6090
+ inPackages = true;
6091
+ continue;
6092
+ }
6093
+ if (inPackages && trimmed.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) {
6094
+ if (!trimmed.startsWith("-")) {
6095
+ inPackages = false;
6096
+ continue;
6097
+ }
6098
+ }
6099
+ if (inPackages) {
6100
+ const m = itemRe.exec(line);
6101
+ if (m) {
6102
+ const entry = m[1] ?? m[2] ?? m[3];
6103
+ if (entry) {
6104
+ dirs.push(...expandGlobPattern(entry, projectRoot));
5964
6105
  }
5965
- } else {
5966
- dirs.push(path13.resolve(projectRoot, entry));
5967
6106
  }
5968
6107
  }
5969
6108
  }
6109
+ return dirs;
6110
+ } catch {
6111
+ return [];
5970
6112
  }
5971
- return dirs;
6113
+ }
6114
+ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
6115
+ if (!moduleSpecifier.startsWith(".")) return [];
6116
+ const dir = path13.dirname(importerPath);
6117
+ const base = path13.resolve(dir, moduleSpecifier);
6118
+ const results = [];
6119
+ const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
6120
+ const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
6121
+ const candidates = skipBase ? [stripped] : [base];
6122
+ for (const candidate of candidates) {
6123
+ if (indexedFiles.has(candidate + ".ts")) results.push(candidate + ".ts");
6124
+ if (indexedFiles.has(candidate + ".tsx")) results.push(candidate + ".tsx");
6125
+ if (indexedFiles.has(candidate + ".js")) results.push(candidate + ".js");
6126
+ if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
6127
+ if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
6128
+ if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
6129
+ if (indexedFiles.has(path13.join(candidate, "index.ts")))
6130
+ results.push(path13.join(candidate, "index.ts"));
6131
+ if (indexedFiles.has(path13.join(candidate, "index.tsx")))
6132
+ results.push(path13.join(candidate, "index.tsx"));
6133
+ if (indexedFiles.has(path13.join(candidate, "index.js")))
6134
+ results.push(path13.join(candidate, "index.js"));
6135
+ if (indexedFiles.has(path13.join(candidate, "index.jsx")))
6136
+ results.push(path13.join(candidate, "index.jsx"));
6137
+ if (indexedFiles.has(path13.join(candidate, "index.mjs")))
6138
+ results.push(path13.join(candidate, "index.mjs"));
6139
+ if (indexedFiles.has(path13.join(candidate, "index.cjs")))
6140
+ results.push(path13.join(candidate, "index.cjs"));
6141
+ }
6142
+ return [...new Set(results)];
6143
+ }
6144
+ function parseNamedExportSymbols(matchText) {
6145
+ const braceStart = matchText.indexOf("{");
6146
+ if (braceStart === -1) return null;
6147
+ const braceEnd = matchText.indexOf("}", braceStart);
6148
+ if (braceEnd === -1) return null;
6149
+ const inner = matchText.slice(braceStart + 1, braceEnd);
6150
+ const symbols = [];
6151
+ for (const part of inner.split(",")) {
6152
+ let s = part.trim();
6153
+ if (!s) continue;
6154
+ s = s.replace(/^type\s+/, "");
6155
+ const asIdx = s.search(/\s+as\s+/);
6156
+ if (asIdx !== -1) {
6157
+ s = s.slice(0, asIdx).trim();
6158
+ }
6159
+ if (s) symbols.push(s);
6160
+ }
6161
+ return symbols;
5972
6162
  }
5973
6163
  function runDeadCodeScan(projectRoot, opts = {}) {
5974
6164
  const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
@@ -5990,12 +6180,71 @@ function runDeadCodeScan(projectRoot, opts = {}) {
5990
6180
  }
5991
6181
  const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
5992
6182
  const entryFileSet = new Set(discoveredFiles.map((f) => path13.resolve(f)));
6183
+ const indexedFiles = /* @__PURE__ */ new Set();
6184
+ for (const s of allSymbols) indexedFiles.add(s.file);
6185
+ for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
5993
6186
  const seedIds = /* @__PURE__ */ new Set();
5994
6187
  for (const s of allSymbols) {
5995
6188
  if (entryFileSet.has(s.file)) {
5996
6189
  seedIds.add(s.id);
5997
6190
  }
5998
6191
  }
6192
+ const fileToSymbolIds = /* @__PURE__ */ new Map();
6193
+ for (const s of allSymbols) {
6194
+ let byFile = fileToSymbolIds.get(s.file);
6195
+ if (!byFile) {
6196
+ byFile = [];
6197
+ fileToSymbolIds.set(s.file, byFile);
6198
+ }
6199
+ byFile.push(s.id);
6200
+ }
6201
+ const scannedBarrels = /* @__PURE__ */ new Set();
6202
+ const barrelWorkList = [...entryFileSet];
6203
+ while (barrelWorkList.length > 0) {
6204
+ const epFile = barrelWorkList.pop();
6205
+ if (scannedBarrels.has(epFile)) continue;
6206
+ scannedBarrels.add(epFile);
6207
+ try {
6208
+ const content = fs12.readFileSync(epFile, "utf8");
6209
+ const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
6210
+ const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
6211
+ let match;
6212
+ while ((match = reExportRe.exec(strippedContent)) !== null) {
6213
+ const moduleSpec = match[1];
6214
+ const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
6215
+ for (const rf of resolvedFiles) {
6216
+ const fileSyms = fileToSymbolIds.get(rf);
6217
+ if (fileSyms) {
6218
+ const namedSymbols = parseNamedExportSymbols(match[0]);
6219
+ if (namedSymbols) {
6220
+ const nameSet = new Set(namedSymbols);
6221
+ for (const sid of fileSyms) {
6222
+ const sym = symbolById.get(sid);
6223
+ if (sym && nameSet.has(sym.name)) seedIds.add(sid);
6224
+ }
6225
+ } else {
6226
+ for (const sid of fileSyms) seedIds.add(sid);
6227
+ }
6228
+ }
6229
+ if (!scannedBarrels.has(rf)) {
6230
+ barrelWorkList.push(rf);
6231
+ }
6232
+ }
6233
+ }
6234
+ } catch (err) {
6235
+ if (err instanceof Error && err.code !== "ENOENT") {
6236
+ console.warn(
6237
+ JSON.stringify({
6238
+ level: "warn",
6239
+ event: "dead_code_scan_barrel_read_failed",
6240
+ message: err.message,
6241
+ file: epFile,
6242
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6243
+ })
6244
+ );
6245
+ }
6246
+ }
6247
+ }
5999
6248
  const alive = new Set(seedIds);
6000
6249
  const frontier = [...seedIds];
6001
6250
  const visitedEdges = /* @__PURE__ */ new Set();
@@ -6029,8 +6278,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
6029
6278
  dead.push({
6030
6279
  name: s.name,
6031
6280
  kind: s.kind,
6032
- lang: "ts",
6033
- // populated from symbol file metadata
6281
+ lang: detectLang(s.file) ?? "ts",
6034
6282
  file: s.file,
6035
6283
  line: s.line,
6036
6284
  reason
@@ -6055,16 +6303,14 @@ function runDeadCodeScan(projectRoot, opts = {}) {
6055
6303
  deadFiles.push({
6056
6304
  file,
6057
6305
  symbolCount: syms.length,
6058
- lang: syms[0]?.kind ?? "ts"
6306
+ lang: detectLang(file) ?? "ts"
6059
6307
  });
6060
6308
  }
6061
6309
  }
6062
6310
  const deadPackages = [];
6063
6311
  const pkgEntries = findPackageEntries(projectRoot);
6064
6312
  for (const [pkgName, pkgDir] of pkgEntries) {
6065
- const pkgFiles = allSymbols.filter(
6066
- (s) => s.file.startsWith(pkgDir + path13.sep)
6067
- );
6313
+ const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path13.sep));
6068
6314
  if (pkgFiles.length === 0) continue;
6069
6315
  const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
6070
6316
  if (pkgUsed.length === 0) {
@@ -6105,7 +6351,10 @@ function findPackageEntries(projectRoot) {
6105
6351
  pkgMap.set(rootPkg.name, projectRoot);
6106
6352
  }
6107
6353
  if (rootPkg) {
6108
- const wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
6354
+ let wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
6355
+ if (wsDirs.length === 0) {
6356
+ wsDirs = extractPnpmWorkspaceDirs(projectRoot);
6357
+ }
6109
6358
  for (const wsDir of wsDirs) {
6110
6359
  const wsPkg = tryReadJson(path13.join(wsDir, "package.json"));
6111
6360
  if (wsPkg && typeof wsPkg.name === "string") {