@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.
package/dist/tool-tier.js CHANGED
@@ -5131,11 +5131,18 @@ async function parseSymbols(opts) {
5131
5131
  } else if (ts.isHeritageClause(node)) {
5132
5132
  for (const t of node.types) {
5133
5133
  const name = getTypeName(t.expression);
5134
- if (name) refs.push({ fromId: 0, toName: name, callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement", line: lineNum });
5134
+ if (name)
5135
+ refs.push({
5136
+ fromId: 0,
5137
+ toName: name,
5138
+ callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
5139
+ line: lineNum
5140
+ });
5135
5141
  }
5136
5142
  } else if (ts.isImportDeclaration(node)) {
5137
- const moduleName = getModuleName(node);
5138
- if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
5143
+ emitImportSpecifierRefs(node, refs, lineNum);
5144
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
5145
+ emitExportSpecifierRefs(node, refs, lineNum);
5139
5146
  }
5140
5147
  const scopeIdx = scopeParts.length;
5141
5148
  pushScopeName(node, scopeParts);
@@ -5151,11 +5158,6 @@ function getTypeName(name) {
5151
5158
  if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
5152
5159
  return "";
5153
5160
  }
5154
- function getModuleName(node) {
5155
- const moduleSpecifier = node.moduleSpecifier;
5156
- if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
5157
- return "";
5158
- }
5159
5161
  function deduplicateRefs(refs) {
5160
5162
  const seen = /* @__PURE__ */ new Set();
5161
5163
  return refs.filter((r) => {
@@ -5165,6 +5167,44 @@ function deduplicateRefs(refs) {
5165
5167
  return true;
5166
5168
  });
5167
5169
  }
5170
+ function getImportSpecifierName(spec) {
5171
+ return spec.propertyName?.text ?? spec.name.text;
5172
+ }
5173
+ function emitImportSpecifierRefs(node, refs, lineNum) {
5174
+ const clause = node.importClause;
5175
+ if (!clause) return;
5176
+ if (clause.name) {
5177
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
5178
+ }
5179
+ const bindings = clause.namedBindings;
5180
+ if (!bindings) return;
5181
+ if (ts.isNamedImports(bindings)) {
5182
+ for (const element of bindings.elements) {
5183
+ refs.push({
5184
+ fromId: 0,
5185
+ toName: getImportSpecifierName(element),
5186
+ callType: "import",
5187
+ line: lineNum
5188
+ });
5189
+ }
5190
+ } else if (ts.isNamespaceImport(bindings)) {
5191
+ refs.push({ fromId: 0, toName: bindings.name.text, callType: "import", line: lineNum });
5192
+ }
5193
+ }
5194
+ function emitExportSpecifierRefs(node, refs, lineNum) {
5195
+ const clause = node.exportClause;
5196
+ if (clause && ts.isNamespaceExport(clause)) {
5197
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
5198
+ return;
5199
+ }
5200
+ if (clause && ts.isNamedExports(clause)) {
5201
+ for (const element of clause.elements) {
5202
+ const originalName = element.propertyName?.text ?? element.name.text;
5203
+ refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
5204
+ }
5205
+ return;
5206
+ }
5207
+ }
5168
5208
  var ts, tsLoad, kindMapCache;
5169
5209
  var init_ts_parser = __esm({
5170
5210
  "src/codebase-index/ts-parser.ts"() {
@@ -8454,9 +8494,50 @@ import { createReadStream } from "node:fs";
8454
8494
  import * as fs7 from "node:fs/promises";
8455
8495
  import * as path10 from "node:path";
8456
8496
  import { atomicWrite, ulid } from "@wrongstack/core/utils";
8497
+ var ARTIFACT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
8498
+ var sweptRoots = /* @__PURE__ */ new Set();
8499
+ function sweepOldArtifacts(root) {
8500
+ if (sweptRoots.has(root)) return;
8501
+ sweptRoots.add(root);
8502
+ void (async () => {
8503
+ const cutoff = Date.now() - ARTIFACT_RETENTION_MS;
8504
+ let sessionDirs;
8505
+ try {
8506
+ const entries = await fs7.readdir(root, { withFileTypes: true });
8507
+ sessionDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
8508
+ } catch {
8509
+ return;
8510
+ }
8511
+ for (const sessionDir of sessionDirs) {
8512
+ const dir = path10.join(root, sessionDir);
8513
+ let names;
8514
+ try {
8515
+ names = await fs7.readdir(dir);
8516
+ } catch {
8517
+ continue;
8518
+ }
8519
+ let removed = 0;
8520
+ for (const name of names) {
8521
+ const target = path10.join(dir, name);
8522
+ try {
8523
+ const stat17 = await fs7.stat(target);
8524
+ if (stat17.isFile() && stat17.mtimeMs < cutoff) {
8525
+ await fs7.rm(target, { force: true });
8526
+ removed++;
8527
+ }
8528
+ } catch {
8529
+ }
8530
+ }
8531
+ if (removed === names.length && names.length > 0) {
8532
+ await fs7.rmdir(dir).catch(() => void 0);
8533
+ }
8534
+ }
8535
+ })();
8536
+ }
8457
8537
  var BrowserArtifactStore = class {
8458
8538
  constructor(root) {
8459
8539
  this.root = root;
8540
+ sweepOldArtifacts(root);
8460
8541
  }
8461
8542
  root;
8462
8543
  async write(sessionId, kind, extension, mimeType, content) {
@@ -9886,42 +9967,6 @@ import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
9886
9967
  import * as fs14 from "node:fs";
9887
9968
  import * as path21 from "node:path";
9888
9969
 
9889
- // src/codebase-index/schema.ts
9890
- var SCHEMA_VERSION = 3;
9891
-
9892
- // src/codebase-index/lsp-kind.ts
9893
- function lspKindToInternalKind(k) {
9894
- switch (k) {
9895
- case 5 /* Class */:
9896
- return "class";
9897
- case 6 /* Method */:
9898
- return "method";
9899
- case 7 /* Property */:
9900
- case 8 /* Field */:
9901
- return "property";
9902
- case 9 /* Constructor */:
9903
- return "class";
9904
- case 10 /* Enum */:
9905
- return "enum";
9906
- case 11 /* Interface */:
9907
- return "interface";
9908
- case 12 /* Function */:
9909
- return "function";
9910
- case 13 /* Variable */:
9911
- return "var";
9912
- case 14 /* Constant */:
9913
- return "const";
9914
- case 22 /* EnumMember */:
9915
- return "enum";
9916
- case 26 /* TypeParameter */:
9917
- return "type";
9918
- case 3 /* Namespace */:
9919
- return "namespace";
9920
- default:
9921
- return null;
9922
- }
9923
- }
9924
-
9925
9970
  // src/codebase-index/bm25.ts
9926
9971
  var K1 = 1.5;
9927
9972
  var B = 0.75;
@@ -10011,6 +10056,42 @@ var Bm25Index = class {
10011
10056
  }
10012
10057
  };
10013
10058
 
10059
+ // src/codebase-index/lsp-kind.ts
10060
+ function lspKindToInternalKind(k) {
10061
+ switch (k) {
10062
+ case 5 /* Class */:
10063
+ return "class";
10064
+ case 6 /* Method */:
10065
+ return "method";
10066
+ case 7 /* Property */:
10067
+ case 8 /* Field */:
10068
+ return "property";
10069
+ case 9 /* Constructor */:
10070
+ return "class";
10071
+ case 10 /* Enum */:
10072
+ return "enum";
10073
+ case 11 /* Interface */:
10074
+ return "interface";
10075
+ case 12 /* Function */:
10076
+ return "function";
10077
+ case 13 /* Variable */:
10078
+ return "var";
10079
+ case 14 /* Constant */:
10080
+ return "const";
10081
+ case 22 /* EnumMember */:
10082
+ return "enum";
10083
+ case 26 /* TypeParameter */:
10084
+ return "type";
10085
+ case 3 /* Namespace */:
10086
+ return "namespace";
10087
+ default:
10088
+ return null;
10089
+ }
10090
+ }
10091
+
10092
+ // src/codebase-index/schema.ts
10093
+ var SCHEMA_VERSION = 3;
10094
+
10014
10095
  // src/codebase-index/sqlite-runtime.ts
10015
10096
  import { createRequire } from "node:module";
10016
10097
  import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
@@ -10078,96 +10159,6 @@ function runSqliteWithRetry(fn) {
10078
10159
  throw lastError;
10079
10160
  }
10080
10161
 
10081
- // src/codebase-index/writer-helpers.ts
10082
- import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
10083
- function escapeLike(value) {
10084
- return value.replace(/[\\%_]/g, (char) => `\\${char}`);
10085
- }
10086
- function assignRefsToSymbols(refs, symbols) {
10087
- if (refs.length === 0 || symbols.length === 0) return [];
10088
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
10089
- const seen = /* @__PURE__ */ new Set();
10090
- const assigned = [];
10091
- for (const ref of refs) {
10092
- let owner2;
10093
- for (const symbol of ordered) {
10094
- if (symbol.line > ref.line) break;
10095
- owner2 = symbol;
10096
- }
10097
- if (!owner2 && ref.callType === "import") owner2 = ordered[0];
10098
- if (!owner2 || owner2.id <= 0) continue;
10099
- const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
10100
- if (seen.has(key)) continue;
10101
- seen.add(key);
10102
- assigned.push({ ...ref, fromId: owner2.id });
10103
- }
10104
- return assigned;
10105
- }
10106
- function resolveIndexDir(projectRoot, override) {
10107
- return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
10108
- }
10109
- function codebaseIndexDirOverride(ctx) {
10110
- const v = ctx.meta?.["codebaseIndexDir"];
10111
- return typeof v === "string" ? v : void 0;
10112
- }
10113
-
10114
- // src/codebase-index/writer-schema.ts
10115
- var METADATA_TABLE_SQL = `
10116
- CREATE TABLE IF NOT EXISTS metadata (
10117
- key TEXT PRIMARY KEY,
10118
- value TEXT NOT NULL
10119
- );
10120
- `;
10121
- var CORE_TABLES_SQL = `
10122
- CREATE TABLE IF NOT EXISTS files (
10123
- file TEXT PRIMARY KEY,
10124
- lang TEXT NOT NULL,
10125
- mtime_ms INTEGER NOT NULL,
10126
- symbol_count INTEGER NOT NULL DEFAULT 0,
10127
- last_indexed INTEGER NOT NULL
10128
- );
10129
- CREATE TABLE IF NOT EXISTS symbols (
10130
- id INTEGER PRIMARY KEY,
10131
- lang TEXT NOT NULL,
10132
- kind TEXT NOT NULL,
10133
- name TEXT NOT NULL,
10134
- file TEXT NOT NULL,
10135
- line INTEGER NOT NULL,
10136
- col INTEGER NOT NULL,
10137
- signature TEXT NOT NULL DEFAULT '',
10138
- doc_comment TEXT NOT NULL DEFAULT '',
10139
- scope TEXT NOT NULL DEFAULT '',
10140
- text TEXT NOT NULL DEFAULT '',
10141
- file_fk TEXT NOT NULL
10142
- );
10143
- `;
10144
- var SYMBOL_INDEX_SQL = [
10145
- "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
10146
- "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
10147
- "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
10148
- "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
10149
- "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
10150
- "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
10151
- "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
10152
- ];
10153
- var REFS_TABLE_SQL = `
10154
- CREATE TABLE IF NOT EXISTS refs (
10155
- id INTEGER PRIMARY KEY,
10156
- from_id INTEGER NOT NULL,
10157
- to_name TEXT NOT NULL,
10158
- to_id INTEGER,
10159
- call_type TEXT NOT NULL,
10160
- line INTEGER NOT NULL
10161
- );
10162
- `;
10163
- var REFS_INDEX_SQL = [
10164
- "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
10165
- "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
10166
- "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
10167
- "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
10168
- ];
10169
- var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
10170
-
10171
10162
  // src/codebase-index/writer-admin.ts
10172
10163
  import * as fs13 from "node:fs";
10173
10164
  import * as path19 from "node:path";
@@ -10640,6 +10631,39 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
10640
10631
  return { nodes, edges };
10641
10632
  }
10642
10633
 
10634
+ // src/codebase-index/writer-helpers.ts
10635
+ import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
10636
+ function escapeLike(value) {
10637
+ return value.replace(/[\\%_]/g, (char) => `\\${char}`);
10638
+ }
10639
+ function assignRefsToSymbols(refs, symbols) {
10640
+ if (refs.length === 0 || symbols.length === 0) return [];
10641
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
10642
+ const seen = /* @__PURE__ */ new Set();
10643
+ const assigned = [];
10644
+ for (const ref of refs) {
10645
+ let owner2;
10646
+ for (const symbol of ordered) {
10647
+ if (symbol.line > ref.line) break;
10648
+ owner2 = symbol;
10649
+ }
10650
+ if (!owner2 && ref.callType === "import") owner2 = ordered[0];
10651
+ if (!owner2 || owner2.id <= 0) continue;
10652
+ const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
10653
+ if (seen.has(key)) continue;
10654
+ seen.add(key);
10655
+ assigned.push({ ...ref, fromId: owner2.id });
10656
+ }
10657
+ return assigned;
10658
+ }
10659
+ function resolveIndexDir(projectRoot, override) {
10660
+ return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
10661
+ }
10662
+ function codebaseIndexDirOverride(ctx) {
10663
+ const v = ctx.meta?.["codebaseIndexDir"];
10664
+ return typeof v === "string" ? v : void 0;
10665
+ }
10666
+
10643
10667
  // src/codebase-index/writer-pragmas.ts
10644
10668
  import { sqliteCachePragmas } from "@wrongstack/core/utils";
10645
10669
  function applyIndexStorePragmas(db) {
@@ -10658,6 +10682,63 @@ function applyIndexStorePragmas(db) {
10658
10682
  }
10659
10683
  }
10660
10684
 
10685
+ // src/codebase-index/writer-schema.ts
10686
+ var METADATA_TABLE_SQL = `
10687
+ CREATE TABLE IF NOT EXISTS metadata (
10688
+ key TEXT PRIMARY KEY,
10689
+ value TEXT NOT NULL
10690
+ );
10691
+ `;
10692
+ var CORE_TABLES_SQL = `
10693
+ CREATE TABLE IF NOT EXISTS files (
10694
+ file TEXT PRIMARY KEY,
10695
+ lang TEXT NOT NULL,
10696
+ mtime_ms INTEGER NOT NULL,
10697
+ symbol_count INTEGER NOT NULL DEFAULT 0,
10698
+ last_indexed INTEGER NOT NULL
10699
+ );
10700
+ CREATE TABLE IF NOT EXISTS symbols (
10701
+ id INTEGER PRIMARY KEY,
10702
+ lang TEXT NOT NULL,
10703
+ kind TEXT NOT NULL,
10704
+ name TEXT NOT NULL,
10705
+ file TEXT NOT NULL,
10706
+ line INTEGER NOT NULL,
10707
+ col INTEGER NOT NULL,
10708
+ signature TEXT NOT NULL DEFAULT '',
10709
+ doc_comment TEXT NOT NULL DEFAULT '',
10710
+ scope TEXT NOT NULL DEFAULT '',
10711
+ text TEXT NOT NULL DEFAULT '',
10712
+ file_fk TEXT NOT NULL
10713
+ );
10714
+ `;
10715
+ var SYMBOL_INDEX_SQL = [
10716
+ "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
10717
+ "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
10718
+ "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
10719
+ "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
10720
+ "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
10721
+ "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
10722
+ "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
10723
+ ];
10724
+ var REFS_TABLE_SQL = `
10725
+ CREATE TABLE IF NOT EXISTS refs (
10726
+ id INTEGER PRIMARY KEY,
10727
+ from_id INTEGER NOT NULL,
10728
+ to_name TEXT NOT NULL,
10729
+ to_id INTEGER,
10730
+ call_type TEXT NOT NULL,
10731
+ line INTEGER NOT NULL
10732
+ );
10733
+ `;
10734
+ var REFS_INDEX_SQL = [
10735
+ "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
10736
+ "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
10737
+ "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
10738
+ "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
10739
+ ];
10740
+ var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
10741
+
10661
10742
  // src/codebase-index/writer-search-helpers.ts
10662
10743
  function normalizeSearchLimit(limit) {
10663
10744
  return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
@@ -10866,9 +10947,15 @@ var IndexStore = class _IndexStore {
10866
10947
  DROP TABLE IF EXISTS refs;
10867
10948
  `);
10868
10949
  this.db.exec("DROP TABLE IF EXISTS symbols_fts");
10869
- this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
10950
+ this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
10951
+ String(SCHEMA_VERSION),
10952
+ "version"
10953
+ );
10870
10954
  } else if (storedVersion === null) {
10871
- this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
10955
+ this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run(
10956
+ "version",
10957
+ String(SCHEMA_VERSION)
10958
+ );
10872
10959
  }
10873
10960
  this.db.exec(CORE_TABLES_SQL);
10874
10961
  for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
@@ -10885,7 +10972,9 @@ var IndexStore = class _IndexStore {
10885
10972
  );
10886
10973
  if (symbolCount !== ftsCount) {
10887
10974
  this.db.exec("DELETE FROM symbols_fts");
10888
- const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
10975
+ const rows = this.stmt(
10976
+ "SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
10977
+ ).all();
10889
10978
  bulkInsertFtsWithStatement(
10890
10979
  (sql) => this.stmt(sql),
10891
10980
  _IndexStore.MAX_SQL_VARS,
@@ -11107,7 +11196,9 @@ var IndexStore = class _IndexStore {
11107
11196
  const limitSql = limit !== void 0 ? " LIMIT ?" : "";
11108
11197
  const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
11109
11198
  const binds = limit !== void 0 ? [...values, limit] : values;
11110
- const rows = this.stmt(sql).all(...binds);
11199
+ const rows = this.stmt(sql).all(
11200
+ ...binds
11201
+ );
11111
11202
  return rows.map((row) => mapWriterSearchRow(row, filter?.lspKind));
11112
11203
  }
11113
11204
  /** Shared WHERE builder for {@link search} / empty-query ranked totals. */
@@ -11283,7 +11374,9 @@ var IndexStore = class _IndexStore {
11283
11374
  }
11284
11375
  setLastIndexed(ts2) {
11285
11376
  this.runWithRetry(() => {
11286
- this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
11377
+ this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
11378
+ String(ts2)
11379
+ );
11287
11380
  });
11288
11381
  }
11289
11382
  getMetadata(key) {
@@ -11394,7 +11487,9 @@ var IndexStore = class _IndexStore {
11394
11487
  this.stmt(
11395
11488
  `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
11396
11489
  ).run(...options.deleteForFiles);
11397
- this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
11490
+ this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(
11491
+ ...options.deleteForFiles
11492
+ );
11398
11493
  }
11399
11494
  const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
11400
11495
  let nextId = this.allocateSymbolIds(totalSymbols);
@@ -11438,11 +11533,7 @@ var IndexStore = class _IndexStore {
11438
11533
  this.ftsAvailable,
11439
11534
  ftsRows
11440
11535
  );
11441
- bulkInsertRefsWithStatement(
11442
- (sql) => this.stmt(sql),
11443
- _IndexStore.MAX_SQL_VARS,
11444
- refsToInsert
11445
- );
11536
+ bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
11446
11537
  const upsertStmt = this.stmt(
11447
11538
  `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
11448
11539
  VALUES (?, ?, ?, ?, ?)
@@ -11471,7 +11562,9 @@ var IndexStore = class _IndexStore {
11471
11562
  */
11472
11563
  deleteRefsForFile(file) {
11473
11564
  this.runWithRetry(() => {
11474
- this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
11565
+ this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
11566
+ file
11567
+ );
11475
11568
  });
11476
11569
  }
11477
11570
  /**
@@ -11626,9 +11719,7 @@ var IndexStore = class _IndexStore {
11626
11719
  * build the full symbol universe for the reachability scan.
11627
11720
  */
11628
11721
  getAllSymbols() {
11629
- return this.stmt(
11630
- "SELECT id, name, file, kind, line FROM symbols ORDER BY id"
11631
- ).all().map((r) => ({ ...r, kind: r.kind }));
11722
+ return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
11632
11723
  }
11633
11724
  /**
11634
11725
  * Returns every resolved reference (to_id IS NOT NULL). Used by
@@ -11640,6 +11731,25 @@ var IndexStore = class _IndexStore {
11640
11731
  "SELECT from_id AS fromId, to_id AS toId, call_type AS callType FROM refs WHERE to_id IS NOT NULL"
11641
11732
  ).all();
11642
11733
  }
11734
+ /**
11735
+ * Returns ALL import refs (including unresolved) with their source-file
11736
+ * path and resolved target id. Used by the dead-code scan's file-level
11737
+ * graph traversal to handle barrel-only entry points where no symbol
11738
+ * carries the ref.
11739
+ *
11740
+ * Refs whose `from_id` doesn't match a known symbol (e.g. pure-barrel
11741
+ * files with no declarations) will have `sourceFile === null`.
11742
+ */
11743
+ getAllImportRefs() {
11744
+ return this.stmt(
11745
+ `SELECT s.file AS sourceFile, r.to_name AS toName, r.to_id AS toId,
11746
+ r.call_type AS callType, r.line
11747
+ FROM refs r
11748
+ LEFT JOIN symbols s ON r.from_id = s.id
11749
+ WHERE r.call_type = 'import'
11750
+ ORDER BY r.line`
11751
+ ).all();
11752
+ }
11643
11753
  close() {
11644
11754
  this.stmtCache.clear();
11645
11755
  this.bm25Dirty = true;
@@ -13349,6 +13459,7 @@ var codebaseStatsTool = {
13349
13459
  };
13350
13460
 
13351
13461
  // src/codebase-index/dead-code-scan.ts
13462
+ init_languages2();
13352
13463
  import * as fs19 from "node:fs";
13353
13464
  import * as path24 from "node:path";
13354
13465
  var deadCodeScanTool = {
@@ -13417,7 +13528,15 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
13417
13528
  if (rootPkg) {
13418
13529
  addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
13419
13530
  }
13420
- const workspaces = rootPkg ? extractWorkspaceGlobs(rootPkg, projectRoot) : [];
13531
+ let workspaces;
13532
+ if (rootPkg) {
13533
+ workspaces = extractWorkspaceGlobs(rootPkg, projectRoot);
13534
+ if (workspaces.length === 0) {
13535
+ workspaces = extractPnpmWorkspaceDirs(projectRoot);
13536
+ }
13537
+ } else {
13538
+ workspaces = [];
13539
+ }
13421
13540
  for (const wsDir of workspaces) {
13422
13541
  const pkgJsonPath = path24.join(wsDir, "package.json");
13423
13542
  const pkg = tryReadJson(pkgJsonPath);
@@ -13439,77 +13558,189 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
13439
13558
  }
13440
13559
  return [...entries];
13441
13560
  }
13561
+ var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
13562
+ var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path24.sep}${d}${path24.sep}`);
13563
+ function trySourceEquivalent(resolved) {
13564
+ resolved = resolved.replace(/[/\\]/g, path24.sep);
13565
+ for (const marker of BUILD_OUTPUT_DIR_NAMES) {
13566
+ const idx = resolved.indexOf(marker);
13567
+ if (idx === -1) continue;
13568
+ const base = resolved.replace(marker, `${path24.sep}src${path24.sep}`);
13569
+ const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
13570
+ if (candidate !== base && fs19.existsSync(candidate)) {
13571
+ return candidate;
13572
+ }
13573
+ const dtsStripped = base.replace(/\.d\.ts$/, "");
13574
+ const candidateDts = dtsStripped + ".ts";
13575
+ if (candidateDts !== base && candidateDts !== candidate && fs19.existsSync(candidateDts)) {
13576
+ return candidateDts;
13577
+ }
13578
+ const candidateNoExt = base + ".ts";
13579
+ if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs19.existsSync(candidateNoExt)) {
13580
+ return candidateNoExt;
13581
+ }
13582
+ }
13583
+ return null;
13584
+ }
13585
+ function tryAddEntryPath(pkgDir, rawPath, entries) {
13586
+ const resolved = resolveAgainst(pkgDir, rawPath);
13587
+ if (fs19.existsSync(resolved)) entries.add(resolved);
13588
+ const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
13589
+ if (tsResolved !== resolved && fs19.existsSync(tsResolved)) {
13590
+ entries.add(tsResolved);
13591
+ }
13592
+ const srcAlt = trySourceEquivalent(resolved);
13593
+ if (srcAlt) entries.add(srcAlt);
13594
+ }
13442
13595
  function addPkgJsonEntryPoints(pkgDir, pkg, entries) {
13443
13596
  if (typeof pkg.main === "string") {
13444
- const resolved = resolveAgainst(pkgDir, pkg.main);
13445
- if (fs19.existsSync(resolved)) entries.add(resolved);
13446
- const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
13447
- if (tsResolved !== resolved && fs19.existsSync(tsResolved)) {
13448
- entries.add(tsResolved);
13449
- }
13597
+ tryAddEntryPath(pkgDir, pkg.main, entries);
13450
13598
  }
13451
13599
  const bin = pkg.bin;
13452
13600
  if (typeof bin === "string") {
13453
- const resolved = resolveAgainst(pkgDir, bin);
13454
- if (fs19.existsSync(resolved)) entries.add(resolved);
13601
+ tryAddEntryPath(pkgDir, bin, entries);
13455
13602
  } else if (bin && typeof bin === "object") {
13456
13603
  for (const value of Object.values(bin)) {
13457
13604
  if (typeof value === "string") {
13458
- const resolved = resolveAgainst(pkgDir, value);
13459
- if (fs19.existsSync(resolved)) entries.add(resolved);
13605
+ tryAddEntryPath(pkgDir, value, entries);
13460
13606
  }
13461
13607
  }
13462
13608
  }
13463
13609
  for (const key of ["types", "typings"]) {
13464
13610
  if (typeof pkg[key] === "string") {
13465
- const resolved = resolveAgainst(pkgDir, pkg[key]);
13466
- if (fs19.existsSync(resolved)) entries.add(resolved);
13611
+ tryAddEntryPath(pkgDir, pkg[key], entries);
13467
13612
  }
13468
13613
  }
13469
13614
  const exports_ = pkg.exports;
13470
13615
  if (exports_ && typeof exports_ === "object") {
13471
13616
  for (const value of Object.values(exports_)) {
13472
13617
  if (typeof value === "string") {
13473
- const resolved = resolveAgainst(pkgDir, value);
13474
- if (fs19.existsSync(resolved)) entries.add(resolved);
13618
+ tryAddEntryPath(pkgDir, value, entries);
13475
13619
  } else if (value && typeof value === "object") {
13476
- for (const nested of Object.values(
13477
- value
13478
- )) {
13620
+ for (const nested of Object.values(value)) {
13479
13621
  if (typeof nested === "string") {
13480
- const resolved = resolveAgainst(pkgDir, nested);
13481
- if (fs19.existsSync(resolved)) entries.add(resolved);
13622
+ tryAddEntryPath(pkgDir, nested, entries);
13482
13623
  }
13483
13624
  }
13484
13625
  }
13485
13626
  }
13486
13627
  }
13487
13628
  }
13629
+ function expandGlobPattern(entry, projectRoot) {
13630
+ const dirs = [];
13631
+ if (entry.includes("*")) {
13632
+ const base = entry.replace(/\/\*+$/, "");
13633
+ const baseDir = path24.resolve(projectRoot, base);
13634
+ try {
13635
+ const children = fs19.readdirSync(baseDir, { withFileTypes: true });
13636
+ for (const child of children) {
13637
+ if (child.isDirectory()) {
13638
+ dirs.push(path24.join(baseDir, child.name));
13639
+ }
13640
+ }
13641
+ } catch {
13642
+ }
13643
+ } else {
13644
+ dirs.push(path24.resolve(projectRoot, entry));
13645
+ }
13646
+ return dirs;
13647
+ }
13488
13648
  function extractWorkspaceGlobs(pkg, projectRoot) {
13489
13649
  const dirs = [];
13490
13650
  const workspaces = pkg.workspaces;
13491
13651
  if (Array.isArray(workspaces)) {
13492
13652
  for (const entry of workspaces) {
13493
13653
  if (typeof entry === "string") {
13494
- if (entry.includes("*")) {
13495
- const base = entry.replace(/\/\*+$/, "");
13496
- const baseDir = path24.resolve(projectRoot, base);
13497
- try {
13498
- const children = fs19.readdirSync(baseDir, { withFileTypes: true });
13499
- for (const child of children) {
13500
- if (child.isDirectory()) {
13501
- dirs.push(path24.join(baseDir, child.name));
13502
- }
13503
- }
13504
- } catch {
13654
+ dirs.push(...expandGlobPattern(entry, projectRoot));
13655
+ }
13656
+ }
13657
+ }
13658
+ return dirs;
13659
+ }
13660
+ function extractPnpmWorkspaceDirs(projectRoot) {
13661
+ const yamlPath = path24.join(projectRoot, "pnpm-workspace.yaml");
13662
+ if (!fs19.existsSync(yamlPath)) return [];
13663
+ try {
13664
+ const content = fs19.readFileSync(yamlPath, "utf8");
13665
+ const dirs = [];
13666
+ let inPackages = false;
13667
+ const lines = content.split("\n");
13668
+ const itemRe = /^\s+-\s+"([^"]+)"|^\s+-\s+'([^']+)'|^\s+-\s+(\S+)/;
13669
+ for (const line of lines) {
13670
+ const trimmed = line.trim();
13671
+ if (/^packages\s*:\s*$/.test(trimmed)) {
13672
+ inPackages = true;
13673
+ continue;
13674
+ }
13675
+ if (inPackages && trimmed.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) {
13676
+ if (!trimmed.startsWith("-")) {
13677
+ inPackages = false;
13678
+ continue;
13679
+ }
13680
+ }
13681
+ if (inPackages) {
13682
+ const m = itemRe.exec(line);
13683
+ if (m) {
13684
+ const entry = m[1] ?? m[2] ?? m[3];
13685
+ if (entry) {
13686
+ dirs.push(...expandGlobPattern(entry, projectRoot));
13505
13687
  }
13506
- } else {
13507
- dirs.push(path24.resolve(projectRoot, entry));
13508
13688
  }
13509
13689
  }
13510
13690
  }
13691
+ return dirs;
13692
+ } catch {
13693
+ return [];
13694
+ }
13695
+ }
13696
+ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
13697
+ if (!moduleSpecifier.startsWith(".")) return [];
13698
+ const dir = path24.dirname(importerPath);
13699
+ const base = path24.resolve(dir, moduleSpecifier);
13700
+ const results = [];
13701
+ const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
13702
+ const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
13703
+ const candidates = skipBase ? [stripped] : [base];
13704
+ for (const candidate of candidates) {
13705
+ if (indexedFiles.has(candidate + ".ts")) results.push(candidate + ".ts");
13706
+ if (indexedFiles.has(candidate + ".tsx")) results.push(candidate + ".tsx");
13707
+ if (indexedFiles.has(candidate + ".js")) results.push(candidate + ".js");
13708
+ if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
13709
+ if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
13710
+ if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
13711
+ if (indexedFiles.has(path24.join(candidate, "index.ts")))
13712
+ results.push(path24.join(candidate, "index.ts"));
13713
+ if (indexedFiles.has(path24.join(candidate, "index.tsx")))
13714
+ results.push(path24.join(candidate, "index.tsx"));
13715
+ if (indexedFiles.has(path24.join(candidate, "index.js")))
13716
+ results.push(path24.join(candidate, "index.js"));
13717
+ if (indexedFiles.has(path24.join(candidate, "index.jsx")))
13718
+ results.push(path24.join(candidate, "index.jsx"));
13719
+ if (indexedFiles.has(path24.join(candidate, "index.mjs")))
13720
+ results.push(path24.join(candidate, "index.mjs"));
13721
+ if (indexedFiles.has(path24.join(candidate, "index.cjs")))
13722
+ results.push(path24.join(candidate, "index.cjs"));
13723
+ }
13724
+ return [...new Set(results)];
13725
+ }
13726
+ function parseNamedExportSymbols(matchText) {
13727
+ const braceStart = matchText.indexOf("{");
13728
+ if (braceStart === -1) return null;
13729
+ const braceEnd = matchText.indexOf("}", braceStart);
13730
+ if (braceEnd === -1) return null;
13731
+ const inner = matchText.slice(braceStart + 1, braceEnd);
13732
+ const symbols = [];
13733
+ for (const part of inner.split(",")) {
13734
+ let s = part.trim();
13735
+ if (!s) continue;
13736
+ s = s.replace(/^type\s+/, "");
13737
+ const asIdx = s.search(/\s+as\s+/);
13738
+ if (asIdx !== -1) {
13739
+ s = s.slice(0, asIdx).trim();
13740
+ }
13741
+ if (s) symbols.push(s);
13511
13742
  }
13512
- return dirs;
13743
+ return symbols;
13513
13744
  }
13514
13745
  function runDeadCodeScan(projectRoot, opts = {}) {
13515
13746
  const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
@@ -13531,12 +13762,71 @@ function runDeadCodeScan(projectRoot, opts = {}) {
13531
13762
  }
13532
13763
  const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
13533
13764
  const entryFileSet = new Set(discoveredFiles.map((f) => path24.resolve(f)));
13765
+ const indexedFiles = /* @__PURE__ */ new Set();
13766
+ for (const s of allSymbols) indexedFiles.add(s.file);
13767
+ for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
13534
13768
  const seedIds = /* @__PURE__ */ new Set();
13535
13769
  for (const s of allSymbols) {
13536
13770
  if (entryFileSet.has(s.file)) {
13537
13771
  seedIds.add(s.id);
13538
13772
  }
13539
13773
  }
13774
+ const fileToSymbolIds = /* @__PURE__ */ new Map();
13775
+ for (const s of allSymbols) {
13776
+ let byFile = fileToSymbolIds.get(s.file);
13777
+ if (!byFile) {
13778
+ byFile = [];
13779
+ fileToSymbolIds.set(s.file, byFile);
13780
+ }
13781
+ byFile.push(s.id);
13782
+ }
13783
+ const scannedBarrels = /* @__PURE__ */ new Set();
13784
+ const barrelWorkList = [...entryFileSet];
13785
+ while (barrelWorkList.length > 0) {
13786
+ const epFile = barrelWorkList.pop();
13787
+ if (scannedBarrels.has(epFile)) continue;
13788
+ scannedBarrels.add(epFile);
13789
+ try {
13790
+ const content = fs19.readFileSync(epFile, "utf8");
13791
+ const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
13792
+ const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
13793
+ let match;
13794
+ while ((match = reExportRe.exec(strippedContent)) !== null) {
13795
+ const moduleSpec = match[1];
13796
+ const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
13797
+ for (const rf of resolvedFiles) {
13798
+ const fileSyms = fileToSymbolIds.get(rf);
13799
+ if (fileSyms) {
13800
+ const namedSymbols = parseNamedExportSymbols(match[0]);
13801
+ if (namedSymbols) {
13802
+ const nameSet = new Set(namedSymbols);
13803
+ for (const sid of fileSyms) {
13804
+ const sym = symbolById.get(sid);
13805
+ if (sym && nameSet.has(sym.name)) seedIds.add(sid);
13806
+ }
13807
+ } else {
13808
+ for (const sid of fileSyms) seedIds.add(sid);
13809
+ }
13810
+ }
13811
+ if (!scannedBarrels.has(rf)) {
13812
+ barrelWorkList.push(rf);
13813
+ }
13814
+ }
13815
+ }
13816
+ } catch (err) {
13817
+ if (err instanceof Error && err.code !== "ENOENT") {
13818
+ console.warn(
13819
+ JSON.stringify({
13820
+ level: "warn",
13821
+ event: "dead_code_scan_barrel_read_failed",
13822
+ message: err.message,
13823
+ file: epFile,
13824
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
13825
+ })
13826
+ );
13827
+ }
13828
+ }
13829
+ }
13540
13830
  const alive = new Set(seedIds);
13541
13831
  const frontier = [...seedIds];
13542
13832
  const visitedEdges = /* @__PURE__ */ new Set();
@@ -13570,8 +13860,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
13570
13860
  dead.push({
13571
13861
  name: s.name,
13572
13862
  kind: s.kind,
13573
- lang: "ts",
13574
- // populated from symbol file metadata
13863
+ lang: detectLang(s.file) ?? "ts",
13575
13864
  file: s.file,
13576
13865
  line: s.line,
13577
13866
  reason
@@ -13596,16 +13885,14 @@ function runDeadCodeScan(projectRoot, opts = {}) {
13596
13885
  deadFiles.push({
13597
13886
  file,
13598
13887
  symbolCount: syms.length,
13599
- lang: syms[0]?.kind ?? "ts"
13888
+ lang: detectLang(file) ?? "ts"
13600
13889
  });
13601
13890
  }
13602
13891
  }
13603
13892
  const deadPackages = [];
13604
13893
  const pkgEntries = findPackageEntries(projectRoot);
13605
13894
  for (const [pkgName, pkgDir] of pkgEntries) {
13606
- const pkgFiles = allSymbols.filter(
13607
- (s) => s.file.startsWith(pkgDir + path24.sep)
13608
- );
13895
+ const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path24.sep));
13609
13896
  if (pkgFiles.length === 0) continue;
13610
13897
  const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
13611
13898
  if (pkgUsed.length === 0) {
@@ -13646,7 +13933,10 @@ function findPackageEntries(projectRoot) {
13646
13933
  pkgMap.set(rootPkg.name, projectRoot);
13647
13934
  }
13648
13935
  if (rootPkg) {
13649
- const wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
13936
+ let wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
13937
+ if (wsDirs.length === 0) {
13938
+ wsDirs = extractPnpmWorkspaceDirs(projectRoot);
13939
+ }
13650
13940
  for (const wsDir of wsDirs) {
13651
13941
  const wsPkg = tryReadJson(path24.join(wsDir, "package.json"));
13652
13942
  if (wsPkg && typeof wsPkg.name === "string") {
@@ -14263,7 +14553,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
14263
14553
 
14264
14554
  // src/e2e.ts
14265
14555
  init_util();
14266
- import { open, readdir as readdir5 } from "node:fs/promises";
14556
+ import { open, readdir as readdir6 } from "node:fs/promises";
14267
14557
  import * as path27 from "node:path";
14268
14558
  async function readBoundedText(filePath, maxBytes) {
14269
14559
  let handle;
@@ -14381,7 +14671,7 @@ async function scanWorkspace(root, maxDepth, signal) {
14381
14671
  }
14382
14672
  let entries;
14383
14673
  try {
14384
- entries = await readdir5(current.directory, { withFileTypes: true });
14674
+ entries = await readdir6(current.directory, { withFileTypes: true });
14385
14675
  } catch {
14386
14676
  continue;
14387
14677
  }
@@ -14440,7 +14730,7 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
14440
14730
  while (true) {
14441
14731
  const names = /* @__PURE__ */ new Set();
14442
14732
  try {
14443
- for (const entry of await readdir5(directory)) names.add(entry);
14733
+ for (const entry of await readdir6(directory)) names.add(entry);
14444
14734
  } catch {
14445
14735
  }
14446
14736
  if (names.has("pnpm-lock.yaml")) return "pnpm";
@@ -14508,7 +14798,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
14508
14798
  if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
14509
14799
  let entries;
14510
14800
  try {
14511
- entries = await readdir5(directory, { withFileTypes: true });
14801
+ entries = await readdir6(directory, { withFileTypes: true });
14512
14802
  } catch {
14513
14803
  continue;
14514
14804
  }
@@ -17217,7 +17507,7 @@ async function detectFixer(cwd) {
17217
17507
  init_util();
17218
17508
  import { spawn as spawn10 } from "node:child_process";
17219
17509
  import { statSync as statSync4 } from "node:fs";
17220
- import { dirname as dirname13, resolve as resolve13, sep as sep6 } from "node:path";
17510
+ import { dirname as dirname14, resolve as resolve13, sep as sep6 } from "node:path";
17221
17511
  import { assessCommitSafety } from "@wrongstack/core/coordination";
17222
17512
  import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
17223
17513
  var TIMEOUT_MS2 = 3e4;
@@ -17379,7 +17669,7 @@ function findGitDir2(cwd, projectRoot) {
17379
17669
  } catch {
17380
17670
  }
17381
17671
  if (dir === root) break;
17382
- const parent = dirname13(dir);
17672
+ const parent = dirname14(dir);
17383
17673
  if (parent === dir) break;
17384
17674
  dir = parent;
17385
17675
  }
@@ -21477,12 +21767,13 @@ init_util();
21477
21767
  import * as fs28 from "node:fs/promises";
21478
21768
  import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
21479
21769
  import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
21770
+ var ADVANCED_MODE_META_KEY = "tools.read.advancedMode";
21480
21771
  var MAX_BYTES2 = 5 * 1024 * 1024;
21481
21772
  var readTool = {
21482
21773
  name: "read",
21483
21774
  category: "Filesystem",
21484
- description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed with a ` N| ` prefix for easy reference in edits.",
21485
- usageHint: "FOUNDATIONAL TOOL \u2014 call this before almost any edit operation.\n\nBest practices:\n- Always read a file before using `edit`, `replace`, or `write` on it (the system often requires it for safety).\n- Use `offset` + `limit` for very large files instead of reading everything at once.\n- Default limit is generous (2000 lines) but can be increased.\n- The output format is designed to be directly usable as context for `edit` operations.",
21775
+ description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed with a ` N| ` prefix for easy reference in edits. When advanced mode is on or `includeSymbols` is set, the result also includes a `symbols` field listing codebase-index symbol names, kinds, and line numbers for the file (not file content).",
21776
+ usageHint: "FOUNDATIONAL TOOL \u2014 call this before almost any edit operation.\n\nBest practices:\n- Always read a file before using `edit`, `replace`, or `write` on it (the system often requires it for safety).\n- Use `offset` + `limit` for very large files instead of reading everything at once.\n- Default limit is generous (2000 lines) but can be increased.\n- The output format is designed to be directly usable as context for `edit` operations.\n- Set `includeSymbols: true` to also receive the codebase-index symbol listing for the file.\n- Enable advanced mode (`ctx.meta['tools.read.advancedMode'] = true`) to auto-inject symbols on every read.",
21486
21777
  selection: {
21487
21778
  doNotUseWhen: "you need to search many files for matching content.",
21488
21779
  useInstead: ["grep"]
@@ -21512,11 +21803,15 @@ var readTool = {
21512
21803
  type: "string",
21513
21804
  enum: ["content", "summary"],
21514
21805
  description: "Return full line-numbered content (default) or a compact file summary with imports/exports/symbols."
21806
+ },
21807
+ includeSymbols: {
21808
+ type: "boolean",
21809
+ description: "When true, include the codebase-index symbol list for this file as a structured `symbols` field in the result. Overrides the advanced-mode meta flag per-call."
21515
21810
  }
21516
21811
  },
21517
21812
  required: ["path"]
21518
21813
  },
21519
- async execute(input, ctx) {
21814
+ async execute(input, ctx, execOpts) {
21520
21815
  if (!input?.path) {
21521
21816
  throw new ToolValidationError5({
21522
21817
  message: "read: path is required",
@@ -21524,6 +21819,7 @@ var readTool = {
21524
21819
  });
21525
21820
  }
21526
21821
  const absPath = await safeResolveReal(input.path, ctx);
21822
+ const shouldIncludeSymbols = input.includeSymbols === true || input.includeSymbols !== false && ctx.meta[ADVANCED_MODE_META_KEY] === true;
21527
21823
  let stat17;
21528
21824
  try {
21529
21825
  stat17 = await fs28.stat(absPath);
@@ -21567,13 +21863,15 @@ var readTool = {
21567
21863
  const requestedEnd = prior ? Math.min(offset + limit - 1, prior.totalLines) : offset + limit - 1;
21568
21864
  if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior, stat17.mtimeMs, offset, requestedEnd)) {
21569
21865
  ctx.recordRead(absPath, stat17.mtimeMs);
21866
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
21570
21867
  return {
21571
21868
  text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(stat17.mtimeMs)}; requested lines ${offset}-${requestedEnd} were already shown. Use offset/limit for a new range if needed.]`,
21572
21869
  total_lines: prior.totalLines,
21573
21870
  encoding: "utf8",
21574
21871
  truncated: requestedEnd < prior.totalLines,
21575
21872
  cached: true,
21576
- note: "Repeated read suppressed to save tokens."
21873
+ note: mergeSymbolNote("Repeated read suppressed to save tokens.", symResult2?.note),
21874
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
21577
21875
  };
21578
21876
  }
21579
21877
  const buf = await fs28.readFile(absPath);
@@ -21587,27 +21885,43 @@ var readTool = {
21587
21885
  if (input.mode === "summary") {
21588
21886
  ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
21589
21887
  rememberReadRange(ctx, absPath, stat17.mtimeMs, total, 1, Math.min(total, 200));
21888
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
21590
21889
  return {
21591
21890
  text: summarizeFile(input.path, stat17.size, allLines),
21592
21891
  total_lines: total,
21593
21892
  encoding: "utf8",
21594
21893
  truncated: total > 200,
21595
- note: "Summary mode returned compact structure instead of full file content."
21894
+ note: mergeSymbolNote(
21895
+ "Summary mode returned compact structure instead of full file content.",
21896
+ symResult2?.note
21897
+ ),
21898
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
21596
21899
  };
21597
21900
  }
21598
21901
  if (limit === 0) {
21599
21902
  ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
21600
21903
  rememberReadRange(ctx, absPath, stat17.mtimeMs, total, 1, 0);
21601
- return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
21904
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
21905
+ return {
21906
+ text: "",
21907
+ total_lines: total,
21908
+ encoding: "utf8",
21909
+ truncated: total > 0,
21910
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {},
21911
+ ...symResult2?.note ? { note: symResult2.note } : {}
21912
+ };
21602
21913
  }
21603
21914
  if (offset > total) {
21604
21915
  ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
21605
21916
  rememberReadRange(ctx, absPath, stat17.mtimeMs, total, total + 1, total + 1);
21917
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
21606
21918
  return {
21607
21919
  text: `[offset ${offset} is past end of file "${input.path}" \u2014 file has ${total} line(s). Do not retry this offset.]`,
21608
21920
  total_lines: total,
21609
21921
  encoding: "utf8",
21610
- truncated: false
21922
+ truncated: false,
21923
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {},
21924
+ ...symResult2?.note ? { note: symResult2.note } : {}
21611
21925
  };
21612
21926
  }
21613
21927
  const slice = allLines.slice(offset - 1, offset - 1 + limit);
@@ -21616,14 +21930,53 @@ var readTool = {
21616
21930
  const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
21617
21931
  ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
21618
21932
  rememberReadRange(ctx, absPath, stat17.mtimeMs, total, offset, offset + slice.length - 1);
21933
+ const symResult = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
21619
21934
  return {
21620
21935
  text: numbered,
21621
21936
  total_lines: total,
21622
21937
  encoding: "utf8",
21623
- truncated
21938
+ truncated,
21939
+ ...symResult?.symbols ? { symbols: symResult.symbols } : {},
21940
+ ...symResult?.note ? { note: symResult.note } : {}
21624
21941
  };
21625
21942
  }
21626
21943
  };
21944
+ async function fetchSymbolsForFile(absPath, ctx, signal) {
21945
+ try {
21946
+ const state = getIndexState();
21947
+ if (!state.ready) return {};
21948
+ const { results, total } = await searchCodebaseIndex(
21949
+ {
21950
+ projectRoot: ctx.projectRoot,
21951
+ indexDir: codebaseIndexDirOverride(ctx),
21952
+ query: "",
21953
+ file: absPath,
21954
+ limit: 500
21955
+ },
21956
+ { signal }
21957
+ );
21958
+ if (results.length === 0) return {};
21959
+ const sorted = results.map((r) => ({
21960
+ name: r.name,
21961
+ kind: r.kind,
21962
+ line: r.line,
21963
+ col: r.col,
21964
+ signature: r.signature
21965
+ })).sort((a, b) => a.line - b.line || a.col - b.col);
21966
+ const result = { symbols: sorted };
21967
+ if (total > results.length) {
21968
+ result.note = `Symbol listing truncated to ${results.length} of ${total} entries.`;
21969
+ }
21970
+ return result;
21971
+ } catch {
21972
+ return {};
21973
+ }
21974
+ }
21975
+ function mergeSymbolNote(note, symNote) {
21976
+ if (!symNote) return note;
21977
+ if (!note) return symNote;
21978
+ return `${note} ${symNote}`;
21979
+ }
21627
21980
  var READ_RANGES_META_KEY = "tools.read.ranges.v1";
21628
21981
  function getReadRanges(ctx) {
21629
21982
  const existing = ctx.meta[READ_RANGES_META_KEY];