@moonquake2004/dsh-doctor 0.3.4 → 0.4.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 +7 -5
- package/README.zh.md +6 -4
- package/dsh-doctor.mjs +133 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Zero npm dependencies. One file. Runs anywhere `node` exists (`zstd` needed only
|
|
|
8
8
|
|
|
9
9
|
## Why
|
|
10
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 —
|
|
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 — 28 built-in checks (env/profile/session) mapped to 18 community reports, each verified with synthetic negative fixtures, plus a self-updating remote catalog of 5 declarative pattern checks.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -26,7 +26,7 @@ Exit codes (default mode): `0` = all pass · `1` = problems found (built-in chec
|
|
|
26
26
|
|
|
27
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
28
|
|
|
29
|
-
## Checks (
|
|
29
|
+
## Checks (28 built-in + 5 catalog = 33)
|
|
30
30
|
|
|
31
31
|
### env
|
|
32
32
|
| ID | Checks | Discussion |
|
|
@@ -47,7 +47,9 @@ With `--envelope` (doctor-contract mode): `0` = all pass · `1` = any WARN · `2
|
|
|
47
47
|
| P4 | `file:` dependencies intact | [#1197](https://github.com/deepseek-ai/deepseek-harness/discussions/1197) |
|
|
48
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
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
|
-
| P12 | profile-installed bundle version vs running CLI (warn on divergence; the web "Doctor" panel / `/dsh-doctor/run` API
|
|
50
|
+
| P12 | profile-installed bundle version vs running CLI (emits vocabulary name `installed_bundle`, #1719 v1.1: skip when unlisted / warn on manifest-lies or divergence / pass when equal; the web "Doctor" panel / `/dsh-doctor/run` API run the bundle) | [#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719) |
|
|
51
|
+
| P13 | client-half `provide` service name clashes with core client services (`chatFileMentions` etc. from `@deepseek-ai/dsh-client-*`, warn) or cross-bundle same-name grabs (browser-side "service already registered" → UI white screen, server logs see nothing) | [#2752](https://github.com/deepseek-ai/deepseek-harness/discussions/2752) |
|
|
52
|
+
| P14 | declared `bin` executability (target file present + shebang required for text `bin`; exec-bit alone does not identify the interpreter → ENOEXEC on direct run, #1846) | [#1846](https://github.com/deepseek-ai/deepseek-harness/discussions/1846) |
|
|
51
53
|
|
|
52
54
|
### session
|
|
53
55
|
| ID | Checks | Discussion |
|
|
@@ -70,7 +72,7 @@ With `--envelope` (doctor-contract mode): `0` = all pass · `1` = any WARN · `2
|
|
|
70
72
|
|
|
71
73
|
## Related community tools
|
|
72
74
|
|
|
73
|
-
> **dsh-doctor/v1 vocabulary r5 compatible** — drafted by [@ciceroyang](https://github.com/ciceroyang) (ciceroyang/dsh-doctor), reviewed by [@sjh9714](https://github.com/sjh9714) (dsh-win32) and [@moonquake2004](https://github.com/moonquake2004) ([#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719)). Our `node`/`pnpm` checks emit the vocabulary names with r5 semantics (pass/warn/fail/skip; `summary.skip` always present).
|
|
75
|
+
> **dsh-doctor/v1 vocabulary r5 compatible, v1.1 `installed_bundle` pending** — drafted by [@ciceroyang](https://github.com/ciceroyang) (ciceroyang/dsh-doctor), reviewed by [@sjh9714](https://github.com/sjh9714) (dsh-win32) and [@moonquake2004](https://github.com/moonquake2004) ([#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719)). Our `node`/`pnpm` checks emit the vocabulary names with r5 semantics (pass/warn/fail/skip; `summary.skip` always present); P12 emits the v1.1 vocabulary name `installed_bundle` (skip/warn/pass/warn four-state, r6 sheet pending).
|
|
74
76
|
|
|
75
77
|
- [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.
|
|
76
78
|
- [boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) — sibling offline diagnostic, cross-verified against the same broken fixtures.
|
|
@@ -141,7 +143,7 @@ Safety invariants: closed probe vocabulary (LLM output is data, never code), pro
|
|
|
141
143
|
|
|
142
144
|
## Also installable as a dsh plugin
|
|
143
145
|
|
|
144
|
-
The tool ships as a proper dsh bundle (`plugin/`), so you can run the same checks (
|
|
146
|
+
The tool ships as a proper dsh bundle (`plugin/`), so you can run the same checks (28 built-in + 5 catalog rules) from inside the web UI:
|
|
145
147
|
|
|
146
148
|
```bash
|
|
147
149
|
# install into a profile (works from a checkout or a published path)
|
package/README.zh.md
CHANGED
|
@@ -21,7 +21,7 @@ node dsh-doctor.mjs --no-catalog # 不拉远程目录(只用内置副
|
|
|
21
21
|
|
|
22
22
|
退出码:`0` = 全部通过 · `1` = 发现问题(内置检查 + 目录中 `severity: error` 的项)· warn 级目录失败不改退出码。
|
|
23
23
|
|
|
24
|
-
## 检查项(
|
|
24
|
+
## 检查项(28 内置 + 5 目录)
|
|
25
25
|
|
|
26
26
|
### env
|
|
27
27
|
| ID | 检查 | 对应讨论 |
|
|
@@ -42,7 +42,9 @@ node dsh-doctor.mjs --no-catalog # 不拉远程目录(只用内置副
|
|
|
42
42
|
| P4 | `file:` 依赖完整 | [#1197](https://github.com/deepseek-ai/deepseek-harness/discussions/1197) |
|
|
43
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
44
|
| P7 | `cordis.patch.yml` 结构 lint(`~ insert:` null 字面量、tab 缩进、缺冒号、顶层映射+序列混排 → UI 打不开) | [#1724](https://github.com/deepseek-ai/deepseek-harness/discussions/1724) |
|
|
45
|
-
| P12 | profile 内 bundle 版本 vs 运行 CLI
|
|
45
|
+
| P12 | profile 内 bundle 版本 vs 运行 CLI(发词汇名 `installed_bundle`,#1719 v1.1:未声明=skip / manifest 撒谎或分歧=warn / 一致=pass;web「诊断」面板 / `/dsh-doctor/run` API 跑的是 bundle) | [#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719) |
|
|
46
|
+
| P13 | client 半 `provide` 服务名抢注核心客户端服务(`chatFileMentions` 等 `@deepseek-ai/dsh-client-*`,warn)或跨 bundle 同名(浏览器端 service already registered → UI 白屏、服务端日志无感知) | [#2752](https://github.com/deepseek-ai/deepseek-harness/discussions/2752) |
|
|
47
|
+
| P14 | 声明 `bin` 可执行性(目标文件在位 + 文本 bin 必须带 shebang;仅可执行位不识别解释器 → 直接执行 ENOEXEC,#1846) | [#1846](https://github.com/deepseek-ai/deepseek-harness/discussions/1846) |
|
|
46
48
|
|
|
47
49
|
### session
|
|
48
50
|
| ID | 检查 | 对应讨论 |
|
|
@@ -65,7 +67,7 @@ node dsh-doctor.mjs --no-catalog # 不拉远程目录(只用内置副
|
|
|
65
67
|
|
|
66
68
|
## 相关社区工具
|
|
67
69
|
|
|
68
|
-
> **dsh-doctor/v1 词汇表 r5
|
|
70
|
+
> **dsh-doctor/v1 词汇表 r5 兼容,v1.1 `installed_bundle` 待认领** —— 起草 [@ciceroyang](https://github.com/ciceroyang)(ciceroyang/dsh-doctor),审阅 [@sjh9714](https://github.com/sjh9714)(dsh-win32)与 [@moonquake2004](https://github.com/moonquake2004)([#1719](https://github.com/deepseek-ai/deepseek-harness/discussions/1719))。我们的 `node`/`pnpm` 检查按 r5 语义输出词汇名(pass/warn/fail/skip;`summary.skip` 常驻);P12 直接发 v1.1 词汇名 `installed_bundle`(四态 skip/warn/pass/warn,r6 表待发)。
|
|
69
71
|
|
|
70
72
|
- [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 从两个方向标记同一个宿主遮蔽前置条件。
|
|
71
73
|
- [boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) —— 同生态位离线诊断兄弟实现,用同一批坏 fixture 交叉验证。
|
|
@@ -134,7 +136,7 @@ The `dsh-doctor/v1` envelope (`--json --envelope`) is the machine-readable form
|
|
|
134
136
|
|
|
135
137
|
## 也可作为 dsh 插件安装
|
|
136
138
|
|
|
137
|
-
工具以标准 dsh bundle 形态发布(`plugin/`),可以在 web UI 里跑同样的检查(
|
|
139
|
+
工具以标准 dsh bundle 形态发布(`plugin/`),可以在 web UI 里跑同样的检查(28 内置 + 5 目录规则):
|
|
138
140
|
|
|
139
141
|
```bash
|
|
140
142
|
# 装进 profile(checkout 或已发布路径均可)
|
package/dsh-doctor.mjs
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* P9 ctx.settings 未声明 inject: ['settings'](#1904⑤:先于 settings 就绪激活 → namespace not registered)
|
|
15
15
|
* P10 inject 引用客户端专属服务(#1947:@deepseek-ai/dsh-client-* 服务端永不提供 → Fiber 永久 PENDING → web boot 失败)
|
|
16
16
|
* P11 已装 bundle 的 main 入口产物缺失(#1965:市场装未构建源码树 → ERR_MODULE_NOT_FOUND → boot 崩)
|
|
17
|
-
*
|
|
17
|
+
* P13 client 端 provide 服务名抢注核心客户端服务 / 跨 bundle 同名(#2752:浏览器端 service already registered → UI 白屏,服务端日志无感知)
|
|
18
|
+
* P14 declared bin 可执行性(#1846:打包成功但 bin 缺 shebang/产物 → 直接执行 ENOEXEC;与 P11 互补)
|
|
19
|
+
* P12 `installed_bundle`(#1719 v1.1 词汇:profile 内 bundle 版本 vs 运行 CLI 版本——web 面板/API 跑的是 profile 里装的 bundle,可与独立 CLI 版本不一致)
|
|
18
20
|
* [session]
|
|
19
21
|
* S1 孤儿 tool_call(#1363:assistant tool_calls 无对应 tool 结果 → INVALID_REQUEST)
|
|
20
22
|
* S2 未闭合 turn(#466/#1265:turn/start 无 turn/end → 会话永久"运行中")
|
|
@@ -537,7 +539,126 @@ function checkProfile(name) {
|
|
|
537
539
|
if (entryIssues.length) report('profile', 'P11', false, `已装 bundle 的 main 入口缺失(#1965:市场装源码不跑构建 → ERR_MODULE_NOT_FOUND → dsh web boot 崩溃): ${entryIssues.join('; ')}`, '在插件目录跑构建(pnpm install && pnpm run build 产出 main 指向的文件),或改用打包好的 npm 包安装;monorepo 插件需装子包(dsh-market #18 同族)');
|
|
538
540
|
else report('profile', 'P11', true, '已装 bundle 的 main 入口产物均在', undefined);
|
|
539
541
|
|
|
540
|
-
//
|
|
542
|
+
// P13:client 端服务名抢注核心客户端服务(#2752:ctx.provide("chatFileMentions") 撞核心 dsh-client-ui-deliverables
|
|
543
|
+
// → 浏览器端 service already registered → Web UI 白屏,服务端日志无感知、报错无冲突来源)
|
|
544
|
+
// 与 P8(adapter provider 服务端冲突)互补:P8 跳过 client/web 产物,P13 专门只扫 client 侧——
|
|
545
|
+
// browser 端 provide 的服务名若与核心客户端服务(@deepseek-ai/dsh-client-*)重名,或两个 bundle 抢注同名,
|
|
546
|
+
// 都会在 client-modules 加载期崩掉整个 UI(fail to load plugins / service has been registered)。
|
|
547
|
+
// 核心名单来源:宿主 dsh 安装目录 + profile node_modules 里的 @deepseek-ai/dsh-client-* 包 client 产物实时收集,
|
|
548
|
+
// 叠加内置种子名单兜底(宿主不可达时仍能查 #2752 的 chatFileMentions 等已知核心服务)。
|
|
549
|
+
const coreClientServices = new Set([
|
|
550
|
+
// 内置种子(核心客户端服务,随 dsh 版本演进,宿主不可达时兜底)
|
|
551
|
+
'chatFileMentions', 'connection', 'sessions', 'workspaces', 'modules', 'locale',
|
|
552
|
+
]);
|
|
553
|
+
const collectProvideNames = (fp) => {
|
|
554
|
+
let src;
|
|
555
|
+
try { src = readFileSync(fp, 'utf8'); } catch { return []; }
|
|
556
|
+
const out = [];
|
|
557
|
+
for (const m of src.matchAll(/\.provide\(\s*['"]([^'"]+)['"]/g)) out.push(m[1]);
|
|
558
|
+
return out;
|
|
559
|
+
};
|
|
560
|
+
// client 产物位置:dsh.client 入口(package.json 的 dsh.client 指向的文件)+ client/ 目录下 js
|
|
561
|
+
const collectClientJsFiles = (root) => {
|
|
562
|
+
const out = [];
|
|
563
|
+
try {
|
|
564
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
565
|
+
const entry = pkg.dsh?.client;
|
|
566
|
+
if (typeof entry === 'string' && entry.endsWith('.js')) {
|
|
567
|
+
const ep = join(root, entry);
|
|
568
|
+
if (existsSync(ep)) out.push(ep);
|
|
569
|
+
} else if (entry && typeof entry === 'object' && typeof entry.entry === 'string' && entry.entry.endsWith('.js')) {
|
|
570
|
+
const ep = join(root, entry.entry);
|
|
571
|
+
if (existsSync(ep)) out.push(ep);
|
|
572
|
+
}
|
|
573
|
+
} catch { /* 无 manifest */ }
|
|
574
|
+
const walk = (dir, depth) => {
|
|
575
|
+
if (depth > 3) return;
|
|
576
|
+
let entries;
|
|
577
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
578
|
+
for (const e of entries) {
|
|
579
|
+
if (e.name.startsWith('.') || e.name === 'node_modules') continue;
|
|
580
|
+
const fp = join(dir, e.name);
|
|
581
|
+
if (e.isDirectory()) walk(fp, depth + 1);
|
|
582
|
+
else if (e.name.endsWith('.js')) out.push(fp);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const cdir = join(root, 'client');
|
|
586
|
+
if (existsSync(cdir)) walk(cdir, 0);
|
|
587
|
+
return [...new Set(out)];
|
|
588
|
+
};
|
|
589
|
+
// 核心客户端服务名单实时收集(宿主 anchor + profile node_modules)
|
|
590
|
+
if (installAnchor) {
|
|
591
|
+
const coreScope = join(installAnchor, '@deepseek-ai');
|
|
592
|
+
if (existsSync(coreScope)) {
|
|
593
|
+
for (const p of readdirSync(coreScope)) {
|
|
594
|
+
if (!/^dsh-client-/.test(p)) continue;
|
|
595
|
+
for (const f of collectClientJsFiles(join(coreScope, p))) {
|
|
596
|
+
for (const n of collectProvideNames(f)) coreClientServices.add(n);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const clientProvideMap = new Map(); // 服务名 → Set(bundle)
|
|
602
|
+
for (const [b, d] of bundleDirs) {
|
|
603
|
+
for (const f of collectClientJsFiles(d)) {
|
|
604
|
+
for (const n of collectProvideNames(f)) {
|
|
605
|
+
if (!clientProvideMap.has(n)) clientProvideMap.set(n, new Set());
|
|
606
|
+
clientProvideMap.get(n).add(b);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const coreHits = [...clientProvideMap].filter(([n]) => coreClientServices.has(n));
|
|
611
|
+
const dupHits = [...clientProvideMap].filter(([, v]) => v.size > 1);
|
|
612
|
+
const p13Issues = [];
|
|
613
|
+
for (const [n, bs] of coreHits) {
|
|
614
|
+
p13Issues.push(`服务名 ${n} ∈ 核心客户端服务(${[...bs].join(', ')} 抢注 → 浏览器端 service already registered,UI 白屏 #2752)`);
|
|
615
|
+
}
|
|
616
|
+
for (const [n, bs] of dupHits) {
|
|
617
|
+
if (!coreClientServices.has(n)) p13Issues.push(`服务名 ${n} 被多个插件 client 同时提供(${[...bs].join(', ')} → 同名注册冲突,加载期崩)`);
|
|
618
|
+
}
|
|
619
|
+
if (p13Issues.length) {
|
|
620
|
+
report('profile', 'P13', false, `client 端服务名冲突(#2752:浏览器端 provide 撞核心服务 → UI 白屏且服务端日志无感知): ${p13Issues.join('; ')}`, '改名自有 client 服务(避开核心 dsh-client-* 已注册名),或让冲突双方协商唯一命名;冲突在应用侧降级为局部警告前仍需避名');
|
|
621
|
+
} else {
|
|
622
|
+
report('profile', 'P13', true, 'client 端 provide 服务名无冲突(未撞核心客户端服务、无跨 bundle 同名抢注)', undefined);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// P14:declared bin 可执行性(#1846 1052326311 贡献检查点②:dsh-instruction-audit v0.1.0 打包成功但 bin 缺 shebang
|
|
626
|
+
// → 直接执行 ENOEXEC;安装/注册/schema 全过但 pnpm dlx 跑不起来)。与 P11(main 产物缺失)互补:
|
|
627
|
+
// P11 查运行时入口,P14 查 CLI 入口。
|
|
628
|
+
// 判定(2026-08-17 1052326311 实证修正,见 #1846 comment 18056208):文本 bin 必须带 shebang——
|
|
629
|
+
// POSIX 上 executable bit 只授予执行权限,不标识文本文件的解释器;仅 exec bit 无 shebang,os.execve
|
|
630
|
+
// 仍返回 ENOEXEC(errno 8)。故"shebang OR exec-bit"会误放行坏包(good/bad fixture 均 100755,只差 shebang)。
|
|
631
|
+
// 离线静态改查"存在 + shebang"两个必要条件(bin 均为 JS 文本,shebang 是解释器声明的唯一可靠来源)。
|
|
632
|
+
const binIssues = [];
|
|
633
|
+
for (const [b, d] of bundleDirs) {
|
|
634
|
+
let pkg;
|
|
635
|
+
try { pkg = JSON.parse(readFileSync(join(d, 'package.json'), 'utf8')); } catch { continue; }
|
|
636
|
+
const bin = pkg.bin;
|
|
637
|
+
if (!bin) continue;
|
|
638
|
+
const bins = typeof bin === 'string' ? { [b.split('/').pop()]: bin } : bin;
|
|
639
|
+
for (const [binName, rel] of Object.entries(bins)) {
|
|
640
|
+
if (typeof rel !== 'string') continue;
|
|
641
|
+
const fp = join(d, rel);
|
|
642
|
+
if (!existsSync(fp)) {
|
|
643
|
+
binIssues.push(`${b}: bin 声明 ${binName} → ${rel} 但产物缺失(发布后 pnpm dlx/直接执行会失败)`);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
let head;
|
|
647
|
+
try { head = readFileSync(fp, 'utf8').slice(0, 2); } catch { head = ''; }
|
|
648
|
+
const shebang = head === '#!';
|
|
649
|
+
// execBit 仅作兜底提示(文本文件解释器识别靠 shebang),不作为通过条件
|
|
650
|
+
if (!shebang) {
|
|
651
|
+
binIssues.push(`${b}: bin ${binName}(${rel})无 shebang——文本 bin 无解释器声明,直接执行 ENOEXEC(#1846 同型,exec bit 不识别解释器)`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (binIssues.length) {
|
|
656
|
+
report('profile', 'P14', false, `declared bin 不可执行(#1846:安装/注册全过但 bin 跑不起来): ${binIssues.join('; ')}`, '给 bin 入口补 `#!/usr/bin/env node`(或 chmod +x);发布前用打包产物实测 `pnpm dlx <pkg>` / 直接执行一次(dsh-testkit 可代为跑真实宿主)');
|
|
657
|
+
} else {
|
|
658
|
+
report('profile', 'P14', true, 'declared bin 均在(存在 + shebang/可执行位)', undefined);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// P12 `installed_bundle`(#1719 v1.1 词汇条目):profile 内 bundle 版本 vs 运行 CLI 版本
|
|
541
662
|
// web 设置「诊断」面板与 /dsh-doctor/run API 跑的是 profile 里装的 bundle;独立 CLI(checkout/npx)是另一个副本——
|
|
542
663
|
// Layer-B 自更新只比 npm latest vs 运行模块,profile 内 bundle 落后/超前都不报警(dsh-win32/bundle 同坑,sjh9714 先发现的)。
|
|
543
664
|
// 语义(#1719 合稿,sjh9714 四态分析 + skip 修正):pass/warn/skip 三态 + detail 注明条件——
|
|
@@ -545,6 +666,8 @@ function checkProfile(name) {
|
|
|
545
666
|
// manifest 声明但 node_modules 缺失 = warn(manifest 撒谎,运行时从不加载);
|
|
546
667
|
// 已装且版本一致 = pass;已装但版本分歧 = warn(detail 含 age-gate 提示,升级可能被 pnpm-workspace.yaml 的
|
|
547
668
|
// minimumReleaseAgeExclude 年龄门暂缓一天,指令不再静默无效——sjh9714 实测)。
|
|
669
|
+
// r6/v1.1 认领后:信封检查名从厂商本地 id `P12-bundle-version` 改为词汇名 `installed_bundle`(#1719 三家对齐,
|
|
670
|
+
// sjh9714 同步改名 `dsh-win32/bundle`→`installed_bundle`,CI 可跨实现断言)。
|
|
548
671
|
try {
|
|
549
672
|
const selfName = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).name ?? '@moonquake2004/dsh-doctor';
|
|
550
673
|
const listed = Object.keys(deps).some((k) => k === selfName || k === 'dsh-doctor');
|
|
@@ -555,19 +678,19 @@ function checkProfile(name) {
|
|
|
555
678
|
if (existsSync(p)) { bundlePkg = p; break; }
|
|
556
679
|
}
|
|
557
680
|
if (!listed && !bundlePkg) {
|
|
558
|
-
reportSkip('profile', '
|
|
681
|
+
reportSkip('profile', 'installed_bundle', 'profile 未声明也未安装 dsh-doctor bundle——无对比对象(CLI 独立运行),skip 而非 pass(#1719 installed_bundle 合稿,sjh9714:pass 会让 CI 误判"已同步")');
|
|
559
682
|
} else if (listed && !bundlePkg) {
|
|
560
|
-
report('profile', '
|
|
683
|
+
report('profile', 'installed_bundle', false, `profile 的 package.json 声明了 ${selfName} 依赖,但 node_modules 里没有对应包(manifest 与运行时不一致,web 面板/API 实际加载不到)`, `dsh plugin --profile ${name} install ${selfName}(或先移除该依赖再重装)`);
|
|
561
684
|
} else {
|
|
562
685
|
const bundleVersion = JSON.parse(readFileSync(bundlePkg, 'utf8')).version;
|
|
563
686
|
const cliVersion = localVersion();
|
|
564
687
|
const same = bundleVersion === cliVersion;
|
|
565
|
-
report('profile', '
|
|
688
|
+
report('profile', 'installed_bundle', same,
|
|
566
689
|
same ? `profile 内 bundle 版本 ${bundleVersion} 与运行 CLI ${cliVersion} 一致` : `profile 内 bundle 版本 ${bundleVersion} ≠ 运行 CLI ${cliVersion}(web 面板/API 跑的是 bundle,两边行为可能不一致;若刚发布过新版本,升级可能被 pnpm-workspace.yaml 的 minimumReleaseAgeExclude 年龄门暂缓,可次日重试)`,
|
|
567
690
|
same ? undefined : `同步安装版本:dsh plugin --profile ${name} update ${selfName}(或让 CLI 与 bundle 走同一安装方式)`);
|
|
568
691
|
}
|
|
569
692
|
} catch (e) {
|
|
570
|
-
report('profile', '
|
|
693
|
+
report('profile', 'installed_bundle', false, `bundle 版本对比异常: ${e.message.slice(0, 60)}`, undefined);
|
|
571
694
|
}
|
|
572
695
|
}
|
|
573
696
|
|
|
@@ -801,10 +924,12 @@ function scanAllSessions() {
|
|
|
801
924
|
const REMOTE_CATALOG_URL = 'https://raw.githubusercontent.com/moonquake2004/dsh-doctor/main/plugin/checks.json';
|
|
802
925
|
const CATALOG_TTL_MS = 6 * 60 * 60 * 1000; // 6h:新检查最长 6h 内自动生效
|
|
803
926
|
const catalogSeverity = new Map(); // catalog 检查 id → severity('error' | 'warn')
|
|
804
|
-
// v1 词汇表 r5 对齐(#1719):E1-pnpm 缺失=warn(corepack 可恢复)、E3-node 越界=warn(EBADENGINE 语义)、P12
|
|
927
|
+
// v1 词汇表 r5 对齐(#1719):E1-pnpm 缺失=warn(corepack 可恢复)、E3-node 越界=warn(EBADENGINE 语义)、installed_bundle(P12)分歧=warn(v1.1 词汇条目)、P13 client 服务名冲突=warn(#2752:按帖子建议降级为局部警告而非白屏)、P14 bin 不可执行=warn(#1846:发布卫生问题,不影响已有 boot 但对新用户 pnpm dlx 失败)——均不翻退出码
|
|
805
928
|
catalogSeverity.set('E1-pnpm', 'warn');
|
|
806
929
|
catalogSeverity.set('E3-node', 'warn');
|
|
807
|
-
catalogSeverity.set('
|
|
930
|
+
catalogSeverity.set('installed_bundle', 'warn');
|
|
931
|
+
catalogSeverity.set('P13', 'warn');
|
|
932
|
+
catalogSeverity.set('P14', 'warn');
|
|
808
933
|
|
|
809
934
|
function bundledCatalog() {
|
|
810
935
|
const p = new URL('./checks.json', import.meta.url);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Offline diagnostic for DeepSeek Harness —
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Offline diagnostic for DeepSeek Harness — 28 built-in + 5 catalog checks across env/profile/session (Layer A checks-as-data), self-update (Layer B), and a semi-automatic LLM observer (Layer C, --observe); Doctor panel in web UI settings.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"README.md",
|