@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/brain.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Mandu MCP - Brain Tools
|
|
3
3
|
*
|
|
4
4
|
* MCP tools for Brain functionality:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
5
|
+
* - mandu.brain.doctor: Guard failure analysis + patch suggestions
|
|
6
|
+
* - mandu.watch.start: Start file watching
|
|
7
|
+
* - mandu.watch.status: Get watch status
|
|
8
|
+
* - mandu.brain.checkLocation: Check if file location is valid (v0.2)
|
|
9
|
+
* - mandu.brain.checkImport: Check if imports are valid (v0.2)
|
|
10
|
+
* - mandu.brain.architecture: Get project architecture rules (v0.2)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -31,9 +31,12 @@ import { getProjectPaths } from "../utils/project.js";
|
|
|
31
31
|
|
|
32
32
|
export const brainToolDefinitions: Tool[] = [
|
|
33
33
|
{
|
|
34
|
-
name: "
|
|
34
|
+
name: "mandu.brain.doctor",
|
|
35
35
|
description:
|
|
36
36
|
"Analyze Guard failures and suggest patches. Works with or without LLM - template-based analysis is always available.",
|
|
37
|
+
annotations: {
|
|
38
|
+
readOnlyHint: true,
|
|
39
|
+
},
|
|
37
40
|
inputSchema: {
|
|
38
41
|
type: "object",
|
|
39
42
|
properties: {
|
|
@@ -47,9 +50,12 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
47
50
|
},
|
|
48
51
|
},
|
|
49
52
|
{
|
|
50
|
-
name: "
|
|
53
|
+
name: "mandu.watch.start",
|
|
51
54
|
description:
|
|
52
55
|
"Start file watching with architecture rule warnings. Watches for common mistakes and emits warnings (no blocking).",
|
|
56
|
+
annotations: {
|
|
57
|
+
readOnlyHint: false,
|
|
58
|
+
},
|
|
53
59
|
inputSchema: {
|
|
54
60
|
type: "object",
|
|
55
61
|
properties: {
|
|
@@ -62,9 +68,12 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
62
68
|
},
|
|
63
69
|
},
|
|
64
70
|
{
|
|
65
|
-
name: "
|
|
71
|
+
name: "mandu.watch.status",
|
|
66
72
|
description:
|
|
67
73
|
"Get the current watch status including recent warnings and active rules.",
|
|
74
|
+
annotations: {
|
|
75
|
+
readOnlyHint: true,
|
|
76
|
+
},
|
|
68
77
|
inputSchema: {
|
|
69
78
|
type: "object",
|
|
70
79
|
properties: {},
|
|
@@ -72,9 +81,12 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
72
81
|
},
|
|
73
82
|
},
|
|
74
83
|
{
|
|
75
|
-
name: "
|
|
84
|
+
name: "mandu.watch.stop",
|
|
76
85
|
description:
|
|
77
86
|
"Stop file watching and clean up MCP notification subscriptions.",
|
|
87
|
+
annotations: {
|
|
88
|
+
readOnlyHint: false,
|
|
89
|
+
},
|
|
78
90
|
inputSchema: {
|
|
79
91
|
type: "object",
|
|
80
92
|
properties: {},
|
|
@@ -83,9 +95,12 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
83
95
|
},
|
|
84
96
|
// Architecture tools (v0.2)
|
|
85
97
|
{
|
|
86
|
-
name: "
|
|
98
|
+
name: "mandu.brain.checkLocation",
|
|
87
99
|
description:
|
|
88
100
|
"Check if a file location follows project architecture rules. Call this BEFORE creating or moving files to ensure proper placement.",
|
|
101
|
+
annotations: {
|
|
102
|
+
readOnlyHint: true,
|
|
103
|
+
},
|
|
89
104
|
inputSchema: {
|
|
90
105
|
type: "object",
|
|
91
106
|
properties: {
|
|
@@ -102,9 +117,12 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
102
117
|
},
|
|
103
118
|
},
|
|
104
119
|
{
|
|
105
|
-
name: "
|
|
120
|
+
name: "mandu.brain.checkImport",
|
|
106
121
|
description:
|
|
107
122
|
"Check if imports in a file follow architecture rules. Call this to validate imports before adding them.",
|
|
123
|
+
annotations: {
|
|
124
|
+
readOnlyHint: true,
|
|
125
|
+
},
|
|
108
126
|
inputSchema: {
|
|
109
127
|
type: "object",
|
|
110
128
|
properties: {
|
|
@@ -122,9 +140,12 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
122
140
|
},
|
|
123
141
|
},
|
|
124
142
|
{
|
|
125
|
-
name: "
|
|
143
|
+
name: "mandu.brain.architecture",
|
|
126
144
|
description:
|
|
127
145
|
"Get the project architecture rules and folder structure. Use this to understand where to place new files.",
|
|
146
|
+
annotations: {
|
|
147
|
+
readOnlyHint: true,
|
|
148
|
+
},
|
|
128
149
|
inputSchema: {
|
|
129
150
|
type: "object",
|
|
130
151
|
properties: {
|
|
@@ -144,8 +165,8 @@ let mcpWarningUnsubscribe: (() => void) | null = null;
|
|
|
144
165
|
export function brainTools(projectRoot: string, server?: Server, monitor?: ActivityMonitor) {
|
|
145
166
|
const paths = getProjectPaths(projectRoot);
|
|
146
167
|
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
169
|
+
"mandu.brain.doctor": async (args: Record<string, unknown>) => {
|
|
149
170
|
const { useLLM = false } = args as { useLLM?: boolean };
|
|
150
171
|
|
|
151
172
|
try {
|
|
@@ -213,7 +234,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
213
234
|
}
|
|
214
235
|
},
|
|
215
236
|
|
|
216
|
-
|
|
237
|
+
"mandu.watch.start": async (args: Record<string, unknown>) => {
|
|
217
238
|
const { debounceMs } = args as { debounceMs?: number };
|
|
218
239
|
|
|
219
240
|
try {
|
|
@@ -256,7 +277,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
256
277
|
);
|
|
257
278
|
}
|
|
258
279
|
|
|
259
|
-
// Push logging message (
|
|
280
|
+
// Push logging message (MCP client receives in real-time)
|
|
260
281
|
server.sendLoggingMessage({
|
|
261
282
|
level: "warning",
|
|
262
283
|
logger: "mandu-watch",
|
|
@@ -313,7 +334,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
313
334
|
}
|
|
314
335
|
},
|
|
315
336
|
|
|
316
|
-
|
|
337
|
+
"mandu.watch.status": async () => {
|
|
317
338
|
try {
|
|
318
339
|
const watcher = getWatcher();
|
|
319
340
|
|
|
@@ -321,7 +342,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
321
342
|
return {
|
|
322
343
|
active: false,
|
|
323
344
|
message: "Watch is not running",
|
|
324
|
-
tip: "Use
|
|
345
|
+
tip: "Use mandu.watch.start to begin watching",
|
|
325
346
|
};
|
|
326
347
|
}
|
|
327
348
|
|
|
@@ -352,7 +373,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
352
373
|
}
|
|
353
374
|
},
|
|
354
375
|
|
|
355
|
-
|
|
376
|
+
"mandu.watch.stop": async () => {
|
|
356
377
|
try {
|
|
357
378
|
// Clean up MCP notification subscription
|
|
358
379
|
if (mcpWarningUnsubscribe) {
|
|
@@ -375,7 +396,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
375
396
|
},
|
|
376
397
|
|
|
377
398
|
// Architecture tools (v0.2)
|
|
378
|
-
|
|
399
|
+
"mandu.brain.checkLocation": async (args: Record<string, unknown>) => {
|
|
379
400
|
const { path: filePath, content } = args as {
|
|
380
401
|
path: string;
|
|
381
402
|
content?: string;
|
|
@@ -423,7 +444,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
423
444
|
}
|
|
424
445
|
},
|
|
425
446
|
|
|
426
|
-
|
|
447
|
+
"mandu.brain.checkImport": async (args: Record<string, unknown>) => {
|
|
427
448
|
const { sourceFile, imports } = args as {
|
|
428
449
|
sourceFile: string;
|
|
429
450
|
imports: string[];
|
|
@@ -466,7 +487,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
466
487
|
}
|
|
467
488
|
},
|
|
468
489
|
|
|
469
|
-
|
|
490
|
+
"mandu.brain.architecture": async (args: Record<string, unknown>) => {
|
|
470
491
|
const { includeStructure = true } = args as {
|
|
471
492
|
includeStructure?: boolean;
|
|
472
493
|
};
|
|
@@ -515,7 +536,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
515
536
|
indexedAt: structure.indexedAt,
|
|
516
537
|
}
|
|
517
538
|
: null,
|
|
518
|
-
tip: "이 규칙을 따라 파일을 생성하세요.
|
|
539
|
+
tip: "이 규칙을 따라 파일을 생성하세요. mandu.brain.checkLocation으로 검증할 수 있습니다.",
|
|
519
540
|
};
|
|
520
541
|
} catch (error) {
|
|
521
542
|
return {
|
|
@@ -525,4 +546,15 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
525
546
|
}
|
|
526
547
|
},
|
|
527
548
|
};
|
|
549
|
+
|
|
550
|
+
// Backward-compatible aliases (deprecated)
|
|
551
|
+
handlers["mandu_doctor"] = handlers["mandu.brain.doctor"];
|
|
552
|
+
handlers["mandu_watch_start"] = handlers["mandu.watch.start"];
|
|
553
|
+
handlers["mandu_watch_stop"] = handlers["mandu.watch.stop"];
|
|
554
|
+
handlers["mandu_watch_status"] = handlers["mandu.watch.status"];
|
|
555
|
+
handlers["mandu_check_location"] = handlers["mandu.brain.checkLocation"];
|
|
556
|
+
handlers["mandu_check_import"] = handlers["mandu.brain.checkImport"];
|
|
557
|
+
handlers["mandu_get_architecture"] = handlers["mandu.brain.architecture"];
|
|
558
|
+
|
|
559
|
+
return handlers;
|
|
528
560
|
}
|
package/src/tools/component.ts
CHANGED
|
@@ -1,185 +1,194 @@
|
|
|
1
|
-
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
|
-
|
|
5
|
-
export const componentToolDefinitions: Tool[] = [
|
|
6
|
-
{
|
|
7
|
-
name: "
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
type: "string",
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"'
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
description: "
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
componentDir = path.join(clientBase,
|
|
102
|
-
indexPath = path.join(clientBase,
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
|
|
5
|
+
export const componentToolDefinitions: Tool[] = [
|
|
6
|
+
{
|
|
7
|
+
name: "mandu.component.add",
|
|
8
|
+
annotations: {
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
readOnlyHint: false,
|
|
11
|
+
},
|
|
12
|
+
description:
|
|
13
|
+
"Scaffold a new client-side component in the correct FSD (Feature-Sliced Design) layer. " +
|
|
14
|
+
"Mandu projects organize client components under src/client/ following FSD layers: " +
|
|
15
|
+
"shared (reusable primitives), entities (domain objects), features (user interactions), " +
|
|
16
|
+
"widgets (composite blocks), pages (page-level controllers). " +
|
|
17
|
+
"Creates the component file and updates the layer's public API index.ts. " +
|
|
18
|
+
"Use this instead of manually creating files in app/ to maintain FSD architecture.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
name: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Component name in PascalCase (e.g., 'ReactionBar', 'UserAvatar')",
|
|
25
|
+
},
|
|
26
|
+
layer: {
|
|
27
|
+
type: "string",
|
|
28
|
+
enum: ["shared", "entities", "features", "widgets", "pages"],
|
|
29
|
+
description:
|
|
30
|
+
"FSD layer: " +
|
|
31
|
+
"'shared' (reusable UI primitives, utils — no business logic), " +
|
|
32
|
+
"'entities' (domain models and their UI — User, Message, Post), " +
|
|
33
|
+
"'features' (user interactions that change state — like, comment, follow), " +
|
|
34
|
+
"'widgets' (composite sections combining entities+features), " +
|
|
35
|
+
"'pages' (page-level client components — use sparingly, prefer features/entities)",
|
|
36
|
+
},
|
|
37
|
+
slice: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description:
|
|
40
|
+
"Feature slice name in kebab-case (required for features/entities/widgets). " +
|
|
41
|
+
"Examples: 'chat-reaction', 'user-profile', 'post-feed'. " +
|
|
42
|
+
"For 'shared' layer, use segment name like 'ui', 'lib', 'api'.",
|
|
43
|
+
},
|
|
44
|
+
segment: {
|
|
45
|
+
type: "string",
|
|
46
|
+
enum: ["ui", "model", "api", "lib", "config"],
|
|
47
|
+
description: "Segment within the slice (default: 'ui'). 'ui' for React components, 'model' for hooks/store, 'api' for data fetching.",
|
|
48
|
+
},
|
|
49
|
+
description: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Brief description of what this component does (added as a comment)",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ["name", "layer"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function toKebabCase(name: string): string {
|
|
60
|
+
return name
|
|
61
|
+
.replace(/([A-Z])/g, "-$1")
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.replace(/^-/, "");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function componentTools(projectRoot: string) {
|
|
67
|
+
const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
68
|
+
"mandu.component.add": async (args: Record<string, unknown>) => {
|
|
69
|
+
const {
|
|
70
|
+
name,
|
|
71
|
+
layer,
|
|
72
|
+
slice,
|
|
73
|
+
segment = "ui",
|
|
74
|
+
description = "",
|
|
75
|
+
} = args as {
|
|
76
|
+
name: string;
|
|
77
|
+
layer: "shared" | "entities" | "features" | "widgets" | "pages";
|
|
78
|
+
slice?: string;
|
|
79
|
+
segment?: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Validate: features/entities/widgets require a slice
|
|
84
|
+
if (["features", "entities", "widgets"].includes(layer) && !slice) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: `The '${layer}' layer requires a 'slice' name (e.g., 'chat-reaction', 'user-profile').`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Build the file path
|
|
92
|
+
const clientBase = path.join(projectRoot, "src", "client");
|
|
93
|
+
let componentDir: string;
|
|
94
|
+
let indexPath: string;
|
|
95
|
+
|
|
96
|
+
if (layer === "shared") {
|
|
97
|
+
const sliceName = slice || "ui";
|
|
98
|
+
componentDir = path.join(clientBase, "shared", sliceName);
|
|
99
|
+
indexPath = path.join(clientBase, "shared", sliceName, "index.ts");
|
|
100
|
+
} else if (layer === "pages") {
|
|
101
|
+
componentDir = path.join(clientBase, "pages");
|
|
102
|
+
indexPath = path.join(clientBase, "pages", "index.ts");
|
|
103
|
+
} else {
|
|
104
|
+
const sliceName = slice!;
|
|
105
|
+
componentDir = path.join(clientBase, layer, sliceName, segment);
|
|
106
|
+
indexPath = path.join(clientBase, layer, sliceName, "index.ts");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const kebabName = toKebabCase(name);
|
|
110
|
+
const componentFile = path.join(componentDir, `${kebabName}.tsx`);
|
|
111
|
+
const relativePath = path.relative(projectRoot, componentFile).replace(/\\/g, "/");
|
|
112
|
+
|
|
113
|
+
// Check if file already exists
|
|
114
|
+
try {
|
|
115
|
+
await fs.access(componentFile);
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: `Component file already exists: ${relativePath}`,
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
// Good - file doesn't exist
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Create directory
|
|
125
|
+
await fs.mkdir(componentDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
// Generate component template
|
|
128
|
+
const descComment = description ? `\n * ${description}` : "";
|
|
129
|
+
const template = `/**
|
|
130
|
+
* ${name} Component${descComment}
|
|
131
|
+
* Layer: ${layer}${slice ? ` / ${slice}` : ""}
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
import { useState } from "react";
|
|
135
|
+
|
|
136
|
+
interface ${name}Props {
|
|
137
|
+
// TODO: Define props
|
|
138
|
+
className?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function ${name}({ className }: ${name}Props) {
|
|
142
|
+
return (
|
|
143
|
+
<div className={className}>
|
|
144
|
+
{/* TODO: Implement ${name} */}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
await fs.writeFile(componentFile, template, "utf-8");
|
|
151
|
+
|
|
152
|
+
// Update index.ts (create or append export)
|
|
153
|
+
const exportLine = `export { ${name} } from "./${segment}/${kebabName}.js";\n`;
|
|
154
|
+
const simpleExportLine = `export { ${name} } from "./${kebabName}.js";\n`;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
let indexContent = "";
|
|
158
|
+
try {
|
|
159
|
+
indexContent = await fs.readFile(indexPath, "utf-8");
|
|
160
|
+
} catch {
|
|
161
|
+
// index.ts doesn't exist yet
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const exportToAdd = layer === "shared" || layer === "pages" ? simpleExportLine : exportLine;
|
|
165
|
+
|
|
166
|
+
if (!indexContent.includes(`{ ${name} }`)) {
|
|
167
|
+
await fs.writeFile(indexPath, indexContent + exportToAdd, "utf-8");
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// index update failed - not critical
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
component: name,
|
|
176
|
+
layer,
|
|
177
|
+
slice: slice || null,
|
|
178
|
+
segment,
|
|
179
|
+
createdFiles: [relativePath],
|
|
180
|
+
updatedFiles: [path.relative(projectRoot, indexPath).replace(/\\/g, "/")],
|
|
181
|
+
message: `Created ${name} in ${layer}${slice ? `/${slice}` : ""}/${segment}`,
|
|
182
|
+
nextSteps: [
|
|
183
|
+
`Edit ${relativePath} to implement the component`,
|
|
184
|
+
`Import with: import { ${name} } from "@/client/${layer}${slice ? `/${slice}` : ""}"`,
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Backward-compatible aliases (deprecated)
|
|
191
|
+
handlers["mandu_add_component"] = handlers["mandu.component.add"];
|
|
192
|
+
|
|
193
|
+
return handlers;
|
|
194
|
+
}
|