Skip to content

aditya20t/Keel

Repository files navigation

keel

A small tool that keeps your AI coding assistant honest.

When you build with an AI agent (Claude Code, Cursor, Copilot…), it forgets things between sessions. It picks a different library than the one you agreed on. It writes the same helper function twice under two names. It does the opposite of what your CLAUDE.md says. You often can't see it, because you didn't write the code.

keel reads your code and points out exactly where this has happened:

Your CLAUDE.md says you use Zod. Your code imports Yup. You have formatDate written twice. You're fetching HTTP with both axios and ky. keel finds this in milliseconds, for $0 — no AI, no cloud, no API key.

It gives you one number — a Coherence Score out of 100 — plus a short list of exactly what's wrong and where.


What it catches

What it means Example
Drift Your notes say one thing, the code does another "Says Zod, but 7 files import Yup"
Library conflicts Two libraries doing the same job "axios in 4 files, ky in 1 — pick one"
Exact duplicates The same function written more than once "formatDate and dateToString are identical"
Near duplicates Almost-the-same function copy-pasted and tweaked "buildSettingsA is ~91% similar to buildSettingsB"
Doc drift Your docs reference files that no longer exist "docs/api.md links to src/old-parser.ts — it's gone"

Everything is computed by reading the code directly — no AI model is involved, so the results are exact, repeatable, and free.


What you'll see

$ keel check

  Coherence Score: 84 / 100

  Drift               -7
  Library conflicts   -5
  Duplication         -4

  Findings
    ✗ DRIFT     CLAUDE.md: "validation: Zod" — but code imports yup (1 file)
    ✗ CONFLICT  http: axios (1 file), ky (1 file)
    ✗ DUP       formatDate / dateToString are identical implementations
    ~ DUP~      buildSettingsA() ~91% similar to buildSettingsB()

  scanned 5 files · 3.9ms · $0.00

Loud findings () are high-confidence. Gentle findings (~) are advisory — they might be intentional. keel never blocks your work unless you explicitly ask it to.

The terminal shows a capped summary. For the complete picture — every finding with its exact location and cause, plus a prioritized "how to improve" — write a Markdown report:

keel check --output-md            # writes keel-report.md
keel check --llm --output-md      # + plain-English explanations and an AI improvement plan

The report is written to where you run keel, never into the scanned repo.


Getting started

You'll need Node.js version 20 or newer.

npm install -g @curiousnerd/keel
keel check /path/to/your/project        # any JavaScript, TypeScript, or Python project

That's it. Run it again any time — it remembers what it already scanned, so repeat runs are nearly instant.

From source (clone & build — for contributing, or to run without installing from npm):

git clone https://github.com/aditya20t/Keel.git keel && cd keel
npm install          # install dependencies
npm run build        # compile TypeScript to dist/
npm link             # optional: makes `keel` available globally

keel check /path/to/your/project          # if you ran `npm link`
node dist/cli.js check /path/to/your/project   # otherwise, run the built file directly

Commands and options

keel check [path]          # scan a project (defaults to the current folder)
Option What it does
-v, --verbose Show extra detail under each finding (file + line).
--json Print the result as JSON, for scripts or CI.
--output-md [file] Write a full Markdown report (every finding + locations + causes + how-to-improve) to a file (default keel-report.md).
--limit <n> Max findings to print in text mode (default 25).
--no-cache Don't read or write the .keel cache — a fully read-only run.
--no-gitignore Scan files even if they're listed in .gitignore.
--no-python Skip Python files (don't load the Python parser).
--llm Add plain-language explanations to findings (off by default — see below).
--fail-under <n> Exit with an error if the score is below <n>. Off by default.
--facts Print the raw data keel extracted (for debugging).

The score is graduated: each category (drift, conflicts, duplication) adds up but eases off as problems pile up, so a repo with 30 duplicates scores low without instantly hitting zero.

keel scans only the code you actually maintain. It respects your .gitignore (root and nested), and on top of that skips generated/minified files (bundles, Prisma runtimes, etc.) and node_modules, dist, build, and similar. Duplicate detection only considers named functions, so inline callbacks don't create noise.


Telling keel about your decisions

To catch drift, keel needs to know what you decided. Add a short block to your CLAUDE.md or AGENTS.md (keel reads both):

## Stack
- Validation: Zod
- Database: PostgreSQL
- Package manager: pnpm
- Naming: camelCase

## Constraints
- never use `any`

keel checks each line it understands against the real code. Lines it can't check, it simply ignores — it never guesses.


Silencing a finding you don't care about

False alarms are the fastest way to make a tool annoying, so keel makes them easy to silence — three ways, from most specific to broadest:

1. A comment in the code (best for an intentional duplicate):

// keel-ignore: this copy is intentional
export function dateToString(value: Date): string { /* ... */ }

2. A .keelignore file in your project root:

# We're mid-migration from axios to ky — don't flag it yet.
bucket:http

# Don't look at old code.
src/legacy/**

3. The config file keel.config.json:

{
  "duplication": {
    "minTokens": 20,
    "near": { "enabled": true, "threshold": 0.85 }
  },
  "libConflicts": { "ignoreBuckets": ["test"] }
}
  • minTokens — ignore functions smaller than this (avoids flagging tiny one-liners).
  • near.threshold — how similar (0–1) two functions must be to count as near-duplicates. Higher = stricter. 0.85 is the conservative default.
  • ignoreBuckets — capability groups (like http, date) to never flag.

Why no AI?

It would seem natural for a tool like this to use an AI model. It deliberately doesn't. A study from ETH Zurich found that AI-generated context files actually made coding agents worse — lower success rate, higher cost. So keel does the opposite: it reads the real code with plain static analysis. The answers are exact and cost nothing.

The detection is always deterministic. keel check --llm adds two opt-in extras:

  1. A one-line plain-English explanation under each finding.
  2. Doc-claim drift — it reads your README/CLAUDE.md prose (not just a structured block), extracts the stack you say you use ("validation: Zod", "database: Postgres"), and checks it against the code. Here the LLM only turns English into a claim — the same deterministic drift engine does the verifying, so the score never depends on the LLM's judgement and stays reproducible whether the LLM is on or off. By default it shells out to the claude or codex CLI you already have (no API key, no separate bill); set ANTHROPIC_API_KEY / OPENAI_API_KEY to use an API instead. Responses are cached, and if nothing's available keel just skips them.

Use it as an MCP server (catch problems before they're written)

keel check is a verifier you run after the fact. The MCP server flips that around: it lets your AI agent ask keel before it writes code, so duplication and inconsistency are prevented at generation time instead of caught later. It exposes three tools, all answered from live, deterministic facts about your code — no AI, no cost:

Tool What the agent gets
check_before_write(intent) Existing functions it might be about to reinvent, and the libraries already in use for that job ("you already have formatDate; you already use axios — don't add ky").
get_conventions(path) The real conventions for that area — naming style, language, libraries in use — derived from the code, not a stale doc.
report_drift() The project's current drift + library conflicts, so the agent doesn't add to them.

Register it with Claude Code (after npm install -g @curiousnerd/keel):

claude mcp add keel -- keel mcp

Or drop a .mcp.json in your project (works in Cursor and other MCP hosts too):

{ "mcpServers": { "keel": { "command": "keel", "args": ["mcp"] } } }

The server serves the current directory by default; pass a path (mcp /some/repo) to serve another. It re-reads the code on every call (the file-hash cache keeps that near-instant), so answers always reflect the current state.


Status

Working today. Drift + doc drift, library conflicts, exact + near duplicates, Coherence Score, suppressions, caching, an opt-in LLM layer, and an MCP server — across JavaScript, TypeScript, and Python. Python is parsed with tree-sitter (WebAssembly), so there's nothing to compile at install time.

Coming next: a pre-commit hook and GitHub Action · semantic duplication · more languages.

Want to help or understand the internals? See CONTRIBUTING.md.

Credits

Built by Aditya, with the help of awesome Claude 🤖.

License

MIT

About

A local-first tool that keeps an AI coding agent honest: it catches when generated code drifts from your stated decisions, contradicts itself, or reinvents code that already exists.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors