dsh-m 0.9.29 → 0.9.31

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
@@ -8,12 +8,28 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.31 变更:doctor 支持 desktop profile(CLI 例外开口 + farmChecked 语义修订,ADR-0011)
12
+
13
+ - **动机**:Windows desktop 机实机报告证实 core 引擎本就 profileDir 参数化无 web 硬编码(desktop 物化布局判 hoisted、farm=0 属常态),但 CLI 全命令一刀切拒绝 `--profile desktop` 把只读体检连坐;desktop-only 机器无 flag 体检还会扫不存在的 web 目录得全 0 报告。
14
+ - **CLI 例外(仅 doctor)**:`dshm doctor --profile desktop` 允许并路由 `desktopProfileDir()`,输出首行加 `[web|desktop]` 标注;HELP 同步;**其余命令对 `--profile desktop` 的拒绝语义逐字不变**(回归钉子测试钉住),desktop 变更管理仍走官方 Desktop 插件页。
15
+ - **空目录提示**:目标 profile 目录不存在时提示「desktop-only 机器请加 --profile desktop」——判据为目录不存在(存在但为空的合法 profile 不误伤)。
16
+ - **宿主 method 零改动**:doctor case 本就 active-profile 无关,desktop 宿主免改生效(新增 desktop-kind 注入钉子测试);MCP 工具 `dshm_doctor` 维持 ADR-0010 决定 3 缓上;Windows Electron 宿主 runtimeVersion 可能仍降级(物化布局 farm=0,stale 无判定对象,实际影响为零,如实记录)。
17
+ - **farmChecked 语义修订**:「0=遍历空转」判定仅适用于存在符号链农场的形态;物化布局 0 为常态(ADR-0011)。
18
+ - **验证**:新增 8 例(desktop 路由/标注、他命令拒绝、非法值、空提示、正常不提示、显式 desktop 不出矛盾文案、显式 web 路由、method 钉子)+ 改写 1 例旧拒绝样本(净增 8,全量 1079→1087);全量 1087 pass / 0 fail / 0 skipped;typecheck 零错误。Windows 外机验收清单见实施计划(本机无法执行 Windows E2E)。
19
+
20
+ ### 0.9.30 变更:修复 bin 符号链接静默 no-op(F2)+ 版本解析 realpath 第三源(F1)
21
+
22
+ - **根因同源**:pnpm 生态里两类关键入口都是**符号链接**——`node_modules/.bin/dshm`(指向 lib/cli.js)与全局 shim `PNPM_HOME/dsh`(指向真实 bin.js)——而两处代码都在 realpath 之前做了路径身份判定。
23
+ - **F2(自 v0.2.0 bin 入口引入以来潜在,本机 0.9.28 装机形态首次踩中并实证;🔴 bin 入口全命令静默 exit 0)**:cli.ts `invokedDirectly` 比较 `argv[1]`(链接路径)与 `import.meta.url`(node realpath 后的真实路径)永假,`dshm <任何命令>` 无输出直接退出。修复:比较前对 `argv[1]` 同样 realpath。新增 `tests/cli-bin-symlink.test.mjs`(经符号链接调用 --help 与 doctor --json 的复活验证,EPERM 环境照仓内先例 skip)。
24
+ - **F1(0.9.29 装机验收实证)**:dsh-version.ts `readLauncherPackageVersion` 从 shim 位置直接向上三级找 package.json,落在 pnpm home 目录树上空走——宿主内纯 FS 版本解析在本机拓扑(`node PNPM_HOME/dsh web`)不可得,doctor 的 stale 判定降级、ping chip 被迫吃 spawn 回退。修复:判定 entry 形态后先 realpath 再上溯(本机实测解析得 0.2.0-rc.2)。新增 dsh-version.test.mjs shim 形态用例。
25
+ - **验证**:专项 9/9(含两新用例);全量 1079 pass / 0 fail / 0 skipped;typecheck 零错误。装机后预期:`dshm doctor`(bin 入口)可用;宿主 method 通路 runtimeVersion=0.2.0-rc.2、stale 判定复活(装机时农场 dsh 链接 0.1.7-rc.2 为现成 stale 形态,heal 后归零)。**装机实测(2026-10-04)**:预期逐项兑现——`.bin/dshm doctor` 经符号链接复活出报告;宿主 method `runtimeVersion=0.2.0-rc.2 / stale=1(dsh→0.1.7-rc.2 现场点名)`;同日按 know-how 014 heal 该链接并清理 20 项残留后,双通路终验 `farm 236 / dangling 0 / stale 0 / residue 0 / errors 0`。
26
+
11
27
  ### 0.9.29 变更:profile 体检 Doctor——`dshm doctor` 上线(只读:农场测活 / 残留物清点 / 账实一致)
12
28
 
13
29
  - **动机**:dsh-m 至今没有诊断能力——know-how 014 的「每次 DSH 升级后重跑农场测活」是唯一现役周期必查项(曾 81 条悬空、230 行手工映射留档),know-how 023 实录「账实分裂」形态;本机实扫另发现 8 个空 scope 目录 + 12 个 `*.bak-*` 累积。学 dsh-market check.ts 的设计纪律(纯 FS 边界、三级严重度、unknown≠broken、误报记账、修复责任外移)落地 Day1 子集,选点裁决与对比详见 ADR-0010 与对比报告(2026-10-04)。
14
30
  - **新增**:`src/core/doctor.ts` 纯函数核心(无进程/无网络/无写入,任意时刻可安全调用)+ `/dshm` 新 method `doctor` + CLI `dshm doctor [--json]`(error 级发现 exit 1;HELP 同步)。三项检查:**农场测活**(`@deepseek-ai/*` 符号链祖先链遍历;悬空=error;dsh 伞包指向旧运行时 store=提示级——lockstep 店内非伞包版本不与 runtimeVersion 比较,防 cordis 等误报)、**残留物清点**(空 scope / 无 manifest 目录 / pnpm `*_tmp_*` / `*.bak-*`,全部零告警清单,「可见而非清理」)、**账实一致**(pin/实装/lock 三处核对,不一致=warning;lockfile 仅认 9.0 importers 形状,其余 unknown 不猜)。
15
31
  - **边界纪律(ADR-0010)**:runtimeVersion 仅用 `readLauncherPackageVersion` 纯 FS 通路(CLI 进程下为 null → stale 判定整体降级 unknown,绝不 spawn);密钥红线只禁含密钥**配置文件**内容(包元数据 version 字段可读);布局判定 workspace 声明优先(本机「hoisted 声明 + 仅 lock.yaml 的残留 .pnpm」并存形态实证);双市场并存(dsh-m+dshmarket 同装)信息级呈现;doctor 永不修复,建议以文字给出。
16
- - **验收锚点(发版装机后核对)**:本机首跑 farmChecked ≈236(=0 即遍历空转)、悬空 0、8 空 scope + 12 bak 入清单、双市场信息级出现。
32
+ - **验收结算(2026-10-04 装机实测)**:farmChecked=236(精确命中评审实测值)/ 悬空 0 / 8 空 scope + 12 bak 入清单 / 账实 13:0 / 双市场信息级 / errors=0 → exit=0 / `--json` 可解析。宿主 method 信封正确但 runtimeVersion=null——**发版日新发现**:本机宿主经 pnpm 全局 shim 启动(`node ~/.local/share/pnpm/dsh web`),`readLauncherPackageVersion` 从 shim 三级上溯落空,stale 判定双通路降级(底层事实人工核对成立:农场 dsh 链接 0.1.7-rc.2 vs 运行时 0.2.0-rc.2;版本第三源列下一批)。另录既有 bug:`.bin/dshm` 符号链接静默 no-op(后经考古为 v0.2.0 起潜在、本机 0.9.28 装机形态首次踩中,见 0.9.30 条目——`invokedDirectly` 比较 symlink 路径与 realpath 永假,bin 入口全命令静默 exit 0;修复列 0.9.30 候选,期间用 `node …/dsh-m/lib/cli.js` 直达)。
17
33
  - **验证**:新增 `tests/doctor.test.mjs`(36 例:布局冲突并存/祖先链反空转/两级 targetVersion/降级路径/零告警清单/023 形态/link 协议/lock peer 后缀/CLI 子进程)+ `tests/doctor-api.test.mjs`(2 例:method 信封真跑 + 空 profile);全量 1076 pass / 0 fail / 0 skipped。
18
34
 
19
35
  ### 0.9.28 变更:页大小档位去上游化——32/64/96 取代 24/48/96,默认 32
@@ -291,6 +307,22 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
291
307
 
292
308
  ## English
293
309
 
310
+ ### Added in 0.9.31 — doctor desktop profile support (CLI exception + farmChecked semantics revision, ADR-0011)
311
+
312
+ - **Motivation**: a Windows desktop machine's report confirmed the core engine is profileDir-parameterized with no web hardcoding (materialized desktop layout detects as hoisted with farm=0 — the norm, not a short-circuit), yet the CLI's blanket `--profile desktop` rejection swept up the read-only doctor; desktop-only machines scanning a nonexistent web dir got an all-zero report.
313
+ - **CLI exception (doctor only)**: `dshm doctor --profile desktop` is allowed and routed to `desktopProfileDir()`, with a `[web|desktop]` tag on the first output line; HELP synced; **every other command's rejection of `--profile desktop` stays byte-identical** (pinned by regression tests) — desktop mutation management remains with the official Desktop plugin page.
314
+ - **Missing-dir hint**: when the target profile dir does not exist, suggest `--profile desktop` (an existing-but-empty legit profile is not flagged).
315
+ - **Host method untouched**: the doctor case was already active-profile agnostic — desktop hosts get it for free (new desktop-kind injection pin test); the `dshm_doctor` MCP tool stays deferred per ADR-0010 decision 3; on Windows Electron hosts runtimeVersion may still degrade (materialized layout means farm=0, so stale has nothing to judge — zero practical impact, recorded honestly).
316
+ - **farmCount semantics revision**: "0 = walk short-circuited" applies only to shapes with a symlink farm; 0 is the norm for materialized layouts (ADR-0011).
317
+ - **Verification**: 8 new cases (desktop routing/tag, other-command rejection, invalid value, missing-dir hint, no-hint-on-normal, explicit-desktop no contradictory hint, explicit-web routing, method pin) + 1 rewritten legacy rejection sample (net +8, 1079→1087); full suite 1087 pass / 0 fail / 0 skipped; typecheck clean. The Windows on-machine acceptance checklist ships with the implementation plan (no Windows E2E possible on this machine).
318
+
319
+ ### Fixed in 0.9.30 — bin symlink silent no-op (F2) + realpath third source for version resolution (F1)
320
+
321
+ - **Shared root cause**: two critical pnpm-ecosystem entry points are **symlinks** — `node_modules/.bin/dshm` (→ lib/cli.js) and the global shim `PNPM_HOME/dsh` (→ the real bin.js) — while both code sites made path-identity decisions before realpath.
322
+ - **F2 (latent since the bin entry shipped in v0.2.0; first hit and proven on this machine's 0.9.28 install shape; 🔴 every command via the bin entry silently exits 0)**: cli.ts `invokedDirectly` compared `argv[1]` (link path) against `import.meta.url` (node's realpath) — never equal, so `dshm <any command>` printed nothing and exited. Fix: realpath `argv[1]` before comparing. New `tests/cli-bin-symlink.test.mjs` (revival via symlinked --help and doctor --json; EPERM environments skip per repo precedent).
323
+ - **F1 (proven at 0.9.29 install acceptance)**: dsh-version.ts `readLauncherPackageVersion` walked up three levels from the shim location, landing in the pnpm home tree — pure-FS version resolution was unreachable under this host topology (`node PNPM_HOME/dsh web`), degrading doctor's stale judgment and forcing the ping chip onto the spawn fallback. Fix: realpath the entry after the shape check, then walk (locally verified resolving 0.2.0-rc.2). New shim-shape case in dsh-version.test.mjs.
324
+ - **Verification**: targeted 9/9 (two new cases); full suite 1079 pass / 0 fail / 0 skipped; typecheck clean. Post-install expectations: `dshm doctor` works via the bin entry; host method path reports runtimeVersion=0.2.0-rc.2 with stale judgment live (the farm's dsh link at 0.1.7-rc.2 is a ready-made stale form until healed).
325
+
294
326
  ### Added in 0.9.29 — profile Doctor: `dshm doctor` (read-only: farm liveness / residue inventory / manifest-reality consistency)
295
327
 
296
328
  - **Motivation**: dsh-m had no diagnostics at all — know-how 014's "re-run farm liveness after every DSH upgrade" was the only standing periodic check (81 dangling links and a 230-line manual heal log), know-how 023 documented the manifest-reality split, and a live scan found 8 empty scope dirs + 12 `*.bak-*` files. Day1 subset adopting dsh-market check.ts's design disciplines (pure-FS boundary, three-tier severity, unknown≠broken, false-positive bookkeeping, repair responsibility shifted outward). See ADR-0010 and the comparison report (2026-10-04).
package/docs/DESIGN.md CHANGED
@@ -175,7 +175,7 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
175
175
  - 工具前缀 `dshm_`,共 7 个:`dshm_search` / `dshm_list` / `dshm_install` / `dshm_uninstall` / `dshm_outdated` / `dshm_upgrade` / `dshm_restart`。Agent tools 运行于 Host 进程,与 Web GUI **共用 `host` namespace 缓存与 active config**;`dshm_search` 走服务端过滤(metadata-only,`withLatest=false`、limit ≤80),返回不含本地路径的短 summary。
176
176
  - 0.5.0:`dshm_search`/`dshm_install` 消费**合并市场**(§2.5);category 参数接受精选 5 分类 + 任意社区分类 slug;summary 增加社区清单状态(条数/来源/是否 stale);CLI 默认合并社区清单,`DSHM_COMMUNITY_CATALOG=0` 退出,缓存用 `cli/awesome/` 子目录。
177
177
  - CLI bin `dshm`:同名同义命令集(`dshm list|search|install|uninstall|outdated|upgrade|restart`),固定 `cli` namespace。`registry`/`search`/`outdated` 在清单不可用时打印配置/实际生效地址并 **exit 1**;`list` 仍列出已装并标记不可用;本地终端可显示完整路径。
178
- - 体检(Doctor,0.9.29):`/dshm` method `doctor` + CLI `dshm doctor [--json]`(**不新增第 9 个 agent 工具**——工具描述是宿主 agent 常驻上下文,行为面代价大于收益)。纯 FS 只读(无进程/无网络/无写入,清单不可用照常工作),三项检查(农场测活/残留物清点/账实一致)+ 三级严重度 + unknown≠broken + 双市场信息级;runtimeVersion 仅 `readLauncherPackageVersion` 纯 FS 通路(CLI 下降级 unknown,禁 spawn 回退)。决策全记载见 [ADR-0010](./adr/0010-doctor-read-only-diagnostics.md)。
178
+ - 体检(Doctor,0.9.29;desktop 支持与 CLI 例外开口 0.9.31 / [ADR-0011](./adr/0011-doctor-desktop-profile.md)):`/dshm` method `doctor` + CLI `dshm doctor [--json] [--profile web|desktop]`(**不新增第 9 个 agent 工具**——工具描述是宿主 agent 常驻上下文,行为面代价大于收益)。纯 FS 只读(无进程/无网络/无写入,清单不可用照常工作),三项检查(农场测活/残留物清点/账实一致)+ 三级严重度 + unknown≠broken + 双市场信息级;runtimeVersion 仅 `readLauncherPackageVersion` 纯 FS 通路(CLI 下降级 unknown,禁 spawn 回退)。决策全记载见 [ADR-0010](./adr/0010-doctor-read-only-diagnostics.md)。
179
179
  - 本地 API:`POST /dshm` 单路由 method 分发;除 `ping` 外全部要求 JSON Content-Type + `trustedRestartRequest` host 等价同源防护;typed 错误映射 400/403/404/405/413/415/422/500;`registry-config-apply` 校验失败 422;清单不可用的 `registry`/`market` 仍返回 200 + 结构化状态。
180
180
  - 实现顺序:**GUI 先行,CLI 收尾**(核心逻辑同一层,CLI 是薄封装)。
181
181
 
package/lib/cli.js CHANGED
@@ -9,13 +9,13 @@ import { installFromRegistry, listInstalledWithMeta, listMarket, uninstallPlugin
9
9
  import { togglePlugin as coreTogglePlugin } from './core/toggle.js';
10
10
  import { runDoctor } from './core/doctor.js';
11
11
  import { readLauncherPackageVersion } from './core/dsh-version.js';
12
- import { webProfileDir } from './core/env.js';
12
+ import { webProfileDir, desktopProfileDir } from './core/env.js';
13
13
  import { loadRegistry } from './core/registry.js';
14
14
  import { COMMUNITY_CATEGORY_LABELS } from './core/community.js';
15
15
  import { scheduleRestart } from './core/restart.js';
16
16
  import { upgradeEffectLine } from './core/activation.js';
17
17
  import { pathToFileURL } from 'node:url';
18
- import { resolve } from 'node:path';
18
+ import { realpathSync, existsSync } from 'node:fs';
19
19
  /** 分类中文标签(0.7.0 Task 6;0.9.16 策展五桶):精选策展桶本地表 + 社区已知标签单一事实源;未知 slug 原样。 */
20
20
  const CLI_CATEGORY_LABELS = {
21
21
  essentials: '装机必备',
@@ -133,7 +133,7 @@ const HELP = `dshm — DSH Marketplace(DSH 插件市场:精选策展 + 社
133
133
  dshm list 列出 web profile 已装插件(含市场标注/可升级)
134
134
  dshm outdated 检查已装插件的最新版本
135
135
  dshm registry 查看收录清单来源与条目
136
- dshm doctor [--json] profile 体检(只读:农场测活 / 残留物清点 / 账实一致;error 级发现 exit 1)
136
+ dshm doctor [--json] [--profile web|desktop] profile 体检(只读:农场测活 / 残留物清点 / 账实一致;error 级发现 exit 1;--profile desktop 为 doctor 专属例外)
137
137
 
138
138
  变更命令(必须 --yes):
139
139
  dshm install --id <收录id> [--version 1.2.3] [--force] (--force:确认兼容风险后跳过预检拦截)
@@ -178,7 +178,10 @@ async function runCliDispatch(argv, deps, io) {
178
178
  // 目标恒为 web profile);--profile desktop / 其他非 web 值显式拒绝并指引官方入口,
179
179
  // 绝不隐式回落 web(报告 §5.3「不能从不存在的 Desktop 模式推断」)。
180
180
  const profileFlag = typeof flags.profile === 'string' ? flags.profile.trim() : '';
181
- if (profileFlag !== '' && profileFlag !== 'web') {
181
+ // 0.9.31(ADR-0011):doctor 是纯 FS 只读体检——CLI 上唯一例外允许 --profile desktop;
182
+ // 其余命令维持「CLI 恒 web profile」拒绝(desktop 的变更类管理仍走官方 Desktop 插件页)。
183
+ const doctorDesktop = cmd === 'doctor' && profileFlag === 'desktop';
184
+ if (profileFlag !== '' && profileFlag !== 'web' && !doctorDesktop) {
182
185
  err(`错误:dshm CLI 仅作用于 web profile,不支持 --profile ${profileFlag}。`);
183
186
  err('Desktop profile 的插件管理请使用官方 Desktop 的插件管理页(Settings → Plugins),或回到 DSH Web 端使用 dsh-m。');
184
187
  return 1;
@@ -409,13 +412,16 @@ async function runCliDispatch(argv, deps, io) {
409
412
  // 体检(ADR-0010):纯 FS 只读、不依赖 registry(清单不可用照常工作)。
410
413
  // runtimeVersion 仅用 readLauncherPackageVersion 纯 FS 通路(CLI 进程下通常为 null
411
414
  // → stale 判定降级 unknown,绝不 spawn);errors>0 → exit 1。
412
- const report = await runDoctor(webProfileDir(), readLauncherPackageVersion());
415
+ // 0.9.31(ADR-0011):--profile desktop 例外开口(只读,不与「desktop 管理走官方页」冲突)。
416
+ const profileName = profileFlag === 'desktop' ? 'desktop' : 'web';
417
+ const targetDir = profileName === 'desktop' ? desktopProfileDir() : webProfileDir();
418
+ const report = await runDoctor(targetDir, readLauncherPackageVersion());
413
419
  if (flags.json === true) {
414
420
  out(JSON.stringify(report, null, 2));
415
421
  return report.summary.errors > 0 ? 1 : 0;
416
422
  }
417
423
  const s = report.summary;
418
- out(`profile 体检 · ${report.profileDir}`);
424
+ out(`profile 体检 · [${profileName}] · ${report.profileDir}`);
419
425
  out(`布局 ${report.layout} · 运行时 ${report.runtimeVersion ?? '未解析(stale 判定降级 unknown,绝不 spawn)'} · 扫描于 ${report.scannedAt}`);
420
426
  out(`农场 ${s.farmChecked}(悬空 ${s.farmDangling} · 指旧 ${s.farmStale}${report.runtimeVersion === null ? '(降级:未判定)' : ''})` +
421
427
  ` · 残留 ${s.residueCount} · 账实 ${s.accountChecked}(不一致 ${s.accountMismatched}) · errors ${s.errors} · warnings ${s.warnings}`);
@@ -455,6 +461,10 @@ async function runCliDispatch(argv, deps, io) {
455
461
  }
456
462
  for (const u of s.unknowns)
457
463
  out(`ℹ️ ${u}`);
464
+ if (!existsSync(targetDir)) {
465
+ // 第二子句只对默认/显式 web 目标出现——显式 --profile desktop 时再建议加 desktop 自相矛盾(评审 G5.2)
466
+ out(`ℹ️ 目标 profile 目录不存在(${targetDir})${profileName === 'web' ? ';desktop-only 机器请加 --profile desktop' : ''}。`);
467
+ }
458
468
  if (report.dualMarket)
459
469
  out(`ℹ️ 双市场并存:${report.dualMarket.join(' + ')} 同时在装(写侧互不知晓,排查时留意)。`);
460
470
  if (errs.length === 0)
@@ -474,7 +484,11 @@ const invokedDirectly = (() => {
474
484
  if (!entry)
475
485
  return false;
476
486
  try {
477
- return import.meta.url === pathToFileURL(resolve(entry)).href;
487
+ // F2(0.9.28-0.9.29 实证):pnpm 的 node_modules/.bin/dshm 是指向 lib/cli.js 的
488
+ // 符号链接——node 对入口做 realpath,import.meta.url 是真实路径而 argv[1] 仍是
489
+ // 链接路径,直接比较永假 → bin 入口全命令静默 exit 0。比较前同样 realpath。
490
+ const realEntry = realpathSync(entry);
491
+ return import.meta.url === pathToFileURL(realEntry).href;
478
492
  }
479
493
  catch {
480
494
  return false;
@@ -90,7 +90,8 @@ async function readTargetPkgVersion(targetDir) {
90
90
  *
91
91
  * - 遍历规则(评审 R1.2 定案):从 profileDir 逐级向上至 DSH_HOME 边界,扫描每级
92
92
  * `node_modules/@deepseek-ai`——本机实况农场在 profile **父目录**的共享 hoisted 店,
93
- * 不得以「工作区根」为限(否则 0 命中空转,farmChecked=0 是验收失败信号)。
93
+ * 不得以「工作区根」为限(否则 0 命中空转——该「farmChecked=0 回炉」判定仅适用于
94
+ * 存在符号链农场的形态;物化布局(典型 desktop)0 为常态,ADR-0011)。
94
95
  * - stale 判定只对 dsh 伞包生效:lockstep 店内各核心包(cordis 4.x、dsh-tools 0.1.x…)
95
96
  * 与运行时版本**不在同一命名空间**,逐包比较会把 cordis@4.0.1 误标 stale——误报纪律
96
97
  * (健康档误报代价 > 漏报)禁止;其余包 targetVersion 仅作信息呈现。stale-target 只进
@@ -11,7 +11,7 @@
11
11
  * .pnpm 目录名多版本残留无判据——均不走。
12
12
  */
13
13
  import { dirname, join } from 'node:path';
14
- import { readFileSync } from 'node:fs';
14
+ import { readFileSync, realpathSync } from 'node:fs';
15
15
  import { spawn } from 'node:child_process';
16
16
  import { dshArgv } from './dsh-cli.js';
17
17
  /** 从 `dsh --version` 输出提取 semver:容忍 v 前缀与前后散文,取首个命中。 */
@@ -26,10 +26,14 @@ export function parseDshVersionOutput(text) {
26
26
  export function readLauncherPackageVersion(input = {}) {
27
27
  try {
28
28
  const argv = dshArgv(input);
29
- const entry = argv.args.length > 0 ? argv.args[argv.args.length - 1] : undefined;
29
+ let entry = argv.args.length > 0 ? argv.args[argv.args.length - 1] : undefined;
30
30
  // 与 dshArgv 同一判定:entry 必须长得像 launcher bin,否则是 CLI/PATH 模式
31
31
  if (!entry || !/[\\/](?:bin\.(?:js|ts)|dsh)$/.test(entry))
32
32
  return null;
33
+ // F1(0.9.29 装机验收实证):pnpm 全局 shim(如 PNPM_HOME/dsh)是指向真实 bin.js
34
+ // 的符号链接——realpath 后再向上找 package.json,否则会在 pnpm home 目录树上
35
+ // 空走三级,宿主内纯 FS 解析落空(ping chip 被迫吃 spawn 回退)。
36
+ entry = realpathSync(entry);
33
37
  let dir = dirname(entry);
34
38
  for (let up = 0; up < 3; up++) {
35
39
  try {
package/lib/core/env.js CHANGED
@@ -2,12 +2,17 @@
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  export const WEB_PROFILE = 'web';
5
+ export const DESKTOP_PROFILE = 'desktop';
5
6
  export function dshHome() {
6
7
  return process.env.DSH_HOME || join(homedir(), '.dsh');
7
8
  }
8
9
  export function webProfileDir() {
9
10
  return join(dshHome(), 'profiles', WEB_PROFILE);
10
11
  }
12
+ /** 0.9.31(ADR-0011):doctor 只读体检的 desktop 目录解析(其余 CLI 命令仍恒 web)。 */
13
+ export function desktopProfileDir() {
14
+ return join(dshHome(), 'profiles', DESKTOP_PROFILE);
15
+ }
11
16
  /**
12
17
  * 缓存根(0.9.0 双 profile,ADR-0005):web 恒走旧路径(DSHM_CACHE_DIR 或
13
18
  * `$DSH_HOME/dshm/cache`)——不迁移不清空;非 web profile 加同名段
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.29",
3
+ "version": "0.9.31",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",