@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.
- package/dist/index.js +274 -14
- 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
|
/**
|
|
@@ -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
|
-
|
|
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
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
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
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
-
|
|
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() {
|