# Splice Manual
**Version**: 2.0.0
---
## Overview
Splice is a span-safe refactoring tool that performs byte-accurate, AST-validated operations on code in 7 languages: Rust, Python, C, C++, Java, JavaScript, and TypeScript.
**NEW in v0.5.0**: Magellan integration for code indexing and label-based symbol discovery.
### Core Philosophy
1. **Span Safety**: All operations work with exact byte spans
2. **Validation Gates**: Every operation passes tree-sitter reparse + language compiler check
3. **Atomic Rollback**: Any failure triggers automatic rollback
4. **No Magic**: Explicit parameters only
### What Splice Does
- Replace function bodies, class definitions, interface definitions, enum variants, trait definitions, impl blocks
- Delete symbol definitions and all references (cross-file for Rust, definition-only for other languages)
- Apply batch patches across multiple files atomically
- Perform multi-file pattern replacements with AST confirmation
- Preview changes before applying (dry-run mode)
- Create backups and undo operations
- Track operation metadata for auditing
- Validate syntax with tree-sitter after every operation
- Validate semantics with language-specific compilers (cargo check, python -m py_compile, javac, etc.)
- Rollback atomically if validation fails
- Orchestrate multi-step refactors with JSON plans
### What Splice Does NOT Do
- Automatic symbol discovery
- Configuration files
- Persistent databases
- Resume mode for failed plans
- Cross-file reference finding for non-Rust languages (planned for future)
---
## Installation
```bash
cargo install splice
```
Or from source:
```bash
git clone https://github.com/oldnordic/splice.git
cd splice
cargo build --release
cp target/release/splice ~/.local/bin/
```
---
## Supported Languages
| Rust | `.rs` | Full | Full | `cargo check` |
| Python | `.py` | Basic | Full | `python -m py_compile` |
| C | `.c`, `.h` | Basic | Full | `gcc -fsyntax-only` |
| C++ | `.cpp`, `.hpp`, `.cc`, `.cxx` | Basic | Full | `g++ -fsyntax-only` |
| Java | `.java` | Basic | Full | `javac` |
| JavaScript | `.js`, `.mjs`, `.cjs` | Basic | Full | `node --check` |
| TypeScript | `.ts`, `.tsx` | Basic | Full | `tsc --noEmit` |
**Delete modes:**
- **Full** (Rust): Finds all references across files via import tracking
- **Basic** (others): Deletes the symbol definition only
---
## Diagnostics Output
Splice now documents its diagnostics JSON contract in `docs/DIAGNOSTICS_HUMAN_LLM.md`. The note explains how rust-analyzer output is normalized alongside cargo, tree-sitter, and the per-language compiler gates so that both humans and LLM agents read the same structured payload (`CliErrorPayload`/`DiagnosticPayload`).
---
## Command Reference
### splice delete
Remove a symbol definition and all its references.
```bash
splice delete --file <PATH> --symbol <NAME> [--kind <KIND>] [--language <LANG>]
```
**Required Arguments:**
- `--file <PATH>`: Path to source file containing the symbol
- `--symbol <NAME>`: Symbol name to delete
**Optional Arguments:**
- `--kind <KIND>`: Symbol kind filter (function, method, class, struct, interface, enum, trait, impl, module, variable, constructor, type-alias)
- `--language <LANG>`: Language override (rust, python, c, cpp, java, java-script, type-script)
- `--analyzer <MODE>`: Validation mode (off, os, path)
- `--create-backup`: Create backup before deleting
- `--operation-id <ID>`: Custom operation ID for auditing (auto-generated UUID if not provided)
- `--metadata <JSON>`: Optional metadata attachment
- `-v, --verbose`: Enable verbose logging
**Rust-specific features:**
1. Finds the symbol definition in the specified file
2. Searches for all references in the workspace:
- Same-file references (unqualified calls, qualified paths)
- Cross-file references (via imports)
3. Handles shadowing correctly (local variables don't count as references)
4. Follows re-export chains to find indirect references
5. Deletes references first (reverse byte order per file to keep offsets valid)
6. Deletes the definition last
**Other languages:**
- Deletes the symbol definition only
- Language auto-detected from file extension
**Example (Rust):**
```bash
splice delete --file src/lib.rs --symbol helper --kind function
```
Output:
```
Deleted 'helper' (3 references + definition) across 2 file(s).
```
**Example (Python):**
```bash
splice delete --file utils.py --symbol old_function --language python
```
### splice patch
Apply a single patch to a symbol's span.
```bash
splice patch --file <PATH> --symbol <NAME> --with <FILE> [--kind <KIND>] [--language <LANG>]
```
**Required Arguments:**
- `--file <PATH>`: Path to source file
- `--symbol <NAME>`: Symbol name to patch
- `--with <FILE>`: Path to replacement file
**Optional Arguments:**
- `--kind <KIND>`: Symbol kind filter (function, method, class, struct, interface, enum, trait, impl, module, variable, constructor, type-alias)
- `--language <LANG>`: Language override (auto-detected from file extension by default)
- `--analyzer <MODE>`: Validation mode (off, os, path)
- `--preview`: Run in preview mode without modifying files (dry-run)
- `--batch <FILE>`: JSON file describing batch replacements
- `--create-backup`: Create backup before patching
- `--operation-id <ID>`: Custom operation ID for auditing (auto-generated UUID if not provided)
- `--metadata <JSON>`: Optional metadata attachment
- `-v, --verbose`: Enable verbose logging
**Symbol Kinds:**
| `function` | All | `pub fn foo() {}`, `def foo():`, `function foo() {}` |
| `method` | All | `pub fn foo(&self) {}`, `def foo(self):`, `foo() {}` |
| `class` | Python, JS, TS | `class Foo:`, `class Foo {}` |
| `struct` | Rust, C++ | `pub struct Foo;`, `struct Foo {}` |
| `interface` | Java, TS | `interface Foo {}` |
| `enum` | All | `pub enum Bar {}`, `enum Bar {}` |
| `trait` | Rust | `pub trait Baz {}` |
| `impl` | Rust | `impl Foo {}` |
| `module` | Rust, Python | `mod foo;`, `import foo` |
| `variable` | JS, TS | `const foo = ...` |
| `constructor` | Java, C++ | `public Foo() {}` |
| `type-alias` | Rust, TS, Python | `type Foo = Bar;`, `type Foo = Bar;`, `Foo = Bar` |
### splice plan
Execute a multi-step refactoring plan.
```bash
splice plan --file <PLAN.json>
```
**Execution Behavior:**
1. Steps execute sequentially
2. Stops on first failure
3. Previous successful steps remain applied
4. Each step has atomic rollback
### splice apply-files
Apply a pattern replacement to multiple files with AST confirmation.
```bash
splice apply-files --glob <GLOB> --find <PATTERN> --replace <REPLACEMENT>
```
**Required Arguments:**
- `--glob <GLOB>`: Glob pattern for matching files (e.g., `tests/**/*.rs`, `src/**/*.py`, `*.java`)
- `--find <PATTERN>`: Text pattern to find
- `--replace <REPLACEMENT>`: Replacement text
**Optional Arguments:**
- `--language <LANG>`: Language override (auto-detected from file extension by default)
- `--no-validate`: Skip validation gates
- `--create-backup`: Create backup before applying
- `--operation-id <ID>`: Custom operation ID for auditing (auto-generated UUID if not provided)
- `--metadata <JSON>`: Optional metadata attachment
- `-v, --verbose`: Enable verbose logging
**Features:**
- AST confirmation ensures replacements land in valid code locations
- Comment filtering (skips matches in comments unless pattern starts with `//`)
- Replacements applied in reverse byte order per file
- Validation gates run per file (unless `--no-validate` is specified)
**Example:**
```bash
# Replace "42" with "99" in all Python files
splice apply-files --glob "*.py" --find "42" --replace "99"
# Replace function name across Rust tests with backup
splice apply-files --glob "tests/**/*.rs" --find "old_func" --replace "new_func" --create-backup
```
### splice undo
Undo a previous operation by restoring from a backup manifest.
```bash
splice undo --manifest <PATH>
```
**Required Arguments:**
- `--manifest <PATH>`: Path to backup manifest file (usually `.splice-backup/<operation-id>/manifest.json`)
**Features:**
- Restores all files to their backed-up state
- SHA-256 hash verification ensures integrity
- Atomically restores each file (temp + fsync + rename)
**Example:**
```bash
# Restore from a backup created with --operation-id "my-change"
splice undo --manifest .splice-backup/my-change/manifest.json
```
**Backup Structure:**
```
.splice-backup/<operation-id>/
├── manifest.json # Metadata and file list
└── files/ # Backed-up files
├── src/lib.rs # Original file contents
└── tests/test.rs
```
### splice query (NEW in v0.5.0)
Query symbols by labels using Magellan integration.
```bash
splice query --db <FILE> [--label <LABEL>]... [--list] [--count] [--show-code]
```
**Required Arguments:**
- `--db <FILE>`: Path to the Magellan database
**Optional Arguments:**
- `--label <LABEL>`: Label to query (can be specified multiple times for AND semantics)
- `--list`: List all available labels with counts
- `--count`: Count entities with specified label(s)
- `--show-code`: Show source code for each result
**Available Labels:**
- Language labels: `rust`, `python`, `javascript`, `typescript`, `c`, `cpp`, `java`
- Symbol kind labels: `fn`, `method`, `struct`, `class`, `enum`, `interface`, `module`, `union`, `namespace`, `typealias`
**Examples:**
```bash
# List all labels with counts
splice query --db code.db --list
# Find all Rust functions
splice query --db code.db --label rust --label fn
# Show code for each struct
splice query --db code.db --label struct --show-code
# Count all classes
splice query --db code.db --label class --count
```
### splice get (NEW in v0.5.0)
Get code chunks from the database using Magellan integration.
```bash
splice get --db <FILE> --file <PATH> --start <N> --end <N>
```
**Required Arguments:**
- `--db <FILE>`: Path to the Magellan database
- `--file <PATH>`: File path
- `--start <N>`: Start byte offset
- `--end <N>`: End byte offset
**Features:**
- Retrieves source code without re-reading the file
- Uses code chunks stored during indexing
- Returns None if no chunk exists at the specified span
**Example:**
```bash
# Get code for a specific function (bytes 0-100)
splice get --db code.db --file src/lib.rs --start 0 --end 100
```
---
## Quick Start Examples
### Delete a Function (Rust)
**Source** (`src/lib.rs`):
```rust
pub fn helper() -> i32 {
42
}
pub fn main() {
let x = helper();
println!("{}", x);
}
```
**Command:**
```bash
splice delete --file src/lib.rs --symbol helper --kind function
```
**Result:**
```rust
pub fn main() {
let x = ();
println!("{}", x);
}
```
### Patch a Function (Rust)
**Original** (`src/lib.rs`):
```rust
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
```
**Patch** (`new_greet.rs`):
```rust
pub fn greet(name: &str) -> String {
format!("Greetings, {}!", name)
}
```
**Command:**
```bash
splice patch --file src/lib.rs --symbol greet --kind function --with new_greet.rs
```
### Patch a Function (Python)
**Original** (`utils.py`):
```python
def calculate(x: int, y: int) -> int:
return x + y
```
**Patch** (`new_calc.py`):
```python
def calculate(x: int, y: int) -> int:
return x * y
```
**Command:**
```bash
splice patch --file utils.py --symbol calculate --language python --with new_calc.py
```
### Patch a Method (Java)
**Original** (`Calculator.java`):
```java
public int add(int a, int b) {
return a + b;
}
```
**Patch** (`new_add.java`):
```java
public int add(int a, int b) {
return a + b + 1;
}
```
**Command:**
```bash
splice patch --file Calculator.java --symbol add --kind method --language java --with new_add.java
```
### Patch an Interface (TypeScript)
**Original** (`User.ts`):
```typescript
interface User {
name: string;
}
```
**Patch** (`new_user.ts`):
```typescript
interface User {
name: string;
age: number;
}
```
**Command:**
```bash
splice patch --file User.ts --symbol User --kind interface --with new_user.ts
```
### Multi-Step Plan
Create `plan.json`:
```json
{
"steps": [
{
"file": "src/lib.rs",
"symbol": "foo",
"kind": "function",
"with": "patches/foo.rs"
},
{
"file": "src/lib.rs",
"symbol": "bar",
"kind": "function",
"with": "patches/bar.rs"
}
]
}
```
Execute:
```bash
splice plan --file plan.json
```
### Batch Patch
Apply multiple patches at once from a JSON file:
```json
{
"patches": [
{
"file": "src/lib.rs",
"symbol": "foo",
"kind": "function",
"with": "patches/foo.rs"
},
{
"file": "src/lib.rs",
"symbol": "bar",
"kind": "function",
"with": "patches/bar.rs"
},
{
"file": "src/helper.rs",
"symbol": "Helper",
"kind": "class",
"with": "patches/helper.rs"
}
]
}
```
Execute:
```bash
splice patch --batch batch.json --language rust
```
**Features:**
- Atomic: all patches succeed or all fail
- Per-file span sorting for correct byte offsets
- Single validation pass for all files
- Automatic rollback on any failure
### Preview Mode
Inspect changes before applying:
```bash
splice patch --file src/lib.rs --symbol foo --with new_foo.rs --preview
```
**Preview output includes:**
- Files that would be modified
- Line/byte statistics for each change
- Validation results (syntax, compiler)
- Workspace remains untouched
### Pattern Replace
Replace a text pattern across multiple files:
```bash
# Replace "42" with "99" in all Python files
splice apply-files --glob "*.py" --find "42" --replace "99"
# Replace function name across Rust tests
splice apply-files --glob "tests/**/*.rs" --find "old_func" --replace "new_func"
```
**Features:**
- Glob-based file discovery
- AST confirmation (replacements in valid code only)
- Comment filtering (skips matches in comments)
- Validation gates per file
### Backup and Undo
Create a backup before making changes:
```bash
splice patch --file src/lib.rs --symbol foo --with new_foo.rs \
--create-backup --operation-id "refactor-foo"
```
Response includes backup manifest path:
```json
{
"status": "ok",
"message": "Patched 'foo' at bytes 123..456",
"data": {
"operation_id": "refactor-foo",
"backup_manifest": "/path/to/.splice-backup/refactor-foo/manifest.json"
}
}
```
Restore from backup:
```bash
splice undo --manifest .splice-backup/refactor-foo/manifest.json
```
---
## Reference Finding Details (Rust Only)
### Same-File References
The delete command finds:
- Unqualified function calls: `foo()`
- Qualified paths: `crate::module::foo()`
- Method calls: `obj.method()`
- Trait methods: `Trait::method(obj)`
### Cross-File References
For public symbols, searches workspace files:
1. Extracts imports from all Rust files
2. Matches imports to the target symbol
3. Finds references in files that import the symbol
### Shadowing Detection
Local definitions correctly shadow imports:
```rust
use crate::utils::helper; // Import
fn main() {
helper(); // This IS a reference (to import)
fn helper() { // Shadows the import
println!("local");
}
helper(); // This is NOT a reference (local, not import)
}
```
### Re-Export Chains
Follows `pub use` re-exports:
```rust
// utils.rs
pub fn helper() -> i32 { 42 }
// mod_a.rs
pub use crate::utils::helper; // Re-export
// main.rs
use crate::mod_a::helper; // Imports via re-export
helper(); // Found when deleting utils::helper
```
---
## Error Handling
### Common Errors
**Symbol Not Found:**
```
Error: Symbol not found: nonexistent
```
Check symbol name and verify `--file` path.
**Ambiguous Symbol:**
```
Error: Ambiguous symbol 'foo': found in multiple files
```
Add `--file` to disambiguate.
**Parse Validation Failed:**
```
Error: Parse validation failed - Tree-sitter detected syntax errors
```
Check patch file for syntax errors.
**Compiler Check Failed (Rust):**
```
Error: Cargo check failed - mismatched types
```
Fix type errors in patch file.
**Python Validation Failed:**
```
Error: Python compilation failed - SyntaxError
```
Fix Python syntax in patch file.
**TypeScript Validation Failed:**
```
Error: tsc validation failed - Type 'number' is not assignable to type 'string'
```
Fix type errors in patch file.
---
## Validation Gates
Every operation passes:
1. UTF-8 boundary validation
2. Tree-sitter reparse (syntax check)
3. Language-specific compiler check
**Compiler by Language:**
- Rust: `cargo check`
- Python: `python -m py_compile`
- C: `gcc -fsyntax-only`
- C++: `g++ -fsyntax-only`
- Java: `javac`
- JavaScript: `node --check`
- TypeScript: `tsc --noEmit`
**Rollback Behavior:**
- Automatic on any failure
- Atomic (temp + fsync + rename)
- No partial patch states
---
## Best Practices
**DO:**
- Run compiler checks on patch files before using splice
- Use `--kind` to disambiguate when needed
- Test patches in git repos
- Use verbose mode for debugging
- Create backup branches
- Specify `--language` for ambiguous file extensions
**DON'T:**
- Manually edit files after splice starts
- Skip compiler validation on patch files
- Use patch files with syntax errors
- Run delete on symbols without committing first
---
## Technical Details
**How Splice Works:**
**Patch:**
1. Detect language from file extension or `--language` flag
2. Extract symbols via tree-sitter (language-specific parser)
3. Resolve symbol byte span
4. Read replacement file
5. Replace span with ropey
6. Validate with tree-sitter reparse
7. Validate with language compiler
8. Commit or rollback atomically
**Delete (Rust):**
1. Extract symbols via tree-sitter
2. Find symbol definition
3. Build workspace import index
4. Find all references (same-file + cross-file)
5. Delete references (reverse byte order per file)
6. Delete definition
7. Validate each file with tree-sitter + cargo check
8. Rollback on any failure
**Delete (other languages):**
1. Extract symbols via tree-sitter
2. Find symbol definition
3. Delete definition span
4. Validate with tree-sitter + language compiler
5. Rollback on any failure
**Why Byte Spans:**
- Deterministic (independent of line endings)
- Exact (no ambiguity)
- Fast (no conversion overhead)
---
## License
GPL-3.0-or-later
---
**End of Manual**
For quick help:
```bash
splice --help
splice delete --help
splice patch --help
splice apply-files --help
splice undo --help
splice plan --help
splice query --help
splice get --help
```