From d5d90d8b9f509dacaac97f7adcf5208acda54e3b Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Wed, 6 May 2026 18:31:14 +0200 Subject: [PATCH] fix(opencode): reject Rust src/tests/ paths as a wrong task spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A workflow run on a Bevy/Rust project produced the test-file path `src/tests/test_.rs`, which @test correctly flagged as contradictory: it isn't a valid Rust test location (would require declaring `mod tests;` in production source, which @test cannot do) yet the file-gate glob `**/tests/**/*.rs` accidentally matched it. Phase 5 now gives language-aware Test File guidance: Python uses colocated or top-level `tests/`, Rust uses crate-level `tests/.rs`, and Rust unit-only tasks are routed to NOT_TESTABLE for @make to handle inline. Phase 6's file gate gains an explicit anti-pattern clause discarding any new file under `src/` even when the glob matches. @test's own File Constraint mirrors the anti-pattern so the agent rejects the bad path with BLOCKED before the orchestrator's gate even runs — defense in depth on both sides of the dispatch boundary. --- config/opencode/agents/test.md | 3 +++ config/opencode/commands/workflow.md | 22 +++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/config/opencode/agents/test.md b/config/opencode/agents/test.md index aec7615..7afacff 100644 --- a/config/opencode/agents/test.md +++ b/config/opencode/agents/test.md @@ -119,6 +119,9 @@ Rust (integration tests only — see "Rust unit tests" below): - `**/test_data/**` - `**/test_fixtures/**` +**Anti-patterns — refuse the path even if the glob above matches:** +- Anything under `src/` (e.g. `src/tests/foo.rs`, `src/**/tests/...`). `src/tests/` is a regular module under `src/`; it would require declaring `mod tests;` in production code (`lib.rs` / `main.rs`) and creating `mod.rs`, which you cannot do. If the caller asks for such a path, treat it as a wrong task spec: return `BLOCKED` with a note that the path is not a valid Rust test location, suggesting `tests/.rs` (or `NOT_TESTABLE: Rust unit-only` if the test really needs to be in-source). + **You may NOT modify production/source code under any circumstances.** ### Rust unit tests diff --git a/config/opencode/commands/workflow.md b/config/opencode/commands/workflow.md index 08105b1..2ff213f 100644 --- a/config/opencode/commands/workflow.md +++ b/config/opencode/commands/workflow.md @@ -101,7 +101,20 @@ Break the approved plan into discrete tasks for `@make`. Each task needs: | **Acceptance Criteria** | Specific, testable criteria (checkbox format) | | **Code Context** | Actual code snippets from the codebase, not just file paths | | **Files to Modify** | Explicit list, mark new files with "(create)" | -| **Test File** | Path for test file (colocated pattern), e.g., `/tests/test_.py (create)` | +| **Test File** | Path for test file. **Pick the pattern that matches the project's language** — see "Test File Path by Language" below. | + +### Test File Path by Language + +The test file path must follow the language's actual test layout. **Do not invent paths that look colocated but aren't valid for the language** (e.g. `src/tests/test_.rs` is *not* a Rust test location — it's a regular `src/` submodule). + +- **Python** + - Colocated: `/tests/test_.py (create)` + - Top-level: `tests/test_.py (create)` +- **Rust** + - Crate-level integration tests: `tests/.rs (create)` (or, in a workspace, `/tests/.rs`) + - **Unit-test-only tasks (in-source `#[cfg(test)] mod tests`):** mark the task as `NOT_TESTABLE` with reason `Rust unit-only` — `@test` cannot write inside production source. `@make` writes those inline as part of its production change. +- **Polyglot Nix flake** + - Match the host language of the code under change (Python or Rust rules above), wrapping commands in `nix develop -c …` per the agents' devshell rule. Include **Integration Contracts** when a task adds/changes function signatures, APIs, config keys, or has dependencies on other tasks. @@ -131,9 +144,12 @@ git diff --name-only | comm -23 - /tmp/pre_test_baseline.txt > /tmp/test_new_fil ``` All new files must match the project's test patterns: - Python: `**/test_*.py`, `**/*_test.py`, `**/conftest.py` (new only), `**/test_data/**`, `**/test_fixtures/**` -- Rust: `tests/**/*.rs`, `**/tests/**/*.rs`, `**/test_data/**`, `**/test_fixtures/**` +- Rust: `tests/**/*.rs`, `**/tests/**/*.rs` (workspace-style `/tests/...`), `**/test_data/**`, `**/test_fixtures/**` -If any non-matching file appears: discard `@test` output, report violation. +**Anti-patterns — discard the output even if the glob matches:** +- Anything under `src/` for Rust (e.g. `src/tests/foo.rs`, `src/**/tests/...`). `src/tests/` is a regular module path under `src/`, not a Rust test location, and `@test` cannot wire it up via `mod` declarations in production source. Such paths indicate the task spec gave a wrong test path — escalate, don't accept the file. + +If any non-matching file appears, or any anti-pattern matches: discard `@test` output, report violation. **Decision table — handling `@test` results:**