@team-semicolon/semo-cli 3.0.22 → 3.0.26

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 +312 -69
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -166,6 +166,7 @@ async function showVersionComparison(cwd) {
166
166
  local: currentCliVersion,
167
167
  remote: latestCliVersion,
168
168
  needsUpdate: latestCliVersion ? isVersionLower(currentCliVersion, latestCliVersion) : false,
169
+ level: 0,
169
170
  });
170
171
  // semo-core (루트 또는 semo-system 내부)
171
172
  const corePathRoot = path.join(cwd, "semo-core", "VERSION");
@@ -179,6 +180,7 @@ async function showVersionComparison(cwd) {
179
180
  local: localCore,
180
181
  remote: remoteCore,
181
182
  needsUpdate: remoteCore ? isVersionLower(localCore, remoteCore) : false,
183
+ level: 0,
182
184
  });
183
185
  }
184
186
  // semo-skills (루트 또는 semo-system 내부)
@@ -193,11 +195,51 @@ async function showVersionComparison(cwd) {
193
195
  local: localSkills,
194
196
  remote: remoteSkills,
195
197
  needsUpdate: remoteSkills ? isVersionLower(localSkills, remoteSkills) : false,
198
+ level: 0,
196
199
  });
197
200
  }
198
- // Extensions (semo-system 내부)
201
+ // 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
202
+ // 그룹별로 묶어서 계층 구조로 출력
199
203
  if (hasSemoSystem) {
200
- for (const key of Object.keys(EXTENSION_PACKAGES)) {
204
+ for (const group of PACKAGE_GROUPS) {
205
+ const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
206
+ const hasGroupVersion = fs.existsSync(groupVersionPath);
207
+ // 해당 그룹의 하위 패키지 찾기
208
+ const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
209
+ const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
210
+ // 그룹 버전이 있거나 하위 패키지가 설치된 경우에만 표시
211
+ if (hasGroupVersion || installedGroupExtensions.length > 0) {
212
+ // 그룹 패키지 버전 추가
213
+ if (hasGroupVersion) {
214
+ const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
215
+ const remoteGroup = await getRemotePackageVersion(group);
216
+ versionInfos.push({
217
+ name: group,
218
+ local: localGroup,
219
+ remote: remoteGroup,
220
+ needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
221
+ level: 1,
222
+ });
223
+ }
224
+ // 하위 Extension 패키지들 추가
225
+ for (const key of installedGroupExtensions) {
226
+ const extVersionPath = path.join(semoSystemDir, key, "VERSION");
227
+ const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
228
+ const remoteExt = await getRemotePackageVersion(key);
229
+ versionInfos.push({
230
+ name: key,
231
+ local: localExt,
232
+ remote: remoteExt,
233
+ needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
234
+ level: 2,
235
+ group: group,
236
+ });
237
+ }
238
+ }
239
+ }
240
+ // 그룹에 속하지 않는 Extension (meta 등)
241
+ const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
242
+ for (const key of nonGroupExtensions) {
201
243
  const extVersionPath = path.join(semoSystemDir, key, "VERSION");
202
244
  if (fs.existsSync(extVersionPath)) {
203
245
  const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
@@ -207,40 +249,61 @@ async function showVersionComparison(cwd) {
207
249
  local: localExt,
208
250
  remote: remoteExt,
209
251
  needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
252
+ level: 1,
210
253
  });
211
254
  }
212
255
  }
213
256
  }
214
- // packages/ 디렉토리의 설치된 패키지들 (로컬 버전만 표시)
257
+ // packages/ 디렉토리의 설치된 패키지들 (로컬 버전만 표시) - 개발 환경용
215
258
  const packagesDir = path.join(cwd, "packages");
216
259
  if (fs.existsSync(packagesDir)) {
217
- // 패키지 경로 매핑 (표시 이름 → 상대 경로)
218
- const packagePaths = {
219
- "packages/core": "core",
220
- "packages/meta": "meta",
221
- "packages/eng/nextjs": "eng/nextjs",
222
- "packages/eng/spring": "eng/spring",
223
- "packages/eng/ms": "eng/ms",
224
- "packages/eng/infra": "eng/infra",
225
- "packages/biz/discovery": "biz/discovery",
226
- "packages/biz/management": "biz/management",
227
- "packages/biz/design": "biz/design",
228
- "packages/biz/poc": "biz/poc",
229
- "packages/ops/qa": "ops/qa",
230
- "packages/ops/monitor": "ops/monitor",
231
- "packages/ops/improve": "ops/improve",
260
+ // 그룹별 패키지 매핑
261
+ const packageGroups = {
262
+ "packages/core": { level: 0, packages: [{ name: "packages/core", path: "core" }] },
263
+ "packages/meta": { level: 0, packages: [{ name: "packages/meta", path: "meta" }] },
264
+ "packages/eng": {
265
+ level: 1,
266
+ packages: [
267
+ { name: "packages/eng/nextjs", path: "eng/nextjs" },
268
+ { name: "packages/eng/spring", path: "eng/spring" },
269
+ { name: "packages/eng/ms", path: "eng/ms" },
270
+ { name: "packages/eng/infra", path: "eng/infra" },
271
+ ],
272
+ },
273
+ "packages/biz": {
274
+ level: 1,
275
+ packages: [
276
+ { name: "packages/biz/discovery", path: "biz/discovery" },
277
+ { name: "packages/biz/management", path: "biz/management" },
278
+ { name: "packages/biz/design", path: "biz/design" },
279
+ { name: "packages/biz/poc", path: "biz/poc" },
280
+ ],
281
+ },
282
+ "packages/ops": {
283
+ level: 1,
284
+ packages: [
285
+ { name: "packages/ops/qa", path: "ops/qa" },
286
+ { name: "packages/ops/monitor", path: "ops/monitor" },
287
+ { name: "packages/ops/improve", path: "ops/improve" },
288
+ ],
289
+ },
232
290
  };
233
- for (const [displayName, relativePath] of Object.entries(packagePaths)) {
234
- const pkgVersionPath = path.join(packagesDir, relativePath, "VERSION");
235
- if (fs.existsSync(pkgVersionPath)) {
236
- const localPkg = fs.readFileSync(pkgVersionPath, "utf-8").trim();
237
- const remotePkg = await getRemotePackageVersion(`packages/${relativePath}`);
238
- versionInfos.push({
239
- name: displayName,
240
- local: localPkg,
241
- remote: remotePkg,
242
- needsUpdate: remotePkg ? isVersionLower(localPkg, remotePkg) : false,
243
- });
291
+ for (const [groupKey, groupData] of Object.entries(packageGroups)) {
292
+ for (const pkg of groupData.packages) {
293
+ const pkgVersionPath = path.join(packagesDir, pkg.path, "VERSION");
294
+ if (fs.existsSync(pkgVersionPath)) {
295
+ const localPkg = fs.readFileSync(pkgVersionPath, "utf-8").trim();
296
+ const remotePkg = await getRemotePackageVersion(`packages/${pkg.path}`);
297
+ const isSubPackage = pkg.path.includes("/");
298
+ versionInfos.push({
299
+ name: pkg.name,
300
+ local: localPkg,
301
+ remote: remotePkg,
302
+ needsUpdate: remotePkg ? isVersionLower(localPkg, remotePkg) : false,
303
+ level: isSubPackage ? 2 : groupData.level,
304
+ group: isSubPackage ? pkg.path.split("/")[0] : undefined,
305
+ });
306
+ }
244
307
  }
245
308
  }
246
309
  }
@@ -251,7 +314,20 @@ async function showVersionComparison(cwd) {
251
314
  console.log(chalk_1.default.gray(" │ 패키지 │ 설치됨 │ 최신 │ 상태 │"));
252
315
  console.log(chalk_1.default.gray(" ├────────────────────────┼──────────┼──────────┼────────┤"));
253
316
  for (const info of versionInfos) {
254
- const name = info.name.padEnd(22);
317
+ // 계층 구조 표시를 위한 접두사
318
+ let prefix = "";
319
+ let displayName = info.name;
320
+ if (info.level === 1) {
321
+ // 그룹 패키지 (eng, biz, ops)
322
+ prefix = "📦 ";
323
+ }
324
+ else if (info.level === 2) {
325
+ // 하위 패키지
326
+ prefix = " └─ ";
327
+ // 그룹명 제거하고 하위 이름만 표시 (예: biz/discovery → discovery)
328
+ displayName = info.name.includes("/") ? info.name.split("/").pop() || info.name : info.name;
329
+ }
330
+ const name = (prefix + displayName).padEnd(22);
255
331
  const local = (info.local || "-").padEnd(8);
256
332
  const remote = (info.remote || "-").padEnd(8);
257
333
  const status = info.needsUpdate
@@ -464,6 +540,7 @@ async function showVersionInfo() {
464
540
  local: VERSION,
465
541
  remote: latestCliVersion,
466
542
  needsUpdate: latestCliVersion ? isVersionLower(VERSION, latestCliVersion) : false,
543
+ level: 0,
467
544
  });
468
545
  // 2. semo-core 버전 (루트 또는 semo-system 내부)
469
546
  const corePathRoot = path.join(cwd, "semo-core", "VERSION");
@@ -477,6 +554,7 @@ async function showVersionInfo() {
477
554
  local: localCore,
478
555
  remote: remoteCore,
479
556
  needsUpdate: remoteCore ? isVersionLower(localCore, remoteCore) : false,
557
+ level: 0,
480
558
  });
481
559
  }
482
560
  // 3. semo-skills 버전 (루트 또는 semo-system 내부)
@@ -491,12 +569,50 @@ async function showVersionInfo() {
491
569
  local: localSkills,
492
570
  remote: remoteSkills,
493
571
  needsUpdate: remoteSkills ? isVersionLower(localSkills, remoteSkills) : false,
572
+ level: 0,
494
573
  });
495
574
  }
496
- // 4. Extension 패키지들 (semo-system 내부)
575
+ // 4. 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
497
576
  const semoSystemDir = path.join(cwd, "semo-system");
498
577
  if (fs.existsSync(semoSystemDir)) {
499
- for (const key of Object.keys(EXTENSION_PACKAGES)) {
578
+ for (const group of PACKAGE_GROUPS) {
579
+ const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
580
+ const hasGroupVersion = fs.existsSync(groupVersionPath);
581
+ // 해당 그룹의 하위 패키지 찾기
582
+ const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
583
+ const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
584
+ if (hasGroupVersion || installedGroupExtensions.length > 0) {
585
+ // 그룹 패키지 버전 추가
586
+ if (hasGroupVersion) {
587
+ const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
588
+ const remoteGroup = await getRemotePackageVersion(group);
589
+ versionInfos.push({
590
+ name: group,
591
+ local: localGroup,
592
+ remote: remoteGroup,
593
+ needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
594
+ level: 1,
595
+ });
596
+ }
597
+ // 하위 Extension 패키지들 추가
598
+ for (const key of installedGroupExtensions) {
599
+ const extVersionPath = path.join(semoSystemDir, key, "VERSION");
600
+ const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
601
+ const remoteExt = await getRemotePackageVersion(key);
602
+ versionInfos.push({
603
+ name: key,
604
+ local: localExt,
605
+ remote: remoteExt,
606
+ needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
607
+ level: 2,
608
+ group: group,
609
+ });
610
+ }
611
+ }
612
+ }
613
+ // 그룹에 속하지 않는 Extension (meta 등)
614
+ const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
615
+ for (const key of nonGroupExtensions) {
500
616
  const extVersionPath = path.join(semoSystemDir, key, "VERSION");
501
617
  if (fs.existsSync(extVersionPath)) {
502
618
  const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
@@ -506,39 +622,60 @@ async function showVersionInfo() {
506
622
  local: localExt,
507
623
  remote: remoteExt,
508
624
  needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
625
+ level: 1,
509
626
  });
510
627
  }
511
628
  }
512
629
  }
513
- // 5. packages/ 디렉토리의 설치된 패키지들
630
+ // 5. packages/ 디렉토리의 설치된 패키지들 - 개발 환경용
514
631
  const packagesDir = path.join(cwd, "packages");
515
632
  if (fs.existsSync(packagesDir)) {
516
- const packagePaths = {
517
- "packages/core": "core",
518
- "packages/meta": "meta",
519
- "packages/eng/nextjs": "eng/nextjs",
520
- "packages/eng/spring": "eng/spring",
521
- "packages/eng/ms": "eng/ms",
522
- "packages/eng/infra": "eng/infra",
523
- "packages/biz/discovery": "biz/discovery",
524
- "packages/biz/management": "biz/management",
525
- "packages/biz/design": "biz/design",
526
- "packages/biz/poc": "biz/poc",
527
- "packages/ops/qa": "ops/qa",
528
- "packages/ops/monitor": "ops/monitor",
529
- "packages/ops/improve": "ops/improve",
633
+ const packageGroups = {
634
+ "packages/core": { level: 0, packages: [{ name: "packages/core", path: "core" }] },
635
+ "packages/meta": { level: 0, packages: [{ name: "packages/meta", path: "meta" }] },
636
+ "packages/eng": {
637
+ level: 1,
638
+ packages: [
639
+ { name: "packages/eng/nextjs", path: "eng/nextjs" },
640
+ { name: "packages/eng/spring", path: "eng/spring" },
641
+ { name: "packages/eng/ms", path: "eng/ms" },
642
+ { name: "packages/eng/infra", path: "eng/infra" },
643
+ ],
644
+ },
645
+ "packages/biz": {
646
+ level: 1,
647
+ packages: [
648
+ { name: "packages/biz/discovery", path: "biz/discovery" },
649
+ { name: "packages/biz/management", path: "biz/management" },
650
+ { name: "packages/biz/design", path: "biz/design" },
651
+ { name: "packages/biz/poc", path: "biz/poc" },
652
+ ],
653
+ },
654
+ "packages/ops": {
655
+ level: 1,
656
+ packages: [
657
+ { name: "packages/ops/qa", path: "ops/qa" },
658
+ { name: "packages/ops/monitor", path: "ops/monitor" },
659
+ { name: "packages/ops/improve", path: "ops/improve" },
660
+ ],
661
+ },
530
662
  };
531
- for (const [displayName, relativePath] of Object.entries(packagePaths)) {
532
- const pkgVersionPath = path.join(packagesDir, relativePath, "VERSION");
533
- if (fs.existsSync(pkgVersionPath)) {
534
- const localPkg = fs.readFileSync(pkgVersionPath, "utf-8").trim();
535
- const remotePkg = await getRemotePackageVersion(`packages/${relativePath}`);
536
- versionInfos.push({
537
- name: displayName,
538
- local: localPkg,
539
- remote: remotePkg,
540
- needsUpdate: remotePkg ? isVersionLower(localPkg, remotePkg) : false,
541
- });
663
+ for (const [, groupData] of Object.entries(packageGroups)) {
664
+ for (const pkg of groupData.packages) {
665
+ const pkgVersionPath = path.join(packagesDir, pkg.path, "VERSION");
666
+ if (fs.existsSync(pkgVersionPath)) {
667
+ const localPkg = fs.readFileSync(pkgVersionPath, "utf-8").trim();
668
+ const remotePkg = await getRemotePackageVersion(`packages/${pkg.path}`);
669
+ const isSubPackage = pkg.path.includes("/");
670
+ versionInfos.push({
671
+ name: pkg.name,
672
+ local: localPkg,
673
+ remote: remotePkg,
674
+ needsUpdate: remotePkg ? isVersionLower(localPkg, remotePkg) : false,
675
+ level: isSubPackage ? 2 : groupData.level,
676
+ group: isSubPackage ? pkg.path.split("/")[0] : undefined,
677
+ });
678
+ }
542
679
  }
543
680
  }
544
681
  }
@@ -567,7 +704,20 @@ async function showVersionInfo() {
567
704
  console.log(chalk_1.default.gray(" │ 패키지 │ 설치됨 │ 최신 │ 상태 │"));
568
705
  console.log(chalk_1.default.gray(" ├────────────────────────┼──────────┼──────────┼────────┤"));
569
706
  for (const info of versionInfos) {
570
- const name = info.name.padEnd(22);
707
+ // 계층 구조 표시를 위한 접두사
708
+ let prefix = "";
709
+ let displayName = info.name;
710
+ if (info.level === 1) {
711
+ // 그룹 패키지 (eng, biz, ops)
712
+ prefix = "📦 ";
713
+ }
714
+ else if (info.level === 2) {
715
+ // 하위 패키지
716
+ prefix = " └─ ";
717
+ // 그룹명 제거하고 하위 이름만 표시
718
+ displayName = info.name.includes("/") ? info.name.split("/").pop() || info.name : info.name;
719
+ }
720
+ const name = (prefix + displayName).padEnd(22);
571
721
  const local = (info.local || "-").padEnd(8);
572
722
  const remote = (info.remote || "-").padEnd(8);
573
723
  const status = info.needsUpdate ? "⬆ 업데이트" : "✓ 최신 ";
@@ -766,6 +916,33 @@ program
766
916
  extensionsToInstall = detected;
767
917
  }
768
918
  }
919
+ else {
920
+ // 프로젝트 유형이 감지되지 않은 경우 패키지 선택 프롬프트
921
+ console.log(chalk_1.default.cyan("\n📦 추가 패키지 선택"));
922
+ console.log(chalk_1.default.gray(" 기본 설치 (semo-core + semo-skills) 외에 추가할 패키지를 선택하세요.\n"));
923
+ // 그룹별로 패키지 구성
924
+ const packageChoices = [
925
+ new inquirer_1.default.Separator(chalk_1.default.yellow("── Engineering ──")),
926
+ { name: `eng/nextjs - ${EXTENSION_PACKAGES["eng/nextjs"].desc}`, value: "eng/nextjs" },
927
+ { name: `eng/spring - ${EXTENSION_PACKAGES["eng/spring"].desc}`, value: "eng/spring" },
928
+ { name: `eng/infra - ${EXTENSION_PACKAGES["eng/infra"].desc}`, value: "eng/infra" },
929
+ new inquirer_1.default.Separator(chalk_1.default.yellow("── Business ──")),
930
+ { name: `biz/discovery - ${EXTENSION_PACKAGES["biz/discovery"].desc}`, value: "biz/discovery" },
931
+ { name: `biz/management - ${EXTENSION_PACKAGES["biz/management"].desc}`, value: "biz/management" },
932
+ { name: `biz/design - ${EXTENSION_PACKAGES["biz/design"].desc}`, value: "biz/design" },
933
+ new inquirer_1.default.Separator(chalk_1.default.yellow("── Operations ──")),
934
+ { name: `ops/qa - ${EXTENSION_PACKAGES["ops/qa"].desc}`, value: "ops/qa" },
935
+ ];
936
+ const { selectedPackages } = await inquirer_1.default.prompt([
937
+ {
938
+ type: "checkbox",
939
+ name: "selectedPackages",
940
+ message: "설치할 패키지 선택 (Space로 선택, Enter로 완료):",
941
+ choices: packageChoices,
942
+ },
943
+ ]);
944
+ extensionsToInstall = selectedPackages;
945
+ }
769
946
  // 3. .claude 디렉토리 생성
770
947
  const claudeDir = path.join(cwd, ".claude");
771
948
  if (!fs.existsSync(claudeDir)) {
@@ -1886,7 +2063,68 @@ _SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
1886
2063
  console.log(chalk_1.default.green("✓ .claude/memory/rules/project-specific.md 생성됨"));
1887
2064
  }
1888
2065
  }
1889
- // === CLAUDE.md 생성 (패키지 CLAUDE.md 병합 지원) ===
2066
+ // === CLAUDE.md 중복 섹션 감지 ===
2067
+ // "Core Rules (상속)" 패턴을 사용하는 Extension은 고유 섹션만 추출
2068
+ function extractUniqueContent(content, pkgName) {
2069
+ // "Core Rules (상속)" 섹션이 있는지 확인
2070
+ const hasCoreRulesRef = /## Core Rules \(상속\)/i.test(content);
2071
+ if (hasCoreRulesRef) {
2072
+ // "고유:" 패턴이 포함된 섹션만 추출
2073
+ const uniqueSectionPattern = /## [^\n]* 고유:/g;
2074
+ const sections = [];
2075
+ // 섹션별로 분리
2076
+ const allSections = content.split(/(?=^## )/gm);
2077
+ for (const section of allSections) {
2078
+ // "고유:" 키워드가 있는 섹션만 포함
2079
+ if (/고유:/i.test(section)) {
2080
+ sections.push(section.trim());
2081
+ }
2082
+ // References 섹션도 포함
2083
+ if (/^## References/i.test(section)) {
2084
+ sections.push(section.trim());
2085
+ }
2086
+ // 패키지 구조, Keywords 섹션 포함
2087
+ if (/^## (패키지 구조|Keywords|Routing)/i.test(section)) {
2088
+ sections.push(section.trim());
2089
+ }
2090
+ }
2091
+ if (sections.length > 0) {
2092
+ return sections.join("\n\n");
2093
+ }
2094
+ }
2095
+ // 공유 규칙 패턴 감지 (이 패턴이 있으면 중복 가능성 높음)
2096
+ const sharedPatterns = [
2097
+ /Orchestrator-First Policy/i,
2098
+ /Quality Gate|Pre-Commit/i,
2099
+ /세션 초기화|Session Init/i,
2100
+ /버저닝 규칙|Versioning/i,
2101
+ /패키지 접두사|PREFIX_ROUTING/i,
2102
+ /SEMO Core 필수 참조/i,
2103
+ /NON-NEGOTIABLE.*Orchestrator/i,
2104
+ ];
2105
+ // 공유 패턴이 많이 발견되면 간소화된 참조만 반환
2106
+ let sharedPatternCount = 0;
2107
+ for (const pattern of sharedPatterns) {
2108
+ if (pattern.test(content)) {
2109
+ sharedPatternCount++;
2110
+ }
2111
+ }
2112
+ // 3개 이상의 공유 패턴이 발견되면 중복이 많은 것으로 판단
2113
+ if (sharedPatternCount >= 3) {
2114
+ // 기본 헤더와 References만 추출
2115
+ const headerMatch = content.match(/^# .+\n\n>[^\n]+/);
2116
+ const referencesMatch = content.match(/## References[\s\S]*$/);
2117
+ let simplified = headerMatch ? headerMatch[0] : `# ${pkgName}`;
2118
+ simplified += "\n\n> Core Rules는 semo-core/principles/를 참조합니다.";
2119
+ if (referencesMatch) {
2120
+ simplified += "\n\n" + referencesMatch[0];
2121
+ }
2122
+ return simplified;
2123
+ }
2124
+ // 그 외에는 전체 내용 반환
2125
+ return content;
2126
+ }
2127
+ // === CLAUDE.md 생성 (패키지 CLAUDE.md 병합 지원 + 중복 제거) ===
1890
2128
  async function setupClaudeMd(cwd, extensions, force) {
1891
2129
  console.log(chalk_1.default.cyan("\n📄 CLAUDE.md 설정"));
1892
2130
  const claudeMdPath = path.join(cwd, ".claude", "CLAUDE.md");
@@ -1905,31 +2143,36 @@ async function setupClaudeMd(cwd, extensions, force) {
1905
2143
  let packageClaudeMdSections = "";
1906
2144
  // 1. 설치된 패키지에서 그룹 추출 (중복 제거)
1907
2145
  const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
1908
- // 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops)
2146
+ // 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
1909
2147
  for (const group of installedGroups) {
1910
2148
  const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
1911
2149
  if (fs.existsSync(groupClaudeMdPath)) {
1912
2150
  const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
1913
- // 그룹 CLAUDE.md 헤더 레벨 조정 (# → ##, ## → ###)
1914
- const adjustedContent = groupContent
2151
+ // 중복 제거 고유 콘텐츠만 추출
2152
+ const uniqueContent = extractUniqueContent(groupContent, group);
2153
+ // 헤더 레벨 조정 (# → ##, ## → ###)
2154
+ const adjustedContent = uniqueContent
1915
2155
  .replace(/^# /gm, "## ")
1916
2156
  .replace(/^## /gm, "### ")
1917
2157
  .replace(/^### /gm, "#### ");
1918
2158
  packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
1919
- console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨`));
2159
+ console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
1920
2160
  }
1921
2161
  }
1922
- // 3. 개별 패키지 CLAUDE.md 병합
2162
+ // 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
1923
2163
  for (const pkg of extensions) {
1924
2164
  const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
1925
2165
  if (fs.existsSync(pkgClaudeMdPath)) {
1926
2166
  const pkgContent = fs.readFileSync(pkgClaudeMdPath, "utf-8");
1927
- // 헤더(#)를 ##로 변경하여 하위 섹션으로 만듦
1928
- const adjustedContent = pkgContent
2167
+ const pkgName = EXTENSION_PACKAGES[pkg]?.name || pkg;
2168
+ // 중복 제거 후 고유 콘텐츠만 추출
2169
+ const uniqueContent = extractUniqueContent(pkgContent, pkgName);
2170
+ // 헤더 레벨 조정
2171
+ const adjustedContent = uniqueContent
1929
2172
  .replace(/^# /gm, "### ")
1930
2173
  .replace(/^## /gm, "#### ");
1931
- packageClaudeMdSections += `\n\n---\n\n## ${EXTENSION_PACKAGES[pkg].name} 패키지 컨텍스트\n\n${adjustedContent}`;
1932
- console.log(chalk_1.default.gray(` + ${pkg}/CLAUDE.md 병합됨`));
2174
+ packageClaudeMdSections += `\n\n---\n\n## ${pkgName} 패키지 컨텍스트\n\n${adjustedContent}`;
2175
+ console.log(chalk_1.default.gray(` + ${pkg}/CLAUDE.md 병합됨 (고유 섹션만)`));
1933
2176
  }
1934
2177
  }
1935
2178
  // 4. Orchestrator 참조 경로 결정 (Extension 패키지 우선)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "3.0.22",
3
+ "version": "3.0.26",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {