@wrongstack/tools 0.73.1 → 0.82.6

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 (95) hide show
  1. package/dist/audit.d.ts +4 -4
  2. package/dist/audit.js +10 -2
  3. package/dist/audit.js.map +1 -1
  4. package/dist/{background-indexer-sbsseCCC.d.ts → background-indexer-DYm1FUxK.d.ts} +49 -17
  5. package/dist/bash.d.ts +4 -4
  6. package/dist/bash.js +18 -4
  7. package/dist/bash.js.map +1 -1
  8. package/dist/batch-tool-use.d.ts +4 -4
  9. package/dist/batch-tool-use.js.map +1 -1
  10. package/dist/builtin.js +266 -71
  11. package/dist/builtin.js.map +1 -1
  12. package/dist/circuit-breaker.d.ts +6 -6
  13. package/dist/circuit-breaker.js.map +1 -1
  14. package/dist/codebase-index/index.d.ts +12 -12
  15. package/dist/codebase-index/index.js +270 -106
  16. package/dist/codebase-index/index.js.map +1 -1
  17. package/dist/diff.d.ts +7 -7
  18. package/dist/diff.js.map +1 -1
  19. package/dist/document.d.ts +6 -6
  20. package/dist/document.js.map +1 -1
  21. package/dist/edit.d.ts +1 -1
  22. package/dist/edit.js.map +1 -1
  23. package/dist/exec.d.ts +3 -3
  24. package/dist/exec.js +15 -3
  25. package/dist/exec.js.map +1 -1
  26. package/dist/fetch.d.ts +1 -1
  27. package/dist/fetch.js +3 -1
  28. package/dist/fetch.js.map +1 -1
  29. package/dist/format.d.ts +4 -4
  30. package/dist/format.js +18 -4
  31. package/dist/format.js.map +1 -1
  32. package/dist/git.d.ts +10 -10
  33. package/dist/git.js +8 -2
  34. package/dist/git.js.map +1 -1
  35. package/dist/glob.d.ts +2 -2
  36. package/dist/glob.js.map +1 -1
  37. package/dist/grep.d.ts +6 -6
  38. package/dist/grep.js +10 -2
  39. package/dist/grep.js.map +1 -1
  40. package/dist/index.d.ts +7 -7
  41. package/dist/index.js +366 -136
  42. package/dist/index.js.map +1 -1
  43. package/dist/install.d.ts +5 -5
  44. package/dist/install.js +18 -4
  45. package/dist/install.js.map +1 -1
  46. package/dist/json.d.ts +8 -8
  47. package/dist/json.js.map +1 -1
  48. package/dist/lint.d.ts +4 -4
  49. package/dist/lint.js +18 -4
  50. package/dist/lint.js.map +1 -1
  51. package/dist/logs.d.ts +8 -8
  52. package/dist/logs.js.map +1 -1
  53. package/dist/memory.d.ts +2 -2
  54. package/dist/memory.js.map +1 -1
  55. package/dist/mode.d.ts +3 -3
  56. package/dist/mode.js.map +1 -1
  57. package/dist/outdated.d.ts +4 -4
  58. package/dist/outdated.js.map +1 -1
  59. package/dist/pack.js +266 -71
  60. package/dist/pack.js.map +1 -1
  61. package/dist/patch.d.ts +3 -3
  62. package/dist/patch.js.map +1 -1
  63. package/dist/process-registry.d.ts +3 -3
  64. package/dist/process-registry.js +7 -1
  65. package/dist/process-registry.js.map +1 -1
  66. package/dist/read.d.ts +2 -2
  67. package/dist/read.js.map +1 -1
  68. package/dist/replace.d.ts +4 -4
  69. package/dist/replace.js +8 -2
  70. package/dist/replace.js.map +1 -1
  71. package/dist/scaffold.d.ts +2 -2
  72. package/dist/scaffold.js.map +1 -1
  73. package/dist/search.d.ts +2 -2
  74. package/dist/search.js +16 -8
  75. package/dist/search.js.map +1 -1
  76. package/dist/test.d.ts +7 -7
  77. package/dist/test.js +18 -4
  78. package/dist/test.js.map +1 -1
  79. package/dist/todo.js +2 -1
  80. package/dist/todo.js.map +1 -1
  81. package/dist/tool-help.d.ts +4 -4
  82. package/dist/tool-help.js.map +1 -1
  83. package/dist/tool-search.d.ts +5 -5
  84. package/dist/tool-search.js.map +1 -1
  85. package/dist/tool-use.d.ts +2 -2
  86. package/dist/tool-use.js.map +1 -1
  87. package/dist/tree.d.ts +7 -7
  88. package/dist/tree.js +10 -2
  89. package/dist/tree.js.map +1 -1
  90. package/dist/typecheck.d.ts +4 -4
  91. package/dist/typecheck.js +18 -4
  92. package/dist/typecheck.js.map +1 -1
  93. package/dist/write.d.ts +1 -1
  94. package/dist/write.js.map +1 -1
  95. package/package.json +2 -2
@@ -81,6 +81,12 @@ function internalKindToLspKind(k) {
81
81
  }
82
82
 
83
83
  // src/codebase-index/writer.ts
84
+ function expectDefined(value) {
85
+ if (value === null || value === void 0) {
86
+ throw new Error("Expected value to be defined");
87
+ }
88
+ return value;
89
+ }
84
90
  var DB_FILE = "index.db";
85
91
  function resolveIndexDir(projectRoot, override) {
86
92
  return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
@@ -225,7 +231,7 @@ var IndexStore = class {
225
231
  "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files WHERE file = ?"
226
232
  ).all(file);
227
233
  if (!rows.length) return null;
228
- const r = rows[0];
234
+ const r = expectDefined(rows[0]);
229
235
  return { file: r.file, lang: r.lang, mtimeMs: r.mtime_ms, symbolCount: r.symbol_count, lastIndexed: r.last_indexed };
230
236
  }
231
237
  getAllFileMetas() {
@@ -307,7 +313,7 @@ var IndexStore = class {
307
313
  const lastRows = this.db.prepare(
308
314
  "SELECT value FROM metadata WHERE key = 'last_indexed'"
309
315
  ).all();
310
- const lastIndexed = lastRows.length ? Number(lastRows[0].value) : null;
316
+ const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
311
317
  const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
312
318
  const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
313
319
  const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
@@ -382,8 +388,9 @@ var IndexStore = class {
382
388
  let resolved = 0;
383
389
  for (const row of unresolved) {
384
390
  const target = this.db.prepare("SELECT id FROM symbols WHERE name = ? LIMIT 1").all(row.to_name);
385
- if (target.length) {
386
- this.db.prepare("UPDATE refs SET to_id = ? WHERE id = ?").run(target[0].id, row.id);
391
+ const first = target[0];
392
+ if (first) {
393
+ this.db.prepare("UPDATE refs SET to_id = ? WHERE id = ?").run(first.id, row.id);
387
394
  resolved++;
388
395
  }
389
396
  }
@@ -1137,6 +1144,12 @@ function syncPyParse(filePath, lang) {
1137
1144
  return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
1138
1145
  }
1139
1146
  }
1147
+ function expectDefined2(value) {
1148
+ if (value === null || value === void 0) {
1149
+ throw new Error("Expected value to be defined");
1150
+ }
1151
+ return value;
1152
+ }
1140
1153
  function parseSymbols4(opts) {
1141
1154
  const { file, content, lang } = opts;
1142
1155
  const nativeAvailable = checkNativeParser();
@@ -1218,14 +1231,14 @@ function regexParse(opts) {
1218
1231
  const lines = content.split("\n");
1219
1232
  const lineOffsets = [0];
1220
1233
  for (let i = 0; i < lines.length; i++) {
1221
- lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
1234
+ lineOffsets.push((lineOffsets[i] ?? 0) + (lines[i]?.length ?? 0) + 1);
1222
1235
  }
1223
1236
  function lineFromOffset(offset) {
1224
1237
  let lo = 0;
1225
1238
  let hi = lineOffsets.length - 1;
1226
1239
  while (lo < hi) {
1227
1240
  const mid = lo + hi + 1 >>> 1;
1228
- if (lineOffsets[mid] <= offset) lo = mid;
1241
+ if (expectDefined2(lineOffsets[mid]) <= offset) lo = mid;
1229
1242
  else hi = mid - 1;
1230
1243
  }
1231
1244
  return lo + 1;
@@ -1237,8 +1250,8 @@ function regexParse(opts) {
1237
1250
  for (const pattern of RS_PATTERNS) {
1238
1251
  pattern.regex.lastIndex = 0;
1239
1252
  for (let match = pattern.regex.exec(content); match !== null; match = pattern.regex.exec(content)) {
1240
- const name = match[1];
1241
- const offset = match.index;
1253
+ const name = expectDefined2(match[1]);
1254
+ const offset = match.index ?? 0;
1242
1255
  const line = lineFromOffset(offset);
1243
1256
  const col = offset - (lineOffsets[line - 1] ?? 0);
1244
1257
  const lineIdx = line - 1;
@@ -1267,6 +1280,12 @@ function regexParse(opts) {
1267
1280
  });
1268
1281
  return { file, lang, symbols: deduped, mtimeMs: Date.now() };
1269
1282
  }
1283
+ function expectDefined3(value) {
1284
+ if (value === null || value === void 0) {
1285
+ throw new Error("Expected value to be defined");
1286
+ }
1287
+ return value;
1288
+ }
1270
1289
  function parseSymbols5(opts) {
1271
1290
  const { file, content, lang } = opts;
1272
1291
  try {
@@ -1286,21 +1305,21 @@ function regexParse2(opts) {
1286
1305
  const lines = content.split("\n");
1287
1306
  const lineOffsets = [0];
1288
1307
  for (let i = 0; i < lines.length; i++) {
1289
- lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
1308
+ lineOffsets.push((lineOffsets[i] ?? 0) + (lines[i]?.length ?? 0) + 1);
1290
1309
  }
1291
1310
  function lineFromOffset(offset) {
1292
1311
  let lo = 0;
1293
1312
  let hi = lineOffsets.length - 1;
1294
1313
  while (lo < hi) {
1295
1314
  const mid = lo + hi + 1 >>> 1;
1296
- if (lineOffsets[mid] <= offset) lo = mid;
1315
+ if (expectDefined3(lineOffsets[mid]) <= offset) lo = mid;
1297
1316
  else hi = mid - 1;
1298
1317
  }
1299
1318
  return lo + 1;
1300
1319
  }
1301
1320
  const rootMatch = content.match(/^\s*\{/m);
1302
1321
  if (rootMatch) {
1303
- const offset = rootMatch.index;
1322
+ const offset = expectDefined3(rootMatch.index);
1304
1323
  const line = lineFromOffset(offset);
1305
1324
  symbols.push(
1306
1325
  makeSymbol({
@@ -1316,8 +1335,8 @@ function regexParse2(opts) {
1316
1335
  }
1317
1336
  const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
1318
1337
  for (let match = topLevelKeyRegex.exec(content); match !== null; match = topLevelKeyRegex.exec(content)) {
1319
- const key = match[1];
1320
- const offset = match.index;
1338
+ const key = expectDefined3(match[1]);
1339
+ const offset = match.index ?? 0;
1321
1340
  const line = lineFromOffset(offset);
1322
1341
  const col = offset - (lineOffsets[line - 1] ?? 0);
1323
1342
  let kind = "property";
@@ -1363,7 +1382,7 @@ function regexParse2(opts) {
1363
1382
  const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
1364
1383
  const defsMatch = defsRegex.exec(content);
1365
1384
  if (defsMatch !== null) {
1366
- const offset = defsMatch.index;
1385
+ const offset = expectDefined3(defsMatch.index);
1367
1386
  const line = lineFromOffset(offset);
1368
1387
  symbols.push(
1369
1388
  makeSymbol({
@@ -1386,9 +1405,9 @@ function regexParse2(opts) {
1386
1405
  for (const pat of defsPatterns) {
1387
1406
  pat.lastIndex = 0;
1388
1407
  for (let match = pat.exec(content); match !== null; match = pat.exec(content)) {
1389
- const offset = match.index;
1408
+ const offset = match.index ?? 0;
1390
1409
  const line = lineFromOffset(offset);
1391
- const key = match[0].match(/"([^"]+)"/)?.[1] ?? match[0];
1410
+ const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined3(match[0]);
1392
1411
  symbols.push(
1393
1412
  makeSymbol({
1394
1413
  name: key,
@@ -1407,12 +1426,12 @@ function regexParse2(opts) {
1407
1426
  function extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFromOffset) {
1408
1427
  const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
1409
1428
  for (let match = scriptsBlockRegex.exec(content); match !== null; match = scriptsBlockRegex.exec(content)) {
1410
- const blockContent = match[0];
1411
- const blockOffset = match.index;
1429
+ const blockContent = expectDefined3(match[0]);
1430
+ const blockOffset = match.index ?? 0;
1412
1431
  const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
1413
1432
  for (let scriptMatch = scriptKeyRegex.exec(blockContent); scriptMatch !== null; scriptMatch = scriptKeyRegex.exec(blockContent)) {
1414
- const key = scriptMatch[1];
1415
- const keyOffset = blockOffset + scriptMatch.index;
1433
+ const key = expectDefined3(scriptMatch[1]);
1434
+ const keyOffset = blockOffset + expectDefined3(scriptMatch.index);
1416
1435
  const line = lineFromOffset(keyOffset);
1417
1436
  symbols.push(
1418
1437
  makeSymbol({
@@ -1431,12 +1450,12 @@ function extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFr
1431
1450
  function extractCompilerOptions(content, symbols, file, lang, lineOffsets, parentLine, lineFromOffset) {
1432
1451
  const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
1433
1452
  for (let match = optsBlockRegex.exec(content); match !== null; match = optsBlockRegex.exec(content)) {
1434
- const blockContent = match[0];
1435
- const blockOffset = match.index;
1453
+ const blockContent = expectDefined3(match[0]);
1454
+ const blockOffset = match.index ?? 0;
1436
1455
  const optKeyRegex = /"(\w[\w]*)"\s*:/g;
1437
1456
  for (let optMatch = optKeyRegex.exec(blockContent); optMatch !== null; optMatch = optKeyRegex.exec(blockContent)) {
1438
- const key = optMatch[1];
1439
- const keyOffset = blockOffset + optMatch.index;
1457
+ const key = expectDefined3(optMatch[1]);
1458
+ const keyOffset = blockOffset + expectDefined3(optMatch.index);
1440
1459
  const line = lineFromOffset(keyOffset);
1441
1460
  if (line <= parentLine) continue;
1442
1461
  symbols.push(
@@ -1470,6 +1489,12 @@ function makeSymbol(opts) {
1470
1489
  }
1471
1490
 
1472
1491
  // src/codebase-index/yaml-parser.ts
1492
+ function expectDefined4(value) {
1493
+ if (value === null || value === void 0) {
1494
+ throw new Error("Expected value to be defined");
1495
+ }
1496
+ return value;
1497
+ }
1473
1498
  function parseSymbols6(opts) {
1474
1499
  const { file, content, lang } = opts;
1475
1500
  try {
@@ -1484,22 +1509,22 @@ function regexParse3(opts) {
1484
1509
  const lines = content.split("\n");
1485
1510
  const lineOffsets = [0];
1486
1511
  for (let i = 0; i < lines.length; i++) {
1487
- lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
1512
+ lineOffsets.push((lineOffsets[i] ?? 0) + (lines[i]?.length ?? 0) + 1);
1488
1513
  }
1489
1514
  function lineFromOffset(offset) {
1490
1515
  let lo = 0;
1491
1516
  let hi = lineOffsets.length - 1;
1492
1517
  while (lo < hi) {
1493
1518
  const mid = lo + hi + 1 >>> 1;
1494
- if (lineOffsets[mid] <= offset) lo = mid;
1519
+ if (expectDefined4(lineOffsets[mid]) <= offset) lo = mid;
1495
1520
  else hi = mid - 1;
1496
1521
  }
1497
1522
  return lo + 1;
1498
1523
  }
1499
1524
  const anchorRegex = /&(\w[\w-]*)/g;
1500
1525
  for (let match = anchorRegex.exec(content); match !== null; match = anchorRegex.exec(content)) {
1501
- const name = match[1];
1502
- const offset = match.index;
1526
+ const name = expectDefined4(match[1]);
1527
+ const offset = match.index ?? 0;
1503
1528
  const line = lineFromOffset(offset);
1504
1529
  const col = offset - (lineOffsets[line - 1] ?? 0);
1505
1530
  symbols.push(
@@ -1516,8 +1541,8 @@ function regexParse3(opts) {
1516
1541
  }
1517
1542
  const aliasRegex = /\*(\w[\w-]*)/g;
1518
1543
  for (let match = aliasRegex.exec(content); match !== null; match = aliasRegex.exec(content)) {
1519
- const name = match[1];
1520
- const offset = match.index;
1544
+ const name = expectDefined4(match[1]);
1545
+ const offset = match.index ?? 0;
1521
1546
  const line = lineFromOffset(offset);
1522
1547
  const col = offset - (lineOffsets[line - 1] ?? 0);
1523
1548
  symbols.push(
@@ -1534,27 +1559,28 @@ function regexParse3(opts) {
1534
1559
  }
1535
1560
  const kvRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:/gm;
1536
1561
  for (let match = kvRegex.exec(content); match !== null; match = kvRegex.exec(content)) {
1537
- const indent = match[1].length;
1562
+ const indent = match[1]?.length ?? 0;
1538
1563
  const key = match[2];
1539
- const offset = match.index;
1564
+ if (!key) continue;
1565
+ const offset = match.index ?? 0;
1540
1566
  const line = lineFromOffset(offset);
1541
1567
  const col = offset - (lineOffsets[line - 1] ?? 0);
1542
1568
  const lineContent = lines[line - 1] ?? "";
1543
1569
  if (/^[|&>]/.test(lineContent.trim())) continue;
1544
1570
  if (key === "---" || key === "...") continue;
1545
1571
  if (indent > 12) continue;
1546
- const value = extractValue(content, match.index);
1572
+ const value = extractValue(content, match.index ?? 0);
1547
1573
  const kind = isScalar(value) ? "literal" : "property";
1548
1574
  const signature = `${key}: ${truncate(value, 60)}`;
1549
1575
  symbols.push(makeSymbol2({ name: key, kind, line, col, signature, file, lang }));
1550
1576
  }
1551
1577
  const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
1552
1578
  for (let match = listItemRegex.exec(content); match !== null; match = listItemRegex.exec(content)) {
1553
- const key = match[2];
1554
- const offset = match.index;
1579
+ const key = expectDefined4(match[2]);
1580
+ const offset = match.index ?? 0;
1555
1581
  const line = lineFromOffset(offset);
1556
1582
  const col = offset - (lineOffsets[line - 1] ?? 0);
1557
- const value = extractValue(content, offset + match[0].length);
1583
+ const value = extractValue(content, offset + match[0]?.length);
1558
1584
  const kind = isScalar(value) ? "literal" : "property";
1559
1585
  symbols.push(
1560
1586
  makeSymbol2({
@@ -1570,8 +1596,8 @@ function regexParse3(opts) {
1570
1596
  }
1571
1597
  const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
1572
1598
  for (let match = blockScalarRegex.exec(content); match !== null; match = blockScalarRegex.exec(content)) {
1573
- const key = match[2];
1574
- const offset = match.index;
1599
+ const key = expectDefined4(match[2]);
1600
+ const offset = match.index ?? 0;
1575
1601
  const line = lineFromOffset(offset);
1576
1602
  const col = offset - (lineOffsets[line - 1] ?? 0);
1577
1603
  symbols.push(
@@ -1670,7 +1696,136 @@ async function loadGitignoreMatcher(projectRoot) {
1670
1696
  return compileGitignore(lines);
1671
1697
  }
1672
1698
 
1699
+ // src/codebase-index/background-indexer.ts
1700
+ var _ready = false;
1701
+ var _indexing = false;
1702
+ var _currentFile = 0;
1703
+ var _totalFiles = 0;
1704
+ var _lastError = null;
1705
+ function isIndexReady() {
1706
+ return _ready;
1707
+ }
1708
+ function setIndexReady() {
1709
+ _ready = true;
1710
+ }
1711
+ function isIndexing() {
1712
+ return _indexing;
1713
+ }
1714
+ function getIndexState() {
1715
+ return {
1716
+ ready: _ready,
1717
+ indexing: _indexing,
1718
+ currentFile: _currentFile,
1719
+ totalFiles: _totalFiles,
1720
+ lastError: _lastError
1721
+ };
1722
+ }
1723
+ var _listeners = [];
1724
+ function onIndexStateChange(listener) {
1725
+ _listeners.push(listener);
1726
+ return () => {
1727
+ _listeners = _listeners.filter((l) => l !== listener);
1728
+ };
1729
+ }
1730
+ function emitState() {
1731
+ const state = getIndexState();
1732
+ for (const l of _listeners) l(state);
1733
+ }
1734
+ function _setIndexProgress(current, total) {
1735
+ _currentFile = current;
1736
+ _totalFiles = total;
1737
+ emitState();
1738
+ }
1739
+ function stubCtx(projectRoot) {
1740
+ return {
1741
+ projectRoot,
1742
+ cwd: projectRoot,
1743
+ messages: [],
1744
+ todos: [],
1745
+ readFiles: /* @__PURE__ */ new Set(),
1746
+ fileMtimes: /* @__PURE__ */ new Map()
1747
+ };
1748
+ }
1749
+ var chain = Promise.resolve();
1750
+ function withMutex(job) {
1751
+ const run = chain.then(job, job);
1752
+ chain = run.then(
1753
+ () => void 0,
1754
+ () => void 0
1755
+ );
1756
+ return run;
1757
+ }
1758
+ var DEFAULT_DEBOUNCE_MS = 400;
1759
+ var debounceTimers = /* @__PURE__ */ new Map();
1760
+ function debounceKey(indexDir, file) {
1761
+ return `${indexDir ?? ""}|${file}`;
1762
+ }
1763
+ function isIndexableFile(filePath) {
1764
+ return detectLang(filePath) !== null;
1765
+ }
1766
+ async function runStartupIndex(opts) {
1767
+ _indexing = true;
1768
+ _currentFile = 0;
1769
+ _totalFiles = 0;
1770
+ _lastError = null;
1771
+ emitState();
1772
+ try {
1773
+ const result = await withMutex(
1774
+ () => runIndexer(stubCtx(opts.projectRoot), {
1775
+ projectRoot: opts.projectRoot,
1776
+ indexDir: opts.indexDir,
1777
+ force: opts.force
1778
+ })
1779
+ );
1780
+ _ready = true;
1781
+ return result;
1782
+ } catch (err) {
1783
+ _lastError = err instanceof Error ? err.message : String(err);
1784
+ _ready = true;
1785
+ throw err;
1786
+ } finally {
1787
+ _indexing = false;
1788
+ emitState();
1789
+ }
1790
+ }
1791
+ function enqueueReindex(opts) {
1792
+ const files = opts.files.filter(isIndexableFile);
1793
+ if (files.length === 0) return;
1794
+ const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
1795
+ for (const file of files) {
1796
+ const key = debounceKey(opts.indexDir, file);
1797
+ const existing = debounceTimers.get(key);
1798
+ if (existing) clearTimeout(existing);
1799
+ const timer = setTimeout(() => {
1800
+ debounceTimers.delete(key);
1801
+ void withMutex(
1802
+ () => runIndexer(stubCtx(opts.projectRoot), {
1803
+ projectRoot: opts.projectRoot,
1804
+ files: [file],
1805
+ indexDir: opts.indexDir
1806
+ })
1807
+ ).catch((err) => opts.onError?.(err));
1808
+ }, ms);
1809
+ timer.unref?.();
1810
+ debounceTimers.set(key, timer);
1811
+ }
1812
+ }
1813
+ function cancelPendingReindexes() {
1814
+ for (const t of debounceTimers.values()) clearTimeout(t);
1815
+ debounceTimers.clear();
1816
+ }
1817
+
1673
1818
  // src/codebase-index/indexer.ts
1819
+ function expectDefined5(value) {
1820
+ if (value === null || value === void 0) {
1821
+ throw new Error("Expected value to be defined");
1822
+ }
1823
+ return value;
1824
+ }
1825
+ var YIELD_EVERY_N = 50;
1826
+ function yieldEventLoop() {
1827
+ return new Promise((resolve2) => setImmediate(resolve2));
1828
+ }
1674
1829
  var DEFAULT_IGNORE = [
1675
1830
  "node_modules",
1676
1831
  ".git",
@@ -1774,7 +1929,12 @@ async function runIndexer(_ctx, opts) {
1774
1929
  if (!force) {
1775
1930
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
1776
1931
  }
1777
- for (const file of files) {
1932
+ for (let fi = 0; fi < files.length; fi++) {
1933
+ const file = expectDefined5(files[fi]);
1934
+ _setIndexProgress(fi + 1, files.length);
1935
+ if (fi > 0 && fi % YIELD_EVERY_N === 0) {
1936
+ await yieldEventLoop();
1937
+ }
1778
1938
  let stat2;
1779
1939
  try {
1780
1940
  stat2 = await fs3.stat(file);
@@ -1827,7 +1987,7 @@ async function runIndexer(_ctx, opts) {
1827
1987
  langStats[lang] = (langStats[lang] ?? 0) + count;
1828
1988
  if (parsed.refs && parsed.refs.length > 0) {
1829
1989
  for (let i = 0; i < symbolsWithIds.length; i++) {
1830
- const sym = symbolsWithIds[i];
1990
+ const sym = expectDefined5(symbolsWithIds[i]);
1831
1991
  const symRefs = parsed.refs.filter((r) => r.line === sym.line);
1832
1992
  if (symRefs.length > 0) {
1833
1993
  const refsWithFromId = symRefs.map((r) => ({ ...r, fromId: sym.id }));
@@ -1888,12 +2048,23 @@ var codebaseIndexTool = {
1888
2048
  }
1889
2049
  },
1890
2050
  async execute(input, ctx) {
2051
+ if (isIndexing()) {
2052
+ return {
2053
+ filesIndexed: 0,
2054
+ symbolsIndexed: 0,
2055
+ langStats: {},
2056
+ durationMs: 0,
2057
+ errors: [],
2058
+ note: "A full index is already in progress. Retry codebase-index after it completes (check codebase-stats)."
2059
+ };
2060
+ }
1891
2061
  const result = await runIndexer(ctx, {
1892
2062
  projectRoot: ctx.projectRoot,
1893
2063
  force: input.force ?? false,
1894
2064
  langs: input.langs,
1895
2065
  indexDir: codebaseIndexDirOverride(ctx)
1896
2066
  });
2067
+ setIndexReady();
1897
2068
  return result;
1898
2069
  }
1899
2070
  };
@@ -1987,6 +2158,12 @@ var Bm25Index = class {
1987
2158
  };
1988
2159
 
1989
2160
  // src/codebase-index/codebase-search-tool.ts
2161
+ function expectDefined6(value) {
2162
+ if (value === null || value === void 0) {
2163
+ throw new Error("Expected value to be defined");
2164
+ }
2165
+ return value;
2166
+ }
1990
2167
  var codebaseSearchTool = {
1991
2168
  name: "codebase-search",
1992
2169
  category: "Project",
@@ -2029,6 +2206,31 @@ var codebaseSearchTool = {
2029
2206
  required: ["query"]
2030
2207
  },
2031
2208
  async execute(input, ctx) {
2209
+ const state = getIndexState();
2210
+ if (!state.ready) {
2211
+ return {
2212
+ results: [],
2213
+ total: 0,
2214
+ query: input.query,
2215
+ indexStatus: state.indexing ? `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.` : "Index not yet built. The codebase is being indexed at startup \u2014 search will be available shortly."
2216
+ };
2217
+ }
2218
+ if (state.indexing) {
2219
+ return {
2220
+ results: [],
2221
+ total: 0,
2222
+ query: input.query,
2223
+ indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files). Results may be incomplete.`
2224
+ };
2225
+ }
2226
+ if (state.lastError) {
2227
+ return {
2228
+ results: [],
2229
+ total: 0,
2230
+ query: input.query,
2231
+ indexStatus: `Index build failed: ${state.lastError}. Try /codebase-reindex.`
2232
+ };
2233
+ }
2032
2234
  const store = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
2033
2235
  try {
2034
2236
  const limit = Math.min(input.limit ?? 20, 100);
@@ -2051,7 +2253,7 @@ var codebaseSearchTool = {
2051
2253
  const top = scored.slice(0, limit);
2052
2254
  const qTokens = tokenise(input.query);
2053
2255
  const results = top.map(({ id, score }) => {
2054
- const c = candidates.find((c2) => c2.id === id);
2256
+ const c = expectDefined6(candidates.find((c2) => c2.id === id));
2055
2257
  const snippet = bm25.extractSnippet(id, qTokens);
2056
2258
  return {
2057
2259
  ...c,
@@ -2086,6 +2288,32 @@ var codebaseStatsTool = {
2086
2288
  additionalProperties: false
2087
2289
  },
2088
2290
  async execute(_input, ctx) {
2291
+ const idxState = getIndexState();
2292
+ if (!idxState.ready) {
2293
+ return {
2294
+ totalSymbols: 0,
2295
+ totalFiles: 0,
2296
+ byLang: {},
2297
+ byKind: {},
2298
+ lastIndexed: null,
2299
+ sizeBytes: 0,
2300
+ indexPath: "",
2301
+ version: SCHEMA_VERSION,
2302
+ indexStatus: idxState.indexing ? `Indexing in progress (${idxState.currentFile}/${idxState.totalFiles} files).` : "Index not yet built."
2303
+ };
2304
+ }
2305
+ if (idxState.indexing) {
2306
+ const store2 = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
2307
+ try {
2308
+ const stats = store2.getStats();
2309
+ return {
2310
+ ...stats,
2311
+ indexStatus: `Index refresh in progress (${idxState.currentFile}/${idxState.totalFiles} files). Stats may be incomplete.`
2312
+ };
2313
+ } finally {
2314
+ store2.close();
2315
+ }
2316
+ }
2089
2317
  const store = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
2090
2318
  try {
2091
2319
  const stats = store.getStats();
@@ -2105,70 +2333,6 @@ var codebaseStatsTool = {
2105
2333
  }
2106
2334
  };
2107
2335
 
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 };
2336
+ export { IndexStore, SCHEMA_VERSION, buildBm25Index, buildIndexableText, cancelPendingReindexes, codebaseIndexDirOverride, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, enqueueReindex, getIndexState, internalKindToLspKind, isIndexReady, isIndexableFile, isIndexing, lspKindToInternalKind, onIndexStateChange, resolveIndexDir, runIndexer, runStartupIndex, tokenise };
2173
2337
  //# sourceMappingURL=index.js.map
2174
2338
  //# sourceMappingURL=index.js.map