| name | scalpel-pruning |
| description | Use when working on scalpel-prune crate, Docker integration, SBOM generation, code removal logic, re-export chain resolution, or any code that modifies user source files |
Pruning & Container Optimization Expert Knowledge
Core Principle
False positive (pruning used code) is catastrophic — production breaks. False negative (keeping unused code) is merely wasteful. When in ANY doubt, KEEP the code.
This is non-negotiable. There is no "probably safe to prune."
When to Use
- Modifying any file in
crates/scalpel-prune/
- Working on Docker integration or multi-stage build optimization
- Modifying SBOM generation (CycloneDX, SPDX)
- Working on code removal (stub or aggressive mode)
- Changing re-export chain resolution
- Modifying the prune safety gate logic
Domain Model
Code Genome (all nodes classified)
↓
Filter: confidence == Unreachable
AND NOT MustKeep
AND NOT in reachable SCC
AND NOT re-exported by reachable module
AND NOT called by reachable same-file function
↓
Three modes:
audit → Report only. Safe. Read-only. Always run first.
stub → Replace with error-throwing stub
aggressive → Delete from source entirely
↓
Outputs: Pruned source tree + SBOM + Prune manifest (JSON)
Decision Trees
Prune Safety Gate (6 checks, ALL must pass)
digraph safety_gate {
"Candidate function" [shape=box];
"MustKeep?" [shape=diamond];
"KEEP" [shape=box style=filled fillcolor=lightgreen];
"In reachable SCC?" [shape=diamond];
"Re-exported by reachable module?" [shape=diamond];
"Called by reachable same-file function?" [shape=diamond];
"In .scalpelignore?" [shape=diamond];
"Confidence == Unreachable?" [shape=diamond];
"PRUNE" [shape=box style=filled fillcolor=lightsalmon];
"Candidate function" -> "MustKeep?";
"MustKeep?" -> "KEEP" [label="yes"];
"MustKeep?" -> "In reachable SCC?" [label="no"];
"In reachable SCC?" -> "KEEP" [label="yes"];
"In reachable SCC?" -> "Re-exported by reachable module?" [label="no"];
"Re-exported by reachable module?" -> "KEEP" [label="yes"];
"Re-exported by reachable module?" -> "Called by reachable same-file function?" [label="no"];
"Called by reachable same-file function?" -> "KEEP" [label="yes"];
"Called by reachable same-file function?" -> "In .scalpelignore?" [label="no"];
"In .scalpelignore?" -> "KEEP" [label="yes (either level)"];
"In .scalpelignore?" -> "Confidence == Unreachable?" [label="no"];
"Confidence == Unreachable?" -> "PRUNE" [label="yes — all 6 checks passed"];
"Confidence == Unreachable?" -> "KEEP" [label="no"];
}
Every check is a safety gate. Failing ANY one means KEEP.
Pruning Level Selection
Is the ENTIRE package unused (all nodes Unreachable)?
→ Package-level removal: delete directory, update lockfile
Is the ENTIRE file unused?
→ File-level removal: delete file
Only some functions unused?
→ Function-level removal: tree-sitter AST edit (preserve formatting)
Always prefer the largest safe unit. Package > file > function. Fewer edits = fewer chances for formatting corruption.
Mode Selection
First time analyzing this project?
→ audit mode ONLY. Never stub or aggressive on first run.
Audit report reviewed and approved?
→ stub mode: replace functions with error-throwing stubs
Stubs validated (tests still pass)?
→ aggressive mode: delete the code entirely
Never skip audit. Never go straight to aggressive.
Known Traps
1. Re-export chains
Module A re-exports foo from Module B: pub use b::foo;. If A is reachable, foo in B MUST stay — even if B itself appears "unused." Must do reverse-edge walk through IMPORTS edges BEFORE pruning.
2. SCC atomicity violation
Circular deps (A→B→C→A). If A is reachable, pruning B breaks the cycle and crashes A at runtime. The safety gate checks this, but any code that bypasses the gate (e.g., "optimized" batch pruning) will violate it.
3. tree-sitter edit formatting
Use tree-sitter's edit ranges to locate the function node, then replace/remove at those byte ranges. Do NOT regenerate source from AST — regeneration destroys comments, whitespace, trailing commas, and formatting.
4. package-lock.json integrity
Parse with serde_json, remove entries, re-serialize with sorted keys. Manual string editing (regex, sed) breaks integrity hashes and causes npm ci failures.
5. Stub error messages
Stubs MUST include the original function name: throw new Error("Pruned by Scalpel: functionName"). Generic "this function was pruned" messages make production debugging impossible when a stub is accidentally hit.
Verification Protocol
After ANY change to scalpel-prune:
cargo test -p scalpel-prune
- Golden file test: known input project → expected prune manifest (exact match)
- Round-trip test: prune project → run its test suite → ALL tests still pass
- SBOM validation: output passes CycloneDX schema validation
- Stub test: verify stubs actually throw/raise when called
- For aggressive mode:
diff -r original/ pruned/ — verify ONLY unreachable code removed
If round-trip test fails (pruned project's tests break): this is a P0 bug. The prune logic has a false positive.
Red Flags — STOP
- Pruning without checking re-export chains first
- Treating SCC members independently (must be atomic)
- Using aggressive mode without running audit mode first
- Modifying tree-sitter edit logic without round-trip testing
- Generating SBOM without dependency chain explanations
- Bypassing the 6-check safety gate for "performance"
- Saying "probably safe to prune" — there is no "probably"
Quick Reference
| Operation | Rule |
|---|
| Any doubt about prunability | KEEP. Always. |
| First analysis of project | Audit mode only |
| Mode progression | audit → stub → aggressive (never skip) |
| Re-export check | Reverse-edge walk BEFORE pruning |
| SCC group | If one member reachable, ALL stay |
| Source editing | tree-sitter edit ranges, not regeneration |
| Lockfile editing | serde_json parse/serialize, not regex |
| Stub message | Must include original function name |
| SBOM entries | Must explain WHY each package retained |
| Round-trip failure | P0 bug. False positive. Fix immediately. |