Skip to content
hehljoPublic

About

Deterministic, token-free quality gate for developers & coding agents (Claude Code, Cursor, Codex, Copilot, Aider). Executable engineering memory for bugs linters miss.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

160 Commits

Folders and files

Ruttla Logo

Ruttla

Ruttla comes from the German rütteln ("to shake").

A normal linter reads the code as it lies. Ruttla shakes it until whatever sits loose falls out — which is exactly what the logo shows: a pair of braces being shaken, a bug tumbling out.

Code → Ruttla shakes → what sits loose falls out.

It doesn't just check whether a rule turns green — it actively tries to make every rule fail with a counter-probe. And when nothing was measured, Ruttla never claims that everything is fine.


Ruttla is a local, deterministic quality gate: executable engineering memory for failures that normal linters miss. A real failure happens → its cause is proven → a deterministic rule is written → a broken and a healthy probe must both behave → from then on the failure costs no debugging or agent tokens. Scanning uses no LLM, network, or telemetry; target code is read, never executed. Network access happens only when explicitly running ruttla update or ruttla hub search|add|sync|submit, or when a scan is started with --allow-network: then the repository check asks the GitHub API whether the scanned repository is public (owner and repository name, nothing from the code).

Status: 0.1.0.dev0, public preview. German version: README.de.md.

Built for Coding Agents & LLMs

Coding agents (Claude Code, Cursor, OpenAI Codex, GitHub Copilot, Aider, Windsurf) waste thousands of tokens looping on subtle bugs and linters' blind spots. Ruttla provides:

  • Token-dense output (--format agent): One line per finding with file, line, severity, and an exact, non-hallucinated fix instruction (FIX: ...).
  • Zero token burn: Scans are offline and deterministic; no network calls or LLM queries unless you explicitly run ruttla update, a ruttla hub command that fetches, or a scan with --allow-network.
  • Copy-paste agent setup: docs/AGENT_SETUP.md is one prompt for any coding agent — install, gate at the end of every task, turn a reported bug into a project rule, and what never to do without asking.
  • Strict exit contracts: Differentiates between clean (0), blocking findings (1), unmeasured scopes (2), and runner errors (3). Never confuses "nothing checked" with success.

30-second start

curl -fsSL https://raw.githubusercontent.com/hehljo/Ruttla/main/install.sh | sh   # Linux, macOS
ruttla path/to/project                 # human-readable report
ruttla . --format agent                # one line per finding, for scripts and agents
ruttla . --format json                 # full report (schemas/report.schema.json)
ruttla . --sarif ruttla.sarif          # SARIF 2.1.0 for GitHub code scanning
ruttla . --changed-only origin/main    # only changed + untracked files
ruttla --list [--format json|markdown] # rule catalog
ruttla --explain secrets.hardcoded_credential
ruttla --self-test                     # shake the gates themselves

From a checkout without installing: python3 master_gate.py … (identical).

Windows (PowerShell): irm https://raw.githubusercontent.com/hehljo/Ruttla/main/install.ps1 | iex. The installer creates its own Python environment (Python ≥ 3.11 required) and installs the package and the matching Rust engine from the latest GitHub release, checked against SHA256SUMS.

One command, detection included

ruttla . needs no configuration. It detects the platforms from marker files (src/ruttla/platforms.toml, e.g. *.xcodeproj, package.json, project.godot, *.csproj) per subproject: in a monorepo an iOS app in ios/ and a web app in web/ each get their own rule pack, and no rule sees the files of another platform's subproject. Platforms without a rule pack are listed in the report (platforms_without_pack) instead of silently passing. [project] platform in .ruttla.toml forces a pack on in addition.

Rule engine (Rust)

New rules are data: one TOML file per rule under src/ruttla/rules/ (docs/RULE_FORMAT.md), executed by ruttla-engine (Rust, linear-time regex crate, files in parallel). Rules that need parsing or state across files remain Python checks.

The installer and ruttla update put a prebuilt engine next to the ruttla script (Linux x86_64/arm64, macOS arm64/x86_64, Windows x86_64). Without an engine the declarative rules are reported as unmeasured, never as green. On other platforms build it yourself (Rust ≥ 1.85):

cargo build --release --locked --manifest-path engine/Cargo.toml
export RUTTLA_ENGINE_BIN="$PWD/engine/target/release/ruttla-engine"   # for a pip install elsewhere

ruttla rule test PATH checks a rule or rule package before it is merged or shared: isolated load, fixtures, an untested-exclusion probe, ID clashes and a runtime budget (docs/RULE_FORMAT.md).

Rule packages from the community hub (hehljo/ruttla-hub) are signed data, pinned in ruttla-hub.lock and loaded offline: ruttla hub search, ruttla hub add NAME, ruttla hub sync. Your own rules go to .ruttla/packages/NAME/ in the project — they run in every scan, no hub command or update overwrites them, and ruttla hub submit NAME --yes opens the PR that makes them available to everyone (docs/RULE_FORMAT.md).

A checkout finds its own build without the variable. The engine is never looked up on PATH or in the scanned directory. --engine required turns a missing engine into a runner error (exit 3) — use it in CI.

Updating

Run ruttla update to install the package and the matching engine from the latest GitHub release into the active Python environment — both from the same commit, the engine checked against SHA256SUMS. Without a tagged release it takes nightly, built from the last green CI run on main (--channel stable|nightly to choose). This explicit command uses pip and requires network access; ordinary scans remain offline. It replaces only the installed package and engine — project files (.ruttla.toml, ruttla-hub.lock, .ruttla/) stay untouched.

Before any download it stops on an editable clone (pip install -e . — update the checkout with git pull instead) and on a system Python marked externally managed (PEP 668 — install Ruttla in a venv or with pipx). The engine is verified before pip runs, so a rejected engine never leaves a half update.

Exit codes

Code Meaning
0 measured, no blocking finding (advisory findings may exist)
1 at least one blocking finding
2 nothing was measured — not a success
3 runner, input, config or plugin error

Capture the status before filtering: ruttla . | head reports head's status.

out=$(ruttla . --format agent); status=$?

Every finding carries blocking: true|false. Without a profile only rules marked safe by default block; with a profile every error blocks; with --strict every finding blocks. Details: docs/CONTRACTS.md.

Profile: .ruttla.toml

config_version = 1

[gate]
strict = false
exclude = ["legacy/**", "extracted/**"]   # glob on the relative path
max_file_bytes = 2000000                   # larger files are reported as skipped

[project]
platform = "apple"          # force a rule pack on (in addition to detection)

[brand]
names = ["Acme"]
sources = ["src/brand.ts"]

[severity]
"secrets.*" = "error"
"godot.untyped_declaration" = "off"

The legacy name .qualitygate.toml is still read; both files at once is an error.

Accepted findings and agent fixes

A finding that stays on purpose goes into .ruttla/baseline.toml: one entry per fingerprint with a count, a reason and an expiry date (at most a year). ruttla baseline create --reason … --expires YYYY-MM-DD records today's findings once when adopting Ruttla; after that each entry is added on its own with ruttla baseline add --check ID --file PATH …. Expired entries stop suppressing, entries without a finding are reported. ruttla baseline show lists them.

--trusted-ref REF reads the profile and the accepted findings from REF (e.g. origin/main) instead of the working tree, so a branch cannot switch a rule off or accept its own finding. ruttla guard --base REF exits 1 when a change touches the gate policy (profile, .ruttla/, workflows, CODEOWNERS, .gitignore), adds suppression comments or removes tests — the acceptance check for automated fixes. ruttla triage REPORT.json … --out DIR turns JSON reports into fix tasks per repository and fix class (mechanical, catalog, judgement, security; table in src/ruttla/fix_classes.toml, unlisted rules default to judgement). A shared CI workflow for many repositories is in docs/integrations/github-actions.md.

What makes it different

  • Four honest states: pass, fail, unmeasured (with a reason), error. "Zero files checked" is never green.
  • Self-tested rules: every rule must FAIL on its broken probe and PASS on its healthy probe (ruttla --self-test); current counts per pack in the generated docs/RULES.md.
  • Evidence-backed catalog: rules come from real failures, see docs/RULES.md, docs/GUIDELINES.md and LESSONS_LEARNED.md.
  • Coverage is visible: skipped oversize files and platforms without a rule pack are reported, not silently dropped.
  • Agent-native output: stable check IDs, compact agent format, JSON, SARIF.

Packs: universal (brand, i18n, secrets, gates, docs, media, protocol), apple (incl. App Store release), web, python services, raspberry, godot, unreal, dotnet (WPF/Avalonia).

Coexistence with other tools

Ruttla does not replace compilers, CodeQL / GitHub Code Quality, Ruff, ESLint or SwiftLint. Use those for the mainstream issues they cover well; use Ruttla for cross-toolchain failure classes, release/store workflows and project knowledge they don't encode. Upload each tool's SARIF under its own category (docs/integrations/github-actions.md).

Known limitation: rule messages are currently German.

Privacy and trust

Offline, no telemetry. The scan target is untrusted data: nothing from it is imported or executed, and symlinks may not leave the scan root. Executable checks load only from the installed package; from the target Ruttla reads declarative rules only — hub packages whose hash matches ruttla-hub.lock and the project's own packages under .ruttla/packages/ — which can only add findings (ADR-0005).

Local statistics (opt-in, off by default)

ruttla stats enable starts recording scans on this machine only; nothing is recorded before that and nothing is ever sent anywhere. The SQLite file lives in the user data directory ($XDG_DATA_HOME/ruttla, ~/Library/Application Support/ruttla, %APPDATA%\ruttla; override with RUTTLA_STATS_DIR), never in the scanned repo. Stored: counts per scan, rule and severity, CI vs. local (CI variable), and per finding an HMAC fingerprint over rule, relative path and normalised evidence with a local salt. Never stored: source text, evidence, messages, secrets, plain paths or repo names — a project is a local random ID. A finding counts as fixed only when the scan re-examined the spot (rule ran and measured, file still in scope); otherwise it is indeterminate. Report, reporters and exit code are identical with and without statistics; a statistics error never fails a scan. ruttla stats [--period 7|30|90|all] shows the dashboard (headed by the all-time balance: distinct problems found, fixed, regressions, runs with a blocking finding — counted per fingerprint, not per scan), stats export writes JSON and Markdown, stats disable stops recording and keeps the data, stats delete --yes removes it. Scans below the system temp directory (test runs, throwaway clones) are not recorded. ruttla . --lifecycle appends this scan's lifecycle to the text report — new · persisting · reappeared (regression) · fixed · indeterminate, with the rules behind regressions, new and fixed findings. Without recording it says not measured, never "0 new"; JSON, SARIF and agent output stay unchanged.

For a public record, ruttla badge update writes a card from a full scan: current and history badge (shields.io), history.jsonl and a summary page — counts and rule IDs only, each line tied to commit, Ruttla version and CI run, history starting at the first recorded scan. Opt-in per repository via a workflow that publishes to its own branch: docs/integrations/stats-badge.md.

Contributing

See CONTRIBUTING.md — a new rule needs a real failure, a broken and a healthy probe, a false-positive analysis and a fix hint that only names APIs that exist. Write it as a TOML rule (docs/RULE_FORMAT.md) unless it cannot be expressed as a pattern. Security: SECURITY.md. License: Apache-2.0.

About

Deterministic, token-free quality gate for developers & coding agents (Claude Code, Cursor, Codex, Copilot, Aider). Executable engineering memory for bugs linters miss.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages