@axis-ds/mcp 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2996 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+
8
+ // src/registry/loader.ts
9
+ import { readFileSync } from "fs";
10
+ import { join, dirname } from "path";
11
+ import { fileURLToPath } from "url";
12
+ var __filename = fileURLToPath(import.meta.url);
13
+ var __dirname = dirname(__filename);
14
+ var DATA_DIR = join(__dirname, "data");
15
+ var cachedRegistry = null;
16
+ function loadComponents() {
17
+ const filePath = join(DATA_DIR, "components.json");
18
+ const content = readFileSync(filePath, "utf-8");
19
+ const data = JSON.parse(content);
20
+ return data.components;
21
+ }
22
+ function loadTokens() {
23
+ const filePath = join(DATA_DIR, "tokens.json");
24
+ const content = readFileSync(filePath, "utf-8");
25
+ const data = JSON.parse(content);
26
+ return data.tokens;
27
+ }
28
+ function getRegistry() {
29
+ if (!cachedRegistry) {
30
+ cachedRegistry = {
31
+ components: loadComponents(),
32
+ tokens: loadTokens()
33
+ };
34
+ }
35
+ return cachedRegistry;
36
+ }
37
+ function searchComponents(query, category) {
38
+ const registry = getRegistry();
39
+ const lowerQuery = query.toLowerCase();
40
+ return registry.components.filter((component) => {
41
+ if (category && component.category !== category) {
42
+ return false;
43
+ }
44
+ return component.name.toLowerCase().includes(lowerQuery) || component.displayName.toLowerCase().includes(lowerQuery) || component.description.toLowerCase().includes(lowerQuery);
45
+ });
46
+ }
47
+ function getComponent(name) {
48
+ const registry = getRegistry();
49
+ return registry.components.find(
50
+ (c) => c.name.toLowerCase() === name.toLowerCase()
51
+ );
52
+ }
53
+ function listComponents(category) {
54
+ const registry = getRegistry();
55
+ if (category) {
56
+ return registry.components.filter((c) => c.category === category);
57
+ }
58
+ return registry.components;
59
+ }
60
+ function listTokens(category) {
61
+ const registry = getRegistry();
62
+ if (category) {
63
+ return registry.tokens.filter((t) => t.category === category);
64
+ }
65
+ return registry.tokens;
66
+ }
67
+ function getToken(path4) {
68
+ const registry = getRegistry();
69
+ return registry.tokens.find(
70
+ (t) => t.path.toLowerCase() === path4.toLowerCase()
71
+ );
72
+ }
73
+ function getComponentStats() {
74
+ const registry = getRegistry();
75
+ const stats = {
76
+ core: 0,
77
+ agentic: 0,
78
+ form: 0,
79
+ layout: 0
80
+ };
81
+ for (const component of registry.components) {
82
+ stats[component.category]++;
83
+ }
84
+ return stats;
85
+ }
86
+ function getTokenStats() {
87
+ const registry = getRegistry();
88
+ const stats = {
89
+ color: 0,
90
+ typography: 0,
91
+ spacing: 0,
92
+ radius: 0,
93
+ shadow: 0,
94
+ animation: 0
95
+ };
96
+ for (const token of registry.tokens) {
97
+ stats[token.category]++;
98
+ }
99
+ return stats;
100
+ }
101
+
102
+ // src/tools/search.ts
103
+ function handleSearchComponents(params) {
104
+ const { query, category } = params;
105
+ if (!query || query.trim() === "") {
106
+ throw new Error("\uAC80\uC0C9\uC5B4(query)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
107
+ }
108
+ const components = searchComponents(query, category);
109
+ return {
110
+ components,
111
+ totalCount: components.length,
112
+ query,
113
+ category
114
+ };
115
+ }
116
+ function formatSearchResult(result) {
117
+ const lines = [];
118
+ lines.push(`## \uAC80\uC0C9 \uACB0\uACFC: "${result.query}"`);
119
+ if (result.category) {
120
+ lines.push(`**\uCE74\uD14C\uACE0\uB9AC**: ${result.category}`);
121
+ }
122
+ lines.push(`**\uCD1D ${result.totalCount}\uAC1C \uCEF4\uD3EC\uB10C\uD2B8 \uBC1C\uACAC**`);
123
+ lines.push("");
124
+ if (result.components.length === 0) {
125
+ lines.push("\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
126
+ return lines.join("\n");
127
+ }
128
+ for (const component of result.components) {
129
+ lines.push(`### ${component.displayName} (\`${component.name}\`)`);
130
+ lines.push(`- **\uC124\uBA85**: ${component.description}`);
131
+ lines.push(`- **\uCE74\uD14C\uACE0\uB9AC**: ${component.category}`);
132
+ lines.push(`- **\uD328\uD0A4\uC9C0**: ${component.package}`);
133
+ if (component.dependencies.length > 0) {
134
+ lines.push(`- **\uC758\uC874\uC131**: ${component.dependencies.join(", ")}`);
135
+ }
136
+ lines.push("");
137
+ }
138
+ return lines.join("\n");
139
+ }
140
+
141
+ // src/tools/get.ts
142
+ function handleGetComponent(params) {
143
+ const { name } = params;
144
+ if (!name || name.trim() === "") {
145
+ throw new Error("\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984(name)\uC740 \uD544\uC218\uC785\uB2C8\uB2E4.");
146
+ }
147
+ const component = getComponent(name);
148
+ if (!component) {
149
+ return null;
150
+ }
151
+ return component;
152
+ }
153
+ function handleGetToken(params) {
154
+ const { path: path4 } = params;
155
+ if (!path4 || path4.trim() === "") {
156
+ throw new Error("\uD1A0\uD070 \uACBD\uB85C(path)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
157
+ }
158
+ const token = getToken(path4);
159
+ if (!token) {
160
+ return null;
161
+ }
162
+ return token;
163
+ }
164
+ function formatComponentDetail(component) {
165
+ if (!component) {
166
+ return "\uCEF4\uD3EC\uB10C\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
167
+ }
168
+ const lines = [];
169
+ lines.push(`# ${component.displayName}`);
170
+ lines.push("");
171
+ lines.push(`> ${component.description}`);
172
+ lines.push("");
173
+ lines.push("## \uAE30\uBCF8 \uC815\uBCF4");
174
+ lines.push(`- **\uC774\uB984**: \`${component.name}\``);
175
+ lines.push(`- **\uCE74\uD14C\uACE0\uB9AC**: ${component.category}`);
176
+ lines.push(`- **\uD328\uD0A4\uC9C0**: \`${component.package}\``);
177
+ lines.push("");
178
+ if (component.dependencies.length > 0) {
179
+ lines.push("## \uC758\uC874\uC131");
180
+ lines.push("```bash");
181
+ lines.push(`npm install ${component.dependencies.join(" ")}`);
182
+ lines.push("```");
183
+ lines.push("");
184
+ }
185
+ if (component.files.length > 0) {
186
+ lines.push("## \uD30C\uC77C \uAD6C\uC870");
187
+ for (const file of component.files) {
188
+ lines.push(`- \`${file.path}\` (${file.type})`);
189
+ }
190
+ lines.push("");
191
+ }
192
+ if (component.props && component.props.length > 0) {
193
+ lines.push("## Props");
194
+ lines.push("| \uC774\uB984 | \uD0C0\uC785 | \uD544\uC218 | \uAE30\uBCF8\uAC12 | \uC124\uBA85 |");
195
+ lines.push("|------|------|------|--------|------|");
196
+ for (const prop of component.props) {
197
+ const required = prop.required ? "\u2713" : "";
198
+ const defaultVal = prop.default || "-";
199
+ lines.push(
200
+ `| \`${prop.name}\` | \`${prop.type}\` | ${required} | ${defaultVal} | ${prop.description} |`
201
+ );
202
+ }
203
+ lines.push("");
204
+ }
205
+ if (component.examples && component.examples.length > 0) {
206
+ lines.push("## \uC608\uC81C");
207
+ for (const example of component.examples) {
208
+ lines.push(`### ${example.title}`);
209
+ if (example.description) {
210
+ lines.push(example.description);
211
+ }
212
+ lines.push("```tsx");
213
+ lines.push(example.code);
214
+ lines.push("```");
215
+ lines.push("");
216
+ }
217
+ }
218
+ return lines.join("\n");
219
+ }
220
+ function formatTokenDetail(token) {
221
+ if (!token) {
222
+ return "\uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
223
+ }
224
+ const lines = [];
225
+ lines.push(`# \uD1A0\uD070: ${token.path}`);
226
+ lines.push("");
227
+ lines.push(`- **\uAC12**: \`${token.value}\``);
228
+ lines.push(`- **\uCE74\uD14C\uACE0\uB9AC**: ${token.category}`);
229
+ if (token.description) {
230
+ lines.push(`- **\uC124\uBA85**: ${token.description}`);
231
+ }
232
+ if (token.cssVariable) {
233
+ lines.push(`- **CSS \uBCC0\uC218**: \`var(${token.cssVariable})\``);
234
+ }
235
+ return lines.join("\n");
236
+ }
237
+
238
+ // src/tools/list.ts
239
+ function handleListComponents(params) {
240
+ const { category } = params;
241
+ const components = listComponents(category);
242
+ const stats = getComponentStats();
243
+ return {
244
+ components,
245
+ totalCount: components.length,
246
+ stats,
247
+ category
248
+ };
249
+ }
250
+ function handleListTokens(params) {
251
+ const { category } = params;
252
+ const tokens = listTokens(category);
253
+ const stats = getTokenStats();
254
+ return {
255
+ tokens,
256
+ totalCount: tokens.length,
257
+ stats,
258
+ category
259
+ };
260
+ }
261
+ function formatComponentList(result) {
262
+ const lines = [];
263
+ lines.push("# AXIS Design System \uCEF4\uD3EC\uB10C\uD2B8 \uBAA9\uB85D");
264
+ lines.push("");
265
+ lines.push("## \uCE74\uD14C\uACE0\uB9AC\uBCC4 \uD1B5\uACC4");
266
+ lines.push("| \uCE74\uD14C\uACE0\uB9AC | \uAC1C\uC218 |");
267
+ lines.push("|----------|------|");
268
+ lines.push(`| Core | ${result.stats.core} |`);
269
+ lines.push(`| Form | ${result.stats.form} |`);
270
+ lines.push(`| Layout | ${result.stats.layout} |`);
271
+ lines.push(`| Agentic | ${result.stats.agentic} |`);
272
+ lines.push(`| **\uD569\uACC4** | **${result.totalCount}** |`);
273
+ lines.push("");
274
+ if (result.category) {
275
+ lines.push(`## ${result.category} \uCE74\uD14C\uACE0\uB9AC \uCEF4\uD3EC\uB10C\uD2B8`);
276
+ } else {
277
+ lines.push("## \uC804\uCCB4 \uCEF4\uD3EC\uB10C\uD2B8");
278
+ }
279
+ lines.push("");
280
+ const grouped = {};
281
+ for (const component of result.components) {
282
+ if (!grouped[component.category]) {
283
+ grouped[component.category] = [];
284
+ }
285
+ grouped[component.category].push(component);
286
+ }
287
+ for (const [category, components] of Object.entries(grouped)) {
288
+ lines.push(`### ${category.charAt(0).toUpperCase() + category.slice(1)}`);
289
+ lines.push("| \uC774\uB984 | \uC124\uBA85 | \uD328\uD0A4\uC9C0 |");
290
+ lines.push("|------|------|--------|");
291
+ for (const c of components) {
292
+ lines.push(`| \`${c.name}\` | ${c.description} | ${c.package} |`);
293
+ }
294
+ lines.push("");
295
+ }
296
+ return lines.join("\n");
297
+ }
298
+ function formatTokenList(result) {
299
+ const lines = [];
300
+ lines.push("# AXIS Design System \uD1A0\uD070 \uBAA9\uB85D");
301
+ lines.push("");
302
+ lines.push("## \uCE74\uD14C\uACE0\uB9AC\uBCC4 \uD1B5\uACC4");
303
+ lines.push("| \uCE74\uD14C\uACE0\uB9AC | \uAC1C\uC218 |");
304
+ lines.push("|----------|------|");
305
+ lines.push(`| Color | ${result.stats.color} |`);
306
+ lines.push(`| Typography | ${result.stats.typography} |`);
307
+ lines.push(`| Spacing | ${result.stats.spacing} |`);
308
+ lines.push(`| Radius | ${result.stats.radius} |`);
309
+ lines.push(`| Shadow | ${result.stats.shadow} |`);
310
+ lines.push(`| Animation | ${result.stats.animation} |`);
311
+ lines.push(`| **\uD569\uACC4** | **${result.totalCount}** |`);
312
+ lines.push("");
313
+ if (result.category) {
314
+ lines.push(`## ${result.category} \uD1A0\uD070`);
315
+ } else {
316
+ lines.push("## \uC804\uCCB4 \uD1A0\uD070");
317
+ }
318
+ lines.push("");
319
+ const grouped = {};
320
+ for (const token of result.tokens) {
321
+ if (!grouped[token.category]) {
322
+ grouped[token.category] = [];
323
+ }
324
+ grouped[token.category].push(token);
325
+ }
326
+ for (const [category, tokens] of Object.entries(grouped)) {
327
+ lines.push(`### ${category.charAt(0).toUpperCase() + category.slice(1)}`);
328
+ lines.push("| \uACBD\uB85C | \uAC12 | \uC124\uBA85 |");
329
+ lines.push("|------|-----|------|");
330
+ for (const t of tokens) {
331
+ const desc = t.description || "-";
332
+ lines.push(`| \`${t.path}\` | \`${t.value}\` | ${desc} |`);
333
+ }
334
+ lines.push("");
335
+ }
336
+ return lines.join("\n");
337
+ }
338
+
339
+ // src/tools/install.ts
340
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
341
+ import { join as join2, dirname as dirname2 } from "path";
342
+ var COMPONENT_TEMPLATES = {
343
+ button: {
344
+ "components/ui/button.tsx": `import * as React from "react"
345
+ import { Slot } from "@radix-ui/react-slot"
346
+ import { cva, type VariantProps } from "class-variance-authority"
347
+
348
+ import { cn } from "@/lib/utils"
349
+
350
+ const buttonVariants = cva(
351
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
352
+ {
353
+ variants: {
354
+ variant: {
355
+ default:
356
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
357
+ destructive:
358
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
359
+ outline:
360
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
361
+ secondary:
362
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
363
+ ghost: "hover:bg-accent hover:text-accent-foreground",
364
+ link: "text-primary underline-offset-4 hover:underline",
365
+ },
366
+ size: {
367
+ default: "h-9 px-4 py-2",
368
+ sm: "h-8 rounded-md px-3 text-xs",
369
+ lg: "h-10 rounded-md px-8",
370
+ icon: "h-9 w-9",
371
+ },
372
+ },
373
+ defaultVariants: {
374
+ variant: "default",
375
+ size: "default",
376
+ },
377
+ }
378
+ )
379
+
380
+ export interface ButtonProps
381
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
382
+ VariantProps<typeof buttonVariants> {
383
+ asChild?: boolean
384
+ }
385
+
386
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
387
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
388
+ const Comp = asChild ? Slot : "button"
389
+ return (
390
+ <Comp
391
+ className={cn(buttonVariants({ variant, size, className }))}
392
+ ref={ref}
393
+ {...props}
394
+ />
395
+ )
396
+ }
397
+ )
398
+ Button.displayName = "Button"
399
+
400
+ export { Button, buttonVariants }
401
+ `
402
+ },
403
+ input: {
404
+ "components/ui/input.tsx": `import * as React from "react"
405
+
406
+ import { cn } from "@/lib/utils"
407
+
408
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
409
+ ({ className, type, ...props }, ref) => {
410
+ return (
411
+ <input
412
+ type={type}
413
+ className={cn(
414
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
415
+ className
416
+ )}
417
+ ref={ref}
418
+ {...props}
419
+ />
420
+ )
421
+ }
422
+ )
423
+ Input.displayName = "Input"
424
+
425
+ export { Input }
426
+ `
427
+ },
428
+ label: {
429
+ "components/ui/label.tsx": `"use client"
430
+
431
+ import * as React from "react"
432
+ import * as LabelPrimitive from "@radix-ui/react-label"
433
+ import { cva, type VariantProps } from "class-variance-authority"
434
+
435
+ import { cn } from "@/lib/utils"
436
+
437
+ const labelVariants = cva(
438
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
439
+ )
440
+
441
+ const Label = React.forwardRef<
442
+ React.ElementRef<typeof LabelPrimitive.Root>,
443
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
444
+ VariantProps<typeof labelVariants>
445
+ >(({ className, ...props }, ref) => (
446
+ <LabelPrimitive.Root
447
+ ref={ref}
448
+ className={cn(labelVariants(), className)}
449
+ {...props}
450
+ />
451
+ ))
452
+ Label.displayName = LabelPrimitive.Root.displayName
453
+
454
+ export { Label }
455
+ `
456
+ },
457
+ card: {
458
+ "components/ui/card.tsx": `import * as React from "react"
459
+
460
+ import { cn } from "@/lib/utils"
461
+
462
+ const Card = React.forwardRef<
463
+ HTMLDivElement,
464
+ React.HTMLAttributes<HTMLDivElement>
465
+ >(({ className, ...props }, ref) => (
466
+ <div
467
+ ref={ref}
468
+ className={cn(
469
+ "rounded-xl border bg-card text-card-foreground shadow",
470
+ className
471
+ )}
472
+ {...props}
473
+ />
474
+ ))
475
+ Card.displayName = "Card"
476
+
477
+ const CardHeader = React.forwardRef<
478
+ HTMLDivElement,
479
+ React.HTMLAttributes<HTMLDivElement>
480
+ >(({ className, ...props }, ref) => (
481
+ <div
482
+ ref={ref}
483
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
484
+ {...props}
485
+ />
486
+ ))
487
+ CardHeader.displayName = "CardHeader"
488
+
489
+ const CardTitle = React.forwardRef<
490
+ HTMLDivElement,
491
+ React.HTMLAttributes<HTMLDivElement>
492
+ >(({ className, ...props }, ref) => (
493
+ <div
494
+ ref={ref}
495
+ className={cn("font-semibold leading-none tracking-tight", className)}
496
+ {...props}
497
+ />
498
+ ))
499
+ CardTitle.displayName = "CardTitle"
500
+
501
+ const CardDescription = React.forwardRef<
502
+ HTMLDivElement,
503
+ React.HTMLAttributes<HTMLDivElement>
504
+ >(({ className, ...props }, ref) => (
505
+ <div
506
+ ref={ref}
507
+ className={cn("text-sm text-muted-foreground", className)}
508
+ {...props}
509
+ />
510
+ ))
511
+ CardDescription.displayName = "CardDescription"
512
+
513
+ const CardContent = React.forwardRef<
514
+ HTMLDivElement,
515
+ React.HTMLAttributes<HTMLDivElement>
516
+ >(({ className, ...props }, ref) => (
517
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
518
+ ))
519
+ CardContent.displayName = "CardContent"
520
+
521
+ const CardFooter = React.forwardRef<
522
+ HTMLDivElement,
523
+ React.HTMLAttributes<HTMLDivElement>
524
+ >(({ className, ...props }, ref) => (
525
+ <div
526
+ ref={ref}
527
+ className={cn("flex items-center p-6 pt-0", className)}
528
+ {...props}
529
+ />
530
+ ))
531
+ CardFooter.displayName = "CardFooter"
532
+
533
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
534
+ `
535
+ },
536
+ badge: {
537
+ "components/ui/badge.tsx": `import * as React from "react"
538
+ import { cva, type VariantProps } from "class-variance-authority"
539
+
540
+ import { cn } from "@/lib/utils"
541
+
542
+ const badgeVariants = cva(
543
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
544
+ {
545
+ variants: {
546
+ variant: {
547
+ default:
548
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
549
+ secondary:
550
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
551
+ destructive:
552
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
553
+ outline: "text-foreground",
554
+ },
555
+ },
556
+ defaultVariants: {
557
+ variant: "default",
558
+ },
559
+ }
560
+ )
561
+
562
+ export interface BadgeProps
563
+ extends React.HTMLAttributes<HTMLDivElement>,
564
+ VariantProps<typeof badgeVariants> {}
565
+
566
+ function Badge({ className, variant, ...props }: BadgeProps) {
567
+ return (
568
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
569
+ )
570
+ }
571
+
572
+ export { Badge, badgeVariants }
573
+ `
574
+ }
575
+ };
576
+ function handleInstallComponent(params) {
577
+ const { name, targetDir } = params;
578
+ if (!name || name.trim() === "") {
579
+ throw new Error("\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984(name)\uC740 \uD544\uC218\uC785\uB2C8\uB2E4.");
580
+ }
581
+ if (!targetDir || targetDir.trim() === "") {
582
+ throw new Error("\uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC(targetDir)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
583
+ }
584
+ const component = getComponent(name);
585
+ if (!component) {
586
+ return {
587
+ success: false,
588
+ installedFiles: [],
589
+ dependencies: [],
590
+ message: `\uCEF4\uD3EC\uB10C\uD2B8 '${name}'\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
591
+ };
592
+ }
593
+ const templates = COMPONENT_TEMPLATES[name.toLowerCase()];
594
+ if (!templates) {
595
+ return {
596
+ success: false,
597
+ installedFiles: [],
598
+ dependencies: component.dependencies,
599
+ message: `\uCEF4\uD3EC\uB10C\uD2B8 '${name}'\uC758 \uD15C\uD50C\uB9BF\uC774 \uC544\uC9C1 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC218\uB3D9\uC73C\uB85C \uC124\uCE58\uD574 \uC8FC\uC138\uC694.`
600
+ };
601
+ }
602
+ const installedFiles = [];
603
+ try {
604
+ for (const [relativePath, content] of Object.entries(templates)) {
605
+ const fullPath = join2(targetDir, relativePath);
606
+ const dir = dirname2(fullPath);
607
+ if (!existsSync(dir)) {
608
+ mkdirSync(dir, { recursive: true });
609
+ }
610
+ writeFileSync(fullPath, content, "utf-8");
611
+ installedFiles.push(relativePath);
612
+ }
613
+ return {
614
+ success: true,
615
+ installedFiles,
616
+ dependencies: component.dependencies,
617
+ message: `\uCEF4\uD3EC\uB10C\uD2B8 '${name}'\uC774(\uAC00) \uC131\uACF5\uC801\uC73C\uB85C \uC124\uCE58\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`
618
+ };
619
+ } catch (error) {
620
+ const errorMessage = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
621
+ return {
622
+ success: false,
623
+ installedFiles,
624
+ dependencies: component.dependencies,
625
+ message: `\uC124\uCE58 \uC911 \uC624\uB958 \uBC1C\uC0DD: ${errorMessage}`
626
+ };
627
+ }
628
+ }
629
+ function formatInstallResult(result) {
630
+ const lines = [];
631
+ if (result.success) {
632
+ lines.push("# \uCEF4\uD3EC\uB10C\uD2B8 \uC124\uCE58 \uC644\uB8CC");
633
+ lines.push("");
634
+ lines.push(result.message);
635
+ lines.push("");
636
+ if (result.installedFiles.length > 0) {
637
+ lines.push("## \uC124\uCE58\uB41C \uD30C\uC77C");
638
+ for (const file of result.installedFiles) {
639
+ lines.push(`- \`${file}\``);
640
+ }
641
+ lines.push("");
642
+ }
643
+ if (result.dependencies.length > 0) {
644
+ lines.push("## \uD544\uC694\uD55C \uC758\uC874\uC131");
645
+ lines.push("\uB2E4\uC74C \uD328\uD0A4\uC9C0\uB97C \uC124\uCE58\uD574 \uC8FC\uC138\uC694:");
646
+ lines.push("");
647
+ lines.push("```bash");
648
+ lines.push(`npm install ${result.dependencies.join(" ")}`);
649
+ lines.push("```");
650
+ }
651
+ } else {
652
+ lines.push("# \uCEF4\uD3EC\uB10C\uD2B8 \uC124\uCE58 \uC2E4\uD328");
653
+ lines.push("");
654
+ lines.push(result.message);
655
+ if (result.dependencies.length > 0) {
656
+ lines.push("");
657
+ lines.push("## \uD544\uC694\uD55C \uC758\uC874\uC131 (\uCC38\uACE0)");
658
+ for (const dep of result.dependencies) {
659
+ lines.push(`- \`${dep}\``);
660
+ }
661
+ }
662
+ }
663
+ return lines.join("\n");
664
+ }
665
+
666
+ // src/tools/prompt/detect.ts
667
+ var DEFAULT_MIN_SCORE = 70;
668
+ var DEFAULT_MAX_CANDIDATES = 5;
669
+ var REUSABLE_PATTERNS = {
670
+ goal: ["\uBAA9\uD45C", "\uBAA9\uC801", "\uB2EC\uC131", "\uC704\uD574", "\uC218\uD589"],
671
+ steps: ["\uB2E8\uACC4", "\uC808\uCC28", "\uC21C\uC11C", "\uBA3C\uC800", "\uB2E4\uC74C", "\uB9C8\uC9C0\uB9C9"],
672
+ output: ["\uCD9C\uB825", "\uACB0\uACFC", "\uD615\uC2DD", "\uD15C\uD50C\uB9BF", "\uC0B0\uCD9C\uBB3C"],
673
+ validation: ["\uAC80\uC99D", "\uD655\uC778", "\uCCB4\uD06C", "\uD14C\uC2A4\uD2B8", "\uAC80\uC0AC"],
674
+ planning: ["\uACC4\uD68D", "\uC124\uACC4", "\uC804\uB7B5", "\uB85C\uB4DC\uB9F5", "\uB9C8\uC77C\uC2A4\uD1A4"],
675
+ quality: ["\uB9AC\uBDF0", "\uC810\uAC80", "\uBD84\uC11D", "\uD3C9\uAC00", "\uD488\uC9C8"],
676
+ documentation: ["\uBB38\uC11C", "\uAC00\uC774\uB4DC", "\uC124\uBA85", "\uB9E4\uB274\uC5BC", "README"],
677
+ workflow: ["\uC6CC\uD06C\uD50C\uB85C\uC6B0", "\uD504\uB85C\uC138\uC2A4", "\uD30C\uC774\uD504\uB77C\uC778", "\uC790\uB3D9\uD654"]
678
+ };
679
+ var PROJECT_SPECIFIC_PATTERNS = [
680
+ // 파일 경로
681
+ /(?:src|packages|apps|lib|components)\/[\w\-\/]+\.(?:ts|tsx|js|jsx|md)/gi,
682
+ // 컴포넌트명 (PascalCase)
683
+ /\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g,
684
+ // 버전
685
+ /\b\d+\.\d+\.\d+(?:-[\w.]+)?\b/g,
686
+ // 날짜
687
+ /\b\d{4}-\d{2}-\d{2}\b/g,
688
+ // 패키지명
689
+ /@[\w-]+\/[\w-]+/g
690
+ ];
691
+ function segmentText(text) {
692
+ const sections = text.split(/(?=^#{1,3}\s)/m);
693
+ return sections.filter((s) => s.trim().length > 50);
694
+ }
695
+ function calculateRepeatability(text, _context) {
696
+ let score = 0;
697
+ const structuredPatterns = [
698
+ REUSABLE_PATTERNS.goal,
699
+ REUSABLE_PATTERNS.steps,
700
+ REUSABLE_PATTERNS.output
701
+ ];
702
+ for (const patterns of structuredPatterns) {
703
+ if (patterns.some((p) => text.includes(p))) {
704
+ score += 5;
705
+ }
706
+ }
707
+ if (/^#{1,3}\s/m.test(text)) score += 5;
708
+ if (/^[\-\*]\s/m.test(text)) score += 3;
709
+ if (/^\d+\.\s/m.test(text)) score += 2;
710
+ return Math.min(25, score);
711
+ }
712
+ function calculateGenerality(text) {
713
+ let score = 30;
714
+ for (const pattern of PROJECT_SPECIFIC_PATTERNS) {
715
+ const matches = text.match(pattern);
716
+ if (matches) {
717
+ score -= Math.min(10, matches.length * 2);
718
+ }
719
+ }
720
+ return Math.max(0, score);
721
+ }
722
+ function calculateIndependence(text) {
723
+ let score = 25;
724
+ const contextDependentPhrases = [
725
+ "\uC704\uC5D0\uC11C \uC5B8\uAE09\uD55C",
726
+ "\uC55E\uC11C \uB9D0\uD55C",
727
+ "\uC774\uC804\uC5D0",
728
+ "\uC544\uAE4C",
729
+ "\uC704 \uCF54\uB4DC",
730
+ "\uC544\uB798 \uCF54\uB4DC"
731
+ ];
732
+ for (const phrase of contextDependentPhrases) {
733
+ if (text.includes(phrase)) {
734
+ score -= 5;
735
+ }
736
+ }
737
+ return Math.max(0, score);
738
+ }
739
+ function calculateClarity(text) {
740
+ let score = 0;
741
+ if (REUSABLE_PATTERNS.goal.some(
742
+ (p) => text.includes(`## ${p}`) || text.includes(`### ${p}`)
743
+ )) {
744
+ score += 5;
745
+ }
746
+ if (REUSABLE_PATTERNS.steps.some(
747
+ (p) => text.includes(`## ${p}`) || text.includes(`### ${p}`)
748
+ )) {
749
+ score += 5;
750
+ }
751
+ if (text.includes("```") && text.includes("\uCD9C\uB825")) {
752
+ score += 5;
753
+ }
754
+ if (/\[[\sx]\]/i.test(text)) {
755
+ score += 5;
756
+ }
757
+ return Math.min(20, score);
758
+ }
759
+ function calculateScores(text, context) {
760
+ const repeatability = calculateRepeatability(text, context);
761
+ const generality = calculateGenerality(text);
762
+ const independence = calculateIndependence(text);
763
+ const clarity = calculateClarity(text);
764
+ return {
765
+ repeatability,
766
+ generality,
767
+ independence,
768
+ clarity,
769
+ total: repeatability + generality + independence + clarity
770
+ };
771
+ }
772
+ function suggestCategory(text) {
773
+ const categoryScores = {
774
+ planning: 0,
775
+ quality: 0,
776
+ documentation: 0,
777
+ workflow: 0
778
+ };
779
+ for (const keyword of REUSABLE_PATTERNS.planning) {
780
+ if (text.includes(keyword)) categoryScores.planning += 1;
781
+ }
782
+ for (const keyword of REUSABLE_PATTERNS.quality) {
783
+ if (text.includes(keyword)) categoryScores.quality += 1;
784
+ }
785
+ for (const keyword of REUSABLE_PATTERNS.documentation) {
786
+ if (text.includes(keyword)) categoryScores.documentation += 1;
787
+ }
788
+ for (const keyword of REUSABLE_PATTERNS.workflow) {
789
+ if (text.includes(keyword)) categoryScores.workflow += 1;
790
+ }
791
+ const entries = Object.entries(categoryScores);
792
+ entries.sort((a, b) => b[1] - a[1]);
793
+ return entries[0][0];
794
+ }
795
+ function extractVariables(text) {
796
+ const variables = [];
797
+ const seen = /* @__PURE__ */ new Set();
798
+ for (const pattern of PROJECT_SPECIFIC_PATTERNS) {
799
+ const matches = text.match(pattern);
800
+ if (matches) {
801
+ for (const match of matches) {
802
+ if (seen.has(match)) continue;
803
+ seen.add(match);
804
+ let varName;
805
+ let description;
806
+ if (match.includes("/")) {
807
+ varName = "{{FILE_PATH}}";
808
+ description = "\uD30C\uC77C \uACBD\uB85C";
809
+ } else if (/^\d+\.\d+\.\d+/.test(match)) {
810
+ varName = "{{VERSION}}";
811
+ description = "\uBC84\uC804";
812
+ } else if (/^\d{4}-\d{2}-\d{2}$/.test(match)) {
813
+ varName = "{{DATE}}";
814
+ description = "\uB0A0\uC9DC";
815
+ } else if (match.startsWith("@")) {
816
+ varName = "{{PACKAGE_NAME}}";
817
+ description = "\uD328\uD0A4\uC9C0\uBA85";
818
+ } else {
819
+ varName = "{{COMPONENT_NAME}}";
820
+ description = "\uCEF4\uD3EC\uB10C\uD2B8/\uAE30\uB2A5\uBA85";
821
+ }
822
+ if (!variables.some((v) => v.name === varName)) {
823
+ variables.push({
824
+ name: varName,
825
+ description,
826
+ originalValue: match
827
+ });
828
+ }
829
+ }
830
+ }
831
+ }
832
+ return variables;
833
+ }
834
+ function generateSuggestions(scores) {
835
+ const suggestions = [];
836
+ if (scores.repeatability < 15) {
837
+ suggestions.push("\uAD6C\uC870\uD654\uB41C \uC139\uC158 (\uBAA9\uD45C, \uB2E8\uACC4, \uCD9C\uB825) \uCD94\uAC00 \uAD8C\uC7A5");
838
+ }
839
+ if (scores.generality < 20) {
840
+ suggestions.push("\uD504\uB85C\uC81D\uD2B8 \uD2B9\uC815 \uAC12\uC744 \uBCC0\uC218\uB85C \uB300\uCCB4 \uD544\uC694");
841
+ }
842
+ if (scores.independence < 15) {
843
+ suggestions.push("\uCEE8\uD14D\uC2A4\uD2B8 \uC758\uC874 \uD45C\uD604 \uC81C\uAC70 \uD544\uC694");
844
+ }
845
+ if (scores.clarity < 12) {
846
+ suggestions.push("\uCD9C\uB825 \uD615\uC2DD \uBC0F \uAC80\uC99D \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uCD94\uAC00 \uAD8C\uC7A5");
847
+ }
848
+ return suggestions;
849
+ }
850
+ function handleDetect(params) {
851
+ const {
852
+ context,
853
+ minScore = DEFAULT_MIN_SCORE,
854
+ maxCandidates = DEFAULT_MAX_CANDIDATES
855
+ } = params;
856
+ if (!context || context.trim() === "") {
857
+ throw new Error("\uCEE8\uD14D\uC2A4\uD2B8(context)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
858
+ }
859
+ const segments = segmentText(context);
860
+ const candidates = [];
861
+ for (let i = 0; i < segments.length; i++) {
862
+ const segment = segments[i];
863
+ const scores = calculateScores(segment, context);
864
+ if (scores.total >= minScore) {
865
+ candidates.push({
866
+ id: `candidate-${i + 1}`,
867
+ source: i === 0 ? "user_input" : "work_output",
868
+ originalText: segment,
869
+ scores,
870
+ suggestedCategory: suggestCategory(segment),
871
+ extractedVariables: extractVariables(segment),
872
+ suggestions: generateSuggestions(scores)
873
+ });
874
+ }
875
+ }
876
+ candidates.sort((a, b) => b.scores.total - a.scores.total);
877
+ const limitedCandidates = candidates.slice(0, maxCandidates);
878
+ return {
879
+ candidates: limitedCandidates,
880
+ totalFound: candidates.length,
881
+ threshold: minScore
882
+ };
883
+ }
884
+ function formatDetectResult(result) {
885
+ const lines = [];
886
+ lines.push("## \uD504\uB86C\uD504\uD2B8 \uD6C4\uBCF4 \uD0D0\uC9C0");
887
+ lines.push("");
888
+ lines.push(`**\uCD5C\uC18C \uC810\uC218 \uAE30\uC900**: ${result.threshold}\uC810`);
889
+ lines.push(`**\uBC1C\uACAC\uB41C \uD6C4\uBCF4**: ${result.totalFound}\uAC1C`);
890
+ lines.push("");
891
+ if (result.candidates.length === 0) {
892
+ lines.push("\uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB86C\uD504\uD2B8 \uD6C4\uBCF4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
893
+ return lines.join("\n");
894
+ }
895
+ lines.push(
896
+ "| # | \uC18C\uC2A4 | \uC810\uC218 | \uCE74\uD14C\uACE0\uB9AC (\uCD94\uCC9C) | \uBCC0\uC218 \uC218 |"
897
+ );
898
+ lines.push("|---|------|------|-----------------|---------|");
899
+ for (let i = 0; i < result.candidates.length; i++) {
900
+ const c = result.candidates[i];
901
+ const sourceLabel = c.source === "user_input" ? "\uC0AC\uC6A9\uC790 \uC785\uB825" : c.source === "work_output" ? "\uC791\uC5C5 \uACB0\uACFC\uBB3C" : "\uCEE8\uD14D\uC2A4\uD2B8";
902
+ lines.push(
903
+ `| ${i + 1} | ${sourceLabel} | ${c.scores.total}\uC810 | ${c.suggestedCategory} | ${c.extractedVariables.length}\uAC1C |`
904
+ );
905
+ }
906
+ lines.push("");
907
+ lines.push("### \uC0C1\uC138 \uC815\uBCF4");
908
+ lines.push("");
909
+ for (let i = 0; i < result.candidates.length; i++) {
910
+ const c = result.candidates[i];
911
+ lines.push(`#### \uD6C4\uBCF4 ${i + 1}`);
912
+ lines.push("");
913
+ lines.push("**\uC810\uC218 \uC0C1\uC138**:");
914
+ lines.push(`- \uBC18\uBCF5\uC131: ${c.scores.repeatability}/25`);
915
+ lines.push(`- \uBC94\uC6A9\uC131: ${c.scores.generality}/30`);
916
+ lines.push(`- \uB3C5\uB9BD\uC131: ${c.scores.independence}/25`);
917
+ lines.push(`- \uBA85\uD655\uC131: ${c.scores.clarity}/20`);
918
+ lines.push("");
919
+ if (c.extractedVariables.length > 0) {
920
+ lines.push("**\uCD94\uCD9C\uB41C \uBCC0\uC218**:");
921
+ for (const v of c.extractedVariables) {
922
+ lines.push(`- \`${v.name}\`: ${v.originalValue}`);
923
+ }
924
+ lines.push("");
925
+ }
926
+ if (c.suggestions.length > 0) {
927
+ lines.push("**\uAC1C\uC120 \uC81C\uC548**:");
928
+ for (const s of c.suggestions) {
929
+ lines.push(`- ${s}`);
930
+ }
931
+ lines.push("");
932
+ }
933
+ lines.push("**\uBBF8\uB9AC\uBCF4\uAE30** (\uCC98\uC74C 200\uC790):");
934
+ lines.push("```");
935
+ lines.push(c.originalText.slice(0, 200) + "...");
936
+ lines.push("```");
937
+ lines.push("");
938
+ }
939
+ lines.push("---");
940
+ lines.push("\uC800\uC7A5\uD558\uB824\uBA74: `/ax-prompt save` \uB610\uB294 \uBC88\uD638 \uC120\uD0DD");
941
+ return lines.join("\n");
942
+ }
943
+
944
+ // src/tools/prompt/analyze.ts
945
+ var SECTION_KEYWORDS = {
946
+ goal: ["\uBAA9\uD45C", "\uBAA9\uC801", "\uAC1C\uC694", "\uC18C\uAC1C", "\uBC30\uACBD"],
947
+ steps: ["\uB2E8\uACC4", "\uC808\uCC28", "\uC218\uD589", "\uC791\uC5C5", "\uBC29\uBC95", "\uAD6C\uD604"],
948
+ output: ["\uCD9C\uB825", "\uACB0\uACFC", "\uD615\uC2DD", "\uC0B0\uCD9C\uBB3C", "\uD15C\uD50C\uB9BF"],
949
+ validation: ["\uAC80\uC99D", "\uD655\uC778", "\uCCB4\uD06C", "\uD14C\uC2A4\uD2B8", "\uAC80\uC0AC", "\uAC80\uD1A0"]
950
+ };
951
+ var CATEGORY_KEYWORDS = {
952
+ planning: [
953
+ "\uACC4\uD68D",
954
+ "\uC124\uACC4",
955
+ "\uC804\uB7B5",
956
+ "\uB85C\uB4DC\uB9F5",
957
+ "\uB9C8\uC77C\uC2A4\uD1A4",
958
+ "\uC694\uAD6C\uC0AC\uD56D",
959
+ "\uBD84\uC11D",
960
+ "\uC544\uD0A4\uD14D\uCC98"
961
+ ],
962
+ quality: [
963
+ "\uB9AC\uBDF0",
964
+ "\uC810\uAC80",
965
+ "\uD488\uC9C8",
966
+ "\uD14C\uC2A4\uD2B8",
967
+ "\uAC80\uC99D",
968
+ "\uBCF4\uC548",
969
+ "\uC131\uB2A5",
970
+ "\uC811\uADFC\uC131"
971
+ ],
972
+ documentation: [
973
+ "\uBB38\uC11C",
974
+ "\uAC00\uC774\uB4DC",
975
+ "\uB9E4\uB274\uC5BC",
976
+ "API",
977
+ "README",
978
+ "\uC124\uBA85",
979
+ "\uD29C\uD1A0\uB9AC\uC5BC"
980
+ ],
981
+ workflow: [
982
+ "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
983
+ "\uD504\uB85C\uC138\uC2A4",
984
+ "\uD30C\uC774\uD504\uB77C\uC778",
985
+ "\uC790\uB3D9\uD654",
986
+ "CI",
987
+ "CD",
988
+ "\uBC30\uD3EC",
989
+ "\uB9B4\uB9AC\uC2A4"
990
+ ]
991
+ };
992
+ var PROJECT_PATTERNS = {
993
+ path: /(?:src|packages|apps|lib|components)\/[\w\-\/]+\.(?:ts|tsx|js|jsx|md)/gi,
994
+ name: /\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g,
995
+ version: /\b\d+\.\d+\.\d+(?:-[\w.]+)?\b/g,
996
+ date: /\b\d{4}-\d{2}-\d{2}\b/g,
997
+ package: /@[\w-]+\/[\w-]+/g
998
+ };
999
+ function calculateRepeatability2(text) {
1000
+ let score = 0;
1001
+ const reasons = [];
1002
+ const headerCount = (text.match(/^#{1,3}\s/gm) || []).length;
1003
+ if (headerCount >= 3) {
1004
+ score += 8;
1005
+ reasons.push(`${headerCount}\uAC1C \uC139\uC158 \uAD6C\uC870 \uBC1C\uACAC`);
1006
+ } else if (headerCount > 0) {
1007
+ score += 4;
1008
+ reasons.push(`${headerCount}\uAC1C \uC139\uC158 \uC874\uC7AC`);
1009
+ }
1010
+ const listCount = (text.match(/^[\-\*]\s/gm) || []).length;
1011
+ const numberedCount = (text.match(/^\d+\.\s/gm) || []).length;
1012
+ if (listCount + numberedCount >= 5) {
1013
+ score += 8;
1014
+ reasons.push("\uCCB4\uACC4\uC801\uC778 \uB9AC\uC2A4\uD2B8 \uAD6C\uC870");
1015
+ } else if (listCount + numberedCount > 0) {
1016
+ score += 4;
1017
+ reasons.push("\uB9AC\uC2A4\uD2B8 \uAD6C\uC870 \uC874\uC7AC");
1018
+ }
1019
+ const structuredKeywords = [
1020
+ ...SECTION_KEYWORDS.goal,
1021
+ ...SECTION_KEYWORDS.steps
1022
+ ];
1023
+ const keywordMatches = structuredKeywords.filter(
1024
+ (k) => text.includes(k)
1025
+ ).length;
1026
+ if (keywordMatches >= 3) {
1027
+ score += 9;
1028
+ reasons.push("\uAD6C\uC870\uD654 \uD0A4\uC6CC\uB4DC \uB2E4\uC218 \uD3EC\uD568");
1029
+ } else if (keywordMatches > 0) {
1030
+ score += 4;
1031
+ reasons.push(`${keywordMatches}\uAC1C \uAD6C\uC870\uD654 \uD0A4\uC6CC\uB4DC`);
1032
+ }
1033
+ return {
1034
+ score: Math.min(25, score),
1035
+ feedback: reasons.length > 0 ? reasons.join(", ") : "\uAD6C\uC870\uD654 \uD328\uD134 \uBD80\uC871"
1036
+ };
1037
+ }
1038
+ function calculateGenerality2(text) {
1039
+ let score = 30;
1040
+ const reasons = [];
1041
+ const projectValues = [];
1042
+ const paths = text.match(PROJECT_PATTERNS.path);
1043
+ if (paths) {
1044
+ score -= Math.min(8, paths.length * 2);
1045
+ reasons.push(`${paths.length}\uAC1C \uD30C\uC77C \uACBD\uB85C \uAC10\uC9C0`);
1046
+ paths.forEach(
1047
+ (p) => projectValues.push({
1048
+ value: p,
1049
+ type: "path",
1050
+ suggestedVariable: "{{FILE_PATH}}"
1051
+ })
1052
+ );
1053
+ }
1054
+ const versions = text.match(PROJECT_PATTERNS.version);
1055
+ if (versions) {
1056
+ score -= Math.min(4, versions.length);
1057
+ reasons.push(`${versions.length}\uAC1C \uBC84\uC804 \uAC10\uC9C0`);
1058
+ versions.forEach(
1059
+ (v) => projectValues.push({
1060
+ value: v,
1061
+ type: "version",
1062
+ suggestedVariable: "{{VERSION}}"
1063
+ })
1064
+ );
1065
+ }
1066
+ const dates = text.match(PROJECT_PATTERNS.date);
1067
+ if (dates) {
1068
+ score -= Math.min(4, dates.length);
1069
+ reasons.push(`${dates.length}\uAC1C \uB0A0\uC9DC \uAC10\uC9C0`);
1070
+ dates.forEach(
1071
+ (d) => projectValues.push({
1072
+ value: d,
1073
+ type: "date",
1074
+ suggestedVariable: "{{DATE}}"
1075
+ })
1076
+ );
1077
+ }
1078
+ const packages = text.match(PROJECT_PATTERNS.package);
1079
+ if (packages) {
1080
+ score -= Math.min(6, packages.length * 2);
1081
+ reasons.push(`${packages.length}\uAC1C \uD328\uD0A4\uC9C0\uBA85 \uAC10\uC9C0`);
1082
+ packages.forEach(
1083
+ (p) => projectValues.push({
1084
+ value: p,
1085
+ type: "name",
1086
+ suggestedVariable: "{{PACKAGE_NAME}}"
1087
+ })
1088
+ );
1089
+ }
1090
+ const names = text.match(PROJECT_PATTERNS.name);
1091
+ if (names) {
1092
+ const filteredNames = names.filter(
1093
+ (n) => ![
1094
+ "README",
1095
+ "TypeScript",
1096
+ "JavaScript",
1097
+ "React",
1098
+ "Next",
1099
+ "Node"
1100
+ ].includes(n)
1101
+ );
1102
+ if (filteredNames.length > 0) {
1103
+ score -= Math.min(8, filteredNames.length * 2);
1104
+ reasons.push(`${filteredNames.length}\uAC1C \uACE0\uC720\uBA85 \uAC10\uC9C0`);
1105
+ filteredNames.forEach(
1106
+ (n) => projectValues.push({
1107
+ value: n,
1108
+ type: "name",
1109
+ suggestedVariable: "{{COMPONENT_NAME}}"
1110
+ })
1111
+ );
1112
+ }
1113
+ }
1114
+ return {
1115
+ score: Math.max(0, score),
1116
+ feedback: reasons.length > 0 ? reasons.join(", ") : "\uD504\uB85C\uC81D\uD2B8 \uC885\uC18D\uC131 \uB0AE\uC74C",
1117
+ projectValues
1118
+ };
1119
+ }
1120
+ function calculateIndependence2(text) {
1121
+ let score = 25;
1122
+ const reasons = [];
1123
+ const contextPhrases = [
1124
+ "\uC704\uC5D0\uC11C",
1125
+ "\uC55E\uC11C",
1126
+ "\uC774\uC804\uC5D0",
1127
+ "\uC544\uAE4C",
1128
+ "\uC704 \uCF54\uB4DC",
1129
+ "\uC544\uB798 \uCF54\uB4DC",
1130
+ "\uB9D0\uC500\uD558\uC2E0",
1131
+ "\uC694\uCCAD\uD558\uC2E0"
1132
+ ];
1133
+ for (const phrase of contextPhrases) {
1134
+ if (text.includes(phrase)) {
1135
+ score -= 5;
1136
+ reasons.push(`"${phrase}" \uD45C\uD604 \uD3EC\uD568`);
1137
+ }
1138
+ }
1139
+ const pronounPatterns = [/그것[을이가]/g, /이것[을이가]/g, /저것[을이가]/g];
1140
+ for (const pattern of pronounPatterns) {
1141
+ if (pattern.test(text)) {
1142
+ score -= 3;
1143
+ reasons.push("\uB300\uBA85\uC0AC \uCC38\uC870 \uD3EC\uD568");
1144
+ break;
1145
+ }
1146
+ }
1147
+ return {
1148
+ score: Math.max(0, score),
1149
+ feedback: reasons.length > 0 ? reasons.join(", ") : "\uB3C5\uB9BD\uC801\uC73C\uB85C \uB3D9\uC791 \uAC00\uB2A5"
1150
+ };
1151
+ }
1152
+ function calculateClarity2(text) {
1153
+ let score = 0;
1154
+ const reasons = [];
1155
+ if (SECTION_KEYWORDS.goal.some((k) => text.includes(`## ${k}`) || text.includes(`### ${k}`))) {
1156
+ score += 5;
1157
+ reasons.push("\uBAA9\uD45C \uC139\uC158 \uC874\uC7AC");
1158
+ }
1159
+ if (SECTION_KEYWORDS.steps.some((k) => text.includes(`## ${k}`) || text.includes(`### ${k}`))) {
1160
+ score += 5;
1161
+ reasons.push("\uC218\uD589 \uB2E8\uACC4 \uC139\uC158 \uC874\uC7AC");
1162
+ }
1163
+ if (text.includes("```") && SECTION_KEYWORDS.output.some((k) => text.includes(k))) {
1164
+ score += 5;
1165
+ reasons.push("\uCD9C\uB825 \uD615\uC2DD \uC815\uC758\uB428");
1166
+ }
1167
+ if (/\[[\sx]\]/i.test(text)) {
1168
+ score += 5;
1169
+ reasons.push("\uAC80\uC99D \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uD3EC\uD568");
1170
+ }
1171
+ return {
1172
+ score: Math.min(20, score),
1173
+ feedback: reasons.length > 0 ? reasons.join(", ") : "\uBA85\uD655\uC131 \uAC1C\uC120 \uD544\uC694"
1174
+ };
1175
+ }
1176
+ function suggestCategory2(text) {
1177
+ const scores = {
1178
+ planning: 0,
1179
+ quality: 0,
1180
+ documentation: 0,
1181
+ workflow: 0
1182
+ };
1183
+ for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
1184
+ for (const keyword of keywords) {
1185
+ const regex = new RegExp(keyword, "gi");
1186
+ const matches = text.match(regex);
1187
+ if (matches) {
1188
+ scores[category] += matches.length;
1189
+ }
1190
+ }
1191
+ }
1192
+ const entries = Object.entries(scores);
1193
+ entries.sort((a, b) => b[1] - a[1]);
1194
+ return entries[0][0];
1195
+ }
1196
+ function extractSections(text) {
1197
+ const sections = {};
1198
+ for (const keyword of SECTION_KEYWORDS.goal) {
1199
+ const goalMatch = text.match(
1200
+ new RegExp(`##?#?\\s*${keyword}[\\s\\S]*?(?=##|$)`, "i")
1201
+ );
1202
+ if (goalMatch) {
1203
+ sections.goal = goalMatch[0].trim();
1204
+ break;
1205
+ }
1206
+ }
1207
+ const stepMatches = text.match(/^\d+\.\s+.+$/gm);
1208
+ if (stepMatches) {
1209
+ sections.steps = stepMatches.map((s) => s.replace(/^\d+\.\s+/, ""));
1210
+ }
1211
+ for (const keyword of SECTION_KEYWORDS.output) {
1212
+ const outputMatch = text.match(
1213
+ new RegExp(`##?#?\\s*${keyword}[\\s\\S]*?(?=##|$)`, "i")
1214
+ );
1215
+ if (outputMatch) {
1216
+ sections.output = outputMatch[0].trim();
1217
+ break;
1218
+ }
1219
+ }
1220
+ const validationMatches = text.match(/^\s*\[[\sx]\]\s*.+$/gim);
1221
+ if (validationMatches) {
1222
+ sections.validation = validationMatches.map(
1223
+ (v) => v.replace(/^\s*\[[\sx]\]\s*/, "")
1224
+ );
1225
+ }
1226
+ return sections;
1227
+ }
1228
+ function handleAnalyze(params) {
1229
+ const { text } = params;
1230
+ if (!text || text.trim() === "") {
1231
+ throw new Error("\uBD84\uC11D\uD560 \uD14D\uC2A4\uD2B8(text)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
1232
+ }
1233
+ const repeatability = calculateRepeatability2(text);
1234
+ const generality = calculateGenerality2(text);
1235
+ const independence = calculateIndependence2(text);
1236
+ const clarity = calculateClarity2(text);
1237
+ const scores = {
1238
+ repeatability: repeatability.score,
1239
+ generality: generality.score,
1240
+ independence: independence.score,
1241
+ clarity: clarity.score,
1242
+ total: repeatability.score + generality.score + independence.score + clarity.score
1243
+ };
1244
+ return {
1245
+ text,
1246
+ scores,
1247
+ feedback: {
1248
+ repeatability: repeatability.feedback,
1249
+ generality: generality.feedback,
1250
+ independence: independence.feedback,
1251
+ clarity: clarity.feedback
1252
+ },
1253
+ suggestedCategory: suggestCategory2(text),
1254
+ isCandidate: scores.total >= 70,
1255
+ sections: extractSections(text),
1256
+ projectSpecificValues: generality.projectValues
1257
+ };
1258
+ }
1259
+ function formatAnalyzeResult(result) {
1260
+ const lines = [];
1261
+ lines.push("## \uD504\uB86C\uD504\uD2B8 \uBD84\uC11D");
1262
+ lines.push("");
1263
+ lines.push(`**\uCD1D\uC810**: ${result.scores.total}/100`);
1264
+ lines.push(
1265
+ `**\uC7AC\uC0AC\uC6A9 \uC801\uD569**: ${result.isCandidate ? "\u2705 \uC608 (70\uC810 \uC774\uC0C1)" : "\u274C \uC544\uB2C8\uC624"}`
1266
+ );
1267
+ lines.push(`**\uCD94\uCC9C \uCE74\uD14C\uACE0\uB9AC**: ${result.suggestedCategory}`);
1268
+ lines.push("");
1269
+ lines.push("### \uC810\uC218 \uC0C1\uC138");
1270
+ lines.push("");
1271
+ lines.push("| \uD56D\uBAA9 | \uC810\uC218 | \uD53C\uB4DC\uBC31 |");
1272
+ lines.push("|------|------|--------|");
1273
+ lines.push(
1274
+ `| \uBC18\uBCF5\uC131 | ${result.scores.repeatability}/25 | ${result.feedback.repeatability} |`
1275
+ );
1276
+ lines.push(
1277
+ `| \uBC94\uC6A9\uC131 | ${result.scores.generality}/30 | ${result.feedback.generality} |`
1278
+ );
1279
+ lines.push(
1280
+ `| \uB3C5\uB9BD\uC131 | ${result.scores.independence}/25 | ${result.feedback.independence} |`
1281
+ );
1282
+ lines.push(
1283
+ `| \uBA85\uD655\uC131 | ${result.scores.clarity}/20 | ${result.feedback.clarity} |`
1284
+ );
1285
+ lines.push("");
1286
+ if (result.projectSpecificValues.length > 0) {
1287
+ lines.push("### \uD504\uB85C\uC81D\uD2B8 \uC885\uC18D \uAC12 (\uBCC0\uC218\uD654 \uB300\uC0C1)");
1288
+ lines.push("");
1289
+ lines.push("| \uC6D0\uBCF8 \uAC12 | \uC720\uD615 | \uC81C\uC548 \uBCC0\uC218 |");
1290
+ lines.push("|---------|------|-----------|");
1291
+ for (const v of result.projectSpecificValues) {
1292
+ lines.push(`| \`${v.value}\` | ${v.type} | \`${v.suggestedVariable}\` |`);
1293
+ }
1294
+ lines.push("");
1295
+ }
1296
+ if (Object.keys(result.sections).length > 0) {
1297
+ lines.push("### \uCD94\uCD9C\uB41C \uC139\uC158");
1298
+ lines.push("");
1299
+ if (result.sections.goal) {
1300
+ lines.push("- \u2705 \uBAA9\uD45C \uC139\uC158");
1301
+ }
1302
+ if (result.sections.steps) {
1303
+ lines.push(`- \u2705 \uB2E8\uACC4 (${result.sections.steps.length}\uAC1C)`);
1304
+ }
1305
+ if (result.sections.output) {
1306
+ lines.push("- \u2705 \uCD9C\uB825 \uD615\uC2DD");
1307
+ }
1308
+ if (result.sections.validation) {
1309
+ lines.push(`- \u2705 \uAC80\uC99D \uD56D\uBAA9 (${result.sections.validation.length}\uAC1C)`);
1310
+ }
1311
+ lines.push("");
1312
+ }
1313
+ const suggestions = [];
1314
+ if (result.scores.repeatability < 15) {
1315
+ suggestions.push("\uAD6C\uC870\uD654\uB41C \uC139\uC158 (\uBAA9\uD45C, \uB2E8\uACC4, \uCD9C\uB825) \uCD94\uAC00 \uAD8C\uC7A5");
1316
+ }
1317
+ if (result.scores.generality < 20) {
1318
+ suggestions.push("\uD504\uB85C\uC81D\uD2B8 \uD2B9\uC815 \uAC12\uC744 \uBCC0\uC218\uB85C \uB300\uCCB4 \uD544\uC694");
1319
+ }
1320
+ if (result.scores.independence < 15) {
1321
+ suggestions.push("\uCEE8\uD14D\uC2A4\uD2B8 \uC758\uC874 \uD45C\uD604 \uC81C\uAC70 \uD544\uC694");
1322
+ }
1323
+ if (result.scores.clarity < 12) {
1324
+ suggestions.push("\uCD9C\uB825 \uD615\uC2DD \uBC0F \uAC80\uC99D \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uCD94\uAC00 \uAD8C\uC7A5");
1325
+ }
1326
+ if (suggestions.length > 0) {
1327
+ lines.push("### \uAC1C\uC120 \uC81C\uC548");
1328
+ lines.push("");
1329
+ for (const s of suggestions) {
1330
+ lines.push(`- ${s}`);
1331
+ }
1332
+ lines.push("");
1333
+ }
1334
+ return lines.join("\n");
1335
+ }
1336
+
1337
+ // src/tools/prompt/refine.ts
1338
+ var VARIABLE_PATTERNS = [
1339
+ {
1340
+ pattern: /(?:src|packages|apps|lib|components)\/[\w\-\/]+\.(?:ts|tsx|js|jsx|md)/gi,
1341
+ variable: "{{FILE_PATH}}",
1342
+ description: "\uD30C\uC77C \uACBD\uB85C",
1343
+ type: "path"
1344
+ },
1345
+ {
1346
+ pattern: /\b\d+\.\d+\.\d+(?:-[\w.]+)?\b/g,
1347
+ variable: "{{VERSION}}",
1348
+ description: "\uBC84\uC804 \uBC88\uD638",
1349
+ type: "version"
1350
+ },
1351
+ {
1352
+ pattern: /\b\d{4}-\d{2}-\d{2}\b/g,
1353
+ variable: "{{DATE}}",
1354
+ description: "\uB0A0\uC9DC",
1355
+ type: "date"
1356
+ },
1357
+ {
1358
+ pattern: /@[\w-]+\/[\w-]+/g,
1359
+ variable: "{{PACKAGE_NAME}}",
1360
+ description: "\uD328\uD0A4\uC9C0\uBA85",
1361
+ type: "package"
1362
+ }
1363
+ ];
1364
+ var COMPONENT_PATTERN = /\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g;
1365
+ var COMMON_WORDS = [
1366
+ "README",
1367
+ "TypeScript",
1368
+ "JavaScript",
1369
+ "React",
1370
+ "Next",
1371
+ "Node",
1372
+ "Tailwind",
1373
+ "WCAG",
1374
+ "HTML",
1375
+ "CSS",
1376
+ "JSON",
1377
+ "YAML",
1378
+ "API",
1379
+ "REST",
1380
+ "GraphQL"
1381
+ ];
1382
+ var CATEGORY_INFERENCE = {
1383
+ \uACC4\uD68D: "planning",
1384
+ \uC124\uACC4: "planning",
1385
+ \uC804\uB7B5: "planning",
1386
+ \uC694\uAD6C\uC0AC\uD56D: "planning",
1387
+ \uB9AC\uBDF0: "quality",
1388
+ \uC810\uAC80: "quality",
1389
+ \uD14C\uC2A4\uD2B8: "quality",
1390
+ \uAC80\uC99D: "quality",
1391
+ \uBB38\uC11C: "documentation",
1392
+ \uAC00\uC774\uB4DC: "documentation",
1393
+ \uB9E4\uB274\uC5BC: "documentation",
1394
+ \uC6CC\uD06C\uD50C\uB85C\uC6B0: "workflow",
1395
+ \uD504\uB85C\uC138\uC2A4: "workflow",
1396
+ \uC790\uB3D9\uD654: "workflow"
1397
+ };
1398
+ function generatePromptName(text) {
1399
+ const headerMatch = text.match(/^#\s+(.+)$/m);
1400
+ if (headerMatch) {
1401
+ return headerMatch[1].toLowerCase().replace(/[^a-z가-힣0-9\s]/g, "").trim().replace(/\s+/g, "-").slice(0, 30);
1402
+ }
1403
+ for (const keyword of Object.keys(CATEGORY_INFERENCE)) {
1404
+ if (text.includes(keyword)) {
1405
+ return `${keyword}-prompt`.replace(/\s+/g, "-");
1406
+ }
1407
+ }
1408
+ return "custom-prompt";
1409
+ }
1410
+ function inferCategory(text, providedCategory) {
1411
+ if (providedCategory) return providedCategory;
1412
+ for (const [keyword, category] of Object.entries(CATEGORY_INFERENCE)) {
1413
+ if (text.includes(keyword)) {
1414
+ return category;
1415
+ }
1416
+ }
1417
+ return "workflow";
1418
+ }
1419
+ function extractAndReplaceVariables(text) {
1420
+ const variables = [];
1421
+ const transformations = [];
1422
+ const seen = /* @__PURE__ */ new Map();
1423
+ let refinedText = text;
1424
+ for (const { pattern, variable, description, type } of VARIABLE_PATTERNS) {
1425
+ const matches = refinedText.match(pattern);
1426
+ if (matches) {
1427
+ for (const match of matches) {
1428
+ if (!seen.has(match)) {
1429
+ seen.set(match, variable);
1430
+ const existingVars = variables.filter((v) => v.name.startsWith(variable.replace("}}", "")));
1431
+ const varName = existingVars.length > 0 ? variable.replace("}}", `_${existingVars.length + 1}}`) : variable;
1432
+ variables.push({
1433
+ name: varName,
1434
+ description,
1435
+ originalValue: match,
1436
+ example: match,
1437
+ required: true
1438
+ });
1439
+ transformations.push({
1440
+ original: match,
1441
+ replacement: varName,
1442
+ type
1443
+ });
1444
+ refinedText = refinedText.split(match).join(varName);
1445
+ }
1446
+ }
1447
+ }
1448
+ }
1449
+ const componentMatches = refinedText.match(COMPONENT_PATTERN);
1450
+ if (componentMatches) {
1451
+ const uniqueComponents = [...new Set(componentMatches)].filter(
1452
+ (c) => !COMMON_WORDS.includes(c)
1453
+ );
1454
+ for (const comp of uniqueComponents) {
1455
+ if (!seen.has(comp)) {
1456
+ const existingVars = variables.filter(
1457
+ (v) => v.name.startsWith("{{COMPONENT_NAME")
1458
+ );
1459
+ const varName = existingVars.length > 0 ? `{{COMPONENT_NAME_${existingVars.length + 1}}}` : "{{COMPONENT_NAME}}";
1460
+ seen.set(comp, varName);
1461
+ variables.push({
1462
+ name: varName,
1463
+ description: "\uCEF4\uD3EC\uB10C\uD2B8/\uAE30\uB2A5\uBA85",
1464
+ originalValue: comp,
1465
+ example: comp,
1466
+ required: true
1467
+ });
1468
+ transformations.push({
1469
+ original: comp,
1470
+ replacement: varName,
1471
+ type: "component"
1472
+ });
1473
+ refinedText = refinedText.split(comp).join(varName);
1474
+ }
1475
+ }
1476
+ }
1477
+ return { refinedText, variables, transformations };
1478
+ }
1479
+ function generateFrontmatter(name, category, description, variables) {
1480
+ const lines = [];
1481
+ lines.push("---");
1482
+ lines.push(`name: ${name}`);
1483
+ lines.push(`category: ${category}`);
1484
+ lines.push(`description: ${description}`);
1485
+ if (variables.length > 0) {
1486
+ lines.push("variables:");
1487
+ for (const v of variables) {
1488
+ lines.push(` - name: "${v.name}"`);
1489
+ lines.push(` description: ${v.description}`);
1490
+ if (v.example) {
1491
+ lines.push(` example: ${v.example}`);
1492
+ }
1493
+ if (v.required !== void 0) {
1494
+ lines.push(` required: ${v.required}`);
1495
+ }
1496
+ }
1497
+ }
1498
+ lines.push("---");
1499
+ return lines.join("\n");
1500
+ }
1501
+ function extractDescription(text) {
1502
+ const firstParagraph = text.match(/^[^#\n].+$/m);
1503
+ if (firstParagraph) {
1504
+ return firstParagraph[0].slice(0, 100).trim();
1505
+ }
1506
+ const goalMatch = text.match(/##?\s*목표[\s\S]*?(?=##|$)/i);
1507
+ if (goalMatch) {
1508
+ const goalText = goalMatch[0].replace(/##?\s*목표\s*/i, "").trim();
1509
+ return goalText.slice(0, 100);
1510
+ }
1511
+ return "\uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB86C\uD504\uD2B8";
1512
+ }
1513
+ function handleRefine(params) {
1514
+ const { text, name: providedName, category: providedCategory } = params;
1515
+ if (!text || text.trim() === "") {
1516
+ throw new Error("\uC815\uC81C\uD560 \uD14D\uC2A4\uD2B8(text)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
1517
+ }
1518
+ const { refinedText, variables, transformations } = extractAndReplaceVariables(text);
1519
+ const name = providedName || generatePromptName(text);
1520
+ const category = inferCategory(text, providedCategory);
1521
+ const description = extractDescription(text);
1522
+ const frontmatter = generateFrontmatter(name, category, description, variables);
1523
+ const finalText = `${frontmatter}
1524
+
1525
+ ${refinedText.trim()}`;
1526
+ return {
1527
+ refinedText: finalText,
1528
+ frontmatter,
1529
+ variables,
1530
+ transformations,
1531
+ metadata: {
1532
+ name,
1533
+ category,
1534
+ description,
1535
+ variables
1536
+ }
1537
+ };
1538
+ }
1539
+ function formatRefineResult(result) {
1540
+ const lines = [];
1541
+ lines.push("## \uC815\uC81C \uACB0\uACFC");
1542
+ lines.push("");
1543
+ lines.push(`**\uC774\uB984**: \`${result.metadata.name}\``);
1544
+ lines.push(`**\uCE74\uD14C\uACE0\uB9AC**: \`${result.metadata.category}\``);
1545
+ lines.push(`**\uC124\uBA85**: ${result.metadata.description}`);
1546
+ lines.push("");
1547
+ if (result.transformations.length > 0) {
1548
+ lines.push("### \uBCC0\uD658 \uB0B4\uC5ED");
1549
+ lines.push("");
1550
+ lines.push("| \uC6D0\uBCF8 | \uBCC0\uC218 | \uC720\uD615 |");
1551
+ lines.push("|------|------|------|");
1552
+ for (const t of result.transformations) {
1553
+ lines.push(`| \`${t.original}\` | \`${t.replacement}\` | ${t.type} |`);
1554
+ }
1555
+ lines.push("");
1556
+ }
1557
+ if (result.variables.length > 0) {
1558
+ lines.push("### \uBCC0\uC218 \uBAA9\uB85D");
1559
+ lines.push("");
1560
+ lines.push("| \uBCC0\uC218 | \uC124\uBA85 | \uC608\uC2DC |");
1561
+ lines.push("|------|------|------|");
1562
+ for (const v of result.variables) {
1563
+ lines.push(`| \`${v.name}\` | ${v.description} | ${v.example || "-"} |`);
1564
+ }
1565
+ lines.push("");
1566
+ }
1567
+ lines.push("### \uC815\uC81C\uB41C \uD504\uB86C\uD504\uD2B8 \uBBF8\uB9AC\uBCF4\uAE30");
1568
+ lines.push("");
1569
+ lines.push("```markdown");
1570
+ lines.push(result.refinedText.slice(0, 1e3));
1571
+ if (result.refinedText.length > 1e3) {
1572
+ lines.push("...(truncated)");
1573
+ }
1574
+ lines.push("```");
1575
+ lines.push("");
1576
+ lines.push("---");
1577
+ lines.push(`\uC800\uC7A5\uD558\uB824\uBA74: \`/ax-prompt save --name=${result.metadata.name} --category=${result.metadata.category}\``);
1578
+ return lines.join("\n");
1579
+ }
1580
+
1581
+ // src/tools/prompt/validate.ts
1582
+ import * as fs from "fs";
1583
+ import * as path from "path";
1584
+ var INDEX_PATH = ".claude/prompts/_registry/prompt-index.json";
1585
+ function parseFrontmatter(text) {
1586
+ const match = text.match(/^---\n([\s\S]*?)\n---/);
1587
+ if (!match) {
1588
+ return { valid: false, error: "\uD504\uB860\uD2B8\uB9E4\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4" };
1589
+ }
1590
+ try {
1591
+ const yaml = match[1];
1592
+ const data = {};
1593
+ const lines = yaml.split("\n");
1594
+ let currentKey = "";
1595
+ let currentArray = null;
1596
+ for (const line of lines) {
1597
+ if (line.trim().startsWith("- ") && currentArray) {
1598
+ currentArray.push(line.trim().substring(2));
1599
+ continue;
1600
+ }
1601
+ const kvMatch = line.match(/^(\w+):\s*(.*)$/);
1602
+ if (kvMatch) {
1603
+ if (currentArray && currentKey) {
1604
+ data[currentKey] = currentArray;
1605
+ }
1606
+ currentKey = kvMatch[1];
1607
+ const value = kvMatch[2].trim();
1608
+ if (value === "") {
1609
+ currentArray = [];
1610
+ } else {
1611
+ data[currentKey] = value;
1612
+ currentArray = null;
1613
+ }
1614
+ }
1615
+ }
1616
+ if (currentArray && currentKey) {
1617
+ data[currentKey] = currentArray;
1618
+ }
1619
+ if (!data.name) {
1620
+ return { valid: false, error: "name \uD544\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4" };
1621
+ }
1622
+ if (!data.category) {
1623
+ return { valid: false, error: "category \uD544\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4" };
1624
+ }
1625
+ const validCategories = ["planning", "quality", "documentation", "workflow"];
1626
+ if (!validCategories.includes(data.category)) {
1627
+ return {
1628
+ valid: false,
1629
+ error: `\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 category: ${data.category}. \uD5C8\uC6A9\uAC12: ${validCategories.join(", ")}`
1630
+ };
1631
+ }
1632
+ return { valid: true, data };
1633
+ } catch (error) {
1634
+ return {
1635
+ valid: false,
1636
+ error: `YAML \uD30C\uC2F1 \uC624\uB958: ${error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`
1637
+ };
1638
+ }
1639
+ }
1640
+ function checkRequiredSections(text) {
1641
+ const details = [];
1642
+ const goalKeywords = ["\uBAA9\uD45C", "\uBAA9\uC801", "\uAC1C\uC694"];
1643
+ const hasGoal = goalKeywords.some(
1644
+ (k) => text.includes(`## ${k}`) || text.includes(`### ${k}`)
1645
+ );
1646
+ if (!hasGoal) {
1647
+ details.push("\uBAA9\uD45C/\uAC1C\uC694 \uC139\uC158\uC774 \uAD8C\uC7A5\uB429\uB2C8\uB2E4");
1648
+ }
1649
+ const stepKeywords = ["\uB2E8\uACC4", "\uC808\uCC28", "\uC218\uD589"];
1650
+ const hasSteps = stepKeywords.some(
1651
+ (k) => text.includes(`## ${k}`) || text.includes(`### ${k}`)
1652
+ ) || /^\d+\.\s/m.test(text);
1653
+ if (!hasSteps) {
1654
+ details.push("\uC218\uD589 \uB2E8\uACC4 \uC139\uC158\uC774 \uAD8C\uC7A5\uB429\uB2C8\uB2E4");
1655
+ }
1656
+ return {
1657
+ hasGoal,
1658
+ hasSteps,
1659
+ details
1660
+ };
1661
+ }
1662
+ function checkHardcodedPaths(text) {
1663
+ const hardcodedPatterns = [
1664
+ // 절대 경로 (변수화되지 않은)
1665
+ /(?<!{{)(?:src|packages|apps|lib|components)\/[\w\-\/]+\.(?:ts|tsx|js|jsx|md)(?!}})/gi,
1666
+ // 특정 프로젝트 경로
1667
+ /D:\\|C:\\|\/Users\/|\/home\//gi
1668
+ ];
1669
+ const found = [];
1670
+ for (const pattern of hardcodedPatterns) {
1671
+ const matches = text.match(pattern);
1672
+ if (matches) {
1673
+ found.push(...matches);
1674
+ }
1675
+ }
1676
+ return {
1677
+ clean: found.length === 0,
1678
+ found
1679
+ };
1680
+ }
1681
+ function checkVariablesDocumented(text) {
1682
+ const usedVariables = text.match(/{{[A-Z_]+}}/g) || [];
1683
+ const uniqueVariables = [...new Set(usedVariables)];
1684
+ const frontmatterMatch = text.match(/^---\n([\s\S]*?)\n---/);
1685
+ const frontmatter = frontmatterMatch ? frontmatterMatch[1] : "";
1686
+ const undocumented = [];
1687
+ for (const variable of uniqueVariables) {
1688
+ if (!frontmatter.includes(variable)) {
1689
+ undocumented.push(variable);
1690
+ }
1691
+ }
1692
+ return {
1693
+ documented: undocumented.length === 0,
1694
+ undocumented
1695
+ };
1696
+ }
1697
+ function checkDuplicate(text, _basePath) {
1698
+ const similar = [];
1699
+ try {
1700
+ const indexPath = path.resolve(process.cwd(), INDEX_PATH);
1701
+ if (!fs.existsSync(indexPath)) {
1702
+ return { noDuplicate: true, similar };
1703
+ }
1704
+ const indexContent = fs.readFileSync(indexPath, "utf-8");
1705
+ const index = JSON.parse(indexContent);
1706
+ const textKeywords = extractKeywords(text);
1707
+ for (const prompt of index.prompts) {
1708
+ const promptPath = path.resolve(process.cwd(), prompt.path);
1709
+ if (!fs.existsSync(promptPath)) continue;
1710
+ const promptContent = fs.readFileSync(promptPath, "utf-8");
1711
+ const promptKeywords = extractKeywords(promptContent);
1712
+ const similarity = calculateSimilarity(textKeywords, promptKeywords);
1713
+ if (similarity >= 0.6) {
1714
+ similar.push({
1715
+ id: prompt.id,
1716
+ name: prompt.name,
1717
+ similarity: Math.round(similarity * 100)
1718
+ });
1719
+ }
1720
+ }
1721
+ return {
1722
+ noDuplicate: !similar.some((s) => s.similarity >= 80),
1723
+ similar: similar.sort((a, b) => b.similarity - a.similarity)
1724
+ };
1725
+ } catch {
1726
+ return { noDuplicate: true, similar };
1727
+ }
1728
+ }
1729
+ function extractKeywords(text) {
1730
+ const words = text.toLowerCase().replace(/[^a-z가-힣\s]/g, " ").split(/\s+/).filter((w) => w.length > 2);
1731
+ return new Set(words);
1732
+ }
1733
+ function calculateSimilarity(set1, set2) {
1734
+ const intersection = new Set([...set1].filter((x) => set2.has(x)));
1735
+ const union = /* @__PURE__ */ new Set([...set1, ...set2]);
1736
+ return intersection.size / union.size;
1737
+ }
1738
+ function handleValidate(params) {
1739
+ const { promptText, checkDuplicate: enableDuplicateCheck = true } = params;
1740
+ if (!promptText || promptText.trim() === "") {
1741
+ throw new Error("\uAC80\uC99D\uD560 \uD504\uB86C\uD504\uD2B8(promptText)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
1742
+ }
1743
+ const errors = [];
1744
+ const frontmatterResult = parseFrontmatter(promptText);
1745
+ if (!frontmatterResult.valid) {
1746
+ errors.push({
1747
+ code: "INVALID_FRONTMATTER",
1748
+ message: frontmatterResult.error || "\uD504\uB860\uD2B8\uB9E4\uD130 \uC624\uB958",
1749
+ severity: "error"
1750
+ });
1751
+ }
1752
+ const sectionResult = checkRequiredSections(promptText);
1753
+ for (const detail of sectionResult.details) {
1754
+ errors.push({
1755
+ code: "MISSING_SECTION",
1756
+ message: detail,
1757
+ severity: "warning"
1758
+ });
1759
+ }
1760
+ const pathResult = checkHardcodedPaths(promptText);
1761
+ if (!pathResult.clean) {
1762
+ errors.push({
1763
+ code: "HARDCODED_PATH",
1764
+ message: `\uD558\uB4DC\uCF54\uB529\uB41C \uACBD\uB85C \uBC1C\uACAC: ${pathResult.found.slice(0, 3).join(", ")}${pathResult.found.length > 3 ? "..." : ""}`,
1765
+ severity: "error"
1766
+ });
1767
+ }
1768
+ const varResult = checkVariablesDocumented(promptText);
1769
+ if (!varResult.documented) {
1770
+ errors.push({
1771
+ code: "UNDOCUMENTED_VARIABLE",
1772
+ message: `\uBB38\uC11C\uD654\uB418\uC9C0 \uC54A\uC740 \uBCC0\uC218: ${varResult.undocumented.join(", ")}`,
1773
+ severity: "warning"
1774
+ });
1775
+ }
1776
+ let duplicateResult = { noDuplicate: true, similar: void 0 };
1777
+ if (enableDuplicateCheck) {
1778
+ duplicateResult = checkDuplicate(promptText);
1779
+ if (!duplicateResult.noDuplicate) {
1780
+ const mostSimilar = duplicateResult.similar?.[0];
1781
+ errors.push({
1782
+ code: "DUPLICATE_PROMPT",
1783
+ message: `\uC720\uC0AC \uD504\uB86C\uD504\uD2B8 \uC874\uC7AC: ${mostSimilar?.name} (${mostSimilar?.similarity}% \uC720\uC0AC)`,
1784
+ severity: "error"
1785
+ });
1786
+ }
1787
+ }
1788
+ const hasErrors = errors.some((e) => e.severity === "error");
1789
+ return {
1790
+ isValid: !hasErrors,
1791
+ errors,
1792
+ checklist: {
1793
+ hasRequiredSections: sectionResult.hasGoal && sectionResult.hasSteps,
1794
+ noHardcodedPaths: pathResult.clean,
1795
+ variablesDocumented: varResult.documented,
1796
+ validFrontmatter: frontmatterResult.valid,
1797
+ noDuplicate: duplicateResult.noDuplicate
1798
+ },
1799
+ similarPrompts: duplicateResult.similar
1800
+ };
1801
+ }
1802
+ function formatValidateResult(result) {
1803
+ const lines = [];
1804
+ lines.push("## \uAC80\uC99D \uACB0\uACFC");
1805
+ lines.push("");
1806
+ lines.push(`**\uC720\uD6A8\uC131**: ${result.isValid ? "\u2705 \uD1B5\uACFC" : "\u274C \uC2E4\uD328"}`);
1807
+ lines.push("");
1808
+ lines.push("### \uCCB4\uD06C\uB9AC\uC2A4\uD2B8");
1809
+ lines.push("");
1810
+ lines.push(
1811
+ `- [${result.checklist.validFrontmatter ? "x" : " "}] YAML \uD504\uB860\uD2B8\uB9E4\uD130 \uC720\uD6A8`
1812
+ );
1813
+ lines.push(
1814
+ `- [${result.checklist.hasRequiredSections ? "x" : " "}] \uD544\uC218 \uC139\uC158 \uC874\uC7AC (\uBAA9\uD45C, \uB2E8\uACC4)`
1815
+ );
1816
+ lines.push(
1817
+ `- [${result.checklist.noHardcodedPaths ? "x" : " "}] \uD558\uB4DC\uCF54\uB529 \uACBD\uB85C \uC5C6\uC74C`
1818
+ );
1819
+ lines.push(
1820
+ `- [${result.checklist.variablesDocumented ? "x" : " "}] \uBCC0\uC218 \uBB38\uC11C\uD654 \uC644\uB8CC`
1821
+ );
1822
+ lines.push(
1823
+ `- [${result.checklist.noDuplicate ? "x" : " "}] \uC911\uBCF5 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C`
1824
+ );
1825
+ lines.push("");
1826
+ if (result.errors.length > 0) {
1827
+ lines.push("### \uBC1C\uACAC\uB41C \uBB38\uC81C");
1828
+ lines.push("");
1829
+ const errorItems = result.errors.filter((e) => e.severity === "error");
1830
+ const warningItems = result.errors.filter((e) => e.severity === "warning");
1831
+ if (errorItems.length > 0) {
1832
+ lines.push("**\uC624\uB958** (\uC218\uC815 \uD544\uC218):");
1833
+ for (const e of errorItems) {
1834
+ lines.push(`- \u274C [${e.code}] ${e.message}`);
1835
+ }
1836
+ lines.push("");
1837
+ }
1838
+ if (warningItems.length > 0) {
1839
+ lines.push("**\uACBD\uACE0** (\uAD8C\uC7A5 \uC218\uC815):");
1840
+ for (const w of warningItems) {
1841
+ lines.push(`- \u26A0\uFE0F [${w.code}] ${w.message}`);
1842
+ }
1843
+ lines.push("");
1844
+ }
1845
+ }
1846
+ if (result.similarPrompts && result.similarPrompts.length > 0) {
1847
+ lines.push("### \uC720\uC0AC \uD504\uB86C\uD504\uD2B8");
1848
+ lines.push("");
1849
+ lines.push("| \uD504\uB86C\uD504\uD2B8 | \uC720\uC0AC\uB3C4 |");
1850
+ lines.push("|----------|--------|");
1851
+ for (const s of result.similarPrompts.slice(0, 3)) {
1852
+ lines.push(`| ${s.name} | ${s.similarity}% |`);
1853
+ }
1854
+ lines.push("");
1855
+ lines.push("*80% \uC774\uC0C1 \uC720\uC0AC \uC2DC \uC911\uBCF5\uC73C\uB85C \uAC04\uC8FC\uB429\uB2C8\uB2E4.*");
1856
+ lines.push("");
1857
+ }
1858
+ if (result.isValid) {
1859
+ lines.push("---");
1860
+ lines.push("\uAC80\uC99D \uD1B5\uACFC! `/ax-prompt save`\uB85C \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
1861
+ } else {
1862
+ lines.push("---");
1863
+ lines.push("\uC624\uB958\uB97C \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uAC80\uC99D\uD558\uC138\uC694.");
1864
+ }
1865
+ return lines.join("\n");
1866
+ }
1867
+
1868
+ // src/tools/prompt/save.ts
1869
+ import * as fs2 from "fs";
1870
+ import * as path2 from "path";
1871
+ var PROMPTS_BASE_PATH = ".claude/prompts";
1872
+ var INDEX_PATH2 = ".claude/prompts/_registry/prompt-index.json";
1873
+ function extractMetadata(promptText) {
1874
+ const match = promptText.match(/^---\n([\s\S]*?)\n---/);
1875
+ if (!match) return null;
1876
+ const yaml = match[1];
1877
+ const lines = yaml.split("\n");
1878
+ let name = "";
1879
+ let category = "workflow";
1880
+ let description = "";
1881
+ const variables = [];
1882
+ let inVariables = false;
1883
+ for (const line of lines) {
1884
+ if (line.startsWith("name:")) {
1885
+ name = line.replace("name:", "").trim();
1886
+ } else if (line.startsWith("category:")) {
1887
+ category = line.replace("category:", "").trim();
1888
+ } else if (line.startsWith("description:")) {
1889
+ description = line.replace("description:", "").trim();
1890
+ } else if (line.startsWith("variables:")) {
1891
+ inVariables = true;
1892
+ } else if (inVariables && line.includes("name:")) {
1893
+ const varMatch = line.match(/name:\s*"?({{[A-Z_]+}})"?/);
1894
+ if (varMatch) {
1895
+ variables.push(varMatch[1]);
1896
+ }
1897
+ }
1898
+ }
1899
+ if (!name || !category) return null;
1900
+ return { name, category, description, variables };
1901
+ }
1902
+ function loadIndex(basePath) {
1903
+ const indexPath = path2.join(basePath, INDEX_PATH2);
1904
+ if (!fs2.existsSync(indexPath)) {
1905
+ return {
1906
+ version: "1.0.0",
1907
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1908
+ categories: {
1909
+ planning: { description: "\uACC4\uD68D \uC218\uB9BD", count: 0 },
1910
+ quality: { description: "\uD488\uC9C8 \uAD00\uB9AC", count: 0 },
1911
+ documentation: { description: "\uBB38\uC11C\uD654", count: 0 },
1912
+ workflow: { description: "\uC6CC\uD06C\uD50C\uB85C\uC6B0", count: 0 }
1913
+ },
1914
+ prompts: [],
1915
+ stats: {
1916
+ totalPrompts: 0,
1917
+ totalUsage: 0,
1918
+ mostUsed: null,
1919
+ recentlyAdded: null
1920
+ }
1921
+ };
1922
+ }
1923
+ const content = fs2.readFileSync(indexPath, "utf-8");
1924
+ return JSON.parse(content);
1925
+ }
1926
+ function saveIndex(basePath, index) {
1927
+ const indexPath = path2.join(basePath, INDEX_PATH2);
1928
+ const indexDir = path2.dirname(indexPath);
1929
+ if (!fs2.existsSync(indexDir)) {
1930
+ fs2.mkdirSync(indexDir, { recursive: true });
1931
+ }
1932
+ fs2.writeFileSync(indexPath, JSON.stringify(index, null, 2), "utf-8");
1933
+ }
1934
+ function savePromptFile(basePath, category, name, content) {
1935
+ const promptDir = path2.join(basePath, PROMPTS_BASE_PATH, category);
1936
+ const promptPath = path2.join(promptDir, `${name}.md`);
1937
+ if (!fs2.existsSync(promptDir)) {
1938
+ fs2.mkdirSync(promptDir, { recursive: true });
1939
+ }
1940
+ fs2.writeFileSync(promptPath, content, "utf-8");
1941
+ return `${PROMPTS_BASE_PATH}/${category}/${name}.md`;
1942
+ }
1943
+ function updateIndex(index, entry, isNew) {
1944
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1945
+ const existingIndex = index.prompts.findIndex((p) => p.id === entry.id);
1946
+ if (existingIndex >= 0) {
1947
+ index.prompts[existingIndex] = {
1948
+ ...index.prompts[existingIndex],
1949
+ ...entry,
1950
+ updatedAt: now
1951
+ };
1952
+ } else {
1953
+ index.prompts.push({
1954
+ ...entry,
1955
+ createdAt: now,
1956
+ updatedAt: now,
1957
+ usageCount: 0
1958
+ });
1959
+ index.categories[entry.category].count++;
1960
+ }
1961
+ index.stats.totalPrompts = index.prompts.length;
1962
+ index.stats.recentlyAdded = entry.id;
1963
+ index.lastUpdated = now;
1964
+ }
1965
+ function checkDuplicateEntry(index, name, category) {
1966
+ const id = `${category}/${name}`;
1967
+ return index.prompts.some((p) => p.id === id);
1968
+ }
1969
+ function handleSave(params) {
1970
+ const { promptText, name, category, force = false } = params;
1971
+ if (!promptText || promptText.trim() === "") {
1972
+ throw new Error("\uC800\uC7A5\uD560 \uD504\uB86C\uD504\uD2B8(promptText)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
1973
+ }
1974
+ if (!name || name.trim() === "") {
1975
+ throw new Error("\uD504\uB86C\uD504\uD2B8 \uC774\uB984(name)\uC740 \uD544\uC218\uC785\uB2C8\uB2E4.");
1976
+ }
1977
+ if (!category) {
1978
+ throw new Error("\uCE74\uD14C\uACE0\uB9AC(category)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
1979
+ }
1980
+ const kebabCaseRegex = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1981
+ if (!kebabCaseRegex.test(name)) {
1982
+ return {
1983
+ success: false,
1984
+ error: `\uC774\uB984\uC740 kebab-case \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: ${name}`
1985
+ };
1986
+ }
1987
+ try {
1988
+ const basePath = process.cwd();
1989
+ const index = loadIndex(basePath);
1990
+ const isDuplicate = checkDuplicateEntry(index, name, category);
1991
+ if (isDuplicate && !force) {
1992
+ return {
1993
+ success: false,
1994
+ error: `\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD504\uB86C\uD504\uD2B8\uC785\uB2C8\uB2E4: ${category}/${name}. --force \uC635\uC158\uC73C\uB85C \uB36E\uC5B4\uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
1995
+ };
1996
+ }
1997
+ const metadata = extractMetadata(promptText);
1998
+ const description = metadata?.description || "\uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB86C\uD504\uD2B8";
1999
+ const variables = metadata?.variables || [];
2000
+ const relativePath = savePromptFile(basePath, category, name, promptText);
2001
+ const entryId = `${category}/${name}`;
2002
+ updateIndex(
2003
+ index,
2004
+ {
2005
+ id: entryId,
2006
+ name,
2007
+ category,
2008
+ description,
2009
+ path: relativePath,
2010
+ variables
2011
+ },
2012
+ !isDuplicate
2013
+ );
2014
+ saveIndex(basePath, index);
2015
+ return {
2016
+ success: true,
2017
+ path: relativePath,
2018
+ promptId: entryId,
2019
+ indexUpdated: true
2020
+ };
2021
+ } catch (error) {
2022
+ return {
2023
+ success: false,
2024
+ error: `\uC800\uC7A5 \uC2E4\uD328: ${error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`
2025
+ };
2026
+ }
2027
+ }
2028
+ function formatSaveResult(result) {
2029
+ const lines = [];
2030
+ if (result.success) {
2031
+ lines.push("## \uC800\uC7A5 \uC644\uB8CC \u2705");
2032
+ lines.push("");
2033
+ lines.push(`- **\uACBD\uB85C**: \`${result.path}\``);
2034
+ lines.push(`- **\uD504\uB86C\uD504\uD2B8 ID**: \`${result.promptId}\``);
2035
+ lines.push(`- **\uC778\uB371\uC2A4 \uAC31\uC2E0**: ${result.indexUpdated ? "\u2705" : "\u274C"}`);
2036
+ lines.push("");
2037
+ lines.push("### \uC0AC\uC6A9 \uBC29\uBC95");
2038
+ lines.push("");
2039
+ lines.push("```");
2040
+ lines.push(`\uC774 \uD504\uB86C\uD504\uD2B8\uB97C \uC0AC\uC6A9\uD558\uC5EC {{VARIABLE}}\uC5D0 \uB300\uD574...`);
2041
+ lines.push("```");
2042
+ lines.push("");
2043
+ lines.push("\uB610\uB294 \uC9C1\uC811 \uCC38\uC870:");
2044
+ lines.push("");
2045
+ lines.push("```");
2046
+ lines.push(`${result.path}`);
2047
+ lines.push("```");
2048
+ } else {
2049
+ lines.push("## \uC800\uC7A5 \uC2E4\uD328 \u274C");
2050
+ lines.push("");
2051
+ lines.push(`**\uC624\uB958**: ${result.error}`);
2052
+ lines.push("");
2053
+ lines.push("### \uD574\uACB0 \uBC29\uBC95");
2054
+ lines.push("");
2055
+ if (result.error?.includes("kebab-case")) {
2056
+ lines.push("- \uC774\uB984\uC744 kebab-case \uD615\uC2DD\uC73C\uB85C \uBCC0\uACBD\uD558\uC138\uC694 (\uC608: my-prompt)");
2057
+ } else if (result.error?.includes("\uC774\uBBF8 \uC874\uC7AC")) {
2058
+ lines.push("- \uB2E4\uB978 \uC774\uB984\uC744 \uC0AC\uC6A9\uD558\uAC70\uB098");
2059
+ lines.push("- `--force` \uC635\uC158\uC73C\uB85C \uB36E\uC5B4\uC4F0\uAE30: `/ax-prompt save --force`");
2060
+ } else {
2061
+ lines.push("- \uC624\uB958 \uBA54\uC2DC\uC9C0\uB97C \uD655\uC778\uD558\uACE0 \uC218\uC815\uD558\uC138\uC694");
2062
+ }
2063
+ }
2064
+ return lines.join("\n");
2065
+ }
2066
+
2067
+ // src/tools/template.ts
2068
+ import fetch from "node-fetch";
2069
+ import fs3 from "fs-extra";
2070
+ import path3 from "path";
2071
+ var TEMPLATE_REGISTRY_URL = process.env.AXIS_TEMPLATE_URL || "https://ds.minu.best/templates";
2072
+ async function fetchTemplateIndex() {
2073
+ const url = `${TEMPLATE_REGISTRY_URL}/index.json`;
2074
+ const res = await fetch(url);
2075
+ if (!res.ok) {
2076
+ throw new Error(`\uD15C\uD50C\uB9BF \uC778\uB371\uC2A4\uB97C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (${res.status}): ${url}`);
2077
+ }
2078
+ return await res.json();
2079
+ }
2080
+ async function fetchTemplateDetail(slug) {
2081
+ const url = `${TEMPLATE_REGISTRY_URL}/${slug}.json`;
2082
+ const res = await fetch(url);
2083
+ if (!res.ok) {
2084
+ throw new Error(`'${slug}' \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (${res.status}): ${url}`);
2085
+ }
2086
+ return await res.json();
2087
+ }
2088
+ async function handleListTemplates(params) {
2089
+ const index = await fetchTemplateIndex();
2090
+ let templates = index.templates;
2091
+ if (params.category) {
2092
+ templates = templates.filter((t) => t.category === params.category);
2093
+ }
2094
+ return {
2095
+ total: templates.length,
2096
+ templates: templates.map((t) => ({
2097
+ name: t.name,
2098
+ slug: t.slug,
2099
+ description: t.description,
2100
+ category: t.category
2101
+ }))
2102
+ };
2103
+ }
2104
+ async function handleGetTemplate(params) {
2105
+ const detail = await fetchTemplateDetail(params.name);
2106
+ return {
2107
+ name: detail.name,
2108
+ slug: detail.slug,
2109
+ description: detail.description,
2110
+ category: detail.category,
2111
+ version: detail.version,
2112
+ features: detail.features,
2113
+ dependencies: detail.dependencies || {},
2114
+ devDependencies: detail.devDependencies || {},
2115
+ files: detail.files.map((f) => f.path)
2116
+ };
2117
+ }
2118
+ async function handleApplyTemplate(params) {
2119
+ const detail = await fetchTemplateDetail(params.name);
2120
+ const baseDir = path3.resolve(params.targetDir);
2121
+ const files = [];
2122
+ for (const file of detail.files) {
2123
+ const dest = path3.join(baseDir, file.path);
2124
+ const exists = await fs3.pathExists(dest);
2125
+ if (!params.dryRun) {
2126
+ await fs3.ensureDir(path3.dirname(dest));
2127
+ await fs3.writeFile(dest, file.content);
2128
+ }
2129
+ files.push({
2130
+ path: file.path,
2131
+ action: exists ? "overwritten" : "created"
2132
+ });
2133
+ }
2134
+ if (!params.dryRun && detail.postInstall?.length) {
2135
+ for (const patch of detail.postInstall) {
2136
+ const target = path3.join(baseDir, patch.file);
2137
+ if (!await fs3.pathExists(target)) continue;
2138
+ if (patch.type === "replace" && patch.search && patch.value) {
2139
+ const content = await fs3.readFile(target, "utf-8");
2140
+ await fs3.writeFile(target, content.replace(patch.search, patch.value));
2141
+ } else if (patch.type === "append" && patch.value) {
2142
+ await fs3.appendFile(target, patch.value);
2143
+ } else if (patch.type === "prepend" && patch.value) {
2144
+ const content = await fs3.readFile(target, "utf-8");
2145
+ await fs3.writeFile(target, patch.value + content);
2146
+ } else if (patch.type === "json-merge" && patch.merge) {
2147
+ const content = await fs3.readJSON(target);
2148
+ await fs3.writeJSON(target, { ...content, ...patch.merge }, { spaces: 2 });
2149
+ }
2150
+ }
2151
+ }
2152
+ return {
2153
+ success: true,
2154
+ templateName: detail.name,
2155
+ files,
2156
+ dependencies: detail.dependencies || {},
2157
+ devDependencies: detail.devDependencies || {},
2158
+ dryRun: !!params.dryRun
2159
+ };
2160
+ }
2161
+ async function handleDiffTemplate(params) {
2162
+ const detail = await fetchTemplateDetail(params.name);
2163
+ const baseDir = path3.resolve(params.targetDir || ".");
2164
+ const entries = [];
2165
+ for (const file of detail.files) {
2166
+ const localPath = path3.join(baseDir, file.path);
2167
+ if (!await fs3.pathExists(localPath)) {
2168
+ entries.push({ path: file.path, status: "added" });
2169
+ continue;
2170
+ }
2171
+ const localContent = await fs3.readFile(localPath, "utf-8");
2172
+ if (localContent === file.content) {
2173
+ entries.push({ path: file.path, status: "unchanged" });
2174
+ } else {
2175
+ entries.push({
2176
+ path: file.path,
2177
+ status: "modified",
2178
+ localLines: localContent.split("\n").length,
2179
+ remoteLines: file.content.split("\n").length
2180
+ });
2181
+ }
2182
+ }
2183
+ const summary = {
2184
+ added: entries.filter((e) => e.status === "added").length,
2185
+ modified: entries.filter((e) => e.status === "modified").length,
2186
+ unchanged: entries.filter((e) => e.status === "unchanged").length,
2187
+ extra: entries.filter((e) => e.status === "extra").length
2188
+ };
2189
+ return { templateName: detail.name, entries, summary };
2190
+ }
2191
+ async function handleCheckProject(params) {
2192
+ const baseDir = path3.resolve(params.targetDir || ".");
2193
+ const results = [];
2194
+ const configExists = await fs3.pathExists(path3.join(baseDir, "axis.config.json"));
2195
+ results.push({
2196
+ name: "axis.config.json",
2197
+ severity: configExists ? "pass" : "fail",
2198
+ message: configExists ? "axis.config.json \uD30C\uC77C\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4" : "axis.config.json \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2192 'axis init'\uC744 \uC2E4\uD589\uD558\uC138\uC694"
2199
+ });
2200
+ const twTs = path3.join(baseDir, "tailwind.config.ts");
2201
+ const twJs = path3.join(baseDir, "tailwind.config.js");
2202
+ if (await fs3.pathExists(twTs)) {
2203
+ const content = await fs3.readFile(twTs, "utf-8");
2204
+ const hasTokens = content.includes("--background") || content.includes("hsl(var(");
2205
+ results.push({
2206
+ name: "tailwind.config",
2207
+ severity: hasTokens ? "pass" : "warn",
2208
+ message: hasTokens ? "tailwind.config.ts\uC5D0 AXIS \uD1A0\uD070\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4" : "tailwind.config.ts\uC5D0 AXIS \uD1A0\uD070 \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
2209
+ });
2210
+ } else if (await fs3.pathExists(twJs)) {
2211
+ results.push({
2212
+ name: "tailwind.config",
2213
+ severity: "warn",
2214
+ message: "tailwind.config.js \uC874\uC7AC (TypeScript \uAD8C\uC7A5)"
2215
+ });
2216
+ } else {
2217
+ results.push({
2218
+ name: "tailwind.config",
2219
+ severity: "fail",
2220
+ message: "tailwind.config \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
2221
+ });
2222
+ }
2223
+ const cssCandidates = [
2224
+ "globals.css",
2225
+ "app/globals.css",
2226
+ "src/app/globals.css",
2227
+ "src/globals.css",
2228
+ "styles/globals.css"
2229
+ ];
2230
+ let cssFound = false;
2231
+ for (const candidate of cssCandidates) {
2232
+ const fullPath = path3.join(baseDir, candidate);
2233
+ if (await fs3.pathExists(fullPath)) {
2234
+ const content = await fs3.readFile(fullPath, "utf-8");
2235
+ const hasTokens = content.includes("--background") && content.includes("--primary");
2236
+ results.push({
2237
+ name: "globals.css",
2238
+ severity: hasTokens ? "pass" : "warn",
2239
+ message: hasTokens ? `${candidate}\uC5D0 AXIS CSS \uBCC0\uC218\uAC00 \uC815\uC758\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4` : `${candidate}\uC5D0 AXIS CSS \uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4`
2240
+ });
2241
+ cssFound = true;
2242
+ break;
2243
+ }
2244
+ }
2245
+ if (!cssFound) {
2246
+ results.push({
2247
+ name: "globals.css",
2248
+ severity: "fail",
2249
+ message: "globals.css \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
2250
+ });
2251
+ }
2252
+ const utilsCandidates = [
2253
+ "lib/utils.ts",
2254
+ "src/lib/utils.ts",
2255
+ "components/ui/utils.ts",
2256
+ "src/components/ui/utils.ts",
2257
+ "utils.ts"
2258
+ ];
2259
+ let utilsFound = false;
2260
+ for (const candidate of utilsCandidates) {
2261
+ const fullPath = path3.join(baseDir, candidate);
2262
+ if (await fs3.pathExists(fullPath)) {
2263
+ const content = await fs3.readFile(fullPath, "utf-8");
2264
+ const hasCn = content.includes("function cn");
2265
+ results.push({
2266
+ name: "utils.ts",
2267
+ severity: hasCn ? "pass" : "warn",
2268
+ message: hasCn ? `${candidate}\uC5D0 cn() \uD568\uC218\uAC00 \uC788\uC2B5\uB2C8\uB2E4` : `${candidate}\uC5D0 cn() \uD568\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4`
2269
+ });
2270
+ utilsFound = true;
2271
+ break;
2272
+ }
2273
+ }
2274
+ if (!utilsFound) {
2275
+ results.push({
2276
+ name: "utils.ts",
2277
+ severity: "warn",
2278
+ message: "utils.ts\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uCEF4\uD3EC\uB10C\uD2B8 \uC0AC\uC6A9 \uC2DC \uD544\uC694)"
2279
+ });
2280
+ }
2281
+ const pkgPath = path3.join(baseDir, "package.json");
2282
+ if (await fs3.pathExists(pkgPath)) {
2283
+ const pkg = await fs3.readJSON(pkgPath);
2284
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2285
+ const required = ["tailwindcss"];
2286
+ const recommended = ["clsx", "tailwind-merge"];
2287
+ const missingRequired = required.filter((d) => !allDeps[d]);
2288
+ const missingRecommended = recommended.filter((d) => !allDeps[d]);
2289
+ if (missingRequired.length > 0) {
2290
+ results.push({
2291
+ name: "dependencies",
2292
+ severity: "fail",
2293
+ message: `\uD544\uC218 \uC758\uC874\uC131 \uB204\uB77D: ${missingRequired.join(", ")}`
2294
+ });
2295
+ } else if (missingRecommended.length > 0) {
2296
+ results.push({
2297
+ name: "dependencies",
2298
+ severity: "warn",
2299
+ message: `\uAD8C\uC7A5 \uC758\uC874\uC131 \uB204\uB77D: ${missingRecommended.join(", ")}`
2300
+ });
2301
+ } else {
2302
+ results.push({
2303
+ name: "dependencies",
2304
+ severity: "pass",
2305
+ message: "\uD544\uC218 \uBC0F \uAD8C\uC7A5 \uC758\uC874\uC131\uC774 \uBAA8\uB450 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4"
2306
+ });
2307
+ }
2308
+ } else {
2309
+ results.push({
2310
+ name: "dependencies",
2311
+ severity: "fail",
2312
+ message: "package.json \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
2313
+ });
2314
+ }
2315
+ const summary = {
2316
+ pass: results.filter((r) => r.severity === "pass").length,
2317
+ warn: results.filter((r) => r.severity === "warn").length,
2318
+ fail: results.filter((r) => r.severity === "fail").length
2319
+ };
2320
+ return { results, summary };
2321
+ }
2322
+ function formatListTemplatesResult(result) {
2323
+ const lines = [`## AXIS \uD15C\uD50C\uB9BF \uBAA9\uB85D (${result.total}\uAC1C)
2324
+ `];
2325
+ if (result.templates.length === 0) {
2326
+ lines.push("\uB4F1\uB85D\uB41C \uD15C\uD50C\uB9BF\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2327
+ return lines.join("\n");
2328
+ }
2329
+ lines.push("| \uC774\uB984 | \uC124\uBA85 | \uCE74\uD14C\uACE0\uB9AC |");
2330
+ lines.push("|------|------|----------|");
2331
+ for (const t of result.templates) {
2332
+ lines.push(`| ${t.name} | ${t.description} | ${t.category} |`);
2333
+ }
2334
+ return lines.join("\n");
2335
+ }
2336
+ function formatGetTemplateResult(result) {
2337
+ const lines = [
2338
+ `## ${result.name}`,
2339
+ "",
2340
+ `- **\uC124\uBA85**: ${result.description}`,
2341
+ `- **\uCE74\uD14C\uACE0\uB9AC**: ${result.category}`,
2342
+ `- **\uBC84\uC804**: ${result.version}`
2343
+ ];
2344
+ if (result.features.length > 0) {
2345
+ lines.push("", "### Features", "");
2346
+ for (const f of result.features) {
2347
+ lines.push(`- ${f}`);
2348
+ }
2349
+ }
2350
+ const deps = Object.entries(result.dependencies);
2351
+ const devDeps = Object.entries(result.devDependencies);
2352
+ if (deps.length > 0 || devDeps.length > 0) {
2353
+ lines.push("", "### \uC758\uC874\uC131", "");
2354
+ for (const [k, v] of deps) {
2355
+ lines.push(`- ${k}@${v}`);
2356
+ }
2357
+ for (const [k, v] of devDeps) {
2358
+ lines.push(`- ${k}@${v} (dev)`);
2359
+ }
2360
+ }
2361
+ if (result.files.length > 0) {
2362
+ lines.push("", "### \uD30C\uC77C \uBAA9\uB85D", "");
2363
+ for (const f of result.files) {
2364
+ lines.push(`- ${f}`);
2365
+ }
2366
+ }
2367
+ return lines.join("\n");
2368
+ }
2369
+ function formatApplyTemplateResult(result) {
2370
+ const mode = result.dryRun ? " (\uBBF8\uB9AC\uBCF4\uAE30)" : "";
2371
+ const lines = [`## \uD15C\uD50C\uB9BF \uC801\uC6A9 \uACB0\uACFC${mode}
2372
+ `];
2373
+ lines.push(`\uD15C\uD50C\uB9BF: **${result.templateName}**
2374
+ `);
2375
+ if (result.files.length > 0) {
2376
+ lines.push("### \uD30C\uC77C", "");
2377
+ for (const f of result.files) {
2378
+ const icon = f.action === "created" ? "+" : "~";
2379
+ lines.push(`- ${icon} ${f.path} (${f.action === "created" ? "\uC0DD\uC131" : "\uB36E\uC5B4\uC4F0\uAE30"})`);
2380
+ }
2381
+ }
2382
+ const deps = Object.entries(result.dependencies);
2383
+ const devDeps = Object.entries(result.devDependencies);
2384
+ if (deps.length > 0 || devDeps.length > 0) {
2385
+ lines.push("", "### \uD544\uC694\uD55C \uC758\uC874\uC131", "");
2386
+ if (deps.length > 0) {
2387
+ lines.push(`\`pnpm add ${deps.map(([k, v]) => `${k}@${v}`).join(" ")}\``);
2388
+ }
2389
+ if (devDeps.length > 0) {
2390
+ lines.push(`\`pnpm add -D ${devDeps.map(([k, v]) => `${k}@${v}`).join(" ")}\``);
2391
+ }
2392
+ }
2393
+ return lines.join("\n");
2394
+ }
2395
+ function formatDiffTemplateResult(result) {
2396
+ const lines = [`## '${result.templateName}' \uD15C\uD50C\uB9BF diff \uACB0\uACFC
2397
+ `];
2398
+ const statusLabel = {
2399
+ added: "+ \uCD94\uAC00 \uD544\uC694",
2400
+ modified: "~ \uBCC0\uACBD\uB428",
2401
+ unchanged: "= \uB3D9\uC77C",
2402
+ extra: "? \uB85C\uCEEC \uC804\uC6A9"
2403
+ };
2404
+ for (const entry of result.entries) {
2405
+ lines.push(`- ${statusLabel[entry.status]} ${entry.path}`);
2406
+ }
2407
+ const s = result.summary;
2408
+ lines.push(
2409
+ "",
2410
+ `**\uC694\uC57D**: \uCD94\uAC00 \uD544\uC694 ${s.added} | \uBCC0\uACBD\uB428 ${s.modified} | \uB3D9\uC77C ${s.unchanged}${s.extra > 0 ? ` | \uB85C\uCEEC \uC804\uC6A9 ${s.extra}` : ""}`
2411
+ );
2412
+ return lines.join("\n");
2413
+ }
2414
+ function formatCheckProjectResult(result) {
2415
+ const lines = ["## AXIS \uD504\uB85C\uC81D\uD2B8 \uAC80\uC99D \uACB0\uACFC\n"];
2416
+ const icon = {
2417
+ pass: "PASS",
2418
+ warn: "WARN",
2419
+ fail: "FAIL"
2420
+ };
2421
+ for (const r of result.results) {
2422
+ lines.push(`- [${icon[r.severity]}] ${r.message}`);
2423
+ }
2424
+ const s = result.summary;
2425
+ lines.push("", `**\uACB0\uACFC**: ${s.pass} pass / ${s.warn} warn / ${s.fail} fail`);
2426
+ return lines.join("\n");
2427
+ }
2428
+
2429
+ // src/index.ts
2430
+ var server = new McpServer({
2431
+ name: "axis-design-system",
2432
+ version: "0.1.0"
2433
+ });
2434
+ var ComponentCategorySchema = z.enum(["core", "agentic", "form", "layout"]).optional().describe("\uCEF4\uD3EC\uB10C\uD2B8 \uCE74\uD14C\uACE0\uB9AC \uD544\uD130");
2435
+ var TokenCategorySchema = z.enum(["color", "typography", "spacing", "radius", "shadow", "animation"]).optional().describe("\uD1A0\uD070 \uCE74\uD14C\uACE0\uB9AC \uD544\uD130");
2436
+ server.tool(
2437
+ "axis.search_components",
2438
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC5D0\uC11C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uAC80\uC0C9\uD569\uB2C8\uB2E4.",
2439
+ {
2440
+ query: z.string().describe("\uAC80\uC0C9\uC5B4 (\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984, \uC124\uBA85 \uB4F1)"),
2441
+ category: ComponentCategorySchema
2442
+ },
2443
+ async ({ query, category }) => {
2444
+ try {
2445
+ const result = handleSearchComponents({
2446
+ query,
2447
+ category
2448
+ });
2449
+ const formatted = formatSearchResult(result);
2450
+ return {
2451
+ content: [
2452
+ {
2453
+ type: "text",
2454
+ text: formatted
2455
+ }
2456
+ ]
2457
+ };
2458
+ } catch (error) {
2459
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2460
+ return {
2461
+ content: [
2462
+ {
2463
+ type: "text",
2464
+ text: `\uC624\uB958: ${message}`
2465
+ }
2466
+ ],
2467
+ isError: true
2468
+ };
2469
+ }
2470
+ }
2471
+ );
2472
+ server.tool(
2473
+ "axis.get_component",
2474
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uCEF4\uD3EC\uB10C\uD2B8\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
2475
+ {
2476
+ name: z.string().describe("\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: button, input)")
2477
+ },
2478
+ async ({ name }) => {
2479
+ try {
2480
+ const component = handleGetComponent({ name });
2481
+ const formatted = formatComponentDetail(component);
2482
+ return {
2483
+ content: [
2484
+ {
2485
+ type: "text",
2486
+ text: formatted
2487
+ }
2488
+ ]
2489
+ };
2490
+ } catch (error) {
2491
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2492
+ return {
2493
+ content: [
2494
+ {
2495
+ type: "text",
2496
+ text: `\uC624\uB958: ${message}`
2497
+ }
2498
+ ],
2499
+ isError: true
2500
+ };
2501
+ }
2502
+ }
2503
+ );
2504
+ server.tool(
2505
+ "axis.list_components",
2506
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC758 \uBAA8\uB4E0 \uCEF4\uD3EC\uB10C\uD2B8 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
2507
+ {
2508
+ category: ComponentCategorySchema
2509
+ },
2510
+ async ({ category }) => {
2511
+ try {
2512
+ const result = handleListComponents({
2513
+ category
2514
+ });
2515
+ const formatted = formatComponentList(result);
2516
+ return {
2517
+ content: [
2518
+ {
2519
+ type: "text",
2520
+ text: formatted
2521
+ }
2522
+ ]
2523
+ };
2524
+ } catch (error) {
2525
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2526
+ return {
2527
+ content: [
2528
+ {
2529
+ type: "text",
2530
+ text: `\uC624\uB958: ${message}`
2531
+ }
2532
+ ],
2533
+ isError: true
2534
+ };
2535
+ }
2536
+ }
2537
+ );
2538
+ server.tool(
2539
+ "axis.list_tokens",
2540
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC758 \uB514\uC790\uC778 \uD1A0\uD070 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
2541
+ {
2542
+ category: TokenCategorySchema
2543
+ },
2544
+ async ({ category }) => {
2545
+ try {
2546
+ const result = handleListTokens({
2547
+ category
2548
+ });
2549
+ const formatted = formatTokenList(result);
2550
+ return {
2551
+ content: [
2552
+ {
2553
+ type: "text",
2554
+ text: formatted
2555
+ }
2556
+ ]
2557
+ };
2558
+ } catch (error) {
2559
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2560
+ return {
2561
+ content: [
2562
+ {
2563
+ type: "text",
2564
+ text: `\uC624\uB958: ${message}`
2565
+ }
2566
+ ],
2567
+ isError: true
2568
+ };
2569
+ }
2570
+ }
2571
+ );
2572
+ server.tool(
2573
+ "axis.get_token",
2574
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uD1A0\uD070\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
2575
+ {
2576
+ path: z.string().describe("\uD1A0\uD070 \uACBD\uB85C (\uC608: colors.primary, spacing.4)")
2577
+ },
2578
+ async ({ path: path4 }) => {
2579
+ try {
2580
+ const token = handleGetToken({ path: path4 });
2581
+ const formatted = formatTokenDetail(token);
2582
+ return {
2583
+ content: [
2584
+ {
2585
+ type: "text",
2586
+ text: formatted
2587
+ }
2588
+ ]
2589
+ };
2590
+ } catch (error) {
2591
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2592
+ return {
2593
+ content: [
2594
+ {
2595
+ type: "text",
2596
+ text: `\uC624\uB958: ${message}`
2597
+ }
2598
+ ],
2599
+ isError: true
2600
+ };
2601
+ }
2602
+ }
2603
+ );
2604
+ server.tool(
2605
+ "axis.install_component",
2606
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC124\uCE58\uD569\uB2C8\uB2E4.",
2607
+ {
2608
+ name: z.string().describe("\uC124\uCE58\uD560 \uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984"),
2609
+ targetDir: z.string().describe("\uC124\uCE58 \uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C")
2610
+ },
2611
+ async ({ name, targetDir }) => {
2612
+ try {
2613
+ const result = handleInstallComponent({ name, targetDir });
2614
+ const formatted = formatInstallResult(result);
2615
+ return {
2616
+ content: [
2617
+ {
2618
+ type: "text",
2619
+ text: formatted
2620
+ }
2621
+ ],
2622
+ isError: !result.success
2623
+ };
2624
+ } catch (error) {
2625
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2626
+ return {
2627
+ content: [
2628
+ {
2629
+ type: "text",
2630
+ text: `\uC624\uB958: ${message}`
2631
+ }
2632
+ ],
2633
+ isError: true
2634
+ };
2635
+ }
2636
+ }
2637
+ );
2638
+ var PromptCategorySchema = z.enum(["planning", "quality", "documentation", "workflow"]).optional().describe("\uD504\uB86C\uD504\uD2B8 \uCE74\uD14C\uACE0\uB9AC");
2639
+ server.tool(
2640
+ "axis.prompt.detect",
2641
+ "\uC138\uC158 \uCEE8\uD14D\uC2A4\uD2B8\uC5D0\uC11C \uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB86C\uD504\uD2B8 \uD6C4\uBCF4\uB97C \uD0D0\uC9C0\uD569\uB2C8\uB2E4.",
2642
+ {
2643
+ context: z.string().describe("\uC138\uC158 \uCEE8\uD14D\uC2A4\uD2B8 \uD14D\uC2A4\uD2B8"),
2644
+ minScore: z.number().optional().describe("\uCD5C\uC18C \uC810\uC218 \uAE30\uC900 (\uAE30\uBCF8: 70)"),
2645
+ maxCandidates: z.number().optional().describe("\uCD5C\uB300 \uD6C4\uBCF4 \uC218 (\uAE30\uBCF8: 5)")
2646
+ },
2647
+ async ({ context, minScore, maxCandidates }) => {
2648
+ try {
2649
+ const result = handleDetect({ context, minScore, maxCandidates });
2650
+ const formatted = formatDetectResult(result);
2651
+ return {
2652
+ content: [
2653
+ {
2654
+ type: "text",
2655
+ text: formatted
2656
+ }
2657
+ ]
2658
+ };
2659
+ } catch (error) {
2660
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2661
+ return {
2662
+ content: [
2663
+ {
2664
+ type: "text",
2665
+ text: `\uC624\uB958: ${message}`
2666
+ }
2667
+ ],
2668
+ isError: true
2669
+ };
2670
+ }
2671
+ }
2672
+ );
2673
+ server.tool(
2674
+ "axis.prompt.analyze",
2675
+ "\uD14D\uC2A4\uD2B8\uC758 \uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uC131\uC744 \uBD84\uC11D\uD558\uACE0 \uAD6C\uC870\uB97C \uD30C\uC545\uD569\uB2C8\uB2E4.",
2676
+ {
2677
+ text: z.string().describe("\uBD84\uC11D\uD560 \uD14D\uC2A4\uD2B8")
2678
+ },
2679
+ async ({ text }) => {
2680
+ try {
2681
+ const result = handleAnalyze({ text });
2682
+ const formatted = formatAnalyzeResult(result);
2683
+ return {
2684
+ content: [
2685
+ {
2686
+ type: "text",
2687
+ text: formatted
2688
+ }
2689
+ ]
2690
+ };
2691
+ } catch (error) {
2692
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2693
+ return {
2694
+ content: [
2695
+ {
2696
+ type: "text",
2697
+ text: `\uC624\uB958: ${message}`
2698
+ }
2699
+ ],
2700
+ isError: true
2701
+ };
2702
+ }
2703
+ }
2704
+ );
2705
+ server.tool(
2706
+ "axis.prompt.refine",
2707
+ "\uD504\uB85C\uC81D\uD2B8 \uC885\uC18D\uC131\uC744 \uC81C\uAC70\uD558\uACE0 \uBC94\uC6A9 \uD504\uB86C\uD504\uD2B8\uB85C \uC815\uC81C\uD569\uB2C8\uB2E4.",
2708
+ {
2709
+ text: z.string().describe("\uC815\uC81C\uD560 \uD14D\uC2A4\uD2B8"),
2710
+ name: z.string().optional().describe("\uD504\uB86C\uD504\uD2B8 \uC774\uB984 (kebab-case)"),
2711
+ category: PromptCategorySchema
2712
+ },
2713
+ async ({ text, name, category }) => {
2714
+ try {
2715
+ const result = handleRefine({
2716
+ text,
2717
+ name,
2718
+ category
2719
+ });
2720
+ const formatted = formatRefineResult(result);
2721
+ return {
2722
+ content: [
2723
+ {
2724
+ type: "text",
2725
+ text: formatted
2726
+ }
2727
+ ]
2728
+ };
2729
+ } catch (error) {
2730
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2731
+ return {
2732
+ content: [
2733
+ {
2734
+ type: "text",
2735
+ text: `\uC624\uB958: ${message}`
2736
+ }
2737
+ ],
2738
+ isError: true
2739
+ };
2740
+ }
2741
+ }
2742
+ );
2743
+ server.tool(
2744
+ "axis.prompt.validate",
2745
+ "\uC815\uC81C\uB41C \uD504\uB86C\uD504\uD2B8\uC758 \uD488\uC9C8\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4.",
2746
+ {
2747
+ promptText: z.string().describe("\uAC80\uC99D\uD560 \uD504\uB86C\uD504\uD2B8 \uD14D\uC2A4\uD2B8 (\uD504\uB860\uD2B8\uB9E4\uD130 \uD3EC\uD568)"),
2748
+ checkDuplicate: z.boolean().optional().describe("\uC911\uBCF5 \uAC80\uC0AC \uD65C\uC131\uD654 (\uAE30\uBCF8: true)")
2749
+ },
2750
+ async ({ promptText, checkDuplicate: checkDuplicate2 }) => {
2751
+ try {
2752
+ const result = handleValidate({ promptText, checkDuplicate: checkDuplicate2 });
2753
+ const formatted = formatValidateResult(result);
2754
+ return {
2755
+ content: [
2756
+ {
2757
+ type: "text",
2758
+ text: formatted
2759
+ }
2760
+ ]
2761
+ };
2762
+ } catch (error) {
2763
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2764
+ return {
2765
+ content: [
2766
+ {
2767
+ type: "text",
2768
+ text: `\uC624\uB958: ${message}`
2769
+ }
2770
+ ],
2771
+ isError: true
2772
+ };
2773
+ }
2774
+ }
2775
+ );
2776
+ server.tool(
2777
+ "axis.prompt.save",
2778
+ "\uAC80\uC99D\uB41C \uD504\uB86C\uD504\uD2B8\uB97C \uD30C\uC77C\uB85C \uC800\uC7A5\uD558\uACE0 \uC778\uB371\uC2A4\uB97C \uAC31\uC2E0\uD569\uB2C8\uB2E4.",
2779
+ {
2780
+ promptText: z.string().describe("\uC800\uC7A5\uD560 \uD504\uB86C\uD504\uD2B8 \uD14D\uC2A4\uD2B8 (\uD504\uB860\uD2B8\uB9E4\uD130 \uD3EC\uD568)"),
2781
+ name: z.string().describe("\uD504\uB86C\uD504\uD2B8 \uC774\uB984 (kebab-case)"),
2782
+ category: z.enum(["planning", "quality", "documentation", "workflow"]).describe("\uCE74\uD14C\uACE0\uB9AC"),
2783
+ force: z.boolean().optional().describe("\uAC15\uC81C \uC800\uC7A5 (\uC911\uBCF5 \uBB34\uC2DC, \uAE30\uBCF8: false)")
2784
+ },
2785
+ async ({ promptText, name, category, force }) => {
2786
+ try {
2787
+ const result = handleSave({
2788
+ promptText,
2789
+ name,
2790
+ category,
2791
+ force
2792
+ });
2793
+ const formatted = formatSaveResult(result);
2794
+ return {
2795
+ content: [
2796
+ {
2797
+ type: "text",
2798
+ text: formatted
2799
+ }
2800
+ ],
2801
+ isError: !result.success
2802
+ };
2803
+ } catch (error) {
2804
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2805
+ return {
2806
+ content: [
2807
+ {
2808
+ type: "text",
2809
+ text: `\uC624\uB958: ${message}`
2810
+ }
2811
+ ],
2812
+ isError: true
2813
+ };
2814
+ }
2815
+ }
2816
+ );
2817
+ server.tool(
2818
+ "axis.list_templates",
2819
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC758 \uD15C\uD50C\uB9BF \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
2820
+ {
2821
+ category: z.string().optional().describe("\uD15C\uD50C\uB9BF \uCE74\uD14C\uACE0\uB9AC \uD544\uD130")
2822
+ },
2823
+ async ({ category }) => {
2824
+ try {
2825
+ const result = await handleListTemplates({ category });
2826
+ const formatted = formatListTemplatesResult(result);
2827
+ return {
2828
+ content: [
2829
+ {
2830
+ type: "text",
2831
+ text: formatted
2832
+ }
2833
+ ]
2834
+ };
2835
+ } catch (error) {
2836
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2837
+ return {
2838
+ content: [
2839
+ {
2840
+ type: "text",
2841
+ text: `\uC624\uB958: ${message}`
2842
+ }
2843
+ ],
2844
+ isError: true
2845
+ };
2846
+ }
2847
+ }
2848
+ );
2849
+ server.tool(
2850
+ "axis.get_template",
2851
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uD15C\uD50C\uB9BF\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
2852
+ {
2853
+ name: z.string().describe("\uD15C\uD50C\uB9BF \uC774\uB984 (slug)")
2854
+ },
2855
+ async ({ name }) => {
2856
+ try {
2857
+ const result = await handleGetTemplate({ name });
2858
+ const formatted = formatGetTemplateResult(result);
2859
+ return {
2860
+ content: [
2861
+ {
2862
+ type: "text",
2863
+ text: formatted
2864
+ }
2865
+ ]
2866
+ };
2867
+ } catch (error) {
2868
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2869
+ return {
2870
+ content: [
2871
+ {
2872
+ type: "text",
2873
+ text: `\uC624\uB958: ${message}`
2874
+ }
2875
+ ],
2876
+ isError: true
2877
+ };
2878
+ }
2879
+ }
2880
+ );
2881
+ server.tool(
2882
+ "axis.apply_template",
2883
+ "AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uD15C\uD50C\uB9BF\uC744 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC801\uC6A9\uD569\uB2C8\uB2E4.",
2884
+ {
2885
+ name: z.string().describe("\uD15C\uD50C\uB9BF \uC774\uB984 (slug)"),
2886
+ targetDir: z.string().describe("\uC801\uC6A9 \uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C"),
2887
+ dryRun: z.boolean().optional().describe("\uBBF8\uB9AC\uBCF4\uAE30 \uBAA8\uB4DC (\uD30C\uC77C \uBBF8\uC0DD\uC131, \uAE30\uBCF8: false)")
2888
+ },
2889
+ async ({ name, targetDir, dryRun }) => {
2890
+ try {
2891
+ const result = await handleApplyTemplate({ name, targetDir, dryRun });
2892
+ const formatted = formatApplyTemplateResult(result);
2893
+ return {
2894
+ content: [
2895
+ {
2896
+ type: "text",
2897
+ text: formatted
2898
+ }
2899
+ ],
2900
+ isError: !result.success
2901
+ };
2902
+ } catch (error) {
2903
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2904
+ return {
2905
+ content: [
2906
+ {
2907
+ type: "text",
2908
+ text: `\uC624\uB958: ${message}`
2909
+ }
2910
+ ],
2911
+ isError: true
2912
+ };
2913
+ }
2914
+ }
2915
+ );
2916
+ server.tool(
2917
+ "axis.diff_template",
2918
+ "\uB85C\uCEEC \uD504\uB85C\uC81D\uD2B8\uC640 \uC6D0\uACA9 \uD15C\uD50C\uB9BF\uC758 \uD30C\uC77C \uCC28\uC774\uB97C \uBE44\uAD50\uD569\uB2C8\uB2E4.",
2919
+ {
2920
+ name: z.string().describe("\uD15C\uD50C\uB9BF \uC774\uB984 (slug)"),
2921
+ targetDir: z.string().optional().describe("\uBE44\uAD50 \uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC)")
2922
+ },
2923
+ async ({ name, targetDir }) => {
2924
+ try {
2925
+ const result = await handleDiffTemplate({ name, targetDir });
2926
+ const formatted = formatDiffTemplateResult(result);
2927
+ return {
2928
+ content: [
2929
+ {
2930
+ type: "text",
2931
+ text: formatted
2932
+ }
2933
+ ]
2934
+ };
2935
+ } catch (error) {
2936
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2937
+ return {
2938
+ content: [
2939
+ {
2940
+ type: "text",
2941
+ text: `\uC624\uB958: ${message}`
2942
+ }
2943
+ ],
2944
+ isError: true
2945
+ };
2946
+ }
2947
+ }
2948
+ );
2949
+ server.tool(
2950
+ "axis.check_project",
2951
+ "\uD504\uB85C\uC81D\uD2B8\uC758 AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uC124\uC815 \uC0C1\uD0DC\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4.",
2952
+ {
2953
+ targetDir: z.string().optional().describe("\uAC80\uC99D \uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC)")
2954
+ },
2955
+ async ({ targetDir }) => {
2956
+ try {
2957
+ const result = await handleCheckProject({ targetDir });
2958
+ const formatted = formatCheckProjectResult(result);
2959
+ return {
2960
+ content: [
2961
+ {
2962
+ type: "text",
2963
+ text: formatted
2964
+ }
2965
+ ]
2966
+ };
2967
+ } catch (error) {
2968
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
2969
+ return {
2970
+ content: [
2971
+ {
2972
+ type: "text",
2973
+ text: `\uC624\uB958: ${message}`
2974
+ }
2975
+ ],
2976
+ isError: true
2977
+ };
2978
+ }
2979
+ }
2980
+ );
2981
+ async function main() {
2982
+ const transport = new StdioServerTransport();
2983
+ await server.connect(transport);
2984
+ process.on("SIGINT", async () => {
2985
+ await server.close();
2986
+ process.exit(0);
2987
+ });
2988
+ process.on("SIGTERM", async () => {
2989
+ await server.close();
2990
+ process.exit(0);
2991
+ });
2992
+ }
2993
+ main().catch((error) => {
2994
+ console.error("MCP \uC11C\uBC84 \uC2DC\uC791 \uC2E4\uD328:", error);
2995
+ process.exit(1);
2996
+ });