@moonquake2004/dsh-doctor 0.5.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -16,6 +16,16 @@
16
16
 
17
17
  ---
18
18
 
19
+ ## [0.6.0] — 2026-09-15
20
+
21
+ ### Added
22
+
23
+ - **P22 profile manifest 带 UTF-8 BOM**(社区 #6758,**error**):DSH 直接 `JSON.parse` 该文件,带 BOM 时抛 `Unexpected token '…' is not valid JSON` 并**启动硬失败**;而 GBK 控制台会把 BOM 三个字节渲染成乱码,用户看不出是编码问题。判据零误报,补的正是"报错不指向病因"缺的那一句,并给出无 BOM 保存的具体做法(PowerShell 5.1 的 `Set-Content -Encoding UTF8` 默认会写 BOM)。
24
+
25
+ ### Fixed — 我们自己在同一输入上的错判
26
+
27
+ - 此前直接 `JSON.parse(readFileSync(profile/package.json))`:**带 BOM 时解析失败 → 置 null → P18 报"未找到 profile manifest"**,是**错误结论**;整个 profile 段更会直接报"检查异常"。现在所有 manifest 读取走**带 BOM 感知**的读取:剥离 BOM 后继续工作,`hadBom` 交给 P22 单独报出。**诊断工具不能被它要诊断的那份输入打败**——#6758 让我们发现了自己在同一个输入上的同类问题。
28
+
19
29
  ## [0.5.1] — 2026-09-15
20
30
 
21
31
  ### Fixed — 把"读取间歇性失败"错判成"文件损坏"(社区 #6739)
package/dsh-doctor.mjs CHANGED
@@ -549,7 +549,14 @@ function checkProfile(name) {
549
549
  try { dir = resolveProfile(name); } catch (e) { report('profile', 'P0', false, e.message); return; }
550
550
  const manifestPath = join(dir, 'package.json');
551
551
  if (!existsSync(manifestPath)) { report('profile', 'P0', false, `profile 不存在: ${dir}`); return; }
552
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
552
+ // 用**带 BOM 感知**的读取:带 BOM 的 manifest 会让 DSH 启动硬失败(#6758),
553
+ // 而诊断工具更不能被它要诊断的那份输入打败——剥离 BOM 后继续工作,并由 P22 单独报出病因。
554
+ const mainRead = readJsonReportingBom(manifestPath);
555
+ const manifest = mainRead.data;
556
+ if (manifest === null) {
557
+ report('profile', 'P0', false, `profile manifest 无法解析为 JSON${mainRead.hadBom ? '(且带 UTF-8 BOM —— 见 P22,这正是 DSH 启动硬失败的原因 #6758)' : ''}`);
558
+ return;
559
+ }
553
560
  const bundles = manifest.dsh?.profile?.bundles ?? [];
554
561
  const deps = manifest.dependencies ?? {};
555
562
 
@@ -678,7 +685,10 @@ function checkProfile(name) {
678
685
  // DeepSeek 请求以 REQUEST_EXTENSION 失败(#6667 报告的最小复现)。故这里按"条件性风险"报 warn,不报 fail。
679
686
  {
680
687
  let profManifest = null;
681
- try { profManifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); } catch { profManifest = null; }
688
+ let manifestHadBom = false;
689
+ const profRead = readJsonReportingBom(join(dir, 'package.json'));
690
+ profManifest = profRead.data;
691
+ manifestHadBom = profRead.hadBom;
682
692
  if (!profManifest || !(profManifest.dsh && profManifest.dsh.profile)) {
683
693
  reportSkip('profile', 'P18', '未找到 profile manifest(无 dsh.profile),跳过 version 检查');
684
694
  } else if (typeof profManifest.version === 'string' && profManifest.version.length > 0) {
@@ -794,6 +804,27 @@ function checkProfile(name) {
794
804
  }
795
805
  }
796
806
 
807
+ // P22:profile manifest 带 UTF-8 BOM(#6758)—— **硬失败且报错不指向病因**
808
+ // DSH 直接 `JSON.parse` 该文件:带 BOM 时抛 `Unexpected token '...' is not valid JSON` 并**起不来**;
809
+ // 而 GBK 控制台会把 BOM 三个字节渲染成乱码,用户完全看不出是编码问题。
810
+ // 这条判据零误报,且正是"报错不指向病因"要补的那一句:**是 BOM,不是 JSON 语法**。
811
+ {
812
+ const profRead22 = readJsonReportingBom(join(dir, 'package.json'));
813
+ if (!profRead22.exists) {
814
+ reportSkip('profile', 'P22', '无 profile manifest,跳过 BOM 检查');
815
+ } else if (profRead22.hadBom) {
816
+ report('profile', 'P22', false,
817
+ `profile manifest 带 **UTF-8 BOM**(EF BB BF)—— DSH 会直接 \`JSON.parse\` 该文件:`
818
+ + `BOM 会让它抛 \`SyntaxError: Unexpected token '…' is not valid JSON\`(#6758)并**启动硬失败**;`
819
+ + `而在 GBK 控制台上那三个字节会显示成乱码,报错里看不出是编码问题`,
820
+ '去掉 BOM(保留 UTF-8 无 BOM):PowerShell 5.1 的 `Set-Content -Encoding UTF8` 默认会写 BOM,改用 '
821
+ + '`[IO.File]::WriteAllText($p, (Get-Content $p -Raw), (New-Object Text.UTF8Encoding $false))`,'
822
+ + '或任何「UTF-8(无 BOM)」保存方式;本工具的其余检查已忽略 BOM 继续工作');
823
+ } else {
824
+ report('profile', 'P22', true, 'profile manifest 无 UTF-8 BOM(不会触发 #6758 的启动硬失败)');
825
+ }
826
+ }
827
+
797
828
  // P19:插件声明的 host peer 范围 vs 实际提供的 host 版本(社区 #6678 @ciceroyang 提案)
798
829
  // 这是"升级后起不来"的常见原因之一:插件声明只支持某段 core 版本,而实际装的核心已在区间外,
799
830
  // 安装时却没有任何警告(#6680 的机制:profile 的 pnpm 配置 autoInstallPeers:false 且 host 由
@@ -1979,7 +2010,8 @@ catalogSeverity.set('P16', 'warn');
1979
2010
  catalogSeverity.set('P17', 'warn');
1980
2011
  catalogSeverity.set('P18', 'warn');
1981
2012
  catalogSeverity.set('P19', 'warn');
1982
- catalogSeverity.set('P20', 'warn'); // #6693:client 格式问题在浏览器才炸,服务端零痕迹——高价值提示但不阻断 CI
2013
+ catalogSeverity.set('P20', 'warn');
2014
+ catalogSeverity.set('P22', 'error'); // #6758:BOM 会让启动硬失败,零误报 → 按 error // #6693:client 格式问题在浏览器才炸,服务端零痕迹——高价值提示但不阻断 CI
1983
2015
  // P21 不设 warn:它是在 apply() 内**同步抛出**、直接中断整棵装载链的致命类(#6693 实测 195+55 次),
1984
2016
  // 且我们两轮去误报(注释/字符串)后在真实 profile 的 8 个 host 入口 + 8 个 client 产物上零误报,故按 error 处理。 // #6678:声明不匹配是风险信号而非确定失败,提示但不阻断 // #6667:条件性风险(需游离本地模块才触发),提示但不翻退出码
1985
2017
 
@@ -2538,6 +2570,26 @@ function classifyReadAttempts(attemptLog) {
2538
2570
  return 'intermittent';
2539
2571
  }
2540
2572
 
2573
+
2574
+ /**
2575
+ * 读 JSON 并**报告是否带 UTF-8 BOM**。
2576
+ *
2577
+ * 社区 #6758:profile 清单只要带 BOM(PowerShell 5.1 的 `Set-Content -Encoding UTF8` 默认会写),
2578
+ * `JSON.parse` 就抛 `Unexpected token '...'`,DSH **硬失败起不来**;而 GBK 控制台会把 BOM 三个字节
2579
+ * 渲染成乱码,报错里完全看不出是编码问题——"报错不指向病因"。
2580
+ *
2581
+ * 我们自己也踩同一个坑:此前直接 `JSON.parse(readFileSync(...))` 失败后置 null,于是 P18 会报
2582
+ * "未找到 profile manifest"——**错误结论**。诊断工具在它要诊断的输入上给出错判,正是最该避免的。
2583
+ * 所以:**剥离 BOM 后再解析**(让其余检查继续工作),并把 hadBom 单独报出来(P22)。
2584
+ */
2585
+ function readJsonReportingBom(file) {
2586
+ let raw;
2587
+ try { raw = readFileSync(file); } catch { return { data: null, hadBom: false, exists: false }; }
2588
+ const hadBom = raw.length >= 3 && raw[0] === 0xEF && raw[1] === 0xBB && raw[2] === 0xBF;
2589
+ const text = hadBom ? raw.subarray(3).toString('utf8') : raw.toString('utf8');
2590
+ try { return { data: JSON.parse(text), hadBom, exists: true }; } catch { return { data: null, hadBom, exists: true }; }
2591
+ }
2592
+
2541
2593
  /* ---- 预检增强(2026-09):快照对比 + 安全安装 ---- */
2542
2594
 
2543
2595
  const SNAPSHOT_FILE = '.dsh-doctor-snapshot.json';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonquake2004/dsh-doctor",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
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": [