@team-semicolon/semo-cli 3.0.12 → 3.0.14

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 +274 -14
  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.12";
62
+ const VERSION = "3.0.14";
63
63
  const PACKAGE_NAME = "@team-semicolon/semo-cli";
64
64
  // === 버전 비교 유틸리티 ===
65
65
  /**
@@ -472,8 +472,16 @@ program
472
472
  if (extensionsToInstall.length > 0) {
473
473
  await setupExtensionSymlinks(cwd, extensionsToInstall);
474
474
  }
475
+ // 11. 설치 검증
476
+ const verificationResult = verifyInstallation(cwd, extensionsToInstall);
477
+ printVerificationResult(verificationResult);
475
478
  // 완료 메시지
476
- console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
479
+ if (verificationResult.success) {
480
+ console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
481
+ }
482
+ else {
483
+ console.log(chalk_1.default.yellow.bold("\n⚠️ SEMO 설치 완료 (일부 문제 발견)\n"));
484
+ }
477
485
  console.log(chalk_1.default.cyan("설치된 구성:"));
478
486
  console.log(chalk_1.default.gray(" [Standard]"));
479
487
  console.log(chalk_1.default.gray(" ✓ semo-core (원칙, 오케스트레이터)"));
@@ -594,6 +602,226 @@ async function createStandardSymlinks(cwd) {
594
602
  console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
595
603
  }
596
604
  }
605
+ /**
606
+ * 설치 상태를 검증하고 문제점을 리포트
607
+ */
608
+ function verifyInstallation(cwd, installedExtensions = []) {
609
+ const claudeDir = path.join(cwd, ".claude");
610
+ const semoSystemDir = path.join(cwd, "semo-system");
611
+ const result = {
612
+ success: true,
613
+ errors: [],
614
+ warnings: [],
615
+ stats: {
616
+ agents: { expected: 0, linked: 0, broken: 0 },
617
+ skills: { expected: 0, linked: 0, broken: 0 },
618
+ commands: { exists: false, valid: false },
619
+ extensions: [],
620
+ },
621
+ };
622
+ // 1. semo-system 기본 구조 검증
623
+ if (!fs.existsSync(semoSystemDir)) {
624
+ result.errors.push("semo-system 디렉토리가 없습니다");
625
+ result.success = false;
626
+ return result;
627
+ }
628
+ const coreDir = path.join(semoSystemDir, "semo-core");
629
+ const skillsDir = path.join(semoSystemDir, "semo-skills");
630
+ if (!fs.existsSync(coreDir)) {
631
+ result.errors.push("semo-core가 설치되지 않았습니다");
632
+ result.success = false;
633
+ }
634
+ if (!fs.existsSync(skillsDir)) {
635
+ result.errors.push("semo-skills가 설치되지 않았습니다");
636
+ result.success = false;
637
+ }
638
+ // 2. agents 링크 검증
639
+ const claudeAgentsDir = path.join(claudeDir, "agents");
640
+ const coreAgentsDir = path.join(coreDir, "agents");
641
+ if (fs.existsSync(coreAgentsDir)) {
642
+ const expectedAgents = fs.readdirSync(coreAgentsDir).filter(f => fs.statSync(path.join(coreAgentsDir, f)).isDirectory());
643
+ result.stats.agents.expected = expectedAgents.length;
644
+ if (fs.existsSync(claudeAgentsDir)) {
645
+ for (const agent of expectedAgents) {
646
+ const linkPath = path.join(claudeAgentsDir, agent);
647
+ if (fs.existsSync(linkPath)) {
648
+ if (fs.lstatSync(linkPath).isSymbolicLink()) {
649
+ try {
650
+ fs.readlinkSync(linkPath);
651
+ const targetExists = fs.existsSync(linkPath);
652
+ if (targetExists) {
653
+ result.stats.agents.linked++;
654
+ }
655
+ else {
656
+ result.stats.agents.broken++;
657
+ result.warnings.push(`깨진 링크: .claude/agents/${agent}`);
658
+ }
659
+ }
660
+ catch {
661
+ result.stats.agents.broken++;
662
+ }
663
+ }
664
+ else {
665
+ result.stats.agents.linked++; // 디렉토리로 복사된 경우
666
+ }
667
+ }
668
+ }
669
+ }
670
+ }
671
+ // 3. skills 링크 검증
672
+ if (fs.existsSync(skillsDir)) {
673
+ const expectedSkills = fs.readdirSync(skillsDir).filter(f => fs.statSync(path.join(skillsDir, f)).isDirectory());
674
+ result.stats.skills.expected = expectedSkills.length;
675
+ const claudeSkillsDir = path.join(claudeDir, "skills");
676
+ if (fs.existsSync(claudeSkillsDir)) {
677
+ for (const skill of expectedSkills) {
678
+ const linkPath = path.join(claudeSkillsDir, skill);
679
+ if (fs.existsSync(linkPath)) {
680
+ if (fs.lstatSync(linkPath).isSymbolicLink()) {
681
+ try {
682
+ fs.readlinkSync(linkPath);
683
+ const targetExists = fs.existsSync(linkPath);
684
+ if (targetExists) {
685
+ result.stats.skills.linked++;
686
+ }
687
+ else {
688
+ result.stats.skills.broken++;
689
+ result.warnings.push(`깨진 링크: .claude/skills/${skill}`);
690
+ }
691
+ }
692
+ catch {
693
+ result.stats.skills.broken++;
694
+ }
695
+ }
696
+ else {
697
+ result.stats.skills.linked++;
698
+ }
699
+ }
700
+ }
701
+ }
702
+ }
703
+ // 4. commands 검증
704
+ const semoCommandsLink = path.join(claudeDir, "commands", "SEMO");
705
+ result.stats.commands.exists = fs.existsSync(semoCommandsLink);
706
+ if (result.stats.commands.exists) {
707
+ if (fs.lstatSync(semoCommandsLink).isSymbolicLink()) {
708
+ result.stats.commands.valid = fs.existsSync(semoCommandsLink);
709
+ if (!result.stats.commands.valid) {
710
+ result.warnings.push("깨진 링크: .claude/commands/SEMO");
711
+ }
712
+ }
713
+ else {
714
+ result.stats.commands.valid = true;
715
+ }
716
+ }
717
+ // 5. Extensions 검증
718
+ for (const ext of installedExtensions) {
719
+ const extDir = path.join(semoSystemDir, ext);
720
+ const extResult = { name: ext, valid: true, issues: [] };
721
+ if (!fs.existsSync(extDir)) {
722
+ extResult.valid = false;
723
+ extResult.issues.push("디렉토리 없음");
724
+ }
725
+ else {
726
+ // Extension agents 검증
727
+ const extAgentsDir = path.join(extDir, "agents");
728
+ if (fs.existsSync(extAgentsDir)) {
729
+ const extAgents = fs.readdirSync(extAgentsDir).filter(f => fs.statSync(path.join(extAgentsDir, f)).isDirectory());
730
+ for (const agent of extAgents) {
731
+ const linkPath = path.join(claudeAgentsDir, agent);
732
+ if (!fs.existsSync(linkPath)) {
733
+ extResult.issues.push(`agent 링크 누락: ${agent}`);
734
+ }
735
+ }
736
+ }
737
+ // Extension skills 검증
738
+ const extSkillsDir = path.join(extDir, "skills");
739
+ if (fs.existsSync(extSkillsDir)) {
740
+ const extSkills = fs.readdirSync(extSkillsDir).filter(f => fs.statSync(path.join(extSkillsDir, f)).isDirectory());
741
+ const claudeSkillsDir = path.join(claudeDir, "skills");
742
+ for (const skill of extSkills) {
743
+ const linkPath = path.join(claudeSkillsDir, skill);
744
+ if (!fs.existsSync(linkPath)) {
745
+ extResult.issues.push(`skill 링크 누락: ${skill}`);
746
+ }
747
+ }
748
+ }
749
+ }
750
+ if (extResult.issues.length > 0) {
751
+ extResult.valid = false;
752
+ }
753
+ result.stats.extensions.push(extResult);
754
+ }
755
+ // 6. 최종 성공 여부 판단
756
+ if (result.stats.agents.expected > 0 && result.stats.agents.linked === 0) {
757
+ result.errors.push("agents가 하나도 링크되지 않았습니다");
758
+ result.success = false;
759
+ }
760
+ if (result.stats.skills.expected > 0 && result.stats.skills.linked === 0) {
761
+ result.errors.push("skills가 하나도 링크되지 않았습니다");
762
+ result.success = false;
763
+ }
764
+ if (!result.stats.commands.exists) {
765
+ result.errors.push("commands/SEMO가 설치되지 않았습니다");
766
+ result.success = false;
767
+ }
768
+ // 부분 누락 경고
769
+ if (result.stats.agents.linked < result.stats.agents.expected) {
770
+ const missing = result.stats.agents.expected - result.stats.agents.linked;
771
+ result.warnings.push(`${missing}개 agent 링크 누락`);
772
+ }
773
+ if (result.stats.skills.linked < result.stats.skills.expected) {
774
+ const missing = result.stats.skills.expected - result.stats.skills.linked;
775
+ result.warnings.push(`${missing}개 skill 링크 누락`);
776
+ }
777
+ return result;
778
+ }
779
+ /**
780
+ * 검증 결과를 콘솔에 출력
781
+ */
782
+ function printVerificationResult(result) {
783
+ console.log(chalk_1.default.cyan("\n🔍 설치 검증"));
784
+ // Stats
785
+ const agentStatus = result.stats.agents.linked === result.stats.agents.expected
786
+ ? chalk_1.default.green("✓")
787
+ : (result.stats.agents.linked > 0 ? chalk_1.default.yellow("△") : chalk_1.default.red("✗"));
788
+ const skillStatus = result.stats.skills.linked === result.stats.skills.expected
789
+ ? chalk_1.default.green("✓")
790
+ : (result.stats.skills.linked > 0 ? chalk_1.default.yellow("△") : chalk_1.default.red("✗"));
791
+ const cmdStatus = result.stats.commands.valid ? chalk_1.default.green("✓") : chalk_1.default.red("✗");
792
+ console.log(` ${agentStatus} agents: ${result.stats.agents.linked}/${result.stats.agents.expected}` +
793
+ (result.stats.agents.broken > 0 ? chalk_1.default.red(` (깨진 링크: ${result.stats.agents.broken})`) : ""));
794
+ console.log(` ${skillStatus} skills: ${result.stats.skills.linked}/${result.stats.skills.expected}` +
795
+ (result.stats.skills.broken > 0 ? chalk_1.default.red(` (깨진 링크: ${result.stats.skills.broken})`) : ""));
796
+ console.log(` ${cmdStatus} commands/SEMO`);
797
+ // Extensions
798
+ for (const ext of result.stats.extensions) {
799
+ const extStatus = ext.valid ? chalk_1.default.green("✓") : chalk_1.default.yellow("△");
800
+ console.log(` ${extStatus} ${ext.name}` +
801
+ (ext.issues.length > 0 ? chalk_1.default.gray(` (${ext.issues.length}개 이슈)`) : ""));
802
+ }
803
+ // Warnings
804
+ if (result.warnings.length > 0) {
805
+ console.log(chalk_1.default.yellow("\n ⚠️ 경고:"));
806
+ result.warnings.forEach(w => console.log(chalk_1.default.yellow(` - ${w}`)));
807
+ }
808
+ // Errors
809
+ if (result.errors.length > 0) {
810
+ console.log(chalk_1.default.red("\n ❌ 오류:"));
811
+ result.errors.forEach(e => console.log(chalk_1.default.red(` - ${e}`)));
812
+ }
813
+ // Final status
814
+ if (result.success && result.warnings.length === 0) {
815
+ console.log(chalk_1.default.green.bold("\n ✅ 설치 검증 완료 - 모든 항목 정상"));
816
+ }
817
+ else if (result.success) {
818
+ console.log(chalk_1.default.yellow.bold("\n ⚠️ 설치 완료 - 일부 경고 확인 필요"));
819
+ }
820
+ else {
821
+ console.log(chalk_1.default.red.bold("\n ❌ 설치 검증 실패 - 오류 확인 필요"));
822
+ console.log(chalk_1.default.gray(" 'semo init --force'로 재설치하거나 수동으로 문제를 해결하세요."));
823
+ }
824
+ }
597
825
  // === Extensions 다운로드 (심볼릭 링크 제외) ===
598
826
  async function downloadExtensions(cwd, packages, force) {
599
827
  console.log(chalk_1.default.cyan("\n📦 Extensions 다운로드"));
@@ -1723,13 +1951,19 @@ program
1723
1951
  .option("--self", "CLI만 업데이트")
1724
1952
  .option("--system", "semo-system만 업데이트")
1725
1953
  .option("--skip-cli", "CLI 업데이트 건너뛰기")
1954
+ .option("--only <packages>", "특정 패키지만 업데이트 (쉼표 구분: semo-core,semo-skills,biz/management)")
1726
1955
  .action(async (options) => {
1727
1956
  console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 업데이트\n"));
1728
1957
  const cwd = process.cwd();
1729
1958
  const semoSystemDir = path.join(cwd, "semo-system");
1730
1959
  const claudeDir = path.join(cwd, ".claude");
1960
+ // --only 옵션 파싱
1961
+ const onlyPackages = options.only
1962
+ ? options.only.split(",").map((p) => p.trim())
1963
+ : [];
1964
+ const isSelectiveUpdate = onlyPackages.length > 0;
1731
1965
  // === 1. CLI 자체 업데이트 ===
1732
- if (options.self || (!options.system && !options.skipCli)) {
1966
+ if (options.self || (!options.system && !options.skipCli && !isSelectiveUpdate)) {
1733
1967
  console.log(chalk_1.default.cyan("📦 CLI 업데이트"));
1734
1968
  const cliSpinner = (0, ora_1.default)(" @team-semicolon/semo-cli 업데이트 중...").start();
1735
1969
  try {
@@ -1765,25 +1999,43 @@ program
1765
1999
  installedExtensions.push(key);
1766
2000
  }
1767
2001
  }
2002
+ // 업데이트 대상 결정
2003
+ const updateSemoCore = !isSelectiveUpdate || onlyPackages.includes("semo-core");
2004
+ const updateSemoSkills = !isSelectiveUpdate || onlyPackages.includes("semo-skills");
2005
+ const extensionsToUpdate = isSelectiveUpdate
2006
+ ? installedExtensions.filter(ext => onlyPackages.includes(ext))
2007
+ : installedExtensions;
1768
2008
  console.log(chalk_1.default.cyan("\n📚 semo-system 업데이트"));
1769
2009
  console.log(chalk_1.default.gray(" 대상:"));
1770
- console.log(chalk_1.default.gray(" - semo-core"));
1771
- console.log(chalk_1.default.gray(" - semo-skills"));
1772
- installedExtensions.forEach(pkg => {
2010
+ if (updateSemoCore)
2011
+ console.log(chalk_1.default.gray(" - semo-core"));
2012
+ if (updateSemoSkills)
2013
+ console.log(chalk_1.default.gray(" - semo-skills"));
2014
+ extensionsToUpdate.forEach(pkg => {
1773
2015
  console.log(chalk_1.default.gray(` - ${pkg}`));
1774
2016
  });
2017
+ if (!updateSemoCore && !updateSemoSkills && extensionsToUpdate.length === 0) {
2018
+ console.log(chalk_1.default.yellow("\n ⚠️ 업데이트할 패키지가 없습니다."));
2019
+ console.log(chalk_1.default.gray(" 설치된 패키지: semo-core, semo-skills" +
2020
+ (installedExtensions.length > 0 ? ", " + installedExtensions.join(", ") : "")));
2021
+ return;
2022
+ }
1775
2023
  const spinner = (0, ora_1.default)("\n 최신 버전 다운로드 중...").start();
1776
2024
  try {
1777
2025
  const tempDir = path.join(cwd, ".semo-temp");
1778
2026
  removeRecursive(tempDir);
1779
2027
  (0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
1780
- // Standard 업데이트
1781
- removeRecursive(path.join(semoSystemDir, "semo-core"));
1782
- removeRecursive(path.join(semoSystemDir, "semo-skills"));
1783
- copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
1784
- copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
1785
- // Extensions 업데이트
1786
- for (const pkg of installedExtensions) {
2028
+ // Standard 업데이트 (선택적)
2029
+ if (updateSemoCore) {
2030
+ removeRecursive(path.join(semoSystemDir, "semo-core"));
2031
+ copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
2032
+ }
2033
+ if (updateSemoSkills) {
2034
+ removeRecursive(path.join(semoSystemDir, "semo-skills"));
2035
+ copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
2036
+ }
2037
+ // Extensions 업데이트 (선택적)
2038
+ for (const pkg of extensionsToUpdate) {
1787
2039
  const srcPath = path.join(tempDir, "packages", pkg);
1788
2040
  const destPath = path.join(semoSystemDir, pkg);
1789
2041
  if (fs.existsSync(srcPath)) {
@@ -1890,7 +2142,15 @@ program
1890
2142
  }
1891
2143
  }
1892
2144
  }
1893
- console.log(chalk_1.default.green.bold("\n✅ SEMO 업데이트 완료!\n"));
2145
+ // === 설치 검증 ===
2146
+ const verificationResult = verifyInstallation(cwd, installedExtensions);
2147
+ printVerificationResult(verificationResult);
2148
+ if (verificationResult.success) {
2149
+ console.log(chalk_1.default.green.bold("\n✅ SEMO 업데이트 완료!\n"));
2150
+ }
2151
+ else {
2152
+ console.log(chalk_1.default.yellow.bold("\n⚠️ SEMO 업데이트 완료 (일부 문제 발견)\n"));
2153
+ }
1894
2154
  });
1895
2155
  // === -v 옵션 처리 (program.parse 전에 직접 처리) ===
1896
2156
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "3.0.12",
3
+ "version": "3.0.14",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {