@moonquake2004/dsh-doctor 0.2.3 → 0.2.4
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 +121 -0
- package/README.zh.md +115 -0
- package/dsh-doctor.mjs +24 -4
- package/package.json +7 -3
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# dsh-doctor
|
|
2
|
+
|
|
3
|
+
> [中文版 README](README.zh.md) · English
|
|
4
|
+
|
|
5
|
+
Offline diagnostic for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) — run it **before** boot or before installing plugins, and it tells you which of the failure classes this community has been reporting will bite.
|
|
6
|
+
|
|
7
|
+
Zero npm dependencies. One file. Runs anywhere `node` exists (`zstd` needed only for `.zstd` session logs; E1 checks for it).
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
dsh's plugin tree is "fragile by install": a dangling reference, a broken `file:` link, a duplicate entry id, or a corrupted session log can brick the profile at boot or stall the whole web server — and `--dump-config` never mounts the loader, so it passes on broken setups. This class of failure was consolidated in [dsh discussion #1496](https://github.com/deepseek-ai/deepseek-harness/discussions/1496) (Advisory: plugin-install path needs guardrails). `dsh-doctor` is the offline check that advisory calls for — 20 built-in checks mapped to 18 community reports, each verified with synthetic negative fixtures, plus a self-updating remote catalog of declarative pattern checks (v0.2.0).
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node dsh-doctor.mjs # everything (env + profile + session)
|
|
17
|
+
node dsh-doctor.mjs --profile web # profile checks only
|
|
18
|
+
node dsh-doctor.mjs --session <path> # session checks (default: latest session)
|
|
19
|
+
node dsh-doctor.mjs --env # env checks
|
|
20
|
+
node dsh-doctor.mjs --json # machine-readable output
|
|
21
|
+
node dsh-doctor.mjs --no-catalog # skip remote catalog fetch (bundled copy only)
|
|
22
|
+
node dsh-doctor.mjs --json --envelope # v1 doctor-contract envelope (lowercase status, exit 0/1/2)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Exit codes (default mode): `0` = all pass · `1` = problems found (built-in checks + catalog `severity: error`) · warn-level catalog failures don't flip the exit code.
|
|
26
|
+
|
|
27
|
+
With `--envelope` (doctor-contract mode): `0` = all pass · `1` = any WARN · `2` = any FAIL. The envelope follows the shared `dsh-doctor/v1` schema (`{ schema, generatedAt, profile, exitCode, summary, ok, checks:[{name,status,detail}] }`) so implementations are interchangeable for CI/marketplace use. Installed via npm, the CLI is also available as the `dsh-doctor` bin.
|
|
28
|
+
|
|
29
|
+
## Checks (20)
|
|
30
|
+
|
|
31
|
+
### env
|
|
32
|
+
| ID | Checks | Discussion |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| E1 | `node`/`pnpm`/`zstd` on PATH | [#1270](https://github.com/deepseek-ai/deepseek-harness/discussions/1270) |
|
|
35
|
+
| E2 | `.env` is a file, not a directory | [#71](https://github.com/deepseek-ai/deepseek-harness/discussions/71) |
|
|
36
|
+
| E3 | node version / `--expose-internals` reachability | [#113](https://github.com/deepseek-ai/deepseek-harness/discussions/113), [#1313](https://github.com/deepseek-ai/deepseek-harness/discussions/1313) |
|
|
37
|
+
| E4 | node-pty native binary present (`prebuilds/<platform>-<arch>/pty.node`) | [#1219](https://github.com/deepseek-ai/deepseek-harness/discussions/1219) |
|
|
38
|
+
| E5 | storage JSON files valid (strict UTF-8 + parse) | [#1357](https://github.com/deepseek-ai/deepseek-harness/discussions/1357) |
|
|
39
|
+
| E6 | anchor tripwire: our S6/S7/S10 contracts still in installed `dsh-session` | [anti-rot idea](https://github.com/deepseek-ai/deepseek-harness/discussions/1534) |
|
|
40
|
+
| E10 | web port 3080 availability before launch (dsh web itself = OK; other process = FAIL; `DSH_DOCTOR_PORT` override) | [#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719) |
|
|
41
|
+
|
|
42
|
+
### profile
|
|
43
|
+
| ID | Checks | Discussion |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| P2 | bundle-layer vs user-patch insert id collisions (boot crash) | [#1404](https://github.com/deepseek-ai/deepseek-harness/discussions/1404) |
|
|
46
|
+
| P3 | user-patch insert `name:` resolvable from the profile anchor | [#1197](https://github.com/deepseek-ai/deepseek-harness/discussions/1197), [#880](https://github.com/deepseek-ai/deepseek-harness/discussions/880) |
|
|
47
|
+
| P4 | `file:` dependencies intact | [#1197](https://github.com/deepseek-ai/deepseek-harness/discussions/1197) |
|
|
48
|
+
| P5 | no top-level `@deepseek-ai/*` duplication (dual module instances) | [#1486](https://github.com/deepseek-ai/deepseek-harness/discussions/1486), [#1697](https://github.com/deepseek-ai/deepseek-harness/discussions/1697) |
|
|
49
|
+
| P7 | `cordis.patch.yml` structural lint (`~ insert:` null-literal typo, tab indentation, missing colon → UI won't boot) | [#1724](https://github.com/deepseek-ai/deepseek-harness/discussions/1724) |
|
|
50
|
+
|
|
51
|
+
### session
|
|
52
|
+
| ID | Checks | Discussion |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| S1 | orphan `tool_call` (no matching tool result) | [#1363](https://github.com/deepseek-ai/deepseek-harness/discussions/1363), [#1544](https://github.com/deepseek-ai/deepseek-harness/discussions/1544) |
|
|
55
|
+
| S2 | unclosed turns (session stuck "running") | [#466](https://github.com/deepseek-ai/deepseek-harness/discussions/466), [#1265](https://github.com/deepseek-ai/deepseek-harness/discussions/1265) |
|
|
56
|
+
| S6 | `seq == index` contiguity (official semantics, chunk rows expanded like `expandRow`) | [#1333](https://github.com/deepseek-ai/deepseek-harness/discussions/1333), [#1452](https://github.com/deepseek-ai/deepseek-harness/discussions/1452), [#1469](https://github.com/deepseek-ai/deepseek-harness/discussions/1469) |
|
|
57
|
+
| S7 | post-`end-seed` replay (replayed committed tail) | [#1497](https://github.com/deepseek-ai/deepseek-harness/discussions/1497) |
|
|
58
|
+
| S8 | unknown event types without `ignorable` (wholesale refusal) | [#1538](https://github.com/deepseek-ai/deepseek-harness/discussions/1538) |
|
|
59
|
+
| S9 | zstd container frame count (single-frame logs → `session.list` 500) | [#1043](https://github.com/deepseek-ai/deepseek-harness/discussions/1043) |
|
|
60
|
+
| S10 | `sourceEventSeqs` referencing non-earlier events | [#1469](https://github.com/deepseek-ai/deepseek-harness/discussions/1469) |
|
|
61
|
+
| S11 | whole-session scan: corrupt → quarantine suggestion; oversized / workspace estimated-heap (max(events×600B, bytes×6), default 1GiB, `DSH_DOCTOR_HEAP_MB`) → cold-start stall risk | [#1550](https://github.com/deepseek-ai/deepseek-harness/discussions/1550) |
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- The S-class checks replicate the harness's own validation (e.g. `SessionLogScanner`'s `seq == events.length` with `expandRow` chunk expansion), so offline verdicts match what boot/resume would do.
|
|
66
|
+
- `$DSH_HOME` is honored (default `~/.dsh`), so you can dry-run against a temp home without touching your real data.
|
|
67
|
+
- In-flight tool calls in the current active turn are reported as warnings, not errors, so scanning a live session never false-positives.
|
|
68
|
+
- Sibling implementation with the same scope: [boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) — the two tools cross-verified against the same broken fixtures.
|
|
69
|
+
|
|
70
|
+
## Related community tools
|
|
71
|
+
|
|
72
|
+
- [zoahdev/dsh-plugin-doctor](https://github.com/zoahdev/dsh-plugin-doctor) — pre-publish plugin bundle health checks (manifest/patch/entry/files/build/pack+fresh-profile install) plus a `profile-shadow` tripwire for host-shadowing (author/CI side). Complementary to this tool's user-side profile/session/env diagnostics; its `profile-shadow` and our P5 flag the same host-shadowing precondition from two sides.
|
|
73
|
+
- [boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) — sibling offline diagnostic, cross-verified against the same broken fixtures.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Self-update check (v0.2.1, Layer B)
|
|
78
|
+
|
|
79
|
+
The tool also watches its own npm version: each run compares the installed version against `dist-tags.latest` (same 6h TTL cache + offline fallback as the catalog). When a newer release exists it prints a notice and reports `update: { current, latest, available }` in JSON — it never touches your install without being asked.
|
|
80
|
+
|
|
81
|
+
- `--update` — apply the update now: runs `pnpm install` in the profile that hosts the plugin (or `DSH_DOCTOR_UPDATE_CMD` to override), then tells you to restart `dsh web`.
|
|
82
|
+
- `DSH_DOCTOR_AUTO_UPDATE=1` — apply updates automatically when one is available.
|
|
83
|
+
- Honest boundary: cordis loads plugins at boot, so the new engine only activates after a restart — Layer B replaces files and reminds you to restart, it doesn't hot-swap the running plugin.
|
|
84
|
+
- `--no-catalog` also disables the update check (pure offline mode).
|
|
85
|
+
|
|
86
|
+
## Remote check catalog (v0.2.0)
|
|
87
|
+
|
|
88
|
+
The built-in 20 checks are compiled into the tool. The **catalog** is a second, self-updating layer: `plugin/checks.json` in this repo holds declarative rules (data, not code), and every installed instance picks up new rules automatically — no reinstall needed.
|
|
89
|
+
|
|
90
|
+
- **How it works**: each run tries to fetch `plugin/checks.json` from GitHub (3s timeout) → on success it's cached to `$DSH_HOME/.cache/dsh-doctor/checks.json` (TTL 6h) → on failure it falls back to the last-known-good cache, then to the bundled copy. New checks therefore arrive within ≤6h of being committed upstream.
|
|
91
|
+
- **Safety**: rules are **read-only probes** executed by the built-in engine (`command-exists`, `path-*`, `json-valid`, `text-contains` / `text-not-contains`, `file-size-above`, `glob-count`). The remote payload can never run code — it can only add pattern checks.
|
|
92
|
+
- **Severity**: `error` (default, flips exit code) or `warn` (reported, exit code unaffected). Disable remote fetch with `--no-catalog`.
|
|
93
|
+
- **Adding a check** (that's the whole point — no plugin release needed): append an entry to `plugin/checks.json` and commit. Catalog checks shipped so far:
|
|
94
|
+
|
|
95
|
+
| ID | Probe | Checks | Discussion |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| E7 | `command-exists` | `dsh` on PATH | [#1270](https://github.com/deepseek-ai/deepseek-harness/discussions/1270) family |
|
|
98
|
+
| E8 | `text-contains` (warn) | `ignore-workspace-root-check=true` present in profile `.npmrc` | [dsh-market #20](https://github.com/dsh-market/dsh-market/issues/20) |
|
|
99
|
+
| E9 | `json-valid` | `config/workspace.json` parses | [#1357](https://github.com/deepseek-ai/deepseek-harness/discussions/1357) family |
|
|
100
|
+
| P6 | `text-not-contains` | patch insert `name:` with spaces (Windows spawn lint) | [#1420](https://github.com/deepseek-ai/deepseek-harness/discussions/1420) |
|
|
101
|
+
|
|
102
|
+
Catalog check results are marked `src: "catalog"` in JSON output and `[目录]` in CLI output.
|
|
103
|
+
|
|
104
|
+
## Also installable as a dsh plugin
|
|
105
|
+
|
|
106
|
+
The tool ships as a proper dsh bundle (`plugin/`), so you can run the same checks (20 built-in + catalog rules) from inside the web UI:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# install into a profile (works from a checkout or a published path)
|
|
110
|
+
dsh plugin --profile web add file:/path/to/dsh-doctor/plugin
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
What you get:
|
|
114
|
+
- **Settings → Doctor** panel: one click runs all checks and renders results grouped by env / profile / session, with per-check fixes and quarantine suggestions (suggestions are shown, never auto-executed);
|
|
115
|
+
- **HTTP API**: `GET /dsh-doctor/run` returns the same checks as JSON (optional `?profile=` / `?session=` to narrow scope).
|
|
116
|
+
|
|
117
|
+
Architecture: the plugin's server route shells out to the bundled `plugin/dsh-doctor.mjs --json` — the same single source of truth as the CLI (the checks are offline/filesystem-based by design, so they don't need harness internals). The repo-root `dsh-doctor.mjs` is a thin wrapper for `node dsh-doctor.mjs` compatibility.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# dsh-doctor
|
|
2
|
+
|
|
3
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的离线诊断工具——在**启动前**或**安装插件前**跑一次,它会告诉你社区反复报告的哪几类故障会在你机器上咬人。
|
|
4
|
+
|
|
5
|
+
零 npm 依赖。单文件。任何有 `node` 的环境都能跑(`zstd` 只在扫 `.zstd` 会话日志时需要,E1 会检查它)。
|
|
6
|
+
|
|
7
|
+
## 为什么
|
|
8
|
+
|
|
9
|
+
dsh 的插件树"装出来就是脆的":一个悬空引用、一个断掉的 `file:` 链接、一个重复的 entry id、一段损坏的会话日志,都能让 profile 在启动时直接挂掉或拖垮整个 web 服务器——而 `--dump-config` 从不挂载 loader,所以在坏配置上也"一切正常"。这类故障被汇总在 [dsh discussion #1496](https://github.com/deepseek-ai/deepseek-harness/discussions/1496)(Advisory:插件安装路径需要护栏)。`dsh-doctor` 就是这个护栏——**20 项内置检查**映射到 18 个社区报告,每一项都用合成负样例验证过;外加一个**自更新的远程检查目录**(声明式规则,v0.2.0 起)。
|
|
10
|
+
|
|
11
|
+
## 用法
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
node dsh-doctor.mjs # 全部检查(env + profile + session)
|
|
15
|
+
node dsh-doctor.mjs --profile web # 仅 profile 检查
|
|
16
|
+
node dsh-doctor.mjs --session <path> # 仅会话检查(默认自动找最新会话)
|
|
17
|
+
node dsh-doctor.mjs --env # 仅环境检查
|
|
18
|
+
node dsh-doctor.mjs --json # 机器可读输出
|
|
19
|
+
node dsh-doctor.mjs --no-catalog # 不拉远程目录(只用内置副本)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
退出码:`0` = 全部通过 · `1` = 发现问题(内置检查 + 目录中 `severity: error` 的项)· warn 级目录失败不改退出码。
|
|
23
|
+
|
|
24
|
+
## 检查项(20 内置 + 4 目录)
|
|
25
|
+
|
|
26
|
+
### env
|
|
27
|
+
| ID | 检查 | 对应讨论 |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| E1 | `node`/`pnpm`/`zstd` 在 PATH | [#1270](https://github.com/deepseek-ai/deepseek-harness/discussions/1270) |
|
|
30
|
+
| E2 | `.env` 是文件而非目录 | [#71](https://github.com/deepseek-ai/deepseek-harness/discussions/71) |
|
|
31
|
+
| E3 | node 版本 / `--expose-internals` 可及性 | [#113](https://github.com/deepseek-ai/deepseek-harness/discussions/113), [#1313](https://github.com/deepseek-ai/deepseek-harness/discussions/1313) |
|
|
32
|
+
| E4 | node-pty 原生二进制在位(`prebuilds/<platform>-<arch>/pty.node`) | [#1219](https://github.com/deepseek-ai/deepseek-harness/discussions/1219) |
|
|
33
|
+
| E5 | 存储 JSON 文件合法(严格 UTF-8 + 可解析) | [#1357](https://github.com/deepseek-ai/deepseek-harness/discussions/1357) |
|
|
34
|
+
| E6 | 锚点 tripwire:S6/S7/S10 依赖的契约仍在安装的 `dsh-session` 里 | [anti-rot idea](https://github.com/deepseek-ai/deepseek-harness/discussions/1534) |
|
|
35
|
+
| E10 | 启动前 Web 端口 3080 可用性(dsh web 自身占用=正常;其他进程=FAIL;`DSH_DOCTOR_PORT` 可覆盖) | [#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719) |
|
|
36
|
+
|
|
37
|
+
### profile
|
|
38
|
+
| ID | 检查 | 对应讨论 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| P2 | bundle 层与用户 patch 的 insert id 冲突(启动必崩) | [#1404](https://github.com/deepseek-ai/deepseek-harness/discussions/1404) |
|
|
41
|
+
| P3 | 用户 patch 的 insert `name:` 能从 profile 锚点解析 | [#1197](https://github.com/deepseek-ai/deepseek-harness/discussions/1197), [#880](https://github.com/deepseek-ai/deepseek-harness/discussions/880) |
|
|
42
|
+
| P4 | `file:` 依赖完整 | [#1197](https://github.com/deepseek-ai/deepseek-harness/discussions/1197) |
|
|
43
|
+
| P5 | 顶层无 `@deepseek-ai/*` 重复(双模块实例) | [#1486](https://github.com/deepseek-ai/deepseek-harness/discussions/1486), [#1697](https://github.com/deepseek-ai/deepseek-harness/discussions/1697) |
|
|
44
|
+
| P7 | `cordis.patch.yml` 结构 lint(`~ insert:` null 字面量、tab 缩进、缺冒号、顶层映射+序列混排 → UI 打不开) | [#1724](https://github.com/deepseek-ai/deepseek-harness/discussions/1724) |
|
|
45
|
+
|
|
46
|
+
### session
|
|
47
|
+
| ID | 检查 | 对应讨论 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| S1 | 孤儿 `tool_call`(无对应 tool result) | [#1363](https://github.com/deepseek-ai/deepseek-harness/discussions/1363), [#1544](https://github.com/deepseek-ai/deepseek-harness/discussions/1544) |
|
|
50
|
+
| S2 | 未闭合 turn(会话卡"运行中") | [#466](https://github.com/deepseek-ai/deepseek-harness/discussions/466), [#1265](https://github.com/deepseek-ai/deepseek-harness/discussions/1265) |
|
|
51
|
+
| S6 | `seq == index` 连续性(官方语义,chunk 行按 `expandRow` 展开) | [#1333](https://github.com/deepseek-ai/deepseek-harness/discussions/1333), [#1452](https://github.com/deepseek-ai/deepseek-harness/discussions/1452), [#1469](https://github.com/deepseek-ai/deepseek-harness/discussions/1469) |
|
|
52
|
+
| S7 | `end-seed` 后重放(重放已提交尾部) | [#1497](https://github.com/deepseek-ai/deepseek-harness/discussions/1497) |
|
|
53
|
+
| S8 | 未知事件类型且无 `ignorable`(整包拒绝) | [#1538](https://github.com/deepseek-ai/deepseek-harness/discussions/1538) |
|
|
54
|
+
| S9 | zstd 容器帧数(单帧日志 → `session.list` 整体 500) | [#1043](https://github.com/deepseek-ai/deepseek-harness/discussions/1043) |
|
|
55
|
+
| S10 | `sourceEventSeqs` 引用非更早事件 | [#1469](https://github.com/deepseek-ai/deepseek-harness/discussions/1469) |
|
|
56
|
+
| S11 | 全会话扫描:损坏 → 隔离建议;超大 / 工作区估算物化堆(max(事件×600B, 字节×6),默认 1GiB,`DSH_DOCTOR_HEAP_MB`)→ 冷启动卡顿风险 | [#1550](https://github.com/deepseek-ai/deepseek-harness/discussions/1550) |
|
|
57
|
+
|
|
58
|
+
## 备注
|
|
59
|
+
|
|
60
|
+
- S 类检查复刻了 harness 自身的校验(如 `SessionLogScanner` 的 `seq == events.length` + `expandRow` chunk 展开),所以离线结论与 boot/resume 实际行为一致。
|
|
61
|
+
- 尊重 `$DSH_HOME`(默认 `~/.dsh`),可以用临时 home 干跑,不碰真实数据。
|
|
62
|
+
- 当前活跃 turn 的尾部 in-flight 工具调用按警告而非错误处理,扫活会话不会误报。
|
|
63
|
+
- 同生态位兄弟实现:[boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) —— 两工具用同一批坏 fixture 交叉验证过。
|
|
64
|
+
|
|
65
|
+
## 相关社区工具
|
|
66
|
+
|
|
67
|
+
- [zoahdev/dsh-plugin-doctor](https://github.com/zoahdev/dsh-plugin-doctor) —— 发布前插件 bundle 健康检查(manifest/patch/entry/files/build/pack+全新 profile 安装)+ 宿主遮蔽 `profile-shadow` 哨兵(作者/CI 侧)。与本工具的用户侧 profile/session/env 诊断互补;它的 `profile-shadow` 与我们的 P5 从两个方向标记同一个宿主遮蔽前置条件。
|
|
68
|
+
- [boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) —— 同生态位离线诊断兄弟实现,用同一批坏 fixture 交叉验证。
|
|
69
|
+
|
|
70
|
+
## 自更新检查(v0.2.1,层 B)
|
|
71
|
+
|
|
72
|
+
工具也会盯着自己的 npm 版本:每次运行对比已装版本与 `dist-tags.latest`(与目录同样的 6h TTL 缓存 + 离线回退)。有新版时打印提示、JSON 里报 `update: { current, latest, available }`——**未经你要求绝不改动你的安装**。
|
|
73
|
+
|
|
74
|
+
- `--update` —— 立即执行更新:在宿主 profile 里跑 `pnpm install`(可用 `DSH_DOCTOR_UPDATE_CMD` 覆盖),然后提示重启 `dsh web`。
|
|
75
|
+
- `DSH_DOCTOR_AUTO_UPDATE=1` —— 有新版时自动更新。
|
|
76
|
+
- 诚实边界:cordis 启动时加载插件,新引擎要重启才生效——层 B 是"换文件 + 提醒重启",不做热替换。
|
|
77
|
+
- `--no-catalog` 同时禁用更新检查(纯离线模式)。
|
|
78
|
+
|
|
79
|
+
## 远程检查目录(v0.2.0,层 A)
|
|
80
|
+
|
|
81
|
+
内置 20 项检查编译在工具里。**目录**是第二层、自更新的:本仓库的 `plugin/checks.json` 放声明式规则(**规则是数据,不是代码**),所有已装实例自动获取新规则——无需重装。
|
|
82
|
+
|
|
83
|
+
- **工作机制**:每次运行尝试从 GitHub 拉 `plugin/checks.json`(3s 超时)→ 成功后缓存到 `$DSH_HOME/.cache/dsh-doctor/checks.json`(TTL 6h)→ 失败回退 last-known-good 缓存 → 再回退内置副本。新检查因此在上游提交后 ≤6h 内自动到达。
|
|
84
|
+
- **安全性**:规则是**只读探测原语**,由内置引擎执行(`command-exists`、`path-*`、`json-valid`、`text-contains` / `text-not-contains`、`file-size-above`、`glob-count`)。远程内容永远无法执行代码——只能新增模式检查。
|
|
85
|
+
- **严重级别**:`error`(默认,改退出码)或 `warn`(只报告,不影响退出码)。`--no-catalog` 关闭远程拉取。
|
|
86
|
+
- **加一条检查**(这就是重点——无需发版):往 `plugin/checks.json` 追加一条并提交即可。目前已随目录发布的检查:
|
|
87
|
+
|
|
88
|
+
| ID | 探测 | 检查 | 对应讨论 |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| E7 | `command-exists` | `dsh` 在 PATH | [#1270](https://github.com/deepseek-ai/deepseek-harness/discussions/1270) 家族 |
|
|
91
|
+
| E8 | `text-contains`(warn) | profile `.npmrc` 含 `ignore-workspace-root-check=true` | [dsh-market #20](https://github.com/dsh-market/dsh-market/issues/20) |
|
|
92
|
+
| E9 | `json-valid` | `config/workspace.json` 可解析 | [#1357](https://github.com/deepseek-ai/deepseek-harness/discussions/1357) 家族 |
|
|
93
|
+
| P6 | `text-not-contains` | patch insert `name:` 含空格(Windows spawn lint) | [#1420](https://github.com/deepseek-ai/deepseek-harness/discussions/1420) |
|
|
94
|
+
|
|
95
|
+
目录检查的结果在 JSON 输出中标 `src: "catalog"`,CLI 输出标 `[目录]`。
|
|
96
|
+
|
|
97
|
+
## 也可作为 dsh 插件安装
|
|
98
|
+
|
|
99
|
+
工具以标准 dsh bundle 形态发布(`plugin/`),可以在 web UI 里跑同样的检查(20 内置 + 目录规则):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# 装进 profile(checkout 或已发布路径均可)
|
|
103
|
+
dsh plugin --profile web add file:/path/to/dsh-doctor/plugin
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
装完你会得到:
|
|
107
|
+
|
|
108
|
+
- **设置 → 诊断**面板:一键跑全部检查,按 env / profile / session 分组渲染结果,带逐项修复建议与隔离建议(只展示建议,绝不自动执行);
|
|
109
|
+
- **HTTP API**:`GET /dsh-doctor/run` 返回同样的 JSON(可选 `?profile=` / `?session=` 收窄范围)。
|
|
110
|
+
|
|
111
|
+
架构:插件的服务端路由 shell 出 `plugin/dsh-doctor.mjs --json`——与 CLI 同一份真相源(检查按设计是离线/文件系统导向的,不需要 harness 内部接口)。仓库根目录的 `dsh-doctor.mjs` 是兼容 `node dsh-doctor.mjs` 的薄封装。
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
package/dsh-doctor.mjs
CHANGED
|
@@ -94,8 +94,12 @@ function report(section, id, ok, detail, fix, src) {
|
|
|
94
94
|
results.push({ section, id, ok, detail, fix, src: src ?? 'builtin' });
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** 解析 --profile 参数:名字(如 web)→ $DSH_HOME/profiles/<name>;含路径分隔符/~/开头 → 直接当 profile 目录(契约 harness 传绝对路径)。 */
|
|
97
98
|
function resolveProfile(name) {
|
|
98
|
-
if (!name
|
|
99
|
+
if (!name) throw new Error('无效 profile 名');
|
|
100
|
+
if (name.includes('/') || name.includes('\\') || name.startsWith('~') || name.startsWith('.')) {
|
|
101
|
+
return name.startsWith('~') ? join(homedir(), name.slice(1)) : name;
|
|
102
|
+
}
|
|
99
103
|
return join(HOME, 'profiles', name);
|
|
100
104
|
}
|
|
101
105
|
|
|
@@ -917,7 +921,23 @@ async function run() {
|
|
|
917
921
|
|
|
918
922
|
// 退出码只计内置失败 + catalog 中 severity=error 的失败;warn 失败提示但不改退出码
|
|
919
923
|
const bad = results.filter((r) => !r.ok && catalogSeverity.get(r.id) !== 'warn');
|
|
920
|
-
if (jsonOut) {
|
|
924
|
+
if (jsonOut && process.argv.includes('--envelope')) {
|
|
925
|
+
// v1 契约信封(dsh doctor 规格,zoahdev/doctor 对齐):status 小写 + 退出码 0/1/2
|
|
926
|
+
const st = (r) => (!r.ok ? (catalogSeverity.get(r.id) === 'warn' ? 'warn' : 'fail') : 'pass');
|
|
927
|
+
const summary = { pass: 0, warn: 0, fail: 0 };
|
|
928
|
+
const checks = results.map((r) => { summary[st(r)]++; return { name: r.id, status: st(r), detail: r.detail }; });
|
|
929
|
+
const exitCode = summary.fail > 0 ? 2 : summary.warn > 0 ? 1 : 0;
|
|
930
|
+
console.log(JSON.stringify({
|
|
931
|
+
schema: 'dsh-doctor/v1',
|
|
932
|
+
generatedAt: new Date().toISOString(),
|
|
933
|
+
profile: profileArg,
|
|
934
|
+
exitCode,
|
|
935
|
+
summary,
|
|
936
|
+
ok: exitCode === 0,
|
|
937
|
+
checks,
|
|
938
|
+
}, null, 2));
|
|
939
|
+
process.exit(exitCode);
|
|
940
|
+
} else if (jsonOut) {
|
|
921
941
|
console.log(JSON.stringify({ ok: bad.length === 0, checks: results, catalog: catalogMeta, update: updateInfo }, null, 2));
|
|
922
942
|
} else {
|
|
923
943
|
const sectionOrder = { env: 0, profile: 1, session: 2, catalog: 3 };
|
|
@@ -940,7 +960,7 @@ async function run() {
|
|
|
940
960
|
process.exit(bad.length === 0 ? 0 : 1);
|
|
941
961
|
}
|
|
942
962
|
|
|
943
|
-
// 直接执行(CLI
|
|
944
|
-
if (process.argv[1] && basename(process.argv[1])
|
|
963
|
+
// 直接执行(CLI:根目录薄封装、plugin 本体、npm bin 均可);被 import(测试/宿主)时不自动运行
|
|
964
|
+
if (process.argv[1] && /^dsh-doctor(\.mjs)?$/.test(basename(process.argv[1]))) run();
|
|
945
965
|
|
|
946
966
|
export { loadCatalog, bundledCatalog, validCatalog, expandPath, globCount, checkCatalog };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Offline diagnostic for DeepSeek Harness — 19 built-in checks + self-updating catalog (Layer A) + self-update check (Layer B); 'Doctor' panel in web UI settings.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"dsh-doctor.mjs",
|
|
10
10
|
"checks.json",
|
|
11
11
|
"cordis.patch.yml",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
"README.zh.md"
|
|
13
14
|
],
|
|
14
15
|
"peerDependencies": {
|
|
15
16
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
@@ -42,5 +43,8 @@
|
|
|
42
43
|
"type": "git",
|
|
43
44
|
"url": "git+https://github.com/moonquake2004/dsh-doctor.git"
|
|
44
45
|
},
|
|
45
|
-
"homepage": "https://github.com/moonquake2004/dsh-doctor"
|
|
46
|
+
"homepage": "https://github.com/moonquake2004/dsh-doctor",
|
|
47
|
+
"bin": {
|
|
48
|
+
"dsh-doctor": "./dsh-doctor.mjs"
|
|
49
|
+
}
|
|
46
50
|
}
|