@moonquake2004/dsh-doctor 0.4.7 → 0.4.9

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.
Files changed (2) hide show
  1. package/dsh-doctor.mjs +43 -5
  2. package/package.json +1 -1
package/dsh-doctor.mjs CHANGED
@@ -493,6 +493,20 @@ function checkProfile(name) {
493
493
  }
494
494
  // P3 insert name 可解析性
495
495
  const req = (() => { try { return createRequire(join(dir, '_anchor.js')); } catch { return null; } })();
496
+ // 判据(#1719 taltara 的正确性陷阱,2026-09-12 实测命中我们):**不能以 require.resolve 为准** ——
497
+ // ESM-only 包(exports 只声明 import 条件)会让 require.resolve 抛 ERR_PACKAGE_PATH_NOT_EXPORTED,
498
+ // 而 loader 能正常 import;用它判定会把健康 profile 报成 FAIL(比没有 doctor 更糟)。
499
+ // 改为**存在性判据**:node_modules/<name>/package.json 存在即可(scoped 取两段),
500
+ // 并向上找一层以覆盖 profile 根安装(~/.dsh/profiles/node_modules)。require.resolve 仅作正向加分。
501
+ const nmRoots = [join(dir, 'node_modules'), join(dirname(dir), 'node_modules')];
502
+ const pkgPresent = (n) => {
503
+ if (n.startsWith('cordis:')) return true; // 宿主内置
504
+ if (n.startsWith('.') || n.startsWith('/') || n.startsWith('~')) return true; // 相对/绝对路径
505
+ const seg = String(n).split('/');
506
+ const pkgName = n.startsWith('@') ? seg.slice(0, 2).join('/') : seg[0]; // scoped 需两段
507
+ if (!pkgName) return true;
508
+ return nmRoots.some((root) => existsSync(join(root, pkgName, 'package.json')));
509
+ };
496
510
  const bad = [];
497
511
  for (const n of userNames) {
498
512
  if (n.startsWith('@local/') || n.startsWith('@liustack/')) {
@@ -503,9 +517,9 @@ function checkProfile(name) {
503
517
  continue;
504
518
  }
505
519
  }
506
- let ok = false;
507
- try { if (req) { req.resolve(n); ok = true; } } catch { ok = false; }
508
- if (!ok) bad.push(n);
520
+ let resolved = false;
521
+ try { if (req) { req.resolve(n); resolved = true; } } catch { resolved = false; }
522
+ if (!resolved && !pkgPresent(n)) bad.push(n);
509
523
  }
510
524
  if (bad.length) report('profile', 'P3', false, `用户 patch 中不可解析的 name(#1197/#880): ${bad.join(', ')}`, `dsh plugin --profile ${name} add <包> 或修复 file: 依赖`);
511
525
  else report('profile', 'P3', true, '用户 patch insert 均可解析', undefined);
@@ -1903,10 +1917,34 @@ async function run() {
1903
1917
  // 获取最新会话文件(供 SR*/SS* 检查使用)
1904
1918
  // 世代感知定位(与 S11 同一规则):三层 sessions/<user>/<session>/session*.jsonl*,
1905
1919
  // 目录内取最高世代(v0/v3 并存时取 v3),跨目录按 mtime 取最新,忽略 session.lock
1920
+ // 运行时检查(SR*/SS*)扫"最新会话",而最新会话通常**就是当前正在写入的会话** ——
1921
+ // 那会把操作者自己的操作报成安全发现(2026-09 实测:SR1 报的 critical 全是本次会话里
1922
+ // 自己写的 `curl|bash` 测试串,SR2 的 `doas` 来自自己引用的规则文本)。
1923
+ // 故默认跳过"仍在写入"的会话:取 mtime 早于活跃窗口的最新会话;
1924
+ // 窗口可用 DSH_DOCTOR_LIVE_WINDOW_MS 调整(默认 120s,设 0 关闭该保护)。
1925
+ const LIVE_WINDOW_MS = (() => {
1926
+ const v = Number(process.env.DSH_DOCTOR_LIVE_WINDOW_MS);
1927
+ return Number.isFinite(v) && v >= 0 ? v : 120000;
1928
+ })();
1906
1929
  const findLatestSession = () => {
1907
- if (sessionArg) return sessionArg;
1908
- try { return latestSessionLog(); } catch { return null; }
1930
+ if (sessionArg) return sessionArg; // 显式指定则始终尊重
1931
+ try {
1932
+ const root = join(HOME, 'sessions');
1933
+ const all = listSessionLogs(root); // 已按 mtime 降序
1934
+ if (!all.length) return null;
1935
+ if (LIVE_WINDOW_MS === 0) return all[0].f;
1936
+ const cutoff = Date.now() - LIVE_WINDOW_MS;
1937
+ const settled = all.find((x) => (x.m ?? 0) < cutoff);
1938
+ return (settled ?? all[0]).f;
1939
+ } catch { return null; }
1909
1940
  };
1941
+ const sessionIsLive = (() => {
1942
+ if (sessionArg || LIVE_WINDOW_MS === 0) return false;
1943
+ try {
1944
+ const all = listSessionLogs(join(HOME, 'sessions'));
1945
+ return all.length > 0 && (all[0].m ?? 0) >= Date.now() - LIVE_WINDOW_MS;
1946
+ } catch { return false; }
1947
+ })();
1910
1948
 
1911
1949
  const { results: secResults, exitCode: secExit, summary: secSummary } = await registry.runAll(
1912
1950
  (check) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonquake2004/dsh-doctor",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
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": [