@team-semicolon/semo-cli 4.7.0 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -123,152 +123,25 @@ function isVersionLower(current, latest) {
123
123
  return false;
124
124
  }
125
125
  /**
126
- * GitHub raw URL에서 패키지 버전 가져오기
126
+ * init/update 시작 CLI 버전 비교 결과 출력
127
127
  */
128
- async function getRemotePackageVersion(packagePath) {
129
- try {
130
- const url = `https://raw.githubusercontent.com/semicolon-devteam/semo/main/packages/${packagePath}/VERSION`;
131
- const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
132
- if (!response.ok)
133
- return null;
134
- const version = await response.text();
135
- return version.trim();
136
- }
137
- catch {
138
- return null;
139
- }
140
- }
141
- /**
142
- * semo-core 등 원격 버전 가져오기 (semo-system/ 하위 경로)
143
- */
144
- async function getRemoteCoreVersion(type) {
145
- try {
146
- // v5.0: semo-system/ 하위에 Standard 패키지가 위치
147
- const url = `https://raw.githubusercontent.com/semicolon-devteam/semo/main/semo-system/${type}/VERSION`;
148
- const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
149
- if (!response.ok)
150
- return null;
151
- const version = await response.text();
152
- return version.trim();
153
- }
154
- catch {
155
- return null;
156
- }
157
- }
158
- /**
159
- * init/update 시작 시 버전 비교 결과 출력
160
- */
161
- async function showVersionComparison(cwd) {
128
+ async function showVersionComparison() {
162
129
  console.log(chalk_1.default.cyan("📊 버전 확인\n"));
163
130
  const spinner = (0, ora_1.default)(" 버전 정보 조회 중...").start();
164
131
  try {
165
- // 1. CLI 버전 비교
166
132
  const currentCliVersion = VERSION;
167
133
  const latestCliVersion = await getLatestVersion();
168
- // 2. semo-core 버전 비교
169
- const semoSystemDir = path.join(cwd, "semo-system");
170
- const hasSemoSystem = fs.existsSync(semoSystemDir);
171
- const versionInfos = [];
172
- // CLI
173
- versionInfos.push({
174
- name: "semo-cli (npm)",
175
- local: currentCliVersion,
176
- remote: latestCliVersion,
177
- needsUpdate: latestCliVersion ? isVersionLower(currentCliVersion, latestCliVersion) : false,
178
- level: 0,
179
- });
180
- // 레거시 환경 경고 (루트에 semo-core가 있는 경우)
181
- const hasLegacyCore = fs.existsSync(path.join(cwd, "semo-core"));
182
- if (hasLegacyCore) {
183
- spinner.warn("레거시 환경 감지됨");
184
- console.log(chalk_1.default.yellow("\n ⚠️ 구버전 SEMO 구조가 감지되었습니다."));
185
- console.log(chalk_1.default.gray(" 루트에 semo-core/가 있습니다."));
186
- console.log(chalk_1.default.cyan("\n 💡 마이그레이션 방법:"));
187
- console.log(chalk_1.default.gray(" 1. 기존 semo-core/ 폴더 삭제"));
188
- console.log(chalk_1.default.gray(" 2. .claude/ 폴더 삭제"));
189
- console.log(chalk_1.default.gray(" 3. semo init 다시 실행\n"));
190
- console.log(chalk_1.default.gray(" 또는: semo migrate --force\n"));
191
- return;
192
- }
193
- // semo-core (semo-system/ 내부만 확인)
194
- const corePathSystem = path.join(semoSystemDir, "semo-core", "VERSION");
195
- if (fs.existsSync(corePathSystem)) {
196
- const localCore = fs.readFileSync(corePathSystem, "utf-8").trim();
197
- const remoteCore = await getRemoteCoreVersion("semo-core");
198
- versionInfos.push({
199
- name: "semo-core",
200
- local: localCore,
201
- remote: remoteCore,
202
- needsUpdate: remoteCore ? isVersionLower(localCore, remoteCore) : false,
203
- level: 0,
204
- });
205
- }
206
- // semo-skills 제거됨 — 중앙 DB 단일 SoT
207
- // semo-agents (semo-system/ 내부)
208
- const agentsPathSystem = path.join(semoSystemDir, "semo-agents", "VERSION");
209
- if (fs.existsSync(agentsPathSystem)) {
210
- const localAgents = fs.readFileSync(agentsPathSystem, "utf-8").trim();
211
- const remoteAgents = await getRemoteCoreVersion("semo-agents");
212
- versionInfos.push({
213
- name: "semo-agents",
214
- local: localAgents,
215
- remote: remoteAgents,
216
- needsUpdate: remoteAgents ? isVersionLower(localAgents, remoteAgents) : false,
217
- level: 0,
218
- });
219
- }
220
- // semo-scripts (semo-system/ 내부)
221
- const scriptsPathSystem = path.join(semoSystemDir, "semo-scripts", "VERSION");
222
- if (fs.existsSync(scriptsPathSystem)) {
223
- const localScripts = fs.readFileSync(scriptsPathSystem, "utf-8").trim();
224
- const remoteScripts = await getRemoteCoreVersion("semo-scripts");
225
- versionInfos.push({
226
- name: "semo-scripts",
227
- local: localScripts,
228
- remote: remoteScripts,
229
- needsUpdate: remoteScripts ? isVersionLower(localScripts, remoteScripts) : false,
230
- level: 0,
231
- });
232
- }
233
134
  spinner.stop();
234
- // 결과 출력
235
- const needsUpdateCount = versionInfos.filter(v => v.needsUpdate).length;
236
- console.log(chalk_1.default.gray(" ┌────────────────────────┬──────────┬──────────┬────────┐"));
237
- console.log(chalk_1.default.gray(" │ 패키지 │ 설치됨 │ 최신 │ 상태 │"));
238
- console.log(chalk_1.default.gray(" ├────────────────────────┼──────────┼──────────┼────────┤"));
239
- for (const info of versionInfos) {
240
- // 계층 구조 표시를 위한 접두사
241
- let prefix = "";
242
- let displayName = info.name;
243
- if (info.level === 1) {
244
- // 그룹 패키지 (eng, biz, ops)
245
- prefix = "📦 ";
246
- }
247
- else if (info.level === 2) {
248
- // 하위 패키지
249
- prefix = " └─ ";
250
- // 그룹명 제거하고 하위 이름만 표시 (예: biz/discovery → discovery)
251
- displayName = info.name.includes("/") ? info.name.split("/").pop() || info.name : info.name;
252
- }
253
- const name = (prefix + displayName).padEnd(22);
254
- const local = (info.local || "-").padEnd(8);
255
- const remote = (info.remote || "-").padEnd(8);
256
- const status = info.needsUpdate
257
- ? chalk_1.default.yellow("⬆ 업데이트")
258
- : chalk_1.default.green("✓ 최신");
259
- if (info.needsUpdate) {
260
- console.log(chalk_1.default.yellow(` │ ${name} │ ${local} │ ${remote} │ ${status} │`));
261
- }
262
- else {
263
- console.log(chalk_1.default.gray(` │ ${name} │ ${local} │ ${remote} │ `) + status + chalk_1.default.gray(" │"));
264
- }
135
+ console.log(chalk_1.default.white(` semo-cli: ${chalk_1.default.green.bold(currentCliVersion)}`));
136
+ if (latestCliVersion) {
137
+ console.log(chalk_1.default.gray(` (최신: ${latestCliVersion})`));
265
138
  }
266
- console.log(chalk_1.default.gray(" └────────────────────────┴──────────┴──────────┴────────┘"));
267
- if (needsUpdateCount > 0) {
268
- console.log(chalk_1.default.yellow(`\n ⚠ ${needsUpdateCount}개 패키지 업데이트 가능`));
139
+ if (latestCliVersion && isVersionLower(currentCliVersion, latestCliVersion)) {
140
+ console.log(chalk_1.default.yellow(`\n ⚠ CLI 업데이트 가능`));
141
+ console.log(chalk_1.default.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
269
142
  }
270
143
  else {
271
- console.log(chalk_1.default.green("\n ✓ 모든 패키지가 최신 버전입니다"));
144
+ console.log(chalk_1.default.green("\n ✓ 최신 버전입니다"));
272
145
  }
273
146
  console.log("");
274
147
  }
@@ -284,97 +157,6 @@ const isWindows = os.platform() === "win32" ||
284
157
  process.env.OSTYPE?.includes("msys") ||
285
158
  process.env.OSTYPE?.includes("cygwin") ||
286
159
  process.env.TERM_PROGRAM === "mintty";
287
- /**
288
- * Windows에서 Junction 링크를 생성하거나, Unix에서 심볼릭 링크를 생성
289
- * Junction은 관리자 권한 없이 디렉토리 링크를 생성할 수 있음
290
- *
291
- * Windows 환경 (Git Bash, PowerShell, CMD 포함):
292
- * 1. Junction 시도 (폴더만 가능)
293
- * 2. 실패 시 xcopy로 복사
294
- *
295
- * Unix/Mac 환경:
296
- * - 상대 경로 심볼릭 링크
297
- */
298
- function createSymlinkOrJunction(targetPath, linkPath) {
299
- // 이미 존재하는 링크/파일 제거 (깨진 심볼릭 링크도 처리)
300
- try {
301
- const stats = fs.lstatSync(linkPath);
302
- if (stats.isSymbolicLink() || stats.isFile() || stats.isDirectory()) {
303
- try {
304
- fs.unlinkSync(linkPath);
305
- }
306
- catch {
307
- removeRecursive(linkPath);
308
- }
309
- }
310
- }
311
- catch {
312
- // 파일이 존재하지 않음 - 정상
313
- }
314
- if (isWindows) {
315
- // Windows: Junction 사용 (절대 경로, Windows 형식 필요)
316
- // path.resolve는 Git Bash에서도 Windows 경로를 반환
317
- const absoluteTarget = path.resolve(targetPath);
318
- const absoluteLink = path.resolve(linkPath);
319
- // 타겟이 파일인지 폴더인지 확인
320
- const targetStats = fs.statSync(targetPath);
321
- const isDirectory = targetStats.isDirectory();
322
- if (isDirectory) {
323
- // 폴더: Junction 시도
324
- let success = false;
325
- try {
326
- // mklink /J 사용 (관리자 권한 불필요)
327
- (0, child_process_1.execSync)(`cmd /c mklink /J "${absoluteLink}" "${absoluteTarget}"`, {
328
- stdio: "pipe",
329
- windowsHide: true,
330
- });
331
- success = true;
332
- }
333
- catch {
334
- // Junction 실패 - xcopy로 복사
335
- success = false;
336
- }
337
- if (!success) {
338
- // fallback: 디렉토리 복사
339
- console.log(chalk_1.default.yellow(` ⚠ Junction 생성 실패, 복사로 대체: ${path.basename(linkPath)}`));
340
- console.log(chalk_1.default.gray(` 💡 업데이트 시 semo update 명령으로 동기화하세요.`));
341
- fs.mkdirSync(absoluteLink, { recursive: true });
342
- (0, child_process_1.execSync)(`xcopy /E /I /Q /Y "${absoluteTarget}" "${absoluteLink}"`, {
343
- stdio: "pipe",
344
- windowsHide: true,
345
- });
346
- }
347
- }
348
- else {
349
- // 파일: 직접 복사 (Junction은 폴더만 지원)
350
- fs.copyFileSync(absoluteTarget, absoluteLink);
351
- }
352
- }
353
- else {
354
- // Unix: 상대 경로 심볼릭 링크
355
- const relativeTarget = path.relative(path.dirname(linkPath), targetPath);
356
- fs.symlinkSync(relativeTarget, linkPath);
357
- }
358
- }
359
- /**
360
- * 심볼릭 링크가 유효한지 확인 (타겟 존재 여부)
361
- */
362
- function isSymlinkValid(linkPath) {
363
- try {
364
- const stats = fs.lstatSync(linkPath);
365
- if (!stats.isSymbolicLink())
366
- return true; // 일반 파일/디렉토리
367
- // 심볼릭 링크인 경우 타겟 존재 확인
368
- const target = fs.readlinkSync(linkPath);
369
- const absoluteTarget = path.isAbsolute(target)
370
- ? target
371
- : path.resolve(path.dirname(linkPath), target);
372
- return fs.existsSync(absoluteTarget);
373
- }
374
- catch {
375
- return false;
376
- }
377
- }
378
160
  /**
379
161
  * 레거시 SEMO 환경을 감지합니다.
380
162
  * 레거시: 프로젝트 루트에 semo-core/ 가 직접 있는 경우
@@ -419,116 +201,7 @@ function detectLegacyEnvironment(cwd) {
419
201
  hasSemoSystem: fs.existsSync(path.join(cwd, "semo-system")),
420
202
  };
421
203
  }
422
- /**
423
- * 레거시 환경을 새 환경으로 마이그레이션합니다.
424
- */
425
- async function migrateLegacyEnvironment(cwd) {
426
- const detection = detectLegacyEnvironment(cwd);
427
- if (!detection.hasLegacy) {
428
- return true; // 마이그레이션 불필요
429
- }
430
- console.log(chalk_1.default.yellow("\n⚠️ 레거시 SEMO 환경이 감지되었습니다.\n"));
431
- console.log(chalk_1.default.gray(" 감지된 레거시 경로:"));
432
- detection.legacyPaths.forEach(p => {
433
- console.log(chalk_1.default.gray(` - ${p}`));
434
- });
435
- console.log();
436
- // 사용자 확인
437
- const { shouldMigrate } = await inquirer_1.default.prompt([
438
- {
439
- type: "confirm",
440
- name: "shouldMigrate",
441
- message: "레거시 환경을 새 구조(semo-system/)로 마이그레이션하시겠습니까?",
442
- default: true,
443
- },
444
- ]);
445
- if (!shouldMigrate) {
446
- console.log(chalk_1.default.yellow("\n마이그레이션이 취소되었습니다."));
447
- console.log(chalk_1.default.gray("💡 수동 마이그레이션 방법:"));
448
- console.log(chalk_1.default.gray(" 1. 기존 레거시 폴더 삭제"));
449
- console.log(chalk_1.default.gray(" 2. .claude/ 폴더 삭제"));
450
- console.log(chalk_1.default.gray(" 3. semo init 다시 실행\n"));
451
- return false;
452
- }
453
- const spinner = (0, ora_1.default)("레거시 환경 마이그레이션 중...").start();
454
- try {
455
- // 1. 루트의 레거시 디렉토리 삭제
456
- const legacyDirs = ["semo-core", "sax-core", "sax-skills"];
457
- for (const dir of legacyDirs) {
458
- const dirPath = path.join(cwd, dir);
459
- if (fs.existsSync(dirPath) && !fs.lstatSync(dirPath).isSymbolicLink()) {
460
- removeRecursive(dirPath);
461
- console.log(chalk_1.default.gray(` ✓ ${dir}/ 삭제됨`));
462
- }
463
- }
464
- // 2. .claude/ 내부의 레거시 심볼릭 링크 삭제
465
- const claudeDir = path.join(cwd, ".claude");
466
- if (fs.existsSync(claudeDir)) {
467
- const linksToCheck = ["agents", "skills", "commands"];
468
- for (const linkName of linksToCheck) {
469
- const linkPath = path.join(claudeDir, linkName);
470
- if (fs.existsSync(linkPath)) {
471
- removeRecursive(linkPath);
472
- }
473
- }
474
- }
475
- // 3. 기존 semo-system이 완전히 레거시인 경우에만 삭제
476
- // (Standard 패키지가 없는 경우)
477
- const semoSystemDir = path.join(cwd, "semo-system");
478
- if (fs.existsSync(semoSystemDir)) {
479
- const hasStandard = fs.existsSync(path.join(semoSystemDir, "semo-core"));
480
- if (!hasStandard) {
481
- removeRecursive(semoSystemDir);
482
- console.log(chalk_1.default.gray(` ✓ semo-system/ 삭제됨 (완전 재설치)`));
483
- }
484
- }
485
- spinner.succeed("레거시 환경 정리 완료");
486
- console.log(chalk_1.default.green(" → 새 환경으로 설치를 진행합니다.\n"));
487
- return true;
488
- }
489
- catch (error) {
490
- spinner.fail("마이그레이션 실패");
491
- console.error(chalk_1.default.red(` ${error}`));
492
- return false;
493
- }
494
- }
495
- /**
496
- * 플랫폼에 맞는 rm -rf 실행
497
- */
498
- function removeRecursive(targetPath) {
499
- if (!fs.existsSync(targetPath))
500
- return;
501
- if (isWindows) {
502
- try {
503
- const stats = fs.lstatSync(targetPath);
504
- if (stats.isSymbolicLink()) {
505
- // Junction/Symlink는 rmdir로 제거 (내용물 보존)
506
- (0, child_process_1.execSync)(`cmd /c "rmdir "${targetPath}""`, { stdio: "pipe" });
507
- }
508
- else {
509
- (0, child_process_1.execSync)(`cmd /c "rd /s /q "${targetPath}""`, { stdio: "pipe" });
510
- }
511
- }
512
- catch {
513
- fs.rmSync(targetPath, { recursive: true, force: true });
514
- }
515
- }
516
- else {
517
- (0, child_process_1.execSync)(`rm -rf "${targetPath}"`, { stdio: "pipe" });
518
- }
519
- }
520
- /**
521
- * 플랫폼에 맞는 cp -r 실행
522
- */
523
- function copyRecursive(src, dest) {
524
- if (isWindows) {
525
- (0, child_process_1.execSync)(`xcopy /E /I /Q "${src}" "${dest}"`, { stdio: "pipe" });
526
- }
527
- else {
528
- (0, child_process_1.execSync)(`cp -r "${src}" "${dest}"`, { stdio: "pipe" });
529
- }
530
- }
531
- const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
204
+ // (migrateLegacyEnvironment / removeRecursive removed — semo-system migration no longer needed)
532
205
  const program = new commander_1.Command();
533
206
  program
534
207
  .name("semo")
@@ -545,107 +218,20 @@ program
545
218
  * 상세 버전 정보 표시 및 업데이트 확인
546
219
  */
547
220
  async function showVersionInfo() {
548
- const cwd = process.cwd();
549
221
  console.log(chalk_1.default.cyan.bold("\n📦 SEMO 버전 정보\n"));
550
- const versionInfos = [];
551
- // 1. CLI 버전
552
222
  const latestCliVersion = await getLatestVersion();
553
- versionInfos.push({
554
- name: "semo-cli",
555
- local: VERSION,
556
- remote: latestCliVersion,
557
- needsUpdate: latestCliVersion ? isVersionLower(VERSION, latestCliVersion) : false,
558
- level: 0,
559
- });
560
- // 2. semo-core 버전 (루트 또는 semo-system 내부)
561
- const corePathRoot = path.join(cwd, "semo-core", "VERSION");
562
- const corePathSystem = path.join(cwd, "semo-system", "semo-core", "VERSION");
563
- const corePath = fs.existsSync(corePathRoot) ? corePathRoot : corePathSystem;
564
- if (fs.existsSync(corePath)) {
565
- const localCore = fs.readFileSync(corePath, "utf-8").trim();
566
- const remoteCore = await getRemoteCoreVersion("semo-core");
567
- versionInfos.push({
568
- name: "semo-core",
569
- local: localCore,
570
- remote: remoteCore,
571
- needsUpdate: remoteCore ? isVersionLower(localCore, remoteCore) : false,
572
- level: 0,
573
- });
223
+ console.log(chalk_1.default.white(` semo-cli: ${chalk_1.default.green.bold(VERSION)}`));
224
+ if (latestCliVersion) {
225
+ console.log(chalk_1.default.gray(` (최신: ${latestCliVersion})`));
574
226
  }
575
- // 결과 출력
576
- const needsUpdateCount = versionInfos.filter(v => v.needsUpdate).length;
577
- if (versionInfos.length === 1) {
578
- // CLI만 있는 경우 (SEMO 미설치)
579
- const cli = versionInfos[0];
580
- console.log(chalk_1.default.white(` semo-cli: ${chalk_1.default.green.bold(cli.local)}`));
581
- if (cli.remote) {
582
- console.log(chalk_1.default.gray(` (최신: ${cli.remote})`));
583
- }
584
- if (cli.needsUpdate) {
585
- console.log();
586
- console.log(chalk_1.default.yellow.bold(" ⚠️ CLI 업데이트 가능"));
587
- console.log(chalk_1.default.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
588
- }
589
- else {
590
- console.log();
591
- console.log(chalk_1.default.green(" ✓ 최신 버전"));
592
- }
227
+ if (latestCliVersion && isVersionLower(VERSION, latestCliVersion)) {
228
+ console.log();
229
+ console.log(chalk_1.default.yellow.bold(" ⚠️ CLI 업데이트 가능"));
230
+ console.log(chalk_1.default.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
593
231
  }
594
232
  else {
595
- // 테이블 형식으로 출력
596
- console.log(chalk_1.default.gray(" ┌────────────────────────┬──────────┬──────────┬────────┐"));
597
- console.log(chalk_1.default.gray(" │ 패키지 │ 설치됨 │ 최신 │ 상태 │"));
598
- console.log(chalk_1.default.gray(" ├────────────────────────┼──────────┼──────────┼────────┤"));
599
- for (const info of versionInfos) {
600
- // 계층 구조 표시를 위한 접두사
601
- let prefix = "";
602
- let displayName = info.name;
603
- if (info.level === 1) {
604
- // 그룹 패키지 (eng, biz, ops)
605
- prefix = "📦 ";
606
- }
607
- else if (info.level === 2) {
608
- // 하위 패키지
609
- prefix = " └─ ";
610
- // 그룹명 제거하고 하위 이름만 표시
611
- displayName = info.name.includes("/") ? info.name.split("/").pop() || info.name : info.name;
612
- }
613
- const name = (prefix + displayName).padEnd(22);
614
- const local = (info.local || "-").padEnd(8);
615
- const remote = (info.remote || "-").padEnd(8);
616
- const status = info.needsUpdate ? "⬆ 업데이트" : "✓ 최신 ";
617
- const statusColor = info.needsUpdate ? chalk_1.default.yellow : chalk_1.default.green;
618
- console.log(chalk_1.default.gray(" │ ") +
619
- chalk_1.default.white(name) +
620
- chalk_1.default.gray(" │ ") +
621
- chalk_1.default.green(local) +
622
- chalk_1.default.gray(" │ ") +
623
- chalk_1.default.blue(remote) +
624
- chalk_1.default.gray(" │ ") +
625
- statusColor(status) +
626
- chalk_1.default.gray(" │"));
627
- }
628
- console.log(chalk_1.default.gray(" └────────────────────────┴──────────┴──────────┴────────┘"));
629
- // CLI와 다른 패키지 업데이트 가이드 분리
630
- const cliNeedsUpdate = versionInfos[0]?.needsUpdate;
631
- const otherNeedsUpdateCount = versionInfos.slice(1).filter((v) => v.needsUpdate).length;
632
- if (cliNeedsUpdate || otherNeedsUpdateCount > 0) {
633
- console.log();
634
- if (cliNeedsUpdate) {
635
- console.log(chalk_1.default.yellow.bold(" ⚠️ CLI 업데이트 가능"));
636
- console.log(chalk_1.default.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
637
- }
638
- if (otherNeedsUpdateCount > 0) {
639
- if (cliNeedsUpdate)
640
- console.log();
641
- console.log(chalk_1.default.yellow.bold(` ⚠️ ${otherNeedsUpdateCount}개 패키지 업데이트 가능`));
642
- console.log(chalk_1.default.gray(" semo update 명령으로 업데이트하세요."));
643
- }
644
- }
645
- else {
646
- console.log();
647
- console.log(chalk_1.default.green(" ✓ 모든 패키지가 최신 버전입니다."));
648
- }
233
+ console.log();
234
+ console.log(chalk_1.default.green(" ✓ 최신 버전"));
649
235
  }
650
236
  console.log();
651
237
  }
@@ -772,7 +358,16 @@ program
772
358
  }
773
359
  else {
774
360
  spinner.warn("DB 연결 실패 — 스킬/봇 미러 설치를 건너뜁니다");
775
- console.log(chalk_1.default.gray(" ~/.claude/semo/.env를 확인하고 다시 시도하세요: semo onboarding\n"));
361
+ console.log(chalk_1.default.gray([
362
+ "",
363
+ " 흔한 원인:",
364
+ " 1. SSH 터널 미실행 — 로컬에서는 SSH 터널이 필요합니다:",
365
+ " ssh -J opc@152.70.244.169 -L 15432:localhost:5432 opc@10.0.0.91 -N -i ~/.ssh/oci_dev_rsa",
366
+ " 2. ~/.claude/semo/.env의 DATABASE_URL 확인",
367
+ "",
368
+ " 터널 실행 후 다시 시도: semo onboarding",
369
+ "",
370
+ ].join("\n")));
776
371
  await (0, database_1.closeConnection)();
777
372
  return;
778
373
  }
@@ -872,278 +467,6 @@ async function setupStandardGlobal() {
872
467
  console.error(chalk_1.default.red(` ${error}`));
873
468
  }
874
469
  }
875
- // (generateClaudeMd removed — setupClaudeMd handles project CLAUDE.md generation)
876
- // === Standard 심볼릭 링크 (레거시 호환) ===
877
- async function createStandardSymlinks(cwd) {
878
- const claudeDir = path.join(cwd, ".claude");
879
- const semoSystemDir = path.join(cwd, "semo-system");
880
- // agents 디렉토리 생성 및 개별 링크 (Extension 병합 지원)
881
- const claudeAgentsDir = path.join(claudeDir, "agents");
882
- const coreAgentsDir = path.join(semoSystemDir, "semo-core", "agents");
883
- if (fs.existsSync(coreAgentsDir)) {
884
- // 기존 심볼릭 링크면 삭제 (디렉토리로 변경)
885
- if (fs.existsSync(claudeAgentsDir) && fs.lstatSync(claudeAgentsDir).isSymbolicLink()) {
886
- fs.unlinkSync(claudeAgentsDir);
887
- }
888
- fs.mkdirSync(claudeAgentsDir, { recursive: true });
889
- const agents = fs.readdirSync(coreAgentsDir).filter(f => fs.statSync(path.join(coreAgentsDir, f)).isDirectory());
890
- for (const agent of agents) {
891
- const agentLink = path.join(claudeAgentsDir, agent);
892
- const agentTarget = path.join(coreAgentsDir, agent);
893
- if (!fs.existsSync(agentLink)) {
894
- createSymlinkOrJunction(agentTarget, agentLink);
895
- }
896
- }
897
- console.log(chalk_1.default.green(` ✓ .claude/agents/ (${agents.length}개 agent 링크됨)`));
898
- }
899
- // skills — 중앙 DB 단일 SoT (semo-skills 파일시스템 제거됨)
900
- const claudeSkillsDir = path.join(claudeDir, "skills");
901
- fs.mkdirSync(claudeSkillsDir, { recursive: true });
902
- // commands 링크
903
- const commandsDir = path.join(claudeDir, "commands");
904
- fs.mkdirSync(commandsDir, { recursive: true });
905
- const semoCommandsLink = path.join(commandsDir, "SEMO");
906
- const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
907
- // 기존 링크가 있으면 삭제 후 재생성 (업데이트 시에도 최신 반영)
908
- if (fs.existsSync(semoCommandsLink)) {
909
- if (fs.lstatSync(semoCommandsLink).isSymbolicLink()) {
910
- fs.unlinkSync(semoCommandsLink);
911
- }
912
- else {
913
- removeRecursive(semoCommandsLink);
914
- }
915
- }
916
- if (fs.existsSync(commandsTarget)) {
917
- createSymlinkOrJunction(commandsTarget, semoCommandsLink);
918
- console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
919
- }
920
- // SEMO-workflow 커맨드 링크 (워크플로우 커맨드)
921
- const workflowCommandsLink = path.join(commandsDir, "SEMO-workflow");
922
- const workflowCommandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO-workflow");
923
- if (fs.existsSync(workflowCommandsLink)) {
924
- if (fs.lstatSync(workflowCommandsLink).isSymbolicLink()) {
925
- fs.unlinkSync(workflowCommandsLink);
926
- }
927
- else {
928
- removeRecursive(workflowCommandsLink);
929
- }
930
- }
931
- if (fs.existsSync(workflowCommandsTarget)) {
932
- createSymlinkOrJunction(workflowCommandsTarget, workflowCommandsLink);
933
- console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO-workflow → semo-system/semo-core/commands/SEMO-workflow"));
934
- }
935
- }
936
- /**
937
- * 설치 상태를 검증하고 문제점을 리포트
938
- * v3.14.0: DB 기반 설치 지원 (semo-system 없이도 검증 가능)
939
- */
940
- function verifyInstallation(cwd, installedExtensions = []) {
941
- const claudeDir = path.join(cwd, ".claude");
942
- const semoSystemDir = path.join(cwd, "semo-system");
943
- const hasSemoSystem = fs.existsSync(semoSystemDir);
944
- const result = {
945
- success: true,
946
- errors: [],
947
- warnings: [],
948
- stats: {
949
- agents: { expected: 0, linked: 0, broken: 0 },
950
- skills: { expected: 0, linked: 0, broken: 0 },
951
- commands: { exists: false, valid: false },
952
- extensions: [],
953
- },
954
- };
955
- // v3.14.0: DB 기반 설치 시 semo-system이 없어도 됨
956
- // .claude/ 디렉토리가 있으면 DB 기반으로 설치된 것으로 간주
957
- if (!hasSemoSystem && !fs.existsSync(claudeDir)) {
958
- result.errors.push(".claude 디렉토리가 없습니다");
959
- result.success = false;
960
- return result;
961
- }
962
- // DB 기반 설치 검증 (semo-system 없음)
963
- if (!hasSemoSystem) {
964
- // agents 검증 (실제 파일 존재 여부)
965
- const claudeAgentsDir = path.join(claudeDir, "agents");
966
- if (fs.existsSync(claudeAgentsDir)) {
967
- const agents = fs.readdirSync(claudeAgentsDir).filter(f => {
968
- const p = path.join(claudeAgentsDir, f);
969
- return fs.existsSync(p) && fs.statSync(p).isDirectory();
970
- });
971
- result.stats.agents.expected = agents.length;
972
- result.stats.agents.linked = agents.length; // DB 기반이므로 실제 파일
973
- }
974
- // skills 검증 (실제 파일 존재 여부)
975
- const claudeSkillsDir = path.join(claudeDir, "skills");
976
- if (fs.existsSync(claudeSkillsDir)) {
977
- const skills = fs.readdirSync(claudeSkillsDir).filter(f => {
978
- const p = path.join(claudeSkillsDir, f);
979
- return fs.existsSync(p) && fs.statSync(p).isDirectory();
980
- });
981
- result.stats.skills.expected = skills.length;
982
- result.stats.skills.linked = skills.length; // DB 기반이므로 실제 파일
983
- }
984
- // commands 검증 (실제 폴더 존재 여부)
985
- const semoCommandsDir = path.join(claudeDir, "commands", "SEMO");
986
- result.stats.commands.exists = fs.existsSync(semoCommandsDir);
987
- result.stats.commands.valid = result.stats.commands.exists;
988
- return result;
989
- }
990
- // === 레거시: semo-system 기반 설치 검증 ===
991
- const coreDir = path.join(semoSystemDir, "semo-core");
992
- if (!fs.existsSync(coreDir)) {
993
- result.errors.push("semo-core가 설치되지 않았습니다");
994
- result.success = false;
995
- }
996
- // 2. agents 링크 검증 (isSymlinkValid 사용)
997
- const claudeAgentsDir = path.join(claudeDir, "agents");
998
- const coreAgentsDir = path.join(coreDir, "agents");
999
- if (fs.existsSync(coreAgentsDir)) {
1000
- const expectedAgents = fs.readdirSync(coreAgentsDir).filter(f => fs.statSync(path.join(coreAgentsDir, f)).isDirectory());
1001
- result.stats.agents.expected = expectedAgents.length;
1002
- if (fs.existsSync(claudeAgentsDir)) {
1003
- for (const agent of expectedAgents) {
1004
- const linkPath = path.join(claudeAgentsDir, agent);
1005
- try {
1006
- if (fs.existsSync(linkPath) || fs.lstatSync(linkPath).isSymbolicLink()) {
1007
- if (isSymlinkValid(linkPath)) {
1008
- result.stats.agents.linked++;
1009
- }
1010
- else {
1011
- result.stats.agents.broken++;
1012
- result.warnings.push(`깨진 링크: .claude/agents/${agent}`);
1013
- }
1014
- }
1015
- }
1016
- catch {
1017
- // path doesn't exist at all — skip
1018
- }
1019
- }
1020
- }
1021
- }
1022
- // 3. skills — 중앙 DB 단일 SoT (파일시스템 검증 불필요)
1023
- // 4. commands 검증 (isSymlinkValid 사용)
1024
- const semoCommandsLink = path.join(claudeDir, "commands", "SEMO");
1025
- try {
1026
- const linkExists = fs.existsSync(semoCommandsLink) || fs.lstatSync(semoCommandsLink).isSymbolicLink();
1027
- result.stats.commands.exists = linkExists;
1028
- if (linkExists) {
1029
- result.stats.commands.valid = isSymlinkValid(semoCommandsLink);
1030
- if (!result.stats.commands.valid) {
1031
- result.warnings.push("깨진 링크: .claude/commands/SEMO");
1032
- }
1033
- }
1034
- }
1035
- catch {
1036
- result.stats.commands.exists = false;
1037
- result.stats.commands.valid = false;
1038
- }
1039
- // 5. Extensions 검증
1040
- for (const ext of installedExtensions) {
1041
- const extDir = path.join(semoSystemDir, ext);
1042
- const extResult = { name: ext, valid: true, issues: [] };
1043
- if (!fs.existsSync(extDir)) {
1044
- extResult.valid = false;
1045
- extResult.issues.push("디렉토리 없음");
1046
- }
1047
- else {
1048
- // Extension agents 검증
1049
- const extAgentsDir = path.join(extDir, "agents");
1050
- if (fs.existsSync(extAgentsDir)) {
1051
- const extAgents = fs.readdirSync(extAgentsDir).filter(f => fs.statSync(path.join(extAgentsDir, f)).isDirectory());
1052
- for (const agent of extAgents) {
1053
- const linkPath = path.join(claudeAgentsDir, agent);
1054
- if (!fs.existsSync(linkPath)) {
1055
- extResult.issues.push(`agent 링크 누락: ${agent}`);
1056
- }
1057
- }
1058
- }
1059
- // Extension skills 검증
1060
- const extSkillsDir = path.join(extDir, "skills");
1061
- if (fs.existsSync(extSkillsDir)) {
1062
- const extSkills = fs.readdirSync(extSkillsDir).filter(f => fs.statSync(path.join(extSkillsDir, f)).isDirectory());
1063
- const claudeSkillsDir = path.join(claudeDir, "skills");
1064
- for (const skill of extSkills) {
1065
- const linkPath = path.join(claudeSkillsDir, skill);
1066
- if (!fs.existsSync(linkPath)) {
1067
- extResult.issues.push(`skill 링크 누락: ${skill}`);
1068
- }
1069
- }
1070
- }
1071
- }
1072
- if (extResult.issues.length > 0) {
1073
- extResult.valid = false;
1074
- }
1075
- result.stats.extensions.push(extResult);
1076
- }
1077
- // 6. 최종 성공 여부 판단
1078
- if (result.stats.agents.expected > 0 && result.stats.agents.linked === 0) {
1079
- result.errors.push("agents가 하나도 링크되지 않았습니다");
1080
- result.success = false;
1081
- }
1082
- if (result.stats.skills.expected > 0 && result.stats.skills.linked === 0) {
1083
- result.errors.push("skills가 하나도 링크되지 않았습니다");
1084
- result.success = false;
1085
- }
1086
- if (!result.stats.commands.exists) {
1087
- result.errors.push("commands/SEMO가 설치되지 않았습니다");
1088
- result.success = false;
1089
- }
1090
- // 부분 누락 경고
1091
- if (result.stats.agents.linked < result.stats.agents.expected) {
1092
- const missing = result.stats.agents.expected - result.stats.agents.linked;
1093
- result.warnings.push(`${missing}개 agent 링크 누락`);
1094
- }
1095
- if (result.stats.skills.linked < result.stats.skills.expected) {
1096
- const missing = result.stats.skills.expected - result.stats.skills.linked;
1097
- result.warnings.push(`${missing}개 skill 링크 누락`);
1098
- }
1099
- return result;
1100
- }
1101
- /**
1102
- * 검증 결과를 콘솔에 출력
1103
- */
1104
- function printVerificationResult(result) {
1105
- console.log(chalk_1.default.cyan("\n🔍 설치 검증"));
1106
- // Stats
1107
- const agentStatus = result.stats.agents.linked === result.stats.agents.expected
1108
- ? chalk_1.default.green("✓")
1109
- : (result.stats.agents.linked > 0 ? chalk_1.default.yellow("△") : chalk_1.default.red("✗"));
1110
- const skillStatus = result.stats.skills.linked === result.stats.skills.expected
1111
- ? chalk_1.default.green("✓")
1112
- : (result.stats.skills.linked > 0 ? chalk_1.default.yellow("△") : chalk_1.default.red("✗"));
1113
- const cmdStatus = result.stats.commands.valid ? chalk_1.default.green("✓") : chalk_1.default.red("✗");
1114
- console.log(` ${agentStatus} agents: ${result.stats.agents.linked}/${result.stats.agents.expected}` +
1115
- (result.stats.agents.broken > 0 ? chalk_1.default.red(` (깨진 링크: ${result.stats.agents.broken})`) : ""));
1116
- console.log(` ${skillStatus} skills: ${result.stats.skills.linked}/${result.stats.skills.expected}` +
1117
- (result.stats.skills.broken > 0 ? chalk_1.default.red(` (깨진 링크: ${result.stats.skills.broken})`) : ""));
1118
- console.log(` ${cmdStatus} commands/SEMO`);
1119
- // Extensions
1120
- for (const ext of result.stats.extensions) {
1121
- const extStatus = ext.valid ? chalk_1.default.green("✓") : chalk_1.default.yellow("△");
1122
- console.log(` ${extStatus} ${ext.name}` +
1123
- (ext.issues.length > 0 ? chalk_1.default.gray(` (${ext.issues.length}개 이슈)`) : ""));
1124
- }
1125
- // Warnings
1126
- if (result.warnings.length > 0) {
1127
- console.log(chalk_1.default.yellow("\n ⚠️ 경고:"));
1128
- result.warnings.forEach(w => console.log(chalk_1.default.yellow(` - ${w}`)));
1129
- }
1130
- // Errors
1131
- if (result.errors.length > 0) {
1132
- console.log(chalk_1.default.red("\n ❌ 오류:"));
1133
- result.errors.forEach(e => console.log(chalk_1.default.red(` - ${e}`)));
1134
- }
1135
- // Final status
1136
- if (result.success && result.warnings.length === 0) {
1137
- console.log(chalk_1.default.green.bold("\n ✅ 설치 검증 완료 - 모든 항목 정상"));
1138
- }
1139
- else if (result.success) {
1140
- console.log(chalk_1.default.yellow.bold("\n ⚠️ 설치 완료 - 일부 경고 확인 필요"));
1141
- }
1142
- else {
1143
- console.log(chalk_1.default.red.bold("\n ❌ 설치 검증 실패 - 오류 확인 필요"));
1144
- console.log(chalk_1.default.gray(" 'semo init --force'로 재설치하거나 수동으로 문제를 해결하세요."));
1145
- }
1146
- }
1147
470
  const BASE_MCP_SERVERS = [
1148
471
  {
1149
472
  name: "context7",
@@ -1821,18 +1144,8 @@ program
1821
1144
  .command("list")
1822
1145
  .description("설치된 SEMO 패키지 상태를 표시합니다")
1823
1146
  .action(async () => {
1824
- const cwd = process.cwd();
1825
- const semoSystemDir = path.join(cwd, "semo-system");
1826
1147
  console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록\n"));
1827
- // Standard 패키지 표시
1828
- console.log(chalk_1.default.white.bold("Standard (필수)"));
1829
- const standardPkgs = ["semo-core", "semo-agents", "semo-scripts"];
1830
- for (const pkg of standardPkgs) {
1831
- const isInstalled = fs.existsSync(path.join(semoSystemDir, pkg));
1832
- console.log(` ${isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} ${pkg}`);
1833
- }
1834
- console.log();
1835
- // DB 패키지 목록 (DB 연결 가능 시)
1148
+ // DB 패키지 목록
1836
1149
  try {
1837
1150
  const packages = await (0, database_1.getPackages)();
1838
1151
  if (packages.length > 0) {
@@ -1842,52 +1155,54 @@ program
1842
1155
  }
1843
1156
  console.log();
1844
1157
  }
1158
+ else {
1159
+ console.log(chalk_1.default.gray(" 등록된 패키지가 없습니다.\n"));
1160
+ }
1845
1161
  }
1846
1162
  catch {
1847
- // DB 연결 실패 무시
1163
+ console.log(chalk_1.default.yellow(" DB 연결 실패 ~/.claude/semo/.env를 확인하세요.\n"));
1848
1164
  }
1849
1165
  });
1850
1166
  // === status 명령어 ===
1851
1167
  program
1852
1168
  .command("status")
1853
1169
  .description("SEMO 설치 상태를 확인합니다")
1854
- .action(() => {
1170
+ .action(async () => {
1855
1171
  console.log(chalk_1.default.cyan.bold("\n📊 SEMO 설치 상태\n"));
1856
- const cwd = process.cwd();
1857
- const semoSystemDir = path.join(cwd, "semo-system");
1858
- // Standard 확인
1859
- console.log(chalk_1.default.white.bold("Standard:"));
1860
- const standardChecks = [
1861
- { name: "semo-core", path: path.join(semoSystemDir, "semo-core") },
1862
- ];
1863
- let standardOk = true;
1864
- for (const check of standardChecks) {
1865
- const exists = fs.existsSync(check.path);
1866
- console.log(` ${exists ? chalk_1.default.green("✓") : chalk_1.default.red("✗")} ${check.name}`);
1867
- if (!exists)
1868
- standardOk = false;
1869
- }
1870
- // 구조 확인
1871
- console.log(chalk_1.default.white.bold("\n구조:"));
1872
- const structureChecks = [
1873
- { name: ".claude/", path: path.join(cwd, ".claude") },
1874
- { name: ".claude/settings.json", path: path.join(cwd, ".claude", "settings.json") },
1875
- { name: ".claude/memory/", path: path.join(cwd, ".claude", "memory") },
1876
- { name: ".claude/memory/context.md", path: path.join(cwd, ".claude", "memory", "context.md") },
1172
+ const home = os.homedir();
1173
+ // 글로벌 설정 확인
1174
+ console.log(chalk_1.default.white.bold("글로벌 설정 (~/.claude/semo/):"));
1175
+ const globalChecks = [
1176
+ { name: "~/.claude/semo/.env", path: path.join(home, ".claude", "semo", ".env") },
1177
+ { name: "~/.claude/semo/SOUL.md", path: path.join(home, ".claude", "semo", "SOUL.md") },
1178
+ { name: "~/.claude/skills/", path: path.join(home, ".claude", "skills") },
1179
+ { name: "~/.claude/commands/", path: path.join(home, ".claude", "commands") },
1180
+ { name: "~/.claude/agents/", path: path.join(home, ".claude", "agents") },
1877
1181
  ];
1878
- let structureOk = true;
1879
- for (const check of structureChecks) {
1182
+ let globalOk = true;
1183
+ for (const check of globalChecks) {
1880
1184
  const exists = fs.existsSync(check.path);
1881
1185
  console.log(` ${exists ? chalk_1.default.green("✓") : chalk_1.default.red("✗")} ${check.name}`);
1882
1186
  if (!exists)
1883
- structureOk = false;
1187
+ globalOk = false;
1188
+ }
1189
+ // DB 연결 확인
1190
+ console.log(chalk_1.default.white.bold("\nDB 연결:"));
1191
+ const connected = await (0, database_1.isDbConnected)();
1192
+ if (connected) {
1193
+ console.log(chalk_1.default.green(" ✓ DB 연결 정상"));
1194
+ }
1195
+ else {
1196
+ console.log(chalk_1.default.red(" ✗ DB 연결 실패"));
1197
+ globalOk = false;
1884
1198
  }
1199
+ await (0, database_1.closeConnection)();
1885
1200
  console.log();
1886
- if (standardOk && structureOk) {
1201
+ if (globalOk) {
1887
1202
  console.log(chalk_1.default.green.bold("SEMO가 정상적으로 설치되어 있습니다."));
1888
1203
  }
1889
1204
  else {
1890
- console.log(chalk_1.default.yellow("일부 구성 요소가 누락되었습니다. 'semo init'을 실행하세요."));
1205
+ console.log(chalk_1.default.yellow("일부 구성 요소가 누락되었습니다. 'semo onboarding'을 실행하세요."));
1891
1206
  }
1892
1207
  console.log();
1893
1208
  });
@@ -1897,50 +1212,11 @@ program
1897
1212
  .description("SEMO를 최신 버전으로 업데이트합니다")
1898
1213
  .option("--self", "CLI만 업데이트")
1899
1214
  .option("--global", "글로벌 스킬/커맨드/에이전트를 DB 최신으로 갱신 (~/.claude/)")
1900
- .option("--system", "semo-system만 업데이트")
1901
- .option("--skip-cli", "CLI 업데이트 건너뛰기")
1902
- .option("--only <packages>", "특정 패키지만 업데이트 (쉼표 구분: semo-core,biz/management)")
1903
- .option("--migrate", "레거시 환경 강제 마이그레이션")
1904
1215
  .action(async (options) => {
1905
- // === --global: 글로벌 스킬 갱신 ===
1906
- if (options.global) {
1907
- console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 글로벌 업데이트\n"));
1908
- console.log(chalk_1.default.gray(" 대상: ~/.claude/skills, commands, agents (DB 최신)\n"));
1909
- const connected = await (0, database_1.isDbConnected)();
1910
- if (!connected) {
1911
- console.log(chalk_1.default.red(" DB 연결 실패 — ~/.claude/semo/.env를 확인하세요."));
1912
- await (0, database_1.closeConnection)();
1913
- process.exit(1);
1914
- }
1915
- await setupStandardGlobal();
1916
- await (0, database_1.closeConnection)();
1917
- console.log(chalk_1.default.green.bold("\n✅ 글로벌 스킬 업데이트 완료!\n"));
1918
- return;
1919
- }
1920
- console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 업데이트\n"));
1921
- const cwd = process.cwd();
1922
- const semoSystemDir = path.join(cwd, "semo-system");
1923
- const claudeDir = path.join(cwd, ".claude");
1924
- // 0. 버전 비교
1925
- await showVersionComparison(cwd);
1926
- // 0.5. 레거시 환경 감지 및 마이그레이션
1927
- const legacyCheck = detectLegacyEnvironment(cwd);
1928
- if (legacyCheck.hasLegacy || options.migrate) {
1929
- console.log(chalk_1.default.yellow("\n⚠️ 레거시 환경이 감지되어 업데이트 전 마이그레이션이 필요합니다.\n"));
1930
- const migrationSuccess = await migrateLegacyEnvironment(cwd);
1931
- if (migrationSuccess) {
1932
- console.log(chalk_1.default.cyan("마이그레이션 완료. 'semo init'으로 새 환경을 설치하세요.\n"));
1933
- }
1934
- process.exit(0);
1935
- }
1936
- // --only 옵션 파싱
1937
- const onlyPackages = options.only
1938
- ? options.only.split(",").map((p) => p.trim())
1939
- : [];
1940
- const isSelectiveUpdate = onlyPackages.length > 0;
1941
- // === 1. CLI 자체 업데이트 ===
1942
- if (options.self || (!options.system && !options.skipCli && !isSelectiveUpdate)) {
1943
- console.log(chalk_1.default.cyan("📦 CLI 업데이트"));
1216
+ // === --self: CLI만 업데이트 ===
1217
+ if (options.self) {
1218
+ console.log(chalk_1.default.cyan.bold("\n🔄 SEMO CLI 업데이트\n"));
1219
+ await showVersionComparison();
1944
1220
  const cliSpinner = (0, ora_1.default)(" @team-semicolon/semo-cli 업데이트 중...").start();
1945
1221
  try {
1946
1222
  (0, child_process_1.execSync)("npm update -g @team-semicolon/semo-cli", { stdio: "pipe" });
@@ -1957,183 +1233,37 @@ program
1957
1233
  console.error(chalk_1.default.gray(` ${errorMsg}`));
1958
1234
  }
1959
1235
  }
1960
- // --self 옵션만 있으면 여기서 종료
1961
- if (options.self) {
1962
- console.log(chalk_1.default.green.bold("\n✅ CLI 업데이트 완료!\n"));
1963
- return;
1964
- }
1965
- }
1966
- // === 2. semo-system 업데이트 ===
1967
- if (!fs.existsSync(semoSystemDir)) {
1968
- console.log(chalk_1.default.red("SEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요."));
1969
- process.exit(1);
1970
- }
1971
- // 업데이트 대상 결정
1972
- const updateSemoCore = !isSelectiveUpdate || onlyPackages.includes("semo-core");
1973
- const updateSemoAgents = !isSelectiveUpdate || onlyPackages.includes("semo-agents");
1974
- const updateSemoScripts = !isSelectiveUpdate || onlyPackages.includes("semo-scripts");
1975
- console.log(chalk_1.default.cyan("\n📚 semo-system 업데이트"));
1976
- console.log(chalk_1.default.gray(" 대상:"));
1977
- if (updateSemoCore)
1978
- console.log(chalk_1.default.gray(" - semo-core"));
1979
- if (updateSemoAgents)
1980
- console.log(chalk_1.default.gray(" - semo-agents"));
1981
- if (updateSemoScripts)
1982
- console.log(chalk_1.default.gray(" - semo-scripts"));
1983
- if (!updateSemoCore && !updateSemoAgents && !updateSemoScripts) {
1984
- console.log(chalk_1.default.yellow("\n ⚠️ 업데이트할 패키지가 없습니다."));
1985
- console.log(chalk_1.default.gray(" 설치된 패키지: semo-core, semo-agents, semo-scripts"));
1986
- return;
1987
- }
1988
- const spinner = (0, ora_1.default)("\n 최신 버전 다운로드 중...").start();
1989
- try {
1990
- const tempDir = path.join(cwd, ".semo-temp");
1991
- removeRecursive(tempDir);
1992
- (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
1993
- // Standard 업데이트 (선택적) - semo-system/ 하위에서 복사
1994
- const standardUpdates = [
1995
- { flag: updateSemoCore, name: "semo-core" },
1996
- { flag: updateSemoAgents, name: "semo-agents" },
1997
- { flag: updateSemoScripts, name: "semo-scripts" },
1998
- ];
1999
- for (const { flag, name } of standardUpdates) {
2000
- if (flag) {
2001
- const srcPath = path.join(tempDir, "semo-system", name);
2002
- const destPath = path.join(semoSystemDir, name);
2003
- if (fs.existsSync(srcPath)) {
2004
- removeRecursive(destPath);
2005
- copyRecursive(srcPath, destPath);
2006
- }
2007
- }
2008
- }
2009
- removeRecursive(tempDir);
2010
- spinner.succeed(" semo-system 업데이트 완료");
2011
- }
2012
- catch (error) {
2013
- spinner.fail(" semo-system 업데이트 실패");
2014
- console.error(chalk_1.default.red(` ${error}`));
1236
+ console.log(chalk_1.default.green.bold("\n✅ CLI 업데이트 완료!\n"));
2015
1237
  return;
2016
1238
  }
2017
- // === 3. 심볼릭 링크 재생성 ===
2018
- console.log(chalk_1.default.cyan("\n🔗 심볼릭 링크 재생성"));
2019
- // 기존 링크 정리
2020
- const claudeAgentsDir = path.join(claudeDir, "agents");
2021
- const claudeSkillsDir = path.join(claudeDir, "skills");
2022
- if (fs.existsSync(claudeAgentsDir)) {
2023
- const existingLinks = fs.readdirSync(claudeAgentsDir);
2024
- for (const link of existingLinks) {
2025
- const linkPath = path.join(claudeAgentsDir, link);
2026
- if (fs.lstatSync(linkPath).isSymbolicLink()) {
2027
- fs.unlinkSync(linkPath);
2028
- }
2029
- }
2030
- }
2031
- if (fs.existsSync(claudeSkillsDir)) {
2032
- const existingLinks = fs.readdirSync(claudeSkillsDir);
2033
- for (const link of existingLinks) {
2034
- const linkPath = path.join(claudeSkillsDir, link);
2035
- if (fs.lstatSync(linkPath).isSymbolicLink()) {
2036
- fs.unlinkSync(linkPath);
2037
- }
2038
- }
2039
- }
2040
- // commands 링크도 정리 (신규 commands 반영 위해)
2041
- const claudeCommandsDir = path.join(claudeDir, "commands");
2042
- const semoCommandsLink = path.join(claudeCommandsDir, "SEMO");
2043
- if (fs.existsSync(semoCommandsLink)) {
2044
- if (fs.lstatSync(semoCommandsLink).isSymbolicLink()) {
2045
- fs.unlinkSync(semoCommandsLink);
2046
- }
2047
- else {
2048
- removeRecursive(semoCommandsLink);
2049
- }
2050
- }
2051
- // Standard 심볼릭 링크 재생성 (agents, skills, commands 포함)
2052
- await createStandardSymlinks(cwd);
2053
- // === 4. CLAUDE.md 재생성 ===
2054
- console.log(chalk_1.default.cyan("\n📄 CLAUDE.md 재생성"));
2055
- await setupClaudeMd(cwd, [], true);
2056
- // === 5. MCP 서버 동기화 ===
2057
- console.log(chalk_1.default.cyan("\n🔧 MCP 서버 동기화"));
2058
- // MCP 서버 등록 상태 확인
2059
- const allServers = [...BASE_MCP_SERVERS];
2060
- const missingServers = [];
2061
- for (const server of allServers) {
2062
- if (!isMCPServerRegistered(server.name)) {
2063
- missingServers.push(server);
2064
- }
2065
- }
2066
- if (missingServers.length === 0) {
2067
- console.log(chalk_1.default.green(" ✓ 모든 MCP 서버가 등록되어 있습니다"));
2068
- }
2069
- else {
2070
- console.log(chalk_1.default.yellow(` ${missingServers.length}개 MCP 서버 미등록`));
2071
- for (const server of missingServers) {
2072
- const result = registerMCPServer(server);
2073
- if (result.success) {
2074
- console.log(chalk_1.default.green(` ✓ ${server.name} 등록 완료`));
2075
- }
2076
- else {
2077
- console.log(chalk_1.default.red(` ✗ ${server.name} 등록 실패`));
2078
- }
2079
- }
1239
+ // === --global 또는 기본: DB 기반 글로벌 갱신 ===
1240
+ console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 업데이트\n"));
1241
+ // 1. 버전 비교 (CLI only)
1242
+ await showVersionComparison();
1243
+ // 2. DB 연결 확인
1244
+ const connected = await (0, database_1.isDbConnected)();
1245
+ if (!connected) {
1246
+ console.log(chalk_1.default.red(" DB 연결 실패 — ~/.claude/semo/.env를 확인하세요."));
1247
+ await (0, database_1.closeConnection)();
1248
+ process.exit(1);
2080
1249
  }
2081
- // === 6. Hooks 업데이트 ===
1250
+ // 3. DB 기반 글로벌 스킬/커맨드/에이전트 갱신
1251
+ await setupStandardGlobal();
1252
+ // 4. Hooks 업데이트
2082
1253
  await setupHooks(true);
2083
- // === 7. 설치 검증 ===
2084
- const verificationResult = verifyInstallation(cwd, []);
2085
- printVerificationResult(verificationResult);
2086
- if (verificationResult.success) {
2087
- console.log(chalk_1.default.green.bold("\n✅ SEMO 업데이트 완료!\n"));
2088
- }
2089
- else {
2090
- console.log(chalk_1.default.yellow.bold("\n⚠️ SEMO 업데이트 완료 (일부 문제 발견)\n"));
2091
- }
1254
+ await (0, database_1.closeConnection)();
1255
+ console.log(chalk_1.default.green.bold("\n✅ SEMO 업데이트 완료!\n"));
1256
+ console.log(chalk_1.default.gray(" 💡 전체 재설치가 필요하면: semo onboarding -f\n"));
2092
1257
  });
2093
- // === migrate 명령어 ===
1258
+ // === migrate 명령어 (deprecated) ===
2094
1259
  program
2095
1260
  .command("migrate")
2096
- .description("레거시 SEMO 환경을 새 구조(semo-system/)로 마이그레이션")
2097
- .option("-f, --force", "확인 없이 강제 마이그레이션")
2098
- .action(async (options) => {
2099
- console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 마이그레이션\n"));
2100
- const cwd = process.cwd();
2101
- const detection = detectLegacyEnvironment(cwd);
2102
- if (!detection.hasLegacy) {
2103
- console.log(chalk_1.default.green("✅ 레거시 환경이 감지되지 않았습니다."));
2104
- if (detection.hasSemoSystem) {
2105
- console.log(chalk_1.default.gray(" 현재 환경: semo-system/ (정상)"));
2106
- }
2107
- else {
2108
- console.log(chalk_1.default.gray(" SEMO가 설치되지 않았습니다. 'semo init'을 실행하세요."));
2109
- }
2110
- console.log();
2111
- return;
2112
- }
2113
- console.log(chalk_1.default.yellow("⚠️ 레거시 SEMO 환경이 감지되었습니다.\n"));
2114
- console.log(chalk_1.default.gray(" 감지된 레거시 경로:"));
2115
- detection.legacyPaths.forEach(p => {
2116
- console.log(chalk_1.default.gray(` - ${p}`));
2117
- });
2118
- console.log();
2119
- if (!options.force) {
2120
- const { confirm } = await inquirer_1.default.prompt([
2121
- {
2122
- type: "confirm",
2123
- name: "confirm",
2124
- message: "레거시 환경을 삭제하고 새 구조로 마이그레이션하시겠습니까?",
2125
- default: true,
2126
- },
2127
- ]);
2128
- if (!confirm) {
2129
- console.log(chalk_1.default.yellow("\n마이그레이션이 취소되었습니다.\n"));
2130
- return;
2131
- }
2132
- }
2133
- const migrationSuccess = await migrateLegacyEnvironment(cwd);
2134
- if (migrationSuccess) {
2135
- console.log(chalk_1.default.cyan("\n새 환경 설치를 위해 'semo init'을 실행하세요.\n"));
2136
- }
1261
+ .description("[deprecated] semo-system 마이그레이션은 더 이상 필요하지 않습니다")
1262
+ .action(async () => {
1263
+ console.log(chalk_1.default.yellow("\n⚠ 'semo migrate'는 더 이상 필요하지 않습니다.\n"));
1264
+ console.log(chalk_1.default.gray(" SEMO 이제 DB 기반으로 동작하며, semo-system/ 의존성이 제거되었습니다."));
1265
+ console.log(chalk_1.default.cyan("\n 전체 재설치가 필요하면:"));
1266
+ console.log(chalk_1.default.gray(" semo onboarding -f\n"));
2137
1267
  });
2138
1268
  // === config 명령어 (설치 후 설정 변경) ===
2139
1269
  const configCmd = program.command("config").description("SEMO 설정 관리");
@@ -2170,9 +1300,8 @@ program
2170
1300
  .description("SEMO 설치 상태를 진단하고 문제를 리포트")
2171
1301
  .action(async () => {
2172
1302
  console.log(chalk_1.default.cyan.bold("\n🩺 SEMO 진단\n"));
1303
+ const home = os.homedir();
2173
1304
  const cwd = process.cwd();
2174
- const semoSystemDir = path.join(cwd, "semo-system");
2175
- const claudeDir = path.join(cwd, ".claude");
2176
1305
  // 1. 레거시 환경 확인
2177
1306
  console.log(chalk_1.default.cyan("1. 레거시 환경 확인"));
2178
1307
  const legacyCheck = detectLegacyEnvironment(cwd);
@@ -2181,79 +1310,43 @@ program
2181
1310
  legacyCheck.legacyPaths.forEach(p => {
2182
1311
  console.log(chalk_1.default.gray(` - ${p}`));
2183
1312
  });
2184
- console.log(chalk_1.default.gray(" 💡 해결: semo migrate 실행"));
1313
+ console.log(chalk_1.default.gray(" 💡 레거시 폴더를 수동 삭제하세요 (semo-system/, semo-core/ 등)"));
2185
1314
  }
2186
1315
  else {
2187
1316
  console.log(chalk_1.default.green(" ✅ 레거시 환경 없음"));
2188
1317
  }
2189
- // 2. semo-system 확인
2190
- console.log(chalk_1.default.cyan("\n2. semo-system 구조 확인"));
2191
- if (!fs.existsSync(semoSystemDir)) {
2192
- console.log(chalk_1.default.red(" ❌ semo-system/ 없음"));
2193
- console.log(chalk_1.default.gray(" 💡 해결: semo init 실행"));
1318
+ // 2. DB 연결 확인
1319
+ console.log(chalk_1.default.cyan("\n2. DB 연결"));
1320
+ const connected = await (0, database_1.isDbConnected)();
1321
+ if (connected) {
1322
+ console.log(chalk_1.default.green(" DB 연결 정상"));
2194
1323
  }
2195
1324
  else {
2196
- const packages = ["semo-core", "semo-agents", "semo-scripts"];
2197
- for (const pkg of packages) {
2198
- const pkgPath = path.join(semoSystemDir, pkg);
2199
- if (fs.existsSync(pkgPath)) {
2200
- const versionPath = path.join(pkgPath, "VERSION");
2201
- const version = fs.existsSync(versionPath)
2202
- ? fs.readFileSync(versionPath, "utf-8").trim()
2203
- : "?";
2204
- console.log(chalk_1.default.green(` ✅ ${pkg} v${version}`));
2205
- }
2206
- else {
2207
- console.log(chalk_1.default.yellow(` ⚠️ ${pkg} 없음`));
2208
- }
1325
+ console.log(chalk_1.default.red(" ❌ DB 연결 실패"));
1326
+ console.log(chalk_1.default.gray(" 💡 흔한 원인:"));
1327
+ console.log(chalk_1.default.gray(" - SSH 터널 미실행 (로컬 개발 시 필수)"));
1328
+ console.log(chalk_1.default.gray(" - ~/.claude/semo/.env의 DATABASE_URL 오류"));
1329
+ console.log(chalk_1.default.gray(" 💡 해결: SSH 터널 실행 후 semo onboarding 재시도"));
1330
+ }
1331
+ // 3. 글로벌 설정 확인
1332
+ console.log(chalk_1.default.cyan("\n3. 글로벌 설정 (~/.claude/semo/)"));
1333
+ const globalChecks = [
1334
+ { name: ".env", path: path.join(home, ".claude", "semo", ".env") },
1335
+ { name: "SOUL.md", path: path.join(home, ".claude", "semo", "SOUL.md") },
1336
+ { name: "skills/", path: path.join(home, ".claude", "skills") },
1337
+ { name: "commands/", path: path.join(home, ".claude", "commands") },
1338
+ { name: "agents/", path: path.join(home, ".claude", "agents") },
1339
+ ];
1340
+ for (const check of globalChecks) {
1341
+ const exists = fs.existsSync(check.path);
1342
+ if (exists) {
1343
+ console.log(chalk_1.default.green(` ✅ ${check.name}`));
2209
1344
  }
2210
- }
2211
- // 3. 심볼릭 링크 확인
2212
- console.log(chalk_1.default.cyan("\n3. 심볼릭 링크 상태"));
2213
- if (fs.existsSync(claudeDir)) {
2214
- const linksToCheck = [
2215
- { name: "agents", dir: path.join(claudeDir, "agents") },
2216
- { name: "skills", dir: path.join(claudeDir, "skills") },
2217
- { name: "commands/SEMO", dir: path.join(claudeDir, "commands", "SEMO") },
2218
- ];
2219
- for (const { name, dir } of linksToCheck) {
2220
- if (fs.existsSync(dir)) {
2221
- if (fs.lstatSync(dir).isSymbolicLink()) {
2222
- if (isSymlinkValid(dir)) {
2223
- console.log(chalk_1.default.green(` ✅ .claude/${name} (심볼릭 링크)`));
2224
- }
2225
- else {
2226
- console.log(chalk_1.default.red(` ❌ .claude/${name} (깨진 링크)`));
2227
- console.log(chalk_1.default.gray(" 💡 해결: semo update 실행"));
2228
- }
2229
- }
2230
- else {
2231
- console.log(chalk_1.default.green(` ✅ .claude/${name} (복사본)`));
2232
- }
2233
- }
2234
- else {
2235
- console.log(chalk_1.default.yellow(` ⚠️ .claude/${name} 없음`));
2236
- }
1345
+ else {
1346
+ console.log(chalk_1.default.yellow(` ⚠️ ${check.name} 없음`));
2237
1347
  }
2238
1348
  }
2239
- else {
2240
- console.log(chalk_1.default.red(" ❌ .claude/ 디렉토리 없음"));
2241
- }
2242
- // 4. 설치 검증
2243
- console.log(chalk_1.default.cyan("\n4. 전체 설치 검증"));
2244
- const verificationResult = verifyInstallation(cwd, []);
2245
- if (verificationResult.success) {
2246
- console.log(chalk_1.default.green(" ✅ 설치 상태 정상"));
2247
- }
2248
- else {
2249
- console.log(chalk_1.default.yellow(" ⚠️ 문제 발견"));
2250
- verificationResult.errors.forEach(err => {
2251
- console.log(chalk_1.default.red(` ❌ ${err}`));
2252
- });
2253
- verificationResult.warnings.forEach(warn => {
2254
- console.log(chalk_1.default.yellow(` ⚠️ ${warn}`));
2255
- });
2256
- }
1349
+ await (0, database_1.closeConnection)();
2257
1350
  console.log();
2258
1351
  });
2259
1352
  // === KB (Knowledge Base) 관리 ===