devclean 0.1.2

A tool to clean up your development environment
Documentation
# Predicates

**Purpose:** Detection logic for identifying cleanable directories.

## FILES

```
predicates/
├── mod.rs           # Trait definitions (Removable, Reportable, Stop)
├── general.rs       # General predicates
├── stop.rs          # Stop conditions (HiddenDirStop, IsFileStop)
└── languages/       # Language-specific predicates
    ├── mod.rs       # LanguagePredicate trait
    ├── node.rs      # NodeModulesPredicate
    ├── rust.rs      # RustTargetPredicate
    └── git.rs       # GitDirtyRepoPredicate
```

## TRAIT SYSTEM

All language predicates implement 4 traits:

1. **LanguagePredicate** - `is_in_project(path)` - Detect project context
2. **Removable** - `is_removable(path)` - Should delete this?
3. **Stop** - `stop(path)` - Stop scanning deeper?
4. **Reportable** - `report(path)` - Include in results?

## ADDING A LANGUAGE

Copy `node.rs` or `rust.rs` pattern:

```rust
#[derive(Debug, Clone)]
pub struct MyLangPredicate;

impl LanguagePredicate for MyLangPredicate {
    fn is_in_project(&self, path: &Path) -> bool {
        // Check for language-specific marker file
        path.parent().map_or(false, |p| p.join("marker.file").is_file())
    }
}

impl Removable for MyLangPredicate {
    fn is_removable(&self, path: &Path) -> bool {
        self.is_in_project(path) && path.file_name() == Some("cache_dir".as_ref())
    }
}

impl Stop for MyLangPredicate {
    fn stop(&self, path: &Path) -> bool { self.is_removable(path) }
}

impl Reportable for MyLangPredicate {
    fn report(&self, path: &Path) -> bool { self.is_removable(path) }
}
```

Register in `scanner.rs`:
```rust
report_conditions: vec![
    Box::new(MyLangPredicate {}),
    // ...
]
```