@axboot-mcp/mcp-server 1.0.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.
Files changed (78) hide show
  1. package/CLAUDE.md +119 -0
  2. package/MCP_TOOL_PLAN.md +710 -0
  3. package/MCP_USAGE.md +914 -0
  4. package/README.md +168 -0
  5. package/REPOSITORY_CONVENTIONS.md +250 -0
  6. package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
  7. package/SEARCH_PARAMS_PLAN.md +2570 -0
  8. package/STORE_PATTERNS.md +1178 -0
  9. package/debug-dto.js +72 -0
  10. package/generate-banner-store.js +62 -0
  11. package/generation-plan.json +2176 -0
  12. package/generation-results.json +1817 -0
  13. package/package.json +45 -0
  14. package/scripts/batch-generate-all.js +159 -0
  15. package/scripts/batch-generate-mcp.js +329 -0
  16. package/scripts/batch-generate-stores-v2.js +272 -0
  17. package/scripts/batch-generate-stores.js +179 -0
  18. package/scripts/batch-plan.json +3810 -0
  19. package/scripts/batch-process.py +90 -0
  20. package/scripts/batch-regenerate.js +356 -0
  21. package/scripts/direct-generate.js +227 -0
  22. package/scripts/execute-batches.js +1911 -0
  23. package/scripts/generate-all-stores.js +144 -0
  24. package/scripts/generate-stores-mcp.js +161 -0
  25. package/scripts/generate-stores-v2.js +450 -0
  26. package/scripts/generate-stores-v3.js +412 -0
  27. package/scripts/generate-stores-v4.js +521 -0
  28. package/scripts/generate-stores.js +382 -0
  29. package/scripts/repos-to-process.json +1899 -0
  30. package/src/config/nh-layout-patterns.ts +166 -0
  31. package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
  32. package/src/docs/NH_STORE_PATTERNS.md +297 -0
  33. package/src/docs/README.md +216 -0
  34. package/src/docs/index.ts +28 -0
  35. package/src/docs/loader.ts +568 -0
  36. package/src/docs/patterns.json +419 -0
  37. package/src/docs/practical-examples.md +732 -0
  38. package/src/docs/quick-start.md +257 -0
  39. package/src/docs/requirements-analysis-guide.md +364 -0
  40. package/src/docs/rules.json +321 -0
  41. package/src/docs/store-pattern-analysis.md +664 -0
  42. package/src/docs/store-patterns-rules.md +1168 -0
  43. package/src/docs/store-patterns-usage-guide.md +1835 -0
  44. package/src/docs/troubleshooting.md +544 -0
  45. package/src/docs/type-selection-guide.md +572 -0
  46. package/src/docs//354/202/254/354/232/251/353/262/225/AntD-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +1515 -0
  47. package/src/docs//354/202/254/354/232/251/353/262/225/DataGrid-/354/202/254/354/232/251/353/262/225.md +866 -0
  48. package/src/docs//354/202/254/354/232/251/353/262/225/FormItem-/354/202/254/354/232/251/353/262/225.md +903 -0
  49. package/src/docs//354/202/254/354/232/251/353/262/225/FormModal-/354/202/254/354/232/251/353/262/225.md +1155 -0
  50. package/src/docs//354/202/254/354/232/251/353/262/225/MCP-/353/260/224/354/235/264/353/270/214/354/275/224/353/224/251-/352/260/200/354/235/264/353/223/234.md +1133 -0
  51. package/src/docs//354/202/254/354/232/251/353/262/225/MSW-Mock-/353/215/260/354/235/264/355/204/260-/354/202/254/354/232/251/353/262/225.md +579 -0
  52. package/src/docs//354/202/254/354/232/251/353/262/225/Search-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +738 -0
  53. package/src/docs//354/202/254/354/232/251/353/262/225/Store-/355/214/250/355/204/264-/354/202/254/354/232/251/353/262/225.md +1135 -0
  54. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/352/265/254/354/204/261-/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234/354/210/234/354/204/234.md +1805 -0
  55. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234-/355/224/204/353/241/254/355/224/204/355/212/270-/352/260/200/354/235/264/353/223/234.md +946 -0
  56. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/225/354/236/245/355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/354/203/201/354/204/270-/355/224/204/353/241/254/355/224/204/355/212/270/352/260/200/354/235/264/353/223/234.md +2422 -0
  57. package/src/features/store-features.ts +232 -0
  58. package/src/handlers/analyze-requirements.ts +403 -0
  59. package/src/handlers/analyze.ts +1373 -0
  60. package/src/handlers/generate-from-requirements.ts +250 -0
  61. package/src/handlers/generate-hook.ts +950 -0
  62. package/src/handlers/generate-interactive.ts +840 -0
  63. package/src/handlers/generate-listdatagrid.ts +521 -0
  64. package/src/handlers/generate-multi-stores.ts +577 -0
  65. package/src/handlers/generate-requirements-from-layout.ts +160 -0
  66. package/src/handlers/generate-search-params.ts +717 -0
  67. package/src/handlers/generate.ts +911 -0
  68. package/src/handlers/list-templates.ts +104 -0
  69. package/src/handlers/scan-metadata.ts +485 -0
  70. package/src/handlers/suggest-layout.ts +326 -0
  71. package/src/index.ts +959 -0
  72. package/src/prompts/search-params.md +793 -0
  73. package/src/templates/index.ts +107 -0
  74. package/src/templates/unified.ts +462 -0
  75. package/store-generation-error-patterns.md +225 -0
  76. package/test/useAgentStore.ts +136 -0
  77. package/test-server.js +78 -0
  78. package/tsconfig.json +20 -0
@@ -0,0 +1,717 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ /**
5
+ * SearchParams 코드 생성 도구
6
+ * App.tsx에 직접 SearchParams를 추가합니다.
7
+ */
8
+ export interface GenerateSearchParamsParams {
9
+ /** App.tsx 파일 경로 (절대 경로) - dtoPath를 사용하면 자동 감지 */
10
+ appPath?: string;
11
+ /** SearchParams 설정 */
12
+ searchParams?: SearchParamsConfig;
13
+ /** DTO 파일 경로 (절대 경로) - DTO를 분석해서 자동으로 SearchParams 생성 및 App.tsx 수정 */
14
+ dtoPath?: string;
15
+ }
16
+
17
+ export interface SearchParamsConfig {
18
+ /** 날짜 범위 검색 설정 */
19
+ dateRange?: DateRangeConfig;
20
+ /** 코드 선택 필드 목록 */
21
+ codes?: CodeFieldConfig[];
22
+ /** 검색어 설정 */
23
+ search?: SearchConfig;
24
+ /** Y/N 필드 목록 */
25
+ ynFields?: YnFieldConfig[];
26
+ /** 숫자 범위 필드 목록 */
27
+ numberRanges?: NumberRangeConfig[];
28
+ /** 줄바꿈 추가 여부 */
29
+ lineBreak?: boolean;
30
+ }
31
+
32
+ export interface DateRangeConfig {
33
+ /** 기간구분 포함 여부 */
34
+ withPeriodType?: boolean;
35
+ /** 기간구분 옵션 */
36
+ periodOptions?: Array<{ label: string; value: string }>;
37
+ /** 날짜 필드명 (기본: dateRange) */
38
+ name?: string;
39
+ /** 라벨 (기본: 등록일) */
40
+ label?: string;
41
+ /** 프리셋 타입 (all 또는 없음) */
42
+ presetType?: 'all' | 'default';
43
+ }
44
+
45
+ export interface CodeFieldConfig {
46
+ /** 코드 필드명 (예: statusCd) */
47
+ name: string;
48
+ /** 라벨 (예: 상태) */
49
+ label: string;
50
+ /** 코드 변수명 (예: STATUS_CODE) */
51
+ codeVar?: string;
52
+ /** 너비 (기본: 150) */
53
+ width?: number;
54
+ /** 다중 선택 여부 */
55
+ multi?: boolean;
56
+ }
57
+
58
+ export interface SearchConfig {
59
+ /** 검색구분 포함 여부 */
60
+ withSearchType?: boolean;
61
+ /** 검색구분 옵션 */
62
+ options?: Array<{ label: string; value: string }>;
63
+ /** 검색어 필드명 (기본: searchText) */
64
+ textName?: string;
65
+ /** 검색구분 필드명 (기본: searchType) */
66
+ typeName?: string;
67
+ }
68
+
69
+ export interface YnFieldConfig {
70
+ /** Y/N 필드명 (예: useYn) */
71
+ name: string;
72
+ /** 라벨 (예: 사용여부) */
73
+ label: string;
74
+ }
75
+
76
+ export interface NumberRangeConfig {
77
+ /** 필드명 */
78
+ name: string;
79
+ /** 라벨 */
80
+ label: string;
81
+ /** MIN 플레이스홀더 */
82
+ minPlaceholder?: string;
83
+ /** MAX 플레이스홀더 */
84
+ maxPlaceholder?: string;
85
+ }
86
+
87
+ /**
88
+ * DTO 필드 정보
89
+ */
90
+ interface DtoField {
91
+ name: string;
92
+ type: string;
93
+ optional: boolean;
94
+ comment?: string;
95
+ }
96
+
97
+ /**
98
+ * SearchParams 자동 생성 및 App.tsx 수정
99
+ */
100
+ export async function generateSearchParams(params: GenerateSearchParamsParams): Promise<{
101
+ content: Array<{ type: string; text: string }>;
102
+ }> {
103
+ const { dtoPath, searchParams } = params;
104
+
105
+ // 1. DTO 파싱
106
+ let config: SearchParamsConfig | null = null;
107
+ let dtoAnalysis = '';
108
+ let detectedCodes: string[] = [];
109
+
110
+ if (dtoPath) {
111
+ const dtoResult = parseDtoFile(dtoPath);
112
+ dtoAnalysis = dtoResult.analysis;
113
+ config = dtoResult.searchParams;
114
+ detectedCodes = dtoResult.detectedCodes;
115
+
116
+ console.error(`[MCP] DTO 분석 완료: ${dtoResult.fields.length}개 필드`);
117
+ console.error(`[MCP] 감지된 코드: ${detectedCodes.join(', ')}`);
118
+ }
119
+
120
+ const finalConfig = config || searchParams;
121
+
122
+ if (!finalConfig) {
123
+ return {
124
+ content: [{
125
+ type: 'text',
126
+ text: JSON.stringify({
127
+ success: false,
128
+ error: 'DTO 분석 실패 또는 searchParams가 없습니다.',
129
+ }),
130
+ }],
131
+ };
132
+ }
133
+
134
+ // 2. App.tsx 경로 결정
135
+ let appPath = params.appPath;
136
+ if (!appPath && dtoPath) {
137
+ // DTO 경로에서 App.tsx 경로 추정
138
+ // 예: /Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerSearch.ts
139
+ // → /Users/kyle/Desktop/nh-fe-bo/src/pages/resources/banner/App.tsx
140
+ const projectRoot = '/Users/kyle/Desktop/nh-fe-bo';
141
+ const dtoName = path.basename(dtoPath, '.ts').replace('Search', '');
142
+ const pageName = dtoName.toLowerCase();
143
+
144
+ const possiblePaths = [
145
+ path.join(projectRoot, 'src/pages/resources', pageName, 'App.tsx'),
146
+ path.join(projectRoot, 'src/pages/resources', dtoName.toLowerCase(), 'App.tsx'),
147
+ path.join(projectRoot, 'src/pages', pageName, 'App.tsx'),
148
+ ];
149
+
150
+ // 존재하는 파일 찾기
151
+ for (const possiblePath of possiblePaths) {
152
+ if (fs.existsSync(possiblePath)) {
153
+ appPath = possiblePath;
154
+ break;
155
+ }
156
+ }
157
+
158
+ if (!appPath) {
159
+ // 기본값 사용
160
+ appPath = path.join(projectRoot, 'src/pages/resources/test/App.tsx');
161
+ }
162
+ }
163
+
164
+ // 3. CodeModel에 코드 추가
165
+ if (detectedCodes.length > 0) {
166
+ await addCodesToCodeModel(detectedCodes);
167
+ }
168
+
169
+ // 4. App.tsx 수정
170
+ const modifications = await modifyAppTsx(appPath!, finalConfig);
171
+
172
+ return {
173
+ content: [{
174
+ type: 'text',
175
+ text: JSON.stringify({
176
+ success: true,
177
+ dtoAnalysis,
178
+ appPath,
179
+ modifications,
180
+ message: generateSuccessMessage(dtoPath, finalConfig, appPath),
181
+ }),
182
+ }],
183
+ };
184
+ }
185
+
186
+ /**
187
+ * DTO 파일 파싱
188
+ */
189
+ function parseDtoFile(dtoPath: string): {
190
+ fields: DtoField[];
191
+ analysis: string;
192
+ searchParams: SearchParamsConfig;
193
+ detectedCodes: string[];
194
+ } {
195
+ let content = '';
196
+ try {
197
+ content = fs.readFileSync(dtoPath, 'utf-8');
198
+ } catch (e) {
199
+ return {
200
+ fields: [],
201
+ analysis: `DTO 파일을 읽을 수 없습니다: ${dtoPath}`,
202
+ searchParams: {},
203
+ detectedCodes: [],
204
+ };
205
+ }
206
+
207
+ // 인터페이스 내용 추출
208
+ const interfaceMatch = content.match(/export\s+interface\s+(\w+)\s+extends\s+[\w.]+\s*\{([\s\S]*?)^\}/m);
209
+ if (!interfaceMatch) {
210
+ return {
211
+ fields: [],
212
+ analysis: 'DTO 인터페이스를 찾을 수 없습니다.',
213
+ searchParams: {},
214
+ detectedCodes: [],
215
+ };
216
+ }
217
+
218
+ const interfaceBody = interfaceMatch[2];
219
+ const interfaceName = interfaceMatch[1];
220
+
221
+ // 필드 파싱
222
+ const fieldLines = interfaceBody.split('\n').filter(line => line.trim() && !line.trim().startsWith('//'));
223
+
224
+ const fields: DtoField[] = [];
225
+ const searchParams: SearchParamsConfig = {};
226
+ const detectedCodes: string[] = [];
227
+ const codes: CodeFieldConfig[] = [];
228
+ const ynFields: YnFieldConfig[] = [];
229
+
230
+ for (const line of fieldLines) {
231
+ // 주석 추출
232
+ const commentMatch = line.match(/\/\/(.*)$/);
233
+ const comment = commentMatch ? commentMatch[1].trim() : '';
234
+
235
+ // 필드 정의 추출
236
+ const fieldMatch = line.match(/^\s*(\w+)\s*:\s*([^;]+);/);
237
+ if (fieldMatch) {
238
+ const name = fieldMatch[1];
239
+ const type = fieldMatch[2].trim();
240
+ const optional = type.endsWith('?');
241
+
242
+ fields.push({ name, type, optional, comment });
243
+ }
244
+ }
245
+
246
+ // SearchParams 설정 생성
247
+ for (const field of fields) {
248
+ const { name, comment } = field;
249
+
250
+ // 페이지네이션 필드 제외
251
+ if (['pageNumber', 'pageSize', 'pageNo', 'rowNum'].includes(name)) {
252
+ continue;
253
+ }
254
+
255
+ // 날짜 범위 필드 감지
256
+ if (name === 'fromDate' || name === 'toDate' || name === 'startDate' || name === 'endDate' ||
257
+ name === 'searchDateFrom' || name === 'searchDateTo' || name === 'regDtFrom' || name === 'regDtTo') {
258
+ if (!searchParams.dateRange) {
259
+ searchParams.dateRange = { label: '등록일' };
260
+ }
261
+ continue;
262
+ }
263
+
264
+ // Y/N 필드 감지
265
+ if (name.endsWith('Yn')) {
266
+ const label = comment || name.replace(/Yn$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
267
+ ynFields.push({ name, label });
268
+ continue;
269
+ }
270
+
271
+ // 코드 필드 감지 (xxxTpcd, xxxClcd, xxxKncd, xxxCd 등)
272
+ if (name.endsWith('Tpcd') || name.endsWith('Clcd') || name.endsWith('Kncd') ||
273
+ (name.endsWith('Cd') && name !== 'dspyYn' && name.length > 2)) {
274
+ const label = comment || name.replace(/Tpcd$|Clcd$|Kncd$|Cd$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
275
+ const codeVar = name.toUpperCase();
276
+
277
+ detectedCodes.push(codeVar);
278
+
279
+ codes.push({
280
+ name: name.charAt(0).toLowerCase() + name.slice(1), // camelCase
281
+ label,
282
+ codeVar,
283
+ width: 150,
284
+ multi: false,
285
+ });
286
+ continue;
287
+ }
288
+
289
+ // 검색어 필드 감지
290
+ if (name.includes('search') || name.includes('keyword') || name.includes('text')) {
291
+ if (!searchParams.search) {
292
+ searchParams.search = {};
293
+ }
294
+ continue;
295
+ }
296
+ }
297
+
298
+ if (codes.length > 0) {
299
+ searchParams.codes = codes;
300
+ }
301
+
302
+ if (ynFields.length > 0) {
303
+ searchParams.ynFields = ynFields;
304
+ }
305
+
306
+ // 분석 결과 생성
307
+ let analysis = `## DTO: ${interfaceName}\n\n`;
308
+ analysis += `### 필드 분석 결과\n\n`;
309
+
310
+ if (searchParams.dateRange) {
311
+ analysis += `- ✅ **날짜 범위**: 감지됨\n`;
312
+ }
313
+
314
+ if (codes.length > 0) {
315
+ analysis += `- ✅ **코드 필드** (${codes.length}개):\n`;
316
+ codes.forEach(code => {
317
+ analysis += ` - \`${code.name}\` → ${code.label} (${code.codeVar})\n`;
318
+ });
319
+ }
320
+
321
+ if (ynFields.length > 0) {
322
+ analysis += `- ✅ **Y/N 필드** (${ynFields.length}개):\n`;
323
+ ynFields.forEach(yn => {
324
+ analysis += ` - \`${yn.name}\` → ${yn.label}\n`;
325
+ });
326
+ }
327
+
328
+ if (searchParams.search) {
329
+ analysis += `- ✅ **검색어**: 감지됨\n`;
330
+ }
331
+
332
+ return { fields, analysis, searchParams, detectedCodes };
333
+ }
334
+
335
+ /**
336
+ * CodeModel에 코드 추가
337
+ */
338
+ async function addCodesToCodeModel(codes: string[]): Promise<void> {
339
+ const codeStorePath = '/Users/kyle/Desktop/nh-fe-bo/src/stores/useCodeStore.ts';
340
+
341
+ let content = '';
342
+ try {
343
+ content = fs.readFileSync(codeStorePath, 'utf-8');
344
+ } catch (e) {
345
+ console.error(`[MCP] useCodeStore.ts를 읽을 수 없습니다: ${codeStorePath}`);
346
+ return;
347
+ }
348
+
349
+ // CodeModel 인터페이스 찾기
350
+ const codeModelMatch = content.match(/export\s+interface\s+CodeModel\s*\{([\s\S]*?)^\n\}/m);
351
+ if (!codeModelMatch) {
352
+ console.error('[MCP] CodeModel 인터페이스를 찾을 수 없습니다.');
353
+ return;
354
+ }
355
+
356
+ const codeModelBody = codeModelMatch[1];
357
+ const addedCodes: string[] = [];
358
+
359
+ // 각 코드가 존재하는지 확인하고 추가
360
+ for (const code of codes) {
361
+ const codeLine = ` ${code}?: Code;`;
362
+
363
+ // 이미 존재하는지 확인
364
+ if (content.includes(`${code}?`)) {
365
+ console.error(`[MCP] ${code}는 이미 존재합니다.`);
366
+ continue;
367
+ }
368
+
369
+ // CodeModel에 추가 (알파벳순으로 정렬)
370
+ const lines = codeModelBody.split('\n');
371
+ let inserted = false;
372
+
373
+ for (let i = 0; i < lines.length; i++) {
374
+ const line = lines[i].trim();
375
+ if (line && !line.startsWith('//') && line.endsWith(';')) {
376
+ const existingCode = line.match(/^(\w+)\?:/)?.[1];
377
+ if (existingCode && existingCode > code) {
378
+ lines.splice(i, 0, ` ${code}?: Code; // 자동 생성`);
379
+ inserted = true;
380
+ break;
381
+ }
382
+ }
383
+ }
384
+
385
+ if (!inserted) {
386
+ lines.push(` ${code}?: Code; // 자동 생성`);
387
+ }
388
+
389
+ addedCodes.push(code);
390
+
391
+ // 전체 내용 업데이트
392
+ const newCodeModelBody = lines.join('\n');
393
+ content = content.replace(codeModelMatch[0], `export interface CodeModel {\n${newCodeModelBody}\n}`);
394
+ }
395
+
396
+ if (addedCodes.length > 0) {
397
+ fs.writeFileSync(codeStorePath, content, 'utf-8');
398
+ console.error(`[MCP] CodeModel에 코드 추가됨: ${addedCodes.join(', ')}`);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * App.tsx 수정
404
+ */
405
+ async function modifyAppTsx(appPath: string, config: SearchParamsConfig): Promise<string[]> {
406
+ const modifications: string[] = [];
407
+
408
+ let content = '';
409
+ try {
410
+ content = fs.readFileSync(appPath, 'utf-8');
411
+ } catch (e) {
412
+ throw new Error(`App.tsx를 읽을 수 없습니다: ${appPath}`);
413
+ }
414
+
415
+ // 1. Import 추가
416
+ const importsToAdd = generateImports(config);
417
+ for (const importLine of importsToAdd) {
418
+ if (!content.includes(importLine.split(' from ')[1]?.replace(/"/g, "'"))) {
419
+ // import 추가 로직 (마지막 import 다음에 추가)
420
+ const lastImportMatch = content.match(/^import .*?;$/m);
421
+ if (lastImportMatch) {
422
+ const lastImportIndex = content.indexOf(lastImportMatch[0]) + lastImportMatch[0].length;
423
+ content = content.slice(0, lastImportIndex) + '\n' + importLine + content.slice(lastImportIndex);
424
+ modifications.push(`Import 추가: ${importLine}`);
425
+ }
426
+ }
427
+ }
428
+
429
+ // 2. Store 감지 및 import
430
+ const storeImport = detectStoreImport(appPath);
431
+ if (storeImport && !content.includes(storeImport)) {
432
+ const lastImportMatch = content.match(/^import .*?;$/m);
433
+ if (lastImportMatch) {
434
+ const lastImportIndex = content.indexOf(lastImportMatch[0]) + lastImportMatch[0].length;
435
+ content = content.slice(0, lastImportIndex) + '\n' + storeImport + content.slice(lastImportIndex);
436
+ modifications.push(`Store import 추가: ${storeImport}`);
437
+ }
438
+ }
439
+
440
+ // 3. Store 상태 선언
441
+ const storeDeclarations = generateStoreDeclarations(appPath);
442
+ for (const declaration of storeDeclarations) {
443
+ if (!content.includes(declaration.split(' = ')[0])) {
444
+ // const resizerContainerRef 근처에 추가
445
+ const refMatch = content.match(/const resizerContainerRef = .+?;/);
446
+ if (refMatch) {
447
+ const refIndex = content.indexOf(refMatch[0]) + refMatch[0].length;
448
+ content = content.slice(0, refIndex) + '\n ' + declaration + content.slice(refIndex);
449
+ modifications.push(`Store 상태 선언: ${declaration}`);
450
+ }
451
+ }
452
+ }
453
+
454
+ // 4. Code 선언
455
+ if (config.codes) {
456
+ for (const code of config.codes) {
457
+ const codeVar = code.codeVar || code.name.toUpperCase();
458
+ const declaration = `const ${codeVar} = useCodeStore((s) => s.${codeVar});`;
459
+ if (!content.includes(`const ${codeVar} =`)) {
460
+ const refMatch = content.match(/const resizerContainerRef = .+?;/);
461
+ if (refMatch) {
462
+ const refIndex = content.indexOf(refMatch[0]) + refMatch[0].length;
463
+ content = content.slice(0, refIndex) + '\n ' + declaration + content.slice(refIndex);
464
+ modifications.push(`Code 선언: ${declaration}`);
465
+ }
466
+ }
467
+ }
468
+ }
469
+
470
+ // 5. params 배열 생성
471
+ const paramsArray = generateParamsArray(config);
472
+ const paramsVariable = 'const params: IParam[] = [\n' + paramsArray + '\n];';
473
+ if (!content.includes('const params: IParam[]')) {
474
+ // const resizerContainerRef 근처에 추가
475
+ const refMatch = content.match(/const resizerContainerRef = .+?;/);
476
+ if (refMatch) {
477
+ const refIndex = content.indexOf(refMatch[0]) + refMatch[0].length;
478
+ content = content.slice(0, refIndex) + '\n ' + paramsVariable + content.slice(refIndex);
479
+ modifications.push('params 배열 추가');
480
+ }
481
+ }
482
+
483
+ // 6. PageSearchBar 스타일 추가
484
+ if (!content.includes('const PageSearchBar = styled(PageLayout.PageSearchBar)')) {
485
+ // styled 컴포넌트 영역에 추가
486
+ const styledMatch = content.match(/const (Container|Header) = styled\(PageLayout\.\w+\)/);
487
+ if (styledMatch) {
488
+ const styledIndex = content.indexOf(styledMatch[0]) + styledMatch[0].length;
489
+ content = content.slice(0, styledIndex) + '\nconst PageSearchBar = styled(PageLayout.PageSearchBar)``;' + content.slice(styledIndex);
490
+ modifications.push('PageSearchBar 스타일 추가');
491
+ }
492
+ }
493
+
494
+ // 7. Form.useForm() 선언 확인
495
+ if (!content.includes('const [searchForm] = Form.useForm()')) {
496
+ const refMatch = content.match(/const resizerContainerRef = .+?;/);
497
+ if (refMatch) {
498
+ const refIndex = content.indexOf(refMatch[0]) + refMatch[0].length;
499
+ content = content.slice(0, refIndex) + '\n const [searchForm] = Form.useForm();' + content.slice(refIndex);
500
+ modifications.push('Form.useForm() 선언 추가');
501
+ }
502
+ }
503
+
504
+ // 8. PageSearchBar 컴포넌트 추가
505
+ const searchParamsJsx = generateSearchParamsJsx();
506
+ if (!content.includes('<PageSearchBar>')) {
507
+ // </Header> 다음에 추가
508
+ const headerEndMatch = content.match(/<\/Header>/);
509
+ if (headerEndMatch) {
510
+ const headerEndIndex = content.indexOf(headerEndMatch[0]) + headerEndMatch[0].length;
511
+ content = content.slice(0, headerEndIndex) + '\n' + searchParamsJsx + content.slice(headerEndIndex);
512
+ modifications.push('PageSearchBar 컴포넌트 추가');
513
+ }
514
+ }
515
+
516
+ // 파일 저장
517
+ fs.writeFileSync(appPath, content, 'utf-8');
518
+ console.error(`[MCP] App.tsx 수정 완료: ${appPath}`);
519
+
520
+ return modifications;
521
+ }
522
+
523
+ /**
524
+ * Store import 감지
525
+ */
526
+ function detectStoreImport(appPath: string): string | null {
527
+ const fileName = path.basename(appPath, '.tsx'); // App.tsx → App
528
+ const dir = path.dirname(appPath);
529
+
530
+ // 같은 경로에서 use*.ts 파일 찾기
531
+ const storeFiles = fs.readdirSync(dir).filter(f => f.startsWith('use') && f.endsWith('.ts') && f !== 'useCodeStore.ts');
532
+
533
+ if (storeFiles.length > 0) {
534
+ const storeFile = storeFiles[0];
535
+ const storeName = storeFile.replace('.ts', '');
536
+ return `import { ${fileName}${storeName.replace('use', '')} } from "./${storeName}";`;
537
+ }
538
+
539
+ return null;
540
+ }
541
+
542
+ /**
543
+ * Store 상태 선언 생성
544
+ */
545
+ function generateStoreDeclarations(appPath: string): string[] {
546
+ const declarations = [
547
+ 'const callListApi = useXxxStore((s) => s.callListApi);',
548
+ 'const listRequestValue = useXxxStore((s) => s.listRequestValue);',
549
+ 'const setListRequestValue = useXxxStore((s) => s.setListRequestValue);',
550
+ 'const listSpinning = useXxxStore((s) => s.listSpinning);',
551
+ ];
552
+
553
+ return declarations;
554
+ }
555
+
556
+ /**
557
+ * Import 생성
558
+ */
559
+ function generateImports(config: SearchParamsConfig): string[] {
560
+ const imports: string[] = [];
561
+
562
+ if (!config.dateRange && !config.codes) {
563
+ imports.push(`import { SearchParams, SearchParamType } from "@core/components/search";`);
564
+ }
565
+
566
+ if (config.dateRange) {
567
+ imports.push(`import { IParam, SearchParams, SearchParamType } from "@core/components/search";`);
568
+ imports.push(`import { DT_FORMAT } from "@types";`);
569
+ imports.push(`import { DtPickerPresetRender } from "components/presetRender/DtPickerPresetRender";`);
570
+ }
571
+
572
+ if (config.codes) {
573
+ imports.push(`import { useCodeStore } from "stores";`);
574
+ }
575
+
576
+ return imports;
577
+ }
578
+
579
+ /**
580
+ * params 배열 생성
581
+ */
582
+ function generateParamsArray(config: SearchParamsConfig): string {
583
+ const params: string[] = [];
584
+
585
+ // 날짜 범위
586
+ if (config.dateRange) {
587
+ const { label = '등록일', name = 'dateRange' } = config.dateRange;
588
+ params.push(` {
589
+ label: t("${label}"),
590
+ placeholder: [t("년월일"), t("년월일")],
591
+ name: "${name}",
592
+ type: SearchParamType.DTPICKER_DATE_RANGE,
593
+ format: DT_FORMAT.DATE,
594
+ isTimeSelector: false,
595
+ presetRender: DtPickerPresetRender({ type: "all" }),
596
+ }`);
597
+ }
598
+
599
+ // 코드 선택
600
+ config.codes?.forEach(code => {
601
+ const { name, label, codeVar = name.toUpperCase(), multi = false } = code;
602
+
603
+ if (multi) {
604
+ params.push(` {
605
+ label: t("${label}"),
606
+ name: "${name}",
607
+ type: SearchParamType.SELECT_MULTI,
608
+ width: 200,
609
+ options: ${codeVar}?.options ?? [],
610
+ checkAllItem: true,
611
+ }`);
612
+ } else {
613
+ params.push(` {
614
+ label: t("${label}"),
615
+ name: "${name}",
616
+ type: SearchParamType.SELECT,
617
+ width: 150,
618
+ options: ${codeVar}?.options ?? [],
619
+ showSearch: false,
620
+ allowClear: true,
621
+ }`);
622
+ }
623
+ });
624
+
625
+ // 검색어
626
+ if (config.search) {
627
+ const { textName = 'searchText' } = config.search;
628
+ params.push(` {
629
+ label: t("검색어"),
630
+ placeholder: t("검색어"),
631
+ name: "${textName}",
632
+ type: SearchParamType.INPUT,
633
+ }`);
634
+ }
635
+
636
+ // Y/N 필드
637
+ config.ynFields?.forEach(yn => {
638
+ const { name, label } = yn;
639
+ params.push(` {
640
+ label: t("${label}"),
641
+ name: "${name}",
642
+ type: SearchParamType.RADIO,
643
+ options: [
644
+ { label: t("전체"), value: "" },
645
+ { label: t("예"), value: "Y" },
646
+ { label: t("아니오"), value: "N" },
647
+ ],
648
+ }`);
649
+ });
650
+
651
+ return params.join(',\n');
652
+ }
653
+
654
+ /**
655
+ * SearchParams JSX 생성
656
+ */
657
+ function generateSearchParamsJsx(): string {
658
+ return `<PageSearchBar>
659
+ <SearchParams
660
+ form={searchForm}
661
+ params={params}
662
+ paramsValue={listRequestValue}
663
+ onChangeParamsValue={(value, changedValues) => setListRequestValue(value, changedValues)}
664
+ onSearch={handleSearch}
665
+ spinning={listSpinning}
666
+ />
667
+ </PageSearchBar>`;
668
+ }
669
+
670
+ /**
671
+ * 성공 메시지 생성
672
+ */
673
+ function generateSuccessMessage(
674
+ dtoPath: string | undefined,
675
+ config: SearchParamsConfig,
676
+ appPath: string | undefined
677
+ ): string {
678
+ let message = '# ✅ SearchParams 자동 생성 완료\n\n';
679
+
680
+ if (dtoPath) {
681
+ message += `## DTO 파일\n`;
682
+ message += `\`${dtoPath}\`\n\n`;
683
+ }
684
+
685
+ message += `## App.tsx 수정 완료\n`;
686
+ message += `\`${appPath}\`\n\n`;
687
+
688
+ message += `## 추가된 내용\n\n`;
689
+
690
+ if (config.dateRange) {
691
+ message += `- ✅ 날짜 범위 검색\n`;
692
+ }
693
+
694
+ config.codes?.forEach(code => {
695
+ message += `- ✅ 코드 선택: ${code.label} (${code.codeVar})\n`;
696
+ });
697
+
698
+ if (config.search) {
699
+ message += `- ✅ 검색어\n`;
700
+ }
701
+
702
+ config.ynFields?.forEach(yn => {
703
+ message += `- ✅ Y/N 필드: ${yn.label}\n`;
704
+ });
705
+
706
+ message += `\n## 추가된 항목\n`;
707
+ message += `- Import 문 (SearchParams, SearchParamType, DT_FORMAT, DtPickerPresetRender, useCodeStore)\n`;
708
+ message += `- CodeModel 인터페이스에 코드 추가\n`;
709
+ message += `- Store 상태 선언\n`;
710
+ message += `- Code 선언\n`;
711
+ message += `- params 배열\n`;
712
+ message += `- PageSearchBar 스타일\n`;
713
+ message += `- Form.useForm() 선언\n`;
714
+ message += `- PageSearchBar 컴포넌트\n`;
715
+
716
+ return message;
717
+ }