# 开发指南(development) > 本文件是本插件的仓库结构、特殊挂载限制、不可破坏约束、开发经验与测试策略的权威位置。 > 用户可见语义见 [`behavior.md`](behavior.md),架构边界见 [`architecture.md`](architecture.md),发布清单见 [`release.md`](release.md)。 ## 仓库结构 | 路径 | 职责 | | --- | --- | | `src/index.ts` | host 插件入口与运行时装配 | | `src/*.ts` | provider、MCP、凭据、设置与热重载模块 | | `src/client.ts` / `src/client-logic.ts` | 浏览器设置卡片及纯状态逻辑 | | `src/bin/` | CLI 与 patch 行管理源码 | | `src/tests/` | `node:test` 测试源码 | | `cordis.patch.yml` | 随包发布的持久 bundle patch | | `lib/` / `bin/` / `.tsbuild/` | 构建产物,由 `npm run build`、`prepare` 或 `prepack` 生成 | | `docs/` | 按主题拆分的权威文档 | `src/` 是唯一手写程序源码;`lib/`、`bin/` 与 `.tsbuild/` 只能由构建命令生成。 ## 热路径选择(智谱专属例外) 本插件的首次挂载因复杂 patch 存在额外限制: | 场景 | 本插件正确通道 | 验证方式 | | --- | --- | --- | | 首次热挂载 | 使用项目 CLI 的桥接/临时行策略;不可仅凭 dsh-zh manifest reconcile 假定成功 | CLI `status` 之外,检查实际 Loader、provider registry 和现有 GUI | | 自监视尚未安装 | 一次性动态插件对目标文件执行 `hmr.stashed.add(fileUrl)` 与 `hmr.partialReload()` 引导 | 确认真实目标路径进入 `hmr.configs` | 原因:`cordis.patch.yml` 除 `insert` 外还含 `web.config`,而 dsh-zh 的简单 manifest `hotMount` 只接受 `insert → id + name`。本插件 Host 必须保持单文件 bundle,query URL 才能可靠刷新入口;Loader 自身 `loadCache` 不会自动清除。 ## 不可破坏的约束 ### 包与 client bundle 1. `package.json` 必须保留本包的 `dsh-zhipu` CLI、`.` / `./client` / `./cordis.patch.yml` / `./package.json` exports,以及 `dsh.bundle.patch` / `dsh.client` 声明。 2. `dsh.client.inject` 填客户端**包名依赖**,浏览器插件 `exports.inject` 填 Cordis **服务名**,二者不可混用。 3. `lib/client.js` 由本项目 Client 构建生成经典 `window.__ModuleLoader__.load(...)` bundle,不能手改。 ### 挂载、注册与卸载 4. 行 id 不得复用:持久行 `dsh-zhipu`、临时热行 `dsh-zhipu-hot`、桥接行 `dsh-zhipu-bridge`。 5. `cordis.patch.yml` 必须解析为顶层数组;清空最后一个条目后写回 `[]`,不能只剩注释或 `null`。 6. 三通道迁移窗口中的预期重复注册可幂等跳过,但不能吞掉其它异常;桥接卸载按 `entry.options.id` 删除对应 Loader entry。 7. 桥接代码访问 `ctx.loader` 必须声明 `inject: ['loader']`;`loader.create({ id, name })` 的 `name` 必须是包名,不能是 file URL。 ### 凭据与设置 8. `dshHome()` 的非空 `$DSH_HOME` → `~/.dsh` 回退不可删除;三层凭据解析的 API Key 永不写入配置、日志或错误信息。 9. 客户端只向 `settings.plugin.item` 注入本插件卡片并使用官方 `settingsScope`;不增加自定义网络请求、遥测或独立持久数据。 ## 实现经验 ### Schema 与工具调用 - 三个 `github_*` 和 scoped `web_search` 的 parameters/output 都使用纯 JSON Schema,不与 schemastery 简写混用;`required` 只放在对象层数组中。 - scoped `web_search` 只在原工具对目标 Agent 可见时建立阴影,并保持 1–4 查询、30 秒预算、轮询合并与同批失败取消;结果上限由本插件定为 **40**(= 上游单查询硬上限 10 条 × 4 查询,一次调用内全量透传,高于内置 tool-web 的 8),不能因改说明文字而退化执行契约;若下游要下调上限,注意超过该值时轮询合并会真实丢弃来源并置 `truncated`,用户会看到「来源列表已截断」。 - `repo_name` 的执行校验保持 `owner/repo`;历史展示可降级,但不能放宽实际 schema。 - 工具参数变更至少走一次 DSH 真实调用,确认实际注册的 schema,而不只验证 TypeScript。 ### 回退传输 - `http-fallback` 不得退化为裸 `fetch(..., { redirect: 'follow' })`:必须保留 HTTP(S)/凭据/显式私网 URL 校验、同源重定向、超时、响应字节/字符上限与文本内容类型检查。 - 调用方取消与插件本地超时必须分开分类;search/reader 的 MCP 失败映射为 `WEB_PROVIDER_ERROR`,仓库工具使用 `ZHIPU_PROVIDER_ERROR`,结构化内容过滤保持 `ZHIPU_CONTENT_FILTERED`。 ### 跨端设置 - 设置卡订阅建立后立即按当前快照渲染;host 与 client 各自维护默认值时,改名后全仓库搜索旧名和新名,避免六个设置字段跨端漂移。 ### 安装与解析 - 持续开发使用 CLI 的 `--link <项目路径>`;`.npmrc` 的 `omit=peer` 用于避免 link 安装时在插件根生成第二份宿主 peer。 - 不在文档或脚本硬编码本机绝对路径。 - Open Design 使用独立 `open-design` profile;安装/验收不能以 `headless` 代替。其 probe/models/stdio stdout 是严格 JSONL,`src/index.ts` 的信息日志在该 profile 必须写 stderr,不能污染协议帧。 ### 上游异常分类 宽泛搜索可能被上游以 `contentFilter` 拒绝,也可能返回 `No results found`。这不是判断协议参数错误的充分依据。实现只识别结构化过滤信号并映射稳定错误码;具体用户行为以 [`behavior.md#搜索工具的接管与说明替换`](behavior.md#搜索工具的接管与说明替换) 和 [`behavior.md#错误码速查`](behavior.md#错误码速查) 为准。 zread 上游对未收录/不存在的仓库在 `tools/call` 的 `isError` content 中返回双层 JSON 错误(内层 msg 含 "repo not found")。`mcp-http` 将其映射为 `ZHIPU_REPO_NOT_FOUND` 固定文案;正则漏检时安全降级为 `ZHIPU_PROVIDER_ERROR`,不放宽脱敏边界。 `github_*` 工具执行路径的错误消息(ZHIPU_DISABLED / ZHIPU_CREDENTIAL_MISSING / ZHIPU_PROVIDER_ERROR / ZHIPU_REPO_NOT_FOUND)随 `zhPrompt` 切换:默认英文,开启后中文;`zhPrompt` 经 `HttpOptions` 与 `resolveApiKey` 的 language 参数传入,search/reader 路径不传保持中文。 上游 `web_search_prime` 无结果条数参数(schema 实测,`additionalProperties:false`),单查询上限恒为 10 条,超量返回固定发生;搜索 provider 按 `request.maxResults` 预裁剪再返回,使 seam 的 `capSources` 不触发(seam 的 `truncated` 语义限定为 seam 自身丢弃来源,provider 预裁剪时报告 false)——对齐官方 Exa provider 的行为,UI 不再出现「来源列表已截断」。 ## 测试策略 - **纯逻辑单测**:覆盖设置归一化、schema、凭据文件解析、patch 行、DeepSeek/HTTP 回退策略和搜索合并边界。 - **假 Context 装配测试**:验证 provider、Agent scope、动态装卸、HMR、可逆清理,以及 `open-design` profile 信息日志不进入 stdout。 - **mock MCP 端到端**:承接真实 initialize → initialized → tools/call → DELETE 请求形状,覆盖 search/reader 主路径、SSE/JSON、错误映射、取消、超时和清理。 - **构建契约验证**:检查产物存在、Node 语法、经典 client bundle 格式和 CLI usage。 - **真实管线验收**:Web 用现有 GUI;Open Design 用 `dsh --profile open-design --probe/--stdio` 检查 stdout 纯 JSONL,再检查新 `od-*` 会话首个 `request/header`。不得为验收重启 DSH。 ## 验证分层 本插件提交审核前至少运行 `npm run typecheck`、`npm run build`、`npm test`、`npm run verify`;发布前再执行 [`release.md#发布前验证`](release.md#发布前验证) 的完整序列和打包检查。 若任一步失败,先定位失败原因;不要用重启、跳过检查或重复执行掩盖问题。 ## 安全审计(2026-09) 项目专属要点(完整清单见工作区根 `docs/audit-2026-09.md`,勿在此复制): - 已确认高危:HTTP 回退无 DNS rebinding 防护(公网域名二次解析到内网不设防,需连接前解析并 pin IP);duplicate 注册被当成功返回 no-op disposer(HMR/双行下旧 Fiber 卸载后 provider/tool 永久消失)。 - 中危:回退超时不覆盖 `credentials.resolve`(卡死占满并发槽);MCP 错误把上游响应体原样进 error/cause(条件性凭据泄漏);外部标题/正文/URL 直接拼 Markdown 工具输出(无不可信内容隔离);CLI `--profile` 未校验。 - 正面范例(保持):`self-hot-reload.ts` 全能力探测+静默降级;`web.config` 复杂 patch 不走 dsh-zh simple reconcile(安装只走项目 CLI 全量通道)。