@wrongstack/tools 0.73.1 → 0.77.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.
package/dist/index.d.ts CHANGED
@@ -33,7 +33,7 @@ export { forgetTool, rememberTool } from './memory.js';
33
33
  export { createModeTool } from './mode.js';
34
34
  export { KillOpts, RegistryStats, TrackedProcess, _resetProcessRegistry, getProcessRegistry } from './process-registry.js';
35
35
  export { CircuitBreaker, CircuitBreakerConfig, CircuitBreakerSnapshot } from './circuit-breaker.js';
36
- export { 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';
36
+ export { 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-C70RD7LU.js';
37
37
  export { builtinTools } from './builtin.js';
38
38
  export { builtinToolsPack } from './pack.js';
39
39
  import 'node:child_process';
package/dist/index.js CHANGED
@@ -2463,9 +2463,10 @@ var todoTool = {
2463
2463
  name: "todo",
2464
2464
  category: "Session",
2465
2465
  description: "Manage the session-level todo list. This is the primary mechanism for tracking multi-step work. The list is fully replaced on every call (not appended).",
2466
- usageHint: "BEST PRACTICE for complex tasks:\n- At the beginning of a non-trivial task, create a clear todo list with specific, actionable items.\n- Only **one** item should be `in_progress` at any time.\n- Update the list frequently as work progresses (mark items done, add new ones, change status).\n- The system and user can see this list, so keep it honest and up-to-date.\nThis tool is extremely valuable for maintaining focus and giving the user visibility into your plan.",
2466
+ usageHint: "BEST PRACTICE for complex tasks:\n- At the beginning of a non-trivial task, create a clear todo list with specific, actionable items.\n- Only **one** item should be `in_progress` at any time.\n- Update the list frequently as work progresses (mark items done, add new ones, change status).\n- **Re-order items** to reflect current priorities \u2014 the full list is replaced each call, so item order is entirely under your control.\n- When all items are completed the board auto-clears \u2014 you do NOT need to send an empty list.\n- The system and user can see this list, so keep it honest and up-to-date.\nThis tool is extremely valuable for maintaining focus and giving the user visibility into your plan.",
2467
2467
  permission: "auto",
2468
2468
  mutating: false,
2469
+ // mutates only conversation state (ctx.todos), not external state — no confirmation needed
2469
2470
  timeoutMs: 1e3,
2470
2471
  inputSchema: {
2471
2472
  type: "object",
@@ -6949,7 +6950,130 @@ async function loadGitignoreMatcher(projectRoot) {
6949
6950
  return compileGitignore(lines);
6950
6951
  }
6951
6952
 
6953
+ // src/codebase-index/background-indexer.ts
6954
+ var _ready = false;
6955
+ var _indexing = false;
6956
+ var _currentFile = 0;
6957
+ var _totalFiles = 0;
6958
+ var _lastError = null;
6959
+ function isIndexReady() {
6960
+ return _ready;
6961
+ }
6962
+ function setIndexReady() {
6963
+ _ready = true;
6964
+ }
6965
+ function isIndexing() {
6966
+ return _indexing;
6967
+ }
6968
+ function getIndexState() {
6969
+ return {
6970
+ ready: _ready,
6971
+ indexing: _indexing,
6972
+ currentFile: _currentFile,
6973
+ totalFiles: _totalFiles,
6974
+ lastError: _lastError
6975
+ };
6976
+ }
6977
+ var _listeners = [];
6978
+ function onIndexStateChange(listener) {
6979
+ _listeners.push(listener);
6980
+ return () => {
6981
+ _listeners = _listeners.filter((l) => l !== listener);
6982
+ };
6983
+ }
6984
+ function emitState() {
6985
+ const state = getIndexState();
6986
+ for (const l of _listeners) l(state);
6987
+ }
6988
+ function _setIndexProgress(current, total) {
6989
+ _currentFile = current;
6990
+ _totalFiles = total;
6991
+ emitState();
6992
+ }
6993
+ function stubCtx(projectRoot) {
6994
+ return {
6995
+ projectRoot,
6996
+ cwd: projectRoot,
6997
+ messages: [],
6998
+ todos: [],
6999
+ readFiles: /* @__PURE__ */ new Set(),
7000
+ fileMtimes: /* @__PURE__ */ new Map()
7001
+ };
7002
+ }
7003
+ var chain = Promise.resolve();
7004
+ function withMutex(job) {
7005
+ const run = chain.then(job, job);
7006
+ chain = run.then(
7007
+ () => void 0,
7008
+ () => void 0
7009
+ );
7010
+ return run;
7011
+ }
7012
+ var DEFAULT_DEBOUNCE_MS = 400;
7013
+ var debounceTimers = /* @__PURE__ */ new Map();
7014
+ function debounceKey(indexDir, file) {
7015
+ return `${indexDir ?? ""}|${file}`;
7016
+ }
7017
+ function isIndexableFile(filePath) {
7018
+ return detectLang(filePath) !== null;
7019
+ }
7020
+ async function runStartupIndex(opts) {
7021
+ _indexing = true;
7022
+ _currentFile = 0;
7023
+ _totalFiles = 0;
7024
+ _lastError = null;
7025
+ emitState();
7026
+ try {
7027
+ const result = await withMutex(
7028
+ () => runIndexer(stubCtx(opts.projectRoot), {
7029
+ projectRoot: opts.projectRoot,
7030
+ indexDir: opts.indexDir,
7031
+ force: opts.force
7032
+ })
7033
+ );
7034
+ _ready = true;
7035
+ return result;
7036
+ } catch (err) {
7037
+ _lastError = err instanceof Error ? err.message : String(err);
7038
+ _ready = true;
7039
+ throw err;
7040
+ } finally {
7041
+ _indexing = false;
7042
+ emitState();
7043
+ }
7044
+ }
7045
+ function enqueueReindex(opts) {
7046
+ const files = opts.files.filter(isIndexableFile);
7047
+ if (files.length === 0) return;
7048
+ const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
7049
+ for (const file of files) {
7050
+ const key = debounceKey(opts.indexDir, file);
7051
+ const existing = debounceTimers.get(key);
7052
+ if (existing) clearTimeout(existing);
7053
+ const timer = setTimeout(() => {
7054
+ debounceTimers.delete(key);
7055
+ void withMutex(
7056
+ () => runIndexer(stubCtx(opts.projectRoot), {
7057
+ projectRoot: opts.projectRoot,
7058
+ files: [file],
7059
+ indexDir: opts.indexDir
7060
+ })
7061
+ ).catch((err) => opts.onError?.(err));
7062
+ }, ms);
7063
+ timer.unref?.();
7064
+ debounceTimers.set(key, timer);
7065
+ }
7066
+ }
7067
+ function cancelPendingReindexes() {
7068
+ for (const t of debounceTimers.values()) clearTimeout(t);
7069
+ debounceTimers.clear();
7070
+ }
7071
+
6952
7072
  // src/codebase-index/indexer.ts
7073
+ var YIELD_EVERY_N = 50;
7074
+ function yieldEventLoop() {
7075
+ return new Promise((resolve7) => setImmediate(resolve7));
7076
+ }
6953
7077
  var DEFAULT_IGNORE5 = [
6954
7078
  "node_modules",
6955
7079
  ".git",
@@ -7053,7 +7177,12 @@ async function runIndexer(_ctx, opts) {
7053
7177
  if (!force) {
7054
7178
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
7055
7179
  }
7056
- for (const file of files) {
7180
+ for (let fi = 0; fi < files.length; fi++) {
7181
+ const file = files[fi];
7182
+ _setIndexProgress(fi + 1, files.length);
7183
+ if (fi > 0 && fi % YIELD_EVERY_N === 0) {
7184
+ await yieldEventLoop();
7185
+ }
7057
7186
  let stat10;
7058
7187
  try {
7059
7188
  stat10 = await fs4.stat(file);
@@ -7167,12 +7296,23 @@ var codebaseIndexTool = {
7167
7296
  }
7168
7297
  },
7169
7298
  async execute(input, ctx) {
7299
+ if (isIndexing()) {
7300
+ return {
7301
+ filesIndexed: 0,
7302
+ symbolsIndexed: 0,
7303
+ langStats: {},
7304
+ durationMs: 0,
7305
+ errors: [],
7306
+ note: "A full index is already in progress. Retry codebase-index after it completes (check codebase-stats)."
7307
+ };
7308
+ }
7170
7309
  const result = await runIndexer(ctx, {
7171
7310
  projectRoot: ctx.projectRoot,
7172
7311
  force: input.force ?? false,
7173
7312
  langs: input.langs,
7174
7313
  indexDir: codebaseIndexDirOverride(ctx)
7175
7314
  });
7315
+ setIndexReady();
7176
7316
  return result;
7177
7317
  }
7178
7318
  };
@@ -7308,6 +7448,31 @@ var codebaseSearchTool = {
7308
7448
  required: ["query"]
7309
7449
  },
7310
7450
  async execute(input, ctx) {
7451
+ const state = getIndexState();
7452
+ if (!state.ready) {
7453
+ return {
7454
+ results: [],
7455
+ total: 0,
7456
+ query: input.query,
7457
+ 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."
7458
+ };
7459
+ }
7460
+ if (state.indexing) {
7461
+ return {
7462
+ results: [],
7463
+ total: 0,
7464
+ query: input.query,
7465
+ indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files). Results may be incomplete.`
7466
+ };
7467
+ }
7468
+ if (state.lastError) {
7469
+ return {
7470
+ results: [],
7471
+ total: 0,
7472
+ query: input.query,
7473
+ indexStatus: `Index build failed: ${state.lastError}. Try /codebase-reindex.`
7474
+ };
7475
+ }
7311
7476
  const store = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
7312
7477
  try {
7313
7478
  const limit = Math.min(input.limit ?? 20, 100);
@@ -7365,6 +7530,32 @@ var codebaseStatsTool = {
7365
7530
  additionalProperties: false
7366
7531
  },
7367
7532
  async execute(_input, ctx) {
7533
+ const idxState = getIndexState();
7534
+ if (!idxState.ready) {
7535
+ return {
7536
+ totalSymbols: 0,
7537
+ totalFiles: 0,
7538
+ byLang: {},
7539
+ byKind: {},
7540
+ lastIndexed: null,
7541
+ sizeBytes: 0,
7542
+ indexPath: "",
7543
+ version: SCHEMA_VERSION,
7544
+ indexStatus: idxState.indexing ? `Indexing in progress (${idxState.currentFile}/${idxState.totalFiles} files).` : "Index not yet built."
7545
+ };
7546
+ }
7547
+ if (idxState.indexing) {
7548
+ const store2 = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
7549
+ try {
7550
+ const stats = store2.getStats();
7551
+ return {
7552
+ ...stats,
7553
+ indexStatus: `Index refresh in progress (${idxState.currentFile}/${idxState.totalFiles} files). Stats may be incomplete.`
7554
+ };
7555
+ } finally {
7556
+ store2.close();
7557
+ }
7558
+ }
7368
7559
  const store = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
7369
7560
  try {
7370
7561
  const stats = store.getStats();
@@ -7384,70 +7575,6 @@ var codebaseStatsTool = {
7384
7575
  }
7385
7576
  };
7386
7577
 
7387
- // src/codebase-index/background-indexer.ts
7388
- function stubCtx(projectRoot) {
7389
- return {
7390
- projectRoot,
7391
- cwd: projectRoot,
7392
- messages: [],
7393
- todos: [],
7394
- readFiles: /* @__PURE__ */ new Set(),
7395
- fileMtimes: /* @__PURE__ */ new Map()
7396
- };
7397
- }
7398
- var chain = Promise.resolve();
7399
- function withMutex(job) {
7400
- const run = chain.then(job, job);
7401
- chain = run.then(
7402
- () => void 0,
7403
- () => void 0
7404
- );
7405
- return run;
7406
- }
7407
- var DEFAULT_DEBOUNCE_MS = 400;
7408
- var debounceTimers = /* @__PURE__ */ new Map();
7409
- function debounceKey(indexDir, file) {
7410
- return `${indexDir ?? ""}|${file}`;
7411
- }
7412
- function isIndexableFile(filePath) {
7413
- return detectLang(filePath) !== null;
7414
- }
7415
- function runStartupIndex(opts) {
7416
- return withMutex(
7417
- () => runIndexer(stubCtx(opts.projectRoot), {
7418
- projectRoot: opts.projectRoot,
7419
- indexDir: opts.indexDir,
7420
- force: opts.force
7421
- })
7422
- );
7423
- }
7424
- function enqueueReindex(opts) {
7425
- const files = opts.files.filter(isIndexableFile);
7426
- if (files.length === 0) return;
7427
- const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
7428
- for (const file of files) {
7429
- const key = debounceKey(opts.indexDir, file);
7430
- const existing = debounceTimers.get(key);
7431
- if (existing) clearTimeout(existing);
7432
- const timer = setTimeout(() => {
7433
- debounceTimers.delete(key);
7434
- void withMutex(
7435
- () => runIndexer(stubCtx(opts.projectRoot), {
7436
- projectRoot: opts.projectRoot,
7437
- files: [file],
7438
- indexDir: opts.indexDir
7439
- })
7440
- ).catch((err) => opts.onError?.(err));
7441
- }, ms);
7442
- timer.unref?.();
7443
- debounceTimers.set(key, timer);
7444
- }
7445
- }
7446
- function cancelPendingReindexes() {
7447
- for (const t of debounceTimers.values()) clearTimeout(t);
7448
- debounceTimers.clear();
7449
- }
7450
-
7451
7578
  // src/builtin.ts
7452
7579
  var builtinTools = [
7453
7580
  readTool,
@@ -7493,6 +7620,6 @@ var builtinToolsPack = {
7493
7620
  tools: builtinTools
7494
7621
  };
7495
7622
 
7496
- export { CircuitBreaker, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, createModeTool, diffTool, documentTool, editTool, enqueueReindex, execTool, fetchTool, forgetTool, formatTool, getProcessRegistry, gitTool, globTool, grepTool, installTool, isIndexableFile, jsonTool, lintTool, logsTool, outdatedTool, patchTool, planTool, readTool, rememberTool, replaceTool, runStartupIndex, scaffoldTool, searchTool, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
7623
+ export { CircuitBreaker, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, createModeTool, diffTool, documentTool, editTool, enqueueReindex, execTool, fetchTool, forgetTool, formatTool, getIndexState, getProcessRegistry, gitTool, globTool, grepTool, installTool, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, logsTool, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, rememberTool, replaceTool, runStartupIndex, scaffoldTool, searchTool, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
7497
7624
  //# sourceMappingURL=index.js.map
7498
7625
  //# sourceMappingURL=index.js.map