@mandujs/mcp 0.18.8 → 0.18.10
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 +0 -1
- package/package.json +42 -42
- package/src/activity-monitor.ts +9 -8
- package/src/adapters/tool-adapter.ts +2 -0
- package/src/executor/error-handler.ts +268 -250
- package/src/index.ts +8 -0
- package/src/new-resources.ts +119 -0
- package/src/profiles.ts +34 -0
- package/src/prompts.ts +104 -0
- package/src/resources/handlers.ts +0 -23
- package/src/server.ts +78 -5
- package/src/tools/ate.ts +28 -0
- package/src/tools/brain.ts +56 -24
- package/src/tools/component.ts +194 -185
- package/src/tools/composite.ts +440 -0
- package/src/tools/contract.ts +58 -58
- package/src/tools/decisions.ts +270 -0
- package/src/tools/generate.ts +23 -21
- package/src/tools/guard.ts +32 -708
- package/src/tools/history.ts +24 -7
- package/src/tools/hydration.ts +40 -13
- package/src/tools/index.ts +28 -2
- package/src/tools/kitchen.ts +107 -0
- package/src/tools/negotiate.ts +263 -0
- package/src/tools/project.ts +464 -382
- package/src/tools/resource.ts +19 -2
- package/src/tools/runtime.ts +533 -508
- package/src/tools/seo.ts +446 -417
- package/src/tools/slot-validation.ts +200 -0
- package/src/tools/slot.ts +20 -21
- package/src/tools/spec.ts +45 -43
- package/src/tools/transaction.ts +55 -13
- package/src/tx-lock.ts +73 -0
- package/src/utils/project.ts +48 -9
- package/src/utils/runtime-control.ts +52 -0
- package/src/utils/withWarnings.ts +2 -1
package/src/tools/contract.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import {
|
|
3
3
|
loadManifest,
|
|
4
|
-
writeLock,
|
|
5
4
|
runContractGuardCheck,
|
|
6
5
|
generateContractTemplate,
|
|
7
6
|
generateOpenAPIDocument,
|
|
@@ -16,16 +15,12 @@ import fs from "fs/promises";
|
|
|
16
15
|
|
|
17
16
|
export const contractToolDefinitions: Tool[] = [
|
|
18
17
|
{
|
|
19
|
-
name: "
|
|
18
|
+
name: "mandu.contract.list",
|
|
19
|
+
annotations: {
|
|
20
|
+
readOnlyHint: true,
|
|
21
|
+
},
|
|
20
22
|
description:
|
|
21
|
-
"List
|
|
22
|
-
"In Mandu, a 'contract' is a Zod-based schema file (spec/contracts/{routeId}.contract.ts) that defines " +
|
|
23
|
-
"the request/response shape for an API route. Contracts enable: " +
|
|
24
|
-
"(1) automatic input validation and sanitization (strip/strict/passthrough normalize modes), " +
|
|
25
|
-
"(2) TypeScript type inference for slot handlers, " +
|
|
26
|
-
"(3) OpenAPI 3.0 spec generation via mandu_generate_openapi, " +
|
|
27
|
-
"(4) ATE L2 (schema validation) and L3 (full behavioral) test generation. " +
|
|
28
|
-
"Also returns a list of API routes that are missing contracts.",
|
|
23
|
+
"List routes with contract modules and API routes missing contracts.",
|
|
29
24
|
inputSchema: {
|
|
30
25
|
type: "object",
|
|
31
26
|
properties: {},
|
|
@@ -33,12 +28,12 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
33
28
|
},
|
|
34
29
|
},
|
|
35
30
|
{
|
|
36
|
-
name: "
|
|
31
|
+
name: "mandu.contract.get",
|
|
32
|
+
annotations: {
|
|
33
|
+
readOnlyHint: true,
|
|
34
|
+
},
|
|
37
35
|
description:
|
|
38
|
-
"
|
|
39
|
-
"Returns the raw content of spec/contracts/{routeId}.contract.ts, " +
|
|
40
|
-
"which contains Zod schemas for each HTTP method's request body/query params and response shape. " +
|
|
41
|
-
"If the route has no contract, returns a suggestion to create one with mandu_create_contract.",
|
|
36
|
+
"Read the TypeScript source of a route's contract file.",
|
|
42
37
|
inputSchema: {
|
|
43
38
|
type: "object",
|
|
44
39
|
properties: {
|
|
@@ -51,13 +46,13 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
51
46
|
},
|
|
52
47
|
},
|
|
53
48
|
{
|
|
54
|
-
name: "
|
|
49
|
+
name: "mandu.contract.create",
|
|
50
|
+
annotations: {
|
|
51
|
+
destructiveHint: false,
|
|
52
|
+
readOnlyHint: false,
|
|
53
|
+
},
|
|
55
54
|
description:
|
|
56
|
-
"Create a new contract file for a route and link it in the manifest.
|
|
57
|
-
"Generates spec/contracts/{routeId}.contract.ts with Zod schema stubs for each HTTP method. " +
|
|
58
|
-
"Template includes: request schema (body for POST/PUT/PATCH, query for GET/DELETE) and response schema. " +
|
|
59
|
-
"After creation: edit the Zod schemas to match your actual API shape, " +
|
|
60
|
-
"then run mandu_generate to regenerate typed handlers with validation.",
|
|
55
|
+
"Create a new contract file with Zod schema stubs for a route and link it in the manifest.",
|
|
61
56
|
inputSchema: {
|
|
62
57
|
type: "object",
|
|
63
58
|
properties: {
|
|
@@ -79,12 +74,12 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
79
74
|
},
|
|
80
75
|
},
|
|
81
76
|
{
|
|
82
|
-
name: "
|
|
77
|
+
name: "mandu.contract.link",
|
|
78
|
+
annotations: {
|
|
79
|
+
readOnlyHint: false,
|
|
80
|
+
},
|
|
83
81
|
description:
|
|
84
|
-
"Update the manifest to link an existing contract file to a route. "
|
|
85
|
-
"Use this when the contract file already exists but the manifest's contractModule path needs updating, " +
|
|
86
|
-
"or when moving a contract file to a new location. " +
|
|
87
|
-
"Does not modify the contract file itself — only updates the manifest reference and lock file.",
|
|
82
|
+
"Update the manifest to link an existing contract file to a route. Only changes the manifest reference.",
|
|
88
83
|
inputSchema: {
|
|
89
84
|
type: "object",
|
|
90
85
|
properties: {
|
|
@@ -101,13 +96,12 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
101
96
|
},
|
|
102
97
|
},
|
|
103
98
|
{
|
|
104
|
-
name: "
|
|
99
|
+
name: "mandu.contract.validate",
|
|
100
|
+
annotations: {
|
|
101
|
+
readOnlyHint: true,
|
|
102
|
+
},
|
|
105
103
|
description:
|
|
106
|
-
"Validate all contracts against their
|
|
107
|
-
"Checks that every HTTP method defined in a contract has a matching handler in the slot, " +
|
|
108
|
-
"response types are compatible, and no orphaned contracts exist. " +
|
|
109
|
-
"Run this after modifying contracts or slots, or before running ATE tests to catch mismatches early. " +
|
|
110
|
-
"Returns a list of violations with file locations, descriptions, and fix suggestions.",
|
|
104
|
+
"Validate all contracts against their slot implementations for method and type consistency.",
|
|
111
105
|
inputSchema: {
|
|
112
106
|
type: "object",
|
|
113
107
|
properties: {},
|
|
@@ -115,15 +109,12 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
115
109
|
},
|
|
116
110
|
},
|
|
117
111
|
{
|
|
118
|
-
name: "
|
|
112
|
+
name: "mandu.contract.sync",
|
|
113
|
+
annotations: {
|
|
114
|
+
readOnlyHint: true,
|
|
115
|
+
},
|
|
119
116
|
description:
|
|
120
|
-
"
|
|
121
|
-
"'contract-to-slot': finds HTTP methods defined in the contract but missing from the slot — " +
|
|
122
|
-
"generates handler stub code to add to the slot file. " +
|
|
123
|
-
"'slot-to-contract': finds slot handlers not documented in the contract — " +
|
|
124
|
-
"generates Zod schema stubs to add to the contract. " +
|
|
125
|
-
"Returns generated code snippets to review — does NOT auto-write files. " +
|
|
126
|
-
"Copy the output and use the Edit tool to apply the changes.",
|
|
117
|
+
"Generate code snippets to resolve HTTP method mismatches between a contract and its slot. Does not write files.",
|
|
127
118
|
inputSchema: {
|
|
128
119
|
type: "object",
|
|
129
120
|
properties: {
|
|
@@ -143,12 +134,12 @@ export const contractToolDefinitions: Tool[] = [
|
|
|
143
134
|
},
|
|
144
135
|
},
|
|
145
136
|
{
|
|
146
|
-
name: "
|
|
137
|
+
name: "mandu.contract.openapi",
|
|
138
|
+
annotations: {
|
|
139
|
+
readOnlyHint: false,
|
|
140
|
+
},
|
|
147
141
|
description:
|
|
148
|
-
"Generate an OpenAPI 3.0
|
|
149
|
-
"Only routes with a defined contractModule are included — add contracts with mandu_create_contract first. " +
|
|
150
|
-
"Output is written to openapi.json (or a custom path). " +
|
|
151
|
-
"The generated spec can be served with Swagger UI, Redoc, or imported into any OpenAPI-compatible tooling.",
|
|
142
|
+
"Generate an OpenAPI 3.0 JSON spec from all routes with contract modules.",
|
|
152
143
|
inputSchema: {
|
|
153
144
|
type: "object",
|
|
154
145
|
properties: {
|
|
@@ -190,8 +181,8 @@ async function readFileContent(filePath: string): Promise<string | null> {
|
|
|
190
181
|
export function contractTools(projectRoot: string) {
|
|
191
182
|
const paths = getProjectPaths(projectRoot);
|
|
192
183
|
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
185
|
+
"mandu.contract.list": async () => {
|
|
195
186
|
const result = await loadManifest(paths.manifestPath);
|
|
196
187
|
if (!result.success || !result.data) {
|
|
197
188
|
return { error: result.errors };
|
|
@@ -217,7 +208,7 @@ export function contractTools(projectRoot: string) {
|
|
|
217
208
|
};
|
|
218
209
|
},
|
|
219
210
|
|
|
220
|
-
|
|
211
|
+
"mandu.contract.get": async (args: Record<string, unknown>) => {
|
|
221
212
|
const { routeId } = args as { routeId: string };
|
|
222
213
|
|
|
223
214
|
const result = await loadManifest(paths.manifestPath);
|
|
@@ -234,7 +225,7 @@ export function contractTools(projectRoot: string) {
|
|
|
234
225
|
return {
|
|
235
226
|
routeId,
|
|
236
227
|
hasContract: false,
|
|
237
|
-
suggestion: `Create a contract with:
|
|
228
|
+
suggestion: `Create a contract with: mandu.contract.create({ routeId: "${routeId}" })`,
|
|
238
229
|
};
|
|
239
230
|
}
|
|
240
231
|
|
|
@@ -258,7 +249,7 @@ export function contractTools(projectRoot: string) {
|
|
|
258
249
|
};
|
|
259
250
|
},
|
|
260
251
|
|
|
261
|
-
|
|
252
|
+
"mandu.contract.create": async (args: Record<string, unknown>) => {
|
|
262
253
|
const { routeId, description, methods } = args as {
|
|
263
254
|
routeId: string;
|
|
264
255
|
description?: string;
|
|
@@ -311,7 +302,6 @@ export function contractTools(projectRoot: string) {
|
|
|
311
302
|
};
|
|
312
303
|
|
|
313
304
|
await writeJsonFile(paths.manifestPath, result.data);
|
|
314
|
-
await writeLock(paths.lockPath, result.data);
|
|
315
305
|
}
|
|
316
306
|
|
|
317
307
|
return {
|
|
@@ -320,13 +310,13 @@ export function contractTools(projectRoot: string) {
|
|
|
320
310
|
message: `Contract created for route '${routeId}'`,
|
|
321
311
|
nextSteps: [
|
|
322
312
|
`Edit ${contractPath} to define your API schema`,
|
|
323
|
-
"Run
|
|
324
|
-
"Run
|
|
313
|
+
"Run mandu.generate to regenerate handlers with validation",
|
|
314
|
+
"Run mandu.contract.validate to check consistency",
|
|
325
315
|
],
|
|
326
316
|
};
|
|
327
317
|
},
|
|
328
318
|
|
|
329
|
-
|
|
319
|
+
"mandu.contract.link": async (args: Record<string, unknown>) => {
|
|
330
320
|
const { routeId, contractModule } = args as {
|
|
331
321
|
routeId: string;
|
|
332
322
|
contractModule: string;
|
|
@@ -352,7 +342,6 @@ export function contractTools(projectRoot: string) {
|
|
|
352
342
|
|
|
353
343
|
// Write updated manifest
|
|
354
344
|
await writeJsonFile(paths.manifestPath, result.data);
|
|
355
|
-
await writeLock(paths.lockPath, result.data);
|
|
356
345
|
|
|
357
346
|
return {
|
|
358
347
|
success: true,
|
|
@@ -361,7 +350,7 @@ export function contractTools(projectRoot: string) {
|
|
|
361
350
|
};
|
|
362
351
|
},
|
|
363
352
|
|
|
364
|
-
|
|
353
|
+
"mandu.contract.validate": async () => {
|
|
365
354
|
const result = await loadManifest(paths.manifestPath);
|
|
366
355
|
if (!result.success || !result.data) {
|
|
367
356
|
return { error: result.errors };
|
|
@@ -392,7 +381,7 @@ export function contractTools(projectRoot: string) {
|
|
|
392
381
|
};
|
|
393
382
|
},
|
|
394
383
|
|
|
395
|
-
|
|
384
|
+
"mandu.contract.sync": async (args: Record<string, unknown>) => {
|
|
396
385
|
const { routeId, direction } = args as {
|
|
397
386
|
routeId: string;
|
|
398
387
|
direction: "contract-to-slot" | "slot-to-contract";
|
|
@@ -526,7 +515,7 @@ export function contractTools(projectRoot: string) {
|
|
|
526
515
|
}
|
|
527
516
|
},
|
|
528
517
|
|
|
529
|
-
|
|
518
|
+
"mandu.contract.openapi": async (args: Record<string, unknown>) => {
|
|
530
519
|
const { output, title, version } = args as {
|
|
531
520
|
output?: string;
|
|
532
521
|
title?: string;
|
|
@@ -581,4 +570,15 @@ export function contractTools(projectRoot: string) {
|
|
|
581
570
|
}
|
|
582
571
|
},
|
|
583
572
|
};
|
|
573
|
+
|
|
574
|
+
// Backward-compatible aliases (deprecated)
|
|
575
|
+
handlers["mandu_list_contracts"] = handlers["mandu.contract.list"];
|
|
576
|
+
handlers["mandu_get_contract"] = handlers["mandu.contract.get"];
|
|
577
|
+
handlers["mandu_create_contract"] = handlers["mandu.contract.create"];
|
|
578
|
+
handlers["mandu_update_route_contract"] = handlers["mandu.contract.link"];
|
|
579
|
+
handlers["mandu_validate_contracts"] = handlers["mandu.contract.validate"];
|
|
580
|
+
handlers["mandu_sync_contract_slot"] = handlers["mandu.contract.sync"];
|
|
581
|
+
handlers["mandu_generate_openapi"] = handlers["mandu.contract.openapi"];
|
|
582
|
+
|
|
583
|
+
return handlers;
|
|
584
584
|
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import {
|
|
3
|
+
searchDecisions,
|
|
4
|
+
saveDecision,
|
|
5
|
+
checkConsistency,
|
|
6
|
+
getCompactArchitecture,
|
|
7
|
+
getNextDecisionId,
|
|
8
|
+
type ArchitectureDecision,
|
|
9
|
+
type DecisionStatus,
|
|
10
|
+
} from "@mandujs/core";
|
|
11
|
+
|
|
12
|
+
export const decisionToolDefinitions: Tool[] = [
|
|
13
|
+
{
|
|
14
|
+
name: "mandu.decision.list",
|
|
15
|
+
description:
|
|
16
|
+
"Search architecture decisions (ADRs) by tags. Use before implementing features for consistency.",
|
|
17
|
+
annotations: {
|
|
18
|
+
readOnlyHint: true,
|
|
19
|
+
},
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
tags: {
|
|
24
|
+
type: "array",
|
|
25
|
+
items: { type: "string" },
|
|
26
|
+
description: "Tags to search for (e.g., ['auth', 'cache', 'api'])",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ["tags"],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "mandu.decision.save",
|
|
34
|
+
description:
|
|
35
|
+
"Save a new architecture decision record (ADR) for future consistency checks.",
|
|
36
|
+
annotations: {
|
|
37
|
+
readOnlyHint: false,
|
|
38
|
+
},
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
title: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Decision title (e.g., 'Use JWT for API Authentication')",
|
|
45
|
+
},
|
|
46
|
+
tags: {
|
|
47
|
+
type: "array",
|
|
48
|
+
items: { type: "string" },
|
|
49
|
+
description: "Tags for searchability (e.g., ['auth', 'api', 'security'])",
|
|
50
|
+
},
|
|
51
|
+
context: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Why this decision was needed",
|
|
54
|
+
},
|
|
55
|
+
decision: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "What was decided",
|
|
58
|
+
},
|
|
59
|
+
consequences: {
|
|
60
|
+
type: "array",
|
|
61
|
+
items: { type: "string" },
|
|
62
|
+
description: "Impact and trade-offs of this decision",
|
|
63
|
+
},
|
|
64
|
+
status: {
|
|
65
|
+
type: "string",
|
|
66
|
+
enum: ["proposed", "accepted", "deprecated", "superseded"],
|
|
67
|
+
description: "Decision status (default: proposed)",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ["title", "tags", "context", "decision", "consequences"],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "mandu.decision.check",
|
|
75
|
+
description:
|
|
76
|
+
"Check if a proposed change conflicts with existing architecture decisions.",
|
|
77
|
+
annotations: {
|
|
78
|
+
readOnlyHint: true,
|
|
79
|
+
},
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
intent: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "What you're trying to do (e.g., 'Add Redis caching layer')",
|
|
86
|
+
},
|
|
87
|
+
tags: {
|
|
88
|
+
type: "array",
|
|
89
|
+
items: { type: "string" },
|
|
90
|
+
description: "Related tags to check against (e.g., ['cache', 'redis'])",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ["intent", "tags"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "mandu.decision.architecture",
|
|
98
|
+
description:
|
|
99
|
+
"Get a compact summary of project architecture decisions and rules.",
|
|
100
|
+
annotations: {
|
|
101
|
+
readOnlyHint: true,
|
|
102
|
+
},
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {},
|
|
106
|
+
required: [],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
export function decisionTools(projectRoot: string) {
|
|
112
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
113
|
+
"mandu.decision.list": async (args: Record<string, unknown>) => {
|
|
114
|
+
const { tags } = args as { tags: string[] };
|
|
115
|
+
|
|
116
|
+
if (!tags || tags.length === 0) {
|
|
117
|
+
return {
|
|
118
|
+
error: "Tags are required",
|
|
119
|
+
tip: "Provide at least one tag to search for (e.g., ['auth', 'cache'])",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = await searchDecisions(projectRoot, tags);
|
|
124
|
+
|
|
125
|
+
if (result.decisions.length === 0) {
|
|
126
|
+
return {
|
|
127
|
+
found: false,
|
|
128
|
+
message: `No decisions found for tags: ${tags.join(", ")}`,
|
|
129
|
+
searchedTags: tags,
|
|
130
|
+
tip: "Try broader tags or check spec/decisions/ directory",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
found: true,
|
|
136
|
+
total: result.total,
|
|
137
|
+
searchedTags: tags,
|
|
138
|
+
decisions: result.decisions.map((d) => ({
|
|
139
|
+
id: d.id,
|
|
140
|
+
title: d.title,
|
|
141
|
+
status: d.status,
|
|
142
|
+
date: d.date,
|
|
143
|
+
tags: d.tags,
|
|
144
|
+
context: d.context.slice(0, 200) + (d.context.length > 200 ? "..." : ""),
|
|
145
|
+
decision: d.decision,
|
|
146
|
+
consequences: d.consequences,
|
|
147
|
+
relatedDecisions: d.relatedDecisions,
|
|
148
|
+
})),
|
|
149
|
+
tip: "Follow these decisions for consistency. Use mandu.decision.save if you make a new architectural choice.",
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
"mandu.decision.save": async (args: Record<string, unknown>) => {
|
|
154
|
+
const { title, tags, context, decision, consequences, status } = args as {
|
|
155
|
+
title: string;
|
|
156
|
+
tags: string[];
|
|
157
|
+
context: string;
|
|
158
|
+
decision: string;
|
|
159
|
+
consequences: string[];
|
|
160
|
+
status?: DecisionStatus;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Validate required fields
|
|
164
|
+
if (!title || !tags || !context || !decision || !consequences) {
|
|
165
|
+
return {
|
|
166
|
+
error: "Missing required fields",
|
|
167
|
+
required: ["title", "tags", "context", "decision", "consequences"],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Get next ID
|
|
172
|
+
const id = await getNextDecisionId(projectRoot);
|
|
173
|
+
|
|
174
|
+
// Save decision
|
|
175
|
+
const newDecision: Omit<ArchitectureDecision, "date"> = {
|
|
176
|
+
id,
|
|
177
|
+
title,
|
|
178
|
+
status: status || "proposed",
|
|
179
|
+
tags: tags.map((t) => t.toLowerCase()),
|
|
180
|
+
context,
|
|
181
|
+
decision,
|
|
182
|
+
consequences,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const result = await saveDecision(projectRoot, newDecision);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
success: result.success,
|
|
189
|
+
decision: {
|
|
190
|
+
id,
|
|
191
|
+
title,
|
|
192
|
+
status: status || "proposed",
|
|
193
|
+
tags,
|
|
194
|
+
},
|
|
195
|
+
filePath: result.filePath,
|
|
196
|
+
message: result.message,
|
|
197
|
+
tip: "Decision saved. It will be found when searching for related tags.",
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
"mandu.decision.check": async (args: Record<string, unknown>) => {
|
|
202
|
+
const { intent, tags } = args as {
|
|
203
|
+
intent: string;
|
|
204
|
+
tags: string[];
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (!intent || !tags || tags.length === 0) {
|
|
208
|
+
return {
|
|
209
|
+
error: "Intent and tags are required",
|
|
210
|
+
tip: "Describe what you're trying to do and provide related tags",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const result = await checkConsistency(projectRoot, intent, tags);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
consistent: result.consistent,
|
|
218
|
+
intent,
|
|
219
|
+
checkedTags: tags,
|
|
220
|
+
relatedDecisions: result.relatedDecisions.map((d) => ({
|
|
221
|
+
id: d.id,
|
|
222
|
+
title: d.title,
|
|
223
|
+
status: d.status,
|
|
224
|
+
decision: d.decision.slice(0, 150) + "...",
|
|
225
|
+
})),
|
|
226
|
+
warnings: result.warnings,
|
|
227
|
+
suggestions: result.suggestions,
|
|
228
|
+
tip: result.consistent
|
|
229
|
+
? "No conflicts found. Proceed with implementation following the suggestions."
|
|
230
|
+
: "⚠️ Review warnings before proceeding. Some decisions may conflict.",
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
"mandu.decision.architecture": async () => {
|
|
235
|
+
const compact = await getCompactArchitecture(projectRoot);
|
|
236
|
+
|
|
237
|
+
if (!compact) {
|
|
238
|
+
return {
|
|
239
|
+
found: false,
|
|
240
|
+
message: "No architecture information found",
|
|
241
|
+
tip: "Save some decisions first using mandu.decision.save",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
found: true,
|
|
247
|
+
project: compact.project,
|
|
248
|
+
lastUpdated: compact.lastUpdated,
|
|
249
|
+
summary: {
|
|
250
|
+
totalDecisions: compact.keyDecisions.length,
|
|
251
|
+
topTags: Object.entries(compact.tagCounts)
|
|
252
|
+
.sort(([, a], [, b]) => b - a)
|
|
253
|
+
.slice(0, 10)
|
|
254
|
+
.map(([tag, count]) => ({ tag, count })),
|
|
255
|
+
},
|
|
256
|
+
keyDecisions: compact.keyDecisions,
|
|
257
|
+
rules: compact.rules,
|
|
258
|
+
tip: "Use mandu.decision.list with specific tags for detailed information.",
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Backward-compatible aliases (deprecated)
|
|
264
|
+
handlers["mandu_get_decisions"] = handlers["mandu.decision.list"];
|
|
265
|
+
handlers["mandu_save_decision"] = handlers["mandu.decision.save"];
|
|
266
|
+
handlers["mandu_check_consistency"] = handlers["mandu.decision.check"];
|
|
267
|
+
handlers["mandu_get_architecture"] = handlers["mandu.decision.architecture"];
|
|
268
|
+
|
|
269
|
+
return handlers;
|
|
270
|
+
}
|
package/src/tools/generate.ts
CHANGED
|
@@ -6,19 +6,15 @@ import fs from "fs/promises";
|
|
|
6
6
|
|
|
7
7
|
export const generateToolDefinitions: Tool[] = [
|
|
8
8
|
{
|
|
9
|
-
name: "
|
|
9
|
+
name: "mandu.generate",
|
|
10
10
|
description:
|
|
11
|
-
"Generate all
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"(2) Resource generation (resources=true, default): scans spec/resources/{name}/schema.ts " +
|
|
19
|
-
"and generates CRUD boilerplate (repository, service, handlers) for each declared resource. " +
|
|
20
|
-
"Run this after adding routes, modifying slot/contract files, or changing resource schemas. " +
|
|
21
|
-
"Use dryRun=true to preview what would be created or overwritten without writing any files.",
|
|
11
|
+
"Generate all framework artifacts (server handlers, web components, resources) from the manifest. " +
|
|
12
|
+
"Run after adding routes or modifying slots/contracts. Use dryRun=true to preview.",
|
|
13
|
+
annotations: {
|
|
14
|
+
destructiveHint: true,
|
|
15
|
+
readOnlyHint: false,
|
|
16
|
+
idempotentHint: true,
|
|
17
|
+
},
|
|
22
18
|
inputSchema: {
|
|
23
19
|
type: "object",
|
|
24
20
|
properties: {
|
|
@@ -35,12 +31,12 @@ export const generateToolDefinitions: Tool[] = [
|
|
|
35
31
|
},
|
|
36
32
|
},
|
|
37
33
|
{
|
|
38
|
-
name: "
|
|
34
|
+
name: "mandu.generate.status",
|
|
39
35
|
description:
|
|
40
|
-
"Show the current state of all generated artifacts from .mandu/generated.map.json.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
"Show the current state of all generated artifacts from .mandu/generated.map.json.",
|
|
37
|
+
annotations: {
|
|
38
|
+
readOnlyHint: true,
|
|
39
|
+
},
|
|
44
40
|
inputSchema: {
|
|
45
41
|
type: "object",
|
|
46
42
|
properties: {},
|
|
@@ -52,8 +48,8 @@ export const generateToolDefinitions: Tool[] = [
|
|
|
52
48
|
export function generateTools(projectRoot: string) {
|
|
53
49
|
const paths = getProjectPaths(projectRoot);
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
52
|
+
"mandu.generate": async (args: Record<string, unknown>) => {
|
|
57
53
|
const { dryRun, resources = true } = args as { dryRun?: boolean; resources?: boolean };
|
|
58
54
|
|
|
59
55
|
// Regenerate manifest from FS Routes first
|
|
@@ -166,14 +162,14 @@ export function generateTools(projectRoot: string) {
|
|
|
166
162
|
};
|
|
167
163
|
},
|
|
168
164
|
|
|
169
|
-
|
|
165
|
+
"mandu.generate.status": async () => {
|
|
170
166
|
// Read generated map
|
|
171
167
|
const generatedMap = await readJsonFile<GeneratedMap>(paths.generatedMapPath);
|
|
172
168
|
|
|
173
169
|
if (!generatedMap) {
|
|
174
170
|
return {
|
|
175
171
|
hasGeneratedFiles: false,
|
|
176
|
-
message: "No generated.map.json found. Run
|
|
172
|
+
message: "No generated.map.json found. Run mandu.generate first.",
|
|
177
173
|
};
|
|
178
174
|
}
|
|
179
175
|
|
|
@@ -197,4 +193,10 @@ export function generateTools(projectRoot: string) {
|
|
|
197
193
|
};
|
|
198
194
|
},
|
|
199
195
|
};
|
|
196
|
+
|
|
197
|
+
// Backward-compatible aliases
|
|
198
|
+
handlers["mandu_generate"] = handlers["mandu.generate"];
|
|
199
|
+
handlers["mandu_generate_status"] = handlers["mandu.generate.status"];
|
|
200
|
+
|
|
201
|
+
return handlers;
|
|
200
202
|
}
|