@team-semicolon/semo-cli 3.8.1 → 3.10.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.js +384 -447
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -235,9 +235,48 @@ async function showVersionComparison(cwd) {
|
|
|
235
235
|
level: 0,
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
|
-
//
|
|
238
|
+
// 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
|
|
239
|
+
// 그룹별로 묶어서 계층 구조로 출력
|
|
239
240
|
if (hasSemoSystem) {
|
|
240
|
-
for (const
|
|
241
|
+
for (const group of PACKAGE_GROUPS) {
|
|
242
|
+
const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
|
|
243
|
+
const hasGroupVersion = fs.existsSync(groupVersionPath);
|
|
244
|
+
// 해당 그룹의 하위 패키지 찾기
|
|
245
|
+
const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
|
|
246
|
+
const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
|
|
247
|
+
// 그룹 버전이 있거나 하위 패키지가 설치된 경우에만 표시
|
|
248
|
+
if (hasGroupVersion || installedGroupExtensions.length > 0) {
|
|
249
|
+
// 그룹 패키지 버전 추가
|
|
250
|
+
if (hasGroupVersion) {
|
|
251
|
+
const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
|
|
252
|
+
const remoteGroup = await getRemotePackageVersion(group);
|
|
253
|
+
versionInfos.push({
|
|
254
|
+
name: group,
|
|
255
|
+
local: localGroup,
|
|
256
|
+
remote: remoteGroup,
|
|
257
|
+
needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
|
|
258
|
+
level: 1,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// 하위 Extension 패키지들 추가
|
|
262
|
+
for (const key of installedGroupExtensions) {
|
|
263
|
+
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
264
|
+
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
265
|
+
const remoteExt = await getRemotePackageVersion(key);
|
|
266
|
+
versionInfos.push({
|
|
267
|
+
name: key,
|
|
268
|
+
local: localExt,
|
|
269
|
+
remote: remoteExt,
|
|
270
|
+
needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
|
|
271
|
+
level: 2,
|
|
272
|
+
group: group,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// 그룹에 속하지 않는 Extension (meta 등)
|
|
278
|
+
const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
|
|
279
|
+
for (const key of nonGroupExtensions) {
|
|
241
280
|
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
242
281
|
if (fs.existsSync(extVersionPath)) {
|
|
243
282
|
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
@@ -354,10 +393,21 @@ async function showVersionComparison(cwd) {
|
|
|
354
393
|
}
|
|
355
394
|
}
|
|
356
395
|
// === Windows 지원 유틸리티 ===
|
|
357
|
-
|
|
396
|
+
// Git Bash, WSL 등에서도 Windows로 인식하도록 확장
|
|
397
|
+
const isWindows = os.platform() === "win32" ||
|
|
398
|
+
process.env.OSTYPE?.includes("msys") ||
|
|
399
|
+
process.env.OSTYPE?.includes("cygwin") ||
|
|
400
|
+
process.env.TERM_PROGRAM === "mintty";
|
|
358
401
|
/**
|
|
359
402
|
* Windows에서 Junction 링크를 생성하거나, Unix에서 심볼릭 링크를 생성
|
|
360
403
|
* Junction은 관리자 권한 없이 디렉토리 링크를 생성할 수 있음
|
|
404
|
+
*
|
|
405
|
+
* Windows 환경 (Git Bash, PowerShell, CMD 포함):
|
|
406
|
+
* 1. Junction 시도 (폴더만 가능)
|
|
407
|
+
* 2. 실패 시 xcopy로 복사
|
|
408
|
+
*
|
|
409
|
+
* Unix/Mac 환경:
|
|
410
|
+
* - 상대 경로 심볼릭 링크
|
|
361
411
|
*/
|
|
362
412
|
function createSymlinkOrJunction(targetPath, linkPath) {
|
|
363
413
|
// 이미 존재하는 링크/파일 제거 (깨진 심볼릭 링크도 처리)
|
|
@@ -376,30 +426,42 @@ function createSymlinkOrJunction(targetPath, linkPath) {
|
|
|
376
426
|
// 파일이 존재하지 않음 - 정상
|
|
377
427
|
}
|
|
378
428
|
if (isWindows) {
|
|
379
|
-
// Windows: Junction 사용 (절대
|
|
429
|
+
// Windows: Junction 사용 (절대 경로, Windows 형식 필요)
|
|
430
|
+
// path.resolve는 Git Bash에서도 Windows 경로를 반환
|
|
380
431
|
const absoluteTarget = path.resolve(targetPath);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
432
|
+
const absoluteLink = path.resolve(linkPath);
|
|
433
|
+
// 타겟이 파일인지 폴더인지 확인
|
|
434
|
+
const targetStats = fs.statSync(targetPath);
|
|
435
|
+
const isDirectory = targetStats.isDirectory();
|
|
436
|
+
if (isDirectory) {
|
|
437
|
+
// 폴더: Junction 시도
|
|
438
|
+
let success = false;
|
|
385
439
|
try {
|
|
386
|
-
|
|
440
|
+
// mklink /J 사용 (관리자 권한 불필요)
|
|
441
|
+
(0, child_process_1.execSync)(`cmd /c mklink /J "${absoluteLink}" "${absoluteTarget}"`, {
|
|
442
|
+
stdio: "pipe",
|
|
443
|
+
windowsHide: true,
|
|
444
|
+
});
|
|
387
445
|
success = true;
|
|
388
446
|
}
|
|
389
|
-
catch
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
447
|
+
catch {
|
|
448
|
+
// Junction 실패 - xcopy로 복사
|
|
449
|
+
success = false;
|
|
450
|
+
}
|
|
451
|
+
if (!success) {
|
|
452
|
+
// fallback: 디렉토리 복사
|
|
453
|
+
console.log(chalk_1.default.yellow(` ⚠ Junction 생성 실패, 복사로 대체: ${path.basename(linkPath)}`));
|
|
454
|
+
console.log(chalk_1.default.gray(` 💡 업데이트 시 semo update 명령으로 동기화하세요.`));
|
|
455
|
+
fs.mkdirSync(absoluteLink, { recursive: true });
|
|
456
|
+
(0, child_process_1.execSync)(`xcopy /E /I /Q /Y "${absoluteTarget}" "${absoluteLink}"`, {
|
|
457
|
+
stdio: "pipe",
|
|
458
|
+
windowsHide: true,
|
|
459
|
+
});
|
|
395
460
|
}
|
|
396
461
|
}
|
|
397
|
-
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
console.log(chalk_1.default.gray(` 원인: ${lastError}`));
|
|
401
|
-
console.log(chalk_1.default.gray(` 💡 관리자 권한으로 실행하거나 개발자 모드를 활성화하세요.`));
|
|
402
|
-
(0, child_process_1.execSync)(`xcopy /E /I /Q "${absoluteTarget}" "${linkPath}"`, { stdio: "pipe" });
|
|
462
|
+
else {
|
|
463
|
+
// 파일: 직접 복사 (Junction은 폴더만 지원)
|
|
464
|
+
fs.copyFileSync(absoluteTarget, absoluteLink);
|
|
403
465
|
}
|
|
404
466
|
}
|
|
405
467
|
else {
|
|
@@ -611,39 +673,92 @@ function copyRecursive(src, dest) {
|
|
|
611
673
|
}
|
|
612
674
|
}
|
|
613
675
|
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
614
|
-
//
|
|
676
|
+
// 확장 패키지 정의 (v3.0 구조)
|
|
615
677
|
const EXTENSION_PACKAGES = {
|
|
616
|
-
|
|
617
|
-
"
|
|
618
|
-
"
|
|
678
|
+
// Business Layer
|
|
679
|
+
"biz/discovery": { name: "Discovery", desc: "아이템 발굴, 시장 조사, Epic/Task", layer: "biz", detect: [] },
|
|
680
|
+
"biz/design": { name: "Design", desc: "컨셉 설계, 목업, UX", layer: "biz", detect: [] },
|
|
681
|
+
"biz/management": { name: "Management", desc: "일정/인력/스프린트 관리", layer: "biz", detect: [] },
|
|
682
|
+
"biz/poc": { name: "PoC", desc: "빠른 PoC, 패스트트랙", layer: "biz", detect: [] },
|
|
683
|
+
// Engineering Layer
|
|
684
|
+
"eng/nextjs": { name: "Next.js", desc: "Next.js 프론트엔드 개발", layer: "eng", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
|
|
685
|
+
"eng/spring": { name: "Spring", desc: "Spring Boot 백엔드 개발", layer: "eng", detect: ["pom.xml", "build.gradle"] },
|
|
686
|
+
"eng/ms": { name: "Microservice", desc: "마이크로서비스 아키텍처", layer: "eng", detect: [] },
|
|
687
|
+
"eng/infra": { name: "Infra", desc: "인프라/배포 관리", layer: "eng", detect: ["docker-compose.yml", "Dockerfile"] },
|
|
688
|
+
// Operations Layer
|
|
689
|
+
"ops/qa": { name: "QA", desc: "테스트/품질 관리", layer: "ops", detect: [] },
|
|
690
|
+
"ops/monitor": { name: "Monitor", desc: "서비스 현황 모니터링", layer: "ops", detect: [] },
|
|
691
|
+
"ops/improve": { name: "Improve", desc: "개선 제안", layer: "ops", detect: [] },
|
|
692
|
+
// Meta
|
|
693
|
+
meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리", layer: "meta", detect: ["semo-core", "semo-skills"] },
|
|
694
|
+
// System (semo-system 하위 패키지)
|
|
695
|
+
"semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템", layer: "system", detect: [] },
|
|
696
|
+
"semo-remote": { name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)", layer: "system", detect: [] },
|
|
619
697
|
};
|
|
620
698
|
// 단축명 → 전체 패키지 경로 매핑
|
|
621
699
|
const SHORTNAME_MAPPING = {
|
|
700
|
+
// 하위 패키지명 단축 (discovery → biz/discovery)
|
|
701
|
+
discovery: "biz/discovery",
|
|
702
|
+
design: "biz/design",
|
|
703
|
+
management: "biz/management",
|
|
704
|
+
poc: "biz/poc",
|
|
705
|
+
nextjs: "eng/nextjs",
|
|
706
|
+
spring: "eng/spring",
|
|
707
|
+
ms: "eng/ms",
|
|
708
|
+
infra: "eng/infra",
|
|
709
|
+
qa: "ops/qa",
|
|
710
|
+
monitor: "ops/monitor",
|
|
711
|
+
improve: "ops/improve",
|
|
712
|
+
// 추가 별칭
|
|
713
|
+
next: "eng/nextjs",
|
|
714
|
+
backend: "eng/spring",
|
|
715
|
+
mvp: "biz/poc",
|
|
716
|
+
// System 패키지 단축명
|
|
622
717
|
hooks: "semo-hooks",
|
|
623
718
|
remote: "semo-remote",
|
|
624
719
|
};
|
|
625
|
-
//
|
|
720
|
+
// 그룹 이름 목록 (biz, eng, ops, meta, system)
|
|
721
|
+
const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
|
|
722
|
+
// 그룹명 → 해당 그룹의 모든 패키지 반환
|
|
723
|
+
function getPackagesByGroup(group) {
|
|
724
|
+
return Object.entries(EXTENSION_PACKAGES)
|
|
725
|
+
.filter(([, pkg]) => pkg.layer === group)
|
|
726
|
+
.map(([key]) => key);
|
|
727
|
+
}
|
|
728
|
+
// 패키지 입력을 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
626
729
|
function resolvePackageInput(input) {
|
|
627
730
|
// 쉼표로 구분된 여러 패키지 처리
|
|
628
731
|
const parts = input.split(",").map(p => p.trim()).filter(p => p);
|
|
629
732
|
const resolvedPackages = [];
|
|
733
|
+
let isGroup = false;
|
|
734
|
+
let groupName;
|
|
630
735
|
for (const part of parts) {
|
|
631
|
-
// 1.
|
|
736
|
+
// 1. 그룹명인지 확인 (biz, eng, ops, meta)
|
|
737
|
+
if (PACKAGE_GROUPS.includes(part)) {
|
|
738
|
+
const groupPackages = getPackagesByGroup(part);
|
|
739
|
+
resolvedPackages.push(...groupPackages);
|
|
740
|
+
isGroup = true;
|
|
741
|
+
groupName = part;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
// 2. 단축명 매핑 확인 (discovery → biz/discovery 등)
|
|
632
745
|
if (part in SHORTNAME_MAPPING) {
|
|
633
746
|
resolvedPackages.push(SHORTNAME_MAPPING[part]);
|
|
634
747
|
continue;
|
|
635
748
|
}
|
|
636
|
-
//
|
|
749
|
+
// 3. 직접 패키지명 확인
|
|
637
750
|
if (part in EXTENSION_PACKAGES) {
|
|
638
751
|
resolvedPackages.push(part);
|
|
639
752
|
continue;
|
|
640
753
|
}
|
|
641
|
-
//
|
|
754
|
+
// 4. 유효하지 않은 패키지명
|
|
642
755
|
// (빈 배열 대신 null을 추가하여 나중에 에러 처리)
|
|
643
756
|
}
|
|
644
757
|
// 중복 제거
|
|
645
758
|
return {
|
|
646
759
|
packages: [...new Set(resolvedPackages)],
|
|
760
|
+
isGroup,
|
|
761
|
+
groupName
|
|
647
762
|
};
|
|
648
763
|
}
|
|
649
764
|
const program = new commander_1.Command();
|
|
@@ -704,10 +819,47 @@ async function showVersionInfo() {
|
|
|
704
819
|
level: 0,
|
|
705
820
|
});
|
|
706
821
|
}
|
|
707
|
-
// 4.
|
|
822
|
+
// 4. 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
|
|
708
823
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
709
824
|
if (fs.existsSync(semoSystemDir)) {
|
|
710
|
-
for (const
|
|
825
|
+
for (const group of PACKAGE_GROUPS) {
|
|
826
|
+
const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
|
|
827
|
+
const hasGroupVersion = fs.existsSync(groupVersionPath);
|
|
828
|
+
// 해당 그룹의 하위 패키지 찾기
|
|
829
|
+
const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
|
|
830
|
+
const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
|
|
831
|
+
if (hasGroupVersion || installedGroupExtensions.length > 0) {
|
|
832
|
+
// 그룹 패키지 버전 추가
|
|
833
|
+
if (hasGroupVersion) {
|
|
834
|
+
const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
|
|
835
|
+
const remoteGroup = await getRemotePackageVersion(group);
|
|
836
|
+
versionInfos.push({
|
|
837
|
+
name: group,
|
|
838
|
+
local: localGroup,
|
|
839
|
+
remote: remoteGroup,
|
|
840
|
+
needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
|
|
841
|
+
level: 1,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
// 하위 Extension 패키지들 추가
|
|
845
|
+
for (const key of installedGroupExtensions) {
|
|
846
|
+
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
847
|
+
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
848
|
+
const remoteExt = await getRemotePackageVersion(key);
|
|
849
|
+
versionInfos.push({
|
|
850
|
+
name: key,
|
|
851
|
+
local: localExt,
|
|
852
|
+
remote: remoteExt,
|
|
853
|
+
needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
|
|
854
|
+
level: 2,
|
|
855
|
+
group: group,
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
// 그룹에 속하지 않는 Extension (meta 등)
|
|
861
|
+
const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
|
|
862
|
+
for (const key of nonGroupExtensions) {
|
|
711
863
|
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
712
864
|
if (fs.existsSync(extVersionPath)) {
|
|
713
865
|
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
@@ -866,6 +1018,18 @@ async function confirmOverwrite(itemName, itemPath) {
|
|
|
866
1018
|
]);
|
|
867
1019
|
return shouldOverwrite;
|
|
868
1020
|
}
|
|
1021
|
+
function detectProjectType(cwd) {
|
|
1022
|
+
const detected = [];
|
|
1023
|
+
for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
|
|
1024
|
+
for (const file of pkg.detect) {
|
|
1025
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
1026
|
+
detected.push(key);
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return detected;
|
|
1032
|
+
}
|
|
869
1033
|
// === 설치된 Extension 패키지 스캔 ===
|
|
870
1034
|
function getInstalledExtensions(cwd) {
|
|
871
1035
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
@@ -995,17 +1159,23 @@ program
|
|
|
995
1159
|
spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
|
|
996
1160
|
process.exit(1);
|
|
997
1161
|
}
|
|
998
|
-
// 2. Extension 패키지 처리
|
|
999
|
-
|
|
1000
|
-
let extensionsToInstall = ["semo-hooks"];
|
|
1162
|
+
// 2. Extension 패키지 처리 (--with 옵션만 지원, 인터랙션 없음)
|
|
1163
|
+
let extensionsToInstall = [];
|
|
1001
1164
|
if (options.with) {
|
|
1002
|
-
|
|
1003
|
-
extensionsToInstall =
|
|
1165
|
+
// --with 옵션으로 명시적 패키지 지정 시에만 Extension 설치
|
|
1166
|
+
extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES || p in SHORTNAME_MAPPING);
|
|
1167
|
+
// 별칭 처리
|
|
1168
|
+
extensionsToInstall = extensionsToInstall.map((p) => SHORTNAME_MAPPING[p] || p);
|
|
1169
|
+
}
|
|
1170
|
+
// 프로젝트 유형 감지는 정보 제공용으로만 사용 (자동 설치 안 함)
|
|
1171
|
+
const detected = detectProjectType(cwd);
|
|
1172
|
+
if (detected.length > 0 && !options.with) {
|
|
1173
|
+
console.log(chalk_1.default.cyan("\n💡 감지된 프로젝트 유형:"));
|
|
1174
|
+
detected.forEach(pkg => {
|
|
1175
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
1176
|
+
});
|
|
1177
|
+
console.log(chalk_1.default.gray(`\n 추가 패키지가 필요하면: semo add ${detected[0].split("/")[1] || detected[0]}`));
|
|
1004
1178
|
}
|
|
1005
|
-
console.log(chalk_1.default.cyan("\n📦 Extension 설치:"));
|
|
1006
|
-
extensionsToInstall.forEach(pkg => {
|
|
1007
|
-
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
1008
|
-
});
|
|
1009
1179
|
// 3. .claude 디렉토리 생성
|
|
1010
1180
|
const claudeDir = path.join(cwd, ".claude");
|
|
1011
1181
|
if (!fs.existsSync(claudeDir)) {
|
|
@@ -1058,23 +1228,12 @@ program
|
|
|
1058
1228
|
console.log(chalk_1.default.gray(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
|
|
1059
1229
|
});
|
|
1060
1230
|
}
|
|
1061
|
-
// GitHub MCP 토큰 설정 안내
|
|
1062
|
-
if (!isGitHubTokenConfigured()) {
|
|
1063
|
-
console.log(chalk_1.default.yellow("\n⚠️ GitHub MCP 설정 필요"));
|
|
1064
|
-
console.log(chalk_1.default.gray(" GitHub 이슈/PR 생성 기능을 사용하려면 토큰을 설정하세요:"));
|
|
1065
|
-
console.log(chalk_1.default.white("\n export GITHUB_PERSONAL_ACCESS_TOKEN=\"$(gh auth token)\""));
|
|
1066
|
-
console.log(chalk_1.default.gray("\n 영구 설정: ~/.zshrc 또는 ~/.bashrc에 위 명령어 추가"));
|
|
1067
|
-
}
|
|
1068
|
-
else {
|
|
1069
|
-
console.log(chalk_1.default.green("\n✓ GitHub 토큰 감지됨 (MCP 사용 가능)"));
|
|
1070
|
-
}
|
|
1071
1231
|
console.log(chalk_1.default.cyan("\n다음 단계:"));
|
|
1072
1232
|
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
1073
1233
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
1074
1234
|
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add meta)"));
|
|
1235
|
+
if (extensionsToInstall.length === 0) {
|
|
1236
|
+
console.log(chalk_1.default.gray("\n💡 추가 패키지가 필요하면: semo add <package> (예: semo add next)"));
|
|
1078
1237
|
}
|
|
1079
1238
|
console.log();
|
|
1080
1239
|
});
|
|
@@ -1163,42 +1322,24 @@ async function createStandardSymlinks(cwd) {
|
|
|
1163
1322
|
}
|
|
1164
1323
|
console.log(chalk_1.default.green(` ✓ .claude/skills/ (${skills.length}개 skill 링크됨)`));
|
|
1165
1324
|
}
|
|
1166
|
-
// commands 링크
|
|
1325
|
+
// commands 링크
|
|
1167
1326
|
const commandsDir = path.join(claudeDir, "commands");
|
|
1168
1327
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1328
|
+
const semoCommandsLink = path.join(commandsDir, "SEMO");
|
|
1329
|
+
const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
|
|
1330
|
+
// 기존 링크가 있으면 삭제 후 재생성 (업데이트 시에도 최신 반영)
|
|
1331
|
+
if (fs.existsSync(semoCommandsLink)) {
|
|
1332
|
+
if (fs.lstatSync(semoCommandsLink).isSymbolicLink()) {
|
|
1333
|
+
fs.unlinkSync(semoCommandsLink);
|
|
1174
1334
|
}
|
|
1175
1335
|
else {
|
|
1176
|
-
removeRecursive(
|
|
1336
|
+
removeRecursive(semoCommandsLink);
|
|
1177
1337
|
}
|
|
1178
1338
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
const commandSources = [
|
|
1183
|
-
{ name: "semo-core", dir: path.join(semoSystemDir, "semo-core", "commands", "SEMO") },
|
|
1184
|
-
{ name: "semo-remote", dir: path.join(semoSystemDir, "semo-remote", "commands", "SEMO") },
|
|
1185
|
-
];
|
|
1186
|
-
let totalCommands = 0;
|
|
1187
|
-
for (const source of commandSources) {
|
|
1188
|
-
if (fs.existsSync(source.dir)) {
|
|
1189
|
-
const commandFiles = fs.readdirSync(source.dir).filter(f => f.endsWith(".md"));
|
|
1190
|
-
for (const cmdFile of commandFiles) {
|
|
1191
|
-
const cmdTarget = path.join(source.dir, cmdFile);
|
|
1192
|
-
const cmdLink = path.join(semoCommandsDir, cmdFile);
|
|
1193
|
-
// 이미 링크가 있으면 건너뛰기 (semo-core 우선)
|
|
1194
|
-
if (!fs.existsSync(cmdLink)) {
|
|
1195
|
-
createSymlinkOrJunction(cmdTarget, cmdLink);
|
|
1196
|
-
totalCommands++;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1339
|
+
if (fs.existsSync(commandsTarget)) {
|
|
1340
|
+
createSymlinkOrJunction(commandsTarget, semoCommandsLink);
|
|
1341
|
+
console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
|
|
1200
1342
|
}
|
|
1201
|
-
console.log(chalk_1.default.green(` ✓ .claude/commands/SEMO (${totalCommands}개 command 링크됨)`));
|
|
1202
1343
|
}
|
|
1203
1344
|
/**
|
|
1204
1345
|
* 설치 상태를 검증하고 문제점을 리포트
|
|
@@ -1279,26 +1420,15 @@ function verifyInstallation(cwd, installedExtensions = []) {
|
|
|
1279
1420
|
}
|
|
1280
1421
|
}
|
|
1281
1422
|
}
|
|
1282
|
-
// 4. commands 검증 (
|
|
1283
|
-
const
|
|
1423
|
+
// 4. commands 검증 (isSymlinkValid 사용)
|
|
1424
|
+
const semoCommandsLink = path.join(claudeDir, "commands", "SEMO");
|
|
1284
1425
|
try {
|
|
1285
|
-
const
|
|
1286
|
-
result.stats.commands.exists =
|
|
1287
|
-
if (
|
|
1288
|
-
|
|
1289
|
-
if (
|
|
1290
|
-
|
|
1291
|
-
result.stats.commands.valid = cmdFiles.length > 0;
|
|
1292
|
-
if (!result.stats.commands.valid) {
|
|
1293
|
-
result.warnings.push("commands/SEMO 디렉토리가 비어 있습니다");
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
else if (fs.lstatSync(semoCommandsDir).isSymbolicLink()) {
|
|
1297
|
-
// 심볼릭 링크인 경우 (구버전 호환)
|
|
1298
|
-
result.stats.commands.valid = isSymlinkValid(semoCommandsDir);
|
|
1299
|
-
if (!result.stats.commands.valid) {
|
|
1300
|
-
result.warnings.push("깨진 링크: .claude/commands/SEMO");
|
|
1301
|
-
}
|
|
1426
|
+
const linkExists = fs.existsSync(semoCommandsLink) || fs.lstatSync(semoCommandsLink).isSymbolicLink();
|
|
1427
|
+
result.stats.commands.exists = linkExists;
|
|
1428
|
+
if (linkExists) {
|
|
1429
|
+
result.stats.commands.valid = isSymlinkValid(semoCommandsLink);
|
|
1430
|
+
if (!result.stats.commands.valid) {
|
|
1431
|
+
result.warnings.push("깨진 링크: .claude/commands/SEMO");
|
|
1302
1432
|
}
|
|
1303
1433
|
}
|
|
1304
1434
|
}
|
|
@@ -1451,8 +1581,7 @@ async function downloadExtensions(cwd, packages, force) {
|
|
|
1451
1581
|
}
|
|
1452
1582
|
// 개별 패키지 복사
|
|
1453
1583
|
for (const pkg of packages) {
|
|
1454
|
-
|
|
1455
|
-
const srcPath = path.join(tempDir, "semo-system", pkg);
|
|
1584
|
+
const srcPath = path.join(tempDir, "packages", pkg);
|
|
1456
1585
|
const destPath = path.join(semoSystemDir, pkg);
|
|
1457
1586
|
if (fs.existsSync(srcPath)) {
|
|
1458
1587
|
if (fs.existsSync(destPath) && !force) {
|
|
@@ -1528,26 +1657,6 @@ function createMergedOrchestrator(claudeAgentsDir, orchestratorSources) {
|
|
|
1528
1657
|
}
|
|
1529
1658
|
}
|
|
1530
1659
|
}
|
|
1531
|
-
// meta 패키지 포함 여부 확인
|
|
1532
|
-
const hasMetaInSources = orchestratorSources.some(s => s.pkg === "meta");
|
|
1533
|
-
// Meta 자동 체이닝 섹션
|
|
1534
|
-
const metaAutoChainSection = hasMetaInSources ? `
|
|
1535
|
-
## 🔴 Meta 환경 자동 체이닝 (NON-NEGOTIABLE)
|
|
1536
|
-
|
|
1537
|
-
> **조건**: semo-system/ 내 파일 수정이 감지되면
|
|
1538
|
-
> **동작**: 작업 종료 전 자동으로 \`skill:meta-workflow\` 호출
|
|
1539
|
-
|
|
1540
|
-
\`\`\`text
|
|
1541
|
-
semo-system/ 파일 수정 감지
|
|
1542
|
-
↓
|
|
1543
|
-
[자동] skill:meta-workflow 호출
|
|
1544
|
-
↓
|
|
1545
|
-
버저닝 → 배포 → 로컬 동기화
|
|
1546
|
-
\`\`\`
|
|
1547
|
-
|
|
1548
|
-
**이 규칙은 우회할 수 없습니다.**
|
|
1549
|
-
|
|
1550
|
-
` : "";
|
|
1551
1660
|
// 병합된 orchestrator.md 생성
|
|
1552
1661
|
const mergedContent = `---
|
|
1553
1662
|
name: orchestrator
|
|
@@ -1616,7 +1725,7 @@ ${routingTables.join("\n\n---\n\n")}
|
|
|
1616
1725
|
3. **Package Priority**: 라우팅 충돌 시 설치 순서대로 우선순위 적용
|
|
1617
1726
|
4. **Cross-Package**: 다른 패키지 전문 영역 요청 시 인계 권유
|
|
1618
1727
|
|
|
1619
|
-
${
|
|
1728
|
+
${crossPackageRouting.length > 0 ? `## 🔄 Cross-Package Routing
|
|
1620
1729
|
|
|
1621
1730
|
${crossPackageRouting[0]}` : ""}
|
|
1622
1731
|
|
|
@@ -1735,29 +1844,8 @@ const BASE_MCP_SERVERS = [
|
|
|
1735
1844
|
name: "github",
|
|
1736
1845
|
command: "npx",
|
|
1737
1846
|
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1738
|
-
env: {
|
|
1739
|
-
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}",
|
|
1740
|
-
},
|
|
1741
1847
|
},
|
|
1742
1848
|
];
|
|
1743
|
-
// === GitHub 토큰 자동 감지 ===
|
|
1744
|
-
function getGitHubTokenFromCLI() {
|
|
1745
|
-
try {
|
|
1746
|
-
const token = (0, child_process_1.execSync)("gh auth token", { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
1747
|
-
return token || null;
|
|
1748
|
-
}
|
|
1749
|
-
catch {
|
|
1750
|
-
return null;
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
function isGitHubTokenConfigured() {
|
|
1754
|
-
// 환경변수 확인
|
|
1755
|
-
if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
|
|
1756
|
-
return true;
|
|
1757
|
-
}
|
|
1758
|
-
// gh CLI 토큰 확인
|
|
1759
|
-
return getGitHubTokenFromCLI() !== null;
|
|
1760
|
-
}
|
|
1761
1849
|
// === Claude MCP 서버 존재 여부 확인 ===
|
|
1762
1850
|
function isMCPServerRegistered(serverName) {
|
|
1763
1851
|
try {
|
|
@@ -2395,12 +2483,34 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2395
2483
|
const extensionsList = extensions.length > 0
|
|
2396
2484
|
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
2397
2485
|
: "";
|
|
2398
|
-
// 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2486
|
+
// 그룹 및 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2399
2487
|
let packageClaudeMdSections = "";
|
|
2400
|
-
// meta
|
|
2401
|
-
const
|
|
2402
|
-
|
|
2488
|
+
// 0. meta 패키지가 설치된 경우 먼저 확인 (meta는 특별 처리)
|
|
2489
|
+
const isMetaInstalled = extensions.includes("meta") ||
|
|
2490
|
+
fs.existsSync(path.join(semoSystemDir, "meta", "VERSION"));
|
|
2491
|
+
// 1. 설치된 패키지에서 그룹 추출 (중복 제거)
|
|
2492
|
+
const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
|
|
2493
|
+
// 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
|
|
2494
|
+
for (const group of installedGroups) {
|
|
2495
|
+
const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
|
|
2496
|
+
if (fs.existsSync(groupClaudeMdPath)) {
|
|
2497
|
+
const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
|
|
2498
|
+
// 중복 제거 후 고유 콘텐츠만 추출
|
|
2499
|
+
const uniqueContent = extractUniqueContent(groupContent, group);
|
|
2500
|
+
// 헤더 레벨 조정 (# → ##, ## → ###)
|
|
2501
|
+
const adjustedContent = uniqueContent
|
|
2502
|
+
.replace(/^# /gm, "## ")
|
|
2503
|
+
.replace(/^## /gm, "### ")
|
|
2504
|
+
.replace(/^### /gm, "#### ");
|
|
2505
|
+
packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
|
|
2506
|
+
console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
// 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
|
|
2403
2510
|
for (const pkg of extensions) {
|
|
2511
|
+
// meta 패키지는 별도 처리 (아래에서 전체 내용 병합)
|
|
2512
|
+
if (pkg === "meta")
|
|
2513
|
+
continue;
|
|
2404
2514
|
const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
|
|
2405
2515
|
if (fs.existsSync(pkgClaudeMdPath)) {
|
|
2406
2516
|
const pkgContent = fs.readFileSync(pkgClaudeMdPath, "utf-8");
|
|
@@ -2415,22 +2525,48 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2415
2525
|
console.log(chalk_1.default.gray(` + ${pkg}/CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2416
2526
|
}
|
|
2417
2527
|
}
|
|
2418
|
-
//
|
|
2528
|
+
// 3.5. meta 패키지 CLAUDE.md 병합 (전체 내용 - Meta 환경 규칙 포함)
|
|
2529
|
+
if (isMetaInstalled) {
|
|
2530
|
+
const metaClaudeMdPath = path.join(semoSystemDir, "meta", "CLAUDE.md");
|
|
2531
|
+
if (fs.existsSync(metaClaudeMdPath)) {
|
|
2532
|
+
const metaContent = fs.readFileSync(metaClaudeMdPath, "utf-8");
|
|
2533
|
+
const pkgName = EXTENSION_PACKAGES["meta"]?.name || "Meta";
|
|
2534
|
+
// meta는 중복 제거 없이 전체 내용 유지 (Core Rules가 중요)
|
|
2535
|
+
// 헤더 레벨만 조정 (# → ###, ## → ####)
|
|
2536
|
+
const adjustedContent = metaContent
|
|
2537
|
+
.replace(/^# /gm, "### ")
|
|
2538
|
+
.replace(/^## /gm, "#### ");
|
|
2539
|
+
packageClaudeMdSections += `\n\n---\n\n## ${pkgName} 패키지 컨텍스트\n\n${adjustedContent}`;
|
|
2540
|
+
console.log(chalk_1.default.green(` + meta/CLAUDE.md 병합됨 (전체 내용 - Meta 환경 규칙 포함)`));
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
// 4. Orchestrator 참조 경로 결정 (Extension 패키지 우선, meta 포함)
|
|
2419
2544
|
// Extension 패키지 중 orchestrator가 있는 첫 번째 패키지를 Primary로 설정
|
|
2420
2545
|
let primaryOrchestratorPath = "semo-core/agents/orchestrator/orchestrator.md";
|
|
2421
2546
|
const orchestratorPaths = [];
|
|
2547
|
+
// meta 패키지 orchestrator 먼저 확인 (meta가 설치되어 있으면 최우선)
|
|
2548
|
+
if (isMetaInstalled) {
|
|
2549
|
+
const metaOrchestratorPath = path.join(semoSystemDir, "meta", "agents/orchestrator/orchestrator.md");
|
|
2550
|
+
if (fs.existsSync(metaOrchestratorPath)) {
|
|
2551
|
+
orchestratorPaths.push("semo-system/meta/agents/orchestrator/orchestrator.md");
|
|
2552
|
+
primaryOrchestratorPath = "meta/agents/orchestrator/orchestrator.md";
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
// 나머지 Extension 패키지 orchestrator 확인
|
|
2422
2556
|
for (const pkg of extensions) {
|
|
2557
|
+
if (pkg === "meta")
|
|
2558
|
+
continue; // meta는 위에서 이미 처리
|
|
2423
2559
|
const pkgOrchestratorPath = path.join(semoSystemDir, pkg, "agents/orchestrator/orchestrator.md");
|
|
2424
2560
|
if (fs.existsSync(pkgOrchestratorPath)) {
|
|
2425
2561
|
orchestratorPaths.push(`semo-system/${pkg}/agents/orchestrator/orchestrator.md`);
|
|
2426
|
-
//
|
|
2562
|
+
// Primary가 아직 semo-core이면 이 패키지를 Primary로 설정
|
|
2427
2563
|
if (primaryOrchestratorPath === "semo-core/agents/orchestrator/orchestrator.md") {
|
|
2428
2564
|
primaryOrchestratorPath = `${pkg}/agents/orchestrator/orchestrator.md`;
|
|
2429
2565
|
}
|
|
2430
2566
|
}
|
|
2431
2567
|
}
|
|
2432
|
-
// semo-core orchestrator는 항상 포함
|
|
2433
|
-
orchestratorPaths.
|
|
2568
|
+
// semo-core orchestrator는 항상 마지막에 포함 (fallback)
|
|
2569
|
+
orchestratorPaths.push("semo-system/semo-core/agents/orchestrator/orchestrator.md");
|
|
2434
2570
|
// Orchestrator 참조 섹션 생성
|
|
2435
2571
|
const orchestratorRefSection = orchestratorPaths.length > 1
|
|
2436
2572
|
? `**Primary Orchestrator**: \`semo-system/${primaryOrchestratorPath}\`
|
|
@@ -2444,47 +2580,6 @@ ${orchestratorPaths.map(p => `- \`${p}\``).join("\n")}
|
|
|
2444
2580
|
: `**반드시 읽어야 할 파일**: \`semo-system/semo-core/agents/orchestrator/orchestrator.md\`
|
|
2445
2581
|
|
|
2446
2582
|
이 파일에서 라우팅 테이블, 의도 분류, 메시지 포맷을 확인하세요.`;
|
|
2447
|
-
// Meta 패키지 설치 시 자동 워크플로우 강제 규칙
|
|
2448
|
-
const metaWorkflowSection = hasMetaPackage ? `
|
|
2449
|
-
### 3. Meta 환경 자동 워크플로우 (NON-NEGOTIABLE)
|
|
2450
|
-
|
|
2451
|
-
> **⚠️ Meta 패키지가 설치된 환경에서는 반드시 아래 규칙이 적용됩니다.**
|
|
2452
|
-
> **이 규칙을 우회하거나 무시하는 것은 금지됩니다.**
|
|
2453
|
-
|
|
2454
|
-
#### 자동 트리거 조건
|
|
2455
|
-
|
|
2456
|
-
\`semo-system/\` 디렉토리 내 파일이 수정되면:
|
|
2457
|
-
1. 작업 종료 전 반드시 \`skill:meta-workflow\` 호출
|
|
2458
|
-
2. 버저닝 → 배포 → 로컬 동기화 체인 자동 실행
|
|
2459
|
-
|
|
2460
|
-
#### 감지 패턴
|
|
2461
|
-
|
|
2462
|
-
다음 경로의 파일 수정 시 자동 트리거:
|
|
2463
|
-
- \`semo-system/semo-core/**\`
|
|
2464
|
-
- \`semo-system/semo-skills/**\`
|
|
2465
|
-
- \`semo-system/meta/**\`
|
|
2466
|
-
- \`semo-system/semo-remote/**\`
|
|
2467
|
-
- \`semo-system/semo-hooks/**\`
|
|
2468
|
-
- \`packages/cli/**\` (CLI 수정 시)
|
|
2469
|
-
|
|
2470
|
-
#### 강제 동작 흐름
|
|
2471
|
-
|
|
2472
|
-
\`\`\`text
|
|
2473
|
-
[작업 완료 감지]
|
|
2474
|
-
↓
|
|
2475
|
-
semo-system/ 또는 packages/ 파일 수정 여부 확인
|
|
2476
|
-
↓
|
|
2477
|
-
수정됨? → [SEMO] Skill 호출: meta-workflow
|
|
2478
|
-
버저닝 → 배포 → 동기화 자동 실행
|
|
2479
|
-
↓
|
|
2480
|
-
수정 안됨? → 정상 종료
|
|
2481
|
-
\`\`\`
|
|
2482
|
-
|
|
2483
|
-
**금지 사항**:
|
|
2484
|
-
- semo-system/ 수정 후 버저닝 없이 종료
|
|
2485
|
-
- "버저닝 나중에 해줘" 요청 수락
|
|
2486
|
-
- meta-workflow 스킬 호출 건너뛰기
|
|
2487
|
-
` : "";
|
|
2488
2583
|
const claudeMdContent = `# SEMO Project Configuration
|
|
2489
2584
|
|
|
2490
2585
|
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
|
|
@@ -2538,7 +2633,46 @@ npm run build # 3. 빌드 검증 (Next.js/TypeScript 프로젝트)
|
|
|
2538
2633
|
- \`--no-verify\` 플래그 사용 금지
|
|
2539
2634
|
- Quality Gate 우회 시도 거부
|
|
2540
2635
|
- "그냥 커밋해줘", "빌드 생략해줘" 등 거부
|
|
2541
|
-
${
|
|
2636
|
+
${isMetaInstalled ? `
|
|
2637
|
+
### 3. Meta 환경 자동 워크플로우 (NON-NEGOTIABLE)
|
|
2638
|
+
|
|
2639
|
+
> **⚠️ Meta 패키지가 설치된 환경에서는 반드시 아래 규칙이 적용됩니다.**
|
|
2640
|
+
> **이 규칙을 우회하거나 무시하는 것은 금지됩니다.**
|
|
2641
|
+
|
|
2642
|
+
#### 자동 트리거 조건
|
|
2643
|
+
|
|
2644
|
+
\`semo-system/\` 디렉토리 내 파일이 수정되면:
|
|
2645
|
+
1. 작업 종료 전 반드시 \`skill:meta-workflow\` 호출
|
|
2646
|
+
2. 버저닝 → 배포 → 로컬 동기화 체인 자동 실행
|
|
2647
|
+
|
|
2648
|
+
#### 감지 패턴
|
|
2649
|
+
|
|
2650
|
+
다음 경로의 파일 수정 시 자동 트리거:
|
|
2651
|
+
- \`semo-system/semo-core/**\`
|
|
2652
|
+
- \`semo-system/semo-skills/**\`
|
|
2653
|
+
- \`semo-system/meta/**\`
|
|
2654
|
+
- \`semo-system/semo-remote/**\`
|
|
2655
|
+
- \`semo-system/semo-hooks/**\`
|
|
2656
|
+
- \`packages/cli/**\` (CLI 수정 시)
|
|
2657
|
+
|
|
2658
|
+
#### 강제 동작 흐름
|
|
2659
|
+
|
|
2660
|
+
\`\`\`text
|
|
2661
|
+
[작업 완료 감지]
|
|
2662
|
+
↓
|
|
2663
|
+
semo-system/ 또는 packages/ 파일 수정 여부 확인
|
|
2664
|
+
↓
|
|
2665
|
+
수정됨? → [SEMO] Skill 호출: meta-workflow
|
|
2666
|
+
버저닝 → 배포 → 동기화 자동 실행
|
|
2667
|
+
↓
|
|
2668
|
+
수정 안됨? → 정상 종료
|
|
2669
|
+
\`\`\`
|
|
2670
|
+
|
|
2671
|
+
**금지 사항**:
|
|
2672
|
+
- semo-system/ 수정 후 버저닝 없이 종료
|
|
2673
|
+
- "버저닝 나중에 해줘" 요청 수락
|
|
2674
|
+
- meta-workflow 스킬 호출 건너뛰기
|
|
2675
|
+
` : ``}
|
|
2542
2676
|
---
|
|
2543
2677
|
|
|
2544
2678
|
## 설치된 구성
|
|
@@ -2607,7 +2741,7 @@ ${packageClaudeMdSections}
|
|
|
2607
2741
|
// === add 명령어 ===
|
|
2608
2742
|
program
|
|
2609
2743
|
.command("add <packages>")
|
|
2610
|
-
.description("Extension 패키지를 추가로 설치합니다 (
|
|
2744
|
+
.description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops, system / 개별: biz/discovery, eng/nextjs, semo-hooks)")
|
|
2611
2745
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
2612
2746
|
.action(async (packagesInput, options) => {
|
|
2613
2747
|
const cwd = process.cwd();
|
|
@@ -2616,16 +2750,25 @@ program
|
|
|
2616
2750
|
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
2617
2751
|
process.exit(1);
|
|
2618
2752
|
}
|
|
2619
|
-
// 패키지 입력 해석
|
|
2620
|
-
const { packages } = resolvePackageInput(packagesInput);
|
|
2753
|
+
// 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
2754
|
+
const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
|
|
2621
2755
|
if (packages.length === 0) {
|
|
2622
2756
|
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
|
|
2757
|
+
console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
|
|
2623
2758
|
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
|
|
2624
2759
|
console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
|
|
2625
2760
|
process.exit(1);
|
|
2626
2761
|
}
|
|
2627
|
-
//
|
|
2628
|
-
if (
|
|
2762
|
+
// 그룹 설치인 경우 안내
|
|
2763
|
+
if (isGroup) {
|
|
2764
|
+
console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
|
|
2765
|
+
console.log(chalk_1.default.gray(" 포함된 패키지:"));
|
|
2766
|
+
for (const pkg of packages) {
|
|
2767
|
+
console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
|
|
2768
|
+
}
|
|
2769
|
+
console.log();
|
|
2770
|
+
}
|
|
2771
|
+
else if (packages.length === 1) {
|
|
2629
2772
|
// 단일 패키지
|
|
2630
2773
|
const pkg = packages[0];
|
|
2631
2774
|
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
|
|
@@ -2691,28 +2834,50 @@ program
|
|
|
2691
2834
|
.action(() => {
|
|
2692
2835
|
const cwd = process.cwd();
|
|
2693
2836
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
2694
|
-
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지
|
|
2695
|
-
// Standard
|
|
2837
|
+
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.0)\n"));
|
|
2838
|
+
// Standard
|
|
2696
2839
|
console.log(chalk_1.default.white.bold("Standard (필수)"));
|
|
2697
2840
|
const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
2698
2841
|
const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
|
|
2699
2842
|
console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
|
|
2700
2843
|
console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
|
|
2701
2844
|
console.log();
|
|
2702
|
-
// Extensions
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
{
|
|
2706
|
-
{
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
const
|
|
2712
|
-
|
|
2713
|
-
|
|
2845
|
+
// Extensions - 레이어별 그룹화
|
|
2846
|
+
const layers = {
|
|
2847
|
+
biz: { title: "Business Layer", emoji: "💼" },
|
|
2848
|
+
eng: { title: "Engineering Layer", emoji: "⚙️" },
|
|
2849
|
+
ops: { title: "Operations Layer", emoji: "📊" },
|
|
2850
|
+
meta: { title: "Meta", emoji: "🔧" },
|
|
2851
|
+
system: { title: "System", emoji: "🔩" },
|
|
2852
|
+
};
|
|
2853
|
+
for (const [layerKey, layerInfo] of Object.entries(layers)) {
|
|
2854
|
+
const layerPackages = Object.entries(EXTENSION_PACKAGES).filter(([, pkg]) => pkg.layer === layerKey);
|
|
2855
|
+
if (layerPackages.length === 0)
|
|
2856
|
+
continue;
|
|
2857
|
+
console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
|
|
2858
|
+
for (const [key, pkg] of layerPackages) {
|
|
2859
|
+
const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
|
|
2860
|
+
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
2861
|
+
const displayKey = key.includes("/") ? key.split("/")[1] : key;
|
|
2862
|
+
console.log(` ${status} ${chalk_1.default.cyan(displayKey)} - ${pkg.desc}`);
|
|
2863
|
+
console.log(chalk_1.default.gray(` semo add ${key}`));
|
|
2864
|
+
}
|
|
2865
|
+
console.log();
|
|
2714
2866
|
}
|
|
2867
|
+
// 그룹 설치 안내
|
|
2868
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2869
|
+
console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
|
|
2870
|
+
console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
|
|
2871
|
+
console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
|
|
2872
|
+
console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
|
|
2873
|
+
console.log(chalk_1.default.gray(" semo add system → System 전체 (hooks, remote)"));
|
|
2715
2874
|
console.log();
|
|
2875
|
+
// 단축명 안내
|
|
2876
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2877
|
+
console.log(chalk_1.default.white.bold("⚡ 단축명 지원"));
|
|
2878
|
+
console.log(chalk_1.default.gray(" semo add discovery → biz/discovery"));
|
|
2879
|
+
console.log(chalk_1.default.gray(" semo add qa → ops/qa"));
|
|
2880
|
+
console.log(chalk_1.default.gray(" semo add nextjs → eng/nextjs\n"));
|
|
2716
2881
|
});
|
|
2717
2882
|
// === status 명령어 ===
|
|
2718
2883
|
program
|
|
@@ -2997,71 +3162,9 @@ program
|
|
|
2997
3162
|
}
|
|
2998
3163
|
}
|
|
2999
3164
|
}
|
|
3000
|
-
// === 6.
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
if (!fs.existsSync(hooksDir)) {
|
|
3004
|
-
console.log(chalk_1.default.gray(" ⏭️ semo-hooks 미설치 (선택 패키지)"));
|
|
3005
|
-
console.log(chalk_1.default.gray(" 💡 설치: semo add hooks"));
|
|
3006
|
-
}
|
|
3007
|
-
else {
|
|
3008
|
-
const hooksVersionPath = path.join(hooksDir, "VERSION");
|
|
3009
|
-
const hooksVersion = fs.existsSync(hooksVersionPath)
|
|
3010
|
-
? fs.readFileSync(hooksVersionPath, "utf-8").trim()
|
|
3011
|
-
: "?";
|
|
3012
|
-
console.log(chalk_1.default.green(` ✓ semo-hooks v${hooksVersion} 설치됨`));
|
|
3013
|
-
// hooks 빌드 및 설정 업데이트
|
|
3014
|
-
await setupHooks(cwd, true);
|
|
3015
|
-
}
|
|
3016
|
-
// === 7. semo-mcp 체크 ===
|
|
3017
|
-
console.log(chalk_1.default.cyan("\n📡 semo-mcp 상태 확인"));
|
|
3018
|
-
const userHomeDir2 = process.env.HOME || process.env.USERPROFILE || "";
|
|
3019
|
-
const claudeSettingsPath = path.join(userHomeDir2, ".claude", "settings.local.json");
|
|
3020
|
-
if (!fs.existsSync(claudeSettingsPath)) {
|
|
3021
|
-
console.log(chalk_1.default.gray(" ⏭️ MCP 설정 없음 (선택사항)"));
|
|
3022
|
-
console.log(chalk_1.default.gray(" 💡 장기 기억이 필요하면 settings.local.json 설정 추가"));
|
|
3023
|
-
}
|
|
3024
|
-
else {
|
|
3025
|
-
try {
|
|
3026
|
-
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf-8"));
|
|
3027
|
-
const mcpServers = settings.mcpServers || {};
|
|
3028
|
-
const semoMcp = mcpServers["semo-integrations"];
|
|
3029
|
-
if (!semoMcp) {
|
|
3030
|
-
console.log(chalk_1.default.gray(" ⏭️ semo-integrations 미등록 (선택사항)"));
|
|
3031
|
-
console.log(chalk_1.default.gray(" 💡 장기 기억/원격 제어가 필요하면 MCP 설정 추가"));
|
|
3032
|
-
}
|
|
3033
|
-
else {
|
|
3034
|
-
console.log(chalk_1.default.green(" ✓ semo-integrations MCP 서버 등록됨"));
|
|
3035
|
-
// v3.0: SEMO_DB_PASSWORD만 체크
|
|
3036
|
-
const env = semoMcp.env || {};
|
|
3037
|
-
if (env["SEMO_DB_PASSWORD"]) {
|
|
3038
|
-
console.log(chalk_1.default.green(" ✓ 장기 기억: 활성화"));
|
|
3039
|
-
}
|
|
3040
|
-
else {
|
|
3041
|
-
console.log(chalk_1.default.gray(" ⏭️ 장기 기억: 비활성화 (SEMO_DB_PASSWORD 미설정)"));
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
catch {
|
|
3046
|
-
console.log(chalk_1.default.yellow(" ⚠ settings.local.json 파싱 오류"));
|
|
3047
|
-
}
|
|
3048
|
-
}
|
|
3049
|
-
// CLI 도구 체크 (v3.0: 스킬에서 CLI 직접 호출)
|
|
3050
|
-
console.log(chalk_1.default.cyan("\n🔧 CLI 도구 확인"));
|
|
3051
|
-
const cliToolsUpdate = [
|
|
3052
|
-
{ name: "gh", desc: "GitHub CLI" },
|
|
3053
|
-
{ name: "supabase", desc: "Supabase CLI" },
|
|
3054
|
-
];
|
|
3055
|
-
for (const tool of cliToolsUpdate) {
|
|
3056
|
-
try {
|
|
3057
|
-
(0, child_process_1.execSync)(`${tool.name} --version`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3058
|
-
console.log(chalk_1.default.green(` ✓ ${tool.name} 설치됨`));
|
|
3059
|
-
}
|
|
3060
|
-
catch {
|
|
3061
|
-
console.log(chalk_1.default.yellow(` ⚠ ${tool.name} 미설치 (${tool.desc})`));
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
// === 8. 설치 검증 ===
|
|
3165
|
+
// === 6. Hooks 업데이트 ===
|
|
3166
|
+
await setupHooks(cwd, true);
|
|
3167
|
+
// === 7. 설치 검증 ===
|
|
3065
3168
|
const verificationResult = verifyInstallation(cwd, installedExtensions);
|
|
3066
3169
|
printVerificationResult(verificationResult);
|
|
3067
3170
|
if (verificationResult.success) {
|
|
@@ -3191,174 +3294,8 @@ program
|
|
|
3191
3294
|
else {
|
|
3192
3295
|
console.log(chalk_1.default.red(" ❌ .claude/ 디렉토리 없음"));
|
|
3193
3296
|
}
|
|
3194
|
-
// 4.
|
|
3195
|
-
console.log(chalk_1.default.cyan("\n4.
|
|
3196
|
-
const hooksDir = path.join(semoSystemDir, "semo-hooks");
|
|
3197
|
-
const hooksDistDir = path.join(hooksDir, "dist");
|
|
3198
|
-
const hooksIndexJs = path.join(hooksDistDir, "index.js");
|
|
3199
|
-
if (!fs.existsSync(hooksDir)) {
|
|
3200
|
-
console.log(chalk_1.default.gray(" ⏭️ semo-hooks 미설치 (선택 패키지)"));
|
|
3201
|
-
console.log(chalk_1.default.gray(" 💡 설치: semo add hooks"));
|
|
3202
|
-
}
|
|
3203
|
-
else {
|
|
3204
|
-
// hooks 버전 확인
|
|
3205
|
-
const hooksVersionPath = path.join(hooksDir, "VERSION");
|
|
3206
|
-
const hooksVersion = fs.existsSync(hooksVersionPath)
|
|
3207
|
-
? fs.readFileSync(hooksVersionPath, "utf-8").trim()
|
|
3208
|
-
: "?";
|
|
3209
|
-
console.log(chalk_1.default.green(` ✅ semo-hooks v${hooksVersion} 설치됨`));
|
|
3210
|
-
// 빌드 상태 확인
|
|
3211
|
-
if (!fs.existsSync(hooksDistDir) || !fs.existsSync(hooksIndexJs)) {
|
|
3212
|
-
console.log(chalk_1.default.red(" ❌ 빌드되지 않음 (dist/index.js 없음)"));
|
|
3213
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3214
|
-
}
|
|
3215
|
-
else {
|
|
3216
|
-
console.log(chalk_1.default.green(" ✅ 빌드 완료 (dist/index.js 존재)"));
|
|
3217
|
-
}
|
|
3218
|
-
// settings.local.json hooks 설정 확인
|
|
3219
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
3220
|
-
const settingsPath = path.join(homeDir, ".claude", "settings.local.json");
|
|
3221
|
-
if (!fs.existsSync(settingsPath)) {
|
|
3222
|
-
console.log(chalk_1.default.yellow(" ⚠️ settings.local.json 없음"));
|
|
3223
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3224
|
-
}
|
|
3225
|
-
else {
|
|
3226
|
-
try {
|
|
3227
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
3228
|
-
const hooksConfig = settings.hooks;
|
|
3229
|
-
if (!hooksConfig) {
|
|
3230
|
-
console.log(chalk_1.default.yellow(" ⚠️ hooks 설정 없음"));
|
|
3231
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3232
|
-
}
|
|
3233
|
-
else {
|
|
3234
|
-
const requiredHooks = ["SessionStart", "UserPromptSubmit", "Stop", "SessionEnd"];
|
|
3235
|
-
const missingHooks = [];
|
|
3236
|
-
const invalidPathHooks = [];
|
|
3237
|
-
for (const hookName of requiredHooks) {
|
|
3238
|
-
const hookArray = hooksConfig[hookName];
|
|
3239
|
-
if (!hookArray || !Array.isArray(hookArray) || hookArray.length === 0) {
|
|
3240
|
-
missingHooks.push(hookName);
|
|
3241
|
-
}
|
|
3242
|
-
else {
|
|
3243
|
-
// 경로 검증
|
|
3244
|
-
const hookEntry = hookArray[0];
|
|
3245
|
-
const innerHooks = hookEntry?.hooks;
|
|
3246
|
-
if (innerHooks && Array.isArray(innerHooks) && innerHooks.length > 0) {
|
|
3247
|
-
const command = innerHooks[0]?.command || "";
|
|
3248
|
-
// 현재 프로젝트의 semo-hooks 경로와 비교
|
|
3249
|
-
if (!command.includes(hooksDir) && !command.includes("semo-hooks")) {
|
|
3250
|
-
invalidPathHooks.push(hookName);
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
}
|
|
3255
|
-
if (missingHooks.length > 0) {
|
|
3256
|
-
console.log(chalk_1.default.yellow(` ⚠️ 누락된 hooks: ${missingHooks.join(", ")}`));
|
|
3257
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3258
|
-
}
|
|
3259
|
-
else if (invalidPathHooks.length > 0) {
|
|
3260
|
-
console.log(chalk_1.default.yellow(` ⚠️ 경로 불일치: ${invalidPathHooks.join(", ")}`));
|
|
3261
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable (다른 프로젝트 설정 감지)"));
|
|
3262
|
-
}
|
|
3263
|
-
else {
|
|
3264
|
-
console.log(chalk_1.default.green(" ✅ hooks 설정 완료 (4개 hook 등록됨)"));
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
catch {
|
|
3269
|
-
console.log(chalk_1.default.red(" ❌ settings.local.json 파싱 오류"));
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
}
|
|
3273
|
-
// 5. semo-mcp (MCP 서버) 상태 확인
|
|
3274
|
-
// v3.0: semo-mcp는 Memory + Remote만 제공 (선택사항)
|
|
3275
|
-
// Slack/GitHub/Supabase는 스킬에서 CLI 직접 호출
|
|
3276
|
-
console.log(chalk_1.default.cyan("\n5. semo-mcp (MCP 서버) - 선택사항"));
|
|
3277
|
-
const userHomeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
3278
|
-
const claudeSettingsPath = path.join(userHomeDir, ".claude", "settings.local.json");
|
|
3279
|
-
// MCP 서버 설정 확인
|
|
3280
|
-
if (!fs.existsSync(claudeSettingsPath)) {
|
|
3281
|
-
console.log(chalk_1.default.gray(" ⏭️ settings.local.json 없음 (MCP 미사용)"));
|
|
3282
|
-
console.log(chalk_1.default.gray(" 💡 장기 기억이 필요하면 semo-mcp 설정 추가"));
|
|
3283
|
-
}
|
|
3284
|
-
else {
|
|
3285
|
-
try {
|
|
3286
|
-
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf-8"));
|
|
3287
|
-
const mcpServers = settings.mcpServers || {};
|
|
3288
|
-
// semo-integrations MCP 서버 확인
|
|
3289
|
-
const semoMcp = mcpServers["semo-integrations"];
|
|
3290
|
-
if (!semoMcp) {
|
|
3291
|
-
console.log(chalk_1.default.gray(" ⏭️ semo-integrations 미등록 (선택사항)"));
|
|
3292
|
-
console.log(chalk_1.default.gray(" 💡 장기 기억/원격 제어가 필요하면 MCP 설정 추가"));
|
|
3293
|
-
}
|
|
3294
|
-
else {
|
|
3295
|
-
console.log(chalk_1.default.green(" ✅ semo-integrations MCP 서버 등록됨"));
|
|
3296
|
-
// 명령어 경로 확인
|
|
3297
|
-
const mcpCommand = semoMcp.command || "";
|
|
3298
|
-
const mcpArgs = semoMcp.args || [];
|
|
3299
|
-
if (mcpCommand === "npx") {
|
|
3300
|
-
console.log(chalk_1.default.green(" ✅ npx 방식 실행 (자동 업데이트)"));
|
|
3301
|
-
}
|
|
3302
|
-
else if (mcpCommand === "node") {
|
|
3303
|
-
const scriptPath = mcpArgs[0] || "";
|
|
3304
|
-
if (scriptPath && fs.existsSync(scriptPath)) {
|
|
3305
|
-
console.log(chalk_1.default.green(` ✅ 로컬 스크립트: ${scriptPath}`));
|
|
3306
|
-
}
|
|
3307
|
-
else if (scriptPath) {
|
|
3308
|
-
console.log(chalk_1.default.red(` ❌ 스크립트 경로 없음: ${scriptPath}`));
|
|
3309
|
-
}
|
|
3310
|
-
}
|
|
3311
|
-
// v3.0: 환경변수 체크 - SEMO_DB_PASSWORD만 확인 (장기 기억용)
|
|
3312
|
-
const env = semoMcp.env || {};
|
|
3313
|
-
if (env["SEMO_DB_PASSWORD"]) {
|
|
3314
|
-
console.log(chalk_1.default.green(" ✅ 장기 기억: 활성화 (SEMO_DB_PASSWORD 설정됨)"));
|
|
3315
|
-
}
|
|
3316
|
-
else {
|
|
3317
|
-
console.log(chalk_1.default.gray(" ⏭️ 장기 기억: 비활성화"));
|
|
3318
|
-
console.log(chalk_1.default.gray(" 💡 SEMO_DB_PASSWORD 설정 시 활성화"));
|
|
3319
|
-
}
|
|
3320
|
-
// v3.0: 도구 목록 업데이트 (Memory + Remote만)
|
|
3321
|
-
const v3Tools = [
|
|
3322
|
-
// Memory
|
|
3323
|
-
"semo_remember", "semo_recall", "semo_save_fact",
|
|
3324
|
-
"semo_get_facts", "semo_get_history", "semo_memory_status",
|
|
3325
|
-
"semo_process_embeddings", "semo_recall_smart",
|
|
3326
|
-
// Remote
|
|
3327
|
-
"semo_remote_request", "semo_remote_respond", "semo_remote_pending",
|
|
3328
|
-
];
|
|
3329
|
-
console.log(chalk_1.default.gray(` 📦 제공 도구: ${v3Tools.length}개 (Memory ${8}, Remote ${3})`));
|
|
3330
|
-
}
|
|
3331
|
-
// 다른 MCP 서버 확인
|
|
3332
|
-
const otherServers = Object.keys(mcpServers).filter(k => k !== "semo-integrations");
|
|
3333
|
-
if (otherServers.length > 0) {
|
|
3334
|
-
console.log(chalk_1.default.gray(` 📡 기타 MCP 서버: ${otherServers.join(", ")}`));
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
catch {
|
|
3338
|
-
console.log(chalk_1.default.red(" ❌ settings.local.json 파싱 오류"));
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
// 5-1. CLI 도구 확인 (v3.0: 스킬에서 CLI 직접 호출)
|
|
3342
|
-
console.log(chalk_1.default.cyan("\n5-1. CLI 도구 (Skill용)"));
|
|
3343
|
-
const cliTools = [
|
|
3344
|
-
{ name: "gh", desc: "GitHub CLI", check: "gh --version" },
|
|
3345
|
-
{ name: "supabase", desc: "Supabase CLI", check: "supabase --version" },
|
|
3346
|
-
];
|
|
3347
|
-
for (const tool of cliTools) {
|
|
3348
|
-
try {
|
|
3349
|
-
const { execSync } = require("child_process");
|
|
3350
|
-
const version = execSync(tool.check, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n")[0];
|
|
3351
|
-
console.log(chalk_1.default.green(` ✅ ${tool.name}: ${version}`));
|
|
3352
|
-
}
|
|
3353
|
-
catch {
|
|
3354
|
-
console.log(chalk_1.default.yellow(` ⚠️ ${tool.name} 미설치 (${tool.desc})`));
|
|
3355
|
-
console.log(chalk_1.default.gray(` 💡 일부 스킬 기능이 제한될 수 있습니다`));
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
// Slack은 curl로 호출하므로 별도 체크 불필요
|
|
3359
|
-
console.log(chalk_1.default.gray(" ℹ️ Slack: curl 사용 (별도 CLI 불필요)"));
|
|
3360
|
-
// 6. 전체 설치 검증
|
|
3361
|
-
console.log(chalk_1.default.cyan("\n6. 전체 설치 검증"));
|
|
3297
|
+
// 4. 설치 검증
|
|
3298
|
+
console.log(chalk_1.default.cyan("\n4. 전체 설치 검증"));
|
|
3362
3299
|
const verificationResult = verifyInstallation(cwd, []);
|
|
3363
3300
|
if (verificationResult.success) {
|
|
3364
3301
|
console.log(chalk_1.default.green(" ✅ 설치 상태 정상"));
|