DeepSeek Harness 的 Cordis 对外 API JSDoc 完整性门禁:把「每个导出都有文档」编译为机械化的 CI 契约
2026/9/19 15:23:46 网站建设 项目流程

DeepSeek Harness 的 Cordis 对外 API JSDoc 完整性门禁:把「每个导出都有文档」编译为机械化的 CI 契约

【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness

本篇技术指南围绕 DeepSeek Harness 仓库中一项已落地(implemented)的工程决策展开:如何为 Cordis 对外服务接口与事件契约建立不可豁免的 JSDoc 完整性门禁,让"每个导出都有解释语义的 JSDoc"这类写作规则从"靠评审人肉检查"升级为"目录生成器 + CI 自动拒绝"。读完你将掌握:门禁覆盖的精确范围(哪些事件、哪些服务方法必须写文档)、每条守卫的具体判定规则与免检例外(this接收者、waterfall 的nextvoid返回值)、它为什么放在生成器而非 ESLint 规则里,以及如何在本地复现verify-cordis-catalog检查。

问题背景:IDE 引导最重要的地方,文档却可以缺席

Cordis 是 DeepSeek Harness 的插件运行时,"Everything is a Plugin" 的架构使得ctx.<key>服务与Events事件成为跨插件协作的公共接口。此前生成的 Cordis 目录只强制了事件分发模式@mode标签),却未强制要求完整的服务与事件契约

  • 方法可以完全没有描述文字;
  • 参数或返回值可以在跨插件 API 接口上不写文档;
  • 而这些恰恰是 IDE 悬停提示、Agent 阅读源码时引导最重要的地方。

仓库根目录 AGENTS.md 中的通用写作规则(「每个导出都有解释语义的 JSDoc」)只能靠评审以行文形式检查,而本仓库的既定偏好是把不变式编码为机械门禁——这正是本次决策的出发点。

核心决策:为什么门禁必须放在目录生成器里

「Cordis 服务函数与事件」这一范围有精确的机器定义,而只有目录生成器知道它:

  • 事件declare module 'cordis'interface Events的成员;
  • 服务接口是每个interface Context键所指向的类的公开方法;
  • ESLint 规则看不到这层映射——它无法知道哪些interface Events成员、哪些ctx.<key>类构成 Cordis 对外服务接口;
  • 生成器在每次运行时恰好计算出这层映射,因此门禁被放置在 scripts/gen-cordis-catalog.ts,它复用同一次遍历和同一套@mode先例,对所有编目内容强制执行 JSDoc 完整性要求。

从 package.json 可以看到配套的命令接线:

# 生成(默认会先执行全部门禁检查,任何违规都会中止) pnpm run gen-cordis-catalog # 校验模式:验证已提交的生成产物是否新鲜 pnpm run verify-cordis-catalog

其中verify-cordis-catalog(即tsx scripts/gen-cordis-catalog.ts --check)被挂入 scripts/run-gates.ts 的doc-sync门禁组,因此相关文档变更和 CI 会执行同一道门禁,无需另行接线。

门禁契约:四条守卫与两类免检

事件:描述文字 + 每个载荷参数的非空@param

每个事件需要:

  1. 描述性文字(JSDoc 正文,位于块标签之上,解释发生了什么、监听者可以做什么);
  2. 为每个载荷参数提供非空的@param

载荷参数是携带事件数据的签名参数;以下两类免检

  • this接收者注解:属于签名机制而非数据载荷;
  • 尾部的 waterfallnext:它是分发机制,其语义已由@mode waterfall标签(及其结构交叉检查)拥有,逐事件重述只是样板代码。

值得注意的有意不对称:为免检参数写文档是允许的,只有"缺失"才被检查——门禁强制载荷契约,但拒绝要求样板代码。

在 packages/typert/generator/src/cordis-catalog.ts 的collectEvents实现中可以看到完整判定:@mode缺失、描述文字为空、@param缺失都会触发违规;checkParams会以parameter => parameter.receiver || (hasNext && parameter === last)作为免检谓词,恰好对应this与尾参next

服务类:类级 JSDoc + 每方法的@param/@returns

服务类需要:

  1. 类级 JSDoc(类名ctx.<key>指向的类或接口必须有说明文字);
  2. 每个公开方法需要描述性文字
  3. 每个参数提供非空的@param
  4. 非空的@returns——除非标注的返回类型是void/Promise<void>,此时@returns可选(resolve 时机有时值得记录),但从不强制要求

checkReturns(见 cordis-catalog.ts)的实现与此完全一致:它通过渲染器解析返回类型,type === 'void' || type === 'Promise<void>'时直接放行,否则要求@returns存在且描述非空。

陈旧标签报错:@param必须命中真实参数

@param命名了一个不存在的参数即为违规,这与@mode与签名矛盾的检查对称(cordis-catalog.ts)。标签描述必须非空;超出此范围的语义质量(描述是否准确、是否传达意图)由评审负责。

遍历可检查的显式性:纯 AST,不用类型检查器

门禁是纯 AST 遍历(不使用类型检查器),因此带来两条硬性约束:

  • 服务方法必须显式标注返回类型——推断的返回类型无法被分类,也就无法判定是否需要@returns
  • 接口参数必须是简单标识符——解构模式没有名称可供@param匹配。

从源码结构看,这两条约束在采纳时均未构成限制(所有方法已有标注、不存在解构的 seam 参数),但它们现在是承重要求,违反时会被机械检测到(checkParams 会对 binding pattern 参数直接报错)。

聚合错误报告:一次看到完整清单

所有违规被聚合为一条错误信息,列出全部违规项——修复时一次看到完整清单,而不是被"快速失败"逐个打断。此前快速失败的@mode检查也被移入同一份聚合报告,消息文本不变(见 reportViolations,所有collectEvents/collectServices收集到的违规统一在此抛出)。

这种设计对"以绿色状态落地"至关重要:采纳本门禁时发现的约139 处缺口在同一变更中全部补齐,生成器拒绝重新生成、CI 拒绝合并,直到清单清零。

两种视图并存:可扫读的正文 + 完整的源码契约

生成器保留同一源码注释的两种视图(cordis-catalog.ts 的parseJsDoc):

  • 正文摘要parseJsDoc在第一个块标签(@param/@returns/@mode等)处结束条目正文,因此可读索引区只含描述文字,块标签文本不会泄漏进周围正文;
  • 签名块ts cordis-catalog围栏包含原始 JSDoc,完整保留@param@returns@mode

读者因此既能看到干净的概览正文,又能在签名旁看到确切契约;同时,源码编辑会同时刷新可读索引和签名旁展示的精确契约——文档与源码之间不存在第二个手写副本。

负路径测试:每条守卫都有合成 fixture 验证

门禁的每条判定都有对应的负路径测试(即"违规必须触发"的测试),运行于合成 fixture 之上,覆盖:

  • 事件缺@mode@mode与尾参next矛盾、@mode waterfall却无next
  • @param、陈旧@param、空描述@param、解构参数;
  • 服务方法缺@param、缺@returns、缺类级 JSDoc、推断返回类型;
  • 免检规则成立:this接收者与 waterfallnext不需要@paramvoid方法不需要@returns

这些测试集中在 packages/typert/generator/tests/cordis-catalog-contract.spec.ts(collectEvents/collectServices的契约级测试),并配合 packages/typert/generator/tests/cordis-catalog.spec.ts 验证真实工作区投影与页面渲染。此外,scripts/gen-cordis-catalog-partition.spec.ts 与 scripts/gen-cordis-catalog-record.spec.ts 分别守护分区映射与记录级产物。

曾考虑的替代方案:为什么是生成器而不是 ESLint

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

配套的失败封闭分区:服务与事件不可能静默消失

门禁之外,生成器还维护一套双向失败的封闭分区(walkPartitionProblems):

  • 每个被渲染的ctx.<key>服务、每个事件 scope 都必须映射到恰好一个docs/subsystems/页面(见 SERVICE_PAGE 与 EVENT_SCOPE_PAGE);
  • 映射表中的条目若不再被发现,报"删除陈旧条目";
  • 独立的 AST 扫描读取每一个declare module '@deepseek-ai/cordis'的 Context/Events merge(任意深度),渲染投影不可见的新键必须显式列入 SERVICE_WALK_EXEMPTIONS 或 EVENT_WALK_EXEMPTIONS 并注明文档所有者(launcher 提供的引导值、client 端浏览器面服务、客户端事件等类别);
  • 反向自检:被渲染的键/事件若扫描不可见,说明扫描自身退化了(glob、prefilter 或模块块遍历),必须修扫描而非改映射表。

这一层确保"JSDoc 完整性门禁"不是孤岛:一个新服务或新事件要么进目录接受文档检查,要么显式登记豁免,永远不可能静默漏检

后果与工程权衡

  • 新事件或服务方法不能带着未记录的参数或结果落地:生成器拒绝重新生成,verify-cordis-catalog使doc-sync和 CI 失败;
  • 服务接口必须显式标注返回类型并使用标识符参数:两项约束在采纳时未构成限制,但现在是承重要求;
  • AGENTS.md 通用 JSDoc 规则在此接口上获得更严格的特例:仅当方法无参数且返回void时,一行摘要才足够(通用规则是"一行能说清就用一行");
  • nextthis@param合法但不检查:这是有意的不对称——门禁强制载荷契约,拒绝要求样板代码;
  • 每个生成的事件或方法片段都携带原始 JSDoc,正文摘要不含标签:源码编辑会同时刷新可读索引与签名旁的确切契约。

在本地复现与运用这套门禁

# 1. 校验已提交的生成产物(包含全部 JSDoc 完整性检查) pnpm run verify-cordis-catalog # 2. 修改了某个服务的公开方法或事件签名后,重新生成目录 pnpm run gen-cordis-catalog # 3. 作为 doc-sync 门禁组的一部分随 CI 执行 pnpm run doc-sync

实践建议:当你在 Harness 的某个packages/*/*/src/**下新增ctx.<key>服务方法、事件或interface Context/interface Eventsmerge 时,遵循以下清单即可一次通过:

  1. 给类/接口写类级 JSDoc,给事件写描述正文;
  2. 每个参数写@param name - 描述(描述非空;this与 waterfall 尾参next可免);
  3. void/Promise<void>的方法写@returns
  4. 使用简单标识符参数,不要用解构模式;显式标注返回类型;
  5. 新服务/事件若渲染投影不可见,在 gen-cordis-catalog.ts 的豁免表登记并注明文档所有者;
  6. 运行pnpm run gen-cordis-catalog重新生成,确认无聚合违规后再提交。

这套门禁的完整实现集中在 scripts/gen-cordis-catalog.ts(驱动器、分区映射、策略常量)与 packages/typert/generator/src/cordis-catalog.ts(投影器、parseJsDoc、各条守卫),测试则见 packages/typert/generator/tests/cordis-catalog-contract.spec.ts。对于想要在自有插件架构中复制"写作规则机械化"模式的团队,本实现提供了一个可参考的范本:把只有特定组件才掌握的精确范围定义,与纯 AST、可测试、聚合报错的门禁逻辑放在同一处,并用双向失败封闭的分区表堵住所有静默逃逸路径。

【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询