Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
25adcea
feat(runtime): converge language guards
majiayu000 Aug 15, 2026
fcddeb9
test(runtime): satisfy scanner CI gates
majiayu000 Aug 15, 2026
1600022
test(guards): align baseline output contract
majiayu000 Aug 15, 2026
020db1a
fix(runtime): preserve language guard coverage
majiayu000 Aug 15, 2026
9e68ebb
fix(runtime): honor baseline guard scope
majiayu000 Aug 15, 2026
80d695d
fix(guards): retain scanner edge-case parity
majiayu000 Aug 15, 2026
d1bd2ce
fix(guards): restore structural edge semantics
majiayu000 Aug 15, 2026
ea05de4
fix(guards): close remaining parity gaps
majiayu000 Aug 15, 2026
2d3f3cc
fix(guards): preserve scanner edge parity
majiayu000 Aug 15, 2026
5026a61
fix(guards): finish runtime parity coverage
majiayu000 Aug 15, 2026
f235248
fix(guards): scope runtime scans to changes
majiayu000 Aug 15, 2026
1c2cb96
fix(ci): keep Rust scanner within size limit
majiayu000 Aug 15, 2026
8f30182
fix(guards): cover enforcement boundary changes
majiayu000 Aug 15, 2026
de0a08b
fix(guards): preserve scanner eligibility boundaries
majiayu000 Aug 15, 2026
9b3d2d8
fix(guards): close remaining scanner syntax gaps
majiayu000 Aug 16, 2026
315f20b
fix(guards): align scans with syntax and staged state
majiayu000 Aug 16, 2026
3c1c6e9
fix(guards): preserve staged and JSX scan semantics
majiayu000 Aug 16, 2026
9cb9d1a
fix(guards): cover syntax and symlink edge cases
majiayu000 Aug 16, 2026
dda3d2d
fix(guards): preserve diff and parser coverage
majiayu000 Aug 16, 2026
8298d3f
fix(guards): normalize identifier edge cases
majiayu000 Aug 16, 2026
4f6c2a4
fix(guards): handle TypeScript lexical boundaries
majiayu000 Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- W-21 "evidence must be provably executed, not merely cited": decisive claims need an out-of-session channel (transcript, filesystem, git, persisted exit codes/hashes), accusing the harness or hooks is a red flag, and two falsified root-cause theories in one investigation terminate the session (#687).
- `vibeguard-runtime scan <language> <rule>` now provides the canonical implementation for all 15 Rust, Go, and TypeScript shell guards, including staged and baseline-aware scanning (#752).

### Changed
- W-01's debugging protocol now starts at step 0, a channel-trust check that rules out degraded reading before any filesystem, harness, or hook is blamed (#687).
- Rust, Go, and TypeScript `check_*.sh` files are now fail-closed runtime exec shims; behavior-eval fixtures pin every migrated rule, and `vibeguard-runtime` is now `1.1.17` (#752).

## [1.1.10] - 2026-07-09

Expand Down
31 changes: 16 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Executable consumer evidence is intentionally limited to syntax-aware `.py`, `.j
| Asset | Owner and use | Focused verification |
|-------|---------------|----------------------|
| `deny.toml` | Cargo dependency-policy configuration consumed by CI and available to maintainers through `cargo deny --manifest-path vibeguard-runtime/Cargo.toml --locked check -c deny.toml licenses bans sources`. | Run the documented `cargo deny` command on Linux. |
| `sgconfig.yml` | Maintainer-only repository-wide ast-grep configuration. Production guards continue to pass explicit `--rule` files. Run it manually with `ast-grep scan --config sgconfig.yml`. | Scan a Rust `Config::default()` fixture and confirm the `rs-14-config-default` rule is reported. |
| `sgconfig.yml` | Maintainer-only repository-wide ast-grep configuration. Production guards use `vibeguard-runtime` and do not invoke ast-grep. Run it manually with `ast-grep scan --config sgconfig.yml`. | Scan a Rust `Config::default()` fixture and confirm the `rs-14-config-default` rule is reported. |
| `rust-toolchain.toml` | Canonical Rust toolchain selected implicitly by rustup and Cargo for repository commands. | `rustup show active-toolchain` |
| `templates/AGENTS.md` | Canonical project instruction template consumed by `scripts/ci/validate-prompt-contract.sh`. | `bash scripts/ci/validate-prompt-contract.sh --strict` |
| `templates/product_spec.md`, `templates/tech_spec.md`, `templates/zh-CN/product_spec.md`, `templates/zh-CN/tech_spec.md` | Optional maintainer-copyable starting points for major architecture or explicitly requested specs; they are not an automatic work queue. | Confirm the paired spec stays near 300 lines and run the documentation validators. |
Expand Down Expand Up @@ -286,7 +286,9 @@ guards/
└── typescript/ # TypeScript guards (TS-XX rules)
```

Bash-based language directories (`rust/`, `go/`, `typescript/`) each contain a `common.sh` with shared utilities. Python guards are standalone scripts.
Rust, Go, and TypeScript detection lives in `vibeguard-runtime/src/guard_scan/`;
their Bash files are compatibility entrypoints. Python guards remain standalone
scripts.
Comment thread
majiayu000 marked this conversation as resolved.

### Step 1: Define the rule

Expand All @@ -307,20 +309,17 @@ Canonical rule headings must use the format `## ID: Title (severity)`. Rules fol

The file format differs by language:

**Bash guards (Rust, Go, TypeScript)** — create `guards/<language>/check_<rule_slug>.sh` and start with:
**Rust, Go, and TypeScript guards** — implement the rule in
`vibeguard-runtime/src/guard_scan/`, register the `scan <language> <rule>`
command, then create `guards/<language>/check_<rule_slug>.sh` as a compatibility
shim:

```bash
#!/usr/bin/env bash
# VibeGuard <Language> Guard: <short description> (<RULE-ID>)
#
# Usage:
# bash check_<rule_slug>.sh [target_dir]
# bash check_<rule_slug>.sh --strict [target_dir] # exit 1 on violations

set -euo pipefail

source "$(dirname "$0")/common.sh"
parse_guard_args "$@"
source "$(dirname "$0")/runtime-shim.sh"
run_runtime_guard <language> <rule-slug> "$@"
```

**Python guards** — create `guards/python/check_<rule_slug>.py` and parse arguments directly from `sys.argv`:
Expand Down Expand Up @@ -355,10 +354,12 @@ if __name__ == "__main__":
Every finding must follow this format because downstream tools consume it:

```text
[RS-14] path/to/file.rs:42 description. Fix: remediation hint
[RS-14] path/to/file.rs:42: description. Fix: remediation hint
```

Use `TMPFILE=$(create_tmpfile)` from `common.sh` to buffer output in Bash guards. Print a clear summary and return `1` only when violations are present in `--strict` mode.
The runtime owns output buffering and exit semantics for Rust, Go, and
TypeScript guards. Print a clear summary and return `1` only when violations
are present in `--strict` mode.

#### Exit codes

Expand Down Expand Up @@ -403,8 +404,8 @@ If the change affects detection quality or scoring, also run `bash tests/run_pre
### Guard Quality Checklist

- [ ] Starts with strict error handling (`set -euo pipefail` for Bash)
- [ ] Uses shared helpers where they already exist (`common.sh`, temp-file helpers, guard-path helpers)
- [ ] Output follows `[RULE-ID] file:line description. Fix: hint`
- [ ] Rust/Go/TypeScript shell entrypoints contain no detection or fallback logic
- [ ] Output follows `[RULE-ID] file:line: description. Fix: hint`
- [ ] Handles expected exclusions and avoids obvious false positives
- [ ] Supports `--strict` mode correctly
- [ ] Has scope-appropriate regression coverage
Expand Down
21 changes: 21 additions & 0 deletions eval/behavior/datasets/v1.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,24 @@
{"id": "codex-pre-write-existing-allow", "description": "Codex PreToolUse(Write) wrapper stays silent when overwriting an existing file", "platform": "codex", "hook": "pre-write-guard", "event": "PreToolUse", "profile": "default", "severity": "medium", "rule": "L1", "runner": "codex_wrapper", "script": "hooks/run-hook-codex.sh", "hook_name": "vibeguard-pre-write-guard.sh", "payload": {"hook_event_name": "PreToolUse", "tool_input": {"file_path": "README.md", "content": "# README"}}, "expect": {"exit_code": 0, "stdout_empty": true}}
{"id": "claude-pre-bash-malformed-input-fail-closed", "description": "Claude PreToolUse(Bash) fails closed on a well-formed payload without tool_input.command and logs a shape diagnostic", "platform": "claude", "hook": "pre-bash-guard", "event": "PreToolUse", "profile": "default", "severity": "high", "rule": "U-29", "runner": "claude_hook", "script": "hooks/pre-bash-guard.sh", "payload": {"hook_event_name": "PreToolUse", "tool_name": "BashOutput", "tool_input": {"bash_id": "bg-1"}}, "expect": {"exit_code": 0, "json": [{"path": "decision", "equals": "block"}], "stdout_contains": ["invalid Bash hook input JSON"]}}
{"id": "claude-pre-write-malformed-input-fail-closed", "description": "Claude PreToolUse(Write) fails closed on a payload missing tool_input.file_path and explains the validation failure", "platform": "claude", "hook": "pre-write-guard", "event": "PreToolUse", "profile": "default", "severity": "high", "rule": "U-29", "runner": "claude_hook", "script": "hooks/pre-write-guard.sh", "payload": {"hook_event_name": "PreToolUse", "tool_name": "NotebookEdit", "tool_input": {"notebook_path": "nb.ipynb"}}, "expect": {"exit_code": 0, "json": [{"path": "decision", "equals": "block"}], "stdout_contains": ["malformed PreToolUse(Write)"]}}
{"id":"guard-rust-unwrap-parity","description":"Rust runtime preserves RS-03 production unwrap detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-03","runner":"guard","script":"guards/rust/check_unwrap_in_prod.sh","payload":{"args":["--strict"],"files":{"src/main.rs":"fn main() { let _ = Some(1).unwrap(); }\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-03]","main.rs"]}}
{"id":"guard-rust-nested-locks-parity","description":"Rust runtime preserves RS-01 nested lock detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-01","runner":"guard","script":"guards/rust/check_nested_locks.sh","payload":{"args":["--strict"],"files":{"src/state.rs":"use std::sync::Mutex;\nstruct State { a: Mutex<i32>, b: Mutex<i32> }\nimpl State { fn update(&self) { let _a = self.a.lock(); let _b = self.b.lock(); } }\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-01]","update"]}}
{"id":"guard-rust-duplicate-types-parity","description":"Rust runtime preserves RS-05 duplicate type detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-05","runner":"guard","script":"guards/rust/check_duplicate_types.sh","payload":{"args":["--strict"],"files":{"src/a.rs":"pub struct SharedType;\n","src/b.rs":"pub struct SharedType;\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-05]","SharedType"]}}
{"id":"guard-rust-workspace-consistency-parity","description":"Rust runtime preserves RS-06 workspace configuration drift detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-06","runner":"guard","script":"guards/rust/check_workspace_consistency.sh","payload":{"args":["--strict"],"files":{"Cargo.toml":"[workspace]\nmembers = [\"server\", \"desktop\"]\n","server/Cargo.toml":"[package]\nname = \"server\"\nversion = \"0.1.0\"\n","server/src/main.rs":"fn main() { let _ = std::env::var(\"SERVER_DB_PATH\"); }\n","desktop/Cargo.toml":"[package]\nname = \"desktop\"\nversion = \"0.1.0\"\n","desktop/src/main.rs":"fn main() { let _ = std::env::var(\"DESKTOP_DB_PATH\"); }\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-06]","SERVER_DB_PATH","DESKTOP_DB_PATH"]}}
{"id":"guard-rust-ssot-parity","description":"Rust runtime preserves RS-12 dual task-system detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-12","runner":"guard","script":"guards/rust/check_single_source_of_truth.sh","payload":{"args":["--strict"],"files":{"src/tools.rs":"pub struct TodoWrite;\npub struct TaskDone;\nstatic TODO_STATE: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());\nstatic TASK_STATE: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-12]","dual task systems"]}}
{"id":"guard-rust-semantic-effect-parity","description":"Rust runtime preserves RS-13 missing side-effect detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-13","runner":"guard","script":"guards/rust/check_semantic_effect.sh","payload":{"args":["--strict"],"files":{"src/task/task_done.rs":"pub fn mark_done(task_id: &str) -> Result<String, String> { Ok(format!(\"task {} done\", task_id)) }\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-13]","mark_done"]}}
{"id":"guard-rust-taste-parity","description":"Rust runtime preserves taste-invariant panic message detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TASTE-PANIC-MSG","runner":"guard","script":"guards/rust/check_taste_invariants.sh","payload":{"args":["--strict"],"files":{"src/lib.rs":"pub fn fail() { panic!(\"\"); }\n"}},"expect":{"exit_code":1,"stdout_contains":["[TASTE-PANIC-MSG]","panic! lacks a meaningful message"]}}
{"id":"guard-rust-declaration-execution-parity","description":"Rust runtime preserves RS-14 Config default/load gap detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"RS-14","runner":"guard","script":"guards/rust/check_declaration_execution_gap.sh","payload":{"args":["--strict"],"files":{"src/config.rs":"pub struct AppConfig;\nimpl AppConfig { pub fn load() -> Self { Self } }\nimpl Default for AppConfig { fn default() -> Self { Self } }\n","src/main.rs":"mod config;\nfn main() { let _ = config::AppConfig::default(); }\n"}},"expect":{"exit_code":1,"stdout_contains":["[RS-14]","AppConfig::default()"]}}
{"id":"guard-go-error-handling-parity","description":"Rust runtime preserves GO-01 discarded error detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"GO-01","runner":"guard","script":"guards/go/check_error_handling.sh","payload":{"args":["--strict"],"files":{"main.go":"package main\nimport \"os\"\nfunc main() {\n _ = os.Remove(\"old\")\n}\n"}},"expect":{"exit_code":1,"stdout_contains":["[GO-01]","main.go"]}}
{"id":"guard-go-goroutine-parity","description":"Rust runtime preserves GO-02 goroutine leak detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"GO-02","runner":"guard","script":"guards/go/check_goroutine_leak.sh","payload":{"args":["--strict"],"files":{"worker.go":"package worker\nfunc start() {\n go func() {\n for { work() }\n }()\n}\nfunc work() {}\n"}},"expect":{"exit_code":1,"stdout_contains":["[GO-02]","worker.go"]}}
{"id":"guard-go-defer-loop-parity","description":"Rust runtime preserves GO-08 defer-in-loop detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"GO-08","runner":"guard","script":"guards/go/check_defer_in_loop.sh","payload":{"args":["--strict"],"files":{"main.go":"package main\nfunc run(items []string) {\n for _, item := range items {\n defer closeItem(item)\n }\n}\nfunc closeItem(string) {}\n"}},"expect":{"exit_code":1,"stdout_contains":["[GO-08]","defer closeItem"]}}
{"id":"guard-typescript-any-parity","description":"Rust runtime preserves TS-01 any-abuse detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TS-01","runner":"guard","script":"guards/typescript/check_any_abuse.sh","payload":{"args":["--strict"],"files":{"src/value.ts":"export const value = input as any;\n"}},"expect":{"exit_code":1,"stdout_contains":["[TS-01]","value.ts"]}}
{"id":"guard-typescript-console-parity","description":"Rust runtime preserves TS-03 console residual detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TS-03","runner":"guard","script":"guards/typescript/check_console_residual.sh","payload":{"args":["--strict"],"files":{"src/service.ts":"export function run(): void { console.log(\"debug\"); }\n"}},"expect":{"exit_code":1,"stdout_contains":["[TS-03]","service.ts"]}}
{"id":"guard-typescript-components-parity","description":"Rust runtime preserves TS-13 repeated component-style detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TS-13","runner":"guard","script":"guards/typescript/check_component_duplication.sh","payload":{"args":["--strict"],"files":{"src/a.tsx":"export const A = () => <div className=\"layout-layout-layout-layout-layout-layout-layout-layout-layout-layout-layout\" />;\n","src/b.tsx":"export const B = () => <div className=\"layout-layout-layout-layout-layout-layout-layout-layout-layout-layout-layout\" />;\n"}},"expect":{"exit_code":1,"stdout_contains":["[TS-13]","Style string duplicated"]}}
{"id":"guard-typescript-constants-parity","description":"Rust runtime preserves duplicate exported constant detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"DUP-CONST","runner":"guard","script":"guards/typescript/check_duplicate_constants.sh","payload":{"args":["--strict"],"files":{"src/a.ts":"export const API_URL = \"a\";\n","src/b.ts":"export const API_URL = \"b\";\n"}},"expect":{"exit_code":1,"stdout_contains":["[DUP-CONST]","API_URL"]}}
{"id":"guard-rust-taste-ansi-parity","description":"Rust runtime preserves hardcoded ANSI escape detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TASTE-ANSI","runner":"guard","script":"guards/rust/check_taste_invariants.sh","payload":{"args":["--strict"],"files":{"src/lib.rs":"pub const RED: &str = \"\\\\x1b[31m\";\n"}},"expect":{"exit_code":1,"stdout_contains":["[TASTE-ANSI]","hardcoded ANSI"]}}
{"id":"guard-rust-taste-async-unwrap-parity","description":"Rust runtime preserves async unwrap detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TASTE-ASYNC-UNWRAP","runner":"guard","script":"guards/rust/check_taste_invariants.sh","payload":{"args":["--strict"],"files":{"src/lib.rs":"pub async fn load() { let _ = Some(1).unwrap(); }\n"}},"expect":{"exit_code":1,"stdout_contains":["[TASTE-ASYNC-UNWRAP]","inside async fn"]}}
{"id":"guard-go-infinite-loop-parity","description":"Rust runtime preserves naked infinite-loop detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"GO-02/loop","runner":"guard","script":"guards/go/check_goroutine_leak.sh","payload":{"args":["--strict"],"files":{"main.go":"package main\nfunc run() {\n for {\n work()\n }\n}\nfunc work() {}\n"}},"expect":{"exit_code":1,"stdout_contains":["[GO-02/loop]","for {"]}}
{"id":"guard-typescript-ignore-parity","description":"Rust runtime preserves TypeScript suppression detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"TS-02","runner":"guard","script":"guards/typescript/check_any_abuse.sh","payload":{"args":["--strict"],"files":{"src/value.ts":"// @ts-ignore\nexport const value = missingName;\n"}},"expect":{"exit_code":1,"stdout_contains":["[TS-02]","@ts-ignore"]}}
{"id":"guard-typescript-duplicate-type-parity","description":"Rust runtime preserves duplicate exported type detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"DUP-TYPE","runner":"guard","script":"guards/typescript/check_duplicate_constants.sh","payload":{"args":["--strict"],"files":{"src/a.ts":"export interface SharedValue { a: string }\n","src/b.ts":"export interface SharedValue { b: number }\n"}},"expect":{"exit_code":1,"stdout_contains":["[DUP-TYPE]","SharedValue"]}}
{"id":"guard-typescript-duplicate-function-parity","description":"Rust runtime preserves repeated function detection","platform":"runtime","hook":"guard-scan","event":"GuardScan","profile":"default","severity":"high","rule":"DUP-FUNC","runner":"guard","script":"guards/typescript/check_duplicate_constants.sh","payload":{"args":["--strict"],"files":{"src/a.ts":"export function formatValue(): string { return \"a\"; }\n","src/b.ts":"export function formatValue(): string { return \"b\"; }\n","src/c.ts":"export function formatValue(): string { return \"c\"; }\n"}},"expect":{"exit_code":1,"stdout_contains":["[DUP-FUNC]","formatValue"]}}
Loading
Loading