@cruxy/cli 0.19.0 → 0.20.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/cli/commands/run.js +12 -1
- package/dist/cli/session-factory.js +12 -0
- package/dist/config/schema.d.ts +127 -14
- package/dist/config/schema.js +39 -0
- package/dist/errors/constructors.d.ts +23 -0
- package/dist/errors/constructors.js +67 -0
- package/dist/errors/types.d.ts +10 -0
- package/dist/errors/types.js +17 -0
- package/dist/lsp/client.d.ts +25 -0
- package/dist/lsp/client.js +43 -0
- package/dist/lsp/index.d.ts +8 -0
- package/dist/lsp/index.js +8 -0
- package/dist/lsp/pool.d.ts +48 -0
- package/dist/lsp/pool.js +132 -0
- package/dist/lsp/registry.d.ts +38 -0
- package/dist/lsp/registry.js +133 -0
- package/dist/lsp/server.d.ts +48 -0
- package/dist/lsp/server.js +264 -0
- package/dist/lsp/service.d.ts +44 -0
- package/dist/lsp/service.js +76 -0
- package/dist/lsp/tools/common.d.ts +23 -0
- package/dist/lsp/tools/common.js +75 -0
- package/dist/lsp/tools/find-definition.d.ts +23 -0
- package/dist/lsp/tools/find-definition.js +41 -0
- package/dist/lsp/tools/find-references.d.ts +23 -0
- package/dist/lsp/tools/find-references.js +41 -0
- package/dist/lsp/tools/get-diagnostics.d.ts +17 -0
- package/dist/lsp/tools/get-diagnostics.js +43 -0
- package/dist/lsp/tools/hover.d.ts +23 -0
- package/dist/lsp/tools/hover.js +38 -0
- package/dist/lsp/tools/index.d.ts +4 -0
- package/dist/lsp/tools/index.js +4 -0
- package/dist/lsp/transport.d.ts +48 -0
- package/dist/lsp/transport.js +264 -0
- package/dist/lsp/types.d.ts +107 -0
- package/dist/lsp/types.js +1 -0
- package/dist/tools/file/grep-files.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CruxyConfig } from "../config/index.js";
|
|
2
|
+
import type { BinaryPresent, LspDiagnostic, LspHover, LspLocation, TransportFactory } from "./types.js";
|
|
3
|
+
/** Logger surface the service reports through (matches the indexing service). */
|
|
4
|
+
interface ServiceLogger {
|
|
5
|
+
debug(message: string): void;
|
|
6
|
+
info(message: string): void;
|
|
7
|
+
warn(message: string): void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A ready-to-use LSP facade for one project root (C.12): it owns the registry,
|
|
11
|
+
* pool, and client, exposes the four normalized queries, and shuts the whole
|
|
12
|
+
* server pool down on `close()`. Build one with {@link getLspService}, cached
|
|
13
|
+
* per cwd so servers are spawned at most once per project per process.
|
|
14
|
+
*/
|
|
15
|
+
export interface LspService {
|
|
16
|
+
definition(file: string, line: number, col: number): Promise<LspLocation[]>;
|
|
17
|
+
references(file: string, line: number, col: number): Promise<LspLocation[]>;
|
|
18
|
+
hover(file: string, line: number, col: number): Promise<LspHover | null>;
|
|
19
|
+
diagnostics(file: string): Promise<LspDiagnostic[]>;
|
|
20
|
+
/** Shut down every managed server (session end / process teardown). */
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Explicit dependency overrides, for tests only. Production callers (the LSP
|
|
25
|
+
* tools) never pass these, so they always go through the real stdio transport
|
|
26
|
+
* and PATH-based binary detection.
|
|
27
|
+
*/
|
|
28
|
+
export interface LspServiceDeps {
|
|
29
|
+
/** Substitute the JSON-RPC transport (a fake stdio peer — no real binary). */
|
|
30
|
+
transportFactory?: TransportFactory;
|
|
31
|
+
/** Substitute PATH-based binary detection (force present/absent). */
|
|
32
|
+
binaryPresent?: BinaryPresent;
|
|
33
|
+
/** Substitute the idle clock. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get (or build) the {@link LspService} for a project root, cached per resolved
|
|
38
|
+
* cwd so the pool is created at most once per process. No server spawns until
|
|
39
|
+
* the first query. `deps` is for explicit test injection only.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getLspService(cwd: string, config: CruxyConfig, logger: ServiceLogger, deps?: LspServiceDeps): LspService;
|
|
42
|
+
/** Drop all cached services (shutting them down). For tests and process teardown. */
|
|
43
|
+
export declare function resetLspServices(): Promise<void>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Server } from "./server.js";
|
|
3
|
+
import { LspRegistry, binaryOnPath } from "./registry.js";
|
|
4
|
+
import { LspPool } from "./pool.js";
|
|
5
|
+
import { LspClient } from "./client.js";
|
|
6
|
+
class LspServiceImpl {
|
|
7
|
+
pool;
|
|
8
|
+
logger;
|
|
9
|
+
client;
|
|
10
|
+
constructor(pool, logger) {
|
|
11
|
+
this.pool = pool;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.client = new LspClient(pool);
|
|
14
|
+
}
|
|
15
|
+
definition(file, line, col) {
|
|
16
|
+
return this.client.definition(file, line, col);
|
|
17
|
+
}
|
|
18
|
+
references(file, line, col) {
|
|
19
|
+
return this.client.references(file, line, col);
|
|
20
|
+
}
|
|
21
|
+
hover(file, line, col) {
|
|
22
|
+
return this.client.hover(file, line, col);
|
|
23
|
+
}
|
|
24
|
+
diagnostics(file) {
|
|
25
|
+
return this.client.diagnostics(file);
|
|
26
|
+
}
|
|
27
|
+
async close() {
|
|
28
|
+
this.logger.debug("shutting down LSP servers");
|
|
29
|
+
await this.pool.shutdownAll();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// ── per-cwd cache ──────────────────────────────────────────────────────────────
|
|
33
|
+
const cache = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Get (or build) the {@link LspService} for a project root, cached per resolved
|
|
36
|
+
* cwd so the pool is created at most once per process. No server spawns until
|
|
37
|
+
* the first query. `deps` is for explicit test injection only.
|
|
38
|
+
*/
|
|
39
|
+
export function getLspService(cwd, config, logger, deps) {
|
|
40
|
+
const root = path.resolve(cwd);
|
|
41
|
+
let service = cache.get(root);
|
|
42
|
+
if (!service) {
|
|
43
|
+
service = buildService(root, config, logger, deps);
|
|
44
|
+
cache.set(root, service);
|
|
45
|
+
}
|
|
46
|
+
return service;
|
|
47
|
+
}
|
|
48
|
+
/** Drop all cached services (shutting them down). For tests and process teardown. */
|
|
49
|
+
export async function resetLspServices() {
|
|
50
|
+
const services = [...cache.values()];
|
|
51
|
+
cache.clear();
|
|
52
|
+
for (const service of services) {
|
|
53
|
+
try {
|
|
54
|
+
await service.close();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* already closed or failed to build */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function buildService(root, config, logger, deps) {
|
|
62
|
+
const lsp = config.lsp;
|
|
63
|
+
const registry = new LspRegistry(lsp, deps?.binaryPresent ?? binaryOnPath);
|
|
64
|
+
const timeouts = {
|
|
65
|
+
startupTimeout: lsp.startupTimeout,
|
|
66
|
+
requestTimeout: lsp.requestTimeout,
|
|
67
|
+
};
|
|
68
|
+
const transportFactory = deps?.transportFactory;
|
|
69
|
+
const pool = new LspPool(registry, root, { ...timeouts, maxServers: lsp.maxServers, idleTimeout: lsp.idleTimeout }, {
|
|
70
|
+
now: deps?.now,
|
|
71
|
+
startServer: transportFactory
|
|
72
|
+
? (spec) => Server.start(transportFactory(spec, root), root, spec, timeouts)
|
|
73
|
+
: undefined,
|
|
74
|
+
});
|
|
75
|
+
return new LspServiceImpl(pool, logger);
|
|
76
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ToolContext, ToolResult } from "../../tools/types.js";
|
|
2
|
+
import { type LspService } from "../service.js";
|
|
3
|
+
import type { LspLocation } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The shared spine of every LSP tool (C.12): enforce the master switch, validate
|
|
6
|
+
* the target path stays in the project root, prove it exists, get the per-cwd
|
|
7
|
+
* service, and run `query`. Read-only throughout — no `ctx.requestApproval`, so
|
|
8
|
+
* these bypass the U.3 gate exactly like `search_codebase` and `grep_files`.
|
|
9
|
+
*
|
|
10
|
+
* Errors are surfaced as `{ ok:false }` text the agent can act on: a coded LSP
|
|
11
|
+
* failure (no server / timeout / crash) is rendered WITH its next step, so the
|
|
12
|
+
* agent can reroute (e.g. to grep) or the user can install the server. A genuine
|
|
13
|
+
* empty answer never reaches here as an error — `query` returns it as `ok:true`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runLspTool(ctx: ToolContext, file: string, query: (service: LspService, absFile: string) => Promise<ToolResult>): Promise<ToolResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Render up to `max` locations as `path:line:col-endLine:endCol`, one per line,
|
|
18
|
+
* with a trailing "N more" note when capped — the same bounded-honest pattern as
|
|
19
|
+
* grep_files and search_codebase (never silently drop the overflow).
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatLocations(locations: LspLocation[], max: number): string;
|
|
22
|
+
/** Relative label for a validated absolute path, for tool messages. */
|
|
23
|
+
export declare function relLabel(cwd: string, absFile: string): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { promises as fsp } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CruxyError } from "../../errors/index.js";
|
|
4
|
+
import { resolveInRoot } from "../../tools/file/paths.js";
|
|
5
|
+
import { getLspService } from "../service.js";
|
|
6
|
+
/**
|
|
7
|
+
* The shared spine of every LSP tool (C.12): enforce the master switch, validate
|
|
8
|
+
* the target path stays in the project root, prove it exists, get the per-cwd
|
|
9
|
+
* service, and run `query`. Read-only throughout — no `ctx.requestApproval`, so
|
|
10
|
+
* these bypass the U.3 gate exactly like `search_codebase` and `grep_files`.
|
|
11
|
+
*
|
|
12
|
+
* Errors are surfaced as `{ ok:false }` text the agent can act on: a coded LSP
|
|
13
|
+
* failure (no server / timeout / crash) is rendered WITH its next step, so the
|
|
14
|
+
* agent can reroute (e.g. to grep) or the user can install the server. A genuine
|
|
15
|
+
* empty answer never reaches here as an error — `query` returns it as `ok:true`.
|
|
16
|
+
*/
|
|
17
|
+
export async function runLspTool(ctx, file, query) {
|
|
18
|
+
if (!ctx.config.lsp.enabled) {
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
error: "LSP tools are disabled (set lsp.enabled = true to use language-server features)",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
let absFile;
|
|
25
|
+
try {
|
|
26
|
+
absFile = await resolveInRoot(ctx, file);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
return { ok: false, error: err.message };
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const stat = await fsp.stat(absFile);
|
|
33
|
+
if (!stat.isFile()) {
|
|
34
|
+
return { ok: false, error: `not a file: ${file}` };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { ok: false, error: `file not found: ${file}` };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const service = getLspService(ctx.cwd, ctx.config, ctx.logger);
|
|
42
|
+
return await query(service, absFile);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return { ok: false, error: describeError(err) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Render a coded LSP error with its actionable next step, for the model to read. */
|
|
49
|
+
function describeError(err) {
|
|
50
|
+
if (CruxyError.is(err)) {
|
|
51
|
+
const cause = err.cause ? ` — ${err.cause}` : "";
|
|
52
|
+
const step = err.nextSteps[0] ? `\n→ ${err.nextSteps[0]}` : "";
|
|
53
|
+
return `[${err.code}] ${err.title}${cause}${step}`;
|
|
54
|
+
}
|
|
55
|
+
return err.message;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Render up to `max` locations as `path:line:col-endLine:endCol`, one per line,
|
|
59
|
+
* with a trailing "N more" note when capped — the same bounded-honest pattern as
|
|
60
|
+
* grep_files and search_codebase (never silently drop the overflow).
|
|
61
|
+
*/
|
|
62
|
+
export function formatLocations(locations, max) {
|
|
63
|
+
const shown = locations.slice(0, max);
|
|
64
|
+
const lines = shown.map((l) => `${l.path}:${l.startLine}:${l.startCol}-${l.endLine}:${l.endCol}`);
|
|
65
|
+
const omitted = locations.length - shown.length;
|
|
66
|
+
if (omitted > 0) {
|
|
67
|
+
lines.push(`… [${omitted} more location(s) omitted]`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
/** Relative label for a validated absolute path, for tool messages. */
|
|
72
|
+
export function relLabel(cwd, absFile) {
|
|
73
|
+
const rel = path.relative(path.resolve(cwd), absFile);
|
|
74
|
+
return rel === "" ? absFile : rel.split(path.sep).join("/");
|
|
75
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../tools/types.js";
|
|
3
|
+
declare const parameters: z.ZodObject<{
|
|
4
|
+
file: z.ZodString;
|
|
5
|
+
line: z.ZodNumber;
|
|
6
|
+
column: z.ZodNumber;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
}, {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* LSP go-to-definition (C.12). Read-only — no approval, like search_codebase.
|
|
18
|
+
* Returns the definition site(s) as `path:line:col-endLine:endCol`. A missing
|
|
19
|
+
* language server is a coded, actionable error (distinct from "no definition
|
|
20
|
+
* found", which is an honest empty result).
|
|
21
|
+
*/
|
|
22
|
+
export declare const findDefinitionTool: Tool<typeof parameters>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatLocations, runLspTool } from "./common.js";
|
|
3
|
+
const parameters = z.object({
|
|
4
|
+
file: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.describe("Project-relative path to the file containing the symbol."),
|
|
8
|
+
line: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.positive()
|
|
12
|
+
.describe("1-based line number of the symbol to resolve."),
|
|
13
|
+
column: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.positive()
|
|
17
|
+
.describe("1-based column of the symbol (position of the identifier)."),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* LSP go-to-definition (C.12). Read-only — no approval, like search_codebase.
|
|
21
|
+
* Returns the definition site(s) as `path:line:col-endLine:endCol`. A missing
|
|
22
|
+
* language server is a coded, actionable error (distinct from "no definition
|
|
23
|
+
* found", which is an honest empty result).
|
|
24
|
+
*/
|
|
25
|
+
export const findDefinitionTool = {
|
|
26
|
+
name: "find_definition",
|
|
27
|
+
description: "Resolve where a symbol is defined using the project's language server (go-to-definition). Give the file and the 1-based line/column of the identifier. Returns definition locations as 'path:line:col-endLine:endCol'. Read-only, no approval. Precise where grep is textual — prefer this to jump to a symbol's definition.",
|
|
28
|
+
parameters,
|
|
29
|
+
execute(input, ctx) {
|
|
30
|
+
return runLspTool(ctx, input.file, async (service, absFile) => {
|
|
31
|
+
const locations = await service.definition(absFile, input.line, input.column);
|
|
32
|
+
if (locations.length === 0) {
|
|
33
|
+
return { ok: true, output: "(no definition found)" };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
output: formatLocations(locations, ctx.config.lsp.maxResults),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../tools/types.js";
|
|
3
|
+
declare const parameters: z.ZodObject<{
|
|
4
|
+
file: z.ZodString;
|
|
5
|
+
line: z.ZodNumber;
|
|
6
|
+
column: z.ZodNumber;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
}, {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* LSP find-references (C.12). Read-only — no approval. Returns every use of the
|
|
18
|
+
* symbol (declaration included) as `path:line:col-endLine:endCol`, capped to
|
|
19
|
+
* `lsp.maxResults` with an "N more" note so a hot symbol can't flood the output.
|
|
20
|
+
* A missing server is a coded error, distinct from an honest "no references".
|
|
21
|
+
*/
|
|
22
|
+
export declare const findReferencesTool: Tool<typeof parameters>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatLocations, runLspTool } from "./common.js";
|
|
3
|
+
const parameters = z.object({
|
|
4
|
+
file: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.describe("Project-relative path to the file containing the symbol."),
|
|
8
|
+
line: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.positive()
|
|
12
|
+
.describe("1-based line number of the symbol."),
|
|
13
|
+
column: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.positive()
|
|
17
|
+
.describe("1-based column of the symbol (position of the identifier)."),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* LSP find-references (C.12). Read-only — no approval. Returns every use of the
|
|
21
|
+
* symbol (declaration included) as `path:line:col-endLine:endCol`, capped to
|
|
22
|
+
* `lsp.maxResults` with an "N more" note so a hot symbol can't flood the output.
|
|
23
|
+
* A missing server is a coded error, distinct from an honest "no references".
|
|
24
|
+
*/
|
|
25
|
+
export const findReferencesTool = {
|
|
26
|
+
name: "find_references",
|
|
27
|
+
description: "Find all references to a symbol using the project's language server. Give the file and the 1-based line/column of the identifier. Returns use sites as 'path:line:col-endLine:endCol' (capped, with an 'N more' note). Read-only, no approval. Precise where grep is textual — prefer this to see every caller/user of a symbol.",
|
|
28
|
+
parameters,
|
|
29
|
+
execute(input, ctx) {
|
|
30
|
+
return runLspTool(ctx, input.file, async (service, absFile) => {
|
|
31
|
+
const locations = await service.references(absFile, input.line, input.column);
|
|
32
|
+
if (locations.length === 0) {
|
|
33
|
+
return { ok: true, output: "(no references found)" };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
output: formatLocations(locations, ctx.config.lsp.maxResults),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../tools/types.js";
|
|
3
|
+
declare const parameters: z.ZodObject<{
|
|
4
|
+
file: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
file: string;
|
|
7
|
+
}, {
|
|
8
|
+
file: string;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* LSP diagnostics (C.12): the language server's errors/warnings for a file.
|
|
12
|
+
* Read-only — no approval. Returns each diagnostic as
|
|
13
|
+
* `severity path:line:col message [source]`, capped to `lsp.maxResults`. A
|
|
14
|
+
* missing server is a coded error; a clean file is an honest empty result.
|
|
15
|
+
*/
|
|
16
|
+
export declare const getDiagnosticsTool: Tool<typeof parameters>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runLspTool } from "./common.js";
|
|
3
|
+
const parameters = z.object({
|
|
4
|
+
file: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.describe("Project-relative path to the file to diagnose."),
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* LSP diagnostics (C.12): the language server's errors/warnings for a file.
|
|
11
|
+
* Read-only — no approval. Returns each diagnostic as
|
|
12
|
+
* `severity path:line:col message [source]`, capped to `lsp.maxResults`. A
|
|
13
|
+
* missing server is a coded error; a clean file is an honest empty result.
|
|
14
|
+
*/
|
|
15
|
+
export const getDiagnosticsTool = {
|
|
16
|
+
name: "get_diagnostics",
|
|
17
|
+
description: "Get the language server's diagnostics (errors, warnings) for a file. Give the project-relative path. Returns diagnostics as 'severity path:line:col message'. Read-only, no approval. Use this after an edit to see type errors the compiler/linter reports, without running a build.",
|
|
18
|
+
parameters,
|
|
19
|
+
execute(input, ctx) {
|
|
20
|
+
return runLspTool(ctx, input.file, async (service, absFile) => {
|
|
21
|
+
const diagnostics = await service.diagnostics(absFile);
|
|
22
|
+
if (diagnostics.length === 0) {
|
|
23
|
+
return { ok: true, output: "(no diagnostics)" };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
output: formatDiagnostics(diagnostics, ctx.config.lsp.maxResults),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
function formatDiagnostics(diagnostics, max) {
|
|
33
|
+
const shown = diagnostics.slice(0, max);
|
|
34
|
+
const lines = shown.map((d) => {
|
|
35
|
+
const src = d.source ? ` [${d.source}]` : "";
|
|
36
|
+
return `${d.severity} ${d.path}:${d.range.startLine}:${d.range.startCol} ${d.message}${src}`;
|
|
37
|
+
});
|
|
38
|
+
const omitted = diagnostics.length - shown.length;
|
|
39
|
+
if (omitted > 0) {
|
|
40
|
+
lines.push(`… [${omitted} more diagnostic(s) omitted]`);
|
|
41
|
+
}
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../tools/types.js";
|
|
3
|
+
declare const parameters: z.ZodObject<{
|
|
4
|
+
file: z.ZodString;
|
|
5
|
+
line: z.ZodNumber;
|
|
6
|
+
column: z.ZodNumber;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
}, {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* LSP hover (C.12): type signature / documentation for the symbol at a position.
|
|
18
|
+
* Read-only — no approval. Returns the server's hover text (markup flattened to
|
|
19
|
+
* plain text). A missing server is a coded error; a symbol with no hover info is
|
|
20
|
+
* an honest empty result.
|
|
21
|
+
*/
|
|
22
|
+
export declare const hoverTool: Tool<typeof parameters>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runLspTool } from "./common.js";
|
|
3
|
+
const parameters = z.object({
|
|
4
|
+
file: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.describe("Project-relative path to the file containing the symbol."),
|
|
8
|
+
line: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.positive()
|
|
12
|
+
.describe("1-based line number of the symbol."),
|
|
13
|
+
column: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.positive()
|
|
17
|
+
.describe("1-based column of the symbol (position of the identifier)."),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* LSP hover (C.12): type signature / documentation for the symbol at a position.
|
|
21
|
+
* Read-only — no approval. Returns the server's hover text (markup flattened to
|
|
22
|
+
* plain text). A missing server is a coded error; a symbol with no hover info is
|
|
23
|
+
* an honest empty result.
|
|
24
|
+
*/
|
|
25
|
+
export const hoverTool = {
|
|
26
|
+
name: "hover",
|
|
27
|
+
description: "Get type information and documentation for a symbol using the project's language server (hover). Give the file and the 1-based line/column of the identifier. Returns the symbol's type signature and docs. Read-only, no approval. Use this to learn a symbol's type without opening and reading its declaration.",
|
|
28
|
+
parameters,
|
|
29
|
+
execute(input, ctx) {
|
|
30
|
+
return runLspTool(ctx, input.file, async (service, absFile) => {
|
|
31
|
+
const hover = await service.hover(absFile, input.line, input.column);
|
|
32
|
+
if (!hover) {
|
|
33
|
+
return { ok: true, output: "(no hover information)" };
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, output: hover.contents };
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { LspTransport, ServerSpec } from "./types.js";
|
|
2
|
+
export declare class StdioTransport implements LspTransport {
|
|
3
|
+
private readonly child;
|
|
4
|
+
private nextId;
|
|
5
|
+
private readonly pending;
|
|
6
|
+
private readonly notificationHandlers;
|
|
7
|
+
private crashHandler;
|
|
8
|
+
/** stdout parse buffer (header + body may arrive across chunks). */
|
|
9
|
+
private buffer;
|
|
10
|
+
private disposed;
|
|
11
|
+
/** Deregisters this process from the process-exit kill-tree backstop. */
|
|
12
|
+
private readonly unregisterCleanup;
|
|
13
|
+
constructor(spec: ServerSpec, root: string);
|
|
14
|
+
request(method: string, params: unknown, timeoutMs: number): Promise<unknown>;
|
|
15
|
+
notify(method: string, params: unknown): void;
|
|
16
|
+
onNotification(method: string, handler: (params: unknown) => void): void;
|
|
17
|
+
onCrash(handler: (info: {
|
|
18
|
+
code: number | null;
|
|
19
|
+
signal: string | null;
|
|
20
|
+
}) => void): void;
|
|
21
|
+
dispose(force?: boolean): Promise<void>;
|
|
22
|
+
private send;
|
|
23
|
+
private onStdout;
|
|
24
|
+
private dispatch;
|
|
25
|
+
private onExit;
|
|
26
|
+
private onSpawnError;
|
|
27
|
+
}
|
|
28
|
+
/** A per-request timeout, distinguishable from an ordinary LSP error response. */
|
|
29
|
+
export declare class TransportTimeoutError extends Error {
|
|
30
|
+
readonly method: string;
|
|
31
|
+
readonly timeoutMs: number;
|
|
32
|
+
constructor(method: string, timeoutMs: number);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Kill the process's entire group (POSIX negative-PID `SIGKILL`), same helper
|
|
36
|
+
* shape as run_command's `killTree`. Swallows errors — the process may be gone.
|
|
37
|
+
*/
|
|
38
|
+
export declare function killTree(pid: number | undefined): void;
|
|
39
|
+
/**
|
|
40
|
+
* Force-kill the process group of every tracked-but-not-yet-shut-down server,
|
|
41
|
+
* then forget them. This is exactly what the `exit`/`SIGINT`/`SIGTERM`/`SIGHUP`
|
|
42
|
+
* handlers run — the last line against orphaned language servers on a hard exit.
|
|
43
|
+
* Exported so it is directly testable (like `resetIndexServices`) without having
|
|
44
|
+
* to raise real process signals. Idempotent: a second call is a no-op.
|
|
45
|
+
*/
|
|
46
|
+
export declare function killTrackedServers(): void;
|
|
47
|
+
/** Number of servers currently tracked by the exit backstop (for tests). */
|
|
48
|
+
export declare function trackedServerCount(): number;
|