@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/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2996 -0
- package/dist/registry/data/components.json +228 -0
- package/dist/registry/data/tokens.json +269 -0
- package/package.json +50 -0
- package/src/registry/data/components.json +228 -0
- package/src/registry/data/tokens.json +269 -0
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
|
+
});
|