repowisestage 0.0.47 → 0.0.49
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 +293 -141
- 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
|
});
|
|
@@ -4106,9 +4129,9 @@ async function isLocallyConsented(flagPath2) {
|
|
|
4106
4129
|
try {
|
|
4107
4130
|
const body = await readFile11(flagPath2, "utf-8");
|
|
4108
4131
|
const parsed = JSON.parse(body);
|
|
4109
|
-
return
|
|
4132
|
+
return parsed.enabled !== false;
|
|
4110
4133
|
} catch {
|
|
4111
|
-
return
|
|
4134
|
+
return true;
|
|
4112
4135
|
}
|
|
4113
4136
|
}
|
|
4114
4137
|
|
|
@@ -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) => {
|
|
@@ -5547,8 +5665,10 @@ async function startMcp(options) {
|
|
|
5547
5665
|
const mcpLogger = createMcpLogger({ filePath: logFilePath, keyStore });
|
|
5548
5666
|
const flagFilePath = join18(mcpHome, "mcp-log.flag");
|
|
5549
5667
|
const watermarkFilePath = join18(mcpHome, "mcp-log.watermark");
|
|
5668
|
+
const consentSentMarkerPath = join18(mcpHome, "mcp-log.consent-sent");
|
|
5550
5669
|
let uploader = null;
|
|
5551
5670
|
let lastConsentState = false;
|
|
5671
|
+
let serverConsentEnsuredThisProcess = false;
|
|
5552
5672
|
function ensureUploader() {
|
|
5553
5673
|
if (uploader)
|
|
5554
5674
|
return uploader;
|
|
@@ -5624,6 +5744,15 @@ async function startMcp(options) {
|
|
|
5624
5744
|
lastConsentState = false;
|
|
5625
5745
|
return { uploaded: 0, skipped: "no-consent" };
|
|
5626
5746
|
}
|
|
5747
|
+
if (!serverConsentEnsuredThisProcess && await logFileExists(logFilePath)) {
|
|
5748
|
+
await ensureServerConsent({
|
|
5749
|
+
apiUrl: options.repos.find((r) => Boolean(r.apiUrl))?.apiUrl ?? options.repos[0]?.apiUrl ?? "",
|
|
5750
|
+
getAuthToken: options.getAuthToken,
|
|
5751
|
+
markerPath: consentSentMarkerPath,
|
|
5752
|
+
fetchImpl: options.fetchImpl ?? fetch
|
|
5753
|
+
});
|
|
5754
|
+
serverConsentEnsuredThisProcess = true;
|
|
5755
|
+
}
|
|
5627
5756
|
const u = ensureUploader();
|
|
5628
5757
|
if (!lastConsentState) {
|
|
5629
5758
|
u.resetConsentLatch();
|
|
@@ -5692,6 +5821,52 @@ function defaultGraphsDir() {
|
|
|
5692
5821
|
function defaultMcpHome() {
|
|
5693
5822
|
return getConfigDir();
|
|
5694
5823
|
}
|
|
5824
|
+
async function logFileExists(path) {
|
|
5825
|
+
try {
|
|
5826
|
+
const { stat: stat7 } = await import("fs/promises");
|
|
5827
|
+
const s = await stat7(path);
|
|
5828
|
+
return s.size > 0;
|
|
5829
|
+
} catch {
|
|
5830
|
+
return false;
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
async function ensureServerConsent(opts) {
|
|
5834
|
+
if (!opts.apiUrl)
|
|
5835
|
+
return;
|
|
5836
|
+
try {
|
|
5837
|
+
const { readFile: readFile18 } = await import("fs/promises");
|
|
5838
|
+
await readFile18(opts.markerPath, "utf-8");
|
|
5839
|
+
return;
|
|
5840
|
+
} catch {
|
|
5841
|
+
}
|
|
5842
|
+
let token;
|
|
5843
|
+
try {
|
|
5844
|
+
token = await opts.getAuthToken();
|
|
5845
|
+
} catch {
|
|
5846
|
+
return;
|
|
5847
|
+
}
|
|
5848
|
+
if (!token)
|
|
5849
|
+
return;
|
|
5850
|
+
try {
|
|
5851
|
+
const res = await opts.fetchImpl(`${opts.apiUrl}/v1/account/mcp-log/consent`, {
|
|
5852
|
+
method: "POST",
|
|
5853
|
+
headers: {
|
|
5854
|
+
"Content-Type": "application/json",
|
|
5855
|
+
Authorization: `Bearer ${token}`
|
|
5856
|
+
}
|
|
5857
|
+
});
|
|
5858
|
+
if (!res.ok)
|
|
5859
|
+
return;
|
|
5860
|
+
const fs18 = await import("fs/promises");
|
|
5861
|
+
const path = await import("path");
|
|
5862
|
+
await fs18.mkdir(path.dirname(opts.markerPath), { recursive: true });
|
|
5863
|
+
await fs18.writeFile(opts.markerPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", {
|
|
5864
|
+
encoding: "utf-8",
|
|
5865
|
+
mode: 384
|
|
5866
|
+
});
|
|
5867
|
+
} catch {
|
|
5868
|
+
}
|
|
5869
|
+
}
|
|
5695
5870
|
|
|
5696
5871
|
// ../listener/dist/mcp/auto-config/index.js
|
|
5697
5872
|
import { homedir as homedir4 } from "os";
|
|
@@ -9598,86 +9773,40 @@ function flagPath() {
|
|
|
9598
9773
|
function logPath() {
|
|
9599
9774
|
return join38(getConfigDir2(), LOG_FILE);
|
|
9600
9775
|
}
|
|
9601
|
-
async function readFlag() {
|
|
9602
|
-
try {
|
|
9603
|
-
const body = await readFile17(flagPath(), "utf-8");
|
|
9604
|
-
return JSON.parse(body);
|
|
9605
|
-
} catch {
|
|
9606
|
-
return {
|
|
9607
|
-
enabled: false,
|
|
9608
|
-
consentGrantedAt: null,
|
|
9609
|
-
consentSentToServer: false
|
|
9610
|
-
};
|
|
9611
|
-
}
|
|
9612
|
-
}
|
|
9613
9776
|
async function writeFlag(flag) {
|
|
9614
9777
|
const path = flagPath();
|
|
9615
9778
|
await mkdir18(dirname17(path), { recursive: true });
|
|
9616
9779
|
await writeFile18(path, JSON.stringify(flag, null, 2), { encoding: "utf-8", mode: 384 });
|
|
9617
9780
|
}
|
|
9618
|
-
async function
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9781
|
+
async function mcpLogOn() {
|
|
9782
|
+
let flagOnDisk = null;
|
|
9783
|
+
try {
|
|
9784
|
+
flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
|
|
9785
|
+
} catch {
|
|
9786
|
+
flagOnDisk = null;
|
|
9622
9787
|
}
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
of every MCP query the listener serves locally. The data stays on
|
|
9628
|
-
your machine unless you also run \`repowise mcp-log upload\` (future).
|
|
9629
|
-
|
|
9630
|
-
Consent is permanent \u2014 only full account deletion removes it.
|
|
9631
|
-
\`repowise mcp-log off\` stops local logging without revoking consent.
|
|
9632
|
-
`);
|
|
9633
|
-
return await promptYesNo("Grant permanent consent and enable logging? [y/N] ");
|
|
9634
|
-
}
|
|
9635
|
-
async function promptYesNo(prompt) {
|
|
9636
|
-
process.stdout.write(prompt);
|
|
9637
|
-
return await new Promise((resolve4) => {
|
|
9638
|
-
const onData = (chunk) => {
|
|
9639
|
-
const answer = chunk.toString("utf-8").trim().toLowerCase();
|
|
9640
|
-
process.stdin.off("data", onData);
|
|
9641
|
-
if (typeof process.stdin.pause === "function") {
|
|
9642
|
-
process.stdin.pause();
|
|
9643
|
-
}
|
|
9644
|
-
resolve4(answer === "y" || answer === "yes");
|
|
9788
|
+
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
9789
|
+
const next = {
|
|
9790
|
+
...flagOnDisk,
|
|
9791
|
+
enabled: true
|
|
9645
9792
|
};
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
}
|
|
9650
|
-
});
|
|
9651
|
-
}
|
|
9652
|
-
async function mcpLogOn() {
|
|
9653
|
-
const flag = await readFlag();
|
|
9654
|
-
if (flag.enabled && flag.consentSentToServer) {
|
|
9655
|
-
process.stderr.write("MCP logging is already enabled.\n");
|
|
9793
|
+
await writeFlag(next);
|
|
9794
|
+
process.stderr.write(`MCP logging re-enabled. Log file: ${logPath()}
|
|
9795
|
+
`);
|
|
9656
9796
|
return;
|
|
9657
9797
|
}
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
if (!consented) {
|
|
9661
|
-
process.stderr.write("Consent not granted. MCP logging remains disabled.\n");
|
|
9662
|
-
process.exitCode = 1;
|
|
9663
|
-
return;
|
|
9664
|
-
}
|
|
9665
|
-
flag.consentGrantedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9666
|
-
}
|
|
9667
|
-
flag.enabled = true;
|
|
9668
|
-
if (!flag.consentSentToServer) {
|
|
9798
|
+
process.stderr.write("MCP logging is already enabled.\n");
|
|
9799
|
+
if (!flagOnDisk || !flagOnDisk.consentSentToServer) {
|
|
9669
9800
|
const synced = await trySendConsentToServer();
|
|
9670
9801
|
if (synced) {
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9802
|
+
const next = {
|
|
9803
|
+
enabled: true,
|
|
9804
|
+
consentGrantedAt: flagOnDisk?.consentGrantedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9805
|
+
consentSentToServer: true
|
|
9806
|
+
};
|
|
9807
|
+
await writeFlag(next);
|
|
9676
9808
|
}
|
|
9677
9809
|
}
|
|
9678
|
-
await writeFlag(flag);
|
|
9679
|
-
process.stderr.write(`MCP logging enabled. Log file: ${logPath()}
|
|
9680
|
-
`);
|
|
9681
9810
|
}
|
|
9682
9811
|
async function trySendConsentToServer() {
|
|
9683
9812
|
let apiUrl = null;
|
|
@@ -9711,25 +9840,42 @@ async function trySendConsentToServer() {
|
|
|
9711
9840
|
}
|
|
9712
9841
|
}
|
|
9713
9842
|
async function mcpLogOff() {
|
|
9714
|
-
|
|
9715
|
-
|
|
9843
|
+
let flagOnDisk = null;
|
|
9844
|
+
try {
|
|
9845
|
+
flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
|
|
9846
|
+
} catch {
|
|
9847
|
+
flagOnDisk = null;
|
|
9848
|
+
}
|
|
9849
|
+
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
9716
9850
|
process.stderr.write("MCP logging is already disabled.\n");
|
|
9717
9851
|
process.exitCode = 1;
|
|
9718
9852
|
return;
|
|
9719
9853
|
}
|
|
9720
|
-
|
|
9721
|
-
|
|
9854
|
+
const next = {
|
|
9855
|
+
enabled: false,
|
|
9856
|
+
consentGrantedAt: flagOnDisk?.consentGrantedAt ?? null,
|
|
9857
|
+
consentSentToServer: flagOnDisk?.consentSentToServer ?? false
|
|
9858
|
+
};
|
|
9859
|
+
await writeFlag(next);
|
|
9722
9860
|
process.stderr.write(
|
|
9723
9861
|
"MCP logging disabled. Local log file is preserved; consent record is NOT revoked.\n"
|
|
9724
9862
|
);
|
|
9725
9863
|
}
|
|
9726
9864
|
async function mcpLogStatus() {
|
|
9727
|
-
|
|
9865
|
+
let flagOnDisk = null;
|
|
9866
|
+
try {
|
|
9867
|
+
flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
|
|
9868
|
+
} catch {
|
|
9869
|
+
flagOnDisk = null;
|
|
9870
|
+
}
|
|
9871
|
+
const enabled = !flagOnDisk || flagOnDisk.enabled !== false;
|
|
9872
|
+
const enabledLabel = flagOnDisk == null ? "yes (default)" : flagOnDisk.enabled ? "yes" : "no";
|
|
9873
|
+
void enabled;
|
|
9728
9874
|
process.stderr.write("MCP Log Status\n");
|
|
9729
9875
|
process.stderr.write("==============\n");
|
|
9730
|
-
process.stderr.write(`Enabled: ${
|
|
9876
|
+
process.stderr.write(`Enabled: ${enabledLabel}
|
|
9731
9877
|
`);
|
|
9732
|
-
process.stderr.write(`Consent granted: ${
|
|
9878
|
+
process.stderr.write(`Consent granted: ${flagOnDisk?.consentGrantedAt ?? "never"}
|
|
9733
9879
|
`);
|
|
9734
9880
|
process.stderr.write(`Log file: ${logPath()}
|
|
9735
9881
|
`);
|
|
@@ -9757,11 +9903,15 @@ function formatBytes(n) {
|
|
|
9757
9903
|
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
9758
9904
|
}
|
|
9759
9905
|
async function mcpLogViewingFlags(flags = {}) {
|
|
9760
|
-
|
|
9761
|
-
|
|
9906
|
+
let flagOnDisk = null;
|
|
9907
|
+
try {
|
|
9908
|
+
flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
|
|
9909
|
+
} catch {
|
|
9910
|
+
flagOnDisk = null;
|
|
9911
|
+
}
|
|
9912
|
+
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
9762
9913
|
process.stderr.write(
|
|
9763
|
-
`MCP
|
|
9764
|
-
Run \`repowise mcp-log on\` to grant consent and view them.
|
|
9914
|
+
`MCP logging is disabled. Run \`repowise mcp-log on\` to re-enable and view logs.
|
|
9765
9915
|
`
|
|
9766
9916
|
);
|
|
9767
9917
|
return;
|
|
@@ -10738,11 +10888,13 @@ async function routeMessage(endpoint, repoId, message, postJson, getJson, header
|
|
|
10738
10888
|
sessionState.sessionId = fresh;
|
|
10739
10889
|
result = await callTool(fresh);
|
|
10740
10890
|
}
|
|
10891
|
+
const inner = result ?? {};
|
|
10741
10892
|
return {
|
|
10742
10893
|
jsonrpc: "2.0",
|
|
10743
10894
|
id: rpcId,
|
|
10744
10895
|
result: {
|
|
10745
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
10896
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
10897
|
+
...inner.isError === true ? { isError: true } : {}
|
|
10746
10898
|
}
|
|
10747
10899
|
};
|
|
10748
10900
|
}
|