@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/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
package/dist/pack.js
CHANGED
|
@@ -7082,6 +7082,10 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
7082
7082
|
const start = Date.now();
|
|
7083
7083
|
const pidStr = String(process.pid);
|
|
7084
7084
|
const hostStr = os2.hostname();
|
|
7085
|
+
try {
|
|
7086
|
+
await fs6.mkdir(path9.dirname(lockfilePath), { recursive: true });
|
|
7087
|
+
} catch {
|
|
7088
|
+
}
|
|
7085
7089
|
while (Date.now() - start < timeoutMs) {
|
|
7086
7090
|
try {
|
|
7087
7091
|
await fs6.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
|
|
@@ -7747,6 +7751,16 @@ function parseKillCommand(command) {
|
|
|
7747
7751
|
if (pgrepMatch) {
|
|
7748
7752
|
return null;
|
|
7749
7753
|
}
|
|
7754
|
+
const posixScriptMatch = normalized.match(SCRIPT_KILL_RE_POSIX);
|
|
7755
|
+
if (posixScriptMatch) {
|
|
7756
|
+
return {
|
|
7757
|
+
name: "kill-script",
|
|
7758
|
+
signal: "FORCE",
|
|
7759
|
+
isGroupKill: false,
|
|
7760
|
+
isAllKill: false,
|
|
7761
|
+
originalCommand: command
|
|
7762
|
+
};
|
|
7763
|
+
}
|
|
7750
7764
|
return null;
|
|
7751
7765
|
}
|
|
7752
7766
|
async function getProtectedEntries() {
|
|
@@ -10538,6 +10552,136 @@ function mapWriterRefRow(row) {
|
|
|
10538
10552
|
}
|
|
10539
10553
|
|
|
10540
10554
|
// src/codebase-index/writer-graph-reader.ts
|
|
10555
|
+
var MAX_SQL_VARS = 900;
|
|
10556
|
+
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
10557
|
+
const results = [];
|
|
10558
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
10559
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
10560
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
10561
|
+
const sql = buildSql(placeholders);
|
|
10562
|
+
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
10563
|
+
}
|
|
10564
|
+
return results;
|
|
10565
|
+
}
|
|
10566
|
+
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
10567
|
+
let total = 0;
|
|
10568
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
10569
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
10570
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
10571
|
+
const sql = buildSql(placeholders);
|
|
10572
|
+
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
10573
|
+
total += rows[0]?.n ?? 0;
|
|
10574
|
+
}
|
|
10575
|
+
return total;
|
|
10576
|
+
}
|
|
10577
|
+
function mapCallSiteRow(row) {
|
|
10578
|
+
return {
|
|
10579
|
+
symbol: {
|
|
10580
|
+
id: row.sym_id,
|
|
10581
|
+
name: row.sym_name,
|
|
10582
|
+
kind: row.sym_kind,
|
|
10583
|
+
lang: row.sym_lang,
|
|
10584
|
+
file: row.sym_file,
|
|
10585
|
+
line: row.sym_line,
|
|
10586
|
+
signature: row.sym_signature
|
|
10587
|
+
},
|
|
10588
|
+
callType: row.call_type,
|
|
10589
|
+
line: row.ref_line
|
|
10590
|
+
};
|
|
10591
|
+
}
|
|
10592
|
+
function resolveSymbolIds(stmt, symbolName, file) {
|
|
10593
|
+
const baseSql = file ? `SELECT id FROM symbols WHERE name = ? AND file = ? ORDER BY id` : `SELECT id FROM symbols WHERE name = ? ORDER BY id`;
|
|
10594
|
+
const args = file ? [symbolName, file] : [symbolName];
|
|
10595
|
+
const rows = stmt(baseSql).all(...args);
|
|
10596
|
+
return rows.map((r) => r.id);
|
|
10597
|
+
}
|
|
10598
|
+
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
10599
|
+
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
10600
|
+
if (targetIds.length === 0) return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
|
|
10601
|
+
let matchIds = targetIds;
|
|
10602
|
+
let ambiguous = false;
|
|
10603
|
+
if (file !== void 0) {
|
|
10604
|
+
const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
|
|
10605
|
+
if (allNamedIds.length > targetIds.length) {
|
|
10606
|
+
matchIds = allNamedIds;
|
|
10607
|
+
ambiguous = true;
|
|
10608
|
+
}
|
|
10609
|
+
}
|
|
10610
|
+
const useFallback = !file;
|
|
10611
|
+
const rows = chunkedIdQuery(
|
|
10612
|
+
stmt,
|
|
10613
|
+
matchIds,
|
|
10614
|
+
(ph) => `SELECT
|
|
10615
|
+
s.id AS sym_id,
|
|
10616
|
+
s.name AS sym_name,
|
|
10617
|
+
s.kind AS sym_kind,
|
|
10618
|
+
s.lang AS sym_lang,
|
|
10619
|
+
s.file AS sym_file,
|
|
10620
|
+
s.line AS sym_line,
|
|
10621
|
+
s.signature AS sym_signature,
|
|
10622
|
+
r.call_type,
|
|
10623
|
+
r.line AS ref_line
|
|
10624
|
+
FROM refs r
|
|
10625
|
+
JOIN symbols s ON s.id = r.from_id
|
|
10626
|
+
WHERE r.to_id IN (${ph})
|
|
10627
|
+
ORDER BY r.line, r.id`,
|
|
10628
|
+
[]
|
|
10629
|
+
);
|
|
10630
|
+
if (useFallback) {
|
|
10631
|
+
const fallbackRows = stmt(
|
|
10632
|
+
`SELECT
|
|
10633
|
+
s.id AS sym_id,
|
|
10634
|
+
s.name AS sym_name,
|
|
10635
|
+
s.kind AS sym_kind,
|
|
10636
|
+
s.lang AS sym_lang,
|
|
10637
|
+
s.file AS sym_file,
|
|
10638
|
+
s.line AS sym_line,
|
|
10639
|
+
s.signature AS sym_signature,
|
|
10640
|
+
r.call_type,
|
|
10641
|
+
r.line AS ref_line
|
|
10642
|
+
FROM refs r
|
|
10643
|
+
JOIN symbols s ON s.id = r.from_id
|
|
10644
|
+
WHERE r.to_id IS NULL AND r.to_name = ?
|
|
10645
|
+
ORDER BY r.line, r.id`
|
|
10646
|
+
).all(symbolName);
|
|
10647
|
+
rows.push(...fallbackRows);
|
|
10648
|
+
}
|
|
10649
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
10650
|
+
const allCalls = rows.map(mapCallSiteRow);
|
|
10651
|
+
return { calls: allCalls.slice(0, limit), symbolFound: true, ambiguous, totalMatches: allCalls.length };
|
|
10652
|
+
}
|
|
10653
|
+
function findOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
10654
|
+
const sourceIds = resolveSymbolIds(stmt, symbolName, file);
|
|
10655
|
+
if (sourceIds.length === 0) return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
|
|
10656
|
+
const unresolvedCount = chunkedIdScalar(
|
|
10657
|
+
stmt,
|
|
10658
|
+
sourceIds,
|
|
10659
|
+
(ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
|
|
10660
|
+
);
|
|
10661
|
+
const rows = chunkedIdQuery(
|
|
10662
|
+
stmt,
|
|
10663
|
+
sourceIds,
|
|
10664
|
+
(ph) => `SELECT
|
|
10665
|
+
s.id AS sym_id,
|
|
10666
|
+
s.name AS sym_name,
|
|
10667
|
+
s.kind AS sym_kind,
|
|
10668
|
+
s.lang AS sym_lang,
|
|
10669
|
+
s.file AS sym_file,
|
|
10670
|
+
s.line AS sym_line,
|
|
10671
|
+
s.signature AS sym_signature,
|
|
10672
|
+
r.call_type,
|
|
10673
|
+
r.line AS ref_line
|
|
10674
|
+
FROM refs r
|
|
10675
|
+
JOIN symbols s ON s.id = r.to_id
|
|
10676
|
+
WHERE r.from_id IN (${ph})
|
|
10677
|
+
AND r.to_id IS NOT NULL -- INNER JOIN already excludes NULL to_id; this is defensive belt-and-suspenders
|
|
10678
|
+
ORDER BY r.line, r.id`,
|
|
10679
|
+
[]
|
|
10680
|
+
);
|
|
10681
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
10682
|
+
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
10683
|
+
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
10684
|
+
}
|
|
10541
10685
|
function findRefsToWithStatement(stmt, symbolId) {
|
|
10542
10686
|
return stmt(
|
|
10543
10687
|
"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 = ?)"
|
|
@@ -11773,6 +11917,20 @@ var IndexStore = class _IndexStore {
|
|
|
11773
11917
|
return false;
|
|
11774
11918
|
}
|
|
11775
11919
|
}
|
|
11920
|
+
/**
|
|
11921
|
+
* Find all symbols that reference the named target symbol (incoming callers).
|
|
11922
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
11923
|
+
*/
|
|
11924
|
+
findIncomingCallsByName(symbolName, file, limit = 100) {
|
|
11925
|
+
return findIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
11926
|
+
}
|
|
11927
|
+
/**
|
|
11928
|
+
* Find all symbols that the named source symbol references (outgoing callees).
|
|
11929
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
11930
|
+
*/
|
|
11931
|
+
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
11932
|
+
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
11933
|
+
}
|
|
11776
11934
|
/**
|
|
11777
11935
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
11778
11936
|
*/
|
|
@@ -13155,6 +13313,22 @@ function symbolGraphService(args) {
|
|
|
13155
13313
|
indexStorePool.release(store);
|
|
13156
13314
|
}
|
|
13157
13315
|
}
|
|
13316
|
+
function incomingCallsService(args) {
|
|
13317
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
13318
|
+
try {
|
|
13319
|
+
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
13320
|
+
} finally {
|
|
13321
|
+
indexStorePool.release(store);
|
|
13322
|
+
}
|
|
13323
|
+
}
|
|
13324
|
+
function outgoingCallsService(args) {
|
|
13325
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
13326
|
+
try {
|
|
13327
|
+
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
13328
|
+
} finally {
|
|
13329
|
+
indexStorePool.release(store);
|
|
13330
|
+
}
|
|
13331
|
+
}
|
|
13158
13332
|
|
|
13159
13333
|
// src/codebase-index/background-indexer.ts
|
|
13160
13334
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
@@ -13366,6 +13540,10 @@ async function callInline(op, args, opts) {
|
|
|
13366
13540
|
return fileGraphService(args);
|
|
13367
13541
|
case "symbolGraph":
|
|
13368
13542
|
return symbolGraphService(args);
|
|
13543
|
+
case "incomingCalls":
|
|
13544
|
+
return incomingCallsService(args);
|
|
13545
|
+
case "outgoingCalls":
|
|
13546
|
+
return outgoingCallsService(args);
|
|
13369
13547
|
default:
|
|
13370
13548
|
throw new Error(`unknown index op: ${String(op)}`);
|
|
13371
13549
|
}
|
|
@@ -13461,6 +13639,12 @@ async function codebaseIndexStats(args, opts = {}) {
|
|
|
13461
13639
|
signal: opts.signal
|
|
13462
13640
|
});
|
|
13463
13641
|
}
|
|
13642
|
+
async function incomingCallsService2(args) {
|
|
13643
|
+
return callIndexOp("incomingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
13644
|
+
}
|
|
13645
|
+
async function outgoingCallsService2(args) {
|
|
13646
|
+
return callIndexOp("outgoingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
13647
|
+
}
|
|
13464
13648
|
|
|
13465
13649
|
// src/codebase-index/codebase-index-tool.ts
|
|
13466
13650
|
var codebaseIndexTool = {
|
|
@@ -13525,6 +13709,214 @@ var codebaseIndexTool = {
|
|
|
13525
13709
|
}
|
|
13526
13710
|
};
|
|
13527
13711
|
|
|
13712
|
+
// src/codebase-index/codebase-incoming-calls-tool.ts
|
|
13713
|
+
var codebaseIncomingCallsTool = {
|
|
13714
|
+
name: "codebase-incoming-calls",
|
|
13715
|
+
category: "Project",
|
|
13716
|
+
icon: "index",
|
|
13717
|
+
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.",
|
|
13718
|
+
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.',
|
|
13719
|
+
permission: "auto",
|
|
13720
|
+
mutating: false,
|
|
13721
|
+
capabilities: ["fs.read"],
|
|
13722
|
+
timeoutMs: 35e3,
|
|
13723
|
+
inputSchema: {
|
|
13724
|
+
type: "object",
|
|
13725
|
+
properties: {
|
|
13726
|
+
symbol: {
|
|
13727
|
+
type: "string",
|
|
13728
|
+
description: "The function/method/type name to find callers for"
|
|
13729
|
+
},
|
|
13730
|
+
file: {
|
|
13731
|
+
type: "string",
|
|
13732
|
+
description: "Scope to a specific file when multiple symbols share the same name"
|
|
13733
|
+
},
|
|
13734
|
+
limit: {
|
|
13735
|
+
type: "integer",
|
|
13736
|
+
description: "Maximum call sites to return (default 50, max 200)",
|
|
13737
|
+
minimum: 1,
|
|
13738
|
+
maximum: 200
|
|
13739
|
+
}
|
|
13740
|
+
},
|
|
13741
|
+
required: ["symbol"]
|
|
13742
|
+
},
|
|
13743
|
+
async execute(input, ctx) {
|
|
13744
|
+
const state = getIndexState();
|
|
13745
|
+
if (state.indexing && !state.ready) {
|
|
13746
|
+
return {
|
|
13747
|
+
symbol: input.symbol,
|
|
13748
|
+
calls: [],
|
|
13749
|
+
total: 0,
|
|
13750
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
13751
|
+
};
|
|
13752
|
+
}
|
|
13753
|
+
if (state.lastError) {
|
|
13754
|
+
const circuit = state.circuit;
|
|
13755
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
|
|
13756
|
+
return {
|
|
13757
|
+
symbol: input.symbol,
|
|
13758
|
+
calls: [],
|
|
13759
|
+
total: 0,
|
|
13760
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
13761
|
+
};
|
|
13762
|
+
}
|
|
13763
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
13764
|
+
const { calls, symbolFound, ambiguous, totalMatches } = await incomingCallsService2(
|
|
13765
|
+
{
|
|
13766
|
+
projectRoot: ctx.projectRoot,
|
|
13767
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
13768
|
+
symbol: input.symbol,
|
|
13769
|
+
file: input.file,
|
|
13770
|
+
limit
|
|
13771
|
+
}
|
|
13772
|
+
);
|
|
13773
|
+
if (!symbolFound) {
|
|
13774
|
+
let hasPersistedIndex = state.ready;
|
|
13775
|
+
if (!hasPersistedIndex) {
|
|
13776
|
+
try {
|
|
13777
|
+
const stats = await codebaseIndexStats({
|
|
13778
|
+
projectRoot: ctx.projectRoot,
|
|
13779
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
13780
|
+
});
|
|
13781
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
13782
|
+
} catch {
|
|
13783
|
+
}
|
|
13784
|
+
}
|
|
13785
|
+
if (!hasPersistedIndex) {
|
|
13786
|
+
return {
|
|
13787
|
+
symbol: input.symbol,
|
|
13788
|
+
calls: [],
|
|
13789
|
+
total: 0,
|
|
13790
|
+
indexStatus: "No persisted index data found. Run codebase-index to build it."
|
|
13791
|
+
};
|
|
13792
|
+
}
|
|
13793
|
+
return {
|
|
13794
|
+
symbol: input.symbol,
|
|
13795
|
+
calls: [],
|
|
13796
|
+
total: 0,
|
|
13797
|
+
note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
|
|
13798
|
+
};
|
|
13799
|
+
}
|
|
13800
|
+
const notes = [];
|
|
13801
|
+
if (totalMatches > limit) {
|
|
13802
|
+
notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
|
|
13803
|
+
}
|
|
13804
|
+
if (ambiguous) {
|
|
13805
|
+
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\`.`);
|
|
13806
|
+
}
|
|
13807
|
+
return {
|
|
13808
|
+
symbol: input.symbol,
|
|
13809
|
+
calls,
|
|
13810
|
+
total: calls.length,
|
|
13811
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
13812
|
+
};
|
|
13813
|
+
}
|
|
13814
|
+
};
|
|
13815
|
+
|
|
13816
|
+
// src/codebase-index/codebase-outgoing-calls-tool.ts
|
|
13817
|
+
var codebaseOutgoingCallsTool = {
|
|
13818
|
+
name: "codebase-outgoing-calls",
|
|
13819
|
+
category: "Project",
|
|
13820
|
+
icon: "index",
|
|
13821
|
+
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.",
|
|
13822
|
+
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.',
|
|
13823
|
+
permission: "auto",
|
|
13824
|
+
mutating: false,
|
|
13825
|
+
capabilities: ["fs.read"],
|
|
13826
|
+
timeoutMs: 35e3,
|
|
13827
|
+
inputSchema: {
|
|
13828
|
+
type: "object",
|
|
13829
|
+
properties: {
|
|
13830
|
+
symbol: {
|
|
13831
|
+
type: "string",
|
|
13832
|
+
description: "The function/method/type name to find callees for"
|
|
13833
|
+
},
|
|
13834
|
+
file: {
|
|
13835
|
+
type: "string",
|
|
13836
|
+
description: "Scope to a specific file when multiple symbols share the same name"
|
|
13837
|
+
},
|
|
13838
|
+
limit: {
|
|
13839
|
+
type: "integer",
|
|
13840
|
+
description: "Maximum call sites to return (default 50, max 200)",
|
|
13841
|
+
minimum: 1,
|
|
13842
|
+
maximum: 200
|
|
13843
|
+
}
|
|
13844
|
+
},
|
|
13845
|
+
required: ["symbol"]
|
|
13846
|
+
},
|
|
13847
|
+
async execute(input, ctx) {
|
|
13848
|
+
const state = getIndexState();
|
|
13849
|
+
if (state.indexing && !state.ready) {
|
|
13850
|
+
return {
|
|
13851
|
+
symbol: input.symbol,
|
|
13852
|
+
calls: [],
|
|
13853
|
+
total: 0,
|
|
13854
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
13855
|
+
};
|
|
13856
|
+
}
|
|
13857
|
+
if (state.lastError) {
|
|
13858
|
+
const circuit = state.circuit;
|
|
13859
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
|
|
13860
|
+
return {
|
|
13861
|
+
symbol: input.symbol,
|
|
13862
|
+
calls: [],
|
|
13863
|
+
total: 0,
|
|
13864
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
13865
|
+
};
|
|
13866
|
+
}
|
|
13867
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
13868
|
+
const { calls, symbolFound, unresolvedCount, totalMatches } = await outgoingCallsService2(
|
|
13869
|
+
{
|
|
13870
|
+
projectRoot: ctx.projectRoot,
|
|
13871
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
13872
|
+
symbol: input.symbol,
|
|
13873
|
+
file: input.file,
|
|
13874
|
+
limit
|
|
13875
|
+
}
|
|
13876
|
+
);
|
|
13877
|
+
if (!symbolFound) {
|
|
13878
|
+
let hasPersistedIndex = state.ready;
|
|
13879
|
+
if (!hasPersistedIndex) {
|
|
13880
|
+
try {
|
|
13881
|
+
const stats = await codebaseIndexStats({
|
|
13882
|
+
projectRoot: ctx.projectRoot,
|
|
13883
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
13884
|
+
});
|
|
13885
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
13886
|
+
} catch {
|
|
13887
|
+
}
|
|
13888
|
+
}
|
|
13889
|
+
if (!hasPersistedIndex) {
|
|
13890
|
+
return {
|
|
13891
|
+
symbol: input.symbol,
|
|
13892
|
+
calls: [],
|
|
13893
|
+
total: 0,
|
|
13894
|
+
indexStatus: "No persisted index data found. Run codebase-index to build it."
|
|
13895
|
+
};
|
|
13896
|
+
}
|
|
13897
|
+
return {
|
|
13898
|
+
symbol: input.symbol,
|
|
13899
|
+
calls: [],
|
|
13900
|
+
total: 0,
|
|
13901
|
+
note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
|
|
13902
|
+
};
|
|
13903
|
+
}
|
|
13904
|
+
const notes = [];
|
|
13905
|
+
if (totalMatches > limit) {
|
|
13906
|
+
notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
|
|
13907
|
+
}
|
|
13908
|
+
if (unresolvedCount > 0) {
|
|
13909
|
+
notes.push(`${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`);
|
|
13910
|
+
}
|
|
13911
|
+
return {
|
|
13912
|
+
symbol: input.symbol,
|
|
13913
|
+
calls,
|
|
13914
|
+
total: calls.length,
|
|
13915
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
13916
|
+
};
|
|
13917
|
+
}
|
|
13918
|
+
};
|
|
13919
|
+
|
|
13528
13920
|
// src/codebase-index/codebase-search-tool.ts
|
|
13529
13921
|
var codebaseSearchTool = {
|
|
13530
13922
|
name: "codebase-search",
|
|
@@ -24898,6 +25290,8 @@ var builtinTools = [
|
|
|
24898
25290
|
editTool,
|
|
24899
25291
|
codebaseStatsTool,
|
|
24900
25292
|
codebaseSearchTool,
|
|
25293
|
+
codebaseIncomingCallsTool,
|
|
25294
|
+
codebaseOutgoingCallsTool,
|
|
24901
25295
|
codebaseIndexTool,
|
|
24902
25296
|
deadCodeScanTool,
|
|
24903
25297
|
replaceTool,
|
package/dist/ps-slash.js
CHANGED
|
@@ -626,6 +626,10 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
626
626
|
const start = Date.now();
|
|
627
627
|
const pidStr = String(process.pid);
|
|
628
628
|
const hostStr = os2.hostname();
|
|
629
|
+
try {
|
|
630
|
+
await fs.mkdir(path.dirname(lockfilePath), { recursive: true });
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
629
633
|
while (Date.now() - start < timeoutMs) {
|
|
630
634
|
try {
|
|
631
635
|
await fs.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
|
package/dist/read.js
CHANGED
|
@@ -2736,6 +2736,136 @@ function mapWriterRefRow(row) {
|
|
|
2736
2736
|
}
|
|
2737
2737
|
|
|
2738
2738
|
// src/codebase-index/writer-graph-reader.ts
|
|
2739
|
+
var MAX_SQL_VARS = 900;
|
|
2740
|
+
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
2741
|
+
const results = [];
|
|
2742
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
2743
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
2744
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
2745
|
+
const sql = buildSql(placeholders);
|
|
2746
|
+
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
2747
|
+
}
|
|
2748
|
+
return results;
|
|
2749
|
+
}
|
|
2750
|
+
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
2751
|
+
let total = 0;
|
|
2752
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
2753
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
2754
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
2755
|
+
const sql = buildSql(placeholders);
|
|
2756
|
+
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
2757
|
+
total += rows[0]?.n ?? 0;
|
|
2758
|
+
}
|
|
2759
|
+
return total;
|
|
2760
|
+
}
|
|
2761
|
+
function mapCallSiteRow(row) {
|
|
2762
|
+
return {
|
|
2763
|
+
symbol: {
|
|
2764
|
+
id: row.sym_id,
|
|
2765
|
+
name: row.sym_name,
|
|
2766
|
+
kind: row.sym_kind,
|
|
2767
|
+
lang: row.sym_lang,
|
|
2768
|
+
file: row.sym_file,
|
|
2769
|
+
line: row.sym_line,
|
|
2770
|
+
signature: row.sym_signature
|
|
2771
|
+
},
|
|
2772
|
+
callType: row.call_type,
|
|
2773
|
+
line: row.ref_line
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
function resolveSymbolIds(stmt, symbolName, file) {
|
|
2777
|
+
const baseSql = file ? `SELECT id FROM symbols WHERE name = ? AND file = ? ORDER BY id` : `SELECT id FROM symbols WHERE name = ? ORDER BY id`;
|
|
2778
|
+
const args = file ? [symbolName, file] : [symbolName];
|
|
2779
|
+
const rows = stmt(baseSql).all(...args);
|
|
2780
|
+
return rows.map((r) => r.id);
|
|
2781
|
+
}
|
|
2782
|
+
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
2783
|
+
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
2784
|
+
if (targetIds.length === 0) return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
|
|
2785
|
+
let matchIds = targetIds;
|
|
2786
|
+
let ambiguous = false;
|
|
2787
|
+
if (file !== void 0) {
|
|
2788
|
+
const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
|
|
2789
|
+
if (allNamedIds.length > targetIds.length) {
|
|
2790
|
+
matchIds = allNamedIds;
|
|
2791
|
+
ambiguous = true;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
const useFallback = !file;
|
|
2795
|
+
const rows = chunkedIdQuery(
|
|
2796
|
+
stmt,
|
|
2797
|
+
matchIds,
|
|
2798
|
+
(ph) => `SELECT
|
|
2799
|
+
s.id AS sym_id,
|
|
2800
|
+
s.name AS sym_name,
|
|
2801
|
+
s.kind AS sym_kind,
|
|
2802
|
+
s.lang AS sym_lang,
|
|
2803
|
+
s.file AS sym_file,
|
|
2804
|
+
s.line AS sym_line,
|
|
2805
|
+
s.signature AS sym_signature,
|
|
2806
|
+
r.call_type,
|
|
2807
|
+
r.line AS ref_line
|
|
2808
|
+
FROM refs r
|
|
2809
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2810
|
+
WHERE r.to_id IN (${ph})
|
|
2811
|
+
ORDER BY r.line, r.id`,
|
|
2812
|
+
[]
|
|
2813
|
+
);
|
|
2814
|
+
if (useFallback) {
|
|
2815
|
+
const fallbackRows = stmt(
|
|
2816
|
+
`SELECT
|
|
2817
|
+
s.id AS sym_id,
|
|
2818
|
+
s.name AS sym_name,
|
|
2819
|
+
s.kind AS sym_kind,
|
|
2820
|
+
s.lang AS sym_lang,
|
|
2821
|
+
s.file AS sym_file,
|
|
2822
|
+
s.line AS sym_line,
|
|
2823
|
+
s.signature AS sym_signature,
|
|
2824
|
+
r.call_type,
|
|
2825
|
+
r.line AS ref_line
|
|
2826
|
+
FROM refs r
|
|
2827
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2828
|
+
WHERE r.to_id IS NULL AND r.to_name = ?
|
|
2829
|
+
ORDER BY r.line, r.id`
|
|
2830
|
+
).all(symbolName);
|
|
2831
|
+
rows.push(...fallbackRows);
|
|
2832
|
+
}
|
|
2833
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
2834
|
+
const allCalls = rows.map(mapCallSiteRow);
|
|
2835
|
+
return { calls: allCalls.slice(0, limit), symbolFound: true, ambiguous, totalMatches: allCalls.length };
|
|
2836
|
+
}
|
|
2837
|
+
function findOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
2838
|
+
const sourceIds = resolveSymbolIds(stmt, symbolName, file);
|
|
2839
|
+
if (sourceIds.length === 0) return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
|
|
2840
|
+
const unresolvedCount = chunkedIdScalar(
|
|
2841
|
+
stmt,
|
|
2842
|
+
sourceIds,
|
|
2843
|
+
(ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
|
|
2844
|
+
);
|
|
2845
|
+
const rows = chunkedIdQuery(
|
|
2846
|
+
stmt,
|
|
2847
|
+
sourceIds,
|
|
2848
|
+
(ph) => `SELECT
|
|
2849
|
+
s.id AS sym_id,
|
|
2850
|
+
s.name AS sym_name,
|
|
2851
|
+
s.kind AS sym_kind,
|
|
2852
|
+
s.lang AS sym_lang,
|
|
2853
|
+
s.file AS sym_file,
|
|
2854
|
+
s.line AS sym_line,
|
|
2855
|
+
s.signature AS sym_signature,
|
|
2856
|
+
r.call_type,
|
|
2857
|
+
r.line AS ref_line
|
|
2858
|
+
FROM refs r
|
|
2859
|
+
JOIN symbols s ON s.id = r.to_id
|
|
2860
|
+
WHERE r.from_id IN (${ph})
|
|
2861
|
+
AND r.to_id IS NOT NULL -- INNER JOIN already excludes NULL to_id; this is defensive belt-and-suspenders
|
|
2862
|
+
ORDER BY r.line, r.id`,
|
|
2863
|
+
[]
|
|
2864
|
+
);
|
|
2865
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
2866
|
+
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
2867
|
+
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
2868
|
+
}
|
|
2739
2869
|
function findRefsToWithStatement(stmt, symbolId) {
|
|
2740
2870
|
return stmt(
|
|
2741
2871
|
"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 = ?)"
|
|
@@ -3971,6 +4101,20 @@ var IndexStore = class _IndexStore {
|
|
|
3971
4101
|
return false;
|
|
3972
4102
|
}
|
|
3973
4103
|
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Find all symbols that reference the named target symbol (incoming callers).
|
|
4106
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
4107
|
+
*/
|
|
4108
|
+
findIncomingCallsByName(symbolName, file, limit = 100) {
|
|
4109
|
+
return findIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4110
|
+
}
|
|
4111
|
+
/**
|
|
4112
|
+
* Find all symbols that the named source symbol references (outgoing callees).
|
|
4113
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
4114
|
+
*/
|
|
4115
|
+
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
4116
|
+
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4117
|
+
}
|
|
3974
4118
|
/**
|
|
3975
4119
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
3976
4120
|
*/
|
|
@@ -4555,6 +4699,22 @@ function symbolGraphService(args) {
|
|
|
4555
4699
|
indexStorePool.release(store);
|
|
4556
4700
|
}
|
|
4557
4701
|
}
|
|
4702
|
+
function incomingCallsService(args) {
|
|
4703
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
4704
|
+
try {
|
|
4705
|
+
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
4706
|
+
} finally {
|
|
4707
|
+
indexStorePool.release(store);
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
function outgoingCallsService(args) {
|
|
4711
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
4712
|
+
try {
|
|
4713
|
+
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
4714
|
+
} finally {
|
|
4715
|
+
indexStorePool.release(store);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4558
4718
|
|
|
4559
4719
|
// src/codebase-index/project-server-client.ts
|
|
4560
4720
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -5456,6 +5616,10 @@ async function callInline(op, args, opts) {
|
|
|
5456
5616
|
return fileGraphService(args);
|
|
5457
5617
|
case "symbolGraph":
|
|
5458
5618
|
return symbolGraphService(args);
|
|
5619
|
+
case "incomingCalls":
|
|
5620
|
+
return incomingCallsService(args);
|
|
5621
|
+
case "outgoingCalls":
|
|
5622
|
+
return outgoingCallsService(args);
|
|
5459
5623
|
default:
|
|
5460
5624
|
throw new Error(`unknown index op: ${String(op)}`);
|
|
5461
5625
|
}
|