# 工具契约(CONTRACT) 本文件是 dsh-useful-tools 的**实现与验收唯一依据**:每条都是可测断言, 实现必须照做;改动契约要显式改本文件并升版本,不允许「代码改了文档没改」。 编号规则:`C-<分组><序号>`。违反后果按「读者感知」写,不写空话。 --- ## 1. 工具装配 ### C-101 目录即工具名 `lib/shared/<名字>/index.js` 一个目录 = 一个工具,注册名就是 `<名字>`(不带前缀、不做驼峰转换)。 - 依据:`lib/index.js` 的 `listToolDirs()` 扫 `lib/shared/*/index.js`,`name` 取目录名。 - 验证:`ls lib/shared/` 与工具清单逐项对照;当前应为 `mark_read` / `sync_plugin` / `minihost` / `preview` / `verify_plugin` / `test_env`。 - 违反后果:调用方按文档名字调不到工具(历史上 `tool_` / `ut_` 前缀就是这么对不上的)。 ### C-102 一个文件夹一个工具,多动作走 `action` 同一功能域的多种动作收进 `action` 参数,不拆成多个工具。 启动 / 停止 / 状态这类生命周期动作同样走 `action`:`status` 与 `list` 同义 (状态查询用 `status` 更直白,旧名保留兼容,不新增工具)。 - 依据:`minihost`(start/call/stop/list/status)、`preview`(gen/serve/stop/list/status)的实现。 - 验证:新增工具时检查 `parameters` 里是否有 `action`;工具总数不应随动作数增长。 - 违反后果:工具清单膨胀,模型选择成本上升。 ### C-103 工具模块只导出三件套 `lib/shared/<名字>/index.js` 导出 `{ description, parameters, run }`: `description` 是字符串,`parameters` 是参数 schema,`run(args, env)` 返回可 JSON 序列化的对象。 - 依据:`lib/index.js` 装配时按这三项构造工具定义。 - 验证:`node -e "import('./lib/shared/x/index.js').then(m=>console.log(Object.keys(m)))"` 应含这三项。 - 违反后果:装配层跳过该工具(日志 warn「未导出 run,跳过」)。 ### C-104 装配层零业务逻辑 `lib/index.js` 只做扫描 + 注册,不含任何工具的业务实现;新增工具不需要改它。 - 依据:`lib/index.js` 全文只有目录扫描、注册、钩子挂载、装配摘要日志。 - 验证:新增一个工具后 `git diff lib/index.js` 应为空。 - 违反后果:每加一个工具就改装配层,改动面随工具数线性增长。 ### C-105 前后端模拟分工 `preview` 负责**前端模拟**(页面渲染自检),`minihost` 负责**后端模拟**(插件服务端 handler); 两者通过 `preview serve` 的 `backend` 串起来:前端页面的 `/api/*` 打到 minihost,即完成不碰真实后端的联调。 - 依据:`lib/core/preview/server.js` 的 backend 反代;`lib/core/minihost/host.mjs` 的插件 handler 加载。 - 验证:`minihost start` 后把 `preview serve` 的 `backend` 指向该端口,页面 `/api/*` 应返回 minihost 的响应; **省略 `backend` 时**自动沿用台账里最近一次存活的 minihost 实例,并在结果里给出 `backendSource`(如 `minihost:31178`)。 - 违反后果:两个工具各自能跑但串不起来,模拟链路断在前端或后端;自动沿用若不给来源,会静默连错实例。 ### C-106 预览页无 spec 兜底(通用自动挂载) `preview gen` 在插件没有 `assets/preview.spec.mjs` 时用**通用自动挂载垫片**生成页面: mock `window.__ModuleLoader__` / `require` / `ctx`(slots / locale / effect),调用插件的 `apply(ctx)`, 把 `slots.register(def, component)` 捕获到的组件用 `createRoot().render()` 挂到 `#root`;返回带 `note` 与 `autoMount:true`。 - **显式**传入不存在的 spec 路径仍然报错——调用方指错了文件,不静默降级。 - 自动挂载是**尽力而为**:组件依赖宿主 hooks/context 时渲染失败,页面显示错误与已捕获插槽清单,**不白屏**。 - 依据:`lib/core/preview/gen.js` + `lib/core/preview/auto-harness.js`。 - 验证:对无 spec 的 dsh-session-conductor 跑 `gen` → 页面 `#root` 有 1 个子节点 / 1378 字符 / 捕获 5 个插槽 / 0 错误。 - 违反后果:没 spec 的插件只能看到空壳;或渲染失败白屏、无从判断原因。 ### C-107 测试环境是隔离实例,路径全由配置决定 `test_env` 管理的实例:独立 `DSH_HOME`(默认 `<工作区>/@Save-test`)、独立端口(3083-3182 首个空闲), 与主实例完全隔离。**环境根不写死**,优先级:参数 `root` > 环境变量 `DSH_TEST_ENV_ROOT` > 插件配置 `/useful-tools/config.json` 的 `testEnvRoot` > 推算(`DSH_WORKSPACE` / `DSH_HOME` 上一级「工作区」)。 `sync_plugin` 的 `dshHome` 同样缺省为当前实例(`process.env.DSH_HOME`),传环境目录即可把插件装进隔离实例。 - 依据:`lib/core/test-env/index.js` 的 `envRoot` / `readPluginConfig` / `detectDshVersion` / `startEnv`;`lib/core/plugin-sync/tools.js` 的 `registerPlugin`。 - 验证:配置 `testEnvRoot` 后 `test_env { action: "status" }` 的 `root` 应等于该配置;`test_env { action: "start" }` 返回 `ready:true` 与端口;`sync_plugin { write:true, register:true, dshHome:"<环境目录>" }` 后该环境 profile 的 `dependencies` 与 `dsh.profile.bundles` 均含插件名。 - 违反后果:测试实例与主实例共用 home/端口,测试崩了会污染生产;或路径写死导致换机不可用。 ### C-108 minihost 路由匹配与服务注入 `minihost` 的路由匹配只比较 **pathname**(`/api/x?y=1` 按 `/api/x` 匹配),query 原样留给 handler; 插件依赖真实宿主服务(`storageDomain` / `sessions` 等)时,用 `services` 参数指向一个模块 (默认导出服务对象,或返回服务对象的函数),注入项覆盖同名默认服务,核心服务(`webServer` / `tools`)仍由 minihost 提供。 - 依据:`lib/core/minihost/host-child.mjs` 的 pathname 匹配与 `DSH_MINIHOST_SERVICES` 注入;`host.mjs` 的 `--services`。 - 环境隔离:`minihost start` 默认把子进程的 `DSH_HOME` 指到 `.minihost/home-`(不写真实会话/附件库), 并用 `--deps <真实 profile>/node_modules` 补回依赖解析;需要贴近真实环境时用 `env: { DSH_HOME: "<真实 .dsh>" }` 覆盖。 - 验证:`/api/x?y=1` 与 `/api/x` 返回同一状态码;注入 storageDomain 后 `/templates` 由 500 变 200; 默认启动后 `home` 指向 `.minihost/home-`,且插件路由仍能注册(依赖解析正常)。 - 违反后果:带参数的接口全部 404;依赖宿主服务的插件在 minihost 里只能跑出 500;不隔离 home 会让自测写脏真实数据。 ### C-109 mark_read 是「读命令集合」配置,不是文件标记 `mark_read` 只有两个参数:`commands`(读命令集合,可直接写、可传文件路径从文件读、不传用预置清单 `lib/core/mark-read/read-commands.json`)与 `undo`(撤销)。集合里的命令读过的**所有文件** 由 `tools/post-execute` 钩子自动补发观测——**不提供文件路径参数**(要指定文件直接 `read`)。 **两种读法都算**:① 宿主工具本身(如 `grep` 工具,exec.name 即命令名);② `bash` 里跑的命令 (`cat a.txt` / `grep x b.txt`,钩子解析命令串提取文件参数)。命令名经跨平台别名归一 (`Get-Content`/`gc`/`type`/`more`→cat,`Select-String`/`sls`/`findstr`→grep,去 `.exe` 后缀)。 配置落盘 `/useful-tools/config.json`,钩子每次现读 ⇒ 命令配置一次、永久有效。 - 依据:`lib/shared/mark_read/index.js`、`lib/core/mark-read/{config.js,hook.js}`、`read-commands.json`。 - 验证:不传 `commands` 时按预置清单写入;`undo` 能从集合移除;`bash cat a.txt` 后可直接 edit 该文件;`Get-Content` 等同理。 - 违反后果:做成按文件标记会偏离需求(用户明确要求「所有文件都生效」),且与「直接 read」重复;不解析 bash 命令串则 `cat` 永远不算读。 --- ### C-110 quick_command 是配置驱动的快捷命令 定义存 `/useful-tools/quick-commands.json`(结构 `{名字: {command, description, cwd}}`,兼容值为纯字符串)。 **无内置预置**:文件不存在时是空表(`list` 返回 `count:0` 并提示用 add),定义完全由使用方创建。 `action=run`(缺省)按 `name` 执行已定义命令;**同时传 `command` 时先 upsert 写入 JSON 再执行**(等效 add 后执行), 不提供「只传 command、不落盘」的临时执行。`add` / `edit` / `delete` 管理定义,`list` 列出所有名字+命令。 `cwd`:缺省空 = 沿用宿主进程 cwd;传了必须**存在且是目录**,并解析成绝对路径。 `add`/`edit`/`run` 带 `command` 时随定义落盘;`run` 单独传 `cwd` 只影响本次执行,不改已有定义。 跨平台:Linux/macOS 走 `bash -c`,Windows 走 `cmd /c`;命令形态由使用方自定(插件不预置任何平台相关命令)。 - 依据:`lib/shared/quick_command/index.js`、`lib/core/quick-command/{store,exec}.js`。 - 验证:`test/quick-command.test.mjs` 覆盖 空表 list / add / edit / delete / run / 超时 / 非法名 / cwd(含不存在的目录被拒); 首次 `list` 的 `count` 必须为 0,`add` 后文件里只应有用户定义(不得出现任何内置预置)。 - 违反后果:内置预置会把「本机专属命令」塞给所有部署方;不落盘则重启后快捷命令消失; 不校验 cwd 会把「路径写错」拖到执行时才暴露。 --- ### C-111 useful_tools 是动态清单 `useful_tools` 的 `action=help`(缺省)动态扫描 `lib/shared/*/index.js`,import 每个模块的 `description` / `parameters`, 输出「名字 + 说明 + 参数 + 示例调用」;`action=list` 给紧凑清单;`tool` 过滤单个工具。 **禁止维护硬编码工具清单**:新增工具只需新建目录,本文件与 help 输出都应自动跟随。 - 依据:`lib/shared/useful_tools/index.js` 的 `loadTools()`(与 `lib/index.js` 的目录扫描同源)。 - 验证:`test/useful-tools.test.mjs` 断言清单含 mark_read / quick_command / sync_plugin,且不写死工具总数。 - 违反后果:新增工具后 help 看不到它(README 与实现对不上就是这么来的)。 --- ### C-112 ui_check 是静态骨架,不是渲染检查 解析 `lib/client.js` 的 React 元素调用(`createElement` / `h` / `jsx` / `(0, ns.fn)(…)`),产出 HTML 骨架页与 JSON 控件清单。 必须遵守四条: ① **不执行代码**(与 C-501 同口径:只读文件、不进浏览器); ② 明确区分「静态未知」与「已渲染」:清单里 `visible` 恒为 `null`,留给将来的渲染探针填 true/false; ③ 排除 DOM API(`document.createElement` 不是 React 节点),且注释里的示例代码不得污染解析结果; ④ 组件只在文件内定义时才展开;跨文件或工厂函数返回的组件标注「未展开」,不得假装展开成功(`resolved:true` 就必须真有节点)。 - 依据:`lib/core/ui-check/parse.js`(扫描/配平/插槽/组件定位)、`lib/core/ui-check/emit.js`(清单与 HTML)、`lib/shared/ui_check/index.js`。 - 验证:`test/ui-check.test.mjs` 覆盖三种调用写法、DOM 排除、注释污染、条件分支收集、组件解构参数、压缩告警、HTML 转义; 真实文件实测:dsh-session-conductor 267 个控件 / 5 个插槽全展开,dsh-skill-scoreboard 76 个控件。 - 违反后果:把它当渲染检查会制造新的误判(静态说有、运行时没有),比不做更糟。 --- ### C-113 静默失败诊断只做离线组合,不启动宿主 `verify_plugin` 的 inject 校验、激活诊断、依赖体检都是**离线**的:只读 profile 的 `package.json` / `cordis.patch.yml` 与各 bundle 的 patch,组合出 loader 行清单;不启动宿主、不 import 被验证插件。 运行期真实装配以宿主日志(`did not activate` 归类)为准,输出里不得把离线推断写成「运行期实测」。 `sync_plugin` 写盘后的权限/属主对齐与读权限自检属于**副作用收尾**:只改权限位与属主,不改文件内容; CIFS 等挂载上 chmod/chown 失败只记录、不中断同步,也不假装成功。 - 依据:`lib/core/verify-plugin/{inject,activation,deps}.mjs`、`lib/core/plugin-sync/permissions.mjs`。 - 验证:`test/verify-enhance.test.mjs` 覆盖 inject 近似建议、激活三项缺失、依赖 `file:` 失效、权限对齐、`dshBin` wrapper 解析。 - 违反后果:把「离线推断」说成「运行期实测」会误导排查方向;权限静默失败会再次变成「装了不激活」。 --- ### C-114 DSH 插件规则两边同源维护,但实现不耦合 同一套 DSH 插件规则存在两处,判定语义必须一致: - `dsh-git-push` 的 `lib/audit-rules/audit-rules-dsh.yml`(提交审计,逐行扫描改动) - 本插件 `lib/core/verify-plugin/dsh-rules.mjs`(`verify_plugin`,整仓扫描) 约束: ① 两个插件**不得互相 import**(各自独立实现,避免耦合与加载顺序问题); ② 规则 id / 严重度 / 判定语义必须一致,增删改要两边同步; ③ 本插件的命中并入 `verify_plugin` 结论:blocker/error → problems,warning → warnings; ④ 与 `checkClient` 重叠的规则(client 未走 `__ModuleLoader__`)只报一次。 - 依据:`lib/core/verify-plugin/dsh-rules.mjs` 的 `DSH_RULES`。 - 验证:`test/dsh-rules.test.mjs` 断言 9 条规则 id 与 git-push 一致,且每条都有正例与关键负例。 - 违反后果:两份实现漂移会让「审计放行、工具报错」或反之,开发者不知道该信哪个。 --- ## 2. 装配入口(cordis) ### C-201 入口必须导出 `apply` `package.json` 的 `main`(或 `exports["."]`)指向的模块必须导出 `apply(ctx, config)`。 入口只做再导出(`export * from ...`)时,`apply` 必须在跟进的文件里能找到。 - 依据:`lib/index.js` 的 `export async function apply`;宿主按 `apply` 装载。 - 验证:`verify_plugin` 的 `hasApply`;或 `grep -n "function apply" lib/index.js`。 - 违反后果:宿主不把它当插件,工具一个都不会注册。 ### C-202 `apply` 的返回值必须是合法 effect 合法形态只有:`undefined` / `null` / dispose 函数 / thenable / iterator。 **禁止返回普通对象**(例如 `return { name, tools }`)。 - 依据:cordis 4.0.5 `_execute → safeCollect`:普通对象抛 `TypeError: Invalid effect`, 整条 fiber 随即 `_unload()`,已注册的工具全部失效(0.1.1 修的就是这个)。 - 验证:`verify_plugin` 的 `analysis.applyReturn.risky` 必须为 `false`;单测 `test/verify-plugin.test.mjs` 覆盖该判定。 - 违反后果:插件看起来装了、工具一个都没有,日志里只有一行 `Invalid effect`。 ### C-203 注册必须走 `ctx.inject` 工具注册写成 `ctx.inject(['tools'], (c) => c.get('tools').register(...))`; 不要在 apply 里直接 `ctx.get('tools')`(未 inject 读取会抛)。 - 依据:`lib/index.js` 的注册块;`dsh-git-push` 也踩过「直接读服务抛 without inject」。 - 验证:`verify_plugin` 的 `registersTools` 为 `true`;`grep -n "inject(\['tools'\]" lib/index.js`。 - 违反后果:注册代码抛错被 catch 吞掉,工具静默缺失。 ### C-204 本插件不提供斜杠指令 宿主 `commands.register` 要求 `input` 必须带非空 `hint` 字符串;本插件已移除指令注册,只提供工具。 若要恢复,必须按该契约补 `input: { hint: '...' }`,并补对应测试。 - 依据:`dsh-commands` 的 `normalizeDefinition`;0.1.2 的移除记录。 - 验证:`grep -rn "commands" lib/` 应无注册调用。 - 违反后果:指令注册抛 `input hint must be a string`,被 catch 吞掉,界面看不到指令。 --- ## 3. 部署与同步 ### C-301 工作区源码是唯一真源 `工作区/dsh-useful-tools` 是源码;profile 的 `local-plugins/<包名>` 是部署副本, `node_modules/<包名>` 是指向它的软链。 - 依据:`lib/core/plugin-sync/sync-plugin.mjs` 的 `detectTargets` 与 profile 布局。 - 验证:`verify_plugin` 的 `deploy.targets[].inSync` 为 `true`。 - 违反后果:改了源码没同步,宿主跑的还是旧代码,排查时被假象带偏。 ### C-302 改完必须同步并逐文件一致 改完源码后执行 `sync_plugin { write: true }`,同步后源码与部署副本逐文件内容一致。 - 依据:`syncPlugin` 逐文件比内容,内容相同不写(幂等)。 - 验证:`verify_plugin` 返回 `deploy.targets[].missing/different` 均为空。 - 违反后果:部署副本落后,`hmr` 重载到的仍是旧代码。 ### C-303 文档随插件同步 `docs/` 必须写进 `package.json` 的 `files` 字段——同步清单的唯一权威就是它 (`SYNC_ENTRIES` 只是未声明 `files` 时的兜底白名单)。否则部署副本的 README 引用会指向不存在的文件。 - 依据:`lib/core/plugin-sync/sync-plugin.mjs` 的 `packageFilesOf`(`files` 优先,未声明才用白名单)。 - 验证:`sync_plugin` 的 dryRun 输出里应出现 `docs/CONTRACT.md`;`ls <部署副本>/docs`。 - 违反后果:部署副本文档残缺,引用断链。 --- ## 4. 依赖与路径 ### C-401 零依赖 只使用 Node 内置模块,`package.json` 不新增 `dependencies`;不为跑通写本地依赖清单。 - 依据:`package.json` 无 dependencies;全部 `import` 均为 `node:` 前缀或相对路径。 - 验证:`grep -rn "from '[^n.]" lib/ | grep -v "from '\./"` 应为空。 - 违反后果:插件要装依赖才能起,部署与迁移成本陡增。 ### C-402 不写死路径 `DSH_HOME`、profile、日志、部署目标一律运行时探测;代码里不出现**作为唯一来源**的机器专属绝对路径。 探测候选(套件安装位、数据根等)允许存在,但必须同时满足三条: ① 集中在文件顶部常量;② 环境变量或工具参数优先(`DSH_ROOT` / `DSH_HOME` / `--deps` 等); ③ 候选只是兜底,前面必须有上溯探测或环境探测。 - 依据:`defaultPluginRoot()`、`inspectProfiles`、`detectTargets`、`logCandidates` 均为探测; `lib/core/preview/locate.js` 的 `DSH_ROOT_CANDIDATES` / `DSH_HOME_ROOTS`、`lib/core/minihost/host.mjs` 的 `SUITE_NODE_MODULES` 是候选清单(env 优先、上溯在前)。 - 验证:`grep -rn "/volume1\|/var/packages" lib/` 的每一处命中都必须是候选常量,且同文件存在环境变量优先分支; 不得出现在候选清单之外的业务逻辑里。 - 违反后果:换机/换版本目录即失效。 ### C-403 单文件不超过 400 行 超过就按职责拆分(例如 `verify-plugin/static.mjs` 与 `check.mjs`)。 - 依据:仓库提交要求;当前最大文件 < 300 行。 - 验证:`wc -l lib/**/*.js lib/**/*.mjs | sort -n | tail -3`。 - 违反后果:单文件难维护,改动互相牵连。 --- ## 5. 验证(verify_plugin) ### C-501 只静态分析,不执行被验证插件 `verify_plugin` 只读文件,**不得 import、不得执行**目标插件代码。 - 依据:`lib/core/verify-plugin/static.mjs` 与 `check.mjs` 只做 `readFileSync` / 目录遍历。 - 验证:跑巡检后宿主日志不应出现被验证插件的启动输出(例如 session-conductor 的分组服务日志)。 - 违反后果:验证动作本身产生副作用(起服务、写台账、改配置)。 ### C-502 硬伤与启发式分开 `problems` 只放确定性硬伤(入口缺失、无 apply、apply 返回对象字面量、声明的 patch 缺 `insert`); `warnings` 放启发式(没找到 tools 注册调用、patch id 与包名不同、入口靠 `export *` 转出)。 - 依据:`analyzePlugin` 的 `problems` / `warnings` 分类。 - 验证:单测覆盖「不注册工具的插件不算失败」。 - 违反后果:把合法插件判成坏插件,工具失去可信度。 ### C-503 入口 `export *` 必须跟进 入口只做再导出时,静态分析要沿 `export * from './x'` 跟进(最多 3 层),否则会误判「没有 apply」。 - 依据:`collectSourceFiles`;`dsh-git-push` 实测为 index → app/index 两层。 - 验证:对 `dsh-git-push` 跑 `verify_plugin` 应为 `ok: true` 且 `filesAnalyzed.length >= 2`。 - 违反后果:主流写法(入口再导出)全部误报。 ### C-504 历史日志降级为提示 日志文件最后写入时间早于源码最后修改时间时,命中的报错判为「修复前的历史记录」,进 `warnings` 而非 `problems`。 - 依据:`verifyOne` 里 `srcMtime` 与日志 `mtime` 的比较。 - 验证:单插件自验时旧报错不阻塞 `ok: true`。 - 违反后果:修好的问题一直显示失败,用户对工具失去信任。 --- ## 6. 测试 ### C-601 新增工具必须带单测 `test/<工具名>.test.mjs`,用 `node:test` + `node:assert/strict`,零依赖。 - 依据:`test/` 现有 10 份测试(统一 `*.test.mjs`),`package.json` 的 `test` 脚本为 `node test/run-tests.mjs`。 - 入口:`test/run-tests.mjs` 自己扫描 `*.test.mjs` 再把文件列表传给 `node --test`——不依赖 glob (Node 20 不支持 `--test` 的 glob,Windows 的 shell 也不展开通配符)。 - CI:`.github/workflows/test.yml` **固定 `ubuntu-latest`** × Node 20/22/24 跑同一命令(零依赖,无需 npm install)。 CI 是门禁,环境固定才稳定;跨平台能力由代码保留,但不把 Windows 挂进每次提交的门禁 (Windows 上 spawn 的 cwd 传递与 cmd 引号/重定向语义尚未验证,需真实环境单独验证)。 - 验证:`npm test` 全绿。 - 跨平台:测试里不得依赖 shell 内置命令(`pwd`/`sleep`/`ls` 只有某类 shell 有),一律用 `node -e` 代替。 - 违反后果:装配类 bug 只能靠宿主日志发现(本次 `Invalid effect` 藏了很久)。 ### C-602 测试不写死路径、不自留垃圾 测试用相对路径 + `mkdtempSync` 临时目录,结束 `rmSync` 清理。 - 依据:`test/verify-plugin.test.mjs` 的 `makePlugin` 辅助。 - 验证:跑完测试后 `/tmp` 无残留 `verify-plugin-*` 目录。 - 违反后果:每跑一次留一堆夹具目录。 --- ## 7. 版本与文档 ### C-701 版本三处同步 `package.json` 的 `version` 与 `README.md` 的版本记录表必须一致(本插件无独立 VERSION 常量)。 - 依据:`package.json` / `README.md` 版本记录。 - 验证:`grep '"version"' package.json` 与 README 表格首行对照。 - 违反后果:装的版本和文档写的版本对不上。 ### C-702 README 只做门面,契约引用本文件 README 保留:简介、工具表、用法、配置、安装、目录结构、版本记录、文档索引。 **契约正文(命名规则、装配规则、部署规则、验证规则)不在 README 重复**,一律引用本文件。 - 依据:本文件与 README 的分工。 - 验证:README 出现契约细节即为漂移,应移到本文件。 - 违反后果:同一事实两处表达,必然漂移(`tool_` 前缀与目录名不符就是这么来的)。 ### C-703 文档不写对话残留 文档、注释只写「做了什么 / 为什么这么做」,不写对话来源、许可表述、商量转述。 - 依据:仓库审计规则 `documentation/comment-suspicious-detection`。 - 验证:提交前审计无该规则 blocker。 - 违反后果:审计拦截提交。 --- ## 修改记录 | 版本 | 变更 | | --- | --- | | 0.1.26 | C-105 补「backend 可省略、自动沿用最近 minihost 实例并给 backendSource」;C-108 补默认隔离 `DSH_HOME` 与 `--deps` 补依赖解析。 | | 0.1.25 | 新增 C-114:DSH 插件规则在 dsh-git-push(审计)与本插件(verify_plugin)两边同源维护、实现不耦合;9 条规则并入 verify_plugin 结论。 | | 0.1.24 | 新增 C-113:静默失败诊断只做离线组合(不启动宿主),`sync_plugin` 的权限收尾只改权限位、失败只记录;inject 校验 / 激活诊断 / 依赖体检 / HMR 判定并入 verify_plugin。 | | 0.1.23 | 新增 C-112:`ui_check` 是静态骨架(不执行代码、`visible` 恒 null、排除 DOM API、`resolved:true` 必须真有节点)。 | | 0.1.22 | C-601 CI 矩阵固定 `ubuntu-latest`(去掉 `windows-latest`):CI 是门禁,环境固定才稳定;跨平台能力由代码保留,Windows 未验证不进门禁。 | | 0.1.21 | C-110 明确「无内置预置」(定义只来自 quick-commands.json,首次为空表);C-601 单测入口改为 `test/run-tests.mjs`(自扫文件,不依赖 glob)。 | | 0.1.20 | C-110 补 `cwd` 语义(缺省宿主 cwd、必须存在且是目录)与跨平台要求(`bash -c` / `cmd /c`,预置 restart 仅 Linux/macOS);C-601 单测入口改为 Node 自身展开的 glob、CI 增加 `windows-latest`、明确测试不得依赖 shell 内置命令。 | | 0.1.16 | C-402 允许探测候选(环境变量/参数优先、不得作为唯一来源);C-601 测试数改 5 份;补记 0.1.9 / 0.1.10 / 0.1.13 三次契约内容更新。 | | 0.1.13 | C-109 补「bash 命令串也算读」与跨平台别名归一。 | | 0.1.10 | C-108 补路由只看 pathname 与 `services` 宿主服务注入。 | | 0.1.9 | C-107 补测试环境根配置驱动(参数 > 环境变量 > 插件配置 > 推算)。 | | 0.1.4 | 首版:把散落在 README 与代码注释里的设计约定收拢成编号契约,README 改为引用。 |