opencode-skills-doctor 0.7.0 → 0.8.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 CHANGED
@@ -117,6 +117,7 @@ All commands accept `--json`, `--lang en|zh`, `--budget`, `--root` and `--no-glo
117
117
  | **Claude Code / Codex** | MCP server (`npx -y opencode-skills-doctor mcp`) |
118
118
  | **Grok CLI / Cursor** | their skill roots are scanned natively |
119
119
  | **Hermes Agent** | MCP entry in `~/.hermes/config.yaml`; `~/.hermes/skills` is scanned |
120
+ | **Kimi Code** | `install --client kimi` writes `~/.kimi-code/mcp.json` + the audit skill |
120
121
 
121
122
  ## Why it is safe
122
123
 
@@ -438,6 +438,16 @@ mcp_servers:
438
438
 
439
439
  Hermes 的工具命名是 `mcp_<server>_<tool>`,即 `mcp_skills_doctor_skills_doctor`。它的 skill 在 `~/.hermes/skills`(含导入的 `openclaw-imports/`)和项目 `.hermes/skills`,`check` 都会扫描。
440
440
 
441
+ ### Kimi Code(Moonshot)
442
+
443
+ 一条命令写入 MCP 配置和审计 skill:
444
+
445
+ ```powershell
446
+ npx -y opencode-skills-doctor install --client kimi
447
+ ```
448
+
449
+ 它会把 `skills-doctor` 合并进 `~/.kimi-code/mcp.json`(或 `$KIMI_CODE_HOME/mcp.json`),并把 skill 复制到 `$KIMI_CODE_HOME/skills/`。之后开新的 Kimi Code 会话即可,工具名是 `mcp__skills_doctor__skills_doctor`。`check` 也会扫描 `~/.kimi-code/skills` 和项目 `.kimi-code/skills`;`uninstall --client kimi` 反向撤销。
450
+
441
451
  ## 检测规则
442
452
 
443
453
  | 规则 | 级别 | 含义 |
package/docs/REFERENCE.md CHANGED
@@ -10,7 +10,7 @@ Local scanner, linter, fixer, updater and **OpenCode plugin** for agent skills
10
10
  |---|---|
11
11
  | **What it is** | Audit, fix and update the skills your coding agent loads |
12
12
  | **Interfaces** | CLI · OpenCode plugin (1.x / 2.x) · MCP server (Claude Code, Codex, DSH, …) · Grok CLI compatible |
13
- | **Skill sources** | `.opencode/skills`, `.claude/skills`, `.agents/skills`, `.grok/skills`, `.cursor/skills`, `.hermes/skills`, `opencode.json(c)` entries, HTTP catalogs |
13
+ | **Skill sources** | `.opencode/skills`, `.claude/skills`, `.agents/skills`, `.grok/skills`, `.cursor/skills`, `.hermes/skills`, `.kimi-code/skills`, `opencode.json(c)` entries, HTTP catalogs |
14
14
  | **What it finds** | shadowed IDs, duplicates, long descriptions, oversized bodies, per-step token budget, broken file references, invalid frontmatter, empty bodies, stale skills |
15
15
  | **What it fixes** | archives shadowed definitions (reversible), hides low-frequency skills, syncs catalogs with drift detection |
16
16
  | **Reports** | terminal, JSON, or a self-contained HTML report (`--html [file] --open`) |
@@ -421,6 +421,16 @@ mcp_servers:
421
421
 
422
422
  Hermes names tools `mcp_<server>_<tool>`, so the tool is `mcp_skills_doctor_skills_doctor`. Its skills live in `~/.hermes/skills` (including imported `openclaw-imports/`) and project `.hermes/skills`; `check` scans both.
423
423
 
424
+ ### Kimi Code (Moonshot)
425
+
426
+ One command writes the MCP entry and the audit skill:
427
+
428
+ ```powershell
429
+ npx -y opencode-skills-doctor install --client kimi
430
+ ```
431
+
432
+ It merges `skills-doctor` into `~/.kimi-code/mcp.json` (or `$KIMI_CODE_HOME/mcp.json`) and copies the skill to `$KIMI_CODE_HOME/skills/`. Start a new Kimi Code session afterwards; the tool is `mcp__skills_doctor__skills_doctor` (Kimi uses the same `mcp__<server>__<tool>` shape as Claude Code). `check` also scans `~/.kimi-code/skills` and project `.kimi-code/skills`; `uninstall --client kimi` reverses both.
433
+
424
434
  ## Rules
425
435
 
426
436
  | Rule | Level | Meaning |
@@ -466,9 +476,11 @@ Hermes names tools `mcp_<server>_<tool>`, so the tool is `mcp_skills_doctor_skil
466
476
  8. project `.grok/skills`
467
477
  9. `~/.hermes/skills`
468
478
  10. project `.hermes/skills`
469
- 11. `~/.config/opencode/skills`
470
- 12. project `.opencode/skills`
471
- 13. 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
479
+ 11. `~/.kimi-code/skills`
480
+ 12. project `.kimi-code/skills`
481
+ 13. `~/.config/opencode/skills`
482
+ 14. project `.opencode/skills`
483
+ 15. 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
472
484
 
473
485
  ## JSON output
474
486
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-skills-doctor",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Audit, fix and update agent skills: token cost, broken references, shadowed duplicates, HTML reports, catalog sync. CLI, OpenCode plugin, MCP server; works with OpenCode, Claude Code, Codex, Grok CLI, Cursor and DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "exports": {
package/src/cli.mjs CHANGED
@@ -10,6 +10,7 @@ import { loadConfig } from "./config.mjs"
10
10
  import { startMcpServer } from "./mcp.mjs"
11
11
  import { installOpenCode, uninstallOpenCode } from "./install.mjs"
12
12
  import { installDsh, uninstallDsh } from "./dsh.mjs"
13
+ import { installKimi, uninstallKimi } from "./kimi.mjs"
13
14
  import { writeHtmlReport, openReport } from "./html.mjs"
14
15
  import { planBudget } from "./plan.mjs"
15
16
  import {
@@ -75,6 +76,7 @@ function parseArgs(argv) {
75
76
  client: "auto",
76
77
  profile: null,
77
78
  dshHome: null,
79
+ kimiHome: null,
78
80
  configDir: null,
79
81
  noNpm: false,
80
82
  html: null,
@@ -162,6 +164,9 @@ function parseArgs(argv) {
162
164
  case "--dsh-home":
163
165
  options.dshHome = path.resolve(args[++i] ?? ".")
164
166
  break
167
+ case "--kimi-home":
168
+ options.kimiHome = path.resolve(args[++i] ?? ".")
169
+ break
165
170
  case "--no-npm":
166
171
  options.noNpm = true
167
172
  break
@@ -600,8 +605,8 @@ export async function main(argv) {
600
605
  process.exit(2)
601
606
  }
602
607
 
603
- if (!["auto", "opencode", "desktop", "dsh"].includes(options.client)) {
604
- console.error("unsupported --client: expected auto, opencode, desktop or dsh")
608
+ if (!["auto", "opencode", "desktop", "dsh", "kimi"].includes(options.client)) {
609
+ console.error("unsupported --client: expected auto, opencode, desktop, dsh or kimi")
605
610
  process.exitCode = 2
606
611
  return
607
612
  }
@@ -648,6 +653,25 @@ export async function main(argv) {
648
653
  return
649
654
  }
650
655
 
656
+ if (options.command === "install" && options.client === "kimi") {
657
+ const result = installKimi({ home: options.kimiHome ?? undefined, dryRun: options.dryRun })
658
+ if (options.json) {
659
+ console.log(JSON.stringify(result, null, 2))
660
+ } else {
661
+ console.log(bold(t("kimi.title")))
662
+ if (result.home) console.log(dim(t("kimi.home", { dir: result.home })))
663
+ if (result.mcpFile) console.log(dim(t("kimi.mcp", { file: result.mcpFile })))
664
+ if (result.skillDir) console.log(dim(t("kimi.skill", { dir: result.skillDir })))
665
+ if (result.dryRun) console.log(dim(t("install.dryRun")))
666
+ for (const action of result.actions) console.log(` · ${action}`)
667
+ for (const error of result.errors) console.log(red(t("install.error", { error })))
668
+ console.log("")
669
+ console.log(result.errors.length ? red(t("kimi.failed")) : dim(t("kimi.restart")))
670
+ }
671
+ process.exitCode = result.errors.length ? 1 : 0
672
+ return
673
+ }
674
+
651
675
  if (options.command === "install") {
652
676
  const result = installOpenCode({
653
677
  configDir: options.configDir ?? undefined,
@@ -698,6 +722,23 @@ export async function main(argv) {
698
722
  return
699
723
  }
700
724
 
725
+ if (options.command === "uninstall" && options.client === "kimi") {
726
+ const result = uninstallKimi({ home: options.kimiHome ?? undefined, dryRun: options.dryRun })
727
+ if (options.json) {
728
+ console.log(JSON.stringify(result, null, 2))
729
+ } else {
730
+ console.log(bold(t("kimi.uninstallTitle")))
731
+ if (result.home) console.log(dim(t("kimi.home", { dir: result.home })))
732
+ if (result.dryRun) console.log(dim(t("install.dryRun")))
733
+ for (const action of result.actions) console.log(` · ${action}`)
734
+ for (const error of result.errors) console.log(red(t("install.error", { error })))
735
+ console.log("")
736
+ console.log(result.errors.length ? red(t("kimi.failed")) : dim(t("kimi.restart")))
737
+ }
738
+ process.exitCode = result.errors.length ? 1 : 0
739
+ return
740
+ }
741
+
701
742
  if (options.command === "uninstall") {
702
743
  const result = uninstallOpenCode({
703
744
  configDir: options.configDir ?? undefined,
package/src/discover.mjs CHANGED
@@ -122,9 +122,11 @@ function isUnder(parent, child) {
122
122
  * 8. 项目 .grok/skills
123
123
  * 9. ~/.hermes/skills(Hermes Agent)
124
124
  * 10. 项目 .hermes/skills
125
- * 11. ~/.config/opencode/skills
126
- * 12. 项目 .opencode/skills(项目根 → cwd)
127
- * 13. opencode.json(c) 里的 skills 数组 / skills.paths
125
+ * 11. ~/.kimi-code/skills(Kimi Code)
126
+ * 12. 项目 .kimi-code/skills
127
+ * 13. ~/.config/opencode/skills
128
+ * 14. 项目 .opencode/skills(项目根 → cwd)
129
+ * 15. opencode.json(c) 里的 skills 数组 / skills.paths
128
130
  */
129
131
  export function discoverSources({ cwd = process.cwd(), home = os.homedir(), includeGlobal = true } = {}) {
130
132
  const projectRoot = findProjectRoot(cwd) ?? path.resolve(cwd)
@@ -156,6 +158,9 @@ export function discoverSources({ cwd = process.cwd(), home = os.homedir(), incl
156
158
  if (includeGlobal) addSource("hermes-global", "global", path.join(home, ".hermes", "skills"))
157
159
  for (const dir of projectChain) addSource("hermes-project", "project", path.join(dir, ".hermes", "skills"))
158
160
 
161
+ if (includeGlobal) addSource("kimi-global", "global", path.join(home, ".kimi-code", "skills"))
162
+ for (const dir of projectChain) addSource("kimi-project", "project", path.join(dir, ".kimi-code", "skills"))
163
+
159
164
  if (includeGlobal) addSource("opencode-global", "global", path.join(home, ".config", "opencode", "skills"))
160
165
  for (const dir of projectChain) addSource("opencode-project", "project", path.join(dir, ".opencode", "skills"))
161
166
 
package/src/i18n.mjs CHANGED
@@ -36,9 +36,10 @@ Options:
36
36
  --html [file] check: write a self-contained HTML report (default: skills-report.html)
37
37
  --open check: open the HTML report after writing it
38
38
  --config-dir <dir> install/uninstall: OpenCode config directory (default: ~/.config/opencode)
39
- --client <name> install/uninstall: auto | opencode | desktop | dsh (default auto)
39
+ --client <name> install/uninstall: auto | opencode | desktop | dsh | kimi (default auto)
40
40
  --profile <name> dsh: profile to install into (default: desktop, or the only one)
41
41
  --dsh-home <dir> dsh: DSH home directory (default: ~/.dsh)
42
+ --kimi-home <dir> kimi: Kimi Code home (default: ~/.kimi-code or $KIMI_CODE_HOME)
42
43
  --no-npm install/uninstall: do not run npm (1.x)
43
44
  --apply upgrade: run npm install -g opencode-skills-doctor@latest
44
45
  --no-update-check skip the automatic update check for this run
@@ -129,6 +130,13 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
129
130
  "dsh.skill": " skill: {dir}",
130
131
  "dsh.restart": "Restart DSH to load the MCP tool (HMR reloads the patch when enabled; the skill updates live).",
131
132
  "dsh.failed": "DSH integration failed.",
133
+ "kimi.title": "Kimi Code integration",
134
+ "kimi.uninstallTitle": "Remove Kimi Code integration",
135
+ "kimi.home": " home: {dir}",
136
+ "kimi.mcp": " mcp: {file}",
137
+ "kimi.skill": " skill: {dir}",
138
+ "kimi.restart": "Start a new Kimi Code session to load the MCP tool.",
139
+ "kimi.failed": "Kimi Code integration failed.",
132
140
  "update.toolAvailable":
133
141
  " ! skills-doctor {latest} is available (you have {current}); update with: {cmd} · {url}",
134
142
  "upgrade.title": "skills-doctor upgrade",
@@ -257,9 +265,10 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
257
265
  --html [file] check: 生成自包含 HTML 报告(默认 skills-report.html)
258
266
  --open check: 写完报告后用浏览器打开
259
267
  --config-dir <dir> install/uninstall: OpenCode 配置目录(默认 ~/.config/opencode)
260
- --client <name> install/uninstall: auto | opencode | desktop | dsh(默认 auto)
268
+ --client <name> install/uninstall: auto | opencode | desktop | dsh | kimi(默认 auto)
261
269
  --profile <name> dsh: 安装到哪个 profile(默认 desktop,或唯一的一个)
262
270
  --dsh-home <dir> dsh: DSH 主目录(默认 ~/.dsh)
271
+ --kimi-home <dir> kimi: Kimi Code 主目录(默认 ~/.kimi-code 或 $KIMI_CODE_HOME)
263
272
  --no-npm install/uninstall: 不执行 npm(1.x)
264
273
  --apply upgrade: 执行 npm install -g opencode-skills-doctor@latest
265
274
  --no-update-check 本次运行跳过自动更新检查
@@ -350,6 +359,13 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
350
359
  "dsh.skill": " skill: {dir}",
351
360
  "dsh.restart": "重启 DSH 加载 MCP 工具(启用 HMR 时会自动重载 patch;skill 即时更新)。",
352
361
  "dsh.failed": "DSH 集成失败。",
362
+ "kimi.title": "Kimi Code 集成",
363
+ "kimi.uninstallTitle": "撤销 Kimi Code 集成",
364
+ "kimi.home": " 主目录: {dir}",
365
+ "kimi.mcp": " mcp: {file}",
366
+ "kimi.skill": " skill: {dir}",
367
+ "kimi.restart": "开一个新的 Kimi Code 会话加载 MCP 工具。",
368
+ "kimi.failed": "Kimi Code 集成失败。",
353
369
  "update.toolAvailable": " ! skills-doctor 有新版本 {latest}(当前 {current});更新:{cmd} · {url}",
354
370
  "upgrade.title": "skills-doctor 自更新",
355
371
  "upgrade.current": " 当前版本: {version}",
package/src/kimi.mjs ADDED
@@ -0,0 +1,121 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import os from "node:os"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ export const SERVER_NAME = "skills-doctor"
7
+ export const SKILL_NAME = "skills-doctor-audit"
8
+
9
+ const ENTRY = { command: "npx", args: ["-y", "opencode-skills-doctor", "mcp"] }
10
+
11
+ export function kimiHome(env = process.env) {
12
+ return env.KIMI_CODE_HOME || path.join(os.homedir(), ".kimi-code")
13
+ }
14
+
15
+ function skillSourceDir() {
16
+ return fileURLToPath(new URL("../skills/dsh-skills-doctor", import.meta.url))
17
+ }
18
+
19
+ function readJson(file) {
20
+ if (!fs.existsSync(file)) return {}
21
+ try {
22
+ return JSON.parse(fs.readFileSync(file, "utf8").replace(/^\uFEFF/, ""))
23
+ } catch {
24
+ return null
25
+ }
26
+ }
27
+
28
+ function copySkill(source, target, dryRun, act, dry) {
29
+ const file = path.join(source, "SKILL.md")
30
+ if (!fs.existsSync(file)) return false
31
+ const targetFile = path.join(target, "SKILL.md")
32
+ if (fs.existsSync(targetFile) && fs.readFileSync(targetFile, "utf8") === fs.readFileSync(file, "utf8")) {
33
+ act(`skill already up to date ${target}`)
34
+ return true
35
+ }
36
+ if (dryRun) {
37
+ dry(`install skill ${SKILL_NAME} into ${target}`)
38
+ return true
39
+ }
40
+ fs.mkdirSync(target, { recursive: true })
41
+ fs.copyFileSync(file, targetFile)
42
+ act(`wrote skill ${targetFile}`)
43
+ return true
44
+ }
45
+
46
+ function removeSkill(target, dryRun, act, dry) {
47
+ const file = path.join(target, "SKILL.md")
48
+ if (!fs.existsSync(file)) return false
49
+ if (!fs.readFileSync(file, "utf8").includes("Skill audit workflow")) {
50
+ act(`kept ${target} (not the generated skill)`)
51
+ return false
52
+ }
53
+ if (dryRun) {
54
+ dry(`delete skill ${target}`)
55
+ return true
56
+ }
57
+ fs.rmSync(target, { recursive: true, force: true })
58
+ act(`deleted skill ${target}`)
59
+ return true
60
+ }
61
+
62
+ export function installKimi({ home = kimiHome(), dryRun = false } = {}) {
63
+ const actions = []
64
+ const errors = []
65
+ const act = (message) => actions.push(message)
66
+ const dry = (message) => actions.push(`[dry-run] ${message}`)
67
+
68
+ const mcpFile = path.join(home, "mcp.json")
69
+ const skillDir = path.join(home, "skills", SKILL_NAME)
70
+
71
+ const config = readJson(mcpFile)
72
+ if (config === null) {
73
+ errors.push(`cannot parse ${mcpFile}`)
74
+ return { ok: false, home, mcpFile, skillDir, actions, errors, dryRun }
75
+ }
76
+ config.mcpServers = config.mcpServers && typeof config.mcpServers === "object" ? config.mcpServers : {}
77
+
78
+ const existing = config.mcpServers[SERVER_NAME]
79
+ const same = existing && existing.command === ENTRY.command && JSON.stringify(existing.args) === JSON.stringify(ENTRY.args)
80
+ if (same) {
81
+ act(`${SERVER_NAME} already present in ${mcpFile}`)
82
+ } else if (dryRun) {
83
+ dry(`add ${SERVER_NAME} to mcpServers in ${mcpFile}`)
84
+ } else {
85
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-")
86
+ if (fs.existsSync(mcpFile)) fs.copyFileSync(mcpFile, `${mcpFile}.bak-${stamp}`)
87
+ config.mcpServers[SERVER_NAME] = ENTRY
88
+ fs.mkdirSync(home, { recursive: true })
89
+ fs.writeFileSync(mcpFile, JSON.stringify(config, null, 2) + "\n", "utf8")
90
+ act(`wrote ${SERVER_NAME} to ${mcpFile}`)
91
+ }
92
+
93
+ copySkill(skillSourceDir(), skillDir, dryRun, act, dry)
94
+ return { ok: errors.length === 0, home, mcpFile, skillDir, actions, errors, dryRun }
95
+ }
96
+
97
+ export function uninstallKimi({ home = kimiHome(), dryRun = false } = {}) {
98
+ const actions = []
99
+ const errors = []
100
+ const act = (message) => actions.push(message)
101
+ const dry = (message) => actions.push(`[dry-run] ${message}`)
102
+
103
+ const mcpFile = path.join(home, "mcp.json")
104
+ const skillDir = path.join(home, "skills", SKILL_NAME)
105
+
106
+ const config = readJson(mcpFile)
107
+ if (config && config.mcpServers && config.mcpServers[SERVER_NAME]) {
108
+ if (dryRun) {
109
+ dry(`remove ${SERVER_NAME} from ${mcpFile}`)
110
+ } else {
111
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-")
112
+ fs.copyFileSync(mcpFile, `${mcpFile}.bak-${stamp}`)
113
+ delete config.mcpServers[SERVER_NAME]
114
+ fs.writeFileSync(mcpFile, JSON.stringify(config, null, 2) + "\n", "utf8")
115
+ act(`removed ${SERVER_NAME} from ${mcpFile}`)
116
+ }
117
+ }
118
+
119
+ removeSkill(skillDir, dryRun, act, dry)
120
+ return { ok: errors.length === 0, home, mcpFile, skillDir, actions, errors, dryRun }
121
+ }