opencode-skills-doctor 0.0.0-stage → 0.3.2
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/LICENSE +21 -0
- package/README.md +196 -2
- package/README.zh-CN.md +197 -0
- package/bin/skills-doctor.mjs +7 -0
- package/package.json +50 -4
- package/src/audit.mjs +93 -0
- package/src/catalog.mjs +166 -0
- package/src/cli.mjs +495 -0
- package/src/config.mjs +34 -0
- package/src/discover.mjs +308 -0
- package/src/fix.mjs +166 -0
- package/src/i18n.mjs +306 -0
- package/src/lint.mjs +218 -0
- package/src/managed.mjs +76 -0
- package/src/parse.mjs +132 -0
- package/src/plugin-v1.mjs +31 -0
- package/src/plugin.mjs +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wangyuchen5110
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# skills-doctor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/wangyuchen5110/opencodeplugginprogram/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
Local scanner, linter, fixer, updater and **OpenCode plugin** for skills — zero dependencies, Node ≥ 20.
|
|
6
|
+
|
|
7
|
+
**English** | [简体中文](README.zh-CN.md)
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
On every model step, OpenCode advertises every skill that has a `description` — the model sees `ID + name + description` for each of them. As skills pile up, this turns into **context bloat, colliding descriptions, shadowed IDs, edits to the wrong file, and skills that silently stop working**.
|
|
12
|
+
|
|
13
|
+
`skills-doctor` surfaces all of that locally, and can also update skills from an HTTP catalog and verify the result.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
# Run straight from the repo (no install)
|
|
19
|
+
node bin/skills-doctor.mjs check
|
|
20
|
+
|
|
21
|
+
# Or install it locally
|
|
22
|
+
npm link
|
|
23
|
+
skills-doctor check
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
All commands accept `--lang en|zh` (default: auto from the system locale) and `--json`.
|
|
29
|
+
|
|
30
|
+
### `check` (default)
|
|
31
|
+
|
|
32
|
+
Scans every skill source, reports problems, the context budget and fix hints.
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
skills-doctor check
|
|
36
|
+
skills-doctor check --json
|
|
37
|
+
skills-doctor check --budget 800 --strict # for CI
|
|
38
|
+
skills-doctor check --root .\some-project --no-global
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `scan`
|
|
42
|
+
|
|
43
|
+
Lists every skill with its source, precedence, whether the model can see it, and token cost.
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
skills-doctor scan
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`*` marks the definition that actually wins (later registration = higher precedence).
|
|
50
|
+
|
|
51
|
+
### `fix`
|
|
52
|
+
|
|
53
|
+
Archives definitions that are shadowed — files that never load because another source defines the same ID. The move is reversible: files go into `.skills-doctor-archive/<timestamp>/` inside the same source, which OpenCode ignores (dot-directory).
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
skills-doctor fix --dry-run # preview what would be archived
|
|
57
|
+
skills-doctor fix
|
|
58
|
+
skills-doctor fix --hide academic-paper,deep-research --dry-run # hide low-frequency skills
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Only shadowed duplicates are touched; the effective definition is never moved;
|
|
62
|
+
- Files installed by `update` are skipped — use `update` to bring them back;
|
|
63
|
+
- Default scope is project-only; pass `--all` to also archive global sources;
|
|
64
|
+
- After fixing, `check` should report no `shadowed` findings.
|
|
65
|
+
|
|
66
|
+
### `update` (updater)
|
|
67
|
+
|
|
68
|
+
Reads HTTP catalogs from the `skills` array in `opencode.json(c)`, compares `version`, and downloads whatever changed.
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
skills-doctor update # installs into .opencode/skills by default (where opencode reads)
|
|
72
|
+
skills-doctor update --check-only # only report available updates
|
|
73
|
+
skills-doctor update --into .\team-skills
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- The output directory is a real OpenCode skill directory, so **`check` picks the update up immediately**;
|
|
77
|
+
- Writes `.skills-doctor.lock.json` (schema v2: a sha1 for every file);
|
|
78
|
+
- `check` reads that lock: shows managed versions, detects hand edits (`managed-drift`) and missing files (`managed-missing`);
|
|
79
|
+
- Running `update` again re-downloads drifted files (self-healing).
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
Optional `.skills-doctor.json` in the working directory (or pass `--config <file>`):
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"budget": 1200,
|
|
88
|
+
"staleDays": 180,
|
|
89
|
+
"lang": "en",
|
|
90
|
+
"rules": { "long-description": "off", "hidden-skill": "info" },
|
|
91
|
+
"ignore": ["legacy-skill"]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`rules` accepts `off`, `info`, `warn` or `error`; `ignore` drops findings that only involve those IDs. CLI flags override the file.
|
|
96
|
+
|
|
97
|
+
## OpenCode plugin
|
|
98
|
+
|
|
99
|
+
The package also exports an OpenCode 2.x plugin:
|
|
100
|
+
|
|
101
|
+
```jsonc
|
|
102
|
+
// opencode.jsonc
|
|
103
|
+
{
|
|
104
|
+
"plugins": ["opencode-skills-doctor"]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It registers:
|
|
109
|
+
|
|
110
|
+
- the `skills_doctor` tool (namespace `skills`) — the agent can audit with `{ directory?, budget? }` and gets a markdown report;
|
|
111
|
+
- the `/skills-doctor` command — asks the agent to run the audit and summarize fixes.
|
|
112
|
+
|
|
113
|
+
`@opencode/plugin` is an optional peer dependency; OpenCode provides it at runtime.
|
|
114
|
+
|
|
115
|
+
### OpenCode 1.x
|
|
116
|
+
|
|
117
|
+
1.x uses a different plugin API. Point the `plugin` array at the `/v1` entry:
|
|
118
|
+
|
|
119
|
+
```jsonc
|
|
120
|
+
// opencode.json (1.x)
|
|
121
|
+
{
|
|
122
|
+
"plugin": ["opencode-skills-doctor/v1"]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
It registers the same `skills_doctor` tool (1.x has no `/skills-doctor` command hook). The package must be installed where OpenCode resolves plugins, e.g.:
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
npm i --prefix ~/.config/opencode opencode-skills-doctor
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> The package root entry (`import "opencode-skills-doctor"`) is the V2 plugin and requires OpenCode 2.x. The CLI works on both lines.
|
|
133
|
+
|
|
134
|
+
## Rules
|
|
135
|
+
|
|
136
|
+
| Rule | Level | Meaning |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `shadowed` | warn | Same ID defined in multiple sources; only one wins, the rest is dead weight and easy to edit by mistake |
|
|
139
|
+
| `flat-dir-collision` | warn | Both `<id>.md` and `<id>/SKILL.md` exist in the same source |
|
|
140
|
+
| `duplicate-content` | warn | Two skills share identical body text |
|
|
141
|
+
| `near-duplicate-description` | warn | Descriptions are ≥ 75% similar; the model may pick the wrong one |
|
|
142
|
+
| `missing-description` | info | No description, so the model never discovers it |
|
|
143
|
+
| `long-description` | warn | Description > 240 chars (paid on every model step) |
|
|
144
|
+
| `large-body` | warn | Body > 4000 tokens (pasted into context when invoked) |
|
|
145
|
+
| `token-budget` | warn/error | Total advertised description tokens exceed the budget (default 1200) |
|
|
146
|
+
| `too-many-skills` | warn/error | More than 60 / 100 skills are advertised |
|
|
147
|
+
| `invalid-id` | info | ID is not kebab-case |
|
|
148
|
+
| `hidden-skill` | info | Hidden via `opencode/autoinvoke: false` or `disable-model-invocation: true` |
|
|
149
|
+
| `stale-skill` | info | Not updated within `--stale-days` |
|
|
150
|
+
| `managed-drift` | warn | A file installed by `update` was modified afterwards |
|
|
151
|
+
| `managed-missing` | warn | Recorded in the lock, but the local file is gone |
|
|
152
|
+
| `managed-unsafe` | warn | Path traversal or another unsafe path in the lock |
|
|
153
|
+
| `lock-version-unsupported` | info | Lock is newer than this tool; parsed in compatibility mode |
|
|
154
|
+
|
|
155
|
+
## Forward compatibility
|
|
156
|
+
|
|
157
|
+
- **Lock**: supports v1 (string versions, no hashes) and v2 (per-file sha1). A newer lock version is parsed in compatibility mode, unknown fields are ignored, and a single `lock-version-unsupported` notice is emitted.
|
|
158
|
+
- **Catalog `index.json`**: only `skills[].name/version/files` is read; any future fields are ignored.
|
|
159
|
+
- **Skill frontmatter**: only `name`, `description`, `metadata.opencode/autoinvoke` and `disable-model-invocation` are read; everything else is ignored.
|
|
160
|
+
- **Paths**: both OpenCode 2.0 locations (`.opencode/skills`, `~/.config/opencode/skills`) and compatibility locations (`.claude/skills`, `.agents/skills`, searched upward level by level).
|
|
161
|
+
- The updater always writes the highest lock version this tool supports, so older readers never break on new fields.
|
|
162
|
+
|
|
163
|
+
## Discovery sources (low → high precedence)
|
|
164
|
+
|
|
165
|
+
1. `~/.claude/skills`
|
|
166
|
+
2. project `.claude/skills` (from the project root up to the current directory)
|
|
167
|
+
3. `~/.agents/skills`
|
|
168
|
+
4. project `.agents/skills`
|
|
169
|
+
5. `~/.config/opencode/skills`
|
|
170
|
+
6. project `.opencode/skills`
|
|
171
|
+
7. the `skills` array in `opencode.json(c)` (2.x) or `skills.paths` (1.x); relative paths resolve from the working directory; supports `~/`, absolute paths and HTTP catalogs
|
|
172
|
+
|
|
173
|
+
## JSON output
|
|
174
|
+
|
|
175
|
+
`check --json`, `scan --json` and `update --json` all print structured JSON, ready for CI or a web report. Fields include `summary`, `findings[]` and `skills[]` (with `managed: { version, url, root }`).
|
|
176
|
+
|
|
177
|
+
## Known limitations
|
|
178
|
+
|
|
179
|
+
- `check` is offline; use `update --check-only` to ask a catalog for new versions.
|
|
180
|
+
- Token counts are heuristic (CJK ≈ 1.7 chars/token, the rest ≈ 4 chars/token) — good for comparisons, not for billing.
|
|
181
|
+
- Config precedence is assumed: global config < project config < `skills` array order. If OpenCode differs, trust the runtime.
|
|
182
|
+
- No automatic file rewriting yet (v0.2 plans `--fix`: archive shadowed definitions, normalize IDs, trim descriptions).
|
|
183
|
+
|
|
184
|
+
## Roadmap
|
|
185
|
+
|
|
186
|
+
- v0.4: content-aware suggestions (which skills are never used), catalog signature verification
|
|
187
|
+
- Later: V1/Claude skill migration report
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```powershell
|
|
192
|
+
npm test
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Coverage: frontmatter parsing, source discovery and precedence, shadowing/duplication/budget rules, catalog download and lock, drift detection and self-healing, old/new lock compatibility, CLI JSON output and exit codes.
|
|
196
|
+
|
|
197
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# skills-doctor
|
|
2
|
+
|
|
3
|
+
[](https://github.com/wangyuchen5110/opencodeplugginprogram/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
OpenCode skill 的**本地检测器 + 更新器 + 修复器**,并附带 **OpenCode 插件入口** —— 零依赖,Node ≥ 20。
|
|
6
|
+
|
|
7
|
+
[English](README.md) | **简体中文**
|
|
8
|
+
|
|
9
|
+
## 为什么需要它
|
|
10
|
+
|
|
11
|
+
opencode 每个模型步骤都会把所有「有 description 的 skill」展示给模型(`ID + name + description`)。skill 一多、一杂,就会出现**上下文膨胀、描述撞车、同 ID 互相遮蔽、改错文件、静默失效**。
|
|
12
|
+
|
|
13
|
+
`skills-doctor` 在本地把这些都查出来,并且能从 HTTP catalog 更新 skill、校验更新结果。
|
|
14
|
+
|
|
15
|
+
## 快速开始
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
# 直接在仓库里跑(免安装)
|
|
19
|
+
node bin/skills-doctor.mjs check
|
|
20
|
+
|
|
21
|
+
# 或本地安装
|
|
22
|
+
npm link
|
|
23
|
+
skills-doctor check
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 命令
|
|
27
|
+
|
|
28
|
+
所有命令都支持 `--lang en|zh`(默认按系统 locale 自动选择)和 `--json`。
|
|
29
|
+
|
|
30
|
+
### `check`(默认)
|
|
31
|
+
|
|
32
|
+
扫描所有 skill 来源,输出问题清单、上下文预算和修复建议。
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
skills-doctor check
|
|
36
|
+
skills-doctor check --json
|
|
37
|
+
skills-doctor check --budget 800 --strict # CI 用
|
|
38
|
+
skills-doctor check --root .\some-project --no-global
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `scan`
|
|
42
|
+
|
|
43
|
+
列出所有 skill 及其来源、优先级、是否会被模型看到、token 占用。
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
skills-doctor scan
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`*` 表示该 ID 的实际生效定义(注册顺序越后优先级越高)。
|
|
50
|
+
|
|
51
|
+
### `fix`
|
|
52
|
+
|
|
53
|
+
归档被遮蔽的定义——那些因为同 ID 已被更高优先级来源覆盖、永远不会加载的文件。操作可逆:文件会被移动到同一来源下的 `.skills-doctor-archive/<时间戳>/`,opencode 不会读取点目录。
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
skills-doctor fix --dry-run # 预览会归档哪些
|
|
57
|
+
skills-doctor fix
|
|
58
|
+
skills-doctor fix --hide academic-paper,deep-research --dry-run # 隐藏低频 skill,给预算减压
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- 只动被遮蔽的重复定义,生效版本永远不会被移动;
|
|
62
|
+
- 由 `update` 安装的文件会跳过——用 `update` 恢复;
|
|
63
|
+
- **默认只处理项目内的 skill**,要动全局来源(`~/.config/opencode/skills` 等)加 `--all`;
|
|
64
|
+
- 修完再跑 `check`,`shadowed` 应该消失。
|
|
65
|
+
|
|
66
|
+
### `update`(更新器)
|
|
67
|
+
|
|
68
|
+
从 `opencode.json(c)` 的 `skills` 数组里读取 HTTP catalog,对比 `version`,把有变化的 skill 下载到本地。
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
skills-doctor update # 默认装进 .opencode/skills(opencode 会直接读取)
|
|
72
|
+
skills-doctor update --check-only # 只看有没有更新
|
|
73
|
+
skills-doctor update --into .\team-skills
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- 落盘位置就是 opencode 的 skill 目录 → **更新完 `check` 立刻能检测到**;
|
|
77
|
+
- 写 `.skills-doctor.lock.json`(v2:记录每个文件的 sha1);
|
|
78
|
+
- `check` 会读这个 lock:显示托管版本、发现文件被手改(`managed-drift`)、文件被删(`managed-missing`);
|
|
79
|
+
- 再跑一次 `update` 会自动重下被改坏的文件(drift 自愈)。
|
|
80
|
+
|
|
81
|
+
## 配置
|
|
82
|
+
|
|
83
|
+
在工作目录放一个可选的 `.skills-doctor.json`(或用 `--config <file>` 指定):
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"budget": 1200,
|
|
88
|
+
"staleDays": 180,
|
|
89
|
+
"lang": "en",
|
|
90
|
+
"rules": { "long-description": "off", "hidden-skill": "info" },
|
|
91
|
+
"ignore": ["legacy-skill"]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`rules` 接受 `off` / `info` / `warn` / `error`;`ignore` 会丢掉只涉及这些 ID 的 finding。命令行参数优先级高于配置文件。
|
|
96
|
+
|
|
97
|
+
## OpenCode 插件
|
|
98
|
+
|
|
99
|
+
这个包同时导出 OpenCode 2.x 插件:
|
|
100
|
+
|
|
101
|
+
```jsonc
|
|
102
|
+
// opencode.jsonc
|
|
103
|
+
{
|
|
104
|
+
"plugins": ["opencode-skills-doctor"]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
它会注册:
|
|
109
|
+
|
|
110
|
+
- `skills_doctor` 工具(命名空间 `skills`)—— agent 传 `{ directory?, budget? }` 就能拿到 markdown 审计报告;
|
|
111
|
+
- `/skills-doctor` 命令 —— 让 agent 跑一次审计并总结修复建议。
|
|
112
|
+
|
|
113
|
+
`@opencode/plugin` 是可选 peer 依赖,运行时由 OpenCode 提供。
|
|
114
|
+
|
|
115
|
+
### OpenCode 1.x
|
|
116
|
+
|
|
117
|
+
1.x 用的是另一套插件 API,把 `plugin` 数组指向 `/v1` 入口:
|
|
118
|
+
|
|
119
|
+
```jsonc
|
|
120
|
+
// opencode.json(1.x)
|
|
121
|
+
{
|
|
122
|
+
"plugin": ["opencode-skills-doctor/v1"]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
它会注册同样的 `skills_doctor` 工具(1.x 没有 `/skills-doctor` 命令钩子)。需要把包装在 OpenCode 能解析插件的位置,例如:
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
npm i --prefix ~/.config/opencode opencode-skills-doctor
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> 包根入口(`import "opencode-skills-doctor"`)是 V2 插件,需要 OpenCode 2.x;CLI 两条线都能用。
|
|
133
|
+
|
|
134
|
+
## 检测规则
|
|
135
|
+
|
|
136
|
+
| 规则 | 级别 | 含义 |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `shadowed` | warn | 同一 ID 多处定义,只有一份生效,其余白占体积且容易改错 |
|
|
139
|
+
| `flat-dir-collision` | warn | 同一来源同时存在 `<id>.md` 和 `<id>/SKILL.md` |
|
|
140
|
+
| `duplicate-content` | warn | 不同 skill 正文完全相同 |
|
|
141
|
+
| `near-duplicate-description` | warn | 两个 skill 的 description 相似度 ≥ 75%,模型容易选错 |
|
|
142
|
+
| `missing-description` | info | 没有 description,模型看不到它 |
|
|
143
|
+
| `long-description` | warn | description > 240 字符(每个模型步骤都占上下文) |
|
|
144
|
+
| `large-body` | warn | 正文 > 4000 tokens(调用时整段进上下文) |
|
|
145
|
+
| `token-budget` | warn/error | 模型每步要看的描述总量超过预算(默认 1200) |
|
|
146
|
+
| `too-many-skills` | warn/error | 展示给模型的 skill 超过 60 / 100 个 |
|
|
147
|
+
| `invalid-id` | info | 不符合 kebab-case 规范 |
|
|
148
|
+
| `hidden-skill` | info | 被 `opencode/autoinvoke: false` 或 `disable-model-invocation: true` 隐藏 |
|
|
149
|
+
| `stale-skill` | info | `--stale-days` 指定的天数内没更新 |
|
|
150
|
+
| `managed-drift` | warn | 由 update 安装的文件在更新后被改过 |
|
|
151
|
+
| `managed-missing` | warn | lock 里记录了,但本地文件没了 |
|
|
152
|
+
| `managed-unsafe` | warn | lock 里有路径穿越等不安全路径 |
|
|
153
|
+
| `lock-version-unsupported` | info | lock 版本比工具新,已按兼容模式解析 |
|
|
154
|
+
|
|
155
|
+
## 向上兼容(forward compatibility)
|
|
156
|
+
|
|
157
|
+
- **lock**:支持版本 1(字符串版本、无哈希)和版本 2(带文件 sha1);遇到更高版本时按兼容模式解析,未知字段忽略,只在报告里提示一条 `lock-version-unsupported`。
|
|
158
|
+
- **catalog index.json**:只读 `skills[].name/version/files`,其余字段(未来新增的)一律忽略。
|
|
159
|
+
- **skill frontmatter**:只读 `name/description/metadata.opencode/autoinvoke/disable-model-invocation`,其它字段原样忽略。
|
|
160
|
+
- **路径**:同时支持 2.0 的 `.opencode/skills`、`~/.config/opencode/skills`,以及兼容目录 `.claude/skills`、`.agents/skills`(含逐级向上搜索)。
|
|
161
|
+
- 更新器写出的 lock 固定为当前支持的最高版本,旧检测器不会因为新字段报错。
|
|
162
|
+
|
|
163
|
+
## 扫描来源(低 → 高优先级)
|
|
164
|
+
|
|
165
|
+
1. `~/.claude/skills`
|
|
166
|
+
2. 项目 `.claude/skills`(项目根 → 当前目录逐级)
|
|
167
|
+
3. `~/.agents/skills`
|
|
168
|
+
4. 项目 `.agents/skills`
|
|
169
|
+
5. `~/.config/opencode/skills`
|
|
170
|
+
6. 项目 `.opencode/skills`
|
|
171
|
+
7. `opencode.json(c)` 的 `skills` 数组(2.x)或 `skills.paths`(1.x);相对路径按当前工作目录解析,支持 `~/`、绝对路径、HTTP catalog
|
|
172
|
+
|
|
173
|
+
## JSON 输出
|
|
174
|
+
|
|
175
|
+
`check --json` / `scan --json` / `update --json` 都会输出结构化 JSON,方便接 CI 或做网页报告。字段包括 `summary`、`findings[]`、`skills[]`(含 `managed: { version, url, root }`)。
|
|
176
|
+
|
|
177
|
+
## 已知限制
|
|
178
|
+
|
|
179
|
+
- 不联网时 `check` 只做本地检查;catalog 是否有新版本要用 `update --check-only`。
|
|
180
|
+
- token 估算是启发式(CJK 约 1.7 字/token,其余约 4 字符/token),用于横向比较,不是精确计费。
|
|
181
|
+
- 配置优先级假设:全局配置 < 项目配置 < `skills` 数组顺序;如与 opencode 实现有出入,以实际加载结果为准。
|
|
182
|
+
- 还不会自动改文件(v0.2 计划加 `--fix`:归档被遮蔽的定义、规范化 ID、裁剪描述)。
|
|
183
|
+
|
|
184
|
+
## Roadmap
|
|
185
|
+
|
|
186
|
+
- v0.4:内容层面的建议(哪些 skill 从未被使用)、catalog 签名校验
|
|
187
|
+
- 后续:V1/Claude skill 兼容迁移报告
|
|
188
|
+
|
|
189
|
+
## 开发
|
|
190
|
+
|
|
191
|
+
```powershell
|
|
192
|
+
npm test
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
测试覆盖:frontmatter 解析、来源发现与优先级、遮蔽/重复/预算规则、catalog 下载与 lock、drift 检测与自愈、旧/新 lock 的向上兼容、CLI JSON 与退出码。
|
|
196
|
+
|
|
197
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-skills-doctor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Local scanner, linter, fixer and updater for OpenCode skills, with OpenCode 2.x and 1.x plugin entries",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/plugin.mjs",
|
|
8
|
+
"./plugin": "./src/plugin.mjs",
|
|
9
|
+
"./v1": "./src/plugin-v1.mjs",
|
|
10
|
+
"./audit": "./src/audit.mjs"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"skills-doctor": "./bin/skills-doctor.mjs"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@opencode/plugin": ">=2.0.0",
|
|
17
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependenciesMeta": {
|
|
20
|
+
"@opencode/plugin": {
|
|
21
|
+
"optional": true
|
|
22
|
+
},
|
|
23
|
+
"@opencode-ai/plugin": {
|
|
24
|
+
"optional": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "node test/doctor.test.mjs"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"src",
|
|
36
|
+
"README.md",
|
|
37
|
+
"README.zh-CN.md"
|
|
38
|
+
],
|
|
39
|
+
"keywords": [
|
|
40
|
+
"opencode",
|
|
41
|
+
"skills",
|
|
42
|
+
"lint",
|
|
43
|
+
"fix",
|
|
44
|
+
"cli",
|
|
45
|
+
"plugin"
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/wangyuchen5110/opencodeplugginprogram.git"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT"
|
|
52
|
+
}
|
package/src/audit.mjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { discoverSources, scanAll } from "./discover.mjs"
|
|
3
|
+
import { lint, summarize, SEVERITY_ORDER } from "./lint.mjs"
|
|
4
|
+
import { inspectManaged } from "./managed.mjs"
|
|
5
|
+
import { loadConfig } from "./config.mjs"
|
|
6
|
+
import { translator, normalizeLang, detectLang } from "./i18n.mjs"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 一次完整审计:扫描来源 → 检测 → 托管校验。
|
|
10
|
+
* CLI、插件、脚本共用同一个入口。
|
|
11
|
+
*/
|
|
12
|
+
export function auditSkills({
|
|
13
|
+
cwd = process.cwd(),
|
|
14
|
+
includeGlobal = true,
|
|
15
|
+
lang = null,
|
|
16
|
+
configPath = null,
|
|
17
|
+
budget = null,
|
|
18
|
+
staleDays = null,
|
|
19
|
+
} = {}) {
|
|
20
|
+
const loaded = loadConfig(cwd, configPath)
|
|
21
|
+
const config = loaded.config
|
|
22
|
+
const resolvedLang = normalizeLang(lang) ?? normalizeLang(config.lang) ?? detectLang()
|
|
23
|
+
const t = translator(resolvedLang)
|
|
24
|
+
|
|
25
|
+
const discovered = scanAll(discoverSources({ cwd, includeGlobal }))
|
|
26
|
+
const managedInfo = inspectManaged(discovered.scans, { t })
|
|
27
|
+
|
|
28
|
+
const effectiveBudget = Number.isFinite(budget) && budget > 0 ? budget : config.budget ?? 1200
|
|
29
|
+
const effectiveStale = Number.isFinite(staleDays) && staleDays >= 0 ? staleDays : config.staleDays ?? 0
|
|
30
|
+
|
|
31
|
+
const findings = [
|
|
32
|
+
...lint(discovered.skills, {
|
|
33
|
+
budget: effectiveBudget,
|
|
34
|
+
staleDays: effectiveStale,
|
|
35
|
+
ruleOverrides: config.rules,
|
|
36
|
+
ignore: config.ignore,
|
|
37
|
+
t,
|
|
38
|
+
}),
|
|
39
|
+
...managedInfo.findings,
|
|
40
|
+
].sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity])
|
|
41
|
+
|
|
42
|
+
const summary = summarize(discovered.skills)
|
|
43
|
+
const counts = {
|
|
44
|
+
errors: findings.filter((f) => f.severity === "error").length,
|
|
45
|
+
warnings: findings.filter((f) => f.severity === "warn").length,
|
|
46
|
+
infos: findings.filter((f) => f.severity === "info").length,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
cwd: path.resolve(cwd),
|
|
51
|
+
projectRoot: discovered.projectRoot,
|
|
52
|
+
lang: resolvedLang,
|
|
53
|
+
config: { file: loaded.file, error: loaded.error ?? null, ...config },
|
|
54
|
+
budget: effectiveBudget,
|
|
55
|
+
staleDays: effectiveStale,
|
|
56
|
+
sources: discovered.sources,
|
|
57
|
+
catalogs: discovered.catalogs,
|
|
58
|
+
missing: discovered.missing,
|
|
59
|
+
skills: discovered.skills,
|
|
60
|
+
managed: managedInfo.managed,
|
|
61
|
+
summary,
|
|
62
|
+
findings,
|
|
63
|
+
counts,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 把审计结果渲染成适合给 agent / PR 评论用的 markdown。 */
|
|
68
|
+
export function formatMarkdown(audit, { limit = 25 } = {}) {
|
|
69
|
+
const lines = []
|
|
70
|
+
lines.push("# skills-doctor")
|
|
71
|
+
lines.push("")
|
|
72
|
+
lines.push(`Workdir: \`${audit.cwd}\``)
|
|
73
|
+
lines.push(
|
|
74
|
+
`Skills: ${audit.summary.total} definitions, ${audit.summary.unique} unique IDs, ` +
|
|
75
|
+
`${audit.summary.advertised} advertised, ~${audit.summary.advertisedTokens} description tokens (budget ${audit.budget})`,
|
|
76
|
+
)
|
|
77
|
+
lines.push("")
|
|
78
|
+
|
|
79
|
+
if (!audit.findings.length) {
|
|
80
|
+
lines.push("No problems found.")
|
|
81
|
+
return lines.join("\n")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
lines.push(`Findings: ${audit.counts.errors} error, ${audit.counts.warnings} warn, ${audit.counts.infos} info`)
|
|
85
|
+
lines.push("")
|
|
86
|
+
for (const finding of audit.findings.slice(0, limit)) {
|
|
87
|
+
lines.push(`- **${finding.severity}** \`${finding.rule}\` — ${finding.message}`)
|
|
88
|
+
}
|
|
89
|
+
if (audit.findings.length > limit) lines.push(`- …and ${audit.findings.length - limit} more`)
|
|
90
|
+
lines.push("")
|
|
91
|
+
lines.push("Next: run `npx opencode-skills-doctor fix` to archive shadowed definitions, or hide low-frequency skills.")
|
|
92
|
+
return lines.join("\n")
|
|
93
|
+
}
|