@wrongstack/tools 0.141.0 → 0.155.0

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,5 +1,5 @@
1
- import { I as IndexResult, S as Symbol, F as FileMeta, a as SymbolKind, b as SymbolLang, c as SearchResult, d as IndexStats, R as Ref } from '../background-indexer-C2014mH0.js';
2
- export { e as FileSymbols, f as SCHEMA_VERSION, g as cancelPendingReindexes, h as codebaseIndexTool, i as codebaseSearchTool, j as codebaseStatsTool, k as enqueueReindex, l as getIndexState, m as isIndexReady, n as isIndexableFile, o as isIndexing, p as onIndexStateChange, r as runStartupIndex } from '../background-indexer-C2014mH0.js';
1
+ import { I as IndexResult, S as Symbol, F as FileMeta, a as SymbolKind, b as SymbolLang, c as SearchResult, d as IndexStats, R as Ref } from '../background-indexer-CtbgPExj.js';
2
+ export { e as FileSymbols, f as SCHEMA_VERSION, g as cancelPendingReindexes, h as codebaseIndexTool, i as codebaseSearchTool, j as codebaseStatsTool, k as enqueueReindex, l as getIndexState, m as isIndexReady, n as isIndexableFile, o as isIndexing, p as onIndexStateChange, r as runStartupIndex } from '../background-indexer-CtbgPExj.js';
3
3
  import { Context } from '@wrongstack/core';
4
4
 
5
5
  interface IndexerOptions {
@@ -10,6 +10,13 @@ interface IndexerOptions {
10
10
  ignore?: string[] | undefined;
11
11
  /** Override the index directory (default: the global per-project dir). */
12
12
  indexDir?: string | undefined;
13
+ /**
14
+ * Signal that cancels indexing cooperatively. Polled at yield points
15
+ * (file walk, per-file loop) so a hung filesystem won't lock up the
16
+ * process. When the tool executor's timeout fires, this signal aborts
17
+ * and `runIndexer` throws, releasing the mutex and resetting flags.
18
+ */
19
+ signal?: AbortSignal | undefined;
13
20
  }
14
21
  /** Run a full or incremental index and return statistics. */
15
22
  declare function runIndexer(_ctx: Context, opts: IndexerOptions): Promise<IndexResult>;
@@ -1742,7 +1742,8 @@ async function runStartupIndex(opts) {
1742
1742
  () => runIndexer(stubCtx(opts.projectRoot), {
1743
1743
  projectRoot: opts.projectRoot,
1744
1744
  indexDir: opts.indexDir,
1745
- force: opts.force
1745
+ force: opts.force,
1746
+ signal: opts.signal
1746
1747
  })
1747
1748
  );
1748
1749
  _ready = true;
@@ -1788,6 +1789,16 @@ var YIELD_EVERY_N = 50;
1788
1789
  function yieldEventLoop() {
1789
1790
  return new Promise((resolve2) => setImmediate(resolve2));
1790
1791
  }
1792
+ function throwIfAborted(signal) {
1793
+ if (!signal?.aborted) return;
1794
+ if (signal.reason instanceof Error) throw signal.reason;
1795
+ throw new Error(
1796
+ typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
1797
+ );
1798
+ }
1799
+ function isAbortError(err) {
1800
+ return err instanceof DOMException && err.name === "AbortError";
1801
+ }
1791
1802
  var DEFAULT_IGNORE = [
1792
1803
  "node_modules",
1793
1804
  ".git",
@@ -1799,7 +1810,7 @@ var DEFAULT_IGNORE = [
1799
1810
  "__snapshots__",
1800
1811
  ".nyc_output"
1801
1812
  ];
1802
- async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
1813
+ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
1803
1814
  const results = [];
1804
1815
  const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
1805
1816
  const globs = [
@@ -1814,13 +1825,20 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
1814
1825
  { ext: ".yaml", pat: compileGlob("**/*.yaml") },
1815
1826
  { ext: ".yml", pat: compileGlob("**/*.yml") }
1816
1827
  ];
1828
+ let dirCount = 0;
1817
1829
  const walk = async (dir) => {
1830
+ throwIfAborted(signal);
1831
+ if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
1832
+ await yieldEventLoop();
1833
+ throwIfAborted(signal);
1834
+ }
1818
1835
  let entries;
1819
1836
  try {
1820
1837
  entries = await fs3.readdir(dir, { withFileTypes: true });
1821
1838
  } catch {
1822
1839
  return;
1823
1840
  }
1841
+ dirCount++;
1824
1842
  for (const e of entries) {
1825
1843
  if (ignoreSet.has(e.name)) continue;
1826
1844
  const full = path4.join(dir, e.name);
@@ -1865,7 +1883,7 @@ async function parseFile(file, content, lang) {
1865
1883
  }
1866
1884
  }
1867
1885
  async function runIndexer(_ctx, opts) {
1868
- const { projectRoot, force = false, langs, ignore = [], indexDir } = opts;
1886
+ const { projectRoot, force = false, langs, ignore = [], indexDir, signal } = opts;
1869
1887
  const store = new IndexStore(projectRoot, { indexDir });
1870
1888
  const startMs = Date.now();
1871
1889
  const errors = [];
@@ -1877,7 +1895,7 @@ async function runIndexer(_ctx, opts) {
1877
1895
  if (opts.files && opts.files.length > 0) {
1878
1896
  files = opts.files.map((f) => path4.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path4.relative(projectRoot, f).replace(/\\/g, "/"), false));
1879
1897
  } else {
1880
- files = await findSourceFiles(projectRoot, ignore, isGitIgnored);
1898
+ files = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
1881
1899
  }
1882
1900
  if (langs && langs.length > 0) {
1883
1901
  const langSet = new Set(langs);
@@ -1896,11 +1914,14 @@ async function runIndexer(_ctx, opts) {
1896
1914
  _setIndexProgress(fi + 1, files.length);
1897
1915
  if (fi > 0 && fi % YIELD_EVERY_N === 0) {
1898
1916
  await yieldEventLoop();
1917
+ throwIfAborted(signal);
1899
1918
  }
1900
1919
  let stat2;
1901
1920
  try {
1902
- stat2 = await fs3.stat(file);
1903
- } catch {
1921
+ const statOpts = signal ? { signal } : {};
1922
+ stat2 = await fs3.stat(file, statOpts);
1923
+ } catch (e) {
1924
+ if (isAbortError(e)) throw e;
1904
1925
  store.deleteFile(file);
1905
1926
  continue;
1906
1927
  }
@@ -1918,8 +1939,9 @@ async function runIndexer(_ctx, opts) {
1918
1939
  store.deleteSymbolsForFile(file);
1919
1940
  let content;
1920
1941
  try {
1921
- content = await fs3.readFile(file, "utf8");
1942
+ content = await fs3.readFile(file, { encoding: "utf8", signal });
1922
1943
  } catch (e) {
1944
+ if (isAbortError(e)) throw e;
1923
1945
  errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
1924
1946
  continue;
1925
1947
  }
@@ -2009,7 +2031,7 @@ var codebaseIndexTool = {
2009
2031
  }
2010
2032
  }
2011
2033
  },
2012
- async execute(input, ctx) {
2034
+ async execute(input, ctx, execOpts) {
2013
2035
  if (isIndexing()) {
2014
2036
  return {
2015
2037
  filesIndexed: 0,
@@ -2024,7 +2046,8 @@ var codebaseIndexTool = {
2024
2046
  projectRoot: ctx.projectRoot,
2025
2047
  force: input.force ?? false,
2026
2048
  langs: input.langs,
2027
- indexDir: codebaseIndexDirOverride(ctx)
2049
+ indexDir: codebaseIndexDirOverride(ctx),
2050
+ signal: execOpts?.signal
2028
2051
  });
2029
2052
  setIndexReady();
2030
2053
  return result;
@@ -2039,7 +2062,7 @@ function tokenise(text) {
2039
2062
  return sanitised.toLowerCase().split(" ").filter(Boolean);
2040
2063
  }
2041
2064
  function splitName(name) {
2042
- return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_\-]+/g, " ").trim();
2065
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
2043
2066
  }
2044
2067
  function buildIndexableText(name, signature, docComment) {
2045
2068
  return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");