Agent Skills › PC2005-cloud/dsh-pet › dsh-plugin-comment-spec

dsh-plugin-comment-spec

GitHub

规范DSH插件代码注释的编写、评审与迁移,强调契约式TSDoc、模块头职责说明及工具校验,旨在提升API可用性与代码可维护性。

.agents/skills/dsh-plugin-comment-spec/SKILL.md PC2005-cloud/dsh-pet

触发场景

编写新插件代码注释 评审现有代码注释质量 迁移旧代码至新注释规范

安装

npx skills add PC2005-cloud/dsh-pet --skill dsh-plugin-comment-spec -g -y
更多选项

非标准路径

npx skills add https://github.com/PC2005-cloud/dsh-pet/tree/main/.agents/skills/dsh-plugin-comment-spec -g -y

不安装直接使用

npx skills use PC2005-cloud/dsh-pet@dsh-plugin-comment-spec

指定 Agent (Claude Code)

npx skills add PC2005-cloud/dsh-pet --skill dsh-plugin-comment-spec -a claude-code -g -y

安装 repo 全部 skill

npx skills add PC2005-cloud/dsh-pet --all -g -y

预览 repo 内 skill

npx skills add PC2005-cloud/dsh-pet --list

SKILL.md

Frontmatter
{
    "name": "dsh-plugin-comment-spec",
    "metadata": {
        "category": "development",
        "language": "zh"
    },
    "whenToUse": "编写或修改 DSH 插件(宿主半侧 \/ 浏览器半侧 \/ 独立模式 \/ 桌面 helper)代码、补 TSDoc 与 API 文档、评审注释质量、清理横幅式或叙述式注释、为注释引入 lint 或文档门禁时。",
    "description": "为 DSH 插件编写、评审或迁移代码注释时使用:以模块头、声明级 TSDoc 契约和少量 why 注释为核心,统一标签、语言与校验方式。",
    "user-invocable": true,
    "disable-model-invocation": false
}

DSH 插件注释规范

目标

让调用方不读实现就能正确使用插件 API,让维护者只读必要信息就能安全改动代码,让注释可以被工具校验、并且不会随实现漂移。

三条基线:

  1. 注释是契约,不是散文。 每条注释只回答类型和命名回答不了的问题:边界、不变量、副作用、为什么这样做。
  2. 一处事实只写一遍。 能从代码、类型、OpenAPI、schema 生成的信息,不手写第二份。
  3. 默认单行,复杂才展开。 先改结构,再写注释;注释不能弥补过大的文件或过深的函数。

速查(十行版)

场景 写什么 写在哪
新文件(入口/驱动/带状态 hook) 职责、非目标、依赖、不变量 文件头 4–8 行
新文件(普通模块) 一句话职责 文件头 1 行
新文件(纯函数叶子) 可省略:导出自身有完整文档时省略 —
导出符号 一行摘要,必要时加标签 声明上方
接口字段 语义不显然才写(配置项、单位、互斥、缺省) 字段上方,1 行
复杂函数 摘要 + @param / @returns / @throws 声明上方,≤ 12 行
私有成员 / 内部类 一行摘要,不承诺兼容 声明上方
非显然实现 为什么、权衡、必须的顺序 行内 1–4 行
生成文件 @generated、生成命令、禁止手改 文件头
凭据相关 来源、生命周期、能否进日志 使用处一行
待办 TODO(#123, @owner): 动作 行内
背景与决策 链接 docs/ 或 ADR 模块头一行

适用范围

适用于 DSH 插件仓库里的手写源码:

  • 宿主半侧(host):Cordis 插件入口、服务注册、路由、配置合并、状态维护。
  • 浏览器半侧(client):./client 入口、React 组件、设置页、渲染与轮询。
  • 独立模式(standalone / CLI):参数解析、伪 ctx、本地 server、shim。
  • 桌面 helper(Electron / runtime):主进程、preload、renderer、窗口与素材调度。

不适用于:lib/ 构建产物、*.d.ts 生成声明、node_modules、以及 package.json 这类无法写注释的清单文件(这类文件的说明放 README)。

其他语言的项目可以复用分层、禁止项与流程,只把标签换成对应生态(例如 Java 用 Javadoc)。

第一原则:先判断“该不该写”

落笔前对每句话做三次判断:

  1. 能否从签名或命名直接得到? 能——删掉。/** 用户名 */ name: string 是噪音。
  2. 描述的是当前事实,还是历史与计划? 历史——删掉或进 CHANGELOG;计划——进 issue;只保留“现在必须成立”的约束。
  3. 下次改代码时它会不会失效? 会——改成约束、改成链接,或者不写。注释的价值不在于“写了”,而在于“仍然为真”。

一句话检验:未来的读者能否用代码或测试验证这句话? 不能验证的叙述,多半不该留在注释里。

否定式注释

模块头允许且鼓励写一条非目标;函数级与字段级默认不写。

落笔前过三问,三问都否就不写:

  1. 越界是否低成本? 不新增 import、不跨类型边界就能顺手写进去。
  2. 读者是否会误会? 名字与上下文没有暗示这条边界。
  3. 是否出过事故? 有过真实的错放或返工。

反例(写了等于噪音):

  • // 本文件不是工具函数集合。 —— 读者不会这样以为。
  • /** 不负责持久化。*/ 挂在 parseX() 上 —— 函数体已经说明了。
  • const ts = Date.now(); // 这里是毫秒而不是秒 —— 应该直接改名 tsMs。

正例(写了才有价值):

  • 配置写入口:不重启桌面 Helper —— 上层曾直接调重启,属跨层顺手错。
  • 事件监听模块:不订阅事件(订阅在装配层)—— 名字叫 watch 却不订阅,反直觉。

变量层面的“是 A 而不是 B”:只在两个相似概念并存时写,且优先用命名区分;命名能说清就不写。

注释分层

层级 位置 必须写什么 长度上限 校验方式
模块头(入口/驱动/带状态) 文件顶部 插件角色、职责、非目标、依赖关系、关键不变量 4–8 行 review
模块头(普通模块) 文件顶部 一句话职责 1 行 review
模块头(纯函数叶子) 文件顶部 可省略 0 行 review
导出声明 export 的函数、类、常量、类型 一行摘要,按需加标签 1–7 行 tsdoc / api-extractor
接口字段 语义不显然的字段 一行语义 1 行 review
私有成员 内部类字段、私有方法、模块内常量 一行摘要,不承诺兼容 1 行 review
复杂函数 参数、返回、异常不显然时 摘要 + @param / @returns / @throws ≤ 12 行 eslint
生命周期 apply / inject / dispose / ctx.on 注册了什么、依赖什么、何时清理 1–4 行 review
实现体内 非显然分支、魔数、外部怪癖 为什么这样做、为什么不能那样做 1–4 行 review
生成文件 文件顶部 @generated、生成命令、禁止手改 3 行 review
工具指令 eslint-disable / v8 ignore 指令 + 原因 1 行 lint

模块头

按文件角色分三档,不要一律写成四段式:

角色 写什么 长度
入口、装配、服务、驱动、带状态的 hook 负责什么、不负责什么(最多一条)、依赖谁、什么必须始终成立 4–8 行
普通模块(一组相关函数或一个组件) 一句话职责,必要时补一条非目标 1 行
纯函数叶子(换算、格式化、纯决策) 省略;文件只有一个导出且该导出已有完整文档时也应省略 0 行

有 @module 时写在首行或末行,只有入口与驱动类文件值得带它。头不是表格:一句话说得清就不该写成四行。

不要写:路由清单、字段清单、配置项清单、变更历史、ASCII 图、目录结构。这些内容要么能生成,要么属于文档站,写进注释就是第二份真相。

/**
 * 宿主半侧:把 <能力> 注册为 DSH 服务与路由,并维护其内存状态。
 * 不负责渲染与素材;浏览器半侧经 /<prefix>/state 轮询读取。
 * 契约见 openapi.yaml;配置只经 readAllConfig 读取,本模块不碰文件。
 * @module dsh-plugin-<name>
 */

超过 8 行通常不是“需要更多说明”,而是“这个文件该拆了”。

导出声明

package.json 的 exports(.、./client)指向的入口,其导出就是插件的公开面:每个符号必须有摘要;字段按下一节的判据写。

摘要用祈使句或名词短语、现在时、句末句号;用中文写,标识符保持原文。不要重复名称和类型。

/** 解析不带前导斜杠的命令名;不合法时返回 undefined。 */
export function parseCommandName(line: string): string | undefined;

/** 轮询状态的唯一数据源;counter 变化才触发渲染。 */
readonly state: PollState;

接口字段

只写语义不显然的字段。以下四类必须写:

  • 配置项与开关:它改变什么行为,以及缺省值。
  • 数值:单位与量纲(_MS / _PX / _BYTES),以及取值范围或夹取规则。
  • 互斥或二选一:另一侧是什么,由谁保证只出现一个。
  • 跨模块契约:谁写、谁读、失败或缺失时怎么降级。

坐标、尺寸、计数、名字这类字段名已经说清的,不写。给几十个字段逐个补一行,读起来更像表格而不像契约:真正需要解释的字段会被同款句式淹没。

私有成员与内部类

内部类、私有字段、模块内常量与导出符号用同一标准,但只写一行摘要,不写完整标签,也不承诺兼容性:

/** 当前实例的注册层级;层级高的定义遮蔽全局定义。 */
private readonly layers: ScopedLayers;

/** 单进程内生成 commandId 的序号,重启后从 0 开始。 */
private commandSeq = 0;

需要明确“不对外”时加 @internal;能被外部直接 import 到的符号一律按导出标准写。

复杂函数

只在“读完签名仍有疑问”时展开。结构固定:摘要一行 → 空行 → 标签区。

  • @param name - 语义:写含义、可选性、缺省值、边界,不写类型。
  • @returns:写边界值(空、undefined、默认值、部分成功)。
  • @throws:写触发条件与错误类型;不抛错就不写。
  • {@link Symbol}:引用相关符号,不写“见 xxx.ts:123”。
  • @example:只给非常规用法,一个函数最多一个。
/**
 * 注册一个插件命令。
 *
 * @param definition - 发现元数据与处理器;同名重复注册会被拒绝。
 * @returns 精确注销该定义的 disposer。
 * @throws {TypeError} 名称不符合 `<规则>` 时。
 */

泛型

类型参数不显然时用 @typeParam 写约束与用途,不重复 extends 已经表达的内容:

/**
 * 按配置键读取值。
 *
 * @typeParam K - 配置键;返回类型由该键推导。
 * @param key - 已声明的键。
 * @returns 对应值;键不存在时返回 `undefined`。
 */
export function readConfigValue<K extends keyof Config>(key: K): Config[K] | undefined;

重载

摘要与标签只写一次,放在实现签名或第一个重载上;各重载只在行为有差异时补一行 @remarks,不要把同一段说明复制三遍。

生命周期与注入

DSH 插件的特有位置,最容易被写成“复述框架”:

  • name / inject / provide / apply / dispose:各写一行,说明为什么需要这个依赖、注册了什么、清理什么,不要解释 Cordis 本身怎么工作。
  • ctx.on(...) / ctx.effect(...):注明监听器的生命周期与解除时机。
  • 路由处理函数:契约链接 OpenAPI,不逐字段列响应体。
  • 客户端组件:props 每个字段一行;useEffect 依赖只解释“为什么这个依赖不能删”。
  • 配置合并:默认值与优先级写进模块头,不逐字段写散文。

实现体内

只写五类内容:非显然的常量来源、反直觉的分支、性能或安全权衡、外部系统的怪癖、必须保持的调用顺序。

不写:逐行翻译、显而易见的判空、getter/setter 说明、变量含义复述。

1–4 行为限。超过 4 行说明需要更好的命名、更小的函数,或者把背景迁到 docs/ 并只留链接。

// 宿主的 dispose 是异步的:先摘监听再关窗,否则回调会打到已销毁的 webContents。

安全与凭据

涉及密钥、token、用户数据的代码,注释只写来源、生命周期、能否进日志,绝不写真实值;示例一律用占位符。

// key 只驻留内存,禁止写日志或落盘;错误信息里只保留后四位。

生成文件

提交进仓库的生成物(bundle、复制出来的 runtime 脚本)必须在文件头三行内声明:谁生成的、生成命令、禁止手改。

/**
 * @generated by `pnpm run build:desktop-core`,请勿手改。
 * 源文件:src/shared/index.ts。
 */

生成器不支持注入文件头时,在最近的手写入口或 README 里标注。

禁止项

反模式 为什么不行 替代做法
// ---- 配置 ---- 这类横幅 掩盖文件过大,制造合并冲突,代码搬走后常留在原地 拆模块,用文件与导出边界代替
**加粗**、## 标题、ASCII 图 编辑器不渲染,grep 与 diff 里全是噪音 纯文本;图进文档站
“改造前 / 旧实现 / 曾经” 无法验证,必然腐化 git、CHANGELOG、ADR
注释掉的代码 双重维护,读者不知道哪份有效 删除,靠版本库找回
行号或文件路径引用 文件一移动就失效 {@link} 或稳定文档链接
@param {string} name 名称 类型已由 TS 表达,纯冗余 只写语义
在注释里抄路由表、字段表、配置表 与实现漂移,且无人校验 链接 OpenAPI、schema 或生成物
无主 TODO 永远不会被处理 TODO(#123, @owner): 具体动作,或只留 issue
同一段注释中英混写 读者来回切换,术语容易漂移 统一用中文;英文只用于标识符与引用原文
为覆盖率凑注释 增加噪音,掩盖真正缺失的契约 宁可缺,不可假
// 本文件不是…、函数级“不负责…”、变量“是 A 而不是 B” 读者本来不会误会,属噪音 删;需要区分时改命名

标签规范

使用 TSDoc 语义(JSDoc 工具链兼容)。

标签 何时用 写法
@module 模块头 @module <包名>
@param 有参数且语义不显然 @param name - 语义
@typeParam 泛型参数不显然 @typeParam T - 约束与用途
@returns 有返回值且边界不显然 @returns 边界与缺省
@throws 会抛出异常 @throws {Type} 触发条件
@deprecated 计划移除 @deprecated 替代品与起始版本
@internal 不对外承诺 @internal
@remarks 需要成段说明 摘要之后的独立段落
@example 非常规用法 代码块,最多一个
@see / {@link} 关联符号或文档 {@link Symbol}

规则:

  • 顺序固定:摘要 → @remarks → @typeParam → @param → @returns → @throws → @example。
  • @param 的名字必须与签名完全一致;改参数名时同步改标签。
  • 标签之间不空行;@param 组与 @returns 之间不空行;与摘要之间空一行。
  • 中文句子用全角标点;代码片段、命令、路径内部的符号保持原样。
  • 不写 @param {type},类型交给 TypeScript。

语言与术语

注释一律用中文。代码标识符、类型名、API 名、命令名保持英文原文,不翻译;引用错误消息、日志、UI 文案时保留原文,不改写。

  • 语言跟随目标仓库。 本文示例用中文;移植或参考其他代码时不照抄语言,按目标仓库统一,同一个文件里不混写。
  • 全仓库统一中文叙述。 不要一句中文一句英文;专有名词直接用英文不算混写。
  • 一个概念一个词。 首次出现可括注英文,例如“成品配置(resolved config)”;同义词不要轮换。
  • 避免项目黑话。 必须使用时,在模块头定义一次;不要让新人靠猜。
  • 标点跟语义走。 中文句子用全角标点;代码片段、命令、路径内部的符号保持原样。
  • 标签名保持英文。 @param、@returns、{@link} 是工具语法,不翻译。

注释与测试的取舍

能被测试钉住的规则不写进注释。优先级表、边界换算、状态转移、降级分支,都写成断言;注释只留测试表达不了的三类:

  • 顺序:为什么必须按这个次序执行,换个次序会坏在哪。
  • 权衡:两个都可行的方案里为什么选这个,放弃了什么。
  • 外部怪癖:宿主或浏览器某处的行为反直觉,测试无法稳定复现。

同一条事实只出现在一处:测试已经断言了换算公式,模块头就不必再抄一遍,只写测试覆盖不到的约定(例如结果与耗时无关、不受输入顺序影响)。两边都写会漂移,且漂移时没人知道该信哪边。

长度与密度

  • 模块头按角色分档(入口 4–8 行 / 普通 1 行 / 纯函数叶子可省);声明摘要 1 行;函数文档 ≤ 12 行;体内 why 1–4 行。
  • 文档块中位数应为 1 行,p90 ≤ 7 行。≥ 10 行的块是“该迁文档”的信号,不是“写得更详细”的信号。
  • 注释行占比 10%–20% 属于常见区间。高于 25% 先查“是不是在写散文、是不是在掩盖结构”;低于 5% 先查“导出契约是不是缺失”。
  • 密度不是 KPI,不按行数考核,也不为了达标补废话。字段注释尤其如此:把显然的字段补满,会让真正需要解释的字段淹没在同款句式里。

写作流程

新增代码

  1. 新文件先按角色定模块头(入口/驱动 4–8 行;普通模块 1 行;纯函数叶子可省),再写 import。
  2. 每个导出符号写一行摘要;接口每个字段一行。
  3. 参数、返回、异常不显然时补标签;不显然的用法才加 @example。
  4. 只在非显然处写 why;能用命名和类型表达的一律不写。
  5. 提交前跑类型检查与 lint(若已配置 TSDoc 规则)。

修改代码

  1. 先判断这次改动让哪些注释失真;只改相关注释,不顺手重写无关注释。
  2. 删除已不存在的符号引用与失效示例。
  3. 行为变化同步 @returns / @throws;要删除导出,先标 @deprecated 保留一个版本。
  4. 新增分支若引入新约束,补一句 why,而不是补一段流程说明。

评审代码

  1. 按检查清单逐条过,把问题分成“错误、过期、风格”三类。
  2. 只对“错误、过期”要求必须改;风格问题给建议,不阻塞。
  3. 长注释只要求迁移到文档,不要求删掉信息。
  4. 发现“注释与实现矛盾”时,以代码为准,先改注释,再判断代码是否有 bug。

迁移存量

  1. 先删装饰:横幅、加粗、标题、ASCII 图。
  2. 再把 ≥ 10 行的块迁到 docs/,代码里留 2–3 行摘要加链接。
  3. 再补齐导出符号的标签。
  4. 最后开门禁:先 warn,稳定后再 error。一次只动一个维度,避免大 diff。

检查清单

  • 模块头与文件角色匹配(入口/驱动写全,纯函数叶子可以不写),回答了“负责什么、依赖谁、什么必须成立”?
  • 每个导出符号有摘要;接口字段只写了语义不显然的那些(配置项、单位、互斥、缺省),明显字段没被凑数?
  • 泛型参数不显然时写了 @typeParam?重载说明只写了一遍?
  • 生成文件是否标了 @generated 与生成命令?
  • 凭据相关注释是否只写来源与脱敏要求,没有真实值?
  • @param 名称与签名一致?返回与异常的边界写清楚?
  • 有没有重复类型、重复命名、重复实现细节?
  • 有没有横幅、加粗、图、历史叙述、行号引用?
  • ≥ 10 行的注释是否应该迁到文档?
  • TODO 是否带 issue 与 owner?
  • 同一事实是否只存在一处(没有复制 OpenAPI、schema、配置表)?
  • 叙述语言是否统一为中文(标识符与引用原文除外)?

门禁与工具(推荐,不强制)

  • eslint-plugin-tsdoc:校验 TSDoc 语法,直接 error。
  • eslint-plugin-jsdoc:require-jsdoc(仅 exported)、require-param、require-returns、check-param-names、check-tag-names、tag-lines,先 warn。
  • @microsoft/api-extractor:生成 .api.md 并在 CI diff,导出变更必须显式批准。
  • TypeDoc:--validation.notDocumented 把“导出无文档”变成失败。
  • CI 检查 TODO 格式:只接受 TODO(#\d+, @[\w-]+)。
  • 存量基线:门禁只对新增文件与 diff 行生效;存量问题用基线文件记录,指标只降不升,避免上千个存量块一次全红。
  • 迁移顺序:只读盘点 → warn → 修存量 → error。不要一次性全开。

指标(建议值,不作硬指标)

指标 建议值
导出符号文档覆盖 ≥ 95%
导出函数带 @param ≥ 90%
接口字段覆盖 不作指标:只覆盖非显然字段,写满反而降质
≥ 10 行注释块 0
横幅、加粗、ASCII 图 0
TODO 带 issue 与 owner 100%
文档块长度 p50 / p90 1 行 / ≤ 7 行
注释行占比 10%–20%

示例:一次改写

// ---- 配置模块 ----
// 改造前配置分散在三个文件,现在统一到这里
// 读取配置
export function readConfig() { /* ... */ }

改成:

/**
 * 配置的唯一读取入口:合并内置默认、用户层与环境变量。
 * 不负责写盘;写盘只经 saveUserConfig。
 * @module dsh-plugin-<name>/config
 */

/** 返回字段已填满的成品配置;解析失败时抛出,不返回半成品。 */
export function readConfig(): Config;

改动只有三件事:横幅换成模块头,历史叙述删掉,函数摘要写契约。没有增加信息量,但每句话都可验证。

守则

  1. 不为了指标写注释;宁缺毋滥。
  2. 不用注释弥补结构:文件过大先拆,函数过长先提,不写横幅。
  3. 有价值的 why 不删,迁移到 docs/ 或 ADR,代码里留链接。
  4. 一次只改一个维度:先装饰,再长文,再标签,最后门禁。
  5. 公开 API 变更必须同步注释与 API 报告。
  6. 拿不准时,用“未来读者能否验证这句话”决定去留。

版本历史

  • 38ae846 当前 2026-11-02 08:57

同 Skill 集合

.agents/skills/dsh-pet-dev/SKILL.md
.agents/skills/dsh-plugin-standards/SKILL.md
.agents/skills/git-commit/SKILL.md
.agents/skills/task-guardrails/SKILL.md

元信息

文件数
0
版本
38ae846
Hash
a959a8a4
收录时间
2026-11-02 08:57

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-11-03 01:07
浙ICP备14020137号-1