@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.
- package/dist/index.js +312 -69
- 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
|
-
//
|
|
201
|
+
// 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
|
|
202
|
+
// 그룹별로 묶어서 계층 구조로 출력
|
|
199
203
|
if (hasSemoSystem) {
|
|
200
|
-
for (const
|
|
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
|
|
219
|
-
"packages/core": "core",
|
|
220
|
-
"packages/meta": "meta",
|
|
221
|
-
"packages/eng
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
"packages/
|
|
231
|
-
|
|
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 [
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
517
|
-
"packages/core": "core",
|
|
518
|
-
"packages/meta": "meta",
|
|
519
|
-
"packages/eng
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
"packages/
|
|
529
|
-
|
|
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 [
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
1914
|
-
const
|
|
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
|
-
|
|
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## ${
|
|
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 패키지 우선)
|