@intentius/chant 0.0.18 → 0.0.24
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/bin/chant +4 -1
- package/package.json +20 -1
- package/src/build.test.ts +4 -2
- package/src/build.ts +3 -0
- package/src/builder.test.ts +3 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -3
- package/src/cli/commands/build.ts +5 -12
- package/src/cli/commands/diff.test.ts +2 -1
- package/src/cli/commands/diff.ts +2 -1
- package/src/cli/commands/init-lexicon/templates/codegen.ts +188 -0
- package/src/cli/commands/init-lexicon/templates/docs.ts +81 -0
- package/src/cli/commands/init-lexicon/templates/examples.ts +35 -0
- package/src/cli/commands/init-lexicon/templates/lint.ts +30 -0
- package/src/cli/commands/init-lexicon/templates/lsp.ts +39 -0
- package/src/cli/commands/init-lexicon/templates/plugin.ts +110 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +182 -0
- package/src/cli/commands/init-lexicon/templates/spec.ts +57 -0
- package/src/cli/commands/init-lexicon/templates/tests.ts +70 -0
- package/src/cli/commands/init-lexicon.test.ts +0 -9
- package/src/cli/commands/init-lexicon.ts +12 -868
- package/src/cli/commands/init.ts +2 -20
- package/src/cli/conflict-check.test.ts +43 -0
- package/src/cli/handlers/build.ts +3 -3
- package/src/cli/handlers/lint.ts +2 -2
- package/src/cli/handlers/spell.ts +396 -0
- package/src/cli/handlers/state.ts +230 -0
- package/src/cli/lsp/server.test.ts +4 -0
- package/src/cli/main.ts +37 -3
- package/src/cli/mcp/resource-handlers.ts +227 -0
- package/src/cli/mcp/server.test.ts +13 -9
- package/src/cli/mcp/server.ts +24 -199
- package/src/cli/mcp/state-tools.ts +138 -0
- package/src/cli/mcp/tools/build.ts +2 -1
- package/src/cli/mcp/types.ts +45 -0
- package/src/cli/plugins.ts +1 -1
- package/src/cli/reporters/stylish.test.ts +2 -2
- package/src/cli/reporters/stylish.ts +1 -1
- package/src/codegen/docs-file-markers.ts +69 -0
- package/src/codegen/docs-rule-scanning.ts +159 -0
- package/src/codegen/docs-sections.ts +159 -0
- package/src/codegen/docs-sidebar.ts +56 -0
- package/src/codegen/docs-types.ts +79 -0
- package/src/codegen/docs.ts +9 -495
- package/src/composite.test.ts +76 -1
- package/src/composite.ts +37 -0
- package/src/config.ts +4 -0
- package/src/declarable.test.ts +2 -1
- package/src/declarable.ts +1 -1
- package/src/discovery/collect.test.ts +34 -0
- package/src/discovery/collect.ts +12 -0
- package/src/discovery/graph.test.ts +40 -0
- package/src/discovery/import.test.ts +5 -5
- package/src/discovery/resolve.test.ts +20 -0
- package/src/discovery/resolve.ts +2 -2
- package/src/index.ts +2 -0
- package/src/lexicon-plugin-helpers.ts +130 -0
- package/src/lexicon.ts +24 -0
- package/src/lint/rule-options.test.ts +3 -3
- package/src/lint/rule-registry.test.ts +1 -1
- package/src/lint/rules/composite-scope.ts +1 -1
- package/src/serializer-walker.ts +2 -1
- package/src/spell/discovery.ts +183 -0
- package/src/spell/index.ts +3 -0
- package/src/spell/prompt.ts +133 -0
- package/src/spell/types.ts +89 -0
- package/src/state/digest.ts +88 -0
- package/src/state/git.ts +317 -0
- package/src/state/index.ts +4 -0
- package/src/state/snapshot.ts +179 -0
- package/src/state/types.ts +59 -0
- package/src/toml-emit.ts +182 -0
- package/src/toml-parse.ts +370 -0
- package/src/toml-utils.ts +60 -0
- package/src/toml.ts +5 -602
- package/src/types.ts +2 -1
- package/src/utils.test.ts +16 -3
- package/src/utils.ts +31 -1
- package/src/validation.test.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/getting-started.mdx +0 -6
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/lint-rules.mdx +0 -6
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/serialization.mdx +0 -6
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/actions/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/composites/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +0 -11
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +0 -10
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +0 -10
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/post-synth/.gitkeep +0 -0
package/src/cli/mcp/server.ts
CHANGED
|
@@ -6,66 +6,17 @@ import { importTool, handleImport } from "./tools/import";
|
|
|
6
6
|
import { explainTool, handleExplain } from "./tools/explain";
|
|
7
7
|
import { scaffoldTool, createScaffoldHandler } from "./tools/scaffold";
|
|
8
8
|
import { searchTool, createSearchHandler } from "./tools/search";
|
|
9
|
-
import { getContext } from "./resources/context";
|
|
10
9
|
import type { LexiconPlugin } from "../../lexicon";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* MCP message types
|
|
15
|
-
*/
|
|
16
|
-
interface McpRequest {
|
|
17
|
-
jsonrpc: "2.0";
|
|
18
|
-
id: string | number;
|
|
19
|
-
method: string;
|
|
20
|
-
params?: Record<string, unknown>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface McpResponse {
|
|
24
|
-
jsonrpc: "2.0";
|
|
25
|
-
id: string | number;
|
|
26
|
-
result?: unknown;
|
|
27
|
-
error?: {
|
|
28
|
-
code: number;
|
|
29
|
-
message: string;
|
|
30
|
-
data?: unknown;
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface McpNotification {
|
|
35
|
-
jsonrpc: "2.0";
|
|
36
|
-
method: string;
|
|
37
|
-
params?: Record<string, unknown>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Tool definition for MCP
|
|
42
|
-
*/
|
|
43
|
-
interface ToolDefinition {
|
|
44
|
-
name: string;
|
|
45
|
-
description: string;
|
|
46
|
-
inputSchema: {
|
|
47
|
-
type: "object";
|
|
48
|
-
properties: Record<string, unknown>;
|
|
49
|
-
required?: string[];
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Resource definition for MCP
|
|
55
|
-
*/
|
|
56
|
-
interface ResourceDefinition {
|
|
57
|
-
uri: string;
|
|
58
|
-
name: string;
|
|
59
|
-
description: string;
|
|
60
|
-
mimeType?: string;
|
|
61
|
-
}
|
|
10
|
+
import type { McpRequest, McpResponse, ToolDefinition, ToolHandler, ResourceDefinition } from "./types";
|
|
11
|
+
import { createSnapshotTool, createDiffTool, createSpellDoneTool } from "./state-tools";
|
|
12
|
+
import { buildResourcesList, handleResourcesRead } from "./resource-handlers";
|
|
62
13
|
|
|
63
14
|
/**
|
|
64
15
|
* MCP Server implementation
|
|
65
16
|
*/
|
|
66
17
|
export class McpServer {
|
|
67
18
|
private tools: Map<string, ToolDefinition> = new Map();
|
|
68
|
-
private toolHandlers: Map<string,
|
|
19
|
+
private toolHandlers: Map<string, ToolHandler> = new Map();
|
|
69
20
|
private pluginResources: Map<string, { definition: ResourceDefinition; handler: () => Promise<string> }> = new Map();
|
|
70
21
|
|
|
71
22
|
constructor(plugins?: LexiconPlugin[]) {
|
|
@@ -77,6 +28,16 @@ export class McpServer {
|
|
|
77
28
|
this.registerTool(scaffoldTool, createScaffoldHandler(plugins ?? []));
|
|
78
29
|
this.registerTool(searchTool, createSearchHandler(plugins ?? []));
|
|
79
30
|
|
|
31
|
+
// Register state tools
|
|
32
|
+
const snapshot = createSnapshotTool(plugins ?? []);
|
|
33
|
+
this.registerTool(snapshot.definition, snapshot.handler);
|
|
34
|
+
|
|
35
|
+
const diff = createDiffTool(plugins ?? []);
|
|
36
|
+
this.registerTool(diff.definition, diff.handler);
|
|
37
|
+
|
|
38
|
+
const spellDone = createSpellDoneTool();
|
|
39
|
+
this.registerTool(spellDone.definition, spellDone.handler);
|
|
40
|
+
|
|
80
41
|
// Register plugin contributions
|
|
81
42
|
if (plugins) {
|
|
82
43
|
for (const plugin of plugins) {
|
|
@@ -128,7 +89,7 @@ export class McpServer {
|
|
|
128
89
|
*/
|
|
129
90
|
private registerTool(
|
|
130
91
|
definition: ToolDefinition,
|
|
131
|
-
handler:
|
|
92
|
+
handler: ToolHandler,
|
|
132
93
|
): void {
|
|
133
94
|
this.tools.set(definition.name, definition);
|
|
134
95
|
this.toolHandlers.set(definition.name, handler);
|
|
@@ -163,51 +124,29 @@ export class McpServer {
|
|
|
163
124
|
private async dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {
|
|
164
125
|
switch (method) {
|
|
165
126
|
case "initialize":
|
|
166
|
-
return
|
|
127
|
+
return {
|
|
128
|
+
protocolVersion: "2024-11-05",
|
|
129
|
+
capabilities: { tools: {}, resources: {} },
|
|
130
|
+
serverInfo: { name: "chant", version: "0.1.0" },
|
|
131
|
+
};
|
|
167
132
|
|
|
168
133
|
case "tools/list":
|
|
169
|
-
return this.
|
|
134
|
+
return { tools: Array.from(this.tools.values()) };
|
|
170
135
|
|
|
171
136
|
case "tools/call":
|
|
172
137
|
return this.handleToolsCall(params);
|
|
173
138
|
|
|
174
139
|
case "resources/list":
|
|
175
|
-
return this.
|
|
140
|
+
return buildResourcesList(this.pluginResources);
|
|
176
141
|
|
|
177
142
|
case "resources/read":
|
|
178
|
-
return
|
|
143
|
+
return handleResourcesRead(params, this.pluginResources);
|
|
179
144
|
|
|
180
145
|
default:
|
|
181
146
|
throw new Error(`Unknown method: ${method}`);
|
|
182
147
|
}
|
|
183
148
|
}
|
|
184
149
|
|
|
185
|
-
/**
|
|
186
|
-
* Handle initialize request
|
|
187
|
-
*/
|
|
188
|
-
private handleInitialize(params: Record<string, unknown>): unknown {
|
|
189
|
-
return {
|
|
190
|
-
protocolVersion: "2024-11-05",
|
|
191
|
-
capabilities: {
|
|
192
|
-
tools: {},
|
|
193
|
-
resources: {},
|
|
194
|
-
},
|
|
195
|
-
serverInfo: {
|
|
196
|
-
name: "chant",
|
|
197
|
-
version: "0.1.0",
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Handle tools/list request
|
|
204
|
-
*/
|
|
205
|
-
private handleToolsList(): unknown {
|
|
206
|
-
return {
|
|
207
|
-
tools: Array.from(this.tools.values()),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
150
|
/**
|
|
212
151
|
* Handle tools/call request
|
|
213
152
|
*/
|
|
@@ -218,12 +157,7 @@ export class McpServer {
|
|
|
218
157
|
const handler = this.toolHandlers.get(name);
|
|
219
158
|
if (!handler) {
|
|
220
159
|
return {
|
|
221
|
-
content: [
|
|
222
|
-
{
|
|
223
|
-
type: "text",
|
|
224
|
-
text: `Error: Unknown tool: ${name}`,
|
|
225
|
-
},
|
|
226
|
-
],
|
|
160
|
+
content: [{ type: "text", text: `Error: Unknown tool: ${name}` }],
|
|
227
161
|
isError: true,
|
|
228
162
|
};
|
|
229
163
|
}
|
|
@@ -251,115 +185,6 @@ export class McpServer {
|
|
|
251
185
|
}
|
|
252
186
|
}
|
|
253
187
|
|
|
254
|
-
/**
|
|
255
|
-
* Handle resources/list request — merges core + plugin resources
|
|
256
|
-
*/
|
|
257
|
-
private handleResourcesList(): unknown {
|
|
258
|
-
const resources: ResourceDefinition[] = [
|
|
259
|
-
{
|
|
260
|
-
uri: "chant://context",
|
|
261
|
-
name: "chant Context",
|
|
262
|
-
description: "Lexicon-specific instructions and patterns for chant development",
|
|
263
|
-
mimeType: "text/markdown",
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
uri: "chant://examples/list",
|
|
267
|
-
name: "Examples List",
|
|
268
|
-
description: "List of available chant examples",
|
|
269
|
-
mimeType: "application/json",
|
|
270
|
-
},
|
|
271
|
-
];
|
|
272
|
-
|
|
273
|
-
// Merge plugin resources
|
|
274
|
-
for (const { definition } of this.pluginResources.values()) {
|
|
275
|
-
resources.push(definition);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return { resources };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Collect example resources from plugins whose URI contains "examples/"
|
|
283
|
-
*/
|
|
284
|
-
private collectExamples(): Array<{ name: string; description: string }> {
|
|
285
|
-
const examples: Array<{ name: string; description: string }> = [];
|
|
286
|
-
for (const [uri, { definition }] of this.pluginResources.entries()) {
|
|
287
|
-
if (uri.includes("/examples/")) {
|
|
288
|
-
const name = uri.replace(/^chant:\/\/[^/]+\/examples\//, "");
|
|
289
|
-
examples.push({ name, description: definition.description });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return examples;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Handle resources/read request — checks plugin resources after core
|
|
297
|
-
*/
|
|
298
|
-
private async handleResourcesRead(params: Record<string, unknown>): Promise<unknown> {
|
|
299
|
-
const uri = params.uri as string;
|
|
300
|
-
|
|
301
|
-
if (uri === "chant://context") {
|
|
302
|
-
return {
|
|
303
|
-
contents: [
|
|
304
|
-
{
|
|
305
|
-
uri,
|
|
306
|
-
mimeType: "text/markdown",
|
|
307
|
-
text: getContext(),
|
|
308
|
-
},
|
|
309
|
-
],
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (uri === "chant://examples/list") {
|
|
314
|
-
return {
|
|
315
|
-
contents: [
|
|
316
|
-
{
|
|
317
|
-
uri,
|
|
318
|
-
mimeType: "application/json",
|
|
319
|
-
text: JSON.stringify(this.collectExamples()),
|
|
320
|
-
},
|
|
321
|
-
],
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (uri.startsWith("chant://examples/")) {
|
|
326
|
-
// Look up example in plugin resources
|
|
327
|
-
const name = uri.replace("chant://examples/", "");
|
|
328
|
-
for (const [pluginUri, pluginResource] of this.pluginResources.entries()) {
|
|
329
|
-
if (pluginUri.endsWith(`/examples/${name}`)) {
|
|
330
|
-
const text = await pluginResource.handler();
|
|
331
|
-
return {
|
|
332
|
-
contents: [
|
|
333
|
-
{
|
|
334
|
-
uri,
|
|
335
|
-
mimeType: pluginResource.definition.mimeType ?? "text/typescript",
|
|
336
|
-
text,
|
|
337
|
-
},
|
|
338
|
-
],
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
throw new Error(`Example not found: ${name}`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Check plugin resources
|
|
346
|
-
const pluginResource = this.pluginResources.get(uri);
|
|
347
|
-
if (pluginResource) {
|
|
348
|
-
const text = await pluginResource.handler();
|
|
349
|
-
return {
|
|
350
|
-
contents: [
|
|
351
|
-
{
|
|
352
|
-
uri,
|
|
353
|
-
mimeType: pluginResource.definition.mimeType ?? "text/plain",
|
|
354
|
-
text,
|
|
355
|
-
},
|
|
356
|
-
],
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
throw new Error(`Unknown resource: ${uri}`);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
188
|
/**
|
|
364
189
|
* Start the MCP server on stdio
|
|
365
190
|
*/
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import type { LexiconPlugin } from "../../lexicon";
|
|
3
|
+
import type { ToolDefinition, ToolHandler } from "./types";
|
|
4
|
+
import { readSnapshot } from "../../state/git";
|
|
5
|
+
import { build } from "../../build";
|
|
6
|
+
import { computeBuildDigest, diffDigests } from "../../state/digest";
|
|
7
|
+
import { takeSnapshot } from "../../state/snapshot";
|
|
8
|
+
import type { StateSnapshot } from "../../state/types";
|
|
9
|
+
import { discoverSpells } from "../../spell/discovery";
|
|
10
|
+
|
|
11
|
+
export interface ToolRegistration {
|
|
12
|
+
definition: ToolDefinition;
|
|
13
|
+
handler: ToolHandler;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create state-snapshot tool definition and handler
|
|
18
|
+
*/
|
|
19
|
+
export function createSnapshotTool(plugins: LexiconPlugin[]): ToolRegistration {
|
|
20
|
+
return {
|
|
21
|
+
definition: {
|
|
22
|
+
name: "state-snapshot",
|
|
23
|
+
description: "Capture deployed state for an environment",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
environment: { type: "string", description: "Target environment" },
|
|
28
|
+
lexicon: { type: "string", description: "Optional — snapshot all lexicons if omitted" },
|
|
29
|
+
},
|
|
30
|
+
required: ["environment"],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
handler: async (params) => {
|
|
34
|
+
const env = params.environment as string;
|
|
35
|
+
const lexiconFilter = params.lexicon as string | undefined;
|
|
36
|
+
const targetPlugins = lexiconFilter
|
|
37
|
+
? plugins.filter((p) => p.name === lexiconFilter)
|
|
38
|
+
: plugins;
|
|
39
|
+
const pluginsWithDescribe = targetPlugins.filter((p) => p.describeResources);
|
|
40
|
+
if (pluginsWithDescribe.length === 0) return "No plugins implement describeResources";
|
|
41
|
+
const serializers = plugins.map((p) => p.serializer);
|
|
42
|
+
const buildResult = await build(resolve("."), serializers);
|
|
43
|
+
if (buildResult.errors.length > 0) return "Build failed";
|
|
44
|
+
const result = await takeSnapshot(env, pluginsWithDescribe, buildResult);
|
|
45
|
+
return { snapshots: result.snapshots.length, warnings: result.warnings, errors: result.errors };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create state-diff tool definition and handler
|
|
52
|
+
*/
|
|
53
|
+
export function createDiffTool(plugins: LexiconPlugin[]): ToolRegistration {
|
|
54
|
+
return {
|
|
55
|
+
definition: {
|
|
56
|
+
name: "state-diff",
|
|
57
|
+
description: "Compare current build declarations against last snapshot's digest",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
environment: { type: "string", description: "Target environment" },
|
|
62
|
+
lexicon: { type: "string", description: "Optional — diff all lexicons if omitted" },
|
|
63
|
+
},
|
|
64
|
+
required: ["environment"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
handler: async (params) => {
|
|
68
|
+
const env = params.environment as string;
|
|
69
|
+
const lexiconFilter = params.lexicon as string | undefined;
|
|
70
|
+
const serializers = plugins.map((p) => p.serializer);
|
|
71
|
+
const buildResult = await build(resolve("."), serializers);
|
|
72
|
+
if (buildResult.errors.length > 0) return "Build failed";
|
|
73
|
+
const currentDigest = computeBuildDigest(buildResult);
|
|
74
|
+
const lexicons = lexiconFilter ? [lexiconFilter] : buildResult.manifest.lexicons;
|
|
75
|
+
const results: Record<string, unknown> = {};
|
|
76
|
+
for (const lex of lexicons) {
|
|
77
|
+
const content = await readSnapshot(env, lex);
|
|
78
|
+
let previousDigest = undefined;
|
|
79
|
+
if (content) {
|
|
80
|
+
const snapshot: StateSnapshot = JSON.parse(content);
|
|
81
|
+
previousDigest = snapshot.digest;
|
|
82
|
+
}
|
|
83
|
+
results[lex] = diffDigests(currentDigest, previousDigest);
|
|
84
|
+
}
|
|
85
|
+
return results;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create spell-done tool definition and handler
|
|
92
|
+
*/
|
|
93
|
+
export function createSpellDoneTool(): ToolRegistration {
|
|
94
|
+
return {
|
|
95
|
+
definition: {
|
|
96
|
+
name: "spell-done",
|
|
97
|
+
description: "Mark a spell task as done",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
name: { type: "string", description: "Spell name" },
|
|
102
|
+
taskNumber: { type: "number", description: "Task number (1-based)" },
|
|
103
|
+
},
|
|
104
|
+
required: ["name", "taskNumber"],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
handler: async (params) => {
|
|
108
|
+
const { readFileSync, writeFileSync } = await import("node:fs");
|
|
109
|
+
const { spells } = await discoverSpells();
|
|
110
|
+
const name = params.name as string;
|
|
111
|
+
const taskNumber = params.taskNumber as number;
|
|
112
|
+
const spell = spells.get(name);
|
|
113
|
+
if (!spell) return `Spell "${name}" not found`;
|
|
114
|
+
if (taskNumber < 1 || taskNumber > spell.definition.tasks.length) {
|
|
115
|
+
return `Invalid task number ${taskNumber}`;
|
|
116
|
+
}
|
|
117
|
+
const task = spell.definition.tasks[taskNumber - 1];
|
|
118
|
+
if (task.done) return `Task ${taskNumber} is already done`;
|
|
119
|
+
|
|
120
|
+
const content = readFileSync(spell.filePath, "utf-8");
|
|
121
|
+
let count = 0;
|
|
122
|
+
const rewritten = content.replace(
|
|
123
|
+
/task\(("[^"]*"|'[^']*'|`[^`]*`)((?:\s*,\s*\{[^}]*\})?)\)/g,
|
|
124
|
+
(match, desc, opts) => {
|
|
125
|
+
count++;
|
|
126
|
+
if (count !== taskNumber) return match;
|
|
127
|
+
if (opts && opts.includes("done:")) {
|
|
128
|
+
return match.replace(/done:\s*false/, "done: true");
|
|
129
|
+
}
|
|
130
|
+
return `task(${desc}, { done: true })`;
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
if (rewritten === content) return `Could not rewrite task ${taskNumber}`;
|
|
134
|
+
writeFileSync(spell.filePath, rewritten);
|
|
135
|
+
return `Task ${taskNumber} marked done: "${task.description}"`;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -58,7 +58,8 @@ export async function handleBuild(params: Record<string, unknown>): Promise<unkn
|
|
|
58
58
|
// Combine all lexicon outputs
|
|
59
59
|
const combined: Record<string, unknown> = {};
|
|
60
60
|
for (const [lexiconName, lexiconOutput] of result.outputs) {
|
|
61
|
-
|
|
61
|
+
const raw = lexiconOutput;
|
|
62
|
+
combined[lexiconName] = JSON.parse(typeof raw === "string" ? raw : raw.primary);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
let output = JSON.stringify(combined, null, 2);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP message types
|
|
3
|
+
*/
|
|
4
|
+
export interface McpRequest {
|
|
5
|
+
jsonrpc: "2.0";
|
|
6
|
+
id: string | number;
|
|
7
|
+
method: string;
|
|
8
|
+
params?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface McpResponse {
|
|
12
|
+
jsonrpc: "2.0";
|
|
13
|
+
id: string | number;
|
|
14
|
+
result?: unknown;
|
|
15
|
+
error?: {
|
|
16
|
+
code: number;
|
|
17
|
+
message: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tool definition for MCP
|
|
24
|
+
*/
|
|
25
|
+
export interface ToolDefinition {
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object";
|
|
30
|
+
properties: Record<string, unknown>;
|
|
31
|
+
required?: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resource definition for MCP
|
|
37
|
+
*/
|
|
38
|
+
export interface ResourceDefinition {
|
|
39
|
+
uri: string;
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
mimeType?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type ToolHandler = (params: Record<string, unknown>) => Promise<unknown>;
|
package/src/cli/plugins.ts
CHANGED
|
@@ -17,9 +17,9 @@ describe("formatStylish", () => {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
test("returns
|
|
20
|
+
test("returns summary line for no diagnostics", () => {
|
|
21
21
|
const result = formatStylish([]);
|
|
22
|
-
expect(result).toBe("");
|
|
22
|
+
expect(result).toBe("\u2713 No problems found");
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
test("formats single diagnostic", () => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File marker interpolation and MDX escaping utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
|
|
8
|
+
/** Escape curly braces so MDX doesn't treat them as JSX expressions. */
|
|
9
|
+
export function escapeMdx(text: string): string {
|
|
10
|
+
return text.replace(/\{/g, "\\{").replace(/\}/g, "\\}");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Expand `{{file:path.ts}}` markers in content with fenced code blocks.
|
|
15
|
+
*
|
|
16
|
+
* Supported forms:
|
|
17
|
+
* - `{{file:path.ts}}` — full file
|
|
18
|
+
* - `{{file:path.ts:5-12}}` — lines 5–12 (1-based, inclusive)
|
|
19
|
+
* - `{{file:path.ts|title=custom.ts}}` — override the code block title
|
|
20
|
+
* - `{{file:path.ts:5-12|title=custom.ts}}` — both
|
|
21
|
+
*/
|
|
22
|
+
export function expandFileMarkers(content: string, examplesDir: string): string {
|
|
23
|
+
return content.replace(
|
|
24
|
+
/\{\{file:([^}]+)\}\}/g,
|
|
25
|
+
(_match, spec: string) => {
|
|
26
|
+
// Parse options after |
|
|
27
|
+
let filePart = spec;
|
|
28
|
+
let title: string | undefined;
|
|
29
|
+
const pipeIdx = spec.indexOf("|");
|
|
30
|
+
if (pipeIdx !== -1) {
|
|
31
|
+
filePart = spec.substring(0, pipeIdx);
|
|
32
|
+
const opts = spec.substring(pipeIdx + 1);
|
|
33
|
+
const titleMatch = opts.match(/title=([^\s|]+)/);
|
|
34
|
+
if (titleMatch) title = titleMatch[1];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Parse line range after :digits-digits at end of filePart
|
|
38
|
+
let lineStart: number | undefined;
|
|
39
|
+
let lineEnd: number | undefined;
|
|
40
|
+
const rangeMatch = filePart.match(/^(.+):(\d+)-(\d+)$/);
|
|
41
|
+
if (rangeMatch) {
|
|
42
|
+
filePart = rangeMatch[1];
|
|
43
|
+
lineStart = parseInt(rangeMatch[2], 10);
|
|
44
|
+
lineEnd = parseInt(rangeMatch[3], 10);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const filePath = join(examplesDir, filePart);
|
|
48
|
+
let fileContent: string;
|
|
49
|
+
try {
|
|
50
|
+
fileContent = readFileSync(filePath, "utf-8");
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error(`File marker {{file:${spec}}} — file not found: ${filePath}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Extract line range if specified
|
|
56
|
+
if (lineStart !== undefined && lineEnd !== undefined) {
|
|
57
|
+
const lines = fileContent.split("\n");
|
|
58
|
+
fileContent = lines.slice(lineStart - 1, lineEnd).join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Determine language from extension
|
|
62
|
+
const ext = filePart.substring(filePart.lastIndexOf(".") + 1);
|
|
63
|
+
const lang = ext === "ts" || ext === "tsx" ? "typescript" : ext;
|
|
64
|
+
const displayTitle = title ?? filePart.substring(filePart.lastIndexOf("/") + 1);
|
|
65
|
+
|
|
66
|
+
return `\`\`\`${lang} title="${displayTitle}"\n${fileContent.trimEnd()}\n\`\`\``;
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
}
|