@wrongstack/tools 0.68.0 → 0.73.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.
@@ -1,6 +1,29 @@
1
- import { d as Symbol, F as FileMeta, e as SymbolKind, f as SymbolLang, c as SearchResult, b as IndexStats, R as Ref } from '../codebase-stats-tool-C8ApERbn.js';
2
- export { a as FileSymbols, I as IndexResult, S as SCHEMA_VERSION, g as codebaseIndexTool, h as codebaseSearchTool, i as codebaseStatsTool } from '../codebase-stats-tool-C8ApERbn.js';
3
- import '@wrongstack/core';
1
+ import { I as IndexResult, d as Symbol, F as FileMeta, e as SymbolKind, f as SymbolLang, c as SearchResult, b as IndexStats, R as Ref } from '../background-indexer-sbsseCCC.js';
2
+ export { a as FileSymbols, S as SCHEMA_VERSION, g as cancelPendingReindexes, h as codebaseIndexTool, i as codebaseSearchTool, j as codebaseStatsTool, k as enqueueReindex, l as isIndexableFile, r as runStartupIndex } from '../background-indexer-sbsseCCC.js';
3
+ import { Context } from '@wrongstack/core';
4
+
5
+ /**
6
+ * Main indexing orchestrator.
7
+ *
8
+ * Given a project root and a list of files:
9
+ * 1. Parse each file with the appropriate parser (TS, Go, Python, Rust, JSON, YAML)
10
+ * 2. Delete old symbols for changed/deleted files
11
+ * 3. Insert new symbols
12
+ * 4. Update file metadata
13
+ * 5. Return index statistics
14
+ */
15
+
16
+ interface IndexerOptions {
17
+ projectRoot: string;
18
+ files?: string[];
19
+ force?: boolean;
20
+ langs?: string[];
21
+ ignore?: string[];
22
+ /** Override the index directory (default: the global per-project dir). */
23
+ indexDir?: string;
24
+ }
25
+ /** Run a full or incremental index and return statistics. */
26
+ declare function runIndexer(_ctx: Context, opts: IndexerOptions): Promise<IndexResult>;
4
27
 
5
28
  /**
6
29
  * SQLite storage layer for the codebase index.
@@ -50,6 +73,14 @@ declare class IndexStore {
50
73
  id: number;
51
74
  text: string;
52
75
  }>;
76
+ /**
77
+ * Largest symbol id currently in the table (0 when empty). New ids must be
78
+ * allocated from this, NOT from `COUNT(*)`: incremental reindexes delete a
79
+ * changed file's rows, so the row count drops below the max id and a
80
+ * count-based id would collide with a surviving row (UNIQUE constraint on
81
+ * `symbols.id`). Ids may have gaps — that is fine.
82
+ */
83
+ getMaxSymbolId(): number;
53
84
  getStats(): IndexStats;
54
85
  setLastIndexed(ts: number): void;
55
86
  clearAll(): void;
@@ -137,4 +168,4 @@ declare function lspKindToInternalKind(k: number): SymbolKind | null;
137
168
  */
138
169
  declare function internalKindToLspKind(k: SymbolKind): number | null;
139
170
 
140
- export { FileMeta, IndexStats, IndexStore, SearchResult, Symbol, SymbolKind, SymbolLang, buildBm25Index, buildIndexableText, codebaseIndexDirOverride, internalKindToLspKind, lspKindToInternalKind, resolveIndexDir, tokenise };
171
+ export { FileMeta, IndexResult, IndexStats, IndexStore, SearchResult, Symbol, SymbolKind, SymbolLang, buildBm25Index, buildIndexableText, codebaseIndexDirOverride, internalKindToLspKind, lspKindToInternalKind, resolveIndexDir, runIndexer, tokenise };
@@ -1,4 +1,4 @@
1
- import * as fs2 from 'node:fs/promises';
1
+ import * as fs3 from 'node:fs/promises';
2
2
  import * as path4 from 'node:path';
3
3
  import { resolveWstackPaths, compileGlob } from '@wrongstack/core';
4
4
  import { createRequire } from 'node:module';
@@ -290,6 +290,17 @@ var IndexStore = class {
290
290
  ({ id, text }) => ({ id, text })
291
291
  );
292
292
  }
293
+ /**
294
+ * Largest symbol id currently in the table (0 when empty). New ids must be
295
+ * allocated from this, NOT from `COUNT(*)`: incremental reindexes delete a
296
+ * changed file's rows, so the row count drops below the max id and a
297
+ * count-based id would collide with a surviving row (UNIQUE constraint on
298
+ * `symbols.id`). Ids may have gaps — that is fine.
299
+ */
300
+ getMaxSymbolId() {
301
+ const rows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
302
+ return rows[0]?.m ?? 0;
303
+ }
293
304
  // ─── Stats ───────────────────────────────────────────────────────────────────
294
305
  getStats() {
295
306
  const sizeBytes = this.sizeBytes();
@@ -1608,6 +1619,56 @@ function makeSymbol2(opts) {
1608
1619
  text: `${opts.name} ${opts.signature}`.trim()
1609
1620
  };
1610
1621
  }
1622
+ function globBody(glob) {
1623
+ return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
1624
+ }
1625
+ function compileGitignore(lines) {
1626
+ const rules = [];
1627
+ for (const raw of lines) {
1628
+ let line = raw.replace(/\r$/, "");
1629
+ if (!line.trim() || line.trimStart().startsWith("#")) continue;
1630
+ line = line.trim();
1631
+ let negated = false;
1632
+ if (line.startsWith("!")) {
1633
+ negated = true;
1634
+ line = line.slice(1);
1635
+ }
1636
+ let dirOnly = false;
1637
+ if (line.endsWith("/")) {
1638
+ dirOnly = true;
1639
+ line = line.slice(0, -1);
1640
+ }
1641
+ if (!line) continue;
1642
+ const anchored = line.startsWith("/") || line.includes("/");
1643
+ if (line.startsWith("/")) line = line.slice(1);
1644
+ const body = globBody(line);
1645
+ const prefix = anchored ? "^" : "(?:^|.*/)";
1646
+ rules.push({
1647
+ eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
1648
+ under: new RegExp(`${prefix}${body}/.*$`),
1649
+ negated,
1650
+ dirOnly
1651
+ });
1652
+ }
1653
+ return (relPath, isDir) => {
1654
+ const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
1655
+ let ignored = false;
1656
+ for (const r of rules) {
1657
+ const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
1658
+ if (re.test(p)) ignored = !r.negated;
1659
+ }
1660
+ return ignored;
1661
+ };
1662
+ }
1663
+ async function loadGitignoreMatcher(projectRoot) {
1664
+ let lines = [];
1665
+ try {
1666
+ const raw = await fs3.readFile(path4.join(projectRoot, ".gitignore"), "utf8");
1667
+ lines = raw.split("\n");
1668
+ } catch {
1669
+ }
1670
+ return compileGitignore(lines);
1671
+ }
1611
1672
 
1612
1673
  // src/codebase-index/indexer.ts
1613
1674
  var DEFAULT_IGNORE = [
@@ -1621,7 +1682,7 @@ var DEFAULT_IGNORE = [
1621
1682
  "__snapshots__",
1622
1683
  ".nyc_output"
1623
1684
  ];
1624
- async function findSourceFiles(projectRoot, ignore) {
1685
+ async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
1625
1686
  const results = [];
1626
1687
  const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
1627
1688
  const globs = [
@@ -1639,17 +1700,19 @@ async function findSourceFiles(projectRoot, ignore) {
1639
1700
  const walk = async (dir) => {
1640
1701
  let entries;
1641
1702
  try {
1642
- entries = await fs2.readdir(dir, { withFileTypes: true });
1703
+ entries = await fs3.readdir(dir, { withFileTypes: true });
1643
1704
  } catch {
1644
1705
  return;
1645
1706
  }
1646
1707
  for (const e of entries) {
1647
1708
  if (ignoreSet.has(e.name)) continue;
1648
1709
  const full = path4.join(dir, e.name);
1710
+ const rel = path4.relative(projectRoot, full).replace(/\\/g, "/");
1649
1711
  if (e.isDirectory()) {
1712
+ if (isGitIgnored(rel, true)) continue;
1650
1713
  await walk(full);
1651
1714
  } else if (e.isFile()) {
1652
- const rel = path4.relative(projectRoot, full).replace(/\\/g, "/");
1715
+ if (isGitIgnored(rel, false)) continue;
1653
1716
  const ext = path4.extname(e.name);
1654
1717
  for (const { ext: extName, pat } of globs) {
1655
1718
  if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
@@ -1692,11 +1755,12 @@ async function runIndexer(_ctx, opts) {
1692
1755
  const langStats = {};
1693
1756
  let filesIndexed = 0;
1694
1757
  let symbolsIndexed = 0;
1758
+ const isGitIgnored = await loadGitignoreMatcher(projectRoot);
1695
1759
  let files;
1696
1760
  if (opts.files && opts.files.length > 0) {
1697
- files = opts.files.map((f) => path4.resolve(projectRoot, f));
1761
+ files = opts.files.map((f) => path4.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path4.relative(projectRoot, f).replace(/\\/g, "/"), false));
1698
1762
  } else {
1699
- files = await findSourceFiles(projectRoot, ignore);
1763
+ files = await findSourceFiles(projectRoot, ignore, isGitIgnored);
1700
1764
  }
1701
1765
  if (langs && langs.length > 0) {
1702
1766
  const langSet = new Set(langs);
@@ -1713,7 +1777,7 @@ async function runIndexer(_ctx, opts) {
1713
1777
  for (const file of files) {
1714
1778
  let stat2;
1715
1779
  try {
1716
- stat2 = await fs2.stat(file);
1780
+ stat2 = await fs3.stat(file);
1717
1781
  } catch {
1718
1782
  store.deleteFile(file);
1719
1783
  continue;
@@ -1728,11 +1792,11 @@ async function runIndexer(_ctx, opts) {
1728
1792
  filesIndexed++;
1729
1793
  continue;
1730
1794
  }
1731
- store.deleteSymbolsForFile(file);
1732
1795
  store.deleteRefsForFile(file);
1796
+ store.deleteSymbolsForFile(file);
1733
1797
  let content;
1734
1798
  try {
1735
- content = await fs2.readFile(file, "utf8");
1799
+ content = await fs3.readFile(file, "utf8");
1736
1800
  } catch (e) {
1737
1801
  errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
1738
1802
  continue;
@@ -1755,7 +1819,7 @@ async function runIndexer(_ctx, opts) {
1755
1819
  filesIndexed++;
1756
1820
  continue;
1757
1821
  }
1758
- const nextId = store.getStats().totalSymbols + 1;
1822
+ const nextId = store.getMaxSymbolId() + 1;
1759
1823
  const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
1760
1824
  store.insertSymbols(symbolsWithIds, nextId);
1761
1825
  const count = symbolsWithIds.length;
@@ -1782,7 +1846,7 @@ async function runIndexer(_ctx, opts) {
1782
1846
  }
1783
1847
  for (const [file_] of existingMeta) {
1784
1848
  try {
1785
- await fs2.stat(file_);
1849
+ await fs3.stat(file_);
1786
1850
  } catch {
1787
1851
  store.deleteFile(file_);
1788
1852
  }
@@ -2041,6 +2105,70 @@ var codebaseStatsTool = {
2041
2105
  }
2042
2106
  };
2043
2107
 
2044
- export { IndexStore, SCHEMA_VERSION, buildBm25Index, buildIndexableText, codebaseIndexDirOverride, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, internalKindToLspKind, lspKindToInternalKind, resolveIndexDir, tokenise };
2108
+ // src/codebase-index/background-indexer.ts
2109
+ function stubCtx(projectRoot) {
2110
+ return {
2111
+ projectRoot,
2112
+ cwd: projectRoot,
2113
+ messages: [],
2114
+ todos: [],
2115
+ readFiles: /* @__PURE__ */ new Set(),
2116
+ fileMtimes: /* @__PURE__ */ new Map()
2117
+ };
2118
+ }
2119
+ var chain = Promise.resolve();
2120
+ function withMutex(job) {
2121
+ const run = chain.then(job, job);
2122
+ chain = run.then(
2123
+ () => void 0,
2124
+ () => void 0
2125
+ );
2126
+ return run;
2127
+ }
2128
+ var DEFAULT_DEBOUNCE_MS = 400;
2129
+ var debounceTimers = /* @__PURE__ */ new Map();
2130
+ function debounceKey(indexDir, file) {
2131
+ return `${indexDir ?? ""}|${file}`;
2132
+ }
2133
+ function isIndexableFile(filePath) {
2134
+ return detectLang(filePath) !== null;
2135
+ }
2136
+ function runStartupIndex(opts) {
2137
+ return withMutex(
2138
+ () => runIndexer(stubCtx(opts.projectRoot), {
2139
+ projectRoot: opts.projectRoot,
2140
+ indexDir: opts.indexDir,
2141
+ force: opts.force
2142
+ })
2143
+ );
2144
+ }
2145
+ function enqueueReindex(opts) {
2146
+ const files = opts.files.filter(isIndexableFile);
2147
+ if (files.length === 0) return;
2148
+ const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
2149
+ for (const file of files) {
2150
+ const key = debounceKey(opts.indexDir, file);
2151
+ const existing = debounceTimers.get(key);
2152
+ if (existing) clearTimeout(existing);
2153
+ const timer = setTimeout(() => {
2154
+ debounceTimers.delete(key);
2155
+ void withMutex(
2156
+ () => runIndexer(stubCtx(opts.projectRoot), {
2157
+ projectRoot: opts.projectRoot,
2158
+ files: [file],
2159
+ indexDir: opts.indexDir
2160
+ })
2161
+ ).catch((err) => opts.onError?.(err));
2162
+ }, ms);
2163
+ timer.unref?.();
2164
+ debounceTimers.set(key, timer);
2165
+ }
2166
+ }
2167
+ function cancelPendingReindexes() {
2168
+ for (const t of debounceTimers.values()) clearTimeout(t);
2169
+ debounceTimers.clear();
2170
+ }
2171
+
2172
+ export { IndexStore, SCHEMA_VERSION, buildBm25Index, buildIndexableText, cancelPendingReindexes, codebaseIndexDirOverride, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, enqueueReindex, internalKindToLspKind, isIndexableFile, lspKindToInternalKind, resolveIndexDir, runIndexer, runStartupIndex, tokenise };
2045
2173
  //# sourceMappingURL=index.js.map
2046
2174
  //# sourceMappingURL=index.js.map