@team-semicolon/semo-cli 3.0.13 → 3.0.15
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 +254 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ const child_process_1 = require("child_process");
|
|
|
59
59
|
const fs = __importStar(require("fs"));
|
|
60
60
|
const path = __importStar(require("path"));
|
|
61
61
|
const os = __importStar(require("os"));
|
|
62
|
-
const VERSION = "3.0.
|
|
62
|
+
const VERSION = "3.0.14";
|
|
63
63
|
const PACKAGE_NAME = "@team-semicolon/semo-cli";
|
|
64
64
|
// === 버전 비교 유틸리티 ===
|
|
65
65
|
/**
|
|
@@ -110,6 +110,21 @@ const isWindows = os.platform() === "win32";
|
|
|
110
110
|
* Junction은 관리자 권한 없이 디렉토리 링크를 생성할 수 있음
|
|
111
111
|
*/
|
|
112
112
|
function createSymlinkOrJunction(targetPath, linkPath) {
|
|
113
|
+
// 이미 존재하는 링크/파일 제거 (깨진 심볼릭 링크도 처리)
|
|
114
|
+
try {
|
|
115
|
+
const stats = fs.lstatSync(linkPath);
|
|
116
|
+
if (stats.isSymbolicLink() || stats.isFile() || stats.isDirectory()) {
|
|
117
|
+
try {
|
|
118
|
+
fs.unlinkSync(linkPath);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
removeRecursive(linkPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// 파일이 존재하지 않음 - 정상
|
|
127
|
+
}
|
|
113
128
|
if (isWindows) {
|
|
114
129
|
// Windows: Junction 사용 (절대 경로 필요)
|
|
115
130
|
const absoluteTarget = path.resolve(targetPath);
|
|
@@ -472,8 +487,16 @@ program
|
|
|
472
487
|
if (extensionsToInstall.length > 0) {
|
|
473
488
|
await setupExtensionSymlinks(cwd, extensionsToInstall);
|
|
474
489
|
}
|
|
490
|
+
// 11. 설치 검증
|
|
491
|
+
const verificationResult = verifyInstallation(cwd, extensionsToInstall);
|
|
492
|
+
printVerificationResult(verificationResult);
|
|
475
493
|
// 완료 메시지
|
|
476
|
-
|
|
494
|
+
if (verificationResult.success) {
|
|
495
|
+
console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
console.log(chalk_1.default.yellow.bold("\n⚠️ SEMO 설치 완료 (일부 문제 발견)\n"));
|
|
499
|
+
}
|
|
477
500
|
console.log(chalk_1.default.cyan("설치된 구성:"));
|
|
478
501
|
console.log(chalk_1.default.gray(" [Standard]"));
|
|
479
502
|
console.log(chalk_1.default.gray(" ✓ semo-core (원칙, 오케스트레이터)"));
|
|
@@ -594,6 +617,226 @@ async function createStandardSymlinks(cwd) {
|
|
|
594
617
|
console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
|
|
595
618
|
}
|
|
596
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* 설치 상태를 검증하고 문제점을 리포트
|
|
622
|
+
*/
|
|
623
|
+
function verifyInstallation(cwd, installedExtensions = []) {
|
|
624
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
625
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
626
|
+
const result = {
|
|
627
|
+
success: true,
|
|
628
|
+
errors: [],
|
|
629
|
+
warnings: [],
|
|
630
|
+
stats: {
|
|
631
|
+
agents: { expected: 0, linked: 0, broken: 0 },
|
|
632
|
+
skills: { expected: 0, linked: 0, broken: 0 },
|
|
633
|
+
commands: { exists: false, valid: false },
|
|
634
|
+
extensions: [],
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
// 1. semo-system 기본 구조 검증
|
|
638
|
+
if (!fs.existsSync(semoSystemDir)) {
|
|
639
|
+
result.errors.push("semo-system 디렉토리가 없습니다");
|
|
640
|
+
result.success = false;
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
const coreDir = path.join(semoSystemDir, "semo-core");
|
|
644
|
+
const skillsDir = path.join(semoSystemDir, "semo-skills");
|
|
645
|
+
if (!fs.existsSync(coreDir)) {
|
|
646
|
+
result.errors.push("semo-core가 설치되지 않았습니다");
|
|
647
|
+
result.success = false;
|
|
648
|
+
}
|
|
649
|
+
if (!fs.existsSync(skillsDir)) {
|
|
650
|
+
result.errors.push("semo-skills가 설치되지 않았습니다");
|
|
651
|
+
result.success = false;
|
|
652
|
+
}
|
|
653
|
+
// 2. agents 링크 검증
|
|
654
|
+
const claudeAgentsDir = path.join(claudeDir, "agents");
|
|
655
|
+
const coreAgentsDir = path.join(coreDir, "agents");
|
|
656
|
+
if (fs.existsSync(coreAgentsDir)) {
|
|
657
|
+
const expectedAgents = fs.readdirSync(coreAgentsDir).filter(f => fs.statSync(path.join(coreAgentsDir, f)).isDirectory());
|
|
658
|
+
result.stats.agents.expected = expectedAgents.length;
|
|
659
|
+
if (fs.existsSync(claudeAgentsDir)) {
|
|
660
|
+
for (const agent of expectedAgents) {
|
|
661
|
+
const linkPath = path.join(claudeAgentsDir, agent);
|
|
662
|
+
if (fs.existsSync(linkPath)) {
|
|
663
|
+
if (fs.lstatSync(linkPath).isSymbolicLink()) {
|
|
664
|
+
try {
|
|
665
|
+
fs.readlinkSync(linkPath);
|
|
666
|
+
const targetExists = fs.existsSync(linkPath);
|
|
667
|
+
if (targetExists) {
|
|
668
|
+
result.stats.agents.linked++;
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
result.stats.agents.broken++;
|
|
672
|
+
result.warnings.push(`깨진 링크: .claude/agents/${agent}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
result.stats.agents.broken++;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
result.stats.agents.linked++; // 디렉토리로 복사된 경우
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// 3. skills 링크 검증
|
|
687
|
+
if (fs.existsSync(skillsDir)) {
|
|
688
|
+
const expectedSkills = fs.readdirSync(skillsDir).filter(f => fs.statSync(path.join(skillsDir, f)).isDirectory());
|
|
689
|
+
result.stats.skills.expected = expectedSkills.length;
|
|
690
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
691
|
+
if (fs.existsSync(claudeSkillsDir)) {
|
|
692
|
+
for (const skill of expectedSkills) {
|
|
693
|
+
const linkPath = path.join(claudeSkillsDir, skill);
|
|
694
|
+
if (fs.existsSync(linkPath)) {
|
|
695
|
+
if (fs.lstatSync(linkPath).isSymbolicLink()) {
|
|
696
|
+
try {
|
|
697
|
+
fs.readlinkSync(linkPath);
|
|
698
|
+
const targetExists = fs.existsSync(linkPath);
|
|
699
|
+
if (targetExists) {
|
|
700
|
+
result.stats.skills.linked++;
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
result.stats.skills.broken++;
|
|
704
|
+
result.warnings.push(`깨진 링크: .claude/skills/${skill}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
result.stats.skills.broken++;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
result.stats.skills.linked++;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// 4. commands 검증
|
|
719
|
+
const semoCommandsLink = path.join(claudeDir, "commands", "SEMO");
|
|
720
|
+
result.stats.commands.exists = fs.existsSync(semoCommandsLink);
|
|
721
|
+
if (result.stats.commands.exists) {
|
|
722
|
+
if (fs.lstatSync(semoCommandsLink).isSymbolicLink()) {
|
|
723
|
+
result.stats.commands.valid = fs.existsSync(semoCommandsLink);
|
|
724
|
+
if (!result.stats.commands.valid) {
|
|
725
|
+
result.warnings.push("깨진 링크: .claude/commands/SEMO");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
result.stats.commands.valid = true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// 5. Extensions 검증
|
|
733
|
+
for (const ext of installedExtensions) {
|
|
734
|
+
const extDir = path.join(semoSystemDir, ext);
|
|
735
|
+
const extResult = { name: ext, valid: true, issues: [] };
|
|
736
|
+
if (!fs.existsSync(extDir)) {
|
|
737
|
+
extResult.valid = false;
|
|
738
|
+
extResult.issues.push("디렉토리 없음");
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
// Extension agents 검증
|
|
742
|
+
const extAgentsDir = path.join(extDir, "agents");
|
|
743
|
+
if (fs.existsSync(extAgentsDir)) {
|
|
744
|
+
const extAgents = fs.readdirSync(extAgentsDir).filter(f => fs.statSync(path.join(extAgentsDir, f)).isDirectory());
|
|
745
|
+
for (const agent of extAgents) {
|
|
746
|
+
const linkPath = path.join(claudeAgentsDir, agent);
|
|
747
|
+
if (!fs.existsSync(linkPath)) {
|
|
748
|
+
extResult.issues.push(`agent 링크 누락: ${agent}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
// Extension skills 검증
|
|
753
|
+
const extSkillsDir = path.join(extDir, "skills");
|
|
754
|
+
if (fs.existsSync(extSkillsDir)) {
|
|
755
|
+
const extSkills = fs.readdirSync(extSkillsDir).filter(f => fs.statSync(path.join(extSkillsDir, f)).isDirectory());
|
|
756
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
757
|
+
for (const skill of extSkills) {
|
|
758
|
+
const linkPath = path.join(claudeSkillsDir, skill);
|
|
759
|
+
if (!fs.existsSync(linkPath)) {
|
|
760
|
+
extResult.issues.push(`skill 링크 누락: ${skill}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (extResult.issues.length > 0) {
|
|
766
|
+
extResult.valid = false;
|
|
767
|
+
}
|
|
768
|
+
result.stats.extensions.push(extResult);
|
|
769
|
+
}
|
|
770
|
+
// 6. 최종 성공 여부 판단
|
|
771
|
+
if (result.stats.agents.expected > 0 && result.stats.agents.linked === 0) {
|
|
772
|
+
result.errors.push("agents가 하나도 링크되지 않았습니다");
|
|
773
|
+
result.success = false;
|
|
774
|
+
}
|
|
775
|
+
if (result.stats.skills.expected > 0 && result.stats.skills.linked === 0) {
|
|
776
|
+
result.errors.push("skills가 하나도 링크되지 않았습니다");
|
|
777
|
+
result.success = false;
|
|
778
|
+
}
|
|
779
|
+
if (!result.stats.commands.exists) {
|
|
780
|
+
result.errors.push("commands/SEMO가 설치되지 않았습니다");
|
|
781
|
+
result.success = false;
|
|
782
|
+
}
|
|
783
|
+
// 부분 누락 경고
|
|
784
|
+
if (result.stats.agents.linked < result.stats.agents.expected) {
|
|
785
|
+
const missing = result.stats.agents.expected - result.stats.agents.linked;
|
|
786
|
+
result.warnings.push(`${missing}개 agent 링크 누락`);
|
|
787
|
+
}
|
|
788
|
+
if (result.stats.skills.linked < result.stats.skills.expected) {
|
|
789
|
+
const missing = result.stats.skills.expected - result.stats.skills.linked;
|
|
790
|
+
result.warnings.push(`${missing}개 skill 링크 누락`);
|
|
791
|
+
}
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 검증 결과를 콘솔에 출력
|
|
796
|
+
*/
|
|
797
|
+
function printVerificationResult(result) {
|
|
798
|
+
console.log(chalk_1.default.cyan("\n🔍 설치 검증"));
|
|
799
|
+
// Stats
|
|
800
|
+
const agentStatus = result.stats.agents.linked === result.stats.agents.expected
|
|
801
|
+
? chalk_1.default.green("✓")
|
|
802
|
+
: (result.stats.agents.linked > 0 ? chalk_1.default.yellow("△") : chalk_1.default.red("✗"));
|
|
803
|
+
const skillStatus = result.stats.skills.linked === result.stats.skills.expected
|
|
804
|
+
? chalk_1.default.green("✓")
|
|
805
|
+
: (result.stats.skills.linked > 0 ? chalk_1.default.yellow("△") : chalk_1.default.red("✗"));
|
|
806
|
+
const cmdStatus = result.stats.commands.valid ? chalk_1.default.green("✓") : chalk_1.default.red("✗");
|
|
807
|
+
console.log(` ${agentStatus} agents: ${result.stats.agents.linked}/${result.stats.agents.expected}` +
|
|
808
|
+
(result.stats.agents.broken > 0 ? chalk_1.default.red(` (깨진 링크: ${result.stats.agents.broken})`) : ""));
|
|
809
|
+
console.log(` ${skillStatus} skills: ${result.stats.skills.linked}/${result.stats.skills.expected}` +
|
|
810
|
+
(result.stats.skills.broken > 0 ? chalk_1.default.red(` (깨진 링크: ${result.stats.skills.broken})`) : ""));
|
|
811
|
+
console.log(` ${cmdStatus} commands/SEMO`);
|
|
812
|
+
// Extensions
|
|
813
|
+
for (const ext of result.stats.extensions) {
|
|
814
|
+
const extStatus = ext.valid ? chalk_1.default.green("✓") : chalk_1.default.yellow("△");
|
|
815
|
+
console.log(` ${extStatus} ${ext.name}` +
|
|
816
|
+
(ext.issues.length > 0 ? chalk_1.default.gray(` (${ext.issues.length}개 이슈)`) : ""));
|
|
817
|
+
}
|
|
818
|
+
// Warnings
|
|
819
|
+
if (result.warnings.length > 0) {
|
|
820
|
+
console.log(chalk_1.default.yellow("\n ⚠️ 경고:"));
|
|
821
|
+
result.warnings.forEach(w => console.log(chalk_1.default.yellow(` - ${w}`)));
|
|
822
|
+
}
|
|
823
|
+
// Errors
|
|
824
|
+
if (result.errors.length > 0) {
|
|
825
|
+
console.log(chalk_1.default.red("\n ❌ 오류:"));
|
|
826
|
+
result.errors.forEach(e => console.log(chalk_1.default.red(` - ${e}`)));
|
|
827
|
+
}
|
|
828
|
+
// Final status
|
|
829
|
+
if (result.success && result.warnings.length === 0) {
|
|
830
|
+
console.log(chalk_1.default.green.bold("\n ✅ 설치 검증 완료 - 모든 항목 정상"));
|
|
831
|
+
}
|
|
832
|
+
else if (result.success) {
|
|
833
|
+
console.log(chalk_1.default.yellow.bold("\n ⚠️ 설치 완료 - 일부 경고 확인 필요"));
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
console.log(chalk_1.default.red.bold("\n ❌ 설치 검증 실패 - 오류 확인 필요"));
|
|
837
|
+
console.log(chalk_1.default.gray(" 'semo init --force'로 재설치하거나 수동으로 문제를 해결하세요."));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
597
840
|
// === Extensions 다운로드 (심볼릭 링크 제외) ===
|
|
598
841
|
async function downloadExtensions(cwd, packages, force) {
|
|
599
842
|
console.log(chalk_1.default.cyan("\n📦 Extensions 다운로드"));
|
|
@@ -1914,7 +2157,15 @@ program
|
|
|
1914
2157
|
}
|
|
1915
2158
|
}
|
|
1916
2159
|
}
|
|
1917
|
-
|
|
2160
|
+
// === 설치 검증 ===
|
|
2161
|
+
const verificationResult = verifyInstallation(cwd, installedExtensions);
|
|
2162
|
+
printVerificationResult(verificationResult);
|
|
2163
|
+
if (verificationResult.success) {
|
|
2164
|
+
console.log(chalk_1.default.green.bold("\n✅ SEMO 업데이트 완료!\n"));
|
|
2165
|
+
}
|
|
2166
|
+
else {
|
|
2167
|
+
console.log(chalk_1.default.yellow.bold("\n⚠️ SEMO 업데이트 완료 (일부 문제 발견)\n"));
|
|
2168
|
+
}
|
|
1918
2169
|
});
|
|
1919
2170
|
// === -v 옵션 처리 (program.parse 전에 직접 처리) ===
|
|
1920
2171
|
async function main() {
|