dsh-m 0.9.92 → 0.9.93

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,6 +8,12 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.93 重构:装后守卫线格式收敛至 InstallGuardError.toWire——加守卫字段编辑点 8 → 2(纯重构,线格式语义零变化)
12
+
13
+ - **需求(2026-10-16 架构巡查摩擦点 3,巡查首推)**:`InstallGuardError` → 线格式的投影在手写 5 处逐字重复(host-api 409 payload / tools install+upgrade 两臂 / cli install+upgrade 两臂)+ client 再拼一次——加一个守卫字段要碰 8 处(类 + 5 投影 + client 读取 + 测试),`repairBasis` 后加时各点手补已实证过一次复发。
14
+ - **方案**:产地方法 `toWire(): InstallGuardWire`(随类导出接口 + `GuardViolation` re-export)成为 6 字段共享线格式单一事实源;五处投影改一行展开(信封差异留在各端:host-api `ok/error`、tools `ok/guard/message`、cli 裸 JSON),`repairBasis` undefined 时键不存在的语义移入方法本体。加字段编辑点降为 2(类+toWire 一处、client 读取一处——浏览器边界不能 import core,防御性拼装留守,如实注记)。自觉差异注记:tools 信封键序 kind/message 交换(JSON 键序对消费方无语义,全 tests/ 无原始 JSON 键序钉)。
15
+ - **验证**:全量 1530 测试全绿(新增 toWire 三钉例:六字段齐/repairBasis 键缺席/键序;新增 host-api dispatcher 409 钉例补 host-api 投影零测试覆盖的缺口;tools 两臂 execute 钉例零迁移)、typecheck 零错误;收敛锚 `repairBasis ? { repairBasis: … }` 三文件 5 → 0(兼证无第 6 处投影);实施计划经两轮独立评审四项收敛 + 执行评审两轮(含 A1 钉例独立成 describe)记录在 `docs/plans/2026-10-17-install-guard-towire-implementation-plan.md`。
16
+
11
17
  ### 0.9.92 修复+重构:三端市场查询文法统一(market-query 单一入口)——CLI 非法 category 吞错修复
12
18
 
13
19
  - **需求(2026-10-16 实测)**:`dshm search --category <非法>` 静默返回「没有匹配的收录条目」+ exit 0,而同输入 GUI 得 400、agent 工具显式报错——「合并市场」词条承诺的「三端同源同语义」被 CLI 打破;根因是分类 slug 正则、source/offset/limit 解析、精选标签表在三端各持一份拷贝(正则 ×3、解析 ×3、标签表 ×2、deadline 常量 ×2),CLI 的拷贝漏了 category 校验。
@@ -649,6 +655,12 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
649
655
 
650
656
  ## English
651
657
 
658
+ ### Refactor in 0.9.93 — post-install guard wire format converged onto InstallGuardError.toWire; adding a guard field now touches 8 → 2 points (pure refactor, zero wire-format semantic change)
659
+
660
+ - **Need (2026-10-16 architecture walk, friction point 3, the walk's top pick)**: the `InstallGuardError` → wire-format projection was hand-written verbatim in 5 places (host-api 409 payload / tools install+upgrade arms / cli install+upgrade arms) plus one more assembly in the client — adding a guard field meant touching 8 points (class + 5 projections + client read + tests); the later addition of `repairBasis` had already demonstrated the recurrence once.
661
+ - **Approach**: a source method `toWire(): InstallGuardWire` (interface exported alongside the class, plus a `GuardViolation` re-export) becomes the single source of the shared 6-field wire format; the five projections become one-line spreads (envelope differences stay per-end: host-api `ok/error`, tools `ok/guard/message`, cli bare JSON), and the "repairBasis key absent when undefined" semantics move into the method body. Adding a field now touches 2 points (class+toWire, and the client read — the browser bundle cannot import core modules, so its defensive assembly stays, honestly noted). Deliberate delta: the tools envelope swaps key order between kind/message (JSON key order carries no consumer semantics; no raw-JSON key-order pins exist across tests/).
662
+ - **Verification**: full suite 1530 green (three new toWire pins: six fields / repairBasis key absence / key order; a new host-api dispatcher 409 pin closing the "host-api projection had zero test coverage" gap; tools-arm execute pins migrated zero), typecheck clean; the convergence anchor `repairBasis ? { repairBasis: … }` went 5 → 0 across the three files (also proving there is no 6th projection); plan review (two rounds, four findings) and execution review (two rounds, including A1 extracting the pin into its own describe) recorded in `docs/plans/2026-10-17-install-guard-towire-implementation-plan.md`.
663
+
652
664
  ### Fix + refactor in 0.9.92 — unified three-end market query intake (market-query single entry); CLI invalid-category silent swallow fixed
653
665
 
654
666
  - **Need (2026-10-16, reproduced live)**: `dshm search --category <invalid>` silently printed "no matching entries" and exited 0, while the same input gets a 400 from the panel API and an explicit error from the agent tool — breaking the "same source, same semantics across GUI / tools / CLI" promise. Root cause: the category slug regex, source/offset/limit parsing, and the curated label map each had per-end copies (regex ×3, parsing ×3, labels ×2, deadline constants ×2), and the CLI copy lacked category validation.
package/lib/cli.js CHANGED
@@ -193,7 +193,7 @@ async function runCliDispatch(argv, deps, io) {
193
193
  // metadata-only:不构造全量 latest;0.9.92 三端查询文法:category/source/limit/offset 经
194
194
  // market-query 单一入口归一(与 host-api/工具同源同语义);文法违规 throw 直飞 dispatch 统一
195
195
  // catch(`错误:${msg}` + exit 1,与此前 source 内联报错输出逐字节同款)——非法 category
196
- // 自此显式报错(0.9.91 前静默空结果吞错,回归钉例在 cli-search.test.mjs)。
196
+ // 自此显式报错(≤0.9.91 静默空结果吞错,回归钉例在 cli-search.test.mjs)。
197
197
  // 双源判定:主清单 unavailable 但社区有条目 → 照常出页(Q42);两层皆不可用 → exit 1
198
198
  const intake = parseMarketQueryInput({ category: flags.category, source: flags.source, limit: flags.limit, offset: flags.offset }, { limitMax: 80, limitDefault: 10 });
199
199
  const result = await d.listMarket(cfg, {
@@ -332,7 +332,7 @@ async function runCliDispatch(argv, deps, io) {
332
332
  }
333
333
  catch (e) {
334
334
  if (e instanceof InstallGuardError) {
335
- err('[⛔ 装后守卫拦截] ' + JSON.stringify({ kind: e.kind, violations: e.violations, compensation: e.compensation, needsRestart: e.needsRestart, restartSafe: e.restartSafe, ...(e.repairBasis ? { repairBasis: e.repairBasis } : {}) }));
335
+ err('[⛔ 装后守卫拦截] ' + JSON.stringify(e.toWire()));
336
336
  err(e.message);
337
337
  err(e.restartSafe ? '可以重启 DSH Web:dshm restart --yes' : '修复后再重启(不要现在重启)。');
338
338
  return 1;
@@ -357,7 +357,7 @@ async function runCliDispatch(argv, deps, io) {
357
357
  }
358
358
  catch (e) {
359
359
  if (e instanceof InstallGuardError) {
360
- err('[⛔ 装后守卫拦截] ' + JSON.stringify({ kind: e.kind, violations: e.violations, compensation: e.compensation, needsRestart: e.needsRestart, restartSafe: e.restartSafe, ...(e.repairBasis ? { repairBasis: e.repairBasis } : {}) }));
360
+ err('[⛔ 装后守卫拦截] ' + JSON.stringify(e.toWire()));
361
361
  err(e.message);
362
362
  err(e.restartSafe ? '可以重启 DSH Web:dshm restart --yes' : '修复后再重启(不要现在重启)。');
363
363
  return 1;
@@ -166,18 +166,14 @@ function errorStatus(err) {
166
166
  return { status, payload: { ok: false, error: err.message, code: err.code } };
167
167
  }
168
168
  if (err instanceof InstallGuardError) {
169
- // 装后守卫拦截(M2 Task 3):结构化投影含 needsRestart/restartSafe/kind——GUI 一键重启只读 restartSafe
169
+ // 装后守卫拦截(M2 Task 3):结构化投影含 needsRestart/restartSafe/kind——GUI 一键重启只读 restartSafe;
170
+ // 0.9.93 起线格式单源于 toWire(信封 ok/error 本端自拼)
170
171
  return {
171
172
  status: 409,
172
173
  payload: {
173
174
  ok: false,
174
175
  error: err.message,
175
- kind: err.kind,
176
- violations: err.violations,
177
- compensation: err.compensation,
178
- needsRestart: err.needsRestart,
179
- restartSafe: err.restartSafe,
180
- ...(err.repairBasis ? { repairBasis: err.repairBasis } : {}),
176
+ ...err.toWire(),
181
177
  },
182
178
  };
183
179
  }
@@ -822,6 +822,21 @@ export class InstallGuardError extends Error {
822
822
  this.compensation = fields.compensation;
823
823
  this.repairBasis = fields.repairBasis;
824
824
  }
825
+ /**
826
+ * 线格式单一事实源(0.9.93):三端共享的 6 字段守卫投影。各端信封自拼(host-api 409 payload 的
827
+ * ok/error、tools 的 ok/guard/message、cli 的裸 JSON),共享部分一律经此方法——加守卫字段只改
828
+ * 本方法 + client 读取一处。repairBasis undefined 时键不存在(今日 5 处投影同语义)。
829
+ */
830
+ toWire() {
831
+ return {
832
+ kind: this.kind,
833
+ violations: this.violations,
834
+ compensation: this.compensation,
835
+ needsRestart: this.needsRestart,
836
+ restartSafe: this.restartSafe,
837
+ ...(this.repairBasis ? { repairBasis: this.repairBasis } : {}),
838
+ };
839
+ }
825
840
  }
826
841
  /** lockfile packages 段的目标包 resolution 提取(行扫描:键行 `pkg@ver:` → 收集至下一键行;引号/嵌套键兼容)。 */
827
842
  function lockResolutionOf(lockText, pkg) {
package/lib/tools.js CHANGED
@@ -241,7 +241,7 @@ export function registerTools(ctx, cfg, deps = {}, profile = resolveActiveProfil
241
241
  catch (err) {
242
242
  if (err instanceof InstallGuardError) {
243
243
  // 结构化 error result(M2 Task 3 ㉑-㉜):不渲染「已安装」,无 force 通道;restartSafe 随结果携带
244
- return { ok: false, guard: true, kind: err.kind, message: err.message, violations: err.violations, compensation: err.compensation, needsRestart: err.needsRestart, restartSafe: err.restartSafe, ...(err.repairBasis ? { repairBasis: err.repairBasis } : {}) };
244
+ return { ok: false, guard: true, message: err.message, ...err.toWire() };
245
245
  }
246
246
  throw err;
247
247
  }
@@ -403,7 +403,7 @@ export function registerTools(ctx, cfg, deps = {}, profile = resolveActiveProfil
403
403
  }
404
404
  catch (err) {
405
405
  if (err instanceof InstallGuardError) {
406
- return { ok: false, guard: true, kind: err.kind, message: err.message, violations: err.violations, compensation: err.compensation, needsRestart: err.needsRestart, restartSafe: err.restartSafe, ...(err.repairBasis ? { repairBasis: err.repairBasis } : {}) };
406
+ return { ok: false, guard: true, message: err.message, ...err.toWire() };
407
407
  }
408
408
  throw err;
409
409
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.92",
3
+ "version": "0.9.93",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",