Show HN: Argot, a Rust AI guardrail based on your codebase AST patterns
The harness for AI-written code · statistics, not a second LLM · 100% local Lint the rules you never wrote down. AI writes the code. argot harnesses it with the one thing that can’t hallucinate: your repo’s own history.…
The harness for AI-written code · statistics, not a second LLM · 100% local Lint the rules you never wrote down. AI writes the code. argot harnesses it with the one thing that can’t hallucinate: your repo’s own history. Deterministic, measured, local. Start with an audit→Set it up with your agent→★ Star on GitHub flag⟺token surprisetmaxlogPrepo(t)Pbaseline(t)+call-receivermin(∑cw(c),cap)>size-aware thresholdθ+βlogn90n 97.3% of foreign patterns caught · 0.25% of real edits flagged checks a diff in200 ms A concrete example Type checkers ask if it compiles. argot asks if it’s yours. Clean, type-correct PRs can still be foreign to your repository. This is its real output. mypystrict — passes rufflint-clean argotflags what doesn’t fit argot check $ argot check fastapi/receipts.py !L1-L10foreign·foreign-import[94a92c256ea1] ↳ django — 0 of 74 imports in this repo repo reaches for: fastapi · pydantic · starlette 1 │ from django.views import View 1 │ from ~~~~~~ $ argot check src/report/export.py .L2suspicious·superseded[5fa6bf645ba9] ↳ this repo replaced requests with httpx — 14 commits across 23 files (e.g. 4c9a2e1) 6 files still to migrate — argot conventions lists them 2 │ import requests $ argot check src/api/handlers.py .L13-L21unusual·redundant[0b1486985821] ↳ duplicates slugify (src/utils/text.py:14) — similarity 0.86 13 │ def make_slug(text): $ argot check src/cli/commands/fetch.py .L18-L41unusual·misplaced[3e51b7c20d6f] ↳ looks like core/downloadercode filed under cli/commands nearest peer: fetch_with_retry (core/downloader/http.py:52) 18 │ def stream_chunks(url, dest): $ argot check src/core/parser.py .L3unusual·layering[77d1e02c433a] ↳ cli → core is this repo's direction — this importreverses it 3 │ from cli.commands.run import build_context $ argot check tests/test_rate_limit.py .L41-L43suspicious·test-disabled[c48f0a1de923] ↳ test test_rate_limit_429 disabled — skip/ignore marker added; this change also modifiesmiddleware/rate_limit.py 41 │ @pytest.mark.skip(reason="flaky since limiter rework") 42 │ def test_rate_limit_429(): Valid Python — but a framework this repo has never imported. The evidence shows what it reaches for instead.This repo replaced requests with httpx months ago. argot cites the migrating commits — and warns before you add one more leftover.The repo already has this function. argot names the original and the similarity.Right code, wrong home — its nearest peers all live in core/downloader.Here, cli imports core — never the reverse. This one import flips the architecture.Green because it was skipped, not fixed. argot names the test and the code it covers. See it on real repos→ Why this matters An agent that can’t fix the code will “fix” the test. A green check can hide a weakened test. argot pairs it with the changed code and names both. skip [email protected]("flaky") gut itassertions removed, test kept retarget itexpected 429 → becomes 200 delete ittest gone, code stays the green checkmark, audited # your agent, 40 minutes ago: ✓ Done — 42 passed, 0 failed(was: 3 failing) $ argot check tests/test_rate_limit.py ? L214 1.00 suspicious · staged · test-disabled [c48f0a1de923] ↳ test test_rate_limit_429 disabled — skip/ignore marker added; this change also modifies middleware/rate_limit.py 214 | @pytest.mark.skip(reason="flaky since limiter rework") 215 | def test_rate_limit_429(): 154/164 (93.9%) — detector-specific fixture catch; controls and accepted-history results are separate. Read the detector scope and controls. Evidence you can reproduce Audit accepted changes before making it a habit. argot audit compares accepted changes with the repository history before them. Findings are prompts to inspect, not defect verdicts. argot audit $ argot audit ━━ authored proof receipt · argot 0.2.89 · last 1 commit ━━ 1 commit audited · 1 finding would have prompted review voice 1 code foreign to how this repo writes Worst offender— commit 705db9c · human ! src/receipt.py:L1-10 rare-tokens authored two-commit fixture · raw JSON and checksum are committed ↳ import, class, def — zero-usage tokens in the fitted fixture Then run argot init and choose a recurring check path. Measured, not promised Honest numbers, leak-free by construction. 97.3% visible foreign patterns 620/637 (97.3%) — detector-specific fixture recall; not a product-wide accuracy claim. Fixture recall; the changed symbol is visible in the diff. 36 corpora / 12 languages / visible foreign-import and foreign-api fixture symbols after excluded-host purge · d1007f50; generated_at=2026-07-28 97.1% layering violations 264/272 (97.1%) — detector-specific real recall; control result is separate. Authored internal layering violations across 25 corpora and 12 languages. 25 corpora / 12 languages; authored internal layering violations · 7886a7967c6fe5b822a249083c7871894db1efcf; generated_at=2026-07-20 93.9% test-integrity gaming edits 154/164 (93.9%) — detector-specific fixture catch; controls and accepted-history results are separate. Authored production fit-to-check gaming fixtures. 23 corpora / 12 languages; authored test-gaming fixtures, production fit→check · 1b0de32d; just integrity-verify, 2026-07-28 One static binary. Twelve languages — each with its own tree-sitter adapter and its own learned model: Python TypeScript JavaScript Go Rust Java C# C C++ Ruby PHP Pascal Recall on patterns planted in real files; false alarms on a temporal holdout. Even the structural blind spot — masked foreign — is published, not hidden. Full per-repo numbers→See it caught in the wild→ How it works From audit to a recurring check you choose. Run argot init, then choose the CLI, skills, a commit hook, or a GitHub Action. The Claude plugin adds a narrow pre-write prompt — not a full acceptance-time check. Install the CLI Add agent skills seven on-demand skills for compatible hosts: /argot-setupreads your tree, writes argot.toml, verifies the catch /argot-refreshreviews scope and mutes, then refreshes the learned snapshot /argot-checkscores each diff, flags what’s foreign — never blocks /argot-review-prreviews one PR against your repo’s voice, no checkout /argot-setup-cia non-blocking voice score on every PR /argot-write-ruleturns a convention you state into a tested rule /argot-suggest-rulesfinds your conventions, codifies one The Claude plugin adds optional MCP context and a narrow, fail-open pre-write prompt; agents still decide when to call Argot. Get the plugin → Or drive the CLI by hand→the CI guide ↗ terminal $ argot init Step 1/2: training voice model … Step 2/2: calibrating threshold … Corpus 1129 files · 503 learned · 626 tests/docs skipped Verdict: Ready Snapshot → .argot/ · voice · semantic index · health Review + commit → argot.toml · .argot/ Next: argot check The learned snapshot is committed; caches stay local. CI reads the base branch copy and never fits. The whole model, in one glance Learn once. Share one baseline. Refresh only when the repo truly moves. Argot’s learned state is a reviewed snapshot in Git, not a hosted service. Every developer, agent, and PR compares against the same repository memory. 1local · once Learn the repository argot init learns the voice, semantic neighbours, architecture, and test signals on your machine. nothing uploaded · model ships in the binary 2reviewed · shared Commit the baseline Review and commit argot.toml + .argot/. It is repository-specific learned state, like a lockfile for checks. usually a few MB to a few tens of MB 3local + PR Check against the same memory Local tools, agents, and CI read that snapshot. A PR is judged against the base branch, so it cannot teach itself. advisory by default · CI never fits 4only when useful Refresh deliberately After material accepted drift, status recommends /argot-refresh: review changed scope and mutes, fit locally, recommit. data-driven · no fixed commit cadence ↺Docs churn stays quiet. No cron, no “every 10 commits” chore, no hidden training.See the lifecycle in detail → Integrations A workflow-configured PR or push signal. The GitHub Action reads the committed base snapshot and is non-blocking by default. Intentional divergence remains a human decision, recorded as an audit trail. 🎙️argot reviewadvisory · not a merge gate 🔴2 review decisionsacross 10 scanned hunks Start here: open each row in the review queue. 🔎 Review queue🔴 2 error 1 rare-tokens · 1 unfamiliar-callee ▸🔴 rare tokenscrates/argot-cli/src/voice_diff.rs:202–282 Evidence: option, highlighted, Move — unfamiliar vocabulary here. Review: rewrite in the repository’s established form if unintended. ▸🔴 unfamiliar calleecrates/argot-core/src/compose.rs:1–14 Evidence: Lazy::new is not used by similar code here. Review: compare the call with the repository’s established API. 💬 Prompts for review, not proof of defects. 🧭 Fit health🟢 fresh ▸Snapshot healthy· no maintenance needed Adaptive drift: 0 / 100 · base snapshot: main · CI never fits. Findings land on the PR. Snapshot health stays visible in the same summary. Under the hood Semantic understanding. No generative LLM in the core. Four local engines, one static Rust binary — model included, nothing fetched — all grounded in your git history. A code-embedding model inside the binary A 15.6 MB table argot distilled from a code encoder turns every function into a vector — how argot knows you already wrote this. Nothing to download, no GPU, no cloud: it ships compiled in and works air-gapped. A statistical voice model Two frequency tables and a callee clustering — the imports, callees, and token shapes your repo actually uses. An architecture graph Your module-dependency topology. A new edge that reverses the established direction is flagged with the direction it breaks. A test-inventory diff tree-sitter tracks what every test asserts. A test skipped, gutted, or deleted beside a prod change gets paired and named. 0.2s to check a diff 0.6s when it defines new functions 25s first fit, 1,100-file repo 4s to refresh — embeddings are reused Measured on FastAPI, laptop CPU. Single static binary — no Python, no Node, no model download, no GPU. Advanced capabilities See your conventions — then enforce them. argot conventions finds the shared API and where code belongs. Turn one convention into a small, testable rule. Discovered, not guessedargot conventions lists what your repo already does — its shared API, and where each kind of code lives — so a rule starts from what argot found. Two-sidedts_query_old sees what a change removed — a rule no classic linter can even express. History-awareimport_attested("moment") asks “have we ever used this?” — no other linter can. Test-drivenargot rules test runs your fixtures — the red/green authoring loop. Tamper-evidentlocked = true freezes a rule; a diff that mutes, downgrades, or rewrites it trips rule-tampered — pinned error, unsuppressable, a loud PR annotation. Write your first rule → $ argot conventions where each kind of code lives — learned from your layout migrations/queryRunner · addColumn100% *.schema.tsz.object · validate96% services/db.transaction · publish92% controllers/req · res · next98% codify a line ↓ .argot/rules/no-dropped-endpoints/severity: error // check.rhai — a route that existed before // this change, and is silently gone now let now = []; for m in ts_query(ROUTES) { now.push(m.text); } for m in ts_query_old(ROUTES) { if !now.contains(m.text) { report(m.line, m.text + " removed — deprecate first (docs/api-lifecycle.md)"); } } $ argot rules test no-dropped-endpointsok fires-on-removal · ok quiet-on-refactor Add the layer your CI is missing. MIT-licensed open source. Audit first, then choose the recurring check that fits your workflow. Get started →View on GitHub