@team-semicolon/semo-cli 2.0.0 → 2.0.2

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 +391 -186
  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.0";
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 = {
@@ -71,6 +133,7 @@ const EXTENSION_PACKAGES = {
71
133
  design: { name: "Design", desc: "디자인 핸드오프 (3 agents, 4 skills)", detect: [] },
72
134
  ms: { name: "Microservice", desc: "마이크로서비스 아키텍처 (5 agents, 5 skills)", detect: [] },
73
135
  mvp: { name: "MVP", desc: "MVP 빠른 개발 (4 agents, 6 skills)", detect: [] },
136
+ meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리 (6 agents, 7 skills)", detect: ["semo-core", "semo-skills", "packages/meta"] },
74
137
  };
75
138
  const program = new commander_1.Command();
76
139
  program
@@ -156,18 +219,22 @@ program
156
219
  }
157
220
  // 4. Standard 설치 (semo-core + semo-skills)
158
221
  await setupStandard(cwd, options.force);
159
- // 5. Extensions 설치
222
+ // 5. Extensions 다운로드 (심볼릭 링크는 아직)
160
223
  if (extensionsToInstall.length > 0) {
161
- await setupExtensions(cwd, extensionsToInstall, options.force);
224
+ await downloadExtensions(cwd, extensionsToInstall, options.force);
162
225
  }
163
- // 6. MCP 설정
226
+ // 6. MCP 설정 (Extension 설정 병합 포함)
164
227
  if (!options.skipMcp) {
165
- await setupMCP(cwd, options.force);
228
+ await setupMCP(cwd, extensionsToInstall, options.force);
166
229
  }
167
230
  // 7. Context Mesh 초기화
168
231
  await setupContextMesh(cwd);
169
232
  // 8. CLAUDE.md 생성
170
233
  await setupClaudeMd(cwd, extensionsToInstall, options.force);
234
+ // 9. Extensions 심볼릭 링크 (agents/skills 병합)
235
+ if (extensionsToInstall.length > 0) {
236
+ await setupExtensionSymlinks(cwd, extensionsToInstall);
237
+ }
171
238
  // 완료 메시지
172
239
  console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
173
240
  console.log(chalk_1.default.cyan("설치된 구성:"));
@@ -202,24 +269,24 @@ async function setupStandard(cwd, force) {
202
269
  console.log(chalk_1.default.gray(" → semo-system/ 건너뜀"));
203
270
  return;
204
271
  }
205
- (0, child_process_1.execSync)(`rm -rf "${semoSystemDir}"`, { stdio: "pipe" });
272
+ removeRecursive(semoSystemDir);
206
273
  console.log(chalk_1.default.green(" ✓ 기존 semo-system/ 삭제됨"));
207
274
  }
208
275
  const spinner = (0, ora_1.default)("semo-core, semo-skills 다운로드 중...").start();
209
276
  try {
210
277
  const tempDir = path.join(cwd, ".semo-temp");
211
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
278
+ removeRecursive(tempDir);
212
279
  (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
213
280
  fs.mkdirSync(semoSystemDir, { recursive: true });
214
281
  // semo-core 복사
215
282
  if (fs.existsSync(path.join(tempDir, "semo-core"))) {
216
- (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"));
217
284
  }
218
285
  // semo-skills 복사
219
286
  if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
220
- (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"));
221
288
  }
222
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
289
+ removeRecursive(tempDir);
223
290
  spinner.succeed("Standard 설치 완료");
224
291
  // 심볼릭 링크 생성
225
292
  await createStandardSymlinks(cwd);
@@ -233,23 +300,43 @@ async function setupStandard(cwd, force) {
233
300
  async function createStandardSymlinks(cwd) {
234
301
  const claudeDir = path.join(cwd, ".claude");
235
302
  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)) {
241
- fs.symlinkSync("../semo-system/semo-core/agents", agentsLink);
242
- 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);
243
310
  }
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)) {
250
- fs.symlinkSync("../semo-system/semo-skills", skillsLink);
251
- console.log(chalk_1.default.green(" ✓ .claude/skills → semo-system/semo-skills"));
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
+ }
252
319
  }
320
+ console.log(chalk_1.default.green(` ✓ .claude/agents/ (${agents.length}개 agent 링크됨)`));
321
+ }
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);
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 링크됨)`));
253
340
  }
254
341
  // commands 링크
255
342
  const commandsDir = path.join(claudeDir, "commands");
@@ -258,14 +345,14 @@ async function createStandardSymlinks(cwd) {
258
345
  if (!fs.existsSync(semoCommandsLink)) {
259
346
  const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
260
347
  if (fs.existsSync(commandsTarget)) {
261
- fs.symlinkSync("../../semo-system/semo-core/commands/SEMO", semoCommandsLink);
348
+ createSymlinkOrJunction(commandsTarget, semoCommandsLink);
262
349
  console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
263
350
  }
264
351
  }
265
352
  }
266
- // === Extensions 설치 ===
267
- async function setupExtensions(cwd, packages, force) {
268
- 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 다운로드"));
269
356
  packages.forEach(pkg => {
270
357
  console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}`));
271
358
  });
@@ -286,36 +373,65 @@ async function setupExtensions(cwd, packages, force) {
286
373
  console.log(chalk_1.default.yellow(` ⚠ ${pkg}/ 이미 존재 (건너뜀)`));
287
374
  continue;
288
375
  }
289
- (0, child_process_1.execSync)(`rm -rf "${destPath}"`, { stdio: "pipe" });
290
- (0, child_process_1.execSync)(`cp -r "${srcPath}" "${destPath}"`, { stdio: "pipe" });
376
+ removeRecursive(destPath);
377
+ copyRecursive(srcPath, destPath);
291
378
  }
292
379
  }
293
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
294
- spinner.succeed(`Extensions 설치 완료 (${packages.length}개)`);
295
- // Extensions 심볼릭 링크 생성
296
- await createExtensionSymlinks(cwd, packages);
380
+ removeRecursive(tempDir);
381
+ spinner.succeed(`Extensions 다운로드 완료 (${packages.length}개)`);
297
382
  }
298
383
  catch (error) {
299
- spinner.fail("Extensions 설치 실패");
384
+ spinner.fail("Extensions 다운로드 실패");
300
385
  console.error(chalk_1.default.red(` ${error}`));
301
386
  }
302
387
  }
303
- // === Extensions 심볼릭 링크 ===
304
- async function createExtensionSymlinks(cwd, packages) {
388
+ // === Extensions 심볼릭 링크 설정 (agents/skills 병합) ===
389
+ async function setupExtensionSymlinks(cwd, packages) {
390
+ console.log(chalk_1.default.cyan("\n🔗 Extensions 연결"));
305
391
  const claudeDir = path.join(cwd, ".claude");
306
392
  const semoSystemDir = path.join(cwd, "semo-system");
307
393
  for (const pkg of packages) {
308
- // sax-{pkg} 호환 링크 (기존 SAX 사용자용)
309
- const saxPkgLink = path.join(claudeDir, `sax-${pkg}`);
310
394
  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}`));
395
+ if (!fs.existsSync(pkgPath))
396
+ continue;
397
+ // 1. semo-{pkg} 링크
398
+ const semoPkgLink = path.join(claudeDir, `semo-${pkg}`);
399
+ if (!fs.existsSync(semoPkgLink)) {
400
+ createSymlinkOrJunction(pkgPath, semoPkgLink);
401
+ console.log(chalk_1.default.green(` ✓ .claude/semo-${pkg} → semo-system/${pkg}`));
402
+ }
403
+ // 2. Extension의 agents를 .claude/agents/에 개별 링크
404
+ const extAgentsDir = path.join(pkgPath, "agents");
405
+ const claudeAgentsDir = path.join(claudeDir, "agents");
406
+ if (fs.existsSync(extAgentsDir)) {
407
+ const agents = fs.readdirSync(extAgentsDir).filter(f => fs.statSync(path.join(extAgentsDir, f)).isDirectory());
408
+ for (const agent of agents) {
409
+ const agentLink = path.join(claudeAgentsDir, agent);
410
+ const agentTarget = path.join(extAgentsDir, agent);
411
+ if (!fs.existsSync(agentLink)) {
412
+ createSymlinkOrJunction(agentTarget, agentLink);
413
+ console.log(chalk_1.default.green(` ✓ .claude/agents/${agent} → semo-system/${pkg}/agents/${agent}`));
414
+ }
415
+ }
416
+ }
417
+ // 3. Extension의 skills를 .claude/skills/에 개별 링크
418
+ const extSkillsDir = path.join(pkgPath, "skills");
419
+ const claudeSkillsDir = path.join(claudeDir, "skills");
420
+ if (fs.existsSync(extSkillsDir)) {
421
+ const skills = fs.readdirSync(extSkillsDir).filter(f => fs.statSync(path.join(extSkillsDir, f)).isDirectory());
422
+ for (const skill of skills) {
423
+ const skillLink = path.join(claudeSkillsDir, skill);
424
+ const skillTarget = path.join(extSkillsDir, skill);
425
+ if (!fs.existsSync(skillLink)) {
426
+ createSymlinkOrJunction(skillTarget, skillLink);
427
+ console.log(chalk_1.default.green(` ✓ .claude/skills/${skill} → semo-system/${pkg}/skills/${skill}`));
428
+ }
429
+ }
314
430
  }
315
431
  }
316
432
  }
317
433
  // === MCP 설정 ===
318
- async function setupMCP(cwd, force) {
434
+ async function setupMCP(cwd, extensions, force) {
319
435
  console.log(chalk_1.default.cyan("\n🔧 Black Box 설정 (MCP Server)"));
320
436
  console.log(chalk_1.default.gray(" 토큰이 격리된 외부 연동 도구\n"));
321
437
  const settingsPath = path.join(cwd, ".claude", "settings.json");
@@ -326,6 +442,7 @@ async function setupMCP(cwd, force) {
326
442
  return;
327
443
  }
328
444
  }
445
+ // Base settings (Standard)
329
446
  const settings = {
330
447
  mcpServers: {
331
448
  "semo-integrations": {
@@ -340,9 +457,91 @@ async function setupMCP(cwd, force) {
340
457
  },
341
458
  },
342
459
  };
460
+ // Extension settings 병합
461
+ const semoSystemDir = path.join(cwd, "semo-system");
462
+ for (const pkg of extensions) {
463
+ const extSettingsPath = path.join(semoSystemDir, pkg, "settings.local.json");
464
+ if (fs.existsSync(extSettingsPath)) {
465
+ try {
466
+ const extSettings = JSON.parse(fs.readFileSync(extSettingsPath, "utf-8"));
467
+ // mcpServers 병합
468
+ if (extSettings.mcpServers) {
469
+ Object.assign(settings.mcpServers, extSettings.mcpServers);
470
+ console.log(chalk_1.default.gray(` + ${pkg} MCP 설정 병합됨`));
471
+ }
472
+ // permissions 병합
473
+ if (extSettings.permissions) {
474
+ if (!settings.permissions) {
475
+ settings.permissions = { allow: [], deny: [] };
476
+ }
477
+ if (extSettings.permissions.allow) {
478
+ settings.permissions.allow = [
479
+ ...(settings.permissions.allow || []),
480
+ ...extSettings.permissions.allow,
481
+ ];
482
+ }
483
+ if (extSettings.permissions.deny) {
484
+ settings.permissions.deny = [
485
+ ...(settings.permissions.deny || []),
486
+ ...extSettings.permissions.deny,
487
+ ];
488
+ }
489
+ console.log(chalk_1.default.gray(` + ${pkg} permissions 병합됨`));
490
+ }
491
+ }
492
+ catch (error) {
493
+ console.log(chalk_1.default.yellow(` ⚠ ${pkg} settings.local.json 파싱 실패`));
494
+ }
495
+ }
496
+ }
343
497
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
344
498
  console.log(chalk_1.default.green("✓ .claude/settings.json 생성됨 (MCP 설정)"));
345
499
  }
500
+ // === Extension settings 병합 (add 명령어용) ===
501
+ async function mergeExtensionSettings(cwd, packages) {
502
+ const settingsPath = path.join(cwd, ".claude", "settings.json");
503
+ const semoSystemDir = path.join(cwd, "semo-system");
504
+ if (!fs.existsSync(settingsPath)) {
505
+ console.log(chalk_1.default.yellow(" ⚠ settings.json이 없습니다. 'semo init'을 먼저 실행하세요."));
506
+ return;
507
+ }
508
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
509
+ for (const pkg of packages) {
510
+ const extSettingsPath = path.join(semoSystemDir, pkg, "settings.local.json");
511
+ if (fs.existsSync(extSettingsPath)) {
512
+ try {
513
+ const extSettings = JSON.parse(fs.readFileSync(extSettingsPath, "utf-8"));
514
+ // mcpServers 병합
515
+ if (extSettings.mcpServers) {
516
+ settings.mcpServers = settings.mcpServers || {};
517
+ Object.assign(settings.mcpServers, extSettings.mcpServers);
518
+ console.log(chalk_1.default.gray(` + ${pkg} MCP 설정 병합됨`));
519
+ }
520
+ // permissions 병합
521
+ if (extSettings.permissions) {
522
+ settings.permissions = settings.permissions || { allow: [], deny: [] };
523
+ if (extSettings.permissions.allow) {
524
+ settings.permissions.allow = [
525
+ ...(settings.permissions.allow || []),
526
+ ...extSettings.permissions.allow,
527
+ ];
528
+ }
529
+ if (extSettings.permissions.deny) {
530
+ settings.permissions.deny = [
531
+ ...(settings.permissions.deny || []),
532
+ ...extSettings.permissions.deny,
533
+ ];
534
+ }
535
+ console.log(chalk_1.default.gray(` + ${pkg} permissions 병합됨`));
536
+ }
537
+ }
538
+ catch (error) {
539
+ console.log(chalk_1.default.yellow(` ⚠ ${pkg} settings.local.json 파싱 실패`));
540
+ }
541
+ }
542
+ }
543
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
544
+ }
346
545
  // === Context Mesh 초기화 ===
347
546
  async function setupContextMesh(cwd) {
348
547
  console.log(chalk_1.default.cyan("\n🧠 Context Mesh 초기화"));
@@ -352,36 +551,36 @@ async function setupContextMesh(cwd) {
352
551
  // context.md
353
552
  const contextPath = path.join(memoryDir, "context.md");
354
553
  if (!fs.existsSync(contextPath)) {
355
- const contextContent = `# Project Context
356
-
357
- > 세션 간 영속화되는 프로젝트 컨텍스트
358
- > SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
359
-
360
- ---
361
-
362
- ## 프로젝트 정보
363
-
364
- | 항목 | 값 |
365
- |------|-----|
366
- | **이름** | ${path.basename(cwd)} |
367
- | **SEMO 버전** | ${VERSION} |
368
- | **설치일** | ${new Date().toISOString().split("T")[0]} |
369
-
370
- ---
371
-
372
- ## 현재 작업 상태
373
-
374
- _아직 작업 기록이 없습니다._
375
-
376
- ---
377
-
378
- ## 기술 스택
379
-
380
- _프로젝트 분석 후 자동으로 채워집니다._
381
-
382
- ---
383
-
384
- *마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
554
+ const contextContent = `# Project Context
555
+
556
+ > 세션 간 영속화되는 프로젝트 컨텍스트
557
+ > SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
558
+
559
+ ---
560
+
561
+ ## 프로젝트 정보
562
+
563
+ | 항목 | 값 |
564
+ |------|-----|
565
+ | **이름** | ${path.basename(cwd)} |
566
+ | **SEMO 버전** | ${VERSION} |
567
+ | **설치일** | ${new Date().toISOString().split("T")[0]} |
568
+
569
+ ---
570
+
571
+ ## 현재 작업 상태
572
+
573
+ _아직 작업 기록이 없습니다._
574
+
575
+ ---
576
+
577
+ ## 기술 스택
578
+
579
+ _프로젝트 분석 후 자동으로 채워집니다._
580
+
581
+ ---
582
+
583
+ *마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
385
584
  `;
386
585
  fs.writeFileSync(contextPath, contextContent);
387
586
  console.log(chalk_1.default.green("✓ .claude/memory/context.md 생성됨"));
@@ -389,36 +588,36 @@ _프로젝트 분석 후 자동으로 채워집니다._
389
588
  // decisions.md
390
589
  const decisionsPath = path.join(memoryDir, "decisions.md");
391
590
  if (!fs.existsSync(decisionsPath)) {
392
- const decisionsContent = `# Architecture Decisions
393
-
394
- > 프로젝트 아키텍처 결정 기록 (ADR)
395
- > 중요한 기술적 결정을 여기에 기록합니다.
396
-
397
- ---
398
-
399
- ## 결정 목록
400
-
401
- _아직 기록된 결정이 없습니다._
402
-
403
- ---
404
-
405
- ## 템플릿
406
-
407
- \`\`\`markdown
408
- ### ADR-XXX: 결정 제목
409
-
410
- **날짜**: YYYY-MM-DD
411
- **상태**: Proposed | Accepted | Deprecated
412
-
413
- #### 배경
414
- 결정이 필요한 이유
415
-
416
- #### 결정
417
- 선택한 방안
418
-
419
- #### 근거
420
- 선택 이유
421
- \`\`\`
591
+ const decisionsContent = `# Architecture Decisions
592
+
593
+ > 프로젝트 아키텍처 결정 기록 (ADR)
594
+ > 중요한 기술적 결정을 여기에 기록합니다.
595
+
596
+ ---
597
+
598
+ ## 결정 목록
599
+
600
+ _아직 기록된 결정이 없습니다._
601
+
602
+ ---
603
+
604
+ ## 템플릿
605
+
606
+ \`\`\`markdown
607
+ ### ADR-XXX: 결정 제목
608
+
609
+ **날짜**: YYYY-MM-DD
610
+ **상태**: Proposed | Accepted | Deprecated
611
+
612
+ #### 배경
613
+ 결정이 필요한 이유
614
+
615
+ #### 결정
616
+ 선택한 방안
617
+
618
+ #### 근거
619
+ 선택 이유
620
+ \`\`\`
422
621
  `;
423
622
  fs.writeFileSync(decisionsPath, decisionsContent);
424
623
  console.log(chalk_1.default.green("✓ .claude/memory/decisions.md 생성됨"));
@@ -428,21 +627,21 @@ _아직 기록된 결정이 없습니다._
428
627
  fs.mkdirSync(rulesDir, { recursive: true });
429
628
  const rulesPath = path.join(rulesDir, "project-specific.md");
430
629
  if (!fs.existsSync(rulesPath)) {
431
- const rulesContent = `# Project-Specific Rules
432
-
433
- > 이 프로젝트에만 적용되는 규칙
434
-
435
- ---
436
-
437
- ## 코딩 규칙
438
-
439
- _프로젝트별 코딩 규칙을 여기에 추가하세요._
440
-
441
- ---
442
-
443
- ## 예외 사항
444
-
445
- _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
630
+ const rulesContent = `# Project-Specific Rules
631
+
632
+ > 이 프로젝트에만 적용되는 규칙
633
+
634
+ ---
635
+
636
+ ## 코딩 규칙
637
+
638
+ _프로젝트별 코딩 규칙을 여기에 추가하세요._
639
+
640
+ ---
641
+
642
+ ## 예외 사항
643
+
644
+ _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
446
645
  `;
447
646
  fs.writeFileSync(rulesPath, rulesContent);
448
647
  console.log(chalk_1.default.green("✓ .claude/memory/rules/project-specific.md 생성됨"));
@@ -462,65 +661,65 @@ async function setupClaudeMd(cwd, extensions, force) {
462
661
  const extensionsList = extensions.length > 0
463
662
  ? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
464
663
  : "";
465
- const claudeMdContent = `# SEMO Project Configuration
466
-
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")}` : ""}
479
-
480
- ## 구조
481
-
482
- \`\`\`
483
- .claude/
484
- ├── settings.json # MCP 서버 설정 (Black Box)
485
- ├── memory/ # Context Mesh (장기 기억)
486
- │ ├── context.md # 프로젝트 상태
487
- │ ├── decisions.md # 아키텍처 결정
488
- │ └── rules/ # 프로젝트별 규칙
489
- ├── agents → semo-system/semo-core/agents
490
- ├── skills → semo-system/semo-skills
491
- └── commands/SEMO → semo-system/semo-core/commands/SEMO
492
-
493
- semo-system/ # White Box (읽기 전용)
494
- ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
495
- ├── semo-skills/ # Layer 1: 통합 스킬
496
- ${extensionsList}
497
- \`\`\`
498
-
499
- ## 사용 가능한 커맨드
500
-
501
- | 커맨드 | 설명 |
502
- |--------|------|
503
- | \`/SEMO:help\` | 도움말 |
504
- | \`/SEMO:slack\` | Slack 메시지 전송 |
505
- | \`/SEMO:feedback\` | 피드백 제출 |
506
- | \`/SEMO:health\` | 환경 검증 |
507
- | \`/SEMO:update\` | SEMO 업데이트 |
508
-
509
- ## Context Mesh 사용
510
-
511
- SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
512
-
513
- - **context.md**: 프로젝트 상태, 진행 중인 작업
514
- - **decisions.md**: 아키텍처 결정 기록 (ADR)
515
- - **rules/**: 프로젝트별 커스텀 규칙
516
-
517
- memory 스킬이 자동으로 이 파일들을 관리합니다.
518
-
519
- ## References
520
-
521
- - [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
522
- - [SEMO Skills](semo-system/semo-skills/)
523
- ${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
664
+ const claudeMdContent = `# SEMO Project Configuration
665
+
666
+ > SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
667
+
668
+ ## 설치된 구성
669
+
670
+ ### Standard (필수)
671
+ - **semo-core**: 원칙, 오케스트레이터, 공통 커맨드
672
+ - **semo-skills**: 13개 통합 스킬
673
+ - 행동: coder, tester, planner, deployer, writer
674
+ - 운영: memory, notify-slack, feedback, version-updater, semo-help, semo-architecture-checker, circuit-breaker, list-bugs
675
+
676
+ ${extensions.length > 0 ? `### Extensions (선택)
677
+ ${extensions.map(pkg => `- **${pkg}**: ${EXTENSION_PACKAGES[pkg].desc}`).join("\n")}` : ""}
678
+
679
+ ## 구조
680
+
681
+ \`\`\`
682
+ .claude/
683
+ ├── settings.json # MCP 서버 설정 (Black Box)
684
+ ├── memory/ # Context Mesh (장기 기억)
685
+ │ ├── context.md # 프로젝트 상태
686
+ │ ├── decisions.md # 아키텍처 결정
687
+ │ └── rules/ # 프로젝트별 규칙
688
+ ├── agents → semo-system/semo-core/agents
689
+ ├── skills → semo-system/semo-skills
690
+ └── commands/SEMO → semo-system/semo-core/commands/SEMO
691
+
692
+ semo-system/ # White Box (읽기 전용)
693
+ ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
694
+ ├── semo-skills/ # Layer 1: 통합 스킬
695
+ ${extensionsList}
696
+ \`\`\`
697
+
698
+ ## 사용 가능한 커맨드
699
+
700
+ | 커맨드 | 설명 |
701
+ |--------|------|
702
+ | \`/SEMO:help\` | 도움말 |
703
+ | \`/SEMO:slack\` | Slack 메시지 전송 |
704
+ | \`/SEMO:feedback\` | 피드백 제출 |
705
+ | \`/SEMO:health\` | 환경 검증 |
706
+ | \`/SEMO:update\` | SEMO 업데이트 |
707
+
708
+ ## Context Mesh 사용
709
+
710
+ SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
711
+
712
+ - **context.md**: 프로젝트 상태, 진행 중인 작업
713
+ - **decisions.md**: 아키텍처 결정 기록 (ADR)
714
+ - **rules/**: 프로젝트별 커스텀 규칙
715
+
716
+ memory 스킬이 자동으로 이 파일들을 관리합니다.
717
+
718
+ ## References
719
+
720
+ - [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
721
+ - [SEMO Skills](semo-system/semo-skills/)
722
+ ${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
524
723
  `;
525
724
  fs.writeFileSync(claudeMdPath, claudeMdContent);
526
725
  console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
@@ -550,7 +749,12 @@ program
550
749
  }
551
750
  console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[packageName].name} 패키지 설치\n`));
552
751
  console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[packageName].desc}\n`));
553
- await setupExtensions(cwd, [packageName], options.force);
752
+ // 1. 다운로드
753
+ await downloadExtensions(cwd, [packageName], options.force);
754
+ // 2. settings.json 병합
755
+ await mergeExtensionSettings(cwd, [packageName]);
756
+ // 3. 심볼릭 링크 설정
757
+ await setupExtensionSymlinks(cwd, [packageName]);
554
758
  console.log(chalk_1.default.green.bold(`\n✅ ${EXTENSION_PACKAGES[packageName].name} 패키지 설치 완료!\n`));
555
759
  });
556
760
  // === list 명령어 ===
@@ -665,22 +869,23 @@ program
665
869
  const spinner = (0, ora_1.default)("\n최신 버전 다운로드 중...").start();
666
870
  try {
667
871
  const tempDir = path.join(cwd, ".semo-temp");
668
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
872
+ removeRecursive(tempDir);
669
873
  (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
670
874
  // 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" });
875
+ removeRecursive(path.join(semoSystemDir, "semo-core"));
876
+ removeRecursive(path.join(semoSystemDir, "semo-skills"));
877
+ copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
878
+ copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
674
879
  // Extensions 업데이트
675
880
  for (const pkg of installedExtensions) {
676
881
  const srcPath = path.join(tempDir, "packages", pkg);
677
882
  const destPath = path.join(semoSystemDir, pkg);
678
883
  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" });
884
+ removeRecursive(destPath);
885
+ copyRecursive(srcPath, destPath);
681
886
  }
682
887
  }
683
- (0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
888
+ removeRecursive(tempDir);
684
889
  spinner.succeed("SEMO 업데이트 완료");
685
890
  }
686
891
  catch (error) {
package/package.json CHANGED
@@ -1,48 +1,48 @@
1
- {
2
- "name": "@team-semicolon/semo-cli",
3
- "version": "2.0.0",
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.2",
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
+ }