codemode-lsp 0.1.1 → 0.1.3
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/README.md +4 -1
- package/dist/index.js +81 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,10 @@ The API surface is 16 functions plus `getDiagnostics`: 8 read ops (`readFile`,
|
|
|
72
72
|
`deleteSymbol`, `writeFile`, `deleteFile`). Symbols are addressed by
|
|
73
73
|
slash-separated paths (`MyClass/myMethod`) discovered via `getSymbols`. The
|
|
74
74
|
full type definitions are embedded in the tool description, generated straight
|
|
75
|
-
from the source (`bun run generate:types`).
|
|
75
|
+
from the source (`bun run generate:types`). If a client truncates the
|
|
76
|
+
description, the script `await lsp.help()` returns the complete reference —
|
|
77
|
+
the description's first lines advertise this, so an agent can always recover
|
|
78
|
+
the full API without probing.
|
|
76
79
|
|
|
77
80
|
See `PRD.md` for the complete spec.
|
|
78
81
|
|
package/dist/index.js
CHANGED
|
@@ -46102,6 +46102,25 @@ function buildTracedLsp(api2, trace, makeSandboxError, wrapAsync, opNames) {
|
|
|
46102
46102
|
return traced;
|
|
46103
46103
|
}
|
|
46104
46104
|
var hostPath = { join: join2, basename, dirname: dirname4, extname };
|
|
46105
|
+
var UNCAPTURED_STATEMENT_NAMES = {
|
|
46106
|
+
IfStatement: "an if statement",
|
|
46107
|
+
ForStatement: "a for loop",
|
|
46108
|
+
ForOfStatement: "a for...of loop",
|
|
46109
|
+
ForInStatement: "a for...in loop",
|
|
46110
|
+
WhileStatement: "a while loop",
|
|
46111
|
+
DoWhileStatement: "a do...while loop",
|
|
46112
|
+
TryStatement: "a try/catch block",
|
|
46113
|
+
SwitchStatement: "a switch statement",
|
|
46114
|
+
BlockStatement: "a block",
|
|
46115
|
+
VariableDeclaration: "a variable declaration",
|
|
46116
|
+
FunctionDeclaration: "a function declaration",
|
|
46117
|
+
ClassDeclaration: "a class declaration"
|
|
46118
|
+
};
|
|
46119
|
+
function describeUncapturedStatement(type) {
|
|
46120
|
+
if (type === "ReturnStatement")
|
|
46121
|
+
return;
|
|
46122
|
+
return UNCAPTURED_STATEMENT_NAMES[type] ?? `a ${type}`;
|
|
46123
|
+
}
|
|
46105
46124
|
function isExpressionStatement(node) {
|
|
46106
46125
|
return node.type === "ExpressionStatement";
|
|
46107
46126
|
}
|
|
@@ -46137,8 +46156,14 @@ function normalizeCode(code) {
|
|
|
46137
46156
|
return { source: `${head}
|
|
46138
46157
|
return (${expr});` };
|
|
46139
46158
|
}
|
|
46140
|
-
return {
|
|
46141
|
-
|
|
46159
|
+
return {
|
|
46160
|
+
source: `${trimmed}
|
|
46161
|
+
return undefined;`,
|
|
46162
|
+
...last ? (() => {
|
|
46163
|
+
const described = describeUncapturedStatement(last.type);
|
|
46164
|
+
return described ? { uncapturedLastStatement: described } : {};
|
|
46165
|
+
})() : {}
|
|
46166
|
+
};
|
|
46142
46167
|
}
|
|
46143
46168
|
function capText(text, cap, marker) {
|
|
46144
46169
|
if (text.length <= cap)
|
|
@@ -46249,7 +46274,9 @@ async function runSandbox(code, options) {
|
|
|
46249
46274
|
};
|
|
46250
46275
|
const opNames = options.readonly ? [...READ_OP_NAMES] : [...READ_OP_NAMES, ...WRITE_OP_NAMES];
|
|
46251
46276
|
sandbox.lsp = buildTracedLsp(options.lsp, trace, makeSandboxError, wrapAsync, opNames);
|
|
46252
|
-
const
|
|
46277
|
+
const helpText = options.helpText ?? "No extended help is available; the tool description is the full reference.";
|
|
46278
|
+
sandbox.lsp.help = wrapAsync(async () => helpText);
|
|
46279
|
+
const { source, uncapturedLastStatement } = normalizeCode(code);
|
|
46253
46280
|
const wrapped = `(async () => {
|
|
46254
46281
|
${source}
|
|
46255
46282
|
})()`;
|
|
@@ -46290,7 +46317,7 @@ ${source}
|
|
|
46290
46317
|
}
|
|
46291
46318
|
let result;
|
|
46292
46319
|
try {
|
|
46293
|
-
result = serializeResult(value);
|
|
46320
|
+
result = value === undefined && uncapturedLastStatement ? `undefined — note: the script's last statement is ${uncapturedLastStatement}, ` + "which produces no capturable value. Only a top-level trailing " + "expression becomes the result — end the script with a bare " + "expression (e.g. `({ count })`), or use a top-level `return`." : serializeResult(value);
|
|
46294
46321
|
} catch (error51) {
|
|
46295
46322
|
throw new SandboxError({
|
|
46296
46323
|
error: error51 instanceof Error ? error51.message : String(error51),
|
|
@@ -46391,11 +46418,11 @@ var LSP_READ_OP_SIGNATURES = ` /** File contents as a raw string (no line numbe
|
|
|
46391
46418
|
getSymbolBody(file: string, symbolPath: string): Promise<string>;
|
|
46392
46419
|
/** Document symbol tree (file outline). Every path is a usable handle. */
|
|
46393
46420
|
getSymbols(file: string): Promise<SymbolInfo[]>;
|
|
46394
|
-
/** Workspace-wide symbol search;
|
|
46421
|
+
/** Workspace-wide symbol search; matches substrings, so filter for exact \`name\`. The index warms lazily — early calls may be empty; getSymbols(file) is exhaustive. */
|
|
46395
46422
|
findSymbol(query: string): Promise<WorkspaceSymbolInfo[]>;
|
|
46396
46423
|
/** All references to a symbol across the workspace (incl. the declaration). */
|
|
46397
46424
|
findReferences(file: string, symbolPath: string): Promise<Reference[]>;
|
|
46398
|
-
/** Jump to a symbol
|
|
46425
|
+
/** Jump to the definition of a symbol DEFINED in \`file\` — imports/callees in a body are not addressable; resolve those names with findSymbol. */
|
|
46399
46426
|
goToDefinition(file: string, symbolPath: string): Promise<Location>;
|
|
46400
46427
|
/** Regex search across project files — escape metacharacters for literal text; optional second arg is a glob string. */
|
|
46401
46428
|
searchText(pattern: string, glob?: string): Promise<SearchResult[]>;
|
|
@@ -46465,17 +46492,25 @@ function renderLspTypes(readonly3) {
|
|
|
46465
46492
|
const interfaces = readonly3 ? LSP_COMMON_INTERFACES : `${LSP_COMMON_INTERFACES}
|
|
46466
46493
|
|
|
46467
46494
|
${LSP_WRITE_INTERFACES}`;
|
|
46468
|
-
const
|
|
46495
|
+
const helpOp = ` /** This full API reference as a string — call it if this description was truncated. */
|
|
46496
|
+
help(): Promise<string>;`;
|
|
46497
|
+
const ops = readonly3 ? `${LSP_READ_OP_SIGNATURES}
|
|
46498
|
+
|
|
46499
|
+
${helpOp}` : `${LSP_READ_OP_SIGNATURES}
|
|
46469
46500
|
|
|
46470
46501
|
// Write operations — buffered, applied atomically when the script succeeds.
|
|
46471
|
-
${LSP_WRITE_OP_SIGNATURES}
|
|
46472
|
-
return `${interfaces}
|
|
46502
|
+
${LSP_WRITE_OP_SIGNATURES}
|
|
46473
46503
|
|
|
46474
|
-
|
|
46504
|
+
${helpOp}`;
|
|
46505
|
+
return `declare const lsp: {
|
|
46475
46506
|
${ops}
|
|
46476
|
-
}
|
|
46507
|
+
};
|
|
46508
|
+
|
|
46509
|
+
${interfaces}`;
|
|
46477
46510
|
}
|
|
46478
46511
|
var TEMPLATE = `Execute JavaScript to perform semantic code operations via LSP (TypeScript).
|
|
46512
|
+
If this description looks truncated, run the script \`await lsp.help()\` — it
|
|
46513
|
+
returns this complete reference. Ops — {{opInventory}}.
|
|
46479
46514
|
|
|
46480
46515
|
Write one script that chains lsp.* calls — filter, loop, and branch in code
|
|
46481
46516
|
instead of across many tool calls. The sandbox provides \`lsp\`,
|
|
@@ -46484,36 +46519,45 @@ instead of across many tool calls. The sandbox provides \`lsp\`,
|
|
|
46484
46519
|
setTimeout). The tool returns { result, logs, changes }, where \`result\` is the
|
|
46485
46520
|
script's last expression, JSON-serialized.
|
|
46486
46521
|
|
|
46487
|
-
## API
|
|
46488
|
-
|
|
46489
|
-
{{types}}
|
|
46490
|
-
|
|
46491
|
-
{{writeSemantics}}
|
|
46492
|
-
|
|
46493
|
-
## Examples
|
|
46494
|
-
|
|
46495
|
-
{{examples}}
|
|
46496
|
-
|
|
46497
46522
|
## Rules
|
|
46498
46523
|
|
|
46499
|
-
- The last expression is the return value — end the script with
|
|
46500
|
-
|
|
46524
|
+
- The last TOP-LEVEL expression is the return value — end the script with a
|
|
46525
|
+
bare expression, e.g. \`({ count })\`. An expression inside a trailing
|
|
46526
|
+
if/for/try block is NOT captured; use a top-level \`return\` from inside
|
|
46527
|
+
blocks.
|
|
46501
46528
|
- Every \`lsp.*\` call returns a Promise — always \`await\` it. An un-awaited
|
|
46502
46529
|
call inside the result serializes as \`{}\`.
|
|
46503
46530
|
- \`.filter()\`/\`.map()\` callbacks cannot be async — use \`for...of\` with \`await\`.
|
|
46504
|
-
-
|
|
46505
|
-
|
|
46506
|
-
\`listFiles()\` for
|
|
46507
|
-
|
|
46531
|
+
- \`searchText\`/\`listFiles\` cover the whole workspace (minus .gitignore);
|
|
46532
|
+
scope with a glob, which matches the full workspace-relative path:
|
|
46533
|
+
\`listFiles("src/**")\` for a directory (a bare name like \`"src"\` is treated
|
|
46534
|
+
as \`src/**\`), \`listFiles()\` for every file. \`searchText\`'s second argument
|
|
46535
|
+
is a glob string — there is no options object; filter results in your script.
|
|
46508
46536
|
- Symbol paths are slash-separated (\`MyClass/myMethod\`); discover exact paths
|
|
46509
46537
|
with \`getSymbols(file)\` rather than guessing. Symbol ops always take the
|
|
46510
46538
|
pair \`(file, symbolPath)\` — a \`SymbolInfo.path\` belongs to the file you
|
|
46511
|
-
called \`getSymbols\` on
|
|
46539
|
+
called \`getSymbols\` on, and round-trips into findReferences/goToDefinition/
|
|
46540
|
+
the write ops.
|
|
46512
46541
|
- \`searchText\` patterns are regexes — escape metacharacters for literal text:
|
|
46513
46542
|
\`searchText("new NotFoundError\\\\(")\`.
|
|
46543
|
+
- \`goToDefinition\` only addresses symbols DEFINED in the given file — it cannot
|
|
46544
|
+
follow an imported or called name into another module. To resolve a name to
|
|
46545
|
+
its definition anywhere in the workspace, use \`findSymbol(name)\` and filter
|
|
46546
|
+
for an exact \`name\` match (it also returns substring matches).
|
|
46514
46547
|
- File paths are relative to the workspace root; anything outside it is
|
|
46515
46548
|
rejected.
|
|
46516
|
-
- Diagnostics cover files touched this session only, never the whole project
|
|
46549
|
+
- Diagnostics cover files touched this session only, never the whole project.
|
|
46550
|
+
\`Diagnostic.range\` is zero-based; every other line/column is 1-based.
|
|
46551
|
+
|
|
46552
|
+
## API
|
|
46553
|
+
|
|
46554
|
+
{{types}}
|
|
46555
|
+
|
|
46556
|
+
{{writeSemantics}}
|
|
46557
|
+
|
|
46558
|
+
## Examples
|
|
46559
|
+
|
|
46560
|
+
{{examples}}`;
|
|
46517
46561
|
var WRITE_SEMANTICS = `Write operations available: writes are buffered during the script and applied
|
|
46518
46562
|
atomically (flushed to disk) only when the whole script succeeds; if it throws,
|
|
46519
46563
|
every buffered write is rolled back and the codebase is unchanged. The tool
|
|
@@ -46528,7 +46572,8 @@ ${example.code}
|
|
|
46528
46572
|
\`\`\``).join(`
|
|
46529
46573
|
|
|
46530
46574
|
`);
|
|
46531
|
-
|
|
46575
|
+
const opInventory = readonly3 ? `read: ${READ_OP_NAMES.join(", ")}` : `read: ${READ_OP_NAMES.join(", ")}; write: ${WRITE_OP_NAMES.join(", ")}`;
|
|
46576
|
+
return TEMPLATE.replace("{{opInventory}}", opInventory).replace("{{types}}", renderLspTypes(readonly3)).replace("{{writeSemantics}}", readonly3 ? READONLY_SEMANTICS : WRITE_SEMANTICS).replace("{{examples}}", examples);
|
|
46532
46577
|
}
|
|
46533
46578
|
|
|
46534
46579
|
// src/mcp-server.ts
|
|
@@ -46565,7 +46610,8 @@ function createExecuteRunner(deps) {
|
|
|
46565
46610
|
const { result, logs } = await runSandbox(code, {
|
|
46566
46611
|
lsp: deps.api,
|
|
46567
46612
|
timeoutMs: deps.timeoutMs,
|
|
46568
|
-
readonly: deps.readonly
|
|
46613
|
+
readonly: deps.readonly,
|
|
46614
|
+
helpText: deps.helpText ?? buildToolDescription(deps.readonly ?? false)
|
|
46569
46615
|
});
|
|
46570
46616
|
const flushed = buffer.flush();
|
|
46571
46617
|
const changes = flushed.map((change) => ({
|
|
@@ -46629,20 +46675,22 @@ function createServer(options = {}) {
|
|
|
46629
46675
|
warmup.catch(() => {
|
|
46630
46676
|
return;
|
|
46631
46677
|
});
|
|
46678
|
+
const description = buildToolDescription(readonly3);
|
|
46632
46679
|
const { execute } = createExecuteRunner({
|
|
46633
46680
|
api: api2,
|
|
46634
46681
|
client,
|
|
46635
46682
|
timeoutMs,
|
|
46636
46683
|
warmup,
|
|
46637
46684
|
rootDir,
|
|
46638
|
-
readonly: readonly3
|
|
46685
|
+
readonly: readonly3,
|
|
46686
|
+
helpText: description
|
|
46639
46687
|
});
|
|
46640
46688
|
const server = new McpServer({
|
|
46641
46689
|
name: "codemode-lsp",
|
|
46642
46690
|
version: "0.1.0"
|
|
46643
46691
|
});
|
|
46644
46692
|
server.registerTool("execute", {
|
|
46645
|
-
description
|
|
46693
|
+
description,
|
|
46646
46694
|
inputSchema: {
|
|
46647
46695
|
code: exports_external.string().describe("JavaScript to run in the LSP sandbox. The last expression is the return value.")
|
|
46648
46696
|
}
|