@team-semicolon/semo-cli 3.4.0 → 3.6.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 +78 -272
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -235,48 +235,9 @@ async function showVersionComparison(cwd) {
|
|
|
235
235
|
level: 0,
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
|
-
//
|
|
239
|
-
// 그룹별로 묶어서 계층 구조로 출력
|
|
238
|
+
// Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
|
|
240
239
|
if (hasSemoSystem) {
|
|
241
|
-
for (const
|
|
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) {
|
|
240
|
+
for (const key of Object.keys(EXTENSION_PACKAGES)) {
|
|
280
241
|
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
281
242
|
if (fs.existsSync(extVersionPath)) {
|
|
282
243
|
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
@@ -481,6 +442,23 @@ function detectLegacyEnvironment(cwd) {
|
|
|
481
442
|
legacyPaths.push(dir);
|
|
482
443
|
}
|
|
483
444
|
}
|
|
445
|
+
// semo-system/ 내부의 레거시 Extension 구조 확인
|
|
446
|
+
// 구버전: semo-system/biz/, semo-system/eng/, semo-system/ops/ (그룹 디렉토리)
|
|
447
|
+
// 신버전: semo-system/biz/design/, semo-system/eng/nextjs/ 등 (개별 패키지)
|
|
448
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
449
|
+
if (fs.existsSync(semoSystemDir)) {
|
|
450
|
+
const legacyExtGroups = ["biz", "eng", "ops"];
|
|
451
|
+
for (const group of legacyExtGroups) {
|
|
452
|
+
const groupDir = path.join(semoSystemDir, group);
|
|
453
|
+
if (fs.existsSync(groupDir) && fs.statSync(groupDir).isDirectory()) {
|
|
454
|
+
// VERSION 파일이 그룹 디렉토리에 직접 있으면 레거시 구조
|
|
455
|
+
const groupVersionFile = path.join(groupDir, "VERSION");
|
|
456
|
+
if (fs.existsSync(groupVersionFile)) {
|
|
457
|
+
legacyPaths.push(`semo-system/${group} (레거시 그룹 구조)`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
484
462
|
// .claude/ 내부의 레거시 구조 확인
|
|
485
463
|
const claudeDir = path.join(cwd, ".claude");
|
|
486
464
|
if (fs.existsSync(claudeDir)) {
|
|
@@ -563,10 +541,28 @@ async function migrateLegacyEnvironment(cwd) {
|
|
|
563
541
|
}
|
|
564
542
|
}
|
|
565
543
|
}
|
|
566
|
-
// 3.
|
|
544
|
+
// 3. semo-system/ 내부의 레거시 Extension 그룹 삭제
|
|
567
545
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
568
546
|
if (fs.existsSync(semoSystemDir)) {
|
|
569
|
-
|
|
547
|
+
const legacyExtGroups = ["biz", "eng", "ops"];
|
|
548
|
+
for (const group of legacyExtGroups) {
|
|
549
|
+
const groupDir = path.join(semoSystemDir, group);
|
|
550
|
+
const groupVersionFile = path.join(groupDir, "VERSION");
|
|
551
|
+
// VERSION 파일이 그룹 디렉토리에 직접 있으면 레거시 구조이므로 삭제
|
|
552
|
+
if (fs.existsSync(groupVersionFile)) {
|
|
553
|
+
removeRecursive(groupDir);
|
|
554
|
+
console.log(chalk_1.default.gray(` ✓ semo-system/${group}/ 삭제됨 (레거시 그룹 구조)`));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// 4. 기존 semo-system이 완전히 레거시인 경우에만 삭제
|
|
559
|
+
// (Standard 패키지가 없는 경우)
|
|
560
|
+
if (fs.existsSync(semoSystemDir)) {
|
|
561
|
+
const hasStandard = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
562
|
+
if (!hasStandard) {
|
|
563
|
+
removeRecursive(semoSystemDir);
|
|
564
|
+
console.log(chalk_1.default.gray(` ✓ semo-system/ 삭제됨 (완전 재설치)`));
|
|
565
|
+
}
|
|
570
566
|
}
|
|
571
567
|
spinner.succeed("레거시 환경 정리 완료");
|
|
572
568
|
console.log(chalk_1.default.green(" → 새 환경으로 설치를 진행합니다.\n"));
|
|
@@ -615,92 +611,39 @@ function copyRecursive(src, dest) {
|
|
|
615
611
|
}
|
|
616
612
|
}
|
|
617
613
|
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
618
|
-
//
|
|
614
|
+
// Extension 패키지 정의 (통합 구조)
|
|
619
615
|
const EXTENSION_PACKAGES = {
|
|
620
|
-
|
|
621
|
-
"
|
|
622
|
-
"
|
|
623
|
-
"biz/management": { name: "Management", desc: "일정/인력/스프린트 관리", layer: "biz", detect: [] },
|
|
624
|
-
"biz/poc": { name: "PoC", desc: "빠른 PoC, 패스트트랙", layer: "biz", detect: [] },
|
|
625
|
-
// Engineering Layer
|
|
626
|
-
"eng/nextjs": { name: "Next.js", desc: "Next.js 프론트엔드 개발", layer: "eng", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
|
|
627
|
-
"eng/spring": { name: "Spring", desc: "Spring Boot 백엔드 개발", layer: "eng", detect: ["pom.xml", "build.gradle"] },
|
|
628
|
-
"eng/ms": { name: "Microservice", desc: "마이크로서비스 아키텍처", layer: "eng", detect: [] },
|
|
629
|
-
"eng/infra": { name: "Infra", desc: "인프라/배포 관리", layer: "eng", detect: ["docker-compose.yml", "Dockerfile"] },
|
|
630
|
-
// Operations Layer
|
|
631
|
-
"ops/qa": { name: "QA", desc: "테스트/품질 관리", layer: "ops", detect: [] },
|
|
632
|
-
"ops/monitor": { name: "Monitor", desc: "서비스 현황 모니터링", layer: "ops", detect: [] },
|
|
633
|
-
"ops/improve": { name: "Improve", desc: "개선 제안", layer: "ops", detect: [] },
|
|
634
|
-
// Meta
|
|
635
|
-
meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리", layer: "meta", detect: ["semo-core", "semo-skills"] },
|
|
636
|
-
// System (semo-system 하위 패키지)
|
|
637
|
-
"semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템", layer: "system", detect: [] },
|
|
638
|
-
"semo-remote": { name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)", layer: "system", detect: [] },
|
|
616
|
+
meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리" },
|
|
617
|
+
"semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템" },
|
|
618
|
+
"semo-remote": { name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)" },
|
|
639
619
|
};
|
|
640
620
|
// 단축명 → 전체 패키지 경로 매핑
|
|
641
621
|
const SHORTNAME_MAPPING = {
|
|
642
|
-
// 하위 패키지명 단축 (discovery → biz/discovery)
|
|
643
|
-
discovery: "biz/discovery",
|
|
644
|
-
design: "biz/design",
|
|
645
|
-
management: "biz/management",
|
|
646
|
-
poc: "biz/poc",
|
|
647
|
-
nextjs: "eng/nextjs",
|
|
648
|
-
spring: "eng/spring",
|
|
649
|
-
ms: "eng/ms",
|
|
650
|
-
infra: "eng/infra",
|
|
651
|
-
qa: "ops/qa",
|
|
652
|
-
monitor: "ops/monitor",
|
|
653
|
-
improve: "ops/improve",
|
|
654
|
-
// 추가 별칭
|
|
655
|
-
next: "eng/nextjs",
|
|
656
|
-
backend: "eng/spring",
|
|
657
|
-
mvp: "biz/poc",
|
|
658
|
-
// System 패키지 단축명
|
|
659
622
|
hooks: "semo-hooks",
|
|
660
623
|
remote: "semo-remote",
|
|
661
624
|
};
|
|
662
|
-
//
|
|
663
|
-
const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
|
|
664
|
-
// 그룹명 → 해당 그룹의 모든 패키지 반환
|
|
665
|
-
function getPackagesByGroup(group) {
|
|
666
|
-
return Object.entries(EXTENSION_PACKAGES)
|
|
667
|
-
.filter(([, pkg]) => pkg.layer === group)
|
|
668
|
-
.map(([key]) => key);
|
|
669
|
-
}
|
|
670
|
-
// 패키지 입력을 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
625
|
+
// 패키지 입력을 해석
|
|
671
626
|
function resolvePackageInput(input) {
|
|
672
627
|
// 쉼표로 구분된 여러 패키지 처리
|
|
673
628
|
const parts = input.split(",").map(p => p.trim()).filter(p => p);
|
|
674
629
|
const resolvedPackages = [];
|
|
675
|
-
let isGroup = false;
|
|
676
|
-
let groupName;
|
|
677
630
|
for (const part of parts) {
|
|
678
|
-
// 1.
|
|
679
|
-
if (PACKAGE_GROUPS.includes(part)) {
|
|
680
|
-
const groupPackages = getPackagesByGroup(part);
|
|
681
|
-
resolvedPackages.push(...groupPackages);
|
|
682
|
-
isGroup = true;
|
|
683
|
-
groupName = part;
|
|
684
|
-
continue;
|
|
685
|
-
}
|
|
686
|
-
// 2. 단축명 매핑 확인 (discovery → biz/discovery 등)
|
|
631
|
+
// 1. 단축명 매핑 확인 (hooks → semo-hooks 등)
|
|
687
632
|
if (part in SHORTNAME_MAPPING) {
|
|
688
633
|
resolvedPackages.push(SHORTNAME_MAPPING[part]);
|
|
689
634
|
continue;
|
|
690
635
|
}
|
|
691
|
-
//
|
|
636
|
+
// 2. 직접 패키지명 확인
|
|
692
637
|
if (part in EXTENSION_PACKAGES) {
|
|
693
638
|
resolvedPackages.push(part);
|
|
694
639
|
continue;
|
|
695
640
|
}
|
|
696
|
-
//
|
|
641
|
+
// 3. 유효하지 않은 패키지명
|
|
697
642
|
// (빈 배열 대신 null을 추가하여 나중에 에러 처리)
|
|
698
643
|
}
|
|
699
644
|
// 중복 제거
|
|
700
645
|
return {
|
|
701
646
|
packages: [...new Set(resolvedPackages)],
|
|
702
|
-
isGroup,
|
|
703
|
-
groupName
|
|
704
647
|
};
|
|
705
648
|
}
|
|
706
649
|
const program = new commander_1.Command();
|
|
@@ -761,47 +704,10 @@ async function showVersionInfo() {
|
|
|
761
704
|
level: 0,
|
|
762
705
|
});
|
|
763
706
|
}
|
|
764
|
-
// 4.
|
|
707
|
+
// 4. Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
|
|
765
708
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
766
709
|
if (fs.existsSync(semoSystemDir)) {
|
|
767
|
-
for (const
|
|
768
|
-
const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
|
|
769
|
-
const hasGroupVersion = fs.existsSync(groupVersionPath);
|
|
770
|
-
// 해당 그룹의 하위 패키지 찾기
|
|
771
|
-
const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
|
|
772
|
-
const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
|
|
773
|
-
if (hasGroupVersion || installedGroupExtensions.length > 0) {
|
|
774
|
-
// 그룹 패키지 버전 추가
|
|
775
|
-
if (hasGroupVersion) {
|
|
776
|
-
const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
|
|
777
|
-
const remoteGroup = await getRemotePackageVersion(group);
|
|
778
|
-
versionInfos.push({
|
|
779
|
-
name: group,
|
|
780
|
-
local: localGroup,
|
|
781
|
-
remote: remoteGroup,
|
|
782
|
-
needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
|
|
783
|
-
level: 1,
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
// 하위 Extension 패키지들 추가
|
|
787
|
-
for (const key of installedGroupExtensions) {
|
|
788
|
-
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
789
|
-
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
790
|
-
const remoteExt = await getRemotePackageVersion(key);
|
|
791
|
-
versionInfos.push({
|
|
792
|
-
name: key,
|
|
793
|
-
local: localExt,
|
|
794
|
-
remote: remoteExt,
|
|
795
|
-
needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
|
|
796
|
-
level: 2,
|
|
797
|
-
group: group,
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
// 그룹에 속하지 않는 Extension (meta 등)
|
|
803
|
-
const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
|
|
804
|
-
for (const key of nonGroupExtensions) {
|
|
710
|
+
for (const key of Object.keys(EXTENSION_PACKAGES)) {
|
|
805
711
|
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
806
712
|
if (fs.existsSync(extVersionPath)) {
|
|
807
713
|
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
@@ -960,18 +866,6 @@ async function confirmOverwrite(itemName, itemPath) {
|
|
|
960
866
|
]);
|
|
961
867
|
return shouldOverwrite;
|
|
962
868
|
}
|
|
963
|
-
function detectProjectType(cwd) {
|
|
964
|
-
const detected = [];
|
|
965
|
-
for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
|
|
966
|
-
for (const file of pkg.detect) {
|
|
967
|
-
if (fs.existsSync(path.join(cwd, file))) {
|
|
968
|
-
detected.push(key);
|
|
969
|
-
break;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
return detected;
|
|
974
|
-
}
|
|
975
869
|
// === 설치된 Extension 패키지 스캔 ===
|
|
976
870
|
function getInstalledExtensions(cwd) {
|
|
977
871
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
@@ -1101,56 +995,17 @@ program
|
|
|
1101
995
|
spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
|
|
1102
996
|
process.exit(1);
|
|
1103
997
|
}
|
|
1104
|
-
// 2.
|
|
1105
|
-
const detected = detectProjectType(cwd);
|
|
998
|
+
// 2. Extension 패키지 처리 (--with 옵션으로만 지정 가능)
|
|
1106
999
|
let extensionsToInstall = [];
|
|
1107
1000
|
if (options.with) {
|
|
1108
1001
|
extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
});
|
|
1115
|
-
const { installDetected } = await inquirer_1.default.prompt([
|
|
1116
|
-
{
|
|
1117
|
-
type: "confirm",
|
|
1118
|
-
name: "installDetected",
|
|
1119
|
-
message: "감지된 패키지를 함께 설치할까요?",
|
|
1120
|
-
default: true,
|
|
1121
|
-
},
|
|
1122
|
-
]);
|
|
1123
|
-
if (installDetected) {
|
|
1124
|
-
extensionsToInstall = detected;
|
|
1002
|
+
if (extensionsToInstall.length > 0) {
|
|
1003
|
+
console.log(chalk_1.default.cyan("\n📦 추가 Extension 설치:"));
|
|
1004
|
+
extensionsToInstall.forEach(pkg => {
|
|
1005
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
1006
|
+
});
|
|
1125
1007
|
}
|
|
1126
1008
|
}
|
|
1127
|
-
else {
|
|
1128
|
-
// 프로젝트 유형이 감지되지 않은 경우 패키지 선택 프롬프트
|
|
1129
|
-
console.log(chalk_1.default.cyan("\n📦 추가 패키지 선택"));
|
|
1130
|
-
console.log(chalk_1.default.gray(" 기본 설치 (semo-core + semo-skills) 외에 추가할 패키지를 선택하세요.\n"));
|
|
1131
|
-
// 그룹별로 패키지 구성
|
|
1132
|
-
const packageChoices = [
|
|
1133
|
-
new inquirer_1.default.Separator(chalk_1.default.yellow("── Engineering ──")),
|
|
1134
|
-
{ name: `eng/nextjs - ${EXTENSION_PACKAGES["eng/nextjs"].desc}`, value: "eng/nextjs" },
|
|
1135
|
-
{ name: `eng/spring - ${EXTENSION_PACKAGES["eng/spring"].desc}`, value: "eng/spring" },
|
|
1136
|
-
{ name: `eng/infra - ${EXTENSION_PACKAGES["eng/infra"].desc}`, value: "eng/infra" },
|
|
1137
|
-
new inquirer_1.default.Separator(chalk_1.default.yellow("── Business ──")),
|
|
1138
|
-
{ name: `biz/discovery - ${EXTENSION_PACKAGES["biz/discovery"].desc}`, value: "biz/discovery" },
|
|
1139
|
-
{ name: `biz/management - ${EXTENSION_PACKAGES["biz/management"].desc}`, value: "biz/management" },
|
|
1140
|
-
{ name: `biz/design - ${EXTENSION_PACKAGES["biz/design"].desc}`, value: "biz/design" },
|
|
1141
|
-
new inquirer_1.default.Separator(chalk_1.default.yellow("── Operations ──")),
|
|
1142
|
-
{ name: `ops/qa - ${EXTENSION_PACKAGES["ops/qa"].desc}`, value: "ops/qa" },
|
|
1143
|
-
];
|
|
1144
|
-
const { selectedPackages } = await inquirer_1.default.prompt([
|
|
1145
|
-
{
|
|
1146
|
-
type: "checkbox",
|
|
1147
|
-
name: "selectedPackages",
|
|
1148
|
-
message: "설치할 패키지 선택 (Space로 선택, Enter로 완료):",
|
|
1149
|
-
choices: packageChoices,
|
|
1150
|
-
},
|
|
1151
|
-
]);
|
|
1152
|
-
extensionsToInstall = selectedPackages;
|
|
1153
|
-
}
|
|
1154
1009
|
// 3. .claude 디렉토리 생성
|
|
1155
1010
|
const claudeDir = path.join(cwd, ".claude");
|
|
1156
1011
|
if (!fs.existsSync(claudeDir)) {
|
|
@@ -1207,8 +1062,8 @@ program
|
|
|
1207
1062
|
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
1208
1063
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
1209
1064
|
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
|
|
1210
|
-
if (extensionsToInstall.length === 0
|
|
1211
|
-
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add
|
|
1065
|
+
if (extensionsToInstall.length === 0) {
|
|
1066
|
+
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add meta)"));
|
|
1212
1067
|
}
|
|
1213
1068
|
console.log();
|
|
1214
1069
|
});
|
|
@@ -2458,27 +2313,9 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2458
2313
|
const extensionsList = extensions.length > 0
|
|
2459
2314
|
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
2460
2315
|
: "";
|
|
2461
|
-
//
|
|
2316
|
+
// 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2462
2317
|
let packageClaudeMdSections = "";
|
|
2463
|
-
//
|
|
2464
|
-
const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
|
|
2465
|
-
// 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
|
|
2466
|
-
for (const group of installedGroups) {
|
|
2467
|
-
const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
|
|
2468
|
-
if (fs.existsSync(groupClaudeMdPath)) {
|
|
2469
|
-
const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
|
|
2470
|
-
// 중복 제거 후 고유 콘텐츠만 추출
|
|
2471
|
-
const uniqueContent = extractUniqueContent(groupContent, group);
|
|
2472
|
-
// 헤더 레벨 조정 (# → ##, ## → ###)
|
|
2473
|
-
const adjustedContent = uniqueContent
|
|
2474
|
-
.replace(/^# /gm, "## ")
|
|
2475
|
-
.replace(/^## /gm, "### ")
|
|
2476
|
-
.replace(/^### /gm, "#### ");
|
|
2477
|
-
packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
|
|
2478
|
-
console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
// 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
|
|
2318
|
+
// 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
|
|
2482
2319
|
for (const pkg of extensions) {
|
|
2483
2320
|
const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
|
|
2484
2321
|
if (fs.existsSync(pkgClaudeMdPath)) {
|
|
@@ -2645,7 +2482,7 @@ ${packageClaudeMdSections}
|
|
|
2645
2482
|
// === add 명령어 ===
|
|
2646
2483
|
program
|
|
2647
2484
|
.command("add <packages>")
|
|
2648
|
-
.description("Extension 패키지를 추가로 설치합니다 (
|
|
2485
|
+
.description("Extension 패키지를 추가로 설치합니다 (meta, semo-hooks, semo-remote)")
|
|
2649
2486
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
2650
2487
|
.action(async (packagesInput, options) => {
|
|
2651
2488
|
const cwd = process.cwd();
|
|
@@ -2654,25 +2491,16 @@ program
|
|
|
2654
2491
|
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
2655
2492
|
process.exit(1);
|
|
2656
2493
|
}
|
|
2657
|
-
// 패키지 입력 해석
|
|
2658
|
-
const { packages
|
|
2494
|
+
// 패키지 입력 해석
|
|
2495
|
+
const { packages } = resolvePackageInput(packagesInput);
|
|
2659
2496
|
if (packages.length === 0) {
|
|
2660
2497
|
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
|
|
2661
|
-
console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
|
|
2662
2498
|
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
|
|
2663
2499
|
console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
|
|
2664
2500
|
process.exit(1);
|
|
2665
2501
|
}
|
|
2666
|
-
//
|
|
2667
|
-
if (
|
|
2668
|
-
console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
|
|
2669
|
-
console.log(chalk_1.default.gray(" 포함된 패키지:"));
|
|
2670
|
-
for (const pkg of packages) {
|
|
2671
|
-
console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
|
|
2672
|
-
}
|
|
2673
|
-
console.log();
|
|
2674
|
-
}
|
|
2675
|
-
else if (packages.length === 1) {
|
|
2502
|
+
// 패키지 설치 안내
|
|
2503
|
+
if (packages.length === 1) {
|
|
2676
2504
|
// 단일 패키지
|
|
2677
2505
|
const pkg = packages[0];
|
|
2678
2506
|
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
|
|
@@ -2738,50 +2566,28 @@ program
|
|
|
2738
2566
|
.action(() => {
|
|
2739
2567
|
const cwd = process.cwd();
|
|
2740
2568
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
2741
|
-
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지
|
|
2742
|
-
// Standard
|
|
2569
|
+
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록\n"));
|
|
2570
|
+
// Standard (필수)
|
|
2743
2571
|
console.log(chalk_1.default.white.bold("Standard (필수)"));
|
|
2744
2572
|
const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
2745
2573
|
const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
|
|
2746
2574
|
console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
|
|
2747
2575
|
console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
|
|
2748
2576
|
console.log();
|
|
2749
|
-
// Extensions
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
const
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
|
|
2762
|
-
for (const [key, pkg] of layerPackages) {
|
|
2763
|
-
const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
|
|
2764
|
-
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
2765
|
-
const displayKey = key.includes("/") ? key.split("/")[1] : key;
|
|
2766
|
-
console.log(` ${status} ${chalk_1.default.cyan(displayKey)} - ${pkg.desc}`);
|
|
2767
|
-
console.log(chalk_1.default.gray(` semo add ${key}`));
|
|
2768
|
-
}
|
|
2769
|
-
console.log();
|
|
2577
|
+
// Extensions
|
|
2578
|
+
console.log(chalk_1.default.white.bold("Extensions (선택)"));
|
|
2579
|
+
const extensionList = [
|
|
2580
|
+
{ key: "meta", name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리" },
|
|
2581
|
+
{ key: "semo-hooks", name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템" },
|
|
2582
|
+
{ key: "semo-remote", name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)" },
|
|
2583
|
+
];
|
|
2584
|
+
for (const ext of extensionList) {
|
|
2585
|
+
const isInstalled = fs.existsSync(path.join(semoSystemDir, ext.key));
|
|
2586
|
+
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
2587
|
+
console.log(` ${status} ${chalk_1.default.cyan(ext.key)} - ${ext.desc}`);
|
|
2588
|
+
console.log(chalk_1.default.gray(` semo add ${ext.key}`));
|
|
2770
2589
|
}
|
|
2771
|
-
// 그룹 설치 안내
|
|
2772
|
-
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2773
|
-
console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
|
|
2774
|
-
console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
|
|
2775
|
-
console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
|
|
2776
|
-
console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
|
|
2777
|
-
console.log(chalk_1.default.gray(" semo add system → System 전체 (hooks, remote)"));
|
|
2778
2590
|
console.log();
|
|
2779
|
-
// 단축명 안내
|
|
2780
|
-
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2781
|
-
console.log(chalk_1.default.white.bold("⚡ 단축명 지원"));
|
|
2782
|
-
console.log(chalk_1.default.gray(" semo add discovery → biz/discovery"));
|
|
2783
|
-
console.log(chalk_1.default.gray(" semo add qa → ops/qa"));
|
|
2784
|
-
console.log(chalk_1.default.gray(" semo add nextjs → eng/nextjs\n"));
|
|
2785
2591
|
});
|
|
2786
2592
|
// === status 명령어 ===
|
|
2787
2593
|
program
|