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.

no-db-from-handlers.yaml
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.

retire-legacy-client.yaml
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

Let’s talk.

daniel@graflabs.org

Bring a repo. We’ll write a rule against it.