@wrongstack/tools 0.275.1 → 0.276.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.
Files changed (47) hide show
  1. package/dist/{background-indexer-BoTUw0EM.d.ts → background-indexer-BeDBxfSh.d.ts} +6 -0
  2. package/dist/builtin.js +1056 -246
  3. package/dist/builtin.js.map +1 -1
  4. package/dist/codebase-index/index.d.ts +30 -2
  5. package/dist/codebase-index/index.js +201 -24
  6. package/dist/codebase-index/index.js.map +1 -1
  7. package/dist/codebase-index/worker.js +196 -23
  8. package/dist/codebase-index/worker.js.map +1 -1
  9. package/dist/document.js +2 -2
  10. package/dist/document.js.map +1 -1
  11. package/dist/edit.js +52 -15
  12. package/dist/edit.js.map +1 -1
  13. package/dist/fetch.js +89 -18
  14. package/dist/fetch.js.map +1 -1
  15. package/dist/glob.js +35 -1
  16. package/dist/glob.js.map +1 -1
  17. package/dist/grep.js +15 -4
  18. package/dist/grep.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1090 -255
  21. package/dist/index.js.map +1 -1
  22. package/dist/install.d.ts +7 -0
  23. package/dist/install.js +6 -0
  24. package/dist/install.js.map +1 -1
  25. package/dist/json.d.ts +26 -1
  26. package/dist/json.js +453 -46
  27. package/dist/json.js.map +1 -1
  28. package/dist/memory.js +26 -4
  29. package/dist/memory.js.map +1 -1
  30. package/dist/outdated.js +2 -2
  31. package/dist/outdated.js.map +1 -1
  32. package/dist/pack.js +1056 -246
  33. package/dist/pack.js.map +1 -1
  34. package/dist/read.js +36 -6
  35. package/dist/read.js.map +1 -1
  36. package/dist/replace.js +27 -9
  37. package/dist/replace.js.map +1 -1
  38. package/dist/search.d.ts +5 -1
  39. package/dist/search.js +179 -62
  40. package/dist/search.js.map +1 -1
  41. package/dist/tool-help.js +2 -2
  42. package/dist/tool-help.js.map +1 -1
  43. package/dist/tool-search.js +2 -2
  44. package/dist/tool-search.js.map +1 -1
  45. package/dist/write.js +13 -3
  46. package/dist/write.js.map +1 -1
  47. package/package.json +2 -2
@@ -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-BoTUw0EM.js';
2
- export { C as CircuitOpenError, e as CircuitSnapshot, f as CircuitState, g as FileSymbols, h as IndexCircuitBreaker, i as IndexTimeoutError, j as SCHEMA_VERSION, k as cancelPendingReindexes, l as codebaseIndexStats, m as codebaseIndexTool, n as codebaseSearchTool, o as codebaseStatsTool, p as enqueueReindex, q as getIndexState, r as indexCircuitBreaker, s as isIndexReady, t as isIndexableFile, u as isIndexing, v as onIndexStateChange, w as resetIndexCircuitBreaker, x as runStartupIndex, y as searchCodebaseIndex, z as shutdownCodebaseIndexHost } from '../background-indexer-BoTUw0EM.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-BeDBxfSh.js';
2
+ export { C as CircuitOpenError, e as CircuitSnapshot, f as CircuitState, g as FileSymbols, h as IndexCircuitBreaker, i as IndexTimeoutError, j as SCHEMA_VERSION, k as cancelPendingReindexes, l as codebaseIndexStats, m as codebaseIndexTool, n as codebaseSearchTool, o as codebaseStatsTool, p as enqueueReindex, q as getIndexState, r as indexCircuitBreaker, s as isIndexReady, t as isIndexableFile, u as isIndexing, v as onIndexStateChange, w as resetIndexCircuitBreaker, x as runStartupIndex, y as searchCodebaseIndex, z as shutdownCodebaseIndexHost } from '../background-indexer-BeDBxfSh.js';
3
3
  import { Context } from '@wrongstack/core';
4
4
 
5
5
  interface IndexerOptions {
@@ -150,6 +150,34 @@ declare class IndexStore {
150
150
  * Each ref's own {@link Ref.fromId} is used; pass an empty array to no-op.
151
151
  */
152
152
  insertRefsBatch(refs: Ref[]): void;
153
+ /**
154
+ * Commit a batch of file-level symbol/refs/upserts in a single transaction.
155
+ *
156
+ * Used by the indexer to amortize SQLite commit overhead across many files.
157
+ * Before this, the indexer issued one transaction per file (BEGIN IMMEDIATE
158
+ * for symbols, plus per-file deletes and an upsertFile call), so a 20-file
159
+ * parallel batch cost ~5+ transactions × 20 files = 100+ commits. With
160
+ * this entry point we do exactly one BEGIN/COMMIT per parallel batch.
161
+ *
162
+ * Each entry must already be a fully-parsed FileSymbols (symbols + refs).
163
+ * The caller is responsible for the per-file prefix accounting
164
+ * (refsByLine → flat list with `fromId` populated). `deleteForFiles` lets
165
+ * the caller clear stale symbols/refs for any files being re-indexed before
166
+ * the inserts run (required to keep refs → symbols FK invariants).
167
+ *
168
+ * Returns the symbols back with their assigned `id` (same shape as
169
+ * {@link insertSymbols}) so callers can build final per-file results.
170
+ */
171
+ commitBatch(entries: Array<{
172
+ file: string;
173
+ lang: SymbolLang;
174
+ symbols: Symbol[];
175
+ refs: Ref[];
176
+ mtimeMs: number;
177
+ symbolCount: number;
178
+ }>, options?: {
179
+ deleteForFiles?: string[] | undefined;
180
+ }): Symbol[];
153
181
  /**
154
182
  * Delete all refs whose source symbols are in a given file.
155
183
  * Used when re-indexing a file to clear stale refs.
@@ -770,6 +770,104 @@ var IndexStore = class {
770
770
  }
771
771
  });
772
772
  }
773
+ /**
774
+ * Commit a batch of file-level symbol/refs/upserts in a single transaction.
775
+ *
776
+ * Used by the indexer to amortize SQLite commit overhead across many files.
777
+ * Before this, the indexer issued one transaction per file (BEGIN IMMEDIATE
778
+ * for symbols, plus per-file deletes and an upsertFile call), so a 20-file
779
+ * parallel batch cost ~5+ transactions × 20 files = 100+ commits. With
780
+ * this entry point we do exactly one BEGIN/COMMIT per parallel batch.
781
+ *
782
+ * Each entry must already be a fully-parsed FileSymbols (symbols + refs).
783
+ * The caller is responsible for the per-file prefix accounting
784
+ * (refsByLine → flat list with `fromId` populated). `deleteForFiles` lets
785
+ * the caller clear stale symbols/refs for any files being re-indexed before
786
+ * the inserts run (required to keep refs → symbols FK invariants).
787
+ *
788
+ * Returns the symbols back with their assigned `id` (same shape as
789
+ * {@link insertSymbols}) so callers can build final per-file results.
790
+ */
791
+ commitBatch(entries, options = {}) {
792
+ if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
793
+ return [];
794
+ }
795
+ return this.runWithRetry(() => {
796
+ this.db.exec("BEGIN IMMEDIATE");
797
+ try {
798
+ if (options.deleteForFiles && options.deleteForFiles.length > 0) {
799
+ const placeholders = options.deleteForFiles.map(() => "?").join(",");
800
+ if (this.ftsAvailable) {
801
+ this.db.prepare(
802
+ `DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
803
+ ).run(...options.deleteForFiles);
804
+ }
805
+ this.db.prepare(
806
+ `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
807
+ ).run(...options.deleteForFiles);
808
+ this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
809
+ }
810
+ const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
811
+ let nextId = (maxRows[0]?.m ?? 0) + 1;
812
+ const symStmt = this.db.prepare(
813
+ `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
814
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
815
+ );
816
+ const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
817
+ const allInserted = [];
818
+ const refsToInsert = [];
819
+ for (const entry of entries) {
820
+ for (const s of entry.symbols) {
821
+ const id = nextId++;
822
+ symStmt.run(
823
+ id,
824
+ s.lang,
825
+ s.kind,
826
+ s.name,
827
+ s.file,
828
+ s.line,
829
+ s.col,
830
+ s.signature,
831
+ s.docComment,
832
+ s.scope,
833
+ s.text,
834
+ s.file
835
+ );
836
+ ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
837
+ allInserted.push({ ...s, id });
838
+ }
839
+ for (const r of entry.refs) refsToInsert.push(r);
840
+ }
841
+ if (refsToInsert.length > 0) {
842
+ const refStmt = this.db.prepare(
843
+ `INSERT INTO refs(from_id, to_name, to_id, call_type, line)
844
+ VALUES (?, ?, ?, ?, ?)`
845
+ );
846
+ for (const ref of refsToInsert) {
847
+ refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
848
+ }
849
+ }
850
+ const upsertStmt = this.db.prepare(
851
+ `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
852
+ VALUES (?, ?, ?, ?, ?)
853
+ ON CONFLICT(file) DO UPDATE SET
854
+ lang = excluded.lang,
855
+ mtime_ms = excluded.mtime_ms,
856
+ symbol_count = excluded.symbol_count,
857
+ last_indexed = excluded.last_indexed`
858
+ );
859
+ const now = Date.now();
860
+ for (const entry of entries) {
861
+ upsertStmt.run(entry.file, entry.lang, entry.mtimeMs, entry.symbolCount, now);
862
+ }
863
+ this.db.exec("COMMIT");
864
+ return allInserted;
865
+ } catch (err) {
866
+ this.db.exec("ROLLBACK");
867
+ throw err;
868
+ }
869
+ });
870
+ }
773
871
  /**
774
872
  * Delete all refs whose source symbols are in a given file.
775
873
  * Used when re-indexing a file to clear stale refs.
@@ -2301,6 +2399,8 @@ async function runIndexerWithStore(store, opts) {
2301
2399
  return { file, stat: stat2, lang, parsed, content };
2302
2400
  })
2303
2401
  );
2402
+ const batchEntries = [];
2403
+ const deleteForFiles = [];
2304
2404
  for (let fi = 0; fi < statReadParse.length; fi++) {
2305
2405
  const settled = statReadParse[fi];
2306
2406
  const file = expectDefined(batchFiles[fi]);
@@ -2325,43 +2425,116 @@ async function runIndexerWithStore(store, opts) {
2325
2425
  }
2326
2426
  if (!lang || !parsed) {
2327
2427
  if (lang) {
2328
- store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: 0, lastIndexed: Date.now() });
2428
+ store.upsertFile({
2429
+ file,
2430
+ lang,
2431
+ mtimeMs: Math.floor(stat2.mtimeMs),
2432
+ symbolCount: 0,
2433
+ lastIndexed: Date.now()
2434
+ });
2329
2435
  filesIndexed++;
2330
2436
  }
2331
2437
  continue;
2332
2438
  }
2333
- store.deleteRefsForFile(file);
2334
- store.deleteSymbolsForFile(file);
2335
2439
  if (parsed.symbols.length === 0) {
2336
- store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: 0, lastIndexed: Date.now() });
2440
+ store.upsertFile({
2441
+ file,
2442
+ lang,
2443
+ mtimeMs: Math.floor(stat2.mtimeMs),
2444
+ symbolCount: 0,
2445
+ lastIndexed: Date.now()
2446
+ });
2337
2447
  filesIndexed++;
2338
2448
  continue;
2339
2449
  }
2340
- const symbolsWithIds = store.insertSymbols(parsed.symbols);
2341
- const count = symbolsWithIds.length;
2342
- symbolsIndexed += count;
2343
- langStats[lang] = (langStats[lang] ?? 0) + count;
2450
+ const refs = [];
2344
2451
  if (parsed.refs && parsed.refs.length > 0) {
2345
- const refsByLine = /* @__PURE__ */ new Map();
2346
- for (const r of parsed.refs) {
2347
- let arr = refsByLine.get(r.line);
2348
- if (!arr) {
2349
- arr = [];
2350
- refsByLine.set(r.line, arr);
2452
+ for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
2453
+ }
2454
+ batchEntries.push({
2455
+ file,
2456
+ lang,
2457
+ symbols: parsed.symbols,
2458
+ refs,
2459
+ mtimeMs: Math.floor(stat2.mtimeMs),
2460
+ symbolCount: parsed.symbols.length
2461
+ });
2462
+ deleteForFiles.push(file);
2463
+ }
2464
+ if (batchEntries.length > 0) {
2465
+ try {
2466
+ const inserted = store.commitBatch(batchEntries, { deleteForFiles });
2467
+ let cursor = 0;
2468
+ for (const entry of batchEntries) {
2469
+ const count = entry.symbols.length;
2470
+ const symbolsWithIds = inserted.slice(cursor, cursor + count);
2471
+ cursor += count;
2472
+ symbolsIndexed += count;
2473
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
2474
+ filesIndexed++;
2475
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
2476
+ const refsByLine = /* @__PURE__ */ new Map();
2477
+ for (let i = 0; i < symbolsWithIds.length; i++) {
2478
+ const sym = symbolsWithIds[i];
2479
+ let arr = refsByLine.get(sym.line);
2480
+ if (!arr) {
2481
+ arr = [];
2482
+ refsByLine.set(sym.line, arr);
2483
+ }
2484
+ arr.push(i);
2485
+ }
2486
+ for (const ref of entry.refs) {
2487
+ const indices = refsByLine.get(ref.line);
2488
+ if (indices && indices.length > 0) {
2489
+ const idx = indices.shift();
2490
+ ref.fromId = symbolsWithIds[idx].id;
2491
+ }
2492
+ }
2351
2493
  }
2352
- arr.push(r);
2353
2494
  }
2354
- const batch = [];
2355
- for (const sym of symbolsWithIds) {
2356
- const symRefs = refsByLine.get(sym.line);
2357
- if (symRefs) {
2358
- for (const r of symRefs) batch.push({ ...r, fromId: sym.id });
2495
+ } catch (err) {
2496
+ const message = err instanceof Error ? err.message : String(err);
2497
+ errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
2498
+ for (const entry of batchEntries) {
2499
+ try {
2500
+ store.deleteRefsForFile(entry.file);
2501
+ store.deleteSymbolsForFile(entry.file);
2502
+ const symbolsWithIds = store.insertSymbols(entry.symbols);
2503
+ symbolsIndexed += symbolsWithIds.length;
2504
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
2505
+ filesIndexed++;
2506
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
2507
+ const refsByLine = /* @__PURE__ */ new Map();
2508
+ for (const sym of symbolsWithIds) {
2509
+ let arr = refsByLine.get(sym.line);
2510
+ if (!arr) {
2511
+ arr = [];
2512
+ refsByLine.set(sym.line, arr);
2513
+ }
2514
+ arr.push(sym);
2515
+ }
2516
+ const fallbackBatch = [];
2517
+ for (const ref of entry.refs) {
2518
+ const syms = refsByLine.get(ref.line);
2519
+ if (syms && syms.length > 0) {
2520
+ const sym = syms.shift();
2521
+ fallbackBatch.push({ ...ref, fromId: sym.id });
2522
+ }
2523
+ }
2524
+ if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
2525
+ }
2526
+ store.upsertFile({
2527
+ file: entry.file,
2528
+ lang: entry.lang,
2529
+ mtimeMs: entry.mtimeMs,
2530
+ symbolCount: entry.symbolCount,
2531
+ lastIndexed: Date.now()
2532
+ });
2533
+ } catch (innerErr) {
2534
+ errors.push(`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`);
2359
2535
  }
2360
2536
  }
2361
- if (batch.length > 0) store.insertRefsBatch(batch);
2362
2537
  }
2363
- store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: count, lastIndexed: Date.now() });
2364
- filesIndexed++;
2365
2538
  }
2366
2539
  }
2367
2540
  if (discoveredFiles) {
@@ -2803,7 +2976,7 @@ var codebaseSearchTool = {
2803
2976
  name: "codebase-search",
2804
2977
  category: "Project",
2805
2978
  icon: "index",
2806
- description: "Semantic/keyword search over the indexed codebase symbols (functions, classes, interfaces, etc.). Uses BM25 ranking. Much more powerful and structured than raw `grep` for finding code by name or concept.",
2979
+ description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Much more powerful and structured than raw `grep` for finding code by name or concept. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
2807
2980
  usageHint: "PREFERRED FOR CODE UNDERSTANDING:\n\n- Use when you need to find where something is defined or used by name.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\nThis is generally better than `grep` when you are looking for symbols rather than arbitrary text patterns.",
2808
2981
  permission: "auto",
2809
2982
  mutating: false,
@@ -2837,6 +3010,10 @@ var codebaseSearchTool = {
2837
3010
  description: "Maximum results to return (default 20, max 100)",
2838
3011
  minimum: 1,
2839
3012
  maximum: 100
3013
+ },
3014
+ preferLsp: {
3015
+ type: "boolean",
3016
+ description: "Prefer live LSP results over the index. Index-only when the LSP plugin is not active. When the LSP plugin is active and this is true, results come from live workspaceSymbol queries."
2840
3017
  }
2841
3018
  },
2842
3019
  required: ["query"]