@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/index.js CHANGED
@@ -5137,11 +5137,18 @@ async function parseSymbols(opts) {
5137
5137
  } else if (ts.isHeritageClause(node)) {
5138
5138
  for (const t of node.types) {
5139
5139
  const name = getTypeName(t.expression);
5140
- if (name) refs.push({ fromId: 0, toName: name, callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement", line: lineNum });
5140
+ if (name)
5141
+ refs.push({
5142
+ fromId: 0,
5143
+ toName: name,
5144
+ callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
5145
+ line: lineNum
5146
+ });
5141
5147
  }
5142
5148
  } else if (ts.isImportDeclaration(node)) {
5143
- const moduleName = getModuleName(node);
5144
- if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
5149
+ emitImportSpecifierRefs(node, refs, lineNum);
5150
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
5151
+ emitExportSpecifierRefs(node, refs, lineNum);
5145
5152
  }
5146
5153
  const scopeIdx = scopeParts.length;
5147
5154
  pushScopeName(node, scopeParts);
@@ -5157,11 +5164,6 @@ function getTypeName(name) {
5157
5164
  if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
5158
5165
  return "";
5159
5166
  }
5160
- function getModuleName(node) {
5161
- const moduleSpecifier = node.moduleSpecifier;
5162
- if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
5163
- return "";
5164
- }
5165
5167
  function deduplicateRefs(refs) {
5166
5168
  const seen = /* @__PURE__ */ new Set();
5167
5169
  return refs.filter((r) => {
@@ -5171,6 +5173,44 @@ function deduplicateRefs(refs) {
5171
5173
  return true;
5172
5174
  });
5173
5175
  }
5176
+ function getImportSpecifierName(spec) {
5177
+ return spec.propertyName?.text ?? spec.name.text;
5178
+ }
5179
+ function emitImportSpecifierRefs(node, refs, lineNum) {
5180
+ const clause = node.importClause;
5181
+ if (!clause) return;
5182
+ if (clause.name) {
5183
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
5184
+ }
5185
+ const bindings2 = clause.namedBindings;
5186
+ if (!bindings2) return;
5187
+ if (ts.isNamedImports(bindings2)) {
5188
+ for (const element of bindings2.elements) {
5189
+ refs.push({
5190
+ fromId: 0,
5191
+ toName: getImportSpecifierName(element),
5192
+ callType: "import",
5193
+ line: lineNum
5194
+ });
5195
+ }
5196
+ } else if (ts.isNamespaceImport(bindings2)) {
5197
+ refs.push({ fromId: 0, toName: bindings2.name.text, callType: "import", line: lineNum });
5198
+ }
5199
+ }
5200
+ function emitExportSpecifierRefs(node, refs, lineNum) {
5201
+ const clause = node.exportClause;
5202
+ if (clause && ts.isNamespaceExport(clause)) {
5203
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
5204
+ return;
5205
+ }
5206
+ if (clause && ts.isNamedExports(clause)) {
5207
+ for (const element of clause.elements) {
5208
+ const originalName = element.propertyName?.text ?? element.name.text;
5209
+ refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
5210
+ }
5211
+ return;
5212
+ }
5213
+ }
5174
5214
  var ts, tsLoad, kindMapCache;
5175
5215
  var init_ts_parser = __esm({
5176
5216
  "src/codebase-index/ts-parser.ts"() {
@@ -8766,9 +8806,50 @@ import { createReadStream } from "node:fs";
8766
8806
  import * as fs7 from "node:fs/promises";
8767
8807
  import * as path10 from "node:path";
8768
8808
  import { atomicWrite, ulid } from "@wrongstack/core/utils";
8809
+ var ARTIFACT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
8810
+ var sweptRoots = /* @__PURE__ */ new Set();
8811
+ function sweepOldArtifacts(root) {
8812
+ if (sweptRoots.has(root)) return;
8813
+ sweptRoots.add(root);
8814
+ void (async () => {
8815
+ const cutoff = Date.now() - ARTIFACT_RETENTION_MS;
8816
+ let sessionDirs;
8817
+ try {
8818
+ const entries = await fs7.readdir(root, { withFileTypes: true });
8819
+ sessionDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
8820
+ } catch {
8821
+ return;
8822
+ }
8823
+ for (const sessionDir of sessionDirs) {
8824
+ const dir = path10.join(root, sessionDir);
8825
+ let names;
8826
+ try {
8827
+ names = await fs7.readdir(dir);
8828
+ } catch {
8829
+ continue;
8830
+ }
8831
+ let removed = 0;
8832
+ for (const name of names) {
8833
+ const target = path10.join(dir, name);
8834
+ try {
8835
+ const stat18 = await fs7.stat(target);
8836
+ if (stat18.isFile() && stat18.mtimeMs < cutoff) {
8837
+ await fs7.rm(target, { force: true });
8838
+ removed++;
8839
+ }
8840
+ } catch {
8841
+ }
8842
+ }
8843
+ if (removed === names.length && names.length > 0) {
8844
+ await fs7.rmdir(dir).catch(() => void 0);
8845
+ }
8846
+ }
8847
+ })();
8848
+ }
8769
8849
  var BrowserArtifactStore = class {
8770
8850
  constructor(root) {
8771
8851
  this.root = root;
8852
+ sweepOldArtifacts(root);
8772
8853
  }
8773
8854
  root;
8774
8855
  async write(sessionId, kind, extension, mimeType, content) {
@@ -10214,42 +10295,6 @@ import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
10214
10295
  import * as fs14 from "node:fs";
10215
10296
  import * as path21 from "node:path";
10216
10297
 
10217
- // src/codebase-index/schema.ts
10218
- var SCHEMA_VERSION = 3;
10219
-
10220
- // src/codebase-index/lsp-kind.ts
10221
- function lspKindToInternalKind(k) {
10222
- switch (k) {
10223
- case 5 /* Class */:
10224
- return "class";
10225
- case 6 /* Method */:
10226
- return "method";
10227
- case 7 /* Property */:
10228
- case 8 /* Field */:
10229
- return "property";
10230
- case 9 /* Constructor */:
10231
- return "class";
10232
- case 10 /* Enum */:
10233
- return "enum";
10234
- case 11 /* Interface */:
10235
- return "interface";
10236
- case 12 /* Function */:
10237
- return "function";
10238
- case 13 /* Variable */:
10239
- return "var";
10240
- case 14 /* Constant */:
10241
- return "const";
10242
- case 22 /* EnumMember */:
10243
- return "enum";
10244
- case 26 /* TypeParameter */:
10245
- return "type";
10246
- case 3 /* Namespace */:
10247
- return "namespace";
10248
- default:
10249
- return null;
10250
- }
10251
- }
10252
-
10253
10298
  // src/codebase-index/bm25.ts
10254
10299
  var K1 = 1.5;
10255
10300
  var B = 0.75;
@@ -10339,6 +10384,42 @@ var Bm25Index = class {
10339
10384
  }
10340
10385
  };
10341
10386
 
10387
+ // src/codebase-index/lsp-kind.ts
10388
+ function lspKindToInternalKind(k) {
10389
+ switch (k) {
10390
+ case 5 /* Class */:
10391
+ return "class";
10392
+ case 6 /* Method */:
10393
+ return "method";
10394
+ case 7 /* Property */:
10395
+ case 8 /* Field */:
10396
+ return "property";
10397
+ case 9 /* Constructor */:
10398
+ return "class";
10399
+ case 10 /* Enum */:
10400
+ return "enum";
10401
+ case 11 /* Interface */:
10402
+ return "interface";
10403
+ case 12 /* Function */:
10404
+ return "function";
10405
+ case 13 /* Variable */:
10406
+ return "var";
10407
+ case 14 /* Constant */:
10408
+ return "const";
10409
+ case 22 /* EnumMember */:
10410
+ return "enum";
10411
+ case 26 /* TypeParameter */:
10412
+ return "type";
10413
+ case 3 /* Namespace */:
10414
+ return "namespace";
10415
+ default:
10416
+ return null;
10417
+ }
10418
+ }
10419
+
10420
+ // src/codebase-index/schema.ts
10421
+ var SCHEMA_VERSION = 3;
10422
+
10342
10423
  // src/codebase-index/sqlite-runtime.ts
10343
10424
  import { createRequire } from "node:module";
10344
10425
  import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
@@ -10406,96 +10487,6 @@ function runSqliteWithRetry(fn) {
10406
10487
  throw lastError;
10407
10488
  }
10408
10489
 
10409
- // src/codebase-index/writer-helpers.ts
10410
- import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
10411
- function escapeLike(value) {
10412
- return value.replace(/[\\%_]/g, (char) => `\\${char}`);
10413
- }
10414
- function assignRefsToSymbols(refs, symbols) {
10415
- if (refs.length === 0 || symbols.length === 0) return [];
10416
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
10417
- const seen = /* @__PURE__ */ new Set();
10418
- const assigned = [];
10419
- for (const ref of refs) {
10420
- let owner2;
10421
- for (const symbol of ordered) {
10422
- if (symbol.line > ref.line) break;
10423
- owner2 = symbol;
10424
- }
10425
- if (!owner2 && ref.callType === "import") owner2 = ordered[0];
10426
- if (!owner2 || owner2.id <= 0) continue;
10427
- const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
10428
- if (seen.has(key)) continue;
10429
- seen.add(key);
10430
- assigned.push({ ...ref, fromId: owner2.id });
10431
- }
10432
- return assigned;
10433
- }
10434
- function resolveIndexDir(projectRoot, override) {
10435
- return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
10436
- }
10437
- function codebaseIndexDirOverride(ctx) {
10438
- const v = ctx.meta?.["codebaseIndexDir"];
10439
- return typeof v === "string" ? v : void 0;
10440
- }
10441
-
10442
- // src/codebase-index/writer-schema.ts
10443
- var METADATA_TABLE_SQL = `
10444
- CREATE TABLE IF NOT EXISTS metadata (
10445
- key TEXT PRIMARY KEY,
10446
- value TEXT NOT NULL
10447
- );
10448
- `;
10449
- var CORE_TABLES_SQL = `
10450
- CREATE TABLE IF NOT EXISTS files (
10451
- file TEXT PRIMARY KEY,
10452
- lang TEXT NOT NULL,
10453
- mtime_ms INTEGER NOT NULL,
10454
- symbol_count INTEGER NOT NULL DEFAULT 0,
10455
- last_indexed INTEGER NOT NULL
10456
- );
10457
- CREATE TABLE IF NOT EXISTS symbols (
10458
- id INTEGER PRIMARY KEY,
10459
- lang TEXT NOT NULL,
10460
- kind TEXT NOT NULL,
10461
- name TEXT NOT NULL,
10462
- file TEXT NOT NULL,
10463
- line INTEGER NOT NULL,
10464
- col INTEGER NOT NULL,
10465
- signature TEXT NOT NULL DEFAULT '',
10466
- doc_comment TEXT NOT NULL DEFAULT '',
10467
- scope TEXT NOT NULL DEFAULT '',
10468
- text TEXT NOT NULL DEFAULT '',
10469
- file_fk TEXT NOT NULL
10470
- );
10471
- `;
10472
- var SYMBOL_INDEX_SQL = [
10473
- "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
10474
- "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
10475
- "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
10476
- "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
10477
- "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
10478
- "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
10479
- "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
10480
- ];
10481
- var REFS_TABLE_SQL = `
10482
- CREATE TABLE IF NOT EXISTS refs (
10483
- id INTEGER PRIMARY KEY,
10484
- from_id INTEGER NOT NULL,
10485
- to_name TEXT NOT NULL,
10486
- to_id INTEGER,
10487
- call_type TEXT NOT NULL,
10488
- line INTEGER NOT NULL
10489
- );
10490
- `;
10491
- var REFS_INDEX_SQL = [
10492
- "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
10493
- "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
10494
- "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
10495
- "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
10496
- ];
10497
- var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
10498
-
10499
10490
  // src/codebase-index/writer-admin.ts
10500
10491
  import * as fs13 from "node:fs";
10501
10492
  import * as path19 from "node:path";
@@ -10968,6 +10959,39 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
10968
10959
  return { nodes, edges };
10969
10960
  }
10970
10961
 
10962
+ // src/codebase-index/writer-helpers.ts
10963
+ import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
10964
+ function escapeLike(value) {
10965
+ return value.replace(/[\\%_]/g, (char) => `\\${char}`);
10966
+ }
10967
+ function assignRefsToSymbols(refs, symbols) {
10968
+ if (refs.length === 0 || symbols.length === 0) return [];
10969
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
10970
+ const seen = /* @__PURE__ */ new Set();
10971
+ const assigned = [];
10972
+ for (const ref of refs) {
10973
+ let owner2;
10974
+ for (const symbol of ordered) {
10975
+ if (symbol.line > ref.line) break;
10976
+ owner2 = symbol;
10977
+ }
10978
+ if (!owner2 && ref.callType === "import") owner2 = ordered[0];
10979
+ if (!owner2 || owner2.id <= 0) continue;
10980
+ const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
10981
+ if (seen.has(key)) continue;
10982
+ seen.add(key);
10983
+ assigned.push({ ...ref, fromId: owner2.id });
10984
+ }
10985
+ return assigned;
10986
+ }
10987
+ function resolveIndexDir(projectRoot, override) {
10988
+ return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
10989
+ }
10990
+ function codebaseIndexDirOverride(ctx) {
10991
+ const v = ctx.meta?.["codebaseIndexDir"];
10992
+ return typeof v === "string" ? v : void 0;
10993
+ }
10994
+
10971
10995
  // src/codebase-index/writer-pragmas.ts
10972
10996
  import { sqliteCachePragmas } from "@wrongstack/core/utils";
10973
10997
  function applyIndexStorePragmas(db) {
@@ -10986,6 +11010,63 @@ function applyIndexStorePragmas(db) {
10986
11010
  }
10987
11011
  }
10988
11012
 
11013
+ // src/codebase-index/writer-schema.ts
11014
+ var METADATA_TABLE_SQL = `
11015
+ CREATE TABLE IF NOT EXISTS metadata (
11016
+ key TEXT PRIMARY KEY,
11017
+ value TEXT NOT NULL
11018
+ );
11019
+ `;
11020
+ var CORE_TABLES_SQL = `
11021
+ CREATE TABLE IF NOT EXISTS files (
11022
+ file TEXT PRIMARY KEY,
11023
+ lang TEXT NOT NULL,
11024
+ mtime_ms INTEGER NOT NULL,
11025
+ symbol_count INTEGER NOT NULL DEFAULT 0,
11026
+ last_indexed INTEGER NOT NULL
11027
+ );
11028
+ CREATE TABLE IF NOT EXISTS symbols (
11029
+ id INTEGER PRIMARY KEY,
11030
+ lang TEXT NOT NULL,
11031
+ kind TEXT NOT NULL,
11032
+ name TEXT NOT NULL,
11033
+ file TEXT NOT NULL,
11034
+ line INTEGER NOT NULL,
11035
+ col INTEGER NOT NULL,
11036
+ signature TEXT NOT NULL DEFAULT '',
11037
+ doc_comment TEXT NOT NULL DEFAULT '',
11038
+ scope TEXT NOT NULL DEFAULT '',
11039
+ text TEXT NOT NULL DEFAULT '',
11040
+ file_fk TEXT NOT NULL
11041
+ );
11042
+ `;
11043
+ var SYMBOL_INDEX_SQL = [
11044
+ "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
11045
+ "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
11046
+ "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
11047
+ "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
11048
+ "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
11049
+ "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
11050
+ "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
11051
+ ];
11052
+ var REFS_TABLE_SQL = `
11053
+ CREATE TABLE IF NOT EXISTS refs (
11054
+ id INTEGER PRIMARY KEY,
11055
+ from_id INTEGER NOT NULL,
11056
+ to_name TEXT NOT NULL,
11057
+ to_id INTEGER,
11058
+ call_type TEXT NOT NULL,
11059
+ line INTEGER NOT NULL
11060
+ );
11061
+ `;
11062
+ var REFS_INDEX_SQL = [
11063
+ "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
11064
+ "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
11065
+ "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
11066
+ "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
11067
+ ];
11068
+ var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
11069
+
10989
11070
  // src/codebase-index/writer-search-helpers.ts
10990
11071
  function normalizeSearchLimit(limit) {
10991
11072
  return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
@@ -11194,9 +11275,15 @@ var IndexStore = class _IndexStore {
11194
11275
  DROP TABLE IF EXISTS refs;
11195
11276
  `);
11196
11277
  this.db.exec("DROP TABLE IF EXISTS symbols_fts");
11197
- this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
11278
+ this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
11279
+ String(SCHEMA_VERSION),
11280
+ "version"
11281
+ );
11198
11282
  } else if (storedVersion === null) {
11199
- this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
11283
+ this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run(
11284
+ "version",
11285
+ String(SCHEMA_VERSION)
11286
+ );
11200
11287
  }
11201
11288
  this.db.exec(CORE_TABLES_SQL);
11202
11289
  for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
@@ -11213,7 +11300,9 @@ var IndexStore = class _IndexStore {
11213
11300
  );
11214
11301
  if (symbolCount !== ftsCount) {
11215
11302
  this.db.exec("DELETE FROM symbols_fts");
11216
- const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
11303
+ const rows = this.stmt(
11304
+ "SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
11305
+ ).all();
11217
11306
  bulkInsertFtsWithStatement(
11218
11307
  (sql) => this.stmt(sql),
11219
11308
  _IndexStore.MAX_SQL_VARS,
@@ -11435,7 +11524,9 @@ var IndexStore = class _IndexStore {
11435
11524
  const limitSql = limit !== void 0 ? " LIMIT ?" : "";
11436
11525
  const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
11437
11526
  const binds = limit !== void 0 ? [...values, limit] : values;
11438
- const rows = this.stmt(sql).all(...binds);
11527
+ const rows = this.stmt(sql).all(
11528
+ ...binds
11529
+ );
11439
11530
  return rows.map((row) => mapWriterSearchRow(row, filter?.lspKind));
11440
11531
  }
11441
11532
  /** Shared WHERE builder for {@link search} / empty-query ranked totals. */
@@ -11611,7 +11702,9 @@ var IndexStore = class _IndexStore {
11611
11702
  }
11612
11703
  setLastIndexed(ts2) {
11613
11704
  this.runWithRetry(() => {
11614
- this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
11705
+ this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
11706
+ String(ts2)
11707
+ );
11615
11708
  });
11616
11709
  }
11617
11710
  getMetadata(key) {
@@ -11722,7 +11815,9 @@ var IndexStore = class _IndexStore {
11722
11815
  this.stmt(
11723
11816
  `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
11724
11817
  ).run(...options.deleteForFiles);
11725
- this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
11818
+ this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(
11819
+ ...options.deleteForFiles
11820
+ );
11726
11821
  }
11727
11822
  const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
11728
11823
  let nextId = this.allocateSymbolIds(totalSymbols);
@@ -11766,11 +11861,7 @@ var IndexStore = class _IndexStore {
11766
11861
  this.ftsAvailable,
11767
11862
  ftsRows
11768
11863
  );
11769
- bulkInsertRefsWithStatement(
11770
- (sql) => this.stmt(sql),
11771
- _IndexStore.MAX_SQL_VARS,
11772
- refsToInsert
11773
- );
11864
+ bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
11774
11865
  const upsertStmt = this.stmt(
11775
11866
  `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
11776
11867
  VALUES (?, ?, ?, ?, ?)
@@ -11799,7 +11890,9 @@ var IndexStore = class _IndexStore {
11799
11890
  */
11800
11891
  deleteRefsForFile(file) {
11801
11892
  this.runWithRetry(() => {
11802
- this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
11893
+ this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
11894
+ file
11895
+ );
11803
11896
  });
11804
11897
  }
11805
11898
  /**
@@ -11954,9 +12047,7 @@ var IndexStore = class _IndexStore {
11954
12047
  * build the full symbol universe for the reachability scan.
11955
12048
  */
11956
12049
  getAllSymbols() {
11957
- return this.stmt(
11958
- "SELECT id, name, file, kind, line FROM symbols ORDER BY id"
11959
- ).all().map((r) => ({ ...r, kind: r.kind }));
12050
+ return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
11960
12051
  }
11961
12052
  /**
11962
12053
  * Returns every resolved reference (to_id IS NOT NULL). Used by
@@ -11968,6 +12059,25 @@ var IndexStore = class _IndexStore {
11968
12059
  "SELECT from_id AS fromId, to_id AS toId, call_type AS callType FROM refs WHERE to_id IS NOT NULL"
11969
12060
  ).all();
11970
12061
  }
12062
+ /**
12063
+ * Returns ALL import refs (including unresolved) with their source-file
12064
+ * path and resolved target id. Used by the dead-code scan's file-level
12065
+ * graph traversal to handle barrel-only entry points where no symbol
12066
+ * carries the ref.
12067
+ *
12068
+ * Refs whose `from_id` doesn't match a known symbol (e.g. pure-barrel
12069
+ * files with no declarations) will have `sourceFile === null`.
12070
+ */
12071
+ getAllImportRefs() {
12072
+ return this.stmt(
12073
+ `SELECT s.file AS sourceFile, r.to_name AS toName, r.to_id AS toId,
12074
+ r.call_type AS callType, r.line
12075
+ FROM refs r
12076
+ LEFT JOIN symbols s ON r.from_id = s.id
12077
+ WHERE r.call_type = 'import'
12078
+ ORDER BY r.line`
12079
+ ).all();
12080
+ }
11971
12081
  close() {
11972
12082
  this.stmtCache.clear();
11973
12083
  this.bm25Dirty = true;
@@ -13852,6 +13962,7 @@ var codebaseStatsTool = {
13852
13962
  };
13853
13963
 
13854
13964
  // src/codebase-index/dead-code-scan.ts
13965
+ init_languages2();
13855
13966
  import * as fs19 from "node:fs";
13856
13967
  import * as path24 from "node:path";
13857
13968
  var deadCodeScanTool = {
@@ -13920,7 +14031,15 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
13920
14031
  if (rootPkg) {
13921
14032
  addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
13922
14033
  }
13923
- const workspaces = rootPkg ? extractWorkspaceGlobs(rootPkg, projectRoot) : [];
14034
+ let workspaces;
14035
+ if (rootPkg) {
14036
+ workspaces = extractWorkspaceGlobs(rootPkg, projectRoot);
14037
+ if (workspaces.length === 0) {
14038
+ workspaces = extractPnpmWorkspaceDirs(projectRoot);
14039
+ }
14040
+ } else {
14041
+ workspaces = [];
14042
+ }
13924
14043
  for (const wsDir of workspaces) {
13925
14044
  const pkgJsonPath = path24.join(wsDir, "package.json");
13926
14045
  const pkg = tryReadJson(pkgJsonPath);
@@ -13942,77 +14061,189 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
13942
14061
  }
13943
14062
  return [...entries];
13944
14063
  }
14064
+ var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
14065
+ var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path24.sep}${d}${path24.sep}`);
14066
+ function trySourceEquivalent(resolved) {
14067
+ resolved = resolved.replace(/[/\\]/g, path24.sep);
14068
+ for (const marker of BUILD_OUTPUT_DIR_NAMES) {
14069
+ const idx = resolved.indexOf(marker);
14070
+ if (idx === -1) continue;
14071
+ const base = resolved.replace(marker, `${path24.sep}src${path24.sep}`);
14072
+ const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
14073
+ if (candidate !== base && fs19.existsSync(candidate)) {
14074
+ return candidate;
14075
+ }
14076
+ const dtsStripped = base.replace(/\.d\.ts$/, "");
14077
+ const candidateDts = dtsStripped + ".ts";
14078
+ if (candidateDts !== base && candidateDts !== candidate && fs19.existsSync(candidateDts)) {
14079
+ return candidateDts;
14080
+ }
14081
+ const candidateNoExt = base + ".ts";
14082
+ if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs19.existsSync(candidateNoExt)) {
14083
+ return candidateNoExt;
14084
+ }
14085
+ }
14086
+ return null;
14087
+ }
14088
+ function tryAddEntryPath(pkgDir, rawPath, entries) {
14089
+ const resolved = resolveAgainst(pkgDir, rawPath);
14090
+ if (fs19.existsSync(resolved)) entries.add(resolved);
14091
+ const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
14092
+ if (tsResolved !== resolved && fs19.existsSync(tsResolved)) {
14093
+ entries.add(tsResolved);
14094
+ }
14095
+ const srcAlt = trySourceEquivalent(resolved);
14096
+ if (srcAlt) entries.add(srcAlt);
14097
+ }
13945
14098
  function addPkgJsonEntryPoints(pkgDir, pkg, entries) {
13946
14099
  if (typeof pkg.main === "string") {
13947
- const resolved = resolveAgainst(pkgDir, pkg.main);
13948
- if (fs19.existsSync(resolved)) entries.add(resolved);
13949
- const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
13950
- if (tsResolved !== resolved && fs19.existsSync(tsResolved)) {
13951
- entries.add(tsResolved);
13952
- }
14100
+ tryAddEntryPath(pkgDir, pkg.main, entries);
13953
14101
  }
13954
14102
  const bin = pkg.bin;
13955
14103
  if (typeof bin === "string") {
13956
- const resolved = resolveAgainst(pkgDir, bin);
13957
- if (fs19.existsSync(resolved)) entries.add(resolved);
14104
+ tryAddEntryPath(pkgDir, bin, entries);
13958
14105
  } else if (bin && typeof bin === "object") {
13959
14106
  for (const value of Object.values(bin)) {
13960
14107
  if (typeof value === "string") {
13961
- const resolved = resolveAgainst(pkgDir, value);
13962
- if (fs19.existsSync(resolved)) entries.add(resolved);
14108
+ tryAddEntryPath(pkgDir, value, entries);
13963
14109
  }
13964
14110
  }
13965
14111
  }
13966
14112
  for (const key of ["types", "typings"]) {
13967
14113
  if (typeof pkg[key] === "string") {
13968
- const resolved = resolveAgainst(pkgDir, pkg[key]);
13969
- if (fs19.existsSync(resolved)) entries.add(resolved);
14114
+ tryAddEntryPath(pkgDir, pkg[key], entries);
13970
14115
  }
13971
14116
  }
13972
14117
  const exports_ = pkg.exports;
13973
14118
  if (exports_ && typeof exports_ === "object") {
13974
14119
  for (const value of Object.values(exports_)) {
13975
14120
  if (typeof value === "string") {
13976
- const resolved = resolveAgainst(pkgDir, value);
13977
- if (fs19.existsSync(resolved)) entries.add(resolved);
14121
+ tryAddEntryPath(pkgDir, value, entries);
13978
14122
  } else if (value && typeof value === "object") {
13979
- for (const nested of Object.values(
13980
- value
13981
- )) {
14123
+ for (const nested of Object.values(value)) {
13982
14124
  if (typeof nested === "string") {
13983
- const resolved = resolveAgainst(pkgDir, nested);
13984
- if (fs19.existsSync(resolved)) entries.add(resolved);
14125
+ tryAddEntryPath(pkgDir, nested, entries);
13985
14126
  }
13986
14127
  }
13987
14128
  }
13988
14129
  }
13989
14130
  }
13990
14131
  }
14132
+ function expandGlobPattern(entry, projectRoot) {
14133
+ const dirs = [];
14134
+ if (entry.includes("*")) {
14135
+ const base = entry.replace(/\/\*+$/, "");
14136
+ const baseDir = path24.resolve(projectRoot, base);
14137
+ try {
14138
+ const children = fs19.readdirSync(baseDir, { withFileTypes: true });
14139
+ for (const child of children) {
14140
+ if (child.isDirectory()) {
14141
+ dirs.push(path24.join(baseDir, child.name));
14142
+ }
14143
+ }
14144
+ } catch {
14145
+ }
14146
+ } else {
14147
+ dirs.push(path24.resolve(projectRoot, entry));
14148
+ }
14149
+ return dirs;
14150
+ }
13991
14151
  function extractWorkspaceGlobs(pkg, projectRoot) {
13992
14152
  const dirs = [];
13993
14153
  const workspaces = pkg.workspaces;
13994
14154
  if (Array.isArray(workspaces)) {
13995
14155
  for (const entry of workspaces) {
13996
14156
  if (typeof entry === "string") {
13997
- if (entry.includes("*")) {
13998
- const base = entry.replace(/\/\*+$/, "");
13999
- const baseDir = path24.resolve(projectRoot, base);
14000
- try {
14001
- const children = fs19.readdirSync(baseDir, { withFileTypes: true });
14002
- for (const child of children) {
14003
- if (child.isDirectory()) {
14004
- dirs.push(path24.join(baseDir, child.name));
14005
- }
14006
- }
14007
- } catch {
14157
+ dirs.push(...expandGlobPattern(entry, projectRoot));
14158
+ }
14159
+ }
14160
+ }
14161
+ return dirs;
14162
+ }
14163
+ function extractPnpmWorkspaceDirs(projectRoot) {
14164
+ const yamlPath = path24.join(projectRoot, "pnpm-workspace.yaml");
14165
+ if (!fs19.existsSync(yamlPath)) return [];
14166
+ try {
14167
+ const content = fs19.readFileSync(yamlPath, "utf8");
14168
+ const dirs = [];
14169
+ let inPackages = false;
14170
+ const lines = content.split("\n");
14171
+ const itemRe = /^\s+-\s+"([^"]+)"|^\s+-\s+'([^']+)'|^\s+-\s+(\S+)/;
14172
+ for (const line of lines) {
14173
+ const trimmed = line.trim();
14174
+ if (/^packages\s*:\s*$/.test(trimmed)) {
14175
+ inPackages = true;
14176
+ continue;
14177
+ }
14178
+ if (inPackages && trimmed.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) {
14179
+ if (!trimmed.startsWith("-")) {
14180
+ inPackages = false;
14181
+ continue;
14182
+ }
14183
+ }
14184
+ if (inPackages) {
14185
+ const m = itemRe.exec(line);
14186
+ if (m) {
14187
+ const entry = m[1] ?? m[2] ?? m[3];
14188
+ if (entry) {
14189
+ dirs.push(...expandGlobPattern(entry, projectRoot));
14008
14190
  }
14009
- } else {
14010
- dirs.push(path24.resolve(projectRoot, entry));
14011
14191
  }
14012
14192
  }
14013
14193
  }
14194
+ return dirs;
14195
+ } catch {
14196
+ return [];
14197
+ }
14198
+ }
14199
+ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
14200
+ if (!moduleSpecifier.startsWith(".")) return [];
14201
+ const dir = path24.dirname(importerPath);
14202
+ const base = path24.resolve(dir, moduleSpecifier);
14203
+ const results = [];
14204
+ const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
14205
+ const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
14206
+ const candidates = skipBase ? [stripped] : [base];
14207
+ for (const candidate of candidates) {
14208
+ if (indexedFiles.has(candidate + ".ts")) results.push(candidate + ".ts");
14209
+ if (indexedFiles.has(candidate + ".tsx")) results.push(candidate + ".tsx");
14210
+ if (indexedFiles.has(candidate + ".js")) results.push(candidate + ".js");
14211
+ if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
14212
+ if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
14213
+ if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
14214
+ if (indexedFiles.has(path24.join(candidate, "index.ts")))
14215
+ results.push(path24.join(candidate, "index.ts"));
14216
+ if (indexedFiles.has(path24.join(candidate, "index.tsx")))
14217
+ results.push(path24.join(candidate, "index.tsx"));
14218
+ if (indexedFiles.has(path24.join(candidate, "index.js")))
14219
+ results.push(path24.join(candidate, "index.js"));
14220
+ if (indexedFiles.has(path24.join(candidate, "index.jsx")))
14221
+ results.push(path24.join(candidate, "index.jsx"));
14222
+ if (indexedFiles.has(path24.join(candidate, "index.mjs")))
14223
+ results.push(path24.join(candidate, "index.mjs"));
14224
+ if (indexedFiles.has(path24.join(candidate, "index.cjs")))
14225
+ results.push(path24.join(candidate, "index.cjs"));
14226
+ }
14227
+ return [...new Set(results)];
14228
+ }
14229
+ function parseNamedExportSymbols(matchText) {
14230
+ const braceStart = matchText.indexOf("{");
14231
+ if (braceStart === -1) return null;
14232
+ const braceEnd = matchText.indexOf("}", braceStart);
14233
+ if (braceEnd === -1) return null;
14234
+ const inner = matchText.slice(braceStart + 1, braceEnd);
14235
+ const symbols = [];
14236
+ for (const part of inner.split(",")) {
14237
+ let s = part.trim();
14238
+ if (!s) continue;
14239
+ s = s.replace(/^type\s+/, "");
14240
+ const asIdx = s.search(/\s+as\s+/);
14241
+ if (asIdx !== -1) {
14242
+ s = s.slice(0, asIdx).trim();
14243
+ }
14244
+ if (s) symbols.push(s);
14014
14245
  }
14015
- return dirs;
14246
+ return symbols;
14016
14247
  }
14017
14248
  function runDeadCodeScan(projectRoot, opts = {}) {
14018
14249
  const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
@@ -14034,12 +14265,71 @@ function runDeadCodeScan(projectRoot, opts = {}) {
14034
14265
  }
14035
14266
  const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
14036
14267
  const entryFileSet = new Set(discoveredFiles.map((f) => path24.resolve(f)));
14268
+ const indexedFiles = /* @__PURE__ */ new Set();
14269
+ for (const s of allSymbols) indexedFiles.add(s.file);
14270
+ for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
14037
14271
  const seedIds = /* @__PURE__ */ new Set();
14038
14272
  for (const s of allSymbols) {
14039
14273
  if (entryFileSet.has(s.file)) {
14040
14274
  seedIds.add(s.id);
14041
14275
  }
14042
14276
  }
14277
+ const fileToSymbolIds = /* @__PURE__ */ new Map();
14278
+ for (const s of allSymbols) {
14279
+ let byFile = fileToSymbolIds.get(s.file);
14280
+ if (!byFile) {
14281
+ byFile = [];
14282
+ fileToSymbolIds.set(s.file, byFile);
14283
+ }
14284
+ byFile.push(s.id);
14285
+ }
14286
+ const scannedBarrels = /* @__PURE__ */ new Set();
14287
+ const barrelWorkList = [...entryFileSet];
14288
+ while (barrelWorkList.length > 0) {
14289
+ const epFile = barrelWorkList.pop();
14290
+ if (scannedBarrels.has(epFile)) continue;
14291
+ scannedBarrels.add(epFile);
14292
+ try {
14293
+ const content = fs19.readFileSync(epFile, "utf8");
14294
+ const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
14295
+ const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
14296
+ let match;
14297
+ while ((match = reExportRe.exec(strippedContent)) !== null) {
14298
+ const moduleSpec = match[1];
14299
+ const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
14300
+ for (const rf of resolvedFiles) {
14301
+ const fileSyms = fileToSymbolIds.get(rf);
14302
+ if (fileSyms) {
14303
+ const namedSymbols = parseNamedExportSymbols(match[0]);
14304
+ if (namedSymbols) {
14305
+ const nameSet = new Set(namedSymbols);
14306
+ for (const sid of fileSyms) {
14307
+ const sym = symbolById.get(sid);
14308
+ if (sym && nameSet.has(sym.name)) seedIds.add(sid);
14309
+ }
14310
+ } else {
14311
+ for (const sid of fileSyms) seedIds.add(sid);
14312
+ }
14313
+ }
14314
+ if (!scannedBarrels.has(rf)) {
14315
+ barrelWorkList.push(rf);
14316
+ }
14317
+ }
14318
+ }
14319
+ } catch (err) {
14320
+ if (err instanceof Error && err.code !== "ENOENT") {
14321
+ console.warn(
14322
+ JSON.stringify({
14323
+ level: "warn",
14324
+ event: "dead_code_scan_barrel_read_failed",
14325
+ message: err.message,
14326
+ file: epFile,
14327
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
14328
+ })
14329
+ );
14330
+ }
14331
+ }
14332
+ }
14043
14333
  const alive = new Set(seedIds);
14044
14334
  const frontier = [...seedIds];
14045
14335
  const visitedEdges = /* @__PURE__ */ new Set();
@@ -14073,8 +14363,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
14073
14363
  dead.push({
14074
14364
  name: s.name,
14075
14365
  kind: s.kind,
14076
- lang: "ts",
14077
- // populated from symbol file metadata
14366
+ lang: detectLang(s.file) ?? "ts",
14078
14367
  file: s.file,
14079
14368
  line: s.line,
14080
14369
  reason
@@ -14099,16 +14388,14 @@ function runDeadCodeScan(projectRoot, opts = {}) {
14099
14388
  deadFiles.push({
14100
14389
  file,
14101
14390
  symbolCount: syms.length,
14102
- lang: syms[0]?.kind ?? "ts"
14391
+ lang: detectLang(file) ?? "ts"
14103
14392
  });
14104
14393
  }
14105
14394
  }
14106
14395
  const deadPackages = [];
14107
14396
  const pkgEntries = findPackageEntries(projectRoot);
14108
14397
  for (const [pkgName, pkgDir] of pkgEntries) {
14109
- const pkgFiles = allSymbols.filter(
14110
- (s) => s.file.startsWith(pkgDir + path24.sep)
14111
- );
14398
+ const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path24.sep));
14112
14399
  if (pkgFiles.length === 0) continue;
14113
14400
  const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
14114
14401
  if (pkgUsed.length === 0) {
@@ -14149,7 +14436,10 @@ function findPackageEntries(projectRoot) {
14149
14436
  pkgMap.set(rootPkg.name, projectRoot);
14150
14437
  }
14151
14438
  if (rootPkg) {
14152
- const wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
14439
+ let wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
14440
+ if (wsDirs.length === 0) {
14441
+ wsDirs = extractPnpmWorkspaceDirs(projectRoot);
14442
+ }
14153
14443
  for (const wsDir of wsDirs) {
14154
14444
  const wsPkg = tryReadJson(path24.join(wsDir, "package.json"));
14155
14445
  if (wsPkg && typeof wsPkg.name === "string") {
@@ -14766,7 +15056,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
14766
15056
 
14767
15057
  // src/e2e.ts
14768
15058
  init_util();
14769
- import { open, readdir as readdir5 } from "node:fs/promises";
15059
+ import { open, readdir as readdir6 } from "node:fs/promises";
14770
15060
  import * as path27 from "node:path";
14771
15061
  async function readBoundedText(filePath, maxBytes) {
14772
15062
  let handle;
@@ -14884,7 +15174,7 @@ async function scanWorkspace(root, maxDepth, signal) {
14884
15174
  }
14885
15175
  let entries;
14886
15176
  try {
14887
- entries = await readdir5(current.directory, { withFileTypes: true });
15177
+ entries = await readdir6(current.directory, { withFileTypes: true });
14888
15178
  } catch {
14889
15179
  continue;
14890
15180
  }
@@ -14943,7 +15233,7 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
14943
15233
  while (true) {
14944
15234
  const names = /* @__PURE__ */ new Set();
14945
15235
  try {
14946
- for (const entry of await readdir5(directory)) names.add(entry);
15236
+ for (const entry of await readdir6(directory)) names.add(entry);
14947
15237
  } catch {
14948
15238
  }
14949
15239
  if (names.has("pnpm-lock.yaml")) return "pnpm";
@@ -15011,7 +15301,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
15011
15301
  if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
15012
15302
  let entries;
15013
15303
  try {
15014
- entries = await readdir5(directory, { withFileTypes: true });
15304
+ entries = await readdir6(directory, { withFileTypes: true });
15015
15305
  } catch {
15016
15306
  continue;
15017
15307
  }
@@ -17468,7 +17758,7 @@ async function detectFixer(cwd) {
17468
17758
  init_util();
17469
17759
  import { spawn as spawn10 } from "node:child_process";
17470
17760
  import { statSync as statSync4 } from "node:fs";
17471
- import { dirname as dirname13, resolve as resolve13, sep as sep6 } from "node:path";
17761
+ import { dirname as dirname14, resolve as resolve13, sep as sep6 } from "node:path";
17472
17762
  import { assessCommitSafety } from "@wrongstack/core/coordination";
17473
17763
  import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
17474
17764
  var TIMEOUT_MS2 = 3e4;
@@ -17630,7 +17920,7 @@ function findGitDir2(cwd, projectRoot) {
17630
17920
  } catch {
17631
17921
  }
17632
17922
  if (dir === root) break;
17633
- const parent = dirname13(dir);
17923
+ const parent = dirname14(dir);
17634
17924
  if (parent === dir) break;
17635
17925
  dir = parent;
17636
17926
  }
@@ -19774,7 +20064,7 @@ function createKanbanPresenceWrapper(projectRoot, input, ctx) {
19774
20064
 
19775
20065
  // src/session-kanban.ts
19776
20066
  import { watch } from "node:fs";
19777
- import { basename as basename12, dirname as dirname14 } from "node:path";
20067
+ import { basename as basename12, dirname as dirname15 } from "node:path";
19778
20068
  import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
19779
20069
  import {
19780
20070
  loadPlan,
@@ -20246,7 +20536,7 @@ function attachSessionKanbanMirror(context) {
20246
20536
  const configureWatcher = () => {
20247
20537
  const planPath = context.meta["plan.path"];
20248
20538
  const taskPath = context.meta["task.path"];
20249
- const candidate = typeof planPath === "string" && planPath ? dirname14(planPath) : typeof taskPath === "string" && taskPath ? dirname14(taskPath) : "";
20539
+ const candidate = typeof planPath === "string" && planPath ? dirname15(planPath) : typeof taskPath === "string" && taskPath ? dirname15(taskPath) : "";
20250
20540
  if (!candidate || candidate === watchedDir) return;
20251
20541
  watcher?.close();
20252
20542
  watcher = null;
@@ -22128,12 +22418,13 @@ init_util();
22128
22418
  import * as fs28 from "node:fs/promises";
22129
22419
  import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
22130
22420
  import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
22421
+ var ADVANCED_MODE_META_KEY = "tools.read.advancedMode";
22131
22422
  var MAX_BYTES2 = 5 * 1024 * 1024;
22132
22423
  var readTool = {
22133
22424
  name: "read",
22134
22425
  category: "Filesystem",
22135
- 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.",
22136
- 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.",
22426
+ 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).",
22427
+ 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.",
22137
22428
  selection: {
22138
22429
  doNotUseWhen: "you need to search many files for matching content.",
22139
22430
  useInstead: ["grep"]
@@ -22163,11 +22454,15 @@ var readTool = {
22163
22454
  type: "string",
22164
22455
  enum: ["content", "summary"],
22165
22456
  description: "Return full line-numbered content (default) or a compact file summary with imports/exports/symbols."
22457
+ },
22458
+ includeSymbols: {
22459
+ type: "boolean",
22460
+ 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."
22166
22461
  }
22167
22462
  },
22168
22463
  required: ["path"]
22169
22464
  },
22170
- async execute(input, ctx) {
22465
+ async execute(input, ctx, execOpts) {
22171
22466
  if (!input?.path) {
22172
22467
  throw new ToolValidationError5({
22173
22468
  message: "read: path is required",
@@ -22175,6 +22470,7 @@ var readTool = {
22175
22470
  });
22176
22471
  }
22177
22472
  const absPath = await safeResolveReal(input.path, ctx);
22473
+ const shouldIncludeSymbols = input.includeSymbols === true || input.includeSymbols !== false && ctx.meta[ADVANCED_MODE_META_KEY] === true;
22178
22474
  let stat18;
22179
22475
  try {
22180
22476
  stat18 = await fs28.stat(absPath);
@@ -22218,13 +22514,15 @@ var readTool = {
22218
22514
  const requestedEnd = prior ? Math.min(offset + limit - 1, prior.totalLines) : offset + limit - 1;
22219
22515
  if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior, stat18.mtimeMs, offset, requestedEnd)) {
22220
22516
  ctx.recordRead(absPath, stat18.mtimeMs);
22517
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
22221
22518
  return {
22222
22519
  text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(stat18.mtimeMs)}; requested lines ${offset}-${requestedEnd} were already shown. Use offset/limit for a new range if needed.]`,
22223
22520
  total_lines: prior.totalLines,
22224
22521
  encoding: "utf8",
22225
22522
  truncated: requestedEnd < prior.totalLines,
22226
22523
  cached: true,
22227
- note: "Repeated read suppressed to save tokens."
22524
+ note: mergeSymbolNote("Repeated read suppressed to save tokens.", symResult2?.note),
22525
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
22228
22526
  };
22229
22527
  }
22230
22528
  const buf = await fs28.readFile(absPath);
@@ -22238,27 +22536,43 @@ var readTool = {
22238
22536
  if (input.mode === "summary") {
22239
22537
  ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
22240
22538
  rememberReadRange(ctx, absPath, stat18.mtimeMs, total, 1, Math.min(total, 200));
22539
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
22241
22540
  return {
22242
22541
  text: summarizeFile(input.path, stat18.size, allLines),
22243
22542
  total_lines: total,
22244
22543
  encoding: "utf8",
22245
22544
  truncated: total > 200,
22246
- note: "Summary mode returned compact structure instead of full file content."
22545
+ note: mergeSymbolNote(
22546
+ "Summary mode returned compact structure instead of full file content.",
22547
+ symResult2?.note
22548
+ ),
22549
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
22247
22550
  };
22248
22551
  }
22249
22552
  if (limit === 0) {
22250
22553
  ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
22251
22554
  rememberReadRange(ctx, absPath, stat18.mtimeMs, total, 1, 0);
22252
- return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
22555
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
22556
+ return {
22557
+ text: "",
22558
+ total_lines: total,
22559
+ encoding: "utf8",
22560
+ truncated: total > 0,
22561
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {},
22562
+ ...symResult2?.note ? { note: symResult2.note } : {}
22563
+ };
22253
22564
  }
22254
22565
  if (offset > total) {
22255
22566
  ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
22256
22567
  rememberReadRange(ctx, absPath, stat18.mtimeMs, total, total + 1, total + 1);
22568
+ const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
22257
22569
  return {
22258
22570
  text: `[offset ${offset} is past end of file "${input.path}" \u2014 file has ${total} line(s). Do not retry this offset.]`,
22259
22571
  total_lines: total,
22260
22572
  encoding: "utf8",
22261
- truncated: false
22573
+ truncated: false,
22574
+ ...symResult2?.symbols ? { symbols: symResult2.symbols } : {},
22575
+ ...symResult2?.note ? { note: symResult2.note } : {}
22262
22576
  };
22263
22577
  }
22264
22578
  const slice = allLines.slice(offset - 1, offset - 1 + limit);
@@ -22267,14 +22581,53 @@ var readTool = {
22267
22581
  const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
22268
22582
  ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
22269
22583
  rememberReadRange(ctx, absPath, stat18.mtimeMs, total, offset, offset + slice.length - 1);
22584
+ const symResult = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
22270
22585
  return {
22271
22586
  text: numbered,
22272
22587
  total_lines: total,
22273
22588
  encoding: "utf8",
22274
- truncated
22589
+ truncated,
22590
+ ...symResult?.symbols ? { symbols: symResult.symbols } : {},
22591
+ ...symResult?.note ? { note: symResult.note } : {}
22275
22592
  };
22276
22593
  }
22277
22594
  };
22595
+ async function fetchSymbolsForFile(absPath, ctx, signal) {
22596
+ try {
22597
+ const state = getIndexState();
22598
+ if (!state.ready) return {};
22599
+ const { results, total } = await searchCodebaseIndex(
22600
+ {
22601
+ projectRoot: ctx.projectRoot,
22602
+ indexDir: codebaseIndexDirOverride(ctx),
22603
+ query: "",
22604
+ file: absPath,
22605
+ limit: 500
22606
+ },
22607
+ { signal }
22608
+ );
22609
+ if (results.length === 0) return {};
22610
+ const sorted = results.map((r) => ({
22611
+ name: r.name,
22612
+ kind: r.kind,
22613
+ line: r.line,
22614
+ col: r.col,
22615
+ signature: r.signature
22616
+ })).sort((a, b) => a.line - b.line || a.col - b.col);
22617
+ const result = { symbols: sorted };
22618
+ if (total > results.length) {
22619
+ result.note = `Symbol listing truncated to ${results.length} of ${total} entries.`;
22620
+ }
22621
+ return result;
22622
+ } catch {
22623
+ return {};
22624
+ }
22625
+ }
22626
+ function mergeSymbolNote(note, symNote) {
22627
+ if (!symNote) return note;
22628
+ if (!note) return symNote;
22629
+ return `${note} ${symNote}`;
22630
+ }
22278
22631
  var READ_RANGES_META_KEY = "tools.read.ranges.v1";
22279
22632
  function getReadRanges(ctx) {
22280
22633
  const existing = ctx.meta[READ_RANGES_META_KEY];