@team-semicolon/semo-cli 1.2.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -7
- package/dist/index.js +358 -114
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* SEMO CLI
|
|
3
|
+
* SEMO CLI v2.0
|
|
4
4
|
*
|
|
5
|
-
* Gemini 하이브리드
|
|
5
|
+
* Gemini 하이브리드 전략 기반 AI Agent Orchestration Framework
|
|
6
6
|
*
|
|
7
7
|
* 사용법:
|
|
8
|
-
* npx @team-semicolon/semo-cli init
|
|
8
|
+
* npx @team-semicolon/semo-cli init # 기본 설치
|
|
9
|
+
* npx @team-semicolon/semo-cli add next # 패키지 추가
|
|
10
|
+
* npx @team-semicolon/semo-cli list # 패키지 목록
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 3. Context Mesh (.claude/memory/) 초기화
|
|
12
|
+
* 구조:
|
|
13
|
+
* - Standard: semo-core + semo-skills (필수)
|
|
14
|
+
* - Extensions: packages/next, packages/backend 등 (선택)
|
|
14
15
|
*/
|
|
15
16
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* SEMO CLI
|
|
4
|
+
* SEMO CLI v2.0
|
|
5
5
|
*
|
|
6
|
-
* Gemini 하이브리드
|
|
6
|
+
* Gemini 하이브리드 전략 기반 AI Agent Orchestration Framework
|
|
7
7
|
*
|
|
8
8
|
* 사용법:
|
|
9
|
-
* npx @team-semicolon/semo-cli init
|
|
9
|
+
* npx @team-semicolon/semo-cli init # 기본 설치
|
|
10
|
+
* npx @team-semicolon/semo-cli add next # 패키지 추가
|
|
11
|
+
* npx @team-semicolon/semo-cli list # 패키지 목록
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 3. Context Mesh (.claude/memory/) 초기화
|
|
13
|
+
* 구조:
|
|
14
|
+
* - Standard: semo-core + semo-skills (필수)
|
|
15
|
+
* - Extensions: packages/next, packages/backend 등 (선택)
|
|
15
16
|
*/
|
|
16
17
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
18
|
if (k2 === undefined) k2 = k;
|
|
@@ -57,11 +58,30 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
57
58
|
const child_process_1 = require("child_process");
|
|
58
59
|
const fs = __importStar(require("fs"));
|
|
59
60
|
const path = __importStar(require("path"));
|
|
60
|
-
const VERSION = "
|
|
61
|
-
|
|
61
|
+
const VERSION = "2.0.1";
|
|
62
|
+
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
63
|
+
// 확장 패키지 정의
|
|
64
|
+
const EXTENSION_PACKAGES = {
|
|
65
|
+
next: { name: "Next.js", desc: "Next.js 프론트엔드 개발 (13 agents, 33 skills)", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
|
|
66
|
+
backend: { name: "Backend", desc: "Spring/Node.js 백엔드 개발 (8 agents, 15 skills)", detect: ["pom.xml", "build.gradle"] },
|
|
67
|
+
po: { name: "PO", desc: "Product Owner - 태스크/에픽 관리 (5 agents, 19 skills)", detect: [] },
|
|
68
|
+
qa: { name: "QA", desc: "QA 테스트 관리 (4 agents, 13 skills)", detect: [] },
|
|
69
|
+
pm: { name: "PM", desc: "프로젝트/스프린트 관리 (5 agents, 16 skills)", detect: [] },
|
|
70
|
+
infra: { name: "Infra", desc: "인프라/배포 관리 (6 agents, 10 skills)", detect: ["docker-compose.yml", "Dockerfile"] },
|
|
71
|
+
design: { name: "Design", desc: "디자인 핸드오프 (3 agents, 4 skills)", detect: [] },
|
|
72
|
+
ms: { name: "Microservice", desc: "마이크로서비스 아키텍처 (5 agents, 5 skills)", detect: [] },
|
|
73
|
+
mvp: { name: "MVP", desc: "MVP 빠른 개발 (4 agents, 6 skills)", detect: [] },
|
|
74
|
+
meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리 (6 agents, 7 skills)", detect: ["semo-core", "semo-skills", "packages/meta"] },
|
|
75
|
+
};
|
|
76
|
+
const program = new commander_1.Command();
|
|
77
|
+
program
|
|
78
|
+
.name("semo")
|
|
79
|
+
.description("SEMO CLI - AI Agent Orchestration Framework")
|
|
80
|
+
.version(VERSION);
|
|
81
|
+
// === 유틸리티 함수들 ===
|
|
62
82
|
async function confirmOverwrite(itemName, itemPath) {
|
|
63
83
|
if (!fs.existsSync(itemPath)) {
|
|
64
|
-
return true;
|
|
84
|
+
return true;
|
|
65
85
|
}
|
|
66
86
|
const { shouldOverwrite } = await inquirer_1.default.prompt([
|
|
67
87
|
{
|
|
@@ -73,19 +93,25 @@ async function confirmOverwrite(itemName, itemPath) {
|
|
|
73
93
|
]);
|
|
74
94
|
return shouldOverwrite;
|
|
75
95
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
function detectProjectType(cwd) {
|
|
97
|
+
const detected = [];
|
|
98
|
+
for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
|
|
99
|
+
for (const file of pkg.detect) {
|
|
100
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
101
|
+
detected.push(key);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return detected;
|
|
107
|
+
}
|
|
82
108
|
// === init 명령어 ===
|
|
83
109
|
program
|
|
84
110
|
.command("init")
|
|
85
|
-
.description("현재 프로젝트에 SEMO를 설치합니다
|
|
111
|
+
.description("현재 프로젝트에 SEMO를 설치합니다")
|
|
86
112
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
87
113
|
.option("--skip-mcp", "MCP 설정 생략")
|
|
88
|
-
.option("--
|
|
114
|
+
.option("--with <packages>", "추가 설치할 패키지 (쉼표 구분: next,backend)")
|
|
89
115
|
.action(async (options) => {
|
|
90
116
|
console.log(chalk_1.default.cyan.bold("\n🚀 SEMO 설치 시작\n"));
|
|
91
117
|
console.log(chalk_1.default.gray("Gemini 하이브리드 전략: White Box + Black Box\n"));
|
|
@@ -100,103 +126,200 @@ program
|
|
|
100
126
|
spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
|
|
101
127
|
process.exit(1);
|
|
102
128
|
}
|
|
103
|
-
// 2.
|
|
129
|
+
// 2. 프로젝트 유형 감지
|
|
130
|
+
const detected = detectProjectType(cwd);
|
|
131
|
+
let extensionsToInstall = [];
|
|
132
|
+
if (options.with) {
|
|
133
|
+
extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
|
|
134
|
+
}
|
|
135
|
+
else if (detected.length > 0) {
|
|
136
|
+
console.log(chalk_1.default.cyan("\n📦 감지된 프로젝트 유형:"));
|
|
137
|
+
detected.forEach(pkg => {
|
|
138
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
139
|
+
});
|
|
140
|
+
const { installDetected } = await inquirer_1.default.prompt([
|
|
141
|
+
{
|
|
142
|
+
type: "confirm",
|
|
143
|
+
name: "installDetected",
|
|
144
|
+
message: "감지된 패키지를 함께 설치할까요?",
|
|
145
|
+
default: true,
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
if (installDetected) {
|
|
149
|
+
extensionsToInstall = detected;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 3. .claude 디렉토리 생성
|
|
104
153
|
const claudeDir = path.join(cwd, ".claude");
|
|
105
154
|
if (!fs.existsSync(claudeDir)) {
|
|
106
155
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
107
|
-
console.log(chalk_1.default.green("✓ .claude/ 디렉토리 생성됨"));
|
|
156
|
+
console.log(chalk_1.default.green("\n✓ .claude/ 디렉토리 생성됨"));
|
|
108
157
|
}
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
// 4. Standard 설치 (semo-core + semo-skills)
|
|
159
|
+
await setupStandard(cwd, options.force);
|
|
160
|
+
// 5. Extensions 설치
|
|
161
|
+
if (extensionsToInstall.length > 0) {
|
|
162
|
+
await setupExtensions(cwd, extensionsToInstall, options.force);
|
|
112
163
|
}
|
|
113
|
-
//
|
|
164
|
+
// 6. MCP 설정
|
|
114
165
|
if (!options.skipMcp) {
|
|
115
|
-
await
|
|
166
|
+
await setupMCP(cwd, options.force);
|
|
116
167
|
}
|
|
117
|
-
//
|
|
168
|
+
// 7. Context Mesh 초기화
|
|
118
169
|
await setupContextMesh(cwd);
|
|
119
|
-
//
|
|
120
|
-
await setupClaudeMd(cwd, options.force);
|
|
170
|
+
// 8. CLAUDE.md 생성
|
|
171
|
+
await setupClaudeMd(cwd, extensionsToInstall, options.force);
|
|
172
|
+
// 완료 메시지
|
|
121
173
|
console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
|
|
122
|
-
console.log(chalk_1.default.cyan("
|
|
174
|
+
console.log(chalk_1.default.cyan("설치된 구성:"));
|
|
175
|
+
console.log(chalk_1.default.gray(" [Standard]"));
|
|
176
|
+
console.log(chalk_1.default.gray(" ✓ semo-core (원칙, 오케스트레이터)"));
|
|
177
|
+
console.log(chalk_1.default.gray(" ✓ semo-skills (13개 통합 스킬)"));
|
|
178
|
+
if (extensionsToInstall.length > 0) {
|
|
179
|
+
console.log(chalk_1.default.gray(" [Extensions]"));
|
|
180
|
+
extensionsToInstall.forEach(pkg => {
|
|
181
|
+
console.log(chalk_1.default.gray(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
console.log(chalk_1.default.cyan("\n다음 단계:"));
|
|
123
185
|
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
124
186
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
125
|
-
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말
|
|
187
|
+
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
|
|
188
|
+
if (extensionsToInstall.length === 0 && detected.length === 0) {
|
|
189
|
+
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add next)"));
|
|
190
|
+
}
|
|
191
|
+
console.log();
|
|
126
192
|
});
|
|
127
|
-
// ===
|
|
128
|
-
async function
|
|
193
|
+
// === Standard 설치 (semo-core + semo-skills) ===
|
|
194
|
+
async function setupStandard(cwd, force) {
|
|
129
195
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
130
|
-
console.log(chalk_1.default.cyan("\n📚
|
|
131
|
-
console.log(chalk_1.default.gray("
|
|
132
|
-
|
|
196
|
+
console.log(chalk_1.default.cyan("\n📚 Standard 설치 (White Box)"));
|
|
197
|
+
console.log(chalk_1.default.gray(" semo-core: 원칙, 오케스트레이터"));
|
|
198
|
+
console.log(chalk_1.default.gray(" semo-skills: 13개 통합 스킬\n"));
|
|
199
|
+
// 기존 디렉토리 확인
|
|
133
200
|
if (fs.existsSync(semoSystemDir) && !force) {
|
|
134
201
|
const shouldOverwrite = await confirmOverwrite("semo-system/", semoSystemDir);
|
|
135
202
|
if (!shouldOverwrite) {
|
|
136
203
|
console.log(chalk_1.default.gray(" → semo-system/ 건너뜀"));
|
|
137
204
|
return;
|
|
138
205
|
}
|
|
139
|
-
|
|
140
|
-
(0, child_process_1.execSync)(`rm -rf ${semoSystemDir}`, { stdio: "pipe" });
|
|
206
|
+
(0, child_process_1.execSync)(`rm -rf "${semoSystemDir}"`, { stdio: "pipe" });
|
|
141
207
|
console.log(chalk_1.default.green(" ✓ 기존 semo-system/ 삭제됨"));
|
|
142
208
|
}
|
|
143
209
|
const spinner = (0, ora_1.default)("semo-core, semo-skills 다운로드 중...").start();
|
|
144
210
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
211
|
+
const tempDir = path.join(cwd, ".semo-temp");
|
|
212
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
213
|
+
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
|
|
214
|
+
fs.mkdirSync(semoSystemDir, { recursive: true });
|
|
215
|
+
// semo-core 복사
|
|
216
|
+
if (fs.existsSync(path.join(tempDir, "semo-core"))) {
|
|
217
|
+
(0, child_process_1.execSync)(`cp -r "${tempDir}/semo-core" "${semoSystemDir}/"`, { stdio: "pipe" });
|
|
148
218
|
}
|
|
149
|
-
// semo-skills
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
const tempDir = path.join(cwd, ".semo-temp");
|
|
153
|
-
if (!fs.existsSync(tempDir)) {
|
|
154
|
-
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} ${tempDir}`, { stdio: "pipe" });
|
|
155
|
-
}
|
|
156
|
-
// semo-core와 semo-skills 복사
|
|
157
|
-
fs.mkdirSync(semoSystemDir, { recursive: true });
|
|
158
|
-
if (fs.existsSync(path.join(tempDir, "semo-core"))) {
|
|
159
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-core ${semoSystemDir}/`, { stdio: "pipe" });
|
|
160
|
-
}
|
|
161
|
-
if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
|
|
162
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-skills ${semoSystemDir}/`, { stdio: "pipe" });
|
|
163
|
-
}
|
|
164
|
-
// 임시 디렉토리 삭제
|
|
165
|
-
(0, child_process_1.execSync)(`rm -rf ${tempDir}`, { stdio: "pipe" });
|
|
219
|
+
// semo-skills 복사
|
|
220
|
+
if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
|
|
221
|
+
(0, child_process_1.execSync)(`cp -r "${tempDir}/semo-skills" "${semoSystemDir}/"`, { stdio: "pipe" });
|
|
166
222
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
223
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
224
|
+
spinner.succeed("Standard 설치 완료");
|
|
225
|
+
// 심볼릭 링크 생성
|
|
226
|
+
await createStandardSymlinks(cwd);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
spinner.fail("Standard 설치 실패");
|
|
230
|
+
console.error(chalk_1.default.red(` ${error}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// === Standard 심볼릭 링크 ===
|
|
234
|
+
async function createStandardSymlinks(cwd) {
|
|
235
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
236
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
237
|
+
// agents 링크
|
|
238
|
+
const agentsLink = path.join(claudeDir, "agents");
|
|
239
|
+
if (!fs.existsSync(agentsLink)) {
|
|
240
|
+
const agentsTarget = path.join(semoSystemDir, "semo-core", "agents");
|
|
241
|
+
if (fs.existsSync(agentsTarget)) {
|
|
173
242
|
fs.symlinkSync("../semo-system/semo-core/agents", agentsLink);
|
|
174
243
|
console.log(chalk_1.default.green(" ✓ .claude/agents → semo-system/semo-core/agents"));
|
|
175
244
|
}
|
|
176
|
-
|
|
245
|
+
}
|
|
246
|
+
// skills 링크
|
|
247
|
+
const skillsLink = path.join(claudeDir, "skills");
|
|
248
|
+
if (!fs.existsSync(skillsLink)) {
|
|
249
|
+
const skillsTarget = path.join(semoSystemDir, "semo-skills");
|
|
250
|
+
if (fs.existsSync(skillsTarget)) {
|
|
177
251
|
fs.symlinkSync("../semo-system/semo-skills", skillsLink);
|
|
178
252
|
console.log(chalk_1.default.green(" ✓ .claude/skills → semo-system/semo-skills"));
|
|
179
253
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
254
|
+
}
|
|
255
|
+
// commands 링크
|
|
256
|
+
const commandsDir = path.join(claudeDir, "commands");
|
|
257
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
258
|
+
const semoCommandsLink = path.join(commandsDir, "SEMO");
|
|
259
|
+
if (!fs.existsSync(semoCommandsLink)) {
|
|
260
|
+
const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
|
|
261
|
+
if (fs.existsSync(commandsTarget)) {
|
|
185
262
|
fs.symlinkSync("../../semo-system/semo-core/commands/SEMO", semoCommandsLink);
|
|
186
263
|
console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
|
|
187
264
|
}
|
|
188
265
|
}
|
|
266
|
+
}
|
|
267
|
+
// === Extensions 설치 ===
|
|
268
|
+
async function setupExtensions(cwd, packages, force) {
|
|
269
|
+
console.log(chalk_1.default.cyan("\n📦 Extensions 설치"));
|
|
270
|
+
packages.forEach(pkg => {
|
|
271
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}`));
|
|
272
|
+
});
|
|
273
|
+
console.log();
|
|
274
|
+
const spinner = (0, ora_1.default)("Extension 패키지 다운로드 중...").start();
|
|
275
|
+
try {
|
|
276
|
+
const tempDir = path.join(cwd, ".semo-temp");
|
|
277
|
+
// 이미 temp가 없으면 clone
|
|
278
|
+
if (!fs.existsSync(tempDir)) {
|
|
279
|
+
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
|
|
280
|
+
}
|
|
281
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
282
|
+
for (const pkg of packages) {
|
|
283
|
+
const srcPath = path.join(tempDir, "packages", pkg);
|
|
284
|
+
const destPath = path.join(semoSystemDir, pkg);
|
|
285
|
+
if (fs.existsSync(srcPath)) {
|
|
286
|
+
if (fs.existsSync(destPath) && !force) {
|
|
287
|
+
console.log(chalk_1.default.yellow(` ⚠ ${pkg}/ 이미 존재 (건너뜀)`));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
(0, child_process_1.execSync)(`rm -rf "${destPath}"`, { stdio: "pipe" });
|
|
291
|
+
(0, child_process_1.execSync)(`cp -r "${srcPath}" "${destPath}"`, { stdio: "pipe" });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
295
|
+
spinner.succeed(`Extensions 설치 완료 (${packages.length}개)`);
|
|
296
|
+
// Extensions 심볼릭 링크 생성
|
|
297
|
+
await createExtensionSymlinks(cwd, packages);
|
|
298
|
+
}
|
|
189
299
|
catch (error) {
|
|
190
|
-
spinner.fail("
|
|
300
|
+
spinner.fail("Extensions 설치 실패");
|
|
191
301
|
console.error(chalk_1.default.red(` ${error}`));
|
|
192
302
|
}
|
|
193
303
|
}
|
|
194
|
-
// ===
|
|
195
|
-
async function
|
|
304
|
+
// === Extensions 심볼릭 링크 ===
|
|
305
|
+
async function createExtensionSymlinks(cwd, packages) {
|
|
306
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
307
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
308
|
+
for (const pkg of packages) {
|
|
309
|
+
// sax-{pkg} 호환 링크 (기존 SAX 사용자용)
|
|
310
|
+
const saxPkgLink = path.join(claudeDir, `sax-${pkg}`);
|
|
311
|
+
const pkgPath = path.join(semoSystemDir, pkg);
|
|
312
|
+
if (fs.existsSync(pkgPath) && !fs.existsSync(saxPkgLink)) {
|
|
313
|
+
fs.symlinkSync(`../semo-system/${pkg}`, saxPkgLink);
|
|
314
|
+
console.log(chalk_1.default.green(` ✓ .claude/sax-${pkg} → semo-system/${pkg}`));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// === MCP 설정 ===
|
|
319
|
+
async function setupMCP(cwd, force) {
|
|
196
320
|
console.log(chalk_1.default.cyan("\n🔧 Black Box 설정 (MCP Server)"));
|
|
197
321
|
console.log(chalk_1.default.gray(" 토큰이 격리된 외부 연동 도구\n"));
|
|
198
322
|
const settingsPath = path.join(cwd, ".claude", "settings.json");
|
|
199
|
-
// 기존 설정 파일 확인 - force가 아니면 사용자에게 확인
|
|
200
323
|
if (fs.existsSync(settingsPath) && !force) {
|
|
201
324
|
const shouldOverwrite = await confirmOverwrite(".claude/settings.json", settingsPath);
|
|
202
325
|
if (!shouldOverwrite) {
|
|
@@ -220,7 +343,6 @@ async function setupBlackBox(cwd, force) {
|
|
|
220
343
|
};
|
|
221
344
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
222
345
|
console.log(chalk_1.default.green("✓ .claude/settings.json 생성됨 (MCP 설정)"));
|
|
223
|
-
console.log(chalk_1.default.gray(" → semo-integrations: github, slack, supabase"));
|
|
224
346
|
}
|
|
225
347
|
// === Context Mesh 초기화 ===
|
|
226
348
|
async function setupContextMesh(cwd) {
|
|
@@ -234,6 +356,7 @@ async function setupContextMesh(cwd) {
|
|
|
234
356
|
const contextContent = `# Project Context
|
|
235
357
|
|
|
236
358
|
> 세션 간 영속화되는 프로젝트 컨텍스트
|
|
359
|
+
> SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
|
|
237
360
|
|
|
238
361
|
---
|
|
239
362
|
|
|
@@ -242,7 +365,7 @@ async function setupContextMesh(cwd) {
|
|
|
242
365
|
| 항목 | 값 |
|
|
243
366
|
|------|-----|
|
|
244
367
|
| **이름** | ${path.basename(cwd)} |
|
|
245
|
-
| **SEMO 버전** |
|
|
368
|
+
| **SEMO 버전** | ${VERSION} |
|
|
246
369
|
| **설치일** | ${new Date().toISOString().split("T")[0]} |
|
|
247
370
|
|
|
248
371
|
---
|
|
@@ -253,6 +376,12 @@ _아직 작업 기록이 없습니다._
|
|
|
253
376
|
|
|
254
377
|
---
|
|
255
378
|
|
|
379
|
+
## 기술 스택
|
|
380
|
+
|
|
381
|
+
_프로젝트 분석 후 자동으로 채워집니다._
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
256
385
|
*마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
|
|
257
386
|
`;
|
|
258
387
|
fs.writeFileSync(contextPath, contextContent);
|
|
@@ -264,6 +393,7 @@ _아직 작업 기록이 없습니다._
|
|
|
264
393
|
const decisionsContent = `# Architecture Decisions
|
|
265
394
|
|
|
266
395
|
> 프로젝트 아키텍처 결정 기록 (ADR)
|
|
396
|
+
> 중요한 기술적 결정을 여기에 기록합니다.
|
|
267
397
|
|
|
268
398
|
---
|
|
269
399
|
|
|
@@ -320,10 +450,9 @@ _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
|
|
|
320
450
|
}
|
|
321
451
|
}
|
|
322
452
|
// === CLAUDE.md 생성 ===
|
|
323
|
-
async function setupClaudeMd(cwd, force) {
|
|
453
|
+
async function setupClaudeMd(cwd, extensions, force) {
|
|
324
454
|
console.log(chalk_1.default.cyan("\n📄 CLAUDE.md 설정"));
|
|
325
455
|
const claudeMdPath = path.join(cwd, ".claude", "CLAUDE.md");
|
|
326
|
-
// 기존 CLAUDE.md 확인 - force가 아니면 사용자에게 확인
|
|
327
456
|
if (fs.existsSync(claudeMdPath) && !force) {
|
|
328
457
|
const shouldOverwrite = await confirmOverwrite("CLAUDE.md", claudeMdPath);
|
|
329
458
|
if (!shouldOverwrite) {
|
|
@@ -331,25 +460,41 @@ async function setupClaudeMd(cwd, force) {
|
|
|
331
460
|
return;
|
|
332
461
|
}
|
|
333
462
|
}
|
|
463
|
+
const extensionsList = extensions.length > 0
|
|
464
|
+
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
465
|
+
: "";
|
|
334
466
|
const claudeMdContent = `# SEMO Project Configuration
|
|
335
467
|
|
|
336
|
-
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework
|
|
468
|
+
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
|
|
469
|
+
|
|
470
|
+
## 설치된 구성
|
|
471
|
+
|
|
472
|
+
### Standard (필수)
|
|
473
|
+
- **semo-core**: 원칙, 오케스트레이터, 공통 커맨드
|
|
474
|
+
- **semo-skills**: 13개 통합 스킬
|
|
475
|
+
- 행동: coder, tester, planner, deployer, writer
|
|
476
|
+
- 운영: memory, notify-slack, feedback, version-updater, sax-help, sax-architecture-checker, circuit-breaker, list-bugs
|
|
477
|
+
|
|
478
|
+
${extensions.length > 0 ? `### Extensions (선택)
|
|
479
|
+
${extensions.map(pkg => `- **${pkg}**: ${EXTENSION_PACKAGES[pkg].desc}`).join("\n")}` : ""}
|
|
337
480
|
|
|
338
481
|
## 구조
|
|
339
482
|
|
|
340
483
|
\`\`\`
|
|
341
484
|
.claude/
|
|
342
485
|
├── settings.json # MCP 서버 설정 (Black Box)
|
|
343
|
-
├── memory/ # Context Mesh
|
|
486
|
+
├── memory/ # Context Mesh (장기 기억)
|
|
344
487
|
│ ├── context.md # 프로젝트 상태
|
|
345
488
|
│ ├── decisions.md # 아키텍처 결정
|
|
346
489
|
│ └── rules/ # 프로젝트별 규칙
|
|
347
490
|
├── agents → semo-system/semo-core/agents
|
|
348
|
-
|
|
491
|
+
├── skills → semo-system/semo-skills
|
|
492
|
+
└── commands/SEMO → semo-system/semo-core/commands/SEMO
|
|
349
493
|
|
|
350
494
|
semo-system/ # White Box (읽기 전용)
|
|
351
495
|
├── semo-core/ # Layer 0: 원칙, 오케스트레이션
|
|
352
|
-
|
|
496
|
+
├── semo-skills/ # Layer 1: 통합 스킬
|
|
497
|
+
${extensionsList}
|
|
353
498
|
\`\`\`
|
|
354
499
|
|
|
355
500
|
## 사용 가능한 커맨드
|
|
@@ -360,26 +505,81 @@ semo-system/ # White Box (읽기 전용)
|
|
|
360
505
|
| \`/SEMO:slack\` | Slack 메시지 전송 |
|
|
361
506
|
| \`/SEMO:feedback\` | 피드백 제출 |
|
|
362
507
|
| \`/SEMO:health\` | 환경 검증 |
|
|
508
|
+
| \`/SEMO:update\` | SEMO 업데이트 |
|
|
509
|
+
|
|
510
|
+
## Context Mesh 사용
|
|
363
511
|
|
|
364
|
-
|
|
512
|
+
SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
|
|
365
513
|
|
|
366
|
-
|
|
514
|
+
- **context.md**: 프로젝트 상태, 진행 중인 작업
|
|
515
|
+
- **decisions.md**: 아키텍처 결정 기록 (ADR)
|
|
516
|
+
- **rules/**: 프로젝트별 커스텀 규칙
|
|
367
517
|
|
|
368
|
-
|
|
369
|
-
|------|--------|
|
|
370
|
-
| \`next.config.js\` | Next.js |
|
|
371
|
-
| \`pom.xml\` | Spring |
|
|
372
|
-
| \`docker-compose.yml\` | Microservice |
|
|
373
|
-
| 기타 | MVP |
|
|
518
|
+
memory 스킬이 자동으로 이 파일들을 관리합니다.
|
|
374
519
|
|
|
375
520
|
## References
|
|
376
521
|
|
|
377
522
|
- [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
|
|
378
523
|
- [SEMO Skills](semo-system/semo-skills/)
|
|
524
|
+
${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
|
|
379
525
|
`;
|
|
380
526
|
fs.writeFileSync(claudeMdPath, claudeMdContent);
|
|
381
527
|
console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
|
|
382
528
|
}
|
|
529
|
+
// === add 명령어 ===
|
|
530
|
+
program
|
|
531
|
+
.command("add <package>")
|
|
532
|
+
.description("Extension 패키지를 추가로 설치합니다")
|
|
533
|
+
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
534
|
+
.action(async (packageName, options) => {
|
|
535
|
+
const cwd = process.cwd();
|
|
536
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
537
|
+
if (!fs.existsSync(semoSystemDir)) {
|
|
538
|
+
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
if (!(packageName in EXTENSION_PACKAGES)) {
|
|
542
|
+
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packageName}`));
|
|
543
|
+
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}\n`));
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
const pkgPath = path.join(semoSystemDir, packageName);
|
|
547
|
+
if (fs.existsSync(pkgPath) && !options.force) {
|
|
548
|
+
console.log(chalk_1.default.yellow(`\n${EXTENSION_PACKAGES[packageName].name} 패키지가 이미 설치되어 있습니다.`));
|
|
549
|
+
console.log(chalk_1.default.gray("강제 재설치: semo add " + packageName + " --force\n"));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[packageName].name} 패키지 설치\n`));
|
|
553
|
+
console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[packageName].desc}\n`));
|
|
554
|
+
await setupExtensions(cwd, [packageName], options.force);
|
|
555
|
+
console.log(chalk_1.default.green.bold(`\n✅ ${EXTENSION_PACKAGES[packageName].name} 패키지 설치 완료!\n`));
|
|
556
|
+
});
|
|
557
|
+
// === list 명령어 ===
|
|
558
|
+
program
|
|
559
|
+
.command("list")
|
|
560
|
+
.description("사용 가능한 모든 패키지를 표시합니다")
|
|
561
|
+
.action(() => {
|
|
562
|
+
const cwd = process.cwd();
|
|
563
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
564
|
+
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록\n"));
|
|
565
|
+
// Standard
|
|
566
|
+
console.log(chalk_1.default.white.bold("Standard (필수)"));
|
|
567
|
+
const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
568
|
+
const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
|
|
569
|
+
console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
|
|
570
|
+
console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 13개 통합 스킬`);
|
|
571
|
+
console.log();
|
|
572
|
+
// Extensions
|
|
573
|
+
console.log(chalk_1.default.white.bold("Extensions (선택)"));
|
|
574
|
+
for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
|
|
575
|
+
const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
|
|
576
|
+
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
577
|
+
console.log(` ${status} ${key} - ${pkg.desc}`);
|
|
578
|
+
}
|
|
579
|
+
console.log();
|
|
580
|
+
console.log(chalk_1.default.gray("설치: semo add <package>"));
|
|
581
|
+
console.log(chalk_1.default.gray("예시: semo add next\n"));
|
|
582
|
+
});
|
|
383
583
|
// === status 명령어 ===
|
|
384
584
|
program
|
|
385
585
|
.command("status")
|
|
@@ -387,26 +587,50 @@ program
|
|
|
387
587
|
.action(() => {
|
|
388
588
|
console.log(chalk_1.default.cyan.bold("\n📊 SEMO 설치 상태\n"));
|
|
389
589
|
const cwd = process.cwd();
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
{ name: "semo-
|
|
395
|
-
{ name: "semo-
|
|
590
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
591
|
+
// Standard 확인
|
|
592
|
+
console.log(chalk_1.default.white.bold("Standard:"));
|
|
593
|
+
const standardChecks = [
|
|
594
|
+
{ name: "semo-core", path: path.join(semoSystemDir, "semo-core") },
|
|
595
|
+
{ name: "semo-skills", path: path.join(semoSystemDir, "semo-skills") },
|
|
396
596
|
];
|
|
397
|
-
let
|
|
398
|
-
for (const check of
|
|
597
|
+
let standardOk = true;
|
|
598
|
+
for (const check of standardChecks) {
|
|
399
599
|
const exists = fs.existsSync(check.path);
|
|
400
|
-
|
|
401
|
-
|
|
600
|
+
console.log(` ${exists ? chalk_1.default.green("✓") : chalk_1.default.red("✗")} ${check.name}`);
|
|
601
|
+
if (!exists)
|
|
602
|
+
standardOk = false;
|
|
603
|
+
}
|
|
604
|
+
// Extensions 확인
|
|
605
|
+
const installedExtensions = [];
|
|
606
|
+
for (const key of Object.keys(EXTENSION_PACKAGES)) {
|
|
607
|
+
if (fs.existsSync(path.join(semoSystemDir, key))) {
|
|
608
|
+
installedExtensions.push(key);
|
|
402
609
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
610
|
+
}
|
|
611
|
+
if (installedExtensions.length > 0) {
|
|
612
|
+
console.log(chalk_1.default.white.bold("\nExtensions:"));
|
|
613
|
+
for (const pkg of installedExtensions) {
|
|
614
|
+
console.log(chalk_1.default.green(` ✓ ${pkg}`));
|
|
406
615
|
}
|
|
407
616
|
}
|
|
617
|
+
// 구조 확인
|
|
618
|
+
console.log(chalk_1.default.white.bold("\n구조:"));
|
|
619
|
+
const structureChecks = [
|
|
620
|
+
{ name: ".claude/", path: path.join(cwd, ".claude") },
|
|
621
|
+
{ name: ".claude/settings.json", path: path.join(cwd, ".claude", "settings.json") },
|
|
622
|
+
{ name: ".claude/memory/", path: path.join(cwd, ".claude", "memory") },
|
|
623
|
+
{ name: ".claude/memory/context.md", path: path.join(cwd, ".claude", "memory", "context.md") },
|
|
624
|
+
];
|
|
625
|
+
let structureOk = true;
|
|
626
|
+
for (const check of structureChecks) {
|
|
627
|
+
const exists = fs.existsSync(check.path);
|
|
628
|
+
console.log(` ${exists ? chalk_1.default.green("✓") : chalk_1.default.red("✗")} ${check.name}`);
|
|
629
|
+
if (!exists)
|
|
630
|
+
structureOk = false;
|
|
631
|
+
}
|
|
408
632
|
console.log();
|
|
409
|
-
if (
|
|
633
|
+
if (standardOk && structureOk) {
|
|
410
634
|
console.log(chalk_1.default.green.bold("SEMO가 정상적으로 설치되어 있습니다."));
|
|
411
635
|
}
|
|
412
636
|
else {
|
|
@@ -418,7 +642,7 @@ program
|
|
|
418
642
|
program
|
|
419
643
|
.command("update")
|
|
420
644
|
.description("SEMO를 최신 버전으로 업데이트합니다")
|
|
421
|
-
.action(() => {
|
|
645
|
+
.action(async () => {
|
|
422
646
|
console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 업데이트\n"));
|
|
423
647
|
const cwd = process.cwd();
|
|
424
648
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
@@ -426,18 +650,38 @@ program
|
|
|
426
650
|
console.log(chalk_1.default.red("SEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요."));
|
|
427
651
|
process.exit(1);
|
|
428
652
|
}
|
|
429
|
-
|
|
653
|
+
// 설치된 Extensions 확인
|
|
654
|
+
const installedExtensions = [];
|
|
655
|
+
for (const key of Object.keys(EXTENSION_PACKAGES)) {
|
|
656
|
+
if (fs.existsSync(path.join(semoSystemDir, key))) {
|
|
657
|
+
installedExtensions.push(key);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
console.log(chalk_1.default.cyan("업데이트 대상:"));
|
|
661
|
+
console.log(chalk_1.default.gray(" - semo-core"));
|
|
662
|
+
console.log(chalk_1.default.gray(" - semo-skills"));
|
|
663
|
+
installedExtensions.forEach(pkg => {
|
|
664
|
+
console.log(chalk_1.default.gray(` - ${pkg}`));
|
|
665
|
+
});
|
|
666
|
+
const spinner = (0, ora_1.default)("\n최신 버전 다운로드 중...").start();
|
|
430
667
|
try {
|
|
431
|
-
// 임시 디렉토리에 최신 버전 clone
|
|
432
668
|
const tempDir = path.join(cwd, ".semo-temp");
|
|
433
|
-
(0, child_process_1.execSync)(`rm -rf ${tempDir}`, { stdio: "pipe" });
|
|
434
|
-
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} ${tempDir}`, { stdio: "pipe" });
|
|
435
|
-
//
|
|
436
|
-
(0, child_process_1.execSync)(`rm -rf ${semoSystemDir}/semo-core ${semoSystemDir}/semo-skills`, { stdio: "pipe" });
|
|
437
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-core ${semoSystemDir}
|
|
438
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-skills ${semoSystemDir}
|
|
439
|
-
//
|
|
440
|
-
|
|
669
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
670
|
+
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
|
|
671
|
+
// Standard 업데이트
|
|
672
|
+
(0, child_process_1.execSync)(`rm -rf "${semoSystemDir}/semo-core" "${semoSystemDir}/semo-skills"`, { stdio: "pipe" });
|
|
673
|
+
(0, child_process_1.execSync)(`cp -r "${tempDir}/semo-core" "${semoSystemDir}/"`, { stdio: "pipe" });
|
|
674
|
+
(0, child_process_1.execSync)(`cp -r "${tempDir}/semo-skills" "${semoSystemDir}/"`, { stdio: "pipe" });
|
|
675
|
+
// Extensions 업데이트
|
|
676
|
+
for (const pkg of installedExtensions) {
|
|
677
|
+
const srcPath = path.join(tempDir, "packages", pkg);
|
|
678
|
+
const destPath = path.join(semoSystemDir, pkg);
|
|
679
|
+
if (fs.existsSync(srcPath)) {
|
|
680
|
+
(0, child_process_1.execSync)(`rm -rf "${destPath}"`, { stdio: "pipe" });
|
|
681
|
+
(0, child_process_1.execSync)(`cp -r "${srcPath}" "${destPath}"`, { stdio: "pipe" });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
441
685
|
spinner.succeed("SEMO 업데이트 완료");
|
|
442
686
|
}
|
|
443
687
|
catch (error) {
|