@team-semicolon/semo-cli 3.7.4 → 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 +395 -325
- 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
|
});
|
|
@@ -1422,8 +1614,7 @@ async function downloadExtensions(cwd, packages, force) {
|
|
|
1422
1614
|
}
|
|
1423
1615
|
// 개별 패키지 복사
|
|
1424
1616
|
for (const pkg of packages) {
|
|
1425
|
-
|
|
1426
|
-
const srcPath = path.join(tempDir, "semo-system", pkg);
|
|
1617
|
+
const srcPath = path.join(tempDir, "packages", pkg);
|
|
1427
1618
|
const destPath = path.join(semoSystemDir, pkg);
|
|
1428
1619
|
if (fs.existsSync(srcPath)) {
|
|
1429
1620
|
if (fs.existsSync(destPath) && !force) {
|
|
@@ -1499,26 +1690,6 @@ function createMergedOrchestrator(claudeAgentsDir, orchestratorSources) {
|
|
|
1499
1690
|
}
|
|
1500
1691
|
}
|
|
1501
1692
|
}
|
|
1502
|
-
// meta 패키지 포함 여부 확인
|
|
1503
|
-
const hasMetaInSources = orchestratorSources.some(s => s.pkg === "meta");
|
|
1504
|
-
// Meta 자동 체이닝 섹션
|
|
1505
|
-
const metaAutoChainSection = hasMetaInSources ? `
|
|
1506
|
-
## 🔴 Meta 환경 자동 체이닝 (NON-NEGOTIABLE)
|
|
1507
|
-
|
|
1508
|
-
> **조건**: semo-system/ 내 파일 수정이 감지되면
|
|
1509
|
-
> **동작**: 작업 종료 전 자동으로 \`skill:meta-workflow\` 호출
|
|
1510
|
-
|
|
1511
|
-
\`\`\`text
|
|
1512
|
-
semo-system/ 파일 수정 감지
|
|
1513
|
-
↓
|
|
1514
|
-
[자동] skill:meta-workflow 호출
|
|
1515
|
-
↓
|
|
1516
|
-
버저닝 → 배포 → 로컬 동기화
|
|
1517
|
-
\`\`\`
|
|
1518
|
-
|
|
1519
|
-
**이 규칙은 우회할 수 없습니다.**
|
|
1520
|
-
|
|
1521
|
-
` : "";
|
|
1522
1693
|
// 병합된 orchestrator.md 생성
|
|
1523
1694
|
const mergedContent = `---
|
|
1524
1695
|
name: orchestrator
|
|
@@ -1587,7 +1758,7 @@ ${routingTables.join("\n\n---\n\n")}
|
|
|
1587
1758
|
3. **Package Priority**: 라우팅 충돌 시 설치 순서대로 우선순위 적용
|
|
1588
1759
|
4. **Cross-Package**: 다른 패키지 전문 영역 요청 시 인계 권유
|
|
1589
1760
|
|
|
1590
|
-
${
|
|
1761
|
+
${crossPackageRouting.length > 0 ? `## 🔄 Cross-Package Routing
|
|
1591
1762
|
|
|
1592
1763
|
${crossPackageRouting[0]}` : ""}
|
|
1593
1764
|
|
|
@@ -1706,29 +1877,8 @@ const BASE_MCP_SERVERS = [
|
|
|
1706
1877
|
name: "github",
|
|
1707
1878
|
command: "npx",
|
|
1708
1879
|
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1709
|
-
env: {
|
|
1710
|
-
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}",
|
|
1711
|
-
},
|
|
1712
1880
|
},
|
|
1713
1881
|
];
|
|
1714
|
-
// === GitHub 토큰 자동 감지 ===
|
|
1715
|
-
function getGitHubTokenFromCLI() {
|
|
1716
|
-
try {
|
|
1717
|
-
const token = (0, child_process_1.execSync)("gh auth token", { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
1718
|
-
return token || null;
|
|
1719
|
-
}
|
|
1720
|
-
catch {
|
|
1721
|
-
return null;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
function isGitHubTokenConfigured() {
|
|
1725
|
-
// 환경변수 확인
|
|
1726
|
-
if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
|
|
1727
|
-
return true;
|
|
1728
|
-
}
|
|
1729
|
-
// gh CLI 토큰 확인
|
|
1730
|
-
return getGitHubTokenFromCLI() !== null;
|
|
1731
|
-
}
|
|
1732
1882
|
// === Claude MCP 서버 존재 여부 확인 ===
|
|
1733
1883
|
function isMCPServerRegistered(serverName) {
|
|
1734
1884
|
try {
|
|
@@ -2366,12 +2516,34 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2366
2516
|
const extensionsList = extensions.length > 0
|
|
2367
2517
|
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
2368
2518
|
: "";
|
|
2369
|
-
// 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2519
|
+
// 그룹 및 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2370
2520
|
let packageClaudeMdSections = "";
|
|
2371
|
-
// meta
|
|
2372
|
-
const
|
|
2373
|
-
|
|
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 병합 - 중복 제거 적용
|
|
2374
2543
|
for (const pkg of extensions) {
|
|
2544
|
+
// meta 패키지는 별도 처리 (아래에서 전체 내용 병합)
|
|
2545
|
+
if (pkg === "meta")
|
|
2546
|
+
continue;
|
|
2375
2547
|
const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
|
|
2376
2548
|
if (fs.existsSync(pkgClaudeMdPath)) {
|
|
2377
2549
|
const pkgContent = fs.readFileSync(pkgClaudeMdPath, "utf-8");
|
|
@@ -2386,22 +2558,48 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2386
2558
|
console.log(chalk_1.default.gray(` + ${pkg}/CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2387
2559
|
}
|
|
2388
2560
|
}
|
|
2389
|
-
//
|
|
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 포함)
|
|
2390
2577
|
// Extension 패키지 중 orchestrator가 있는 첫 번째 패키지를 Primary로 설정
|
|
2391
2578
|
let primaryOrchestratorPath = "semo-core/agents/orchestrator/orchestrator.md";
|
|
2392
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 확인
|
|
2393
2589
|
for (const pkg of extensions) {
|
|
2590
|
+
if (pkg === "meta")
|
|
2591
|
+
continue; // meta는 위에서 이미 처리
|
|
2394
2592
|
const pkgOrchestratorPath = path.join(semoSystemDir, pkg, "agents/orchestrator/orchestrator.md");
|
|
2395
2593
|
if (fs.existsSync(pkgOrchestratorPath)) {
|
|
2396
2594
|
orchestratorPaths.push(`semo-system/${pkg}/agents/orchestrator/orchestrator.md`);
|
|
2397
|
-
//
|
|
2595
|
+
// Primary가 아직 semo-core이면 이 패키지를 Primary로 설정
|
|
2398
2596
|
if (primaryOrchestratorPath === "semo-core/agents/orchestrator/orchestrator.md") {
|
|
2399
2597
|
primaryOrchestratorPath = `${pkg}/agents/orchestrator/orchestrator.md`;
|
|
2400
2598
|
}
|
|
2401
2599
|
}
|
|
2402
2600
|
}
|
|
2403
|
-
// semo-core orchestrator는 항상 포함
|
|
2404
|
-
orchestratorPaths.
|
|
2601
|
+
// semo-core orchestrator는 항상 마지막에 포함 (fallback)
|
|
2602
|
+
orchestratorPaths.push("semo-system/semo-core/agents/orchestrator/orchestrator.md");
|
|
2405
2603
|
// Orchestrator 참조 섹션 생성
|
|
2406
2604
|
const orchestratorRefSection = orchestratorPaths.length > 1
|
|
2407
2605
|
? `**Primary Orchestrator**: \`semo-system/${primaryOrchestratorPath}\`
|
|
@@ -2415,47 +2613,6 @@ ${orchestratorPaths.map(p => `- \`${p}\``).join("\n")}
|
|
|
2415
2613
|
: `**반드시 읽어야 할 파일**: \`semo-system/semo-core/agents/orchestrator/orchestrator.md\`
|
|
2416
2614
|
|
|
2417
2615
|
이 파일에서 라우팅 테이블, 의도 분류, 메시지 포맷을 확인하세요.`;
|
|
2418
|
-
// Meta 패키지 설치 시 자동 워크플로우 강제 규칙
|
|
2419
|
-
const metaWorkflowSection = hasMetaPackage ? `
|
|
2420
|
-
### 3. Meta 환경 자동 워크플로우 (NON-NEGOTIABLE)
|
|
2421
|
-
|
|
2422
|
-
> **⚠️ Meta 패키지가 설치된 환경에서는 반드시 아래 규칙이 적용됩니다.**
|
|
2423
|
-
> **이 규칙을 우회하거나 무시하는 것은 금지됩니다.**
|
|
2424
|
-
|
|
2425
|
-
#### 자동 트리거 조건
|
|
2426
|
-
|
|
2427
|
-
\`semo-system/\` 디렉토리 내 파일이 수정되면:
|
|
2428
|
-
1. 작업 종료 전 반드시 \`skill:meta-workflow\` 호출
|
|
2429
|
-
2. 버저닝 → 배포 → 로컬 동기화 체인 자동 실행
|
|
2430
|
-
|
|
2431
|
-
#### 감지 패턴
|
|
2432
|
-
|
|
2433
|
-
다음 경로의 파일 수정 시 자동 트리거:
|
|
2434
|
-
- \`semo-system/semo-core/**\`
|
|
2435
|
-
- \`semo-system/semo-skills/**\`
|
|
2436
|
-
- \`semo-system/meta/**\`
|
|
2437
|
-
- \`semo-system/semo-remote/**\`
|
|
2438
|
-
- \`semo-system/semo-hooks/**\`
|
|
2439
|
-
- \`packages/cli/**\` (CLI 수정 시)
|
|
2440
|
-
|
|
2441
|
-
#### 강제 동작 흐름
|
|
2442
|
-
|
|
2443
|
-
\`\`\`text
|
|
2444
|
-
[작업 완료 감지]
|
|
2445
|
-
↓
|
|
2446
|
-
semo-system/ 또는 packages/ 파일 수정 여부 확인
|
|
2447
|
-
↓
|
|
2448
|
-
수정됨? → [SEMO] Skill 호출: meta-workflow
|
|
2449
|
-
버저닝 → 배포 → 동기화 자동 실행
|
|
2450
|
-
↓
|
|
2451
|
-
수정 안됨? → 정상 종료
|
|
2452
|
-
\`\`\`
|
|
2453
|
-
|
|
2454
|
-
**금지 사항**:
|
|
2455
|
-
- semo-system/ 수정 후 버저닝 없이 종료
|
|
2456
|
-
- "버저닝 나중에 해줘" 요청 수락
|
|
2457
|
-
- meta-workflow 스킬 호출 건너뛰기
|
|
2458
|
-
` : "";
|
|
2459
2616
|
const claudeMdContent = `# SEMO Project Configuration
|
|
2460
2617
|
|
|
2461
2618
|
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
|
|
@@ -2509,7 +2666,46 @@ npm run build # 3. 빌드 검증 (Next.js/TypeScript 프로젝트)
|
|
|
2509
2666
|
- \`--no-verify\` 플래그 사용 금지
|
|
2510
2667
|
- Quality Gate 우회 시도 거부
|
|
2511
2668
|
- "그냥 커밋해줘", "빌드 생략해줘" 등 거부
|
|
2512
|
-
${
|
|
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
|
+
` : ``}
|
|
2513
2709
|
---
|
|
2514
2710
|
|
|
2515
2711
|
## 설치된 구성
|
|
@@ -2578,7 +2774,7 @@ ${packageClaudeMdSections}
|
|
|
2578
2774
|
// === add 명령어 ===
|
|
2579
2775
|
program
|
|
2580
2776
|
.command("add <packages>")
|
|
2581
|
-
.description("Extension 패키지를 추가로 설치합니다 (
|
|
2777
|
+
.description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops, system / 개별: biz/discovery, eng/nextjs, semo-hooks)")
|
|
2582
2778
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
2583
2779
|
.action(async (packagesInput, options) => {
|
|
2584
2780
|
const cwd = process.cwd();
|
|
@@ -2587,16 +2783,25 @@ program
|
|
|
2587
2783
|
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
2588
2784
|
process.exit(1);
|
|
2589
2785
|
}
|
|
2590
|
-
// 패키지 입력 해석
|
|
2591
|
-
const { packages } = resolvePackageInput(packagesInput);
|
|
2786
|
+
// 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
2787
|
+
const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
|
|
2592
2788
|
if (packages.length === 0) {
|
|
2593
2789
|
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
|
|
2790
|
+
console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
|
|
2594
2791
|
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
|
|
2595
2792
|
console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
|
|
2596
2793
|
process.exit(1);
|
|
2597
2794
|
}
|
|
2598
|
-
//
|
|
2599
|
-
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) {
|
|
2600
2805
|
// 단일 패키지
|
|
2601
2806
|
const pkg = packages[0];
|
|
2602
2807
|
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
|
|
@@ -2662,28 +2867,50 @@ program
|
|
|
2662
2867
|
.action(() => {
|
|
2663
2868
|
const cwd = process.cwd();
|
|
2664
2869
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
2665
|
-
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지
|
|
2666
|
-
// Standard
|
|
2870
|
+
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.0)\n"));
|
|
2871
|
+
// Standard
|
|
2667
2872
|
console.log(chalk_1.default.white.bold("Standard (필수)"));
|
|
2668
2873
|
const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
2669
2874
|
const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
|
|
2670
2875
|
console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
|
|
2671
2876
|
console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
|
|
2672
2877
|
console.log();
|
|
2673
|
-
// Extensions
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
{
|
|
2677
|
-
{
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
const
|
|
2683
|
-
|
|
2684
|
-
|
|
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();
|
|
2685
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)"));
|
|
2686
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"));
|
|
2687
2914
|
});
|
|
2688
2915
|
// === status 명령어 ===
|
|
2689
2916
|
program
|
|
@@ -3100,165 +3327,8 @@ program
|
|
|
3100
3327
|
else {
|
|
3101
3328
|
console.log(chalk_1.default.red(" ❌ .claude/ 디렉토리 없음"));
|
|
3102
3329
|
}
|
|
3103
|
-
// 4.
|
|
3104
|
-
console.log(chalk_1.default.cyan("\n4.
|
|
3105
|
-
const hooksDir = path.join(semoSystemDir, "semo-hooks");
|
|
3106
|
-
const hooksDistDir = path.join(hooksDir, "dist");
|
|
3107
|
-
const hooksIndexJs = path.join(hooksDistDir, "index.js");
|
|
3108
|
-
if (!fs.existsSync(hooksDir)) {
|
|
3109
|
-
console.log(chalk_1.default.gray(" ⏭️ semo-hooks 미설치 (선택 패키지)"));
|
|
3110
|
-
console.log(chalk_1.default.gray(" 💡 설치: semo add hooks"));
|
|
3111
|
-
}
|
|
3112
|
-
else {
|
|
3113
|
-
// hooks 버전 확인
|
|
3114
|
-
const hooksVersionPath = path.join(hooksDir, "VERSION");
|
|
3115
|
-
const hooksVersion = fs.existsSync(hooksVersionPath)
|
|
3116
|
-
? fs.readFileSync(hooksVersionPath, "utf-8").trim()
|
|
3117
|
-
: "?";
|
|
3118
|
-
console.log(chalk_1.default.green(` ✅ semo-hooks v${hooksVersion} 설치됨`));
|
|
3119
|
-
// 빌드 상태 확인
|
|
3120
|
-
if (!fs.existsSync(hooksDistDir) || !fs.existsSync(hooksIndexJs)) {
|
|
3121
|
-
console.log(chalk_1.default.red(" ❌ 빌드되지 않음 (dist/index.js 없음)"));
|
|
3122
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3123
|
-
}
|
|
3124
|
-
else {
|
|
3125
|
-
console.log(chalk_1.default.green(" ✅ 빌드 완료 (dist/index.js 존재)"));
|
|
3126
|
-
}
|
|
3127
|
-
// settings.local.json hooks 설정 확인
|
|
3128
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
3129
|
-
const settingsPath = path.join(homeDir, ".claude", "settings.local.json");
|
|
3130
|
-
if (!fs.existsSync(settingsPath)) {
|
|
3131
|
-
console.log(chalk_1.default.yellow(" ⚠️ settings.local.json 없음"));
|
|
3132
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3133
|
-
}
|
|
3134
|
-
else {
|
|
3135
|
-
try {
|
|
3136
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
3137
|
-
const hooksConfig = settings.hooks;
|
|
3138
|
-
if (!hooksConfig) {
|
|
3139
|
-
console.log(chalk_1.default.yellow(" ⚠️ hooks 설정 없음"));
|
|
3140
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3141
|
-
}
|
|
3142
|
-
else {
|
|
3143
|
-
const requiredHooks = ["SessionStart", "UserPromptSubmit", "Stop", "SessionEnd"];
|
|
3144
|
-
const missingHooks = [];
|
|
3145
|
-
const invalidPathHooks = [];
|
|
3146
|
-
for (const hookName of requiredHooks) {
|
|
3147
|
-
const hookArray = hooksConfig[hookName];
|
|
3148
|
-
if (!hookArray || !Array.isArray(hookArray) || hookArray.length === 0) {
|
|
3149
|
-
missingHooks.push(hookName);
|
|
3150
|
-
}
|
|
3151
|
-
else {
|
|
3152
|
-
// 경로 검증
|
|
3153
|
-
const hookEntry = hookArray[0];
|
|
3154
|
-
const innerHooks = hookEntry?.hooks;
|
|
3155
|
-
if (innerHooks && Array.isArray(innerHooks) && innerHooks.length > 0) {
|
|
3156
|
-
const command = innerHooks[0]?.command || "";
|
|
3157
|
-
// 현재 프로젝트의 semo-hooks 경로와 비교
|
|
3158
|
-
if (!command.includes(hooksDir) && !command.includes("semo-hooks")) {
|
|
3159
|
-
invalidPathHooks.push(hookName);
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
}
|
|
3164
|
-
if (missingHooks.length > 0) {
|
|
3165
|
-
console.log(chalk_1.default.yellow(` ⚠️ 누락된 hooks: ${missingHooks.join(", ")}`));
|
|
3166
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable"));
|
|
3167
|
-
}
|
|
3168
|
-
else if (invalidPathHooks.length > 0) {
|
|
3169
|
-
console.log(chalk_1.default.yellow(` ⚠️ 경로 불일치: ${invalidPathHooks.join(", ")}`));
|
|
3170
|
-
console.log(chalk_1.default.gray(" 💡 해결: semo hooks enable (다른 프로젝트 설정 감지)"));
|
|
3171
|
-
}
|
|
3172
|
-
else {
|
|
3173
|
-
console.log(chalk_1.default.green(" ✅ hooks 설정 완료 (4개 hook 등록됨)"));
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3177
|
-
catch {
|
|
3178
|
-
console.log(chalk_1.default.red(" ❌ settings.local.json 파싱 오류"));
|
|
3179
|
-
}
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
// 5. semo-mcp (MCP 서버) 상태 확인
|
|
3183
|
-
console.log(chalk_1.default.cyan("\n5. semo-mcp (MCP 서버)"));
|
|
3184
|
-
const userHomeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
3185
|
-
const claudeSettingsPath = path.join(userHomeDir, ".claude", "settings.local.json");
|
|
3186
|
-
// MCP 서버 설정 확인
|
|
3187
|
-
if (!fs.existsSync(claudeSettingsPath)) {
|
|
3188
|
-
console.log(chalk_1.default.yellow(" ⚠️ settings.local.json 없음"));
|
|
3189
|
-
console.log(chalk_1.default.gray(" 💡 MCP 서버 설정이 필요합니다"));
|
|
3190
|
-
}
|
|
3191
|
-
else {
|
|
3192
|
-
try {
|
|
3193
|
-
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf-8"));
|
|
3194
|
-
const mcpServers = settings.mcpServers || {};
|
|
3195
|
-
// semo-integrations MCP 서버 확인
|
|
3196
|
-
const semoMcp = mcpServers["semo-integrations"];
|
|
3197
|
-
if (!semoMcp) {
|
|
3198
|
-
console.log(chalk_1.default.yellow(" ⚠️ semo-integrations MCP 서버 미등록"));
|
|
3199
|
-
console.log(chalk_1.default.gray(" 💡 .claude/settings.json에 MCP 서버 추가 필요"));
|
|
3200
|
-
}
|
|
3201
|
-
else {
|
|
3202
|
-
console.log(chalk_1.default.green(" ✅ semo-integrations MCP 서버 등록됨"));
|
|
3203
|
-
// 명령어 경로 확인
|
|
3204
|
-
const mcpCommand = semoMcp.command || "";
|
|
3205
|
-
const mcpArgs = semoMcp.args || [];
|
|
3206
|
-
if (mcpCommand === "npx") {
|
|
3207
|
-
console.log(chalk_1.default.green(" ✅ npx 방식 실행 (자동 업데이트)"));
|
|
3208
|
-
}
|
|
3209
|
-
else if (mcpCommand === "node") {
|
|
3210
|
-
const scriptPath = mcpArgs[0] || "";
|
|
3211
|
-
if (scriptPath && fs.existsSync(scriptPath)) {
|
|
3212
|
-
console.log(chalk_1.default.green(` ✅ 로컬 스크립트: ${scriptPath}`));
|
|
3213
|
-
}
|
|
3214
|
-
else if (scriptPath) {
|
|
3215
|
-
console.log(chalk_1.default.red(` ❌ 스크립트 경로 없음: ${scriptPath}`));
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
// 환경변수 확인
|
|
3219
|
-
const env = semoMcp.env || {};
|
|
3220
|
-
const requiredEnvVars = ["SLACK_BOT_TOKEN", "GITHUB_TOKEN"];
|
|
3221
|
-
const missingEnvVars = [];
|
|
3222
|
-
const configuredEnvVars = [];
|
|
3223
|
-
for (const envVar of requiredEnvVars) {
|
|
3224
|
-
if (env[envVar]) {
|
|
3225
|
-
configuredEnvVars.push(envVar);
|
|
3226
|
-
}
|
|
3227
|
-
else {
|
|
3228
|
-
missingEnvVars.push(envVar);
|
|
3229
|
-
}
|
|
3230
|
-
}
|
|
3231
|
-
if (configuredEnvVars.length > 0) {
|
|
3232
|
-
console.log(chalk_1.default.green(` ✅ 환경변수: ${configuredEnvVars.join(", ")}`));
|
|
3233
|
-
}
|
|
3234
|
-
if (missingEnvVars.length > 0) {
|
|
3235
|
-
console.log(chalk_1.default.yellow(` ⚠️ 미설정 환경변수: ${missingEnvVars.join(", ")}`));
|
|
3236
|
-
console.log(chalk_1.default.gray(" 💡 일부 기능이 제한될 수 있습니다"));
|
|
3237
|
-
}
|
|
3238
|
-
// 도구 목록 (알려진 도구)
|
|
3239
|
-
const knownTools = [
|
|
3240
|
-
"slack_send_message",
|
|
3241
|
-
"slack_lookup_user",
|
|
3242
|
-
"slack_list_channels",
|
|
3243
|
-
"slack_find_channel",
|
|
3244
|
-
"github_create_issue",
|
|
3245
|
-
"github_create_pr",
|
|
3246
|
-
"supabase_query",
|
|
3247
|
-
];
|
|
3248
|
-
console.log(chalk_1.default.gray(` 📦 제공 도구: ${knownTools.length}개`));
|
|
3249
|
-
}
|
|
3250
|
-
// 다른 MCP 서버 확인
|
|
3251
|
-
const otherServers = Object.keys(mcpServers).filter(k => k !== "semo-integrations");
|
|
3252
|
-
if (otherServers.length > 0) {
|
|
3253
|
-
console.log(chalk_1.default.gray(` 📡 기타 MCP 서버: ${otherServers.join(", ")}`));
|
|
3254
|
-
}
|
|
3255
|
-
}
|
|
3256
|
-
catch {
|
|
3257
|
-
console.log(chalk_1.default.red(" ❌ settings.local.json 파싱 오류"));
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
// 6. 전체 설치 검증
|
|
3261
|
-
console.log(chalk_1.default.cyan("\n6. 전체 설치 검증"));
|
|
3330
|
+
// 4. 설치 검증
|
|
3331
|
+
console.log(chalk_1.default.cyan("\n4. 전체 설치 검증"));
|
|
3262
3332
|
const verificationResult = verifyInstallation(cwd, []);
|
|
3263
3333
|
if (verificationResult.success) {
|
|
3264
3334
|
console.log(chalk_1.default.green(" ✅ 설치 상태 정상"));
|