dsh-plugin-review · git:20261105.b43a092 · 2026-11-05 · sha256 f4389c6e71481596

dsh-plugin-review git:20261105.b43a092A

Immutable. This exact content never changes and is served at /api/v1/blob/f4389c6e71481596.

---
name: dsh-plugin-review
description: DSH 插件代码审查与防御性架构审核车道。基于 Git 范围或工作区源码,检查 Cordis 生命周期泄漏、未逆向注销、单例状态漂移、已废弃模板变量(如 {{cwd}})、Vendored Peer 依赖污染、准入账本覆盖,按 P0/P1/P2/P3 输出精炼结构化报告;提供子 Agent 隔离委派协议(Sub-agent Dispatch Pattern),在后台沙箱运行紧凑分析并回传精炼结果,防止主会话上下文污染。触发词:代码审查、代码审核、code review、插件体检、防御性审查、生命周期检查、单例泄漏、Peer污染、子Agent审查、紧凑报告。
---

# 代码审查与防御性架构车道

在插件适配或交付前,对代码进行深度架构审计。重点排查 Cordis 生命周期泄漏、单例漂移、Vendored Peer 依赖污染以及废弃 API,按统一严重度(P0/P1/P2/P3)给出明确判定。

同时定义**子 Agent 隔离委派协议(Sub-Agent Dispatch Pattern)**:当面对复杂插件或批量巡检时,主 Agent 派发独立子 Agent 执行重型静态分析与冒烟,仅回传结构化紧凑报告(`<20` 行),避免海量日志填满主会话上下文。

以下命令都相对工具根目录执行,工具根即本文件真实路径往上两级、含 `scripts/` 的那一层。脚本用自身文件的绝对路径定位,不必先 cd。`<插件目录>` 指被查插件的仓库根。

## 第一步:会话内极速体检

```sh
node scripts/plugin-audit.mjs <插件目录> --compact
```

使用 `--compact` 参数时,脚本仅输出高密度结构化 JSON 摘要与关键结论,屏蔽全量文件遍历噪音:

```json
{
  "plugin": "my-plugin@1.0.0",
  "overallScore": 95,
  "scores": { "performance": 100, "resilience": 90, "contextBudget": 100, "lifecycle": 90 },
  "priorityCounts": { "P0": 0, "P1": 1, "P2": 0, "P3": 0 },
  "blockers": [
    {
      "priority": "P1",
      "dimension": "lifecycle",
      "file": "lib/index.js",
      "title": "事件监听或定时器未绑定清理逻辑",
      "detail": "Cordis 插件重载时未清理的监听器与定时器会泄漏,建议注册进 ctx.effect 或提供 dispose。"
    }
  ]
}
```

## 第二步:子 Agent 隔离委派协议(Sub-Agent Dispatch Pattern)

当审查整个插件仓库或运行全流程适配验证时,主 Agent 应按以下协议委派后台子 Agent:

1. **派发子 Agent**:通过 `invoke_subagent` 或独立终端沙箱唤醒子 Agent,指定只读工作区;
2. **执行紧凑流水线**:
   ```sh
   node scripts/run.mjs <插件目录> --compact
   ```
3. **回传协议**:子 Agent **严禁**直接复制全量调试日志或终端文本到父会话,必须提炼为以下三段式紧凑摘要返回:
   - **通过性总账**:宿主版本、窗口、体检分(各维度分)、冒烟与 typecheck 状态;
   - **阻断项账本**:仅列出 `P0`(致命阻断)与 `P1`(严重缺陷)条目(文件 + 标题 + 根因);
   - **下一步行动**:具体的修改建议(如 peer 替换、dispose 补充、模板变量清理)。
4. **主 Agent 决策**:主会话上下文保持清爽,主 Agent 仅依据紧凑报告决定修改代码或交付用户。

## 第三步:四级严重度标准(Priority Standards)

| 级别 | 定义与影响 | 典型案例 | 处理原则 |
| --- | --- | --- | --- |
| **P0** | **阻断发版/启动失败**:宿主启动必崩溃或类型严重分裂 | 强 import 已废弃包、提示词含已移除 `{{cwd}}` 抛错、核心 Peer 依赖写在 `dependencies` 产生 Vendored 污染 | 立即阻断,必须清零后才能交付 |
| **P1** | **资源泄漏/状态异常**:运行时静默失效或跨会话污染 | `ctx.on` / `setInterval` 缺失清理逻辑、准入账本范围未覆盖目标宿主、高频热路径同步 I/O 阻塞 | 适配阶段应优先修复 |
| **P2** | **架构异味/上下文开销**:不影响当前运行但存在隐患 | 模块顶层定义可变全局集合(Map/Set/数组)重载残留、无字节上限文件读取、工具 Schema 文件偏大 | 建议重构,顺带优化 |
| **P3** | **规范建议/提示**:声明性约定或类型维护 | `import type` 引用即将变动包、工具描述中引用已消亡宿主路径、peer 声明严格 semver 提示 | 人工知悉,按需调整 |

## 常见缺陷与标准修复模式

### 1. 核心 Peer 依赖污染(P0)
- **错误**:`package.json` 的 `dependencies` 里声明了 `"cordis": "^4.0.0"` 或 `"@deepseek-ai/dsh": "^0.2.1"`;
- **修复**:将其移至 `peerDependencies`,并在 `devDependencies` 保留开发类型支持。

### 2. 严格模板变量移除(P0)
- **错误**:系统提示词模板中包含 `{{cwd}}`;
- **修复**:删除模板中的 `{{cwd}}` 子句,直接依赖 DSH 运行时每轮注入的上下文。

### 3. Cordis 生命周期清理(P1)
- **错误**:`ctx.on('chat', handler)` 未保存 dispose 句柄;
- **修复**:改用 `ctx.effect(() => { ctx.on('chat', handler) })` 或在 `ctx.on('dispose', () => { ... })` 中解绑。

### 4. 语言缺失不等于删除(P1,来自生态实证)

- **错误**:写 `if (!name) delete ctx.services[name]` 或把空值当成「没配置」跳过,从而连带丢掉合法的空值配置;
- **机制**:空值与缺失是两件事。上游一个真实事故是工具导出索引式配置块时把空值项丢掉,导致从 CLI 启动编辑器后整条 Git 发现链断掉。本生态里对应的是「`Config` 给了默认值却被当成未声明」「`inject` 空数组与不声明混为一谈」;
- **修复**:要区分 `undefined` 与 `''` / `[]` / `0` / `false`;删除判断用「键是否存在」而不是「值是否真」。

### 5. 注册表按名字写入前先查重(P2)

- **错误**:注册工具、服务、命令、slot 时直接覆盖同名条目;
- **机制**:多数宿主的注册表是后写覆盖先写且不报警,两个插件抢同一个名字时表现为「其中一个静默失效」,排查成本极高。上游多个 Agent 宿主已把这条从「静默覆盖」改成「拒绝重复注册并快速失败」;
- **修复**:注册前查重,冲突时抛明确错误或加命名空间前缀,不要静默覆盖。

### 6. 环境变量擦除按变量组原子处理(P1,本工具自身实录)

- **错误**:按名字过滤环境变量(例如「凡名字含 KEY/SECRET/TOKEN 就删」)却只删了组里的部分成员;
- **机制**:git 的索引式配置是一个**原子组**(`GIT_CONFIG_COUNT` + `GIT_CONFIG_KEY_n` + `GIT_CONFIG_VALUE_n`),缺任一键就整体拒跑并 fatal。宿主的子进程凭证擦除正好会摘掉 `KEY_n`(KEY 三个字母既在 git 配置通道名里、也在密钥字段名里),留下计数与值,于是一切 git 子进程全挂;
- **修复**:擦除逻辑按组原子处理;需要起子进程的一方在**自己的边界**上净化(本工具的做法见 `scripts/lib/proc-env.mjs`),不要假设上游环境一定干净。

### 7. 删路径不要跟随链接(P2,常设规则锚点)

- **错误**:对可能是软链/junction 的路径用 `rmSync(p, { recursive: true })`;
- **机制**:POSIX 上会穿过链接删掉目标内容(纯数据丢失),Windows 上对 junction 抛 `ERR_FS_EISDIR`;
- **修复**:先 `lstatSync().isSymbolicLink()`,链接用 `unlinkSync`(只删链接本身、拒绝真实目录),真实目录才递归删。本工具的实现是 `removeDirSafely`。

## 常设规则从哪读(不复制进技能)

上表第 4/5/7 条不是自己编的,出处是上游仓库 `docs/defensive-patterns/`(五篇:package-surface / general / client-components / conversation-and-session / remote-and-api)。那一层是**不随版本变的硬要求**,`docs/upgrade-guide/` 是**随版本变的破坏性变更**,两者互不覆盖:只扫升级指南会漏掉「从来没变过但插件一直违反」的那一半。

读法:先按上表定位到大致类别,再去对应那一篇核对规则原文、判定条件、边界与例外,**以原文为准**;技能里这几条只是指路牌,不是规则的副本——上游改了原文,这里不该跟着漂移。