@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.
Files changed (2) hide show
  1. package/dist/index.js +254 -3
  2. 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.13";
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
- console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
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
- console.log(chalk_1.default.green.bold("\n✅ SEMO 업데이트 완료!\n"));
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "3.0.13",
3
+ "version": "3.0.15",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {