|
| 1 | +# Design & Implementation Plan: EDM Scanner -- terraphim_negative_contribution Crate (#626) |
| 2 | + |
| 3 | +## 1. Summary of Target Behavior |
| 4 | + |
| 5 | +A new workspace crate `terraphim_negative_contribution` that statically detects Explicit Deferral Markers (EDMs) -- `todo!()`, `unimplemented!()`, and panic stubs -- in Rust source files. The scanner uses Aho-Corasick for O(n) multi-pattern matching, excludes non-production files, respects inline suppressions, and returns findings as `Vec<ReviewFinding>` for direct consumption by the compound review pipeline. |
| 6 | + |
| 7 | +## 2. Key Invariants and Acceptance Criteria |
| 8 | + |
| 9 | +### Invariants |
| 10 | +- Aho-Corasick automaton built once at construction, never rebuilt |
| 11 | +- `NegativeContributionScanner` is `Clone` + `Send + Sync` (Arc-safe, no interior mutability) |
| 12 | +- Tier 1 patterns only -- no Tier 2 (`// FIXME`, `// HACK`) |
| 13 | +- Single-pass byte scanning for all patterns simultaneously |
| 14 | + |
| 15 | +### Acceptance Criteria (from issue #626) |
| 16 | +- [ ] `todo!()` detected as High severity finding |
| 17 | +- [ ] `unimplemented!()` detected as High severity finding |
| 18 | +- [ ] `panic!("not implemented")` and `panic!("TODO")` detected as High severity |
| 19 | +- [ ] `todo!("` and `unimplemented!("` (with message) detected |
| 20 | +- [ ] `// terraphim: allow(stub)` suppresses finding on same line |
| 21 | +- [ ] Files under `/tests/`, `/examples/`, `/benches/` excluded by path |
| 22 | +- [ ] Files named `build.rs` excluded by path |
| 23 | +- [ ] Files with suffix `_test.rs` excluded by path |
| 24 | +- [ ] Files containing `#[test]` or `#[cfg(test)]` in full content excluded |
| 25 | +- [ ] `// FIXME` and `// HACK` NOT flagged (Tier 2, out of scope) |
| 26 | +- [ ] `cargo build -p terraphim_negative_contribution` succeeds |
| 27 | +- [ ] `cargo test -p terraphim_negative_contribution` passes |
| 28 | +- [ ] `cargo clippy -p terraphim_negative_contribution -- -D warnings` passes |
| 29 | +- [ ] `cargo fmt --check` passes |
| 30 | + |
| 31 | +## 3. High-Level Design and Boundaries |
| 32 | + |
| 33 | +``` |
| 34 | +┌─────────────────────────────────────────────────┐ |
| 35 | +│ terraphim_negative_contribution │ |
| 36 | +│ │ |
| 37 | +│ ┌──────────┐ ┌───────────┐ ┌──────────────┐│ |
| 38 | +│ │patterns │──>│ scanner │──>│ReviewFinding ││ |
| 39 | +│ │(Tier 1) │ │(AC-based) │ │(from types) ││ |
| 40 | +│ └──────────┘ └───────────┘ └──────────────┘│ |
| 41 | +│ │ │ │ |
| 42 | +│ │ ┌────┴─────┐ │ |
| 43 | +│ │ │exclusion │ │ |
| 44 | +│ │ │logic │ │ |
| 45 | +│ │ └──────────┘ │ |
| 46 | +└─────────────────────────────────────────────────┘ |
| 47 | + │ │ |
| 48 | + v v |
| 49 | + aho-corasick terraphim_types |
| 50 | + (1.0.2) (ReviewFinding etc.) |
| 51 | +``` |
| 52 | + |
| 53 | +### Module Layout |
| 54 | + |
| 55 | +| Module | Responsibility | |
| 56 | +|--------|---------------| |
| 57 | +| `src/lib.rs` | Crate root, re-exports, `NegativeContributionScanner` struct | |
| 58 | +| `src/patterns.rs` | Tier 1 pattern definitions, severity mapping | |
| 59 | +| `src/scanner.rs` | Aho-Corasick scanning, byte-offset-to-line conversion, suppression check | |
| 60 | +| `src/exclusion.rs` | `is_non_production()` path and content exclusion logic | |
| 61 | + |
| 62 | +### Boundaries |
| 63 | +- **Inside**: Pattern definitions, scanning logic, exclusion logic, finding construction |
| 64 | +- **Outside**: File I/O (caller provides content), diff parsing (future), LSP integration (future) |
| 65 | +- **No dependency on `terraphim_automata`**: Our patterns are compile-time constants, not user thesauruses |
| 66 | + |
| 67 | +## 4. File/Module-Level Change Plan |
| 68 | + |
| 69 | +| File | Action | Before | After | Dependencies | |
| 70 | +|------|--------|--------|-------|--------------| |
| 71 | +| `crates/terraphim_negative_contribution/Cargo.toml` | Create | - | Crate manifest with aho-corasick, terraphim_types deps | - | |
| 72 | +| `crates/terraphim_negative_contribution/src/lib.rs` | Create | - | Crate root, re-exports, `NegativeContributionScanner` struct | patterns, scanner, exclusion | |
| 73 | +| `crates/terraphim_negative_contribution/src/patterns.rs` | Create | - | `NEGATIVE_PATTERNS_TIER1`, `PatternInfo` with severity | - | |
| 74 | +| `crates/terraphim_negative_contribution/src/scanner.rs` | Create | - | `scan_content()` method, byte-offset-to-line, suppression | patterns, terraphim_types | |
| 75 | +| `crates/terraphim_negative_contribution/src/exclusion.rs` | Create | - | `is_non_production(path, content)` | - | |
| 76 | + |
| 77 | +No existing files modified. Pure addition. |
| 78 | + |
| 79 | +## 5. Step-by-Step Implementation Sequence |
| 80 | + |
| 81 | +### Step 1: Cargo.toml + empty lib.rs (~5 min) |
| 82 | +- Create crate directory |
| 83 | +- `Cargo.toml` with `aho-corasick = "1.0.2"`, `terraphim_types` path dep |
| 84 | +- Empty `lib.rs` with crate-level docs |
| 85 | +- **Deployable**: `cargo check -p terraphim_negative_contribution` passes |
| 86 | + |
| 87 | +### Step 2: patterns.rs (~10 min) |
| 88 | +- Define `NEGATIVE_PATTERNS_TIER1: &[&str]` with the 6 patterns |
| 89 | +- Define `PatternInfo` struct mapping pattern index to severity and description |
| 90 | +- Unit test: verify pattern count and content |
| 91 | +- **Deployable**: compiles, pattern unit test passes |
| 92 | + |
| 93 | +### Step 3: exclusion.rs (~15 min) |
| 94 | +- Implement `is_non_production(path: &str, full_content: &str) -> bool` |
| 95 | +- Path checks: `/tests/`, `/examples/`, `/benches/`, `build.rs`, `_test.rs` suffix |
| 96 | +- Content checks: `#[test]` or `#[cfg(test)]` anywhere in file |
| 97 | +- Unit tests for each exclusion rule (positive and negative) |
| 98 | +- **Deployable**: exclusion tests pass |
| 99 | + |
| 100 | +### Step 4: scanner.rs (~30 min) |
| 101 | +- `NegativeContributionScanner` struct with `AhoCorasick` field + `PatternInfo` vec |
| 102 | +- `new()` constructor: build automaton from `NEGATIVE_PATTERNS_TIER1` |
| 103 | +- `scan_file(path, content) -> Vec<ReviewFinding>`: |
| 104 | + 1. Check `is_non_production()` -> return empty if excluded |
| 105 | + 2. Build line offset map (Vec<usize> of `\n` positions) for byte-to-line conversion |
| 106 | + 3. Run `automaton.find_iter(content)` over byte slices |
| 107 | + 4. For each match: get line number, check suppression, construct `ReviewFinding` |
| 108 | +- `scan_files(files: &[(String, String)]) -> Vec<ReviewFinding>`: iterate + flatten |
| 109 | +- Unit tests for: basic detection, suppression, exclusion, multi-pattern, zero findings |
| 110 | +- **Deployable**: all scanner tests pass |
| 111 | + |
| 112 | +### Step 5: lib.rs integration + derive (~10 min) |
| 113 | +- `NegativeContributionScanner` derives `Clone` (AhoCorasick is Clone) |
| 114 | +- Re-export public API |
| 115 | +- Wire everything together |
| 116 | +- **Deployable**: full crate compiles and tests pass |
| 117 | + |
| 118 | +### Step 6: Clippy + fmt + final verification (~5 min) |
| 119 | +- `cargo clippy -p terraphim_negative_contribution -- -D warnings` |
| 120 | +- `cargo fmt -p terraphim_negative_contribution -- --check` |
| 121 | +- `cargo test -p terraphim_negative_contribution` |
| 122 | +- **Deployable**: all quality gates green |
| 123 | + |
| 124 | +## 6. Testing & Verification Strategy |
| 125 | + |
| 126 | +| Acceptance Criteria | Test Type | Test Location | |
| 127 | +|---------------------|-----------|---------------| |
| 128 | +| `todo!()` detected | Unit | `scanner.rs` `test_detect_todo` | |
| 129 | +| `unimplemented!()` detected | Unit | `scanner.rs` `test_detect_unimplemented` | |
| 130 | +| `panic!("not implemented")` detected | Unit | `scanner.rs` `test_detect_panic_not_implemented` | |
| 131 | +| `panic!("TODO")` detected | Unit | `scanner.rs` `test_detect_panic_todo` | |
| 132 | +| `todo!("with message")` detected | Unit | `scanner.rs` `test_detect_todo_with_message` | |
| 133 | +| `// terraphim: allow(stub)` suppresses | Unit | `scanner.rs` `test_suppression` | |
| 134 | +| `/tests/` path excluded | Unit | `exclusion.rs` `test_tests_dir_excluded` | |
| 135 | +| `/examples/` path excluded | Unit | `exclusion.rs` `test_examples_dir_excluded` | |
| 136 | +| `/benches/` path excluded | Unit | `exclusion.rs` `test_benches_dir_excluded` | |
| 137 | +| `build.rs` excluded | Unit | `exclusion.rs` `test_build_rs_excluded` | |
| 138 | +| `_test.rs` suffix excluded | Unit | `exclusion.rs` `test_test_suffix_excluded` | |
| 139 | +| `#[test]` in content excluded | Unit | `exclusion.rs` `test_inline_test_excluded` | |
| 140 | +| `#[cfg(test)]` in content excluded | Unit | `exclusion.rs` `test_cfg_test_excluded` | |
| 141 | +| `// FIXME` NOT flagged | Unit | `scanner.rs` `test_fixme_not_flagged` | |
| 142 | +| `// HACK` NOT flagged | Unit | `scanner.rs` `test_hack_not_flagged` | |
| 143 | +| Multiple patterns in one file | Unit | `scanner.rs` `test_multiple_patterns` | |
| 144 | +| Clean file returns empty | Unit | `scanner.rs` `test_clean_file` | |
| 145 | + |
| 146 | +## 7. Risk & Complexity Review |
| 147 | + |
| 148 | +| Risk | Mitigation | Residual Risk | |
| 149 | +|------|------------|---------------| |
| 150 | +| Aho-Corasick version conflict | Pin `aho-corasick = "1.0.2"` matching terraphim_automata | None | |
| 151 | +| False positive in doc comments | Patterns include `()` which don't appear in doc comments | Very low | |
| 152 | +| `Clone` derive on AhoCorasick | AhoCorasick implements Clone natively | None | |
| 153 | +| Line number off-by-one | Use 0-based `\n` counting, convert to 1-indexed | Test thoroughly | |
| 154 | + |
| 155 | +## 8. Open Questions / Decisions for Human Reviewer |
| 156 | + |
| 157 | +1. **`diffy` dependency**: I recommend NOT adding `diffy` for Step 1. We scan full file content, not diffs. Diff-based scanning can be added in Step 3 (compound review wiring) when needed. |
| 158 | + |
| 159 | +2. **Scanner return type**: I recommend returning `Vec<ReviewFinding>` directly rather than a custom signal type. This gives immediate compatibility with the compound review pipeline. |
| 160 | + |
| 161 | +3. **`is_non_production` visibility**: I recommend making it `pub` so the orchestrator can pre-filter file lists before calling the scanner. |
| 162 | + |
| 163 | +4. **Confidence value**: I recommend `0.95` for exact macro matches since they are deterministic. |
0 commit comments