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

dsh-plugin-comment-spec

Skill5.9k starsChanged 15 days agoMITView on GitHub

为 DSH 插件编写、评审或迁移代码注释时使用:以模块头、声明级 TSDoc 契约和少量 why 注释为核心,统一标签、语言与校验方式。

Adopted by

Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data

Week of Sep 13: 0 new projectsWeek of Sep 20: 0 new projectsWeek of Sep 27: 0 new projectsWeek of Oct 4: 0 new projectsWeek of Oct 11: 0 new projectsWeek of Oct 18: 0 new projectsWeek of Oct 25: 0 new projectsWeek of Nov 1: 0 new projects
Sep 2026Oct 2026Nov 2026
New projects taking a version of this file each week, over the 8 weeks our latest run read: only 0 in all, so this is a hint, not a trend.

Who's talking about PC2005-cloud/dsh-pet

The biggest 5 of 14 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 14

Talked about on GitHub and Bluesky (14 times).

On GitHub · 13

8 more on GitHub

The file

Files
SKILL.md

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 与 owner100%
文档块长度 p50 / p901 行 / ≤ 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. 拿不准时,用“未来读者能否验证这句话”决定去留。

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.