@coralai/sps-cli 0.42.0 → 0.44.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 (147) hide show
  1. package/README.md +59 -4
  2. package/dist/commands/consoleCommand.d.ts +2 -0
  3. package/dist/commands/consoleCommand.d.ts.map +1 -0
  4. package/dist/commands/consoleCommand.js +129 -0
  5. package/dist/commands/consoleCommand.js.map +1 -0
  6. package/dist/commands/projectInit.d.ts.map +1 -1
  7. package/dist/commands/projectInit.js +40 -53
  8. package/dist/commands/projectInit.js.map +1 -1
  9. package/dist/commands/setup.d.ts.map +1 -1
  10. package/dist/commands/setup.js +14 -2
  11. package/dist/commands/setup.js.map +1 -1
  12. package/dist/commands/skillCommand.d.ts +2 -0
  13. package/dist/commands/skillCommand.d.ts.map +1 -0
  14. package/dist/commands/skillCommand.js +235 -0
  15. package/dist/commands/skillCommand.js.map +1 -0
  16. package/dist/console-assets/assets/index-Bhd2f9AP.js +125 -0
  17. package/dist/console-assets/assets/index-bsAN2a12.css +1 -0
  18. package/dist/console-assets/index.html +16 -0
  19. package/dist/console-server/index.d.ts +29 -0
  20. package/dist/console-server/index.d.ts.map +1 -0
  21. package/dist/console-server/index.js +145 -0
  22. package/dist/console-server/index.js.map +1 -0
  23. package/dist/console-server/lib/lockFile.d.ts +17 -0
  24. package/dist/console-server/lib/lockFile.d.ts.map +1 -0
  25. package/dist/console-server/lib/lockFile.js +61 -0
  26. package/dist/console-server/lib/lockFile.js.map +1 -0
  27. package/dist/console-server/lib/portPicker.d.ts +3 -0
  28. package/dist/console-server/lib/portPicker.d.ts.map +1 -0
  29. package/dist/console-server/lib/portPicker.js +25 -0
  30. package/dist/console-server/lib/portPicker.js.map +1 -0
  31. package/dist/console-server/routes/projects.d.ts +11 -0
  32. package/dist/console-server/routes/projects.d.ts.map +1 -0
  33. package/dist/console-server/routes/projects.js +149 -0
  34. package/dist/console-server/routes/projects.js.map +1 -0
  35. package/dist/console-server/routes/system.d.ts +7 -0
  36. package/dist/console-server/routes/system.d.ts.map +1 -0
  37. package/dist/console-server/routes/system.js +19 -0
  38. package/dist/console-server/routes/system.js.map +1 -0
  39. package/dist/console-server/sse/eventBus.d.ts +25 -0
  40. package/dist/console-server/sse/eventBus.d.ts.map +1 -0
  41. package/dist/console-server/sse/eventBus.js +32 -0
  42. package/dist/console-server/sse/eventBus.js.map +1 -0
  43. package/dist/console-server/watchers/cardWatcher.d.ts +9 -0
  44. package/dist/console-server/watchers/cardWatcher.d.ts.map +1 -0
  45. package/dist/console-server/watchers/cardWatcher.js +42 -0
  46. package/dist/console-server/watchers/cardWatcher.js.map +1 -0
  47. package/dist/core/skillStore.d.ts +46 -0
  48. package/dist/core/skillStore.d.ts.map +1 -0
  49. package/dist/core/skillStore.js +210 -0
  50. package/dist/core/skillStore.js.map +1 -0
  51. package/dist/core/skillStore.test.d.ts +2 -0
  52. package/dist/core/skillStore.test.d.ts.map +1 -0
  53. package/dist/core/skillStore.test.js +203 -0
  54. package/dist/core/skillStore.test.js.map +1 -0
  55. package/dist/main.js +27 -17
  56. package/dist/main.js.map +1 -1
  57. package/package.json +8 -2
  58. package/skills/architecture-decision-records/SKILL.md +207 -0
  59. package/skills/backend/SKILL.md +62 -0
  60. package/skills/backend/references/api-design.md +168 -0
  61. package/skills/backend/references/caching.md +181 -0
  62. package/skills/backend/references/data-access.md +173 -0
  63. package/skills/backend/references/layering.md +181 -0
  64. package/skills/backend/references/observability.md +190 -0
  65. package/skills/backend/references/resilience.md +201 -0
  66. package/skills/backend/references/security.md +186 -0
  67. package/skills/backend-architect/SKILL.md +119 -0
  68. package/skills/code-reviewer/SKILL.md +143 -0
  69. package/skills/coding-standards/SKILL.md +60 -0
  70. package/skills/coding-standards/references/clean-code.md +258 -0
  71. package/skills/coding-standards/references/code-review.md +192 -0
  72. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  73. package/skills/coding-standards/references/error-strategy.md +193 -0
  74. package/skills/coding-standards/references/naming.md +185 -0
  75. package/skills/coding-standards/references/tdd.md +171 -0
  76. package/skills/database/SKILL.md +53 -0
  77. package/skills/database/references/indexing.md +190 -0
  78. package/skills/database/references/migrations.md +199 -0
  79. package/skills/database/references/nosql.md +185 -0
  80. package/skills/database/references/queries.md +295 -0
  81. package/skills/database/references/scaling.md +203 -0
  82. package/skills/database/references/schema.md +191 -0
  83. package/skills/database-optimizer/SKILL.md +168 -0
  84. package/skills/debugging-workflow/SKILL.md +244 -0
  85. package/skills/devops/SKILL.md +55 -0
  86. package/skills/devops/references/ci-cd.md +204 -0
  87. package/skills/devops/references/containers.md +272 -0
  88. package/skills/devops/references/deploy.md +201 -0
  89. package/skills/devops/references/iac.md +252 -0
  90. package/skills/devops/references/observability.md +228 -0
  91. package/skills/devops/references/secrets.md +178 -0
  92. package/skills/devops-automator/SKILL.md +164 -0
  93. package/skills/frontend/SKILL.md +52 -0
  94. package/skills/frontend/references/accessibility.md +222 -0
  95. package/skills/frontend/references/components.md +206 -0
  96. package/skills/frontend/references/performance.md +219 -0
  97. package/skills/frontend/references/routing.md +209 -0
  98. package/skills/frontend/references/state.md +190 -0
  99. package/skills/frontend/references/testing.md +216 -0
  100. package/skills/frontend-developer/SKILL.md +115 -0
  101. package/skills/git-workflow/SKILL.md +355 -0
  102. package/skills/golang/SKILL.md +49 -0
  103. package/skills/golang/references/concurrency.md +284 -0
  104. package/skills/golang/references/errors.md +241 -0
  105. package/skills/golang/references/idioms.md +285 -0
  106. package/skills/golang/references/testing.md +238 -0
  107. package/skills/java/SKILL.md +50 -0
  108. package/skills/java/references/concurrency.md +194 -0
  109. package/skills/java/references/idioms.md +283 -0
  110. package/skills/java/references/testing.md +228 -0
  111. package/skills/kotlin/SKILL.md +47 -0
  112. package/skills/kotlin/references/coroutines.md +240 -0
  113. package/skills/kotlin/references/idioms.md +268 -0
  114. package/skills/kotlin/references/testing.md +219 -0
  115. package/skills/mobile/SKILL.md +50 -0
  116. package/skills/mobile/references/architecture.md +204 -0
  117. package/skills/mobile/references/navigation.md +158 -0
  118. package/skills/mobile/references/performance.md +152 -0
  119. package/skills/mobile/references/platform.md +166 -0
  120. package/skills/mobile/references/state-and-data.md +174 -0
  121. package/skills/python/SKILL.md +51 -0
  122. package/skills/python/THIRD_PARTY.md +14 -0
  123. package/skills/python/references/async.md +218 -0
  124. package/skills/python/references/error-handling.md +254 -0
  125. package/skills/python/references/idioms.md +279 -0
  126. package/skills/python/references/packaging.md +233 -0
  127. package/skills/python/references/testing.md +269 -0
  128. package/skills/python/references/typing.md +292 -0
  129. package/skills/qa-tester/SKILL.md +186 -0
  130. package/skills/rust/SKILL.md +50 -0
  131. package/skills/rust/references/async.md +224 -0
  132. package/skills/rust/references/errors.md +240 -0
  133. package/skills/rust/references/ownership.md +263 -0
  134. package/skills/rust/references/testing.md +274 -0
  135. package/skills/rust/references/traits.md +250 -0
  136. package/skills/security-engineer/SKILL.md +157 -0
  137. package/skills/swift/SKILL.md +48 -0
  138. package/skills/swift/references/concurrency.md +280 -0
  139. package/skills/swift/references/idioms.md +334 -0
  140. package/skills/swift/references/testing.md +229 -0
  141. package/skills/typescript/SKILL.md +51 -0
  142. package/skills/typescript/references/async.md +241 -0
  143. package/skills/typescript/references/errors.md +208 -0
  144. package/skills/typescript/references/idioms.md +246 -0
  145. package/skills/typescript/references/testing.md +225 -0
  146. package/skills/typescript/references/tooling.md +208 -0
  147. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @module console-server/sse/eventBus
3
+ * @description 内部事件总线 —— watchers 推事件,SSE handlers 订阅推给客户端
4
+ *
5
+ * @role core
6
+ * @layer console-server
7
+ * @boundedContext console
8
+ */
9
+ import { EventEmitter } from 'node:events';
10
+ const MAX_HISTORY = 1000;
11
+ class ConsoleEventBus extends EventEmitter {
12
+ lastEventId = 0;
13
+ history = [];
14
+ publish(event, data) {
15
+ const id = ++this.lastEventId;
16
+ const record = { id, event, data, ts: Date.now() };
17
+ this.history.push(record);
18
+ if (this.history.length > MAX_HISTORY)
19
+ this.history.shift();
20
+ this.emit(event, data);
21
+ this.emit('*', record);
22
+ return id;
23
+ }
24
+ since(lastEventId) {
25
+ return this.history.filter((h) => h.id > lastEventId);
26
+ }
27
+ clearHistory() {
28
+ this.history = [];
29
+ }
30
+ }
31
+ export const eventBus = new ConsoleEventBus();
32
+ //# sourceMappingURL=eventBus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventBus.js","sourceRoot":"","sources":["../../../src/console-server/sse/eventBus.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS3C,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB,MAAM,eAAgB,SAAQ,YAAY;IAChC,WAAW,GAAG,CAAC,CAAC;IAChB,OAAO,GAAe,EAAE,CAAC;IAEjC,OAAO,CAAC,KAAa,EAAE,IAAa;QAClC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,WAAW,CAAC;QAC9B,MAAM,MAAM,GAAa,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC5D,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,WAAmB;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC;IACxD,CAAC;IAED,YAAY;QACV,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @module console-server/watchers/cardWatcher
3
+ * @description chokidar 监听 ~/.coral/cards 目录,变化推 eventBus
4
+ *
5
+ * 文件变化 → card.created / card.updated / card.deleted 事件
6
+ */
7
+ import { type FSWatcher } from 'chokidar';
8
+ export declare function startCardWatcher(coralRoot: string): FSWatcher;
9
+ //# sourceMappingURL=cardWatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardWatcher.d.ts","sourceRoot":"","sources":["../../../src/console-server/watchers/cardWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAWpD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAuB7D"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @module console-server/watchers/cardWatcher
3
+ * @description chokidar 监听 ~/.coral/cards 目录,变化推 eventBus
4
+ *
5
+ * 文件变化 → card.created / card.updated / card.deleted 事件
6
+ */
7
+ import chokidar from 'chokidar';
8
+ import { basename } from 'node:path';
9
+ import { eventBus } from '../sse/eventBus.js';
10
+ function extractProjectAndSeq(path) {
11
+ // 路径形如 ~/.coral/projects/<project>/cards/<seq>-<slug>.md
12
+ const match = path.match(/projects\/([^/]+)\/cards\/(\d+)(?:-[^/]*)?\.md$/);
13
+ if (!match)
14
+ return null;
15
+ return { project: match[1], seq: Number.parseInt(match[2], 10) };
16
+ }
17
+ export function startCardWatcher(coralRoot) {
18
+ const pattern = `${coralRoot}/cards 目录`;
19
+ const watcher = chokidar.watch(pattern, {
20
+ persistent: true,
21
+ ignoreInitial: true,
22
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
23
+ });
24
+ watcher
25
+ .on('add', (path) => {
26
+ const info = extractProjectAndSeq(path);
27
+ if (info)
28
+ eventBus.publish('card.created', { ...info, path: basename(path) });
29
+ })
30
+ .on('change', (path) => {
31
+ const info = extractProjectAndSeq(path);
32
+ if (info)
33
+ eventBus.publish('card.updated', { ...info, path: basename(path) });
34
+ })
35
+ .on('unlink', (path) => {
36
+ const info = extractProjectAndSeq(path);
37
+ if (info)
38
+ eventBus.publish('card.deleted', info);
39
+ });
40
+ return watcher;
41
+ }
42
+ //# sourceMappingURL=cardWatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardWatcher.js","sourceRoot":"","sources":["../../../src/console-server/watchers/cardWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,QAA4B,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,SAAS,oBAAoB,CAAC,IAAY;IACxC,yDAAyD;IACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAC5E,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,OAAO,GAAG,GAAG,SAAS,WAAW,CAAC;IACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE;QACtC,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,EAAE,kBAAkB,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE;KAChE,CAAC,CAAC;IAEH,OAAO;SACJ,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;QAClB,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,IAAI;YAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC;SACD,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;QACrB,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,IAAI;YAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC;SACD,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;QACrB,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,IAAI;YAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEL,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,46 @@
1
+ export type SkillLinkState = 'absent' | 'linked' | 'frozen';
2
+ export interface SkillInfo {
3
+ name: string;
4
+ userPath: string;
5
+ hasSkillMd: boolean;
6
+ }
7
+ export interface ProjectSkillState extends SkillInfo {
8
+ state: SkillLinkState;
9
+ target?: string;
10
+ }
11
+ export declare function userSkillsRoot(): string;
12
+ export declare function listUserSkills(): SkillInfo[];
13
+ export declare function projectSkillsDir(projectDir: string): string;
14
+ export declare function projectSkillPath(projectDir: string, name: string): string;
15
+ export declare function inspectProjectSkill(projectDir: string, name: string): ProjectSkillState | null;
16
+ export type AddResult = 'linked' | 'copied' | 'skipped-linked' | 'skipped-frozen' | 'skipped-absent';
17
+ export declare function addSkillToProject(projectDir: string, name: string): AddResult;
18
+ export declare function removeSkillFromProject(projectDir: string, name: string): boolean;
19
+ export declare function freezeSkillInProject(projectDir: string, name: string): boolean;
20
+ export declare function unfreezeSkillInProject(projectDir: string, name: string): boolean;
21
+ /**
22
+ * Bulk link every user-level skill into the project. Idempotent:
23
+ * - absent → symlink(或回退 cpSync)
24
+ * - linked → 保留
25
+ * - frozen → 保留(用户已 freeze 的不重建)
26
+ */
27
+ export declare function syncAllSkillsToProject(projectDir: string): {
28
+ linked: number;
29
+ copied: number;
30
+ kept: number;
31
+ };
32
+ /**
33
+ * 把 bundled skills(npm 包内 skills/)拷贝到 ~/.coral/skills/。
34
+ * Non-destructive:已存在的 skill 目录保留,不覆盖用户改动。
35
+ * Bundled → user 必须是 cpSync,因为 npm 包路径会随重装变化,symlink 会失效。
36
+ */
37
+ export declare function syncBundledSkillsToUser(bundledSkillsDir: string): {
38
+ copied: number;
39
+ skipped: number;
40
+ };
41
+ /**
42
+ * 把 .claude/skills/ 条目追加到 .gitignore(幂等)。
43
+ * 项目级 skill 是 symlink 到本机 ~/.coral/,不该进仓库。
44
+ */
45
+ export declare function ensureSkillsGitignore(projectDir: string): void;
46
+ //# sourceMappingURL=skillStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillStore.d.ts","sourceRoot":"","sources":["../../src/core/skillStore.ts"],"names":[],"mappings":"AAgCA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAClD,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,cAAc,IAAI,SAAS,EAAE,CAoB5C;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAgB9F;AAED,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,QAAQ,GACR,gBAAgB,GAChB,gBAAgB,GAChB,gBAAgB,CAAC;AAErB,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAmB7E;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKhF;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAU9E;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAahF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,GACjB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAYlD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,GACvB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoBrC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAY9D"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @module skillStore
3
+ * @description Skill 分发核心:user-level ~/.coral/skills/ ↔ project-level .claude/skills/
4
+ *
5
+ * @role core
6
+ * @layer core
7
+ * @boundedContext skill-distribution
8
+ *
9
+ * @responsibilities
10
+ * - 列举 user-level skills(~/.coral/skills/)
11
+ * - 在项目中 add/remove/freeze/unfreeze skill
12
+ * - 默认用 symlink(同机器稳定路径),失败回退 cpSync
13
+ * - 区分 linked(symlink)/ frozen(真实副本)/ foreign(未知目录)
14
+ */
15
+ import { appendFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync, } from 'node:fs';
16
+ import { resolve } from 'node:path';
17
+ const HOME = process.env.HOME || '/home/coral';
18
+ const USER_SKILLS_DIR = resolve(HOME, '.coral', 'skills');
19
+ export function userSkillsRoot() {
20
+ return USER_SKILLS_DIR;
21
+ }
22
+ export function listUserSkills() {
23
+ if (!existsSync(USER_SKILLS_DIR))
24
+ return [];
25
+ return readdirSync(USER_SKILLS_DIR, { withFileTypes: true })
26
+ .filter((e) => {
27
+ // 普通目录或指向目录的 symlink 都算(外部 skill 包经常用 symlink 接入)
28
+ if (e.isDirectory())
29
+ return true;
30
+ if (!e.isSymbolicLink())
31
+ return false;
32
+ try {
33
+ // statSync 跟随 symlink,解析到目标
34
+ return statSync(resolve(USER_SKILLS_DIR, e.name)).isDirectory();
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ })
40
+ .map((e) => ({
41
+ name: e.name,
42
+ userPath: resolve(USER_SKILLS_DIR, e.name),
43
+ hasSkillMd: existsSync(resolve(USER_SKILLS_DIR, e.name, 'SKILL.md')),
44
+ }))
45
+ .filter((s) => s.hasSkillMd);
46
+ }
47
+ export function projectSkillsDir(projectDir) {
48
+ return resolve(projectDir, '.claude', 'skills');
49
+ }
50
+ export function projectSkillPath(projectDir, name) {
51
+ return resolve(projectSkillsDir(projectDir), name);
52
+ }
53
+ export function inspectProjectSkill(projectDir, name) {
54
+ const user = listUserSkills().find((s) => s.name === name);
55
+ if (!user)
56
+ return null;
57
+ const p = projectSkillPath(projectDir, name);
58
+ if (!existsSync(p))
59
+ return { ...user, state: 'absent' };
60
+ const stat = lstatSync(p);
61
+ if (stat.isSymbolicLink()) {
62
+ let target;
63
+ try {
64
+ target = readlinkSync(p);
65
+ }
66
+ catch {
67
+ /* ignore */
68
+ }
69
+ return { ...user, state: 'linked', target };
70
+ }
71
+ return { ...user, state: 'frozen' };
72
+ }
73
+ export function addSkillToProject(projectDir, name) {
74
+ const user = listUserSkills().find((s) => s.name === name);
75
+ if (!user)
76
+ return 'skipped-absent';
77
+ const skillsDir = projectSkillsDir(projectDir);
78
+ if (!existsSync(skillsDir))
79
+ mkdirSync(skillsDir, { recursive: true });
80
+ const dst = projectSkillPath(projectDir, name);
81
+ if (existsSync(dst)) {
82
+ return lstatSync(dst).isSymbolicLink() ? 'skipped-linked' : 'skipped-frozen';
83
+ }
84
+ try {
85
+ symlinkSync(user.userPath, dst, 'dir');
86
+ return 'linked';
87
+ }
88
+ catch {
89
+ cpSync(user.userPath, dst, { recursive: true, force: false });
90
+ return 'copied';
91
+ }
92
+ }
93
+ export function removeSkillFromProject(projectDir, name) {
94
+ const p = projectSkillPath(projectDir, name);
95
+ if (!existsSync(p) && !isDanglingSymlink(p))
96
+ return false;
97
+ rmSync(p, { recursive: true, force: true });
98
+ return true;
99
+ }
100
+ export function freezeSkillInProject(projectDir, name) {
101
+ const p = projectSkillPath(projectDir, name);
102
+ if (!existsSync(p))
103
+ return false;
104
+ const stat = lstatSync(p);
105
+ if (!stat.isSymbolicLink())
106
+ return false;
107
+ const user = listUserSkills().find((s) => s.name === name);
108
+ if (!user)
109
+ return false;
110
+ rmSync(p);
111
+ cpSync(user.userPath, p, { recursive: true, force: true });
112
+ return true;
113
+ }
114
+ export function unfreezeSkillInProject(projectDir, name) {
115
+ const p = projectSkillPath(projectDir, name);
116
+ const user = listUserSkills().find((s) => s.name === name);
117
+ if (!user)
118
+ return false;
119
+ if (existsSync(p)) {
120
+ const stat = lstatSync(p);
121
+ if (stat.isSymbolicLink())
122
+ return false;
123
+ rmSync(p, { recursive: true, force: true });
124
+ }
125
+ const skillsDir = projectSkillsDir(projectDir);
126
+ if (!existsSync(skillsDir))
127
+ mkdirSync(skillsDir, { recursive: true });
128
+ symlinkSync(user.userPath, p, 'dir');
129
+ return true;
130
+ }
131
+ /**
132
+ * Bulk link every user-level skill into the project. Idempotent:
133
+ * - absent → symlink(或回退 cpSync)
134
+ * - linked → 保留
135
+ * - frozen → 保留(用户已 freeze 的不重建)
136
+ */
137
+ export function syncAllSkillsToProject(projectDir) {
138
+ const users = listUserSkills();
139
+ let linked = 0;
140
+ let copied = 0;
141
+ let kept = 0;
142
+ for (const u of users) {
143
+ const r = addSkillToProject(projectDir, u.name);
144
+ if (r === 'linked')
145
+ linked++;
146
+ else if (r === 'copied')
147
+ copied++;
148
+ else
149
+ kept++;
150
+ }
151
+ return { linked, copied, kept };
152
+ }
153
+ /**
154
+ * 把 bundled skills(npm 包内 skills/)拷贝到 ~/.coral/skills/。
155
+ * Non-destructive:已存在的 skill 目录保留,不覆盖用户改动。
156
+ * Bundled → user 必须是 cpSync,因为 npm 包路径会随重装变化,symlink 会失效。
157
+ */
158
+ export function syncBundledSkillsToUser(bundledSkillsDir) {
159
+ if (!existsSync(bundledSkillsDir))
160
+ return { copied: 0, skipped: 0 };
161
+ if (!existsSync(USER_SKILLS_DIR))
162
+ mkdirSync(USER_SKILLS_DIR, { recursive: true });
163
+ let copied = 0;
164
+ let skipped = 0;
165
+ const entries = readdirSync(bundledSkillsDir, { withFileTypes: true });
166
+ for (const entry of entries) {
167
+ if (!entry.isDirectory())
168
+ continue;
169
+ const src = resolve(bundledSkillsDir, entry.name);
170
+ if (!existsSync(resolve(src, 'SKILL.md')))
171
+ continue;
172
+ const dst = resolve(USER_SKILLS_DIR, entry.name);
173
+ if (existsSync(dst)) {
174
+ skipped++;
175
+ continue;
176
+ }
177
+ cpSync(src, dst, { recursive: true, force: false });
178
+ copied++;
179
+ }
180
+ return { copied, skipped };
181
+ }
182
+ /**
183
+ * 把 .claude/skills/ 条目追加到 .gitignore(幂等)。
184
+ * 项目级 skill 是 symlink 到本机 ~/.coral/,不该进仓库。
185
+ */
186
+ export function ensureSkillsGitignore(projectDir) {
187
+ const gitignore = resolve(projectDir, '.gitignore');
188
+ const entry = '.claude/skills/';
189
+ let existing = '';
190
+ if (existsSync(gitignore))
191
+ existing = readFileSync(gitignore, 'utf-8');
192
+ const has = existing
193
+ .split('\n')
194
+ .map((l) => l.trim())
195
+ .some((l) => l === entry || l === '.claude/skills');
196
+ if (has)
197
+ return;
198
+ const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
199
+ appendFileSync(gitignore, `${prefix}${entry}\n`);
200
+ }
201
+ function isDanglingSymlink(p) {
202
+ try {
203
+ const stat = lstatSync(p);
204
+ return stat.isSymbolicLink();
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ //# sourceMappingURL=skillStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillStore.js","sourceRoot":"","sources":["../../src/core/skillStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skillStore.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillStore.test.d.ts","sourceRoot":"","sources":["../../src/core/skillStore.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @module skillStore.test
3
+ * @description skillStore 单元测试 — symlink / frozen / 回退、幂等语义
4
+ *
5
+ * 测试 symlink 路径:在 tmp 目录构造一个假 HOME 和假 project,所以不污染真实 ~/.coral/。
6
+ */
7
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
8
+ import { tmpdir } from 'node:os';
9
+ import { join, resolve } from 'node:path';
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
+ function tempDir(prefix) {
12
+ return mkdtempSync(join(tmpdir(), prefix));
13
+ }
14
+ /**
15
+ * skillStore 用 process.env.HOME 计算 ~/.coral/skills/,所以要在 import 前
16
+ * 覆盖 HOME;再 dynamic import 重新读取。用 vi.resetModules 确保不用缓存。
17
+ */
18
+ async function loadStoreWithFakeHome(fakeHome) {
19
+ process.env.HOME = fakeHome;
20
+ vi.resetModules();
21
+ return await import('./skillStore.js');
22
+ }
23
+ function makeUserSkill(fakeHome, name, extraFiles = {}) {
24
+ const dir = resolve(fakeHome, '.coral', 'skills', name);
25
+ mkdirSync(dir, { recursive: true });
26
+ writeFileSync(resolve(dir, 'SKILL.md'), `---\nname: ${name}\n---\n# ${name}\n`);
27
+ for (const [rel, content] of Object.entries(extraFiles)) {
28
+ const p = resolve(dir, rel);
29
+ mkdirSync(resolve(p, '..'), { recursive: true });
30
+ writeFileSync(p, content);
31
+ }
32
+ return dir;
33
+ }
34
+ function makeProject(root) {
35
+ const dir = resolve(root, 'proj');
36
+ mkdirSync(resolve(dir, '.claude'), { recursive: true });
37
+ return dir;
38
+ }
39
+ describe('skillStore', () => {
40
+ let fakeHome;
41
+ let root;
42
+ const origHome = process.env.HOME;
43
+ beforeEach(() => {
44
+ root = tempDir('sps-skillstore-');
45
+ fakeHome = resolve(root, 'home');
46
+ mkdirSync(fakeHome, { recursive: true });
47
+ });
48
+ afterEach(() => {
49
+ process.env.HOME = origHome;
50
+ rmSync(root, { recursive: true, force: true });
51
+ });
52
+ it('listUserSkills 返回含 SKILL.md 的目录', async () => {
53
+ makeUserSkill(fakeHome, 'python');
54
+ makeUserSkill(fakeHome, 'backend');
55
+ // 无 SKILL.md 的目录不应出现
56
+ mkdirSync(resolve(fakeHome, '.coral', 'skills', 'not-a-skill'), { recursive: true });
57
+ const store = await loadStoreWithFakeHome(fakeHome);
58
+ const users = store.listUserSkills();
59
+ const names = users.map((u) => u.name).sort();
60
+ expect(names).toEqual(['backend', 'python']);
61
+ });
62
+ it('addSkillToProject 建 symlink', async () => {
63
+ makeUserSkill(fakeHome, 'python');
64
+ const proj = makeProject(root);
65
+ const store = await loadStoreWithFakeHome(fakeHome);
66
+ const result = store.addSkillToProject(proj, 'python');
67
+ expect(['linked', 'copied']).toContain(result);
68
+ const linkPath = resolve(proj, '.claude', 'skills', 'python');
69
+ expect(existsSync(linkPath)).toBe(true);
70
+ expect(existsSync(resolve(linkPath, 'SKILL.md'))).toBe(true);
71
+ });
72
+ it('addSkillToProject 对已 linked 的 skill 幂等', async () => {
73
+ makeUserSkill(fakeHome, 'python');
74
+ const proj = makeProject(root);
75
+ const store = await loadStoreWithFakeHome(fakeHome);
76
+ store.addSkillToProject(proj, 'python');
77
+ const again = store.addSkillToProject(proj, 'python');
78
+ expect(['skipped-linked', 'skipped-frozen']).toContain(again);
79
+ });
80
+ it('addSkillToProject 不覆盖已 frozen 的真实目录', async () => {
81
+ makeUserSkill(fakeHome, 'python');
82
+ const proj = makeProject(root);
83
+ const store = await loadStoreWithFakeHome(fakeHome);
84
+ // 先手工放一个真实目录(模拟已 frozen / v0.42 cpSync 后的目录)
85
+ const frozenPath = resolve(proj, '.claude', 'skills', 'python');
86
+ mkdirSync(frozenPath, { recursive: true });
87
+ writeFileSync(resolve(frozenPath, 'SKILL.md'), '# custom\n');
88
+ const result = store.addSkillToProject(proj, 'python');
89
+ expect(result).toBe('skipped-frozen');
90
+ // 内容保留
91
+ expect(readFileSync(resolve(frozenPath, 'SKILL.md'), 'utf-8')).toBe('# custom\n');
92
+ });
93
+ it('addSkillToProject 对不存在的 skill 返回 skipped-absent', async () => {
94
+ const proj = makeProject(root);
95
+ const store = await loadStoreWithFakeHome(fakeHome);
96
+ expect(store.addSkillToProject(proj, 'nope')).toBe('skipped-absent');
97
+ });
98
+ it('inspectProjectSkill 区分 absent / linked / frozen', async () => {
99
+ makeUserSkill(fakeHome, 'python');
100
+ makeUserSkill(fakeHome, 'backend');
101
+ const proj = makeProject(root);
102
+ const store = await loadStoreWithFakeHome(fakeHome);
103
+ expect(store.inspectProjectSkill(proj, 'python')?.state).toBe('absent');
104
+ store.addSkillToProject(proj, 'python');
105
+ const linked = store.inspectProjectSkill(proj, 'python');
106
+ expect(linked?.state).toBe('linked');
107
+ // 手工造一个 frozen
108
+ const backendDir = resolve(proj, '.claude', 'skills', 'backend');
109
+ mkdirSync(backendDir, { recursive: true });
110
+ writeFileSync(resolve(backendDir, 'SKILL.md'), '# custom\n');
111
+ expect(store.inspectProjectSkill(proj, 'backend')?.state).toBe('frozen');
112
+ });
113
+ it('freezeSkillInProject 把 symlink 转成独立副本', async () => {
114
+ makeUserSkill(fakeHome, 'python', { 'references/a.md': 'hello\n' });
115
+ const proj = makeProject(root);
116
+ const store = await loadStoreWithFakeHome(fakeHome);
117
+ store.addSkillToProject(proj, 'python');
118
+ expect(store.freezeSkillInProject(proj, 'python')).toBe(true);
119
+ const projPath = resolve(proj, '.claude', 'skills', 'python');
120
+ const stat = lstatSync(projPath);
121
+ expect(stat.isSymbolicLink()).toBe(false);
122
+ // 改源文件后,项目里的副本不应跟着变
123
+ const userPath = resolve(fakeHome, '.coral', 'skills', 'python', 'references', 'a.md');
124
+ writeFileSync(userPath, 'modified\n');
125
+ expect(readFileSync(resolve(projPath, 'references', 'a.md'), 'utf-8')).toBe('hello\n');
126
+ });
127
+ it('unfreezeSkillInProject 把真实副本转回 symlink', async () => {
128
+ makeUserSkill(fakeHome, 'python', { 'references/a.md': 'hello\n' });
129
+ const proj = makeProject(root);
130
+ const store = await loadStoreWithFakeHome(fakeHome);
131
+ store.addSkillToProject(proj, 'python');
132
+ store.freezeSkillInProject(proj, 'python');
133
+ expect(store.unfreezeSkillInProject(proj, 'python')).toBe(true);
134
+ const projPath = resolve(proj, '.claude', 'skills', 'python');
135
+ expect(lstatSync(projPath).isSymbolicLink()).toBe(true);
136
+ // 改源文件后,项目里会跟着变
137
+ const userPath = resolve(fakeHome, '.coral', 'skills', 'python', 'references', 'a.md');
138
+ writeFileSync(userPath, 'modified\n');
139
+ expect(readFileSync(resolve(projPath, 'references', 'a.md'), 'utf-8')).toBe('modified\n');
140
+ });
141
+ it('removeSkillFromProject 移除 symlink 不动源', async () => {
142
+ makeUserSkill(fakeHome, 'python');
143
+ const proj = makeProject(root);
144
+ const store = await loadStoreWithFakeHome(fakeHome);
145
+ store.addSkillToProject(proj, 'python');
146
+ expect(store.removeSkillFromProject(proj, 'python')).toBe(true);
147
+ expect(existsSync(resolve(proj, '.claude', 'skills', 'python'))).toBe(false);
148
+ // 源还在
149
+ expect(existsSync(resolve(fakeHome, '.coral', 'skills', 'python', 'SKILL.md'))).toBe(true);
150
+ });
151
+ it('syncAllSkillsToProject 对 20 个 skill 批量建 symlink,幂等', async () => {
152
+ const names = Array.from({ length: 20 }, (_, i) => `skill-${i}`);
153
+ for (const n of names)
154
+ makeUserSkill(fakeHome, n);
155
+ const proj = makeProject(root);
156
+ const store = await loadStoreWithFakeHome(fakeHome);
157
+ const first = store.syncAllSkillsToProject(proj);
158
+ expect(first.linked + first.copied).toBe(20);
159
+ const second = store.syncAllSkillsToProject(proj);
160
+ expect(second.kept).toBe(20);
161
+ });
162
+ it('syncBundledSkillsToUser 把 bundled 目录 cpSync 到 ~/.coral/skills/,不覆盖已存在', async () => {
163
+ const bundled = resolve(root, 'bundled');
164
+ mkdirSync(resolve(bundled, 'python'), { recursive: true });
165
+ writeFileSync(resolve(bundled, 'python', 'SKILL.md'), '# bundled python\n');
166
+ mkdirSync(resolve(bundled, 'rust'), { recursive: true });
167
+ writeFileSync(resolve(bundled, 'rust', 'SKILL.md'), '# bundled rust\n');
168
+ // 用户已有一个 python(用户改过)
169
+ makeUserSkill(fakeHome, 'python', {});
170
+ writeFileSync(resolve(fakeHome, '.coral', 'skills', 'python', 'SKILL.md'), '# user customized\n');
171
+ const store = await loadStoreWithFakeHome(fakeHome);
172
+ const res = store.syncBundledSkillsToUser(bundled);
173
+ expect(res.copied).toBe(1); // rust 新建
174
+ expect(res.skipped).toBe(1); // python 保留
175
+ expect(readFileSync(resolve(fakeHome, '.coral', 'skills', 'python', 'SKILL.md'), 'utf-8'))
176
+ .toBe('# user customized\n');
177
+ });
178
+ it('listUserSkills 包括 symlink 指向目录的 skill(外部包 vendor 接入模式)', async () => {
179
+ makeUserSkill(fakeHome, 'python');
180
+ // 模拟外部 skill 包:在别处建 skill 目录,再 symlink 进 ~/.coral/skills/
181
+ const externalRoot = resolve(root, 'external-pack');
182
+ mkdirSync(externalRoot, { recursive: true });
183
+ writeFileSync(resolve(externalRoot, 'SKILL.md'), '---\nname: external\n---\n# external\n');
184
+ const skillsDir = resolve(fakeHome, '.coral', 'skills');
185
+ const { symlinkSync } = await import('node:fs');
186
+ symlinkSync(externalRoot, resolve(skillsDir, 'external'), 'dir');
187
+ const store = await loadStoreWithFakeHome(fakeHome);
188
+ const names = store.listUserSkills().map((s) => s.name).sort();
189
+ expect(names).toEqual(['external', 'python']);
190
+ });
191
+ it('ensureSkillsGitignore 幂等追加 .claude/skills/', async () => {
192
+ const proj = makeProject(root);
193
+ const store = await loadStoreWithFakeHome(fakeHome);
194
+ const gitignore = resolve(proj, '.gitignore');
195
+ store.ensureSkillsGitignore(proj);
196
+ expect(readFileSync(gitignore, 'utf-8')).toContain('.claude/skills/');
197
+ // 再跑一次不应重复
198
+ store.ensureSkillsGitignore(proj);
199
+ const lines = readFileSync(gitignore, 'utf-8').split('\n').filter((l) => l.trim() === '.claude/skills/');
200
+ expect(lines.length).toBe(1);
201
+ });
202
+ });
203
+ //# sourceMappingURL=skillStore.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillStore.test.js","sourceRoot":"","sources":["../../src/core/skillStore.test.ts"],"names":[],"mappings":"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"}