@moonquake2004/dsh-doctor 0.7.1 → 0.7.2
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 +42 -1
- package/dsh-doctor.mjs +46 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,44 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [0.7.2] — 2026-09-16
|
|
20
|
+
|
|
21
|
+
### 用新规则复查旧错误 —— **确认重复犯了同一个错**
|
|
22
|
+
|
|
23
|
+
按 `docs/check-authoring-rules.md` 的 R1–R9 重新审前面犯过错的案例(尤其审 0.7.0/0.7.1 这批新代码)。
|
|
24
|
+
结论:**是的,重复犯了,而且是"治症状"的典型**——我修了 #6788 那个**具体形态**,没修**这一类**。
|
|
25
|
+
|
|
26
|
+
**① 同一个缺陷出现在三处输出路径**(R3 覆盖律只在 `report()` 里,而这些路径不走它):
|
|
27
|
+
|
|
28
|
+
| 路径 | 零对象时的行为(修前) |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `--boot-check` | 打印"✓ 所有可探测 entry 均可导入",退出码 0 |
|
|
31
|
+
| `--safe-add` | `ok: true, stage: 'verified'` **并写入"已知良好"快照**——把"未验证"固化成"已验证" |
|
|
32
|
+
| `--post-upgrade` | 打印"✓ 装载模拟通过"并写快照 |
|
|
33
|
+
|
|
34
|
+
改为**唯一判定入口 `bootVerdict()`**(三处统一):零对象一律 `skip`,**不写快照、不宣称通过**。
|
|
35
|
+
按 R7 的问法——"如果它完全坏了,会打印什么?"——修前与健康时相同,即**不可证伪**;现在三态可区分。
|
|
36
|
+
|
|
37
|
+
**② 清单机制不够**(R1/R5):`docs/check-inventory.md` 曾**缺 8 个动态构造的 id**
|
|
38
|
+
(`E1-node`/`E1-pnpm`/`E1-zstd`/`E7-dsh-in-path`/`E8-npmrc-workspace-flag`/`E9-storages-json-valid`/
|
|
39
|
+
`E11-settings-writable`/`P6-patch-name-space`),即"查清单"可能查不到它们。现已补齐(**51 项、缺失 0**)。
|
|
40
|
+
**范围重叠仍无自动检测**——见下。
|
|
41
|
+
|
|
42
|
+
**③ 更正我自己的过度声称**(R8 的来历):0.7.1 的 release note 写"P22 与 P15 重复那件事**由此可被拦住**",
|
|
43
|
+
但该机制只强制一次可见 diff,**并不检测重叠**。已在原处标注更正。
|
|
44
|
+
|
|
45
|
+
**④ R4 隔离律远未落地**:复查 profile/env 段的读取,**14 处疑似未加保护**,抽查 3 处
|
|
46
|
+
(`readFileSync(patchFile)`、`JSON.parse(readFileSync(.../package.json))`、`readFileSync(patchPath)`)
|
|
47
|
+
**全部确认在 try 之外**。此前只修了 manifest 那一处。
|
|
48
|
+
|
|
49
|
+
**⑤ 规则集本身缺两条,已补**(这是复查最有价值的产出):
|
|
50
|
+
- **R8 断言强度律**:断言的强度不得超过证据(复现过 > 读过代码 > 推断;一个样本 ≠ 全体;**机制存在 ≠ 机制足够**)。
|
|
51
|
+
**这是唯一一条没有机制的规则**,已明确标注为待补。
|
|
52
|
+
- **R9 版本漂移律**:宿主收紧前置条件后,先假设 **fixture 失真**而不是代码坏了
|
|
53
|
+
(0.1.6-alpha.1 收紧 loader 后,我们多个 fixture 悄悄变成不合法输入,测试还绿着)。
|
|
54
|
+
|
|
55
|
+
测试 122 → 124。
|
|
56
|
+
|
|
19
57
|
## [0.7.1] — 2026-09-16
|
|
20
58
|
|
|
21
59
|
把"事后总结"变成**规则 + 机制**(`docs/check-authoring-rules.md`)。起因是用户指出:一次次的修复是头疼医头,
|
|
@@ -33,7 +71,10 @@
|
|
|
33
71
|
**落地当天它就抓出了我自己另外 5 处同类错误**——这一类从此可由机制消灭,不必靠小心。
|
|
34
72
|
- **R5 唯一归属律 / R1 来源律**:新增 `scripts/gen-check-inventory.mjs` 生成 `docs/check-inventory.md`
|
|
35
73
|
(43 项,含段、范围、来源),并加 CI 断言"清单与代码一致"。
|
|
36
|
-
于是"加检查前先查清单"不再是自觉,而是**一次可见的 diff
|
|
74
|
+
于是"加检查前先查清单"不再是自觉,而是**一次可见的 diff**。
|
|
75
|
+
⚠️ **更正(同日复查)**:我最初写的是"P22 与 P15 重复那件事**由此可被拦住**"——**这是过度声称**:
|
|
76
|
+
该机制只强制一次可见 diff,**并不检测范围重叠**;而且清单当时还**缺 8 个动态构造的 id**。
|
|
77
|
+
现已补齐清单(51 项、缺失 0),并把"机制存在 ≠ 机制足够"写成了规则 R8。
|
|
37
78
|
清单中**尚有 8 项未标注权威来源(R1 欠账)**,如实列出。
|
|
38
79
|
- **R3 收尾**:环境段补齐覆盖量(每个环境探针各检查一个对象),**全环境覆盖量欠账 18 → 0**;
|
|
39
80
|
CI 断言"空环境下未报告覆盖量的 pass 必须为 0"(此前只是打印数字)。
|
package/dsh-doctor.mjs
CHANGED
|
@@ -2712,8 +2712,13 @@ function safeAdd(profileArg, pkg) {
|
|
|
2712
2712
|
return { ok: false, stage: 'install', restored: true, exit: install.status };
|
|
2713
2713
|
}
|
|
2714
2714
|
const results = runBootCheckSync(profDir);
|
|
2715
|
-
const
|
|
2716
|
-
|
|
2715
|
+
const verdictAdd = bootVerdict(results);
|
|
2716
|
+
const failed = verdictAdd.failed;
|
|
2717
|
+
if (verdictAdd.state === 'skip') {
|
|
2718
|
+
// 零对象:**不写"已知良好"快照、不宣称已验证**——否则"未验证"会被固化成"已知良好"
|
|
2719
|
+
return { ok: true, stage: 'unchecked', checked: 0, failed: 0, reason: verdictAdd.reason };
|
|
2720
|
+
}
|
|
2721
|
+
if (verdictAdd.state === 'pass') {
|
|
2717
2722
|
writeSnapshot(profDir, bootSnapshot(profDir));
|
|
2718
2723
|
return { ok: true, stage: 'verified', checked: results.length, failed: 0 };
|
|
2719
2724
|
}
|
|
@@ -2733,6 +2738,25 @@ function safeAdd(profileArg, pkg) {
|
|
|
2733
2738
|
return { ok: true, stage: 'quarantined', quarantined, failures: failed.map((f) => `${f.bundle}/${f.id}: ${f.kind} ${f.error}`) };
|
|
2734
2739
|
}
|
|
2735
2740
|
|
|
2741
|
+
|
|
2742
|
+
/**
|
|
2743
|
+
* **装载模拟的唯一判定入口**(R3 覆盖律的中央落实)。
|
|
2744
|
+
*
|
|
2745
|
+
* 来历:同一个缺陷曾出现在**三处**输出路径(--boot-check / --safe-add / --post-upgrade)——
|
|
2746
|
+
* "零 entry 可探"被当成"通过",其中 --safe-add 还会写"已知良好"快照。
|
|
2747
|
+
* 我最初只修了 --boot-check 那一个实例(#6788 的具体形态),没修这一类。
|
|
2748
|
+
* 所以判定必须只在一个地方:任何"探测了 0 个对象"的运行都是 **skip**,不是 pass;
|
|
2749
|
+
* 也不得据此写快照(否则"未验证"会被固化成"已知良好")。
|
|
2750
|
+
*/
|
|
2751
|
+
function bootVerdict(results) {
|
|
2752
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
2753
|
+
if (failed.length) return { state: 'fail', failed, checked: results.length };
|
|
2754
|
+
if (results.length === 0) {
|
|
2755
|
+
return { state: 'skip', failed: [], checked: 0, reason: '启动列表里没有任何 entry 可探测,未做装载模拟(这不等于通过)' };
|
|
2756
|
+
}
|
|
2757
|
+
return { state: 'pass', failed: [], checked: results.length };
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2736
2760
|
/** 同步版装载模拟(--safe-add 内部用;与 --boot-check 同一逻辑) */
|
|
2737
2761
|
function runBootCheckSync(profileDir) {
|
|
2738
2762
|
const out = [];
|
|
@@ -2894,7 +2918,10 @@ async function run() {
|
|
|
2894
2918
|
}
|
|
2895
2919
|
console.log(`升级后复检(基线取自 ${String(base.at).slice(0, 16)}):`);
|
|
2896
2920
|
for (const l of lines) console.log(` · ${l}`);
|
|
2897
|
-
|
|
2921
|
+
const verdictPost = bootVerdict(results);
|
|
2922
|
+
if (verdictPost.state === 'skip') {
|
|
2923
|
+
console.log(` ⊖ ${verdictPost.reason}——**不改写已知良好基线**`);
|
|
2924
|
+
} else if (verdictPost.state === 'pass') {
|
|
2898
2925
|
console.log(' ✓ 装载模拟通过——升级未破坏任何可探测 entry');
|
|
2899
2926
|
writeSnapshot(profDir, nowSnap);
|
|
2900
2927
|
} else {
|
|
@@ -2968,7 +2995,22 @@ async function run() {
|
|
|
2968
2995
|
console.log(' ——若本次启动失败,上面这些就是首要嫌疑。');
|
|
2969
2996
|
}
|
|
2970
2997
|
}
|
|
2971
|
-
|
|
2998
|
+
// R3 覆盖律(2026-09 复查):**"零检查对象"不得等于"通过"**。
|
|
2999
|
+
// 这个输出路径不走 report(),所以 report() 里的不变量盖不到它——实测它曾对空启动列表打印
|
|
3000
|
+
// "✓ 所有可探测 entry 均可导入"。这正是 R7 要问的那句:"如果它完全坏了,会打印什么?"
|
|
3001
|
+
// 答案与健康时相同 ⇒ 不可证伪。故这里把"检查了 0 个对象"单列为 skip(不更新快照、不宣称通过)。
|
|
3002
|
+
const verdict = bootVerdict(results);
|
|
3003
|
+
const vacuous = verdict.state === 'skip';
|
|
3004
|
+
if (vacuous) {
|
|
3005
|
+
if (jsonOut) {
|
|
3006
|
+
console.log(JSON.stringify({ ok: true, skip: true, reason: '启动列表为空:没有任何 entry 可探测,未做装载模拟', profile: profDir, checked: 0, failed: 0, drift, results }, null, 2));
|
|
3007
|
+
} else {
|
|
3008
|
+
console.log(`装载模拟(${profDir}):⊖ 无可检查对象——启动列表为空,**未做任何装载模拟**(这不等于"通过")`);
|
|
3009
|
+
console.log(' 若你预期这里有插件:检查 profile 的 dsh.profile.bundles 是否为空,或插件是否装到了别的 profile。');
|
|
3010
|
+
}
|
|
3011
|
+
process.exit(0);
|
|
3012
|
+
}
|
|
3013
|
+
if (!failed.length) writeSnapshot(profDir, curSnap); // 只有**实际检查过且通过**时才更新"已知良好"快照
|
|
2972
3014
|
if (jsonOut) {
|
|
2973
3015
|
console.log(JSON.stringify({ ok: failed.length === 0, profile: profDir, checked: results.length, failed: failed.length, drift, results }, null, 2));
|
|
2974
3016
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
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": [
|