@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
@@ -727,6 +727,104 @@ var IndexStore = class {
727
727
  }
728
728
  });
729
729
  }
730
+ /**
731
+ * Commit a batch of file-level symbol/refs/upserts in a single transaction.
732
+ *
733
+ * Used by the indexer to amortize SQLite commit overhead across many files.
734
+ * Before this, the indexer issued one transaction per file (BEGIN IMMEDIATE
735
+ * for symbols, plus per-file deletes and an upsertFile call), so a 20-file
736
+ * parallel batch cost ~5+ transactions × 20 files = 100+ commits. With
737
+ * this entry point we do exactly one BEGIN/COMMIT per parallel batch.
738
+ *
739
+ * Each entry must already be a fully-parsed FileSymbols (symbols + refs).
740
+ * The caller is responsible for the per-file prefix accounting
741
+ * (refsByLine → flat list with `fromId` populated). `deleteForFiles` lets
742
+ * the caller clear stale symbols/refs for any files being re-indexed before
743
+ * the inserts run (required to keep refs → symbols FK invariants).
744
+ *
745
+ * Returns the symbols back with their assigned `id` (same shape as
746
+ * {@link insertSymbols}) so callers can build final per-file results.
747
+ */
748
+ commitBatch(entries, options = {}) {
749
+ if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
750
+ return [];
751
+ }
752
+ return this.runWithRetry(() => {
753
+ this.db.exec("BEGIN IMMEDIATE");
754
+ try {
755
+ if (options.deleteForFiles && options.deleteForFiles.length > 0) {
756
+ const placeholders = options.deleteForFiles.map(() => "?").join(",");
757
+ if (this.ftsAvailable) {
758
+ this.db.prepare(
759
+ `DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
760
+ ).run(...options.deleteForFiles);
761
+ }
762
+ this.db.prepare(
763
+ `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
764
+ ).run(...options.deleteForFiles);
765
+ this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
766
+ }
767
+ const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
768
+ let nextId = (maxRows[0]?.m ?? 0) + 1;
769
+ const symStmt = this.db.prepare(
770
+ `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
771
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
772
+ );
773
+ const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
774
+ const allInserted = [];
775
+ const refsToInsert = [];
776
+ for (const entry of entries) {
777
+ for (const s of entry.symbols) {
778
+ const id = nextId++;
779
+ symStmt.run(
780
+ id,
781
+ s.lang,
782
+ s.kind,
783
+ s.name,
784
+ s.file,
785
+ s.line,
786
+ s.col,
787
+ s.signature,
788
+ s.docComment,
789
+ s.scope,
790
+ s.text,
791
+ s.file
792
+ );
793
+ ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
794
+ allInserted.push({ ...s, id });
795
+ }
796
+ for (const r of entry.refs) refsToInsert.push(r);
797
+ }
798
+ if (refsToInsert.length > 0) {
799
+ const refStmt = this.db.prepare(
800
+ `INSERT INTO refs(from_id, to_name, to_id, call_type, line)
801
+ VALUES (?, ?, ?, ?, ?)`
802
+ );
803
+ for (const ref of refsToInsert) {
804
+ refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
805
+ }
806
+ }
807
+ const upsertStmt = this.db.prepare(
808
+ `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
809
+ VALUES (?, ?, ?, ?, ?)
810
+ ON CONFLICT(file) DO UPDATE SET
811
+ lang = excluded.lang,
812
+ mtime_ms = excluded.mtime_ms,
813
+ symbol_count = excluded.symbol_count,
814
+ last_indexed = excluded.last_indexed`
815
+ );
816
+ const now = Date.now();
817
+ for (const entry of entries) {
818
+ upsertStmt.run(entry.file, entry.lang, entry.mtimeMs, entry.symbolCount, now);
819
+ }
820
+ this.db.exec("COMMIT");
821
+ return allInserted;
822
+ } catch (err) {
823
+ this.db.exec("ROLLBACK");
824
+ throw err;
825
+ }
826
+ });
827
+ }
730
828
  /**
731
829
  * Delete all refs whose source symbols are in a given file.
732
830
  * Used when re-indexing a file to clear stale refs.
@@ -2258,6 +2356,8 @@ async function runIndexerWithStore(store, opts) {
2258
2356
  return { file, stat: stat2, lang, parsed, content };
2259
2357
  })
2260
2358
  );
2359
+ const batchEntries = [];
2360
+ const deleteForFiles = [];
2261
2361
  for (let fi = 0; fi < statReadParse.length; fi++) {
2262
2362
  const settled = statReadParse[fi];
2263
2363
  const file = expectDefined(batchFiles[fi]);
@@ -2282,43 +2382,116 @@ async function runIndexerWithStore(store, opts) {
2282
2382
  }
2283
2383
  if (!lang || !parsed) {
2284
2384
  if (lang) {
2285
- store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: 0, lastIndexed: Date.now() });
2385
+ store.upsertFile({
2386
+ file,
2387
+ lang,
2388
+ mtimeMs: Math.floor(stat2.mtimeMs),
2389
+ symbolCount: 0,
2390
+ lastIndexed: Date.now()
2391
+ });
2286
2392
  filesIndexed++;
2287
2393
  }
2288
2394
  continue;
2289
2395
  }
2290
- store.deleteRefsForFile(file);
2291
- store.deleteSymbolsForFile(file);
2292
2396
  if (parsed.symbols.length === 0) {
2293
- store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: 0, lastIndexed: Date.now() });
2397
+ store.upsertFile({
2398
+ file,
2399
+ lang,
2400
+ mtimeMs: Math.floor(stat2.mtimeMs),
2401
+ symbolCount: 0,
2402
+ lastIndexed: Date.now()
2403
+ });
2294
2404
  filesIndexed++;
2295
2405
  continue;
2296
2406
  }
2297
- const symbolsWithIds = store.insertSymbols(parsed.symbols);
2298
- const count = symbolsWithIds.length;
2299
- symbolsIndexed += count;
2300
- langStats[lang] = (langStats[lang] ?? 0) + count;
2407
+ const refs = [];
2301
2408
  if (parsed.refs && parsed.refs.length > 0) {
2302
- const refsByLine = /* @__PURE__ */ new Map();
2303
- for (const r of parsed.refs) {
2304
- let arr = refsByLine.get(r.line);
2305
- if (!arr) {
2306
- arr = [];
2307
- refsByLine.set(r.line, arr);
2409
+ for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
2410
+ }
2411
+ batchEntries.push({
2412
+ file,
2413
+ lang,
2414
+ symbols: parsed.symbols,
2415
+ refs,
2416
+ mtimeMs: Math.floor(stat2.mtimeMs),
2417
+ symbolCount: parsed.symbols.length
2418
+ });
2419
+ deleteForFiles.push(file);
2420
+ }
2421
+ if (batchEntries.length > 0) {
2422
+ try {
2423
+ const inserted = store.commitBatch(batchEntries, { deleteForFiles });
2424
+ let cursor = 0;
2425
+ for (const entry of batchEntries) {
2426
+ const count = entry.symbols.length;
2427
+ const symbolsWithIds = inserted.slice(cursor, cursor + count);
2428
+ cursor += count;
2429
+ symbolsIndexed += count;
2430
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
2431
+ filesIndexed++;
2432
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
2433
+ const refsByLine = /* @__PURE__ */ new Map();
2434
+ for (let i = 0; i < symbolsWithIds.length; i++) {
2435
+ const sym = symbolsWithIds[i];
2436
+ let arr = refsByLine.get(sym.line);
2437
+ if (!arr) {
2438
+ arr = [];
2439
+ refsByLine.set(sym.line, arr);
2440
+ }
2441
+ arr.push(i);
2442
+ }
2443
+ for (const ref of entry.refs) {
2444
+ const indices = refsByLine.get(ref.line);
2445
+ if (indices && indices.length > 0) {
2446
+ const idx = indices.shift();
2447
+ ref.fromId = symbolsWithIds[idx].id;
2448
+ }
2449
+ }
2308
2450
  }
2309
- arr.push(r);
2310
2451
  }
2311
- const batch = [];
2312
- for (const sym of symbolsWithIds) {
2313
- const symRefs = refsByLine.get(sym.line);
2314
- if (symRefs) {
2315
- for (const r of symRefs) batch.push({ ...r, fromId: sym.id });
2452
+ } catch (err) {
2453
+ const message = err instanceof Error ? err.message : String(err);
2454
+ errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
2455
+ for (const entry of batchEntries) {
2456
+ try {
2457
+ store.deleteRefsForFile(entry.file);
2458
+ store.deleteSymbolsForFile(entry.file);
2459
+ const symbolsWithIds = store.insertSymbols(entry.symbols);
2460
+ symbolsIndexed += symbolsWithIds.length;
2461
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
2462
+ filesIndexed++;
2463
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
2464
+ const refsByLine = /* @__PURE__ */ new Map();
2465
+ for (const sym of symbolsWithIds) {
2466
+ let arr = refsByLine.get(sym.line);
2467
+ if (!arr) {
2468
+ arr = [];
2469
+ refsByLine.set(sym.line, arr);
2470
+ }
2471
+ arr.push(sym);
2472
+ }
2473
+ const fallbackBatch = [];
2474
+ for (const ref of entry.refs) {
2475
+ const syms = refsByLine.get(ref.line);
2476
+ if (syms && syms.length > 0) {
2477
+ const sym = syms.shift();
2478
+ fallbackBatch.push({ ...ref, fromId: sym.id });
2479
+ }
2480
+ }
2481
+ if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
2482
+ }
2483
+ store.upsertFile({
2484
+ file: entry.file,
2485
+ lang: entry.lang,
2486
+ mtimeMs: entry.mtimeMs,
2487
+ symbolCount: entry.symbolCount,
2488
+ lastIndexed: Date.now()
2489
+ });
2490
+ } catch (innerErr) {
2491
+ errors.push(`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`);
2316
2492
  }
2317
2493
  }
2318
- if (batch.length > 0) store.insertRefsBatch(batch);
2319
2494
  }
2320
- store.upsertFile({ file, lang, mtimeMs: Math.floor(stat2.mtimeMs), symbolCount: count, lastIndexed: Date.now() });
2321
- filesIndexed++;
2322
2495
  }
2323
2496
  }
2324
2497
  if (discoveredFiles) {