@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.
@@ -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: "mandu_guard_check",
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: "mandu_analyze_error",
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: "mandu_guard_heal",
57
+ name: "mandu.guard.heal",
76
58
  description:
77
- "Run Self-Healing Guard: detect architecture violations and provide actionable fix suggestions with auto-fix capabilities. " +
78
- "This tool not only detects violations but also explains WHY they are wrong and HOW to fix them.",
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: "mandu_guard_explain",
84
+ name: "mandu.guard.explain",
101
85
  description:
102
- "Explain a specific guard rule in detail. " +
103
- "Provides WHY the rule exists, HOW to fix violations, and code examples.",
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
- return {
373
- mandu_guard_check: async (args: Record<string, unknown>) => {
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
- mandu_analyze_error: async (args: Record<string, unknown>) => {
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
- mandu_guard_explain: async (args: Record<string, unknown>) => {
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
- // Decision Memory Tools Implementation
701
- // ═══════════════════════════════════════════════════════════════════════════
702
-
703
- mandu_get_decisions: async (args: Record<string, unknown>) => {
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
- return {
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
  }