@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.
- package/CLAUDE.md +119 -0
- package/MCP_TOOL_PLAN.md +710 -0
- package/MCP_USAGE.md +914 -0
- package/README.md +168 -0
- package/REPOSITORY_CONVENTIONS.md +250 -0
- package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
- package/SEARCH_PARAMS_PLAN.md +2570 -0
- package/STORE_PATTERNS.md +1178 -0
- package/debug-dto.js +72 -0
- package/generate-banner-store.js +62 -0
- package/generation-plan.json +2176 -0
- package/generation-results.json +1817 -0
- package/package.json +45 -0
- package/scripts/batch-generate-all.js +159 -0
- package/scripts/batch-generate-mcp.js +329 -0
- package/scripts/batch-generate-stores-v2.js +272 -0
- package/scripts/batch-generate-stores.js +179 -0
- package/scripts/batch-plan.json +3810 -0
- package/scripts/batch-process.py +90 -0
- package/scripts/batch-regenerate.js +356 -0
- package/scripts/direct-generate.js +227 -0
- package/scripts/execute-batches.js +1911 -0
- package/scripts/generate-all-stores.js +144 -0
- package/scripts/generate-stores-mcp.js +161 -0
- package/scripts/generate-stores-v2.js +450 -0
- package/scripts/generate-stores-v3.js +412 -0
- package/scripts/generate-stores-v4.js +521 -0
- package/scripts/generate-stores.js +382 -0
- package/scripts/repos-to-process.json +1899 -0
- package/src/config/nh-layout-patterns.ts +166 -0
- package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
- package/src/docs/NH_STORE_PATTERNS.md +297 -0
- package/src/docs/README.md +216 -0
- package/src/docs/index.ts +28 -0
- package/src/docs/loader.ts +568 -0
- package/src/docs/patterns.json +419 -0
- package/src/docs/practical-examples.md +732 -0
- package/src/docs/quick-start.md +257 -0
- package/src/docs/requirements-analysis-guide.md +364 -0
- package/src/docs/rules.json +321 -0
- package/src/docs/store-pattern-analysis.md +664 -0
- package/src/docs/store-patterns-rules.md +1168 -0
- package/src/docs/store-patterns-usage-guide.md +1835 -0
- package/src/docs/troubleshooting.md +544 -0
- package/src/docs/type-selection-guide.md +572 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/src/features/store-features.ts +232 -0
- package/src/handlers/analyze-requirements.ts +403 -0
- package/src/handlers/analyze.ts +1373 -0
- package/src/handlers/generate-from-requirements.ts +250 -0
- package/src/handlers/generate-hook.ts +950 -0
- package/src/handlers/generate-interactive.ts +840 -0
- package/src/handlers/generate-listdatagrid.ts +521 -0
- package/src/handlers/generate-multi-stores.ts +577 -0
- package/src/handlers/generate-requirements-from-layout.ts +160 -0
- package/src/handlers/generate-search-params.ts +717 -0
- package/src/handlers/generate.ts +911 -0
- package/src/handlers/list-templates.ts +104 -0
- package/src/handlers/scan-metadata.ts +485 -0
- package/src/handlers/suggest-layout.ts +326 -0
- package/src/index.ts +959 -0
- package/src/prompts/search-params.md +793 -0
- package/src/templates/index.ts +107 -0
- package/src/templates/unified.ts +462 -0
- package/store-generation-error-patterns.md +225 -0
- package/test/useAgentStore.ts +136 -0
- package/test-server.js +78 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store 피처/태그 정의
|
|
3
|
+
* 각 피처는 State와 Action을 포함
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface StoreFeature {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
keywords: string[]; // 자연어 분석용 키워드
|
|
10
|
+
states: string[];
|
|
11
|
+
actions: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const FEATURES: Record<string, StoreFeature> = {
|
|
15
|
+
// 기본 기능
|
|
16
|
+
LIST: {
|
|
17
|
+
name: '리스트',
|
|
18
|
+
description: '기본 리스트 표시',
|
|
19
|
+
keywords: ['리스트', '목록', '목록보기', '조회', '표시', '나열'],
|
|
20
|
+
states: ['listData', 'listPage', 'listSpinning'],
|
|
21
|
+
actions: ['callListApi', 'changeListPage'],
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
SEARCH: {
|
|
25
|
+
name: '검색',
|
|
26
|
+
description: '검색 기능',
|
|
27
|
+
keywords: ['검색', '찾기', '필터', '서치'],
|
|
28
|
+
states: ['searchValue'],
|
|
29
|
+
actions: ['onSearch'],
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// 선택 기능
|
|
33
|
+
SELECTED_ITEM: {
|
|
34
|
+
name: '선택 아이템',
|
|
35
|
+
description: '선택된 항목 저장',
|
|
36
|
+
keywords: ['선택', '클릭', '선택된', '하나 선택', '단일 선택'],
|
|
37
|
+
states: ['selectedItem'],
|
|
38
|
+
actions: ['setSelectedItem'],
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
CHECKBOX: {
|
|
42
|
+
name: '체크박스',
|
|
43
|
+
description: '다중 선택 체크박스',
|
|
44
|
+
keywords: ['체크', '체크박스', '다중 선택', '여러개', '복수 선택', 'checkbox'],
|
|
45
|
+
states: ['checkedRowKeys'],
|
|
46
|
+
actions: ['setCheckedRowKeys'],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// 상세 영역
|
|
50
|
+
DETAIL_PANEL: {
|
|
51
|
+
name: '상세 영역',
|
|
52
|
+
description: '별도의 상세 표시 영역',
|
|
53
|
+
keywords: ['상세', '디테일', '상세보기', 'detail', '우측 상세', '오른쪽 상세'],
|
|
54
|
+
states: ['detail', 'detailSpinning'],
|
|
55
|
+
actions: ['setDetail'],
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
DETAIL_API: {
|
|
59
|
+
name: '상세 조회 API',
|
|
60
|
+
description: '상세 조회 API 호출',
|
|
61
|
+
keywords: ['상세조회', '상세 api', '디테일 조회', '상세 정보 가져오기'],
|
|
62
|
+
states: ['detailSpinning'],
|
|
63
|
+
actions: ['callDetailApi'],
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// CRUD 기능
|
|
67
|
+
SAVE: {
|
|
68
|
+
name: '저장',
|
|
69
|
+
description: '저장/수정 기능',
|
|
70
|
+
keywords: ['저장', '수정', '등록', '추가', 'create', 'update', 'save'],
|
|
71
|
+
states: ['saveRequestValue', 'saveSpinning'],
|
|
72
|
+
actions: ['callSaveApi'],
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
DELETE: {
|
|
76
|
+
name: '삭제',
|
|
77
|
+
description: '삭제 기능',
|
|
78
|
+
keywords: ['삭제', '제거', 'delete', 'remove'],
|
|
79
|
+
states: ['deleteSpinning'],
|
|
80
|
+
actions: ['callDeleteApi'],
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// UI 기능
|
|
84
|
+
MODAL: {
|
|
85
|
+
name: '모달',
|
|
86
|
+
description: '모달/팝업 창',
|
|
87
|
+
keywords: ['모달', '팝업', 'modal', 'popup', '다이얼로그', 'dialog'],
|
|
88
|
+
states: ['modalOpen'],
|
|
89
|
+
actions: ['setModalOpen'],
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
EXCEL_DOWNLOAD: {
|
|
93
|
+
name: '엑셀 다운로드',
|
|
94
|
+
description: '엑셀 다운로드 기능',
|
|
95
|
+
keywords: ['엑셀', 'excel', '다운로드', '내보내기', 'export'],
|
|
96
|
+
states: ['excelSpinning'],
|
|
97
|
+
actions: ['callExcelDownloadApi'],
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// 특수 패턴
|
|
101
|
+
TREE: {
|
|
102
|
+
name: '트리',
|
|
103
|
+
description: '트리 구조',
|
|
104
|
+
keywords: ['트리', 'tree', '계층', '폴더', '카테고리', '메뉴'],
|
|
105
|
+
states: ['treeData', 'treeSpinning', 'expandedKeys'],
|
|
106
|
+
actions: ['callTreeApi', 'setExpandedKeys'],
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
REORDER: {
|
|
110
|
+
name: '재정렬',
|
|
111
|
+
description: '드래그 앤 드롭 재정렬',
|
|
112
|
+
keywords: ['순서', '재정렬', '드래그', '정렬', '위치 변경', 'reorder', 'drag', 'drop'],
|
|
113
|
+
states: ['isReordered'],
|
|
114
|
+
actions: ['onDrop'],
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
DASHBOARD: {
|
|
118
|
+
name: '대시보드',
|
|
119
|
+
description: '대시보드 요약 정보',
|
|
120
|
+
keywords: ['대시보드', '요약', 'summary', '통계', '집계'],
|
|
121
|
+
states: ['summaryData', 'summarySpinning'],
|
|
122
|
+
actions: ['callSummaryApi'],
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
TAB: {
|
|
126
|
+
name: '탭 전환',
|
|
127
|
+
description: '탭 전환 기능',
|
|
128
|
+
keywords: ['탭', 'tab', '전환'],
|
|
129
|
+
states: ['activeTabKey'],
|
|
130
|
+
actions: ['setActiveTabKey'],
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Type별 피처 조합
|
|
136
|
+
*/
|
|
137
|
+
export const TYPE_FEATURES: Record<number, string[]> = {
|
|
138
|
+
1: ['LIST', 'SEARCH', 'SELECTED_ITEM', 'SAVE', 'CHECKBOX'],
|
|
139
|
+
2: ['LIST', 'SEARCH', 'SELECTED_ITEM', 'DETAIL_PANEL', 'MODAL', 'DELETE', 'CHECKBOX'],
|
|
140
|
+
3: ['TREE', 'SAVE'],
|
|
141
|
+
4: ['LIST', 'DASHBOARD', 'TAB'],
|
|
142
|
+
5: ['LIST'],
|
|
143
|
+
6: ['LIST', 'REORDER'],
|
|
144
|
+
7: ['LIST', 'SEARCH', 'SELECTED_ITEM', 'DETAIL_PANEL', 'MODAL', 'DELETE', 'SAVE', 'EXCEL_DOWNLOAD', 'CHECKBOX'],
|
|
145
|
+
8: ['LIST', 'SELECTED_ITEM', 'CHECKBOX', 'EXCEL_DOWNLOAD'],
|
|
146
|
+
9: ['LIST', 'SELECTED_ITEM', 'DETAIL_PANEL', 'MODAL', 'DETAIL_API'],
|
|
147
|
+
10: ['LIST', 'SELECTED_ITEM', 'EXCEL_DOWNLOAD'],
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Type 정보
|
|
152
|
+
*/
|
|
153
|
+
export interface TypeInfo {
|
|
154
|
+
type: number;
|
|
155
|
+
name: string;
|
|
156
|
+
description: string;
|
|
157
|
+
features: string[];
|
|
158
|
+
useCases: string[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const TYPE_INFOS: TypeInfo[] = [
|
|
162
|
+
{
|
|
163
|
+
type: 1,
|
|
164
|
+
name: '기본 리스트 + 상세',
|
|
165
|
+
description: '선택된 아이템의 상세 정보 표시, 저장/수정 지원',
|
|
166
|
+
features: TYPE_FEATURES[1],
|
|
167
|
+
useCases: ['회원 목록', '상품 목록', '쿠폰 목록'],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
type: 2,
|
|
171
|
+
name: '마스터-디테일 + 모달 + 삭제',
|
|
172
|
+
description: '왼쪽 리스트, 오른쪽 상세, 모달, 삭제 기능 (B2B 패턴)',
|
|
173
|
+
features: TYPE_FEATURES[2],
|
|
174
|
+
useCases: ['B2B 상품', '견적 상담', '구독 관리'],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 3,
|
|
178
|
+
name: '트리 기반',
|
|
179
|
+
description: '트리 구조 데이터, 펼치기/접기, 저장 지원',
|
|
180
|
+
features: TYPE_FEATURES[3],
|
|
181
|
+
useCases: ['카테고리 관리', '메뉴 관리', '조직도'],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 4,
|
|
185
|
+
name: '대시보드',
|
|
186
|
+
description: '여러 개의 리스트와 요약 정보, 탭 전환',
|
|
187
|
+
features: TYPE_FEATURES[4],
|
|
188
|
+
useCases: ['회원 대시보드', '주문 대시보드', '매출 대시보드'],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: 5,
|
|
192
|
+
name: '단순 리스트',
|
|
193
|
+
description: '최소한의 기능만 있는 단순 리스트',
|
|
194
|
+
features: TYPE_FEATURES[5],
|
|
195
|
+
useCases: ['참조 데이터 조회', '드롭다운 목록'],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 6,
|
|
199
|
+
name: '재정렬 가능 리스트',
|
|
200
|
+
description: '드래그 앤 드롭으로 순서 변경 가능',
|
|
201
|
+
features: TYPE_FEATURES[6],
|
|
202
|
+
useCases: ['팝업 메뉴', '배너 순서', '메뉴 순서'],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: 7,
|
|
206
|
+
name: '마스터-디테일 + 모달 + 삭제 + 엑셀',
|
|
207
|
+
description: '왼쪽 리스트, 오른쪽 상세, 모달, 삭제, 엑셀 다운로드',
|
|
208
|
+
features: TYPE_FEATURES[7],
|
|
209
|
+
useCases: ['B2B 견적 상담', '상담 관리', '주문 관리'],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 8,
|
|
213
|
+
name: '리스트 + 선택 + 엑셀 다운로드',
|
|
214
|
+
description: '선택 아이템, 체크박스, 엑셀 다운로드 (상세 없음)',
|
|
215
|
+
features: TYPE_FEATURES[8],
|
|
216
|
+
useCases: ['주문 전체 조회', '결제 조회', '배송 조회'],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: 9,
|
|
220
|
+
name: '마스터-디테일 + 모달 + 상세조회 API',
|
|
221
|
+
description: '왼쪽 리스트, 오른쪽 상세, 모달, 상세 조회 API 호출',
|
|
222
|
+
features: TYPE_FEATURES[9],
|
|
223
|
+
useCases: ['직배송 구독', '상담 내역', '주문 상세'],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
type: 10,
|
|
227
|
+
name: '마스터-디테일 + 엑셀 다운로드',
|
|
228
|
+
description: '선택된 행 표시, 엑셀 다운로드 (상세/모달 없음)',
|
|
229
|
+
features: TYPE_FEATURES[10],
|
|
230
|
+
useCases: ['상품 그룹 마스터', '분류 관리', '카테고리 마스터'],
|
|
231
|
+
},
|
|
232
|
+
];
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { FEATURES, TYPE_FEATURES, TYPE_INFOS, TypeInfo } from '../features/store-features.js';
|
|
2
|
+
import {
|
|
3
|
+
getStoreTypeInfo,
|
|
4
|
+
getAllFields,
|
|
5
|
+
suggestStoreType,
|
|
6
|
+
getContextualDocumentation
|
|
7
|
+
} from '../docs/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 요구사항 분석 결과
|
|
11
|
+
*/
|
|
12
|
+
export interface RequirementsAnalysis {
|
|
13
|
+
success: boolean;
|
|
14
|
+
requirements: string;
|
|
15
|
+
extractedFeatures: string[];
|
|
16
|
+
matchedTypes: TypeMatch[];
|
|
17
|
+
recommendedType?: TypeMatch;
|
|
18
|
+
canUseExistingType: boolean;
|
|
19
|
+
isComplexRequest: boolean;
|
|
20
|
+
contextualDocs?: string;
|
|
21
|
+
relevantDocs?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 타입 매칭 결과
|
|
26
|
+
*/
|
|
27
|
+
export interface TypeMatch {
|
|
28
|
+
type: number;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
matchScore: number;
|
|
32
|
+
matchedFeatures: string[];
|
|
33
|
+
missingFeatures: string[];
|
|
34
|
+
extraFeatures: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 자연어 요구사항을 분석하여 필요한 피처 추출
|
|
39
|
+
*/
|
|
40
|
+
export async function analyzeRequirements(args: {
|
|
41
|
+
requirements: string;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
content: Array<{ type: string; text: string }>;
|
|
44
|
+
}> {
|
|
45
|
+
const { requirements } = args;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// 1. 요구사항에서 피처 추출
|
|
49
|
+
const extractedFeatures = extractFeaturesFromRequirements(requirements);
|
|
50
|
+
|
|
51
|
+
// 2. 각 타입과 매칭 점수 계산
|
|
52
|
+
const matchedTypes = matchTypes(extractedFeatures);
|
|
53
|
+
|
|
54
|
+
// 3. 가장 적합한 타입 추천
|
|
55
|
+
const recommendedType = findBestMatch(matchedTypes);
|
|
56
|
+
|
|
57
|
+
// 4. 기존 타입 사용 가능 여부 판단 (매칭 점수 0.8 이상이면 사용 가능)
|
|
58
|
+
const canUseExistingType = recommendedType ? recommendedType.matchScore >= 0.8 : false;
|
|
59
|
+
|
|
60
|
+
// 5. 복잡한 요청 여부 판단 및 문서 컨텍스트 생성
|
|
61
|
+
const matchScore = recommendedType?.matchScore || 0;
|
|
62
|
+
const docsContext = getContextualDocumentation(
|
|
63
|
+
matchScore,
|
|
64
|
+
extractedFeatures,
|
|
65
|
+
recommendedType?.type
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const analysis: RequirementsAnalysis = {
|
|
69
|
+
success: true,
|
|
70
|
+
requirements,
|
|
71
|
+
extractedFeatures,
|
|
72
|
+
matchedTypes,
|
|
73
|
+
recommendedType,
|
|
74
|
+
canUseExistingType,
|
|
75
|
+
isComplexRequest: docsContext.isComplex,
|
|
76
|
+
contextualDocs: docsContext.isComplex ? docsContext.context : undefined,
|
|
77
|
+
relevantDocs: docsContext.relevantDocs,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: JSON.stringify(analysis, null, 2),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: 'text',
|
|
93
|
+
text: JSON.stringify({
|
|
94
|
+
success: false,
|
|
95
|
+
error: error instanceof Error ? error.message : String(error),
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 요구사항에서 피처 추출 (키워드 매칭)
|
|
105
|
+
*/
|
|
106
|
+
export function extractFeaturesFromRequirements(requirements: string): string[] {
|
|
107
|
+
const extracted: string[] = [];
|
|
108
|
+
const lowerRequirements = requirements.toLowerCase();
|
|
109
|
+
|
|
110
|
+
for (const [featureKey, feature] of Object.entries(FEATURES)) {
|
|
111
|
+
// 키워드 매칭
|
|
112
|
+
for (const keyword of feature.keywords) {
|
|
113
|
+
if (lowerRequirements.includes(keyword.toLowerCase())) {
|
|
114
|
+
if (!extracted.includes(featureKey)) {
|
|
115
|
+
extracted.push(featureKey);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 항상 LIST는 기본으로 포함 (명시적 언급이 없어도)
|
|
123
|
+
if (!extracted.includes('LIST')) {
|
|
124
|
+
extracted.unshift('LIST');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return extracted;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 추출된 피처와 각 타입을 매칭하여 점수 계산
|
|
132
|
+
*/
|
|
133
|
+
export function matchTypes(extractedFeatures: string[]): TypeMatch[] {
|
|
134
|
+
const matches: TypeMatch[] = [];
|
|
135
|
+
|
|
136
|
+
for (const typeInfo of TYPE_INFOS) {
|
|
137
|
+
const typeFeatures = TYPE_FEATURES[typeInfo.type] || [];
|
|
138
|
+
|
|
139
|
+
// 일치하는 피처
|
|
140
|
+
const matchedFeatures = extractedFeatures.filter((f) => typeFeatures.includes(f));
|
|
141
|
+
|
|
142
|
+
// 부족한 피처 (요구사항에는 있는데 타입에는 없는)
|
|
143
|
+
const missingFeatures = extractedFeatures.filter((f) => !typeFeatures.includes(f));
|
|
144
|
+
|
|
145
|
+
// 추가 피처 (타입에는 있는데 요구사항에는 없는)
|
|
146
|
+
const extraFeatures = typeFeatures.filter((f) => !extractedFeatures.includes(f));
|
|
147
|
+
|
|
148
|
+
// 매칭 점수 계산
|
|
149
|
+
// - 일치하는 피처: +1점
|
|
150
|
+
// - 부족한 피처: -0.5점
|
|
151
|
+
// - 추가 피처: -0.2점 (불필요한 기능이 있음)
|
|
152
|
+
let matchScore = 0;
|
|
153
|
+
matchScore += matchedFeatures.length * 1;
|
|
154
|
+
matchScore += missingFeatures.length * -0.5;
|
|
155
|
+
matchScore += extraFeatures.length * -0.1;
|
|
156
|
+
|
|
157
|
+
// 정규화 (0-1 사이)
|
|
158
|
+
matchScore = Math.max(0, Math.min(1, (matchScore + 1) / (extractedFeatures.length + 1)));
|
|
159
|
+
|
|
160
|
+
matches.push({
|
|
161
|
+
type: typeInfo.type,
|
|
162
|
+
name: typeInfo.name,
|
|
163
|
+
description: typeInfo.description,
|
|
164
|
+
matchScore,
|
|
165
|
+
matchedFeatures,
|
|
166
|
+
missingFeatures,
|
|
167
|
+
extraFeatures,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 매칭 점수순 정렬
|
|
172
|
+
return matches.sort((a, b) => b.matchScore - a.matchScore);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 가장 적합한 타입 찾기
|
|
177
|
+
*/
|
|
178
|
+
export function findBestMatch(matchedTypes: TypeMatch[]): TypeMatch | undefined {
|
|
179
|
+
if (matchedTypes.length === 0) return undefined;
|
|
180
|
+
|
|
181
|
+
const best = matchedTypes[0];
|
|
182
|
+
|
|
183
|
+
// 최소 매칭 기준: 적어도 하나의 피처는 일치해야 함
|
|
184
|
+
if (best.matchedFeatures.length === 0) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return best;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 동적 Store 생성을 위한 피처 조합 추천
|
|
193
|
+
* 기존 타입으로 커버되지 않을 때 사용
|
|
194
|
+
*/
|
|
195
|
+
export function suggestFeatureCombination(extractedFeatures: string[]): {
|
|
196
|
+
features: string[];
|
|
197
|
+
recommendedTemplate: string;
|
|
198
|
+
} {
|
|
199
|
+
// 기본 템플릿 (Type 1)
|
|
200
|
+
let recommendedType = 1;
|
|
201
|
+
let bestScore = 0;
|
|
202
|
+
|
|
203
|
+
// 가장 가까운 타입 찾기
|
|
204
|
+
for (const typeInfo of TYPE_INFOS) {
|
|
205
|
+
const typeFeatures = TYPE_FEATURES[typeInfo.type] || [];
|
|
206
|
+
const intersection = extractedFeatures.filter((f) => typeFeatures.includes(f));
|
|
207
|
+
const score = intersection.length;
|
|
208
|
+
|
|
209
|
+
if (score > bestScore) {
|
|
210
|
+
bestScore = score;
|
|
211
|
+
recommendedType = typeInfo.type;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
features: extractedFeatures,
|
|
217
|
+
recommendedTemplate: `type${recommendedType}`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* docs/patterns.json 기반 정밀 타입 매칭
|
|
223
|
+
* 기존 키워드 매칭보다 더 정확한 타입 추천 제공
|
|
224
|
+
*/
|
|
225
|
+
export function matchTypesWithJsonPatterns(extractedFeatures: string[]): TypeMatch[] {
|
|
226
|
+
const matches: TypeMatch[] = [];
|
|
227
|
+
|
|
228
|
+
// types 1-12 순회
|
|
229
|
+
for (let typeNum = 1; typeNum <= 12; typeNum++) {
|
|
230
|
+
const typeInfo = getStoreTypeInfo(typeNum);
|
|
231
|
+
|
|
232
|
+
if (!typeInfo) {
|
|
233
|
+
// 패턴 정보가 없는 타�은 기본 TYPE_INFOS 사용
|
|
234
|
+
const baseTypeInfo = TYPE_INFOS.find(t => t.type === typeNum);
|
|
235
|
+
if (baseTypeInfo) {
|
|
236
|
+
const typeFeatures = TYPE_FEATURES[baseTypeInfo.type] || [];
|
|
237
|
+
const matchedFeatures = extractedFeatures.filter((f) => typeFeatures.includes(f));
|
|
238
|
+
const missingFeatures = extractedFeatures.filter((f) => !typeFeatures.includes(f));
|
|
239
|
+
const extraFeatures = typeFeatures.filter((f) => !extractedFeatures.includes(f));
|
|
240
|
+
|
|
241
|
+
let matchScore = matchedFeatures.length * 1;
|
|
242
|
+
matchScore += missingFeatures.length * -0.5;
|
|
243
|
+
matchScore += extraFeatures.length * -0.1;
|
|
244
|
+
matchScore = Math.max(0, Math.min(1, (matchScore + 1) / (extractedFeatures.length + 1)));
|
|
245
|
+
|
|
246
|
+
matches.push({
|
|
247
|
+
type: baseTypeInfo.type,
|
|
248
|
+
name: baseTypeInfo.name,
|
|
249
|
+
description: baseTypeInfo.description,
|
|
250
|
+
matchScore,
|
|
251
|
+
matchedFeatures,
|
|
252
|
+
missingFeatures,
|
|
253
|
+
extraFeatures,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// JSON 패턴에서 필드 정보 추출
|
|
260
|
+
const allFields = [
|
|
261
|
+
...typeInfo.metadata.required,
|
|
262
|
+
...typeInfo.metadata.optional,
|
|
263
|
+
...typeInfo.states.required,
|
|
264
|
+
...typeInfo.states.optional,
|
|
265
|
+
...typeInfo.actions.required,
|
|
266
|
+
...typeInfo.actions.optional,
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
// 필드 이름을 피처 키워드와 매칭
|
|
270
|
+
const matchedFeatures: string[] = [];
|
|
271
|
+
const allFieldsLower = allFields.map(f => f.toLowerCase());
|
|
272
|
+
|
|
273
|
+
for (const feature of extractedFeatures) {
|
|
274
|
+
const featureLower = feature.toLowerCase();
|
|
275
|
+
// 직접 필드명 매칭
|
|
276
|
+
if (allFieldsLower.some(field => field.includes(featureLower))) {
|
|
277
|
+
matchedFeatures.push(feature);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 특수 매칭 규칙
|
|
282
|
+
switch (feature) {
|
|
283
|
+
case 'LIST':
|
|
284
|
+
if (allFieldsLower.some(f => f.includes('list'))) matchedFeatures.push(feature);
|
|
285
|
+
break;
|
|
286
|
+
case 'SEARCH':
|
|
287
|
+
if (allFieldsLower.some(f => f.includes('request'))) matchedFeatures.push(feature);
|
|
288
|
+
break;
|
|
289
|
+
case 'DETAIL':
|
|
290
|
+
case 'DETAIL_PANEL':
|
|
291
|
+
if (allFieldsLower.some(f => f.includes('detail') || f.includes('save'))) matchedFeatures.push(feature);
|
|
292
|
+
break;
|
|
293
|
+
case 'MODAL':
|
|
294
|
+
if (allFieldsLower.some(f => f.includes('modal'))) matchedFeatures.push(feature);
|
|
295
|
+
break;
|
|
296
|
+
case 'SAVE':
|
|
297
|
+
if (allFieldsLower.some(f => f.includes('save'))) matchedFeatures.push(feature);
|
|
298
|
+
break;
|
|
299
|
+
case 'DELETE':
|
|
300
|
+
if (allFieldsLower.some(f => f.includes('delete'))) matchedFeatures.push(feature);
|
|
301
|
+
break;
|
|
302
|
+
case 'EXCEL':
|
|
303
|
+
case 'EXCEL_DOWNLOAD':
|
|
304
|
+
if (allFieldsLower.some(f => f.includes('excel'))) matchedFeatures.push(feature);
|
|
305
|
+
break;
|
|
306
|
+
case 'CHECKBOX':
|
|
307
|
+
if (allFieldsLower.some(f => f.includes('checked') || f.includes('check'))) matchedFeatures.push(feature);
|
|
308
|
+
break;
|
|
309
|
+
case 'TREE':
|
|
310
|
+
if (allFieldsLower.some(f => f.includes('tree'))) matchedFeatures.push(feature);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const missingFeatures = extractedFeatures.filter((f) => !matchedFeatures.includes(f));
|
|
316
|
+
const extraFeatures: string[] = [];
|
|
317
|
+
|
|
318
|
+
// 매칭 점수 계산 (JSON 기반 가중치)
|
|
319
|
+
let matchScore = 0;
|
|
320
|
+
matchScore += matchedFeatures.length * 1.2; // JSON 기반 매칭에 더 높은 가중치
|
|
321
|
+
matchScore += missingFeatures.length * -0.3;
|
|
322
|
+
matchScore = Math.max(0, Math.min(1, (matchScore + 1) / (extractedFeatures.length + 1)));
|
|
323
|
+
|
|
324
|
+
matches.push({
|
|
325
|
+
type: typeNum,
|
|
326
|
+
name: typeInfo.name || `Type ${typeNum}`,
|
|
327
|
+
description: typeInfo.description || '',
|
|
328
|
+
matchScore,
|
|
329
|
+
matchedFeatures,
|
|
330
|
+
missingFeatures,
|
|
331
|
+
extraFeatures,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return matches.sort((a, b) => b.matchScore - a.matchScore);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 요구사항으로부터 최적의 Store 타입 추천 (JSON 패턴 활용)
|
|
340
|
+
*/
|
|
341
|
+
export function recommendStoreTypeFromRequirements(requirements: string): {
|
|
342
|
+
type: number;
|
|
343
|
+
name: string;
|
|
344
|
+
description: string;
|
|
345
|
+
confidence: number;
|
|
346
|
+
} {
|
|
347
|
+
const extractedFeatures = extractFeaturesFromRequirements(requirements);
|
|
348
|
+
const matches = matchTypesWithJsonPatterns(extractedFeatures);
|
|
349
|
+
|
|
350
|
+
if (matches.length === 0 || matches[0].matchedFeatures.length === 0) {
|
|
351
|
+
return {
|
|
352
|
+
type: 1,
|
|
353
|
+
name: 'Basic List',
|
|
354
|
+
description: '단순 조회 페이지',
|
|
355
|
+
confidence: 0.5,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const best = matches[0];
|
|
360
|
+
return {
|
|
361
|
+
type: best.type,
|
|
362
|
+
name: best.name,
|
|
363
|
+
description: best.description,
|
|
364
|
+
confidence: best.matchScore,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 특정 Store 타입에 필요한 필드 정보 반환
|
|
370
|
+
*/
|
|
371
|
+
export function getStoreTypeFields(type: number): {
|
|
372
|
+
metadata: { required: string[]; optional: string[] };
|
|
373
|
+
states: { required: string[]; optional: string[] };
|
|
374
|
+
actions: { required: string[]; optional: string[] };
|
|
375
|
+
subscribeSelector: string[];
|
|
376
|
+
} {
|
|
377
|
+
const typeInfo = getStoreTypeInfo(type);
|
|
378
|
+
|
|
379
|
+
if (!typeInfo) {
|
|
380
|
+
return {
|
|
381
|
+
metadata: { required: [], optional: [] },
|
|
382
|
+
states: { required: [], optional: [] },
|
|
383
|
+
actions: { required: [], optional: [] },
|
|
384
|
+
subscribeSelector: [],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
metadata: {
|
|
390
|
+
required: typeInfo.metadata.required || [],
|
|
391
|
+
optional: typeInfo.metadata.optional || [],
|
|
392
|
+
},
|
|
393
|
+
states: {
|
|
394
|
+
required: typeInfo.states.required || [],
|
|
395
|
+
optional: typeInfo.states.optional || [],
|
|
396
|
+
},
|
|
397
|
+
actions: {
|
|
398
|
+
required: typeInfo.actions.required || [],
|
|
399
|
+
optional: typeInfo.actions.optional || [],
|
|
400
|
+
},
|
|
401
|
+
subscribeSelector: typeInfo.subscribePattern?.selector || [],
|
|
402
|
+
};
|
|
403
|
+
}
|