Your coding agent giving you trust issues?
Enforce architecture, instead of asking nicely
graf run architecture-rules/
internal/http/handler.go:88
CreateOrder reaches the DB layer without a service
internal/http/user.go:41
GetUser calls legacy_fetch() — use fetch_v2()
2 violations · exit 1
Asking nicely doesn't scale.
graf builds a semantic map of your codebase — AST, cross-file dependencies, types, modules — and lets you write rules against actual structure, not text patterns.
id: no-db-from-handlersseverity: erroradoption: incrementalmatch: kind: function files: "app/http/**" calls: files: "app/db/**" transitive: barrier: { files: "app/service/**" } as: $dbmessage: "handler ${name} reaches ${db.name} in app/db/** without crossing app/service/**" Turn your project's rules into checks that actually run — across files, through the call graph, through the type layer.
Bigger jobs run the same way.
A migration uses the same syntax, just with more steps — each with its own selector. Write the recipe once, then apply it consistently across all of your repositories.
id: retire-legacy-clientsteps: - id: thread-a-timeout # signature ripple — every call site remapped positionally match: { kind: symbol.method, name: fetch, memberOf: Client, files: "internal/legacy/**" } do: - sig: { append: "timeout: float = 5.0" } - id: repoint-the-constructor # every reference repo-wide, imports fixed per file match: { kind: symbol.function, name: new_legacy_client } do: - retarget: { to: "internal.data.client.new_client" } - id: relocate-the-helpers # move the file, rewrite every importer match: { kind: file, files: "internal/legacy/util.py" } do: - move: { to: "internal/data/util.py" } - id: reap-what-is-now-dead # only what nothing uses — a live decl is refused match: { kind: symbol.function, files: "internal/legacy/**", usedBy: { count: 0 } } do: - delete: {} verify: - match: { kind: symbol, name: new_legacy_client, referencedBy: {} } expect: { count: 0 }gate: "pytest -q"Every step commits atomically, behind one gate — and a step that would break something pauses instead, with the blockers listed.
Fits your stack, not the other way round.
One binary on your machine, one rule language across every repo — driven from your terminal, or by your coding agent.
Local-first
One binary, no daemon or network access needed. Nothing leaves the machine.
Cross-file by construction
Symbols and types resolved repo-wide.
Polyglot by design
One graph schema, one rule language, per-language importers.
CLI-first, agent-ready
Drive it yourself from the terminal, or let your coding agent drive it — we ship a skill.
Your linter sees one file at a time. graf sees the whole project.
Platforms & languages.
Runs anywhere you build. macOS · Linux · Windows.
- Goshipped
- Pythonshipped
- TypeScriptnext
- Javaplanned
- JavaScriptplanned
- .NETplanned
- C / C++planned
- Rubyplanned
- PHPplanned
- Rustplanned