towl 0.3.7

A fast CLI tool to scan codebases for TODO comments and output them in multiple formats
Documentation
# Scanner

The scanner walks a directory tree, filters files by extension and exclude patterns, reads content, and delegates to the parser for TODO extraction.

## `Scanner`

```rust
pub struct Scanner {
    parser: Parser,
    config: ParsingConfig,
}
```

### Constructor

```rust
pub fn new(config: ParsingConfig) -> Result<Self, TowlScannerError>
```

Creates a new scanner. Compiles all regex patterns from the config during construction so pattern errors are caught early.

### `scan`

```rust
pub async fn scan(&self, path: PathBuf) -> Result<ScanResult, TowlScannerError>
```

Recursively scans `path` for TODO comments. Returns a `ScanResult` on success.

**Behaviour:**

1. Validates the path (rejects path traversal)
2. Walks the directory using the `ignore` crate (respects `.gitignore`)
3. Filters files by extension (`file_extensions` config)
4. Skips files matching `exclude_patterns`
5. Skips files larger than `MAX_FILE_SIZE` (10 MB)
6. Reads and parses each file asynchronously via `tokio::fs`
7. Collects results until a resource limit is reached or the walk completes

**Errors:**

- `InvalidPath` -- Path contains traversal components (`..`)
- `FileTooLarge` -- File exceeds 10 MB
- `TooManyTodos` -- Single file exceeds 10,000 TODOs
- `TooManyFiles` -- Walk exceeds 100,000 files
- `UnableToReadFileAtPath` -- I/O error reading a specific file
- `UnableToWalkFile` -- Directory walk error
- `ParsingError` -- Regex or parsing failure (propagated from parser)

## `ScanResult`

```rust
pub struct ScanResult {
    pub todos: Vec<TodoComment>,
    pub files_scanned: usize,
    pub files_skipped: usize,
    pub files_errored: usize,
    pub duration: std::time::Duration,
}
```

### Methods

```rust
pub const fn all_files_failed(&self) -> bool
```

Returns `true` when `files_scanned == 0` and `files_errored > 0`. Indicates a likely permissions or path issue where no files could be read.

```rust
pub const fn is_clean(&self) -> bool
```

Returns `true` when `todos` is empty and `files_errored == 0`. A clean scan with no issues.

## Resource Limits

| Constant | Value | Trigger |
|----------|-------|---------|
| `MAX_FILE_SIZE` | 10,485,760 bytes (10 MB) | File skipped |
| `MAX_TODO_COUNT` | 10,000 | Error for that file |
| `MAX_TOTAL_TODO_COUNT` | 100,000 | Scan stops, returns partial |
| `MAX_FILES_SCANNED` | 100,000 | Scan stops, returns partial |

## Example

```rust
use towl::config::ParsingConfig;
use towl::scanner::Scanner;
use std::path::PathBuf;

let config = ParsingConfig::default();
let scanner = Scanner::new(config)?;
let result = scanner.scan(PathBuf::from(".")).await?;

println!("Found {} TODOs in {} files", result.todos.len(), result.files_scanned);

if result.all_files_failed() {
    eprintln!("Warning: no files could be read");
}
```