@mandujs/mcp 0.18.9 → 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 -25
- 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/guard.ts
CHANGED
|
@@ -13,35 +13,17 @@ import {
|
|
|
13
13
|
type GuardConfig,
|
|
14
14
|
type ViolationType,
|
|
15
15
|
type GuardPreset,
|
|
16
|
-
// Decision Memory imports
|
|
17
|
-
searchDecisions,
|
|
18
|
-
saveDecision,
|
|
19
|
-
checkConsistency,
|
|
20
|
-
getCompactArchitecture,
|
|
21
|
-
getNextDecisionId,
|
|
22
|
-
type ArchitectureDecision,
|
|
23
|
-
type DecisionStatus,
|
|
24
|
-
// Semantic Slots imports
|
|
25
|
-
validateSlotConstraints,
|
|
26
|
-
validateSlots,
|
|
27
|
-
DEFAULT_SLOT_CONSTRAINTS,
|
|
28
|
-
API_SLOT_CONSTRAINTS,
|
|
29
|
-
READONLY_SLOT_CONSTRAINTS,
|
|
30
|
-
type SlotConstraints,
|
|
31
|
-
// Architecture Negotiation imports
|
|
32
|
-
negotiate,
|
|
33
|
-
generateScaffold,
|
|
34
|
-
analyzeExistingStructure,
|
|
35
|
-
type NegotiationRequest,
|
|
36
|
-
type FeatureCategory,
|
|
37
16
|
} from "@mandujs/core";
|
|
38
17
|
import { getProjectPaths, readJsonFile, readConfig } from "../utils/project.js";
|
|
39
18
|
|
|
40
19
|
export const guardToolDefinitions: Tool[] = [
|
|
41
20
|
{
|
|
42
|
-
name: "
|
|
21
|
+
name: "mandu.guard.check",
|
|
43
22
|
description:
|
|
44
23
|
"Run guard checks to validate spec integrity, generated files, and slot files",
|
|
24
|
+
annotations: {
|
|
25
|
+
readOnlyHint: true,
|
|
26
|
+
},
|
|
45
27
|
inputSchema: {
|
|
46
28
|
type: "object",
|
|
47
29
|
properties: {
|
|
@@ -54,9 +36,12 @@ export const guardToolDefinitions: Tool[] = [
|
|
|
54
36
|
},
|
|
55
37
|
},
|
|
56
38
|
{
|
|
57
|
-
name: "
|
|
39
|
+
name: "mandu.guard.analyze",
|
|
58
40
|
description:
|
|
59
41
|
"Analyze a ManduError JSON to provide actionable fix guidance",
|
|
42
|
+
annotations: {
|
|
43
|
+
readOnlyHint: true,
|
|
44
|
+
},
|
|
60
45
|
inputSchema: {
|
|
61
46
|
type: "object",
|
|
62
47
|
properties: {
|
|
@@ -68,14 +53,13 @@ export const guardToolDefinitions: Tool[] = [
|
|
|
68
53
|
required: ["errorJson"],
|
|
69
54
|
},
|
|
70
55
|
},
|
|
71
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
72
|
-
// Self-Healing Guard Tools (NEW)
|
|
73
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
74
56
|
{
|
|
75
|
-
name: "
|
|
57
|
+
name: "mandu.guard.heal",
|
|
76
58
|
description:
|
|
77
|
-
"
|
|
78
|
-
|
|
59
|
+
"Detect architecture violations with auto-fix suggestions. Use autoFix=true to apply fixes automatically.",
|
|
60
|
+
annotations: {
|
|
61
|
+
readOnlyHint: false,
|
|
62
|
+
},
|
|
79
63
|
inputSchema: {
|
|
80
64
|
type: "object",
|
|
81
65
|
properties: {
|
|
@@ -97,10 +81,12 @@ export const guardToolDefinitions: Tool[] = [
|
|
|
97
81
|
},
|
|
98
82
|
},
|
|
99
83
|
{
|
|
100
|
-
name: "
|
|
84
|
+
name: "mandu.guard.explain",
|
|
101
85
|
description:
|
|
102
|
-
"Explain a specific guard rule
|
|
103
|
-
|
|
86
|
+
"Explain a specific guard rule with rationale, fix guidance, and code examples.",
|
|
87
|
+
annotations: {
|
|
88
|
+
readOnlyHint: true,
|
|
89
|
+
},
|
|
104
90
|
inputSchema: {
|
|
105
91
|
type: "object",
|
|
106
92
|
properties: {
|
|
@@ -126,251 +112,13 @@ export const guardToolDefinitions: Tool[] = [
|
|
|
126
112
|
required: ["type", "fromLayer", "toLayer"],
|
|
127
113
|
},
|
|
128
114
|
},
|
|
129
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
-
// Decision Memory Tools
|
|
131
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
132
|
-
{
|
|
133
|
-
name: "mandu_get_decisions",
|
|
134
|
-
description:
|
|
135
|
-
"Search and retrieve architecture decisions (ADRs) by tags. " +
|
|
136
|
-
"Use this before implementing features to ensure consistency with past decisions. " +
|
|
137
|
-
"Example: Before adding 'auth' feature, search for ['auth', 'security'] to find related decisions.",
|
|
138
|
-
inputSchema: {
|
|
139
|
-
type: "object",
|
|
140
|
-
properties: {
|
|
141
|
-
tags: {
|
|
142
|
-
type: "array",
|
|
143
|
-
items: { type: "string" },
|
|
144
|
-
description: "Tags to search for (e.g., ['auth', 'cache', 'api'])",
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
required: ["tags"],
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: "mandu_save_decision",
|
|
152
|
-
description:
|
|
153
|
-
"Save a new architecture decision record (ADR). " +
|
|
154
|
-
"Use this when making significant architectural choices that should be remembered for consistency.",
|
|
155
|
-
inputSchema: {
|
|
156
|
-
type: "object",
|
|
157
|
-
properties: {
|
|
158
|
-
title: {
|
|
159
|
-
type: "string",
|
|
160
|
-
description: "Decision title (e.g., 'Use JWT for API Authentication')",
|
|
161
|
-
},
|
|
162
|
-
tags: {
|
|
163
|
-
type: "array",
|
|
164
|
-
items: { type: "string" },
|
|
165
|
-
description: "Tags for searchability (e.g., ['auth', 'api', 'security'])",
|
|
166
|
-
},
|
|
167
|
-
context: {
|
|
168
|
-
type: "string",
|
|
169
|
-
description: "Why this decision was needed",
|
|
170
|
-
},
|
|
171
|
-
decision: {
|
|
172
|
-
type: "string",
|
|
173
|
-
description: "What was decided",
|
|
174
|
-
},
|
|
175
|
-
consequences: {
|
|
176
|
-
type: "array",
|
|
177
|
-
items: { type: "string" },
|
|
178
|
-
description: "Impact and trade-offs of this decision",
|
|
179
|
-
},
|
|
180
|
-
status: {
|
|
181
|
-
type: "string",
|
|
182
|
-
enum: ["proposed", "accepted", "deprecated", "superseded"],
|
|
183
|
-
description: "Decision status (default: proposed)",
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
required: ["title", "tags", "context", "decision", "consequences"],
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: "mandu_check_consistency",
|
|
191
|
-
description:
|
|
192
|
-
"Check if a proposed change is consistent with existing architecture decisions. " +
|
|
193
|
-
"Use this before implementing to catch potential conflicts with past decisions.",
|
|
194
|
-
inputSchema: {
|
|
195
|
-
type: "object",
|
|
196
|
-
properties: {
|
|
197
|
-
intent: {
|
|
198
|
-
type: "string",
|
|
199
|
-
description: "What you're trying to do (e.g., 'Add Redis caching layer')",
|
|
200
|
-
},
|
|
201
|
-
tags: {
|
|
202
|
-
type: "array",
|
|
203
|
-
items: { type: "string" },
|
|
204
|
-
description: "Related tags to check against (e.g., ['cache', 'redis'])",
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
required: ["intent", "tags"],
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: "mandu_get_architecture",
|
|
212
|
-
description:
|
|
213
|
-
"Get a compact summary of project architecture decisions. " +
|
|
214
|
-
"Returns key decisions, tag statistics, and architecture rules for quick context.",
|
|
215
|
-
inputSchema: {
|
|
216
|
-
type: "object",
|
|
217
|
-
properties: {},
|
|
218
|
-
required: [],
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
222
|
-
// Semantic Slots Tools
|
|
223
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
224
|
-
{
|
|
225
|
-
name: "mandu_validate_slot",
|
|
226
|
-
description:
|
|
227
|
-
"Validate a slot file against semantic constraints. " +
|
|
228
|
-
"Checks code lines, complexity, required/forbidden patterns, and import rules.",
|
|
229
|
-
inputSchema: {
|
|
230
|
-
type: "object",
|
|
231
|
-
properties: {
|
|
232
|
-
file: {
|
|
233
|
-
type: "string",
|
|
234
|
-
description: "Path to the slot file to validate",
|
|
235
|
-
},
|
|
236
|
-
preset: {
|
|
237
|
-
type: "string",
|
|
238
|
-
enum: ["default", "api", "readonly"],
|
|
239
|
-
description: "Constraint preset to use (default: 'default')",
|
|
240
|
-
},
|
|
241
|
-
constraints: {
|
|
242
|
-
type: "object",
|
|
243
|
-
description: "Custom constraints (overrides preset)",
|
|
244
|
-
properties: {
|
|
245
|
-
maxLines: { type: "number" },
|
|
246
|
-
maxCyclomaticComplexity: { type: "number" },
|
|
247
|
-
requiredPatterns: { type: "array", items: { type: "string" } },
|
|
248
|
-
forbiddenPatterns: { type: "array", items: { type: "string" } },
|
|
249
|
-
allowedImports: { type: "array", items: { type: "string" } },
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
required: ["file"],
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: "mandu_get_slot_constraints",
|
|
258
|
-
description:
|
|
259
|
-
"Get recommended slot constraints for different use cases. " +
|
|
260
|
-
"Returns preset constraints that can be used with .constraints() in Filling API.",
|
|
261
|
-
inputSchema: {
|
|
262
|
-
type: "object",
|
|
263
|
-
properties: {
|
|
264
|
-
preset: {
|
|
265
|
-
type: "string",
|
|
266
|
-
enum: ["default", "api", "readonly"],
|
|
267
|
-
description: "Constraint preset to retrieve",
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
required: [],
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
274
|
-
// Architecture Negotiation Tools
|
|
275
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
276
|
-
{
|
|
277
|
-
name: "mandu_negotiate",
|
|
278
|
-
description:
|
|
279
|
-
"Negotiate with the framework before implementing a feature. " +
|
|
280
|
-
"Describes your intent and gets back the recommended project structure, " +
|
|
281
|
-
"file templates, and related architecture decisions. " +
|
|
282
|
-
"Use this BEFORE writing code to ensure architectural consistency. " +
|
|
283
|
-
"IMPORTANT: Always provide 'featureName' as a short English slug (e.g., 'chat', 'user-auth', 'payment'). " +
|
|
284
|
-
"Even if the user speaks Korean, YOU must translate the feature name to English.",
|
|
285
|
-
inputSchema: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: {
|
|
288
|
-
intent: {
|
|
289
|
-
type: "string",
|
|
290
|
-
description: "What you want to implement, in any language (e.g., '사용자 인증 기능 추가', 'Add payment integration')",
|
|
291
|
-
},
|
|
292
|
-
featureName: {
|
|
293
|
-
type: "string",
|
|
294
|
-
description: "REQUIRED: Short English slug for the feature name (e.g., 'chat', 'user-auth', 'payment', 'file-upload'). " +
|
|
295
|
-
"You MUST translate the user's intent to a concise English identifier. " +
|
|
296
|
-
"Use lowercase kebab-case. This becomes the directory/module name.",
|
|
297
|
-
},
|
|
298
|
-
requirements: {
|
|
299
|
-
type: "array",
|
|
300
|
-
items: { type: "string" },
|
|
301
|
-
description: "Specific requirements (e.g., ['JWT-based', 'OAuth support'])",
|
|
302
|
-
},
|
|
303
|
-
constraints: {
|
|
304
|
-
type: "array",
|
|
305
|
-
items: { type: "string" },
|
|
306
|
-
description: "Constraints to respect (e.g., ['use existing User model', 'Redis sessions'])",
|
|
307
|
-
},
|
|
308
|
-
category: {
|
|
309
|
-
type: "string",
|
|
310
|
-
enum: ["auth", "crud", "api", "ui", "integration", "data", "util", "config", "other"],
|
|
311
|
-
description: "Feature category (auto-detected if not specified)",
|
|
312
|
-
},
|
|
313
|
-
preset: {
|
|
314
|
-
type: "string",
|
|
315
|
-
enum: ["fsd", "clean", "hexagonal", "atomic", "cqrs", "mandu"],
|
|
316
|
-
description: "Architecture preset (default: mandu). Use 'cqrs' for Command/Query separation.",
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
required: ["intent"],
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: "mandu_generate_scaffold",
|
|
324
|
-
description:
|
|
325
|
-
"Generate scaffold files from a negotiation plan. " +
|
|
326
|
-
"Creates directories and file templates based on the approved structure.",
|
|
327
|
-
inputSchema: {
|
|
328
|
-
type: "object",
|
|
329
|
-
properties: {
|
|
330
|
-
intent: {
|
|
331
|
-
type: "string",
|
|
332
|
-
description: "Feature intent (used to get the structure plan)",
|
|
333
|
-
},
|
|
334
|
-
category: {
|
|
335
|
-
type: "string",
|
|
336
|
-
enum: ["auth", "crud", "api", "ui", "integration", "data", "util", "config", "other"],
|
|
337
|
-
description: "Feature category",
|
|
338
|
-
},
|
|
339
|
-
dryRun: {
|
|
340
|
-
type: "boolean",
|
|
341
|
-
description: "If true, only show what would be created without actually creating files",
|
|
342
|
-
},
|
|
343
|
-
overwrite: {
|
|
344
|
-
type: "boolean",
|
|
345
|
-
description: "If true, overwrite existing files (default: false)",
|
|
346
|
-
},
|
|
347
|
-
preset: {
|
|
348
|
-
type: "string",
|
|
349
|
-
enum: ["fsd", "clean", "hexagonal", "atomic", "cqrs", "mandu"],
|
|
350
|
-
description: "Architecture preset (default: mandu)",
|
|
351
|
-
},
|
|
352
|
-
},
|
|
353
|
-
required: ["intent"],
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
name: "mandu_analyze_structure",
|
|
358
|
-
description:
|
|
359
|
-
"Analyze the existing project structure. " +
|
|
360
|
-
"Returns detected layers, existing features, and recommendations.",
|
|
361
|
-
inputSchema: {
|
|
362
|
-
type: "object",
|
|
363
|
-
properties: {},
|
|
364
|
-
required: [],
|
|
365
|
-
},
|
|
366
|
-
},
|
|
367
115
|
];
|
|
368
116
|
|
|
369
117
|
export function guardTools(projectRoot: string) {
|
|
370
118
|
const paths = getProjectPaths(projectRoot);
|
|
371
119
|
|
|
372
|
-
|
|
373
|
-
|
|
120
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
121
|
+
"mandu.guard.check": async (args: Record<string, unknown>) => {
|
|
374
122
|
const { autoCorrect = false } = args as { autoCorrect?: boolean };
|
|
375
123
|
|
|
376
124
|
// Load manifest
|
|
@@ -390,6 +138,7 @@ export function guardTools(projectRoot: string) {
|
|
|
390
138
|
passed: true,
|
|
391
139
|
violations: [],
|
|
392
140
|
message: "All guard checks passed",
|
|
141
|
+
relatedSkills: ["mandu-guard-guide", "mandu-debug"],
|
|
393
142
|
};
|
|
394
143
|
}
|
|
395
144
|
|
|
@@ -425,10 +174,11 @@ export function guardTools(projectRoot: string) {
|
|
|
425
174
|
})),
|
|
426
175
|
message: `Found ${checkResult.violations.length} violation(s)`,
|
|
427
176
|
tip: "Use autoCorrect: true to attempt automatic fixes",
|
|
177
|
+
relatedSkills: ["mandu-guard-guide", "mandu-debug"],
|
|
428
178
|
};
|
|
429
179
|
},
|
|
430
180
|
|
|
431
|
-
|
|
181
|
+
"mandu.guard.analyze": async (args: Record<string, unknown>) => {
|
|
432
182
|
const { errorJson } = args as { errorJson: string };
|
|
433
183
|
|
|
434
184
|
let error: ManduError;
|
|
@@ -532,11 +282,7 @@ export function guardTools(projectRoot: string) {
|
|
|
532
282
|
};
|
|
533
283
|
},
|
|
534
284
|
|
|
535
|
-
|
|
536
|
-
// Self-Healing Guard Tools Implementation
|
|
537
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
538
|
-
|
|
539
|
-
mandu_guard_heal: async (args: Record<string, unknown>) => {
|
|
285
|
+
"mandu.guard.heal": async (args: Record<string, unknown>) => {
|
|
540
286
|
const {
|
|
541
287
|
preset: inputPreset,
|
|
542
288
|
autoFix = false,
|
|
@@ -666,7 +412,7 @@ export function guardTools(projectRoot: string) {
|
|
|
666
412
|
};
|
|
667
413
|
},
|
|
668
414
|
|
|
669
|
-
|
|
415
|
+
"mandu.guard.explain": async (args: Record<string, unknown>) => {
|
|
670
416
|
const { type, fromLayer, toLayer, preset } = args as {
|
|
671
417
|
type: ViolationType;
|
|
672
418
|
fromLayer: string;
|
|
@@ -695,435 +441,13 @@ export function guardTools(projectRoot: string) {
|
|
|
695
441
|
preset: preset ?? "mandu",
|
|
696
442
|
};
|
|
697
443
|
},
|
|
444
|
+
};
|
|
698
445
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const { tags } = args as { tags: string[] };
|
|
705
|
-
|
|
706
|
-
if (!tags || tags.length === 0) {
|
|
707
|
-
return {
|
|
708
|
-
error: "Tags are required",
|
|
709
|
-
tip: "Provide at least one tag to search for (e.g., ['auth', 'cache'])",
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const result = await searchDecisions(projectRoot, tags);
|
|
714
|
-
|
|
715
|
-
if (result.decisions.length === 0) {
|
|
716
|
-
return {
|
|
717
|
-
found: false,
|
|
718
|
-
message: `No decisions found for tags: ${tags.join(", ")}`,
|
|
719
|
-
searchedTags: tags,
|
|
720
|
-
tip: "Try broader tags or check spec/decisions/ directory",
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
return {
|
|
725
|
-
found: true,
|
|
726
|
-
total: result.total,
|
|
727
|
-
searchedTags: tags,
|
|
728
|
-
decisions: result.decisions.map((d) => ({
|
|
729
|
-
id: d.id,
|
|
730
|
-
title: d.title,
|
|
731
|
-
status: d.status,
|
|
732
|
-
date: d.date,
|
|
733
|
-
tags: d.tags,
|
|
734
|
-
context: d.context.slice(0, 200) + (d.context.length > 200 ? "..." : ""),
|
|
735
|
-
decision: d.decision,
|
|
736
|
-
consequences: d.consequences,
|
|
737
|
-
relatedDecisions: d.relatedDecisions,
|
|
738
|
-
})),
|
|
739
|
-
tip: "Follow these decisions for consistency. Use mandu_save_decision if you make a new architectural choice.",
|
|
740
|
-
};
|
|
741
|
-
},
|
|
742
|
-
|
|
743
|
-
mandu_save_decision: async (args: Record<string, unknown>) => {
|
|
744
|
-
const { title, tags, context, decision, consequences, status } = args as {
|
|
745
|
-
title: string;
|
|
746
|
-
tags: string[];
|
|
747
|
-
context: string;
|
|
748
|
-
decision: string;
|
|
749
|
-
consequences: string[];
|
|
750
|
-
status?: DecisionStatus;
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
// Validate required fields
|
|
754
|
-
if (!title || !tags || !context || !decision || !consequences) {
|
|
755
|
-
return {
|
|
756
|
-
error: "Missing required fields",
|
|
757
|
-
required: ["title", "tags", "context", "decision", "consequences"],
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// Get next ID
|
|
762
|
-
const id = await getNextDecisionId(projectRoot);
|
|
763
|
-
|
|
764
|
-
// Save decision
|
|
765
|
-
const newDecision: Omit<ArchitectureDecision, "date"> = {
|
|
766
|
-
id,
|
|
767
|
-
title,
|
|
768
|
-
status: status || "proposed",
|
|
769
|
-
tags: tags.map((t) => t.toLowerCase()),
|
|
770
|
-
context,
|
|
771
|
-
decision,
|
|
772
|
-
consequences,
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
const result = await saveDecision(projectRoot, newDecision);
|
|
776
|
-
|
|
777
|
-
return {
|
|
778
|
-
success: result.success,
|
|
779
|
-
decision: {
|
|
780
|
-
id,
|
|
781
|
-
title,
|
|
782
|
-
status: status || "proposed",
|
|
783
|
-
tags,
|
|
784
|
-
},
|
|
785
|
-
filePath: result.filePath,
|
|
786
|
-
message: result.message,
|
|
787
|
-
tip: "Decision saved. It will be found when searching for related tags.",
|
|
788
|
-
};
|
|
789
|
-
},
|
|
790
|
-
|
|
791
|
-
mandu_check_consistency: async (args: Record<string, unknown>) => {
|
|
792
|
-
const { intent, tags } = args as {
|
|
793
|
-
intent: string;
|
|
794
|
-
tags: string[];
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
if (!intent || !tags || tags.length === 0) {
|
|
798
|
-
return {
|
|
799
|
-
error: "Intent and tags are required",
|
|
800
|
-
tip: "Describe what you're trying to do and provide related tags",
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
const result = await checkConsistency(projectRoot, intent, tags);
|
|
805
|
-
|
|
806
|
-
return {
|
|
807
|
-
consistent: result.consistent,
|
|
808
|
-
intent,
|
|
809
|
-
checkedTags: tags,
|
|
810
|
-
relatedDecisions: result.relatedDecisions.map((d) => ({
|
|
811
|
-
id: d.id,
|
|
812
|
-
title: d.title,
|
|
813
|
-
status: d.status,
|
|
814
|
-
decision: d.decision.slice(0, 150) + "...",
|
|
815
|
-
})),
|
|
816
|
-
warnings: result.warnings,
|
|
817
|
-
suggestions: result.suggestions,
|
|
818
|
-
tip: result.consistent
|
|
819
|
-
? "No conflicts found. Proceed with implementation following the suggestions."
|
|
820
|
-
: "⚠️ Review warnings before proceeding. Some decisions may conflict.",
|
|
821
|
-
};
|
|
822
|
-
},
|
|
823
|
-
|
|
824
|
-
mandu_get_architecture: async () => {
|
|
825
|
-
const compact = await getCompactArchitecture(projectRoot);
|
|
826
|
-
|
|
827
|
-
if (!compact) {
|
|
828
|
-
return {
|
|
829
|
-
found: false,
|
|
830
|
-
message: "No architecture information found",
|
|
831
|
-
tip: "Save some decisions first using mandu_save_decision",
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return {
|
|
836
|
-
found: true,
|
|
837
|
-
project: compact.project,
|
|
838
|
-
lastUpdated: compact.lastUpdated,
|
|
839
|
-
summary: {
|
|
840
|
-
totalDecisions: compact.keyDecisions.length,
|
|
841
|
-
topTags: Object.entries(compact.tagCounts)
|
|
842
|
-
.sort(([, a], [, b]) => b - a)
|
|
843
|
-
.slice(0, 10)
|
|
844
|
-
.map(([tag, count]) => ({ tag, count })),
|
|
845
|
-
},
|
|
846
|
-
keyDecisions: compact.keyDecisions,
|
|
847
|
-
rules: compact.rules,
|
|
848
|
-
tip: "Use mandu_get_decisions with specific tags for detailed information.",
|
|
849
|
-
};
|
|
850
|
-
},
|
|
851
|
-
|
|
852
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
853
|
-
// Semantic Slots Tools Implementation
|
|
854
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
855
|
-
|
|
856
|
-
mandu_validate_slot: async (args: Record<string, unknown>) => {
|
|
857
|
-
const { file, preset, constraints: customConstraints } = args as {
|
|
858
|
-
file: string;
|
|
859
|
-
preset?: "default" | "api" | "readonly";
|
|
860
|
-
constraints?: SlotConstraints;
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
if (!file) {
|
|
864
|
-
return {
|
|
865
|
-
error: "File path is required",
|
|
866
|
-
tip: "Provide the path to the slot file to validate",
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// 프리셋 선택
|
|
871
|
-
let constraints: SlotConstraints;
|
|
872
|
-
if (customConstraints) {
|
|
873
|
-
constraints = customConstraints;
|
|
874
|
-
} else {
|
|
875
|
-
switch (preset) {
|
|
876
|
-
case "api":
|
|
877
|
-
constraints = API_SLOT_CONSTRAINTS;
|
|
878
|
-
break;
|
|
879
|
-
case "readonly":
|
|
880
|
-
constraints = READONLY_SLOT_CONSTRAINTS;
|
|
881
|
-
break;
|
|
882
|
-
default:
|
|
883
|
-
constraints = DEFAULT_SLOT_CONSTRAINTS;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// 파일 경로 정규화 및 보안 검증 (LFI 방지)
|
|
888
|
-
const path = await import("path");
|
|
889
|
-
const rawPath = file.startsWith("/") || file.includes(":")
|
|
890
|
-
? file
|
|
891
|
-
: path.join(projectRoot, file);
|
|
892
|
-
const filePath = path.normalize(path.resolve(rawPath));
|
|
893
|
-
const normalizedRoot = path.normalize(path.resolve(projectRoot));
|
|
894
|
-
|
|
895
|
-
// 경로가 프로젝트 루트 내에 있는지 검증
|
|
896
|
-
if (!filePath.startsWith(normalizedRoot)) {
|
|
897
|
-
return {
|
|
898
|
-
error: "Access denied: File path is outside project root",
|
|
899
|
-
tip: "Only files within the project directory can be validated",
|
|
900
|
-
requestedPath: file,
|
|
901
|
-
projectRoot: projectRoot,
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const result = await validateSlotConstraints(filePath, constraints);
|
|
906
|
-
|
|
907
|
-
return {
|
|
908
|
-
valid: result.valid,
|
|
909
|
-
file: result.filePath,
|
|
910
|
-
stats: result.stats,
|
|
911
|
-
violations: result.violations.map((v) => ({
|
|
912
|
-
type: v.type,
|
|
913
|
-
severity: v.severity,
|
|
914
|
-
message: v.message,
|
|
915
|
-
suggestion: v.suggestion,
|
|
916
|
-
line: v.line,
|
|
917
|
-
})),
|
|
918
|
-
suggestions: result.suggestions,
|
|
919
|
-
constraintsUsed: constraints,
|
|
920
|
-
tip: result.valid
|
|
921
|
-
? "✅ Slot passes all constraints"
|
|
922
|
-
: "Fix violations before deployment. Use mandu_get_slot_constraints for guidance.",
|
|
923
|
-
};
|
|
924
|
-
},
|
|
925
|
-
|
|
926
|
-
mandu_get_slot_constraints: async (args: Record<string, unknown>) => {
|
|
927
|
-
const { preset } = args as { preset?: "default" | "api" | "readonly" };
|
|
928
|
-
|
|
929
|
-
const presets = {
|
|
930
|
-
default: {
|
|
931
|
-
name: "Default",
|
|
932
|
-
description: "Basic constraints for general slots",
|
|
933
|
-
constraints: DEFAULT_SLOT_CONSTRAINTS,
|
|
934
|
-
},
|
|
935
|
-
api: {
|
|
936
|
-
name: "API Slot",
|
|
937
|
-
description: "Constraints for API handlers with validation requirements",
|
|
938
|
-
constraints: API_SLOT_CONSTRAINTS,
|
|
939
|
-
},
|
|
940
|
-
readonly: {
|
|
941
|
-
name: "Read-only Slot",
|
|
942
|
-
description: "Strict constraints for read-only operations (no DB writes)",
|
|
943
|
-
constraints: READONLY_SLOT_CONSTRAINTS,
|
|
944
|
-
},
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
if (preset) {
|
|
948
|
-
const selected = presets[preset];
|
|
949
|
-
return {
|
|
950
|
-
preset: preset,
|
|
951
|
-
...selected,
|
|
952
|
-
usage: `
|
|
953
|
-
.constraints(${JSON.stringify(selected.constraints, null, 2)})
|
|
954
|
-
`.trim(),
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
return {
|
|
959
|
-
available: Object.entries(presets).map(([key, value]) => ({
|
|
960
|
-
preset: key,
|
|
961
|
-
name: value.name,
|
|
962
|
-
description: value.description,
|
|
963
|
-
constraints: value.constraints,
|
|
964
|
-
})),
|
|
965
|
-
tip: "Use these constraints with Mandu.filling().constraints({...}) to enforce slot rules.",
|
|
966
|
-
example: `
|
|
967
|
-
Mandu.filling()
|
|
968
|
-
.purpose("사용자 목록 조회 API")
|
|
969
|
-
.constraints({
|
|
970
|
-
maxLines: 50,
|
|
971
|
-
maxCyclomaticComplexity: 10,
|
|
972
|
-
requiredPatterns: ["input-validation", "error-handling"],
|
|
973
|
-
forbiddenPatterns: ["direct-db-write"],
|
|
974
|
-
allowedImports: ["server/domain/*", "shared/utils/*"],
|
|
975
|
-
})
|
|
976
|
-
.get(async (ctx) => { ... });
|
|
977
|
-
`.trim(),
|
|
978
|
-
};
|
|
979
|
-
},
|
|
980
|
-
|
|
981
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
982
|
-
// Architecture Negotiation Tools Implementation
|
|
983
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
984
|
-
|
|
985
|
-
mandu_negotiate: async (args: Record<string, unknown>) => {
|
|
986
|
-
const { intent, featureName, requirements, constraints, category, preset } = args as {
|
|
987
|
-
intent: string;
|
|
988
|
-
featureName?: string;
|
|
989
|
-
requirements?: string[];
|
|
990
|
-
constraints?: string[];
|
|
991
|
-
category?: FeatureCategory;
|
|
992
|
-
preset?: GuardPreset;
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
if (!intent) {
|
|
996
|
-
return {
|
|
997
|
-
error: "Intent is required",
|
|
998
|
-
tip: "Describe what you want to implement (e.g., '사용자 인증 기능 추가')",
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
const request: NegotiationRequest = {
|
|
1003
|
-
intent,
|
|
1004
|
-
featureName,
|
|
1005
|
-
requirements,
|
|
1006
|
-
constraints,
|
|
1007
|
-
category,
|
|
1008
|
-
preset,
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
const result = await negotiate(request, projectRoot);
|
|
1012
|
-
|
|
1013
|
-
return {
|
|
1014
|
-
approved: result.approved,
|
|
1015
|
-
intent,
|
|
1016
|
-
detectedCategory: category || "auto",
|
|
1017
|
-
preset: result.preset,
|
|
1018
|
-
|
|
1019
|
-
// Structure summary
|
|
1020
|
-
structure: result.structure.map((dir) => ({
|
|
1021
|
-
path: dir.path,
|
|
1022
|
-
purpose: dir.purpose,
|
|
1023
|
-
layer: dir.layer,
|
|
1024
|
-
files: dir.files.map((f) => ({
|
|
1025
|
-
name: f.name,
|
|
1026
|
-
purpose: f.purpose,
|
|
1027
|
-
isSlot: f.isSlot || false,
|
|
1028
|
-
})),
|
|
1029
|
-
})),
|
|
1030
|
-
|
|
1031
|
-
// Slots to implement
|
|
1032
|
-
slots: result.slots,
|
|
1033
|
-
|
|
1034
|
-
// Context
|
|
1035
|
-
relatedDecisions: result.relatedDecisions,
|
|
1036
|
-
warnings: result.warnings,
|
|
1037
|
-
recommendations: result.recommendations,
|
|
1038
|
-
|
|
1039
|
-
// Summary
|
|
1040
|
-
summary: {
|
|
1041
|
-
estimatedFiles: result.estimatedFiles,
|
|
1042
|
-
slotsToImplement: result.slots.length,
|
|
1043
|
-
relatedDecisionsCount: result.relatedDecisions.length,
|
|
1044
|
-
},
|
|
1045
|
-
|
|
1046
|
-
// Next steps
|
|
1047
|
-
nextSteps: result.nextSteps,
|
|
1048
|
-
tip: "Use mandu_generate_scaffold to create the file structure, then implement the TODO sections.",
|
|
1049
|
-
};
|
|
1050
|
-
},
|
|
1051
|
-
|
|
1052
|
-
mandu_generate_scaffold: async (args: Record<string, unknown>) => {
|
|
1053
|
-
const { intent, featureName, category, dryRun = false, overwrite = false, preset } = args as {
|
|
1054
|
-
intent: string;
|
|
1055
|
-
featureName?: string;
|
|
1056
|
-
category?: FeatureCategory;
|
|
1057
|
-
dryRun?: boolean;
|
|
1058
|
-
overwrite?: boolean;
|
|
1059
|
-
preset?: GuardPreset;
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
if (!intent) {
|
|
1063
|
-
return {
|
|
1064
|
-
error: "Intent is required",
|
|
1065
|
-
tip: "Provide the same intent you used with mandu_negotiate",
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// 먼저 협상하여 구조 계획 얻기
|
|
1070
|
-
const plan = await negotiate({ intent, featureName, category, preset }, projectRoot);
|
|
1071
|
-
|
|
1072
|
-
if (!plan.approved) {
|
|
1073
|
-
return {
|
|
1074
|
-
error: "Negotiation not approved",
|
|
1075
|
-
reason: plan.rejectionReason,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// Scaffold 생성
|
|
1080
|
-
const result = await generateScaffold(plan.structure, projectRoot, {
|
|
1081
|
-
dryRun,
|
|
1082
|
-
overwrite,
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
return {
|
|
1086
|
-
success: result.success,
|
|
1087
|
-
dryRun,
|
|
1088
|
-
created: {
|
|
1089
|
-
directories: result.createdDirs,
|
|
1090
|
-
files: result.createdFiles,
|
|
1091
|
-
},
|
|
1092
|
-
skipped: result.skippedFiles,
|
|
1093
|
-
errors: result.errors,
|
|
1094
|
-
summary: {
|
|
1095
|
-
dirsCreated: result.createdDirs.length,
|
|
1096
|
-
filesCreated: result.createdFiles.length,
|
|
1097
|
-
filesSkipped: result.skippedFiles.length,
|
|
1098
|
-
},
|
|
1099
|
-
nextSteps: [
|
|
1100
|
-
"1. Review the generated files",
|
|
1101
|
-
"2. Implement the TODO sections in each file",
|
|
1102
|
-
"3. Run mandu_guard_heal to verify architecture compliance",
|
|
1103
|
-
"4. Add tests for your implementation",
|
|
1104
|
-
],
|
|
1105
|
-
tip: dryRun
|
|
1106
|
-
? "This was a dry run. Remove dryRun: true to actually create files."
|
|
1107
|
-
: "Files created! Start implementing the TODO sections.",
|
|
1108
|
-
};
|
|
1109
|
-
},
|
|
1110
|
-
|
|
1111
|
-
mandu_analyze_structure: async () => {
|
|
1112
|
-
const result = await analyzeExistingStructure(projectRoot);
|
|
446
|
+
// Backward-compatible aliases (deprecated)
|
|
447
|
+
handlers["mandu_guard_check"] = handlers["mandu.guard.check"];
|
|
448
|
+
handlers["mandu_analyze_error"] = handlers["mandu.guard.analyze"];
|
|
449
|
+
handlers["mandu_guard_heal"] = handlers["mandu.guard.heal"];
|
|
450
|
+
handlers["mandu_guard_explain"] = handlers["mandu.guard.explain"];
|
|
1113
451
|
|
|
1114
|
-
|
|
1115
|
-
projectRoot,
|
|
1116
|
-
detected: {
|
|
1117
|
-
layers: result.layers,
|
|
1118
|
-
layerCount: result.layers.length,
|
|
1119
|
-
existingFeatures: result.existingFeatures,
|
|
1120
|
-
featureCount: result.existingFeatures.length,
|
|
1121
|
-
},
|
|
1122
|
-
recommendations: result.recommendations,
|
|
1123
|
-
tip: result.layers.length > 0
|
|
1124
|
-
? "Use mandu_negotiate to add new features following the existing structure."
|
|
1125
|
-
: "Use mandu_negotiate to establish your project structure.",
|
|
1126
|
-
};
|
|
1127
|
-
},
|
|
1128
|
-
};
|
|
452
|
+
return handlers;
|
|
1129
453
|
}
|