# `amql-mutate`
- Pure source mutations — ∅ I/O, ∅ file system
- All fns: source text + node refs in → modified source + updated node refs out
- Same inputs → same outputs; no hidden state
- Newtypes defined here, re-exported by `amql-engine`:
- `NodeKind` — tree-sitter AST node kind
- `RelativePath` — path relative to project root
## `NodeRef`
- `NodeRef { file: RelativePath, start_byte: usize, end_byte: usize, kind: NodeKind, line: usize, column: usize, end_line: usize, end_column: usize }`
- Byte offsets are into the source text at time of creation
- `NodeRef` is stale after any mutation — byte offsets shift; ∄ reuse after any op
- Obtain `NodeRef` from nav/select results — do not construct manually
## Operations
- `remove_node(source: &str, node: &NodeRef) → Result<RemoveResult, String>`
- does NOT take `file` arg
- `RemoveResult { result: MutationResult, detached: String }` — `detached` = removed text
- `insert_source(source: &str, file: &RelativePath, target: &NodeRef, position: InsertPosition, new_source: &str) → Result<MutationResult, String>`
- `InsertPosition`: `Before`, `After`, `Into`
- `Into` → before the closing delimiter (`}`, `]`, or `)`), indented +1 level (auto-detected from target line)
- `replace_node(source: &str, file: &RelativePath, node: &NodeRef, new_source: &str) → Result<MutationResult, String>`
- `move_node(source: &str, file: &RelativePath, node: &NodeRef, target: &NodeRef, position: InsertPosition) → Result<MutationResult, String>`
- `MutationResult { source: String, affected_nodes: Vec<NodeRef> }`
- `source` = full updated text
- `affected_nodes` = valid refs into updated source; may be empty if no stable refs remain
## Chaining ops on the same file
```
CHAIN(source₀: String, ops: Vec<Op>) → String:
s ← source₀
∀ op ∈ ops:
r ← op(s, ...) // use a ref valid for current s
s ← r.source
// prior refs invalid; re-select from s if needed ref absent from affected_nodes
return s
```
- ∀ op: feed `result.source` as next `source`
- `affected_nodes` may not contain ∀ nodes — re-select from `result.source` when needed ref is absent
- `amql-engine` transaction engine buffers ops per file in `FxHashMap<RelativePath, String>`
## `RelativePath` type inference
- `RelativePath` implements both `AsRef<str>` and `AsRef<Path>`
- At call sites with ambiguous inference → use `AsRef::<str>::as_ref()` to disambiguate
## Gotchas
- `remove_node` takes no `file` arg; `insert_source`, `replace_node`, `move_node` do
- `InsertPosition::Into` → last child, ≠ first child
- `Before`/`After` → sibling of target, ∉ inside target
- `move_node`: op order is position-dependent — if node precedes target, removes first then inserts (target shifts left); otherwise inserts first then removes (source range adjusted); caller sees only the final `MutationResult`
- Pre-mutation refs are invalid after any op — including refs to untouched nodes
- `affected_nodes` can be empty — always be prepared to re-select