diff --git a/.cargo/audit.toml b/.cargo/audit.toml index bd0c80958..cc39343af 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -23,4 +23,29 @@ ignore = [ # - Mitigation: discord feature disabled by default, patch attempted in Cargo.toml [patch.crates-io] # - Resolution: Remove once serenity 0.13+ releases with rustls 0.23+ support "RUSTSEC-2026-0049", + # RUSTSEC-2026-0098: rustls-webpki name constraints for URI names incorrectly accepted + # - Package: rustls-webpki 0.102.8 (via serenity 0.12.5 chain, same as RUSTSEC-2026-0049) + # - serenity is OPTIONAL (discord feature), NOT in default features + # - semver-incompatible with 0.103.x fix; blocked on serenity 0.13+ + # - Resolution: Remove once serenity 0.13+ releases + "RUSTSEC-2026-0098", + # RUSTSEC-2026-0099: rustls-webpki wildcard name constraints incorrectly accepted + # - Package: rustls-webpki 0.102.8 (via serenity 0.12.5 chain, same as RUSTSEC-2026-0049/0098) + # - serenity is OPTIONAL (discord feature), NOT in default features + # - semver-incompatible with 0.103.x fix; blocked on serenity 0.13+ + # - Resolution: Remove once serenity 0.13+ releases + "RUSTSEC-2026-0099", + # RUSTSEC-2026-0097: rand unsound with custom logger using rand::rng() + # - Affects rand 0.8.5 (transitive via tungstenite/sqlx/portpicker/mcp-client) + # - and rand 0.9.2 (transitive via ulid/tokio-tungstenite/redis/rmcp) + # - Our codebase does not use custom loggers that call rand::rng() + # - No practical exploit path in our usage + # - Resolution: Transitive deps will update naturally + "RUSTSEC-2026-0097", + # RUSTSEC-2025-0141: bincode 1.3.3 unmaintained + # - Used by terraphim_automata and heed-types (via fff-search) + # - Migration to bincode 2.0 or postcard planned as separate task + # - No known vulnerabilities, just unmaintained + # - Resolution: Migrate to bincode 2.0 or postcard (tracked in backlog) + "RUSTSEC-2025-0141", ] diff --git a/.docs/design-edm-scanner-626.md b/.docs/design-edm-scanner-626.md new file mode 100644 index 000000000..8a1ef50d1 --- /dev/null +++ b/.docs/design-edm-scanner-626.md @@ -0,0 +1,163 @@ +# Design & Implementation Plan: EDM Scanner -- terraphim_negative_contribution Crate (#626) + +## 1. Summary of Target Behavior + +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` for direct consumption by the compound review pipeline. + +## 2. Key Invariants and Acceptance Criteria + +### Invariants +- Aho-Corasick automaton built once at construction, never rebuilt +- `NegativeContributionScanner` is `Clone` + `Send + Sync` (Arc-safe, no interior mutability) +- Tier 1 patterns only -- no Tier 2 (`// FIXME`, `// HACK`) +- Single-pass byte scanning for all patterns simultaneously + +### Acceptance Criteria (from issue #626) +- [ ] `todo!()` detected as High severity finding +- [ ] `unimplemented!()` detected as High severity finding +- [ ] `panic!("not implemented")` and `panic!("TODO")` detected as High severity +- [ ] `todo!("` and `unimplemented!("` (with message) detected +- [ ] `// terraphim: allow(stub)` suppresses finding on same line +- [ ] Files under `/tests/`, `/examples/`, `/benches/` excluded by path +- [ ] Files named `build.rs` excluded by path +- [ ] Files with suffix `_test.rs` excluded by path +- [ ] Files containing `#[test]` or `#[cfg(test)]` in full content excluded +- [ ] `// FIXME` and `// HACK` NOT flagged (Tier 2, out of scope) +- [ ] `cargo build -p terraphim_negative_contribution` succeeds +- [ ] `cargo test -p terraphim_negative_contribution` passes +- [ ] `cargo clippy -p terraphim_negative_contribution -- -D warnings` passes +- [ ] `cargo fmt --check` passes + +## 3. High-Level Design and Boundaries + +``` +┌─────────────────────────────────────────────────┐ +│ terraphim_negative_contribution │ +│ │ +│ ┌──────────┐ ┌───────────┐ ┌──────────────┐│ +│ │patterns │──>│ scanner │──>│ReviewFinding ││ +│ │(Tier 1) │ │(AC-based) │ │(from types) ││ +│ └──────────┘ └───────────┘ └──────────────┘│ +│ │ │ │ +│ │ ┌────┴─────┐ │ +│ │ │exclusion │ │ +│ │ │logic │ │ +│ │ └──────────┘ │ +└─────────────────────────────────────────────────┘ + │ │ + v v + aho-corasick terraphim_types + (1.0.2) (ReviewFinding etc.) +``` + +### Module Layout + +| Module | Responsibility | +|--------|---------------| +| `src/lib.rs` | Crate root, re-exports, `NegativeContributionScanner` struct | +| `src/patterns.rs` | Tier 1 pattern definitions, severity mapping | +| `src/scanner.rs` | Aho-Corasick scanning, byte-offset-to-line conversion, suppression check | +| `src/exclusion.rs` | `is_non_production()` path and content exclusion logic | + +### Boundaries +- **Inside**: Pattern definitions, scanning logic, exclusion logic, finding construction +- **Outside**: File I/O (caller provides content), diff parsing (future), LSP integration (future) +- **No dependency on `terraphim_automata`**: Our patterns are compile-time constants, not user thesauruses + +## 4. File/Module-Level Change Plan + +| File | Action | Before | After | Dependencies | +|------|--------|--------|-------|--------------| +| `crates/terraphim_negative_contribution/Cargo.toml` | Create | - | Crate manifest with aho-corasick, terraphim_types deps | - | +| `crates/terraphim_negative_contribution/src/lib.rs` | Create | - | Crate root, re-exports, `NegativeContributionScanner` struct | patterns, scanner, exclusion | +| `crates/terraphim_negative_contribution/src/patterns.rs` | Create | - | `NEGATIVE_PATTERNS_TIER1`, `PatternInfo` with severity | - | +| `crates/terraphim_negative_contribution/src/scanner.rs` | Create | - | `scan_content()` method, byte-offset-to-line, suppression | patterns, terraphim_types | +| `crates/terraphim_negative_contribution/src/exclusion.rs` | Create | - | `is_non_production(path, content)` | - | + +No existing files modified. Pure addition. + +## 5. Step-by-Step Implementation Sequence + +### Step 1: Cargo.toml + empty lib.rs (~5 min) +- Create crate directory +- `Cargo.toml` with `aho-corasick = "1.0.2"`, `terraphim_types` path dep +- Empty `lib.rs` with crate-level docs +- **Deployable**: `cargo check -p terraphim_negative_contribution` passes + +### Step 2: patterns.rs (~10 min) +- Define `NEGATIVE_PATTERNS_TIER1: &[&str]` with the 6 patterns +- Define `PatternInfo` struct mapping pattern index to severity and description +- Unit test: verify pattern count and content +- **Deployable**: compiles, pattern unit test passes + +### Step 3: exclusion.rs (~15 min) +- Implement `is_non_production(path: &str, full_content: &str) -> bool` +- Path checks: `/tests/`, `/examples/`, `/benches/`, `build.rs`, `_test.rs` suffix +- Content checks: `#[test]` or `#[cfg(test)]` anywhere in file +- Unit tests for each exclusion rule (positive and negative) +- **Deployable**: exclusion tests pass + +### Step 4: scanner.rs (~30 min) +- `NegativeContributionScanner` struct with `AhoCorasick` field + `PatternInfo` vec +- `new()` constructor: build automaton from `NEGATIVE_PATTERNS_TIER1` +- `scan_file(path, content) -> Vec`: + 1. Check `is_non_production()` -> return empty if excluded + 2. Build line offset map (Vec of `\n` positions) for byte-to-line conversion + 3. Run `automaton.find_iter(content)` over byte slices + 4. For each match: get line number, check suppression, construct `ReviewFinding` +- `scan_files(files: &[(String, String)]) -> Vec`: iterate + flatten +- Unit tests for: basic detection, suppression, exclusion, multi-pattern, zero findings +- **Deployable**: all scanner tests pass + +### Step 5: lib.rs integration + derive (~10 min) +- `NegativeContributionScanner` derives `Clone` (AhoCorasick is Clone) +- Re-export public API +- Wire everything together +- **Deployable**: full crate compiles and tests pass + +### Step 6: Clippy + fmt + final verification (~5 min) +- `cargo clippy -p terraphim_negative_contribution -- -D warnings` +- `cargo fmt -p terraphim_negative_contribution -- --check` +- `cargo test -p terraphim_negative_contribution` +- **Deployable**: all quality gates green + +## 6. Testing & Verification Strategy + +| Acceptance Criteria | Test Type | Test Location | +|---------------------|-----------|---------------| +| `todo!()` detected | Unit | `scanner.rs` `test_detect_todo` | +| `unimplemented!()` detected | Unit | `scanner.rs` `test_detect_unimplemented` | +| `panic!("not implemented")` detected | Unit | `scanner.rs` `test_detect_panic_not_implemented` | +| `panic!("TODO")` detected | Unit | `scanner.rs` `test_detect_panic_todo` | +| `todo!("with message")` detected | Unit | `scanner.rs` `test_detect_todo_with_message` | +| `// terraphim: allow(stub)` suppresses | Unit | `scanner.rs` `test_suppression` | +| `/tests/` path excluded | Unit | `exclusion.rs` `test_tests_dir_excluded` | +| `/examples/` path excluded | Unit | `exclusion.rs` `test_examples_dir_excluded` | +| `/benches/` path excluded | Unit | `exclusion.rs` `test_benches_dir_excluded` | +| `build.rs` excluded | Unit | `exclusion.rs` `test_build_rs_excluded` | +| `_test.rs` suffix excluded | Unit | `exclusion.rs` `test_test_suffix_excluded` | +| `#[test]` in content excluded | Unit | `exclusion.rs` `test_inline_test_excluded` | +| `#[cfg(test)]` in content excluded | Unit | `exclusion.rs` `test_cfg_test_excluded` | +| `// FIXME` NOT flagged | Unit | `scanner.rs` `test_fixme_not_flagged` | +| `// HACK` NOT flagged | Unit | `scanner.rs` `test_hack_not_flagged` | +| Multiple patterns in one file | Unit | `scanner.rs` `test_multiple_patterns` | +| Clean file returns empty | Unit | `scanner.rs` `test_clean_file` | + +## 7. Risk & Complexity Review + +| Risk | Mitigation | Residual Risk | +|------|------------|---------------| +| Aho-Corasick version conflict | Pin `aho-corasick = "1.0.2"` matching terraphim_automata | None | +| False positive in doc comments | Patterns include `()` which don't appear in doc comments | Very low | +| `Clone` derive on AhoCorasick | AhoCorasick implements Clone natively | None | +| Line number off-by-one | Use 0-based `\n` counting, convert to 1-indexed | Test thoroughly | + +## 8. Open Questions / Decisions for Human Reviewer + +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. + +2. **Scanner return type**: I recommend returning `Vec` directly rather than a custom signal type. This gives immediate compatibility with the compound review pipeline. + +3. **`is_non_production` visibility**: I recommend making it `pub` so the orchestrator can pre-filter file lists before calling the scanner. + +4. **Confidence value**: I recommend `0.95` for exact macro matches since they are deterministic. diff --git a/.docs/research-edm-scanner-626.md b/.docs/research-edm-scanner-626.md new file mode 100644 index 000000000..fdff0c718 --- /dev/null +++ b/.docs/research-edm-scanner-626.md @@ -0,0 +1,127 @@ +# Research Document: EDM Scanner -- terraphim_negative_contribution Crate + +## 1. Problem Restatement and Scope + +**Problem**: LLM-generated code frequently contains deferral macros (`todo!()`, `unimplemented!()`, panic stubs) as scaffolding. These "negative contributions" slip through code review and accumulate as latent bugs. A zero-LLM static scanner can detect these before expensive LLM-based review runs. + +**In scope** (#626 only): +- New crate `terraphim_negative_contribution` with Tier 1 pattern matching +- Aho-Corasick automaton for multi-pattern byte scanning +- File exclusion logic (tests, examples, benches, build.rs, inline test modules) +- Inline suppression via `// terraphim: allow(stub)` +- Output as `Vec` compatible with existing compound review pipeline +- Unit tests covering all specified cases + +**Out of scope** (deferred to later steps): +- Compound review wiring (`run_static_precheck()` in orchestrator) -- #627 +- Integration test baseline against codebase -- #628 +- LSP server crate (`terraphim_lsp`) -- #629 +- Tier 2 patterns (`// FIXME`, `// HACK`) +- CoverageSignal analysis (eliminated by via-negativa) + +## 2. User & Business Outcomes + +- Compound review detects stubs before spending LLM tokens on them +- Review agents spend time on real issues, not scaffolding detection +- Foundational crate enables future LSP integration for real-time editor feedback +- Zero LLM cost for this class of detection + +## 3. System Elements and Dependencies + +### New Crate +| Element | Location | Role | +|---------|----------|------| +| `terraphim_negative_contribution` | `crates/terraphim_negative_contribution/` | Core EDM scanner | + +### Existing Dependencies (incoming) +| Crate | Version | What We Use | +|-------|---------|-------------| +| `terraphim_types` | 1.15+ (workspace) | `ReviewFinding`, `FindingSeverity`, `FindingCategory`, `ReviewAgentOutput`, `deduplicate_findings()` | +| `aho-corasick` | 1.0.2 (via terraphim_automata) | Multi-pattern byte scanning. Direct dep needed since we don't use Thesaurus/find_matches. | + +### Downstream Consumers (outgoing, future) +| Crate | How They'll Use It | +|-------|-------------------| +| `terraphim_orchestrator` | `Arc` in compound review pre-check (#627) | +| `terraphim_lsp` | Scanner reuse for editor diagnostics (#629) | + +### Key Type Mappings +``` +aho_corasick::AhoCorasick -> built once at new(), shared via &self +pattern index -> NEGATIVE_PATTERNS_TIER1[index] -> pattern name +byte offset -> count \n before offset -> line number +line content -> contains "terraphim: allow(stub)" -> suppress +file path -> is_non_production() -> skip entirely +``` + +### Review Types (from terraphim_types::review) +```rust +ReviewFinding { + file: String, // relative path + line: u32, // 1-indexed line number + severity: FindingSeverity, // High for todo!/unimplemented!() + category: FindingCategory, // Quality + finding: String, // human-readable description + suggestion: Option, // "Replace with implementation" + confidence: f64, // 0.95 for exact macro match +} + +ReviewAgentOutput { + agent: String, // "edm-scanner" + findings: Vec, + summary: String, + pass: bool, // true if 0 findings +} +``` + +## 4. Constraints and Their Implications + +| Constraint | Why It Matters | Implication | +|------------|----------------|-------------| +| Aho-Corasick built once at `new()` | Avoids rebuilding per file | Scanner must be `Clone` + `Arc`-safe, no interior mutability | +| `is_non_production` checks full content | `#[test]` can appear anywhere, not just top 20 lines | Must scan entire file content for test markers -- acceptable since we're already scanning for patterns | +| Tier 1 patterns only | Tier 2 (`// FIXME`) has too many false positives | Only exact macro invocations: `todo!()`, `unimplemented!()`, panic variants | +| Inline suppression | Some stubs are intentional (e.g., trait defaults) | Check line content for `// terraphim: allow(stub)` before creating finding | +| No `diff.rs` | Use `diffy` crate | Don't reinvent diff parsing. But for Step 1, we scan file content, not diffs | +| Edition 2024 | Workspace standard | `use` statements must follow 2024 edition rules | +| Must not depend on `terraphim_automata` Thesaurus | Our patterns are fixed strings, not user-configurable thesaurus entries | Use `aho-corasick` directly, not `find_matches()` | + +## 5. Risks, Unknowns, and Assumptions + +### Risks +| Risk | Impact | Likelihood | Mitigation | +|------|--------|------------|------------| +| False positives in macro-heavy crates | Medium | Low | Inline suppression mechanism | +| `aho-corasick` version conflict with `terraphim_automata` | Build failure | Low | Pin same version (1.0.2) | +| Scanning large files slowly | Performance | Low | Aho-Corasick is O(n); single pass | + +### Unknowns +- Whether `diffy` is needed for Step 1 (likely no -- we scan full file content, not diffs) +- Whether `ReviewAgentOutput` needs to be produced by the scanner itself or by the orchestrator wrapper + +### Assumptions +- The scanner operates on file content strings, not git diffs (diff-based scanning is future work) +- `ReviewAgentOutput` is constructed by the caller, not the scanner itself +- Confidence for exact macro matches is 0.95 (near-certain) +- File paths are relative to repo root + +## 6. Context Complexity vs. Simplicity Opportunities + +### Sources of Complexity +- Two exclusion mechanisms (path-based AND content-based) +- Suppression syntax parsing + +### Simplifications +1. **Direct `aho-corasick` instead of `terraphim_automata`**: Our patterns are fixed compile-time constants. No need for Thesaurus builder, NormalizedTerm, or any KG machinery. +2. **Single-pass scanning**: Aho-Corasick scans for all patterns simultaneously. No need for multiple passes. +3. **Separate `is_non_production` from scanning**: Clean separation of concerns. Scanner only scans; caller decides what files to feed it. + +## 7. Questions for Human Reviewer + +1. **Should `diffy` be a dependency of this crate, or only added when diff-based scanning is needed in Steps 2-3?** I recommend omitting it for Step 1 since we scan full file content. + +2. **Should the scanner return `Vec` directly, or a custom `NegativeContributionSignal` type that the caller converts?** I recommend `Vec` directly for simplicity and immediate compatibility. + +3. **Is `confidence: 0.95` appropriate for exact `todo!()` macro matches?** These are deterministic string matches, not probabilistic. + +4. **Should `is_non_production()` be a public function or internal to the scanner?** Making it public allows the orchestrator to pre-filter file lists before calling the scanner. diff --git a/.docs/research-project-status-2026-04-21.md b/.docs/research-project-status-2026-04-21.md new file mode 100644 index 000000000..b1fc080d2 --- /dev/null +++ b/.docs/research-project-status-2026-04-21.md @@ -0,0 +1,163 @@ +# Research Document: Project Status Assessment 2026-04-21 + +## 1. Problem Restatement and Scope + +**Problem**: Determine what work can be progressed right now, given the current state of the codebase, open issues, PRs, and infrastructure. + +**In scope**: +- Current build/test/lint status +- Open PRs on GitHub and Gitea +- High-priority unblocked issues from PageRank triage +- Infrastructure blockers (CI runners, disk, memory) +- Security vulnerabilities + +**Out of scope**: +- New feature design from scratch +- Long-term roadmap planning +- Marketing/content tasks + +## 2. Current System State + +### 2.1 Build & Lint Status +- **cargo build --workspace**: PASSES (5 warnings in terraphim_agent -- unused functions in procedure.rs) +- **cargo clippy --workspace**: PASSES with same 5 warnings +- **cargo test --workspace**: TIMED OUT after 180s (needs investigation -- likely individual slow tests, not a fundamental breakage) +- **Branch**: main, up-to-date with origin/main + +### 2.2 Untracked Files +- `session-ses_2898.md` (session artifact) +- `update_output.txt` (temporary output) + +### 2.3 Warnings to Fix +- `terraphim_agent/src/learnings/procedure.rs`: `TRIVIAL_COMMANDS`, `is_trivial_command`, `from_session_commands` are unused + +## 3. Open PRs + +### GitHub PRs (2 open) + +| PR | Title | State | Mergeable | Branch | +|----|-------|-------|-----------|--------| +| #825 | feat(repl): add --format and --robot flags to /search command | OPEN | CONFLICTING | task/696-repl-format-robot-dispatch | +| #823 | fix(security): RUSTSEC-2026-0098/0099 + rand 0.10.1 + remove dead mcp-client dep | OPEN | UNKNOWN | task/632-security_checklist-remediation-russecurity_checklist-webpki | + +### Gitea PRs +- Gitea has open PRs (list-pulls output was large/truncated) -- need further investigation + +### PR Analysis + +**PR #823 (security fixes)**: HIGHEST PRIORITY +- Fixes RUSTSEC-2026-0098/0099/0097 (critical CVEs) +- Upgrades rustls-webpki, removes dead mcp-client dep +- Adds Ollama binding fix script +- Mergeable status UNKNOWN -- needs rebase/conflict check +- CI cannot validate (runners stopped) +- **Actionable**: Rebase onto main, resolve conflicts, merge manually if CI is down + +**PR #825 (REPL format flags)**: CONFLICTING +- Adds --format and --robot flags to REPL /search command +- Has merge conflicts with main +- Gitea issue #696 already CLOSED (work was tracked there) +- Tests: 29/29 pass +- **Actionable**: Rebase onto main, resolve conflicts + +### Gitea Issue #326: Merge feature/warp-drive-theme into main +- Priority 182 (highest in entire backlog) +- Assigned to merge-coordinator +- Contains ADF pipeline gap fixes, compound review improvements +- 91 comments -- long-running coordination issue +- **Actionable**: Check branch status, resolve conflicts, merge + +## 4. Top Unblocked Issues by PageRank + +From `gtr ready` output, these are unblocked and have the highest PageRank/priority: + +### Critical / Infrastructure (Do First) + +| Index | Title | Priority | Status | +|-------|-------|----------|--------| +| #725 | [Infra] All GitHub Actions runners STOPPED | 0 | open, unblocked | +| #695 | [Infra] Rust target dir 602G -- schedule cargo clean | 0 | open, unblocked | +| #874 | [ADF] CRITICAL: Memory exhaustion -- 99.5% (63.7G/64G) | 0 | open, unblocked | +| #644 | Security Audit: Critical CVEs + Port 11434 Exposure | 12 | open, unblocked | + +### High-Value Feature Work (Unblocked) + +| Index | Title | Priority | PageRank | +|-------|-------|----------|----------| +| #326 | Merge feature/warp-drive-theme into main | 182 | 0.15 | +| #625 | Epic: EDM Scanner | 40 | 0.15 | +| #626 | EDM Scanner: terraphim_negative_contribution crate | 40 | 0.005 | +| #680 | feat(codebase-eval): evaluation manifest types and TOML/YAML loader | 0 | 0.008 | +| #144 | Epic: Inter-agent orchestration via Gitea mentions | 50 | 0.017 | +| #416 | [ADF] Epic: Ship ADF agent logs to Quickwit | 100 | 0.15 | +| #330 | Phase 1: SQLite shared learning store | 37 | 0.004 | + +### Quick Wins + +| Index | Title | Effort | +|-------|-------|--------| +| #867/#695 | cargo clean (602GB target dir) | 1 command | +| #868 | Remove unused procedure store methods | Small | +| #869 | Format code in mention.rs | Trivial | +| #216 | Clean up dead code and deprecated functions | Small | + +## 5. Constraints and Blockers + +### Hard Blockers +1. **CI runners all STOPPED** (#725) -- cannot validate PRs automatically +2. **Memory at 99.5%** (#874) -- system may OOM during builds/tests +3. **Target dir 602GB** (#695/#867) -- disk pressure affecting builds + +### Soft Constraints +1. Security CVEs block production merges (#644, #632) +2. PR #825 has merge conflicts +3. No tea CLI login available (use gtr via API instead) + +### Assumptions +- The security PR #823 was created on a previous session and may need rebase +- The warp-drive-theme branch may be stale (created Apr 5, last updated Apr 7) +- cargo test timeout may be due to memory pressure (99.5% RAM used) + +## 6. Risks, Unknowns, and Assumptions + +### Risks +| Risk | Impact | Likelihood | +|------|--------|------------| +| OOM during cargo operations | High (system crash) | High (99.5% memory) | +| Stale warp-drive-theme branch conflicts | Medium (merge difficulty) | Medium | +| Security PR #823 conflicts with main | Medium (delays CVE fix) | Medium | + +### Unknowns +- Whether cargo clean will free enough space for comfortable operation +- Whether the memory exhaustion is from running agents or leaked processes +- State of the warp-drive-theme branch after 2 weeks + +### Assumptions +- CI runners can be restarted by running the commands in issue #725 +- Security fixes in PR #823 are sound (already reviewed in previous session) +- The 5 clippy warnings are safe to fix in passing + +## 7. Recommended Action Sequence + +### Immediate (Do Now) +1. **cargo clean** (#695/#867) -- frees ~600GB, reduces memory pressure +2. **Kill zombie/leaked processes** (#874, #857) -- free RAM +3. **Merge security PR #823** (#632, #644) -- resolve conflicts, merge. Critical CVEs. + +### Short-Term (This Session) +4. **Resolve PR #825 conflicts** -- rebase task/696 branch, fix conflicts +5. **Fix 5 clippy warnings** in procedure.rs -- remove dead code +6. **Start EDM Scanner Step 1** (#626) -- create terraphim_negative_contribution crate skeleton + +### After Infrastructure Restored +7. **Restart CI runners** (#725) +8. **Assess warp-drive-theme branch** (#326) -- check if still viable or stale +9. **Continue codebase-eval manifest types** (#680) + +## 8. Questions for Human Reviewer + +1. **Should I proceed with cargo clean immediately?** It will require rebuilding everything (~3-5 min) but frees 600GB and may resolve the memory issue. +2. **Security PR #823**: Should I rebase and merge it now, or wait for CI to be restored? +3. **warp-drive-theme branch (#326)**: Is this still relevant after 2 weeks, or should it be closed as stale? +4. **Which feature should I prioritise after infrastructure fixes?** EDM Scanner (#626), codebase-eval (#680), or shared learning store (#330)? +5. **Should I restart the GitHub Actions runners?** This is a manual operation on bigbox. diff --git a/Cargo.lock b/Cargo.lock index 8dbe56863..44b51c5f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,10 +280,10 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "itoa", "matchit 0.7.3", @@ -296,9 +296,9 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -316,10 +316,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "itoa", "matchit 0.8.4", @@ -332,10 +332,10 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-tungstenite 0.28.0", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -350,13 +350,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -370,12 +370,12 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -404,9 +404,9 @@ dependencies = [ "bytesize", "cookie", "expect-json", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "mime", "pretty_assertions", @@ -416,7 +416,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tower 0.5.3", + "tower", "url", ] @@ -611,9 +611,9 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-named-pipe", "hyper-util", "hyperlocal", @@ -2218,22 +2218,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "eventsource-client" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c80c6714d1a380314fcb11a22eeff022e1e1c9642f0bb54e15dc9cb29f37b29" -dependencies = [ - "futures", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "hyper-timeout", - "log", - "pin-project", - "rand 0.8.5", - "tokio", -] - [[package]] name = "eventsource-stream" version = "0.2.3" @@ -2685,8 +2669,8 @@ dependencies = [ "derive_more 2.1.1", "eventsource-stream", "futures", - "reqwest 0.12.28", - "reqwest-eventsource 0.6.0", + "reqwest", + "reqwest-eventsource", "serde", "serde_json", "serde_with", @@ -2843,11 +2827,11 @@ dependencies = [ [[package]] name = "grepapp_haystack" -version = "1.16.34" +version = "1.16.35" dependencies = [ "anyhow", "haystack_core", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "terraphim_types", @@ -2858,25 +2842,6 @@ dependencies = [ "wiremock", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.13.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.13" @@ -2888,7 +2853,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap 2.13.1", "slab", "tokio", @@ -2980,7 +2945,7 @@ dependencies = [ [[package]] name = "haystack_core" -version = "1.16.34" +version = "1.16.35" dependencies = [ "terraphim_types", "tokio", @@ -2995,7 +2960,7 @@ dependencies = [ "env_logger", "haystack_core", "log", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "terraphim_types", @@ -3136,17 +3101,6 @@ dependencies = [ "markup5ever 0.36.1", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -3157,17 +3111,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -3175,7 +3118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -3186,8 +3129,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -3209,30 +3152,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.9.0" @@ -3243,9 +3162,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -3262,7 +3181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.9.0", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -3270,33 +3189,17 @@ dependencies = [ "winapi", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "log", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", - "hyper 1.9.0", + "http", + "hyper", "hyper-util", "rustls 0.23.37", - "rustls-native-certs 0.8.3", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -3304,18 +3207,6 @@ dependencies = [ "webpki-roots 1.0.6", ] -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.32", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -3324,7 +3215,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "native-tls", "tokio", @@ -3342,15 +3233,15 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.9.0", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", - "system-configuration 0.7.0", + "socket2", + "system-configuration", "tokio", "tower-service", "tracing", @@ -3365,7 +3256,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -4214,47 +4105,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" -[[package]] -name = "mcp-client" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7e00680bf6bd7430397536dd07ce9027aebb2854c2270b2da1f489bc20955b" -dependencies = [ - "anyhow", - "async-trait", - "eventsource-client", - "futures", - "mcp-spec", - "rand 0.8.5", - "reqwest 0.11.27", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tower 0.4.13", - "tower-service", - "tracing", - "tracing-subscriber", - "url", -] - -[[package]] -name = "mcp-spec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a294f4fcf9c3a0b3d168937947ecb906feb691089dd9fbbefc9707c8a4557163" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.21.7", - "chrono", - "schemars 0.8.22", - "serde", - "serde_json", - "thiserror 1.0.69", - "url", -] - [[package]] name = "md-5" version = "0.10.6" @@ -4433,10 +4283,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.2.1", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 3.7.0", + "security-framework", "security-framework-sys", "tempfile", ] @@ -4987,8 +4837,8 @@ dependencies = [ "dashmap 6.1.0", "futures", "getrandom 0.2.17", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "log", "md-5", "ouroboros", @@ -4998,7 +4848,7 @@ dependencies = [ "redb", "redis", "reqsign", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "sqlx", @@ -5032,12 +4882,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -5801,7 +5645,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls 0.23.37", - "socket2 0.6.3", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -5838,7 +5682,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -6113,10 +5957,10 @@ dependencies = [ "pin-project-lite", "rand 0.9.2", "rustls 0.23.37", - "rustls-native-certs 0.8.3", + "rustls-native-certs", "ryu", "sha1_smol", - "socket2 0.6.3", + "socket2", "tokio", "tokio-rustls 0.26.4", "tokio-util", @@ -6236,12 +6080,12 @@ dependencies = [ "hex", "hmac", "home", - "http 1.4.0", + "http", "log", "percent-encoding", "quick-xml 0.37.5", "rand 0.8.5", - "reqwest 0.12.28", + "reqwest", "rust-ini", "serde", "serde_json", @@ -6250,49 +6094,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-rustls 0.24.1", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots 0.25.4", - "winreg", -] - [[package]] name = "reqwest" version = "0.12.28" @@ -6305,12 +6106,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.9.0", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -6326,12 +6127,12 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-native-tls", "tokio-rustls 0.26.4", "tokio-util", - "tower 0.5.3", + "tower", "tower-http 0.6.8", "tower-service", "url", @@ -6342,22 +6143,6 @@ dependencies = [ "webpki-roots 1.0.6", ] -[[package]] -name = "reqwest-eventsource" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f529a5ff327743addc322af460761dff5b50e0c826b9e6ac44c3195c50bb2026" -dependencies = [ - "eventsource-stream", - "futures-core", - "futures-timer", - "mime", - "nom", - "pin-project-lite", - "reqwest 0.11.27", - "thiserror 1.0.69", -] - [[package]] name = "reqwest-eventsource" version = "0.6.0" @@ -6370,7 +6155,7 @@ dependencies = [ "mime", "nom", "pin-project-lite", - "reqwest 0.12.28", + "reqwest", "thiserror 1.0.69", ] @@ -6418,8 +6203,8 @@ dependencies = [ "bytes", "chrono", "futures", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "paste", "pin-project-lite", @@ -6568,7 +6353,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http", "mime", "rand 0.10.0", "thiserror 2.0.18", @@ -6621,18 +6406,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.22.4" @@ -6662,37 +6435,16 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", + "security-framework", ] [[package]] @@ -6705,16 +6457,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.8" @@ -6918,16 +6660,6 @@ dependencies = [ "tendril", ] -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sdd" version = "3.0.10" @@ -6944,19 +6676,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -7017,13 +6736,13 @@ source = "git+https://github.com/AlexMikhalev/self_update.git?branch=update-zips dependencies = [ "either", "flate2", - "http 1.4.0", - "hyper 1.9.0", + "http", + "hyper", "indicatif 0.17.11", "log", "quick-xml 0.37.5", "regex", - "reqwest 0.12.28", + "reqwest", "self-replace", "semver", "serde", @@ -7257,7 +6976,7 @@ dependencies = [ "mime_guess", "parking_lot 0.12.5", "percent-encoding", - "reqwest 0.12.28", + "reqwest", "rustc-hash 2.1.2", "secrecy", "serde", @@ -7487,10 +7206,10 @@ dependencies = [ "futures-util", "hex", "hmac", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.9.0", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-util", "lazy_static", "mime", @@ -7533,16 +7252,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -7770,7 +7479,7 @@ checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" dependencies = [ "bytes", "futures-util", - "http-body 1.0.1", + "http-body", "http-body-util", "pin-project-lite", ] @@ -8061,12 +7770,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -8111,17 +7814,6 @@ dependencies = [ "windows 0.52.0", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - [[package]] name = "system-configuration" version = "0.7.0" @@ -8130,17 +7822,7 @@ checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.11.0", "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -8249,7 +7931,7 @@ dependencies = [ "once_cell", "pin-project", "rc-box", - "reqwest 0.12.28", + "reqwest", "rgb", "serde", "serde_json", @@ -8390,7 +8072,7 @@ dependencies = [ [[package]] name = "terraphim-cli" -version = "1.16.34" +version = "1.16.35" dependencies = [ "anyhow", "assert_cmd", @@ -8430,7 +8112,7 @@ dependencies = [ "futures", "log", "parking_lot 0.12.5", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "tempfile", @@ -8454,7 +8136,7 @@ dependencies = [ [[package]] name = "terraphim-session-analyzer" -version = "1.16.34" +version = "1.16.35" dependencies = [ "aho-corasick", "anyhow", @@ -8493,7 +8175,7 @@ dependencies = [ [[package]] name = "terraphim_agent" -version = "1.16.34" +version = "1.16.35" dependencies = [ "ahash", "anyhow", @@ -8515,7 +8197,7 @@ dependencies = [ "pulldown-cmark 0.13.3", "ratatui", "regex", - "reqwest 0.12.28", + "reqwest", "rustc_version", "rustyline", "serde", @@ -8674,7 +8356,7 @@ dependencies = [ "jiff", "js-sys", "rand_core 0.6.4", - "reqwest 0.12.28", + "reqwest", "serde", "serde-wasm-bindgen", "serde_jcs", @@ -8703,7 +8385,7 @@ dependencies = [ "getrandom 0.3.4", "log", "regex", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "strsim", @@ -8721,7 +8403,7 @@ dependencies = [ [[package]] name = "terraphim_ccusage" -version = "1.16.34" +version = "1.16.35" dependencies = [ "chrono", "serde", @@ -8769,7 +8451,7 @@ dependencies = [ [[package]] name = "terraphim_file_search" -version = "1.16.34" +version = "1.16.35" dependencies = [ "ahash", "criterion 0.5.1", @@ -8926,7 +8608,7 @@ dependencies = [ [[package]] name = "terraphim_middleware" -version = "1.16.34" +version = "1.16.35" dependencies = [ "ahash", "async-trait", @@ -8940,10 +8622,8 @@ dependencies = [ "html2md", "jiff", "log", - "mcp-client", - "mcp-spec", - "reqwest 0.12.28", - "reqwest-eventsource 0.5.0", + "reqwest", + "reqwest-eventsource", "rmcp", "scraper", "serde", @@ -8981,7 +8661,7 @@ dependencies = [ "genai", "log", "regex", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "tempfile", @@ -9001,6 +8681,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "terraphim_negative_contribution" +version = "0.1.0" +dependencies = [ + "log", + "terraphim_automata", + "terraphim_types", +] + [[package]] name = "terraphim_onepassword_cli" version = "1.0.0" @@ -9031,7 +8720,7 @@ dependencies = [ "hmac", "opendal", "regex", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "serial_test", @@ -9135,7 +8824,7 @@ dependencies = [ [[package]] name = "terraphim_server" -version = "1.16.34" +version = "1.16.35" dependencies = [ "ahash", "anyhow", @@ -9150,7 +8839,7 @@ dependencies = [ "portpicker", "rand 0.9.2", "regex", - "reqwest 0.12.28", + "reqwest", "rust-embed", "schemars 0.8.22", "serde", @@ -9188,7 +8877,7 @@ dependencies = [ "once_cell", "opendal", "regex", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "serial_test", @@ -9212,7 +8901,7 @@ dependencies = [ [[package]] name = "terraphim_sessions" -version = "1.16.34" +version = "1.16.35" dependencies = [ "anyhow", "async-trait", @@ -9294,14 +8983,14 @@ dependencies = [ [[package]] name = "terraphim_test_utils" -version = "1.16.34" +version = "1.16.35" dependencies = [ "rustc_version", ] [[package]] name = "terraphim_tinyclaw" -version = "1.16.34" +version = "1.16.35" dependencies = [ "anyhow", "async-trait", @@ -9315,8 +9004,8 @@ dependencies = [ "log", "parking_lot 0.12.5", "regex", - "reqwest 0.12.28", - "reqwest-eventsource 0.6.0", + "reqwest", + "reqwest-eventsource", "serde", "serde_json", "serde_yaml", @@ -9341,7 +9030,7 @@ version = "1.13.0" dependencies = [ "async-trait", "jiff", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "thiserror 1.0.69", @@ -9402,7 +9091,7 @@ dependencies = [ [[package]] name = "terraphim_usage" -version = "1.16.34" +version = "1.16.35" dependencies = [ "anyhow", "async-trait", @@ -9411,7 +9100,7 @@ dependencies = [ "colored 2.2.0", "indicatif 0.17.11", "opendal", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "terraphim_persistence", @@ -9448,7 +9137,7 @@ dependencies = [ "predicates", "pretty_assertions", "regex", - "reqwest 0.12.28", + "reqwest", "ring", "rustc_version", "serde", @@ -9465,7 +9154,7 @@ dependencies = [ "tokio", "tokio-test", "toml 0.8.23", - "tower 0.5.3", + "tower", "tower-http 0.5.2", "urlencoding", "uuid", @@ -9668,22 +9357,12 @@ dependencies = [ "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "tracing", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.7.0" @@ -9705,16 +9384,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.25.0" @@ -9781,7 +9450,7 @@ source = "git+https://github.com/snapview/tokio-tungstenite.git?tag=v0.28.0#35d1 dependencies = [ "futures-util", "log", - "rustls-native-certs 0.8.3", + "rustls-native-certs", "tokio", "tungstenite 0.28.0", ] @@ -9881,22 +9550,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.3" @@ -9906,7 +9559,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -9921,8 +9574,8 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.11.0", "bytes", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "pin-project-lite", "tower-layer", @@ -9940,8 +9593,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "http-range-header", "httpdate", @@ -9952,7 +9605,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -10112,7 +9765,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.8.5", @@ -10132,7 +9785,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.9.2", @@ -10653,12 +10306,6 @@ dependencies = [ "string_cache_codegen 0.6.1", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.11" @@ -11257,16 +10904,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winsafe" version = "0.0.19" @@ -11283,9 +10920,9 @@ dependencies = [ "base64 0.22.1", "deadpool", "futures", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "log", "once_cell", diff --git a/crates/terraphim_agent/src/learnings/procedure.rs b/crates/terraphim_agent/src/learnings/procedure.rs index d0486c016..ad72c43a4 100644 --- a/crates/terraphim_agent/src/learnings/procedure.rs +++ b/crates/terraphim_agent/src/learnings/procedure.rs @@ -138,6 +138,7 @@ impl ProcedureStore { /// (> 0.8) exists, merge the steps instead of creating a duplicate. /// /// Returns the saved (or merged) procedure. + #[allow(dead_code)] pub fn save_with_dedup( &self, mut procedure: CapturedProcedure, @@ -377,12 +378,14 @@ impl ProcedureStore { /// /// These are navigational, informational, or read-only commands that do not /// contribute meaningful steps to a procedure. +#[allow(dead_code)] pub const TRIVIAL_COMMANDS: &[&str] = &[ "cd ", "ls", "pwd", "echo ", "cat ", "head ", "tail ", "wc ", "which ", "type ", "date", "whoami", ]; /// Check whether a command is trivial (should be excluded from procedure extraction). +#[allow(dead_code)] fn is_trivial_command(command: &str) -> bool { let trimmed = command.trim(); TRIVIAL_COMMANDS @@ -405,6 +408,7 @@ fn is_trivial_command(command: &str) -> bool { /// # Returns /// /// A `CapturedProcedure` with steps derived from the successful, non-trivial commands. +#[allow(dead_code)] pub fn from_session_commands( commands: Vec<(String, i32)>, title: Option, diff --git a/crates/terraphim_agent/src/service.rs b/crates/terraphim_agent/src/service.rs index a52db1248..e9ec4beb4 100644 --- a/crates/terraphim_agent/src/service.rs +++ b/crates/terraphim_agent/src/service.rs @@ -265,6 +265,7 @@ impl TuiService { /// /// `selected_role` passed to `auto_select_role` is normalised: persisted /// `selected_role` is treated as `None` when it does not exist in `config.roles`. + #[allow(dead_code)] pub async fn resolve_or_auto_route( &self, role: Option<&str>, diff --git a/crates/terraphim_middleware/Cargo.toml b/crates/terraphim_middleware/Cargo.toml index 99c3e885d..6bafbd0f2 100644 --- a/crates/terraphim_middleware/Cargo.toml +++ b/crates/terraphim_middleware/Cargo.toml @@ -46,9 +46,7 @@ urlencoding = "2.1" reqwest = { workspace = true, features = ["json", "rustls-tls"] } scraper = "0.25.0" -reqwest-eventsource = { version = "0.5", optional = true } -mcp-client = { version = "0.1", optional = true } -mcp-spec = { version = "0.1", optional = true } +reqwest-eventsource = { version = "0.6", optional = true } rmcp = { version = "0.9", features = ["client", "transport-child-process"], optional = true } [dev-dependencies] @@ -85,7 +83,7 @@ mcp-sse = ["reqwest-eventsource"] # High-level MCP support using SSE/http-only (no rust-sdk) mcp = ["mcp-sse"] # Optional: use rust-sdk for full protocol clients -mcp-rust-sdk = ["mcp-sse", "mcp-client", "mcp-spec", "rmcp"] +mcp-rust-sdk = ["mcp-sse", "rmcp"] # Knowledge graph integration with learning capture kg-integration = ["terraphim_rolegraph/kg-integration", "terraphim_types/kg-integration"] feedback-loop = ["kg-integration"] diff --git a/crates/terraphim_negative_contribution/Cargo.toml b/crates/terraphim_negative_contribution/Cargo.toml new file mode 100644 index 000000000..2ebbbfd3f --- /dev/null +++ b/crates/terraphim_negative_contribution/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "terraphim_negative_contribution" +version = "0.1.0" +edition.workspace = true +authors = ["Terraphim AI"] +description = "Static detection of Explicit Deferral Markers (todo!(), unimplemented!()) in Rust source files" +homepage = "https://terraphim.ai" +license = "Apache-2.0" +repository = "https://github.com/terraphim/terraphim-ai" +keywords = ["static-analysis", "code-quality", "edm", "deferral-marker"] + +[dependencies] +terraphim_automata = { path = "../terraphim_automata", version = "1.0.0" } +terraphim_types = { path = "../terraphim_types", version = "1.0.0" } +log = { workspace = true } + +[dev-dependencies] diff --git a/crates/terraphim_negative_contribution/data/edm_tier1.json b/crates/terraphim_negative_contribution/data/edm_tier1.json new file mode 100644 index 000000000..d73b6edc2 --- /dev/null +++ b/crates/terraphim_negative_contribution/data/edm_tier1.json @@ -0,0 +1,41 @@ +{ + "name": "edm_tier1_patterns", + "data": { + "todo!()": { + "id": 1, + "nterm": "todo_macro", + "display_value": "todo!()", + "url": "todo!() macro -- unimplemented stub||Replace with implementation||high" + }, + "todo!(\"": { + "id": 1, + "nterm": "todo_macro_message", + "display_value": "todo!(\"...\")", + "url": "todo!() macro with message -- unimplemented stub||Replace with implementation||high" + }, + "unimplemented!()": { + "id": 2, + "nterm": "unimplemented_macro", + "display_value": "unimplemented!()", + "url": "unimplemented!() macro -- explicit deferral||Replace with implementation or return appropriate error||high" + }, + "unimplemented!(\"": { + "id": 2, + "nterm": "unimplemented_macro_message", + "display_value": "unimplemented!(\"...\")", + "url": "unimplemented!() macro with message -- explicit deferral||Replace with implementation or return appropriate error||high" + }, + "panic!(\"not implemented\")": { + "id": 3, + "nterm": "panic_not_implemented", + "display_value": "panic!(\"not implemented\")", + "url": "panic with 'not implemented' -- runtime deferral||Replace with proper error handling||high" + }, + "panic!(\"TODO\")": { + "id": 3, + "nterm": "panic_todo", + "display_value": "panic!(\"TODO\")", + "url": "panic with 'TODO' -- runtime deferral||Replace with proper error handling||high" + } + } +} diff --git a/crates/terraphim_negative_contribution/src/exclusion.rs b/crates/terraphim_negative_contribution/src/exclusion.rs new file mode 100644 index 000000000..341f18fd8 --- /dev/null +++ b/crates/terraphim_negative_contribution/src/exclusion.rs @@ -0,0 +1,131 @@ +const EXCLUDED_PATH_PREFIXES: &[&str] = &["tests/", "examples/", "benches/"]; +const EXCLUDED_PATH_INFIXES: &[&str] = &["/tests/", "/examples/", "/benches/"]; +const EXCLUDED_FILENAMES: &[&str] = &["build.rs"]; +const EXCLUDED_SUFFIXES: &[&str] = &["_test.rs"]; +const TEST_MARKERS: &[&str] = &["#[test]", "#[cfg(test)]"]; + +pub fn is_non_production(path: &str, full_content: &str) -> bool { + if is_excluded_path(path) { + return true; + } + contains_test_markers(full_content) +} + +fn is_excluded_path(path: &str) -> bool { + let normalized = path.replace('\\', "/"); + + for infix in EXCLUDED_PATH_INFIXES { + if normalized.contains(infix) { + return true; + } + } + + for prefix in EXCLUDED_PATH_PREFIXES { + if normalized.starts_with(prefix) { + return true; + } + } + + let filename = normalized.rsplit('/').next().unwrap_or(&normalized); + + for excluded in EXCLUDED_FILENAMES { + if filename == *excluded { + return true; + } + } + + for suffix in EXCLUDED_SUFFIXES { + if filename.ends_with(suffix) { + return true; + } + } + + false +} + +fn contains_test_markers(content: &str) -> bool { + TEST_MARKERS.iter().any(|marker| content.contains(marker)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tests_dir_excluded() { + assert!(is_excluded_path("crates/foo/tests/integration.rs")); + assert!(is_excluded_path("tests/common/mod.rs")); + } + + #[test] + fn test_examples_dir_excluded() { + assert!(is_excluded_path("examples/demo.rs")); + assert!(is_excluded_path("crates/bar/examples/basic.rs")); + } + + #[test] + fn test_benches_dir_excluded() { + assert!(is_excluded_path("benches/perf.rs")); + assert!(is_excluded_path("crates/baz/benches/benchmark.rs")); + } + + #[test] + fn test_build_rs_excluded() { + assert!(is_excluded_path("build.rs")); + assert!(is_excluded_path("crates/qux/build.rs")); + } + + #[test] + fn test_test_suffix_excluded() { + assert!(is_excluded_path("src/foo_test.rs")); + assert!(is_excluded_path("crates/my_crate/src/bar_test.rs")); + } + + #[test] + fn test_normal_file_not_excluded() { + assert!(!is_excluded_path("src/main.rs")); + assert!(!is_excluded_path("crates/my_crate/src/lib.rs")); + assert!(!is_excluded_path("src/some_module.rs")); + } + + #[test] + fn test_inline_test_excluded() { + let content = "fn foo() {}\n#[test]\nfn test_foo() {}\n"; + assert!(contains_test_markers(content)); + } + + #[test] + fn test_cfg_test_excluded() { + let content = "#[cfg(test)]\nmod tests {}\n"; + assert!(contains_test_markers(content)); + } + + #[test] + fn test_no_test_markers() { + let content = "fn main() { println!(\"hello\"); }\n"; + assert!(!contains_test_markers(content)); + } + + #[test] + fn test_is_non_production_path_based() { + assert!(is_non_production("tests/foo.rs", "fn main() {}")); + } + + #[test] + fn test_is_non_production_content_based() { + assert!(is_non_production( + "src/lib.rs", + "#[cfg(test)]\nmod tests {}\n" + )); + } + + #[test] + fn test_is_non_production_production_file() { + assert!(!is_non_production("src/lib.rs", "fn main() {}")); + } + + #[test] + fn test_backslash_paths() { + assert!(is_excluded_path("crates\\foo\\tests\\integration.rs")); + } +} diff --git a/crates/terraphim_negative_contribution/src/lib.rs b/crates/terraphim_negative_contribution/src/lib.rs new file mode 100644 index 000000000..a7bedf82f --- /dev/null +++ b/crates/terraphim_negative_contribution/src/lib.rs @@ -0,0 +1,7 @@ +//! Static detection of Explicit Deferral Markers (EDMs) in Rust source files. + +mod exclusion; +mod scanner; + +pub use exclusion::is_non_production; +pub use scanner::NegativeContributionScanner; diff --git a/crates/terraphim_negative_contribution/src/scanner.rs b/crates/terraphim_negative_contribution/src/scanner.rs new file mode 100644 index 000000000..14c6321d3 --- /dev/null +++ b/crates/terraphim_negative_contribution/src/scanner.rs @@ -0,0 +1,401 @@ +use terraphim_automata::{find_matches, load_thesaurus_from_json}; +use terraphim_types::{ + FindingCategory, FindingSeverity, ReviewAgentOutput, ReviewFinding, Thesaurus, +}; + +use crate::exclusion::is_non_production; + +const DEFAULT_EDM_TIER1_JSON: &str = include_str!("../data/edm_tier1.json"); +const SUPPRESSION_MARKER: &str = "terraphim: allow(stub)"; +const SCANNER_AGENT_NAME: &str = "edm-scanner"; + +#[derive(Debug, Clone)] +pub struct NegativeContributionScanner { + thesaurus: Thesaurus, +} + +impl NegativeContributionScanner { + pub fn new() -> Self { + let thesaurus = load_thesaurus_from_json(DEFAULT_EDM_TIER1_JSON) + .expect("Failed to load embedded edm_tier1.json"); + Self { thesaurus } + } + + pub fn from_thesaurus(thesaurus: Thesaurus) -> Self { + Self { thesaurus } + } + + pub fn scan_file(&self, path: &str, content: &str) -> Vec { + if is_non_production(path, content) { + return Vec::new(); + } + + let line_starts = build_line_starts(content); + + let matches = match find_matches(content, self.thesaurus.clone(), true) { + Ok(m) => m, + Err(e) => { + log::warn!("EDM scan failed for {}: {e}", path); + return Vec::new(); + } + }; + + let mut findings = Vec::new(); + + for mat in &matches { + let (start, end) = mat.pos.unwrap_or((0, 0)); + let line_number = byte_to_line(&line_starts, start); + let line_content = get_line_content(content, &line_starts, line_number); + + if line_content.contains(SUPPRESSION_MARKER) { + continue; + } + + let (description, suggestion, severity) = + parse_url_metadata(mat.normalized_term.url.as_deref(), &mat.term); + + findings.push(ReviewFinding { + file: path.to_string(), + line: line_number as u32, + severity, + category: FindingCategory::Quality, + finding: format!("{description}: {}", &content[start..end.min(content.len())]), + suggestion: Some(suggestion), + confidence: 0.95, + }); + } + + findings + } + + pub fn scan_files(&self, files: &[(String, String)]) -> Vec { + files + .iter() + .flat_map(|(path, content)| self.scan_file(path, content)) + .collect() + } + + pub fn scan_to_output(&self, files: &[(String, String)]) -> ReviewAgentOutput { + let findings = self.scan_files(files); + let pass = findings.is_empty(); + let summary = if pass { + "No Explicit Deferral Markers detected.".to_string() + } else { + format!( + "Found {} Explicit Deferral Marker(s): {}", + findings.len(), + findings + .iter() + .map(|f| format!("{}:{}", f.file, f.line)) + .collect::>() + .join(", ") + ) + }; + + ReviewAgentOutput { + agent: SCANNER_AGENT_NAME.to_string(), + findings, + summary, + pass, + } + } + + pub fn thesaurus(&self) -> &Thesaurus { + &self.thesaurus + } +} + +impl Default for NegativeContributionScanner { + fn default() -> Self { + Self::new() + } +} + +fn parse_url_metadata(url: Option<&str>, term: &str) -> (String, String, FindingSeverity) { + let default_desc = format!("EDM pattern matched: {term}"); + let default_suggestion = "Replace with implementation".to_string(); + + let Some(url) = url else { + return (default_desc, default_suggestion, FindingSeverity::High); + }; + + let parts: Vec<&str> = url.split("||").collect(); + let description = parts.first().map(|s| s.to_string()).unwrap_or(default_desc); + let suggestion = parts + .get(1) + .map(|s| s.to_string()) + .unwrap_or(default_suggestion); + let severity = match parts.get(2).copied() { + Some("critical") => FindingSeverity::Critical, + Some("high") => FindingSeverity::High, + Some("medium") => FindingSeverity::Medium, + Some("low") => FindingSeverity::Low, + Some("info") => FindingSeverity::Info, + _ => FindingSeverity::High, + }; + + (description, suggestion, severity) +} + +fn build_line_starts(content: &str) -> Vec { + let mut starts = vec![0]; + for (i, byte) in content.as_bytes().iter().enumerate() { + if *byte == b'\n' { + starts.push(i + 1); + } + } + starts +} + +fn byte_to_line(line_starts: &[usize], byte_offset: usize) -> usize { + match line_starts.binary_search(&byte_offset) { + Ok(idx) => idx + 1, + Err(idx) => idx, + } +} + +fn get_line_content<'a>(content: &'a str, line_starts: &[usize], line_number: usize) -> &'a str { + let start_idx = line_number.saturating_sub(1); + if start_idx >= line_starts.len() { + return ""; + } + let line_start = line_starts[start_idx]; + if line_start >= content.len() { + return ""; + } + let end = content[line_start..] + .find('\n') + .map(|i| line_start + i) + .unwrap_or(content.len()); + &content[line_start..end] +} + +#[cfg(test)] +mod tests { + use super::*; + + fn scanner() -> NegativeContributionScanner { + NegativeContributionScanner::new() + } + + #[test] + fn test_detect_todo() { + let findings = scanner().scan_file("src/main.rs", "fn foo() {\n todo!()\n}\n"); + assert_eq!(findings.len(), 1); + assert_eq!(findings[0].line, 2); + assert_eq!(findings[0].severity, FindingSeverity::High); + assert_eq!(findings[0].category, FindingCategory::Quality); + assert!(findings[0].finding.contains("todo!()")); + } + + #[test] + fn test_detect_unimplemented() { + let findings = scanner().scan_file("src/main.rs", "fn foo() {\n unimplemented!()\n}\n"); + assert_eq!(findings.len(), 1); + assert_eq!(findings[0].line, 2); + } + + #[test] + fn test_detect_panic_not_implemented() { + let findings = scanner().scan_file( + "src/main.rs", + "fn foo() {\n panic!(\"not implemented\")\n}\n", + ); + assert_eq!(findings.len(), 1); + assert_eq!(findings[0].line, 2); + } + + #[test] + fn test_detect_panic_todo() { + let findings = scanner().scan_file("src/main.rs", "fn foo() {\n panic!(\"TODO\")\n}\n"); + assert_eq!(findings.len(), 1); + } + + #[test] + fn test_detect_todo_with_message() { + let findings = scanner().scan_file( + "src/main.rs", + "fn foo() {\n todo!(\"implement later\")\n}\n", + ); + assert_eq!(findings.len(), 1); + assert!(findings[0].finding.contains("todo!")); + } + + #[test] + fn test_detect_unimplemented_with_message() { + let findings = scanner().scan_file( + "src/main.rs", + "fn foo() {\n unimplemented!(\"need auth\")\n}\n", + ); + assert_eq!(findings.len(), 1); + } + + #[test] + fn test_suppression() { + let findings = scanner().scan_file( + "src/main.rs", + "fn foo() {\n todo!() // terraphim: allow(stub)\n}\n", + ); + assert!(findings.is_empty()); + } + + #[test] + fn test_suppression_does_not_affect_other_lines() { + let findings = scanner().scan_file( + "src/main.rs", + "fn foo() {\n todo!()\n}\n// terraphim: allow(stub)\n", + ); + assert_eq!(findings.len(), 1); + } + + #[test] + fn test_excluded_tests_dir() { + let findings = + scanner().scan_file("tests/integration.rs", "fn test_foo() {\n todo!()\n}\n"); + assert!(findings.is_empty()); + } + + #[test] + fn test_excluded_examples_dir() { + let findings = scanner().scan_file("examples/demo.rs", "fn main() {\n todo!()\n}\n"); + assert!(findings.is_empty()); + } + + #[test] + fn test_excluded_build_rs() { + let findings = scanner().scan_file("build.rs", "fn main() {\n todo!()\n}\n"); + assert!(findings.is_empty()); + } + + #[test] + fn test_excluded_inline_test() { + let findings = scanner().scan_file( + "src/lib.rs", + "fn foo() { todo!() }\n#[test]\nfn test_foo() {}\n", + ); + assert!(findings.is_empty()); + } + + #[test] + fn test_excluded_cfg_test() { + let findings = scanner().scan_file( + "src/lib.rs", + "fn foo() { todo!() }\n#[cfg(test)]\nmod tests {}\n", + ); + assert!(findings.is_empty()); + } + + #[test] + fn test_fixme_not_flagged() { + let findings = scanner().scan_file("src/main.rs", "fn foo() {\n // FIXME: broken\n}\n"); + assert!(findings.is_empty()); + } + + #[test] + fn test_hack_not_flagged() { + let findings = + scanner().scan_file("src/main.rs", "fn foo() {\n // HACK: temporary\n}\n"); + assert!(findings.is_empty()); + } + + #[test] + fn test_multiple_patterns() { + let content = "fn foo() {\n todo!()\n}\nfn bar() {\n unimplemented!()\n}\n"; + let findings = scanner().scan_file("src/main.rs", content); + assert_eq!(findings.len(), 2); + } + + #[test] + fn test_clean_file() { + let content = + "fn foo() -> i32 {\n 42\n}\nfn bar() -> String {\n \"hello\".to_string()\n}\n"; + let findings = scanner().scan_file("src/main.rs", content); + assert!(findings.is_empty()); + } + + #[test] + fn test_scan_files_multiple() { + let files = vec![ + ("src/a.rs".to_string(), "todo!()".to_string()), + ("src/b.rs".to_string(), "fn main() {}".to_string()), + ("src/c.rs".to_string(), "unimplemented!()".to_string()), + ]; + let findings = scanner().scan_files(&files); + assert_eq!(findings.len(), 2); + } + + #[test] + fn test_scan_to_output_pass() { + let output = scanner().scan_to_output(&[("src/lib.rs".into(), "fn main() {}".into())]); + assert!(output.pass); + assert!(output.summary.contains("No Explicit Deferral Markers")); + assert!(output.findings.is_empty()); + } + + #[test] + fn test_scan_to_output_fail() { + let output = scanner().scan_to_output(&[("src/lib.rs".into(), "todo!()".into())]); + assert!(!output.pass); + assert_eq!(output.findings.len(), 1); + assert_eq!(output.agent, "edm-scanner"); + } + + #[test] + fn test_from_thesaurus_custom() { + use terraphim_types::{NormalizedTerm, NormalizedTermValue}; + let mut t = terraphim_types::Thesaurus::new("custom".to_string()); + t.insert( + NormalizedTermValue::from("FIXME_MAGIC"), + NormalizedTerm::with_auto_id(NormalizedTermValue::from("FIXME_MAGIC")), + ); + let s = NegativeContributionScanner::from_thesaurus(t); + let findings = s.scan_file("src/main.rs", "fn foo() { FIXME_MAGIC }"); + assert_eq!(findings.len(), 1); + } + + #[test] + fn test_scanner_is_clone() { + let s = scanner(); + let s2 = s.clone(); + let findings = s2.scan_file("src/main.rs", "todo!()"); + assert_eq!(findings.len(), 1); + } + + #[test] + fn test_scanner_default() { + let s = NegativeContributionScanner::default(); + let findings = s.scan_file("src/main.rs", "todo!()"); + assert_eq!(findings.len(), 1); + } + + #[test] + fn test_confidence_value() { + let findings = scanner().scan_file("src/main.rs", "todo!()"); + assert!((findings[0].confidence - 0.95).abs() < f64::EPSILON); + } + + #[test] + fn test_suggestion_from_thesaurus() { + let findings = scanner().scan_file("src/main.rs", "todo!()"); + assert_eq!( + findings[0].suggestion.as_deref(), + Some("Replace with implementation") + ); + } + + #[test] + fn test_parse_url_metadata() { + let (desc, sugg, sev) = + parse_url_metadata(Some("description||suggestion||high"), "todo!()"); + assert_eq!(desc, "description"); + assert_eq!(sugg, "suggestion"); + assert_eq!(sev, FindingSeverity::High); + } + + #[test] + fn test_parse_url_metadata_none() { + let (desc, _, sev) = parse_url_metadata(None, "todo!()"); + assert!(desc.contains("todo!()")); + assert_eq!(sev, FindingSeverity::High); + } +}