关于 cheez-write
cheez-write 是用于修改受跟踪源代码的编码技能,通过防止过期写入的锚定后端执行编辑。它要求先读取当前内容,再应用限定范围的修改,或将语义与结构化变更交由合适工具处理。
主要功能
- 01按编辑类型选择后端
- 02锚定行与代码块编辑
- 03过期标签检测与恢复
- 04批量及跨文件编辑
- 05语义重构与代码转换路由
适用场景
- 01安全替换已知函数体
- 02对多个文件应用编辑
- 03处理读取后文件发生变化
- 04将符号重命名交由 LSP
MCP 相关
该技能说明如何使用 tilth MCP 的 tilth_write 工具,根据文件标签和编号行应用编辑。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paulnsorensen/ easy-cheese --skill cheez-write安装后在 Claude Code 中输入/cheez-write确认已加载。
附带文件references/edit-patterns.mdreferences/routing.md
详细介绍
cheez-write
后端契约:使用具备陈旧状态保护的编辑后端。后端必须锚定当前文件状态(tilth 文件标签、快照标签或 LSP 工作区编辑),或者在干净的代码树上执行有意的 AST 代码修改。
后端检测
根据编辑形态选择后端,优先使用代码智能后端(LSP、AST 重写、tilth),而不是基础的 harness 编辑工具——模型调优会倾向于使用宿主 Edit/Write,但这些工具不具备语义或结构感知能力:
- 语义工作区编辑优先使用 LSP: 当服务器能够识别符号或修复项时,用于重命名/代码操作。
- 结构化代码修改优先使用 AST 重写: 用于带元变量的重复语法结构;先进行试运行。
- 锚定块/范围编辑优先使用 tilth MCP:
tilth_read会输出[path#TAG]标头,然后tilth_write通过edits: [{path, tag, ops}]应用带行号的操作。 - 原生锚定编辑(回退方案)用于已显示的快照: 会拒绝过期范围的行/快照编辑——仅在没有与编辑形态匹配的代码智能后端时使用。
不要将 sed、awk、patch、shell 重定向或盲目写入视为等效的源代码编辑方式。
过期标签和被拒绝的区段属于内容问题——参见过期标签处理。
示例
“替换 src/auth.ts 中 handleAuth 的函数体”
步骤 1 — 读取行范围以获取文件标签:
tilth_read(paths: ["src/auth.ts#44-89"])
# returns a [src/auth.ts#b2c4] header, then numbered lines 44:... to 89:...
步骤 2 — 针对这些行号应用操作,并原样引用标签:
tilth_write(edits: [{
"path": "src/auth.ts",
"tag": "b2c4",
"ops": [{
"op": "replace",
"start": 44,
"end": 89,
"content": "export function handleAuth(req, res, next) {\n const token = extractToken(req);\n if (!validateToken(token)) return res.status(401).end();\n next();\n}"
}]
}])
响应会按 ## src/auth.ts 报告结果,也可能列出需要检查的调用方。
“标签已过期——文件在我操作期间发生了变化”
恢复步骤参见过期标签处理。
核心原则:锚定,而非重写
编辑后端必须识别当前文件状态——tilth 文件标签、harness 快照标签或 LSP 工作区编辑——并在文件发生变化时限制或拒绝写入。
协议:
- 使用 cheez-read 读取文件区段 → 获取当前的
[path#TAG]标头或快照 ID - 记录要修改代码块的行号
- 通过锚定后端应用编辑
适用范围:何时使用锚定编辑,何时不使用
cheez-write 负责受版本控制源代码的块编辑——函数体、签名、导入、单行修改、多编辑批次以及跨文件更改。后端必须具备陈旧状态保护;对于任何重要的代码更改,都必须遵循读取-编辑协议。
其他情况应优先使用合适的工具:
| 更改 | 改用此工具 | 原因 |
|---|---|---|
跨多处文件的结构化代码修改(JSON.parse(JSON.stringify($X)) → structuredClone($X)) | sg --rewrite(参见 ## Structural codemods — sg --rewrite escape) | 代码修改工具会对变量部分进行模板化;锚定编辑更适用于已知代码块 |
| 语义重命名或服务器已知的重构 | LSP 重命名/代码操作 | LSP 会跟踪作用域、重载、重新导出和导入 |
锁文件更改(Cargo.lock、package-lock.json、uv.lock 等) | 包管理器(cargo update、npm i、uv lock) | 手动编辑锁文件会破坏校验和完整性 |
生成文件/构建产物(编译后的 JS、转译输出、*.pb.go) | 从源文件重新生成 | 编辑产物会在下次构建时失效 |
| 全新文件,没有既有内容 | 锚定式创建/写入后端(tilth:省略 tag 的 tilth_write 区段) | 即使创建文件,也要确保具备陈旧状态保护 |
仓库外部或依赖缓存(node_modules、.cargo/registry)中的文件 | 不要编辑 | 修改依赖几乎总是错误的做法——应修复源代码或上游问题 |
| 二进制文件、图像、PDF | 生成它的工具 | 代码编辑后端仅支持文本 |
路由至 LSP 重命名或 Serena
进入 cheez-write 之前的路由——当工作流技能应优先使用 LSP 重命名或 Serena 符号范围编辑,而非 tilth_write 时——详见 references/routing.md。
锚点格式
使用所选后端生成的锚点格式:
- tilth:
[path#TAG]标头位于带编号的N:content行上方;将 4 位十六进制 TAG 复制到tilth_write的tag中,并在ops中引用这些行号。切勿自行编造 TAG(mode: "stripped"读取结果不含 TAG,无法往返用于编辑)。 - OMP 风格的快照编辑:
[file#TAG]加显示的行号;将 tag 和原始行范围传递给编辑工具。 - LSP 重构:符号位置加工作区版本;让服务端构建工作区编辑。
不要在不同锚点系统之间转换。读取和写入时使用同一系列的后端。
MCP 工具参考
tilth_write — 精确文件编辑
最简格式——一个文件区段,一个 op:
tilth_write(edits: [{
"path": "src/auth.ts",
"tag": "a3f9",
"ops": [
{ "op": "replace", "start": 42, "end": 42, "content": " let x = recompute();" }
]
}])
edits 是一个 JSON 数组,由 {path, tag?, ops} 区段对象组成(每次调用最多 20 个)——不能是字符串,也不能包在 files 参数中。每个 op 都由 op 标记:replace {start, end, content}、delete {start, end}、insert_before / insert_after {line, content}、prepend / append {content}、replace_block / insert_after_block {at, content} 和 delete_block {at}(at 是行号或 "#symbol")、delete_file、move_file {dest}。只有在创建全新文件时才省略 tag。
各 op 的示例、跨文件批处理以及 diff: true
响应选项,请参阅
references/edit-patterns.md。
过期 Tag 的处理
TAG 会将你的 ops 绑定到读取时的内容。如果文件在读取后发生变化,tilth 会将你的 ops 三方合并到变更后的文件上;如果无法安全合并,它会拒绝该区段,并在每个 ## <path> 的结果中说明原因。
恢复步骤:
- 再次读取该区段 → 获取当前的
[path#TAG]和内容。 - 检查当前内容(其他人可能已经做了更改)。
- 使用新的 tag,针对最新行号重新应用 ops。
连续遭到拒绝 → 停止,不要循环重试
如果同一文件连续两次遭到拒绝,说明你正与并发写入者发生冲突。没有“忽略 tag,只匹配此字符串”的覆盖选项——第三次重试很可能会再次遭遇相同冲突。
正确做法是停止并报告:
- 最后再读取一次最新区段并捕获当前内容。
- 准备新内容的统一差异或完整区块,但不要应用。
- 报告
"tag race on <path>; current content and proposed replacement attached. Retry once the file is quiescent or apply manually.",并附上捕获的 tag 和建议内容。 - 停止。由编排器(或人工)决定是应用更改还是升级处理。
签名变更后的调用方更新
更改签名时,请在完成前使用 LSP 引用或编辑后端提供的调用方提示。遗漏调用位置属于错误。
常见模式
何时可以接受完整文件重写
默认采用带锚点的精准编辑。只有当所选后端仍会拒绝过时快照,且文件足够小,以至于替换内容比一堆逐行编辑更容易审核时,才可以完整重写文件。
| 文件大小 | 策略 |
|---|---|
| > 150 lines | 绝不重写整个文件。始终使用带锚点的块编辑。 |
| ≤ 150 lines | 优先采用带锚点的单次编辑,但当文件中 ≥ 80% 的内容都要更改时,可以完整重写。低于此阈值时,应进行精准编辑。 |
150 行 / 80% 的阈值依据 2026 年的数据(Cursor、can.ac、Morph 基准测试):对于小文件,完整重写的表现与 diff 风格编辑相当或更好,而大文件始终应采用带锚点的编辑。
对于小文件,不要退回到不带锚点的主机 Write;文件大小并不能消除过时写入的风险。
结构性 codemod——sg --rewrite 例外
带锚点的编辑用于处理已知块,每个位置读取一次以获取锚点。对于周边文本存在差异的跨域结构性 codemod——例如“将每个 JSON.parse(JSON.stringify($X)) 重写为 structuredClone($X)”——应通过 Bash 使用 sg --rewrite(ast-grep),它会通过 metavars 为变量部分构建模板。它没有哈希锚点安全保障:每个 codemod 都要作为一次事务性更改,在两个干净的 git 状态之间执行。有关 metavar 语法和不可妥协的先试运行协议,请参阅 ../cheez-search/references/sg-patterns.md。
禁止事项
- 禁止臆造标签——始终先读取,并逐字复制
[path#TAG]标题。 - 禁止忽略被拒绝的部分——重新读取并重试(参阅“过时标签处理”)。
- 禁止使用 sed / awk / perl -i 编辑代码——它们会绕过锚点和结构安全机制,且没有不匹配检测功能。
sg --rewrite是唯一获准的 Shell 例外——参阅## 结构性 codemod——sg --rewrite 例外。 - 禁止使用
patch将 diff 应用于代码——带锚点的范围编辑是安全的等效方式。 - 禁止使用
tee或 Shell 重定向(>、>>) 覆盖 / 追加代码文件——两者都会绕过锚点。请使用带锚点的编辑后端。 - 禁止使用不带锚点的主机 Edit/Write 工具——请使用 tilth_write、框架原生的带锚点编辑,或 LSP 工作区编辑。
- 禁止使用
sg --rewrite进行一次性的块编辑——应使用带锚点的编辑。何时适用 codemod 例外,请参阅## 结构性 codemod——sg --rewrite 例外。 - 禁止跳过
sg --rewrite的先试运行协议——请参阅## 结构性 codemod——sg --rewrite 例外。 - 禁止不先读取就编辑——锚点来自读取步骤。
- 禁止通过此技能运行测试、提交、发布或审查——请使用项目的测试 / 构建门禁,使用
/plate进行提交 / 发布,使用/age进行审查。 - 禁止将此技能用于读取或搜索——使用
/cheez-read读取,使用/cheez-search搜索。