repowisestage 0.0.47 → 0.0.48
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/bin/repowise.js +181 -61
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -1316,6 +1316,7 @@ var init_sidecar_client = __esm({
|
|
|
1316
1316
|
// ../listener/dist/lsp/lsp-tools.js
|
|
1317
1317
|
var lsp_tools_exports = {};
|
|
1318
1318
|
__export(lsp_tools_exports, {
|
|
1319
|
+
emptyLspResult: () => emptyLspResult,
|
|
1319
1320
|
lspCallHierarchy: () => lspCallHierarchy,
|
|
1320
1321
|
lspDefinition: () => lspDefinition,
|
|
1321
1322
|
lspDocumentSymbol: () => lspDocumentSymbol,
|
|
@@ -1357,6 +1358,25 @@ function pickAvailableConfig(configs, isAvailable) {
|
|
|
1357
1358
|
}
|
|
1358
1359
|
return null;
|
|
1359
1360
|
}
|
|
1361
|
+
function emptyLspResult(reason, details) {
|
|
1362
|
+
if (reason === "unsupported-language") {
|
|
1363
|
+
return {
|
|
1364
|
+
ok: false,
|
|
1365
|
+
reason,
|
|
1366
|
+
isError: true,
|
|
1367
|
+
error: "unsupported-language" + (details?.language ? `: ${details.language}` : "") + " \u2014 no LSP server registered for this file extension.",
|
|
1368
|
+
hint: "RepoWise auto-installs LSP servers for known languages (TS/JS, Python, Go, Rust, Ruby, etc.). For an unsupported language fall back to graph-only tools: `find_symbol`, `find_callers`, `find_references`, `get_call_graph`."
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
const triedFrag = details?.tried && details.tried.length > 0 ? ` (tried: ${details.tried.join(", ")})` : "";
|
|
1372
|
+
return {
|
|
1373
|
+
ok: false,
|
|
1374
|
+
reason,
|
|
1375
|
+
isError: true,
|
|
1376
|
+
error: `no-server-available${triedFrag} \u2014 the LSP binary for this language isn't on PATH or in the listener's install dir.`,
|
|
1377
|
+
hint: "The listener auto-installs npm-based LSP servers (pyright, typescript-language-server, etc.) on boot. If the install failed, check listener stderr for the underlying npm error. Non-npm servers (gopls, rust-analyzer, sourcekit-lsp, ruby-lsp) need manual install \u2014 see the listener log for the per-language hint. Until then, use graph-only tools (`find_symbol`, `find_callers`, etc.) for this file."
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1360
1380
|
function toOneIndexed(pos) {
|
|
1361
1381
|
return { line: pos.line + 1, column: pos.character + 1 };
|
|
1362
1382
|
}
|
|
@@ -1419,13 +1439,13 @@ function normalizeLocations(raw, repoRoot) {
|
|
|
1419
1439
|
async function getSession(deps, file) {
|
|
1420
1440
|
const language = detectLanguage(file);
|
|
1421
1441
|
if (!language)
|
|
1422
|
-
return
|
|
1442
|
+
return emptyLspResult("unsupported-language");
|
|
1423
1443
|
const configs = LSP_REGISTRY[language];
|
|
1424
1444
|
const config2 = pickAvailableConfig(configs, deps.binaryAvailable);
|
|
1425
1445
|
if (!config2) {
|
|
1426
|
-
const tried = configs.map((c) => c.command)
|
|
1427
|
-
console.warn(`[lsp:${language}] no server available on PATH \u2014 tried: ${tried}`);
|
|
1428
|
-
return
|
|
1446
|
+
const tried = configs.map((c) => c.command);
|
|
1447
|
+
console.warn(`[lsp:${language}] no server available on PATH \u2014 tried: ${tried.join(", ")}`);
|
|
1448
|
+
return emptyLspResult("no-server-available", { language, tried });
|
|
1429
1449
|
}
|
|
1430
1450
|
const session = await deps.workspaces.getOrOpen({
|
|
1431
1451
|
repoRoot: deps.repoRoot,
|
|
@@ -1438,7 +1458,7 @@ async function getSession(deps, file) {
|
|
|
1438
1458
|
async function lspDefinition(deps, req) {
|
|
1439
1459
|
const got = await getSession(deps, req.file);
|
|
1440
1460
|
if (!got.ok)
|
|
1441
|
-
return
|
|
1461
|
+
return got;
|
|
1442
1462
|
const result = await got.session.client.request("textDocument/definition", {
|
|
1443
1463
|
textDocument: { uri: got.uri },
|
|
1444
1464
|
position: toZeroIndexed(req.position)
|
|
@@ -1448,7 +1468,7 @@ async function lspDefinition(deps, req) {
|
|
|
1448
1468
|
async function lspReferences(deps, req) {
|
|
1449
1469
|
const got = await getSession(deps, req.file);
|
|
1450
1470
|
if (!got.ok)
|
|
1451
|
-
return
|
|
1471
|
+
return got;
|
|
1452
1472
|
const result = await got.session.client.request("textDocument/references", {
|
|
1453
1473
|
textDocument: { uri: got.uri },
|
|
1454
1474
|
position: toZeroIndexed(req.position),
|
|
@@ -1459,7 +1479,7 @@ async function lspReferences(deps, req) {
|
|
|
1459
1479
|
async function lspHover(deps, req) {
|
|
1460
1480
|
const got = await getSession(deps, req.file);
|
|
1461
1481
|
if (!got.ok)
|
|
1462
|
-
return
|
|
1482
|
+
return got;
|
|
1463
1483
|
const result = await got.session.client.request("textDocument/hover", {
|
|
1464
1484
|
textDocument: { uri: got.uri },
|
|
1465
1485
|
position: toZeroIndexed(req.position)
|
|
@@ -1502,7 +1522,7 @@ function normalizeHoverContents(contents) {
|
|
|
1502
1522
|
async function lspCallHierarchy(deps, req) {
|
|
1503
1523
|
const got = await getSession(deps, req.file);
|
|
1504
1524
|
if (!got.ok)
|
|
1505
|
-
return
|
|
1525
|
+
return got;
|
|
1506
1526
|
const limit = req.limit ?? 200;
|
|
1507
1527
|
const prepared = await got.session.client.request("textDocument/prepareCallHierarchy", {
|
|
1508
1528
|
textDocument: { uri: got.uri },
|
|
@@ -1541,7 +1561,7 @@ async function lspCallHierarchy(deps, req) {
|
|
|
1541
1561
|
async function lspImplementation(deps, req) {
|
|
1542
1562
|
const got = await getSession(deps, req.file);
|
|
1543
1563
|
if (!got.ok)
|
|
1544
|
-
return
|
|
1564
|
+
return got;
|
|
1545
1565
|
const result = await got.session.client.request("textDocument/implementation", {
|
|
1546
1566
|
textDocument: { uri: got.uri },
|
|
1547
1567
|
position: toZeroIndexed(req.position)
|
|
@@ -1551,7 +1571,7 @@ async function lspImplementation(deps, req) {
|
|
|
1551
1571
|
async function lspTypeHierarchy(deps, req) {
|
|
1552
1572
|
const got = await getSession(deps, req.file);
|
|
1553
1573
|
if (!got.ok)
|
|
1554
|
-
return
|
|
1574
|
+
return got;
|
|
1555
1575
|
const limit = req.limit ?? 200;
|
|
1556
1576
|
const prepared = await got.session.client.request("textDocument/prepareTypeHierarchy", {
|
|
1557
1577
|
textDocument: { uri: got.uri },
|
|
@@ -1587,8 +1607,11 @@ async function lspTypeHierarchy(deps, req) {
|
|
|
1587
1607
|
}
|
|
1588
1608
|
async function lspWorkspaceSymbol(deps, req) {
|
|
1589
1609
|
const sessions = deps.workspaces.activeSessions().filter((s) => !req.language || s.language === req.language);
|
|
1590
|
-
if (sessions.length === 0)
|
|
1591
|
-
return
|
|
1610
|
+
if (sessions.length === 0) {
|
|
1611
|
+
return emptyLspResult("no-server-available", {
|
|
1612
|
+
language: req.language
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1592
1615
|
const symbols = [];
|
|
1593
1616
|
for (const session of sessions) {
|
|
1594
1617
|
const raw = await session.client.request("workspace/symbol", { query: req.query });
|
|
@@ -1616,7 +1639,7 @@ async function lspWorkspaceSymbol(deps, req) {
|
|
|
1616
1639
|
async function lspDocumentSymbol(deps, req) {
|
|
1617
1640
|
const got = await getSession(deps, req.file);
|
|
1618
1641
|
if (!got.ok)
|
|
1619
|
-
return
|
|
1642
|
+
return got;
|
|
1620
1643
|
const raw = await got.session.client.request("textDocument/documentSymbol", {
|
|
1621
1644
|
textDocument: { uri: got.uri }
|
|
1622
1645
|
});
|
|
@@ -4333,8 +4356,8 @@ var TOOL_CATALOG = [
|
|
|
4333
4356
|
},
|
|
4334
4357
|
{
|
|
4335
4358
|
name: "search_pattern",
|
|
4336
|
-
description: "
|
|
4337
|
-
whenToUse: 'Use when the user asks for a pattern rather than a literal substring \u2014 e.g. "anything ending in `Repository`", "every `handle*` function". For plain substring search use `find_symbol`.',
|
|
4359
|
+
description: "JavaScript-flavor regex match against symbol names. Uses `new RegExp(pattern, flags)` \u2014 no Python-only constructs (`(?P<name>...)`, `(?i)`-style inline flags, `\\A` / `\\Z` anchors).",
|
|
4360
|
+
whenToUse: 'Use when the user asks for a pattern rather than a literal substring \u2014 e.g. "anything ending in `Repository`", "every `handle*` function". For plain substring search use `find_symbol`. For case-insensitive matching set `flags: "i"`, NOT a `(?i)` prefix.',
|
|
4338
4361
|
inputSchema: {
|
|
4339
4362
|
type: "object",
|
|
4340
4363
|
properties: {
|
|
@@ -4801,16 +4824,31 @@ function listEdges(graphJson, req) {
|
|
|
4801
4824
|
freshness: freshnessEnvelope(graph)
|
|
4802
4825
|
};
|
|
4803
4826
|
}
|
|
4827
|
+
function detectCommonRegexMistake(pattern) {
|
|
4828
|
+
if (/\(\?P</.test(pattern)) {
|
|
4829
|
+
return "Python named groups `(?P<name>\u2026)` aren't supported. Use `(?<name>\u2026)` (JS named-group syntax) or just `(\u2026)`.";
|
|
4830
|
+
}
|
|
4831
|
+
if (/^\(\?[ims]+\)/.test(pattern)) {
|
|
4832
|
+
return 'Python-style inline flags `(?i)` aren\'t supported. Pass them as the `flags` parameter, e.g. `flags: "i"`.';
|
|
4833
|
+
}
|
|
4834
|
+
if (/\\A|\\Z/.test(pattern)) {
|
|
4835
|
+
return "Python anchors `\\A` / `\\Z` are valid JS regex but match LITERAL `A` / `Z`, not start-of-string / end-of-string. Use `^` / `$` (JS anchors) with the `m` flag for multiline.";
|
|
4836
|
+
}
|
|
4837
|
+
return void 0;
|
|
4838
|
+
}
|
|
4804
4839
|
var MAX_PATTERN_LENGTH = 200;
|
|
4805
4840
|
var REDOS_PATTERN = /(\([^)]*[+*]\)|\[[^\]]*[+*]\])[+*?]/;
|
|
4806
4841
|
function searchPattern(graphJson, req) {
|
|
4807
4842
|
const graph = graphJson;
|
|
4808
4843
|
if (!req.pattern || req.pattern.length > MAX_PATTERN_LENGTH) {
|
|
4844
|
+
const reason = !req.pattern ? "Empty pattern \u2014 pass a non-empty regex string." : `Pattern exceeds ${MAX_PATTERN_LENGTH.toString()} chars (${req.pattern.length.toString()} chars). Use a tighter expression or filter via \`kind\` / \`language\` / \`file\`.`;
|
|
4809
4845
|
return {
|
|
4810
4846
|
matches: [],
|
|
4811
4847
|
coverage: coverageEnvelope(graph, 0),
|
|
4812
4848
|
freshness: freshnessEnvelope(graph),
|
|
4813
|
-
unsafePattern: true
|
|
4849
|
+
unsafePattern: true,
|
|
4850
|
+
error: reason,
|
|
4851
|
+
isError: true
|
|
4814
4852
|
};
|
|
4815
4853
|
}
|
|
4816
4854
|
if (REDOS_PATTERN.test(req.pattern)) {
|
|
@@ -4818,19 +4856,26 @@ function searchPattern(graphJson, req) {
|
|
|
4818
4856
|
matches: [],
|
|
4819
4857
|
coverage: coverageEnvelope(graph, 0),
|
|
4820
4858
|
freshness: freshnessEnvelope(graph),
|
|
4821
|
-
unsafePattern: true
|
|
4859
|
+
unsafePattern: true,
|
|
4860
|
+
error: "Pattern rejected by ReDoS guard \u2014 nested quantifiers like `(a+)+` or `[\u2026]*+` can cause catastrophic backtracking. Flatten the quantifiers or split into multiple calls.",
|
|
4861
|
+
isError: true
|
|
4822
4862
|
};
|
|
4823
4863
|
}
|
|
4824
4864
|
const flags = (req.flags ?? "").replace(/g/g, "");
|
|
4825
4865
|
let re;
|
|
4826
4866
|
try {
|
|
4827
4867
|
re = new RegExp(req.pattern, flags);
|
|
4828
|
-
} catch {
|
|
4868
|
+
} catch (err) {
|
|
4869
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4870
|
+
const hint = detectCommonRegexMistake(req.pattern);
|
|
4829
4871
|
return {
|
|
4830
4872
|
matches: [],
|
|
4831
4873
|
coverage: coverageEnvelope(graph, 0),
|
|
4832
4874
|
freshness: freshnessEnvelope(graph),
|
|
4833
|
-
invalidPattern: true
|
|
4875
|
+
invalidPattern: true,
|
|
4876
|
+
error: msg,
|
|
4877
|
+
...hint ? { hint } : {},
|
|
4878
|
+
isError: true
|
|
4834
4879
|
};
|
|
4835
4880
|
}
|
|
4836
4881
|
const kinds = normaliseFilter(req.kind);
|
|
@@ -4866,10 +4911,12 @@ function searchPattern(graphJson, req) {
|
|
|
4866
4911
|
return bScore - aScore;
|
|
4867
4912
|
return a.id.localeCompare(b.id);
|
|
4868
4913
|
});
|
|
4914
|
+
const proactiveHint = matches.length === 0 ? detectCommonRegexMistake(req.pattern) : void 0;
|
|
4869
4915
|
return {
|
|
4870
4916
|
matches: matches.slice(0, limit),
|
|
4871
4917
|
coverage: coverageEnvelope(graph, matches.length),
|
|
4872
|
-
freshness: freshnessEnvelope(graph)
|
|
4918
|
+
freshness: freshnessEnvelope(graph),
|
|
4919
|
+
...proactiveHint ? { hint: proactiveHint } : {}
|
|
4873
4920
|
};
|
|
4874
4921
|
}
|
|
4875
4922
|
function batchQuery(graphJson, req) {
|
|
@@ -4893,11 +4940,7 @@ function batchQuery(graphJson, req) {
|
|
|
4893
4940
|
try {
|
|
4894
4941
|
switch (toolName) {
|
|
4895
4942
|
case "find_symbol":
|
|
4896
|
-
return {
|
|
4897
|
-
ok: true,
|
|
4898
|
-
tool: toolName,
|
|
4899
|
-
result: findSymbol(graph, args)
|
|
4900
|
-
};
|
|
4943
|
+
return { ok: true, tool: toolName, result: findSymbol(graph, args) };
|
|
4901
4944
|
case "get_symbol":
|
|
4902
4945
|
return { ok: true, tool: toolName, result: getSymbol(graph, args) };
|
|
4903
4946
|
case "get_impact":
|
|
@@ -4910,11 +4953,47 @@ function batchQuery(graphJson, req) {
|
|
|
4910
4953
|
tool: toolName,
|
|
4911
4954
|
result: searchPattern(graph, args)
|
|
4912
4955
|
};
|
|
4956
|
+
// The 7 graph tools below were advertised by the schema's
|
|
4957
|
+
// whenToUse but missing from the switch — Claude calling
|
|
4958
|
+
// batch_query with `find_callers` etc. used to hit the
|
|
4959
|
+
// "unsupported sub-query tool" branch and bail. Wired up here
|
|
4960
|
+
// so the catalog promise matches the implementation.
|
|
4961
|
+
case "find_callers":
|
|
4962
|
+
return {
|
|
4963
|
+
ok: true,
|
|
4964
|
+
tool: toolName,
|
|
4965
|
+
result: findCallers(graph, args)
|
|
4966
|
+
};
|
|
4967
|
+
case "find_references":
|
|
4968
|
+
return {
|
|
4969
|
+
ok: true,
|
|
4970
|
+
tool: toolName,
|
|
4971
|
+
result: findReferences(graph, args)
|
|
4972
|
+
};
|
|
4973
|
+
case "get_deps":
|
|
4974
|
+
return { ok: true, tool: toolName, result: getDeps(graph, args) };
|
|
4975
|
+
case "get_call_graph":
|
|
4976
|
+
return {
|
|
4977
|
+
ok: true,
|
|
4978
|
+
tool: toolName,
|
|
4979
|
+
result: getCallGraph(graph, args)
|
|
4980
|
+
};
|
|
4981
|
+
case "find_tests_for_symbol":
|
|
4982
|
+
return {
|
|
4983
|
+
ok: true,
|
|
4984
|
+
tool: toolName,
|
|
4985
|
+
result: findTestsForSymbol(graph, args)
|
|
4986
|
+
};
|
|
4987
|
+
case "get_todos":
|
|
4988
|
+
return { ok: true, tool: toolName, result: getTodos(graph, args) };
|
|
4989
|
+
case "get_freshness":
|
|
4990
|
+
return { ok: true, tool: toolName, result: getFreshness(graph) };
|
|
4913
4991
|
default:
|
|
4914
4992
|
return {
|
|
4915
4993
|
ok: false,
|
|
4916
4994
|
tool: toolName,
|
|
4917
|
-
error: `unsupported sub-query tool: ${String(toolName)}
|
|
4995
|
+
error: `unsupported sub-query tool: ${String(toolName)}`,
|
|
4996
|
+
hint: `Supported tools: find_symbol, get_symbol, get_impact, list_edges, search_pattern, find_callers, find_references, get_deps, get_call_graph, find_tests_for_symbol, get_todos, get_freshness.`
|
|
4918
4997
|
};
|
|
4919
4998
|
}
|
|
4920
4999
|
} catch (err) {
|
|
@@ -4925,7 +5004,15 @@ function batchQuery(graphJson, req) {
|
|
|
4925
5004
|
};
|
|
4926
5005
|
}
|
|
4927
5006
|
});
|
|
4928
|
-
|
|
5007
|
+
const failedCount = results.filter((r) => !r.ok).length;
|
|
5008
|
+
const isError = queries.length > 0 && failedCount === queries.length;
|
|
5009
|
+
return {
|
|
5010
|
+
results,
|
|
5011
|
+
freshness: freshnessEnvelope(graph),
|
|
5012
|
+
droppedCount,
|
|
5013
|
+
failedCount,
|
|
5014
|
+
...isError ? { isError: true } : {}
|
|
5015
|
+
};
|
|
4929
5016
|
}
|
|
4930
5017
|
function findCallers(graphJson, req) {
|
|
4931
5018
|
const graph = graphJson;
|
|
@@ -5181,62 +5268,73 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
5181
5268
|
}).catch((err) => sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) }));
|
|
5182
5269
|
return;
|
|
5183
5270
|
}
|
|
5184
|
-
function validationError(
|
|
5185
|
-
return { __validationError:
|
|
5271
|
+
function validationError(error, hint) {
|
|
5272
|
+
return { __validationError: { error, hint } };
|
|
5186
5273
|
}
|
|
5187
5274
|
function isValidationError(v) {
|
|
5188
5275
|
return typeof v === "object" && v !== null && "__validationError" in v;
|
|
5189
5276
|
}
|
|
5277
|
+
const HINT_USE_FIND_SYMBOL_FIRST = "To get a valid `id`, call `find_symbol` (fuzzy name match) or `search_pattern` (regex) first; pass the resulting `matches[].id` here.";
|
|
5190
5278
|
const toolDispatch = {
|
|
5191
5279
|
"/mcp/tools/find_symbol": (g, b) => {
|
|
5192
|
-
if (typeof b["query"] !== "string")
|
|
5193
|
-
return validationError("query required (string)");
|
|
5280
|
+
if (typeof b["query"] !== "string") {
|
|
5281
|
+
return validationError("query required (string)", "Pass a name or substring to fuzzy-match against. For regex use `search_pattern`.");
|
|
5282
|
+
}
|
|
5194
5283
|
return findSymbol(g, b);
|
|
5195
5284
|
},
|
|
5196
5285
|
"/mcp/tools/get_symbol": (g, b) => {
|
|
5197
|
-
if (typeof b["id"] !== "string")
|
|
5198
|
-
return validationError("id required (string)");
|
|
5286
|
+
if (typeof b["id"] !== "string") {
|
|
5287
|
+
return validationError("id required (string)", HINT_USE_FIND_SYMBOL_FIRST);
|
|
5288
|
+
}
|
|
5199
5289
|
return getSymbol(g, b);
|
|
5200
5290
|
},
|
|
5201
5291
|
"/mcp/tools/get_impact": (g, b) => {
|
|
5202
|
-
if (typeof b["id"] !== "string")
|
|
5203
|
-
return validationError("id required (string)");
|
|
5292
|
+
if (typeof b["id"] !== "string") {
|
|
5293
|
+
return validationError("id required (string)", HINT_USE_FIND_SYMBOL_FIRST);
|
|
5294
|
+
}
|
|
5204
5295
|
return getImpact(g, b);
|
|
5205
5296
|
},
|
|
5206
5297
|
"/mcp/tools/list_edges": (g, b) => listEdges(g, b),
|
|
5207
5298
|
"/mcp/tools/search_pattern": (g, b) => {
|
|
5208
|
-
if (typeof b["pattern"] !== "string")
|
|
5209
|
-
return validationError("pattern required (string)");
|
|
5299
|
+
if (typeof b["pattern"] !== "string") {
|
|
5300
|
+
return validationError("pattern required (string)", "Pattern is a JavaScript regex string (no Python `(?P<>` / `(?i)` syntax). For literal substring search use `find_symbol` instead.");
|
|
5301
|
+
}
|
|
5210
5302
|
return searchPattern(g, b);
|
|
5211
5303
|
},
|
|
5212
5304
|
"/mcp/tools/batch_query": (g, b) => {
|
|
5213
|
-
if (!Array.isArray(b["queries"]))
|
|
5214
|
-
return validationError("queries required (array)");
|
|
5305
|
+
if (!Array.isArray(b["queries"])) {
|
|
5306
|
+
return validationError("queries required (array)", "Pass an array of `{tool, params}` (or `{tool, arguments}`) objects. Up to 10 sub-queries per batch. See the schema for valid `tool` values.");
|
|
5307
|
+
}
|
|
5215
5308
|
return batchQuery(g, b);
|
|
5216
5309
|
},
|
|
5217
5310
|
"/mcp/tools/find_callers": (g, b) => {
|
|
5218
|
-
if (typeof b["id"] !== "string")
|
|
5219
|
-
return validationError("id required (string)");
|
|
5311
|
+
if (typeof b["id"] !== "string") {
|
|
5312
|
+
return validationError("id required (string)", HINT_USE_FIND_SYMBOL_FIRST);
|
|
5313
|
+
}
|
|
5220
5314
|
return findCallers(g, b);
|
|
5221
5315
|
},
|
|
5222
5316
|
"/mcp/tools/find_references": (g, b) => {
|
|
5223
|
-
if (typeof b["id"] !== "string")
|
|
5224
|
-
return validationError("id required (string)");
|
|
5317
|
+
if (typeof b["id"] !== "string") {
|
|
5318
|
+
return validationError("id required (string)", HINT_USE_FIND_SYMBOL_FIRST);
|
|
5319
|
+
}
|
|
5225
5320
|
return findReferences(g, b);
|
|
5226
5321
|
},
|
|
5227
5322
|
"/mcp/tools/get_deps": (g, b) => {
|
|
5228
|
-
if (typeof b["file"] !== "string")
|
|
5229
|
-
return validationError("file required (string)");
|
|
5323
|
+
if (typeof b["file"] !== "string") {
|
|
5324
|
+
return validationError("file required (string)", "Pass a repo-relative file path (e.g., `src/main.py`, NOT an absolute path or symbol id).");
|
|
5325
|
+
}
|
|
5230
5326
|
return getDeps(g, b);
|
|
5231
5327
|
},
|
|
5232
5328
|
"/mcp/tools/get_call_graph": (g, b) => {
|
|
5233
|
-
if (typeof b["id"] !== "string")
|
|
5234
|
-
return validationError("id required (string)");
|
|
5329
|
+
if (typeof b["id"] !== "string") {
|
|
5330
|
+
return validationError("id required (string)", HINT_USE_FIND_SYMBOL_FIRST);
|
|
5331
|
+
}
|
|
5235
5332
|
return getCallGraph(g, b);
|
|
5236
5333
|
},
|
|
5237
5334
|
"/mcp/tools/find_tests_for_symbol": (g, b) => {
|
|
5238
|
-
if (typeof b["id"] !== "string")
|
|
5239
|
-
return validationError("id required (string)");
|
|
5335
|
+
if (typeof b["id"] !== "string") {
|
|
5336
|
+
return validationError("id required (string)", HINT_USE_FIND_SYMBOL_FIRST);
|
|
5337
|
+
}
|
|
5240
5338
|
return findTestsForSymbol(g, b);
|
|
5241
5339
|
},
|
|
5242
5340
|
"/mcp/tools/get_todos": (g, b) => getTodos(g, b),
|
|
@@ -5297,11 +5395,11 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
5297
5395
|
const root = getRepoIdToRoot().get(session.repoId) ?? fallbackRoot;
|
|
5298
5396
|
if (!root) {
|
|
5299
5397
|
return {
|
|
5300
|
-
ok:
|
|
5301
|
-
|
|
5302
|
-
body: {
|
|
5398
|
+
ok: true,
|
|
5399
|
+
value: {
|
|
5303
5400
|
error: "unknown_repo",
|
|
5304
|
-
|
|
5401
|
+
hint: `LSP tool dispatch needs a workspace root for repoId ${session.repoId}, but none is registered. This usually means the listener hasn't yet reconciled the repo's localPath. Use graph-only tools (find_symbol, find_callers, etc.) until reconcile completes.`,
|
|
5402
|
+
isError: true
|
|
5305
5403
|
}
|
|
5306
5404
|
};
|
|
5307
5405
|
}
|
|
@@ -5315,11 +5413,25 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
5315
5413
|
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, async (session, body) => {
|
|
5316
5414
|
const graph = options.graphCache.peek(session.repoId);
|
|
5317
5415
|
if (!graph) {
|
|
5318
|
-
return {
|
|
5416
|
+
return {
|
|
5417
|
+
ok: true,
|
|
5418
|
+
value: {
|
|
5419
|
+
error: "graph_not_loaded",
|
|
5420
|
+
hint: "The graph for this repo is still downloading or has been invalidated. Retry in a few seconds; if the problem persists, the listener may not have completed the initial sync.",
|
|
5421
|
+
isError: true
|
|
5422
|
+
}
|
|
5423
|
+
};
|
|
5319
5424
|
}
|
|
5320
5425
|
const raw = handler(graph, body);
|
|
5321
5426
|
if (isValidationError(raw)) {
|
|
5322
|
-
return {
|
|
5427
|
+
return {
|
|
5428
|
+
ok: true,
|
|
5429
|
+
value: {
|
|
5430
|
+
error: raw.__validationError.error,
|
|
5431
|
+
hint: raw.__validationError.hint,
|
|
5432
|
+
isError: true
|
|
5433
|
+
}
|
|
5434
|
+
};
|
|
5323
5435
|
}
|
|
5324
5436
|
const resolved = await Promise.race([
|
|
5325
5437
|
raw instanceof Promise ? raw : Promise.resolve(raw),
|
|
@@ -5396,12 +5508,18 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
5396
5508
|
});
|
|
5397
5509
|
logToStdout("error", tool, session.repoId, Date.now() - startedAt, safeMsg);
|
|
5398
5510
|
if (isTimeout) {
|
|
5399
|
-
return sendJson(res,
|
|
5511
|
+
return sendJson(res, 200, {
|
|
5512
|
+
error: `tool-timeout: ${tool} exceeded its execution budget`,
|
|
5513
|
+
hint: "The tool was killed at its per-tool timeout. Try a narrower query (smaller limit, depth, or filter) or split into multiple calls.",
|
|
5514
|
+
isError: true,
|
|
5515
|
+
tool
|
|
5516
|
+
});
|
|
5400
5517
|
}
|
|
5401
|
-
return sendJson(res,
|
|
5402
|
-
error:
|
|
5403
|
-
tool,
|
|
5404
|
-
|
|
5518
|
+
return sendJson(res, 200, {
|
|
5519
|
+
error: `tool_error: ${safeMsg}`,
|
|
5520
|
+
hint: "The tool threw an unexpected exception. This is a server-side bug \u2014 file an issue with the tool name and the error text.",
|
|
5521
|
+
isError: true,
|
|
5522
|
+
tool
|
|
5405
5523
|
});
|
|
5406
5524
|
}
|
|
5407
5525
|
}).catch((err) => {
|
|
@@ -10738,11 +10856,13 @@ async function routeMessage(endpoint, repoId, message, postJson, getJson, header
|
|
|
10738
10856
|
sessionState.sessionId = fresh;
|
|
10739
10857
|
result = await callTool(fresh);
|
|
10740
10858
|
}
|
|
10859
|
+
const inner = result ?? {};
|
|
10741
10860
|
return {
|
|
10742
10861
|
jsonrpc: "2.0",
|
|
10743
10862
|
id: rpcId,
|
|
10744
10863
|
result: {
|
|
10745
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
10864
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
10865
|
+
...inner.isError === true ? { isError: true } : {}
|
|
10746
10866
|
}
|
|
10747
10867
|
};
|
|
10748
10868
|
}
|