@wrongstack/tools 0.298.2 → 0.299.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.js CHANGED
@@ -7440,6 +7440,10 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
7440
7440
  const start = Date.now();
7441
7441
  const pidStr = String(process.pid);
7442
7442
  const hostStr = os2.hostname();
7443
+ try {
7444
+ await fs6.mkdir(path9.dirname(lockfilePath), { recursive: true });
7445
+ } catch {
7446
+ }
7443
7447
  while (Date.now() - start < timeoutMs) {
7444
7448
  try {
7445
7449
  await fs6.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
@@ -8111,6 +8115,16 @@ function parseKillCommand(command) {
8111
8115
  if (pgrepMatch) {
8112
8116
  return null;
8113
8117
  }
8118
+ const posixScriptMatch = normalized.match(SCRIPT_KILL_RE_POSIX);
8119
+ if (posixScriptMatch) {
8120
+ return {
8121
+ name: "kill-script",
8122
+ signal: "FORCE",
8123
+ isGroupKill: false,
8124
+ isAllKill: false,
8125
+ originalCommand: command
8126
+ };
8127
+ }
8114
8128
  return null;
8115
8129
  }
8116
8130
  async function getProtectedEntries() {
@@ -10908,6 +10922,136 @@ function mapWriterRefRow(row) {
10908
10922
  }
10909
10923
 
10910
10924
  // src/codebase-index/writer-graph-reader.ts
10925
+ var MAX_SQL_VARS = 900;
10926
+ function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
10927
+ const results = [];
10928
+ for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
10929
+ const chunk = ids.slice(start, start + MAX_SQL_VARS);
10930
+ const placeholders = chunk.map(() => "?").join(",");
10931
+ const sql = buildSql(placeholders);
10932
+ results.push(...stmt(sql).all(...chunk, ...extraArgs));
10933
+ }
10934
+ return results;
10935
+ }
10936
+ function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
10937
+ let total = 0;
10938
+ for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
10939
+ const chunk = ids.slice(start, start + MAX_SQL_VARS);
10940
+ const placeholders = chunk.map(() => "?").join(",");
10941
+ const sql = buildSql(placeholders);
10942
+ const rows = stmt(sql).all(...chunk, ...extraArgs);
10943
+ total += rows[0]?.n ?? 0;
10944
+ }
10945
+ return total;
10946
+ }
10947
+ function mapCallSiteRow(row) {
10948
+ return {
10949
+ symbol: {
10950
+ id: row.sym_id,
10951
+ name: row.sym_name,
10952
+ kind: row.sym_kind,
10953
+ lang: row.sym_lang,
10954
+ file: row.sym_file,
10955
+ line: row.sym_line,
10956
+ signature: row.sym_signature
10957
+ },
10958
+ callType: row.call_type,
10959
+ line: row.ref_line
10960
+ };
10961
+ }
10962
+ function resolveSymbolIds(stmt, symbolName, file) {
10963
+ const baseSql = file ? `SELECT id FROM symbols WHERE name = ? AND file = ? ORDER BY id` : `SELECT id FROM symbols WHERE name = ? ORDER BY id`;
10964
+ const args = file ? [symbolName, file] : [symbolName];
10965
+ const rows = stmt(baseSql).all(...args);
10966
+ return rows.map((r) => r.id);
10967
+ }
10968
+ function findIncomingCallsByName(stmt, symbolName, file, limit) {
10969
+ const targetIds = resolveSymbolIds(stmt, symbolName, file);
10970
+ if (targetIds.length === 0) return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
10971
+ let matchIds = targetIds;
10972
+ let ambiguous = false;
10973
+ if (file !== void 0) {
10974
+ const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
10975
+ if (allNamedIds.length > targetIds.length) {
10976
+ matchIds = allNamedIds;
10977
+ ambiguous = true;
10978
+ }
10979
+ }
10980
+ const useFallback = !file;
10981
+ const rows = chunkedIdQuery(
10982
+ stmt,
10983
+ matchIds,
10984
+ (ph) => `SELECT
10985
+ s.id AS sym_id,
10986
+ s.name AS sym_name,
10987
+ s.kind AS sym_kind,
10988
+ s.lang AS sym_lang,
10989
+ s.file AS sym_file,
10990
+ s.line AS sym_line,
10991
+ s.signature AS sym_signature,
10992
+ r.call_type,
10993
+ r.line AS ref_line
10994
+ FROM refs r
10995
+ JOIN symbols s ON s.id = r.from_id
10996
+ WHERE r.to_id IN (${ph})
10997
+ ORDER BY r.line, r.id`,
10998
+ []
10999
+ );
11000
+ if (useFallback) {
11001
+ const fallbackRows = stmt(
11002
+ `SELECT
11003
+ s.id AS sym_id,
11004
+ s.name AS sym_name,
11005
+ s.kind AS sym_kind,
11006
+ s.lang AS sym_lang,
11007
+ s.file AS sym_file,
11008
+ s.line AS sym_line,
11009
+ s.signature AS sym_signature,
11010
+ r.call_type,
11011
+ r.line AS ref_line
11012
+ FROM refs r
11013
+ JOIN symbols s ON s.id = r.from_id
11014
+ WHERE r.to_id IS NULL AND r.to_name = ?
11015
+ ORDER BY r.line, r.id`
11016
+ ).all(symbolName);
11017
+ rows.push(...fallbackRows);
11018
+ }
11019
+ rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
11020
+ const allCalls = rows.map(mapCallSiteRow);
11021
+ return { calls: allCalls.slice(0, limit), symbolFound: true, ambiguous, totalMatches: allCalls.length };
11022
+ }
11023
+ function findOutgoingCallsByName(stmt, symbolName, file, limit) {
11024
+ const sourceIds = resolveSymbolIds(stmt, symbolName, file);
11025
+ if (sourceIds.length === 0) return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
11026
+ const unresolvedCount = chunkedIdScalar(
11027
+ stmt,
11028
+ sourceIds,
11029
+ (ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
11030
+ );
11031
+ const rows = chunkedIdQuery(
11032
+ stmt,
11033
+ sourceIds,
11034
+ (ph) => `SELECT
11035
+ s.id AS sym_id,
11036
+ s.name AS sym_name,
11037
+ s.kind AS sym_kind,
11038
+ s.lang AS sym_lang,
11039
+ s.file AS sym_file,
11040
+ s.line AS sym_line,
11041
+ s.signature AS sym_signature,
11042
+ r.call_type,
11043
+ r.line AS ref_line
11044
+ FROM refs r
11045
+ JOIN symbols s ON s.id = r.to_id
11046
+ WHERE r.from_id IN (${ph})
11047
+ AND r.to_id IS NOT NULL -- INNER JOIN already excludes NULL to_id; this is defensive belt-and-suspenders
11048
+ ORDER BY r.line, r.id`,
11049
+ []
11050
+ );
11051
+ rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
11052
+ const calls = rows.map(mapCallSiteRow).slice(0, limit);
11053
+ return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
11054
+ }
10911
11055
  function findRefsToWithStatement(stmt, symbolId) {
10912
11056
  return stmt(
10913
11057
  "SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE to_id = ? OR to_name = (SELECT name FROM symbols WHERE id = ?)"
@@ -12143,6 +12287,20 @@ var IndexStore = class _IndexStore {
12143
12287
  return false;
12144
12288
  }
12145
12289
  }
12290
+ /**
12291
+ * Find all symbols that reference the named target symbol (incoming callers).
12292
+ * Accepts a name instead of an id so the agent doesn't need a prior lookup.
12293
+ */
12294
+ findIncomingCallsByName(symbolName, file, limit = 100) {
12295
+ return findIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
12296
+ }
12297
+ /**
12298
+ * Find all symbols that the named source symbol references (outgoing callees).
12299
+ * Accepts a name instead of an id so the agent doesn't need a prior lookup.
12300
+ */
12301
+ findOutgoingCallsByName(symbolName, file, limit = 100) {
12302
+ return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
12303
+ }
12146
12304
  /**
12147
12305
  * Find all references TO a given symbol (who calls / uses this symbol?).
12148
12306
  */
@@ -13559,6 +13717,22 @@ function symbolGraphService(args) {
13559
13717
  indexStorePool.release(store);
13560
13718
  }
13561
13719
  }
13720
+ function incomingCallsService(args) {
13721
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13722
+ try {
13723
+ return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
13724
+ } finally {
13725
+ indexStorePool.release(store);
13726
+ }
13727
+ }
13728
+ function outgoingCallsService(args) {
13729
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13730
+ try {
13731
+ return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
13732
+ } finally {
13733
+ indexStorePool.release(store);
13734
+ }
13735
+ }
13562
13736
 
13563
13737
  // src/codebase-index/background-indexer.ts
13564
13738
  init_languages2();
@@ -13796,6 +13970,10 @@ async function callInline(op, args, opts) {
13796
13970
  return fileGraphService(args);
13797
13971
  case "symbolGraph":
13798
13972
  return symbolGraphService(args);
13973
+ case "incomingCalls":
13974
+ return incomingCallsService(args);
13975
+ case "outgoingCalls":
13976
+ return outgoingCallsService(args);
13799
13977
  default:
13800
13978
  throw new Error(`unknown index op: ${String(op)}`);
13801
13979
  }
@@ -13989,6 +14167,12 @@ async function fileGraphService2(args) {
13989
14167
  async function symbolGraphService2(args) {
13990
14168
  return callIndexOp("symbolGraph", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
13991
14169
  }
14170
+ async function incomingCallsService2(args) {
14171
+ return callIndexOp("incomingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
14172
+ }
14173
+ async function outgoingCallsService2(args) {
14174
+ return callIndexOp("outgoingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
14175
+ }
13992
14176
  function shutdownCodebaseIndexServer(projectRoot, indexDir, reason) {
13993
14177
  return shutdownProjectIndexServer(projectRoot, indexDir, reason);
13994
14178
  }
@@ -14072,6 +14256,214 @@ var codebaseIndexTool = {
14072
14256
  }
14073
14257
  };
14074
14258
 
14259
+ // src/codebase-index/codebase-incoming-calls-tool.ts
14260
+ var codebaseIncomingCallsTool = {
14261
+ name: "codebase-incoming-calls",
14262
+ category: "Project",
14263
+ icon: "index",
14264
+ description: "Find all callers of a function, method, or symbol \u2014 who invokes or references it. Uses the codebase index ref graph for instant, exact results. Always use this instead of grep when checking impact of a change.",
14265
+ usageHint: 'CALL THIS BEFORE REFACTORING OR CHANGING ANY FUNCTION:\n\n- NEVER use grep or manual line reading to check where a function is called.\n- ALWAYS call codebase-incoming-calls({ symbol: "funcName" }) first.\n- Returns exact files, line numbers, caller signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Combine with codebase-outgoing-calls to see what the symbol itself calls.\nIf the index is not built, run codebase-index first.',
14266
+ permission: "auto",
14267
+ mutating: false,
14268
+ capabilities: ["fs.read"],
14269
+ timeoutMs: 35e3,
14270
+ inputSchema: {
14271
+ type: "object",
14272
+ properties: {
14273
+ symbol: {
14274
+ type: "string",
14275
+ description: "The function/method/type name to find callers for"
14276
+ },
14277
+ file: {
14278
+ type: "string",
14279
+ description: "Scope to a specific file when multiple symbols share the same name"
14280
+ },
14281
+ limit: {
14282
+ type: "integer",
14283
+ description: "Maximum call sites to return (default 50, max 200)",
14284
+ minimum: 1,
14285
+ maximum: 200
14286
+ }
14287
+ },
14288
+ required: ["symbol"]
14289
+ },
14290
+ async execute(input, ctx) {
14291
+ const state = getIndexState();
14292
+ if (state.indexing && !state.ready) {
14293
+ return {
14294
+ symbol: input.symbol,
14295
+ calls: [],
14296
+ total: 0,
14297
+ indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
14298
+ };
14299
+ }
14300
+ if (state.lastError) {
14301
+ const circuit = state.circuit;
14302
+ const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
14303
+ return {
14304
+ symbol: input.symbol,
14305
+ calls: [],
14306
+ total: 0,
14307
+ indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
14308
+ };
14309
+ }
14310
+ const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
14311
+ const { calls, symbolFound, ambiguous, totalMatches } = await incomingCallsService2(
14312
+ {
14313
+ projectRoot: ctx.projectRoot,
14314
+ indexDir: codebaseIndexDirOverride(ctx),
14315
+ symbol: input.symbol,
14316
+ file: input.file,
14317
+ limit
14318
+ }
14319
+ );
14320
+ if (!symbolFound) {
14321
+ let hasPersistedIndex = state.ready;
14322
+ if (!hasPersistedIndex) {
14323
+ try {
14324
+ const stats = await codebaseIndexStats({
14325
+ projectRoot: ctx.projectRoot,
14326
+ indexDir: codebaseIndexDirOverride(ctx)
14327
+ });
14328
+ hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
14329
+ } catch {
14330
+ }
14331
+ }
14332
+ if (!hasPersistedIndex) {
14333
+ return {
14334
+ symbol: input.symbol,
14335
+ calls: [],
14336
+ total: 0,
14337
+ indexStatus: "No persisted index data found. Run codebase-index to build it."
14338
+ };
14339
+ }
14340
+ return {
14341
+ symbol: input.symbol,
14342
+ calls: [],
14343
+ total: 0,
14344
+ note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
14345
+ };
14346
+ }
14347
+ const notes = [];
14348
+ if (totalMatches > limit) {
14349
+ notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
14350
+ }
14351
+ if (ambiguous) {
14352
+ notes.push(`Symbol "${input.symbol}" exists in multiple files. Results include callers of all same-named symbols. Use codebase-search to find the exact file and pass it as \`file\`.`);
14353
+ }
14354
+ return {
14355
+ symbol: input.symbol,
14356
+ calls,
14357
+ total: calls.length,
14358
+ ...notes.length > 0 ? { note: notes.join(" ") } : {}
14359
+ };
14360
+ }
14361
+ };
14362
+
14363
+ // src/codebase-index/codebase-outgoing-calls-tool.ts
14364
+ var codebaseOutgoingCallsTool = {
14365
+ name: "codebase-outgoing-calls",
14366
+ category: "Project",
14367
+ icon: "index",
14368
+ description: "Find all functions/methods/symbols that a given symbol calls or depends on \u2014 its callees. Uses the codebase index ref graph for instant, exact results. Use this to understand a function's dependencies before modifying it.",
14369
+ usageHint: 'USE THIS TO UNDERSTAND A FUNCTION\'S DEPENDENCIES:\n\n- Call codebase-outgoing-calls({ symbol: "funcName" }) to see everything it calls.\n- Returns exact files, line numbers, callee signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Pair with codebase-incoming-calls for a complete impact picture: incoming = who calls you, outgoing = what you call.\nIf the index is not built, run codebase-index first.',
14370
+ permission: "auto",
14371
+ mutating: false,
14372
+ capabilities: ["fs.read"],
14373
+ timeoutMs: 35e3,
14374
+ inputSchema: {
14375
+ type: "object",
14376
+ properties: {
14377
+ symbol: {
14378
+ type: "string",
14379
+ description: "The function/method/type name to find callees for"
14380
+ },
14381
+ file: {
14382
+ type: "string",
14383
+ description: "Scope to a specific file when multiple symbols share the same name"
14384
+ },
14385
+ limit: {
14386
+ type: "integer",
14387
+ description: "Maximum call sites to return (default 50, max 200)",
14388
+ minimum: 1,
14389
+ maximum: 200
14390
+ }
14391
+ },
14392
+ required: ["symbol"]
14393
+ },
14394
+ async execute(input, ctx) {
14395
+ const state = getIndexState();
14396
+ if (state.indexing && !state.ready) {
14397
+ return {
14398
+ symbol: input.symbol,
14399
+ calls: [],
14400
+ total: 0,
14401
+ indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
14402
+ };
14403
+ }
14404
+ if (state.lastError) {
14405
+ const circuit = state.circuit;
14406
+ const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
14407
+ return {
14408
+ symbol: input.symbol,
14409
+ calls: [],
14410
+ total: 0,
14411
+ indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
14412
+ };
14413
+ }
14414
+ const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
14415
+ const { calls, symbolFound, unresolvedCount, totalMatches } = await outgoingCallsService2(
14416
+ {
14417
+ projectRoot: ctx.projectRoot,
14418
+ indexDir: codebaseIndexDirOverride(ctx),
14419
+ symbol: input.symbol,
14420
+ file: input.file,
14421
+ limit
14422
+ }
14423
+ );
14424
+ if (!symbolFound) {
14425
+ let hasPersistedIndex = state.ready;
14426
+ if (!hasPersistedIndex) {
14427
+ try {
14428
+ const stats = await codebaseIndexStats({
14429
+ projectRoot: ctx.projectRoot,
14430
+ indexDir: codebaseIndexDirOverride(ctx)
14431
+ });
14432
+ hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
14433
+ } catch {
14434
+ }
14435
+ }
14436
+ if (!hasPersistedIndex) {
14437
+ return {
14438
+ symbol: input.symbol,
14439
+ calls: [],
14440
+ total: 0,
14441
+ indexStatus: "No persisted index data found. Run codebase-index to build it."
14442
+ };
14443
+ }
14444
+ return {
14445
+ symbol: input.symbol,
14446
+ calls: [],
14447
+ total: 0,
14448
+ note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
14449
+ };
14450
+ }
14451
+ const notes = [];
14452
+ if (totalMatches > limit) {
14453
+ notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
14454
+ }
14455
+ if (unresolvedCount > 0) {
14456
+ notes.push(`${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`);
14457
+ }
14458
+ return {
14459
+ symbol: input.symbol,
14460
+ calls,
14461
+ total: calls.length,
14462
+ ...notes.length > 0 ? { note: notes.join(" ") } : {}
14463
+ };
14464
+ }
14465
+ };
14466
+
14075
14467
  // src/codebase-index/codebase-search-tool.ts
14076
14468
  var codebaseSearchTool = {
14077
14469
  name: "codebase-search",
@@ -25561,6 +25953,8 @@ var TIER1_TOOLS = [
25561
25953
  editTool,
25562
25954
  codebaseStatsTool,
25563
25955
  codebaseSearchTool,
25956
+ codebaseIncomingCallsTool,
25957
+ codebaseOutgoingCallsTool,
25564
25958
  codebaseIndexTool,
25565
25959
  bashTool,
25566
25960
  grepTool,
@@ -25611,6 +26005,8 @@ var builtinTools = [
25611
26005
  editTool,
25612
26006
  codebaseStatsTool,
25613
26007
  codebaseSearchTool,
26008
+ codebaseIncomingCallsTool,
26009
+ codebaseOutgoingCallsTool,
25614
26010
  codebaseIndexTool,
25615
26011
  deadCodeScanTool,
25616
26012
  replaceTool,