@brms/ai-skills 0.1.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 (90) hide show
  1. package/README.md +256 -0
  2. package/bin/brms-skills.mjs +411 -0
  3. package/package.json +30 -0
  4. package/skills/brms-prototype-generator/SKILL.md +129 -0
  5. package/skills/brms-prototype-generator/agents/openai.yaml +7 -0
  6. package/skills/brms-prototype-generator/examples/few-shot-examples.md +577 -0
  7. package/skills/brms-prototype-generator/references/01-list-query.md +444 -0
  8. package/skills/brms-prototype-generator/references/02-form-entry.md +129 -0
  9. package/skills/brms-prototype-generator/references/03-detail-display.md +125 -0
  10. package/skills/brms-prototype-generator/references/04-composite-page-package.md +339 -0
  11. package/skills/brms-prototype-generator/references/05-dialog-patterns.md +113 -0
  12. package/skills/brms-prototype-generator/references/06-backend-request-patterns.md +118 -0
  13. package/skills/brms-prototype-generator/references/resource-index.md +46 -0
  14. package/skills/brms-prototype-generator/references/system-prompt.md +242 -0
  15. package/skills/brms-prototype-generator/scripts/analyze-doc.mjs +554 -0
  16. package/skills/brms-prototype-generator/scripts/check-project.mjs +228 -0
  17. package/skills/brms-prototype-generator/scripts/discover-targets.mjs +158 -0
  18. package/skills/brms-prototype-generator/scripts/install-codex.mjs +74 -0
  19. package/skills/brms-prototype-generator/scripts/plan-pages.mjs +390 -0
  20. package/skills/brms-prototype-generator/scripts/validate-generated.mjs +838 -0
  21. package/skills/brms-prototype-generator/templates/user-input-template.md +182 -0
  22. package/skills/brms-vxe-plus-developer/SKILL.md +105 -0
  23. package/skills/brms-vxe-plus-developer/agents/openai.yaml +7 -0
  24. package/skills/brms-vxe-plus-developer/references/prototype-to-real.md +54 -0
  25. package/skills/brms-vxe-plus-developer/references/real-base-development.md +110 -0
  26. package/skills/brms-vxe-plus-developer/references/resource-index.md +43 -0
  27. package/skills/brms-vxe-plus-developer/references/review-checklist.md +49 -0
  28. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/01-mental-model.md +150 -0
  29. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/02-vxe-plus-form.md +302 -0
  30. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/03-vxe-plus-table.md +253 -0
  31. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/04-example-map.md +488 -0
  32. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/05-request-and-eiinfo.md +170 -0
  33. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/90-anti-patterns.md +137 -0
  34. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/README.md +43 -0
  35. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/README.md +21 -0
  36. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P0/A1P01601.vue +483 -0
  37. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P1/A1P11011.vue +444 -0
  38. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AB/BP/ABBP0201.vue +1648 -0
  39. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Bidding/formConfig.ts +228 -0
  40. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Record/columns.ts +110 -0
  41. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/BMBR01.vue +130 -0
  42. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/columns.ts +94 -0
  43. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/formConfig.ts +108 -0
  44. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/formConfig.ts +123 -0
  45. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/index.vue +103 -0
  46. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/columns.ts +48 -0
  47. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/index.vue +202 -0
  48. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/formConfig.ts +117 -0
  49. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/index.vue +103 -0
  50. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/Payment/formConfig.ts +90 -0
  51. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/Payment/index.vue +42 -0
  52. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/columns.ts +376 -0
  53. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/index.vue +619 -0
  54. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Domestic/formConfig.ts +73 -0
  55. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Domestic/index.vue +47 -0
  56. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Foreign/formConfig.ts +141 -0
  57. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Foreign/index.vue +42 -0
  58. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/columns.ts +123 -0
  59. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/index.vue +593 -0
  60. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/index.vue +68 -0
  61. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/columns.ts +150 -0
  62. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/index.vue +235 -0
  63. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/columns.ts +63 -0
  64. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/index.vue +117 -0
  65. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/columns.ts +327 -0
  66. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/index.vue +790 -0
  67. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve/formConfig.ts +341 -0
  68. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve/index.vue +63 -0
  69. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve2/formConfig.ts +232 -0
  70. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve2/index.vue +27 -0
  71. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Diff/columns.ts +46 -0
  72. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Diff/index.vue +92 -0
  73. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/formConfig.ts +979 -0
  74. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/index.vue +62 -0
  75. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Other/formConfig.ts +179 -0
  76. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Other/index.vue +140 -0
  77. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Sign/formConfig.ts +118 -0
  78. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Sign/index.vue +44 -0
  79. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/index.vue +168 -0
  80. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/Major/formConfig.ts +257 -0
  81. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/Major/index.vue +47 -0
  82. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/columns.ts +256 -0
  83. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/index.vue +738 -0
  84. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/formConfig.ts +174 -0
  85. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/index.vue +51 -0
  86. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Top/index.vue +924 -0
  87. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/SM/SW/SMSW0101.vue +567 -0
  88. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/demo/index.vue +448 -0
  89. package/skills/brms-vxe-plus-developer/scripts/check-project.mjs +259 -0
  90. package/skills/brms-vxe-plus-developer/scripts/check-vxe-plus-page.mjs +137 -0
package/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # BRMS AI Skills
2
+
3
+ BRMS 共享 AI Skill 工具包。
4
+
5
+ 这个仓库维护 BRMS 项目可复用的 Claude/Codex skills,并提供 `brms-skills` CLI,把包内 skills 同步到业务项目的 `.claude/skills/` 目录。
6
+
7
+ ## Skill 清单
8
+
9
+ | Skill | 用途 |
10
+ | --- | --- |
11
+ | `brms-prototype-generator` | 从产品 Markdown 生成 BRMS/eplat-vue-base mock-first 原型页面。 |
12
+ | `brms-vxe-plus-developer` | 面向真实 `project/*` 开发的 VxePlus、EiInfo、业务页面开发规则。 |
13
+
14
+ 包内结构:
15
+
16
+ ```text
17
+ @brms/ai-skills/
18
+ |-- bin/
19
+ | `-- brms-skills.mjs
20
+ `-- skills/
21
+ |-- brms-prototype-generator/
22
+ `-- brms-vxe-plus-developer/
23
+ ```
24
+
25
+ 同步后的业务项目结构:
26
+
27
+ ```text
28
+ <project>/.claude/skills/
29
+ |-- brms-prototype-generator/
30
+ |-- brms-vxe-plus-developer/
31
+ `-- .brms-ai-skills-lock.json
32
+ ```
33
+
34
+ ## 命令清单
35
+
36
+ 在本仓库内直接运行:
37
+
38
+ ```bash
39
+ node ./bin/brms-skills.mjs list
40
+ node ./bin/brms-skills.mjs sync --project D:/Git/AI/eplat-vue-base
41
+ node ./bin/brms-skills.mjs status --project D:/Git/AI/eplat-vue-base
42
+ ```
43
+
44
+ 在业务项目安装依赖后运行:
45
+
46
+ ```bash
47
+ pnpm brms-skills list
48
+ pnpm brms-skills sync
49
+ pnpm brms-skills sync --link
50
+ pnpm brms-skills status
51
+ pnpm brms-skills clean --force
52
+ ```
53
+
54
+ Skill 执行前的项目检查命令:
55
+
56
+ ```bash
57
+ node .claude/skills/brms-prototype-generator/scripts/check-project.mjs --repo-root . --target-project project/<name> --format both
58
+ node .claude/skills/brms-vxe-plus-developer/scripts/check-project.mjs --repo-root . --target-project project/<name> --format both
59
+ ```
60
+
61
+ 检查结果分为 `Blockers`、`Warnings`、`Signals`。只有 `Blockers` 需要停止执行;`Warnings` 用来提醒当前项目可能采用不同的路由、菜单或文档布局。
62
+ 如果当前仓库里有多个有效的 `project/*` 子项目,检查会阻断执行并列出候选项目;此时需要先让用户确认目标项目,再带上 `--target-project project/<name>` 重新运行。
63
+
64
+ 命令说明:
65
+
66
+ | 命令 | 说明 |
67
+ | --- | --- |
68
+ | `brms-skills list` | 列出当前包内 skills。 |
69
+ | `brms-skills sync` | 复制 skills 到当前项目 `.claude/skills/`。 |
70
+ | `brms-skills sync --link` | 用链接模式同步,适合维护者本地调试。 |
71
+ | `brms-skills status` | 检查当前项目 skills 是否已同步、过期或被改动。 |
72
+ | `brms-skills clean --force` | 删除 lock 文件记录过的 managed skills。 |
73
+
74
+ 推荐在业务项目 `package.json` 中添加脚本:
75
+
76
+ ```json
77
+ {
78
+ "scripts": {
79
+ "skills:sync": "brms-skills sync",
80
+ "skills:status": "brms-skills status"
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## 本地维护流程
86
+
87
+ 适用于维护 `D:/Git/AI/brms-ai-skills` 的人。
88
+
89
+ 先在业务项目里安装本地包:
90
+
91
+ ```bash
92
+ cd D:/Git/AI/eplat-vue-base
93
+ pnpm add -D D:/Git/AI/brms-ai-skills
94
+ ```
95
+
96
+ 然后用 link 模式同步:
97
+
98
+ ```bash
99
+ pnpm brms-skills sync --link
100
+ pnpm brms-skills status
101
+ ```
102
+
103
+ 这时链路大致是:
104
+
105
+ ```text
106
+ <project>/node_modules/@brms/ai-skills
107
+ -> D:/Git/AI/brms-ai-skills
108
+
109
+ <project>/.claude/skills/brms-prototype-generator
110
+ -> <project>/node_modules/@brms/ai-skills/skills/brms-prototype-generator
111
+
112
+ <project>/.claude/skills/brms-vxe-plus-developer
113
+ -> <project>/node_modules/@brms/ai-skills/skills/brms-vxe-plus-developer
114
+ ```
115
+
116
+ 之后只需要修改:
117
+
118
+ ```text
119
+ D:/Git/AI/brms-ai-skills/skills/...
120
+ ```
121
+
122
+ 业务项目里的 `.claude/skills/...` 会立即看到变化,不需要每次重新复制。
123
+
124
+ 准备发布、打版本,或给其他项目正式使用前,再用复制模式做一次冒烟验证:
125
+
126
+ ```bash
127
+ pnpm brms-skills sync
128
+ pnpm brms-skills status
129
+ ```
130
+
131
+ ## 新项目接入流程
132
+
133
+ 适用于业务项目首次接入这套 skills。
134
+
135
+ 1. 安装工具包。
136
+
137
+ 本地调试阶段:
138
+
139
+ ```bash
140
+ pnpm add -D D:/Git/AI/brms-ai-skills
141
+ ```
142
+
143
+ 正式团队使用时,建议改成内部 npm 包、Git tag 依赖,或其他团队成员都能访问的来源:
144
+
145
+ ```bash
146
+ pnpm add -D @brms/ai-skills
147
+ ```
148
+
149
+ 2. 添加项目脚本。
150
+
151
+ ```json
152
+ {
153
+ "scripts": {
154
+ "skills:sync": "brms-skills sync",
155
+ "skills:status": "brms-skills status"
156
+ }
157
+ }
158
+ ```
159
+
160
+ 3. 添加 `.gitignore`。
161
+
162
+ 业务项目不要提交同步出来的 skill 内容,否则会重新变成“每个项目复制一份”的版本漂移模式。
163
+
164
+ 建议忽略:
165
+
166
+ ```gitignore
167
+ .claude/skills/brms-prototype-generator/
168
+ .claude/skills/brms-vxe-plus-developer/
169
+ .claude/skills/.brms-ai-skills-lock.json
170
+ ```
171
+
172
+ 业务项目只提交依赖声明和锁定文件:
173
+
174
+ ```text
175
+ package.json
176
+ pnpm-lock.yaml
177
+ ```
178
+
179
+ `package.json` 声明依赖和脚本;`pnpm-lock.yaml` 锁定实际安装到的精确版本或来源。
180
+
181
+ 4. 同步 skills。
182
+
183
+ ```bash
184
+ pnpm install
185
+ pnpm skills:sync
186
+ pnpm skills:status
187
+ ```
188
+
189
+ 团队正式使用建议走默认复制模式。复制后的 `.claude/skills/...` 是本地生成物,不进入业务项目 Git。
190
+
191
+ ## 同步模式
192
+
193
+ 默认复制模式:
194
+
195
+ ```bash
196
+ pnpm brms-skills sync
197
+ ```
198
+
199
+ 复制模式最稳。即使业务项目删除了 `node_modules`,`.claude/skills` 里的 skills 仍然可用。团队正式使用时,建议默认走复制模式。
200
+
201
+ 本地 link 模式:
202
+
203
+ ```bash
204
+ pnpm brms-skills sync --link
205
+ ```
206
+
207
+ Windows 下 link 模式会创建目录 junction;macOS/Linux 下会创建目录 symlink。它适合维护者本地快速调试 skill 内容。
208
+
209
+ ## 锁文件和状态
210
+
211
+ `sync` 会写入:
212
+
213
+ ```text
214
+ .claude/skills/.brms-ai-skills-lock.json
215
+ ```
216
+
217
+ 锁文件记录包名、包版本、同步模式、同步时间、来源路径、目标路径和每个 skill 的内容 hash。
218
+
219
+ `status` 状态说明:
220
+
221
+ | 状态 | 含义 |
222
+ | --- | --- |
223
+ | `ok` | 目标内容和当前包版本一致。 |
224
+ | `outdated` | 包版本或包内 skill 内容已经变化,但目标项目还没重新同步。 |
225
+ | `modified` | 目标项目 `.claude/skills` 里的 skill 同步后被改过。 |
226
+ | `missing` | lock 记录了该 skill,但目标目录已经不存在。 |
227
+ | `unmanaged` | 目标目录存在 skill,但它不在 BRMS lock 里。 |
228
+ | `not-synced` | 包里有该 skill,但目标项目还没有同步。 |
229
+
230
+ ## 清理
231
+
232
+ `clean` 只删除 BRMS lock 文件记录过的 managed skills:
233
+
234
+ ```bash
235
+ pnpm brms-skills clean --force
236
+ ```
237
+
238
+ 该命令必须显式加 `--force`,避免误删项目文件。
239
+
240
+ ## 版本策略
241
+
242
+ 当前包仍然是:
243
+
244
+ ```json
245
+ {
246
+ "private": true
247
+ }
248
+ ```
249
+
250
+ 如果后续要发布到内部 npm registry,再移除 `private`,配置 registry,并按语义化版本维护:
251
+
252
+ | 版本 | 场景 |
253
+ | --- | --- |
254
+ | `patch` | 修 skill 规则、校验器、文档、示例。 |
255
+ | `minor` | 新增 skill,或新增向后兼容的 CLI 能力。 |
256
+ | `major` | 改变 skill 发现方式、同步布局、命令语义等破坏性行为。 |
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env node
2
+ import crypto from 'node:crypto'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const PACKAGE_NAME = '@brms/ai-skills'
8
+ const LOCK_FILE = '.brms-ai-skills-lock.json'
9
+
10
+ const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
11
+ const packageJsonPath = path.join(rootDir, 'package.json')
12
+ const skillsSourceRoot = path.join(rootDir, 'skills')
13
+
14
+ function readPackageJson() {
15
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
16
+ }
17
+
18
+ function usage() {
19
+ return `BRMS AI Skills
20
+
21
+ Usage:
22
+ brms-skills list [--json]
23
+ brms-skills sync [--project <dir>] [--target <dir>] [--copy|--link] [--force] [--json]
24
+ brms-skills status [--project <dir>] [--target <dir>] [--json]
25
+ brms-skills clean [--project <dir>] [--target <dir>] [--force] [--json]
26
+
27
+ Defaults:
28
+ project: current working directory
29
+ target: <project>/.claude/skills
30
+ mode: copy
31
+ `
32
+ }
33
+
34
+ function parseArgs(argv) {
35
+ const parsed = {
36
+ command: argv[0] || 'help',
37
+ project: process.cwd(),
38
+ target: '',
39
+ mode: 'copy',
40
+ force: false,
41
+ json: false,
42
+ }
43
+
44
+ for (let i = 1; i < argv.length; i += 1) {
45
+ const arg = argv[i]
46
+ if (arg === '--project') {
47
+ parsed.project = argv[i + 1] || ''
48
+ i += 1
49
+ }
50
+ else if (arg === '--target') {
51
+ parsed.target = argv[i + 1] || ''
52
+ i += 1
53
+ }
54
+ else if (arg === '--copy') {
55
+ parsed.mode = 'copy'
56
+ }
57
+ else if (arg === '--link') {
58
+ parsed.mode = 'link'
59
+ }
60
+ else if (arg === '--force') {
61
+ parsed.force = true
62
+ }
63
+ else if (arg === '--json') {
64
+ parsed.json = true
65
+ }
66
+ else if (arg === '--help' || arg === '-h') {
67
+ parsed.command = 'help'
68
+ }
69
+ else {
70
+ throw new Error(`Unknown argument: ${arg}`)
71
+ }
72
+ }
73
+
74
+ parsed.project = path.resolve(parsed.project || process.cwd())
75
+ parsed.target = path.resolve(parsed.target || path.join(parsed.project, '.claude', 'skills'))
76
+ return parsed
77
+ }
78
+
79
+ function listSkills() {
80
+ if (!fs.existsSync(skillsSourceRoot))
81
+ return []
82
+
83
+ return fs.readdirSync(skillsSourceRoot, { withFileTypes: true })
84
+ .filter(entry => entry.isDirectory())
85
+ .map(entry => {
86
+ const dir = path.join(skillsSourceRoot, entry.name)
87
+ return {
88
+ name: entry.name,
89
+ source: dir,
90
+ hasSkillMd: fs.existsSync(path.join(dir, 'SKILL.md')),
91
+ hash: hashDirectory(dir),
92
+ }
93
+ })
94
+ .filter(skill => skill.hasSkillMd)
95
+ .sort((a, b) => a.name.localeCompare(b.name))
96
+ }
97
+
98
+ function hashDirectory(dir) {
99
+ const hash = crypto.createHash('sha256')
100
+ const files = []
101
+
102
+ function walk(current) {
103
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
104
+ if (entry.name === '.DS_Store')
105
+ continue
106
+
107
+ const fullPath = path.join(current, entry.name)
108
+ if (entry.isDirectory()) {
109
+ walk(fullPath)
110
+ }
111
+ else if (entry.isFile()) {
112
+ files.push(fullPath)
113
+ }
114
+ }
115
+ }
116
+
117
+ walk(dir)
118
+ files.sort()
119
+
120
+ for (const file of files) {
121
+ const rel = path.relative(dir, file).replaceAll(path.sep, '/')
122
+ hash.update(rel)
123
+ hash.update('\0')
124
+ hash.update(fs.readFileSync(file))
125
+ hash.update('\0')
126
+ }
127
+
128
+ return hash.digest('hex')
129
+ }
130
+
131
+ function removePath(target) {
132
+ fs.rmSync(target, { recursive: true, force: true })
133
+ }
134
+
135
+ function copyDirectory(source, target) {
136
+ removePath(target)
137
+ fs.mkdirSync(path.dirname(target), { recursive: true })
138
+ fs.cpSync(source, target, {
139
+ recursive: true,
140
+ filter: src => !src.endsWith(`${path.sep}.DS_Store`),
141
+ })
142
+ }
143
+
144
+ function linkDirectory(source, target) {
145
+ removePath(target)
146
+ fs.mkdirSync(path.dirname(target), { recursive: true })
147
+ const linkType = process.platform === 'win32' ? 'junction' : 'dir'
148
+ fs.symlinkSync(source, target, linkType)
149
+ }
150
+
151
+ function readLock(targetRoot) {
152
+ const lockPath = path.join(targetRoot, LOCK_FILE)
153
+ if (!fs.existsSync(lockPath))
154
+ return null
155
+
156
+ try {
157
+ return JSON.parse(fs.readFileSync(lockPath, 'utf8'))
158
+ }
159
+ catch {
160
+ return null
161
+ }
162
+ }
163
+
164
+ function writeLock(targetRoot, lock) {
165
+ fs.mkdirSync(targetRoot, { recursive: true })
166
+ fs.writeFileSync(path.join(targetRoot, LOCK_FILE), `${JSON.stringify(lock, null, 2)}\n`)
167
+ }
168
+
169
+ function ensureSafeTarget(source, target) {
170
+ const resolvedSource = path.resolve(source)
171
+ const resolvedTarget = path.resolve(target)
172
+ if (resolvedSource === resolvedTarget)
173
+ throw new Error(`Refusing to sync ${target}: source and target are the same path.`)
174
+ if (resolvedTarget.startsWith(`${resolvedSource}${path.sep}`))
175
+ throw new Error(`Refusing to sync ${target}: target is inside source.`)
176
+ }
177
+
178
+ function canOverwriteSkill(targetRoot, skillName, force) {
179
+ const target = path.join(targetRoot, skillName)
180
+ if (!fs.existsSync(target))
181
+ return true
182
+
183
+ const lock = readLock(targetRoot)
184
+ if (lock?.skills?.[skillName])
185
+ return true
186
+
187
+ if (force)
188
+ return true
189
+
190
+ return false
191
+ }
192
+
193
+ function commandList(args) {
194
+ const pkg = readPackageJson()
195
+ const skills = listSkills()
196
+ const result = {
197
+ packageName: pkg.name || PACKAGE_NAME,
198
+ packageVersion: pkg.version,
199
+ skills: skills.map(({ name, source, hash }) => ({ name, source, hash })),
200
+ }
201
+
202
+ if (args.json) {
203
+ console.log(JSON.stringify(result, null, 2))
204
+ return
205
+ }
206
+
207
+ console.log(`BRMS AI Skills ${result.packageVersion}`)
208
+ console.log('')
209
+ for (const skill of result.skills)
210
+ console.log(`- ${skill.name}`)
211
+ }
212
+
213
+ function commandSync(args) {
214
+ const pkg = readPackageJson()
215
+ const skills = listSkills()
216
+ if (!skills.length)
217
+ throw new Error(`No skills found under ${skillsSourceRoot}`)
218
+
219
+ fs.mkdirSync(args.target, { recursive: true })
220
+
221
+ const synced = []
222
+ for (const skill of skills) {
223
+ const target = path.join(args.target, skill.name)
224
+ ensureSafeTarget(skill.source, target)
225
+
226
+ if (!canOverwriteSkill(args.target, skill.name, args.force)) {
227
+ throw new Error(
228
+ `Refusing to overwrite unmanaged skill ${target}. Re-run with --force if this is intentional.`,
229
+ )
230
+ }
231
+
232
+ if (args.mode === 'link')
233
+ linkDirectory(skill.source, target)
234
+ else
235
+ copyDirectory(skill.source, target)
236
+
237
+ synced.push({
238
+ name: skill.name,
239
+ version: pkg.version,
240
+ mode: args.mode,
241
+ source: path.relative(args.project, skill.source).replaceAll(path.sep, '/'),
242
+ target: path.relative(args.project, target).replaceAll(path.sep, '/'),
243
+ hash: skill.hash,
244
+ })
245
+ }
246
+
247
+ const lock = {
248
+ packageName: pkg.name || PACKAGE_NAME,
249
+ packageVersion: pkg.version,
250
+ syncMode: args.mode,
251
+ syncedAt: new Date().toISOString(),
252
+ targetRoot: path.relative(args.project, args.target).replaceAll(path.sep, '/'),
253
+ skills: Object.fromEntries(synced.map(skill => [skill.name, {
254
+ version: skill.version,
255
+ mode: skill.mode,
256
+ source: skill.source,
257
+ target: skill.target,
258
+ hash: skill.hash,
259
+ }])),
260
+ }
261
+ writeLock(args.target, lock)
262
+
263
+ const result = { status: 'ok', target: args.target, synced, lockFile: path.join(args.target, LOCK_FILE) }
264
+ if (args.json) {
265
+ console.log(JSON.stringify(result, null, 2))
266
+ return
267
+ }
268
+
269
+ console.log(`Synced ${synced.length} skills to ${args.target}`)
270
+ for (const skill of synced)
271
+ console.log(`- ${skill.name} ${pkg.version} ${args.mode}`)
272
+ console.log(`Lock: ${path.join(args.target, LOCK_FILE)}`)
273
+ }
274
+
275
+ function skillStatus(args) {
276
+ const pkg = readPackageJson()
277
+ const lock = readLock(args.target)
278
+ const sourceSkills = new Map(listSkills().map(skill => [skill.name, skill]))
279
+
280
+ const names = Array.from(new Set([
281
+ ...sourceSkills.keys(),
282
+ ...Object.keys(lock?.skills || {}),
283
+ ])).sort()
284
+
285
+ const skills = names.map(name => {
286
+ const source = sourceSkills.get(name)
287
+ const target = path.join(args.target, name)
288
+ const locked = lock?.skills?.[name] || null
289
+ const exists = fs.existsSync(target)
290
+ const currentHash = exists && fs.statSync(target).isDirectory() ? hashDirectory(target) : ''
291
+ let status = 'missing'
292
+
293
+ if (!source) {
294
+ status = 'unknown'
295
+ }
296
+ else if (!locked) {
297
+ status = exists ? 'unmanaged' : 'not-synced'
298
+ }
299
+ else if (!exists) {
300
+ status = 'missing'
301
+ }
302
+ else if (locked.hash !== currentHash) {
303
+ status = 'modified'
304
+ }
305
+ else if (locked.hash !== source.hash || locked.version !== pkg.version) {
306
+ status = 'outdated'
307
+ }
308
+ else {
309
+ status = 'ok'
310
+ }
311
+
312
+ return {
313
+ name,
314
+ status,
315
+ syncedVersion: locked?.version || null,
316
+ packageVersion: source ? pkg.version : null,
317
+ mode: locked?.mode || null,
318
+ target,
319
+ }
320
+ })
321
+
322
+ const overall = skills.every(skill => skill.status === 'ok') ? 'up to date' : 'attention needed'
323
+ return {
324
+ packageName: pkg.name || PACKAGE_NAME,
325
+ packageVersion: pkg.version,
326
+ project: args.project,
327
+ target: args.target,
328
+ lockFile: path.join(args.target, LOCK_FILE),
329
+ lockFound: Boolean(lock),
330
+ overall,
331
+ skills,
332
+ }
333
+ }
334
+
335
+ function commandStatus(args) {
336
+ const result = skillStatus(args)
337
+ if (args.json) {
338
+ console.log(JSON.stringify(result, null, 2))
339
+ return
340
+ }
341
+
342
+ console.log('BRMS AI Skills')
343
+ console.log('')
344
+ console.log(`Package: ${result.packageName} ${result.packageVersion}`)
345
+ console.log(`Project: ${result.project}`)
346
+ console.log(`Target: ${result.target}`)
347
+ console.log(`Lock: ${result.lockFound ? result.lockFile : 'not found'}`)
348
+ console.log('')
349
+ for (const skill of result.skills) {
350
+ const version = skill.syncedVersion || '-'
351
+ console.log(`${skill.name.padEnd(28)} ${version.padEnd(8)} ${skill.status}`)
352
+ }
353
+ console.log('')
354
+ console.log(`Status: ${result.overall}`)
355
+ }
356
+
357
+ function commandClean(args) {
358
+ const lock = readLock(args.target)
359
+ const names = Object.keys(lock?.skills || {})
360
+ if (!names.length)
361
+ throw new Error(`No managed skills found in ${path.join(args.target, LOCK_FILE)}`)
362
+
363
+ if (!args.force)
364
+ throw new Error('Clean removes managed skill directories. Re-run with --force if this is intentional.')
365
+
366
+ for (const name of names)
367
+ removePath(path.join(args.target, name))
368
+
369
+ removePath(path.join(args.target, LOCK_FILE))
370
+
371
+ const result = { status: 'ok', removed: names, target: args.target }
372
+ if (args.json) {
373
+ console.log(JSON.stringify(result, null, 2))
374
+ return
375
+ }
376
+
377
+ console.log(`Removed ${names.length} managed skills from ${args.target}`)
378
+ for (const name of names)
379
+ console.log(`- ${name}`)
380
+ }
381
+
382
+ function main() {
383
+ const args = parseArgs(process.argv.slice(2))
384
+ switch (args.command) {
385
+ case 'list':
386
+ commandList(args)
387
+ break
388
+ case 'sync':
389
+ commandSync(args)
390
+ break
391
+ case 'status':
392
+ commandStatus(args)
393
+ break
394
+ case 'clean':
395
+ commandClean(args)
396
+ break
397
+ case 'help':
398
+ console.log(usage())
399
+ break
400
+ default:
401
+ throw new Error(`Unknown command: ${args.command}\n\n${usage()}`)
402
+ }
403
+ }
404
+
405
+ try {
406
+ main()
407
+ }
408
+ catch (error) {
409
+ console.error(error instanceof Error ? error.message : String(error))
410
+ process.exit(1)
411
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@brms/ai-skills",
3
+ "version": "0.1.0",
4
+ "description": "Shared BRMS AI skills and project sync tooling.",
5
+ "type": "module",
6
+ "bin": {
7
+ "brms-skills": "./bin/brms-skills.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "skills",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "skills:list": "node ./bin/brms-skills.mjs list",
16
+ "skills:status": "node ./bin/brms-skills.mjs status",
17
+ "skills:sync": "node ./bin/brms-skills.mjs sync",
18
+ "skills:clean": "node ./bin/brms-skills.mjs clean"
19
+ },
20
+ "engines": {
21
+ "node": ">=18.17.0"
22
+ },
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/YOUR_USERNAME/brms-ai-skills.git"
27
+ },
28
+ "keywords": ["ai", "skills", "brms"],
29
+ "author": "池樱千幻"
30
+ }