@team-semicolon/semo-cli 3.13.1 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database.d.ts +84 -0
- package/dist/database.js +727 -0
- package/dist/index.js +209 -58
- package/package.json +7 -6
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEMO CLI - PostgreSQL 데이터베이스 클라이언트
|
|
3
|
+
*
|
|
4
|
+
* 팀 코어 PostgreSQL에서 스킬, 커맨드, 에이전트 정보를 조회합니다.
|
|
5
|
+
* DB 연결 실패 시 하드코딩된 폴백 데이터를 사용합니다.
|
|
6
|
+
*
|
|
7
|
+
* v3.14.0: Supabase → PostgreSQL 전환
|
|
8
|
+
* - semo-system 의존성 제거
|
|
9
|
+
* - 문서 내용을 DB에서 직접 조회
|
|
10
|
+
*/
|
|
11
|
+
export interface Skill {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
display_name: string;
|
|
15
|
+
description: string | null;
|
|
16
|
+
content: string;
|
|
17
|
+
category: string;
|
|
18
|
+
package: string;
|
|
19
|
+
is_active: boolean;
|
|
20
|
+
is_required: boolean;
|
|
21
|
+
install_order: number;
|
|
22
|
+
version: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SemoCommand {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
folder: string;
|
|
28
|
+
content: string;
|
|
29
|
+
description: string | null;
|
|
30
|
+
is_active: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface Agent {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
display_name: string;
|
|
36
|
+
content: string;
|
|
37
|
+
package: string;
|
|
38
|
+
is_active: boolean;
|
|
39
|
+
install_order: number;
|
|
40
|
+
}
|
|
41
|
+
export interface Package {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
display_name: string;
|
|
45
|
+
description: string | null;
|
|
46
|
+
layer: string;
|
|
47
|
+
package_type: string;
|
|
48
|
+
version: string;
|
|
49
|
+
is_active: boolean;
|
|
50
|
+
is_required: boolean;
|
|
51
|
+
install_order: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 활성 스킬 목록 조회
|
|
55
|
+
*/
|
|
56
|
+
export declare function getActiveSkills(): Promise<Skill[]>;
|
|
57
|
+
/**
|
|
58
|
+
* 스킬 이름 목록만 조회
|
|
59
|
+
*/
|
|
60
|
+
export declare function getActiveSkillNames(): Promise<string[]>;
|
|
61
|
+
/**
|
|
62
|
+
* 커맨드 목록 조회
|
|
63
|
+
*/
|
|
64
|
+
export declare function getCommands(): Promise<SemoCommand[]>;
|
|
65
|
+
/**
|
|
66
|
+
* 에이전트 목록 조회
|
|
67
|
+
*/
|
|
68
|
+
export declare function getAgents(): Promise<Agent[]>;
|
|
69
|
+
/**
|
|
70
|
+
* 패키지 목록 조회
|
|
71
|
+
*/
|
|
72
|
+
export declare function getPackages(layer?: string): Promise<Package[]>;
|
|
73
|
+
/**
|
|
74
|
+
* 카테고리별 스킬 개수 조회
|
|
75
|
+
*/
|
|
76
|
+
export declare function getSkillCountByCategory(): Promise<Record<string, number>>;
|
|
77
|
+
/**
|
|
78
|
+
* DB 연결 종료
|
|
79
|
+
*/
|
|
80
|
+
export declare function closeConnection(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* DB 연결 상태 확인
|
|
83
|
+
*/
|
|
84
|
+
export declare function isDbConnected(): Promise<boolean>;
|
package/dist/database.js
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SEMO CLI - PostgreSQL 데이터베이스 클라이언트
|
|
4
|
+
*
|
|
5
|
+
* 팀 코어 PostgreSQL에서 스킬, 커맨드, 에이전트 정보를 조회합니다.
|
|
6
|
+
* DB 연결 실패 시 하드코딩된 폴백 데이터를 사용합니다.
|
|
7
|
+
*
|
|
8
|
+
* v3.14.0: Supabase → PostgreSQL 전환
|
|
9
|
+
* - semo-system 의존성 제거
|
|
10
|
+
* - 문서 내용을 DB에서 직접 조회
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.getActiveSkills = getActiveSkills;
|
|
14
|
+
exports.getActiveSkillNames = getActiveSkillNames;
|
|
15
|
+
exports.getCommands = getCommands;
|
|
16
|
+
exports.getAgents = getAgents;
|
|
17
|
+
exports.getPackages = getPackages;
|
|
18
|
+
exports.getSkillCountByCategory = getSkillCountByCategory;
|
|
19
|
+
exports.closeConnection = closeConnection;
|
|
20
|
+
exports.isDbConnected = isDbConnected;
|
|
21
|
+
const pg_1 = require("pg");
|
|
22
|
+
// PostgreSQL 연결 정보 (팀 코어)
|
|
23
|
+
const DB_CONFIG = {
|
|
24
|
+
host: process.env.SEMO_DB_HOST || "3.38.162.21",
|
|
25
|
+
port: parseInt(process.env.SEMO_DB_PORT || "5432"),
|
|
26
|
+
user: process.env.SEMO_DB_USER || "app",
|
|
27
|
+
password: process.env.SEMO_DB_PASSWORD || "ProductionPassword2024!@#",
|
|
28
|
+
database: process.env.SEMO_DB_NAME || "appdb",
|
|
29
|
+
ssl: false,
|
|
30
|
+
connectionTimeoutMillis: 5000,
|
|
31
|
+
idleTimeoutMillis: 30000,
|
|
32
|
+
};
|
|
33
|
+
// PostgreSQL Pool (싱글톤)
|
|
34
|
+
let pool = null;
|
|
35
|
+
function getPool() {
|
|
36
|
+
if (!pool) {
|
|
37
|
+
pool = new pg_1.Pool(DB_CONFIG);
|
|
38
|
+
}
|
|
39
|
+
return pool;
|
|
40
|
+
}
|
|
41
|
+
// DB 연결 가능 여부 확인
|
|
42
|
+
let dbAvailable = null;
|
|
43
|
+
async function checkDbConnection() {
|
|
44
|
+
if (dbAvailable !== null)
|
|
45
|
+
return dbAvailable;
|
|
46
|
+
try {
|
|
47
|
+
const client = await getPool().connect();
|
|
48
|
+
await client.query("SELECT 1");
|
|
49
|
+
client.release();
|
|
50
|
+
dbAvailable = true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
dbAvailable = false;
|
|
54
|
+
}
|
|
55
|
+
return dbAvailable;
|
|
56
|
+
}
|
|
57
|
+
// ============================================================
|
|
58
|
+
// 폴백 데이터 (DB 연결 실패 시 사용)
|
|
59
|
+
// ============================================================
|
|
60
|
+
const FALLBACK_SKILLS = [
|
|
61
|
+
// Workflow Management (3개)
|
|
62
|
+
{
|
|
63
|
+
id: "sk-1",
|
|
64
|
+
name: "workflow-start",
|
|
65
|
+
display_name: "워크플로우 시작",
|
|
66
|
+
description: "워크플로우 인스턴스 생성 및 시작",
|
|
67
|
+
content: `---
|
|
68
|
+
name: workflow-start
|
|
69
|
+
description: 워크플로우 인스턴스 생성 및 시작
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
# workflow-start
|
|
73
|
+
|
|
74
|
+
워크플로우 인스턴스를 생성하고 시작합니다.
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
\`\`\`
|
|
79
|
+
skill:workflow-start workflow_command="greenfield"
|
|
80
|
+
\`\`\`
|
|
81
|
+
`,
|
|
82
|
+
category: "workflow",
|
|
83
|
+
package: "core",
|
|
84
|
+
is_active: true,
|
|
85
|
+
is_required: true,
|
|
86
|
+
install_order: 1,
|
|
87
|
+
version: "1.0.0",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "sk-2",
|
|
91
|
+
name: "workflow-progress",
|
|
92
|
+
display_name: "워크플로우 진행",
|
|
93
|
+
description: "워크플로우 진행 상황 조회",
|
|
94
|
+
content: `---
|
|
95
|
+
name: workflow-progress
|
|
96
|
+
description: 워크플로우 진행 상황 조회
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
# workflow-progress
|
|
100
|
+
|
|
101
|
+
진행 중인 워크플로우의 현재 상태를 조회합니다.
|
|
102
|
+
`,
|
|
103
|
+
category: "workflow",
|
|
104
|
+
package: "core",
|
|
105
|
+
is_active: true,
|
|
106
|
+
is_required: true,
|
|
107
|
+
install_order: 2,
|
|
108
|
+
version: "1.0.0",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "sk-3",
|
|
112
|
+
name: "workflow-resume",
|
|
113
|
+
display_name: "워크플로우 재개",
|
|
114
|
+
description: "중단된 워크플로우 재개",
|
|
115
|
+
content: `---
|
|
116
|
+
name: workflow-resume
|
|
117
|
+
description: 중단된 워크플로우 재개
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
# workflow-resume
|
|
121
|
+
|
|
122
|
+
중단된 워크플로우를 재개합니다.
|
|
123
|
+
`,
|
|
124
|
+
category: "workflow",
|
|
125
|
+
package: "core",
|
|
126
|
+
is_active: true,
|
|
127
|
+
is_required: true,
|
|
128
|
+
install_order: 3,
|
|
129
|
+
version: "1.0.0",
|
|
130
|
+
},
|
|
131
|
+
// Discovery (1개)
|
|
132
|
+
{
|
|
133
|
+
id: "sk-10",
|
|
134
|
+
name: "ideate",
|
|
135
|
+
display_name: "아이디에이션",
|
|
136
|
+
description: "아이디어 발굴 및 분석",
|
|
137
|
+
content: `---
|
|
138
|
+
name: ideate
|
|
139
|
+
description: 아이디어 발굴 및 분석
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
# ideate
|
|
143
|
+
|
|
144
|
+
새로운 아이디어를 발굴하고 분석합니다.
|
|
145
|
+
`,
|
|
146
|
+
category: "discovery",
|
|
147
|
+
package: "core",
|
|
148
|
+
is_active: true,
|
|
149
|
+
is_required: true,
|
|
150
|
+
install_order: 10,
|
|
151
|
+
version: "1.0.0",
|
|
152
|
+
},
|
|
153
|
+
// Planning (3개)
|
|
154
|
+
{
|
|
155
|
+
id: "sk-20",
|
|
156
|
+
name: "create-epic",
|
|
157
|
+
display_name: "Epic 생성",
|
|
158
|
+
description: "Epic 이슈 생성",
|
|
159
|
+
content: `---
|
|
160
|
+
name: create-epic
|
|
161
|
+
description: Epic 이슈 생성
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
# create-epic
|
|
165
|
+
|
|
166
|
+
Epic 이슈를 생성합니다.
|
|
167
|
+
`,
|
|
168
|
+
category: "planning",
|
|
169
|
+
package: "core",
|
|
170
|
+
is_active: true,
|
|
171
|
+
is_required: true,
|
|
172
|
+
install_order: 20,
|
|
173
|
+
version: "1.0.0",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "sk-21",
|
|
177
|
+
name: "design-user-flow",
|
|
178
|
+
display_name: "사용자 흐름 설계",
|
|
179
|
+
description: "UX 사용자 흐름 다이어그램 설계",
|
|
180
|
+
content: `---
|
|
181
|
+
name: design-user-flow
|
|
182
|
+
description: UX 사용자 흐름 다이어그램 설계
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# design-user-flow
|
|
186
|
+
|
|
187
|
+
사용자 흐름을 설계합니다.
|
|
188
|
+
`,
|
|
189
|
+
category: "planning",
|
|
190
|
+
package: "core",
|
|
191
|
+
is_active: true,
|
|
192
|
+
is_required: true,
|
|
193
|
+
install_order: 21,
|
|
194
|
+
version: "1.0.0",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "sk-22",
|
|
198
|
+
name: "generate-mockup",
|
|
199
|
+
display_name: "목업 생성",
|
|
200
|
+
description: "UI 목업 생성",
|
|
201
|
+
content: `---
|
|
202
|
+
name: generate-mockup
|
|
203
|
+
description: UI 목업 생성
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
# generate-mockup
|
|
207
|
+
|
|
208
|
+
UI 목업을 생성합니다.
|
|
209
|
+
`,
|
|
210
|
+
category: "planning",
|
|
211
|
+
package: "core",
|
|
212
|
+
is_active: true,
|
|
213
|
+
is_required: true,
|
|
214
|
+
install_order: 22,
|
|
215
|
+
version: "1.0.0",
|
|
216
|
+
},
|
|
217
|
+
// Solutioning (4개)
|
|
218
|
+
{
|
|
219
|
+
id: "sk-30",
|
|
220
|
+
name: "scaffold-domain",
|
|
221
|
+
display_name: "도메인 스캐폴딩",
|
|
222
|
+
description: "DDD 4-layer 도메인 구조 생성",
|
|
223
|
+
content: `---
|
|
224
|
+
name: scaffold-domain
|
|
225
|
+
description: DDD 4-layer 도메인 구조 생성
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
# scaffold-domain
|
|
229
|
+
|
|
230
|
+
DDD 4-layer 도메인 구조를 생성합니다.
|
|
231
|
+
`,
|
|
232
|
+
category: "solutioning",
|
|
233
|
+
package: "core",
|
|
234
|
+
is_active: true,
|
|
235
|
+
is_required: true,
|
|
236
|
+
install_order: 30,
|
|
237
|
+
version: "1.0.0",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "sk-31",
|
|
241
|
+
name: "validate-architecture",
|
|
242
|
+
display_name: "아키텍처 검증",
|
|
243
|
+
description: "DDD 4-layer 아키텍처 준수 검증",
|
|
244
|
+
content: `---
|
|
245
|
+
name: validate-architecture
|
|
246
|
+
description: DDD 4-layer 아키텍처 준수 검증
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
# validate-architecture
|
|
250
|
+
|
|
251
|
+
아키텍처 준수 여부를 검증합니다.
|
|
252
|
+
`,
|
|
253
|
+
category: "solutioning",
|
|
254
|
+
package: "core",
|
|
255
|
+
is_active: true,
|
|
256
|
+
is_required: true,
|
|
257
|
+
install_order: 31,
|
|
258
|
+
version: "1.0.0",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "sk-32",
|
|
262
|
+
name: "generate-spec",
|
|
263
|
+
display_name: "명세 생성",
|
|
264
|
+
description: "Speckit 워크플로우 통합 실행",
|
|
265
|
+
content: `---
|
|
266
|
+
name: generate-spec
|
|
267
|
+
description: Speckit 워크플로우 통합 실행
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
# generate-spec
|
|
271
|
+
|
|
272
|
+
명세 문서를 생성합니다.
|
|
273
|
+
`,
|
|
274
|
+
category: "solutioning",
|
|
275
|
+
package: "core",
|
|
276
|
+
is_active: true,
|
|
277
|
+
is_required: true,
|
|
278
|
+
install_order: 32,
|
|
279
|
+
version: "1.0.0",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "sk-33",
|
|
283
|
+
name: "design-tests",
|
|
284
|
+
display_name: "테스트 설계",
|
|
285
|
+
description: "구현 전 테스트 케이스 설계 (TDD)",
|
|
286
|
+
content: `---
|
|
287
|
+
name: design-tests
|
|
288
|
+
description: 구현 전 테스트 케이스 설계 (TDD)
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
# design-tests
|
|
292
|
+
|
|
293
|
+
테스트 케이스를 설계합니다.
|
|
294
|
+
`,
|
|
295
|
+
category: "solutioning",
|
|
296
|
+
package: "core",
|
|
297
|
+
is_active: true,
|
|
298
|
+
is_required: true,
|
|
299
|
+
install_order: 33,
|
|
300
|
+
version: "1.0.0",
|
|
301
|
+
},
|
|
302
|
+
// Implementation (6개)
|
|
303
|
+
{
|
|
304
|
+
id: "sk-40",
|
|
305
|
+
name: "create-sprint",
|
|
306
|
+
display_name: "스프린트 생성",
|
|
307
|
+
description: "Sprint 목표 설정 및 시작",
|
|
308
|
+
content: `---
|
|
309
|
+
name: create-sprint
|
|
310
|
+
description: Sprint 목표 설정 및 시작
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
# create-sprint
|
|
314
|
+
|
|
315
|
+
스프린트를 생성하고 시작합니다.
|
|
316
|
+
`,
|
|
317
|
+
category: "implementation",
|
|
318
|
+
package: "core",
|
|
319
|
+
is_active: true,
|
|
320
|
+
is_required: true,
|
|
321
|
+
install_order: 40,
|
|
322
|
+
version: "1.0.0",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: "sk-41",
|
|
326
|
+
name: "start-task",
|
|
327
|
+
display_name: "태스크 시작",
|
|
328
|
+
description: "작업 시작 (이슈 상태 변경, 브랜치 생성)",
|
|
329
|
+
content: `---
|
|
330
|
+
name: start-task
|
|
331
|
+
description: 작업 시작 (이슈 상태 변경, 브랜치 생성)
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
# start-task
|
|
335
|
+
|
|
336
|
+
태스크를 시작합니다.
|
|
337
|
+
`,
|
|
338
|
+
category: "implementation",
|
|
339
|
+
package: "core",
|
|
340
|
+
is_active: true,
|
|
341
|
+
is_required: true,
|
|
342
|
+
install_order: 41,
|
|
343
|
+
version: "1.0.0",
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "sk-42",
|
|
347
|
+
name: "review-task",
|
|
348
|
+
display_name: "태스크 리뷰",
|
|
349
|
+
description: "태스크 이슈 기반 구현 완료 리뷰",
|
|
350
|
+
content: `---
|
|
351
|
+
name: review-task
|
|
352
|
+
description: 태스크 이슈 기반 구현 완료 리뷰
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
# review-task
|
|
356
|
+
|
|
357
|
+
태스크 구현을 리뷰합니다.
|
|
358
|
+
`,
|
|
359
|
+
category: "implementation",
|
|
360
|
+
package: "core",
|
|
361
|
+
is_active: true,
|
|
362
|
+
is_required: true,
|
|
363
|
+
install_order: 42,
|
|
364
|
+
version: "1.0.0",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: "sk-43",
|
|
368
|
+
name: "write-code",
|
|
369
|
+
display_name: "코드 작성",
|
|
370
|
+
description: "코드 작성, 수정, 구현",
|
|
371
|
+
content: `---
|
|
372
|
+
name: write-code
|
|
373
|
+
description: 코드 작성, 수정, 구현
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
# write-code
|
|
377
|
+
|
|
378
|
+
코드를 작성합니다.
|
|
379
|
+
`,
|
|
380
|
+
category: "implementation",
|
|
381
|
+
package: "core",
|
|
382
|
+
is_active: true,
|
|
383
|
+
is_required: true,
|
|
384
|
+
install_order: 43,
|
|
385
|
+
version: "1.0.0",
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "sk-44",
|
|
389
|
+
name: "run-code-review",
|
|
390
|
+
display_name: "코드 리뷰",
|
|
391
|
+
description: "프로젝트 통합 코드 리뷰",
|
|
392
|
+
content: `---
|
|
393
|
+
name: run-code-review
|
|
394
|
+
description: 프로젝트 통합 코드 리뷰
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
# run-code-review
|
|
398
|
+
|
|
399
|
+
코드 리뷰를 실행합니다.
|
|
400
|
+
`,
|
|
401
|
+
category: "implementation",
|
|
402
|
+
package: "core",
|
|
403
|
+
is_active: true,
|
|
404
|
+
is_required: true,
|
|
405
|
+
install_order: 44,
|
|
406
|
+
version: "1.0.0",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: "sk-45",
|
|
410
|
+
name: "close-sprint",
|
|
411
|
+
display_name: "스프린트 종료",
|
|
412
|
+
description: "Sprint 종료 및 회고 정리",
|
|
413
|
+
content: `---
|
|
414
|
+
name: close-sprint
|
|
415
|
+
description: Sprint 종료 및 회고 정리
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
# close-sprint
|
|
419
|
+
|
|
420
|
+
스프린트를 종료합니다.
|
|
421
|
+
`,
|
|
422
|
+
category: "implementation",
|
|
423
|
+
package: "core",
|
|
424
|
+
is_active: true,
|
|
425
|
+
is_required: true,
|
|
426
|
+
install_order: 45,
|
|
427
|
+
version: "1.0.0",
|
|
428
|
+
},
|
|
429
|
+
// Supporting (2개)
|
|
430
|
+
{
|
|
431
|
+
id: "sk-50",
|
|
432
|
+
name: "git-workflow",
|
|
433
|
+
display_name: "Git 워크플로우",
|
|
434
|
+
description: "Git 커밋/푸시/PR 자동화",
|
|
435
|
+
content: `---
|
|
436
|
+
name: git-workflow
|
|
437
|
+
description: Git 커밋/푸시/PR 자동화
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
# git-workflow
|
|
441
|
+
|
|
442
|
+
Git 워크플로우를 자동화합니다.
|
|
443
|
+
`,
|
|
444
|
+
category: "supporting",
|
|
445
|
+
package: "core",
|
|
446
|
+
is_active: true,
|
|
447
|
+
is_required: true,
|
|
448
|
+
install_order: 50,
|
|
449
|
+
version: "1.0.0",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: "sk-51",
|
|
453
|
+
name: "notify-slack",
|
|
454
|
+
display_name: "Slack 알림",
|
|
455
|
+
description: "Slack 채널에 메시지 전송",
|
|
456
|
+
content: `---
|
|
457
|
+
name: notify-slack
|
|
458
|
+
description: Slack 채널에 메시지 전송
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
# notify-slack
|
|
462
|
+
|
|
463
|
+
Slack에 알림을 전송합니다.
|
|
464
|
+
`,
|
|
465
|
+
category: "supporting",
|
|
466
|
+
package: "core",
|
|
467
|
+
is_active: true,
|
|
468
|
+
is_required: true,
|
|
469
|
+
install_order: 51,
|
|
470
|
+
version: "1.0.0",
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
const FALLBACK_COMMANDS = [
|
|
474
|
+
{
|
|
475
|
+
id: "cmd-1",
|
|
476
|
+
name: "help",
|
|
477
|
+
folder: "SEMO",
|
|
478
|
+
content: `# /SEMO:help
|
|
479
|
+
|
|
480
|
+
SEMO 도움말을 표시합니다.
|
|
481
|
+
|
|
482
|
+
## Usage
|
|
483
|
+
|
|
484
|
+
\`\`\`
|
|
485
|
+
/SEMO:help
|
|
486
|
+
\`\`\`
|
|
487
|
+
`,
|
|
488
|
+
description: "SEMO 도움말",
|
|
489
|
+
is_active: true,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: "cmd-2",
|
|
493
|
+
name: "dry-run",
|
|
494
|
+
folder: "SEMO",
|
|
495
|
+
content: `# /SEMO:dry-run
|
|
496
|
+
|
|
497
|
+
명령을 실행하지 않고 라우팅 결과만 확인합니다.
|
|
498
|
+
|
|
499
|
+
## Usage
|
|
500
|
+
|
|
501
|
+
\`\`\`
|
|
502
|
+
/SEMO:dry-run {프롬프트}
|
|
503
|
+
\`\`\`
|
|
504
|
+
`,
|
|
505
|
+
description: "명령 검증 (라우팅 시뮬레이션)",
|
|
506
|
+
is_active: true,
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
id: "cmd-3",
|
|
510
|
+
name: "greenfield",
|
|
511
|
+
folder: "SEMO-workflow",
|
|
512
|
+
content: `# /SEMO-workflow:greenfield
|
|
513
|
+
|
|
514
|
+
BMad Method Greenfield Workflow를 시작합니다.
|
|
515
|
+
|
|
516
|
+
## Purpose
|
|
517
|
+
|
|
518
|
+
새로운 프로젝트를 처음부터 구축하는 4-Phase 워크플로우입니다.
|
|
519
|
+
|
|
520
|
+
\`\`\`
|
|
521
|
+
Phase 1: Discovery (Optional) - 아이디어 발굴 및 분석
|
|
522
|
+
Phase 2: Planning (Required) - PRD/Epic 생성 및 UX 설계
|
|
523
|
+
Phase 3: Solutioning (Required) - 아키텍처 및 명세 작성
|
|
524
|
+
Phase 4: Implementation (Required) - 스프린트 기반 개발
|
|
525
|
+
\`\`\`
|
|
526
|
+
|
|
527
|
+
## Usage
|
|
528
|
+
|
|
529
|
+
\`\`\`
|
|
530
|
+
/SEMO-workflow:greenfield
|
|
531
|
+
\`\`\`
|
|
532
|
+
`,
|
|
533
|
+
description: "Greenfield 워크플로우 시작",
|
|
534
|
+
is_active: true,
|
|
535
|
+
},
|
|
536
|
+
];
|
|
537
|
+
const FALLBACK_AGENTS = [
|
|
538
|
+
{
|
|
539
|
+
id: "agent-1",
|
|
540
|
+
name: "orchestrator",
|
|
541
|
+
display_name: "Orchestrator",
|
|
542
|
+
content: `---
|
|
543
|
+
name: orchestrator
|
|
544
|
+
description: SEMO 메인 오케스트레이터
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
# Orchestrator Agent
|
|
548
|
+
|
|
549
|
+
사용자 요청을 분석하고 적절한 Agent/Skill로 라우팅합니다.
|
|
550
|
+
|
|
551
|
+
## Routing Table
|
|
552
|
+
|
|
553
|
+
| Intent | Agent/Skill |
|
|
554
|
+
|--------|-------------|
|
|
555
|
+
| 코드 작성 | write-code |
|
|
556
|
+
| Git 커밋 | git-workflow |
|
|
557
|
+
| 테스트 작성 | write-test |
|
|
558
|
+
`,
|
|
559
|
+
package: "core",
|
|
560
|
+
is_active: true,
|
|
561
|
+
install_order: 1,
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
const FALLBACK_PACKAGES = [
|
|
565
|
+
{
|
|
566
|
+
id: "pkg-core",
|
|
567
|
+
name: "semo-core",
|
|
568
|
+
display_name: "SEMO Core",
|
|
569
|
+
description: "원칙, 오케스트레이터",
|
|
570
|
+
layer: "standard",
|
|
571
|
+
package_type: "standard",
|
|
572
|
+
version: "3.14.0",
|
|
573
|
+
is_active: true,
|
|
574
|
+
is_required: true,
|
|
575
|
+
install_order: 10,
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: "pkg-skills",
|
|
579
|
+
name: "semo-skills",
|
|
580
|
+
display_name: "SEMO Skills",
|
|
581
|
+
description: "19개 핵심 스킬",
|
|
582
|
+
layer: "standard",
|
|
583
|
+
package_type: "standard",
|
|
584
|
+
version: "3.14.0",
|
|
585
|
+
is_active: true,
|
|
586
|
+
is_required: true,
|
|
587
|
+
install_order: 20,
|
|
588
|
+
},
|
|
589
|
+
];
|
|
590
|
+
// ============================================================
|
|
591
|
+
// DB 조회 함수
|
|
592
|
+
// ============================================================
|
|
593
|
+
/**
|
|
594
|
+
* 활성 스킬 목록 조회
|
|
595
|
+
*/
|
|
596
|
+
async function getActiveSkills() {
|
|
597
|
+
const isConnected = await checkDbConnection();
|
|
598
|
+
if (!isConnected) {
|
|
599
|
+
console.warn("⚠️ DB 연결 실패, 폴백 스킬 목록 사용 (19개)");
|
|
600
|
+
return FALLBACK_SKILLS.filter((s) => s.is_active);
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
const result = await getPool().query(`
|
|
604
|
+
SELECT id, name, display_name, description, content, category, package,
|
|
605
|
+
is_active, is_required, install_order, version
|
|
606
|
+
FROM semo.skills
|
|
607
|
+
WHERE is_active = true
|
|
608
|
+
ORDER BY install_order
|
|
609
|
+
`);
|
|
610
|
+
return result.rows;
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
console.warn("⚠️ 스킬 조회 실패, 폴백 데이터 사용:", error);
|
|
614
|
+
return FALLBACK_SKILLS.filter((s) => s.is_active);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* 스킬 이름 목록만 조회
|
|
619
|
+
*/
|
|
620
|
+
async function getActiveSkillNames() {
|
|
621
|
+
const skills = await getActiveSkills();
|
|
622
|
+
return skills.map((s) => s.name);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* 커맨드 목록 조회
|
|
626
|
+
*/
|
|
627
|
+
async function getCommands() {
|
|
628
|
+
const isConnected = await checkDbConnection();
|
|
629
|
+
if (!isConnected) {
|
|
630
|
+
console.warn("⚠️ DB 연결 실패, 폴백 커맨드 목록 사용");
|
|
631
|
+
return FALLBACK_COMMANDS.filter((c) => c.is_active);
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
const result = await getPool().query(`
|
|
635
|
+
SELECT id, name, folder, content, description, is_active
|
|
636
|
+
FROM semo.commands
|
|
637
|
+
WHERE is_active = true
|
|
638
|
+
`);
|
|
639
|
+
return result.rows;
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
console.warn("⚠️ 커맨드 조회 실패, 폴백 데이터 사용:", error);
|
|
643
|
+
return FALLBACK_COMMANDS.filter((c) => c.is_active);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* 에이전트 목록 조회
|
|
648
|
+
*/
|
|
649
|
+
async function getAgents() {
|
|
650
|
+
const isConnected = await checkDbConnection();
|
|
651
|
+
if (!isConnected) {
|
|
652
|
+
console.warn("⚠️ DB 연결 실패, 폴백 에이전트 목록 사용");
|
|
653
|
+
return FALLBACK_AGENTS.filter((a) => a.is_active);
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const result = await getPool().query(`
|
|
657
|
+
SELECT id, name, display_name, content, package, is_active, install_order
|
|
658
|
+
FROM semo.agents
|
|
659
|
+
WHERE is_active = true
|
|
660
|
+
ORDER BY install_order
|
|
661
|
+
`);
|
|
662
|
+
return result.rows;
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
console.warn("⚠️ 에이전트 조회 실패, 폴백 데이터 사용:", error);
|
|
666
|
+
return FALLBACK_AGENTS.filter((a) => a.is_active);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* 패키지 목록 조회
|
|
671
|
+
*/
|
|
672
|
+
async function getPackages(layer) {
|
|
673
|
+
const isConnected = await checkDbConnection();
|
|
674
|
+
if (!isConnected) {
|
|
675
|
+
console.warn("⚠️ DB 연결 실패, 폴백 패키지 목록 사용");
|
|
676
|
+
const fallback = FALLBACK_PACKAGES.filter((p) => p.is_active);
|
|
677
|
+
return layer ? fallback.filter((p) => p.layer === layer) : fallback;
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
let query = `
|
|
681
|
+
SELECT id, name, display_name, description, layer, package_type,
|
|
682
|
+
version, is_active, is_required, install_order
|
|
683
|
+
FROM semo.packages
|
|
684
|
+
WHERE is_active = true
|
|
685
|
+
`;
|
|
686
|
+
const params = [];
|
|
687
|
+
if (layer) {
|
|
688
|
+
query += ` AND layer = $1`;
|
|
689
|
+
params.push(layer);
|
|
690
|
+
}
|
|
691
|
+
query += ` ORDER BY install_order`;
|
|
692
|
+
const result = await getPool().query(query, params);
|
|
693
|
+
return result.rows;
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
console.warn("⚠️ 패키지 조회 실패, 폴백 데이터 사용:", error);
|
|
697
|
+
const fallback = FALLBACK_PACKAGES.filter((p) => p.is_active);
|
|
698
|
+
return layer ? fallback.filter((p) => p.layer === layer) : fallback;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* 카테고리별 스킬 개수 조회
|
|
703
|
+
*/
|
|
704
|
+
async function getSkillCountByCategory() {
|
|
705
|
+
const skills = await getActiveSkills();
|
|
706
|
+
const counts = {};
|
|
707
|
+
for (const skill of skills) {
|
|
708
|
+
counts[skill.category] = (counts[skill.category] || 0) + 1;
|
|
709
|
+
}
|
|
710
|
+
return counts;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* DB 연결 종료
|
|
714
|
+
*/
|
|
715
|
+
async function closeConnection() {
|
|
716
|
+
if (pool) {
|
|
717
|
+
await pool.end();
|
|
718
|
+
pool = null;
|
|
719
|
+
dbAvailable = null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* DB 연결 상태 확인
|
|
724
|
+
*/
|
|
725
|
+
async function isDbConnected() {
|
|
726
|
+
return checkDbConnection();
|
|
727
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ const child_process_1 = require("child_process");
|
|
|
59
59
|
const fs = __importStar(require("fs"));
|
|
60
60
|
const path = __importStar(require("path"));
|
|
61
61
|
const os = __importStar(require("os"));
|
|
62
|
-
const
|
|
62
|
+
const database_1 = require("./database");
|
|
63
63
|
const PACKAGE_NAME = "@team-semicolon/semo-cli";
|
|
64
64
|
// package.json에서 버전 동적 로드
|
|
65
65
|
function getCliVersion() {
|
|
@@ -675,27 +675,20 @@ function copyRecursive(src, dest) {
|
|
|
675
675
|
}
|
|
676
676
|
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
677
677
|
// ============================================================
|
|
678
|
-
//
|
|
678
|
+
// 패키지 관리 (v3.14.0 - 폴백 데이터 사용)
|
|
679
679
|
// ============================================================
|
|
680
|
-
//
|
|
680
|
+
// v3.14.0: Extensions는 아직 git 기반이므로 폴백 데이터 직접 사용
|
|
681
|
+
// 향후 Extensions도 DB 기반으로 전환 예정
|
|
682
|
+
// 캐시된 패키지 데이터
|
|
681
683
|
let cachedExtensionPackages = null;
|
|
682
684
|
let cachedShortnameMappings = null;
|
|
683
|
-
|
|
684
|
-
// 패키지 데이터 초기화 (DB에서 조회)
|
|
685
|
+
// 패키지 데이터 초기화 (폴백 데이터 사용)
|
|
685
686
|
async function initPackageData() {
|
|
686
687
|
if (cachedExtensionPackages && cachedShortnameMappings)
|
|
687
688
|
return;
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
cachedPackageDefinitions = await (0, supabase_1.getExtensionPackages)();
|
|
692
|
-
}
|
|
693
|
-
catch {
|
|
694
|
-
// 폴백: 하드코딩된 데이터 사용
|
|
695
|
-
cachedExtensionPackages = EXTENSION_PACKAGES_FALLBACK;
|
|
696
|
-
cachedShortnameMappings = SHORTNAME_MAPPING_FALLBACK;
|
|
697
|
-
cachedPackageDefinitions = null;
|
|
698
|
-
}
|
|
689
|
+
// v3.14.0: Extensions는 아직 git 기반이므로 폴백 데이터 사용
|
|
690
|
+
cachedExtensionPackages = EXTENSION_PACKAGES_FALLBACK;
|
|
691
|
+
cachedShortnameMappings = SHORTNAME_MAPPING_FALLBACK;
|
|
699
692
|
}
|
|
700
693
|
// EXTENSION_PACKAGES 동기 접근용 (초기화 후 사용)
|
|
701
694
|
function getExtensionPackagesSync() {
|
|
@@ -754,10 +747,10 @@ const EXTENSION_PACKAGES = EXTENSION_PACKAGES_FALLBACK;
|
|
|
754
747
|
const SHORTNAME_MAPPING = SHORTNAME_MAPPING_FALLBACK;
|
|
755
748
|
// 그룹 이름 목록 (biz, eng, ops, meta, system)
|
|
756
749
|
const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
|
|
757
|
-
// 그룹명 → 해당 그룹의 모든 패키지 반환
|
|
750
|
+
// 그룹명 → 해당 그룹의 모든 패키지 반환
|
|
758
751
|
async function getPackagesByGroupAsync(group) {
|
|
759
|
-
|
|
760
|
-
return
|
|
752
|
+
// v3.14.0: 동기 함수와 동일하게 폴백 데이터 사용
|
|
753
|
+
return getPackagesByGroupSync(group);
|
|
761
754
|
}
|
|
762
755
|
// 그룹명 → 해당 그룹의 모든 패키지 반환 (동기, 폴백)
|
|
763
756
|
function getPackagesByGroupSync(group) {
|
|
@@ -1288,50 +1281,176 @@ program
|
|
|
1288
1281
|
}
|
|
1289
1282
|
console.log();
|
|
1290
1283
|
});
|
|
1291
|
-
// === Standard 설치 (
|
|
1284
|
+
// === Standard 설치 (DB 기반) ===
|
|
1292
1285
|
async function setupStandard(cwd, force) {
|
|
1293
|
-
const
|
|
1294
|
-
console.log(chalk_1.default.cyan("\n📚 Standard 설치 (
|
|
1295
|
-
console.log(chalk_1.default.gray("
|
|
1296
|
-
console.log(chalk_1.default.gray("
|
|
1297
|
-
console.log(chalk_1.default.gray("
|
|
1298
|
-
|
|
1299
|
-
// 기존 디렉토리 확인
|
|
1300
|
-
if (fs.existsSync(semoSystemDir) && !force) {
|
|
1301
|
-
const shouldOverwrite = await confirmOverwrite("semo-system/", semoSystemDir);
|
|
1302
|
-
if (!shouldOverwrite) {
|
|
1303
|
-
console.log(chalk_1.default.gray(" → semo-system/ 건너뜀"));
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
removeRecursive(semoSystemDir);
|
|
1307
|
-
console.log(chalk_1.default.green(" ✓ 기존 semo-system/ 삭제됨"));
|
|
1308
|
-
}
|
|
1309
|
-
const spinner = (0, ora_1.default)("semo-core, semo-skills, semo-agents, semo-scripts 다운로드 중...").start();
|
|
1286
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
1287
|
+
console.log(chalk_1.default.cyan("\n📚 Standard 설치 (DB 기반)"));
|
|
1288
|
+
console.log(chalk_1.default.gray(" 스킬: DB에서 조회하여 파일 생성"));
|
|
1289
|
+
console.log(chalk_1.default.gray(" 커맨드: DB에서 조회하여 파일 생성"));
|
|
1290
|
+
console.log(chalk_1.default.gray(" 에이전트: DB에서 조회하여 파일 생성\n"));
|
|
1291
|
+
const spinner = (0, ora_1.default)("DB에서 스킬/커맨드/에이전트 조회 중...").start();
|
|
1310
1292
|
try {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
(
|
|
1314
|
-
|
|
1315
|
-
// Standard 패키지 목록 (semo-system/ 하위에 있는 것들)
|
|
1316
|
-
const standardPackages = ["semo-core", "semo-skills", "semo-agents", "semo-scripts"];
|
|
1317
|
-
for (const pkg of standardPackages) {
|
|
1318
|
-
const srcPath = path.join(tempDir, "semo-system", pkg);
|
|
1319
|
-
const destPath = path.join(semoSystemDir, pkg);
|
|
1320
|
-
if (fs.existsSync(srcPath)) {
|
|
1321
|
-
copyRecursive(srcPath, destPath);
|
|
1322
|
-
}
|
|
1293
|
+
// DB 연결 확인
|
|
1294
|
+
const connected = await (0, database_1.isDbConnected)();
|
|
1295
|
+
if (connected) {
|
|
1296
|
+
spinner.text = "DB 연결 성공, 데이터 조회 중...";
|
|
1323
1297
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1298
|
+
else {
|
|
1299
|
+
spinner.text = "DB 연결 실패, 폴백 데이터 사용 중...";
|
|
1300
|
+
}
|
|
1301
|
+
// .claude 디렉토리 생성
|
|
1302
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
1303
|
+
// 1. 스킬 설치
|
|
1304
|
+
const skillsDir = path.join(claudeDir, "skills");
|
|
1305
|
+
if (force && fs.existsSync(skillsDir)) {
|
|
1306
|
+
removeRecursive(skillsDir);
|
|
1307
|
+
}
|
|
1308
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
1309
|
+
const skills = await (0, database_1.getActiveSkills)();
|
|
1310
|
+
for (const skill of skills) {
|
|
1311
|
+
const skillFolder = path.join(skillsDir, skill.name);
|
|
1312
|
+
fs.mkdirSync(skillFolder, { recursive: true });
|
|
1313
|
+
fs.writeFileSync(path.join(skillFolder, "SKILL.md"), skill.content);
|
|
1314
|
+
}
|
|
1315
|
+
console.log(chalk_1.default.green(` ✓ skills 설치 완료 (${skills.length}개)`));
|
|
1316
|
+
// 2. 커맨드 설치
|
|
1317
|
+
const commandsDir = path.join(claudeDir, "commands");
|
|
1318
|
+
if (force && fs.existsSync(commandsDir)) {
|
|
1319
|
+
removeRecursive(commandsDir);
|
|
1320
|
+
}
|
|
1321
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
1322
|
+
const commands = await (0, database_1.getCommands)();
|
|
1323
|
+
// 폴더별로 그룹핑
|
|
1324
|
+
const commandsByFolder = {};
|
|
1325
|
+
for (const cmd of commands) {
|
|
1326
|
+
if (!commandsByFolder[cmd.folder]) {
|
|
1327
|
+
commandsByFolder[cmd.folder] = [];
|
|
1328
|
+
}
|
|
1329
|
+
commandsByFolder[cmd.folder].push(cmd);
|
|
1330
|
+
}
|
|
1331
|
+
let cmdCount = 0;
|
|
1332
|
+
for (const [folder, cmds] of Object.entries(commandsByFolder)) {
|
|
1333
|
+
const folderPath = path.join(commandsDir, folder);
|
|
1334
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
1335
|
+
for (const cmd of cmds) {
|
|
1336
|
+
fs.writeFileSync(path.join(folderPath, `${cmd.name}.md`), cmd.content);
|
|
1337
|
+
cmdCount++;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
console.log(chalk_1.default.green(` ✓ commands 설치 완료 (${cmdCount}개)`));
|
|
1341
|
+
// 3. 에이전트 설치
|
|
1342
|
+
const agentsDir = path.join(claudeDir, "agents");
|
|
1343
|
+
if (force && fs.existsSync(agentsDir)) {
|
|
1344
|
+
removeRecursive(agentsDir);
|
|
1345
|
+
}
|
|
1346
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
1347
|
+
const agents = await (0, database_1.getAgents)();
|
|
1348
|
+
for (const agent of agents) {
|
|
1349
|
+
const agentFolder = path.join(agentsDir, agent.name);
|
|
1350
|
+
fs.mkdirSync(agentFolder, { recursive: true });
|
|
1351
|
+
fs.writeFileSync(path.join(agentFolder, `${agent.name}.md`), agent.content);
|
|
1352
|
+
}
|
|
1353
|
+
console.log(chalk_1.default.green(` ✓ agents 설치 완료 (${agents.length}개)`));
|
|
1354
|
+
spinner.succeed("Standard 설치 완료 (DB 기반)");
|
|
1355
|
+
// CLAUDE.md 생성
|
|
1356
|
+
await generateClaudeMd(cwd);
|
|
1328
1357
|
}
|
|
1329
1358
|
catch (error) {
|
|
1330
1359
|
spinner.fail("Standard 설치 실패");
|
|
1331
1360
|
console.error(chalk_1.default.red(` ${error}`));
|
|
1332
1361
|
}
|
|
1333
1362
|
}
|
|
1334
|
-
// ===
|
|
1363
|
+
// === CLAUDE.md 생성 (DB 기반) ===
|
|
1364
|
+
async function generateClaudeMd(cwd) {
|
|
1365
|
+
console.log(chalk_1.default.cyan("\n📄 CLAUDE.md 생성"));
|
|
1366
|
+
const claudeMdPath = path.join(cwd, ".claude", "CLAUDE.md");
|
|
1367
|
+
const skills = await (0, database_1.getActiveSkills)();
|
|
1368
|
+
const skillCategories = await (0, database_1.getSkillCountByCategory)();
|
|
1369
|
+
const skillList = Object.entries(skillCategories)
|
|
1370
|
+
.map(([cat, count]) => ` - ${cat}: ${count}개`)
|
|
1371
|
+
.join("\n");
|
|
1372
|
+
const claudeMdContent = `# SEMO Project Configuration
|
|
1373
|
+
|
|
1374
|
+
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v3.14.0
|
|
1375
|
+
|
|
1376
|
+
---
|
|
1377
|
+
|
|
1378
|
+
## 🔴 MANDATORY: Orchestrator-First Execution
|
|
1379
|
+
|
|
1380
|
+
> **⚠️ 이 규칙은 모든 사용자 요청에 적용됩니다. 예외 없음.**
|
|
1381
|
+
|
|
1382
|
+
### 실행 흐름 (필수)
|
|
1383
|
+
|
|
1384
|
+
\`\`\`
|
|
1385
|
+
1. 사용자 요청 수신
|
|
1386
|
+
2. Orchestrator가 의도 분석 후 적절한 Agent/Skill 라우팅
|
|
1387
|
+
3. Agent/Skill이 작업 수행
|
|
1388
|
+
4. 실행 결과 반환
|
|
1389
|
+
\`\`\`
|
|
1390
|
+
|
|
1391
|
+
### Orchestrator 참조
|
|
1392
|
+
|
|
1393
|
+
**Primary Orchestrator**: \`.claude/agents/orchestrator/orchestrator.md\`
|
|
1394
|
+
|
|
1395
|
+
이 파일에서 라우팅 테이블, 의도 분류, 메시지 포맷을 확인하세요.
|
|
1396
|
+
|
|
1397
|
+
---
|
|
1398
|
+
|
|
1399
|
+
## 🔴 NON-NEGOTIABLE RULES
|
|
1400
|
+
|
|
1401
|
+
### 1. Orchestrator-First Policy
|
|
1402
|
+
|
|
1403
|
+
> **모든 요청은 반드시 Orchestrator를 통해 라우팅됩니다. 직접 처리 금지.**
|
|
1404
|
+
|
|
1405
|
+
**직접 처리 금지 항목**:
|
|
1406
|
+
- 코드 작성/수정 → \`write-code\` 스킬
|
|
1407
|
+
- Git 커밋/푸시 → \`git-workflow\` 스킬
|
|
1408
|
+
- 품질 검증 → \`quality-gate\` 스킬
|
|
1409
|
+
|
|
1410
|
+
### 2. Pre-Commit Quality Gate
|
|
1411
|
+
|
|
1412
|
+
> **코드 변경이 포함된 커밋 전 반드시 Quality Gate를 통과해야 합니다.**
|
|
1413
|
+
|
|
1414
|
+
\`\`\`bash
|
|
1415
|
+
# 필수 검증 순서
|
|
1416
|
+
npm run lint # 1. ESLint 검사
|
|
1417
|
+
npx tsc --noEmit # 2. TypeScript 타입 체크
|
|
1418
|
+
npm run build # 3. 빌드 검증
|
|
1419
|
+
\`\`\`
|
|
1420
|
+
|
|
1421
|
+
---
|
|
1422
|
+
|
|
1423
|
+
## 설치된 구성
|
|
1424
|
+
|
|
1425
|
+
### 스킬 (${skills.length}개)
|
|
1426
|
+
${skillList}
|
|
1427
|
+
|
|
1428
|
+
## 구조
|
|
1429
|
+
|
|
1430
|
+
\`\`\`
|
|
1431
|
+
.claude/
|
|
1432
|
+
├── settings.json # MCP 서버 설정
|
|
1433
|
+
├── agents/ # 에이전트 (DB 기반 설치)
|
|
1434
|
+
├── skills/ # 스킬 (DB 기반 설치)
|
|
1435
|
+
└── commands/ # 커맨드 (DB 기반 설치)
|
|
1436
|
+
\`\`\`
|
|
1437
|
+
|
|
1438
|
+
## 사용 가능한 커맨드
|
|
1439
|
+
|
|
1440
|
+
| 커맨드 | 설명 |
|
|
1441
|
+
|--------|------|
|
|
1442
|
+
| \`/SEMO:help\` | 도움말 |
|
|
1443
|
+
| \`/SEMO:dry-run {프롬프트}\` | 명령 검증 (라우팅 시뮬레이션) |
|
|
1444
|
+
| \`/SEMO-workflow:greenfield\` | Greenfield 워크플로우 시작 |
|
|
1445
|
+
|
|
1446
|
+
---
|
|
1447
|
+
|
|
1448
|
+
> Generated by SEMO CLI v3.14.0 (DB-based installation)
|
|
1449
|
+
`;
|
|
1450
|
+
fs.writeFileSync(claudeMdPath, claudeMdContent);
|
|
1451
|
+
console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
|
|
1452
|
+
}
|
|
1453
|
+
// === Standard 심볼릭 링크 (레거시 호환) ===
|
|
1335
1454
|
async function createStandardSymlinks(cwd) {
|
|
1336
1455
|
const claudeDir = path.join(cwd, ".claude");
|
|
1337
1456
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
@@ -1364,7 +1483,7 @@ async function createStandardSymlinks(cwd) {
|
|
|
1364
1483
|
}
|
|
1365
1484
|
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
1366
1485
|
// DB에서 활성 스킬 목록 조회 (19개 핵심 스킬만)
|
|
1367
|
-
const activeSkillNames = await (0,
|
|
1486
|
+
const activeSkillNames = await (0, database_1.getActiveSkillNames)();
|
|
1368
1487
|
let linkedCount = 0;
|
|
1369
1488
|
for (const skillName of activeSkillNames) {
|
|
1370
1489
|
const skillLink = path.join(claudeSkillsDir, skillName);
|
|
@@ -1413,10 +1532,12 @@ async function createStandardSymlinks(cwd) {
|
|
|
1413
1532
|
}
|
|
1414
1533
|
/**
|
|
1415
1534
|
* 설치 상태를 검증하고 문제점을 리포트
|
|
1535
|
+
* v3.14.0: DB 기반 설치 지원 (semo-system 없이도 검증 가능)
|
|
1416
1536
|
*/
|
|
1417
1537
|
function verifyInstallation(cwd, installedExtensions = []) {
|
|
1418
1538
|
const claudeDir = path.join(cwd, ".claude");
|
|
1419
1539
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
1540
|
+
const hasSemoSystem = fs.existsSync(semoSystemDir);
|
|
1420
1541
|
const result = {
|
|
1421
1542
|
success: true,
|
|
1422
1543
|
errors: [],
|
|
@@ -1428,12 +1549,42 @@ function verifyInstallation(cwd, installedExtensions = []) {
|
|
|
1428
1549
|
extensions: [],
|
|
1429
1550
|
},
|
|
1430
1551
|
};
|
|
1431
|
-
//
|
|
1432
|
-
|
|
1433
|
-
|
|
1552
|
+
// v3.14.0: DB 기반 설치 시 semo-system이 없어도 됨
|
|
1553
|
+
// .claude/ 디렉토리가 있으면 DB 기반으로 설치된 것으로 간주
|
|
1554
|
+
if (!hasSemoSystem && !fs.existsSync(claudeDir)) {
|
|
1555
|
+
result.errors.push(".claude 디렉토리가 없습니다");
|
|
1434
1556
|
result.success = false;
|
|
1435
1557
|
return result;
|
|
1436
1558
|
}
|
|
1559
|
+
// DB 기반 설치 검증 (semo-system 없음)
|
|
1560
|
+
if (!hasSemoSystem) {
|
|
1561
|
+
// agents 검증 (실제 파일 존재 여부)
|
|
1562
|
+
const claudeAgentsDir = path.join(claudeDir, "agents");
|
|
1563
|
+
if (fs.existsSync(claudeAgentsDir)) {
|
|
1564
|
+
const agents = fs.readdirSync(claudeAgentsDir).filter(f => {
|
|
1565
|
+
const p = path.join(claudeAgentsDir, f);
|
|
1566
|
+
return fs.existsSync(p) && fs.statSync(p).isDirectory();
|
|
1567
|
+
});
|
|
1568
|
+
result.stats.agents.expected = agents.length;
|
|
1569
|
+
result.stats.agents.linked = agents.length; // DB 기반이므로 실제 파일
|
|
1570
|
+
}
|
|
1571
|
+
// skills 검증 (실제 파일 존재 여부)
|
|
1572
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
1573
|
+
if (fs.existsSync(claudeSkillsDir)) {
|
|
1574
|
+
const skills = fs.readdirSync(claudeSkillsDir).filter(f => {
|
|
1575
|
+
const p = path.join(claudeSkillsDir, f);
|
|
1576
|
+
return fs.existsSync(p) && fs.statSync(p).isDirectory();
|
|
1577
|
+
});
|
|
1578
|
+
result.stats.skills.expected = skills.length;
|
|
1579
|
+
result.stats.skills.linked = skills.length; // DB 기반이므로 실제 파일
|
|
1580
|
+
}
|
|
1581
|
+
// commands 검증 (실제 폴더 존재 여부)
|
|
1582
|
+
const semoCommandsDir = path.join(claudeDir, "commands", "SEMO");
|
|
1583
|
+
result.stats.commands.exists = fs.existsSync(semoCommandsDir);
|
|
1584
|
+
result.stats.commands.valid = result.stats.commands.exists;
|
|
1585
|
+
return result;
|
|
1586
|
+
}
|
|
1587
|
+
// === 레거시: semo-system 기반 설치 검증 ===
|
|
1437
1588
|
const coreDir = path.join(semoSystemDir, "semo-core");
|
|
1438
1589
|
const skillsDir = path.join(semoSystemDir, "semo-skills");
|
|
1439
1590
|
if (!fs.existsSync(coreDir)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-semicolon/semo-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"description": "SEMO CLI - AI Agent Orchestration Framework Installer",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -27,17 +27,18 @@
|
|
|
27
27
|
"directory": "packages/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@supabase/supabase-js": "^2.49.1",
|
|
31
30
|
"chalk": "^4.1.2",
|
|
32
31
|
"commander": "^12.0.0",
|
|
32
|
+
"inquirer": "^8.2.6",
|
|
33
33
|
"ora": "^5.4.1",
|
|
34
|
-
"
|
|
34
|
+
"pg": "^8.17.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@types/node": "^20.0.0",
|
|
38
37
|
"@types/inquirer": "^8.2.0",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"@types/node": "^20.0.0",
|
|
39
|
+
"@types/pg": "^8.16.0",
|
|
40
|
+
"ts-node": "^10.0.0",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=18.0.0"
|