@wrongstack/plug-lsp 0.7.4 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +294 -68
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { TOKENS, buildChildEnv, atomicWrite } from '@wrongstack/core';
|
|
|
5
5
|
import { pathToFileURL, fileURLToPath } from 'url';
|
|
6
6
|
import { EventEmitter } from 'events';
|
|
7
7
|
import * as fs3 from 'fs';
|
|
8
|
+
import { IndexStore, buildIndexableText, buildBm25Index, tokenise, internalKindToLspKind, lspKindToInternalKind } from '@wrongstack/tools/codebase-index/index';
|
|
8
9
|
|
|
9
10
|
// src/presets.ts
|
|
10
11
|
var PRESETS = {
|
|
@@ -1426,6 +1427,298 @@ function formatActions(actions) {
|
|
|
1426
1427
|
return actions.map((a, i) => `[${i}] ${a.kind ?? "action"} ${a.title}`).join("\n");
|
|
1427
1428
|
}
|
|
1428
1429
|
|
|
1430
|
+
// src/constants.ts
|
|
1431
|
+
var LSP_CONSTANTS = Object.freeze({
|
|
1432
|
+
/** Default timeout for LSP tool operations (5 seconds). */
|
|
1433
|
+
TOOL_TIMEOUT_MS: 5e3
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
// src/formatters/symbols.ts
|
|
1437
|
+
function formatDocumentSymbols(path8, symbols, cwd) {
|
|
1438
|
+
if (!symbols || symbols.length === 0) return "No symbols found.";
|
|
1439
|
+
const lines = [`${displayPath(path8, cwd)}:`];
|
|
1440
|
+
for (const sym of symbols) appendSymbol(lines, sym, 1, cwd);
|
|
1441
|
+
return lines.join("\n");
|
|
1442
|
+
}
|
|
1443
|
+
function formatWorkspaceSymbols(symbols, query, cwd, limit = 100) {
|
|
1444
|
+
if (!symbols || symbols.length === 0) return `No symbols matching "${query}".`;
|
|
1445
|
+
const lines = [`${symbols.length} symbols matching "${query}":`];
|
|
1446
|
+
for (const sym of symbols.slice(0, limit)) {
|
|
1447
|
+
lines.push(
|
|
1448
|
+
` ${kindName(sym.kind)} ${sym.name} ${displayPath(uriToPath(sym.location.uri), cwd)}:${sym.location.range.start.line + 1}`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
if (symbols.length > limit) lines.push(` ... truncated ${symbols.length - limit} more`);
|
|
1452
|
+
return lines.join("\n");
|
|
1453
|
+
}
|
|
1454
|
+
function appendSymbol(lines, sym, depth, cwd) {
|
|
1455
|
+
const indent = " ".repeat(depth);
|
|
1456
|
+
if ("selectionRange" in sym) {
|
|
1457
|
+
lines.push(
|
|
1458
|
+
`${indent}${kindName(sym.kind)} ${sym.name} (L${sym.selectionRange.start.line + 1})`
|
|
1459
|
+
);
|
|
1460
|
+
for (const child of sym.children ?? []) appendSymbol(lines, child, depth + 1, cwd);
|
|
1461
|
+
} else {
|
|
1462
|
+
lines.push(
|
|
1463
|
+
`${indent}${kindName(sym.kind)} ${sym.name} ${displayPath(uriToPath(sym.location.uri), cwd)}:${sym.location.range.start.line + 1}`
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function kindName(kind) {
|
|
1468
|
+
return [
|
|
1469
|
+
"file",
|
|
1470
|
+
"module",
|
|
1471
|
+
"namespace",
|
|
1472
|
+
"package",
|
|
1473
|
+
"class",
|
|
1474
|
+
"method",
|
|
1475
|
+
"property",
|
|
1476
|
+
"field",
|
|
1477
|
+
"constructor",
|
|
1478
|
+
"enum",
|
|
1479
|
+
"interface",
|
|
1480
|
+
"function",
|
|
1481
|
+
"variable",
|
|
1482
|
+
"constant",
|
|
1483
|
+
"string",
|
|
1484
|
+
"number",
|
|
1485
|
+
"boolean",
|
|
1486
|
+
"array",
|
|
1487
|
+
"object",
|
|
1488
|
+
"key",
|
|
1489
|
+
"null",
|
|
1490
|
+
"enumMember",
|
|
1491
|
+
"struct",
|
|
1492
|
+
"event",
|
|
1493
|
+
"operator",
|
|
1494
|
+
"typeParameter"
|
|
1495
|
+
][kind - 1] ?? "symbol";
|
|
1496
|
+
}
|
|
1497
|
+
function formatCodebaseLspResults(output, cwd) {
|
|
1498
|
+
const { results, totalIndex, totalLsp, query, usedIndex, usedLsp } = output;
|
|
1499
|
+
if (results.length === 0) {
|
|
1500
|
+
const sources = [];
|
|
1501
|
+
if (usedIndex) sources.push(`index(${totalIndex})`);
|
|
1502
|
+
if (usedLsp) sources.push(`lsp(${totalLsp})`);
|
|
1503
|
+
return `No symbols matching "${query}". Searched: ${sources.join(", ") || "none"}.`;
|
|
1504
|
+
}
|
|
1505
|
+
const lines = [];
|
|
1506
|
+
lines.push(`${results.length} results for "${query}" (index:${totalIndex} lsp:${totalLsp}):`);
|
|
1507
|
+
for (const r of results) {
|
|
1508
|
+
const sourceTag = r.source === "index" ? "[index]" : `[lsp:${r.server ?? "?"}]`;
|
|
1509
|
+
const scoreTag = r.score !== void 0 ? ` score=${r.score.toFixed(2)}` : "";
|
|
1510
|
+
const location = `${displayPath(r.file, cwd)}:${r.line}`;
|
|
1511
|
+
if (r.snippet) {
|
|
1512
|
+
lines.push(` ${sourceTag} ${r.kind} ${r.name} ${location}${scoreTag}`);
|
|
1513
|
+
lines.push(` ${r.snippet}`);
|
|
1514
|
+
} else {
|
|
1515
|
+
lines.push(` ${sourceTag} ${r.kind} ${r.name} ${location}${scoreTag}`);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return lines.join("\n");
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// src/tools/codebase-lsp-search.ts
|
|
1522
|
+
function createCodebaseLspSearchTool(deps) {
|
|
1523
|
+
return {
|
|
1524
|
+
name: "codebase-lsp-search",
|
|
1525
|
+
description: "Search code symbols using a fast SQLite+BM25 index, falling back to live LSP workspaceSymbol queries when needed.",
|
|
1526
|
+
usageHint: "Pass `query` to search. Use `limit` (default 20) to cap results. Set `preferLsp=true` to skip the index and query LSP servers directly for live precision.",
|
|
1527
|
+
inputSchema: {
|
|
1528
|
+
type: "object",
|
|
1529
|
+
properties: {
|
|
1530
|
+
query: { type: "string", description: "Search query string" },
|
|
1531
|
+
limit: {
|
|
1532
|
+
type: "integer",
|
|
1533
|
+
description: "Maximum number of results to return (default 20, max 100)",
|
|
1534
|
+
minimum: 1,
|
|
1535
|
+
maximum: 100
|
|
1536
|
+
},
|
|
1537
|
+
preferLsp: {
|
|
1538
|
+
type: "boolean",
|
|
1539
|
+
description: "If true, skip the index and query LSP servers directly. Useful for live precision when the index may be stale."
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
required: ["query"]
|
|
1543
|
+
},
|
|
1544
|
+
permission: "auto",
|
|
1545
|
+
mutating: false,
|
|
1546
|
+
timeoutMs: LSP_CONSTANTS.TOOL_TIMEOUT_MS * 2,
|
|
1547
|
+
// Allow extra time for LSP round-robins
|
|
1548
|
+
async execute(input, ctx, opts) {
|
|
1549
|
+
try {
|
|
1550
|
+
const limit = Math.min(input.limit ?? 20, 100);
|
|
1551
|
+
const query = input.query ?? "";
|
|
1552
|
+
let indexResults = [];
|
|
1553
|
+
let totalIndex = 0;
|
|
1554
|
+
let usedIndex = false;
|
|
1555
|
+
let usedLsp = false;
|
|
1556
|
+
let lspResults = [];
|
|
1557
|
+
let totalLsp = 0;
|
|
1558
|
+
if (!input.preferLsp) {
|
|
1559
|
+
const indexOutcome = await searchIndex(ctx.projectRoot, query, limit);
|
|
1560
|
+
indexResults = indexOutcome.results;
|
|
1561
|
+
totalIndex = indexOutcome.total;
|
|
1562
|
+
usedIndex = true;
|
|
1563
|
+
}
|
|
1564
|
+
const needsLsp = input.preferLsp || indexResults.length === 0;
|
|
1565
|
+
if (needsLsp) {
|
|
1566
|
+
const lspOutcome = await searchLsp(deps, query, limit, opts.signal);
|
|
1567
|
+
lspResults = lspOutcome.results;
|
|
1568
|
+
totalLsp = lspOutcome.total;
|
|
1569
|
+
usedLsp = true;
|
|
1570
|
+
}
|
|
1571
|
+
const output = mergeResults(indexResults, lspResults, limit);
|
|
1572
|
+
const fullOutput = {
|
|
1573
|
+
results: output,
|
|
1574
|
+
totalIndex,
|
|
1575
|
+
totalLsp,
|
|
1576
|
+
query,
|
|
1577
|
+
usedIndex,
|
|
1578
|
+
usedLsp
|
|
1579
|
+
};
|
|
1580
|
+
return formatCodebaseLspResults(fullOutput, ctx.cwd);
|
|
1581
|
+
} catch (err) {
|
|
1582
|
+
return stringifyToolError(err);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
async function searchIndex(projectRoot, query, limit) {
|
|
1588
|
+
const store = new IndexStore(projectRoot);
|
|
1589
|
+
try {
|
|
1590
|
+
const candidates = store.search(query);
|
|
1591
|
+
if (candidates.length === 0) {
|
|
1592
|
+
return { results: [], total: 0 };
|
|
1593
|
+
}
|
|
1594
|
+
const indexable = candidates.map((c) => ({
|
|
1595
|
+
id: c.id,
|
|
1596
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
1597
|
+
}));
|
|
1598
|
+
const bm25 = buildBm25Index(indexable);
|
|
1599
|
+
const scored = bm25.score(query, (id) => candidates.some((c) => c.id === id));
|
|
1600
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1601
|
+
const top = scored.slice(0, limit);
|
|
1602
|
+
const qTokens = tokenise(query);
|
|
1603
|
+
const results = top.map(({ id, score }) => {
|
|
1604
|
+
const c = candidates.find((c2) => c2.id === id);
|
|
1605
|
+
const lspKind = internalKindToLspKind(c.kind) ?? 0;
|
|
1606
|
+
const snippet = bm25.extractSnippet(id, qTokens);
|
|
1607
|
+
return {
|
|
1608
|
+
name: c.name,
|
|
1609
|
+
kind: c.kind,
|
|
1610
|
+
lspKind,
|
|
1611
|
+
file: c.file,
|
|
1612
|
+
line: c.line,
|
|
1613
|
+
source: "index",
|
|
1614
|
+
score,
|
|
1615
|
+
snippet
|
|
1616
|
+
};
|
|
1617
|
+
});
|
|
1618
|
+
return { results, total: candidates.length };
|
|
1619
|
+
} finally {
|
|
1620
|
+
store.close();
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
async function searchLsp(deps, query, limit, signal) {
|
|
1624
|
+
const merged = [];
|
|
1625
|
+
const servers = deps.registry.list();
|
|
1626
|
+
const promises = [];
|
|
1627
|
+
for (const server of servers) {
|
|
1628
|
+
if (server.state !== "ready") continue;
|
|
1629
|
+
if (server.capabilities && !supportsWorkspaceSymbol(server.capabilities)) continue;
|
|
1630
|
+
promises.push(
|
|
1631
|
+
(async () => {
|
|
1632
|
+
try {
|
|
1633
|
+
const result = await server.workspaceSymbol(
|
|
1634
|
+
{ query },
|
|
1635
|
+
LSP_CONSTANTS.TOOL_TIMEOUT_MS,
|
|
1636
|
+
signal
|
|
1637
|
+
);
|
|
1638
|
+
if (result) {
|
|
1639
|
+
for (const sym of result) {
|
|
1640
|
+
merged.push(sym);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
} catch {
|
|
1644
|
+
}
|
|
1645
|
+
})()
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
await Promise.all(promises);
|
|
1649
|
+
const deduplicated = deduplicateByKey(
|
|
1650
|
+
merged.map((sym) => ({
|
|
1651
|
+
name: sym.name,
|
|
1652
|
+
kind: lspKindToInternalKind(sym.kind) ?? "symbol",
|
|
1653
|
+
lspKind: sym.kind,
|
|
1654
|
+
file: sym.location.uri.startsWith("file://") ? sym.location.uri.slice(7) : sym.location.uri,
|
|
1655
|
+
line: sym.location.range.start.line + 1,
|
|
1656
|
+
// convert to 1-based
|
|
1657
|
+
source: "lsp",
|
|
1658
|
+
server: serverNameFromConfig(deps, sym)
|
|
1659
|
+
}))
|
|
1660
|
+
);
|
|
1661
|
+
return {
|
|
1662
|
+
results: deduplicated.slice(0, limit),
|
|
1663
|
+
total: deduplicated.length
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function serverNameFromConfig(deps, sym) {
|
|
1667
|
+
const file = sym.location.uri;
|
|
1668
|
+
const ext = file.includes(".") ? file.split(".").pop().toLowerCase() : "";
|
|
1669
|
+
const langMap = {
|
|
1670
|
+
ts: ["typescript", "tsserver"],
|
|
1671
|
+
tsx: ["typescript", "tsserver"],
|
|
1672
|
+
js: ["javascript", "typescript"],
|
|
1673
|
+
jsx: ["javascript", "typescript"],
|
|
1674
|
+
py: ["python", "pyright"],
|
|
1675
|
+
go: ["go", "gopls"],
|
|
1676
|
+
rs: ["rust", "rust-analyzer"]
|
|
1677
|
+
};
|
|
1678
|
+
const langs = langMap[ext] ?? [ext];
|
|
1679
|
+
const servers = deps.registry.list();
|
|
1680
|
+
for (const lang of langs) {
|
|
1681
|
+
for (const server of servers) {
|
|
1682
|
+
if (server.state === "ready" && server.config.languages.some((l) => l.toLowerCase() === lang.toLowerCase())) {
|
|
1683
|
+
return server.name;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return servers.find((s) => s.state === "ready")?.name ?? "unknown";
|
|
1688
|
+
}
|
|
1689
|
+
function mergeResults(indexResults, lspResults, limit) {
|
|
1690
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1691
|
+
for (const r of indexResults) {
|
|
1692
|
+
const key = `${r.file}:${r.line}:${r.name}`;
|
|
1693
|
+
seen.set(key, r);
|
|
1694
|
+
}
|
|
1695
|
+
for (const r of lspResults) {
|
|
1696
|
+
const key = `${r.file}:${r.line}:${r.name}`;
|
|
1697
|
+
if (!seen.has(key)) {
|
|
1698
|
+
seen.set(key, r);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
const merged = Array.from(seen.values());
|
|
1702
|
+
merged.sort((a, b) => {
|
|
1703
|
+
if (a.source === "index" && b.source !== "index") return -1;
|
|
1704
|
+
if (a.source !== "index" && b.source === "index") return 1;
|
|
1705
|
+
if (a.source === "index" && b.source === "index") {
|
|
1706
|
+
return (b.score ?? 0) - (a.score ?? 0);
|
|
1707
|
+
}
|
|
1708
|
+
return 0;
|
|
1709
|
+
});
|
|
1710
|
+
return merged.slice(0, limit);
|
|
1711
|
+
}
|
|
1712
|
+
function deduplicateByKey(items) {
|
|
1713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1714
|
+
return items.filter((item) => {
|
|
1715
|
+
const key = `${item.file}:${item.line}:${item.name}`;
|
|
1716
|
+
if (seen.has(key)) return false;
|
|
1717
|
+
seen.add(key);
|
|
1718
|
+
return true;
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1429
1722
|
// src/formatters/location.ts
|
|
1430
1723
|
function formatLocations(locations, cwd, limit = 100) {
|
|
1431
1724
|
if (!locations || locations.length === 0) return "No locations found.";
|
|
@@ -1438,12 +1731,6 @@ function formatLocations(locations, cwd, limit = 100) {
|
|
|
1438
1731
|
return lines.join("\n");
|
|
1439
1732
|
}
|
|
1440
1733
|
|
|
1441
|
-
// src/constants.ts
|
|
1442
|
-
var LSP_CONSTANTS = Object.freeze({
|
|
1443
|
-
/** Default timeout for LSP tool operations (5 seconds). */
|
|
1444
|
-
TOOL_TIMEOUT_MS: 5e3
|
|
1445
|
-
});
|
|
1446
|
-
|
|
1447
1734
|
// src/tools/definition.ts
|
|
1448
1735
|
function createDefinitionTool(deps) {
|
|
1449
1736
|
return {
|
|
@@ -1697,68 +1984,6 @@ Applied: ${applied.edits} edits across ${applied.files.length} files.`;
|
|
|
1697
1984
|
};
|
|
1698
1985
|
}
|
|
1699
1986
|
|
|
1700
|
-
// src/formatters/symbols.ts
|
|
1701
|
-
function formatDocumentSymbols(path8, symbols, cwd) {
|
|
1702
|
-
if (!symbols || symbols.length === 0) return "No symbols found.";
|
|
1703
|
-
const lines = [`${displayPath(path8, cwd)}:`];
|
|
1704
|
-
for (const sym of symbols) appendSymbol(lines, sym, 1, cwd);
|
|
1705
|
-
return lines.join("\n");
|
|
1706
|
-
}
|
|
1707
|
-
function formatWorkspaceSymbols(symbols, query, cwd, limit = 100) {
|
|
1708
|
-
if (!symbols || symbols.length === 0) return `No symbols matching "${query}".`;
|
|
1709
|
-
const lines = [`${symbols.length} symbols matching "${query}":`];
|
|
1710
|
-
for (const sym of symbols.slice(0, limit)) {
|
|
1711
|
-
lines.push(
|
|
1712
|
-
` ${kindName(sym.kind)} ${sym.name} ${displayPath(uriToPath(sym.location.uri), cwd)}:${sym.location.range.start.line + 1}`
|
|
1713
|
-
);
|
|
1714
|
-
}
|
|
1715
|
-
if (symbols.length > limit) lines.push(` ... truncated ${symbols.length - limit} more`);
|
|
1716
|
-
return lines.join("\n");
|
|
1717
|
-
}
|
|
1718
|
-
function appendSymbol(lines, sym, depth, cwd) {
|
|
1719
|
-
const indent = " ".repeat(depth);
|
|
1720
|
-
if ("selectionRange" in sym) {
|
|
1721
|
-
lines.push(
|
|
1722
|
-
`${indent}${kindName(sym.kind)} ${sym.name} (L${sym.selectionRange.start.line + 1})`
|
|
1723
|
-
);
|
|
1724
|
-
for (const child of sym.children ?? []) appendSymbol(lines, child, depth + 1, cwd);
|
|
1725
|
-
} else {
|
|
1726
|
-
lines.push(
|
|
1727
|
-
`${indent}${kindName(sym.kind)} ${sym.name} ${displayPath(uriToPath(sym.location.uri), cwd)}:${sym.location.range.start.line + 1}`
|
|
1728
|
-
);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
function kindName(kind) {
|
|
1732
|
-
return [
|
|
1733
|
-
"file",
|
|
1734
|
-
"module",
|
|
1735
|
-
"namespace",
|
|
1736
|
-
"package",
|
|
1737
|
-
"class",
|
|
1738
|
-
"method",
|
|
1739
|
-
"property",
|
|
1740
|
-
"field",
|
|
1741
|
-
"constructor",
|
|
1742
|
-
"enum",
|
|
1743
|
-
"interface",
|
|
1744
|
-
"function",
|
|
1745
|
-
"variable",
|
|
1746
|
-
"constant",
|
|
1747
|
-
"string",
|
|
1748
|
-
"number",
|
|
1749
|
-
"boolean",
|
|
1750
|
-
"array",
|
|
1751
|
-
"object",
|
|
1752
|
-
"key",
|
|
1753
|
-
"null",
|
|
1754
|
-
"enumMember",
|
|
1755
|
-
"struct",
|
|
1756
|
-
"event",
|
|
1757
|
-
"operator",
|
|
1758
|
-
"typeParameter"
|
|
1759
|
-
][kind - 1] ?? "symbol";
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
1987
|
// src/tools/symbols.ts
|
|
1763
1988
|
function createSymbolsTool(deps) {
|
|
1764
1989
|
return {
|
|
@@ -1818,6 +2043,7 @@ function makeLSPTools(deps) {
|
|
|
1818
2043
|
createReferencesTool(deps),
|
|
1819
2044
|
createHoverTool(deps),
|
|
1820
2045
|
createSymbolsTool(deps),
|
|
2046
|
+
createCodebaseLspSearchTool(deps),
|
|
1821
2047
|
createRenameTool(deps),
|
|
1822
2048
|
createCodeActionsTool(deps)
|
|
1823
2049
|
];
|