@neuralsea/workspace-indexer 0.3.6 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -47,11 +47,14 @@ __export(index_exports, {
47
47
  WorkspaceLinker: () => WorkspaceLinker,
48
48
  WorkspaceStore: () => WorkspaceStore,
49
49
  asProgressSink: () => asProgressSink,
50
+ betterSqlite3Adapter: () => betterSqlite3Adapter,
50
51
  chunkSource: () => chunkSource,
51
52
  createAnnIndex: () => createAnnIndex,
52
53
  createNeo4jGraphStore: () => createNeo4jGraphStore,
53
54
  createVSCodeSymbolGraphProvider: () => createVSCodeSymbolGraphProvider,
54
55
  createVectorIndex: () => createVectorIndex,
56
+ createWorkspaceStore: () => createWorkspaceStore,
57
+ createWorkspaceStoreAsync: () => createWorkspaceStoreAsync,
55
58
  deepMergeProfile: () => deepMergeProfile,
56
59
  discoverGitRepos: () => discoverGitRepos,
57
60
  languageFromPath: () => languageFromPath,
@@ -59,6 +62,7 @@ __export(index_exports, {
59
62
  loadConfigFile: () => loadConfigFile,
60
63
  mergeIndexerConfig: () => mergeIndexerConfig,
61
64
  pickRepoOverride: () => pickRepoOverride,
65
+ sqlJsAdapter: () => sqlJsAdapter,
62
66
  stableSymbolId: () => stableSymbolId
63
67
  });
64
68
  module.exports = __toCommonJS(index_exports);
@@ -1263,8 +1267,8 @@ function mergeIndexerConfig(target, patch) {
1263
1267
  }
1264
1268
 
1265
1269
  // src/store/workspaceStore.ts
1266
- var import_node_fs5 = __toESM(require("fs"), 1);
1267
- var import_node_path7 = __toESM(require("path"), 1);
1270
+ var import_node_fs7 = __toESM(require("fs"), 1);
1271
+ var import_node_path9 = __toESM(require("path"), 1);
1268
1272
 
1269
1273
  // src/store/workspace/unitOfWork.ts
1270
1274
  var UnitOfWork = class {
@@ -1484,15 +1488,35 @@ var RepoLinksRepository = class {
1484
1488
  };
1485
1489
 
1486
1490
  // src/store/workspace/factory.ts
1487
- var import_node_fs4 = __toESM(require("fs"), 1);
1488
- var import_node_path6 = __toESM(require("path"), 1);
1491
+ var import_node_fs5 = __toESM(require("fs"), 1);
1492
+ var import_node_path7 = __toESM(require("path"), 1);
1489
1493
 
1490
1494
  // src/store/workspace/db.ts
1491
1495
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
1496
+ var import_node_fs4 = __toESM(require("fs"), 1);
1497
+ var import_node_path6 = __toESM(require("path"), 1);
1498
+ function detectFts5Support(db) {
1499
+ try {
1500
+ const rows = db.prepare(`PRAGMA compile_options`).all();
1501
+ if (rows.some((r) => String(r.compile_options ?? "").includes("ENABLE_FTS5"))) return true;
1502
+ } catch {
1503
+ }
1504
+ try {
1505
+ db.exec(`
1506
+ CREATE VIRTUAL TABLE IF NOT EXISTS __fts5_probe USING fts5(x);
1507
+ DROP TABLE __fts5_probe;
1508
+ `);
1509
+ return true;
1510
+ } catch {
1511
+ return false;
1512
+ }
1513
+ }
1492
1514
  var BetterSqlite3Adapter = class {
1493
1515
  db;
1516
+ capabilities;
1494
1517
  constructor(dbPath) {
1495
1518
  this.db = new import_better_sqlite3.default(dbPath);
1519
+ this.capabilities = { supportsFts5: detectFts5Support(this.db) };
1496
1520
  }
1497
1521
  pragma(sql) {
1498
1522
  this.db.pragma(sql);
@@ -1510,6 +1534,17 @@ var BetterSqlite3Adapter = class {
1510
1534
  this.db.close();
1511
1535
  }
1512
1536
  };
1537
+ var betterSqlite3Adapter = {
1538
+ open(dbPath) {
1539
+ import_node_fs4.default.mkdirSync(import_node_path6.default.dirname(dbPath), { recursive: true });
1540
+ const db = new BetterSqlite3Adapter(dbPath);
1541
+ db.pragma("journal_mode = WAL");
1542
+ return db;
1543
+ }
1544
+ };
1545
+
1546
+ // src/store/workspace/fts5.sql
1547
+ var fts5_default = "CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(\n id UNINDEXED,\n repo_id UNINDEXED,\n repo_root UNINDEXED,\n path,\n language,\n kind,\n text,\n tokenize='unicode61'\n);\n\n";
1513
1548
 
1514
1549
  // src/store/workspace/fts.ts
1515
1550
  var NoopFtsStrategy = class {
@@ -1533,18 +1568,7 @@ var Fts5Strategy = class {
1533
1568
  enabled = true;
1534
1569
  ins = null;
1535
1570
  init(db) {
1536
- db.exec(`
1537
- CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
1538
- id UNINDEXED,
1539
- repo_id UNINDEXED,
1540
- repo_root UNINDEXED,
1541
- path,
1542
- language,
1543
- kind,
1544
- text,
1545
- tokenize='unicode61'
1546
- );
1547
- `);
1571
+ db.exec(fts5_default);
1548
1572
  }
1549
1573
  clearRepo(repoId) {
1550
1574
  this.db.prepare(`DELETE FROM chunks_fts WHERE repo_id = ?`).run(repoId);
@@ -1628,109 +1652,26 @@ var WorkspaceMigrator = class {
1628
1652
  }
1629
1653
  };
1630
1654
 
1655
+ // src/store/workspace/baseSchema.sql
1656
+ var baseSchema_default = "CREATE TABLE IF NOT EXISTS meta (\n k TEXT PRIMARY KEY,\n v TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS repos (\n repo_id TEXT PRIMARY KEY,\n repo_root TEXT NOT NULL,\n head_commit TEXT NOT NULL,\n head_branch TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_repos_root ON repos(repo_root);\n\nCREATE TABLE IF NOT EXISTS files (\n repo_id TEXT NOT NULL,\n path TEXT NOT NULL,\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n language TEXT NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY(repo_id, path)\n);\n\nCREATE INDEX IF NOT EXISTS idx_files_repo ON files(repo_id);\n\nCREATE TABLE IF NOT EXISTS chunks (\n id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n repo_root TEXT NOT NULL,\n path TEXT NOT NULL,\n language TEXT NOT NULL,\n kind TEXT NOT NULL DEFAULT 'chunk',\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n content_hash TEXT NOT NULL,\n tokens INTEGER NOT NULL,\n file_mtime INTEGER NOT NULL,\n text TEXT NOT NULL,\n embedding BLOB NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_chunks_repo_path ON chunks(repo_id, path);\nCREATE INDEX IF NOT EXISTS idx_chunks_kind_repo_path ON chunks(kind, repo_id, path);\n\nCREATE TABLE IF NOT EXISTS edges (\n repo_id TEXT NOT NULL,\n from_path TEXT NOT NULL,\n kind TEXT NOT NULL,\n value TEXT NOT NULL,\n PRIMARY KEY(repo_id, from_path, kind, value)\n);\n\nCREATE INDEX IF NOT EXISTS idx_edges_repo_from ON edges(repo_id, from_path);\n\nCREATE TABLE IF NOT EXISTS symbols (\n id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n repo_root TEXT NOT NULL,\n path TEXT NOT NULL,\n language TEXT NOT NULL,\n name TEXT NOT NULL,\n kind TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n start_char INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n end_char INTEGER NOT NULL,\n container_name TEXT NOT NULL DEFAULT '',\n detail TEXT NOT NULL DEFAULT ''\n);\n\nCREATE INDEX IF NOT EXISTS idx_symbols_repo_path ON symbols(repo_id, path);\nCREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);\n\nCREATE TABLE IF NOT EXISTS symbol_edges (\n repo_id TEXT NOT NULL,\n from_id TEXT NOT NULL,\n to_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n from_path TEXT NOT NULL,\n to_path TEXT NOT NULL,\n PRIMARY KEY(repo_id, from_id, to_id, kind)\n);\n\nCREATE INDEX IF NOT EXISTS idx_symbol_edges_from ON symbol_edges(repo_id, from_id);\nCREATE INDEX IF NOT EXISTS idx_symbol_edges_paths ON symbol_edges(repo_id, from_path);\n\n";
1657
+
1631
1658
  // src/store/workspace/factory.ts
1632
- function createWorkspaceDb(dbPath) {
1633
- import_node_fs4.default.mkdirSync(import_node_path6.default.dirname(dbPath), { recursive: true });
1634
- const db = new BetterSqlite3Adapter(dbPath);
1635
- db.pragma("journal_mode = WAL");
1636
- return db;
1659
+ function createWorkspaceDb(dbPath, opts = {}) {
1660
+ import_node_fs5.default.mkdirSync(import_node_path7.default.dirname(dbPath), { recursive: true });
1661
+ return (opts.db ?? betterSqlite3Adapter).open(dbPath);
1637
1662
  }
1638
1663
  function createWorkspaceBaseSchema(db) {
1639
- db.exec(`
1640
- CREATE TABLE IF NOT EXISTS meta (
1641
- k TEXT PRIMARY KEY,
1642
- v TEXT NOT NULL
1643
- );
1644
-
1645
- CREATE TABLE IF NOT EXISTS repos (
1646
- repo_id TEXT PRIMARY KEY,
1647
- repo_root TEXT NOT NULL,
1648
- head_commit TEXT NOT NULL,
1649
- head_branch TEXT NOT NULL,
1650
- updated_at INTEGER NOT NULL
1651
- );
1652
-
1653
- CREATE UNIQUE INDEX IF NOT EXISTS idx_repos_root ON repos(repo_root);
1654
-
1655
- CREATE TABLE IF NOT EXISTS files (
1656
- repo_id TEXT NOT NULL,
1657
- path TEXT NOT NULL,
1658
- hash TEXT NOT NULL,
1659
- mtime INTEGER NOT NULL,
1660
- language TEXT NOT NULL,
1661
- size INTEGER NOT NULL,
1662
- PRIMARY KEY(repo_id, path)
1663
- );
1664
-
1665
- CREATE INDEX IF NOT EXISTS idx_files_repo ON files(repo_id);
1666
-
1667
- CREATE TABLE IF NOT EXISTS chunks (
1668
- id TEXT PRIMARY KEY,
1669
- repo_id TEXT NOT NULL,
1670
- repo_root TEXT NOT NULL,
1671
- path TEXT NOT NULL,
1672
- language TEXT NOT NULL,
1673
- kind TEXT NOT NULL DEFAULT 'chunk',
1674
- start_line INTEGER NOT NULL,
1675
- end_line INTEGER NOT NULL,
1676
- content_hash TEXT NOT NULL,
1677
- tokens INTEGER NOT NULL,
1678
- file_mtime INTEGER NOT NULL,
1679
- text TEXT NOT NULL,
1680
- embedding BLOB NOT NULL
1681
- );
1682
-
1683
- CREATE INDEX IF NOT EXISTS idx_chunks_repo_path ON chunks(repo_id, path);
1684
- CREATE INDEX IF NOT EXISTS idx_chunks_kind_repo_path ON chunks(kind, repo_id, path);
1685
-
1686
- CREATE TABLE IF NOT EXISTS edges (
1687
- repo_id TEXT NOT NULL,
1688
- from_path TEXT NOT NULL,
1689
- kind TEXT NOT NULL,
1690
- value TEXT NOT NULL,
1691
- PRIMARY KEY(repo_id, from_path, kind, value)
1692
- );
1693
-
1694
- CREATE INDEX IF NOT EXISTS idx_edges_repo_from ON edges(repo_id, from_path);
1695
-
1696
- CREATE TABLE IF NOT EXISTS symbols (
1697
- id TEXT PRIMARY KEY,
1698
- repo_id TEXT NOT NULL,
1699
- repo_root TEXT NOT NULL,
1700
- path TEXT NOT NULL,
1701
- language TEXT NOT NULL,
1702
- name TEXT NOT NULL,
1703
- kind TEXT NOT NULL,
1704
- start_line INTEGER NOT NULL,
1705
- start_char INTEGER NOT NULL,
1706
- end_line INTEGER NOT NULL,
1707
- end_char INTEGER NOT NULL,
1708
- container_name TEXT NOT NULL DEFAULT '',
1709
- detail TEXT NOT NULL DEFAULT ''
1710
- );
1711
-
1712
- CREATE INDEX IF NOT EXISTS idx_symbols_repo_path ON symbols(repo_id, path);
1713
- CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
1714
-
1715
- CREATE TABLE IF NOT EXISTS symbol_edges (
1716
- repo_id TEXT NOT NULL,
1717
- from_id TEXT NOT NULL,
1718
- to_id TEXT NOT NULL,
1719
- kind TEXT NOT NULL,
1720
- from_path TEXT NOT NULL,
1721
- to_path TEXT NOT NULL,
1722
- PRIMARY KEY(repo_id, from_id, to_id, kind)
1723
- );
1724
-
1725
- CREATE INDEX IF NOT EXISTS idx_symbol_edges_from ON symbol_edges(repo_id, from_id);
1726
- CREATE INDEX IF NOT EXISTS idx_symbol_edges_paths ON symbol_edges(repo_id, from_path);
1727
- `);
1664
+ db.exec(baseSchema_default);
1728
1665
  }
1729
1666
  function createWorkspaceFts(db, meta, opts = {}) {
1730
1667
  if (opts.fts === "off") {
1731
1668
  meta.set("fts", "0");
1732
1669
  return new NoopFtsStrategy();
1733
1670
  }
1671
+ if (!db.capabilities.supportsFts5) {
1672
+ meta.set("fts", "0");
1673
+ return new NoopFtsStrategy();
1674
+ }
1734
1675
  try {
1735
1676
  const fts = new Fts5Strategy(db);
1736
1677
  fts.init(db);
@@ -1746,17 +1687,153 @@ function migrateWorkspaceDb(db, meta) {
1746
1687
  migrator.migrateToLatest();
1747
1688
  }
1748
1689
 
1690
+ // src/store/workspace/sqlJsAdapter.ts
1691
+ var import_node_fs6 = __toESM(require("fs"), 1);
1692
+ var import_node_module2 = require("module");
1693
+ var import_node_path8 = __toESM(require("path"), 1);
1694
+ var import_meta2 = {};
1695
+ function detectFts5Support2(db) {
1696
+ try {
1697
+ db.exec(`
1698
+ CREATE VIRTUAL TABLE IF NOT EXISTS __fts5_probe USING fts5(x);
1699
+ DROP TABLE __fts5_probe;
1700
+ `);
1701
+ return true;
1702
+ } catch {
1703
+ return false;
1704
+ }
1705
+ }
1706
+ var SqlJsStatement = class {
1707
+ constructor(stmt) {
1708
+ this.stmt = stmt;
1709
+ }
1710
+ run(...args) {
1711
+ this.stmt.run(args);
1712
+ return void 0;
1713
+ }
1714
+ get(...args) {
1715
+ this.stmt.bind(args);
1716
+ const hasRow = this.stmt.step();
1717
+ if (!hasRow) {
1718
+ this.stmt.reset();
1719
+ return void 0;
1720
+ }
1721
+ const row = this.stmt.getAsObject();
1722
+ this.stmt.reset();
1723
+ return row;
1724
+ }
1725
+ all(...args) {
1726
+ this.stmt.bind(args);
1727
+ const rows = [];
1728
+ while (this.stmt.step()) rows.push(this.stmt.getAsObject());
1729
+ this.stmt.reset();
1730
+ return rows;
1731
+ }
1732
+ };
1733
+ var SqlJsDbAdapter = class {
1734
+ constructor(db, dbPath) {
1735
+ this.db = db;
1736
+ this.dbPath = dbPath;
1737
+ this.capabilities = { supportsFts5: detectFts5Support2(db) };
1738
+ }
1739
+ capabilities;
1740
+ pragma(sql) {
1741
+ this.exec(`PRAGMA ${sql}`);
1742
+ }
1743
+ exec(sql) {
1744
+ this.db.exec(sql);
1745
+ }
1746
+ prepare(sql) {
1747
+ return new SqlJsStatement(this.db.prepare(sql));
1748
+ }
1749
+ transaction(fn) {
1750
+ return () => {
1751
+ this.db.exec("BEGIN");
1752
+ try {
1753
+ const out = fn();
1754
+ this.db.exec("COMMIT");
1755
+ return out;
1756
+ } catch (e) {
1757
+ try {
1758
+ this.db.exec("ROLLBACK");
1759
+ } catch {
1760
+ }
1761
+ throw e;
1762
+ }
1763
+ };
1764
+ }
1765
+ close() {
1766
+ if (this.dbPath && this.dbPath !== ":memory:") {
1767
+ import_node_fs6.default.mkdirSync(import_node_path8.default.dirname(this.dbPath), { recursive: true });
1768
+ const bytes = this.db.export();
1769
+ import_node_fs6.default.writeFileSync(this.dbPath, Buffer.from(bytes));
1770
+ }
1771
+ this.db.close();
1772
+ }
1773
+ };
1774
+ function defaultLocateFile(file) {
1775
+ const spec = `sql.js/dist/${file}`;
1776
+ try {
1777
+ if (typeof require === "function" && typeof require.resolve === "function") {
1778
+ return require.resolve(spec);
1779
+ }
1780
+ } catch {
1781
+ }
1782
+ try {
1783
+ const req = (0, import_node_module2.createRequire)(import_meta2.url);
1784
+ return req.resolve(spec);
1785
+ } catch {
1786
+ return file;
1787
+ }
1788
+ }
1789
+ async function sqlJsAdapter(opts = {}) {
1790
+ let init;
1791
+ try {
1792
+ const mod = await import("sql.js");
1793
+ init = mod?.default ?? mod;
1794
+ } catch (e) {
1795
+ throw new Error(`sqlJsAdapter requires optional dependency 'sql.js' (install it to use this adapter): ${String(e?.message ?? e)}`);
1796
+ }
1797
+ const SQL = await init({
1798
+ locateFile: opts.locateFile ?? defaultLocateFile,
1799
+ wasmBinary: opts.wasmBinary
1800
+ });
1801
+ return {
1802
+ open(dbPath) {
1803
+ const abs = dbPath === ":memory:" ? ":memory:" : import_node_path8.default.resolve(dbPath);
1804
+ const bytes = abs !== ":memory:" && import_node_fs6.default.existsSync(abs) ? new Uint8Array(import_node_fs6.default.readFileSync(abs)) : void 0;
1805
+ const db = bytes ? new SQL.Database(bytes) : new SQL.Database();
1806
+ return new SqlJsDbAdapter(db, abs);
1807
+ }
1808
+ };
1809
+ }
1810
+
1749
1811
  // src/store/workspaceStore.ts
1812
+ function createWorkspaceStore(dbPath, opts = {}) {
1813
+ return new WorkspaceStore(dbPath, opts);
1814
+ }
1815
+ async function defaultWorkspaceDbFactory() {
1816
+ try {
1817
+ return await sqlJsAdapter();
1818
+ } catch {
1819
+ return betterSqlite3Adapter;
1820
+ }
1821
+ }
1822
+ async function createWorkspaceStoreAsync(dbPath, opts = {}) {
1823
+ const dbFactory = opts.db ? await Promise.resolve(opts.db) : await defaultWorkspaceDbFactory();
1824
+ return new WorkspaceStore(dbPath, { ...opts, db: dbFactory });
1825
+ }
1750
1826
  var WorkspaceStore = class {
1751
1827
  constructor(dbPath, opts = {}) {
1752
1828
  this.dbPath = dbPath;
1753
1829
  this.opts = opts;
1754
- this.db = createWorkspaceDb(dbPath);
1830
+ this.db = createWorkspaceDb(dbPath, { db: opts.db });
1755
1831
  this.uow = new UnitOfWork(this.db);
1756
1832
  createWorkspaceBaseSchema(this.db);
1757
1833
  this.meta = new MetaRepository(this.db);
1758
1834
  migrateWorkspaceDb(this.db, this.meta);
1759
1835
  const fts = createWorkspaceFts(this.db, this.meta, opts);
1836
+ this.ftsEnabledInternal = fts.enabled;
1760
1837
  this.repoHeads = new RepoHeadsRepository(this.db);
1761
1838
  this.files = new FilesRepository(this.db);
1762
1839
  this.edges = new EdgesRepository(this.db);
@@ -1766,6 +1843,7 @@ var WorkspaceStore = class {
1766
1843
  }
1767
1844
  db;
1768
1845
  uow;
1846
+ ftsEnabledInternal;
1769
1847
  meta;
1770
1848
  repoHeads;
1771
1849
  files;
@@ -1774,6 +1852,9 @@ var WorkspaceStore = class {
1774
1852
  symbols;
1775
1853
  chunks;
1776
1854
  opts;
1855
+ get ftsEnabled() {
1856
+ return this.ftsEnabledInternal;
1857
+ }
1777
1858
  setMeta(k, v) {
1778
1859
  this.meta.set(k, v);
1779
1860
  }
@@ -1857,9 +1938,9 @@ var WorkspaceStore = class {
1857
1938
  * The chunk boundaries are approximate; the stored row includes start/end line.
1858
1939
  */
1859
1940
  getChunkTextFallback(row) {
1860
- const abs = import_node_path7.default.join(row.repo_root, row.path.split("/").join(import_node_path7.default.sep));
1941
+ const abs = import_node_path9.default.join(row.repo_root, row.path.split("/").join(import_node_path9.default.sep));
1861
1942
  try {
1862
- const raw = import_node_fs5.default.readFileSync(abs, "utf8");
1943
+ const raw = import_node_fs7.default.readFileSync(abs, "utf8");
1863
1944
  const lines = raw.split(/\r?\n/);
1864
1945
  const start = Math.max(1, row.start_line);
1865
1946
  const end = Math.max(start, row.end_line);
@@ -1886,17 +1967,17 @@ range:${r.startLine}:${r.startCharacter}-${r.endLine}:${r.endCharacter}`;
1886
1967
  }
1887
1968
 
1888
1969
  // src/symbolGraph/vscodeProvider.ts
1889
- var import_node_path9 = __toESM(require("path"), 1);
1890
- var import_node_module2 = require("module");
1970
+ var import_node_path11 = __toESM(require("path"), 1);
1971
+ var import_node_module3 = require("module");
1891
1972
 
1892
1973
  // src/symbolGraph/strategies.ts
1893
- var import_node_path8 = __toESM(require("path"), 1);
1974
+ var import_node_path10 = __toESM(require("path"), 1);
1894
1975
  function toPosixRel(repoRoot, absPath) {
1895
- const abs = import_node_path8.default.resolve(absPath);
1896
- const root = import_node_path8.default.resolve(repoRoot);
1897
- const rel = import_node_path8.default.relative(root, abs);
1898
- if (!rel || rel.startsWith("..") || import_node_path8.default.isAbsolute(rel)) return null;
1899
- return rel.split(import_node_path8.default.sep).join("/");
1976
+ const abs = import_node_path10.default.resolve(absPath);
1977
+ const root = import_node_path10.default.resolve(repoRoot);
1978
+ const rel = import_node_path10.default.relative(root, abs);
1979
+ if (!rel || rel.startsWith("..") || import_node_path10.default.isAbsolute(rel)) return null;
1980
+ return rel.split(import_node_path10.default.sep).join("/");
1900
1981
  }
1901
1982
  function fromLspRange(r) {
1902
1983
  return {
@@ -1963,7 +2044,7 @@ var SymbolGraphIndexer = class {
1963
2044
  }
1964
2045
  cache = /* @__PURE__ */ new Map();
1965
2046
  async indexDocument(input, cancel = {}) {
1966
- const doc = await this.lsp.openTextDocument(import_node_path8.default.join(input.repoRoot, input.path.split("/").join(import_node_path8.default.sep)));
2047
+ const doc = await this.lsp.openTextDocument(import_node_path10.default.join(input.repoRoot, input.path.split("/").join(import_node_path10.default.sep)));
1967
2048
  const key = cacheKeyFor(doc, input);
1968
2049
  const cached2 = this.cache.get(key);
1969
2050
  if (cached2) {
@@ -1997,7 +2078,7 @@ var SymbolGraphIndexer = class {
1997
2078
  * Intended for staged indexing and on-demand expansion during retrieval.
1998
2079
  */
1999
2080
  async expandDocumentEdges(input, cancel = {}) {
2000
- const doc = await this.lsp.openTextDocument(import_node_path8.default.join(input.repoRoot, input.path.split("/").join(import_node_path8.default.sep)));
2081
+ const doc = await this.lsp.openTextDocument(import_node_path10.default.join(input.repoRoot, input.path.split("/").join(import_node_path10.default.sep)));
2001
2082
  const key = cacheKeyFor(doc, input);
2002
2083
  const cached2 = this.cache.get(key);
2003
2084
  if (!cached2) {
@@ -2117,12 +2198,12 @@ function findSymbolAt(syms, pos) {
2117
2198
  }
2118
2199
 
2119
2200
  // src/symbolGraph/vscodeProvider.ts
2120
- var import_meta3 = {};
2201
+ var import_meta4 = {};
2121
2202
  function fromPosixPath2(posixRelPath) {
2122
- return posixRelPath.split("/").join(import_node_path9.default.sep);
2203
+ return posixRelPath.split("/").join(import_node_path11.default.sep);
2123
2204
  }
2124
2205
  function toPosixPath(p) {
2125
- return p.split(import_node_path9.default.sep).join("/");
2206
+ return p.split(import_node_path11.default.sep).join("/");
2126
2207
  }
2127
2208
  function kindFromVscode(vscode, k) {
2128
2209
  const SK = vscode?.SymbolKind;
@@ -2183,7 +2264,7 @@ function toLspSymbols(vscode, res) {
2183
2264
  return res.map(visit);
2184
2265
  }
2185
2266
  function toIndexInput(doc, input) {
2186
- const rel = toPosixPath(import_node_path9.default.relative(import_node_path9.default.resolve(input.repoRoot), import_node_path9.default.resolve(doc.fsPath)));
2267
+ const rel = toPosixPath(import_node_path11.default.relative(import_node_path11.default.resolve(input.repoRoot), import_node_path11.default.resolve(doc.fsPath)));
2187
2268
  return { ...input, path: rel || input.path };
2188
2269
  }
2189
2270
  function makeVscodeFacade(vscode) {
@@ -2225,7 +2306,7 @@ function makeVscodeFacade(vscode) {
2225
2306
  async function createVSCodeSymbolGraphProvider(opts) {
2226
2307
  let vscode;
2227
2308
  try {
2228
- const require2 = (0, import_node_module2.createRequire)(import_meta3.url);
2309
+ const require2 = (0, import_node_module3.createRequire)(import_meta4.url);
2229
2310
  vscode = require2("vscode");
2230
2311
  } catch {
2231
2312
  return null;
@@ -2239,12 +2320,12 @@ async function createVSCodeSymbolGraphProvider(opts) {
2239
2320
  id: "vscode.symbolGraph",
2240
2321
  supports: (language) => languages.has(language),
2241
2322
  async indexDocument(input) {
2242
- const absPath = import_node_path9.default.join(input.repoRoot, fromPosixPath2(input.path));
2323
+ const absPath = import_node_path11.default.join(input.repoRoot, fromPosixPath2(input.path));
2243
2324
  const doc = await lsp.openTextDocument(absPath);
2244
2325
  return await indexer.indexDocument(toIndexInput(doc, input), { signal: input.signal });
2245
2326
  },
2246
2327
  async expandDocumentEdges(input, o) {
2247
- const absPath = import_node_path9.default.join(input.repoRoot, fromPosixPath2(input.path));
2328
+ const absPath = import_node_path11.default.join(input.repoRoot, fromPosixPath2(input.path));
2248
2329
  const doc = await lsp.openTextDocument(absPath);
2249
2330
  return await indexer.expandDocumentEdges(toIndexInput(doc, input), { signal: o?.signal });
2250
2331
  }
@@ -2252,8 +2333,8 @@ async function createVSCodeSymbolGraphProvider(opts) {
2252
2333
  }
2253
2334
 
2254
2335
  // src/graph/neo4j.ts
2255
- var import_node_module3 = require("module");
2256
- var import_meta4 = {};
2336
+ var import_node_module4 = require("module");
2337
+ var import_meta5 = {};
2257
2338
  async function runSession(driver, database, fn) {
2258
2339
  const session = driver.session(database ? { database } : void 0);
2259
2340
  try {
@@ -2694,7 +2775,7 @@ var Neo4jGraphStore = class {
2694
2775
  };
2695
2776
  async function createNeo4jGraphStore(cfg) {
2696
2777
  try {
2697
- const require2 = (0, import_node_module3.createRequire)(import_meta4.url);
2778
+ const require2 = (0, import_node_module4.createRequire)(import_meta5.url);
2698
2779
  const neo4j = require2("neo4j-driver");
2699
2780
  const driver = neo4j.driver(cfg.uri, neo4j.auth.basic(cfg.user, cfg.password));
2700
2781
  const store = new Neo4jGraphStore(driver, cfg);
@@ -2737,8 +2818,8 @@ function createAnnIndex(config) {
2737
2818
  }
2738
2819
 
2739
2820
  // src/indexer/repoIndexer.ts
2740
- var import_node_fs12 = __toESM(require("fs"), 1);
2741
- var import_node_path17 = __toESM(require("path"), 1);
2821
+ var import_node_fs14 = __toESM(require("fs"), 1);
2822
+ var import_node_path19 = __toESM(require("path"), 1);
2742
2823
  var import_p_limit2 = __toESM(require("p-limit"), 1);
2743
2824
 
2744
2825
  // src/git.ts
@@ -2774,28 +2855,28 @@ async function listChangedFiles(repoRoot, baseRef = "HEAD~1") {
2774
2855
  }
2775
2856
 
2776
2857
  // src/ignore.ts
2777
- var import_node_fs6 = __toESM(require("fs"), 1);
2778
- var import_node_path10 = __toESM(require("path"), 1);
2858
+ var import_node_fs8 = __toESM(require("fs"), 1);
2859
+ var import_node_path12 = __toESM(require("path"), 1);
2779
2860
  var import_ignore = __toESM(require("ignore"), 1);
2780
2861
  function loadExtraIgnore(repoRoot, ignoreFiles) {
2781
2862
  const ig = (0, import_ignore.default)();
2782
2863
  for (const name of ignoreFiles) {
2783
- const p = import_node_path10.default.join(repoRoot, name);
2784
- if (!import_node_fs6.default.existsSync(p)) continue;
2785
- const raw = import_node_fs6.default.readFileSync(p, "utf8");
2864
+ const p = import_node_path12.default.join(repoRoot, name);
2865
+ if (!import_node_fs8.default.existsSync(p)) continue;
2866
+ const raw = import_node_fs8.default.readFileSync(p, "utf8");
2786
2867
  ig.add(raw.split(/\r?\n/));
2787
2868
  }
2788
2869
  return (posixRelPath) => ig.ignores(posixRelPath);
2789
2870
  }
2790
2871
 
2791
2872
  // src/store/embeddingCache.ts
2792
- var import_node_fs7 = __toESM(require("fs"), 1);
2793
- var import_node_path11 = __toESM(require("path"), 1);
2873
+ var import_node_fs9 = __toESM(require("fs"), 1);
2874
+ var import_node_path13 = __toESM(require("path"), 1);
2794
2875
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
2795
2876
  var EmbeddingCache = class {
2796
2877
  db;
2797
2878
  constructor(cacheFilePath) {
2798
- import_node_fs7.default.mkdirSync(import_node_path11.default.dirname(cacheFilePath), { recursive: true });
2879
+ import_node_fs9.default.mkdirSync(import_node_path13.default.dirname(cacheFilePath), { recursive: true });
2799
2880
  this.db = new import_better_sqlite32.default(cacheFilePath);
2800
2881
  this.db.pragma("journal_mode = WAL");
2801
2882
  this.db.exec(`
@@ -2831,13 +2912,13 @@ var EmbeddingCache = class {
2831
2912
  };
2832
2913
 
2833
2914
  // src/store/repoStore.ts
2834
- var import_node_fs8 = __toESM(require("fs"), 1);
2835
- var import_node_path12 = __toESM(require("path"), 1);
2915
+ var import_node_fs10 = __toESM(require("fs"), 1);
2916
+ var import_node_path14 = __toESM(require("path"), 1);
2836
2917
  var import_better_sqlite33 = __toESM(require("better-sqlite3"), 1);
2837
2918
  var RepoStore = class {
2838
2919
  db;
2839
2920
  constructor(dbPath) {
2840
- import_node_fs8.default.mkdirSync(import_node_path12.default.dirname(dbPath), { recursive: true });
2921
+ import_node_fs10.default.mkdirSync(import_node_path14.default.dirname(dbPath), { recursive: true });
2841
2922
  this.db = new import_better_sqlite33.default(dbPath);
2842
2923
  this.db.pragma("journal_mode = WAL");
2843
2924
  this.db.exec(`
@@ -3132,10 +3213,10 @@ function resolveIndexerConfig(config = {}) {
3132
3213
  }
3133
3214
 
3134
3215
  // src/indexer/repoIndexer/utils.ts
3135
- var import_node_fs9 = __toESM(require("fs"), 1);
3136
- var import_node_path13 = __toESM(require("path"), 1);
3216
+ var import_node_fs11 = __toESM(require("fs"), 1);
3217
+ var import_node_path15 = __toESM(require("path"), 1);
3137
3218
  function repoIdFromRoot(repoRoot) {
3138
- return sha256Hex(import_node_path13.default.resolve(repoRoot)).slice(0, 16);
3219
+ return sha256Hex(import_node_path15.default.resolve(repoRoot)).slice(0, 16);
3139
3220
  }
3140
3221
  function looksBinary(buf) {
3141
3222
  let nul = 0;
@@ -3222,8 +3303,8 @@ var VectorManager = class {
3222
3303
  };
3223
3304
 
3224
3305
  // src/indexer/repoIndexer/fileIndexer.ts
3225
- var import_node_fs10 = __toESM(require("fs"), 1);
3226
- var import_node_path14 = __toESM(require("path"), 1);
3306
+ var import_node_fs12 = __toESM(require("fs"), 1);
3307
+ var import_node_path16 = __toESM(require("path"), 1);
3227
3308
 
3228
3309
  // src/relations.ts
3229
3310
  function extractTsRelations(virtualFileName, sourceText) {
@@ -3462,10 +3543,10 @@ var RepoFileIndexer = class {
3462
3543
  });
3463
3544
  };
3464
3545
  const readStartedAt = Date.now();
3465
- const abs = import_node_path14.default.join(this.repoRoot, fromPosixPath(posixRelPath));
3546
+ const abs = import_node_path16.default.join(this.repoRoot, fromPosixPath(posixRelPath));
3466
3547
  let stat;
3467
3548
  try {
3468
- stat = import_node_fs10.default.statSync(abs);
3549
+ stat = import_node_fs12.default.statSync(abs);
3469
3550
  } catch {
3470
3551
  this.emit({ type: "repo/index/file/skip", repoRoot: this.repoRoot, path: posixRelPath, reason: "missing" });
3471
3552
  return;
@@ -3478,7 +3559,7 @@ var RepoFileIndexer = class {
3478
3559
  this.emit({ type: "repo/index/file/skip", repoRoot: this.repoRoot, path: posixRelPath, reason: "too_large" });
3479
3560
  return;
3480
3561
  }
3481
- const buf = import_node_fs10.default.readFileSync(abs);
3562
+ const buf = import_node_fs12.default.readFileSync(abs);
3482
3563
  if (looksBinary(buf)) {
3483
3564
  this.emit({ type: "repo/index/file/skip", repoRoot: this.repoRoot, path: posixRelPath, reason: "binary" });
3484
3565
  return;
@@ -3564,7 +3645,7 @@ var RepoFileIndexer = class {
3564
3645
  continue;
3565
3646
  }
3566
3647
  embedTexts.push(
3567
- `repo:${import_node_path14.default.basename(this.repoRoot)}
3648
+ `repo:${import_node_path16.default.basename(this.repoRoot)}
3568
3649
  path:${posixRelPath}
3569
3650
  language:${language}
3570
3651
  kind:${ch.kind}
@@ -3758,8 +3839,8 @@ ${ch.text}`
3758
3839
  };
3759
3840
 
3760
3841
  // src/indexer/repoIndexer/retriever.ts
3761
- var import_node_fs11 = __toESM(require("fs"), 1);
3762
- var import_node_path15 = __toESM(require("path"), 1);
3842
+ var import_node_fs13 = __toESM(require("fs"), 1);
3843
+ var import_node_path17 = __toESM(require("path"), 1);
3763
3844
 
3764
3845
  // src/retrieval/fts.ts
3765
3846
  function ftsQueryFromText(input) {
@@ -3850,9 +3931,9 @@ var RepoRetriever = class {
3850
3931
  return rows.map((r) => ({ id: r.id, score: bm25ToScore01(r.bm25) }));
3851
3932
  }
3852
3933
  readChunkTextFallback(row) {
3853
- const abs = import_node_path15.default.join(this.repoRoot, fromPosixPath(row.path));
3934
+ const abs = import_node_path17.default.join(this.repoRoot, fromPosixPath(row.path));
3854
3935
  try {
3855
- const raw = import_node_fs11.default.readFileSync(abs, "utf8");
3936
+ const raw = import_node_fs13.default.readFileSync(abs, "utf8");
3856
3937
  const lines = raw.split(/\r?\n/);
3857
3938
  const start = Math.max(1, row.start_line);
3858
3939
  const end = Math.max(start, row.end_line);
@@ -3944,7 +4025,7 @@ var RepoRetriever = class {
3944
4025
  `${spec}/index.tsx`,
3945
4026
  `${spec}/index.js`,
3946
4027
  `${spec}/index.jsx`
3947
- ].map((s) => import_node_path15.default.posix.normalize(import_node_path15.default.posix.join(import_node_path15.default.posix.dirname(row.path), s)));
4028
+ ].map((s) => import_node_path17.default.posix.normalize(import_node_path17.default.posix.join(import_node_path17.default.posix.dirname(row.path), s)));
3948
4029
  for (const c of candidates) {
3949
4030
  const syn = store.listChunksForFile(c, "synopsis")[0];
3950
4031
  if (syn) {
@@ -3971,7 +4052,7 @@ var RepoRetriever = class {
3971
4052
  };
3972
4053
 
3973
4054
  // src/indexer/repoIndexer/watcher.ts
3974
- var import_node_path16 = __toESM(require("path"), 1);
4055
+ var import_node_path18 = __toESM(require("path"), 1);
3975
4056
  var import_chokidar = __toESM(require("chokidar"), 1);
3976
4057
  var RepoWatcher = class {
3977
4058
  constructor(repoRoot, debounceMs, ignored, onHeadChanged, onFileChanged, onFileAdded, onFileDeleted, onWatchEvent) {
@@ -3992,14 +4073,14 @@ var RepoWatcher = class {
3992
4073
  if (timer) clearTimeout(timer);
3993
4074
  timer = setTimeout(fn, this.debounceMs);
3994
4075
  };
3995
- const headPath = import_node_path16.default.join(this.repoRoot, ".git", "HEAD");
4076
+ const headPath = import_node_path18.default.join(this.repoRoot, ".git", "HEAD");
3996
4077
  this.watcher = import_chokidar.default.watch([this.repoRoot, headPath], {
3997
4078
  ignoreInitial: true,
3998
4079
  ignored: this.ignored
3999
4080
  });
4000
4081
  this.watcher.on("change", (p) => {
4001
- const rel = import_node_path16.default.relative(this.repoRoot, p);
4002
- const posix = rel.split(import_node_path16.default.sep).join("/");
4082
+ const rel = import_node_path18.default.relative(this.repoRoot, p);
4083
+ const posix = rel.split(import_node_path18.default.sep).join("/");
4003
4084
  if (posix === ".git/HEAD") {
4004
4085
  this.onWatchEvent?.("head", posix);
4005
4086
  schedule(() => this.onHeadChanged());
@@ -4009,14 +4090,14 @@ var RepoWatcher = class {
4009
4090
  schedule(() => this.onFileChanged(posix));
4010
4091
  });
4011
4092
  this.watcher.on("add", (p) => {
4012
- const rel = import_node_path16.default.relative(this.repoRoot, p);
4013
- const posix = rel.split(import_node_path16.default.sep).join("/");
4093
+ const rel = import_node_path18.default.relative(this.repoRoot, p);
4094
+ const posix = rel.split(import_node_path18.default.sep).join("/");
4014
4095
  this.onWatchEvent?.("add", posix);
4015
4096
  schedule(() => this.onFileAdded(posix));
4016
4097
  });
4017
4098
  this.watcher.on("unlink", (p) => {
4018
- const rel = import_node_path16.default.relative(this.repoRoot, p);
4019
- const posix = rel.split(import_node_path16.default.sep).join("/");
4099
+ const rel = import_node_path18.default.relative(this.repoRoot, p);
4100
+ const posix = rel.split(import_node_path18.default.sep).join("/");
4020
4101
  this.onWatchEvent?.("unlink", posix);
4021
4102
  schedule(() => this.onFileDeleted(posix));
4022
4103
  });
@@ -4031,7 +4112,7 @@ var RepoWatcher = class {
4031
4112
  var RepoIndexer = class {
4032
4113
  constructor(repoRoot, embedder, config = {}, workspaceStore, graphStore) {
4033
4114
  this.embedder = embedder;
4034
- this.repoRoot = import_node_path17.default.resolve(repoRoot);
4115
+ this.repoRoot = import_node_path19.default.resolve(repoRoot);
4035
4116
  this.repoId = repoIdFromRoot(this.repoRoot);
4036
4117
  this.rawConfig = { ...config };
4037
4118
  if (!this.rawConfig.cacheDir) this.rawConfig.cacheDir = defaultCacheDir();
@@ -4041,7 +4122,7 @@ var RepoIndexer = class {
4041
4122
  this.workspaceStore = workspaceStore ?? null;
4042
4123
  this.graphStore = graphStore ?? null;
4043
4124
  this.ann = createAnnIndex(this.rawConfig.ann);
4044
- this.embeddingCache = new EmbeddingCache(import_node_path17.default.join(this.config.cacheDir, "embedding-cache.sqlite"));
4125
+ this.embeddingCache = new EmbeddingCache(import_node_path19.default.join(this.config.cacheDir, "embedding-cache.sqlite"));
4045
4126
  this.vector = new VectorManager(
4046
4127
  this.config.vector,
4047
4128
  this.vectorMetric(),
@@ -4105,7 +4186,7 @@ var RepoIndexer = class {
4105
4186
  return this.store;
4106
4187
  }
4107
4188
  dbPathForCommit(commit) {
4108
- return import_node_path17.default.join(this.config.cacheDir, "index", this.repoId, `${commit}.sqlite`);
4189
+ return import_node_path19.default.join(this.config.cacheDir, "index", this.repoId, `${commit}.sqlite`);
4109
4190
  }
4110
4191
  vectorMetric() {
4111
4192
  return this.config.vector.metric ?? "cosine";
@@ -4232,8 +4313,8 @@ var RepoIndexer = class {
4232
4313
  return this.serial(async () => {
4233
4314
  await this.openForCurrentHead();
4234
4315
  if (!this.store || !this.fileIndexer) throw new Error("RepoStore not initialised");
4235
- const abs = import_node_path17.default.join(this.repoRoot, posixRelPath.split("/").join(import_node_path17.default.sep));
4236
- if (!import_node_fs12.default.existsSync(abs)) {
4316
+ const abs = import_node_path19.default.join(this.repoRoot, posixRelPath.split("/").join(import_node_path19.default.sep));
4317
+ if (!import_node_fs14.default.existsSync(abs)) {
4237
4318
  await this.deleteFile(posixRelPath);
4238
4319
  return;
4239
4320
  }
@@ -4293,10 +4374,10 @@ var RepoIndexer = class {
4293
4374
  if (opts?.signal?.aborted) return;
4294
4375
  const startedAt = Date.now();
4295
4376
  this.emitProgress({ type: "repo/symbolGraph/expand/start", repoRoot: this.repoRoot, path: p });
4296
- const abs = import_node_path17.default.join(this.repoRoot, p.split("/").join(import_node_path17.default.sep));
4377
+ const abs = import_node_path19.default.join(this.repoRoot, p.split("/").join(import_node_path19.default.sep));
4297
4378
  let text = "";
4298
4379
  try {
4299
- text = import_node_fs12.default.readFileSync(abs, "utf8");
4380
+ text = import_node_fs14.default.readFileSync(abs, "utf8");
4300
4381
  } catch {
4301
4382
  this.emitProgress({ type: "repo/symbolGraph/expand/done", repoRoot: this.repoRoot, path: p, edges: 0, ms: Date.now() - startedAt });
4302
4383
  continue;
@@ -4337,9 +4418,9 @@ var RepoIndexer = class {
4337
4418
  await this.openForCurrentHead();
4338
4419
  this.emitProgress({ type: "repo/watch/start", repoRoot: this.repoRoot });
4339
4420
  const ignored = (p) => {
4340
- const rel = import_node_path17.default.relative(this.repoRoot, p);
4421
+ const rel = import_node_path19.default.relative(this.repoRoot, p);
4341
4422
  if (!rel) return false;
4342
- const posix = rel.split(import_node_path17.default.sep).join("/");
4423
+ const posix = rel.split(import_node_path19.default.sep).join("/");
4343
4424
  if (posix.startsWith(".git/")) return true;
4344
4425
  if (posix.includes("node_modules/")) return true;
4345
4426
  if (posix.includes("/.cache/")) return true;
@@ -4380,14 +4461,14 @@ var RepoIndexer = class {
4380
4461
  };
4381
4462
 
4382
4463
  // src/indexer/workspaceIndexer.ts
4383
- var import_node_path19 = __toESM(require("path"), 1);
4464
+ var import_node_path22 = __toESM(require("path"), 1);
4384
4465
 
4385
4466
  // src/indexer/workspaceLinker.ts
4386
- var import_node_fs13 = __toESM(require("fs"), 1);
4387
- var import_node_path18 = __toESM(require("path"), 1);
4467
+ var import_node_fs15 = __toESM(require("fs"), 1);
4468
+ var import_node_path20 = __toESM(require("path"), 1);
4388
4469
  function readText(absPath) {
4389
4470
  try {
4390
- return import_node_fs13.default.readFileSync(absPath, "utf8");
4471
+ return import_node_fs15.default.readFileSync(absPath, "utf8");
4391
4472
  } catch {
4392
4473
  return null;
4393
4474
  }
@@ -4417,7 +4498,7 @@ var NestedRepoLinkStrategy = class {
4417
4498
  for (const child of sorted) {
4418
4499
  for (const parent of sorted) {
4419
4500
  if (child.repoId === parent.repoId) continue;
4420
- if (child.absRoot.startsWith(parent.absRoot + import_node_path18.default.sep)) {
4501
+ if (child.absRoot.startsWith(parent.absRoot + import_node_path20.default.sep)) {
4421
4502
  out.push({
4422
4503
  fromRepoId: child.repoId,
4423
4504
  toRepoId: parent.repoId,
@@ -4437,7 +4518,7 @@ var NpmDependencyLinkStrategy = class {
4437
4518
  const out = [];
4438
4519
  const depSections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
4439
4520
  for (const r of ctx.repos) {
4440
- const pkg = readJson(import_node_path18.default.join(r.absRoot, "package.json"));
4521
+ const pkg = readJson(import_node_path20.default.join(r.absRoot, "package.json"));
4441
4522
  if (!pkg) continue;
4442
4523
  for (const sec of depSections) {
4443
4524
  const deps = pkg?.[sec];
@@ -4455,13 +4536,13 @@ var NpmDependencyLinkStrategy = class {
4455
4536
  }
4456
4537
  };
4457
4538
  function parseGoModule(absRepoRoot) {
4458
- const raw = readText(import_node_path18.default.join(absRepoRoot, "go.mod"));
4539
+ const raw = readText(import_node_path20.default.join(absRepoRoot, "go.mod"));
4459
4540
  if (!raw) return null;
4460
4541
  const m = raw.match(/^\s*module\s+(.+)\s*$/m);
4461
4542
  return m ? String(m[1]).trim() : null;
4462
4543
  }
4463
4544
  function parseGoRequires(absRepoRoot) {
4464
- const raw = readText(import_node_path18.default.join(absRepoRoot, "go.mod"));
4545
+ const raw = readText(import_node_path20.default.join(absRepoRoot, "go.mod"));
4465
4546
  if (!raw) return [];
4466
4547
  const out = [];
4467
4548
  for (const line of raw.split(/\r?\n/)) {
@@ -4501,13 +4582,13 @@ function walkFiles(root, opts, onFile) {
4501
4582
  if (depth > maxDepth) return;
4502
4583
  let ents = [];
4503
4584
  try {
4504
- ents = import_node_fs13.default.readdirSync(dir, { withFileTypes: true });
4585
+ ents = import_node_fs15.default.readdirSync(dir, { withFileTypes: true });
4505
4586
  } catch {
4506
4587
  return;
4507
4588
  }
4508
4589
  for (const e of ents) {
4509
4590
  if (seen >= maxFiles) return;
4510
- const abs = import_node_path18.default.join(dir, e.name);
4591
+ const abs = import_node_path20.default.join(dir, e.name);
4511
4592
  if (opts.shouldVisit && !opts.shouldVisit(abs, e)) continue;
4512
4593
  if (e.isDirectory()) {
4513
4594
  if (isSkippableDir(e.name)) continue;
@@ -4531,7 +4612,7 @@ function collectVsCodeLanguagesForRepo(absRepoRoot) {
4531
4612
  shouldVisit: (_abs, dirent) => !(dirent.isDirectory() && isSkippableDir(dirent.name))
4532
4613
  },
4533
4614
  (absPath) => {
4534
- if (import_node_path18.default.basename(absPath) !== "package.json") return;
4615
+ if (import_node_path20.default.basename(absPath) !== "package.json") return;
4535
4616
  const pkg = readJson(absPath);
4536
4617
  const langs = pkg?.contributes?.languages;
4537
4618
  if (!Array.isArray(langs)) return;
@@ -4560,7 +4641,7 @@ function repoUsedExtensions(absRepoRoot, exts) {
4560
4641
  shouldVisit: (_abs, dirent) => !(dirent.isDirectory() && isSkippableDir(dirent.name))
4561
4642
  },
4562
4643
  (absPath) => {
4563
- const ext = import_node_path18.default.extname(absPath).toLowerCase();
4644
+ const ext = import_node_path20.default.extname(absPath).toLowerCase();
4564
4645
  if (!ext) return;
4565
4646
  if (exts.has(ext)) used.add(ext);
4566
4647
  }
@@ -4632,12 +4713,12 @@ var WorkspaceLinker = class _WorkspaceLinker {
4632
4713
  const repos = repoRoots.map((repoRoot) => ({
4633
4714
  repoRoot,
4634
4715
  repoId: repoIdFromRoot(repoRoot),
4635
- absRoot: import_node_path18.default.resolve(repoRoot)
4716
+ absRoot: import_node_path20.default.resolve(repoRoot)
4636
4717
  }));
4637
4718
  const npmNameToRepoId = /* @__PURE__ */ new Map();
4638
4719
  const goModuleToRepoId = /* @__PURE__ */ new Map();
4639
4720
  for (const r of repos) {
4640
- const pkg = readJson(import_node_path18.default.join(r.absRoot, "package.json"));
4721
+ const pkg = readJson(import_node_path20.default.join(r.absRoot, "package.json"));
4641
4722
  const name = typeof pkg?.name === "string" ? pkg.name : null;
4642
4723
  if (name) npmNameToRepoId.set(name, r.repoId);
4643
4724
  const mod = parseGoModule(r.absRoot);
@@ -4681,13 +4762,205 @@ async function linkWorkspaceRepos(args) {
4681
4762
  return { repos: ctx.repos, links };
4682
4763
  }
4683
4764
 
4684
- // src/indexer/workspaceIndexer.ts
4765
+ // src/indexer/workspaceRetrieveCandidates.ts
4766
+ var import_node_path21 = __toESM(require("path"), 1);
4767
+ function resolveWorkspaceProfile(config, opts) {
4768
+ const name = opts?.profile ?? "search";
4769
+ const base = DEFAULT_PROFILES[name] ?? DEFAULT_PROFILES.search;
4770
+ const configPatch = config.profiles?.[name] ?? {};
4771
+ const merged1 = deepMergeProfile(base, configPatch);
4772
+ const merged2 = deepMergeProfile(merged1, opts?.profileOverrides);
4773
+ const w = merged2.weights;
4774
+ const sum = Math.max(1e-6, w.vector + w.lexical + w.recency);
4775
+ merged2.weights = { vector: w.vector / sum, lexical: w.lexical / sum, recency: w.recency / sum };
4776
+ return merged2;
4777
+ }
4685
4778
  function halfLifeDaysForProfile(profileName) {
4686
4779
  if (profileName === "rca") return 7;
4687
4780
  if (profileName === "review") return 14;
4688
4781
  if (profileName === "refactor") return 21;
4689
4782
  return 30;
4690
4783
  }
4784
+ function buildWorkspaceLexByRepoRoot(args) {
4785
+ const { workspaceStore, repos, query, lexicalK, repoFilters } = args;
4786
+ const ftq = ftsQueryFromText(query);
4787
+ if (!ftq) return { lexByRepoRoot: /* @__PURE__ */ new Map(), count: 0 };
4788
+ const allowRoots = repoFilters ? new Set(repoFilters.map((r) => import_node_path21.default.resolve(r))) : null;
4789
+ const repoIds = allowRoots ? repos.filter((r) => allowRoots.has(import_node_path21.default.resolve(r.repoRoot))).map((r) => r.repoId) : void 0;
4790
+ const rows = workspaceStore.searchFts(ftq, lexicalK, repoIds);
4791
+ const lexByRepoRoot = /* @__PURE__ */ new Map();
4792
+ for (const r of rows) {
4793
+ const row = workspaceStore.getChunkById(r.id);
4794
+ if (!row) continue;
4795
+ const rootKey = import_node_path21.default.resolve(row.repo_root);
4796
+ const arr = lexByRepoRoot.get(rootKey) ?? [];
4797
+ arr.push({ id: r.id, score: bm25ToScore01(r.bm25) });
4798
+ lexByRepoRoot.set(rootKey, arr);
4799
+ }
4800
+ return { lexByRepoRoot, count: rows.length };
4801
+ }
4802
+ async function collectWorkspaceCandidates(args) {
4803
+ const { repos, qVec, query, vectorK, lexicalK, profile, opts, lexByRepoRoot, canUseWorkspaceLex } = args;
4804
+ const repoFilters = opts.filters?.repoRoots;
4805
+ const langFilter = opts.filters?.language;
4806
+ const pathPrefix = opts.filters?.pathPrefix;
4807
+ const candidates = [];
4808
+ let vecCount = 0;
4809
+ let lexCount = 0;
4810
+ for (const repo of repos) {
4811
+ if (repoFilters && !repoFilters.includes(repo.repoRoot)) continue;
4812
+ let includePaths = opts.scope?.includePaths?.slice();
4813
+ if (opts.scope?.changedOnly) {
4814
+ try {
4815
+ const changed = await listChangedFiles(repo.repoRoot, opts.scope.baseRef ?? "HEAD~1");
4816
+ includePaths = includePaths ? includePaths.filter((p) => changed.includes(p)) : changed;
4817
+ } catch {
4818
+ }
4819
+ }
4820
+ const [vHits, lHits] = await Promise.all([
4821
+ repo.vectorCandidates(qVec, vectorK, includePaths),
4822
+ canUseWorkspaceLex ? Promise.resolve(lexByRepoRoot?.get(import_node_path21.default.resolve(repo.repoRoot)) ?? []) : repo.lexicalCandidates(query, lexicalK, includePaths)
4823
+ ]);
4824
+ vecCount += vHits.length;
4825
+ if (!canUseWorkspaceLex) lexCount += lHits.length;
4826
+ const m = /* @__PURE__ */ new Map();
4827
+ for (const vh of vHits) {
4828
+ const id = vh.id;
4829
+ const vector01 = vectorCosineToScore01(vh.score);
4830
+ m.set(id, { repo, id, vector01, combined: 0 });
4831
+ }
4832
+ for (const lh of lHits) {
4833
+ const id = lh.id;
4834
+ const prev = m.get(id);
4835
+ if (prev) prev.lexical01 = lh.score;
4836
+ else m.set(id, { repo, id, lexical01: lh.score, combined: 0 });
4837
+ }
4838
+ const halfLife = halfLifeDaysForProfile(profile.name);
4839
+ for (const c of m.values()) {
4840
+ const meta = repo.getChunkMeta(c.id);
4841
+ if (!meta) continue;
4842
+ if (langFilter && meta.language !== langFilter) continue;
4843
+ if (pathPrefix && !meta.path.startsWith(pathPrefix)) continue;
4844
+ c.recency01 = profile.weights.recency > 0 ? recencyScore(meta.fileMtimeMs, halfLife) : 0;
4845
+ let kindFactor = 1;
4846
+ if (meta.kind === "synopsis" && profile.name === "search") kindFactor = 0.85;
4847
+ if (meta.kind === "synopsis" && profile.name === "architecture") kindFactor = 1.05;
4848
+ const v = c.vector01 ?? 0;
4849
+ const l = c.lexical01 ?? 0;
4850
+ const r = c.recency01 ?? 0;
4851
+ c.combined = clamp(kindFactor * (profile.weights.vector * v + profile.weights.lexical * l + profile.weights.recency * r), 0, 1);
4852
+ candidates.push(c);
4853
+ }
4854
+ }
4855
+ return { candidates, vecCount, lexCount };
4856
+ }
4857
+ function rankWorkspaceCandidates(args) {
4858
+ const { candidates, maxMerged, k } = args;
4859
+ candidates.sort((a, b) => b.combined - a.combined);
4860
+ const merged = candidates.slice(0, maxMerged);
4861
+ const top = merged.slice(0, k);
4862
+ const hits = top.map((c) => {
4863
+ const meta = c.repo.getChunkMeta(c.id);
4864
+ const preview = makePreview(c.repo.getChunkText(c.id));
4865
+ return {
4866
+ score: c.combined,
4867
+ scoreBreakdown: { vector: c.vector01, lexical: c.lexical01, recency: c.recency01 },
4868
+ chunk: { ...meta, preview }
4869
+ };
4870
+ });
4871
+ return { merged, hits };
4872
+ }
4873
+
4874
+ // src/indexer/workspaceRetrieveContext.ts
4875
+ async function warmSymbolGraphForHits(repos, hits) {
4876
+ const byRepo = /* @__PURE__ */ new Map();
4877
+ for (const h of hits) {
4878
+ const s = byRepo.get(h.chunk.repoRoot) ?? /* @__PURE__ */ new Set();
4879
+ s.add(h.chunk.path);
4880
+ byRepo.set(h.chunk.repoRoot, s);
4881
+ }
4882
+ for (const [repoRoot, paths] of byRepo) {
4883
+ const repo = repos.find((r) => r.repoRoot === repoRoot);
4884
+ if (!repo) continue;
4885
+ await repo.warmSymbolGraphEdges(Array.from(paths), { maxFiles: 6 });
4886
+ }
4887
+ }
4888
+ async function fetchGraphNeighborFiles(args) {
4889
+ const { graphStore, repos, hits, profile, workspaceRoot, emitProgress } = args;
4890
+ if (!graphStore?.neighborFiles) return [];
4891
+ const seeds = [];
4892
+ const seen = /* @__PURE__ */ new Set();
4893
+ for (const h of hits) {
4894
+ const repo = repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4895
+ if (!repo) continue;
4896
+ const key = `${repo.repoId}:${h.chunk.path}`;
4897
+ if (seen.has(key)) continue;
4898
+ seen.add(key);
4899
+ seeds.push({ repoId: repo.repoId, path: h.chunk.path });
4900
+ if (seeds.length >= 4) break;
4901
+ }
4902
+ if (seeds.length === 0) return [];
4903
+ const startedAt = Date.now();
4904
+ emitProgress({ type: "workspace/retrieve/graph/start", workspaceRoot, seeds: seeds.length });
4905
+ try {
4906
+ const neighbors = await graphStore.neighborFiles({
4907
+ seeds,
4908
+ limit: profile.name === "architecture" ? 16 : 10,
4909
+ kinds: ["definition", "reference", "implementation", "typeDefinition"]
4910
+ });
4911
+ emitProgress({ type: "workspace/retrieve/graph/done", workspaceRoot, neighbors: neighbors.length, ms: Date.now() - startedAt });
4912
+ return neighbors;
4913
+ } catch {
4914
+ return [];
4915
+ }
4916
+ }
4917
+ async function buildContextBlocks(args) {
4918
+ const { repos, hits, graphNeighborFiles, profile } = args;
4919
+ const contextBlocks = [];
4920
+ const seenKey = /* @__PURE__ */ new Set();
4921
+ const addBlock = (repoRoot, filePath, startLine, endLine, text, reason) => {
4922
+ const key = `${repoRoot}:${filePath}:${startLine}:${endLine}:${text.length}:${reason}`;
4923
+ if (seenKey.has(key)) return;
4924
+ seenKey.add(key);
4925
+ if (!text.trim()) return;
4926
+ contextBlocks.push({ repoRoot, path: filePath, startLine, endLine, text, reason });
4927
+ };
4928
+ try {
4929
+ const byRepoId = /* @__PURE__ */ new Map();
4930
+ for (const r of repos) byRepoId.set(r.repoId, r);
4931
+ for (const n of graphNeighborFiles.slice(0, 10)) {
4932
+ const repo = byRepoId.get(n.repoId);
4933
+ if (!repo) continue;
4934
+ const chunkId = await repo.getRepresentativeChunkIdForFile(n.path, true);
4935
+ if (!chunkId) continue;
4936
+ const meta = repo.getChunkMeta(chunkId);
4937
+ if (!meta) continue;
4938
+ const text = repo.getChunkText(chunkId);
4939
+ addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, text, `graph neighbor (${n.weight})`);
4940
+ }
4941
+ } catch {
4942
+ }
4943
+ for (const h of hits) {
4944
+ const repo = repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4945
+ if (!repo) continue;
4946
+ const hitText = repo.getChunkText(h.chunk.id);
4947
+ addBlock(h.chunk.repoRoot, h.chunk.path, h.chunk.startLine, h.chunk.endLine, hitText, "primary hit");
4948
+ const expanded = await repo.expandContext(h.chunk.id, {
4949
+ adjacentChunks: profile.expand.adjacentChunks ?? 0,
4950
+ followImports: profile.expand.followImports ?? 0,
4951
+ includeFileSynopsis: profile.expand.includeFileSynopsis ?? false
4952
+ });
4953
+ for (const ex of expanded) {
4954
+ const meta = repo.getChunkMeta(ex.id);
4955
+ if (!meta) continue;
4956
+ const text = repo.getChunkText(ex.id);
4957
+ addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, text, ex.reason);
4958
+ }
4959
+ }
4960
+ return contextBlocks;
4961
+ }
4962
+
4963
+ // src/indexer/workspaceIndexer.ts
4691
4964
  var WorkspaceIndexer = class {
4692
4965
  constructor(workspaceRoot, embedder, config = {}) {
4693
4966
  this.workspaceRoot = workspaceRoot;
@@ -4695,23 +4968,30 @@ var WorkspaceIndexer = class {
4695
4968
  this.config = { ...config };
4696
4969
  if (!this.config.cacheDir) this.config.cacheDir = defaultCacheDir();
4697
4970
  this.progress = asProgressSink(this.config.progress);
4698
- const wsId = sha256Hex(import_node_path19.default.resolve(this.workspaceRoot)).slice(0, 16);
4699
- const dbPath = import_node_path19.default.join(this.config.cacheDir, "workspace", wsId, "workspace.sqlite");
4700
- this.workspaceStore = new WorkspaceStore(dbPath);
4701
- this.workspaceStore.setMeta("workspaceRoot", import_node_path19.default.resolve(this.workspaceRoot));
4971
+ const wsId = sha256Hex(import_node_path22.default.resolve(this.workspaceRoot)).slice(0, 16);
4972
+ this.workspaceDbPath = import_node_path22.default.join(this.config.cacheDir, "workspace", wsId, "workspace.sqlite");
4702
4973
  }
4703
4974
  repos = [];
4704
4975
  config;
4705
4976
  progress = asProgressSink();
4706
4977
  workspaceStore = null;
4707
4978
  graphStore = null;
4979
+ workspaceDbPath;
4708
4980
  emitProgress(event) {
4709
4981
  try {
4710
4982
  this.progress?.emit(event);
4711
4983
  } catch {
4712
4984
  }
4713
4985
  }
4986
+ async ensureWorkspaceStore() {
4987
+ if (this.workspaceStore) return this.workspaceStore;
4988
+ const ws = await createWorkspaceStoreAsync(this.workspaceDbPath, { db: this.config.workspace?.db });
4989
+ ws.setMeta("workspaceRoot", import_node_path22.default.resolve(this.workspaceRoot));
4990
+ this.workspaceStore = ws;
4991
+ return ws;
4992
+ }
4714
4993
  async open() {
4994
+ await this.ensureWorkspaceStore();
4715
4995
  if (!this.graphStore && this.config.workspace?.graph?.provider === "neo4j") {
4716
4996
  try {
4717
4997
  const n = this.config.workspace.graph.neo4j;
@@ -4783,195 +5063,49 @@ var WorkspaceIndexer = class {
4783
5063
  getRepoIndexers() {
4784
5064
  return this.repos.slice();
4785
5065
  }
4786
- resolveProfile(opts) {
4787
- const name = opts?.profile ?? "search";
4788
- const base = DEFAULT_PROFILES[name] ?? DEFAULT_PROFILES.search;
4789
- const configPatch = this.config.profiles?.[name] ?? {};
4790
- const merged1 = deepMergeProfile(base, configPatch);
4791
- const merged2 = deepMergeProfile(merged1, opts?.profileOverrides);
4792
- const w = merged2.weights;
4793
- const sum = Math.max(1e-6, w.vector + w.lexical + w.recency);
4794
- merged2.weights = { vector: w.vector / sum, lexical: w.lexical / sum, recency: w.recency / sum };
4795
- return merged2;
4796
- }
4797
5066
  async retrieve(query, opts = {}) {
4798
5067
  if (this.repos.length === 0) await this.open();
4799
- const profile = this.resolveProfile(opts);
5068
+ const profile = resolveWorkspaceProfile(this.config, opts);
4800
5069
  const startedAt = Date.now();
4801
5070
  this.emitProgress({ type: "workspace/retrieve/start", workspaceRoot: this.workspaceRoot, profile: profile.name, query });
4802
5071
  const qVec = (await this.embedder.embed([query]))[0];
4803
5072
  const vectorK = profile.candidates?.vectorK ?? Math.max(profile.k * 3, 30);
4804
5073
  const lexicalK = profile.candidates?.lexicalK ?? Math.max(profile.k * 3, 30);
4805
5074
  const maxMerged = profile.candidates?.maxMergedCandidates ?? Math.max(profile.k * 8, 120);
4806
- const repoFilters = opts.filters?.repoRoots;
4807
- const langFilter = opts.filters?.language;
4808
- const pathPrefix = opts.filters?.pathPrefix;
4809
- const candidates = [];
4810
- let vecCount = 0;
4811
- let lexCount = 0;
4812
- const canUseWorkspaceLex = !!this.workspaceStore && this.config.storage?.ftsMode !== "off" && !opts.scope?.includePaths && !opts.scope?.changedOnly;
4813
- const workspaceLexByRepoRoot = /* @__PURE__ */ new Map();
4814
- if (canUseWorkspaceLex && profile.weights.lexical > 0) {
4815
- const ftq = ftsQueryFromText(query);
4816
- const allowRoots = repoFilters ? new Set(repoFilters.map((r) => import_node_path19.default.resolve(r))) : null;
4817
- const repoIds = allowRoots ? this.repos.filter((r) => allowRoots.has(import_node_path19.default.resolve(r.repoRoot))).map((r) => r.repoId) : void 0;
4818
- if (ftq) {
4819
- const rows = this.workspaceStore.searchFts(ftq, lexicalK, repoIds);
4820
- lexCount += rows.length;
4821
- for (const r of rows) {
4822
- const row = this.workspaceStore.getChunkById(r.id);
4823
- if (!row) continue;
4824
- const rootKey = import_node_path19.default.resolve(row.repo_root);
4825
- const arr = workspaceLexByRepoRoot.get(rootKey) ?? [];
4826
- arr.push({ id: r.id, score: bm25ToScore01(r.bm25) });
4827
- workspaceLexByRepoRoot.set(rootKey, arr);
4828
- }
4829
- }
4830
- }
4831
- for (const repo of this.repos) {
4832
- if (repoFilters && !repoFilters.includes(repo.repoRoot)) continue;
4833
- let includePaths = opts.scope?.includePaths?.slice();
4834
- if (opts.scope?.changedOnly) {
4835
- try {
4836
- const changed = await listChangedFiles(repo.repoRoot, opts.scope.baseRef ?? "HEAD~1");
4837
- includePaths = includePaths ? includePaths.filter((p) => changed.includes(p)) : changed;
4838
- } catch {
4839
- }
4840
- }
4841
- const [vHits, lHits] = await Promise.all([
4842
- repo.vectorCandidates(qVec, vectorK, includePaths),
4843
- canUseWorkspaceLex ? Promise.resolve(workspaceLexByRepoRoot.get(import_node_path19.default.resolve(repo.repoRoot)) ?? []) : repo.lexicalCandidates(query, lexicalK, includePaths)
4844
- ]);
4845
- vecCount += vHits.length;
4846
- if (!canUseWorkspaceLex) lexCount += lHits.length;
4847
- const m = /* @__PURE__ */ new Map();
4848
- for (const vh of vHits) {
4849
- const id = vh.id;
4850
- const vector01 = vectorCosineToScore01(vh.score);
4851
- m.set(id, { repo, id, vector01, combined: 0 });
4852
- }
4853
- for (const lh of lHits) {
4854
- const id = lh.id;
4855
- const prev = m.get(id);
4856
- if (prev) prev.lexical01 = lh.score;
4857
- else m.set(id, { repo, id, lexical01: lh.score, combined: 0 });
4858
- }
4859
- const halfLife = halfLifeDaysForProfile(profile.name);
4860
- for (const c of m.values()) {
4861
- const meta = repo.getChunkMeta(c.id);
4862
- if (!meta) continue;
4863
- if (langFilter && meta.language !== langFilter) continue;
4864
- if (pathPrefix && !meta.path.startsWith(pathPrefix)) continue;
4865
- c.recency01 = profile.weights.recency > 0 ? recencyScore(meta.fileMtimeMs, halfLife) : 0;
4866
- let kindFactor = 1;
4867
- if (meta.kind === "synopsis" && profile.name === "search") kindFactor = 0.85;
4868
- if (meta.kind === "synopsis" && profile.name === "architecture") kindFactor = 1.05;
4869
- const v = c.vector01 ?? 0;
4870
- const l = c.lexical01 ?? 0;
4871
- const r = c.recency01 ?? 0;
4872
- c.combined = clamp(
4873
- kindFactor * (profile.weights.vector * v + profile.weights.lexical * l + profile.weights.recency * r),
4874
- 0,
4875
- 1
4876
- );
4877
- candidates.push(c);
4878
- }
4879
- }
4880
- candidates.sort((a, b) => b.combined - a.combined);
4881
- const merged = candidates.slice(0, maxMerged);
4882
- const top = merged.slice(0, profile.k);
4883
- const hits = top.map((c) => {
4884
- const meta = c.repo.getChunkMeta(c.id);
4885
- const preview = makePreview(c.repo.getChunkText(c.id));
4886
- return {
4887
- score: c.combined,
4888
- scoreBreakdown: { vector: c.vector01, lexical: c.lexical01, recency: c.recency01 },
4889
- chunk: { ...meta, preview }
4890
- };
5075
+ const canUseWorkspaceLex = !!this.workspaceStore && this.workspaceStore.ftsEnabled && this.config.storage?.ftsMode !== "off" && !opts.scope?.includePaths && !opts.scope?.changedOnly;
5076
+ const { lexByRepoRoot, count: workspaceLexCount } = canUseWorkspaceLex && profile.weights.lexical > 0 && this.workspaceStore ? buildWorkspaceLexByRepoRoot({
5077
+ workspaceStore: this.workspaceStore,
5078
+ repos: this.repos,
5079
+ query,
5080
+ lexicalK,
5081
+ repoFilters: opts.filters?.repoRoots
5082
+ }) : { lexByRepoRoot: void 0, count: 0 };
5083
+ const { candidates, vecCount, lexCount } = await collectWorkspaceCandidates({
5084
+ repos: this.repos,
5085
+ qVec,
5086
+ query,
5087
+ vectorK,
5088
+ lexicalK,
5089
+ profile,
5090
+ opts,
5091
+ lexByRepoRoot,
5092
+ canUseWorkspaceLex
4891
5093
  });
5094
+ const { merged, hits } = rankWorkspaceCandidates({ candidates, maxMerged, k: profile.k });
5095
+ const totalLexCount = (canUseWorkspaceLex ? workspaceLexCount : 0) + lexCount;
4892
5096
  try {
4893
- const byRepo = /* @__PURE__ */ new Map();
4894
- for (const h of hits) {
4895
- const s = byRepo.get(h.chunk.repoRoot) ?? /* @__PURE__ */ new Set();
4896
- s.add(h.chunk.path);
4897
- byRepo.set(h.chunk.repoRoot, s);
4898
- }
4899
- for (const [repoRoot, paths] of byRepo) {
4900
- const repo = this.repos.find((r) => r.repoRoot === repoRoot);
4901
- if (!repo) continue;
4902
- await repo.warmSymbolGraphEdges(Array.from(paths), { maxFiles: 6 });
4903
- }
4904
- } catch {
4905
- }
4906
- let graphNeighborFiles = [];
4907
- try {
4908
- if (this.graphStore?.neighborFiles) {
4909
- const seeds = [];
4910
- const seen = /* @__PURE__ */ new Set();
4911
- for (const h of hits) {
4912
- const repo = this.repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4913
- if (!repo) continue;
4914
- const key = `${repo.repoId}:${h.chunk.path}`;
4915
- if (seen.has(key)) continue;
4916
- seen.add(key);
4917
- seeds.push({ repoId: repo.repoId, path: h.chunk.path });
4918
- if (seeds.length >= 4) break;
4919
- }
4920
- if (seeds.length > 0) {
4921
- const gs = Date.now();
4922
- this.emitProgress({ type: "workspace/retrieve/graph/start", workspaceRoot: this.workspaceRoot, seeds: seeds.length });
4923
- graphNeighborFiles = await this.graphStore.neighborFiles({
4924
- seeds,
4925
- limit: profile.name === "architecture" ? 16 : 10,
4926
- kinds: ["definition", "reference", "implementation", "typeDefinition"]
4927
- });
4928
- this.emitProgress({ type: "workspace/retrieve/graph/done", workspaceRoot: this.workspaceRoot, neighbors: graphNeighborFiles.length, ms: Date.now() - gs });
4929
- }
4930
- }
4931
- } catch {
4932
- graphNeighborFiles = [];
4933
- }
4934
- const contextBlocks = [];
4935
- const seenKey = /* @__PURE__ */ new Set();
4936
- const addBlock = (repoRoot, path21, startLine, endLine, text, reason) => {
4937
- const key = `${repoRoot}:${path21}:${startLine}:${endLine}:${text.length}:${reason}`;
4938
- if (seenKey.has(key)) return;
4939
- seenKey.add(key);
4940
- if (!text.trim()) return;
4941
- contextBlocks.push({ repoRoot, path: path21, startLine, endLine, text, reason });
4942
- };
4943
- try {
4944
- const byRepoId = /* @__PURE__ */ new Map();
4945
- for (const r of this.repos) byRepoId.set(r.repoId, r);
4946
- for (const n of graphNeighborFiles.slice(0, 10)) {
4947
- const repo = byRepoId.get(n.repoId);
4948
- if (!repo) continue;
4949
- const chunkId = await repo.getRepresentativeChunkIdForFile(n.path, true);
4950
- if (!chunkId) continue;
4951
- const meta = repo.getChunkMeta(chunkId);
4952
- if (!meta) continue;
4953
- const text = repo.getChunkText(chunkId);
4954
- addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, text, `graph neighbor (${n.weight})`);
4955
- }
5097
+ await warmSymbolGraphForHits(this.repos, hits);
4956
5098
  } catch {
4957
5099
  }
4958
- for (const h of hits) {
4959
- const repo = this.repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4960
- if (!repo) continue;
4961
- const text = repo.getChunkText(h.chunk.id);
4962
- addBlock(h.chunk.repoRoot, h.chunk.path, h.chunk.startLine, h.chunk.endLine, text, "primary hit");
4963
- const expanded = await repo.expandContext(h.chunk.id, {
4964
- adjacentChunks: profile.expand.adjacentChunks ?? 0,
4965
- followImports: profile.expand.followImports ?? 0,
4966
- includeFileSynopsis: profile.expand.includeFileSynopsis ?? false
4967
- });
4968
- for (const ex of expanded) {
4969
- const meta = repo.getChunkMeta(ex.id);
4970
- if (!meta) continue;
4971
- const t = repo.getChunkText(ex.id);
4972
- addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, t, ex.reason);
4973
- }
4974
- }
5100
+ const graphNeighborFiles = await fetchGraphNeighborFiles({
5101
+ graphStore: this.graphStore,
5102
+ repos: this.repos,
5103
+ hits,
5104
+ profile,
5105
+ workspaceRoot: this.workspaceRoot,
5106
+ emitProgress: (e) => this.emitProgress(e)
5107
+ });
5108
+ const contextBlocks = await buildContextBlocks({ repos: this.repos, hits, graphNeighborFiles, profile });
4975
5109
  const bundle = {
4976
5110
  hits,
4977
5111
  context: contextBlocks,
@@ -4980,7 +5114,7 @@ var WorkspaceIndexer = class {
4980
5114
  reposSearched: this.repos.length,
4981
5115
  candidates: {
4982
5116
  vector: vecCount,
4983
- lexical: lexCount,
5117
+ lexical: totalLexCount,
4984
5118
  merged: merged.length,
4985
5119
  returned: hits.length
4986
5120
  }
@@ -4992,7 +5126,7 @@ var WorkspaceIndexer = class {
4992
5126
  profile: profile.name,
4993
5127
  ms: Date.now() - startedAt,
4994
5128
  hits: hits.length,
4995
- candidates: { vector: vecCount, lexical: lexCount, merged: merged.length }
5129
+ candidates: { vector: vecCount, lexical: totalLexCount, merged: merged.length }
4996
5130
  });
4997
5131
  return bundle;
4998
5132
  }
@@ -5016,11 +5150,11 @@ var WorkspaceIndexer = class {
5016
5150
  };
5017
5151
 
5018
5152
  // src/config.ts
5019
- var import_node_fs14 = __toESM(require("fs"), 1);
5020
- var import_node_path20 = __toESM(require("path"), 1);
5153
+ var import_node_fs16 = __toESM(require("fs"), 1);
5154
+ var import_node_path23 = __toESM(require("path"), 1);
5021
5155
  function loadConfigFile(filePath) {
5022
- const abs = import_node_path20.default.resolve(filePath);
5023
- const raw = import_node_fs14.default.readFileSync(abs, "utf8");
5156
+ const abs = import_node_path23.default.resolve(filePath);
5157
+ const raw = import_node_fs16.default.readFileSync(abs, "utf8");
5024
5158
  const json = JSON.parse(raw);
5025
5159
  const cfg = { ...json };
5026
5160
  if (json.redact?.patterns && Array.isArray(json.redact.patterns)) {
@@ -5058,11 +5192,14 @@ function loadConfigFile(filePath) {
5058
5192
  WorkspaceLinker,
5059
5193
  WorkspaceStore,
5060
5194
  asProgressSink,
5195
+ betterSqlite3Adapter,
5061
5196
  chunkSource,
5062
5197
  createAnnIndex,
5063
5198
  createNeo4jGraphStore,
5064
5199
  createVSCodeSymbolGraphProvider,
5065
5200
  createVectorIndex,
5201
+ createWorkspaceStore,
5202
+ createWorkspaceStoreAsync,
5066
5203
  deepMergeProfile,
5067
5204
  discoverGitRepos,
5068
5205
  languageFromPath,
@@ -5070,5 +5207,6 @@ function loadConfigFile(filePath) {
5070
5207
  loadConfigFile,
5071
5208
  mergeIndexerConfig,
5072
5209
  pickRepoOverride,
5210
+ sqlJsAdapter,
5073
5211
  stableSymbolId
5074
5212
  });