@intentius/chant 0.0.22 → 0.1.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.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/src/cli/commands/init-lexicon/templates/codegen.ts +188 -0
  3. package/src/cli/commands/init-lexicon/templates/docs.ts +81 -0
  4. package/src/cli/commands/init-lexicon/templates/examples.ts +35 -0
  5. package/src/cli/commands/init-lexicon/templates/lint.ts +30 -0
  6. package/src/cli/commands/init-lexicon/templates/lsp.ts +39 -0
  7. package/src/cli/commands/init-lexicon/templates/plugin.ts +110 -0
  8. package/src/cli/commands/init-lexicon/templates/project.ts +182 -0
  9. package/src/cli/commands/init-lexicon/templates/spec.ts +57 -0
  10. package/src/cli/commands/init-lexicon/templates/tests.ts +70 -0
  11. package/src/cli/commands/init-lexicon.ts +12 -774
  12. package/src/cli/conflict-check.test.ts +43 -0
  13. package/src/cli/main.ts +1 -1
  14. package/src/cli/mcp/resource-handlers.ts +227 -0
  15. package/src/cli/mcp/server.ts +20 -409
  16. package/src/cli/mcp/state-tools.ts +138 -0
  17. package/src/cli/mcp/types.ts +45 -0
  18. package/src/codegen/docs-file-markers.ts +69 -0
  19. package/src/codegen/docs-rule-scanning.ts +159 -0
  20. package/src/codegen/docs-sections.ts +159 -0
  21. package/src/codegen/docs-sidebar.ts +56 -0
  22. package/src/codegen/docs-types.ts +79 -0
  23. package/src/codegen/docs.ts +9 -495
  24. package/src/codegen/typecheck.ts +13 -0
  25. package/src/composite.test.ts +75 -0
  26. package/src/composite.ts +37 -0
  27. package/src/discovery/collect.test.ts +34 -0
  28. package/src/discovery/collect.ts +25 -0
  29. package/src/lexicon-plugin-helpers.ts +130 -0
  30. package/src/toml-emit.ts +182 -0
  31. package/src/toml-parse.ts +370 -0
  32. package/src/toml-utils.ts +60 -0
  33. package/src/toml.ts +5 -602
  34. package/src/yaml.ts +7 -2
@@ -6,68 +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 { McpToolContribution, McpResourceContribution } from "../../mcp/types";
12
- import { readSnapshot, readEnvironmentSnapshots } from "../../state/git";
13
- import { build } from "../../build";
14
- import { computeBuildDigest, diffDigests } from "../../state/digest";
15
- import { takeSnapshot } from "../../state/snapshot";
16
- import type { StateSnapshot } from "../../state/types";
17
- import { discoverSpells } from "../../spell/discovery";
18
- import { generatePrompt } from "../../spell/prompt";
19
- import { getRuntime } from "../../runtime-adapter";
20
-
21
- /**
22
- * MCP message types
23
- */
24
- interface McpRequest {
25
- jsonrpc: "2.0";
26
- id: string | number;
27
- method: string;
28
- params?: Record<string, unknown>;
29
- }
30
-
31
- interface McpResponse {
32
- jsonrpc: "2.0";
33
- id: string | number;
34
- result?: unknown;
35
- error?: {
36
- code: number;
37
- message: string;
38
- data?: unknown;
39
- };
40
- }
41
-
42
- /**
43
- * Tool definition for MCP
44
- */
45
- interface ToolDefinition {
46
- name: string;
47
- description: string;
48
- inputSchema: {
49
- type: "object";
50
- properties: Record<string, unknown>;
51
- required?: string[];
52
- };
53
- }
54
-
55
- /**
56
- * Resource definition for MCP
57
- */
58
- interface ResourceDefinition {
59
- uri: string;
60
- name: string;
61
- description: string;
62
- mimeType?: string;
63
- }
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";
64
13
 
65
14
  /**
66
15
  * MCP Server implementation
67
16
  */
68
17
  export class McpServer {
69
18
  private tools: Map<string, ToolDefinition> = new Map();
70
- private toolHandlers: Map<string, (params: Record<string, unknown>) => Promise<unknown>> = new Map();
19
+ private toolHandlers: Map<string, ToolHandler> = new Map();
71
20
  private pluginResources: Map<string, { definition: ResourceDefinition; handler: () => Promise<string> }> = new Map();
72
21
 
73
22
  constructor(plugins?: LexiconPlugin[]) {
@@ -80,115 +29,14 @@ export class McpServer {
80
29
  this.registerTool(searchTool, createSearchHandler(plugins ?? []));
81
30
 
82
31
  // Register state tools
83
- this.registerTool(
84
- {
85
- name: "state-snapshot",
86
- description: "Capture deployed state for an environment",
87
- inputSchema: {
88
- type: "object",
89
- properties: {
90
- environment: { type: "string", description: "Target environment" },
91
- lexicon: { type: "string", description: "Optional — snapshot all lexicons if omitted" },
92
- },
93
- required: ["environment"],
94
- },
95
- },
96
- async (params) => {
97
- const env = params.environment as string;
98
- const lexiconFilter = params.lexicon as string | undefined;
99
- const targetPlugins = lexiconFilter
100
- ? (plugins ?? []).filter((p) => p.name === lexiconFilter)
101
- : (plugins ?? []);
102
- const pluginsWithDescribe = targetPlugins.filter((p) => p.describeResources);
103
- if (pluginsWithDescribe.length === 0) return "No plugins implement describeResources";
104
- const serializers = (plugins ?? []).map((p) => p.serializer);
105
- const buildResult = await build(resolve("."), serializers);
106
- if (buildResult.errors.length > 0) return "Build failed";
107
- const result = await takeSnapshot(env, pluginsWithDescribe, buildResult);
108
- return { snapshots: result.snapshots.length, warnings: result.warnings, errors: result.errors };
109
- },
110
- );
111
-
112
- this.registerTool(
113
- {
114
- name: "state-diff",
115
- description: "Compare current build declarations against last snapshot's digest",
116
- inputSchema: {
117
- type: "object",
118
- properties: {
119
- environment: { type: "string", description: "Target environment" },
120
- lexicon: { type: "string", description: "Optional — diff all lexicons if omitted" },
121
- },
122
- required: ["environment"],
123
- },
124
- },
125
- async (params) => {
126
- const env = params.environment as string;
127
- const lexiconFilter = params.lexicon as string | undefined;
128
- const serializers = (plugins ?? []).map((p) => p.serializer);
129
- const buildResult = await build(resolve("."), serializers);
130
- if (buildResult.errors.length > 0) return "Build failed";
131
- const currentDigest = computeBuildDigest(buildResult);
132
- const lexicons = lexiconFilter ? [lexiconFilter] : buildResult.manifest.lexicons;
133
- const results: Record<string, unknown> = {};
134
- for (const lex of lexicons) {
135
- const content = await readSnapshot(env, lex);
136
- let previousDigest = undefined;
137
- if (content) {
138
- const snapshot: StateSnapshot = JSON.parse(content);
139
- previousDigest = snapshot.digest;
140
- }
141
- results[lex] = diffDigests(currentDigest, previousDigest);
142
- }
143
- return results;
144
- },
145
- );
32
+ const snapshot = createSnapshotTool(plugins ?? []);
33
+ this.registerTool(snapshot.definition, snapshot.handler);
146
34
 
147
- // Register spell tools
148
- this.registerTool(
149
- {
150
- name: "spell-done",
151
- description: "Mark a spell task as done",
152
- inputSchema: {
153
- type: "object",
154
- properties: {
155
- name: { type: "string", description: "Spell name" },
156
- taskNumber: { type: "number", description: "Task number (1-based)" },
157
- },
158
- required: ["name", "taskNumber"],
159
- },
160
- },
161
- async (params) => {
162
- const { readFileSync, writeFileSync } = await import("node:fs");
163
- const { spells } = await discoverSpells();
164
- const name = params.name as string;
165
- const taskNumber = params.taskNumber as number;
166
- const spell = spells.get(name);
167
- if (!spell) return `Spell "${name}" not found`;
168
- if (taskNumber < 1 || taskNumber > spell.definition.tasks.length) {
169
- return `Invalid task number ${taskNumber}`;
170
- }
171
- const task = spell.definition.tasks[taskNumber - 1];
172
- if (task.done) return `Task ${taskNumber} is already done`;
35
+ const diff = createDiffTool(plugins ?? []);
36
+ this.registerTool(diff.definition, diff.handler);
173
37
 
174
- const content = readFileSync(spell.filePath, "utf-8");
175
- let count = 0;
176
- const rewritten = content.replace(
177
- /task\(("[^"]*"|'[^']*'|`[^`]*`)((?:\s*,\s*\{[^}]*\})?)\)/g,
178
- (match, desc, opts) => {
179
- count++;
180
- if (count !== taskNumber) return match;
181
- if (opts && opts.includes("done:")) {
182
- return match.replace(/done:\s*false/, "done: true");
183
- }
184
- return `task(${desc}, { done: true })`;
185
- },
186
- );
187
- if (rewritten === content) return `Could not rewrite task ${taskNumber}`;
188
- writeFileSync(spell.filePath, rewritten);
189
- return `Task ${taskNumber} marked done: "${task.description}"`;
190
- },
191
- );
38
+ const spellDone = createSpellDoneTool();
39
+ this.registerTool(spellDone.definition, spellDone.handler);
192
40
 
193
41
  // Register plugin contributions
194
42
  if (plugins) {
@@ -241,7 +89,7 @@ export class McpServer {
241
89
  */
242
90
  private registerTool(
243
91
  definition: ToolDefinition,
244
- handler: (params: Record<string, unknown>) => Promise<unknown>
92
+ handler: ToolHandler,
245
93
  ): void {
246
94
  this.tools.set(definition.name, definition);
247
95
  this.toolHandlers.set(definition.name, handler);
@@ -276,51 +124,29 @@ export class McpServer {
276
124
  private async dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {
277
125
  switch (method) {
278
126
  case "initialize":
279
- return this.handleInitialize(params);
127
+ return {
128
+ protocolVersion: "2024-11-05",
129
+ capabilities: { tools: {}, resources: {} },
130
+ serverInfo: { name: "chant", version: "0.1.0" },
131
+ };
280
132
 
281
133
  case "tools/list":
282
- return this.handleToolsList();
134
+ return { tools: Array.from(this.tools.values()) };
283
135
 
284
136
  case "tools/call":
285
137
  return this.handleToolsCall(params);
286
138
 
287
139
  case "resources/list":
288
- return this.handleResourcesList();
140
+ return buildResourcesList(this.pluginResources);
289
141
 
290
142
  case "resources/read":
291
- return this.handleResourcesRead(params);
143
+ return handleResourcesRead(params, this.pluginResources);
292
144
 
293
145
  default:
294
146
  throw new Error(`Unknown method: ${method}`);
295
147
  }
296
148
  }
297
149
 
298
- /**
299
- * Handle initialize request
300
- */
301
- private handleInitialize(params: Record<string, unknown>): unknown {
302
- return {
303
- protocolVersion: "2024-11-05",
304
- capabilities: {
305
- tools: {},
306
- resources: {},
307
- },
308
- serverInfo: {
309
- name: "chant",
310
- version: "0.1.0",
311
- },
312
- };
313
- }
314
-
315
- /**
316
- * Handle tools/list request
317
- */
318
- private handleToolsList(): unknown {
319
- return {
320
- tools: Array.from(this.tools.values()),
321
- };
322
- }
323
-
324
150
  /**
325
151
  * Handle tools/call request
326
152
  */
@@ -331,12 +157,7 @@ export class McpServer {
331
157
  const handler = this.toolHandlers.get(name);
332
158
  if (!handler) {
333
159
  return {
334
- content: [
335
- {
336
- type: "text",
337
- text: `Error: Unknown tool: ${name}`,
338
- },
339
- ],
160
+ content: [{ type: "text", text: `Error: Unknown tool: ${name}` }],
340
161
  isError: true,
341
162
  };
342
163
  }
@@ -364,216 +185,6 @@ export class McpServer {
364
185
  }
365
186
  }
366
187
 
367
- /**
368
- * Handle resources/list request — merges core + plugin resources
369
- */
370
- private handleResourcesList(): unknown {
371
- const resources: ResourceDefinition[] = [
372
- {
373
- uri: "chant://context",
374
- name: "chant Context",
375
- description: "Lexicon-specific instructions and patterns for chant development",
376
- mimeType: "text/markdown",
377
- },
378
- {
379
- uri: "chant://examples/list",
380
- name: "Examples List",
381
- description: "List of available chant examples",
382
- mimeType: "application/json",
383
- },
384
- {
385
- uri: "chant://spells",
386
- name: "Spells",
387
- description: "List all spells with status, tasks, and lexicon",
388
- mimeType: "application/json",
389
- },
390
- {
391
- uri: "chant://spell/{name}",
392
- name: "Spell details",
393
- description: "Show spell definition and status",
394
- mimeType: "application/json",
395
- },
396
- {
397
- uri: "chant://spell/{name}/prompt",
398
- name: "Spell bootstrap prompt",
399
- description: "Bootstrap prompt for agent consumption",
400
- mimeType: "text/markdown",
401
- },
402
- {
403
- uri: "chant://state/{environment}",
404
- name: "State (all lexicons)",
405
- description: "All lexicon snapshots for an environment",
406
- mimeType: "application/json",
407
- },
408
- {
409
- uri: "chant://state/{environment}/{lexicon}",
410
- name: "State (single lexicon)",
411
- description: "Single lexicon snapshot for an environment",
412
- mimeType: "application/json",
413
- },
414
- ];
415
-
416
- // Merge plugin resources
417
- for (const { definition } of this.pluginResources.values()) {
418
- resources.push(definition);
419
- }
420
-
421
- return { resources };
422
- }
423
-
424
- /**
425
- * Collect example resources from plugins whose URI contains "examples/"
426
- */
427
- private collectExamples(): Array<{ name: string; description: string }> {
428
- const examples: Array<{ name: string; description: string }> = [];
429
- for (const [uri, { definition }] of this.pluginResources.entries()) {
430
- if (uri.includes("/examples/")) {
431
- const name = uri.replace(/^chant:\/\/[^/]+\/examples\//, "");
432
- examples.push({ name, description: definition.description });
433
- }
434
- }
435
- return examples;
436
- }
437
-
438
- /**
439
- * Handle resources/read request — checks plugin resources after core
440
- */
441
- private async handleResourcesRead(params: Record<string, unknown>): Promise<unknown> {
442
- const uri = params.uri as string;
443
-
444
- if (uri === "chant://context") {
445
- return {
446
- contents: [
447
- {
448
- uri,
449
- mimeType: "text/markdown",
450
- text: getContext(),
451
- },
452
- ],
453
- };
454
- }
455
-
456
- if (uri === "chant://examples/list") {
457
- return {
458
- contents: [
459
- {
460
- uri,
461
- mimeType: "application/json",
462
- text: JSON.stringify(this.collectExamples()),
463
- },
464
- ],
465
- };
466
- }
467
-
468
- // Spell resources
469
- if (uri === "chant://spells") {
470
- const { spells } = await discoverSpells();
471
- const list = Array.from(spells.entries()).map(([name, s]) => ({
472
- name,
473
- status: s.status,
474
- tasks: `${s.definition.tasks.filter((t) => t.done).length}/${s.definition.tasks.length}`,
475
- lexicon: s.definition.lexicon ?? null,
476
- overview: s.definition.overview,
477
- }));
478
- return {
479
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(list, null, 2) }],
480
- };
481
- }
482
-
483
- if (uri.startsWith("chant://spell/") && uri.endsWith("/prompt")) {
484
- const name = uri.replace("chant://spell/", "").replace("/prompt", "");
485
- const { spells } = await discoverSpells();
486
- const spell = spells.get(name);
487
- if (!spell) throw new Error(`Spell "${name}" not found`);
488
- const rt = getRuntime();
489
- const gitRootResult = await rt.spawn(["git", "rev-parse", "--show-toplevel"]);
490
- const gitRoot = gitRootResult.stdout.trim();
491
- const prompt = await generatePrompt(spell.definition, { gitRoot });
492
- return {
493
- contents: [{ uri, mimeType: "text/markdown", text: prompt }],
494
- };
495
- }
496
-
497
- if (uri.startsWith("chant://spell/")) {
498
- const name = uri.replace("chant://spell/", "");
499
- const { spells } = await discoverSpells();
500
- const spell = spells.get(name);
501
- if (!spell) throw new Error(`Spell "${name}" not found`);
502
- return {
503
- contents: [{
504
- uri,
505
- mimeType: "application/json",
506
- text: JSON.stringify({
507
- ...spell.definition,
508
- status: spell.status,
509
- filePath: spell.filePath,
510
- }, null, 2),
511
- }],
512
- };
513
- }
514
-
515
- // State resources: chant://state/{environment} and chant://state/{environment}/{lexicon}
516
- if (uri.startsWith("chant://state/")) {
517
- const parts = uri.replace("chant://state/", "").split("/");
518
- const environment = parts[0];
519
- const lexicon = parts[1];
520
-
521
- if (lexicon) {
522
- const content = await readSnapshot(environment, lexicon);
523
- if (!content) throw new Error(`No snapshot found for ${environment}/${lexicon}`);
524
- return {
525
- contents: [{ uri, mimeType: "application/json", text: content }],
526
- };
527
- } else {
528
- const snapshots = await readEnvironmentSnapshots(environment);
529
- const result: Record<string, unknown> = {};
530
- for (const [lex, content] of snapshots) {
531
- result[lex] = JSON.parse(content);
532
- }
533
- return {
534
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(result, null, 2) }],
535
- };
536
- }
537
- }
538
-
539
- if (uri.startsWith("chant://examples/")) {
540
- // Look up example in plugin resources
541
- const name = uri.replace("chant://examples/", "");
542
- for (const [pluginUri, pluginResource] of this.pluginResources.entries()) {
543
- if (pluginUri.endsWith(`/examples/${name}`)) {
544
- const text = await pluginResource.handler();
545
- return {
546
- contents: [
547
- {
548
- uri,
549
- mimeType: pluginResource.definition.mimeType ?? "text/typescript",
550
- text,
551
- },
552
- ],
553
- };
554
- }
555
- }
556
- throw new Error(`Example not found: ${name}`);
557
- }
558
-
559
- // Check plugin resources
560
- const pluginResource = this.pluginResources.get(uri);
561
- if (pluginResource) {
562
- const text = await pluginResource.handler();
563
- return {
564
- contents: [
565
- {
566
- uri,
567
- mimeType: pluginResource.definition.mimeType ?? "text/plain",
568
- text,
569
- },
570
- ],
571
- };
572
- }
573
-
574
- throw new Error(`Unknown resource: ${uri}`);
575
- }
576
-
577
188
  /**
578
189
  * Start the MCP server on stdio
579
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
+ }
@@ -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>;