DSH / Atlas
2026-07-04implementedprocessarchived 2026-07-27

JSDoc completeness gate for the cordis surface

针对 Cordis 对外服务接口的 JSDoc 完整性门禁

The generated Cordis catalog enforced event dispatch modes but not complete service and event contracts. Methods could lack descriptions, and parameters or returns could be undocumented on the cross-plugin API surface where IDE guidance matters most. The AGENTS.md rule ("every export has a JSDoc explaining semantics") is prose-checkable only by review; the repo's stated preference is to encode invariants in mechanica

English

Problem

The generated Cordis catalog enforced event dispatch modes but not complete service and event contracts. Methods could lack descriptions, and parameters or returns could be undocumented on the cross-plugin API surface where IDE guidance matters most.

The AGENTS.md rule ("every export has a JSDoc explaining semantics") is prose-checkable only by review; the repo's stated preference is to encode invariants in mechanical gates. The scope "cordis service functions and events" has a precise machine definition that only the catalog generator knows: events are the interface Events members inside declare module 'cordis', and the service surface is the public methods of the class each interface Context key names. An ESLint rule cannot see that mapping; the generator computes it on every run.

Decision

Extend scripts/gen-cordis-catalog.ts — the same walk, the same @mode precedent — to enforce JSDoc COMPLETENESS on everything it catalogs. verify-cordis-catalog runs inside doc-sync, so relevant documentation changes and CI exercise the same gate without separate wiring.

The contract:

  • Events need description prose plus a non-empty @param for every payload parameter. A payload parameter is a signature parameter that carries event data; the this receiver annotation and the trailing waterfall next are exempt — next is dispatch machinery whose semantics the @mode waterfall tag (and its structural cross-check) already owns, so restating it per event would be boilerplate. Documenting an exempt parameter anyway is allowed; only absence is checked.
  • Service classes need class-level JSDoc, and every public method needs description prose, a non-empty @param per parameter, and a non-empty @returns unless the annotated return type is void/Promise<void> (where @returns stays optional — resolution timing can be worth documenting — but is never required).
  • Stale tags error: an @param naming no real parameter is a violation, mirroring the @mode-contradicts-signature check. Tag descriptions must be non-empty; their semantic quality beyond that is review's job.
  • Explicitness the walk can check: the gate is a pure-AST pass (no type checker), so a service method must annotate its return type (an inferred return cannot be classified) and surface parameters must be simple identifiers (a binding pattern has no name for @param to match).
  • Violations aggregate into one error listing every offender — a remediation pass sees the whole list at once. The previously fail-fast @mode checks moved into the same aggregated report, with their message texts unchanged.

The generator keeps two views of the same source comment: parseJsDoc ends entry prose at the first block tag, while the ts cordis-catalog signature block includes the original JSDoc with @param, @returns, and @mode intact. Readers therefore see the complete source contract without block-tag text leaking into the surrounding prose.

Negative-path tests in packages/core/agent/tests/gen-cordis-catalog.spec.ts drive collectEvents/collectServices against synthetic fixtures to prove each guard fires and that the exemptions hold. The authoring rule lives in the root AGENTS.md conventions bullet alongside the @mode rule.

Alternatives considered

  • An ESLint rule — cannot see the scope's machine definition (which interface Events members and which ctx.<key> classes are the cordis surface); the catalog generator computes exactly that mapping on every run, so the gate lives there.
  • Expanding every method into a separate prose section — rejected: the catalog stays skimmable by keeping one service section and one signature block, while the JSDoc attached to each declaration preserves the full method contract in place.
  • An escape-hatch tag — none exists; the surface is small and curated (12 services, 57 methods, 27 events at adoption), and the point is that the check cannot be waved off.

Consequences

  • A new event or service method cannot land with an undocumented parameter or result: the generator refuses to regenerate and verify-cordis-catalog fails doc-sync and CI. The ~139 gaps found at adoption were filled in the same change, so the gate landed green.
  • The service surface must annotate return types explicitly and use identifier parameters. Neither constraint bound at adoption (every method already annotated; no destructured seam parameters existed); both are now load-bearing requirements a violating change will discover mechanically.
  • The general AGENTS.md JSDoc rule ("one-liners when one line suffices") acquires a stricter carve-out on this surface: a one-line summary still suffices only when the method has no parameters and a void result.
  • @param on next or this stays legal but unchecked — a deliberate asymmetry: the gate enforces the payload contract and refuses to demand boilerplate.
  • Each generated event or method fragment carries its original JSDoc, while the prose summary remains tag-free. Source edits therefore refresh both the readable index and the exact contract shown beside the signature.

中文

问题

生成的 Cordis 目录此前强制了事件分发模式,但未强制要求完整的服务与事件契约。方法可以缺少描述,参数或返回值可以在跨插件 API 接口上不写文档——而这恰恰是 IDE 引导最重要的地方。

AGENTS.md 中的规则(「每个导出都有解释语义的 JSDoc」)只能靠评审以行文形式检查;本仓库的既定偏好是将不变式编码为机械门禁。「Cordis 服务函数与事件」这一范围有精确的机器定义,只有目录生成器知道:事件是 declare module 'cordis'interface Events 的成员,服务接口是每个 interface Context 键所指向的类的公开方法。ESLint 规则看不到这层映射;生成器在每次运行时计算它。

决策

扩展 scripts/gen-cordis-catalog.ts——复用同一次遍历和同一套 @mode 先例——对它编目的所有内容强制执行 JSDoc 完整性要求。verify-cordis-catalogdoc-sync 内运行,因此相关文档变更和 CI 会执行同一门禁,无需另行接线。

契约如下:

  • 事件需要描述性文字,以及为每个载荷参数提供非空的 @param。载荷参数是携带事件数据的签名参数;this 接收者注解和尾部的 waterfall(瀑布式事件) next 免检——next 是分发机制,其语义已由 @mode waterfall 标签(及其结构交叉检查)拥有,逐事件重述只是样板代码。为免检参数写文档是允许的;只有缺失才被检查。
  • 服务类需要类级 JSDoc,每个公开方法需要描述性文字、为每个参数提供非空的 @param,以及非空的 @returns——除非标注的返回类型是 void/Promise<void>(此时 @returns 可选——resolve 时机有时值得记录——但从不强制要求)。
  • 陈旧标签报错@param 命名了一个不存在的参数即为违规,与 @mode 与签名矛盾的检查对称。标签描述必须非空;超出此范围的语义质量由评审负责。
  • 遍历可检查的显式性:门禁是纯 AST 遍历(不使用类型检查器),因此服务方法必须显式标注返回类型(推断的返回类型无法分类),接口参数必须是简单标识符(解构模式没有名称供 @param 匹配)。
  • 违规聚合为一条错误信息,列出所有违规项——修复时一次看到完整清单。此前快速失败的 @mode 检查也移入同一份聚合报告,消息文本不变。

生成器保留同一源码注释的两种视图:parseJsDoc 在第一个块标签处结束条目正文,而 ts cordis-catalog 签名块包含原始 JSDoc,并完整保留 @param@returns@mode。因此,读者可以看到完整的源码契约,而块标签文本不会泄漏到周围正文中。

packages/core/agent/tests/gen-cordis-catalog.spec.ts 中的负路径测试对合成 fixture(测试前置数据)运行 collectEvents/collectServices,验证每条守卫都会触发且免检规则成立。撰写规则写在根 AGENTS.md 的约定条目中,与 @mode 规则并列。

曾考虑的替代方案

  • ESLint 规则:无法看到该范围的机器定义(哪些 interface Events 成员、哪些 ctx.<key> 类构成 Cordis 对外服务接口);目录生成器在每次运行时恰好计算这层映射,因此门禁放在那里。
  • 将每个方法展开为单独的正文小节:否决。目录保留一个服务章节和一个签名块,以维持可扫读性;附着于每个声明的 JSDoc 则在原处保留完整的方法契约。
  • 逃逸标签:不设。该接口面小且经过策展(采纳时 12 个服务、57 个方法、27 个事件),要点在于检查不可豁免。

后果

  • 新事件或服务方法不能带着未记录的参数或结果落地:生成器会拒绝重新生成,verify-cordis-catalog 也会使 doc-sync 和 CI 失败。采纳时发现的约 139 处缺口已在同一变更中补齐,因此门禁以绿色状态落地。
  • 服务接口必须显式标注返回类型并使用标识符参数。两项约束在采纳时均未构成限制(所有方法已有标注;不存在解构的 seam 参数);但二者现在是承重要求,违反时会被机械检测到。
  • AGENTS.md 中通用的 JSDoc 规则(「一行能说清就用一行」)在此接口上获得了更严格的特例:仅当方法无参数且返回 void 时,一行摘要才足够。
  • nextthis@param 合法但不检查——这是有意的不对称:门禁强制载荷契约,拒绝要求样板代码。
  • 每个生成的事件或方法片段都带有其原始 JSDoc,而正文摘要不含标签。因此,源码编辑会同时刷新可读索引和签名旁展示的确切契约。