- 人工智能
- AI Agent
- Agent 框架
- DeepSeek
【免费下载链接】deepseek-harness
DeepSeek Harness: Everything is a Plugin.
本篇技术指南聚焦 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)附近的辅助函数(如runBash、readForEdit、htmlToMarkdown)、格式编解码器、整个未文档化的接口与类型别名。这些恰恰是 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修饰符与私有标识符)。
命名空间与别名
- 命名空间递归遍历;在ambient
declare命名空间内部,成员隐式导出(无需export修饰符),因此递归时把每个语句都当作导出 API。 - 点分命名空间
namespace A.B会逐层累加限定前缀(A.B.)。 - 合并(merging)惯用法:
class Fix+namespace Fix是 DSH 中"用 Config 命名空间为插件一次成文"的惯用法,只要同名兄弟声明已有文档化正文,命名空间本身就不再需要第二份文档块。 export import别名:别名是独立的导出名,其目标可能是遍历永远访问不到的非导出命名空间成员,因此别名必须文档化它自己;但只有"仅正文"类别的目标受支持——若目标是可调用、类或命名空间(其签名/成员契约是别名散文无法承载的),门禁直接拒绝并建议直接导出原声明。
三类豁免:避免逼迫样板文档
门禁有意避免把插件作者逼进写无意义文档的境地。文档明确指出:给豁免名称写文档是被允许的,只有"缺失"不受检查。三类豁免家族如下:
- Heritage 成员(继承成员):重写(override)从基类声明继承文档。但新增的公开 API 仍然需要文档——包括新增参数、对受保护成员的公开重写、以及在 void 基类之上长出具体返回值的重写。这是门禁唯一的类型检查工作(
heritageExemption使用 TypeChecker 在 extends/implements 子句中查找基类成员,inferredReturnIsVoidish用于分类无标注重写的推断返回类型);其余检查都在 AST 上进行。一个值得注意的细节:参数名前导下划线(如_cwd重写cwd)被识别为"刻意未使用的标记"(lint 的argsIgnorePattern),比较时按去掉下划线后的名字对齐,因此不算重命名。 - 插件协议槽位(Plugin-protocol slots):模块顶层的
name/inject/reusable/Configconst 与apply入口,以及插件类上的同名静态成员——它们是 cordis 框架协议,形状由框架固定,真正的语义由模块文档注释与interface Config承载。源码中对应PROTOCOL_EXPORTS与PROTOCOL_STATICS两个 Set。 - 构造器(Constructors):与 cordis 门禁一致,插件类由框架构造,类文档负责叙述。
Fail Closed:没有任何导出形式可以漏检
门禁最核心的承诺是"未检查的 API 不可能存在",因此对外部无法识别的形式一律失败关闭:
export =赋值直接拒绝(该仓库没有export =的 ESM 消费者 API,且遍历无法分类操作数的类型,见checkScope中对isExportEquals的处理);- 基类从未命名过的参数,即便写成绑定模式(binding pattern),仍然保留
@param义务——且绑定模式本身会被标记,因为"导出 API 需要简单标识符参数,@param才能为其命名"; - 任何派发(dispatch)无法识别的导出语句种类本身就是一条违规(提示
extend the gate)。
此外还有两个精准的范围控制:
- 受限包(restricted packages):部分包在
package.json的exports中没有暴露./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-jsdoc(
require-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内可接受。 - 协议槽位名称在模块顶层按约定保留:一个碰巧命名为
apply或Config的非协议导出会漏检——这一取舍被显式接受并记录在案。
小结
verify-export-jsdoc展示了 DeepSeek Harness 把"散文式规则"机械化为 CI 门禁的完整方法论:共享一套"文档化"定义(scripts/jsdoc.ts)、按声明种类分层检查、用三类豁免避免样板文档、对未知形式失败关闭、以可断言的违规列表支撑负路径测试。对于任何维护多包 TypeScript 仓库的团队,这套"每个导出名都必须在悬停时给出可读契约"的门禁模式都值得借鉴——它让文档质量不再依赖评审者的耐心,而是像类型检查一样成为代码入库前的硬性前提。
- 人工智能
- AI Agent
- Agent 框架
- DeepSeek
【免费下载链接】deepseek-harness
DeepSeek Harness: Everything is a Plugin.
相关推荐
DeepSeek Harness 的 Cordis JSDoc 完整性门禁:把"每个导出都要有 JSDoc"从评审义务变成机械检查
DeepSeek Harness 的 Cordis JSDoc 完整性门禁:把"每个导出都要有 JSDoc"从评审义务变成机械检查 Cordis 是 DeepS
人工智能AI AgentAgent 框架DeepSeekDeepSeek Harness 的 Cordis 对外 API JSDoc 完整性门禁:把「每个导出都有文档」编译为机械化的 CI 契约
DeepSeek Harness 的 Cordis 对外 API JSDoc 完整性门禁:把「每个导出都有文档」编译为机械化的 CI 契约 本篇技术指南围绕 D
人工智能AI AgentAgent 框架DeepSeekOptiScaler完整指南:任意显卡自由切换DLSS、FSR与XeSS
OptiScaler完整指南:任意显卡自由切换DLSS、FSR与XeSS OptiScaler是一款跨显卡上采样中间层:它拦截游戏里的上采样调用(DLSS、FS
图形学游戏开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考