agent harness · ts · py · swift

Rules give
your agent freedom.

klint is the bridge between vibe coding and agentic engineering — architecture-level rules your AI cannot ignore, declared in YAML, enforced at the Stop hook.

$bun add -d @konvert7/klint
01 / LANGS
ts · py · swift
architecture checks
02 / CONFIG
yaml, not code
declarative architecture
03 / RUNTIME
rust-backed
macos · linux · windows
04 / EXITS
--json
structured violations
structurally enforced agent-blocking exit codes declared in yaml imports · layers · forbidden · singleton typescript · python · swift rust-backed architecture checks homebrew · pypi · npm stop hook integration custom rules in typescript the agent ships the fix
structurally enforced agent-blocking exit codes declared in yaml imports · layers · forbidden · singleton typescript · python · swift rust-backed architecture checks homebrew · pypi · npm stop hook integration custom rules in typescript the agent ships the fix
01
01 — the gap

The agent didn't read your AGENTS.md.

Or it did, then it forgot. Or it drifted three turns later. Instructions in a prompt are a contract with no enforcement — a model that's context-starved or just wrong will violate them silently and ship anyway.

AGENTS.md tells the model what to do.
klint ensures it actually did.
The gate runs after every agent edit. Violations exit 2, block the Stop hook, and arrive in the agent's event stream as structured JSON — so it fixes them before the session can close.
02
02 — architecture-as-code

Architecture
as code.

Four primitives. One YAML file. Every rule you'd otherwise hide in AGENTS.md and pray, expressed declaratively and enforced structurally.

┌──────────────────────┐ klint.yaml    4 primitives └──────────────────────┘
klint.yaml4 / 4 PRIMITIVES IN USE
# yaml-language-server: $schema=./klint.schema.yaml

include: ["src", "!**/node_modules/**"]
plugins: [sonar]

arch:
  layers:
    core:   ["src/hooks/lib/**", "src/tools/**"]
    skills: ["assets/skills/**"]
    dao:    ["src/dao/**"]

  imports:
    # skills must stay portable — no cross-layer reach
    - from: skills
      deny: core
      message: "Skills must be self-contained and portable"
      severity: warn

    # dao gets a strict allowlist — anything else is denied
    - from: ["src/dao/**"]
      allow: ["src/dao/**", "src/prisma/**", "src/types/**"]

  forbidden:
    - pattern: "console.log("
      in: core
      message: "Leaks into the agent event stream"

  singleton:
    - pattern: "process.env.API_KEY"
      only: "src/lib/auth.ts"
PRIMITIVE / 01layers

Layers

Name your architectural zones once. Reference them everywhere — no glob copy-paste, no drift between rules.

PRIMITIVE / 02imports

Imports

Deny lists, allowlists, type-only exceptions. The dependency graph your README claims you have.

PRIMITIVE / 03forbidden

Forbidden

Block literal strings — console.log, process.exit, raw SDK fetches — scoped to the layer.

PRIMITIVE / 04singleton

Singleton

Pin a pattern to one file. Every other touch is a violation — the only honest way to enforce a module of record.

03
03 — engines and distribution

Native where
it should be.

ENGINE / 01

TypeScript engine

The full compatibility path: type-aware checks, custom rules, plugins, and fixes.

ENGINE / 02

Rust engine

Portable architecture checks and syntax-local rules backed by tree-sitter.

ENGINE / 03

Hybrid auto mode

Rust-supported rules run natively while TypeScript-owned rules stay semantic.

installprimary + secondary paths
npm packageprimary
$bun add -d @konvert7/klint

Primary path for TypeScript projects and the full CLI surface.

homebrew
$brew install klint

Native CLI for local architecture checks on macOS.

python
$pip install klint

PyPI package wrapping the native architecture engine.

04
04 — the gate

Ships the fix,
not the violation.

~/code/catalystbun klint
$ bun klint

 src/skills/email/send.ts:14
  arch/imports
  Skills must be self-contained and portable
  → remove import from "src/hooks/lib/paths"

 src/dao/user.ts:42
  no-floating-promise
  Promise-returning call whose result is discarded
  → await it, or .catch() it explicitly

! src/hooks/lib/auth.ts:8
  arch/singleton
  process.env.API_KEY must only appear in src/lib/auth.ts

──────────────────────────────────────────────
2 errors, 1 warning in 184ms
exit 2
application/jsonbun klint --json
{
  "violations": [
    {
      "rule":     "arch/imports",
      "file":     "src/skills/email/send.ts",
      "line":     14,
      "severity": "error",
      "message":  "Skills must be self-contained...",
      "fix":      null
    },
    {
      "rule":     "no-floating-promise",
      "file":     "src/dao/user.ts",
      "line":     42,
      "severity": "error"
    }
  ],
  "summary": {
    "errors":   2,
    "warnings": 1
  }
}
05
05 — stop hook

Wired into
the Stop hook.

Every agent edit ends with klint. Exit 2 means the session can't close. The model reads the structured violations, fixes them, and tries again — no human in the loop, no silent drift to production.

.agents/hooks/stop/klint.tstypescript
import { runHook } from "./run-hook";

const exitCode = runHook([
  "bun", "klint/cli.ts", "--json",
]);

process.exit(exitCode);
  1. 01

    agent edits files

    The model writes whatever its context permits — refactor, feature, bug fix. No interference during the work loop.

  2. 02

    stop hook fires klint

    Before the agent's turn closes, klint runs against the changed layers and emits machine-readable diagnostics on stdout.

  3. 03

    exit 2 blocks the close

    Violations land in the agent's event stream as structured JSON. The session can't end until they're resolved — fix or explicit override.

  4. 04

    agent self-corrects

    The model reads the rule, the file, the line, and the message it wrote itself. Then it does the right thing. Quietly. Every time.

[ works with any agent that runs a stop hook ]

claude code/opencode/codex/cursor/your own
06
06 — philosophy

Rules don't
constrain your agent.
They free it.

An agent that knows the boundaries spends zero cognitive overhead second-guessing them. It just executes — confidently, fast, inside the zone you defined. That is the freedom. The tighter the harness, the wider the runway.

PILLAR / 01

Architecture,
not style

Formatters and language linters enforce local correctness. klint enforces project boundaries and policies an agent must not bypass.

PILLAR / 02

Agent-blocking,
not advisory

A warning the model ignores is the same as no rule at all. klint exits with intent — the Stop hook respects it, the session waits, the fix lands.

PILLAR / 03

YAML,
not code

Architecture rules belong in declarative config a reviewer can read in ten seconds. Escape hatch to TypeScript exists, but most of the time you'll never reach for it.

[ install ]

The bridge between
vibe coding and agentic engineering.

$bun add -d @konvert7/klint