oh-my-pi 编辑基准结构类变更任务提示词模板structural-task.md解析与实战【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi导读packages/typescript-edit-benchmark是 oh-my-pi 项目内用于评测 AI 编码 Agent 编辑精度的基准套件其核心思路是对真实 TypeScript/JavaScript 源码注入微小的人为 bug再让模型修复从而检验模型在困难上下文中的外科手术式打补丁能力。本篇文章聚焦该套件三套提示词模板中语义最特殊的一套——structural-task.md。它把结构类变更交换、移动、删除、补标签、去包装渲染成自然语言指令而不是机械的 before/after 补丁块。读完本文你将掌握这套模板的完整语法、七类结构变更各自的指令语义、生成器中唯一解锚点校验的底层实现以及如何把它接入新的结构类变更。一、模板在基准套件中的定位1.1 测的是编辑精度不是找 bug 能力generate.ts 开篇注释写得很直白The goal is testing edit precision, not bug-finding ability.注入的 mutation 可以是微不足道的关键是模型能否在困难上下文里精准打上补丁这些困难上下文包括重复行目标行在文件中多次出现长文件300 行以上且改动位于文件中部相似块多个结构相似的函数并存密集代码极少空白上下文更难读深层嵌套高缩进层级上的空白敏感编辑。1.2 三套提示词模板的分工generate.ts 同时导入三套模板模板适用 mutation指令形态mutation-task.md运算符、字面量、正则、复合多编辑等显式 before/after 代码块Delete this block / Replace this withidentifier-task.mdidentifier-multi-edit把拼错的标识符全部替换为正确拼写的重命名指令structural-task.md7 类结构变更自然语言指令 after-state 期望代码块三者有一个共同约束每条 prompt 都必须唯一确定一个字节级精确的答案before/after 块会通过重新求解验证见 generate.ts。1.3 结构类 mutation 清单结构类变更由 generate.ts 中的STRUCTURAL_KINDS映射到模板的kind参数const STRUCTURAL_KINDS: Recordstring, StructuralKind { remove-case-label: case-label, duplicate-block: duplicate-block, move-distant-block: move-block, wrap-redundant-if: wrap-if, swap-sibling-blocks: swap-blocks, swap-adjacent-lines: swap-lines, swap-if-else: swap-if-else, };对应的 7 个StructuralKind是case-label、duplicate-block、move-block、wrap-if、swap-blocks、swap-lines、swap-if-else它们的底层实现位于 mutations.ts 的ALL_MUTATIONS中。二、模板骨架标题、条件分支与期望结果structural-task.md 是一个 Handlebars 模板{{变量}}/{{#when}}/{{#each}}整体由三部分构成2.1 任务标题与分支选择标题固定为# Fix a bug in {{filename}}filename是path.basename(filePath)generate.ts。随后用 7 个{{#when kind xxx}}...{{/when}}条件分支渲染不同的指令文案——kind就是上文StructuralKind运行时只会命中一个分支。2.2 期望结果区指令之后是期望结果区structural-task.mdAfter the fix, the affected {{#when hunkCount 1}}regions must{{else}}region must{{/when}} read exactly: {{#each hunks}} {{#if startLine}} Around line {{startLine}}: {{/if}} {{../fence}}{{../language}} {{newCode}} {{../fence}} {{/each}}这里的关键在于结构类任务不展示被删掉的旧代码只展示修复后应达到的代码状态newCode并且总是携带行号锚点startLine。生成器中的注释解释了原因generate.tsAfter-state regions are only interpretable with a position——期望区域只有在给出行号时才可定位、可解释。fence和language由 generate.ts 计算LANGUAGE_BY_EXTENSION[ext]根据.js/.ts/.tsx等扩展名给出语言标签pickFence(hunks)则根据 hunk 内容自动挑选不会冲突的代码围栏字符。2.3 收尾约束Make exactly this change; do not modify anything else.这是所有提示词的统一铁律配合 2.2 的 after-state 代码块把做什么、做到什么程度完全钉死。三、逐类拆解七种结构变更指令的语义3.1 case-label补回 switch fall-through 标签In this files switch, the value in {{label}} must be handled exactly like the case after it: add a fall-through {{label}} label directly before {{before}}.场景case A: case B:这种 fall-through 对中RemoveCaseLabelMutationmutations.ts把前一个无consequent的标签删掉了修复就是原样补回。这是一个纯插入任务——插入的内容完全由可见的兄弟标签决定不涉及任何被隐藏的代码因此指令可以做到唯一确定。生成器在structuralPromptData的case-label分支中校验generate.ts期望结果必须恰好新增 1 行added.length ! 1则拒绝label必须以case开头紧随其后的锚点行before必须以case或default开头锚点行在整个文件中必须只出现一次countTrimmedLine(inputLines, before) ! 1则拒绝。3.2 duplicate-block删除复制粘贴的重复块The block starting with {{head}} appears twice in a row — the second copy is a copy-paste accident. Delete the second copy and keep the first.场景DuplicateBlockMutationmutations.ts把一段多行语句原样复制一份接到自己后面模拟复制粘贴事故。它刻意只选择if / for / for-of / while / try / switch / 表达式语句这类重复后仍能通过语法解析的语句mutations.ts并拒绝会产生重复声明语法错误的场景复制后还会再次parseCode验证。这是纯删除任务幸存的第一份副本完全可见任务确定性由此而来。校验时要求期望结果区删除内容非空、新增内容为空且head锚点在文件中恰好出现 2 次正是两份相同块的特征generate.ts。3.3 move-block还原被移错位置的块The block starting with {{head}} was moved to the wrong place — it currently sits after {{currentPrev}}. Move it back so it comes directly before {{destination}}.场景MoveDistantBlockMutationmutations.ts把一个多行语句剪切后拼接到同一语句列表中相隔至少 3 个位置的目标语句之后对应真实编辑中占比最高的两种 hunk 形态一个删除 hunk 一个插入 hunk且移动的内容完全可见。这是模板中最复杂的指令需要 3 个锚点head被移动块的首行、currentPrev当前所处位置的前一行、destination应到达位置的后一行。校验逻辑generate.ts期望结果区必须恰好有 2 个 core纯插入 纯删除删除的旧内容与插入的新内容必须逐行一致说明确实是同一块被搬走3 个锚点行都必须包含标识符isAnchorLine且各自在文件中只出现一次。3.4 wrap-if移除冗余的调试包装A leftover debugging wrapper is redundant: remove the if (true) { on line {{wrapperLine}} together with its closing brace, and dedent the wrapped body one level.场景WrapRedundantIfMutationmutations.ts把一个 4160 行的块体包进if (true) { ... }。它刻意避免包装本身就是if (true)的块体防止重复 mutation 噪声修复就是删除包装并整体退一格缩进。这是缩进平移类多行编辑整个被包住的内容在 buggy 文件中完全可见无隐藏代码重建难点在于行号wrapperLine的定位与缩进重排。structuralPromptData中通过查找旧内容里if (true) {的位置换算出行号generate.ts。3.5 swap-blocks / swap-lines相邻块与相邻语句交换Two adjacent blocks are in the wrong order: the block starting with {{secondHead}} belongs before the block starting with {{firstHead}}. Swap the two blocks.Two adjacent statements are in the wrong order: {{secondHead}} belongs before {{firstHead}}. Swap the two statements.两条指令几乎同构区别只在规模swap-blocks对应SwapSiblingBlocksMutationmutations.ts相邻的多行兄弟语句函数、if 链、循环至少一个行跨度 ≥ 3且两边总跨度 ≤ 120 行swap-lines对应SwapAdjacentLinesMutationmutations.ts相邻的单行语句间距 ≤ 2 行。两者都用{{secondHead}}应排在{{firstHead}}之前的语序来表达交换。渲染时依赖一个微妙的 jsdiff 行为A,B - B,A会被渲染成在 B 前插入 A 在 A 后删除 AstructuralPromptData会先尝试mergeNearbyPlacements合并成单个 core否则按插入/删除成对匹配来提取两个 headgenerate.ts。3.6 swap-if-else交换 if/else 分支体The branch bodies of {{condition}} are swapped: the current else body belongs under the if, and vice versa. Swap the two branch bodies.场景SwapIfElseBranchesMutationmutations.ts交换if的 consequent 与 alternate 两个块体。它对候选有严格限制两侧都必须是块语句、都非空、且每侧不超过 5 条语句保证交换后提示词依然可读。锚点condition取自期望 hunk 之前的最近可见行必须形如if (...)且在文件中唯一generate.ts。四、锚点校验指令唯一解是如何保证的结构类指令全部是自然语言没有 before/after 旧代码兜底所以生成器必须保证指令指代的对象唯一。核心函数是 structuralPromptDatagenerate.ts 的注释说明了设计意图Every referenced anchor line is validated to occur exactly the expected number of times, so the instruction identifies a unique edit; returns null when the instruction would be ambiguous.落到具体手段上有四点锚点行计数所有指令中引用的锚点before、head、currentPrev、destination、condition、firstHead、secondHead都通过countTrimmedLine统计全文件出现次数必须等于预期值通常是 1duplicate-block 的head是 2锚点可读性isAnchorLine要求锚点包含至少 2 个字母/下划线/美元符排除);、};这类纯标点行generate.ts成对匹配move-block、swap 类任务通过删除的旧内容 插入的新内容验证确实是同一块在移动而非内容不同的偶然巧合拒绝而非降级任何歧义候选直接return null由调用方重新尝试别的候选buildPrompt的注释明确never downgraded to a raw before/after patchgenerate.ts。五、期望结果区diff → hunk → 重新求解验证结构类模板的期望结果区数据来自 hunks.ts 提供的一系列工具调用链位于 buildPromptplacementsFromDiff(input, expected)用 jsdiff 对注入 bug 后的文件和原始文件做行级 diff得到变更位置集合placementrenderHunks(inputLines, placements)把 placement 渲染成带上下文与唯一性信息的 hunksolveRenderedHunks(inputLines, hunks)反向验证——只凭渲染出的 hunk 内容重新求解如果求解结果与原文件不一致说明 prompt 有歧义return null触发重试渲染时 hunk 只保留startLine和newCode两个字段交给模板generate.ts。这里有一个与 mutation-task.md 的显著差异值得注意在 mutation-task 模板中startLine只在hunk.unique || difficulty easy时才给出generate.ts即中等难度以上要求模型自行定位而 structural-task无条件给出startLine——因为结构类指令的期望代码块只有配合行号才可解释After-state regions are only interpretable with a position。定位难度由指令本身承载而非行号隐去。六、与底层 mutation 的对应及规模控制6.1 mutation 与模板 kind 的映射每个kind都对应 mutations.ts 中一个类别为structural的 mutationkindmutation编辑形态multiHunkcase-labelRemoveCaseLabelMutation纯插入单行—duplicate-blockDuplicateBlockMutation大块删除—move-blockMoveDistantBlockMutation删除 插入两 hunk✔wrap-ifWrapRedundantIfMutation多行缩进平移—swap-blocksSwapSiblingBlocksMutation大块连续替换—swap-linesSwapAdjacentLinesMutation单行交换—swap-if-elseSwapIfElseBranchesMutation分支体交换—其中move-distant-block显式声明multiHunk true因为一个删除 hunk 一个插入 hunk正是真实编辑中占主导的两种 hunk 形态mutations.ts 注释。generateCase里对非结构、非多 hunk 的 mutation 强制要求changedHunks 1 positionalHunks 1 changedLines ≤ 30generate.ts而结构类任务不受此限制以容纳大块交换、移动与删除。6.2 规模分布贴合真实编辑形态MUTATION_PLANS 为每个 mutation 设定用例数量与变更行数目标校准依据来自真实 Agent 会话的 per-tool-call 编辑规模分布1 行约 23%、2-5 行约 30%、6-20 行约 29%、21-60 行约 14%、61 行约 5%。块级结构类 mutationwrap-redundant-if、swap-sibling-blocks、duplicate-block、move-distant-block通过sizes: BLOCK_SIZESgenerate.ts在[6,20]、[21,60]、[61,150]三个区间循环取值精确复刻真实编辑的大块分布。6.3 运行生成package.json 中的generate脚本bun run src/generate.ts --typescript-dir /tmp/pi-mono-source支持--typescript-dir源码目录缺省时浅克隆 pi-mono 并扫描packages/、--output默认fixtures.tar.gz、--count-scale、--seed默认 42、--categories、--difficulty默认easy,medium,hard,nightmare、--min-score、--dry-run等参数generate.ts。生成的每个用例目录包含prompt.md、input/、expected/、metadata.json由 tasks.ts 的loadTasksFromDir加载或从fixtures.tar.gz解包加载。七、质量保障验证器与重试机制7.1 字节级验证与格式化等价verify.ts 的verifyExpectedFileSubset对 Agent 输出与期望 fixture 做三层比较格式化等价双方都经 Prettier 格式化后比较代码文件额外忽略空行数量差异避免完美的编辑但残留一条缝空白被判失败markdown/yaml 等空白敏感格式保持严格相等verify.ts、verify.tsindentScore 缩进距离统计 Agent 原始输出与格式化输出之间的缩进差computeIndentDistanceForDiff衡量格式化器替 Agent 修正了多少缩进diff 报告失败时生成带 3 行上下文的紧凑 diff 与行数/字符数统计便于复盘。7.2 生成期的重试闭环generateCasegenerate.ts对每个用例最多尝试 100 次mutation 无法应用、prompt 构建返回 null、行号与既有用例冲突regionAvailablegenerate.ts、难度分数不足easy 0 / medium 2 / hard 5 / nightmare 8都会触发重试。模板渲染结果超过 20,000 字符同样被拒绝generate.ts保证提示词在上下文窗口内可控。nightmare 难度还会额外要求目标行必须是全文件重复出现的行generate.ts制造近重复代码多处存在、只准改指定块的极端场景。八、如何扩展新的结构类变更若要在基准套件中加入新的结构类任务需要改动四处仓库当前为只读以下仅说明设计思路mutation 实现在 mutations.ts 新增一个category structural的Mutation必要时声明multiHunk并注册进ALL_MUTATIONS与CATEGORY_MAPmutations.tskind 映射在 generate.ts 的STRUCTURAL_KINDS中加入新的StructuralKind锚点校验在structuralPromptData中为新 kind 编写case分支遵循每个锚点行唯一、可读、成对匹配的校验纪律任何歧义返回 null模板文案在 structural-task.md 中为新 kind 增加{{#when kind xxx}}分支并确保期望结果区沿用around line {{startLine}} 代码围栏 newCode的既有结构。结语structural-task.md 虽然只有 37 行却是整个编辑基准中最讲究提示词可解性的一环它以自然语言承载结构变更指令靠锚点计数、成对 hunk 匹配、反向重新求解三重校验保证每个 prompt 都收敛到唯一字节级答案又通过 multiHunk 声明与规模分布设计精确复刻真实 Agent 会话中的编辑形态。对任何想为编码 Agent 构建高质量编辑基准的开发者来说这套指令语义 锚点验证 after-state 期望区 重试闭环的组合都极具参考价值。若要深入阅读可继续查看 hunks.tsdiff 到 hunk 的渲染管线、tasks.tsfixture 加载与校验与 verify.ts输出验证器。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考