LLMs that emit unified diffs against moving files will corrupt JSX “successfully.” Safer coding agents emit intents (AST operations, codemod names, parameter maps). A deterministic apply layer turns intents into patches.
⚡ TL;DR: Model outputs
IntentJSON; apply layer runs jscodeshift/libcst/ts-morph. Reject freeform file bytes. Validate AST after apply.
Intent schema
# intents/schema.py
from pydantic import BaseModel, Field
from typing import Literal, Any
class Intent(BaseModel):
kind: Literal["rename_symbol", "add_import", "wrap_call", "codemod"]
path: str
params: dict[str, Any] = Field(default_factory=dict)
rationale: str
{
"kind": "rename_symbol",
"path": "src/billing/invoice.ts",
"params": {"from": "calcTotal", "to": "calculateInvoiceTotal"},
"rationale": "Match domain language in ADR-17"
}
Deterministic apply
// apply/rename.ts
import { Project } from "ts-morph";
export function applyRename(path: string, from: string, to: string) {
const project = new Project();
const sf = project.addSourceFileAtPath(path);
sf.getDescendantsOfKind(/* Identifier */).forEach((id) => {
if (id.getText() === from) id.replaceWithText(to); // ✅ AST-aware
});
sf.saveSync();
}
# apply/dispatch.py
HANDLERS = {
"rename_symbol": apply_rename,
"add_import": apply_add_import,
"codemod": apply_named_codemod,
}
def apply_intent(intent: Intent):
fn = HANDLERS.get(intent.kind)
if not fn:
raise ValueError(f"unknown_intent:{intent.kind}")
return fn(intent.path, **intent.params)
❌ open(path,'w').write(model_bytes) — never let the model own the file body.
Closing checklist
- [ ] Typed intents only
- [ ] AST/codemod apply layer
- [ ] Parse/typecheck after apply
- [ ] Allowlist codemod names
- [ ] Keep a rollback snapshot per intent
Series navigation
Day 56: Supervisor Patterns on Bedrock Agents · Day 58: Spec-First: OpenAPI Remains Source of Truth
Last updated September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
