/cheez-write¶
Skill metadata
- License: MIT
- Source:
skills/cheez-write/SKILL.md
When to invoke: Edit code via hash-anchored tilth MCP edits โ replaces sed / awk / perl -i / patch / tee / Edit / Write / shell redirects (>, >>). Use when the user asks to edit, replace, modify, update, change, delete, or insert code โ phrases like "replace this function", "delete lines 44-89", "update validateToken", "change the implementation", "add this import", "fix this bug" (when fixing requires editing), or apply a cross-cutting structural change (codemod) like "rewrite every X to Y". Sanctions sg --rewrite (ast-grep) for structural codemods that span many files. Always read first via cheez-read to get hash anchors for anchored edits. Prefer surgical anchored edits over whole-file rewrites. If tilth MCP is unavailable, stop and report rather than fall back. Do NOT use for reading files (cheez-read first to get anchors), searching code (use cheez-search), or running tests/builds.
Hard dependency: If
mcp__tilth__tilth_editis unavailable, stop immediately and report "tilth MCP server is not loaded โ cannot proceed." Do NOT fall back toEdit,Write, or any host tool. Install viatilth install <host> --editโ the--editflag is required to exposetilth_edit(see README "Installing tilth MCP").
Capability detection¶
Before the first call, verify tilth's edit tool is reachable:
- Check that
mcp__tilth__tilth_editis in your tool list. If onlytilth_readandtilth_searchare present, tilth was installed without--edit. Stop and report"tilth MCP server is loaded but edit mode is disabled โ re-install with 'tilth install <host> --edit'." - The first edit call is a probe by definition โ if it returns a JSON-RPC transport error (not a hash mismatch), stop and report
"tilth MCP server present but unhealthy: <error>". - Hash mismatches, syntax errors in the new content, or anchor-not-found are content issues โ recover via the protocol below, do not bail.
Hash-anchored file editing via tilth MCP (tilth_edit).
Use hash anchors from tilth_read to make precise, surgical edits. Avoid
rewriting whole files unless the size and change ratio justify it (see
"When full-file rewrite is acceptable" below).
Examples¶
"Replace the body of handleAuth in src/auth.ts"¶
Step 1 โ read with edit mode to get anchors:
Step 2 โ apply with the captured anchors:
tilth_edit({
"path": "src/auth.ts",
"edits": [{
"start": "44:b2c",
"end": "89:e1d",
"content": "export function handleAuth(req, res, next) {\n const token = extractToken(req);\n if (!validateToken(token)) return res.status(401).end();\n next();\n}"
}]
})
Response confirms Edit applied to src/auth.ts and may list callers to
review.
"Add an import without nuking the existing one on line 13"¶
tilth_edit replaces โ there is no native insert. Anchor on line 13 and put
the original line back at the top of content:
tilth_edit({
"path": "src/auth.ts",
"edits": [{
"start": "13:abc",
"content": "import { existingThing } from './existing';\nimport { newHelper } from './helpers';"
}]
})
"Hash mismatch โ file changed under me"¶
Re-read the section, capture the new anchors, retry once. If it mismatches
again, stop โ see "Hash Mismatch Handling โ Repeated mismatches" below
(tilth_edit has no fuzzy / search-replace mode, so blind retries lose
races, not win them).
Core Principle: Anchors, Not Rewrites¶
Traditional AI editing rewrites entire files, wasting tokens and risking data loss. tilth_edit uses hash anchors โ unique identifiers for each line โ to: - Make precise, surgical changes - Reject edits if the file changed (hash mismatch) - Show you exactly what changed
The protocol:
1. Read the file section with tilth_read (cheez-read) โ get hash anchors
2. Note start/end anchors for the block you'll change
3. Call tilth_edit with those anchors and new content
Scope: when tilth_edit, when not¶
tilth_edit owns block edits to tracked source code โ function bodies, signatures, imports, single-line tweaks, multi-edit batches, and cross-file specific changes. Hash anchors give concurrency safety; the read-edit protocol is mandatory for any code change that matters.
For everything else, prefer the right tool:
| Change | Use this instead | Why |
|---|---|---|
Cross-cutting structural codemod (JSON.parse(JSON.stringify($X)) โ structuredClone($X)) across N files |
sg --rewrite (dry-run-first protocol) |
tilth_edit needs N reads-for-anchors; codemods template the variable parts |
Lockfile changes (Cargo.lock, package-lock.json, uv.lock, etc.) |
the package manager (cargo update, npm i, uv lock) |
Hand-editing lockfiles loses checksum integrity |
Generated / build artifacts (compiled JS, transpiled output, *.pb.go) |
regenerate from source | Editing the artifact rots on the next build |
| Brand-new files, no prior content | tilth_edit (anchor on line 1, end-anchor on the last line for a single-edit insert) |
Stay on one path; the anchor cost is negligible for new files |
Files outside the repo or inside dependency caches (node_modules, .cargo/registry) |
don't edit them | Modifying dependencies is almost always a mistake โ fix the source or upstream |
| Binary files, images, PDFs | the producing tool | tilth_edit is text-only |
If the question is "which tool for this specific source-code block edit?" โ tilth_edit. If it's "rewrite this pattern everywhere" โ sg --rewrite with the dry-run protocol.
When LSP rename beats tilth_edit (if your harness has one)¶
easy-cheese does not install LSP โ it is whatever language servers your harness already exposes. There is one editing operation where an available LSP materially outperforms tilth_edit: type-aware rename of a symbol across the project.
| Edit | Use this instead | Why LSP wins |
|---|---|---|
| Rename a function / class / variable across all type-correct usages, including aliased re-exports and generic instantiations | textDocument/rename (or the harness's rename refactor) |
Returns a typechecker-validated WorkspaceEdit; covers aliased imports without textual collisions, and skips coincidental name matches in unrelated scopes. tilth_edit would need a separate read-edit cycle per call site, and sg --rewrite matches on syntax not type identity (overshoots on shadowed names, undershoots on aliased re-exports) |
For everything else โ block edits, signature changes, body rewrites, hand-written codemods โ tilth_edit (one-off) and sg --rewrite (cross-cutting) remain the right tools. LSP rename is narrowly the best fit for identifier renames specifically; nothing else in LSP's edit surface improves on the cheez-write protocol.
If no LSP is installed, or the rename touches a symbol the typechecker can't resolve (broken code, generated bindings), fall back to sg --rewrite with the dry-run-first protocol โ see "Structural codemods" below.
When Serena beats tilth_edit for symbol-bounded edits (if your harness has it)¶
Serena is an LSP-driven MCP that exposes symbol-bounded edits as named tools. When Serena is configured for the codebase (.serena/project.yml present) and the edit is symbol-shaped, the calling workflow skill should route directly to Serena rather than entering /cheez-write โ same pattern as the abstract LSP rename above, with a broader edit surface:
| Edit | Serena tool | When to prefer over tilth_edit |
|---|---|---|
| Rename a symbol type-correctly across the project | mcp__serena__rename_symbol |
The LSP rename case above โ Serena gives it a concrete tool |
| Replace a whole function / class body by name | mcp__serena__replace_symbol_body |
Skips the "read for anchors โ edit" round-trip when the boundary is a named symbol |
| Insert before / after a named symbol (e.g. add a method to a class, or a function next to its sibling) | mcp__serena__insert_before_symbol, mcp__serena__insert_after_symbol |
No anchor needed for a moving boundary |
| Delete a symbol and check for orphaned references | mcp__serena__safe_delete_symbol |
Validates xrefs before the cut โ tilth_edit would happily strand callers |
/cheez-write itself stays tilth-only โ its allowed-tools frontmatter does not include mcp__serena__* and shouldn't. The routing decision happens in the workflow skill before it enters /cheez-write, preserving the hash-anchor concurrency floor.
Caveat โ no hash-anchor concurrency safety. Serena's edits rely on LSP and file mtime, not the content-hash check that makes tilth_edit race-safe. The workflow skill should route to Serena only when the file is quiescent (no parallel writers, no in-flight /cook or /cure on the same path). Route back into /cheez-write whenever concurrency safety dominates, the symbol isn't LSP-resolvable (broken or generated code), the edit is sub-symbol (one line inside a function), or Serena is unavailable. The cheez-write floor โ tilth_edit for everything that touches code from inside this skill โ still applies; Serena is a routing alternative the caller chooses, not an in-skill acceleration.
Hash Anchor Format¶
When you read a file with tilth_read in edit mode, lines have anchors:
Format: <line>:<hash>|<content> (ASCII pipe, no space).
The hash is a short content fingerprint. If someone else edits the file, hashes change, and your edit is safely rejected.
MCP Tool Reference¶
tilth_edit โ Precise File Editing¶
The minimal shape โ single anchor, replacement content:
tilth_edit({
"path": "src/auth.ts",
"edits": [
{ "start": "42:a3f", "content": " let x = recompute();" }
]
})
For range replacement, deletion, multi-edit, insert-after, cross-file
batches, and the diff: true response option, see
references/edit-patterns.md. That file is the
JSON cookbook; this body sticks to the protocol.
The Read-Edit Protocol¶
Step 1: Read to Get Anchors¶
Output:
44:b2c|export function handleAuth(req, res, next) {
45:c3d| const token = req.headers.authorization?.split(' ')[1];
...
88:d4e| next();
89:e1d|}
Step 2: Note Your Anchors¶
- Start anchor:
44:b2c(first line of function) - End anchor:
89:e1d(closing brace)
Step 3: Edit with Anchors¶
tilth_edit({
"path": "src/auth.ts",
"edits": [{
"start": "44:b2c",
"end": "89:e1d",
"content": "export function handleAuth(req, res, next) {\n const token = extractToken(req);\n if (!validateToken(token)) {\n return res.status(401).json({ error: 'Invalid token' });\n }\n req.user = decodeToken(token);\n next();\n}"
}]
})
Replacing Entire Functions¶
This is the most common use case. The pattern:
-
Read the function (outline first if file is large):
-
Note start/end anchors from the hashlined output.
-
Replace the entire function body:
Hash Mismatch Handling¶
If the file changed since you read it:
Error: Hash mismatch at line 44
Expected: b2c
Found: f9a
Current content:
44:f9a|export async function handleAuth(req, res, next) {
...
Recovery: 1. Read the section again โ get new anchors. 2. Review the current content (someone else may have made changes). 3. Edit with new anchors.
This is a safety feature, not a bug.
Repeated mismatches โ bail out, don't loop¶
If you hit two consecutive mismatches on the same anchor, you're racing a
concurrent writer. tilth_edit has no fuzzy / search-replace mode โ there
is no "ignore the hash, just match this string" option. A third retry will
likely lose the same race.
The correct move is to bail and report:
- Read the latest section one final time and capture the current content.
- Prepare the new content as a unified diff or full block, but do not apply it.
- Report
"hash-anchor race on <path>:<line>; current content and proposed replacement attached. Retry once the file is quiescent or apply manually."along with the captured anchors and proposed content. - Stop. Let the orchestrator (or a human) decide whether to apply the change or escalate.
This trades automation for safety โ losing a race twice means whatever's writing the file is faster than your read-edit cycle, and a third blind retry could overwrite real work.
Caller Updates After Signature Changes¶
When you edit a function signature, tilth_edit shows callers that may need updating:
Edit applied to src/auth.ts
โโ callers that may need updates โโ
src/routes/api.ts:34 router.use('/api/*', handleAuth)
src/routes/admin.ts:12 app.use(handleAuth)
src/middleware.ts:8 const wrapped = handleAuth(...)
Check these locations and update if needed.
Common Patterns¶
| Goal | Pattern | Reference |
|---|---|---|
| Replace one line | single anchor, new content |
edit-patterns.md#single-line-replacement |
| Replace a range | start + end anchors |
edit-patterns.md#multi-line-range-replacement |
| Delete a block | range with content: "" |
edit-patterns.md#delete-a-block |
| Insert after a line | anchor on that line, prepend its content | edit-patterns.md#insert-after-a-line |
| Multi-edit in one file | edits: [...] ordered bottom-up |
edit-patterns.md#multiple-edits-in-one-call |
| Cross-file change | one tilth_edit call per file |
edit-patterns.md#edits-across-multiple-files |
Large Files: Outline First¶
For large files, tilth_read shows an outline, not hashlined content:
# src/giant.ts (2400 lines, ~32k tokens) [outline]
[1-20] imports
[22-89] interface Config
[91-450] class GiantHandler
[100-180] fn process
[182-340] fn validate
To edit, drill into the specific section:
Then edit with those anchors.
When full-file rewrite is acceptable¶
Hash-anchored, surgical edits are the default. There is one exception:
| File size | Policy |
|---|---|
| > 150 lines | Never rewrite the whole file. Always hash-anchored. |
| โค 150 lines | Anchored single-edit preferred, but a full rewrite (delete-everything + insert) is acceptable when โฅ 80% of the file is changing. Below that threshold, do the surgical edit. |
The 150-line / 80% threshold is informed by 2026 industry data (Cursor's published numbers, can.ac analysis, the Morph benchmark) showing full-file rewrites tie or beat diff-style on small files. The threshold keeps the spirit conservative โ large files always stay anchored.
When you do rewrite a small file in full, still use tilth_edit (anchor on
line 1, end-anchor on the last line). Do not drop to host Write โ
that bypasses tilth's hash-mismatch safety.
Structural codemods โ sg --rewrite escape¶
tilth_edit excels at "replace this specific block in this specific file"
with hash-anchor concurrency safety. It handles cross-cutting structural
changes awkwardly: one file at a time, one read-for-anchors per location.
For codemods โ "rewrite every JSON.parse(JSON.stringify($X)) to
structuredClone($X)", "convert every var $X = $Y to let $X = $Y" โ
drop to sg --rewrite (ast-grep) via Bash. This is the only sanctioned
shell escape from cheez-write.
The two tools are complementary, not redundant:
| Tool | Safety property | Best for |
|---|---|---|
tilth_edit |
Hash-anchor (concurrency) | Specific-block edits, signature changes |
sg --rewrite |
Structural match (CST) | Cross-cutting codemods over N files |
When the change repeats across many locations and the surrounding text
varies, sg --rewrite captures the variable parts via metavars and templates
them back into the rewrite โ tilth_edit cannot express that without N
reads.
For invocation rules (--lang, --json, no --interactive), pitfalls
(CST-not-AST, metavar binding, lenient-by-default), and the non-negotiable
dry-run-first protocol (search โ clean tree โ -U โ diff โ revert if too
loose), see
../cheez-search/references/sg-patterns.md
โ the "Structural codemods (sg --rewrite)" and "Pitfalls" sections in
particular.
sg --rewrite does not have hash-anchor safety. Treat each codemod as a
single transactional change between two clean git states; never layer
additional edits on top until the codemod is committed or reverted.
DO NOT¶
- DO NOT rewrite files > 150 lines โ use hash anchors for surgical edits.
- DO NOT rewrite small files when the change is < 80% โ anchor the changed range only.
- DO NOT guess hash values โ always read first to get current anchors.
- DO NOT ignore hash mismatches โ re-read and retry (see Hash Mismatch Handling).
- DO NOT use sed / awk / perl -i to edit code โ they bypass hash anchors and structural safety, and have no mismatch detection.
sg --rewriteis the only sanctioned shell escape, and only for structural codemods that follow the dry-run-first protocol. - DO NOT use
patchto apply diffs to code โtilth_edit's anchored ranges are the safe equivalent. - DO NOT use
teeor shell redirects (>,>>) to overwrite/append code files โ both bypass anchors. Usetilth_edit. - DO NOT use the host Edit/Write tool โ use
tilth_edit(orsg --rewritefor structural codemods) exclusively for code. - DO NOT use
sg --rewritefor one-off block edits โ that'stilth_editterritory. The codemod escape is only for cross-cutting structural changes; using it on a single location wastes its strength and skips hash-anchor safety. - DO NOT skip the dry-run-first protocol for
sg --rewriteโ search-only first, clean working tree, then-U. Never combine search+rewrite blindly. - DO NOT edit without reading โ you need the anchors.
- DO NOT use for reading โ use cheez-read.
- DO NOT use for searching โ use cheez-search.
What This Skill Doesn't Do¶
- Read files โ use cheez-read first to get anchors.
- Search code โ use cheez-search to find what to edit.
- Run tests after editing โ use test/build skills.
- Commit changes โ use git/gh skills.
- Review your edits โ use the
/ageskill.