@wrongstack/tools 0.298.3 → 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/bash.js +14 -0
- package/dist/builtin.js +396 -0
- package/dist/codebase-index/background-indexer.d.ts +6 -1
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +34 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +34 -0
- package/dist/codebase-index/index-service.d.ts +24 -2
- package/dist/codebase-index/index.d.ts +4 -2
- package/dist/codebase-index/index.js +382 -0
- package/dist/codebase-index/project-server.js +177 -1
- package/dist/codebase-index/schema.d.ts +23 -0
- package/dist/codebase-index/worker-protocol.d.ts +14 -0
- package/dist/codebase-index/worker.js +164 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +24 -1
- package/dist/codebase-index/writer.d.ts +21 -1
- package/dist/exec.js +4 -0
- package/dist/index.js +396 -0
- package/dist/pack.js +394 -0
- package/dist/ps-slash.js +4 -0
- package/dist/read.js +164 -0
- package/dist/tool-tier.js +396 -0
- package/package.json +3 -3
|
@@ -2602,6 +2602,136 @@ function mapWriterRefRow(row) {
|
|
|
2602
2602
|
}
|
|
2603
2603
|
|
|
2604
2604
|
// src/codebase-index/writer-graph-reader.ts
|
|
2605
|
+
var MAX_SQL_VARS = 900;
|
|
2606
|
+
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
2607
|
+
const results = [];
|
|
2608
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
2609
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
2610
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
2611
|
+
const sql = buildSql(placeholders);
|
|
2612
|
+
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
2613
|
+
}
|
|
2614
|
+
return results;
|
|
2615
|
+
}
|
|
2616
|
+
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
2617
|
+
let total = 0;
|
|
2618
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
2619
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
2620
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
2621
|
+
const sql = buildSql(placeholders);
|
|
2622
|
+
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
2623
|
+
total += rows[0]?.n ?? 0;
|
|
2624
|
+
}
|
|
2625
|
+
return total;
|
|
2626
|
+
}
|
|
2627
|
+
function mapCallSiteRow(row) {
|
|
2628
|
+
return {
|
|
2629
|
+
symbol: {
|
|
2630
|
+
id: row.sym_id,
|
|
2631
|
+
name: row.sym_name,
|
|
2632
|
+
kind: row.sym_kind,
|
|
2633
|
+
lang: row.sym_lang,
|
|
2634
|
+
file: row.sym_file,
|
|
2635
|
+
line: row.sym_line,
|
|
2636
|
+
signature: row.sym_signature
|
|
2637
|
+
},
|
|
2638
|
+
callType: row.call_type,
|
|
2639
|
+
line: row.ref_line
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
function resolveSymbolIds(stmt, symbolName, file) {
|
|
2643
|
+
const baseSql = file ? `SELECT id FROM symbols WHERE name = ? AND file = ? ORDER BY id` : `SELECT id FROM symbols WHERE name = ? ORDER BY id`;
|
|
2644
|
+
const args = file ? [symbolName, file] : [symbolName];
|
|
2645
|
+
const rows = stmt(baseSql).all(...args);
|
|
2646
|
+
return rows.map((r) => r.id);
|
|
2647
|
+
}
|
|
2648
|
+
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
2649
|
+
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
2650
|
+
if (targetIds.length === 0) return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
|
|
2651
|
+
let matchIds = targetIds;
|
|
2652
|
+
let ambiguous = false;
|
|
2653
|
+
if (file !== void 0) {
|
|
2654
|
+
const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
|
|
2655
|
+
if (allNamedIds.length > targetIds.length) {
|
|
2656
|
+
matchIds = allNamedIds;
|
|
2657
|
+
ambiguous = true;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
const useFallback = !file;
|
|
2661
|
+
const rows = chunkedIdQuery(
|
|
2662
|
+
stmt,
|
|
2663
|
+
matchIds,
|
|
2664
|
+
(ph) => `SELECT
|
|
2665
|
+
s.id AS sym_id,
|
|
2666
|
+
s.name AS sym_name,
|
|
2667
|
+
s.kind AS sym_kind,
|
|
2668
|
+
s.lang AS sym_lang,
|
|
2669
|
+
s.file AS sym_file,
|
|
2670
|
+
s.line AS sym_line,
|
|
2671
|
+
s.signature AS sym_signature,
|
|
2672
|
+
r.call_type,
|
|
2673
|
+
r.line AS ref_line
|
|
2674
|
+
FROM refs r
|
|
2675
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2676
|
+
WHERE r.to_id IN (${ph})
|
|
2677
|
+
ORDER BY r.line, r.id`,
|
|
2678
|
+
[]
|
|
2679
|
+
);
|
|
2680
|
+
if (useFallback) {
|
|
2681
|
+
const fallbackRows = stmt(
|
|
2682
|
+
`SELECT
|
|
2683
|
+
s.id AS sym_id,
|
|
2684
|
+
s.name AS sym_name,
|
|
2685
|
+
s.kind AS sym_kind,
|
|
2686
|
+
s.lang AS sym_lang,
|
|
2687
|
+
s.file AS sym_file,
|
|
2688
|
+
s.line AS sym_line,
|
|
2689
|
+
s.signature AS sym_signature,
|
|
2690
|
+
r.call_type,
|
|
2691
|
+
r.line AS ref_line
|
|
2692
|
+
FROM refs r
|
|
2693
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2694
|
+
WHERE r.to_id IS NULL AND r.to_name = ?
|
|
2695
|
+
ORDER BY r.line, r.id`
|
|
2696
|
+
).all(symbolName);
|
|
2697
|
+
rows.push(...fallbackRows);
|
|
2698
|
+
}
|
|
2699
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
2700
|
+
const allCalls = rows.map(mapCallSiteRow);
|
|
2701
|
+
return { calls: allCalls.slice(0, limit), symbolFound: true, ambiguous, totalMatches: allCalls.length };
|
|
2702
|
+
}
|
|
2703
|
+
function findOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
2704
|
+
const sourceIds = resolveSymbolIds(stmt, symbolName, file);
|
|
2705
|
+
if (sourceIds.length === 0) return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
|
|
2706
|
+
const unresolvedCount = chunkedIdScalar(
|
|
2707
|
+
stmt,
|
|
2708
|
+
sourceIds,
|
|
2709
|
+
(ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
|
|
2710
|
+
);
|
|
2711
|
+
const rows = chunkedIdQuery(
|
|
2712
|
+
stmt,
|
|
2713
|
+
sourceIds,
|
|
2714
|
+
(ph) => `SELECT
|
|
2715
|
+
s.id AS sym_id,
|
|
2716
|
+
s.name AS sym_name,
|
|
2717
|
+
s.kind AS sym_kind,
|
|
2718
|
+
s.lang AS sym_lang,
|
|
2719
|
+
s.file AS sym_file,
|
|
2720
|
+
s.line AS sym_line,
|
|
2721
|
+
s.signature AS sym_signature,
|
|
2722
|
+
r.call_type,
|
|
2723
|
+
r.line AS ref_line
|
|
2724
|
+
FROM refs r
|
|
2725
|
+
JOIN symbols s ON s.id = r.to_id
|
|
2726
|
+
WHERE r.from_id IN (${ph})
|
|
2727
|
+
AND r.to_id IS NOT NULL -- INNER JOIN already excludes NULL to_id; this is defensive belt-and-suspenders
|
|
2728
|
+
ORDER BY r.line, r.id`,
|
|
2729
|
+
[]
|
|
2730
|
+
);
|
|
2731
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
2732
|
+
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
2733
|
+
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
2734
|
+
}
|
|
2605
2735
|
function findRefsToWithStatement(stmt, symbolId) {
|
|
2606
2736
|
return stmt(
|
|
2607
2737
|
"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 = ?)"
|
|
@@ -3837,6 +3967,20 @@ var IndexStore = class _IndexStore {
|
|
|
3837
3967
|
return false;
|
|
3838
3968
|
}
|
|
3839
3969
|
}
|
|
3970
|
+
/**
|
|
3971
|
+
* Find all symbols that reference the named target symbol (incoming callers).
|
|
3972
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
3973
|
+
*/
|
|
3974
|
+
findIncomingCallsByName(symbolName, file, limit = 100) {
|
|
3975
|
+
return findIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
3976
|
+
}
|
|
3977
|
+
/**
|
|
3978
|
+
* Find all symbols that the named source symbol references (outgoing callees).
|
|
3979
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
3980
|
+
*/
|
|
3981
|
+
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
3982
|
+
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
3983
|
+
}
|
|
3840
3984
|
/**
|
|
3841
3985
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
3842
3986
|
*/
|
|
@@ -5264,6 +5408,22 @@ function symbolGraphService(args) {
|
|
|
5264
5408
|
indexStorePool.release(store);
|
|
5265
5409
|
}
|
|
5266
5410
|
}
|
|
5411
|
+
function incomingCallsService(args) {
|
|
5412
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
5413
|
+
try {
|
|
5414
|
+
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
5415
|
+
} finally {
|
|
5416
|
+
indexStorePool.release(store);
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
function outgoingCallsService(args) {
|
|
5420
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
5421
|
+
try {
|
|
5422
|
+
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
5423
|
+
} finally {
|
|
5424
|
+
indexStorePool.release(store);
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5267
5427
|
|
|
5268
5428
|
// src/codebase-index/background-indexer.ts
|
|
5269
5429
|
init_languages();
|
|
@@ -5501,6 +5661,10 @@ async function callInline(op, args, opts) {
|
|
|
5501
5661
|
return fileGraphService(args);
|
|
5502
5662
|
case "symbolGraph":
|
|
5503
5663
|
return symbolGraphService(args);
|
|
5664
|
+
case "incomingCalls":
|
|
5665
|
+
return incomingCallsService(args);
|
|
5666
|
+
case "outgoingCalls":
|
|
5667
|
+
return outgoingCallsService(args);
|
|
5504
5668
|
default:
|
|
5505
5669
|
throw new Error(`unknown index op: ${String(op)}`);
|
|
5506
5670
|
}
|
|
@@ -5694,6 +5858,12 @@ async function fileGraphService2(args) {
|
|
|
5694
5858
|
async function symbolGraphService2(args) {
|
|
5695
5859
|
return callIndexOp("symbolGraph", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
5696
5860
|
}
|
|
5861
|
+
async function incomingCallsService2(args) {
|
|
5862
|
+
return callIndexOp("incomingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
5863
|
+
}
|
|
5864
|
+
async function outgoingCallsService2(args) {
|
|
5865
|
+
return callIndexOp("outgoingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
5866
|
+
}
|
|
5697
5867
|
function shutdownCodebaseIndexServer(projectRoot, indexDir, reason) {
|
|
5698
5868
|
return shutdownProjectIndexServer(projectRoot, indexDir, reason);
|
|
5699
5869
|
}
|
|
@@ -5777,6 +5947,214 @@ var codebaseIndexTool = {
|
|
|
5777
5947
|
}
|
|
5778
5948
|
};
|
|
5779
5949
|
|
|
5950
|
+
// src/codebase-index/codebase-incoming-calls-tool.ts
|
|
5951
|
+
var codebaseIncomingCallsTool = {
|
|
5952
|
+
name: "codebase-incoming-calls",
|
|
5953
|
+
category: "Project",
|
|
5954
|
+
icon: "index",
|
|
5955
|
+
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.",
|
|
5956
|
+
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.',
|
|
5957
|
+
permission: "auto",
|
|
5958
|
+
mutating: false,
|
|
5959
|
+
capabilities: ["fs.read"],
|
|
5960
|
+
timeoutMs: 35e3,
|
|
5961
|
+
inputSchema: {
|
|
5962
|
+
type: "object",
|
|
5963
|
+
properties: {
|
|
5964
|
+
symbol: {
|
|
5965
|
+
type: "string",
|
|
5966
|
+
description: "The function/method/type name to find callers for"
|
|
5967
|
+
},
|
|
5968
|
+
file: {
|
|
5969
|
+
type: "string",
|
|
5970
|
+
description: "Scope to a specific file when multiple symbols share the same name"
|
|
5971
|
+
},
|
|
5972
|
+
limit: {
|
|
5973
|
+
type: "integer",
|
|
5974
|
+
description: "Maximum call sites to return (default 50, max 200)",
|
|
5975
|
+
minimum: 1,
|
|
5976
|
+
maximum: 200
|
|
5977
|
+
}
|
|
5978
|
+
},
|
|
5979
|
+
required: ["symbol"]
|
|
5980
|
+
},
|
|
5981
|
+
async execute(input, ctx) {
|
|
5982
|
+
const state = getIndexState();
|
|
5983
|
+
if (state.indexing && !state.ready) {
|
|
5984
|
+
return {
|
|
5985
|
+
symbol: input.symbol,
|
|
5986
|
+
calls: [],
|
|
5987
|
+
total: 0,
|
|
5988
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
5989
|
+
};
|
|
5990
|
+
}
|
|
5991
|
+
if (state.lastError) {
|
|
5992
|
+
const circuit = state.circuit;
|
|
5993
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
|
|
5994
|
+
return {
|
|
5995
|
+
symbol: input.symbol,
|
|
5996
|
+
calls: [],
|
|
5997
|
+
total: 0,
|
|
5998
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
5999
|
+
};
|
|
6000
|
+
}
|
|
6001
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
6002
|
+
const { calls, symbolFound, ambiguous, totalMatches } = await incomingCallsService2(
|
|
6003
|
+
{
|
|
6004
|
+
projectRoot: ctx.projectRoot,
|
|
6005
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
6006
|
+
symbol: input.symbol,
|
|
6007
|
+
file: input.file,
|
|
6008
|
+
limit
|
|
6009
|
+
}
|
|
6010
|
+
);
|
|
6011
|
+
if (!symbolFound) {
|
|
6012
|
+
let hasPersistedIndex = state.ready;
|
|
6013
|
+
if (!hasPersistedIndex) {
|
|
6014
|
+
try {
|
|
6015
|
+
const stats = await codebaseIndexStats({
|
|
6016
|
+
projectRoot: ctx.projectRoot,
|
|
6017
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
6018
|
+
});
|
|
6019
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
6020
|
+
} catch {
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
if (!hasPersistedIndex) {
|
|
6024
|
+
return {
|
|
6025
|
+
symbol: input.symbol,
|
|
6026
|
+
calls: [],
|
|
6027
|
+
total: 0,
|
|
6028
|
+
indexStatus: "No persisted index data found. Run codebase-index to build it."
|
|
6029
|
+
};
|
|
6030
|
+
}
|
|
6031
|
+
return {
|
|
6032
|
+
symbol: input.symbol,
|
|
6033
|
+
calls: [],
|
|
6034
|
+
total: 0,
|
|
6035
|
+
note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
|
|
6036
|
+
};
|
|
6037
|
+
}
|
|
6038
|
+
const notes = [];
|
|
6039
|
+
if (totalMatches > limit) {
|
|
6040
|
+
notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
|
|
6041
|
+
}
|
|
6042
|
+
if (ambiguous) {
|
|
6043
|
+
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\`.`);
|
|
6044
|
+
}
|
|
6045
|
+
return {
|
|
6046
|
+
symbol: input.symbol,
|
|
6047
|
+
calls,
|
|
6048
|
+
total: calls.length,
|
|
6049
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
};
|
|
6053
|
+
|
|
6054
|
+
// src/codebase-index/codebase-outgoing-calls-tool.ts
|
|
6055
|
+
var codebaseOutgoingCallsTool = {
|
|
6056
|
+
name: "codebase-outgoing-calls",
|
|
6057
|
+
category: "Project",
|
|
6058
|
+
icon: "index",
|
|
6059
|
+
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.",
|
|
6060
|
+
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.',
|
|
6061
|
+
permission: "auto",
|
|
6062
|
+
mutating: false,
|
|
6063
|
+
capabilities: ["fs.read"],
|
|
6064
|
+
timeoutMs: 35e3,
|
|
6065
|
+
inputSchema: {
|
|
6066
|
+
type: "object",
|
|
6067
|
+
properties: {
|
|
6068
|
+
symbol: {
|
|
6069
|
+
type: "string",
|
|
6070
|
+
description: "The function/method/type name to find callees for"
|
|
6071
|
+
},
|
|
6072
|
+
file: {
|
|
6073
|
+
type: "string",
|
|
6074
|
+
description: "Scope to a specific file when multiple symbols share the same name"
|
|
6075
|
+
},
|
|
6076
|
+
limit: {
|
|
6077
|
+
type: "integer",
|
|
6078
|
+
description: "Maximum call sites to return (default 50, max 200)",
|
|
6079
|
+
minimum: 1,
|
|
6080
|
+
maximum: 200
|
|
6081
|
+
}
|
|
6082
|
+
},
|
|
6083
|
+
required: ["symbol"]
|
|
6084
|
+
},
|
|
6085
|
+
async execute(input, ctx) {
|
|
6086
|
+
const state = getIndexState();
|
|
6087
|
+
if (state.indexing && !state.ready) {
|
|
6088
|
+
return {
|
|
6089
|
+
symbol: input.symbol,
|
|
6090
|
+
calls: [],
|
|
6091
|
+
total: 0,
|
|
6092
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
6093
|
+
};
|
|
6094
|
+
}
|
|
6095
|
+
if (state.lastError) {
|
|
6096
|
+
const circuit = state.circuit;
|
|
6097
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
|
|
6098
|
+
return {
|
|
6099
|
+
symbol: input.symbol,
|
|
6100
|
+
calls: [],
|
|
6101
|
+
total: 0,
|
|
6102
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
6103
|
+
};
|
|
6104
|
+
}
|
|
6105
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
6106
|
+
const { calls, symbolFound, unresolvedCount, totalMatches } = await outgoingCallsService2(
|
|
6107
|
+
{
|
|
6108
|
+
projectRoot: ctx.projectRoot,
|
|
6109
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
6110
|
+
symbol: input.symbol,
|
|
6111
|
+
file: input.file,
|
|
6112
|
+
limit
|
|
6113
|
+
}
|
|
6114
|
+
);
|
|
6115
|
+
if (!symbolFound) {
|
|
6116
|
+
let hasPersistedIndex = state.ready;
|
|
6117
|
+
if (!hasPersistedIndex) {
|
|
6118
|
+
try {
|
|
6119
|
+
const stats = await codebaseIndexStats({
|
|
6120
|
+
projectRoot: ctx.projectRoot,
|
|
6121
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
6122
|
+
});
|
|
6123
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
6124
|
+
} catch {
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
if (!hasPersistedIndex) {
|
|
6128
|
+
return {
|
|
6129
|
+
symbol: input.symbol,
|
|
6130
|
+
calls: [],
|
|
6131
|
+
total: 0,
|
|
6132
|
+
indexStatus: "No persisted index data found. Run codebase-index to build it."
|
|
6133
|
+
};
|
|
6134
|
+
}
|
|
6135
|
+
return {
|
|
6136
|
+
symbol: input.symbol,
|
|
6137
|
+
calls: [],
|
|
6138
|
+
total: 0,
|
|
6139
|
+
note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
|
|
6140
|
+
};
|
|
6141
|
+
}
|
|
6142
|
+
const notes = [];
|
|
6143
|
+
if (totalMatches > limit) {
|
|
6144
|
+
notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
|
|
6145
|
+
}
|
|
6146
|
+
if (unresolvedCount > 0) {
|
|
6147
|
+
notes.push(`${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`);
|
|
6148
|
+
}
|
|
6149
|
+
return {
|
|
6150
|
+
symbol: input.symbol,
|
|
6151
|
+
calls,
|
|
6152
|
+
total: calls.length,
|
|
6153
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
6154
|
+
};
|
|
6155
|
+
}
|
|
6156
|
+
};
|
|
6157
|
+
|
|
5780
6158
|
// src/codebase-index/codebase-search-tool.ts
|
|
5781
6159
|
var codebaseSearchTool = {
|
|
5782
6160
|
name: "codebase-search",
|
|
@@ -6492,9 +6870,11 @@ export {
|
|
|
6492
6870
|
buildIndexableText,
|
|
6493
6871
|
cancelPendingReindexes,
|
|
6494
6872
|
checkCodebaseIndexServerHealth,
|
|
6873
|
+
codebaseIncomingCallsTool,
|
|
6495
6874
|
codebaseIndexDirOverride,
|
|
6496
6875
|
codebaseIndexStats,
|
|
6497
6876
|
codebaseIndexTool,
|
|
6877
|
+
codebaseOutgoingCallsTool,
|
|
6498
6878
|
codebaseSearchTool,
|
|
6499
6879
|
codebaseStatsTool,
|
|
6500
6880
|
deadCodeScanTool,
|
|
@@ -6503,6 +6883,7 @@ export {
|
|
|
6503
6883
|
ensureCodebaseIndexServer,
|
|
6504
6884
|
fileGraphService2 as fileGraphService,
|
|
6505
6885
|
getIndexState,
|
|
6886
|
+
incomingCallsService2 as incomingCallsService,
|
|
6506
6887
|
indexCircuitBreaker,
|
|
6507
6888
|
internalKindToLspKind,
|
|
6508
6889
|
isIndexReady,
|
|
@@ -6511,6 +6892,7 @@ export {
|
|
|
6511
6892
|
isIndexing,
|
|
6512
6893
|
lspKindToInternalKind,
|
|
6513
6894
|
onIndexStateChange,
|
|
6895
|
+
outgoingCallsService2 as outgoingCallsService,
|
|
6514
6896
|
packageGraphService2 as packageGraphService,
|
|
6515
6897
|
resetIndexCircuitBreaker,
|
|
6516
6898
|
resolveIndexDir,
|
|
@@ -2671,6 +2671,136 @@ function mapWriterRefRow(row) {
|
|
|
2671
2671
|
}
|
|
2672
2672
|
|
|
2673
2673
|
// src/codebase-index/writer-graph-reader.ts
|
|
2674
|
+
var MAX_SQL_VARS = 900;
|
|
2675
|
+
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
2676
|
+
const results = [];
|
|
2677
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
2678
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
2679
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
2680
|
+
const sql = buildSql(placeholders);
|
|
2681
|
+
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
2682
|
+
}
|
|
2683
|
+
return results;
|
|
2684
|
+
}
|
|
2685
|
+
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
2686
|
+
let total = 0;
|
|
2687
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
2688
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
2689
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
2690
|
+
const sql = buildSql(placeholders);
|
|
2691
|
+
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
2692
|
+
total += rows[0]?.n ?? 0;
|
|
2693
|
+
}
|
|
2694
|
+
return total;
|
|
2695
|
+
}
|
|
2696
|
+
function mapCallSiteRow(row) {
|
|
2697
|
+
return {
|
|
2698
|
+
symbol: {
|
|
2699
|
+
id: row.sym_id,
|
|
2700
|
+
name: row.sym_name,
|
|
2701
|
+
kind: row.sym_kind,
|
|
2702
|
+
lang: row.sym_lang,
|
|
2703
|
+
file: row.sym_file,
|
|
2704
|
+
line: row.sym_line,
|
|
2705
|
+
signature: row.sym_signature
|
|
2706
|
+
},
|
|
2707
|
+
callType: row.call_type,
|
|
2708
|
+
line: row.ref_line
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function resolveSymbolIds(stmt, symbolName, file) {
|
|
2712
|
+
const baseSql = file ? `SELECT id FROM symbols WHERE name = ? AND file = ? ORDER BY id` : `SELECT id FROM symbols WHERE name = ? ORDER BY id`;
|
|
2713
|
+
const args = file ? [symbolName, file] : [symbolName];
|
|
2714
|
+
const rows = stmt(baseSql).all(...args);
|
|
2715
|
+
return rows.map((r) => r.id);
|
|
2716
|
+
}
|
|
2717
|
+
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
2718
|
+
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
2719
|
+
if (targetIds.length === 0) return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
|
|
2720
|
+
let matchIds = targetIds;
|
|
2721
|
+
let ambiguous = false;
|
|
2722
|
+
if (file !== void 0) {
|
|
2723
|
+
const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
|
|
2724
|
+
if (allNamedIds.length > targetIds.length) {
|
|
2725
|
+
matchIds = allNamedIds;
|
|
2726
|
+
ambiguous = true;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
const useFallback = !file;
|
|
2730
|
+
const rows = chunkedIdQuery(
|
|
2731
|
+
stmt,
|
|
2732
|
+
matchIds,
|
|
2733
|
+
(ph) => `SELECT
|
|
2734
|
+
s.id AS sym_id,
|
|
2735
|
+
s.name AS sym_name,
|
|
2736
|
+
s.kind AS sym_kind,
|
|
2737
|
+
s.lang AS sym_lang,
|
|
2738
|
+
s.file AS sym_file,
|
|
2739
|
+
s.line AS sym_line,
|
|
2740
|
+
s.signature AS sym_signature,
|
|
2741
|
+
r.call_type,
|
|
2742
|
+
r.line AS ref_line
|
|
2743
|
+
FROM refs r
|
|
2744
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2745
|
+
WHERE r.to_id IN (${ph})
|
|
2746
|
+
ORDER BY r.line, r.id`,
|
|
2747
|
+
[]
|
|
2748
|
+
);
|
|
2749
|
+
if (useFallback) {
|
|
2750
|
+
const fallbackRows = stmt(
|
|
2751
|
+
`SELECT
|
|
2752
|
+
s.id AS sym_id,
|
|
2753
|
+
s.name AS sym_name,
|
|
2754
|
+
s.kind AS sym_kind,
|
|
2755
|
+
s.lang AS sym_lang,
|
|
2756
|
+
s.file AS sym_file,
|
|
2757
|
+
s.line AS sym_line,
|
|
2758
|
+
s.signature AS sym_signature,
|
|
2759
|
+
r.call_type,
|
|
2760
|
+
r.line AS ref_line
|
|
2761
|
+
FROM refs r
|
|
2762
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2763
|
+
WHERE r.to_id IS NULL AND r.to_name = ?
|
|
2764
|
+
ORDER BY r.line, r.id`
|
|
2765
|
+
).all(symbolName);
|
|
2766
|
+
rows.push(...fallbackRows);
|
|
2767
|
+
}
|
|
2768
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
2769
|
+
const allCalls = rows.map(mapCallSiteRow);
|
|
2770
|
+
return { calls: allCalls.slice(0, limit), symbolFound: true, ambiguous, totalMatches: allCalls.length };
|
|
2771
|
+
}
|
|
2772
|
+
function findOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
2773
|
+
const sourceIds = resolveSymbolIds(stmt, symbolName, file);
|
|
2774
|
+
if (sourceIds.length === 0) return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
|
|
2775
|
+
const unresolvedCount = chunkedIdScalar(
|
|
2776
|
+
stmt,
|
|
2777
|
+
sourceIds,
|
|
2778
|
+
(ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
|
|
2779
|
+
);
|
|
2780
|
+
const rows = chunkedIdQuery(
|
|
2781
|
+
stmt,
|
|
2782
|
+
sourceIds,
|
|
2783
|
+
(ph) => `SELECT
|
|
2784
|
+
s.id AS sym_id,
|
|
2785
|
+
s.name AS sym_name,
|
|
2786
|
+
s.kind AS sym_kind,
|
|
2787
|
+
s.lang AS sym_lang,
|
|
2788
|
+
s.file AS sym_file,
|
|
2789
|
+
s.line AS sym_line,
|
|
2790
|
+
s.signature AS sym_signature,
|
|
2791
|
+
r.call_type,
|
|
2792
|
+
r.line AS ref_line
|
|
2793
|
+
FROM refs r
|
|
2794
|
+
JOIN symbols s ON s.id = r.to_id
|
|
2795
|
+
WHERE r.from_id IN (${ph})
|
|
2796
|
+
AND r.to_id IS NOT NULL -- INNER JOIN already excludes NULL to_id; this is defensive belt-and-suspenders
|
|
2797
|
+
ORDER BY r.line, r.id`,
|
|
2798
|
+
[]
|
|
2799
|
+
);
|
|
2800
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
2801
|
+
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
2802
|
+
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
2803
|
+
}
|
|
2674
2804
|
function findRefsToWithStatement(stmt, symbolId) {
|
|
2675
2805
|
return stmt(
|
|
2676
2806
|
"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 = ?)"
|
|
@@ -3902,6 +4032,20 @@ var IndexStore = class _IndexStore {
|
|
|
3902
4032
|
return false;
|
|
3903
4033
|
}
|
|
3904
4034
|
}
|
|
4035
|
+
/**
|
|
4036
|
+
* Find all symbols that reference the named target symbol (incoming callers).
|
|
4037
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
4038
|
+
*/
|
|
4039
|
+
findIncomingCallsByName(symbolName, file, limit = 100) {
|
|
4040
|
+
return findIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4041
|
+
}
|
|
4042
|
+
/**
|
|
4043
|
+
* Find all symbols that the named source symbol references (outgoing callees).
|
|
4044
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
4045
|
+
*/
|
|
4046
|
+
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
4047
|
+
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4048
|
+
}
|
|
3905
4049
|
/**
|
|
3906
4050
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
3907
4051
|
*/
|
|
@@ -4486,6 +4630,22 @@ function symbolGraphService(args) {
|
|
|
4486
4630
|
indexStorePool.release(store);
|
|
4487
4631
|
}
|
|
4488
4632
|
}
|
|
4633
|
+
function incomingCallsService(args) {
|
|
4634
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
4635
|
+
try {
|
|
4636
|
+
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
4637
|
+
} finally {
|
|
4638
|
+
indexStorePool.release(store);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
function outgoingCallsService(args) {
|
|
4642
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
4643
|
+
try {
|
|
4644
|
+
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
4645
|
+
} finally {
|
|
4646
|
+
indexStorePool.release(store);
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4489
4649
|
|
|
4490
4650
|
// src/codebase-index/project-server.ts
|
|
4491
4651
|
init_languages();
|
|
@@ -4639,6 +4799,8 @@ var statsCache = new GenerationLruCache(1);
|
|
|
4639
4799
|
var packageGraphCache = new GenerationLruCache(1);
|
|
4640
4800
|
var fileGraphCache = new GenerationLruCache(32);
|
|
4641
4801
|
var symbolGraphCache = new GenerationLruCache(64);
|
|
4802
|
+
var incomingCallsCache = new GenerationLruCache(128);
|
|
4803
|
+
var outgoingCallsCache = new GenerationLruCache(128);
|
|
4642
4804
|
var indexActivity = {
|
|
4643
4805
|
indexing: false,
|
|
4644
4806
|
currentFile: 0,
|
|
@@ -4658,7 +4820,9 @@ var stopMemoryWatchdog = startSharedHeapWatchdog({
|
|
|
4658
4820
|
statsCache: statsCache.size,
|
|
4659
4821
|
packageGraphCache: packageGraphCache.size,
|
|
4660
4822
|
fileGraphCache: fileGraphCache.size,
|
|
4661
|
-
symbolGraphCache: symbolGraphCache.size
|
|
4823
|
+
symbolGraphCache: symbolGraphCache.size,
|
|
4824
|
+
incomingCallsCache: incomingCallsCache.size,
|
|
4825
|
+
outgoingCallsCache: outgoingCallsCache.size
|
|
4662
4826
|
})
|
|
4663
4827
|
});
|
|
4664
4828
|
var lastProgressBroadcastAt = 0;
|
|
@@ -4674,6 +4838,8 @@ function clearQueryCaches() {
|
|
|
4674
4838
|
packageGraphCache.clear();
|
|
4675
4839
|
fileGraphCache.clear();
|
|
4676
4840
|
symbolGraphCache.clear();
|
|
4841
|
+
incomingCallsCache.clear();
|
|
4842
|
+
outgoingCallsCache.clear();
|
|
4677
4843
|
}
|
|
4678
4844
|
function cachedRead(cache, key, load) {
|
|
4679
4845
|
const generation = indexActivity.generation;
|
|
@@ -4881,6 +5047,16 @@ async function dispatchOperation(state, message) {
|
|
|
4881
5047
|
message.args.fileFilter,
|
|
4882
5048
|
() => symbolGraphService(fixedArgs(message.args))
|
|
4883
5049
|
);
|
|
5050
|
+
case "incomingCalls": {
|
|
5051
|
+
const callArgs = fixedArgs(message.args);
|
|
5052
|
+
const cacheKey = JSON.stringify([callArgs.symbol, callArgs.file ?? "", callArgs.limit ?? 100]);
|
|
5053
|
+
return cachedRead(incomingCallsCache, cacheKey, () => incomingCallsService(callArgs));
|
|
5054
|
+
}
|
|
5055
|
+
case "outgoingCalls": {
|
|
5056
|
+
const callArgs = fixedArgs(message.args);
|
|
5057
|
+
const cacheKey = JSON.stringify([callArgs.symbol, callArgs.file ?? "", callArgs.limit ?? 100]);
|
|
5058
|
+
return cachedRead(outgoingCallsCache, cacheKey, () => outgoingCallsService(callArgs));
|
|
5059
|
+
}
|
|
4884
5060
|
default:
|
|
4885
5061
|
throw new Error(`unknown index operation: ${String(op)}`);
|
|
4886
5062
|
}
|
|
@@ -96,6 +96,29 @@ export interface Ref {
|
|
|
96
96
|
callType: CallType;
|
|
97
97
|
line: number;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* A call site — the enriched result of an incoming/outgoing calls query.
|
|
101
|
+
*
|
|
102
|
+
* Unlike the raw `Ref` type, this carries the full caller/callee symbol
|
|
103
|
+
* metadata (name, kind, file, line, signature) so the agent gets a
|
|
104
|
+
* self-contained answer without a second lookup.
|
|
105
|
+
*/
|
|
106
|
+
export interface CallSite {
|
|
107
|
+
/** The symbol that makes or receives the call. */
|
|
108
|
+
symbol: {
|
|
109
|
+
id: number;
|
|
110
|
+
name: string;
|
|
111
|
+
kind: SymbolKind;
|
|
112
|
+
lang: SymbolLang;
|
|
113
|
+
file: string;
|
|
114
|
+
line: number;
|
|
115
|
+
signature: string;
|
|
116
|
+
};
|
|
117
|
+
/** Kind of reference: call, type_ref, inherit, implement, import. */
|
|
118
|
+
callType: CallType;
|
|
119
|
+
/** Source line where the reference occurs. */
|
|
120
|
+
line: number;
|
|
121
|
+
}
|
|
99
122
|
/** A node in the code-map dependency graph. */
|
|
100
123
|
export interface GraphNode {
|
|
101
124
|
id: string;
|