@perrylink/dsh-plugin-doctor 0.1.4 → 0.1.5

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/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 0.1.5
4
+
5
+ - **Group aliases (encoding-safe)**: `--only` now accepts the ASCII aliases `R`, `K`, `D`, `CC` (case-insensitive) in addition to the Chinese group names. Chinese group names had silently rotted into double-encoded mojibake in 35 repos' `plugin-doctor.yml`, which made `--only` match nothing.
6
+ - **Refuse to pass silently**: an `--only` value that matches no registered group, or a run in which zero checks execute, now exits **2** with the available groups instead of the previous "0 checks, exit 0" false green. This is the root-cause guard for the mojibake class of bug.
7
+ - **`tests/selftest.mjs`**: seven real CLI cases (good fixture, alias case-insensitivity, single group, Chinese full name, unknown group, mojibake group, broken fixture) run in a `%TEMP%` sandbox; wired into `npm test`, `ci.yml` and `publish.yml`.
8
+ - **Verified badge program**: `data/verified-repos.json` (declared repos), `scripts/verify.mjs` (audits each repo's own `plugin-doctor` CI gate through the GitHub API — no cloning, no installing, no executing third-party code), `scripts/badge.mjs` (SVG renderer), `data/verified.json` (registry, generated by CI), `badges/<owner>__<repo>.svg`, and `.github/workflows/verified.yml` (daily + on push). Badge meaning: `dsh-doctor passing|warning|failing|no data` — the repo's own static R+K gate is green on the current default-branch HEAD. The gate covers 16 checks (R0/R1/R3/R5/R6/R7/R8 + K1–K9) on the committed tree; R2/R4 read built artifacts and are gated by each repo's own `ci.yml`. Not a certification badge: no Scorecard, provenance or install smoke.
9
+ - **Badge visual design (2026-09-09)**: silver/platinum metallic label with deep-navy text plus GitHub-conventional state colors (green/amber/red/grey) and per-state path-drawn glyphs (check / exclamation / cross / dash), 20px, 1px border. Chosen against rendered candidates with a vision-model review: gold was rejected as a pass color (it reads as a warning), a borderless silver label was rejected as invisible on white, and every state now differs by both color and glyph. Per-state gradient ids keep several badges safe to inline in one document.
10
+ - **Gate hardening after the 2026-09-09 sweep**: the family `plugin-doctor.yml` no longer runs install/build. A naive `npm run build` fails without the pinned harness aliases (`HARNESS_COMMIT` + `gen-aliases`) and its prebuild wiped the committed `lib/`, which produced 17 false-red gates. The gate now reads the committed tree only and reports R2/R4 without gating on them.
11
+ - **Repo infrastructure**: `ci.yml` (node 22/24: syntax check, self-test, `npm pack --dry-run`), `scorecard.yml` (OpenSSF Scorecard with `publish_results`), `publish.yml` (tag-triggered npm publish with provenance; skips cleanly when `NPM_TOKEN` is absent), README badge row.
12
+
13
+ ## 0.1.4
14
+
15
+ - K1 no longer flags local type-parameter `ctx` declarations; K3 detects variable-level cleanup and checks the preceding line (three false positives removed; `dsh-github` K group is 9/9 PASS).
16
+
17
+ ## 0.1.3
18
+
19
+ - R3 strips YAML comment lines before the heuristic scan; R5 treats a pure-JS repo with no cordis dependency as passing.
20
+
21
+ ## 0.1.2
22
+
23
+ - `verdict.ok` follows the documented exit-code contract: `warn`/`skip` do not fail the run.
24
+
25
+ ## 0.1.1
26
+
27
+ - Fix duplicated shebang in the entry file (ESM SyntaxError on first publish).
28
+
29
+ ## 0.1.0
30
+
31
+ - First release: four layers — static package structure (R0–R8), cordis contract scan (K1–K9), keyless-headless sandbox smoke (D0–D3, D9), ecosystem listing checks (CC1–CC5). Zero dependencies, Node ≥22 built-ins only.
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # dsh-plugin-doctor
2
2
 
3
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
4
+ [![npm version](https://img.shields.io/npm/v/%40perrylink%2Fdsh-plugin-doctor)](https://www.npmjs.com/package/@perrylink/dsh-plugin-doctor)
5
+ [![npm downloads](https://img.shields.io/npm/dm/%40perrylink%2Fdsh-plugin-doctor)](https://www.npmjs.com/package/@perrylink/dsh-plugin-doctor)
6
+ [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/PerryLink/dsh-plugin-doctor/ci.yml?branch=main&label=CI)](https://github.com/PerryLink/dsh-plugin-doctor/actions)
8
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PerryLink/dsh-plugin-doctor/badge)](https://api.securityscorecards.dev/projects/github.com/PerryLink/dsh-plugin-doctor)
9
+
3
10
  dsh 插件「完整性 + 运行流畅」一体检测器。零依赖(Node ≥22 自带能力),一次运行同时覆盖
4
11
  **包结构静态检查(R)→ cordis 契约扫描(K)→ 动态沙箱冒烟(D)→ 生态集合站清单校验(CC)** 四层。
5
12
  判据全部来自 2026-09-07 三路一手调研:deepseek-harness 文档/源码、cordiverse/cordis 源码契约、
@@ -11,11 +18,31 @@ dsh 插件「完整性 + 运行流畅」一体检测器。零依赖(Node ≥22
11
18
  node doctor.mjs --repo <插件仓路径> # 全量(含动态冒烟,需网络 + pnpm)
12
19
  node doctor.mjs --repo <路径> --no-smoke # 仅静态 + 清单
13
20
  node doctor.mjs --repo <路径> --dsh 0.1.2-rc.1 # 冒烟宿主版本(默认 npm latest 已发布线)
14
- node doctor.mjs --repo <路径> --only 静态·包结构,静态·cordis 契约扫描
21
+ node doctor.mjs --repo <路径> --only R,K # 只跑静态两层(推荐用 ASCII 别名)
15
22
  node doctor.mjs --repo <路径> --json report.json
16
23
  ```
17
24
 
18
- - 退出码:`0` = fail/error(可含 warn/skip);`1` = 存在 fail/error。适合直接挂 CI。
25
+ ### `--only` 分组与 ASCII 别名
26
+
27
+ | 别名 | 分组全名 | 内容 |
28
+ |---|---|---|
29
+ | `R` | 静态·包结构 | R0–R8 |
30
+ | `K` | 静态·cordis 契约扫描 | K1–K9 |
31
+ | `D` | 动态·沙箱冒烟 | D0–D3、D9 |
32
+ | `CC` | 生态·集合站清单 | CC1–CC5 |
33
+
34
+ 别名大小写不敏感,中文全名同样可用。**工作流里请一律使用别名**:中文分组名一旦被编辑器/脚本按错误编码往返,`--only` 就会一个分组都匹配不上。
35
+
36
+ ### 退出码契约
37
+
38
+ | 码 | 含义 |
39
+ |---|---|
40
+ | `0` | 无 fail/error(可含 warn/skip) |
41
+ | `1` | 存在 fail/error |
42
+ | `2` | 用法错误、未知分组、或**零检查执行** |
43
+
44
+ **防静默通过**:`--only` 里只要有一个分组名不匹配,或最终零检查执行,本工具立即以 `2` 失败。0.1.4 及更早版本在分组名乱码时会"零检查 + exit 0",这曾让 35 个仓的 CI 门禁变成假绿(2026-09-09 实测:`checks_run=0`、`exit=0`)。
45
+
19
46
  - 冒烟全程使用 `%TEMP%` mkdtemp 临时 `DSH_HOME`/`DSH_AGENTS_HOME`,绝不触碰真实 `~/.dsh`(红线 3)。
20
47
  - 每步子进程 stdout/stderr 落盘 `%TEMP%\dsh-doctor-logs-*`,报告尾部打印路径,证据可查。
21
48
 
@@ -28,6 +55,45 @@ node doctor.mjs --repo <路径> --json report.json
28
55
  | 动态·沙箱冒烟 | D0–D3、D9 | npm pack → `dsh plugin --profile headless add <tarball>` → 断言 `dsh.profile.bundles` 含包名 → `--dump-config` 层标记 → keyless headless 运行期望 **exit 1 + `dsh: MISSING_CREDENTIAL`**(=组合 boot 到请求阶段;排除 NO_ADAPTER/ERR_MODULE_NOT_FOUND/SyntaxError/TypeError)→ 沙箱清理 |
29
56
  | 生态·集合站清单 | CC1–CC5 | 认证注册表 spec v1 五维 evidence;adp-list yml 字段/枚举/描述;dsh-catalog 目录条目约束(禁安装命令、截断启发式);omdsh `dshWorkshop` activation 5 值;dsh-plugin-kit 三门(license/五语 README/seam 三角色,优先调用 kit 官方 CLI) |
30
57
 
58
+ ## Verified 徽章
59
+
60
+ 挂这枚徽章的含义只有一条,且可审计:**该仓在自己的 CI 里跑 dsh-plugin-doctor 的静态 R+K 门禁(16 项:R0/R1/R3/R5/R6/R7/R8 + K1–K9),且门禁在默认分支当前 HEAD 上是绿的**。**不是**认证徽章:不含 Scorecard/provenance/安装冒烟。R2(tarball 完整性)与 R4(入口契约)读取构建产物 `lib/`,而家族多数仓的构建需要 `HARNESS_COMMIT` + `gen-aliases` 才能通过——这两项由各仓自己的 `ci.yml`(build drift gate + pack smoke)把关,不在本门禁内。
61
+
62
+ ```markdown
63
+ [![dsh-doctor](https://raw.githubusercontent.com/PerryLink/dsh-plugin-doctor/main/badges/PerryLink__dsh-github.svg)](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章)
64
+ ```
65
+
66
+ - 注册表 `data/verified.json` 是唯一事实来源,由 `.github/workflows/verified.yml` 每日 + 每次相关 push 刷新。刷新只读 GitHub API:解析各仓 HEAD 的 `plugin-doctor.yml` 门禁配置(必须钉住 `@perrylink/dsh-plugin-doctor@<版本>`、`--only` 参数可用、含 R0/K1 实跑自校验),再核对 HEAD 那次 `plugin-doctor` workflow run 的结论。**本仓 CI 不克隆、不安装、不执行任何第三方代码。**
67
+ - 徽章外观:label 用**银白/铂金金属渐变 + 墨蓝字**(金行主导、水行在字),值区用 **GitHub 惯例状态色**(绿/橙/红/灰);状态另用**路径绘制的图标**(✓ / ! / ✕ / –)冗余表达,色觉障碍下同样可读。1px 描边是必需的——去掉后银白 label 在白色 README 背景上会消失。
68
+ - 四种状态(值文本用 shields / GitHub Actions 惯用词):`passing`(绿,HEAD 上 run success)/ `warning`(橙:HEAD 还没跑、run 仍在队列、或缺少门禁配置的前置条件)/ `failing`(红:HEAD 上 run 失败,或门禁配置不成立——含 `--only` 参数是双重编码乱码的"假门禁")/ `no data`(灰:API 查询失败)。徽章是**动态**的:不再通过就会变红。R+K 的精确口径不在徽章文字里,而在本节与注册表 `meaning` 字段(徽章链接指回本节)。
69
+ - 加入方式:向 `data/verified-repos.json` 提 PR 增加 `{ "repo": "<owner>/<name>", "package": "<npm 包名>" }`,并按下面的门禁在自己的仓里加 `plugin-doctor.yml`;条目必须通过上面的门禁核对。
70
+ - 门禁步骤(完整工作流见任一家族仓的 `.github/workflows/plugin-doctor.yml`;分组名用 YAML `\u` 转义构造,文件保持纯 ASCII,避免编码往返把中文分组名变成乱码;末尾自校验 R0/K1 确实跑了):
71
+
72
+ ```yaml
73
+ - name: Run dsh-plugin-doctor (static R/K on the committed tree)
74
+ env:
75
+ DOCTOR_ONLY: "\u9759\u6001\u00b7\u5305\u7ed3\u6784,\u9759\u6001\u00b7cordis \u5951\u7ea6\u626b\u63cf"
76
+ run: |
77
+ if [ -z "$DOCTOR_ONLY" ]; then echo "DOCTOR_ONLY is empty"; exit 1; fi
78
+ set +e
79
+ out="$(npx --yes @perrylink/dsh-plugin-doctor@0.1.4 --repo . --no-smoke --only "$DOCTOR_ONLY" --json /tmp/doctor.json 2>&1)"
80
+ set -e
81
+ printf '%s\n' "$out"
82
+ echo "$out" | grep -q 'R0 ' || { echo "::error::doctor ran no R checks"; exit 1; }
83
+ echo "$out" | grep -q 'K1 ' || { echo "::error::doctor ran no K checks"; exit 1; }
84
+ if [ ! -f /tmp/doctor.json ]; then echo "::error::doctor produced no JSON report"; exit 1; fi
85
+ node -e '
86
+ const r = JSON.parse(require("fs").readFileSync("/tmp/doctor.json", "utf8")).results
87
+ const buildDep = r.filter((x) => /^R[24] /.test(x.name))
88
+ const gated = r.filter((x) => !/^R[24] /.test(x.name))
89
+ const bad = gated.filter((x) => x.status === "fail" || x.status === "error")
90
+ console.log("gated " + gated.length + " checks; build-dependent (reported, not gated): " + (buildDep.map((x) => x.name.split(" ")[0] + "=" + x.status).join(" ") || "none"))
91
+ if (bad.length) { console.error("::error::failing: " + bad.map((x) => x.name).join(" | ")); process.exit(1) }
92
+ '
93
+ ```
94
+
95
+ > 为什么门禁不 install/build、徽章也不由本仓自跑:静态 R/K 检查只读已提交的树(无需依赖);而 `npm run build` 在缺 harness 别名的环境里会失败,其 prebuild 还会清空已提交的 `lib/`,制造假红。把 35 个第三方仓的依赖安装集中到本仓 CI 执行则是供应链风险。因此门禁在各仓自己的 CI 里执行、只读提交树,本仓只做审计与发徽。
96
+
31
97
  ## 判据来源(SURVEY.md 有全文与 URL)
32
98
 
33
99
  - **harness 侧**:`docs/user/develop/basic/publish.md`、`apps/cli/src/plugin.ts`(激活门=唯一开关)、
@@ -58,16 +124,22 @@ lib/checks-package.mjs 静态·包结构 R0–R8
58
124
  lib/checks-cordis.mjs 静态·cordis 契约 K1–K9
59
125
  lib/checks-smoke.mjs 动态·沙箱冒烟 D0–D3、D9
60
126
  lib/checks-collections.mjs 生态·集合站清单 CC1–CC5
127
+ tests/selftest.mjs 7 例真实 CLI 自检(退出码契约 + 防静默通过回归守卫)
128
+ scripts/verify.mjs verified 注册表与徽章刷新(只读 GitHub API 审计各仓门禁)
129
+ scripts/badge.mjs verified SVG 渲染
130
+ data/verified-repos.json verified 声明仓清单
131
+ data/verified.json verified 注册表(CI 生成)
132
+ badges/ verified 徽章(CI 生成)
61
133
  SURVEY.md 全渠道检测方法梳理 + 判据出处
62
134
  ```
63
135
 
64
136
  ## 状态
65
137
 
66
- 正式仓库:GitHub `PerryLink/dsh-plugin-doctor`(MIT 外 Apache-2.0),npm `@perrylink/dsh-plugin-doctor`
67
- latest=0.1.3,2026-09-07)。CI 用法:
138
+ 正式仓库:GitHub `PerryLink/dsh-plugin-doctor`(Apache-2.0),npm `@perrylink/dsh-plugin-doctor`
139
+ (**latest=0.1.4**;0.1.5 已入库待发布,见 `CHANGELOG.md`)。CI 用法(**请用 ASCII 别名**):
68
140
 
69
141
  ```powershell
70
- npx --yes @perrylink/dsh-plugin-doctor@0.1.3 --repo . --no-smoke --only "静态·包结构,静态·cordis 契约扫描"
142
+ npx --yes @perrylink/dsh-plugin-doctor@0.1.4 --repo . --no-smoke --only "R,K"
71
143
  ```
72
144
 
73
145
  35 个插件仓已内置 `.github/workflows/plugin-doctor.yml`(install→build→npx 静态 R/K 门禁)。
package/doctor.mjs CHANGED
@@ -17,13 +17,28 @@ const USAGE = `dsh-plugin-doctor —— dsh 插件完整性 + 运行流畅一体
17
17
  选项:
18
18
  --no-smoke 跳过动态沙箱冒烟(默认执行;需网络 + pnpm)
19
19
  --dsh <版本> 冒烟宿主版本(默认 0.1.2-rc.1 = npm 已发布 latest)
20
- --only <分组> 只跑指定分组(逗号分隔):
21
- 静态·包结构, 静态·cordis 契约扫描, 动态·沙箱冒烟, 生态·集合站清单
20
+ --only <分组> 只跑指定分组(逗号分隔)。推荐用 ASCII 别名(编码安全):
21
+ R = 静态·包结构
22
+ K = 静态·cordis 契约扫描
23
+ D = 动态·沙箱冒烟
24
+ CC = 生态·集合站清单
25
+ 中文全名同样可用;大小写不敏感。
22
26
  --json <路径> 另存 JSON 报告
23
27
  -h, --help 显示帮助
24
28
 
25
- 退出码: 0 = 无 fail/error(可含 warn/skip);1 = 存在 fail/error
26
- 安全: 冒烟全程使用 %TEMP% mkdtemp 临时 DSH_HOME,绝不触碰真实 ~/.dsh`
29
+ 退出码: 0 = 无 fail/error(可含 warn/skip);1 = 存在 fail/error;2 = 用法/分组错误或零检查
30
+ 安全: 冒烟全程使用 %TEMP% mkdtemp 临时 DSH_HOME,绝不触碰真实 ~/.dsh
31
+
32
+ 防静默通过: --only 里的分组名只要有一个不匹配(例如文件编码损坏导致中文变成乱码),
33
+ 本工具立即以退出码 2 失败,绝不"零检查 + exit 0"。`
34
+
35
+ // 分组 ASCII 别名(工作流里用它们可免受文件编码影响;中文全名仍可用)
36
+ const GROUP_ALIASES = {
37
+ R: '静态·包结构',
38
+ K: '静态·cordis 契约扫描',
39
+ D: '动态·沙箱冒烟',
40
+ CC: '生态·集合站清单',
41
+ }
27
42
 
28
43
  function parseArgs(argv) {
29
44
  const opts = { repo: null, smoke: true, dshVersion: '0.1.2-rc.1', json: null, groups: null, help: false }
@@ -65,7 +80,28 @@ async function main() {
65
80
  addCollectionChecks(doctor, ctx)
66
81
  if (opts.smoke) addSmokeChecks(doctor, ctx, { dshVersion: opts.dshVersion })
67
82
 
83
+ const knownGroups = [...new Set(doctor.checks.map((c) => c.group))]
84
+ if (opts.groups) {
85
+ // 别名 → 分组全名;大小写不敏感。未命中的 token 原样保留,下面统一报错。
86
+ const resolved = opts.groups.map((t) => GROUP_ALIASES[t.toUpperCase()] ?? t)
87
+ const unknown = resolved.filter((g) => !knownGroups.includes(g))
88
+ if (unknown.length > 0) {
89
+ console.error(
90
+ `未知分组: ${unknown.join(', ')}\n` +
91
+ `可用分组: ${knownGroups.map((g) => `${g}(${Object.keys(GROUP_ALIASES).find((a) => GROUP_ALIASES[a] === g) ?? '-'})`).join(', ')}`,
92
+ )
93
+ process.exitCode = 2
94
+ return
95
+ }
96
+ opts.groups = resolved
97
+ }
98
+
68
99
  const results = await doctor.run(ctx, { groups: opts.groups })
100
+ if (results.length === 0) {
101
+ console.error('没有任何检查被执行(--only 未匹配到任何分组)——拒绝静默通过,退出码 2')
102
+ process.exitCode = 2
103
+ return
104
+ }
69
105
  console.log(`# dsh-plugin-doctor 报告\n目标: ${repoPath}\n包名: ${ctx.pkgName}`)
70
106
  console.log(render(results))
71
107
  console.log(`日志目录: ${logDir}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perrylink/dsh-plugin-doctor",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Zero-dependency static + sandbox smoke detector for DeepSeek Harness (dsh) plugins: package-structure gates (R), cordis contract scans (K), keyless-headless sandbox smoke (D), and ecosystem-listing checks (CC).",
5
5
  "type": "module",
6
6
  "main": "doctor.mjs",
@@ -11,6 +11,7 @@
11
11
  "doctor.mjs",
12
12
  "lib/",
13
13
  "README.md",
14
+ "CHANGELOG.md",
14
15
  "SURVEY.md"
15
16
  ],
16
17
  "engines": {
@@ -31,7 +32,18 @@
31
32
  "type": "git",
32
33
  "url": "git+https://github.com/PerryLink/dsh-plugin-doctor.git"
33
34
  },
35
+ "homepage": "https://www.npmjs.com/package/@perrylink/dsh-plugin-doctor",
36
+ "bugs": {
37
+ "url": "https://github.com/PerryLink/dsh-plugin-doctor/issues"
38
+ },
39
+ "funding": {
40
+ "type": "github",
41
+ "url": "https://github.com/sponsors/PerryLink"
42
+ },
34
43
  "scripts": {
35
- "test": "node --check doctor.mjs && node --check lib/framework.mjs && node --check lib/util.mjs && node --check lib/checks-package.mjs && node --check lib/checks-cordis.mjs && node --check lib/checks-smoke.mjs && node --check lib/checks-collections.mjs"
44
+ "test": "node --check doctor.mjs && node --check lib/framework.mjs && node --check lib/util.mjs && node --check lib/checks-package.mjs && node --check lib/checks-cordis.mjs && node --check lib/checks-smoke.mjs && node --check lib/checks-collections.mjs && node --check scripts/badge.mjs && node --check scripts/verify.mjs && node tests/selftest.mjs",
45
+ "prepublishOnly": "npm test",
46
+ "verify:registry": "node scripts/verify.mjs",
47
+ "badge": "node scripts/badge.mjs"
36
48
  }
37
49
  }