DeepSeek Harness 语义门禁:基于 TypeScript Program 与 TypeChecker 的强类型仓库检查

DeepSeek Harness 语义门禁:基于 TypeScript Program 与 TypeChecker 的强类型仓库检查 人工智能AI AgentAgent 框架DeepSeek【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址https://gitcode.com/gh_mirrors/de/deepseek-harness点击查看免费下载导读DeepSeek Harness 是一个「万物皆插件」的多包仓库monorepo其代码门禁经常需要判断 TypeScript 语法本身并不携带的事实——某个调用接收者是不是 CordisContext、哪些具体事件名会流入转发辅助函数、声明合并是否改变了事件签名。本篇文章基于仓库中已落地Status: implemented的实现方案讲解 DeepSeek Harness 如何用ts.Program汇集项目级类型信息、借助TypeChecker提取强类型事实替换掉命名约定、手写表格与 JSDoc 元数据为事件关系图docs/event-producer-consumer.md与 scoped 事件路由解析表packages/core/scope/src/scoped-events.generated.ts两套门禁提供单一语义真源。读完你既能复现这两个生成器的运行方式也能掌握在大型 TS monorepo 中做「跨文件语义检查」的通用方法。背景语法解析门禁的先天局限仓库门禁在演进中遇到了三类语法层面不可见的事实接收者身份某次调用到底是不是在向 CordisContext、AgentEventDispatch或EventsService发事件事件名集合哪些具体事件名会经由转发辅助函数进入EventsService.dispatch()声明合并declare module deepseek-ai/cordis中的合并是否会改变事件签名。此前的门禁基于 TypeScript 单文件语法解析用命名约定、手写的表格、JSDoc 注解来维护这些信息。问题在于这类「第二份表示」与源码极易失同步——重命名事件、新增辅助函数形态时手写表格必须同步更新而完备性检查只能发现「生产方缺失」无法证明某个覆盖项仍然与源码一致。因此仓库需要一个语义真源同时满足三条硬约束不引入运行时包之间的循环依赖不做宽泛的兜底启发式逻辑不复述 TypeScript 本已掌握的信息即不做机器可读的元数据标注。核心决策用 ts.Program TypeChecker 提取强类型事实仓库的决策是门禁通过ts.Program汇集项目级类型信息再用TypeChecker提取强类型事实从而把对命名约定、手写表格和 JSDoc 的依赖降到最低。这一模型被应用到两个门禁上门禁 Agen-doc-graphs生成事件生产者/消费者关系矩阵docs/event-producer-consumer.md门禁 Bgen-scoped-events生成 scoped 事件的路由解析函数表packages/core/scope/src/scoped-events.generated.ts。一个项目模型展开根项目配置TypeScriptProject两个生成器共享同一个封装scripts/ts-project.ts中的TypeScriptProject类。它做了三件事解析根 tsconfig 并递归展开项目引用loadProjectGraph()从根目录读取tsconfig.host.jsonCompilerFace host | client对每个项目引用调用ts.resolveProjectReferencePath递归收集fileNames把所有引用项目的源码根合并为一个不输出文件的语义 Program清除仅发射选项semanticCompilerOptions()把noEmit设为true同时关闭composite、declaration、declarationMap、sourceMap、incremental得到一个纯语义视图统一暴露配置诊断、语义编译选项、仓库相对路径、源码查找和共享 checkersourceFiles()、relativePath()、sourceFile()三个方法与共享的program/checker属性让各门禁不再自行按文件通配模式扫描包源码也不再各自构建不完整的 Program。// scripts/ts-project.ts节选 const graph loadProjectGraph(projectRoot, face) this.program ts.createProgram(graph.rootNames, semanticCompilerOptions(graph.options)) this.checker this.program.getTypeChecker()两点设计细节值得注意不直接以根 solution 配置建 Program源码注释明确指出「flattening hostclient into one program collides the cordis Context merges」——直接把根配置转成普通 ProgramTypeScript 可能把引用项目重定向到构建后的.d.ts声明文件且 host/client 两面的Context合并会冲突。显式展开能保留各包src文件供 AST 遍历并保持符号同一性配置诊断即失败parseConfig()对每个 tsconfig 调用ts.getParsedCommandLineOfConfigFile一旦出现任何配置诊断立刻抛错。语义门禁因此天然依赖一个有效的根项目图。门禁 A事件关系由接收者类型与值类型决定gen-doc-graphs.ts的核心是EventRelationCollector类。它不读变量名而是用isTypeAssignableTo做接收者分类// scripts/gen-doc-graphs.ts节选 if (this.project.checker.isTypeAssignableTo(type, this.eventsServiceType)) return events-service if (this.project.checker.isTypeAssignableTo(type, this.contextType)) return context if (this.project.checker.isTypeAssignableTo(type, this.agentDispatchType)) return agent-dispatch三个锚点类型都从仓库真实声明解析而来vendor/cordis/src/context.ts的Context、packages/core/agent/src/dispatch.ts的AgentEventDispatch、vendor/cordis/src/events.ts的EventsService而不是手写的白名单。有限事件名集合的恢复路径Context 与 agent-dispatch 调用只贡献由字符串字面量构成的有限事件集合finiteStringValues()只接受字面量、闭合的字符串字面量联合类型凡被拓宽成string或保持泛型的一律拒绝AgentEventDispatch转发对象内的上下文参数通过isForwardedAgentEventParameter()被显式排除——泛型转发参数不算生产方事件归属始终回到「传入封闭事件值」的调用点直接EventsService.dispatch()调用eventNamesFromArgumentList()会沿数组字面量、const常量别名、条件分支逐级恢复事件槽位并进一步通过未导出本地辅助函数的已解析调用点回溯参数调用点预过滤EVENT_API_METHODS集合on、once、emit、parallel、serial、waterfall、dispatch先行过滤只有方法名命中后才做接收者类型分类避免对每个调用都求解签名。需求式辅助函数索引证明只影响开销不影响结果这是实现中最精巧的部分。callSitesFor()对每个辅助函数先尝试provenLocalCallee()如果一个函数未导出、位于真正的 ES 模块文件、且同文件所有引用都是直接调用位那么按模块作用域规则它的全部调用必然在本文件内——此时只索引这一个文件。任一前提无法证明带导出修饰符、位于全局 script 文件、存在别名化引用如 re-export 或默认导出、存在无法归类的引用就回退到原全部包源码索引。// scripts/gen-doc-graphs.ts节选 if (!this.globalCallSites !this.provenLocalCallee(owner)) { this.globalCallSites this.buildCallSiteIndex(this.packageSourceFiles) } if (this.globalCallSites) return this.globalCallSites.get(owner) ?? [] // 仅索引 owner 所在文件回退路径就是原来的语义本身因此证明失败只会增加扫描成本绝不改变结果。文档同时记录了被否决的方案惰性单一全局索引被放弃因为当前源码树确实会走到辅助函数参数路径它仍要支付几乎全额的getResolvedSignature扫描成本。完备性契约renderEventRelations()生成的矩阵要求每个已声明的 harness 事件都存在扫描得到的生产方找不到生产方 → 直接抛错视为「死词汇」或尚不支持的语义 dispatch 形态docs/event-producer-consumer.md的生成失败信息会明确提示没有监听方的扩展点仍然合法listener-free extension points remain validinternal/dispatch插桩不会被当作它观察的每个事件的订阅关系矩阵只记录直接的产品监听方客户端声明的事件packages/client/前缀豁免生产方检查因为关系扫描只以 host 聚合为种子见源码 TODO 注释hostclient 不能共享一个 ProgramClient 包仅在 host 文件 import 它时才进入。门禁 B带作用域的事件路由生成强类型解析函数表gen-scoped-events.ts负责dsh-scope的运行时不变式。它的输入契约是两段真实代码真实的scopeTarget(base, key)调用定义在packages/core/scope/src/index.ts——为每种 scoped 基础对象确立路由键类型带this: ScopedBase的 CordisEvents成员通过isCordisModuleInterface()限定在declare module deepseek-ai/cordis内部。生成器对每个事件成员执行三步解析路由键类型routingKeyType()收集所有 base 类型可赋值给该 scoped base 的scopeTarget调用去重后若出现多个不同的键类型报「inconsistent routing-key types」搜索 payload 候选subjectCandidates()枚举每个事件参数及其第一层公开属性剔除__内部符号与 private/protected 声明用typesEquivalent()做精确类型同一性比较先getNonNullableType()移除null/undefined拒绝any/unknown三分支裁决恰好一个匹配→ 生成解析函数如agent/created: args (args[0] as Recordstring, unknown)[agent]多个匹配→ 含义不明确收集 violation 并失败零匹配→ 事件必须标记dshScopeScan unsupported。dshScopeScan unsupported只用于路由键有意留在事件参数之外的场景例如按所属 agent 路由的会话事件session/created、session/event和按父 agent 路由的 subagent 生命周期事件subagent/start、subagent/end。该标记只表达「扫描不受支持」不编码事件名、参数下标、属性路径或替代类型——生成器对非unsupported形态的 tag、多余 tag、以及「有 tag 但并非 scoped 事件」等组合都会逐一报错。生成的scoped-events.generated.ts是纯运行时映射Object.freeze冻结的Recordstring, ScopedSubjectResolver | null加上一个scopedSubjectResolverFor(event)查询函数。它位于 scoped dispatch 所属的dsh-scope包内不 import 任何事件声明方包——null解析器表示「payload 无法暴露外部路由键不变式只检查载体存在性」undefined表示「该事件不是 scope-filtered 事件」。// packages/core/scope/src/scoped-events.generated.ts节选 export function scopedSubjectResolverFor(event: string): ScopedSubjectResolver | null | undefined { return scopedSubjectResolvers[event] }消费端packages/core/scope/src/invariant.ts直接 import 这份映射做主体提取不再维护手写事件表。因为 Program 分析发生在仓库门禁内而非依赖生成的类型导入dsh-scope与dsh-invariants都无需依赖所有事件声明方——这正是文档强调的「没有循环依赖」约束的落地。语义缺口必须显式失败两个生成器对语义缺口一律失败快、失败明。触发拒绝的情况包括类别示例声明缺失无法解析Context、EventsService等锚点类型配置诊断任意 tsconfig 出现解析错误事件名被拓宽或保持泛型finiteStringValues()返回undefined路由键类型不一致同一 scoped base 出现多个键类型事件参数匹配不唯一多个 payload 候选与键类型同一不必要的 unsupported 标记payload 明明暴露了路由键却标注不支持生成产物陈旧--check模式下与已提交文件不一致设计上通过本地辅助函数调用点恢复信息的能力被刻意限制在窄范围如果数据流经过导出或无法解析的边界正确的做法是新增一条通用语义规则而不是添加特定包的覆盖项。验证与门禁集成两个生成器都提供「生成」与「校验」双模式见根package.json的 scriptspnpm run gen-doc-graphs # 生成文档图含事件关系矩阵 pnpm run verify-doc-graphs # 对语义生产方/监听方扫描做新鲜度检查 pnpm run gen-scoped-events # 生成 scoped 事件解析表 pnpm run verify-scoped-events # 重跑 Program 分析并校验生成映射新鲜度在生成器内部--check模式会把渲染结果与已提交文件逐字比对gen-doc-graphs.ts的main()中committed ! doc.content即判陈旧gen-scoped-events.ts同理。此外两套校验都被挂进统一门禁编排器scripts/run-gates.ts与文档构建、类型等价、Cordis 目录、翻译配对等检查并行执行pnpmScript(doc-graphs, verify-doc-graphs, { label: doc graphs }), // ... pnpmScript(scoped-events, verify-scoped-events, { label: scoped events }),配套验证还包括根 TypeScript 构建会编译运行时适配器scoped-events.generated.ts随dsh-scope包一起编译workspace 约束与运行时依赖闭包检查确保事件声明方聚合不会进入部署依赖。考虑过的替代方案仓库明确评估并否决了「保留语法扫描 接收者白名单 手写覆盖项」的方案每个例外都容易单独处理但重命名和新增辅助函数形态时还必须更新第二份表示。完整性检查能够发现生产方缺失却无法证明覆盖项仍与源码一致。这正是语义方案的核心优势生成器自己就是完整性的证明——根 Program 枚举所有 scopedEvents声明与真实scopeTarget约定用 checker 解析唯一 payload 路径并在渲染unknown[]运行时边界之前拒绝缺失、陈旧或含义不明确的条目。后果与权衡正向收益事件关系生成依据语义接收者身份和封闭事件值不再依赖局部命名约定scoped 事件成员关系、主体提取和运行时不变式覆盖来自事件声明与真实 dispatch 约定不再来自手写表修改事件名、参数位置、主体属性或路由键类型时会在其所属约定处触发生成失败——重构错误在提交前就被拦截成本构建扁平化 Program 比解析孤立文件消耗更多启动时间和内存语义门禁依赖有效的根项目图任何 tsconfig 破损都会让校验直接失败提交约束生成的 TypeScript 仍属于提交到仓库的源码。事件声明方或 dispatch 形态发生变化后必须重新运行pnpm run gen-scoped-events与pnpm run gen-doc-graphs并提交受影响文件和文档——新鲜度检查verify-*正是为此设计。这套模式给出了一条可迁移的路径当语法无法表达的事实需要被门禁约束时与其手写第二份表示不如构建一次不输出的语义 Program让 TypeChecker 替你回答。代价是更长的启动时间与对项目图完整性的依赖换来的是重命名、签名变更、声明合并等场景下的即时、精确且可证明的失败。赞分享人工智能AI AgentAgent 框架DeepSeek【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址https://gitcode.com/gh_mirrors/de/deepseek-harness点击查看免费下载相关推荐DeepSeek Harness 的 TypeScript Program 语义化检查门用 ts.Program TypeChecker 取代命名约定与手写表格DeepSeek Harness 的 TypeScript Program 语义化检查门用 ts.Program TypeChecker 取代命名约定与手人工智能AI AgentAgent 框架DeepSeekDeepSeek Harness 持久化日志事件目录基于 TypeScript AST 生成 docs/persistence-catalog.md 与新鲜度门禁DeepSeek Harness 持久化日志事件目录基于 TypeScript AST 生成 docs/persistence catalog.md 与新鲜度人工智能AI AgentAgent 框架DeepSeekty 类型检查器中的 TypeVar 下标与切片语义解析基于 ruff 仓库 mdtest 测试套件ty 类型检查器中的 TypeVar 下标与切片语义解析基于 ruff 仓库 mdtest 测试套件 导读 本文以 ruff 仓库内 ty 类型检查器的 M开发工具Lint格式化静态分析CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考