@team-semicolon/semo-cli 3.8.1 → 3.9.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 +417 -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,56 @@ program
|
|
|
995
1159
|
spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
|
|
996
1160
|
process.exit(1);
|
|
997
1161
|
}
|
|
998
|
-
// 2.
|
|
999
|
-
|
|
1000
|
-
let extensionsToInstall = [
|
|
1162
|
+
// 2. 프로젝트 유형 감지
|
|
1163
|
+
const detected = detectProjectType(cwd);
|
|
1164
|
+
let extensionsToInstall = [];
|
|
1001
1165
|
if (options.with) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1166
|
+
extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
|
|
1167
|
+
}
|
|
1168
|
+
else if (detected.length > 0) {
|
|
1169
|
+
console.log(chalk_1.default.cyan("\n📦 감지된 프로젝트 유형:"));
|
|
1170
|
+
detected.forEach(pkg => {
|
|
1171
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
1172
|
+
});
|
|
1173
|
+
const { installDetected } = await inquirer_1.default.prompt([
|
|
1174
|
+
{
|
|
1175
|
+
type: "confirm",
|
|
1176
|
+
name: "installDetected",
|
|
1177
|
+
message: "감지된 패키지를 함께 설치할까요?",
|
|
1178
|
+
default: true,
|
|
1179
|
+
},
|
|
1180
|
+
]);
|
|
1181
|
+
if (installDetected) {
|
|
1182
|
+
extensionsToInstall = detected;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
// 프로젝트 유형이 감지되지 않은 경우 패키지 선택 프롬프트
|
|
1187
|
+
console.log(chalk_1.default.cyan("\n📦 추가 패키지 선택"));
|
|
1188
|
+
console.log(chalk_1.default.gray(" 기본 설치 (semo-core + semo-skills) 외에 추가할 패키지를 선택하세요.\n"));
|
|
1189
|
+
// 그룹별로 패키지 구성
|
|
1190
|
+
const packageChoices = [
|
|
1191
|
+
new inquirer_1.default.Separator(chalk_1.default.yellow("── Engineering ──")),
|
|
1192
|
+
{ name: `eng/nextjs - ${EXTENSION_PACKAGES["eng/nextjs"].desc}`, value: "eng/nextjs" },
|
|
1193
|
+
{ name: `eng/spring - ${EXTENSION_PACKAGES["eng/spring"].desc}`, value: "eng/spring" },
|
|
1194
|
+
{ name: `eng/infra - ${EXTENSION_PACKAGES["eng/infra"].desc}`, value: "eng/infra" },
|
|
1195
|
+
new inquirer_1.default.Separator(chalk_1.default.yellow("── Business ──")),
|
|
1196
|
+
{ name: `biz/discovery - ${EXTENSION_PACKAGES["biz/discovery"].desc}`, value: "biz/discovery" },
|
|
1197
|
+
{ name: `biz/management - ${EXTENSION_PACKAGES["biz/management"].desc}`, value: "biz/management" },
|
|
1198
|
+
{ name: `biz/design - ${EXTENSION_PACKAGES["biz/design"].desc}`, value: "biz/design" },
|
|
1199
|
+
new inquirer_1.default.Separator(chalk_1.default.yellow("── Operations ──")),
|
|
1200
|
+
{ name: `ops/qa - ${EXTENSION_PACKAGES["ops/qa"].desc}`, value: "ops/qa" },
|
|
1201
|
+
];
|
|
1202
|
+
const { selectedPackages } = await inquirer_1.default.prompt([
|
|
1203
|
+
{
|
|
1204
|
+
type: "checkbox",
|
|
1205
|
+
name: "selectedPackages",
|
|
1206
|
+
message: "설치할 패키지 선택 (Space로 선택, Enter로 완료):",
|
|
1207
|
+
choices: packageChoices,
|
|
1208
|
+
},
|
|
1209
|
+
]);
|
|
1210
|
+
extensionsToInstall = selectedPackages;
|
|
1004
1211
|
}
|
|
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
1212
|
// 3. .claude 디렉토리 생성
|
|
1010
1213
|
const claudeDir = path.join(cwd, ".claude");
|
|
1011
1214
|
if (!fs.existsSync(claudeDir)) {
|
|
@@ -1058,23 +1261,12 @@ program
|
|
|
1058
1261
|
console.log(chalk_1.default.gray(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
|
|
1059
1262
|
});
|
|
1060
1263
|
}
|
|
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
1264
|
console.log(chalk_1.default.cyan("\n다음 단계:"));
|
|
1072
1265
|
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
1073
1266
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
1074
1267
|
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)"));
|
|
1268
|
+
if (extensionsToInstall.length === 0 && detected.length === 0) {
|
|
1269
|
+
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add next)"));
|
|
1078
1270
|
}
|
|
1079
1271
|
console.log();
|
|
1080
1272
|
});
|
|
@@ -1163,42 +1355,24 @@ async function createStandardSymlinks(cwd) {
|
|
|
1163
1355
|
}
|
|
1164
1356
|
console.log(chalk_1.default.green(` ✓ .claude/skills/ (${skills.length}개 skill 링크됨)`));
|
|
1165
1357
|
}
|
|
1166
|
-
// commands 링크
|
|
1358
|
+
// commands 링크
|
|
1167
1359
|
const commandsDir = path.join(claudeDir, "commands");
|
|
1168
1360
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1361
|
+
const semoCommandsLink = path.join(commandsDir, "SEMO");
|
|
1362
|
+
const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
|
|
1363
|
+
// 기존 링크가 있으면 삭제 후 재생성 (업데이트 시에도 최신 반영)
|
|
1364
|
+
if (fs.existsSync(semoCommandsLink)) {
|
|
1365
|
+
if (fs.lstatSync(semoCommandsLink).isSymbolicLink()) {
|
|
1366
|
+
fs.unlinkSync(semoCommandsLink);
|
|
1174
1367
|
}
|
|
1175
1368
|
else {
|
|
1176
|
-
removeRecursive(
|
|
1369
|
+
removeRecursive(semoCommandsLink);
|
|
1177
1370
|
}
|
|
1178
1371
|
}
|
|
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
|
-
}
|
|
1372
|
+
if (fs.existsSync(commandsTarget)) {
|
|
1373
|
+
createSymlinkOrJunction(commandsTarget, semoCommandsLink);
|
|
1374
|
+
console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
|
|
1200
1375
|
}
|
|
1201
|
-
console.log(chalk_1.default.green(` ✓ .claude/commands/SEMO (${totalCommands}개 command 링크됨)`));
|
|
1202
1376
|
}
|
|
1203
1377
|
/**
|
|
1204
1378
|
* 설치 상태를 검증하고 문제점을 리포트
|
|
@@ -1279,26 +1453,15 @@ function verifyInstallation(cwd, installedExtensions = []) {
|
|
|
1279
1453
|
}
|
|
1280
1454
|
}
|
|
1281
1455
|
}
|
|
1282
|
-
// 4. commands 검증 (
|
|
1283
|
-
const
|
|
1456
|
+
// 4. commands 검증 (isSymlinkValid 사용)
|
|
1457
|
+
const semoCommandsLink = path.join(claudeDir, "commands", "SEMO");
|
|
1284
1458
|
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
|
-
}
|
|
1459
|
+
const linkExists = fs.existsSync(semoCommandsLink) || fs.lstatSync(semoCommandsLink).isSymbolicLink();
|
|
1460
|
+
result.stats.commands.exists = linkExists;
|
|
1461
|
+
if (linkExists) {
|
|
1462
|
+
result.stats.commands.valid = isSymlinkValid(semoCommandsLink);
|
|
1463
|
+
if (!result.stats.commands.valid) {
|
|
1464
|
+
result.warnings.push("깨진 링크: .claude/commands/SEMO");
|
|
1302
1465
|
}
|
|
1303
1466
|
}
|
|
1304
1467
|
}
|
|
@@ -1451,8 +1614,7 @@ async function downloadExtensions(cwd, packages, force) {
|
|
|
1451
1614
|
}
|
|
1452
1615
|
// 개별 패키지 복사
|
|
1453
1616
|
for (const pkg of packages) {
|
|
1454
|
-
|
|
1455
|
-
const srcPath = path.join(tempDir, "semo-system", pkg);
|
|
1617
|
+
const srcPath = path.join(tempDir, "packages", pkg);
|
|
1456
1618
|
const destPath = path.join(semoSystemDir, pkg);
|
|
1457
1619
|
if (fs.existsSync(srcPath)) {
|
|
1458
1620
|
if (fs.existsSync(destPath) && !force) {
|
|
@@ -1528,26 +1690,6 @@ function createMergedOrchestrator(claudeAgentsDir, orchestratorSources) {
|
|
|
1528
1690
|
}
|
|
1529
1691
|
}
|
|
1530
1692
|
}
|
|
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
1693
|
// 병합된 orchestrator.md 생성
|
|
1552
1694
|
const mergedContent = `---
|
|
1553
1695
|
name: orchestrator
|
|
@@ -1616,7 +1758,7 @@ ${routingTables.join("\n\n---\n\n")}
|
|
|
1616
1758
|
3. **Package Priority**: 라우팅 충돌 시 설치 순서대로 우선순위 적용
|
|
1617
1759
|
4. **Cross-Package**: 다른 패키지 전문 영역 요청 시 인계 권유
|
|
1618
1760
|
|
|
1619
|
-
${
|
|
1761
|
+
${crossPackageRouting.length > 0 ? `## 🔄 Cross-Package Routing
|
|
1620
1762
|
|
|
1621
1763
|
${crossPackageRouting[0]}` : ""}
|
|
1622
1764
|
|
|
@@ -1735,29 +1877,8 @@ const BASE_MCP_SERVERS = [
|
|
|
1735
1877
|
name: "github",
|
|
1736
1878
|
command: "npx",
|
|
1737
1879
|
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1738
|
-
env: {
|
|
1739
|
-
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}",
|
|
1740
|
-
},
|
|
1741
1880
|
},
|
|
1742
1881
|
];
|
|
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
1882
|
// === Claude MCP 서버 존재 여부 확인 ===
|
|
1762
1883
|
function isMCPServerRegistered(serverName) {
|
|
1763
1884
|
try {
|
|
@@ -2395,12 +2516,34 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2395
2516
|
const extensionsList = extensions.length > 0
|
|
2396
2517
|
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
2397
2518
|
: "";
|
|
2398
|
-
// 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2519
|
+
// 그룹 및 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2399
2520
|
let packageClaudeMdSections = "";
|
|
2400
|
-
// meta
|
|
2401
|
-
const
|
|
2402
|
-
|
|
2521
|
+
// 0. meta 패키지가 설치된 경우 먼저 확인 (meta는 특별 처리)
|
|
2522
|
+
const isMetaInstalled = extensions.includes("meta") ||
|
|
2523
|
+
fs.existsSync(path.join(semoSystemDir, "meta", "VERSION"));
|
|
2524
|
+
// 1. 설치된 패키지에서 그룹 추출 (중복 제거)
|
|
2525
|
+
const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
|
|
2526
|
+
// 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
|
|
2527
|
+
for (const group of installedGroups) {
|
|
2528
|
+
const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
|
|
2529
|
+
if (fs.existsSync(groupClaudeMdPath)) {
|
|
2530
|
+
const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
|
|
2531
|
+
// 중복 제거 후 고유 콘텐츠만 추출
|
|
2532
|
+
const uniqueContent = extractUniqueContent(groupContent, group);
|
|
2533
|
+
// 헤더 레벨 조정 (# → ##, ## → ###)
|
|
2534
|
+
const adjustedContent = uniqueContent
|
|
2535
|
+
.replace(/^# /gm, "## ")
|
|
2536
|
+
.replace(/^## /gm, "### ")
|
|
2537
|
+
.replace(/^### /gm, "#### ");
|
|
2538
|
+
packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
|
|
2539
|
+
console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
// 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
|
|
2403
2543
|
for (const pkg of extensions) {
|
|
2544
|
+
// meta 패키지는 별도 처리 (아래에서 전체 내용 병합)
|
|
2545
|
+
if (pkg === "meta")
|
|
2546
|
+
continue;
|
|
2404
2547
|
const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
|
|
2405
2548
|
if (fs.existsSync(pkgClaudeMdPath)) {
|
|
2406
2549
|
const pkgContent = fs.readFileSync(pkgClaudeMdPath, "utf-8");
|
|
@@ -2415,22 +2558,48 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2415
2558
|
console.log(chalk_1.default.gray(` + ${pkg}/CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2416
2559
|
}
|
|
2417
2560
|
}
|
|
2418
|
-
//
|
|
2561
|
+
// 3.5. meta 패키지 CLAUDE.md 병합 (전체 내용 - Meta 환경 규칙 포함)
|
|
2562
|
+
if (isMetaInstalled) {
|
|
2563
|
+
const metaClaudeMdPath = path.join(semoSystemDir, "meta", "CLAUDE.md");
|
|
2564
|
+
if (fs.existsSync(metaClaudeMdPath)) {
|
|
2565
|
+
const metaContent = fs.readFileSync(metaClaudeMdPath, "utf-8");
|
|
2566
|
+
const pkgName = EXTENSION_PACKAGES["meta"]?.name || "Meta";
|
|
2567
|
+
// meta는 중복 제거 없이 전체 내용 유지 (Core Rules가 중요)
|
|
2568
|
+
// 헤더 레벨만 조정 (# → ###, ## → ####)
|
|
2569
|
+
const adjustedContent = metaContent
|
|
2570
|
+
.replace(/^# /gm, "### ")
|
|
2571
|
+
.replace(/^## /gm, "#### ");
|
|
2572
|
+
packageClaudeMdSections += `\n\n---\n\n## ${pkgName} 패키지 컨텍스트\n\n${adjustedContent}`;
|
|
2573
|
+
console.log(chalk_1.default.green(` + meta/CLAUDE.md 병합됨 (전체 내용 - Meta 환경 규칙 포함)`));
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
// 4. Orchestrator 참조 경로 결정 (Extension 패키지 우선, meta 포함)
|
|
2419
2577
|
// Extension 패키지 중 orchestrator가 있는 첫 번째 패키지를 Primary로 설정
|
|
2420
2578
|
let primaryOrchestratorPath = "semo-core/agents/orchestrator/orchestrator.md";
|
|
2421
2579
|
const orchestratorPaths = [];
|
|
2580
|
+
// meta 패키지 orchestrator 먼저 확인 (meta가 설치되어 있으면 최우선)
|
|
2581
|
+
if (isMetaInstalled) {
|
|
2582
|
+
const metaOrchestratorPath = path.join(semoSystemDir, "meta", "agents/orchestrator/orchestrator.md");
|
|
2583
|
+
if (fs.existsSync(metaOrchestratorPath)) {
|
|
2584
|
+
orchestratorPaths.push("semo-system/meta/agents/orchestrator/orchestrator.md");
|
|
2585
|
+
primaryOrchestratorPath = "meta/agents/orchestrator/orchestrator.md";
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
// 나머지 Extension 패키지 orchestrator 확인
|
|
2422
2589
|
for (const pkg of extensions) {
|
|
2590
|
+
if (pkg === "meta")
|
|
2591
|
+
continue; // meta는 위에서 이미 처리
|
|
2423
2592
|
const pkgOrchestratorPath = path.join(semoSystemDir, pkg, "agents/orchestrator/orchestrator.md");
|
|
2424
2593
|
if (fs.existsSync(pkgOrchestratorPath)) {
|
|
2425
2594
|
orchestratorPaths.push(`semo-system/${pkg}/agents/orchestrator/orchestrator.md`);
|
|
2426
|
-
//
|
|
2595
|
+
// Primary가 아직 semo-core이면 이 패키지를 Primary로 설정
|
|
2427
2596
|
if (primaryOrchestratorPath === "semo-core/agents/orchestrator/orchestrator.md") {
|
|
2428
2597
|
primaryOrchestratorPath = `${pkg}/agents/orchestrator/orchestrator.md`;
|
|
2429
2598
|
}
|
|
2430
2599
|
}
|
|
2431
2600
|
}
|
|
2432
|
-
// semo-core orchestrator는 항상 포함
|
|
2433
|
-
orchestratorPaths.
|
|
2601
|
+
// semo-core orchestrator는 항상 마지막에 포함 (fallback)
|
|
2602
|
+
orchestratorPaths.push("semo-system/semo-core/agents/orchestrator/orchestrator.md");
|
|
2434
2603
|
// Orchestrator 참조 섹션 생성
|
|
2435
2604
|
const orchestratorRefSection = orchestratorPaths.length > 1
|
|
2436
2605
|
? `**Primary Orchestrator**: \`semo-system/${primaryOrchestratorPath}\`
|
|
@@ -2444,47 +2613,6 @@ ${orchestratorPaths.map(p => `- \`${p}\``).join("\n")}
|
|
|
2444
2613
|
: `**반드시 읽어야 할 파일**: \`semo-system/semo-core/agents/orchestrator/orchestrator.md\`
|
|
2445
2614
|
|
|
2446
2615
|
이 파일에서 라우팅 테이블, 의도 분류, 메시지 포맷을 확인하세요.`;
|
|
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
2616
|
const claudeMdContent = `# SEMO Project Configuration
|
|
2489
2617
|
|
|
2490
2618
|
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
|
|
@@ -2538,7 +2666,46 @@ npm run build # 3. 빌드 검증 (Next.js/TypeScript 프로젝트)
|
|
|
2538
2666
|
- \`--no-verify\` 플래그 사용 금지
|
|
2539
2667
|
- Quality Gate 우회 시도 거부
|
|
2540
2668
|
- "그냥 커밋해줘", "빌드 생략해줘" 등 거부
|
|
2541
|
-
${
|
|
2669
|
+
${isMetaInstalled ? `
|
|
2670
|
+
### 3. Meta 환경 자동 워크플로우 (NON-NEGOTIABLE)
|
|
2671
|
+
|
|
2672
|
+
> **⚠️ Meta 패키지가 설치된 환경에서는 반드시 아래 규칙이 적용됩니다.**
|
|
2673
|
+
> **이 규칙을 우회하거나 무시하는 것은 금지됩니다.**
|
|
2674
|
+
|
|
2675
|
+
#### 자동 트리거 조건
|
|
2676
|
+
|
|
2677
|
+
\`semo-system/\` 디렉토리 내 파일이 수정되면:
|
|
2678
|
+
1. 작업 종료 전 반드시 \`skill:meta-workflow\` 호출
|
|
2679
|
+
2. 버저닝 → 배포 → 로컬 동기화 체인 자동 실행
|
|
2680
|
+
|
|
2681
|
+
#### 감지 패턴
|
|
2682
|
+
|
|
2683
|
+
다음 경로의 파일 수정 시 자동 트리거:
|
|
2684
|
+
- \`semo-system/semo-core/**\`
|
|
2685
|
+
- \`semo-system/semo-skills/**\`
|
|
2686
|
+
- \`semo-system/meta/**\`
|
|
2687
|
+
- \`semo-system/semo-remote/**\`
|
|
2688
|
+
- \`semo-system/semo-hooks/**\`
|
|
2689
|
+
- \`packages/cli/**\` (CLI 수정 시)
|
|
2690
|
+
|
|
2691
|
+
#### 강제 동작 흐름
|
|
2692
|
+
|
|
2693
|
+
\`\`\`text
|
|
2694
|
+
[작업 완료 감지]
|
|
2695
|
+
↓
|
|
2696
|
+
semo-system/ 또는 packages/ 파일 수정 여부 확인
|
|
2697
|
+
↓
|
|
2698
|
+
수정됨? → [SEMO] Skill 호출: meta-workflow
|
|
2699
|
+
버저닝 → 배포 → 동기화 자동 실행
|
|
2700
|
+
↓
|
|
2701
|
+
수정 안됨? → 정상 종료
|
|
2702
|
+
\`\`\`
|
|
2703
|
+
|
|
2704
|
+
**금지 사항**:
|
|
2705
|
+
- semo-system/ 수정 후 버저닝 없이 종료
|
|
2706
|
+
- "버저닝 나중에 해줘" 요청 수락
|
|
2707
|
+
- meta-workflow 스킬 호출 건너뛰기
|
|
2708
|
+
` : ``}
|
|
2542
2709
|
---
|
|
2543
2710
|
|
|
2544
2711
|
## 설치된 구성
|
|
@@ -2607,7 +2774,7 @@ ${packageClaudeMdSections}
|
|
|
2607
2774
|
// === add 명령어 ===
|
|
2608
2775
|
program
|
|
2609
2776
|
.command("add <packages>")
|
|
2610
|
-
.description("Extension 패키지를 추가로 설치합니다 (
|
|
2777
|
+
.description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops, system / 개별: biz/discovery, eng/nextjs, semo-hooks)")
|
|
2611
2778
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
2612
2779
|
.action(async (packagesInput, options) => {
|
|
2613
2780
|
const cwd = process.cwd();
|
|
@@ -2616,16 +2783,25 @@ program
|
|
|
2616
2783
|
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
2617
2784
|
process.exit(1);
|
|
2618
2785
|
}
|
|
2619
|
-
// 패키지 입력 해석
|
|
2620
|
-
const { packages } = resolvePackageInput(packagesInput);
|
|
2786
|
+
// 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
2787
|
+
const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
|
|
2621
2788
|
if (packages.length === 0) {
|
|
2622
2789
|
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
|
|
2790
|
+
console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
|
|
2623
2791
|
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
|
|
2624
2792
|
console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
|
|
2625
2793
|
process.exit(1);
|
|
2626
2794
|
}
|
|
2627
|
-
//
|
|
2628
|
-
if (
|
|
2795
|
+
// 그룹 설치인 경우 안내
|
|
2796
|
+
if (isGroup) {
|
|
2797
|
+
console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
|
|
2798
|
+
console.log(chalk_1.default.gray(" 포함된 패키지:"));
|
|
2799
|
+
for (const pkg of packages) {
|
|
2800
|
+
console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
|
|
2801
|
+
}
|
|
2802
|
+
console.log();
|
|
2803
|
+
}
|
|
2804
|
+
else if (packages.length === 1) {
|
|
2629
2805
|
// 단일 패키지
|
|
2630
2806
|
const pkg = packages[0];
|
|
2631
2807
|
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
|
|
@@ -2691,28 +2867,50 @@ program
|
|
|
2691
2867
|
.action(() => {
|
|
2692
2868
|
const cwd = process.cwd();
|
|
2693
2869
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
2694
|
-
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지
|
|
2695
|
-
// Standard
|
|
2870
|
+
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.0)\n"));
|
|
2871
|
+
// Standard
|
|
2696
2872
|
console.log(chalk_1.default.white.bold("Standard (필수)"));
|
|
2697
2873
|
const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
2698
2874
|
const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
|
|
2699
2875
|
console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
|
|
2700
2876
|
console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
|
|
2701
2877
|
console.log();
|
|
2702
|
-
// Extensions
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
{
|
|
2706
|
-
{
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
const
|
|
2712
|
-
|
|
2713
|
-
|
|
2878
|
+
// Extensions - 레이어별 그룹화
|
|
2879
|
+
const layers = {
|
|
2880
|
+
biz: { title: "Business Layer", emoji: "💼" },
|
|
2881
|
+
eng: { title: "Engineering Layer", emoji: "⚙️" },
|
|
2882
|
+
ops: { title: "Operations Layer", emoji: "📊" },
|
|
2883
|
+
meta: { title: "Meta", emoji: "🔧" },
|
|
2884
|
+
system: { title: "System", emoji: "🔩" },
|
|
2885
|
+
};
|
|
2886
|
+
for (const [layerKey, layerInfo] of Object.entries(layers)) {
|
|
2887
|
+
const layerPackages = Object.entries(EXTENSION_PACKAGES).filter(([, pkg]) => pkg.layer === layerKey);
|
|
2888
|
+
if (layerPackages.length === 0)
|
|
2889
|
+
continue;
|
|
2890
|
+
console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
|
|
2891
|
+
for (const [key, pkg] of layerPackages) {
|
|
2892
|
+
const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
|
|
2893
|
+
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
2894
|
+
const displayKey = key.includes("/") ? key.split("/")[1] : key;
|
|
2895
|
+
console.log(` ${status} ${chalk_1.default.cyan(displayKey)} - ${pkg.desc}`);
|
|
2896
|
+
console.log(chalk_1.default.gray(` semo add ${key}`));
|
|
2897
|
+
}
|
|
2898
|
+
console.log();
|
|
2714
2899
|
}
|
|
2900
|
+
// 그룹 설치 안내
|
|
2901
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2902
|
+
console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
|
|
2903
|
+
console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
|
|
2904
|
+
console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
|
|
2905
|
+
console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
|
|
2906
|
+
console.log(chalk_1.default.gray(" semo add system → System 전체 (hooks, remote)"));
|
|
2715
2907
|
console.log();
|
|
2908
|
+
// 단축명 안내
|
|
2909
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2910
|
+
console.log(chalk_1.default.white.bold("⚡ 단축명 지원"));
|
|
2911
|
+
console.log(chalk_1.default.gray(" semo add discovery → biz/discovery"));
|
|
2912
|
+
console.log(chalk_1.default.gray(" semo add qa → ops/qa"));
|
|
2913
|
+
console.log(chalk_1.default.gray(" semo add nextjs → eng/nextjs\n"));
|
|
2716
2914
|
});
|
|
2717
2915
|
// === status 명령어 ===
|
|
2718
2916
|
program
|
|
@@ -2997,71 +3195,9 @@ program
|
|
|
2997
3195
|
}
|
|
2998
3196
|
}
|
|
2999
3197
|
}
|
|
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. 설치 검증 ===
|
|
3198
|
+
// === 6. Hooks 업데이트 ===
|
|
3199
|
+
await setupHooks(cwd, true);
|
|
3200
|
+
// === 7. 설치 검증 ===
|
|
3065
3201
|
const verificationResult = verifyInstallation(cwd, installedExtensions);
|
|
3066
3202
|
printVerificationResult(verificationResult);
|
|
3067
3203
|
if (verificationResult.success) {
|
|
@@ -3191,174 +3327,8 @@ program
|
|
|
3191
3327
|
else {
|
|
3192
3328
|
console.log(chalk_1.default.red(" ❌ .claude/ 디렉토리 없음"));
|
|
3193
3329
|
}
|
|
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. 전체 설치 검증"));
|
|
3330
|
+
// 4. 설치 검증
|
|
3331
|
+
console.log(chalk_1.default.cyan("\n4. 전체 설치 검증"));
|
|
3362
3332
|
const verificationResult = verifyInstallation(cwd, []);
|
|
3363
3333
|
if (verificationResult.success) {
|
|
3364
3334
|
console.log(chalk_1.default.green(" ✅ 설치 상태 정상"));
|