@team-semicolon/semo-cli 1.1.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 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 하이브리드 전략에 따른 SEMO 설치 자동화 도구
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
- * 1. White Box (semo-core, semo-skills) → Git Subtree로 주입
12
- * 2. Black Box (semo-integrations) MCP 설정 파일 생성
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 하이브리드 전략에 따른 SEMO 설치 자동화 도구
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
- * 1. White Box (semo-core, semo-skills) → Git Subtree로 주입
13
- * 2. Black Box (semo-integrations) MCP 설정 파일 생성
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 = "1.1.0";
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
- const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
77
- const program = new commander_1.Command();
78
- program
79
- .name("semo")
80
- .description("SEMO CLI - AI Agent Orchestration Framework")
81
- .version(VERSION);
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를 설치합니다 (Gemini 하이브리드 전략)")
110
+ .description("현재 프로젝트에 SEMO를 설치합니다")
86
111
  .option("-f, --force", "기존 설정 덮어쓰기")
87
112
  .option("--skip-mcp", "MCP 설정 생략")
88
- .option("--skip-subtree", "Git Subtree 생략 (MCP만 설정)")
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,95 +125,200 @@ program
100
125
  spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
101
126
  process.exit(1);
102
127
  }
103
- // 2. .claude 디렉토리 생성
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
- // 3. White Box: Git Subtree로 semo-core, semo-skills 주입
110
- if (!options.skipSubtree) {
111
- await setupWhiteBox(cwd, options.force);
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
- // 4. Black Box: MCP 설정 파일 생성
163
+ // 6. MCP 설정
114
164
  if (!options.skipMcp) {
115
- await setupBlackBox(cwd, options.force);
165
+ await setupMCP(cwd, options.force);
116
166
  }
117
- // 5. Context Mesh 초기화
167
+ // 7. Context Mesh 초기화
118
168
  await setupContextMesh(cwd);
119
- // 6. CLAUDE.md 생성
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로 도움말 확인\n"));
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
- // === White Box 설정 (Git Subtree) ===
128
- async function setupWhiteBox(cwd, force) {
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📚 White Box 설정 (Git Subtree)"));
131
- console.log(chalk_1.default.gray(" 에이전트가 읽고 학습할 지식 베이스\n"));
132
- // semo-system 디렉토리 확인 - force가 아니면 사용자에게 확인
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
- // Git Subtree로 semo-core 추가
146
- if (!fs.existsSync(path.join(semoSystemDir, "semo-core"))) {
147
- (0, child_process_1.execSync)(`git subtree add --prefix=semo-system/semo-core ${SEMO_REPO} main --squash 2>/dev/null || true`, { cwd, stdio: "pipe" });
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 (!fs.existsSync(path.join(semoSystemDir, "semo-skills"))) {
151
- // subtree split으로 특정 폴더만 가져오기는 복잡하므로 전체 clone 후 복사
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
- spinner.succeed("White Box 설정 완료 (semo-core, semo-skills)");
168
- // .claude 심볼릭 링크 생성
169
- const claudeDir = path.join(cwd, ".claude");
170
- const agentsLink = path.join(claudeDir, "agents");
171
- const skillsLink = path.join(claudeDir, "skills");
172
- if (!fs.existsSync(agentsLink)) {
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
- if (!fs.existsSync(skillsLink)) {
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
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)) {
261
+ fs.symlinkSync("../../semo-system/semo-core/commands/SEMO", semoCommandsLink);
262
+ console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
263
+ }
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
+ }
181
298
  catch (error) {
182
- spinner.fail("White Box 설정 실패");
299
+ spinner.fail("Extensions 설치 실패");
183
300
  console.error(chalk_1.default.red(` ${error}`));
184
301
  }
185
302
  }
186
- // === Black Box 설정 (MCP) ===
187
- async function setupBlackBox(cwd, force) {
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) {
188
319
  console.log(chalk_1.default.cyan("\n🔧 Black Box 설정 (MCP Server)"));
189
320
  console.log(chalk_1.default.gray(" 토큰이 격리된 외부 연동 도구\n"));
190
321
  const settingsPath = path.join(cwd, ".claude", "settings.json");
191
- // 기존 설정 파일 확인 - force가 아니면 사용자에게 확인
192
322
  if (fs.existsSync(settingsPath) && !force) {
193
323
  const shouldOverwrite = await confirmOverwrite(".claude/settings.json", settingsPath);
194
324
  if (!shouldOverwrite) {
@@ -212,7 +342,6 @@ async function setupBlackBox(cwd, force) {
212
342
  };
213
343
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
214
344
  console.log(chalk_1.default.green("✓ .claude/settings.json 생성됨 (MCP 설정)"));
215
- console.log(chalk_1.default.gray(" → semo-integrations: github, slack, supabase"));
216
345
  }
217
346
  // === Context Mesh 초기화 ===
218
347
  async function setupContextMesh(cwd) {
@@ -226,6 +355,7 @@ async function setupContextMesh(cwd) {
226
355
  const contextContent = `# Project Context
227
356
 
228
357
  > 세션 간 영속화되는 프로젝트 컨텍스트
358
+ > SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
229
359
 
230
360
  ---
231
361
 
@@ -234,7 +364,7 @@ async function setupContextMesh(cwd) {
234
364
  | 항목 | 값 |
235
365
  |------|-----|
236
366
  | **이름** | ${path.basename(cwd)} |
237
- | **SEMO 버전** | 2.0.0 |
367
+ | **SEMO 버전** | ${VERSION} |
238
368
  | **설치일** | ${new Date().toISOString().split("T")[0]} |
239
369
 
240
370
  ---
@@ -245,6 +375,12 @@ _아직 작업 기록이 없습니다._
245
375
 
246
376
  ---
247
377
 
378
+ ## 기술 스택
379
+
380
+ _프로젝트 분석 후 자동으로 채워집니다._
381
+
382
+ ---
383
+
248
384
  *마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
249
385
  `;
250
386
  fs.writeFileSync(contextPath, contextContent);
@@ -256,6 +392,7 @@ _아직 작업 기록이 없습니다._
256
392
  const decisionsContent = `# Architecture Decisions
257
393
 
258
394
  > 프로젝트 아키텍처 결정 기록 (ADR)
395
+ > 중요한 기술적 결정을 여기에 기록합니다.
259
396
 
260
397
  ---
261
398
 
@@ -312,10 +449,9 @@ _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
312
449
  }
313
450
  }
314
451
  // === CLAUDE.md 생성 ===
315
- async function setupClaudeMd(cwd, force) {
452
+ async function setupClaudeMd(cwd, extensions, force) {
316
453
  console.log(chalk_1.default.cyan("\n📄 CLAUDE.md 설정"));
317
454
  const claudeMdPath = path.join(cwd, ".claude", "CLAUDE.md");
318
- // 기존 CLAUDE.md 확인 - force가 아니면 사용자에게 확인
319
455
  if (fs.existsSync(claudeMdPath) && !force) {
320
456
  const shouldOverwrite = await confirmOverwrite("CLAUDE.md", claudeMdPath);
321
457
  if (!shouldOverwrite) {
@@ -323,25 +459,41 @@ async function setupClaudeMd(cwd, force) {
323
459
  return;
324
460
  }
325
461
  }
462
+ const extensionsList = extensions.length > 0
463
+ ? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
464
+ : "";
326
465
  const claudeMdContent = `# SEMO Project Configuration
327
466
 
328
- > 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")}` : ""}
329
479
 
330
480
  ## 구조
331
481
 
332
482
  \`\`\`
333
483
  .claude/
334
484
  ├── settings.json # MCP 서버 설정 (Black Box)
335
- ├── memory/ # Context Mesh
485
+ ├── memory/ # Context Mesh (장기 기억)
336
486
  │ ├── context.md # 프로젝트 상태
337
487
  │ ├── decisions.md # 아키텍처 결정
338
488
  │ └── rules/ # 프로젝트별 규칙
339
489
  ├── agents → semo-system/semo-core/agents
340
- └── skills → semo-system/semo-skills
490
+ ├── skills → semo-system/semo-skills
491
+ └── commands/SEMO → semo-system/semo-core/commands/SEMO
341
492
 
342
493
  semo-system/ # White Box (읽기 전용)
343
494
  ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
344
- └── semo-skills/ # Layer 1: coder, tester, planner
495
+ ├── semo-skills/ # Layer 1: 통합 스킬
496
+ ${extensionsList}
345
497
  \`\`\`
346
498
 
347
499
  ## 사용 가능한 커맨드
@@ -352,26 +504,81 @@ semo-system/ # White Box (읽기 전용)
352
504
  | \`/SEMO:slack\` | Slack 메시지 전송 |
353
505
  | \`/SEMO:feedback\` | 피드백 제출 |
354
506
  | \`/SEMO:health\` | 환경 검증 |
507
+ | \`/SEMO:update\` | SEMO 업데이트 |
508
+
509
+ ## Context Mesh 사용
355
510
 
356
- ## 플랫폼 자동 감지
511
+ SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
357
512
 
358
- SEMO는 프로젝트 파일을 분석하여 플랫폼을 자동 감지합니다:
513
+ - **context.md**: 프로젝트 상태, 진행 중인 작업
514
+ - **decisions.md**: 아키텍처 결정 기록 (ADR)
515
+ - **rules/**: 프로젝트별 커스텀 규칙
359
516
 
360
- | 파일 | 플랫폼 |
361
- |------|--------|
362
- | \`next.config.js\` | Next.js |
363
- | \`pom.xml\` | Spring |
364
- | \`docker-compose.yml\` | Microservice |
365
- | 기타 | MVP |
517
+ memory 스킬이 자동으로 파일들을 관리합니다.
366
518
 
367
519
  ## References
368
520
 
369
521
  - [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
370
522
  - [SEMO Skills](semo-system/semo-skills/)
523
+ ${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
371
524
  `;
372
525
  fs.writeFileSync(claudeMdPath, claudeMdContent);
373
526
  console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
374
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
+ });
375
582
  // === status 명령어 ===
376
583
  program
377
584
  .command("status")
@@ -379,26 +586,50 @@ program
379
586
  .action(() => {
380
587
  console.log(chalk_1.default.cyan.bold("\n📊 SEMO 설치 상태\n"));
381
588
  const cwd = process.cwd();
382
- const checks = [
383
- { name: ".claude/", path: path.join(cwd, ".claude"), type: "dir" },
384
- { name: ".claude/settings.json", path: path.join(cwd, ".claude", "settings.json"), type: "file" },
385
- { name: ".claude/memory/", path: path.join(cwd, ".claude", "memory"), type: "dir" },
386
- { name: "semo-system/semo-core/", path: path.join(cwd, "semo-system", "semo-core"), type: "dir" },
387
- { name: "semo-system/semo-skills/", path: path.join(cwd, "semo-system", "semo-skills"), type: "dir" },
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") },
388
595
  ];
389
- let allPassed = true;
390
- for (const check of checks) {
596
+ let standardOk = true;
597
+ for (const check of standardChecks) {
391
598
  const exists = fs.existsSync(check.path);
392
- if (exists) {
393
- console.log(chalk_1.default.green(`✓ ${check.name}`));
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);
394
608
  }
395
- else {
396
- console.log(chalk_1.default.red(`✗ ${check.name}`));
397
- allPassed = false;
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}`));
398
614
  }
399
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
+ }
400
631
  console.log();
401
- if (allPassed) {
632
+ if (standardOk && structureOk) {
402
633
  console.log(chalk_1.default.green.bold("SEMO가 정상적으로 설치되어 있습니다."));
403
634
  }
404
635
  else {
@@ -410,7 +641,7 @@ program
410
641
  program
411
642
  .command("update")
412
643
  .description("SEMO를 최신 버전으로 업데이트합니다")
413
- .action(() => {
644
+ .action(async () => {
414
645
  console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 업데이트\n"));
415
646
  const cwd = process.cwd();
416
647
  const semoSystemDir = path.join(cwd, "semo-system");
@@ -418,18 +649,38 @@ program
418
649
  console.log(chalk_1.default.red("SEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요."));
419
650
  process.exit(1);
420
651
  }
421
- const spinner = (0, ora_1.default)("최신 버전 다운로드 중...").start();
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();
422
666
  try {
423
- // 임시 디렉토리에 최신 버전 clone
424
667
  const tempDir = path.join(cwd, ".semo-temp");
425
- (0, child_process_1.execSync)(`rm -rf ${tempDir}`, { stdio: "pipe" });
426
- (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} ${tempDir}`, { stdio: "pipe" });
427
- // semo-core, semo-skills 업데이트
428
- (0, child_process_1.execSync)(`rm -rf ${semoSystemDir}/semo-core ${semoSystemDir}/semo-skills`, { stdio: "pipe" });
429
- (0, child_process_1.execSync)(`cp -r ${tempDir}/semo-core ${semoSystemDir}/`, { stdio: "pipe" });
430
- (0, child_process_1.execSync)(`cp -r ${tempDir}/semo-skills ${semoSystemDir}/`, { stdio: "pipe" });
431
- // 임시 디렉토리 삭제
432
- (0, child_process_1.execSync)(`rm -rf ${tempDir}`, { stdio: "pipe" });
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" });
433
684
  spinner.succeed("SEMO 업데이트 완료");
434
685
  }
435
686
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {