@perrylink/dsh-plugin-doctor 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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 dsh-memento contributors
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # dsh-plugin-doctor
2
+
3
+ dsh 插件「完整性 + 运行流畅」一体检测器。零依赖(Node ≥22 自带能力),一次运行同时覆盖
4
+ **包结构静态检查(R)→ cordis 契约扫描(K)→ 动态沙箱冒烟(D)→ 生态集合站清单校验(CC)** 四层。
5
+ 判据全部来自 2026-09-07 三路一手调研:deepseek-harness 文档/源码、cordiverse/cordis 源码契约、
6
+ 工作区全渠道存量盘点(详见 `SURVEY.md`)。
7
+
8
+ ## 用法
9
+
10
+ ```powershell
11
+ node doctor.mjs --repo <插件仓路径> # 全量(含动态冒烟,需网络 + pnpm)
12
+ node doctor.mjs --repo <路径> --no-smoke # 仅静态 + 清单
13
+ node doctor.mjs --repo <路径> --dsh 0.1.2-rc.1 # 冒烟宿主版本(默认 npm latest 已发布线)
14
+ node doctor.mjs --repo <路径> --only 静态·包结构,静态·cordis 契约扫描
15
+ node doctor.mjs --repo <路径> --json report.json
16
+ ```
17
+
18
+ - 退出码:`0` = 无 fail/error(可含 warn/skip);`1` = 存在 fail/error。适合直接挂 CI。
19
+ - 冒烟全程使用 `%TEMP%` mkdtemp 临时 `DSH_HOME`/`DSH_AGENTS_HOME`,绝不触碰真实 `~/.dsh`(红线 3)。
20
+ - 每步子进程 stdout/stderr 落盘 `%TEMP%\dsh-doctor-logs-*`,报告尾部打印路径,证据可查。
21
+
22
+ ## 检测目录
23
+
24
+ | 分组 | 项 | 判据要点 |
25
+ |---|---|---|
26
+ | 静态·包结构 | R0–R8 | 基础字段;**激活门 `dsh.bundle.patch`(关键)**;`npm pack --dry-run` tarball 含入口与 patch;cordis.patch.yml 结构;入口 name/apply 导出;rescope 依赖口径(禁裸 `cordis`);engines 对齐 `^22.19.0 \|\| >=24.0.0`;预构建 + files 白名单;peer 旧 rc 残留(2026-09-05 双基线教训) |
27
+ | 静态·cordis 契约 | K1–K9 | 服务访问 vs inject 声明;ctx 活数据序列化红线;定时器/监听器未包 `ctx.effect`;Schema 含函数;apply 返回形状(v4 Effect 契约);inject 服务名 seam;**v3 遗留 API(3.x→4.x 已删除清单)**;Config 必须是 Standard Schema;`name==='apply'` 特例 |
28
+ | 动态·沙箱冒烟 | 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
+ | 生态·集合站清单 | CC1–CC5 | 认证注册表 spec v1 五维 evidence;adp-list yml 字段/枚举/描述;dsh-catalog 目录条目约束(禁安装命令、截断启发式);omdsh `dshWorkshop` activation 5 值;dsh-plugin-kit 三门(license/五语 README/seam 三角色,优先调用 kit 官方 CLI) |
30
+
31
+ ## 判据来源(SURVEY.md 有全文与 URL)
32
+
33
+ - **harness 侧**:`docs/user/develop/basic/publish.md`、`apps/cli/src/plugin.ts`(激活门=唯一开关)、
34
+ `packages/bundle/headless/README.md`(MISSING_CREDENTIAL 判据)、Releases(0.1.2-rc.1 / 0.1.3-alpha.1 变更)、
35
+ `@deepseek-ai/dsh-loader-smoke`(官方化"临时 DSH_HOME + 期望退出码"模式)。
36
+ - **cordis 侧**:cordiverse/cordis v4 源码(registry/fiber/reflect/events.ts)+ DSH cordis-primer/tutorial 文档 +
37
+ v3 `@cordisjs/core@3.10.2` d.ts 差异(3.x→4.x 黑名单)。
38
+ - **生态侧**:dsh-plugin-certification spec v1、adp-list `entries.mjs`/`check-submission.mjs`、
39
+ dsh-catalog `validate.mjs`/`deploy.yml` live smoke、omdsh build-submission、dsh-plugin-kit `verify/*`。
40
+
41
+ ## 已知局限(诚实声明)
42
+
43
+ - K 组为**启发式静态扫描**:K1/K3/K4 会漏掉复杂包装、也可能误报——warn 级均需人工复核,不自动判死。
44
+ - D3 只证明「组合可 boot 到模型请求」,**不证明工具 schema 合法或业务逻辑正确**(需带钥 e2e 或 mock LLM 补充)。
45
+ - pnpm `ignored-builds` 属环境配方问题:D1 命中时降级为 warn 并给出 compat.yml allowBuilds 配方提示,
46
+ 与认证 spec v1 的 environment-blocked 口径一致,不计为插件缺陷。
47
+ - npm 线宿主(0.1.2-rc.1)packument 无 engines/peerDependencies 强制,R6 为建议门。
48
+ - 本环境实测陷阱:`$` 锚点(无 m 标志)不匹配行尾孤立 `\r` 之前的位置,解析 CRLF 文本必须按
49
+ `/\r?\n/` 切行(已内建处理,勿回退)。
50
+
51
+ ## 目录结构
52
+
53
+ ```
54
+ doctor.mjs CLI 入口(分组编排、退出码、JSON 报告)
55
+ lib/framework.mjs 检查注册/运行/判定/渲染(零依赖)
56
+ lib/util.mjs 临时沙箱 + 子进程执行(stdout/stderr 落盘,规避管道捕获限制)
57
+ lib/checks-package.mjs 静态·包结构 R0–R8
58
+ lib/checks-cordis.mjs 静态·cordis 契约 K1–K9
59
+ lib/checks-smoke.mjs 动态·沙箱冒烟 D0–D3、D9
60
+ lib/checks-collections.mjs 生态·集合站清单 CC1–CC5
61
+ SURVEY.md 全渠道检测方法梳理 + 判据出处
62
+ ```
63
+
64
+ ## 状态
65
+
66
+ WIP 工作夹(非 git 仓,同 `dsh-ticktick-handoff` 口径)。检测器本体零依赖、可独立运行;
67
+ 后续如需发布,可参照工作区发布流程另立仓库。
package/SURVEY.md ADDED
@@ -0,0 +1,265 @@
1
+ # dsh 插件「完整性 + 运行流畅」检测方法全梳理
2
+
3
+ > 2026-09-07 · 依据三路一手调研:① deepseek-harness 官方文档/源码/Releases(web + 只读 checkout
4
+ > `D:\deepseek-harness`@3801401cac);② cordiverse/cordis v4 源码契约 + DSH cordis 文档 + v3 d.ts 差异;
5
+ > ③ 本工作区 `D:\Projects\dsh\plugins` 全渠道存量盘点(认证体系/adp-list/catalog/omdsh/plugin-kit/CI/compat/docs)。
6
+ > 自研检测器实现:本目录 `doctor.mjs`(四层 R/K/D/CC)。所有"通过"结论必须来自真实执行输出(证据纪律)。
7
+
8
+ ## 0. 摘要(最重要的 8 条)
9
+
10
+ 1. **激活门是唯一硬开关**:host 扫描 profile dependencies,凡解析出的 package.json 声明
11
+ `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 即加入 `dsh.profile.bundles`;没有它 =
12
+ 装为普通依赖 + stderr 警告、**永不激活**。`dshWorkshop` 是 omdsh 社区元数据,harness 不读,不能替代。
13
+ 2. **keyless headless 冒烟判据**:无凭据 `dsh --profile headless <任务>` 期望 **exit 1 且 stderr 严格匹配
14
+ `dsh: MISSING_CREDENTIAL`**;必须同时排除 `NO_ADAPTER`/`ERR_MODULE_NOT_FOUND`/`SyntaxError`/`TypeError`。
15
+ 走到 MISSING_CREDENTIAL ⇒ bundle 层生效 + 插件 apply 成功 + 组合到达模型请求阶段。官方
16
+ `@deepseek-ai/dsh-loader-smoke` 的 `expectedExitCode` 就是这个模式的成品(隔离临时 DSH_HOME + 超时 + 清场)。
17
+ 3. **无崩溃隔离**:插件与宿主同进程;boot 期任何 parse/schema/resolution/加载失败 → fail-loud 全 profile
18
+ 非零退出。坏插件会杀死启动——这本身是检测信号,但也意味着不能只测 apply,必须测真实 Loader 组合。
19
+ 4. **cordis v4 契约要点**:apply 返回只接受 函数/null/Promise\<disposer\>/(async) iterable,其余
20
+ `TypeError('Invalid effect')`;inject=硬依赖(数组/对象形状),可选依赖走 `ctx.get(name)`;未声明直接
21
+ 访问 `ctx.xxx` 抛 `cannot get property ... without inject`;一切注册皆 effect,卸载自动回滚;v3 的
22
+ fork/reusable/using/scope/runtime/lifecycle/config/collect/accept/decline/alias/off 全部删除(3.x 插件必查)。
23
+ 5. **npm 线 vs checkout 线分裂必须进检测**:npm latest=0.1.2-rc.1(packument 无 engines/peerDependencies);
24
+ checkout 0.1.3-alpha.1 的 session 破坏性变更(SessionHandle / 异步 create / SESSION_FORMAT v2)未上 npm。
25
+ 冒烟基线默认钉 npm 线;会话类插件另做 0.1.3-alpha.1 专项审查。
26
+ 6. **生态存量检测资产已相当成熟**:认证 spec v1 五维、adp-list 七 workflow 门禁、catalog 双 schema + live
27
+ smoke、plugin-kit 三门、compat.yml 三段——自研检测器的正确姿势是**复用 + 补缺**,不是另起炉灶。
28
+ 7. **工程红线**:一切动态测试用 `%TEMP%` mkdtemp 临时 DSH_HOME(绝不碰真实 `~/.dsh`);pnpm ignored-builds
29
+ 属环境配方问题(按 allowBuilds compat 配方),记 environment-blocked 而非插件缺陷;证据不足记
30
+ `no-evidence`,禁止编造通过。
31
+ 8. **自研检测器 `dsh-plugin-doctor`**:零依赖、四层(R 包结构 / K cordis 契约 / D 沙箱冒烟 / CC 集合站清单)、
32
+ 退出码 CI 友好;2026-09-07 已对 dsh-github、dsh-memento 实测(§5)。
33
+
34
+ ## 1. 渠道全景:每个集合站/渠道的检测方法与判据
35
+
36
+ ### 1.1 deepseek-harness 官方运行时(deepseek-ai/deepseek-harness)
37
+
38
+ **安装与激活链**(`docs/user/develop/basic/publish.md`、`apps/cli/src/plugin.ts`、`apps/cli/reference/README.md`):
39
+
40
+ - `dsh plugin --profile <name> <args...>` = pnpm 转发器:首用初始化 profile(`$DSH_HOME/profiles/<name>/`)→
41
+ `spawnSync('pnpm', …)` → pnpm exit 0 后 reconcile bundles。DSH_HOME 优先级:显式路径 > `$DSH_HOME` env > `~/.dsh`。
42
+ - 五内建模板:web / headless / sdk / sdk-minimal / acp。web=常驻+浏览器+热载(`dsh web`,默认端口 3080);
43
+ headless=一次性(位置参数任务、无端口)。**bundle 成员变化是启动边界**:add/remove/update 后必须重启。
44
+ - 层序:bundles 列表序 → profile `cordis.patch.yml` → 家级 `$DSH_HOME/cordis.patch.yml` → `--patch` overlay;
45
+ 后层按 id **整行替换**(非深合并)。`--dump-config` 不 boot 验证层,输出 `# == <pkg>` 来源标记。
46
+
47
+ **npm 插件包形态(官方范式)**:`name`/`version`/`type: "module"`/`main`(构建产物,范式 `lib/index.js`)/
48
+ `files` 含入口 + `cordis.patch.yml`/**`"dsh": {"bundle": {"patch": "./cordis.patch.yml"}}`**;patch 为 YAML 数组
49
+ `- insert: - id: <rowId>, name: <包名>`(name 经 profile node_modules 解析,必须是包名);入口导出
50
+ `export const name` + `export function apply(ctx)`,可选 `inject: [...]` 与 `Config`(schemastery Schema);
51
+ 依赖口径 `@deepseek-ai/cordis` 同时 peer+dev(裸 `cordis` 是上游 cordiverse 包);工具注册正典
52
+ `ctx.tools.register(defineTool({...}))`(来自 `@deepseek-ai/dsh-tools`);git 直装需 `prepare` + 用户侧
53
+ `pnpm-workspace.yaml allowBuilds`,npm/tarball 必须预构建。
54
+
55
+ **官方验证设施(可直接复用的检测模式)**:`@deepseek-ai/dsh-loader-smoke`(`runLoaderSmoke({tempDirPrefix,
56
+ binScript, configPath, expectedExitCode, processTimeoutMs, inspect})`:隔离临时 cwd + 隔离
57
+ DSH_HOME/DSH_AGENTS_HOME + 超时杀进程 + 期望退出码判定 + 清场);`apps/cli/tests/profiles/headless/tests/
58
+ keyless-smoke.e2e.ts`(真实 Loader 树 + 断言会话 JSONL 落盘 + 工具清单);打包门 publint /
59
+ verify-node-next-types / verify-npm-install-layout / verify-application-entrypoints。
60
+
61
+ **引擎与版本**:`engines.node: "^22.19.0 || >=24.0.0"`(CI 22.19/24/26;Node 23 整线排除;22.19 地板由
62
+ pi-ai 抬升);pnpm 11.7(Corepack);npm 线 `@deepseek-ai/dsh@0.1.2-rc.1` packument **无 engines/无
63
+ peerDependencies**(插件 engines 声明是建议非强制)。
64
+
65
+ **影响插件的变更史**(Releases 即 changelog):rc.1 = `Session.events` → `seq`/`eventAt()`/`snapshotEvents()`、
66
+ `ApiProxy` 移除 → `@Remote`、SQLite session 后端移除、Node 24.0–24.11.1 修复、官方请求默认附带插件包名+版本;
67
+ 0.1.3-alpha.1(未上 npm)= SessionHandle / 异步 `agentLoop.create()` / **SESSION_FORMAT v2**(v0/v1 相邻
68
+ generation 迁移);rc.8 SQLite 格式不兼容;rc.7 设置卡片能力。README 明示 developer preview、破坏必续。
69
+
70
+ ### 1.2 awesome-dsh-plugin(`adp-list/` 上游收录)
71
+
72
+ - **条目 schema**(`scripts/lib/entries.mjs` `validateEntries()`):键白名单 `{url,name,category,description,tarball}`;
73
+ url 必须 `https://github.com/owner/repo`;文件名 = slugFor(url)(monorepo 子包 `owner__repo--path.yml`);
74
+ category 23 值枚举;`description.en` 必填单行(zh 可选);tarball 必须 GitHub Release 托管 https `.tgz`
75
+ (github.com / objects.githubusercontent.com / release-assets.githubusercontent.com);BOM 剥离。
76
+ - **contributing 硬要求**:`dsh.bundle`(仅 `dsh.client` 拒)、真实代码(占位/README-only 拒)、仓库 ≥1 天、
77
+ 活跃维护、`dsh-plugin` topic、描述属实(数字/API 名与代码核对)、分类贴切、纯聚合包不收、依赖指向上游作者、
78
+ 每 PR ≤3 条、screenshots 1–8 张 GitHub 托管、npm 映射自动采集(手写 `npm:` 键拒)。
79
+ - **CI 门禁五件套**:`pr-check`(stale-fork 守卫、yml 位置、README 与数据一致 `generate-readme.mjs --check`、
80
+ awesome-lint、build-site);`pr-gate`(`check-submission.mjs`:仓库存在且未归档、非 DSH 本体、未 vendor
81
+ dsh-base/web-app/headless、全树扫 `dsh.bundle` ≤40 manifest、年龄 ≥1 天、每 PR ≤3 条;rate-limit 未检项
82
+ 记 incomplete **不阻断**);`pr-guard`(动 `.github/` = stale fork;90 分钟无 check fail);`regate`(6 小时
83
+ 重跑未验/过期/中性/48h 旧 PASS);`decay-scan`(周扫 gone/archived/dormant 6 月/unbundled,只报 issue 不删,
84
+ inconclusive >5% 拒发布)。辅助:check-bleed(描述窜扰)、probe-npm(registry repository 回指)、
85
+ probe-tarballs(死链剔除)、probe-readmes/downloads/stars/screenshots/updates(新鲜度)。
86
+
87
+ ### 1.3 dsh-plugin-certification(spec v1 认证体系)
88
+
89
+ - **五维**:A 清单合规(bundle/LICENSE SPDX/keywords/五语 README/engines)· B 构建卫生(files 白名单、peer/
90
+ optional、无恶意 postinstall、lint/typecheck 门禁)· C 供应链(OpenSSF Scorecard 官方 API)· D 发布完整性
91
+ (npm provenance SLSA,`npm audit signatures` 可验)· E 安装冒烟(隔离一次性 profile 真实 install/load/
92
+ keyless boot,四态 `ok`/`load-fail`/`install-fail`/`skip`)。
93
+ - **判据**:A = 五维全过且 E=ok 无 veto;B = E 过且 A–D ≥3;C = E 过其余不全;D = 硬门失败(缺 bundle/无
94
+ license/恶意模式);Security veto(混淆/凭据外传/异常安装期行为)直接 D 并公布原因;**environment-blocked E
95
+ (如 pnpm approve-builds 交互门)保 B 并记录复现命令,绝不记 D**;无证据记 `no-evidence`。
96
+ - 数据 `data/certified.json`(specVersion v1,entries 含 grade/snapshot/dimensions 五维 evidence/veto);
97
+ `scripts/badge.mjs` 生成徽章;`registry.yml` 每日 cron 重渲染;`dsh-cert-mcp` 只读 MCP 暴露注册表(get/list/
98
+ spec 三工具)。现态:PerryLink/dsh-auto-review grade A。
99
+
100
+ ### 1.4 DSH Desktop Market(`dsh-catalog/` 标准目录源)
101
+
102
+ - 源数据 `data/packages.json`(37 条 `{npm,repo,displayName,categories,summary}`)→ `build-catalog.mjs [origin]`
103
+ → `artifacts/v1/plugins.json` + `catalog-source.json`(v1 双 schema 镜像)。
104
+ - **validate.mjs 判据**:manifestVersion 1.0.0;providerId 模式;endpoint https 且 /v1/plugins;items ≤200;id
105
+ 模式+去重;长度上限(name≤160/displayName≤120/summary≤1000);禁控制/bidi 字符;repository 或 package 二选一;
106
+ icon 与 endpoint 同源 `/icons/*.png`;**条目文本禁安装命令**(`dsh plugin --profile|pnpm add|npm i` 正则);
107
+ 失败 exit 1。`refresh-snapshot.mjs` 带截断启发式(未以 `[.!?)—–"」』]` 结尾告警)与 mojibake 启发式。
108
+ - **CI live smoke**(deploy.yml):build-icons → build(真实 origin) → validate → 部署(Vercel / CF workers 两段式)
109
+ → `curl -fsS` manifest + 插件页 + 一个 icon PNG 魔数断言 `[137,80,78,71,13,10,26,10]`;items<34 或缺
110
+ media.icon.url 即 fail。
111
+
112
+ ### 1.5 omdsh hub(Workshop 提交)
113
+
114
+ - `build-omdsh-submission.mjs`:package.json 原文读取;`dshWorkshop.lifecycle.activation` ∈
115
+ {immediate, hot-reload, restart-plugin, restart-profile, restart-host} 否则 throw;`restartRequired=/^restart-/`
116
+ (hub intake-lib.mjs 规则);产出 `omdsh-workshop-submission/v2` create-project JSON,`packageManifest`
117
+ verbatim 复制 dshWorkshop。
118
+ - hub 侧前置:`prepare-issue-intake.mjs <event.json>` + `npm run check` 双过;投稿后盯 intake run 与 bot 评论,
119
+ preflight failed 须修后重投。
120
+
121
+ ### 1.6 deepseek1024 官方榜与 Showcase
122
+
123
+ - `dsh1024 plugin --profile web add <pkg>` 安装行计入安装排行(真实可装 = 上榜资格;36 个 PerryLink 条目已收录)。
124
+ - Showcase 帖(官方 Discussion)人工审查:描述属实、可复现。
125
+
126
+ ### 1.7 Gitee 镜像
127
+
128
+ - 存在性与同步(perrylink gitee-sync,每日 cron),不构成质量门,仅传播渠道。
129
+
130
+ ### 1.8 各插件仓自身门禁链(37 仓通用 + 特例)
131
+
132
+ - **check 链**:`pnpm run typecheck && pnpm test && pnpm run build && pnpm run verify && pnpm pack`;
133
+ `prepublishOnly` 重跑全门禁;发布 = push tag → publish workflow(`--provenance`、Trusted Publishing 11 包)。
134
+ - **compat.yml(每月+手动,宿主钉 `@deepseek-ai/dsh@0.1.2-rc.1`)三段**:① bare-import(allowBuilds 预置四包
135
+ dsh-subprocess-local/koffi/node-pty/protobufjs + tarball 空项目 import)② profile(DSH_HOME 临时 + add dsh-base/
136
+ dsh-headless/tarball → `--dump-config` grep 包名 → `timeout 120 dsh --profile compat "Reply with exactly: ok"`,
137
+ 判据:日志含 `MISSING_CREDENTIAL`=PASS(树加载证明)或 exit 0 且边界正则匹配 ok=PASS,否则 FAIL exit 1)③
138
+ uninstall 可逆。
139
+ - **CI 标配**:Scorecard、host-compat 版本钉号(`check-host-versions.mjs`)、README 官方仓库声明、安装行、funding、
140
+ `dshWorkshop` manifest;特例门:dsh-memento 覆盖率阈值(lib≥90%/index≥85%/全文件≥90%)+ `test:conformance`
141
+ 协议黄金参考;check-endpoints 存活探测(**401/403/405/5xx=存活、404/410/DNS/TLS/超时=失败**)。
142
+ - 可复用模板资产:`scripts/loader-runner.mjs`(独立进程真 Loader)、`verify-self-contained.mjs`、
143
+ `verify-artifacts.mjs`、`check-readmes.mjs`、`smoke-package.mjs`、`check-host-versions.mjs`、dsh-test-drive
144
+ 全链路 e2e(install/config/smoke/uninstall/cleanup 五阶段)。
145
+
146
+ ## 2. cordis v4 契约要点(插件运行正确的硬约束)
147
+
148
+ 来源:cordiverse/cordis v4 源码(registry/fiber/reflect/events/service.ts)+ DSH cordis-primer/cordis-api/
149
+ framework/cordis-tutorial 文档 + v3 `@cordisjs/core@3.10.2` d.ts 差异。cordis 上游 4.0.x stable 从未发布
150
+ (latest=4.0.0-rc.9),DSH 将其 vendored 为 `@deepseek-ai/cordis`——**以 rescope 包为准**。
151
+
152
+ - **插件形态**:function / 有 prototype 的 class / `{apply}` 对象;非法抛 `invalid plugin, expect function or
153
+ object with an "apply" method`;`name==='apply'` 被重置为 undefined;`Plugin.Transform {schema:true}` 用于配置变换。
154
+ - **apply 返回(v4 Effect 契约)**:同步或 async 均可,但返回值只接受 函数 disposer / null / undefined /
155
+ `Promise<disposer>`(fiber 停留 LOADING 直至 settle,期间服务不可见)/ (async) iterable of disposers;
156
+ **其余形状(含 v3 `{dispose}` 对象)抛 `TypeError('Invalid effect')`**。
157
+ - **注入**:`inject: (keyof M)[] | { name?: interceptConfig }`;任一缺失 → fiber 永久 PENDING、apply 不执行;
158
+ 服务消失自动卸载、恢复重载;可选依赖 = `ctx.get(name)`(strict=true 只看 ACTIVE provider,缺失 undefined);
159
+ 未声明访问抛 `cannot get property "x" without inject`;`ctx.set` 需先 provide(否则
160
+ `cannot set property ... without provide`);同隔离域重复 provide 抛 `service "x" has been registered at ...`;
161
+ provide 卸载链先 `await Promise.allSettled(依赖方.await())` 再清理自身。
162
+ - **生命周期**:`ctx.effect(() => disposer)` 立即执行、disposer 逆序、双调 no-op、卸载后注册抛
163
+ `CordisError('INACTIVE_EFFECT')`;多个异步 disposer **并发**执行(顺序敏感的 teardown 必须写在同一个
164
+ disposer 内串行 await);`ctx.on/plugin/provide/accessor/mixin` 皆 effect 返回 disposer;状态机
165
+ `PENDING→LOADING→ACTIVE→UNLOADING→DISPOSED`(`LOADING↘FAILED`),迁移发 `internal/status`;**FAILED 后依赖
166
+ 变化不复活,只能 `update()`**(rc.9 #98);`fiber.await()/restart()/update()/state/getEffects()` 为诊断接口。
167
+ - **事件五模式**:emit 同步不等待;parallel 并发+AggregateError;serial/bail 首 bail 停;waterfall 中间件链
168
+ (**不调 next() = 短路 veto**;next() 二调抛错;返回值同步)。bail 判定 = 非 null/false/undefined。
169
+ `on/once(name, listener, opts)` 返回 disposer;v4 **删除 off()**;事件名可 symbol/原型名(rc.9 #51);
170
+ `internal/*` 保留事件(plugin/status/service/update(waterfall)/get/set/listener/dispatch)。
171
+ - **Schema**:Config 必须是 Standard Schema 校验器("普通对象导出为 Config 无法工作"——教程 05);异步校验抛
172
+ `TypeError('Async config validation is not supported')`;失败抛 `ValidationError` 聚合 issues 带路径
173
+ (`... (at targets)`);default 补齐;schema 可 JSON round-trip、函数/类实例值禁止、`!!js` 仅限 config/disabled。
174
+ - **3.x→4.x 黑名单(迁移必查)**:`reusable`/`reactive`/fork 机制、`inject:{required,optional}`/`using`、
175
+ 函数型 Config、`{dispose}` 返回、`ctx.using/scope/runtime/lifecycle/config/start()/stop()/collect()/accept()/
176
+ decline()/alias()/off()`、`Context.service()` 装饰器、v3 Service 类 `static immediate`/`protected start/stop/fork`。
177
+ - **序列化红线**:Context 是 Proxy(inspect.custom),JSON.stringify/structuredClone/全量枚举 = 违规。
178
+ - 上游无插件 lint 工具;可借鉴 cordis-rs 163 场景对照表、DSH `@mode` 事件目录交叉校验(gen-cordis-catalog)、
179
+ `ctx.invariants` 注册表(InvariantError 机械校验)。
180
+
181
+ ## 3. 自研检测器:dsh-plugin-doctor(本目录)
182
+
183
+ ### 3.1 设计原则
184
+
185
+ 零依赖(Node 22 内置);判据全部溯源到 §1/§2 的文件或 URL;静态层启发式只降 warn 不判死、动态层严格断言;
186
+ 子进程 stdout/stderr 落盘 `%TEMP%\dsh-doctor-logs-*`(证据纪律);退出码 0/1 直接挂 CI;
187
+ 冒烟全程 `%TEMP%` mkdtemp DSH_HOME(红线 3);pnpm ignored-builds 按 environment-blocked 口径降 warn。
188
+
189
+ ### 3.2 判据映射表
190
+
191
+ | 检测项 | 来源依据 | 实现方式 |
192
+ |---|---|---|
193
+ | R1 激活门 `dsh.bundle.patch`(关键) | plugin.ts reconcile 逻辑 | 静态 JSON 字段 |
194
+ | R2 tarball 完整性 | publish.md 包形态 + `files` | `npm pack --dry-run --json` 清单核对 |
195
+ | R3 patch 结构 | patch YAML 约定(id/name=包名) | 启发式解析;D2 dump-config 动态兜底 |
196
+ | R4 入口 name/apply/inject | index.md 入口契约 | 产物 + 源码双重 grep |
197
+ | R5 rescope 依赖口径 | publish.md + 工作区双基线 | 依赖字段扫描 + 源码 import 对照 |
198
+ | R6 engines | 官方 `^22.19.0 \|\| >=24.0.0` | 声明解析(建议门) |
199
+ | R7 预构建 + files 覆盖 | publish.md(npm 预构建/git prepare) | main 指向 + files 白名单覆盖 |
200
+ | R8 peer 旧 rc 残留 | 2026-09-05 全仓统一教训 | 旧 prerelease-tuple 正则 |
201
+ | K1 服务访问 vs inject | reflect.ts throw 文案 | ctx.X 全集 vs 全仓 inject 并集 |
202
+ | K2 序列化红线 | Proxy/def-site 追踪 | JSON.stringify(ctx 等正则 |
203
+ | K3 资源未包 ctx.effect | 教程 02 原文 | 定时器/监听器上下文窗口 |
204
+ | K4 Schema 含函数 | schemastery README | Schema+箭头函数同文件 |
205
+ | K5 apply 返回形状 | fiber.ts Invalid effect | async apply 提示 + {dispose} 告警 |
206
+ | K6 inject 已知 seam | architecture.md seam 表 | seam 白名单(含实测补充) |
207
+ | K7 v3 遗留 API | v3 d.ts 差异 11 条 | 黑名单正则(fail 级) |
208
+ | K8 Config 校验器 | 教程 05 | Config 导出初值启发式 |
209
+ | K9 name==='apply' | registry.ts 特例 | 字面量正则 |
210
+ | D0–D3 沙箱冒烟 | compat.yml 正典 + loader-smoke | 临时 DSH_HOME 全链路 + 严格 MISSING_CREDENTIAL |
211
+ | CC1 认证注册表 | spec v1 certified.json | 五维 evidence/veto 核对 |
212
+ | CC2 adp-list 条目 | entries.mjs schema | 本地字段/枚举/描述校验 |
213
+ | CC3 市场目录条目 | catalog v1 schema | 条目约束 + 截断/安装命令启发式 |
214
+ | CC4 omdsh 清单 | build-submission.mjs | activation 5 值 + restartRequired |
215
+ | CC5 插件三门 | dsh-plugin-kit verify/* | 优先调 kit 官方 CLI(未构建则本地降级) |
216
+
217
+ ### 3.3 局限(诚实声明)
218
+
219
+ - K 组为启发式:漏检复杂包装、可能误报——warn 均需人工复核;`$` 锚点与行尾 `\r` 的环境陷阱已内建规避。
220
+ - D3 只证明「组合可 boot 到模型请求」,不证明工具 schema/业务正确(需带钥 e2e 或 mock LLM 补)。
221
+ - 未覆盖:L9 浏览器 UI(Playwright)、L8 soak、性能/启动开销、跨平台矩阵——可后续加组。
222
+
223
+ ## 4. 使用建议
224
+
225
+ 1. **提交前**:`node doctor.mjs --repo . --no-smoke`(秒级);发版/收录前:全量(含 D 组,约几分钟)。
226
+ 2. **CI 接入**:静态层 job(`--no-smoke --json`)+ 每月 compat.yml 已有冒烟,双轨互补。
227
+ 3. **双基线**:npm 线冒烟钉 `--dsh 0.1.2-rc.1`;checkout/0.1.3-alpha.1 线用各仓 `typecheck`(tsconfig paths 对照)
228
+ + 会话类插件按 §1.1 变更清单专项审查(SessionHandle/异步 create/v2)。
229
+ 4. **收录/认证工作流**:改动 adp-list yml、catalog 条目、认证五维证据后跑对应 CC 项 + D 组取证。
230
+ 5. **pnpm ignored-builds**:按 compat.yml allowBuilds 配方(dsh-subprocess-local/koffi/node-pty/protobufjs)处理,
231
+ 记录复现命令,不判插件缺陷。
232
+
233
+ ## 5. 实测记录(2026-09-07 真实执行,证据纪律:以下结论均出自真实运行输出)
234
+
235
+ ### dsh-github(@perrylink/dsh-github 0.7.5 · 全量含动态冒烟)
236
+
237
+ - 静态 R0–R8 **全 PASS**(激活门 / tarball 88 文件 / patch 结构 / 入口导出 / rescope 口径 / engines /
238
+ 预构建 / peer 对齐)。
239
+ - K 组:**K1 WARN**(`state.ts:97` 的 `subagents: ctx.subagents` 未在 inject 声明——硬依赖应 inject、
240
+ 可选依赖应 `ctx.get()`);**K3 WARN**(`bot.ts:145` setInterval、`github.ts:98–99` timeout+abort 监听、
241
+ `jobs.ts:100` timeout 疑似未包 `ctx.effect`,需人工复核);其余 K2/K4–K9 PASS。
242
+ - CC 组:CC2/CC3/CC4 PASS;CC1 SKIP(未进认证注册表);**CC5 FAIL**(kit 官方 CLI:src 缺
243
+ Service Definition/Service Provider/Consumer 三角色 marker)。
244
+ - D 组 **D0–D3 全 PASS**:`plugin add` 后 `dsh.profile.bundles` 含 `@perrylink/dsh-github` → `--dump-config`
245
+ 层标记 `# == @perrylink/dsh-github` → keyless headless **exit 1 + `dsh: MISSING_CREDENTIAL`**(组合 boot
246
+ 至模型请求阶段)→ 沙箱清理。证据:`reports/dsh-github.json` + 日志 `dsh-doctor-logs-WdvLA1`。
247
+
248
+ ### dsh-memento(dsh-memento 0.5.6 · 全量含动态冒烟)
249
+
250
+ - **R8 FAIL(真实缺陷,检测器首个战果)**:peer `@deepseek-ai/dsh-settings: >=0.1.0-rc.1 <0.2.0` 是旧
251
+ prerelease-tuple 区间(裸解析只匹配 0.1.0-rc.x 单行,与宿主 0.1.2-rc.1 波次冲突——2026-09-05 同款教训;
252
+ devDep 已正确钉 0.1.2-rc.1,建议 peer 改为 `>=0.1.2-rc.1 <0.2.0`)。
253
+ - K 组:仅 **K4 WARN**(`index.mjs` 同文件出现 Schema 与箭头函数,需人工复核是否有函数值进 schema);
254
+ K1/K2/K3/K5–K9 PASS(剥注释扫描后假阳性清零)。
255
+ - CC 组:CC2/CC3/CC4 PASS;CC1 SKIP;**CC5 FAIL**(纯 JS 仓无 `src/`,kit verify-seam 结构不适用——
256
+ 检测器已自动加注,建议 kit 侧扩展扫描范围或人工豁免)。
257
+ - D 组 **D0–D3 全 PASS**(MISSING_CREDENTIAL 判据,树加载证明)。证据:`reports/dsh-memento.json` +
258
+ 日志 `dsh-doctor-logs-lJNAmQ`。
259
+
260
+ ### 检测器自身迭代记录(本会话修复清单)
261
+
262
+ Windows shell 解析 npm.cmd shim;**行尾孤立 `\r` 与 `$` 锚点的环境陷阱**(本机 `$` 不匹配行尾 `\r` 前,
263
+ CRLF 行必须按 `/\r?\n/` 切分);node.exe 直调不走 shell(路径含空格);K 组:全仓 inject 并集、
264
+ 剥注释后扫描(JSDoc 假阳性清零)、纯 JS 仓源文件发现(无 build 脚本且 main 指向 lib/ 时 lib 即源码)、
265
+ 自 provide 服务从"未知 seam"豁免;CC5 纯 JS 仓注记。
package/doctor.mjs ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ // dsh-plugin-doctor:dsh 插件「完整性 + 运行流畅」一体检测器
4
+ // 分组:静态·包结构 / 静态·cordis 契约扫描 / 动态·沙箱冒烟 / 生态·集合站清单
5
+ import path from 'node:path'
6
+ import { readFileSync, existsSync, mkdtempSync, writeFileSync } from 'node:fs'
7
+ import { tmpdir } from 'node:os'
8
+ import { Doctor, verdict, render } from './lib/framework.mjs'
9
+ import { addChecks as addPackageChecks } from './lib/checks-package.mjs'
10
+ import { addChecks as addCordisChecks } from './lib/checks-cordis.mjs'
11
+ import { addSmokeChecks } from './lib/checks-smoke.mjs'
12
+ import { addChecks as addCollectionChecks } from './lib/checks-collections.mjs'
13
+
14
+ const USAGE = `dsh-plugin-doctor —— dsh 插件完整性 + 运行流畅一体检测器
15
+
16
+ 用法: node doctor.mjs --repo <插件仓路径> [选项]
17
+
18
+ 选项:
19
+ --no-smoke 跳过动态沙箱冒烟(默认执行;需网络 + pnpm)
20
+ --dsh <版本> 冒烟宿主版本(默认 0.1.2-rc.1 = npm 已发布 latest)
21
+ --only <分组> 只跑指定分组(逗号分隔):
22
+ 静态·包结构, 静态·cordis 契约扫描, 动态·沙箱冒烟, 生态·集合站清单
23
+ --json <路径> 另存 JSON 报告
24
+ -h, --help 显示帮助
25
+
26
+ 退出码: 0 = 无 fail/error(可含 warn/skip);1 = 存在 fail/error
27
+ 安全: 冒烟全程使用 %TEMP% mkdtemp 临时 DSH_HOME,绝不触碰真实 ~/.dsh`
28
+
29
+ function parseArgs(argv) {
30
+ const opts = { repo: null, smoke: true, dshVersion: '0.1.2-rc.1', json: null, groups: null, help: false }
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const a = argv[i]
33
+ if (a === '--repo' || a === '-r') opts.repo = argv[++i]
34
+ else if (a === '--no-smoke') opts.smoke = false
35
+ else if (a === '--dsh') opts.dshVersion = argv[++i]
36
+ else if (a === '--json') opts.json = argv[++i]
37
+ else if (a === '--only') opts.groups = argv[++i].split(',').map((s) => s.trim())
38
+ else if (a === '--help' || a === '-h') opts.help = true
39
+ else if (!a.startsWith('-')) opts.repo = a
40
+ }
41
+ return opts
42
+ }
43
+
44
+ async function main() {
45
+ const opts = parseArgs(process.argv.slice(2))
46
+ if (opts.help) { console.log(USAGE); return }
47
+ if (!opts.repo) { console.error('缺少 --repo <路径>\n\n' + USAGE); process.exitCode = 2; return }
48
+ const repoPath = path.resolve(opts.repo)
49
+ const pkgPath = path.join(repoPath, 'package.json')
50
+ if (!existsSync(pkgPath)) { console.error(`未找到 ${pkgPath}`); process.exitCode = 2; return }
51
+ let pkg
52
+ try { pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) } catch (e) {
53
+ console.error(`package.json 解析失败: ${e.message}`); process.exitCode = 2; return
54
+ }
55
+ const logDir = mkdtempSync(path.join(tmpdir(), 'dsh-doctor-logs-'))
56
+ const ctx = {
57
+ repoPath,
58
+ pkg,
59
+ pkgName: pkg.name,
60
+ logDir,
61
+ workspaceRoot: path.resolve(import.meta.dirname, '..'),
62
+ }
63
+ const doctor = new Doctor()
64
+ addPackageChecks(doctor, ctx)
65
+ addCordisChecks(doctor, ctx)
66
+ addCollectionChecks(doctor, ctx)
67
+ if (opts.smoke) addSmokeChecks(doctor, ctx, { dshVersion: opts.dshVersion })
68
+
69
+ const results = await doctor.run(ctx, { groups: opts.groups })
70
+ console.log(`# dsh-plugin-doctor 报告\n目标: ${repoPath}\n包名: ${ctx.pkgName}`)
71
+ console.log(render(results))
72
+ console.log(`日志目录: ${logDir}`)
73
+ if (opts.json) {
74
+ writeFileSync(opts.json, JSON.stringify({ repo: repoPath, pkgName: ctx.pkgName, results }, null, 2), 'utf8')
75
+ console.log(`JSON 报告: ${opts.json}`)
76
+ }
77
+ process.exitCode = verdict(results).ok ? 0 : 1
78
+ }
79
+
80
+ main().catch((err) => { console.error(err); process.exitCode = 2 })