@team-semicolon/semo-cli 3.5.0 → 3.7.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.
Files changed (2) hide show
  1. package/dist/index.js +168 -270
  2. 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
- // 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
239
- // 그룹별로 묶어서 계층 구조로 출력
238
+ // Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
240
239
  if (hasSemoSystem) {
241
- for (const group of PACKAGE_GROUPS) {
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();
@@ -650,92 +611,39 @@ function copyRecursive(src, dest) {
650
611
  }
651
612
  }
652
613
  const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
653
- // 확장 패키지 정의 (v3.0 구조)
614
+ // Extension 패키지 정의 (통합 구조)
654
615
  const EXTENSION_PACKAGES = {
655
- // Business Layer
656
- "biz/discovery": { name: "Discovery", desc: "아이템 발굴, 시장 조사, Epic/Task", layer: "biz", detect: [] },
657
- "biz/design": { name: "Design", desc: "컨셉 설계, 목업, UX", layer: "biz", detect: [] },
658
- "biz/management": { name: "Management", desc: "일정/인력/스프린트 관리", layer: "biz", detect: [] },
659
- "biz/poc": { name: "PoC", desc: "빠른 PoC, 패스트트랙", layer: "biz", detect: [] },
660
- // Engineering Layer
661
- "eng/nextjs": { name: "Next.js", desc: "Next.js 프론트엔드 개발", layer: "eng", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
662
- "eng/spring": { name: "Spring", desc: "Spring Boot 백엔드 개발", layer: "eng", detect: ["pom.xml", "build.gradle"] },
663
- "eng/ms": { name: "Microservice", desc: "마이크로서비스 아키텍처", layer: "eng", detect: [] },
664
- "eng/infra": { name: "Infra", desc: "인프라/배포 관리", layer: "eng", detect: ["docker-compose.yml", "Dockerfile"] },
665
- // Operations Layer
666
- "ops/qa": { name: "QA", desc: "테스트/품질 관리", layer: "ops", detect: [] },
667
- "ops/monitor": { name: "Monitor", desc: "서비스 현황 모니터링", layer: "ops", detect: [] },
668
- "ops/improve": { name: "Improve", desc: "개선 제안", layer: "ops", detect: [] },
669
- // Meta
670
- meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리", layer: "meta", detect: ["semo-core", "semo-skills"] },
671
- // System (semo-system 하위 패키지)
672
- "semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템", layer: "system", detect: [] },
673
- "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)" },
674
619
  };
675
620
  // 단축명 → 전체 패키지 경로 매핑
676
621
  const SHORTNAME_MAPPING = {
677
- // 하위 패키지명 단축 (discovery → biz/discovery)
678
- discovery: "biz/discovery",
679
- design: "biz/design",
680
- management: "biz/management",
681
- poc: "biz/poc",
682
- nextjs: "eng/nextjs",
683
- spring: "eng/spring",
684
- ms: "eng/ms",
685
- infra: "eng/infra",
686
- qa: "ops/qa",
687
- monitor: "ops/monitor",
688
- improve: "ops/improve",
689
- // 추가 별칭
690
- next: "eng/nextjs",
691
- backend: "eng/spring",
692
- mvp: "biz/poc",
693
- // System 패키지 단축명
694
622
  hooks: "semo-hooks",
695
623
  remote: "semo-remote",
696
624
  };
697
- // 그룹 이름 목록 (biz, eng, ops, meta, system)
698
- const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
699
- // 그룹명 → 해당 그룹의 모든 패키지 반환
700
- function getPackagesByGroup(group) {
701
- return Object.entries(EXTENSION_PACKAGES)
702
- .filter(([, pkg]) => pkg.layer === group)
703
- .map(([key]) => key);
704
- }
705
- // 패키지 입력을 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
625
+ // 패키지 입력을 해석
706
626
  function resolvePackageInput(input) {
707
627
  // 쉼표로 구분된 여러 패키지 처리
708
628
  const parts = input.split(",").map(p => p.trim()).filter(p => p);
709
629
  const resolvedPackages = [];
710
- let isGroup = false;
711
- let groupName;
712
630
  for (const part of parts) {
713
- // 1. 그룹명인지 확인 (biz, eng, ops, meta)
714
- if (PACKAGE_GROUPS.includes(part)) {
715
- const groupPackages = getPackagesByGroup(part);
716
- resolvedPackages.push(...groupPackages);
717
- isGroup = true;
718
- groupName = part;
719
- continue;
720
- }
721
- // 2. 단축명 매핑 확인 (discovery → biz/discovery 등)
631
+ // 1. 단축명 매핑 확인 (hooks semo-hooks )
722
632
  if (part in SHORTNAME_MAPPING) {
723
633
  resolvedPackages.push(SHORTNAME_MAPPING[part]);
724
634
  continue;
725
635
  }
726
- // 3. 직접 패키지명 확인
636
+ // 2. 직접 패키지명 확인
727
637
  if (part in EXTENSION_PACKAGES) {
728
638
  resolvedPackages.push(part);
729
639
  continue;
730
640
  }
731
- // 4. 유효하지 않은 패키지명
641
+ // 3. 유효하지 않은 패키지명
732
642
  // (빈 배열 대신 null을 추가하여 나중에 에러 처리)
733
643
  }
734
644
  // 중복 제거
735
645
  return {
736
646
  packages: [...new Set(resolvedPackages)],
737
- isGroup,
738
- groupName
739
647
  };
740
648
  }
741
649
  const program = new commander_1.Command();
@@ -796,47 +704,10 @@ async function showVersionInfo() {
796
704
  level: 0,
797
705
  });
798
706
  }
799
- // 4. 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
707
+ // 4. Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
800
708
  const semoSystemDir = path.join(cwd, "semo-system");
801
709
  if (fs.existsSync(semoSystemDir)) {
802
- for (const group of PACKAGE_GROUPS) {
803
- const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
804
- const hasGroupVersion = fs.existsSync(groupVersionPath);
805
- // 해당 그룹의 하위 패키지 찾기
806
- const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
807
- const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
808
- if (hasGroupVersion || installedGroupExtensions.length > 0) {
809
- // 그룹 패키지 버전 추가
810
- if (hasGroupVersion) {
811
- const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
812
- const remoteGroup = await getRemotePackageVersion(group);
813
- versionInfos.push({
814
- name: group,
815
- local: localGroup,
816
- remote: remoteGroup,
817
- needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
818
- level: 1,
819
- });
820
- }
821
- // 하위 Extension 패키지들 추가
822
- for (const key of installedGroupExtensions) {
823
- const extVersionPath = path.join(semoSystemDir, key, "VERSION");
824
- const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
825
- const remoteExt = await getRemotePackageVersion(key);
826
- versionInfos.push({
827
- name: key,
828
- local: localExt,
829
- remote: remoteExt,
830
- needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
831
- level: 2,
832
- group: group,
833
- });
834
- }
835
- }
836
- }
837
- // 그룹에 속하지 않는 Extension (meta 등)
838
- const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
839
- for (const key of nonGroupExtensions) {
710
+ for (const key of Object.keys(EXTENSION_PACKAGES)) {
840
711
  const extVersionPath = path.join(semoSystemDir, key, "VERSION");
841
712
  if (fs.existsSync(extVersionPath)) {
842
713
  const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
@@ -995,18 +866,6 @@ async function confirmOverwrite(itemName, itemPath) {
995
866
  ]);
996
867
  return shouldOverwrite;
997
868
  }
998
- function detectProjectType(cwd) {
999
- const detected = [];
1000
- for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
1001
- for (const file of pkg.detect) {
1002
- if (fs.existsSync(path.join(cwd, file))) {
1003
- detected.push(key);
1004
- break;
1005
- }
1006
- }
1007
- }
1008
- return detected;
1009
- }
1010
869
  // === 설치된 Extension 패키지 스캔 ===
1011
870
  function getInstalledExtensions(cwd) {
1012
871
  const semoSystemDir = path.join(cwd, "semo-system");
@@ -1136,56 +995,17 @@ program
1136
995
  spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
1137
996
  process.exit(1);
1138
997
  }
1139
- // 2. 프로젝트 유형 감지
1140
- const detected = detectProjectType(cwd);
998
+ // 2. Extension 패키지 처리 (--with 옵션으로만 지정 가능)
1141
999
  let extensionsToInstall = [];
1142
1000
  if (options.with) {
1143
1001
  extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
1144
- }
1145
- else if (detected.length > 0) {
1146
- console.log(chalk_1.default.cyan("\n📦 감지된 프로젝트 유형:"));
1147
- detected.forEach(pkg => {
1148
- console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
1149
- });
1150
- const { installDetected } = await inquirer_1.default.prompt([
1151
- {
1152
- type: "confirm",
1153
- name: "installDetected",
1154
- message: "감지된 패키지를 함께 설치할까요?",
1155
- default: true,
1156
- },
1157
- ]);
1158
- if (installDetected) {
1159
- 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
+ });
1160
1007
  }
1161
1008
  }
1162
- else {
1163
- // 프로젝트 유형이 감지되지 않은 경우 패키지 선택 프롬프트
1164
- console.log(chalk_1.default.cyan("\n📦 추가 패키지 선택"));
1165
- console.log(chalk_1.default.gray(" 기본 설치 (semo-core + semo-skills) 외에 추가할 패키지를 선택하세요.\n"));
1166
- // 그룹별로 패키지 구성
1167
- const packageChoices = [
1168
- new inquirer_1.default.Separator(chalk_1.default.yellow("── Engineering ──")),
1169
- { name: `eng/nextjs - ${EXTENSION_PACKAGES["eng/nextjs"].desc}`, value: "eng/nextjs" },
1170
- { name: `eng/spring - ${EXTENSION_PACKAGES["eng/spring"].desc}`, value: "eng/spring" },
1171
- { name: `eng/infra - ${EXTENSION_PACKAGES["eng/infra"].desc}`, value: "eng/infra" },
1172
- new inquirer_1.default.Separator(chalk_1.default.yellow("── Business ──")),
1173
- { name: `biz/discovery - ${EXTENSION_PACKAGES["biz/discovery"].desc}`, value: "biz/discovery" },
1174
- { name: `biz/management - ${EXTENSION_PACKAGES["biz/management"].desc}`, value: "biz/management" },
1175
- { name: `biz/design - ${EXTENSION_PACKAGES["biz/design"].desc}`, value: "biz/design" },
1176
- new inquirer_1.default.Separator(chalk_1.default.yellow("── Operations ──")),
1177
- { name: `ops/qa - ${EXTENSION_PACKAGES["ops/qa"].desc}`, value: "ops/qa" },
1178
- ];
1179
- const { selectedPackages } = await inquirer_1.default.prompt([
1180
- {
1181
- type: "checkbox",
1182
- name: "selectedPackages",
1183
- message: "설치할 패키지 선택 (Space로 선택, Enter로 완료):",
1184
- choices: packageChoices,
1185
- },
1186
- ]);
1187
- extensionsToInstall = selectedPackages;
1188
- }
1189
1009
  // 3. .claude 디렉토리 생성
1190
1010
  const claudeDir = path.join(cwd, ".claude");
1191
1011
  if (!fs.existsSync(claudeDir)) {
@@ -1238,12 +1058,22 @@ program
1238
1058
  console.log(chalk_1.default.gray(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
1239
1059
  });
1240
1060
  }
1061
+ // GitHub MCP 토큰 설정 안내
1062
+ if (!isGitHubTokenConfigured()) {
1063
+ console.log(chalk_1.default.yellow("\n⚠️ GitHub MCP 설정 필요"));
1064
+ console.log(chalk_1.default.gray(" GitHub 이슈/PR 생성 기능을 사용하려면 토큰을 설정하세요:"));
1065
+ console.log(chalk_1.default.white("\n export GITHUB_PERSONAL_ACCESS_TOKEN=\"$(gh auth token)\""));
1066
+ console.log(chalk_1.default.gray("\n 영구 설정: ~/.zshrc 또는 ~/.bashrc에 위 명령어 추가"));
1067
+ }
1068
+ else {
1069
+ console.log(chalk_1.default.green("\n✓ GitHub 토큰 감지됨 (MCP 사용 가능)"));
1070
+ }
1241
1071
  console.log(chalk_1.default.cyan("\n다음 단계:"));
1242
1072
  console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
1243
1073
  console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
1244
1074
  console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
1245
- if (extensionsToInstall.length === 0 && detected.length === 0) {
1246
- console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add next)"));
1075
+ if (extensionsToInstall.length === 0) {
1076
+ console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add meta)"));
1247
1077
  }
1248
1078
  console.log();
1249
1079
  });
@@ -1854,8 +1684,29 @@ const BASE_MCP_SERVERS = [
1854
1684
  name: "github",
1855
1685
  command: "npx",
1856
1686
  args: ["-y", "@modelcontextprotocol/server-github"],
1687
+ env: {
1688
+ GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}",
1689
+ },
1857
1690
  },
1858
1691
  ];
1692
+ // === GitHub 토큰 자동 감지 ===
1693
+ function getGitHubTokenFromCLI() {
1694
+ try {
1695
+ const token = (0, child_process_1.execSync)("gh auth token", { stdio: "pipe", encoding: "utf-8" }).trim();
1696
+ return token || null;
1697
+ }
1698
+ catch {
1699
+ return null;
1700
+ }
1701
+ }
1702
+ function isGitHubTokenConfigured() {
1703
+ // 환경변수 확인
1704
+ if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
1705
+ return true;
1706
+ }
1707
+ // gh CLI 토큰 확인
1708
+ return getGitHubTokenFromCLI() !== null;
1709
+ }
1859
1710
  // === Claude MCP 서버 존재 여부 확인 ===
1860
1711
  function isMCPServerRegistered(serverName) {
1861
1712
  try {
@@ -2493,27 +2344,9 @@ async function setupClaudeMd(cwd, extensions, force) {
2493
2344
  const extensionsList = extensions.length > 0
2494
2345
  ? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
2495
2346
  : "";
2496
- // 그룹 및 패키지별 CLAUDE.md 병합 섹션 생성
2347
+ // 패키지별 CLAUDE.md 병합 섹션 생성
2497
2348
  let packageClaudeMdSections = "";
2498
- // 1. 설치된 패키지에서 그룹 추출 (중복 제거)
2499
- const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
2500
- // 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
2501
- for (const group of installedGroups) {
2502
- const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
2503
- if (fs.existsSync(groupClaudeMdPath)) {
2504
- const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
2505
- // 중복 제거 후 고유 콘텐츠만 추출
2506
- const uniqueContent = extractUniqueContent(groupContent, group);
2507
- // 헤더 레벨 조정 (# → ##, ## → ###)
2508
- const adjustedContent = uniqueContent
2509
- .replace(/^# /gm, "## ")
2510
- .replace(/^## /gm, "### ")
2511
- .replace(/^### /gm, "#### ");
2512
- packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
2513
- console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
2514
- }
2515
- }
2516
- // 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
2349
+ // 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
2517
2350
  for (const pkg of extensions) {
2518
2351
  const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
2519
2352
  if (fs.existsSync(pkgClaudeMdPath)) {
@@ -2680,7 +2513,7 @@ ${packageClaudeMdSections}
2680
2513
  // === add 명령어 ===
2681
2514
  program
2682
2515
  .command("add <packages>")
2683
- .description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops, system / 개별: biz/discovery, eng/nextjs, semo-hooks)")
2516
+ .description("Extension 패키지를 추가로 설치합니다 (meta, semo-hooks, semo-remote)")
2684
2517
  .option("-f, --force", "기존 설정 덮어쓰기")
2685
2518
  .action(async (packagesInput, options) => {
2686
2519
  const cwd = process.cwd();
@@ -2689,25 +2522,16 @@ program
2689
2522
  console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
2690
2523
  process.exit(1);
2691
2524
  }
2692
- // 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
2693
- const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
2525
+ // 패키지 입력 해석
2526
+ const { packages } = resolvePackageInput(packagesInput);
2694
2527
  if (packages.length === 0) {
2695
2528
  console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
2696
- console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
2697
2529
  console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
2698
2530
  console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
2699
2531
  process.exit(1);
2700
2532
  }
2701
- // 그룹 설치인 경우 안내
2702
- if (isGroup) {
2703
- console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
2704
- console.log(chalk_1.default.gray(" 포함된 패키지:"));
2705
- for (const pkg of packages) {
2706
- console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
2707
- }
2708
- console.log();
2709
- }
2710
- else if (packages.length === 1) {
2533
+ // 패키지 설치 안내
2534
+ if (packages.length === 1) {
2711
2535
  // 단일 패키지
2712
2536
  const pkg = packages[0];
2713
2537
  console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
@@ -2773,50 +2597,28 @@ program
2773
2597
  .action(() => {
2774
2598
  const cwd = process.cwd();
2775
2599
  const semoSystemDir = path.join(cwd, "semo-system");
2776
- console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.0)\n"));
2777
- // Standard
2600
+ console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록\n"));
2601
+ // Standard (필수)
2778
2602
  console.log(chalk_1.default.white.bold("Standard (필수)"));
2779
2603
  const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
2780
2604
  const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
2781
2605
  console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
2782
2606
  console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
2783
2607
  console.log();
2784
- // Extensions - 레이어별 그룹화
2785
- const layers = {
2786
- biz: { title: "Business Layer", emoji: "💼" },
2787
- eng: { title: "Engineering Layer", emoji: "⚙️" },
2788
- ops: { title: "Operations Layer", emoji: "📊" },
2789
- meta: { title: "Meta", emoji: "🔧" },
2790
- system: { title: "System", emoji: "🔩" },
2791
- };
2792
- for (const [layerKey, layerInfo] of Object.entries(layers)) {
2793
- const layerPackages = Object.entries(EXTENSION_PACKAGES).filter(([, pkg]) => pkg.layer === layerKey);
2794
- if (layerPackages.length === 0)
2795
- continue;
2796
- console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
2797
- for (const [key, pkg] of layerPackages) {
2798
- const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
2799
- const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
2800
- const displayKey = key.includes("/") ? key.split("/")[1] : key;
2801
- console.log(` ${status} ${chalk_1.default.cyan(displayKey)} - ${pkg.desc}`);
2802
- console.log(chalk_1.default.gray(` semo add ${key}`));
2803
- }
2804
- console.log();
2608
+ // Extensions
2609
+ console.log(chalk_1.default.white.bold("Extensions (선택)"));
2610
+ const extensionList = [
2611
+ { key: "meta", name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리" },
2612
+ { key: "semo-hooks", name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템" },
2613
+ { key: "semo-remote", name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)" },
2614
+ ];
2615
+ for (const ext of extensionList) {
2616
+ const isInstalled = fs.existsSync(path.join(semoSystemDir, ext.key));
2617
+ const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
2618
+ console.log(` ${status} ${chalk_1.default.cyan(ext.key)} - ${ext.desc}`);
2619
+ console.log(chalk_1.default.gray(` semo add ${ext.key}`));
2805
2620
  }
2806
- // 그룹 설치 안내
2807
- console.log(chalk_1.default.gray("─".repeat(50)));
2808
- console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
2809
- console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
2810
- console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
2811
- console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
2812
- console.log(chalk_1.default.gray(" semo add system → System 전체 (hooks, remote)"));
2813
2621
  console.log();
2814
- // 단축명 안내
2815
- console.log(chalk_1.default.gray("─".repeat(50)));
2816
- console.log(chalk_1.default.white.bold("⚡ 단축명 지원"));
2817
- console.log(chalk_1.default.gray(" semo add discovery → biz/discovery"));
2818
- console.log(chalk_1.default.gray(" semo add qa → ops/qa"));
2819
- console.log(chalk_1.default.gray(" semo add nextjs → eng/nextjs\n"));
2820
2622
  });
2821
2623
  // === status 명령어 ===
2822
2624
  program
@@ -2876,6 +2678,102 @@ program
2876
2678
  }
2877
2679
  console.log();
2878
2680
  });
2681
+ // === doctor 명령어 ===
2682
+ program
2683
+ .command("doctor")
2684
+ .description("SEMO 및 MCP 설정 문제를 진단합니다")
2685
+ .action(() => {
2686
+ console.log(chalk_1.default.cyan.bold("\n🩺 SEMO 진단\n"));
2687
+ const cwd = process.cwd();
2688
+ let hasIssues = false;
2689
+ // 1. gh CLI 확인
2690
+ console.log(chalk_1.default.white.bold("GitHub CLI:"));
2691
+ try {
2692
+ const ghVersion = (0, child_process_1.execSync)("gh --version", { stdio: "pipe", encoding: "utf-8" }).split("\n")[0];
2693
+ console.log(chalk_1.default.green(` ✓ 설치됨 (${ghVersion.replace("gh version ", "")})`));
2694
+ // gh 인증 상태
2695
+ try {
2696
+ (0, child_process_1.execSync)("gh auth status", { stdio: "pipe" });
2697
+ console.log(chalk_1.default.green(" ✓ 인증됨"));
2698
+ }
2699
+ catch {
2700
+ console.log(chalk_1.default.red(" ✗ 인증 필요: gh auth login"));
2701
+ hasIssues = true;
2702
+ }
2703
+ }
2704
+ catch {
2705
+ console.log(chalk_1.default.red(" ✗ 미설치: brew install gh"));
2706
+ hasIssues = true;
2707
+ }
2708
+ // 2. GitHub 토큰 확인
2709
+ console.log(chalk_1.default.white.bold("\nGitHub MCP 토큰:"));
2710
+ const ghToken = getGitHubTokenFromCLI();
2711
+ const envToken = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
2712
+ if (envToken) {
2713
+ console.log(chalk_1.default.green(" ✓ 환경변수 설정됨 (GITHUB_PERSONAL_ACCESS_TOKEN)"));
2714
+ }
2715
+ else if (ghToken) {
2716
+ console.log(chalk_1.default.yellow(" ⚠ gh CLI 토큰 있음 (환경변수 미설정)"));
2717
+ console.log(chalk_1.default.gray(" GitHub MCP 사용하려면:"));
2718
+ console.log(chalk_1.default.white(' export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"'));
2719
+ hasIssues = true;
2720
+ }
2721
+ else {
2722
+ console.log(chalk_1.default.red(" ✗ 토큰 없음"));
2723
+ console.log(chalk_1.default.gray(" 1. gh auth login 실행"));
2724
+ console.log(chalk_1.default.white(' 2. export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"'));
2725
+ hasIssues = true;
2726
+ }
2727
+ // 3. MCP 서버 등록 상태
2728
+ console.log(chalk_1.default.white.bold("\nMCP 서버:"));
2729
+ const requiredServers = ["semo-integrations", "github", "context7"];
2730
+ for (const server of requiredServers) {
2731
+ if (isMCPServerRegistered(server)) {
2732
+ console.log(chalk_1.default.green(` ✓ ${server}`));
2733
+ }
2734
+ else {
2735
+ console.log(chalk_1.default.yellow(` ⚠ ${server} 미등록`));
2736
+ hasIssues = true;
2737
+ }
2738
+ }
2739
+ // 4. settings.json 확인
2740
+ console.log(chalk_1.default.white.bold("\n설정 파일:"));
2741
+ const settingsPath = path.join(cwd, ".claude", "settings.json");
2742
+ if (fs.existsSync(settingsPath)) {
2743
+ console.log(chalk_1.default.green(" ✓ .claude/settings.json"));
2744
+ try {
2745
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
2746
+ if (settings.mcpServers?.github?.env?.GITHUB_PERSONAL_ACCESS_TOKEN) {
2747
+ console.log(chalk_1.default.green(" ✓ GitHub MCP 환경변수 설정됨"));
2748
+ }
2749
+ else {
2750
+ console.log(chalk_1.default.yellow(" ⚠ GitHub MCP 환경변수 누락"));
2751
+ hasIssues = true;
2752
+ }
2753
+ }
2754
+ catch {
2755
+ console.log(chalk_1.default.yellow(" ⚠ settings.json 파싱 실패"));
2756
+ hasIssues = true;
2757
+ }
2758
+ }
2759
+ else {
2760
+ console.log(chalk_1.default.red(" ✗ .claude/settings.json 없음"));
2761
+ hasIssues = true;
2762
+ }
2763
+ // 결과
2764
+ console.log();
2765
+ if (hasIssues) {
2766
+ console.log(chalk_1.default.yellow.bold("⚠️ 일부 문제가 발견되었습니다."));
2767
+ console.log(chalk_1.default.gray("\nGitHub MCP 빠른 설정:"));
2768
+ console.log(chalk_1.default.white(' export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"'));
2769
+ console.log(chalk_1.default.gray("\n영구 설정 (셸 재시작 후 적용):"));
2770
+ console.log(chalk_1.default.white(' echo \'export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"\' >> ~/.zshrc'));
2771
+ }
2772
+ else {
2773
+ console.log(chalk_1.default.green.bold("✅ 모든 설정이 정상입니다."));
2774
+ }
2775
+ console.log();
2776
+ });
2879
2777
  // === update 명령어 ===
2880
2778
  program
2881
2779
  .command("update")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {