@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 +8 -7
- package/dist/index.js +360 -109
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* SEMO CLI
|
|
3
|
+
* SEMO CLI v2.0
|
|
4
4
|
*
|
|
5
|
-
* Gemini 하이브리드
|
|
5
|
+
* Gemini 하이브리드 전략 기반 AI Agent Orchestration Framework
|
|
6
6
|
*
|
|
7
7
|
* 사용법:
|
|
8
|
-
* npx @team-semicolon/semo-cli init
|
|
8
|
+
* npx @team-semicolon/semo-cli init # 기본 설치
|
|
9
|
+
* npx @team-semicolon/semo-cli add next # 패키지 추가
|
|
10
|
+
* npx @team-semicolon/semo-cli list # 패키지 목록
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 3. Context Mesh (.claude/memory/) 초기화
|
|
12
|
+
* 구조:
|
|
13
|
+
* - Standard: semo-core + semo-skills (필수)
|
|
14
|
+
* - Extensions: packages/next, packages/backend 등 (선택)
|
|
14
15
|
*/
|
|
15
16
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* SEMO CLI
|
|
4
|
+
* SEMO CLI v2.0
|
|
5
5
|
*
|
|
6
|
-
* Gemini 하이브리드
|
|
6
|
+
* Gemini 하이브리드 전략 기반 AI Agent Orchestration Framework
|
|
7
7
|
*
|
|
8
8
|
* 사용법:
|
|
9
|
-
* npx @team-semicolon/semo-cli init
|
|
9
|
+
* npx @team-semicolon/semo-cli init # 기본 설치
|
|
10
|
+
* npx @team-semicolon/semo-cli add next # 패키지 추가
|
|
11
|
+
* npx @team-semicolon/semo-cli list # 패키지 목록
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 3. Context Mesh (.claude/memory/) 초기화
|
|
13
|
+
* 구조:
|
|
14
|
+
* - Standard: semo-core + semo-skills (필수)
|
|
15
|
+
* - Extensions: packages/next, packages/backend 등 (선택)
|
|
15
16
|
*/
|
|
16
17
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
18
|
if (k2 === undefined) k2 = k;
|
|
@@ -57,11 +58,29 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
57
58
|
const child_process_1 = require("child_process");
|
|
58
59
|
const fs = __importStar(require("fs"));
|
|
59
60
|
const path = __importStar(require("path"));
|
|
60
|
-
const VERSION = "
|
|
61
|
-
|
|
61
|
+
const VERSION = "2.0.0";
|
|
62
|
+
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
63
|
+
// 확장 패키지 정의
|
|
64
|
+
const EXTENSION_PACKAGES = {
|
|
65
|
+
next: { name: "Next.js", desc: "Next.js 프론트엔드 개발 (13 agents, 33 skills)", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
|
|
66
|
+
backend: { name: "Backend", desc: "Spring/Node.js 백엔드 개발 (8 agents, 15 skills)", detect: ["pom.xml", "build.gradle"] },
|
|
67
|
+
po: { name: "PO", desc: "Product Owner - 태스크/에픽 관리 (5 agents, 19 skills)", detect: [] },
|
|
68
|
+
qa: { name: "QA", desc: "QA 테스트 관리 (4 agents, 13 skills)", detect: [] },
|
|
69
|
+
pm: { name: "PM", desc: "프로젝트/스프린트 관리 (5 agents, 16 skills)", detect: [] },
|
|
70
|
+
infra: { name: "Infra", desc: "인프라/배포 관리 (6 agents, 10 skills)", detect: ["docker-compose.yml", "Dockerfile"] },
|
|
71
|
+
design: { name: "Design", desc: "디자인 핸드오프 (3 agents, 4 skills)", detect: [] },
|
|
72
|
+
ms: { name: "Microservice", desc: "마이크로서비스 아키텍처 (5 agents, 5 skills)", detect: [] },
|
|
73
|
+
mvp: { name: "MVP", desc: "MVP 빠른 개발 (4 agents, 6 skills)", detect: [] },
|
|
74
|
+
};
|
|
75
|
+
const program = new commander_1.Command();
|
|
76
|
+
program
|
|
77
|
+
.name("semo")
|
|
78
|
+
.description("SEMO CLI - AI Agent Orchestration Framework")
|
|
79
|
+
.version(VERSION);
|
|
80
|
+
// === 유틸리티 함수들 ===
|
|
62
81
|
async function confirmOverwrite(itemName, itemPath) {
|
|
63
82
|
if (!fs.existsSync(itemPath)) {
|
|
64
|
-
return true;
|
|
83
|
+
return true;
|
|
65
84
|
}
|
|
66
85
|
const { shouldOverwrite } = await inquirer_1.default.prompt([
|
|
67
86
|
{
|
|
@@ -73,19 +92,25 @@ async function confirmOverwrite(itemName, itemPath) {
|
|
|
73
92
|
]);
|
|
74
93
|
return shouldOverwrite;
|
|
75
94
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
function detectProjectType(cwd) {
|
|
96
|
+
const detected = [];
|
|
97
|
+
for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
|
|
98
|
+
for (const file of pkg.detect) {
|
|
99
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
100
|
+
detected.push(key);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return detected;
|
|
106
|
+
}
|
|
82
107
|
// === init 명령어 ===
|
|
83
108
|
program
|
|
84
109
|
.command("init")
|
|
85
|
-
.description("현재 프로젝트에 SEMO를 설치합니다
|
|
110
|
+
.description("현재 프로젝트에 SEMO를 설치합니다")
|
|
86
111
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
87
112
|
.option("--skip-mcp", "MCP 설정 생략")
|
|
88
|
-
.option("--
|
|
113
|
+
.option("--with <packages>", "추가 설치할 패키지 (쉼표 구분: next,backend)")
|
|
89
114
|
.action(async (options) => {
|
|
90
115
|
console.log(chalk_1.default.cyan.bold("\n🚀 SEMO 설치 시작\n"));
|
|
91
116
|
console.log(chalk_1.default.gray("Gemini 하이브리드 전략: White Box + Black Box\n"));
|
|
@@ -100,95 +125,200 @@ program
|
|
|
100
125
|
spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
|
|
101
126
|
process.exit(1);
|
|
102
127
|
}
|
|
103
|
-
// 2.
|
|
128
|
+
// 2. 프로젝트 유형 감지
|
|
129
|
+
const detected = detectProjectType(cwd);
|
|
130
|
+
let extensionsToInstall = [];
|
|
131
|
+
if (options.with) {
|
|
132
|
+
extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
|
|
133
|
+
}
|
|
134
|
+
else if (detected.length > 0) {
|
|
135
|
+
console.log(chalk_1.default.cyan("\n📦 감지된 프로젝트 유형:"));
|
|
136
|
+
detected.forEach(pkg => {
|
|
137
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
138
|
+
});
|
|
139
|
+
const { installDetected } = await inquirer_1.default.prompt([
|
|
140
|
+
{
|
|
141
|
+
type: "confirm",
|
|
142
|
+
name: "installDetected",
|
|
143
|
+
message: "감지된 패키지를 함께 설치할까요?",
|
|
144
|
+
default: true,
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
if (installDetected) {
|
|
148
|
+
extensionsToInstall = detected;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// 3. .claude 디렉토리 생성
|
|
104
152
|
const claudeDir = path.join(cwd, ".claude");
|
|
105
153
|
if (!fs.existsSync(claudeDir)) {
|
|
106
154
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
107
|
-
console.log(chalk_1.default.green("✓ .claude/ 디렉토리 생성됨"));
|
|
155
|
+
console.log(chalk_1.default.green("\n✓ .claude/ 디렉토리 생성됨"));
|
|
108
156
|
}
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
157
|
+
// 4. Standard 설치 (semo-core + semo-skills)
|
|
158
|
+
await setupStandard(cwd, options.force);
|
|
159
|
+
// 5. Extensions 설치
|
|
160
|
+
if (extensionsToInstall.length > 0) {
|
|
161
|
+
await setupExtensions(cwd, extensionsToInstall, options.force);
|
|
112
162
|
}
|
|
113
|
-
//
|
|
163
|
+
// 6. MCP 설정
|
|
114
164
|
if (!options.skipMcp) {
|
|
115
|
-
await
|
|
165
|
+
await setupMCP(cwd, options.force);
|
|
116
166
|
}
|
|
117
|
-
//
|
|
167
|
+
// 7. Context Mesh 초기화
|
|
118
168
|
await setupContextMesh(cwd);
|
|
119
|
-
//
|
|
120
|
-
await setupClaudeMd(cwd, options.force);
|
|
169
|
+
// 8. CLAUDE.md 생성
|
|
170
|
+
await setupClaudeMd(cwd, extensionsToInstall, options.force);
|
|
171
|
+
// 완료 메시지
|
|
121
172
|
console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
|
|
122
|
-
console.log(chalk_1.default.cyan("
|
|
173
|
+
console.log(chalk_1.default.cyan("설치된 구성:"));
|
|
174
|
+
console.log(chalk_1.default.gray(" [Standard]"));
|
|
175
|
+
console.log(chalk_1.default.gray(" ✓ semo-core (원칙, 오케스트레이터)"));
|
|
176
|
+
console.log(chalk_1.default.gray(" ✓ semo-skills (13개 통합 스킬)"));
|
|
177
|
+
if (extensionsToInstall.length > 0) {
|
|
178
|
+
console.log(chalk_1.default.gray(" [Extensions]"));
|
|
179
|
+
extensionsToInstall.forEach(pkg => {
|
|
180
|
+
console.log(chalk_1.default.gray(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
console.log(chalk_1.default.cyan("\n다음 단계:"));
|
|
123
184
|
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
124
185
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
125
|
-
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말
|
|
186
|
+
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
|
|
187
|
+
if (extensionsToInstall.length === 0 && detected.length === 0) {
|
|
188
|
+
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add next)"));
|
|
189
|
+
}
|
|
190
|
+
console.log();
|
|
126
191
|
});
|
|
127
|
-
// ===
|
|
128
|
-
async function
|
|
192
|
+
// === Standard 설치 (semo-core + semo-skills) ===
|
|
193
|
+
async function setupStandard(cwd, force) {
|
|
129
194
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
130
|
-
console.log(chalk_1.default.cyan("\n📚
|
|
131
|
-
console.log(chalk_1.default.gray("
|
|
132
|
-
|
|
195
|
+
console.log(chalk_1.default.cyan("\n📚 Standard 설치 (White Box)"));
|
|
196
|
+
console.log(chalk_1.default.gray(" semo-core: 원칙, 오케스트레이터"));
|
|
197
|
+
console.log(chalk_1.default.gray(" semo-skills: 13개 통합 스킬\n"));
|
|
198
|
+
// 기존 디렉토리 확인
|
|
133
199
|
if (fs.existsSync(semoSystemDir) && !force) {
|
|
134
200
|
const shouldOverwrite = await confirmOverwrite("semo-system/", semoSystemDir);
|
|
135
201
|
if (!shouldOverwrite) {
|
|
136
202
|
console.log(chalk_1.default.gray(" → semo-system/ 건너뜀"));
|
|
137
203
|
return;
|
|
138
204
|
}
|
|
139
|
-
|
|
140
|
-
(0, child_process_1.execSync)(`rm -rf ${semoSystemDir}`, { stdio: "pipe" });
|
|
205
|
+
(0, child_process_1.execSync)(`rm -rf "${semoSystemDir}"`, { stdio: "pipe" });
|
|
141
206
|
console.log(chalk_1.default.green(" ✓ 기존 semo-system/ 삭제됨"));
|
|
142
207
|
}
|
|
143
208
|
const spinner = (0, ora_1.default)("semo-core, semo-skills 다운로드 중...").start();
|
|
144
209
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
210
|
+
const tempDir = path.join(cwd, ".semo-temp");
|
|
211
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
212
|
+
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
|
|
213
|
+
fs.mkdirSync(semoSystemDir, { recursive: true });
|
|
214
|
+
// semo-core 복사
|
|
215
|
+
if (fs.existsSync(path.join(tempDir, "semo-core"))) {
|
|
216
|
+
(0, child_process_1.execSync)(`cp -r "${tempDir}/semo-core" "${semoSystemDir}/"`, { stdio: "pipe" });
|
|
148
217
|
}
|
|
149
|
-
// semo-skills
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
const tempDir = path.join(cwd, ".semo-temp");
|
|
153
|
-
if (!fs.existsSync(tempDir)) {
|
|
154
|
-
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} ${tempDir}`, { stdio: "pipe" });
|
|
155
|
-
}
|
|
156
|
-
// semo-core와 semo-skills 복사
|
|
157
|
-
fs.mkdirSync(semoSystemDir, { recursive: true });
|
|
158
|
-
if (fs.existsSync(path.join(tempDir, "semo-core"))) {
|
|
159
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-core ${semoSystemDir}/`, { stdio: "pipe" });
|
|
160
|
-
}
|
|
161
|
-
if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
|
|
162
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-skills ${semoSystemDir}/`, { stdio: "pipe" });
|
|
163
|
-
}
|
|
164
|
-
// 임시 디렉토리 삭제
|
|
165
|
-
(0, child_process_1.execSync)(`rm -rf ${tempDir}`, { stdio: "pipe" });
|
|
218
|
+
// semo-skills 복사
|
|
219
|
+
if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
|
|
220
|
+
(0, child_process_1.execSync)(`cp -r "${tempDir}/semo-skills" "${semoSystemDir}/"`, { stdio: "pipe" });
|
|
166
221
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
222
|
+
(0, child_process_1.execSync)(`rm -rf "${tempDir}"`, { stdio: "pipe" });
|
|
223
|
+
spinner.succeed("Standard 설치 완료");
|
|
224
|
+
// 심볼릭 링크 생성
|
|
225
|
+
await createStandardSymlinks(cwd);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
spinner.fail("Standard 설치 실패");
|
|
229
|
+
console.error(chalk_1.default.red(` ${error}`));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// === Standard 심볼릭 링크 ===
|
|
233
|
+
async function createStandardSymlinks(cwd) {
|
|
234
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
235
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
236
|
+
// agents 링크
|
|
237
|
+
const agentsLink = path.join(claudeDir, "agents");
|
|
238
|
+
if (!fs.existsSync(agentsLink)) {
|
|
239
|
+
const agentsTarget = path.join(semoSystemDir, "semo-core", "agents");
|
|
240
|
+
if (fs.existsSync(agentsTarget)) {
|
|
173
241
|
fs.symlinkSync("../semo-system/semo-core/agents", agentsLink);
|
|
174
242
|
console.log(chalk_1.default.green(" ✓ .claude/agents → semo-system/semo-core/agents"));
|
|
175
243
|
}
|
|
176
|
-
|
|
244
|
+
}
|
|
245
|
+
// skills 링크
|
|
246
|
+
const skillsLink = path.join(claudeDir, "skills");
|
|
247
|
+
if (!fs.existsSync(skillsLink)) {
|
|
248
|
+
const skillsTarget = path.join(semoSystemDir, "semo-skills");
|
|
249
|
+
if (fs.existsSync(skillsTarget)) {
|
|
177
250
|
fs.symlinkSync("../semo-system/semo-skills", skillsLink);
|
|
178
251
|
console.log(chalk_1.default.green(" ✓ .claude/skills → semo-system/semo-skills"));
|
|
179
252
|
}
|
|
180
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("
|
|
299
|
+
spinner.fail("Extensions 설치 실패");
|
|
183
300
|
console.error(chalk_1.default.red(` ${error}`));
|
|
184
301
|
}
|
|
185
302
|
}
|
|
186
|
-
// ===
|
|
187
|
-
async function
|
|
303
|
+
// === Extensions 심볼릭 링크 ===
|
|
304
|
+
async function createExtensionSymlinks(cwd, packages) {
|
|
305
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
306
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
307
|
+
for (const pkg of packages) {
|
|
308
|
+
// sax-{pkg} 호환 링크 (기존 SAX 사용자용)
|
|
309
|
+
const saxPkgLink = path.join(claudeDir, `sax-${pkg}`);
|
|
310
|
+
const pkgPath = path.join(semoSystemDir, pkg);
|
|
311
|
+
if (fs.existsSync(pkgPath) && !fs.existsSync(saxPkgLink)) {
|
|
312
|
+
fs.symlinkSync(`../semo-system/${pkg}`, saxPkgLink);
|
|
313
|
+
console.log(chalk_1.default.green(` ✓ .claude/sax-${pkg} → semo-system/${pkg}`));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// === MCP 설정 ===
|
|
318
|
+
async function setupMCP(cwd, force) {
|
|
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 버전** |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
{ name: "semo-
|
|
387
|
-
{ name: "semo-
|
|
589
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
590
|
+
// Standard 확인
|
|
591
|
+
console.log(chalk_1.default.white.bold("Standard:"));
|
|
592
|
+
const standardChecks = [
|
|
593
|
+
{ name: "semo-core", path: path.join(semoSystemDir, "semo-core") },
|
|
594
|
+
{ name: "semo-skills", path: path.join(semoSystemDir, "semo-skills") },
|
|
388
595
|
];
|
|
389
|
-
let
|
|
390
|
-
for (const check of
|
|
596
|
+
let standardOk = true;
|
|
597
|
+
for (const check of standardChecks) {
|
|
391
598
|
const exists = fs.existsSync(check.path);
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
//
|
|
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}
|
|
430
|
-
(0, child_process_1.execSync)(`cp -r ${tempDir}/semo-skills ${semoSystemDir}
|
|
431
|
-
//
|
|
432
|
-
|
|
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) {
|