@team-semicolon/semo-cli 2.0.1 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +118 -118
  2. package/dist/index.js +607 -198
  3. package/package.json +48 -48
package/README.md CHANGED
@@ -1,118 +1,118 @@
1
- # @team-semicolon/semo-cli
2
-
3
- > SEMO CLI - AI Agent Orchestration Framework Installer
4
-
5
- ## 개요
6
-
7
- Gemini의 하이브리드 전략에 따라 SEMO를 자동 설치하는 CLI 도구입니다.
8
-
9
- ```bash
10
- npx @team-semicolon/semo-cli init
11
- ```
12
-
13
- ## 동작 방식
14
-
15
- `semo init` 명령어는 다음을 자동으로 수행합니다:
16
-
17
- ### 1. White Box 설정 (Git Subtree)
18
-
19
- 에이전트가 **읽고 학습**해야 하는 지식 베이스:
20
-
21
- ```
22
- semo-system/
23
- ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
24
- └── semo-skills/ # Layer 1: coder, tester, planner
25
- ```
26
-
27
- ### 2. Black Box 설정 (MCP Server)
28
-
29
- 토큰이 격리된 **외부 연동 도구**:
30
-
31
- ```json
32
- // .claude/settings.json
33
- {
34
- "mcpServers": {
35
- "semo-integrations": {
36
- "command": "npx",
37
- "args": ["-y", "@team-semicolon/semo-mcp"]
38
- }
39
- }
40
- }
41
- ```
42
-
43
- ### 3. Context Mesh 초기화
44
-
45
- 세션 간 **컨텍스트 영속화**:
46
-
47
- ```
48
- .claude/memory/
49
- ├── context.md # 프로젝트 상태
50
- ├── decisions.md # 아키텍처 결정
51
- └── rules/ # 프로젝트별 규칙
52
- ```
53
-
54
- ## 명령어
55
-
56
- ### init
57
-
58
- 현재 프로젝트에 SEMO를 설치합니다.
59
-
60
- ```bash
61
- semo init # 기본 설치
62
- semo init --force # 기존 설정 덮어쓰기
63
- semo init --skip-mcp # MCP 설정 생략
64
- semo init --skip-subtree # Git Subtree 생략
65
- ```
66
-
67
- ### status
68
-
69
- SEMO 설치 상태를 확인합니다.
70
-
71
- ```bash
72
- semo status
73
- ```
74
-
75
- ### update
76
-
77
- SEMO를 최신 버전으로 업데이트합니다.
78
-
79
- ```bash
80
- semo update
81
- ```
82
-
83
- ## 설치 후 구조
84
-
85
- ```
86
- your-project/
87
- ├── .claude/
88
- │ ├── CLAUDE.md # 프로젝트 설정
89
- │ ├── settings.json # MCP 서버 설정
90
- │ ├── memory/ # Context Mesh
91
- │ ├── agents → semo-system/semo-core/agents
92
- │ └── skills → semo-system/semo-skills
93
-
94
- └── semo-system/ # White Box (읽기 전용)
95
- ├── semo-core/
96
- └── semo-skills/
97
- ```
98
-
99
- ## 환경변수
100
-
101
- MCP 연동을 위해 다음 환경변수를 설정하세요:
102
-
103
- | 변수 | 설명 |
104
- |------|------|
105
- | `GITHUB_TOKEN` | GitHub API 토큰 |
106
- | `SLACK_BOT_TOKEN` | Slack Bot 토큰 |
107
- | `SUPABASE_URL` | Supabase 프로젝트 URL |
108
- | `SUPABASE_KEY` | Supabase 서비스 키 |
109
-
110
- ## 참조
111
-
112
- - [SEMO 레포지토리](https://github.com/semicolon-devteam/semo)
113
- - [SEMO MCP Server](../mcp-server/README.md)
114
- - [Gemini 하이브리드 전략](../../docs/SEMO_ARCHITECTURE_REVIEW.md)
115
-
116
- ## 라이선스
117
-
118
- MIT
1
+ # @team-semicolon/semo-cli
2
+
3
+ > SEMO CLI - AI Agent Orchestration Framework Installer
4
+
5
+ ## 개요
6
+
7
+ Gemini의 하이브리드 전략에 따라 SEMO를 자동 설치하는 CLI 도구입니다.
8
+
9
+ ```bash
10
+ npx @team-semicolon/semo-cli init
11
+ ```
12
+
13
+ ## 동작 방식
14
+
15
+ `semo init` 명령어는 다음을 자동으로 수행합니다:
16
+
17
+ ### 1. White Box 설정 (Git Subtree)
18
+
19
+ 에이전트가 **읽고 학습**해야 하는 지식 베이스:
20
+
21
+ ```
22
+ semo-system/
23
+ ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
24
+ └── semo-skills/ # Layer 1: coder, tester, planner
25
+ ```
26
+
27
+ ### 2. Black Box 설정 (MCP Server)
28
+
29
+ 토큰이 격리된 **외부 연동 도구**:
30
+
31
+ ```json
32
+ // .claude/settings.json
33
+ {
34
+ "mcpServers": {
35
+ "semo-integrations": {
36
+ "command": "npx",
37
+ "args": ["-y", "@team-semicolon/semo-mcp"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### 3. Context Mesh 초기화
44
+
45
+ 세션 간 **컨텍스트 영속화**:
46
+
47
+ ```
48
+ .claude/memory/
49
+ ├── context.md # 프로젝트 상태
50
+ ├── decisions.md # 아키텍처 결정
51
+ └── rules/ # 프로젝트별 규칙
52
+ ```
53
+
54
+ ## 명령어
55
+
56
+ ### init
57
+
58
+ 현재 프로젝트에 SEMO를 설치합니다.
59
+
60
+ ```bash
61
+ semo init # 기본 설치
62
+ semo init --force # 기존 설정 덮어쓰기
63
+ semo init --skip-mcp # MCP 설정 생략
64
+ semo init --skip-subtree # Git Subtree 생략
65
+ ```
66
+
67
+ ### status
68
+
69
+ SEMO 설치 상태를 확인합니다.
70
+
71
+ ```bash
72
+ semo status
73
+ ```
74
+
75
+ ### update
76
+
77
+ SEMO를 최신 버전으로 업데이트합니다.
78
+
79
+ ```bash
80
+ semo update
81
+ ```
82
+
83
+ ## 설치 후 구조
84
+
85
+ ```
86
+ your-project/
87
+ ├── .claude/
88
+ │ ├── CLAUDE.md # 프로젝트 설정
89
+ │ ├── settings.json # MCP 서버 설정
90
+ │ ├── memory/ # Context Mesh
91
+ │ ├── agents → semo-system/semo-core/agents
92
+ │ └── skills → semo-system/semo-skills
93
+
94
+ └── semo-system/ # White Box (읽기 전용)
95
+ ├── semo-core/
96
+ └── semo-skills/
97
+ ```
98
+
99
+ ## 환경변수
100
+
101
+ MCP 연동을 위해 다음 환경변수를 설정하세요:
102
+
103
+ | 변수 | 설명 |
104
+ |------|------|
105
+ | `GITHUB_TOKEN` | GitHub API 토큰 |
106
+ | `SLACK_BOT_TOKEN` | Slack Bot 토큰 |
107
+ | `SUPABASE_URL` | Supabase 프로젝트 URL |
108
+ | `SUPABASE_KEY` | Supabase 서비스 키 |
109
+
110
+ ## 참조
111
+
112
+ - [SEMO 레포지토리](https://github.com/semicolon-devteam/semo)
113
+ - [SEMO MCP Server](../mcp-server/README.md)
114
+ - [Gemini 하이브리드 전략](../../docs/SEMO_ARCHITECTURE_REVIEW.md)
115
+
116
+ ## 라이선스
117
+
118
+ MIT
package/dist/index.js CHANGED
@@ -58,7 +58,69 @@ const inquirer_1 = __importDefault(require("inquirer"));
58
58
  const child_process_1 = require("child_process");
59
59
  const fs = __importStar(require("fs"));
60
60
  const path = __importStar(require("path"));
61
- const VERSION = "2.0.1";
61
+ const os = __importStar(require("os"));
62
+ const VERSION = "2.0.2";
63
+ // === Windows 지원 유틸리티 ===
64
+ const isWindows = os.platform() === "win32";
65
+ /**
66
+ * Windows에서 Junction 링크를 생성하거나, Unix에서 심볼릭 링크를 생성
67
+ * Junction은 관리자 권한 없이 디렉토리 링크를 생성할 수 있음
68
+ */
69
+ function createSymlinkOrJunction(targetPath, linkPath) {
70
+ if (isWindows) {
71
+ // Windows: Junction 사용 (절대 경로 필요)
72
+ const absoluteTarget = path.resolve(targetPath);
73
+ try {
74
+ (0, child_process_1.execSync)(`cmd /c "mklink /J "${linkPath}" "${absoluteTarget}""`, { stdio: "pipe" });
75
+ }
76
+ catch {
77
+ // fallback: 디렉토리 복사
78
+ console.log(chalk_1.default.yellow(` ⚠ Junction 생성 실패, 복사로 대체: ${path.basename(linkPath)}`));
79
+ (0, child_process_1.execSync)(`xcopy /E /I /Q "${absoluteTarget}" "${linkPath}"`, { stdio: "pipe" });
80
+ }
81
+ }
82
+ else {
83
+ // Unix: 상대 경로 심볼릭 링크
84
+ const relativeTarget = path.relative(path.dirname(linkPath), targetPath);
85
+ fs.symlinkSync(relativeTarget, linkPath);
86
+ }
87
+ }
88
+ /**
89
+ * 플랫폼에 맞는 rm -rf 실행
90
+ */
91
+ function removeRecursive(targetPath) {
92
+ if (!fs.existsSync(targetPath))
93
+ return;
94
+ if (isWindows) {
95
+ try {
96
+ const stats = fs.lstatSync(targetPath);
97
+ if (stats.isSymbolicLink()) {
98
+ // Junction/Symlink는 rmdir로 제거 (내용물 보존)
99
+ (0, child_process_1.execSync)(`cmd /c "rmdir "${targetPath}""`, { stdio: "pipe" });
100
+ }
101
+ else {
102
+ (0, child_process_1.execSync)(`cmd /c "rd /s /q "${targetPath}""`, { stdio: "pipe" });
103
+ }
104
+ }
105
+ catch {
106
+ fs.rmSync(targetPath, { recursive: true, force: true });
107
+ }
108
+ }
109
+ else {
110
+ (0, child_process_1.execSync)(`rm -rf "${targetPath}"`, { stdio: "pipe" });
111
+ }
112
+ }
113
+ /**
114
+ * 플랫폼에 맞는 cp -r 실행
115
+ */
116
+ function copyRecursive(src, dest) {
117
+ if (isWindows) {
118
+ (0, child_process_1.execSync)(`xcopy /E /I /Q "${src}" "${dest}"`, { stdio: "pipe" });
119
+ }
120
+ else {
121
+ (0, child_process_1.execSync)(`cp -r "${src}" "${dest}"`, { stdio: "pipe" });
122
+ }
123
+ }
62
124
  const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
63
125
  // 확장 패키지 정의
64
126
  const EXTENSION_PACKAGES = {
@@ -157,18 +219,22 @@ program
157
219
  }
158
220
  // 4. Standard 설치 (semo-core + semo-skills)
159
221
  await setupStandard(cwd, options.force);
160
- // 5. Extensions 설치
222
+ // 5. Extensions 다운로드 (심볼릭 링크는 아직)
161
223
  if (extensionsToInstall.length > 0) {
162
- await setupExtensions(cwd, extensionsToInstall, options.force);
224
+ await downloadExtensions(cwd, extensionsToInstall, options.force);
163
225
  }
164
- // 6. MCP 설정
226
+ // 6. MCP 설정 (Extension 설정 병합 포함)
165
227
  if (!options.skipMcp) {
166
- await setupMCP(cwd, options.force);
228
+ await setupMCP(cwd, extensionsToInstall, options.force);
167
229
  }
168
230
  // 7. Context Mesh 초기화
169
231
  await setupContextMesh(cwd);
170
232
  // 8. CLAUDE.md 생성
171
233
  await setupClaudeMd(cwd, extensionsToInstall, options.force);
234
+ // 9. Extensions 심볼릭 링크 (agents/skills 병합)
235
+ if (extensionsToInstall.length > 0) {
236
+ await setupExtensionSymlinks(cwd, extensionsToInstall);
237
+ }
172
238
  // 완료 메시지
173
239
  console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
174
240
  console.log(chalk_1.default.cyan("설치된 구성:"));
@@ -203,24 +269,24 @@ async function setupStandard(cwd, force) {
203
269
  console.log(chalk_1.default.gray(" → semo-system/ 건너뜀"));
204
270
  return;
205
271
  }
206
- (0, child_process_1.execSync)(`rm -rf "${semoSystemDir}"`, { stdio: "pipe" });
272
+ removeRecursive(semoSystemDir);
207
273
  console.log(chalk_1.default.green(" ✓ 기존 semo-system/ 삭제됨"));
208
274
  }
209
275
  const spinner = (0, ora_1.default)("semo-core, semo-skills 다운로드 중...").start();
210
276
  try {
211
277
  const tempDir = path.join(cwd, ".semo-temp");
212
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
278
+ removeRecursive(tempDir);
213
279
  (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
214
280
  fs.mkdirSync(semoSystemDir, { recursive: true });
215
281
  // semo-core 복사
216
282
  if (fs.existsSync(path.join(tempDir, "semo-core"))) {
217
- (0, child_process_1.execSync)(`cp -r "${tempDir}/semo-core" "${semoSystemDir}/"`, { stdio: "pipe" });
283
+ copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
218
284
  }
219
285
  // semo-skills 복사
220
286
  if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
221
- (0, child_process_1.execSync)(`cp -r "${tempDir}/semo-skills" "${semoSystemDir}/"`, { stdio: "pipe" });
287
+ copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
222
288
  }
223
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
289
+ removeRecursive(tempDir);
224
290
  spinner.succeed("Standard 설치 완료");
225
291
  // 심볼릭 링크 생성
226
292
  await createStandardSymlinks(cwd);
@@ -234,23 +300,43 @@ async function setupStandard(cwd, force) {
234
300
  async function createStandardSymlinks(cwd) {
235
301
  const claudeDir = path.join(cwd, ".claude");
236
302
  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)) {
242
- fs.symlinkSync("../semo-system/semo-core/agents", agentsLink);
243
- console.log(chalk_1.default.green(" ✓ .claude/agents → semo-system/semo-core/agents"));
303
+ // agents 디렉토리 생성 및 개별 링크 (Extension 병합 지원)
304
+ const claudeAgentsDir = path.join(claudeDir, "agents");
305
+ const coreAgentsDir = path.join(semoSystemDir, "semo-core", "agents");
306
+ if (fs.existsSync(coreAgentsDir)) {
307
+ // 기존 심볼릭 링크면 삭제 (디렉토리로 변경)
308
+ if (fs.existsSync(claudeAgentsDir) && fs.lstatSync(claudeAgentsDir).isSymbolicLink()) {
309
+ fs.unlinkSync(claudeAgentsDir);
244
310
  }
311
+ fs.mkdirSync(claudeAgentsDir, { recursive: true });
312
+ const agents = fs.readdirSync(coreAgentsDir).filter(f => fs.statSync(path.join(coreAgentsDir, f)).isDirectory());
313
+ for (const agent of agents) {
314
+ const agentLink = path.join(claudeAgentsDir, agent);
315
+ const agentTarget = path.join(coreAgentsDir, agent);
316
+ if (!fs.existsSync(agentLink)) {
317
+ createSymlinkOrJunction(agentTarget, agentLink);
318
+ }
319
+ }
320
+ console.log(chalk_1.default.green(` ✓ .claude/agents/ (${agents.length}개 agent 링크됨)`));
245
321
  }
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)) {
251
- fs.symlinkSync("../semo-system/semo-skills", skillsLink);
252
- console.log(chalk_1.default.green(" ✓ .claude/skills → semo-system/semo-skills"));
322
+ // skills 디렉토리 생성 및 개별 링크 (Extension 병합 지원)
323
+ const claudeSkillsDir = path.join(claudeDir, "skills");
324
+ const coreSkillsDir = path.join(semoSystemDir, "semo-skills");
325
+ if (fs.existsSync(coreSkillsDir)) {
326
+ // 기존 심볼릭 링크면 삭제 (디렉토리로 변경)
327
+ if (fs.existsSync(claudeSkillsDir) && fs.lstatSync(claudeSkillsDir).isSymbolicLink()) {
328
+ removeRecursive(claudeSkillsDir);
253
329
  }
330
+ fs.mkdirSync(claudeSkillsDir, { recursive: true });
331
+ const skills = fs.readdirSync(coreSkillsDir).filter(f => fs.statSync(path.join(coreSkillsDir, f)).isDirectory());
332
+ for (const skill of skills) {
333
+ const skillLink = path.join(claudeSkillsDir, skill);
334
+ const skillTarget = path.join(coreSkillsDir, skill);
335
+ if (!fs.existsSync(skillLink)) {
336
+ createSymlinkOrJunction(skillTarget, skillLink);
337
+ }
338
+ }
339
+ console.log(chalk_1.default.green(` ✓ .claude/skills/ (${skills.length}개 skill 링크됨)`));
254
340
  }
255
341
  // commands 링크
256
342
  const commandsDir = path.join(claudeDir, "commands");
@@ -259,14 +345,14 @@ async function createStandardSymlinks(cwd) {
259
345
  if (!fs.existsSync(semoCommandsLink)) {
260
346
  const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
261
347
  if (fs.existsSync(commandsTarget)) {
262
- fs.symlinkSync("../../semo-system/semo-core/commands/SEMO", semoCommandsLink);
348
+ createSymlinkOrJunction(commandsTarget, semoCommandsLink);
263
349
  console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
264
350
  }
265
351
  }
266
352
  }
267
- // === Extensions 설치 ===
268
- async function setupExtensions(cwd, packages, force) {
269
- console.log(chalk_1.default.cyan("\n📦 Extensions 설치"));
353
+ // === Extensions 다운로드 (심볼릭 링크 제외) ===
354
+ async function downloadExtensions(cwd, packages, force) {
355
+ console.log(chalk_1.default.cyan("\n📦 Extensions 다운로드"));
270
356
  packages.forEach(pkg => {
271
357
  console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}`));
272
358
  });
@@ -287,36 +373,113 @@ async function setupExtensions(cwd, packages, force) {
287
373
  console.log(chalk_1.default.yellow(` ⚠ ${pkg}/ 이미 존재 (건너뜀)`));
288
374
  continue;
289
375
  }
290
- (0, child_process_1.execSync)(`rm -rf "${destPath}"`, { stdio: "pipe" });
291
- (0, child_process_1.execSync)(`cp -r "${srcPath}" "${destPath}"`, { stdio: "pipe" });
376
+ removeRecursive(destPath);
377
+ copyRecursive(srcPath, destPath);
292
378
  }
293
379
  }
294
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
295
- spinner.succeed(`Extensions 설치 완료 (${packages.length}개)`);
296
- // Extensions 심볼릭 링크 생성
297
- await createExtensionSymlinks(cwd, packages);
380
+ removeRecursive(tempDir);
381
+ spinner.succeed(`Extensions 다운로드 완료 (${packages.length}개)`);
298
382
  }
299
383
  catch (error) {
300
- spinner.fail("Extensions 설치 실패");
384
+ spinner.fail("Extensions 다운로드 실패");
301
385
  console.error(chalk_1.default.red(` ${error}`));
302
386
  }
303
387
  }
304
- // === Extensions 심볼릭 링크 ===
305
- async function createExtensionSymlinks(cwd, packages) {
388
+ // === Extensions 심볼릭 링크 설정 (agents/skills 병합) ===
389
+ async function setupExtensionSymlinks(cwd, packages) {
390
+ console.log(chalk_1.default.cyan("\n🔗 Extensions 연결"));
306
391
  const claudeDir = path.join(cwd, ".claude");
307
392
  const semoSystemDir = path.join(cwd, "semo-system");
308
393
  for (const pkg of packages) {
309
- // sax-{pkg} 호환 링크 (기존 SAX 사용자용)
310
- const saxPkgLink = path.join(claudeDir, `sax-${pkg}`);
311
394
  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}`));
395
+ if (!fs.existsSync(pkgPath))
396
+ continue;
397
+ // Note: .claude/semo-{pkg} 링크는 생성하지 않음 (불필요)
398
+ // Extension의 agents/skills만 개별 링크하여 병합
399
+ // 1. Extension의 agents를 .claude/agents/에 개별 링크
400
+ const extAgentsDir = path.join(pkgPath, "agents");
401
+ const claudeAgentsDir = path.join(claudeDir, "agents");
402
+ if (fs.existsSync(extAgentsDir)) {
403
+ const agents = fs.readdirSync(extAgentsDir).filter(f => fs.statSync(path.join(extAgentsDir, f)).isDirectory());
404
+ for (const agent of agents) {
405
+ const agentLink = path.join(claudeAgentsDir, agent);
406
+ const agentTarget = path.join(extAgentsDir, agent);
407
+ if (!fs.existsSync(agentLink)) {
408
+ createSymlinkOrJunction(agentTarget, agentLink);
409
+ console.log(chalk_1.default.green(` ✓ .claude/agents/${agent} → semo-system/${pkg}/agents/${agent}`));
410
+ }
411
+ }
412
+ }
413
+ // 3. Extension의 skills를 .claude/skills/에 개별 링크
414
+ const extSkillsDir = path.join(pkgPath, "skills");
415
+ const claudeSkillsDir = path.join(claudeDir, "skills");
416
+ if (fs.existsSync(extSkillsDir)) {
417
+ const skills = fs.readdirSync(extSkillsDir).filter(f => fs.statSync(path.join(extSkillsDir, f)).isDirectory());
418
+ for (const skill of skills) {
419
+ const skillLink = path.join(claudeSkillsDir, skill);
420
+ const skillTarget = path.join(extSkillsDir, skill);
421
+ if (!fs.existsSync(skillLink)) {
422
+ createSymlinkOrJunction(skillTarget, skillLink);
423
+ console.log(chalk_1.default.green(` ✓ .claude/skills/${skill} → semo-system/${pkg}/skills/${skill}`));
424
+ }
425
+ }
315
426
  }
316
427
  }
317
428
  }
429
+ const BASE_MCP_SERVERS = [
430
+ {
431
+ name: "semo-integrations",
432
+ command: "npx",
433
+ args: ["-y", "@team-semicolon/semo-mcp"],
434
+ env: {
435
+ GITHUB_TOKEN: "${GITHUB_TOKEN}",
436
+ SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
437
+ SUPABASE_URL: "${SUPABASE_URL}",
438
+ SUPABASE_KEY: "${SUPABASE_KEY}",
439
+ },
440
+ },
441
+ {
442
+ name: "context7",
443
+ command: "npx",
444
+ args: ["-y", "@upstash/context7-mcp"],
445
+ },
446
+ {
447
+ name: "sequential-thinking",
448
+ command: "npx",
449
+ args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
450
+ },
451
+ ];
452
+ // === Claude MCP 등록 함수 ===
453
+ function registerMCPServer(server) {
454
+ try {
455
+ // 환경변수가 있는 경우 --env 옵션 추가
456
+ const envArgs = [];
457
+ if (server.env) {
458
+ for (const [key, value] of Object.entries(server.env)) {
459
+ envArgs.push("-e", `${key}=${value}`);
460
+ }
461
+ }
462
+ // claude mcp add 명령어 실행
463
+ const args = [
464
+ "mcp", "add",
465
+ server.name,
466
+ "--",
467
+ server.command,
468
+ ...server.args,
469
+ ];
470
+ // 환경변수가 있으면 명령어 앞에 추가
471
+ if (envArgs.length > 0) {
472
+ args.splice(2, 0, ...envArgs);
473
+ }
474
+ (0, child_process_1.execSync)(`claude ${args.join(" ")}`, { stdio: "pipe" });
475
+ return { success: true };
476
+ }
477
+ catch (error) {
478
+ return { success: false, error: String(error) };
479
+ }
480
+ }
318
481
  // === MCP 설정 ===
319
- async function setupMCP(cwd, force) {
482
+ async function setupMCP(cwd, extensions, force) {
320
483
  console.log(chalk_1.default.cyan("\n🔧 Black Box 설정 (MCP Server)"));
321
484
  console.log(chalk_1.default.gray(" 토큰이 격리된 외부 연동 도구\n"));
322
485
  const settingsPath = path.join(cwd, ".claude", "settings.json");
@@ -327,22 +490,192 @@ async function setupMCP(cwd, force) {
327
490
  return;
328
491
  }
329
492
  }
493
+ // Base settings (Standard)
330
494
  const settings = {
331
- mcpServers: {
332
- "semo-integrations": {
333
- command: "npx",
334
- args: ["-y", "@team-semicolon/semo-mcp"],
335
- env: {
336
- GITHUB_TOKEN: "${GITHUB_TOKEN}",
337
- SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
338
- SUPABASE_URL: "${SUPABASE_URL}",
339
- SUPABASE_KEY: "${SUPABASE_KEY}",
340
- },
341
- },
342
- },
495
+ mcpServers: {},
343
496
  };
497
+ // MCP 서버 목록 수집
498
+ const allServers = [...BASE_MCP_SERVERS];
499
+ // Extension settings 병합
500
+ const semoSystemDir = path.join(cwd, "semo-system");
501
+ for (const pkg of extensions) {
502
+ const extSettingsPath = path.join(semoSystemDir, pkg, "settings.local.json");
503
+ if (fs.existsSync(extSettingsPath)) {
504
+ try {
505
+ const extSettings = JSON.parse(fs.readFileSync(extSettingsPath, "utf-8"));
506
+ // mcpServers 병합
507
+ if (extSettings.mcpServers) {
508
+ for (const [name, config] of Object.entries(extSettings.mcpServers)) {
509
+ const serverConfig = config;
510
+ allServers.push({
511
+ name,
512
+ command: serverConfig.command,
513
+ args: serverConfig.args,
514
+ env: serverConfig.env,
515
+ });
516
+ }
517
+ console.log(chalk_1.default.gray(` + ${pkg} MCP 설정 수집됨`));
518
+ }
519
+ // permissions 병합
520
+ if (extSettings.permissions) {
521
+ if (!settings.permissions) {
522
+ settings.permissions = { allow: [], deny: [] };
523
+ }
524
+ if (extSettings.permissions.allow) {
525
+ settings.permissions.allow = [
526
+ ...(settings.permissions.allow || []),
527
+ ...extSettings.permissions.allow,
528
+ ];
529
+ }
530
+ if (extSettings.permissions.deny) {
531
+ settings.permissions.deny = [
532
+ ...(settings.permissions.deny || []),
533
+ ...extSettings.permissions.deny,
534
+ ];
535
+ }
536
+ console.log(chalk_1.default.gray(` + ${pkg} permissions 병합됨`));
537
+ }
538
+ }
539
+ catch (error) {
540
+ console.log(chalk_1.default.yellow(` ⚠ ${pkg} settings.local.json 파싱 실패`));
541
+ }
542
+ }
543
+ }
544
+ // settings.json에 mcpServers 저장 (백업용)
545
+ for (const server of allServers) {
546
+ const serverConfig = {
547
+ command: server.command,
548
+ args: server.args,
549
+ };
550
+ if (server.env) {
551
+ serverConfig.env = server.env;
552
+ }
553
+ settings.mcpServers[server.name] = serverConfig;
554
+ }
344
555
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
345
- console.log(chalk_1.default.green("✓ .claude/settings.json 생성됨 (MCP 설정)"));
556
+ console.log(chalk_1.default.green("✓ .claude/settings.json 생성됨 (MCP 설정 백업)"));
557
+ // Claude Code에 MCP 서버 등록 시도
558
+ console.log(chalk_1.default.cyan("\n🔌 Claude Code에 MCP 서버 등록 중..."));
559
+ const successServers = [];
560
+ const failedServers = [];
561
+ for (const server of allServers) {
562
+ const spinner = (0, ora_1.default)(` ${server.name} 등록 중...`).start();
563
+ const result = registerMCPServer(server);
564
+ if (result.success) {
565
+ spinner.succeed(` ${server.name} 등록 완료`);
566
+ successServers.push(server.name);
567
+ }
568
+ else {
569
+ spinner.fail(` ${server.name} 등록 실패`);
570
+ failedServers.push(server);
571
+ }
572
+ }
573
+ // 결과 요약
574
+ if (successServers.length > 0) {
575
+ console.log(chalk_1.default.green(`\n✓ ${successServers.length}개 MCP 서버 자동 등록 완료`));
576
+ }
577
+ // 실패한 서버가 있으면 수동 등록 안내
578
+ if (failedServers.length > 0) {
579
+ console.log(chalk_1.default.yellow(`\n⚠ ${failedServers.length}개 MCP 서버 자동 등록 실패`));
580
+ console.log(chalk_1.default.cyan("\n📋 수동 등록 명령어:"));
581
+ console.log(chalk_1.default.gray(" 다음 명령어를 터미널에서 실행하세요:\n"));
582
+ for (const server of failedServers) {
583
+ const envArgs = server.env
584
+ ? Object.entries(server.env).map(([k, v]) => `-e ${k}="${v}"`).join(" ")
585
+ : "";
586
+ const cmd = `claude mcp add ${server.name} ${envArgs} -- ${server.command} ${server.args.join(" ")}`.trim();
587
+ console.log(chalk_1.default.white(` ${cmd}`));
588
+ }
589
+ console.log();
590
+ }
591
+ }
592
+ // === Extension settings 병합 (add 명령어용) ===
593
+ async function mergeExtensionSettings(cwd, packages) {
594
+ const settingsPath = path.join(cwd, ".claude", "settings.json");
595
+ const semoSystemDir = path.join(cwd, "semo-system");
596
+ if (!fs.existsSync(settingsPath)) {
597
+ console.log(chalk_1.default.yellow(" ⚠ settings.json이 없습니다. 'semo init'을 먼저 실행하세요."));
598
+ return;
599
+ }
600
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
601
+ const newServers = [];
602
+ for (const pkg of packages) {
603
+ const extSettingsPath = path.join(semoSystemDir, pkg, "settings.local.json");
604
+ if (fs.existsSync(extSettingsPath)) {
605
+ try {
606
+ const extSettings = JSON.parse(fs.readFileSync(extSettingsPath, "utf-8"));
607
+ // mcpServers 병합
608
+ if (extSettings.mcpServers) {
609
+ settings.mcpServers = settings.mcpServers || {};
610
+ for (const [name, config] of Object.entries(extSettings.mcpServers)) {
611
+ const serverConfig = config;
612
+ settings.mcpServers[name] = serverConfig;
613
+ newServers.push({
614
+ name,
615
+ command: serverConfig.command,
616
+ args: serverConfig.args,
617
+ env: serverConfig.env,
618
+ });
619
+ }
620
+ console.log(chalk_1.default.gray(` + ${pkg} MCP 설정 병합됨`));
621
+ }
622
+ // permissions 병합
623
+ if (extSettings.permissions) {
624
+ settings.permissions = settings.permissions || { allow: [], deny: [] };
625
+ if (extSettings.permissions.allow) {
626
+ settings.permissions.allow = [
627
+ ...(settings.permissions.allow || []),
628
+ ...extSettings.permissions.allow,
629
+ ];
630
+ }
631
+ if (extSettings.permissions.deny) {
632
+ settings.permissions.deny = [
633
+ ...(settings.permissions.deny || []),
634
+ ...extSettings.permissions.deny,
635
+ ];
636
+ }
637
+ console.log(chalk_1.default.gray(` + ${pkg} permissions 병합됨`));
638
+ }
639
+ }
640
+ catch (error) {
641
+ console.log(chalk_1.default.yellow(` ⚠ ${pkg} settings.local.json 파싱 실패`));
642
+ }
643
+ }
644
+ }
645
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
646
+ // 새 MCP 서버 Claude Code에 등록
647
+ if (newServers.length > 0) {
648
+ console.log(chalk_1.default.cyan("\n🔌 Claude Code에 MCP 서버 등록 중..."));
649
+ const successServers = [];
650
+ const failedServers = [];
651
+ for (const server of newServers) {
652
+ const spinner = (0, ora_1.default)(` ${server.name} 등록 중...`).start();
653
+ const result = registerMCPServer(server);
654
+ if (result.success) {
655
+ spinner.succeed(` ${server.name} 등록 완료`);
656
+ successServers.push(server.name);
657
+ }
658
+ else {
659
+ spinner.fail(` ${server.name} 등록 실패`);
660
+ failedServers.push(server);
661
+ }
662
+ }
663
+ if (successServers.length > 0) {
664
+ console.log(chalk_1.default.green(`\n✓ ${successServers.length}개 MCP 서버 자동 등록 완료`));
665
+ }
666
+ if (failedServers.length > 0) {
667
+ console.log(chalk_1.default.yellow(`\n⚠ ${failedServers.length}개 MCP 서버 자동 등록 실패`));
668
+ console.log(chalk_1.default.cyan("\n📋 수동 등록 명령어:"));
669
+ for (const server of failedServers) {
670
+ const envArgs = server.env
671
+ ? Object.entries(server.env).map(([k, v]) => `-e ${k}="${v}"`).join(" ")
672
+ : "";
673
+ const cmd = `claude mcp add ${server.name} ${envArgs} -- ${server.command} ${server.args.join(" ")}`.trim();
674
+ console.log(chalk_1.default.white(` ${cmd}`));
675
+ }
676
+ console.log();
677
+ }
678
+ }
346
679
  }
347
680
  // === Context Mesh 초기화 ===
348
681
  async function setupContextMesh(cwd) {
@@ -353,36 +686,36 @@ async function setupContextMesh(cwd) {
353
686
  // context.md
354
687
  const contextPath = path.join(memoryDir, "context.md");
355
688
  if (!fs.existsSync(contextPath)) {
356
- const contextContent = `# Project Context
357
-
358
- > 세션 간 영속화되는 프로젝트 컨텍스트
359
- > SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
360
-
361
- ---
362
-
363
- ## 프로젝트 정보
364
-
365
- | 항목 | 값 |
366
- |------|-----|
367
- | **이름** | ${path.basename(cwd)} |
368
- | **SEMO 버전** | ${VERSION} |
369
- | **설치일** | ${new Date().toISOString().split("T")[0]} |
370
-
371
- ---
372
-
373
- ## 현재 작업 상태
374
-
375
- _아직 작업 기록이 없습니다._
376
-
377
- ---
378
-
379
- ## 기술 스택
380
-
381
- _프로젝트 분석 후 자동으로 채워집니다._
382
-
383
- ---
384
-
385
- *마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
689
+ const contextContent = `# Project Context
690
+
691
+ > 세션 간 영속화되는 프로젝트 컨텍스트
692
+ > SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
693
+
694
+ ---
695
+
696
+ ## 프로젝트 정보
697
+
698
+ | 항목 | 값 |
699
+ |------|-----|
700
+ | **이름** | ${path.basename(cwd)} |
701
+ | **SEMO 버전** | ${VERSION} |
702
+ | **설치일** | ${new Date().toISOString().split("T")[0]} |
703
+
704
+ ---
705
+
706
+ ## 현재 작업 상태
707
+
708
+ _아직 작업 기록이 없습니다._
709
+
710
+ ---
711
+
712
+ ## 기술 스택
713
+
714
+ _프로젝트 분석 후 자동으로 채워집니다._
715
+
716
+ ---
717
+
718
+ *마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
386
719
  `;
387
720
  fs.writeFileSync(contextPath, contextContent);
388
721
  console.log(chalk_1.default.green("✓ .claude/memory/context.md 생성됨"));
@@ -390,36 +723,36 @@ _프로젝트 분석 후 자동으로 채워집니다._
390
723
  // decisions.md
391
724
  const decisionsPath = path.join(memoryDir, "decisions.md");
392
725
  if (!fs.existsSync(decisionsPath)) {
393
- const decisionsContent = `# Architecture Decisions
394
-
395
- > 프로젝트 아키텍처 결정 기록 (ADR)
396
- > 중요한 기술적 결정을 여기에 기록합니다.
397
-
398
- ---
399
-
400
- ## 결정 목록
401
-
402
- _아직 기록된 결정이 없습니다._
403
-
404
- ---
405
-
406
- ## 템플릿
407
-
408
- \`\`\`markdown
409
- ### ADR-XXX: 결정 제목
410
-
411
- **날짜**: YYYY-MM-DD
412
- **상태**: Proposed | Accepted | Deprecated
413
-
414
- #### 배경
415
- 결정이 필요한 이유
416
-
417
- #### 결정
418
- 선택한 방안
419
-
420
- #### 근거
421
- 선택 이유
422
- \`\`\`
726
+ const decisionsContent = `# Architecture Decisions
727
+
728
+ > 프로젝트 아키텍처 결정 기록 (ADR)
729
+ > 중요한 기술적 결정을 여기에 기록합니다.
730
+
731
+ ---
732
+
733
+ ## 결정 목록
734
+
735
+ _아직 기록된 결정이 없습니다._
736
+
737
+ ---
738
+
739
+ ## 템플릿
740
+
741
+ \`\`\`markdown
742
+ ### ADR-XXX: 결정 제목
743
+
744
+ **날짜**: YYYY-MM-DD
745
+ **상태**: Proposed | Accepted | Deprecated
746
+
747
+ #### 배경
748
+ 결정이 필요한 이유
749
+
750
+ #### 결정
751
+ 선택한 방안
752
+
753
+ #### 근거
754
+ 선택 이유
755
+ \`\`\`
423
756
  `;
424
757
  fs.writeFileSync(decisionsPath, decisionsContent);
425
758
  console.log(chalk_1.default.green("✓ .claude/memory/decisions.md 생성됨"));
@@ -429,21 +762,21 @@ _아직 기록된 결정이 없습니다._
429
762
  fs.mkdirSync(rulesDir, { recursive: true });
430
763
  const rulesPath = path.join(rulesDir, "project-specific.md");
431
764
  if (!fs.existsSync(rulesPath)) {
432
- const rulesContent = `# Project-Specific Rules
433
-
434
- > 이 프로젝트에만 적용되는 규칙
435
-
436
- ---
437
-
438
- ## 코딩 규칙
439
-
440
- _프로젝트별 코딩 규칙을 여기에 추가하세요._
441
-
442
- ---
443
-
444
- ## 예외 사항
445
-
446
- _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
765
+ const rulesContent = `# Project-Specific Rules
766
+
767
+ > 이 프로젝트에만 적용되는 규칙
768
+
769
+ ---
770
+
771
+ ## 코딩 규칙
772
+
773
+ _프로젝트별 코딩 규칙을 여기에 추가하세요._
774
+
775
+ ---
776
+
777
+ ## 예외 사항
778
+
779
+ _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
447
780
  `;
448
781
  fs.writeFileSync(rulesPath, rulesContent);
449
782
  console.log(chalk_1.default.green("✓ .claude/memory/rules/project-specific.md 생성됨"));
@@ -463,65 +796,135 @@ async function setupClaudeMd(cwd, extensions, force) {
463
796
  const extensionsList = extensions.length > 0
464
797
  ? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
465
798
  : "";
466
- const claudeMdContent = `# SEMO Project Configuration
467
-
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")}` : ""}
480
-
481
- ## 구조
482
-
483
- \`\`\`
484
- .claude/
485
- ├── settings.json # MCP 서버 설정 (Black Box)
486
- ├── memory/ # Context Mesh (장기 기억)
487
- │ ├── context.md # 프로젝트 상태
488
- │ ├── decisions.md # 아키텍처 결정
489
- │ └── rules/ # 프로젝트별 규칙
490
- ├── agents semo-system/semo-core/agents
491
- ├── skills → semo-system/semo-skills
492
- └── commands/SEMO → semo-system/semo-core/commands/SEMO
493
-
494
- semo-system/ # White Box (읽기 전용)
495
- ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
496
- ├── semo-skills/ # Layer 1: 통합 스킬
497
- ${extensionsList}
498
- \`\`\`
499
-
500
- ## 사용 가능한 커맨드
501
-
502
- | 커맨드 | 설명 |
503
- |--------|------|
504
- | \`/SEMO:help\` | 도움말 |
505
- | \`/SEMO:slack\` | Slack 메시지 전송 |
506
- | \`/SEMO:feedback\` | 피드백 제출 |
507
- | \`/SEMO:health\` | 환경 검증 |
508
- | \`/SEMO:update\` | SEMO 업데이트 |
509
-
510
- ## Context Mesh 사용
511
-
512
- SEMO는 \`.claude/memory/\`를 통해 세션 컨텍스트를 유지합니다:
513
-
514
- - **context.md**: 프로젝트 상태, 진행 중인 작업
515
- - **decisions.md**: 아키텍처 결정 기록 (ADR)
516
- - **rules/**: 프로젝트별 커스텀 규칙
517
-
518
- memory 스킬이 자동으로 이 파일들을 관리합니다.
519
-
520
- ## References
521
-
522
- - [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
523
- - [SEMO Skills](semo-system/semo-skills/)
524
- ${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
799
+ const claudeMdContent = `# SEMO Project Configuration
800
+
801
+ > SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
802
+
803
+ ---
804
+
805
+ ## 🔴 MANDATORY: Orchestrator-First Execution
806
+
807
+ > **⚠️ 규칙은 모든 사용자 요청에 적용됩니다. 예외 없음.**
808
+
809
+ ### 실행 흐름 (필수)
810
+
811
+ \`\`\`
812
+ 1. 사용자 요청 수신
813
+ 2. [SEMO] Orchestrator 메시지 출력 (의도 분석)
814
+ 3. Orchestrator가 적절한 Agent/Skill 라우팅
815
+ 4. [SEMO] Agent/Skill 메시지 출력
816
+ 5. 실행 결과 반환
817
+ \`\`\`
818
+
819
+ ### 모든 응답은 다음으로 시작
820
+
821
+ \`\`\`
822
+ [SEMO] Orchestrator: 의도 분석 완료 → {intent_category}
823
+ [SEMO] {Agent/Skill} 호출: {target} (사유: {reason})
824
+ \`\`\`
825
+
826
+ ### Orchestrator 참조
827
+
828
+ **반드시 읽어야 할 파일**: \`semo-system/semo-core/agents/orchestrator/orchestrator.md\`
829
+
830
+ 이 파일에서 라우팅 테이블, 의도 분류, 메시지 포맷을 확인하세요.
831
+
832
+ ---
833
+
834
+ ## 🔴 NON-NEGOTIABLE RULES
835
+
836
+ ### 1. Orchestrator-First Policy
837
+
838
+ > **모든 요청은 반드시 Orchestrator를 통해 라우팅됩니다. 직접 처리 금지.**
839
+
840
+ **직접 처리 금지 항목**:
841
+ - 코드 작성/수정 → \`implementation-master\` 또는 \`coder\` 스킬
842
+ - Git 커밋/푸시 → \`git-workflow\` 스킬
843
+ - 품질 검증 → \`quality-master\` 또는 \`verify\` 스킬
844
+ - 명세 작성 → \`spec-master\`
845
+ - 일반 작업 Orchestrator 분석 후 라우팅
846
+
847
+ ### 2. Pre-Commit Quality Gate
848
+
849
+ > **코드 변경이 포함된 커밋 전 반드시 Quality Gate를 통과해야 합니다.**
850
+
851
+ \`\`\`bash
852
+ # 필수 검증 순서
853
+ npm run lint # 1. ESLint 검사
854
+ npx tsc --noEmit # 2. TypeScript 타입 체크
855
+ npm run build # 3. 빌드 검증 (Next.js/TypeScript 프로젝트)
856
+ \`\`\`
857
+
858
+ **차단 항목**:
859
+ - \`--no-verify\` 플래그 사용 금지
860
+ - Quality Gate 우회 시도 거부
861
+ - "그냥 커밋해줘", "빌드 생략해줘" 등 거부
862
+
863
+ ### 3. SEMO Message Format
864
+
865
+ 모든 SEMO 동작은 시스템 메시지로 시작:
866
+
867
+ \`\`\`
868
+ [SEMO] {Component}: {Action} → {Result}
869
+ \`\`\`
870
+
871
+ ---
872
+
873
+ ## 설치된 구성
874
+
875
+ ### Standard (필수)
876
+ - **semo-core**: 원칙, 오케스트레이터, 공통 커맨드
877
+ - **semo-skills**: 13개 통합 스킬
878
+ - 행동: coder, tester, planner, deployer, writer
879
+ - 운영: memory, notify-slack, feedback, version-updater, semo-help, semo-architecture-checker, circuit-breaker, list-bugs
880
+
881
+ ${extensions.length > 0 ? `### Extensions (선택)
882
+ ${extensions.map(pkg => `- **${pkg}**: ${EXTENSION_PACKAGES[pkg].desc}`).join("\n")}` : ""}
883
+
884
+ ## 구조
885
+
886
+ \`\`\`
887
+ .claude/
888
+ ├── settings.json # MCP 서버 설정 (Black Box)
889
+ ├── memory/ # Context Mesh (장기 기억)
890
+ │ ├── context.md # 프로젝트 상태
891
+ │ ├── decisions.md # 아키텍처 결정
892
+ │ └── rules/ # 프로젝트별 규칙
893
+ ├── agents → semo-system/semo-core/agents
894
+ ├── skills → semo-system/semo-skills
895
+ └── commands/SEMO → semo-system/semo-core/commands/SEMO
896
+
897
+ semo-system/ # White Box (읽기 전용)
898
+ ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
899
+ ├── semo-skills/ # Layer 1: 통합 스킬
900
+ ${extensionsList}
901
+ \`\`\`
902
+
903
+ ## 사용 가능한 커맨드
904
+
905
+ | 커맨드 | 설명 |
906
+ |--------|------|
907
+ | \`/SEMO:help\` | 도움말 |
908
+ | \`/SEMO:slack\` | Slack 메시지 전송 |
909
+ | \`/SEMO:feedback\` | 피드백 제출 |
910
+ | \`/SEMO:health\` | 환경 검증 |
911
+ | \`/SEMO:update\` | SEMO 업데이트 |
912
+
913
+ ## Context Mesh 사용
914
+
915
+ SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
916
+
917
+ - **context.md**: 프로젝트 상태, 진행 중인 작업
918
+ - **decisions.md**: 아키텍처 결정 기록 (ADR)
919
+ - **rules/**: 프로젝트별 커스텀 규칙
920
+
921
+ memory 스킬이 자동으로 이 파일들을 관리합니다.
922
+
923
+ ## References
924
+
925
+ - [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
926
+ - [SEMO Skills](semo-system/semo-skills/)
927
+ ${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
525
928
  `;
526
929
  fs.writeFileSync(claudeMdPath, claudeMdContent);
527
930
  console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
@@ -551,7 +954,12 @@ program
551
954
  }
552
955
  console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[packageName].name} 패키지 설치\n`));
553
956
  console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[packageName].desc}\n`));
554
- await setupExtensions(cwd, [packageName], options.force);
957
+ // 1. 다운로드
958
+ await downloadExtensions(cwd, [packageName], options.force);
959
+ // 2. settings.json 병합
960
+ await mergeExtensionSettings(cwd, [packageName]);
961
+ // 3. 심볼릭 링크 설정
962
+ await setupExtensionSymlinks(cwd, [packageName]);
555
963
  console.log(chalk_1.default.green.bold(`\n✅ ${EXTENSION_PACKAGES[packageName].name} 패키지 설치 완료!\n`));
556
964
  });
557
965
  // === list 명령어 ===
@@ -666,22 +1074,23 @@ program
666
1074
  const spinner = (0, ora_1.default)("\n최신 버전 다운로드 중...").start();
667
1075
  try {
668
1076
  const tempDir = path.join(cwd, ".semo-temp");
669
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
1077
+ removeRecursive(tempDir);
670
1078
  (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
671
1079
  // 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" });
1080
+ removeRecursive(path.join(semoSystemDir, "semo-core"));
1081
+ removeRecursive(path.join(semoSystemDir, "semo-skills"));
1082
+ copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
1083
+ copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
675
1084
  // Extensions 업데이트
676
1085
  for (const pkg of installedExtensions) {
677
1086
  const srcPath = path.join(tempDir, "packages", pkg);
678
1087
  const destPath = path.join(semoSystemDir, pkg);
679
1088
  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" });
1089
+ removeRecursive(destPath);
1090
+ copyRecursive(srcPath, destPath);
682
1091
  }
683
1092
  }
684
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
1093
+ removeRecursive(tempDir);
685
1094
  spinner.succeed("SEMO 업데이트 완료");
686
1095
  }
687
1096
  catch (error) {
package/package.json CHANGED
@@ -1,48 +1,48 @@
1
- {
2
- "name": "@team-semicolon/semo-cli",
3
- "version": "2.0.1",
4
- "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "semo": "./dist/index.js",
8
- "semo-cli": "./dist/index.js"
9
- },
10
- "scripts": {
11
- "build": "tsc",
12
- "start": "node dist/index.js",
13
- "dev": "ts-node src/index.ts"
14
- },
15
- "keywords": [
16
- "semo",
17
- "cli",
18
- "ai-agent",
19
- "claude",
20
- "semicolon"
21
- ],
22
- "author": "Semicolon DevTeam",
23
- "license": "MIT",
24
- "repository": {
25
- "type": "git",
26
- "url": "https://github.com/semicolon-devteam/semo.git",
27
- "directory": "packages/cli"
28
- },
29
- "dependencies": {
30
- "chalk": "^5.3.0",
31
- "commander": "^12.0.0",
32
- "ora": "^8.0.0",
33
- "inquirer": "^9.2.0"
34
- },
35
- "devDependencies": {
36
- "@types/node": "^20.0.0",
37
- "@types/inquirer": "^9.0.0",
38
- "typescript": "^5.0.0",
39
- "ts-node": "^10.0.0"
40
- },
41
- "engines": {
42
- "node": ">=18.0.0"
43
- },
44
- "files": [
45
- "dist",
46
- "README.md"
47
- ]
48
- }
1
+ {
2
+ "name": "@team-semicolon/semo-cli",
3
+ "version": "2.0.4",
4
+ "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "semo": "./dist/index.js",
8
+ "semo-cli": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "ts-node src/index.ts"
14
+ },
15
+ "keywords": [
16
+ "semo",
17
+ "cli",
18
+ "ai-agent",
19
+ "claude",
20
+ "semicolon"
21
+ ],
22
+ "author": "Semicolon DevTeam",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/semicolon-devteam/semo.git",
27
+ "directory": "packages/cli"
28
+ },
29
+ "dependencies": {
30
+ "chalk": "^5.3.0",
31
+ "commander": "^12.0.0",
32
+ "ora": "^8.0.0",
33
+ "inquirer": "^9.2.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "@types/inquirer": "^9.0.0",
38
+ "typescript": "^5.0.0",
39
+ "ts-node": "^10.0.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md"
47
+ ]
48
+ }