opencode-skills-doctor 0.0.0-stage → 0.3.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/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,177 @@
1
- # Temporary Holding Version
1
+ # skills-doctor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![test](https://github.com/wangyuchen5110/opencodeplugginprogram/actions/workflows/test.yml/badge.svg)](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
+ - After fixing, `check` should report no `shadowed` findings.
64
+
65
+ ### `update` (updater)
66
+
67
+ Reads HTTP catalogs from the `skills` array in `opencode.json(c)`, compares `version`, and downloads whatever changed.
68
+
69
+ ```powershell
70
+ skills-doctor update # installs into .opencode/skills by default (where opencode reads)
71
+ skills-doctor update --check-only # only report available updates
72
+ skills-doctor update --into .\team-skills
73
+ ```
74
+
75
+ - The output directory is a real OpenCode skill directory, so **`check` picks the update up immediately**;
76
+ - Writes `.skills-doctor.lock.json` (schema v2: a sha1 for every file);
77
+ - `check` reads that lock: shows managed versions, detects hand edits (`managed-drift`) and missing files (`managed-missing`);
78
+ - Running `update` again re-downloads drifted files (self-healing).
79
+
80
+ ## Configuration
81
+
82
+ Optional `.skills-doctor.json` in the working directory (or pass `--config <file>`):
83
+
84
+ ```json
85
+ {
86
+ "budget": 1200,
87
+ "staleDays": 180,
88
+ "lang": "en",
89
+ "rules": { "long-description": "off", "hidden-skill": "info" },
90
+ "ignore": ["legacy-skill"]
91
+ }
92
+ ```
93
+
94
+ `rules` accepts `off`, `info`, `warn` or `error`; `ignore` drops findings that only involve those IDs. CLI flags override the file.
95
+
96
+ ## OpenCode plugin
97
+
98
+ The package also exports an OpenCode 2.x plugin:
99
+
100
+ ```jsonc
101
+ // opencode.jsonc
102
+ {
103
+ "plugins": ["opencode-skills-doctor"]
104
+ }
105
+ ```
106
+
107
+ It registers:
108
+
109
+ - the `skills_doctor` tool (namespace `skills`) — the agent can audit with `{ directory?, budget? }` and gets a markdown report;
110
+ - the `/skills-doctor` command — asks the agent to run the audit and summarize fixes.
111
+
112
+ `@opencode/plugin` is an optional peer dependency; OpenCode provides it at runtime.
113
+
114
+ ## Rules
115
+
116
+ | Rule | Level | Meaning |
117
+ |---|---|---|
118
+ | `shadowed` | warn | Same ID defined in multiple sources; only one wins, the rest is dead weight and easy to edit by mistake |
119
+ | `flat-dir-collision` | warn | Both `<id>.md` and `<id>/SKILL.md` exist in the same source |
120
+ | `duplicate-content` | warn | Two skills share identical body text |
121
+ | `near-duplicate-description` | warn | Descriptions are ≥ 75% similar; the model may pick the wrong one |
122
+ | `missing-description` | info | No description, so the model never discovers it |
123
+ | `long-description` | warn | Description > 240 chars (paid on every model step) |
124
+ | `large-body` | warn | Body > 4000 tokens (pasted into context when invoked) |
125
+ | `token-budget` | warn/error | Total advertised description tokens exceed the budget (default 1200) |
126
+ | `too-many-skills` | warn/error | More than 60 / 100 skills are advertised |
127
+ | `invalid-id` | info | ID is not kebab-case |
128
+ | `hidden-skill` | info | Hidden via `opencode/autoinvoke: false` or `disable-model-invocation: true` |
129
+ | `stale-skill` | info | Not updated within `--stale-days` |
130
+ | `managed-drift` | warn | A file installed by `update` was modified afterwards |
131
+ | `managed-missing` | warn | Recorded in the lock, but the local file is gone |
132
+ | `managed-unsafe` | warn | Path traversal or another unsafe path in the lock |
133
+ | `lock-version-unsupported` | info | Lock is newer than this tool; parsed in compatibility mode |
134
+
135
+ ## Forward compatibility
136
+
137
+ - **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.
138
+ - **Catalog `index.json`**: only `skills[].name/version/files` is read; any future fields are ignored.
139
+ - **Skill frontmatter**: only `name`, `description`, `metadata.opencode/autoinvoke` and `disable-model-invocation` are read; everything else is ignored.
140
+ - **Paths**: both OpenCode 2.0 locations (`.opencode/skills`, `~/.config/opencode/skills`) and compatibility locations (`.claude/skills`, `.agents/skills`, searched upward level by level).
141
+ - The updater always writes the highest lock version this tool supports, so older readers never break on new fields.
142
+
143
+ ## Discovery sources (low → high precedence)
144
+
145
+ 1. `~/.claude/skills`
146
+ 2. project `.claude/skills` (from the project root up to the current directory)
147
+ 3. `~/.agents/skills`
148
+ 4. project `.agents/skills`
149
+ 5. `~/.config/opencode/skills`
150
+ 6. project `.opencode/skills`
151
+ 7. the `skills` array in `opencode.json(c)` (relative paths resolve from the working directory; supports `~/`, absolute paths and HTTP catalogs)
152
+
153
+ ## JSON output
154
+
155
+ `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 }`).
156
+
157
+ ## Known limitations
158
+
159
+ - `check` is offline; use `update --check-only` to ask a catalog for new versions.
160
+ - Token counts are heuristic (CJK ≈ 1.7 chars/token, the rest ≈ 4 chars/token) — good for comparisons, not for billing.
161
+ - Config precedence is assumed: global config < project config < `skills` array order. If OpenCode differs, trust the runtime.
162
+ - No automatic file rewriting yet (v0.2 plans `--fix`: archive shadowed definitions, normalize IDs, trim descriptions).
163
+
164
+ ## Roadmap
165
+
166
+ - v0.4: content-aware suggestions (which skills are never used), catalog signature verification
167
+ - Later: V1/Claude skill migration report
168
+
169
+ ## Development
170
+
171
+ ```powershell
172
+ npm test
173
+ ```
174
+
175
+ 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.
176
+
177
+ MIT
@@ -0,0 +1,177 @@
1
+ # skills-doctor
2
+
3
+ [![test](https://github.com/wangyuchen5110/opencodeplugginprogram/actions/workflows/test.yml/badge.svg)](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
+ - 修完再跑 `check`,`shadowed` 应该消失。
64
+
65
+ ### `update`(更新器)
66
+
67
+ 从 `opencode.json(c)` 的 `skills` 数组里读取 HTTP catalog,对比 `version`,把有变化的 skill 下载到本地。
68
+
69
+ ```powershell
70
+ skills-doctor update # 默认装进 .opencode/skills(opencode 会直接读取)
71
+ skills-doctor update --check-only # 只看有没有更新
72
+ skills-doctor update --into .\team-skills
73
+ ```
74
+
75
+ - 落盘位置就是 opencode 的 skill 目录 → **更新完 `check` 立刻能检测到**;
76
+ - 写 `.skills-doctor.lock.json`(v2:记录每个文件的 sha1);
77
+ - `check` 会读这个 lock:显示托管版本、发现文件被手改(`managed-drift`)、文件被删(`managed-missing`);
78
+ - 再跑一次 `update` 会自动重下被改坏的文件(drift 自愈)。
79
+
80
+ ## 配置
81
+
82
+ 在工作目录放一个可选的 `.skills-doctor.json`(或用 `--config <file>` 指定):
83
+
84
+ ```json
85
+ {
86
+ "budget": 1200,
87
+ "staleDays": 180,
88
+ "lang": "en",
89
+ "rules": { "long-description": "off", "hidden-skill": "info" },
90
+ "ignore": ["legacy-skill"]
91
+ }
92
+ ```
93
+
94
+ `rules` 接受 `off` / `info` / `warn` / `error`;`ignore` 会丢掉只涉及这些 ID 的 finding。命令行参数优先级高于配置文件。
95
+
96
+ ## OpenCode 插件
97
+
98
+ 这个包同时导出 OpenCode 2.x 插件:
99
+
100
+ ```jsonc
101
+ // opencode.jsonc
102
+ {
103
+ "plugins": ["opencode-skills-doctor"]
104
+ }
105
+ ```
106
+
107
+ 它会注册:
108
+
109
+ - `skills_doctor` 工具(命名空间 `skills`)—— agent 传 `{ directory?, budget? }` 就能拿到 markdown 审计报告;
110
+ - `/skills-doctor` 命令 —— 让 agent 跑一次审计并总结修复建议。
111
+
112
+ `@opencode/plugin` 是可选 peer 依赖,运行时由 OpenCode 提供。
113
+
114
+ ## 检测规则
115
+
116
+ | 规则 | 级别 | 含义 |
117
+ |---|---|---|
118
+ | `shadowed` | warn | 同一 ID 多处定义,只有一份生效,其余白占体积且容易改错 |
119
+ | `flat-dir-collision` | warn | 同一来源同时存在 `<id>.md` 和 `<id>/SKILL.md` |
120
+ | `duplicate-content` | warn | 不同 skill 正文完全相同 |
121
+ | `near-duplicate-description` | warn | 两个 skill 的 description 相似度 ≥ 75%,模型容易选错 |
122
+ | `missing-description` | info | 没有 description,模型看不到它 |
123
+ | `long-description` | warn | description > 240 字符(每个模型步骤都占上下文) |
124
+ | `large-body` | warn | 正文 > 4000 tokens(调用时整段进上下文) |
125
+ | `token-budget` | warn/error | 模型每步要看的描述总量超过预算(默认 1200) |
126
+ | `too-many-skills` | warn/error | 展示给模型的 skill 超过 60 / 100 个 |
127
+ | `invalid-id` | info | 不符合 kebab-case 规范 |
128
+ | `hidden-skill` | info | 被 `opencode/autoinvoke: false` 或 `disable-model-invocation: true` 隐藏 |
129
+ | `stale-skill` | info | `--stale-days` 指定的天数内没更新 |
130
+ | `managed-drift` | warn | 由 update 安装的文件在更新后被改过 |
131
+ | `managed-missing` | warn | lock 里记录了,但本地文件没了 |
132
+ | `managed-unsafe` | warn | lock 里有路径穿越等不安全路径 |
133
+ | `lock-version-unsupported` | info | lock 版本比工具新,已按兼容模式解析 |
134
+
135
+ ## 向上兼容(forward compatibility)
136
+
137
+ - **lock**:支持版本 1(字符串版本、无哈希)和版本 2(带文件 sha1);遇到更高版本时按兼容模式解析,未知字段忽略,只在报告里提示一条 `lock-version-unsupported`。
138
+ - **catalog index.json**:只读 `skills[].name/version/files`,其余字段(未来新增的)一律忽略。
139
+ - **skill frontmatter**:只读 `name/description/metadata.opencode/autoinvoke/disable-model-invocation`,其它字段原样忽略。
140
+ - **路径**:同时支持 2.0 的 `.opencode/skills`、`~/.config/opencode/skills`,以及兼容目录 `.claude/skills`、`.agents/skills`(含逐级向上搜索)。
141
+ - 更新器写出的 lock 固定为当前支持的最高版本,旧检测器不会因为新字段报错。
142
+
143
+ ## 扫描来源(低 → 高优先级)
144
+
145
+ 1. `~/.claude/skills`
146
+ 2. 项目 `.claude/skills`(项目根 → 当前目录逐级)
147
+ 3. `~/.agents/skills`
148
+ 4. 项目 `.agents/skills`
149
+ 5. `~/.config/opencode/skills`
150
+ 6. 项目 `.opencode/skills`
151
+ 7. `opencode.json(c)` 的 `skills` 数组(相对路径按当前工作目录解析,支持 `~/`、绝对路径、HTTP catalog)
152
+
153
+ ## JSON 输出
154
+
155
+ `check --json` / `scan --json` / `update --json` 都会输出结构化 JSON,方便接 CI 或做网页报告。字段包括 `summary`、`findings[]`、`skills[]`(含 `managed: { version, url, root }`)。
156
+
157
+ ## 已知限制
158
+
159
+ - 不联网时 `check` 只做本地检查;catalog 是否有新版本要用 `update --check-only`。
160
+ - token 估算是启发式(CJK 约 1.7 字/token,其余约 4 字符/token),用于横向比较,不是精确计费。
161
+ - 配置优先级假设:全局配置 < 项目配置 < `skills` 数组顺序;如与 opencode 实现有出入,以实际加载结果为准。
162
+ - 还不会自动改文件(v0.2 计划加 `--fix`:归档被遮蔽的定义、规范化 ID、裁剪描述)。
163
+
164
+ ## Roadmap
165
+
166
+ - v0.4:内容层面的建议(哪些 skill 从未被使用)、catalog 签名校验
167
+ - 后续:V1/Claude skill 兼容迁移报告
168
+
169
+ ## 开发
170
+
171
+ ```powershell
172
+ npm test
173
+ ```
174
+
175
+ 测试覆盖:frontmatter 解析、来源发现与优先级、遮蔽/重复/预算规则、catalog 下载与 lock、drift 检测与自愈、旧/新 lock 的向上兼容、CLI JSON 与退出码。
176
+
177
+ MIT
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../src/cli.mjs"
3
+
4
+ main(process.argv.slice(2)).catch((err) => {
5
+ console.error(`skills-doctor 出错: ${err?.message ?? err}`)
6
+ process.exit(2)
7
+ })
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "opencode-skills-doctor",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Local scanner, linter, fixer and updater for OpenCode skills, with an OpenCode plugin entry",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/plugin.mjs",
8
+ "./plugin": "./src/plugin.mjs"
9
+ },
10
+ "bin": {
11
+ "skills-doctor": "./bin/skills-doctor.mjs"
12
+ },
13
+ "peerDependencies": {
14
+ "@opencode/plugin": ">=2.0.0"
15
+ },
16
+ "peerDependenciesMeta": {
17
+ "@opencode/plugin": {
18
+ "optional": true
19
+ }
20
+ },
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "scripts": {
25
+ "test": "node test/doctor.test.mjs"
26
+ },
27
+ "files": [
28
+ "bin",
29
+ "src",
30
+ "README.md",
31
+ "README.zh-CN.md"
32
+ ],
33
+ "keywords": [
34
+ "opencode",
35
+ "skills",
36
+ "lint",
37
+ "fix",
38
+ "cli",
39
+ "plugin"
40
+ ],
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/wangyuchen5110/opencodeplugginprogram.git"
44
+ },
45
+ "license": "MIT"
46
+ }
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
+ }