@decencia/ch-cli 1.34.5 → 1.35.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 (37) hide show
  1. package/agent/2t-decencia-channel-issue-coder.md +1 -1
  2. package/agent/2t-decencia-channel-pr-merger.md +1 -1
  3. package/agent/2t-decencia-channel-terraformer.md +1 -1
  4. package/dist/client.d.ts +4 -0
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +7 -1
  7. package/dist/client.js.map +1 -1
  8. package/dist/commands/skills.d.ts +29 -0
  9. package/dist/commands/skills.d.ts.map +1 -1
  10. package/dist/commands/skills.js +707 -2
  11. package/dist/commands/skills.js.map +1 -1
  12. package/dist/skills-cache.d.ts +140 -0
  13. package/dist/skills-cache.d.ts.map +1 -0
  14. package/dist/skills-cache.js +388 -0
  15. package/dist/skills-cache.js.map +1 -0
  16. package/dist/skills-diff.d.ts +151 -0
  17. package/dist/skills-diff.d.ts.map +1 -0
  18. package/dist/skills-diff.js +476 -0
  19. package/dist/skills-diff.js.map +1 -0
  20. package/package.json +1 -1
  21. package/skill/2t-decencia-channel-change-manager/SKILL.md +1 -1
  22. package/skill/2t-decencia-channel-change-propagation-v2/SKILL.md +1 -1
  23. package/skill/2t-decencia-channel-cli-v2/SKILL.md +1 -1
  24. package/skill/2t-decencia-channel-db-schema-v2/SKILL.md +1 -1
  25. package/skill/2t-decencia-channel-github-issue/SKILL.md +1 -1
  26. package/skill/2t-decencia-channel-ia-v2/SKILL.md +1 -1
  27. package/skill/2t-decencia-channel-orchestrator/SKILL.md +5 -1
  28. package/skill/2t-decencia-channel-policy-v2/SKILL.md +1 -1
  29. package/skill/2t-decencia-channel-prd-v2/SKILL.md +1 -1
  30. package/skill/2t-decencia-channel-project-bootstrap/SKILL.md +1 -1
  31. package/skill/2t-decencia-channel-screen-v2/SKILL.md +1 -1
  32. package/skill/2t-decencia-channel-spec-v2/SKILL.md +1 -1
  33. package/skill/2t-decencia-channel-sprint-builder-v2/SKILL.md +1 -1
  34. package/skill/2t-decencia-channel-sprint-runner/SKILL.md +1 -1
  35. package/skill/2t-decencia-channel-sqa-v2/SKILL.md +1 -1
  36. package/skill/2t-decencia-channel-userflow-v2/SKILL.md +1 -1
  37. package/skill/2t-decencia-channel-work-status-v2/SKILL.md +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-cache.js","sourceRoot":"","sources":["../src/skills-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ import type { SkillScope } from './commands/skills';
2
+ /**
3
+ * 대조 판정.
4
+ * - same 본문도 번들 파일 수도 같다 → 로컬 사본은 잉여
5
+ * - server-newer 본문이 다르고 서버가 나중 → 로컬 사본은 낡음
6
+ * - local-newer 본문이 다르고 로컬이 나중 → 로컬에만 있는 작업이다(먼저 올려야 한다)
7
+ * - undecided 어느 쪽이 나중인지 못 정한다(시각 부재·동시각·번들 불일치)
8
+ * - local-only 서버에 없는 로컬 스킬
9
+ * - server-only 로컬 사본이 없는 서버 스킬
10
+ */
11
+ export type SkillDiffStatus = 'same' | 'server-newer' | 'local-newer' | 'undecided' | 'local-only' | 'server-only';
12
+ /** 서버 목록 항목 중 대조에 쓰는 부분. */
13
+ export interface ServerSkillEntry {
14
+ scope: SkillScope;
15
+ id: string;
16
+ name: string;
17
+ /** 신선도 토큰(`updatedAt ?? createdAt`) — 서버가 시각을 안 주면 null. */
18
+ token: string | null;
19
+ fileCount: number | null;
20
+ }
21
+ /** 로컬 폴더에서 읽은 스킬 사본 하나. */
22
+ export interface LocalSkillCopy {
23
+ /** 폴더명(로컬 표기). */
24
+ dirName: string;
25
+ /** 폴더 절대경로. */
26
+ dir: string;
27
+ /** SKILL.md frontmatter 의 name. 없으면 null. */
28
+ name: string | null;
29
+ /** SKILL.md 본문. */
30
+ manifest: string;
31
+ /** 폴더 안 파일 개수(SKILL.md 포함). */
32
+ fileCount: number;
33
+ /** 폴더 안 파일 mtime 중 가장 나중 값(ISO). 못 읽으면 null. */
34
+ modifiedAt: string | null;
35
+ }
36
+ /** 대조 결과 한 줄. */
37
+ export interface SkillDiffRow {
38
+ status: SkillDiffStatus;
39
+ /** 서버 스코프. 로컬에만 있으면 null. */
40
+ scope: SkillScope | null;
41
+ /** 서버 이름(없으면 로컬 이름). */
42
+ name: string;
43
+ /** 로컬 폴더명. 서버에만 있으면 null. */
44
+ localName: string | null;
45
+ localDir: string | null;
46
+ serverUpdatedAt: string | null;
47
+ localModifiedAt: string | null;
48
+ serverFiles: number | null;
49
+ localFiles: number | null;
50
+ /** 판정에 딸린 단서(번들 개수 차이·시각 부재 사유 등). */
51
+ note: string | null;
52
+ /** 권장 조치 — 사람이 실행할 것. 이 명령은 실행하지 않는다. */
53
+ action: string;
54
+ }
55
+ /** 대조 전체 결과(기계 모드 JSON 그대로). */
56
+ export interface SkillDiffResult {
57
+ localDir: string;
58
+ scopes: SkillScope[];
59
+ serverCount: number;
60
+ localCount: number;
61
+ /** 폴더인데 SKILL.md 가 없어 스킬로 보지 않은 항목들. */
62
+ skipped: string[];
63
+ summary: Record<SkillDiffStatus, number>;
64
+ rows: SkillDiffRow[];
65
+ }
66
+ /**
67
+ * 이름을 매칭 키로 정규화한다.
68
+ *
69
+ * 같은 스킬이 서버와 로컬에서 다르게 적힌다 — 서버 `[publishing] figma-code-setup`
70
+ * ↔ 로컬 폴더 `publishing-figma-code-setup`. 폴더명에 못 쓰는 글자를 사람이 손으로
71
+ * 바꿔 복사했기 때문이다. 그래서 글자·숫자만 남기고 그 사이의 기호·공백은 하이픈
72
+ * 하나로 접어 비교한다. 한글 등 비라틴 문자는 유니코드 속성으로 보존한다
73
+ * (서버에 한글 이름 스킬이 있다).
74
+ */
75
+ export declare function normalizeSkillKey(name: string): string;
76
+ /**
77
+ * 로컬 사본이 가질 수 있는 매칭 키들 — frontmatter 이름과 폴더명 둘 다.
78
+ * frontmatter 가 정본이지만, 그게 없거나 손으로 고쳐졌을 수 있어 폴더명도 본다.
79
+ */
80
+ export declare function localMatchKeys(copy: LocalSkillCopy): string[];
81
+ /**
82
+ * `--local` 기본값 — Claude Code 가 디스크에서 바로 읽는 폴더.
83
+ * 손으로 복사한 사본이 쌓인 자리라 대조의 기본 대상이 된다.
84
+ */
85
+ export declare function defaultLocalSkillsDir(): string;
86
+ /**
87
+ * 폴더 하나를 스킬 사본으로 읽는다. SKILL.md 가 없으면 스킬이 아니다(null).
88
+ *
89
+ * 로컬 시각은 폴더 안 파일 mtime 의 **최대값**이다. 본문만 보면 참조 파일만 고친
90
+ * 작업이 "옛날 것"으로 보이고, 그 오판의 대가가 로컬 작업 유실이라 나중 쪽으로
91
+ * 잡는다(로컬이 최신으로 기울면 사람이 한 번 더 확인할 뿐이다).
92
+ */
93
+ export declare function readLocalSkillCopy(dir: string, parseName: (markdown: string) => string): LocalSkillCopy | null;
94
+ /**
95
+ * 로컬 스킬 폴더를 훑는다 — **읽기만 한다**(생성·수정·삭제 없음).
96
+ * SKILL.md 가 없는 폴더는 스킬이 아니므로 `skipped` 로 따로 알린다(사람이 폴더를
97
+ * 잘못 뒀는지 스스로 판단하게).
98
+ */
99
+ export declare function scanLocalSkills(root: string, parseName: (markdown: string) => string): {
100
+ copies: LocalSkillCopy[];
101
+ skipped: string[];
102
+ };
103
+ /**
104
+ * 본문 비교용 정규화 — BOM·줄끝(CRLF/LF)·꼬리 공백만 걷어낸다.
105
+ * 사람이 손으로 복사하는 과정에서 줄끝이 바뀌는 일이 흔한데, 그건 스킬의 내용
106
+ * 차이가 아니다. 그 외의 글자는 하나도 건드리지 않는다.
107
+ */
108
+ export declare function normalizeManifest(text: string): string;
109
+ /** 본문이 같은지(줄끝·꼬리 공백 무시). */
110
+ export declare function manifestsEqual(a: string, b: string): boolean;
111
+ /**
112
+ * 본문이 다를 때 어느 쪽이 나중인지 정한다.
113
+ *
114
+ * 서버 시각은 `updatedAt ?? createdAt`(freshnessToken)이고, 로컬 시각은 파일
115
+ * mtime 이다. 어느 한쪽이라도 모르면 **판정을 보류한다** — 모르면서 "서버가 최신"
116
+ * 이라고 우기면 사람이 로컬 사본을 지우고 그 작업을 잃는다. (라이브 ch-api 가 아직
117
+ * 목록에 시각을 싣지 않는 구간이 있어 실제로 일어나는 경우다.)
118
+ */
119
+ export declare function decideNewer(serverAt: string | null, localAt: string | null): {
120
+ status: 'server-newer' | 'local-newer' | 'undecided';
121
+ reason: string | null;
122
+ };
123
+ /** 겹치는 한 쌍의 판정 줄. `serverManifest` 가 null 이면 본문을 못 받은 것이다. */
124
+ export declare function buildOverlapRow(entry: ServerSkillEntry, copy: LocalSkillCopy, serverManifest: string | null): SkillDiffRow;
125
+ /**
126
+ * 대조 결과를 만든다.
127
+ *
128
+ * 매칭은 정규화 키로 한다. 한 로컬 사본이 두 스코프의 스킬과 이름이 겹치면 두
129
+ * 줄이 나온다 — 어느 하나를 임의로 고르지 않고 사실대로 둘 다 보인다.
130
+ *
131
+ * @param manifests 겹치는 서버 스킬의 본문(`<scope>:<id>` → 본문). 없으면 null.
132
+ */
133
+ export declare function buildDiffResult(input: {
134
+ localDir: string;
135
+ scopes: SkillScope[];
136
+ serverEntries: ServerSkillEntry[];
137
+ localCopies: LocalSkillCopy[];
138
+ skipped: string[];
139
+ manifests: Map<string, string | null>;
140
+ }): SkillDiffResult;
141
+ /** 본문 맵의 키 — 스코프마다 id 공간이 다르므로 스코프를 붙인다. */
142
+ export declare function manifestKey(entry: {
143
+ scope: SkillScope;
144
+ id: string;
145
+ }): string;
146
+ /**
147
+ * 사람용 보고문. 겹치는 항목은 근거(시각·파일 수)까지, 한쪽에만 있는 항목은
148
+ * 이름만 나열한다 — 후자는 수십 개라 한 줄씩 늘어놓으면 결론이 묻힌다.
149
+ */
150
+ export declare function renderDiffReport(result: SkillDiffResult): string;
151
+ //# sourceMappingURL=skills-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-diff.d.ts","sourceRoot":"","sources":["../src/skills-diff.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAyBpD;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,cAAc,GACd,aAAa,GACb,WAAW,GACX,YAAY,GACZ,aAAa,CAAC;AAElB,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,UAAU,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,2BAA2B;AAC3B,MAAM,WAAW,cAAc;IAC7B,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,iBAAiB;AACjB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,eAAe,CAAC;IACxB,6BAA6B;IAC7B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sCAAsC;IACtC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gCAAgC;AAChC,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAID;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,EAAE,CAG7D;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAgCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GACtC,cAAc,GAAG,IAAI,CAoBvB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GACtC;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAqBjD;AAID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,4BAA4B;AAC5B,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE5D;AAQD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB;IAAE,MAAM,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAejF;AAyBD,8DAA8D;AAC9D,wBAAgB,eAAe,CAC7B,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,cAAc,EACpB,cAAc,EAAE,MAAM,GAAG,IAAI,GAC5B,YAAY,CAqCd;AAuBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC,GAAG,eAAe,CAyElB;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE5E;AA8CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CA6ChE"}
@@ -0,0 +1,476 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.normalizeSkillKey = normalizeSkillKey;
37
+ exports.localMatchKeys = localMatchKeys;
38
+ exports.defaultLocalSkillsDir = defaultLocalSkillsDir;
39
+ exports.readLocalSkillCopy = readLocalSkillCopy;
40
+ exports.scanLocalSkills = scanLocalSkills;
41
+ exports.normalizeManifest = normalizeManifest;
42
+ exports.manifestsEqual = manifestsEqual;
43
+ exports.decideNewer = decideNewer;
44
+ exports.buildOverlapRow = buildOverlapRow;
45
+ exports.buildDiffResult = buildDiffResult;
46
+ exports.manifestKey = manifestKey;
47
+ exports.renderDiffReport = renderDiffReport;
48
+ const fs = __importStar(require("fs"));
49
+ const os = __importStar(require("os"));
50
+ const path = __importStar(require("path"));
51
+ /**
52
+ * 서버 공유스킬 ↔ 로컬 사본 대조 (SKILL-012).
53
+ *
54
+ * ── 왜 필요한가
55
+ * 공유스킬에 로컬 적재 경로가 없던 동안 사람이 손으로 `~/.claude/skills/` 에
56
+ * 복사해 왔다(실측: 로컬 88개 중 15개가 공유스킬과 겹친다). 그 사본들은 Claude
57
+ * Code 가 디스크에서 바로 읽으므로 신선도 게이트를 우회한다. 그렇다고 비교 없이
58
+ * 지우면 로컬에만 있는 최신 작업이 유실된다 — 그래서 먼저 대조한다.
59
+ *
60
+ * ── 이 모듈이 하는 일 / 하지 않는 일
61
+ * 로컬 폴더를 읽고(읽기 전용) 서버 목록과 맞춰 판정·보고문을 만든다. HTTP 는
62
+ * 호출측(commands/skills.ts)의 몫이고, **삭제·업로드는 누구의 몫도 아니다** —
63
+ * 이 명령은 판정까지만 하고 실제 정리는 사람이 항목별로 결정한다(REQ-004).
64
+ */
65
+ /** 스킬 폴더 규약상 고정된 매니페스트 파일명. */
66
+ const MANIFEST_FILENAME = 'SKILL.md';
67
+ /** 로컬 폴더를 훑는 최대 깊이 — 스킬 폴더는 얕다(무한 중첩 방어). */
68
+ const MAX_SCAN_DEPTH = 8;
69
+ // ── 이름 정규화 ───────────────────────────────────────────────────────────
70
+ /**
71
+ * 이름을 매칭 키로 정규화한다.
72
+ *
73
+ * 같은 스킬이 서버와 로컬에서 다르게 적힌다 — 서버 `[publishing] figma-code-setup`
74
+ * ↔ 로컬 폴더 `publishing-figma-code-setup`. 폴더명에 못 쓰는 글자를 사람이 손으로
75
+ * 바꿔 복사했기 때문이다. 그래서 글자·숫자만 남기고 그 사이의 기호·공백은 하이픈
76
+ * 하나로 접어 비교한다. 한글 등 비라틴 문자는 유니코드 속성으로 보존한다
77
+ * (서버에 한글 이름 스킬이 있다).
78
+ */
79
+ function normalizeSkillKey(name) {
80
+ return (name ?? '')
81
+ .normalize('NFC')
82
+ .toLowerCase()
83
+ .replace(/[^\p{L}\p{N}]+/gu, '-')
84
+ .replace(/^-+|-+$/g, '');
85
+ }
86
+ /**
87
+ * 로컬 사본이 가질 수 있는 매칭 키들 — frontmatter 이름과 폴더명 둘 다.
88
+ * frontmatter 가 정본이지만, 그게 없거나 손으로 고쳐졌을 수 있어 폴더명도 본다.
89
+ */
90
+ function localMatchKeys(copy) {
91
+ const keys = [normalizeSkillKey(copy.name ?? ''), normalizeSkillKey(copy.dirName)];
92
+ return Array.from(new Set(keys.filter((k) => k.length > 0)));
93
+ }
94
+ // ── 로컬 폴더 읽기 (읽기 전용) ────────────────────────────────────────────
95
+ /**
96
+ * `--local` 기본값 — Claude Code 가 디스크에서 바로 읽는 폴더.
97
+ * 손으로 복사한 사본이 쌓인 자리라 대조의 기본 대상이 된다.
98
+ */
99
+ function defaultLocalSkillsDir() {
100
+ return path.join(os.homedir(), '.claude', 'skills');
101
+ }
102
+ /** 폴더 안 파일 개수와 가장 나중 mtime. 심볼릭 링크는 건너뛴다(루프·바깥 참조 방지). */
103
+ function walkStats(dir, depth) {
104
+ let files = 0;
105
+ let mtimeMs = 0;
106
+ let entries;
107
+ try {
108
+ entries = fs.readdirSync(dir, { withFileTypes: true });
109
+ }
110
+ catch {
111
+ return { files, mtimeMs };
112
+ }
113
+ for (const entry of entries) {
114
+ if (entry.isSymbolicLink())
115
+ continue;
116
+ const abs = path.join(dir, entry.name);
117
+ if (entry.isDirectory()) {
118
+ if (depth >= MAX_SCAN_DEPTH)
119
+ continue;
120
+ const nested = walkStats(abs, depth + 1);
121
+ files += nested.files;
122
+ mtimeMs = Math.max(mtimeMs, nested.mtimeMs);
123
+ }
124
+ else if (entry.isFile()) {
125
+ files += 1;
126
+ try {
127
+ mtimeMs = Math.max(mtimeMs, fs.statSync(abs).mtimeMs);
128
+ }
129
+ catch {
130
+ // 읽을 수 없는 파일 하나가 폴더 전체의 판정을 막지는 않는다.
131
+ }
132
+ }
133
+ }
134
+ return { files, mtimeMs };
135
+ }
136
+ /**
137
+ * 폴더 하나를 스킬 사본으로 읽는다. SKILL.md 가 없으면 스킬이 아니다(null).
138
+ *
139
+ * 로컬 시각은 폴더 안 파일 mtime 의 **최대값**이다. 본문만 보면 참조 파일만 고친
140
+ * 작업이 "옛날 것"으로 보이고, 그 오판의 대가가 로컬 작업 유실이라 나중 쪽으로
141
+ * 잡는다(로컬이 최신으로 기울면 사람이 한 번 더 확인할 뿐이다).
142
+ */
143
+ function readLocalSkillCopy(dir, parseName) {
144
+ const manifestFile = path.join(dir, MANIFEST_FILENAME);
145
+ let manifest;
146
+ try {
147
+ if (!fs.statSync(manifestFile).isFile())
148
+ return null;
149
+ manifest = fs.readFileSync(manifestFile, 'utf-8');
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ const stats = walkStats(dir, 0);
155
+ const parsed = (parseName(manifest) ?? '').trim();
156
+ return {
157
+ dirName: path.basename(dir),
158
+ dir,
159
+ name: parsed.length > 0 ? parsed : null,
160
+ manifest,
161
+ fileCount: stats.files,
162
+ modifiedAt: stats.mtimeMs > 0 ? new Date(stats.mtimeMs).toISOString() : null,
163
+ };
164
+ }
165
+ /**
166
+ * 로컬 스킬 폴더를 훑는다 — **읽기만 한다**(생성·수정·삭제 없음).
167
+ * SKILL.md 가 없는 폴더는 스킬이 아니므로 `skipped` 로 따로 알린다(사람이 폴더를
168
+ * 잘못 뒀는지 스스로 판단하게).
169
+ */
170
+ function scanLocalSkills(root, parseName) {
171
+ const copies = [];
172
+ const skipped = [];
173
+ let entries;
174
+ try {
175
+ entries = fs.readdirSync(root, { withFileTypes: true });
176
+ }
177
+ catch {
178
+ return { copies, skipped };
179
+ }
180
+ for (const entry of entries) {
181
+ // 폴더만 스킬이 될 수 있다. 링크는 따라가지 않는다.
182
+ if (!entry.isDirectory() || entry.isSymbolicLink())
183
+ continue;
184
+ const copy = readLocalSkillCopy(path.join(root, entry.name), parseName);
185
+ if (copy)
186
+ copies.push(copy);
187
+ else
188
+ skipped.push(entry.name);
189
+ }
190
+ copies.sort((a, b) => a.dirName.localeCompare(b.dirName));
191
+ skipped.sort((a, b) => a.localeCompare(b));
192
+ return { copies, skipped };
193
+ }
194
+ // ── 판정 ──────────────────────────────────────────────────────────────────
195
+ /**
196
+ * 본문 비교용 정규화 — BOM·줄끝(CRLF/LF)·꼬리 공백만 걷어낸다.
197
+ * 사람이 손으로 복사하는 과정에서 줄끝이 바뀌는 일이 흔한데, 그건 스킬의 내용
198
+ * 차이가 아니다. 그 외의 글자는 하나도 건드리지 않는다.
199
+ */
200
+ function normalizeManifest(text) {
201
+ const body = (text ?? '').replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
202
+ return body.replace(/[ \t]+$/gm, '').trimEnd();
203
+ }
204
+ /** 본문이 같은지(줄끝·꼬리 공백 무시). */
205
+ function manifestsEqual(a, b) {
206
+ return normalizeManifest(a) === normalizeManifest(b);
207
+ }
208
+ function parseTime(value) {
209
+ if (!value)
210
+ return null;
211
+ const ms = Date.parse(value);
212
+ return Number.isNaN(ms) ? null : ms;
213
+ }
214
+ /**
215
+ * 본문이 다를 때 어느 쪽이 나중인지 정한다.
216
+ *
217
+ * 서버 시각은 `updatedAt ?? createdAt`(freshnessToken)이고, 로컬 시각은 파일
218
+ * mtime 이다. 어느 한쪽이라도 모르면 **판정을 보류한다** — 모르면서 "서버가 최신"
219
+ * 이라고 우기면 사람이 로컬 사본을 지우고 그 작업을 잃는다. (라이브 ch-api 가 아직
220
+ * 목록에 시각을 싣지 않는 구간이 있어 실제로 일어나는 경우다.)
221
+ */
222
+ function decideNewer(serverAt, localAt) {
223
+ const server = parseTime(serverAt);
224
+ const local = parseTime(localAt);
225
+ if (server === null || local === null) {
226
+ const missing = server === null && local === null
227
+ ? '서버·로컬 시각을 둘 다 알 수 없다'
228
+ : server === null
229
+ ? '서버가 시각을 주지 않았다'
230
+ : '로컬 파일 시각을 읽지 못했다';
231
+ return { status: 'undecided', reason: `${missing} — 내용을 직접 비교해야 한다` };
232
+ }
233
+ if (server > local)
234
+ return { status: 'server-newer', reason: null };
235
+ if (local > server)
236
+ return { status: 'local-newer', reason: null };
237
+ return { status: 'undecided', reason: '내용은 다른데 시각이 같다 — 직접 비교해야 한다' };
238
+ }
239
+ /** 권장 조치 문구 — 사람이 실행할 명령이다(이 명령은 실행하지 않는다). */
240
+ function actionFor(status, entry, copy) {
241
+ const scopeFlag = entry?.scope === 'global' ? ' --global' : '';
242
+ switch (status) {
243
+ case 'same':
244
+ return `로컬 사본 제거 (${copy?.dir ?? ''})`;
245
+ case 'server-newer':
246
+ return `로컬 사본 제거 (${copy?.dir ?? ''}) — 서버본이 최신이다`;
247
+ case 'local-newer':
248
+ return `ch skills update "${entry?.name ?? ''}" "${copy?.dir ?? ''}"${scopeFlag} 로 올린 뒤 로컬 사본 제거`;
249
+ case 'undecided':
250
+ return '내용을 직접 비교한 뒤 결정';
251
+ case 'local-only':
252
+ return `공유할 것이면 ch skills register "${copy?.dir ?? ''}" --global`;
253
+ case 'server-only':
254
+ return `필요하면 ch skills use "${entry?.name ?? ''}"${scopeFlag}`;
255
+ }
256
+ }
257
+ /** 겹치는 한 쌍의 판정 줄. `serverManifest` 가 null 이면 본문을 못 받은 것이다. */
258
+ function buildOverlapRow(entry, copy, serverManifest) {
259
+ const notes = [];
260
+ let status;
261
+ if (serverManifest === null) {
262
+ status = 'undecided';
263
+ notes.push('서버 본문을 받지 못했다');
264
+ }
265
+ else if (manifestsEqual(serverManifest, copy.manifest)) {
266
+ // 본문이 같아도 번들 파일 수가 다르면 온전한 복제가 아니다. 그걸 "동일"이라
267
+ // 부르면 사람이 로컬에만 있는 참조 파일까지 함께 지운다 — 그래서 보류한다.
268
+ if (entry.fileCount !== null && entry.fileCount !== copy.fileCount) {
269
+ status = 'undecided';
270
+ notes.push(`본문은 같은데 번들 파일 수가 다르다(서버 ${entry.fileCount} / 로컬 ${copy.fileCount})`);
271
+ }
272
+ else {
273
+ status = 'same';
274
+ }
275
+ }
276
+ else {
277
+ const decided = decideNewer(entry.token, copy.modifiedAt);
278
+ status = decided.status;
279
+ if (decided.reason)
280
+ notes.push(decided.reason);
281
+ }
282
+ return {
283
+ status,
284
+ scope: entry.scope,
285
+ name: entry.name,
286
+ localName: copy.dirName,
287
+ localDir: copy.dir,
288
+ serverUpdatedAt: entry.token,
289
+ localModifiedAt: copy.modifiedAt,
290
+ serverFiles: entry.fileCount,
291
+ localFiles: copy.fileCount,
292
+ note: notes.length > 0 ? notes.join(' · ') : null,
293
+ action: actionFor(status, entry, copy),
294
+ };
295
+ }
296
+ const STATUS_ORDER = [
297
+ 'same',
298
+ 'server-newer',
299
+ 'local-newer',
300
+ 'undecided',
301
+ 'local-only',
302
+ 'server-only',
303
+ ];
304
+ /** 빈 집계표 — 없는 판정도 0 으로 드러나야 기계 소비자가 키를 믿을 수 있다. */
305
+ function emptySummary() {
306
+ return {
307
+ same: 0,
308
+ 'server-newer': 0,
309
+ 'local-newer': 0,
310
+ undecided: 0,
311
+ 'local-only': 0,
312
+ 'server-only': 0,
313
+ };
314
+ }
315
+ /**
316
+ * 대조 결과를 만든다.
317
+ *
318
+ * 매칭은 정규화 키로 한다. 한 로컬 사본이 두 스코프의 스킬과 이름이 겹치면 두
319
+ * 줄이 나온다 — 어느 하나를 임의로 고르지 않고 사실대로 둘 다 보인다.
320
+ *
321
+ * @param manifests 겹치는 서버 스킬의 본문(`<scope>:<id>` → 본문). 없으면 null.
322
+ */
323
+ function buildDiffResult(input) {
324
+ const byKey = new Map();
325
+ for (const copy of input.localCopies) {
326
+ for (const key of localMatchKeys(copy)) {
327
+ const bucket = byKey.get(key);
328
+ if (bucket)
329
+ bucket.push(copy);
330
+ else
331
+ byKey.set(key, [copy]);
332
+ }
333
+ }
334
+ const rows = [];
335
+ const matched = new Set();
336
+ for (const entry of input.serverEntries) {
337
+ const copies = byKey.get(normalizeSkillKey(entry.name)) ?? [];
338
+ if (copies.length === 0) {
339
+ rows.push({
340
+ status: 'server-only',
341
+ scope: entry.scope,
342
+ name: entry.name,
343
+ localName: null,
344
+ localDir: null,
345
+ serverUpdatedAt: entry.token,
346
+ localModifiedAt: null,
347
+ serverFiles: entry.fileCount,
348
+ localFiles: null,
349
+ note: null,
350
+ action: actionFor('server-only', entry, null),
351
+ });
352
+ continue;
353
+ }
354
+ for (const copy of copies) {
355
+ matched.add(copy);
356
+ rows.push(buildOverlapRow(entry, copy, input.manifests.get(manifestKey(entry)) ?? null));
357
+ }
358
+ }
359
+ for (const copy of input.localCopies) {
360
+ if (matched.has(copy))
361
+ continue;
362
+ rows.push({
363
+ status: 'local-only',
364
+ scope: null,
365
+ name: copy.name ?? copy.dirName,
366
+ localName: copy.dirName,
367
+ localDir: copy.dir,
368
+ serverUpdatedAt: null,
369
+ localModifiedAt: copy.modifiedAt,
370
+ serverFiles: null,
371
+ localFiles: copy.fileCount,
372
+ note: null,
373
+ action: actionFor('local-only', null, copy),
374
+ });
375
+ }
376
+ rows.sort((a, b) => {
377
+ const order = STATUS_ORDER.indexOf(a.status) - STATUS_ORDER.indexOf(b.status);
378
+ return order !== 0 ? order : a.name.localeCompare(b.name);
379
+ });
380
+ const summary = emptySummary();
381
+ for (const row of rows)
382
+ summary[row.status] += 1;
383
+ return {
384
+ localDir: input.localDir,
385
+ scopes: input.scopes,
386
+ serverCount: input.serverEntries.length,
387
+ localCount: input.localCopies.length,
388
+ skipped: input.skipped,
389
+ summary,
390
+ rows,
391
+ };
392
+ }
393
+ /** 본문 맵의 키 — 스코프마다 id 공간이 다르므로 스코프를 붙인다. */
394
+ function manifestKey(entry) {
395
+ return `${entry.scope}:${entry.id}`;
396
+ }
397
+ // ── 사람용 보고문 ─────────────────────────────────────────────────────────
398
+ const STATUS_LABEL = {
399
+ same: '동일',
400
+ 'server-newer': '서버가 최신',
401
+ 'local-newer': '로컬이 최신',
402
+ undecided: '판정보류',
403
+ 'local-only': '로컬에만 있음',
404
+ 'server-only': '서버에만 있음',
405
+ };
406
+ const STATUS_ADVICE = {
407
+ same: '권장: 로컬 사본 제거',
408
+ 'server-newer': '권장: 로컬 사본 제거',
409
+ 'local-newer': '권장: 내용 확인 후 ch skills update 로 올린 뒤 로컬 사본 제거',
410
+ undecided: '권장: 내용을 직접 비교한 뒤 결정',
411
+ 'local-only': '서버에 없다. 공유할 것이면 ch skills register',
412
+ 'server-only': '로컬 사본이 없다. 필요하면 ch skills use',
413
+ };
414
+ /** 시각 표기 — 서버(UTC ISO)와 로컬 mtime 을 같은 자(UTC)로 보여준다. */
415
+ function stamp(iso) {
416
+ const ms = parseTime(iso);
417
+ if (ms === null)
418
+ return '?';
419
+ return new Date(ms).toISOString().slice(0, 16).replace('T', ' ') + 'Z';
420
+ }
421
+ /** 이름 나열을 폭에 맞춰 접는다(로컬에만/서버에만 목록은 길다). */
422
+ function wrapNames(names, width, indent) {
423
+ const lines = [];
424
+ let current = '';
425
+ for (const name of names) {
426
+ const next = current ? `${current}, ${name}` : name;
427
+ if (current && next.length > width) {
428
+ lines.push(indent + current);
429
+ current = name;
430
+ }
431
+ else {
432
+ current = next;
433
+ }
434
+ }
435
+ if (current)
436
+ lines.push(indent + current);
437
+ return lines;
438
+ }
439
+ /**
440
+ * 사람용 보고문. 겹치는 항목은 근거(시각·파일 수)까지, 한쪽에만 있는 항목은
441
+ * 이름만 나열한다 — 후자는 수십 개라 한 줄씩 늘어놓으면 결론이 묻힌다.
442
+ */
443
+ function renderDiffReport(result) {
444
+ const lines = [];
445
+ const scopeText = result.scopes.join('+');
446
+ lines.push(`스킬 대조 — 서버 ${scopeText} ${result.serverCount}개 · 로컬 ${result.localCount}개 (${result.localDir})`);
447
+ lines.push(STATUS_ORDER.map((s) => `${STATUS_LABEL[s]} ${result.summary[s]}`).join(' · '));
448
+ if (result.skipped.length > 0) {
449
+ lines.push(`SKILL.md 가 없어 건너뛴 폴더 ${result.skipped.length}개: ${result.skipped.join(', ')}`);
450
+ }
451
+ for (const status of STATUS_ORDER) {
452
+ const rows = result.rows.filter((r) => r.status === status);
453
+ if (rows.length === 0)
454
+ continue;
455
+ lines.push('');
456
+ lines.push(`■ ${STATUS_LABEL[status]} (${rows.length}) — ${STATUS_ADVICE[status]}`);
457
+ if (status === 'local-only' || status === 'server-only') {
458
+ lines.push(...wrapNames(rows.map((r) => r.name), 92, ' '));
459
+ continue;
460
+ }
461
+ for (const row of rows) {
462
+ lines.push(` ${row.name} [${row.scope}] ← 로컬 ${row.localName}`);
463
+ const files = row.serverFiles === null && row.localFiles === null
464
+ ? ''
465
+ : ` · 파일 서버 ${row.serverFiles ?? '?'} / 로컬 ${row.localFiles ?? '?'}`;
466
+ lines.push(` 서버 ${stamp(row.serverUpdatedAt)} · 로컬 ${stamp(row.localModifiedAt)}${files}` +
467
+ (row.note ? ` · ${row.note}` : ''));
468
+ if (status === 'local-newer')
469
+ lines.push(` ↑ ${row.action}`);
470
+ }
471
+ }
472
+ lines.push('');
473
+ lines.push('판정 기준은 SKILL.md 본문이다. 이 명령은 아무것도 지우거나 올리지 않는다 — 정리는 항목별로 사람이 결정한다.');
474
+ return lines.join('\n');
475
+ }
476
+ //# sourceMappingURL=skills-diff.js.map