@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.
- package/README.md +256 -0
- package/bin/brms-skills.mjs +411 -0
- package/package.json +30 -0
- package/skills/brms-prototype-generator/SKILL.md +129 -0
- package/skills/brms-prototype-generator/agents/openai.yaml +7 -0
- package/skills/brms-prototype-generator/examples/few-shot-examples.md +577 -0
- package/skills/brms-prototype-generator/references/01-list-query.md +444 -0
- package/skills/brms-prototype-generator/references/02-form-entry.md +129 -0
- package/skills/brms-prototype-generator/references/03-detail-display.md +125 -0
- package/skills/brms-prototype-generator/references/04-composite-page-package.md +339 -0
- package/skills/brms-prototype-generator/references/05-dialog-patterns.md +113 -0
- package/skills/brms-prototype-generator/references/06-backend-request-patterns.md +118 -0
- package/skills/brms-prototype-generator/references/resource-index.md +46 -0
- package/skills/brms-prototype-generator/references/system-prompt.md +242 -0
- package/skills/brms-prototype-generator/scripts/analyze-doc.mjs +554 -0
- package/skills/brms-prototype-generator/scripts/check-project.mjs +228 -0
- package/skills/brms-prototype-generator/scripts/discover-targets.mjs +158 -0
- package/skills/brms-prototype-generator/scripts/install-codex.mjs +74 -0
- package/skills/brms-prototype-generator/scripts/plan-pages.mjs +390 -0
- package/skills/brms-prototype-generator/scripts/validate-generated.mjs +838 -0
- package/skills/brms-prototype-generator/templates/user-input-template.md +182 -0
- package/skills/brms-vxe-plus-developer/SKILL.md +105 -0
- package/skills/brms-vxe-plus-developer/agents/openai.yaml +7 -0
- package/skills/brms-vxe-plus-developer/references/prototype-to-real.md +54 -0
- package/skills/brms-vxe-plus-developer/references/real-base-development.md +110 -0
- package/skills/brms-vxe-plus-developer/references/resource-index.md +43 -0
- package/skills/brms-vxe-plus-developer/references/review-checklist.md +49 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/01-mental-model.md +150 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/02-vxe-plus-form.md +302 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/03-vxe-plus-table.md +253 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/04-example-map.md +488 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/05-request-and-eiinfo.md +170 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/90-anti-patterns.md +137 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/README.md +43 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/README.md +21 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P0/A1P01601.vue +483 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P1/A1P11011.vue +444 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AB/BP/ABBP0201.vue +1648 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Bidding/formConfig.ts +228 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Record/columns.ts +110 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/BMBR01.vue +130 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/columns.ts +94 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/formConfig.ts +108 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/formConfig.ts +123 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/index.vue +103 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/columns.ts +48 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/index.vue +202 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/formConfig.ts +117 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/index.vue +103 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/index.vue +68 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/columns.ts +150 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/index.vue +235 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/columns.ts +63 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/index.vue +117 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/columns.ts +327 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/index.vue +790 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/index.vue +168 -0
- 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
- 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
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/columns.ts +256 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/index.vue +738 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/formConfig.ts +174 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/index.vue +51 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Top/index.vue +924 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/SM/SW/SMSW0101.vue +567 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/demo/index.vue +448 -0
- package/skills/brms-vxe-plus-developer/scripts/check-project.mjs +259 -0
- 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
|
+
}
|