ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

oh-my-pi 内置规则解析:为什么 Coding Agent 不应提取 1~2 行的微型 TypeScript 函数

oh-my-pi 内置规则解析:为什么 Coding Agent 不应提取 1~2 行的微型 TypeScript 函数 oh-my-pi 内置规则解析为什么 Coding Agent 不应提取 1~2 行的微型 TypeScript 函数【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本篇技术指南围绕 oh-my-piCoding agent with the IDE wired in内置规则ts-no-tiny-functions展开完整解读这条规则的判定标准、触发条件、例外清单以及它作为 TTSRTime-Traveling Stream Rules内置规则在 内置规则源文件 与规则发现/分桶流水线中的实现原理。读完你将掌握如何理解并审查 Agent 生成代码中的微型函数提取行为、如何判断什么情况下应该内联、什么情况下保留小型函数是合理的以及如何通过配置禁用或覆盖这条内置规则。规则定位一条面向 Agent 编辑流的 TypeScript 编码约束ts-no-tiny-functions是 oh-my-pi 随二进制内置的一组默认规则builtin default rules之一与ts-no-any、ts-import-type、ts-bare-catch等规则并列共同构成面向 TypeScript/TSX 代码的编码约束集合。规则的 YAML frontmatter 如下--- description: Do not extract 1-2 line functions that only wrap an expression — inline them condition: (?m)\\{\\s*return [^;{}\\n];?\\s*\\}|\\b(?:const|let|var)\\s[\\w$]\\s*\\s*(\\([^)]*\\)|[a-zA-Z_$][\\w$]*)\\s*\\s*[^{\\n]$ scope: tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx) interruptMode: never --- Inline functions whose whole body: one expression or return, unless name creates a durable contract.四个字段分别决定了规则如何被发现、何时触发、作用在哪些工具流上description规则的语义摘要同时是规则进入rulebook 桶的必要条件详见 规则分桶实现。conditionTTSR 触发用的正则表达式命中后规则会以中断提示的形式出现在 Agent 的编辑流中。scope限定规则只对tool:edit/tool:write作用于.ts/.tsx文件时生效即只约束 Agent 正在写入的 TypeScript 代码。interruptMode: never声明这条规则采用永不强制中断模式——它只作为提示注入不会打断当前工具调用流。规则动机为什么微型函数值得警惕原文档给出了四条核心理由这是整条规则的存在根基一行包装没有真实行为function isEmpty(value) { return value.length 0; }只是给表达式起了一个名字没有增加任何逻辑读者需要跳转验证琐碎代码把一行逻辑抽成函数后阅读者必须跳到函数定义处确认它确实只有一行反而增加认知负担过早固定函数签名一旦抽成具名函数形参和返回类型就被冻结了后续演进反而被签名束缚内联表达式有更好的搜索与类型流动name.length 0直接出现在调用处IDE 全局搜索、类型推断链路都更短更清晰。这些理由的共同前提是抽取的成本跳转、命名、签名固化大于收益复用、语义命名。当函数体只有一个表达式时收益趋近于零而成本是实打实的。判定标准什么算微小函数规则的判定聚焦两类模式对应 frontmatter 中condition正则的两个分支均以(?m)多行模式开启分支一单表达式return的函数体\{\s*return [^;{}\n];?\s*\}匹配{ return 表达式; }这种函数体。注意[^;{}\n]排除分号、花括号与换行即要求 return 语句是单行、无嵌套块的结尾的;?容忍省略分号。典型的命中样例function double(value: number) { return value * 2; }分支二单表达式箭头函数\b(?:const|let|var)\s[\w$]\s*\s*(\([^)]*\)|[a-zA-Z_$][\w$]*)\s*\s*[^{\n]$匹配const/let/var 名称 (参数) 表达式或名称 参数 表达式这种省略return与函数体的表达式箭头函数。之后不能出现{即排除了块体箭头函数$要求表达式到行尾结束。两条分支合在一起把整个函数体只有一个表达式这个抽象标准落地为可机械判定的正则。而多语句函数体不会被命中——测试用例对此有明确验证// 以下均不触发规则块体、多语句 function f(v) { const x v.a; return x; } const f (v) { const x v.a; return x; };对应的单元测试位于 builtin-defaults.test.ts它同时验证了带尾随换行、不带换行、以及夹在多行语句之间三种真实文件场景下规则都能命中const getName (u) u.profile.name;这种单行箭头函数且多语句块体函数不会被误报。反例与正例从代码层面理解内联Avoid应避免的写法// Bad — pure rename, no behavior added. function isEmpty(value: string): boolean { return value.length 0; } const getDisplayName (user: User) user.profile.displayName; function double(value: number) { return value * 2; } if (isEmpty(name)) { ... }三个例子分别是函数声明 单行 return、表达式箭头函数、以及把一行算术包装成函数。它们都只是给表达式起名字没有任何行为增量。Use应改为内联if (name.length 0) { ... } const displayName user.profile.displayName; const doubled value * 2;把表达式直接写在调用处读者无需跳转即可看到完整逻辑类型推断也保持在同一作用域内。例外清单什么时候允许保留小型函数规则并不是一刀切禁止所有小型函数。原文档明确了六类可以保留的例外判定原则是名称是否构成持久契约durable contract三个及以上调用点需要锁步行为多处调用必须同步保持一致时一个统一入口能防止各自改乱导出名称是稳定的领域概念例如一个库对外导出的公共工具函数名称本身就是 API 的一部分回调身份callback identity很重要内联表达式每次都会创建新函数引用而具名函数引用稳定这对依赖引用相等性的场景如 useEffect 依赖数组、事件解绑至关重要类型守卫保留类型收窄isFoo(x): x is Foo这类 type guard 必须具名才能被类型系统识别并在if中收窄公共 API、测试替身test seam或依赖注入边界需要间接层在这些边界上具名函数提供了可替换、可 mock 的间接层非显而易见的公式或魔法常量计算如果内联表达式本身无法解释其含义如复杂的加密哈希算法、魔数换算保留一个自解释的函数名是有价值的。如果以上六条都不适用就应内联。这个例外清单本质上回答了规则的边界规则反对的是无行为的重命名包装而非一切小函数。源码级原理内置规则如何进入 Agent 会话这条规则之所以能开箱即用是因为它被注册为builtin-defaults提供方provider的一部分。从源码可以梳理出完整的加载链路1. 编译期嵌入在 builtin-rules/index.ts 中每个规则文件通过with { type: text }导入为原始文本再按名称顺序组装进BUILTIN_RULE_SOURCES数组import tsNoTinyFunctions from ./ts-no-tiny-functions.md with { type: text }; // ... { name: ts-no-tiny-functions, content: tsNoTinyFunctions },注释明确指出这样做的目的是让规则在bun build --compile之后随编译产物一起存活——发布出的二进制不携带松散的规则文件只携带嵌入的文本原生源码/tarball 安装读取的是同一份模块。2. 最低优先级注册在 builtin-defaults.ts 中builtin-defaults提供方以PRIORITY 1全系统最低优先级注册const DISPLAY_NAME Builtin Defaults; // Lowest priority: every other rule provider wins a name conflict. const PRIORITY 1;这意味着任何用户级、项目级或工具级来源中若存在同名规则ts-no-tiny-functions都会在按名称去重时优先胜出覆盖内置版本。这也是规则可被定制的机制基础。3. Frontmatter 解析与条件编译加载时经由 helpers.ts 中的buildRuleFromMarkdown/discoverRuleFromMarkdown完成解析剥离 frontmatter、按文件名推导规则名、解析description、condition、scope、interruptMode等字段。其中condition字段的正则会被 compileRuleCondition 处理规则文件开头的(?m)属于 PCRE 风格的行内标志会被翻译成原生RegExp的m标志——这正是condition中^/$能以行首/行尾语义工作的原因测试 builtin-defaults.test.ts 专门校验了这一点。4. TTSR 分桶与触发规则加载后进入 bucketRules 分桶流水线。由于该规则带有非空condition它会被TtsrManager.addRule(rule)接受成为TTSR-only 规则不进入 always-apply 桶也不进入 rulebook 桶而是注册到 TTSR 管理器在 Agent 每次tool:edit/tool:write产生流式增量时按scope过滤文件类型并匹配condition命中即注入提示。interruptMode: never则进一步保证该提示是软性的——不会强制打断执行只是给模型提供即时纠偏信息。5. 禁用与覆盖的三条路径根据 builtin-defaults.ts 头部注释与bucketRules的实现用户可以用三种方式关闭或覆盖内置规则方式做法效果整组关闭将ttsr.builtinRules置为false丢弃整个builtin-defaults提供方的全部规则单条禁用在ttsr.disabledRules中列出ts-no-tiny-functions该规则在任何分桶之前被丢弃既不参与 TTSR 匹配也无法通过rule://访问同名覆盖在任意更高优先级来源项目.omp/rules/、用户~/.omp/agent/rules/等定义同名规则按名称去重后自定规则替换内置版本其中前两种在bucketRules中强制执行先检查disabledRules集合再检查builtinRules开关_source.provider BUILTIN_DEFAULTS_PROVIDER_ID时跳过。规则的发现/分桶整体流程可参见 rulebook-matching-pipeline.md。实战建议把规则内化为代码审查清单结合规则本体与触发机制可以总结出供日常开发和 Agent 使用中自查的要点先问有没有行为增量新函数是否只是给表达式起名若是直接内联。再问有没有持久契约是否命中六类例外多处调用锁步、导出领域概念、回调身份、类型守卫、测试/DI 边界、魔法计算命中则保留。关注 Agent 生成的代码由于scope限定在tool:edit/tool:write的.ts/.tsx流这条规则专门约束 Agent 编辑文件时的产出人工审查 Agent 提交的 diff 时可重点留意新出现的单行 return 包装函数与单行箭头函数。按团队偏好定制如果团队希望更严格或更宽松可以通过ttsr.disabledRules禁用或在更高优先级来源提供同名规则覆盖默认判定。这条内置规则的价值不在于禁止小函数而在于消除无行为的重命名层让代码库中的每个函数名都承载真实的语义契约——这是它能被编码进 Agent 编辑流、成为自动纠偏信号的深层原因。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表