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