
oh-my-pi 内置编码规则解析为什么禁止用ReturnTypetypeof fn发布类型契约【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本篇技术指南围绕 oh-my-pi⌥ Coding agent with the IDE wired in内置规则ts-no-return-type展开讲解该规则禁止通过ReturnTypetypeof fn这种派生类型发布契约的原因、正确改写姿势与唯一例外并结合仓库源码说明这条规则在 Agent 中是如何被内置注册、按条件TTSR触发以及用户如何通过配置禁用或覆盖它的完整机制。读完本文你将能熟练地在自己的 TypeScript 项目中落实「显式命名类型」这一契约设计并能自主编写/调优同类 Agent 编码规则。规则速览一条关于 TypeScript 类型契约的内置规则ts-no-return-type是 oh-my-pi 随 Agent 二进制内置发布的一组默认规则builtin rules之一源文件位于 packages/coding-agent/src/discovery/builtin-rules/ts-no-return-type.md。其规则正文用一句话概括Do not publish contracts throughReturnTypetypeof fn. Name the type at the module that owns the value and import that name at consumers.即不要通过ReturnTypetypeof fn向外发布类型契约应当在「拥有该值的模块」中显式命名这个类型并让消费方import这个具名类型。规则文件的 frontmatter 定义了它的元数据与触发条件--- description: Do not use ReturnTypetypeof fn — name the type explicitly condition: ReturnType scope: tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx) interruptMode: never ---各字段含义如下frontmatter 字段值作用description规则的一句话描述规则被收集进 rulebook 时展示给模型的说明conditionReturnType正则条件Agent 在编辑/写入.ts/.tsx文件的输出流中命中该模式时触发规则注入scopetool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)规则生效的流作用域仅限对 TypeScript 文件的 edit/write 工具输出interruptModenever规则命中时不打断模型输出流采用延迟注入/折叠提醒的方式生效从源码结构看condition与scope由 parseRuleConditionAndScope 统一解析condition支持字符串或数组scope同样支持字符串或数组并被按逗号与括号深度正确切分为独立的流作用域 token。这条规则恰好演示了「正则 condition 精确 scope」这一最常用的内置规则写法。为什么不能通过ReturnTypetypeof fn发布契约规则正文给出了四条理由逐条展开如下具名类型直接记录契约Named types document the contract directlyinterface LoadedConfig这类具名类型本身就是一份可读的契约声明字段、可选性、嵌套结构一目了然而ReturnTypetypeof loadConfig把契约「藏」在函数返回类型的推导结果里读者必须回到函数定义处才能还原契约全貌。消费方不再耦合到实现辅助符号Consumers stop coupling to implementation helpersloadConfig只是产生这个值的实现函数它的名称、参数签名都属于实现细节。消费方依赖ReturnTypetypeof loadConfig等于把类型契约与实现符号的名字绑定在一起——一旦函数改名或重构所有引用ReturnTypetypeof ...的地方都会跟着破裂。JSDoc 与 changelog 能附着在导出的类型上JSDoc and changelog notes attach to the exported type具名导出的类型可以携带完整的 JSDoc 注释changelog 也可以精确标注「某类型某字段发生变更」而ReturnTypetypeof fn没有自己的名字文档和变更记录无处附着。类型错误指向预期的 API 边界Type errors point at the intended API boundary当消费方误用时错误信息会指向LoadedConfig这个公开契约类型而不是间接指向loadConfig的实现返回值定位问题更直接。Avoid应当避免的写法规则正文给出了三组典型反例覆盖了类型别名、索引访问与变量标注三种常见误用形态// Bad — opaque and coupled to implementation names. type Config AwaitedReturnTypetypeof loadConfig; type Message ReturnTypetypeof buildMessage[message]; let service: ReturnTypetypeof createService | undefined;逐一分析这三处问题type Config AwaitedReturnTypetypeof loadConfigloadConfig返回Promise消费方被迫叠加Awaited同时依赖「该函数返回 Promise」这一实现细节type Message ReturnTypetypeof buildMessage[message]通过索引访问取出嵌套字段契约碎片化且与buildMessage的返回结构强耦合let service: ReturnTypetypeof createService | undefined在变量声明处内联派生类型类型不可命名、不可复用、不可加 JSDoc。这些写法共同的问题是类型契约不是一等公民而是实现函数的「影子」。Use推荐的显式命名写法正确做法是「在拥有值的模块内命名类型在消费端导入具名类型」// In the module that owns the function: export interface LoadedConfig { path: string; values: Recordstring, unknown; } export function loadConfig(path: string): PromiseLoadedConfig { ... } // At the consumer: import type { LoadedConfig } from ./config;这一写法同时满足四条要求LoadedConfig直接声明了契约内容path: string、values: Recordstring, unknown不依赖任何推导消费端通过import type显式引入类型依赖一目了然与另一条内置规则 ts-import-type 所倡导的「类型位置一律使用顶层import type」完全同向LoadedConfig可以被复用、被 JSDoc 注释、被 changelog 追踪函数实现可以自由重构改名、改参数只要返回类型不变消费方契约就保持稳定。Exceptions唯一的例外规则允许一种例外情形Generic type utilities where the function is a type parameter.即当函数本身是泛型类型工具的类型参数时可以使用ReturnType。例如在编写通用的、以函数为类型参数的工具类型时ReturnTypeF正是这种工具语义的一部分不属于「用实现函数泄漏契约」。除此之外规则明确要求Concrete function? Export a concrete type.遇到具体函数就导出具体类型。源码佐证内置规则如何嵌入与注册ts-no-return-type并非一份游离的文档而是被编译进 Agent 二进制的真实规则。在 packages/coding-agent/src/discovery/builtin-rules/index.ts 中每一条内置规则都通过with { type: text }以文本资源形式导入import tsNoReturnType from ./ts-no-return-type.md with { type: text };其注释明确说明这样做的目的是让规则在bun build --compile之后依然存活——编译产物不携带松散的规则文件只有这段内嵌文本源码/tarball 安装方式则直接读取同一批模块。随后它们被组装进BUILTIN_RULE_SOURCES数组index.ts以{ name, content }形式供加载器消费。这些内置规则由专门的builtin-defaultsprovider 加载见 packages/coding-agent/src/discovery/builtin-defaults.tsasync function loadRules(_ctx: LoadContext): PromiseLoadResultRule { const items BUILTIN_RULE_SOURCES.map(({ name, content }) { const virtualPath ${BUILTIN_DEFAULTS_PROVIDER_ID}:${name}.md; const source createSourceMeta(BUILTIN_DEFAULTS_PROVIDER_ID, virtualPath, user); return buildRuleFromMarkdown(name, content, virtualPath, source, { ruleName: name }); }); return { items }; }关键设计是优先级最低PRIORITY 1builtin-defaults.ts注释写得很清楚——“Lowest priority: every other rule provider wins a name conflict.” 也就是说任何来自用户/项目/工具的同名规则都会覆盖这份内置副本而内置规则只是兜底默认值。每个内置规则的 frontmatter 由 buildRuleFromMarkdown / discoverRuleFromMarkdown 解析最终收敛为Rule对象condition、astCondition、scope、globs、alwaysApply、description、agents、interruptMode等字段都会按 capability/rule.ts 定义的契约规范化。interruptMode只接受never | prose-only | tool-only | always四种取值其余值会被丢弃见 helpers.ts。触发链路condition与scope如何在 TTSR 中生效ts-no-return-type的condition: ReturnType意味着当 Agent 在编辑edit或写入write.ts/.tsx文件时若输出内容包含ReturnType文本该规则就会被触发。这属于 oh-my-pi 的 TTSRTurn-Time Stream Rules机制完整的匹配与注入生命周期记录在 docs/ttsr-injection-lifecycle.md规则管线概览见 docs/rulebook-matching-pipeline.md。每条规则在进入会话前都要经过统一的bucketRules漏斗见 packages/coding-agent/src/capability/rule-buckets.ts其优先级如下ttsr.disabledRules中列出的名字被直接丢弃ttsr.builtinRules false时整个builtin-defaultsprovider 的规则被丢弃agentsglob 不匹配当前会话 agent 的规则被丢弃非空condition/astCondition的规则尝试注册进TtsrManager.addRulealwaysApply true的规则进入 always-apply 桶有description的规则进入 rulebook 桶。ts-no-return-type属于第 4 步的 TTSR 规则它有condition因此会被注册进 TTSR 管理器一旦命中即在对应流的合适时机注入。关于interruptMode: never的语义docs/ttsr-injection-lifecycle.md 给出了精确定义never模式下prose 来源的匹配会排队在一条成功的 assistant 消息之后进行延迟的隐藏注入tool 来源的匹配则通过afterToolCall钩子把一条带内system-reminder折叠进被匹配工具调用的toolResult内容中——不会中断模型输出流也不会额外产生一个后续回合。换句话说这条规则采用「悄悄提醒」的方式引导模型纠正写法而不是粗暴打断生成。实战配置如何禁用、覆盖或自定义这条规则如果你不希望该规则生效有两条配置路径均由bucketRules强制执行见 rule-buckets.ts单独禁用某条规则在ttsr.disabledRules中列出规则名例如{ ttsr: { disabledRules: [ts-no-return-type] } }整体关闭内置规则集设置ttsr.builtinRules: false一次性丢弃所有builtin-defaults规则用户/项目规则不受影响仍会加载{ ttsr: { builtinRules: false } }第三种方式不是禁用而是覆盖由于builtin-defaults的优先级最低PRIORITY 1你只要在~/.omp/agent/rules/或项目.omp/rules/下放置一个同名ts-no-return-type.md支持.md与.mdc后缀见 builtin.ts 的 rules 加载逻辑你自己的版本就会在同名去重中胜出。这为团队定制留出了充分空间——例如在默认规则基础上补充自己团队对「函数返回值类型必须显式声明」的更严格约定或者将condition扩展为覆盖PromiseReturnType...等更多变体。小结ts-no-return-type以极小篇幅传达了一条高价值的 TypeScript 契约设计原则契约应当是有名字的一等公民而不是实现函数的推导影子。在 oh-my-pi 中它由builtin-defaultsprovider 以最低优先级内置注册通过 TTSR 机制在编辑/写入.ts/.tsx文件时以interruptMode: never的非侵入方式提醒模型同时保留了ttsr.disabledRules、ttsr.builtinRules与同名覆盖三种灵活的控制手段。理解这条规则及其实现链路既是写出更可维护 TypeScript 的实践课也是理解 oh-my-pi 内置规则系统「内嵌发布、低优先级兜底、按条件注入、可配置覆盖」整体设计的一个最佳切片。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考