@team-semicolon/semo-cli 1.2.0 → 2.0.1

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