DeepSeek Harness 导出 API 的 JSDoc 完整性门禁:verify-export-jsdoc 设计与实践
2026/9/20 16:38:17 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • DeepSeek

【免费下载链接】deepseek-harness

DeepSeek Harness: Everything is a Plugin.

项目地址:https://gitcode.com/gh_mirrors/de/deepseek-harness
点击查看免费下载

本篇技术指南聚焦 DeepSeek Harness(DSH)仓库中的文档工程实践:如何通过机械化的门禁(gate)强制要求每一个模块级导出名称都具备完整的 JSDoc 契约(描述性正文、@param@returns),而不是依赖人工评审。文章会完整拆解scripts/verify-export-jsdoc.ts的判定规则、豁免家族、失败关闭策略,以及它如何被接入doc-sync与 CI;读完你既能理解该门禁的完整契约,也能将其中的"文档完整性即工程约束"的思路迁移到自己的 TS 仓库。

背景:为什么需要一个导出级 JSDoc 门禁

DeepSeek Harness 是一个以"一切皆插件"(Everything is a Plugin)为核心的开源仓库,仓库内大量包以 npm workspace 形式组织在 packages/ 下,插件作者会从这些包导入大量符号。仓库此前已有一个针对 cordis 表面的 JSDoc 完整性门禁(详见历史记录 2026-07-04-cordis-jsdoc-completeness-gate.md),它保证interface Events的成员与ctx.<key>对应的服务类方法必须有完整的 JSDoc。

但 cordis 表面只是插件作者导入面的一小部分。仓库根 AGENTS.md 中"每个导出(及不明显的私有方法)都要有解释语义的 JSDoc"这一规则,在其他所有地方只能靠评审来人工把关,而且对普通导出函数根本没有@param/@returns的强制要求。采用新门禁时的普查发现:34 个包中存在 203 处文档缺失的模块级导出——包括接缝(seam)附近的辅助函数(如runBashreadForEdithtmlToMarkdown)、格式编解码器、整个未文档化的接口与类型别名。这些恰恰是 IDE 使用者悬停鼠标时最想看到说明的名字。

于是仓库决定把"导出必须文档化"从一条散文式规则,编码成一个可机械执行的 CI 门禁。

门禁总体设计:一条共享的"文档化"定义

新门禁由脚本 scripts/verify-export-jsdoc.ts 实现,通过根 package.json 中的脚本运行:

pnpm run verify-export-jsdoc

它被接入doc-sync门禁组,与verify-cordis-catalog并排执行(见 scripts/run-gates.ts 中doc-sync的叶子门禁列表)。门禁遍历每个packages/<group>/<pkg>/src/目录树下的每一个模块级导出名称

关键设计决策是"文档化"的定义只有一处:解析与检查辅助函数从目录生成器gen-cordis-catalog.ts中提取出来,放进了共享模块 scripts/jsdoc.ts。这意味着 cordis 表面与全导出表面使用同一套语义

  • 描述性正文在第一个块标签(block tag)处结束(parseJsDoc);
  • 每个可检查的参数都需要非空的@param
  • 非 void 的已标注返回类型需要非空的@returns
  • 过期的@param(指向不存在的参数)本身即违规;
  • 所有违规聚合到一份报告中输出(reportViolations),而不是快速失败——一次修复就能看到全部问题。

脚本内部先通过globSync('packages/*/*/src/**/*.ts')收集源文件,再用 TypeScript 编译器构建一个ts.Program(文档中也提到这是唯一一个需要付出类型解析代价的文档门禁,约 6 秒,位于doc-sync内可接受)。CLI 入口main()在违规列表非空时向 stderr 输出全部违规并以退出码 1 结束。

按声明种类的契约(Contract by Declaration Kind)

门禁对每种导出声明实施不同的检查深度:

导出种类检查内容
函数声明 / 函数式 const / 非标识符默认导出完整函数契约:正文 + 每个参数的@param+ 非 void 返回的@returns
类(class)类级正文;公开方法走完整函数契约;公开属性与访问器需正文;重载实现豁免
接口 / 类型别名 / 枚举声明处需正文;成员级强制有意推迟
命名空间(namespace)递归检查成员;命名空间自身仅在未与已文档化同名声明合并时需要正文
declare module/declare global整体跳过(增强不是本包的导出)
export … from再导出跳过(在定义处检查)
export import X = N.member别名别名自身需文档;可调用/类/命名空间目标被拒绝
export =直接拒绝
无法识别的导出语句种类视为违规(fail closed)

函数式导出的细化规则

对 const 声明的函数式导出,判定逻辑是:

  • 命名类型标注export const f: Handler = …):签名契约推迟到该类型自身的声明处,@returns可选(由returnsWaived控制)。
  • 内联可调用标注export const f: (x: T) => U = …)或单一调用签名的类型字面量{ (x: number): number }):内联标注就是导出的签名本身,必须就地承担完整函数契约(参数与返回都要文档)。
  • 混合调用/构造签名的类型字面量(如{ (x: number): number; flush: () => void }):直接拒绝——没有单一签名可让标签对号入座,门禁提示提取命名类型并到类型声明处去写文档,而不是静默收窄检查范围。

另外,判断 const 是否为"函数式"时,会先剥离不构成 API 的包装表达式:括号、as/satisfies断言、非空断言、类型断言(源码中的unwrapExpression),因此export const f = (((x: number): number => x)) satisfies Fn依然被识别为函数式导出并走完整契约。

类的细化规则

  • 类本身需要正文;公开方法(含静态方法,因为通过导出名可达)遵循函数契约。
  • 公开属性与访问器需要正文;get/set 对由 getter 的文档覆盖(setter 不再单独要求)。
  • 重载实现(有方法体的最终实现)豁免——文档由各重载签名承载。
  • 构造器豁免(与 cordis 门禁一致:插件类由框架构造,类文档负责叙述)。
  • 私有/受保护/#private成员跳过(isNonPublic同时检查private/protected修饰符与私有标识符)。

命名空间与别名

  • 命名空间递归遍历;在ambientdeclare命名空间内部,成员隐式导出(无需export修饰符),因此递归时把每个语句都当作导出 API。
  • 点分命名空间namespace A.B会逐层累加限定前缀(A.B.)。
  • 合并(merging)惯用法:class Fix+namespace Fix是 DSH 中"用 Config 命名空间为插件一次成文"的惯用法,只要同名兄弟声明已有文档化正文,命名空间本身就不再需要第二份文档块。
  • export import别名:别名是独立的导出名,其目标可能是遍历永远访问不到的非导出命名空间成员,因此别名必须文档化它自己;但只有"仅正文"类别的目标受支持——若目标是可调用、类或命名空间(其签名/成员契约是别名散文无法承载的),门禁直接拒绝并建议直接导出原声明。

三类豁免:避免逼迫样板文档

门禁有意避免把插件作者逼进写无意义文档的境地。文档明确指出:给豁免名称写文档是被允许的,只有"缺失"不受检查。三类豁免家族如下:

  1. Heritage 成员(继承成员):重写(override)从基类声明继承文档。但新增的公开 API 仍然需要文档——包括新增参数、对受保护成员的公开重写、以及在 void 基类之上长出具体返回值的重写。这是门禁唯一的类型检查工作(heritageExemption使用 TypeChecker 在 extends/implements 子句中查找基类成员,inferredReturnIsVoidish用于分类无标注重写的推断返回类型);其余检查都在 AST 上进行。一个值得注意的细节:参数名前导下划线(如_cwd重写cwd)被识别为"刻意未使用的标记"(lint 的argsIgnorePattern),比较时按去掉下划线后的名字对齐,因此不算重命名。
  2. 插件协议槽位(Plugin-protocol slots):模块顶层的name/inject/reusable/Configconst 与apply入口,以及插件类上的同名静态成员——它们是 cordis 框架协议,形状由框架固定,真正的语义由模块文档注释与interface Config承载。源码中对应PROTOCOL_EXPORTSPROTOCOL_STATICS两个 Set。
  3. 构造器(Constructors):与 cordis 门禁一致,插件类由框架构造,类文档负责叙述。

Fail Closed:没有任何导出形式可以漏检

门禁最核心的承诺是"未检查的 API 不可能存在",因此对外部无法识别的形式一律失败关闭:

  • export =赋值直接拒绝(该仓库没有export =的 ESM 消费者 API,且遍历无法分类操作数的类型,见checkScope中对isExportEquals的处理);
  • 基类从未命名过的参数,即便写成绑定模式(binding pattern),仍然保留@param义务——且绑定模式本身会被标记,因为"导出 API 需要简单标识符参数,@param才能为其命名";
  • 任何派发(dispatch)无法识别的导出语句种类本身就是一条违规(提示extend the gate)。

此外还有两个精准的范围控制:

  • 受限包(restricted packages):部分包在package.jsonexports中没有暴露./src/*,门禁通过restrictedPublicNames解析这些包的入口(./lib/types/*.d.ts/./lib/*.js映射回src/*.ts),只检查从公开入口可达的声明——未被导出的内部文件里的"导出"不视为公开 API。
  • export { … }列表解析export { publicValue }会解析回局部声明;同一语句中未被列表点名、从未导出的兄弟声明符(sibling declarator)不被当作 API;跨多个导出列表的声明符合并去重。默认导出标识符也按同样方式定位到自己的声明符。

测试保障:负路径用例直接断言违规列表

门禁的可测试性来自collectExportJsdocViolations(scanRoot)的返回值设计:返回违规列表而非抛异常,CLI 在列表非空时退出 1。这样 packages/core/agent/tests/verify-export-jsdoc.spec.ts 可以构造临时 fixture 包(packages/group/fix/src/),驱动每个拒绝路径与每个豁免路径直接断言违规内容。测试覆盖包括:

  • 完全文档化的 API 零违规、无 JSDoc / 缺@param/ 缺@returns/ 缺返回类型标注 / 纯标签无正文 / 过期@param/ 绑定模式参数分别被标记;
  • this接收者注解豁免@param
  • 声明符标注 const 的@returns豁免与未标注 const 的强制;
  • 接口、类型别名、枚举的正文要求,declare module增强体跳过;
  • 导出列表解析、默认导出、再导出在定义处检查、重载实现豁免;
  • 类的各类豁免(继承成员、私有/构造器、协议静态成员、get/set 对)与检查(公开属性/访问器);
  • 命名空间递归、合并惯用法、ambient 命名空间隐式导出;
  • 失败关闭形式:混合可调用字面量拒绝、export =拒绝、别名目标分类;
  • 继承细化:新增参数需@param、公开重写受保护成员不豁免、下划线参数视为同名、void 基类上长出具体返回值时@returns义务复活、无标注重写返回经 checker 分类。

备选方案与取舍

文档记录了三个被否决的备选方案,理解它们有助于把握门禁的边界:

  • eslint-plugin-jsdocrequire-jsdoc/require-param/require-returns):能覆盖机械化核心,但表达不了本仓库的契约——继承成员豁免需要跨包类型解析,协议槽位与命名空间合并惯用法是 cordis 特有的,"文档化"的完整性语义(正文先于标签、过期标签报错、聚合报告)已经与目录生成器共享scripts/jsdoc.ts。两种微妙不同的"文档化"定义,正是本仓库"one home"规则要消除的失败模式。
  • 扩展现有的gen-cordis-catalog.ts:目录生成器渲染的是策划好的 API 并门禁其新鲜度;全仓库遍历没有目录可渲染。共享辅助函数、保持遍历分离,能让每个门禁的职责范围清晰可读。
  • 强制接口/类型别名的成员文档:被推迟——那会把检查范围放大到大量"基本自描述"的字段成员,而承载成员级契约重担的接缝类已经在 cordis 门禁下。若评审中出现成员文档漂移再回头处理。

落地后果与约定

门禁落地带来一批成为惯例的工程约束:

  • 新导出无法再未文档化地合入verify-export-jsdoc失败会导致doc-sync与 CI 失败。采用时普查出的 203 处缺口在同一个变更中补齐,门禁以全绿状态落地。
  • 导出函数必须标注返回类型(采用时已普遍如此,现在成为承重约束),且@param需要命名的参数必须使用标识符参数而非解构绑定。
  • 接缝文档成为权威:实现继承其继承文档;值得保留在实现上的行为说明属于"补充",而非"要求"。
  • 门禁构建ts.Program(约 6 秒)——唯一付出类型解析代价的文档门禁;在本身就会编译文档片段的doc-sync内可接受。
  • 协议槽位名称在模块顶层按约定保留:一个碰巧命名为applyConfig的非协议导出会漏检——这一取舍被显式接受并记录在案。

小结

verify-export-jsdoc展示了 DeepSeek Harness 把"散文式规则"机械化为 CI 门禁的完整方法论:共享一套"文档化"定义(scripts/jsdoc.ts)、按声明种类分层检查、用三类豁免避免样板文档、对未知形式失败关闭、以可断言的违规列表支撑负路径测试。对于任何维护多包 TypeScript 仓库的团队,这套"每个导出名都必须在悬停时给出可读契约"的门禁模式都值得借鉴——它让文档质量不再依赖评审者的耐心,而是像类型检查一样成为代码入库前的硬性前提。

  • 人工智能
  • AI Agent
  • Agent 框架
  • DeepSeek

【免费下载链接】deepseek-harness

DeepSeek Harness: Everything is a Plugin.

项目地址:https://gitcode.com/gh_mirrors/de/deepseek-harness
点击查看免费下载

相关推荐

上一篇:从毫秒启动到百万容器:Firecracker如何重塑AWS无服务器底层架构
下一篇:炉石传说HsMod插件:55项功能全面解锁你的游戏体验

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

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

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

立即咨询