crimes

v0.30.0 · Java, Kotlin and Swift · release notes open source · MIT · local · no cloud

A crime scene investigator for your codebase.

Built for agents, readable by humans.

crimes is an open-source CLI that scans TypeScript, JavaScript, Python, Java, Kotlin and Swift repositories for change risk and agent risk. It finds duplicated business rules, unclear sources of truth, weakly tested hotspots and swallowed errors. It also checks for disagreements between Python and TypeScript, such as an API path that exists on only one side. File-level and Git checks work in any language. All analysis runs locally.

$ npx crimes scan .
CRIME SCENE REPORT
repo: messy-ts-app  ·  41 findings across 19 files  ·  6 high  15 medium  20 low

Top files by risk
src/billing.ts  7 findings · 1 high, 4 medium, 2 low
   1. God Function · generateInvoice()   lines 50–253 (204 lines), 3.4× the domain function threshold (60 lines)
   2. False Identity · calculateRefundPreview()   name prefix suggests a pure transformation, side-effect-like calls: saveRefundAudit, sendRefundEmail
   3. Logic in the Alibi   comment says: "Only owners can refund enterprise plans unless billing support approves.", rule terms: only, unless
   4. Catch and Release · generateInvoice → catch_clause   protected operation: if (body.length === 0) throw new Error("empty body"); (+1 more statement(s)), handler kind: try/catch (line 245)
     Risk: churn low · test gap ~median · blast bottom-quartile (0 importers)
     id=crime_00003 … crime_00022

Use the findings to decide where an edit needs more care, which tests to run and what related code to check.

Install

crimes is published on npm and requires Node.js 18 or newer.

# Global install
npm install -g crimes
crimes scan .

Or one-shot via npx:

npx crimes scan .

pnpm dlx crimes scan and bunx crimes scan also work. See the installation plans in the roadmap.

Quick start

Start with these commands. The CLI reference lists all commands and options:

Scan a directory

crimes scan .

Scans TypeScript, JavaScript, Python, Java, Kotlin and Swift with enabled detectors. Shows the top five files ranked by strongest risk plus diminishing contributions from distinct claims. Pass --all for every finding.

JSON output (the product contract)

crimes scan . --format json

Versioned via schema_version (currently "0.8.0"). Every report carries a report_type discriminator so consumers can route on one field. Every Finding ships effort + fix_shape alongside the existing scores.

Pre-edit briefing for a single file

crimes context src/billing/tax.ts --format json

Returns applicable findings from the shared scan analysis, resolved import neighbors, likely tests and safe-editing notes. Pass --root . for monorepo context. Review analysis status and coverage warnings.

Working-tree-only scan (with optional CI gate)

crimes scan --changed --format json
crimes scan --changed --base main --format json
crimes scan --changed --fail-on high

Restricts to files changed in the working tree, optionally including commits unique to the current branch. --fail-on low|medium|high exits 1 when a visible finding in the selected files meets the threshold. Requires --changed, --files, or --related-to. Use diff, verdict or baseline to gate only new findings.

Branch-level diff between two refs

crimes diff main...HEAD --format json
crimes diff origin/main...HEAD --format json

New, fixed, and unchanged crimes between two Git refs. Exports each ref into a temporary directory via git archive, leaving your working tree intact. Findings are matched by stable fingerprint, so unrelated line shifts don't register as fix + new.

Baseline: gate on new findings

crimes baseline save
git add .crimes/baseline.json && git commit -m "Add crimes baseline"
crimes baseline check --fail-on medium

Snapshot the current findings to .crimes/baseline.json, commit the file, then fail CI only on findings absent from the baseline. --fail-on defaults to medium; exit 1 blocks CI, exit 2 is reserved for missing / malformed baselines.

Branch verdict: compare risk

crimes verdict --base origin/main --format json
crimes verdict --base origin/main --fail-on new-high

One-line "did this branch help or hurt?" summary, built on crimes diff. Default base picks origin/main first, then main. Advisory by default; opt into a hard gate with --fail-on worse | new-high | new-medium.

Triage: record a decision for each finding

crimes triage
crimes triage --list
crimes triage --retriage src/billing.ts

Interactive per-finding walk, top-of-rank first. Each prompt records a disposition (fix-now, fix-this-PR, needs-design, wont-fix, scaffolding), a one-line reason, and an owner. Silenced dispositions and baseline entries resurface in crimes scan when the file is in the branch diff against main, so earlier decisions can be reviewed. Stored in .crimes/triage.json, intended to be committed.

Feedback: report a false positive

crimes feedback <fingerprint> --verdict fp --note "…"
crimes feedback summary
crimes feedback recheck

--verdict fp requires a note and writes a suppression pinned to the current crimes minor, so the finding returns for review after a minor-version update. crimes feedback recheck lists each one with what changed for that detector. Verdicts are stored in .crimes/feedback.jsonl and feed the eval harness that gates every release.

Churn × findings hotspots

crimes hotspots --since 90d --format json

Ranks files by an aggregate score of git churn and current findings. Use it to triage where to look first.

Built for agents

Run crimes init --agents in your project to install the bundled Claude Code and Codex skills, a managed section in AGENTS.md, and the Claude Code edit hook. Normal CLI use checks for skill updates after an npm upgrade. See agent setup and updates for details.

Claude Code

.claude/skills/crimes/SKILL.md

The skill explains when to run crimes, which command to use and how to interpret the JSON. The PreToolUse hook in .claude/settings.local.json supplies context before supported edits.

  • Pre-edit (automatic via PreToolUse): crimes context <file> --format json
  • Triage existing findings: crimes triage
  • Post-edit: crimes scan --changed --format json
  • New severity: "high" after your edit → blocker
Codex and other agents

.agents/skills/crimes/SKILL.md

Codex uses the bundled skill to choose commands and interpret findings. The managed AGENTS.md section gives agents that read it the same pre-edit and post-edit workflow. Setup preserves existing project instructions.

  • Versioned JSON lets agents check the report format before using it
  • Same pre / post-edit workflow as Claude
  • Works with any agent that reads AGENTS.md

Recommended loop

  1. Before editing a specific file, run crimes context <file> --format json. Read every high severity finding first. Under Claude Code with the PreToolUse hook installed, this fires automatically on every Edit / Write / NotebookEdit.
  2. Triage findings interactively. crimes triage walks each finding top-of-rank first, asking for a disposition (fix-now / fix-this-PR / needs-design / wont-fix / scaffolding), one-line reason, and owner. Silenced entries resurface in scan when the file is touched on the branch.
  3. After editing, re-scan what you touched: crimes scan --changed --format json. Diff the findings against the pre-edit run.
  4. Before opening the PR, run crimes verdict --base origin/main --fail-on new-high for the one-line "did this branch help or hurt?" answer, with a hard gate on any new high finding.
  5. To review the wider repo, use crimes hotspots --format json ranks files by an aggregate of git churn and current findings.

Decision rule: any new severity: "high" finding introduced by your edit is a blocker. Fix it, or surface it to the user citing the finding id and charge. Quote evidence[] back to the user when you explain a decision, and check it against the code.

CI

Choose a gate for selected files, new findings against a baseline, or changes across a branch. All three use the same exit codes: 0 success / no blocking findings, 1 a configured --fail-on threshold met, 2 a usage or environment error.

Mode Command When to use
A · Changed-files crimes scan --changed --fail-on high Repo already clean, or you only want to gate the diff itself.
B · Baseline crimes baseline check --fail-on medium Commit existing findings to .crimes/baseline.json, then fail only on new debt.
C · Branch verdict crimes verdict --base origin/main --fail-on new-high PR summary that flips to a hard gate on any new high finding.

GitHub Actions example

# .github/workflows/crimes.yml
name: crimes
on:
  pull_request:
  push:
    branches: [main]

jobs:
  crimes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          # Fetch full history so `verdict` can resolve `origin/main`.
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g crimes
      - run: crimes verdict --base origin/main --fail-on new-high

Full integration guide and Mode A / B swaps: docs/ci.md. Copy-paste workflow: examples/github-actions/crimes.yml.

Example output

Human report (bundled fixture, without Git history)

stale pins: 1 recorded entry match no finding in this scan.
            1 × suppressions: still reported under a new fingerprint — NOT silenced any more
            Run with --explain-coverage for the detail.

CRIME SCENE REPORT
repo: messy-ts-app  ·  37 findings across 18 files  ·  5 high  16 medium  16 low  ·  +2 in non-domain paths

Repo-level
  1. src/api/files.ts
     Charge: Missing Agent Context
     Summary: Repo ships no agent-readable instruction files (AGENTS.md, CLAUDE.md, .claude/skills/*/SKILL.md, .agents/skills/*/SKILL.md). Agents loading this repo will not see project commands, conventions, or safety rules and are more likely to make confident-but-wrong edits.
     Evidence:
       · no AGENTS.md found at repo root
       · no CLAUDE.md found at repo root
       · no .claude/skills/*/SKILL.md present
       · no .agents/skills/*/SKILL.md present
       · package.json declares bin(s): messy — agents have no way to discover commands
     id=crime_00002  confidence=0.90
  2. package.json:1 (undeclared imports)
     Charge: Phantom Accomplice
     Summary: 1 external package(s) are imported but declared in no applicable package.json. They resolve today through hoisting; a clean install can fail.
     Evidence:
… (excerpt)

JSON finding (excerpt; other report fields omitted)

{
  "schema_version": "0.8.0",
  "report_type": "scan",
  "findings": [
    {
      "id": "crime_00001",
      "type": "return_shape_roulette",
      "charge": "Return Shape Roulette",
      "severity": "low",
      "confidence": 0.73,
      "file": "src/petty.ts",
      "symbol": "resolvePaymentResult",
      "lines": [
        21,
        32
      ],
      "summary": "resolvePaymentResult returns several object shapes without an explicit return type. Callers and agents may infer the wrong result contract.",
      "evidence": [
        "3 object-literal return shapes",
        "lowest key overlap: 0%",
        "example keys: { code, message, ok } vs { permanent, url }"
      ],
      "effort": "small",
      "fix_shape": "pick one return shape; use a discriminated union if you need both",
      "scores": {
        "severity": 0.3,
        "confidence": 0.73,
        "agent_risk": 0.43,
        "churn": 0,
        "test_gap": 0.5,
        "blast_radius": 0,
        "blast_radius_transitive_importers": 0,
        "blast_radius_direct_importers": 0,
        "recency": 0
      },
      "suggested_actions": [
        {
          "kind": "name_return_shape",
          "description": "Add an explicit return type or split branch-specific results into named result variants.",
          "risk": "low"
        }
      ],
      "pack": "language-js",
      "detector_id": "return_shape_roulette.js",
      "tier": "domain",
      "fingerprint": "return_shape_roulette::src/petty.ts::resolvePaymentResult"
    }
  ]
}

schema_version is bumped on breaking changes, so agents can refuse to consume an unfamiliar shape. Every finding includes effort (quick/small/medium/large) and fix_shape (a one-line description of the shape of the fix). These references describe the contract:

What it detects today

81 detectors across ten families. The two tables below are the founding families, shown in full because they are the easiest to picture. Every family after them links to its own reference page. Each lists the detectors, what raises and lowers its confidence, and the near-misses it deliberately does not fire on.

Family Since The question it asks
Structural v0.1.0 Is this unit too big, too deep, or too temporally reckless to edit safely?
Information architecture v0.3.0 Does the repo tell more than one story about the same product concept?
Dependency graph v0.6.0 Which edges between modules are cycles, layer violations, or reach past a public entry point?
Duplication v0.6.0 Where does the same body exist twice, so every fix has to land twice?
Frontend / UI v0.6.0 Which UI code will an agent restyle, relabel, or duplicate because nothing marks it as canonical?
Petty crimes v0.3.0 Could dead code, misleading names or logic hidden in comments confuse the next reader?
Assets v0.10.0 Which images are the wrong format or weight for how they are actually used?
Correctness risk v0.16.0 What does this code do when something goes wrong? A swallowed failure, a retry that replays a payment, a test green through any refactor.
Cross-file authority v0.16.0 Where does the repo keep its truth twice? The same authorization rule in two files, one record with two disagreeing declarations.
Agent hygiene v0.16.0 Imports with no declaring manifest, and repo-local agent config granting unrestricted execution. Uses local records without contacting registries or executing configuration.

Beyond one language

Language packs decide what can be seen at all. The universal pack (v0.12.0) runs on every file in any language, so a Rust or Go repo still gets file-level and git-derived findings. The Python pack (v0.14.0) brings real parsing to .py sources, and the Java (v0.29.6) and Kotlin (v0.29.8) packs to .java and .kt, with one dependency graph across both, including the same-package references the JVM resolves without an import, and the Swift pack (v0.29.8) to .swift, where a module's types need no import at all. The cross-language pack (v0.15.0) reports what neither language's own tooling can: a frontend calling a path the backend does not serve, a closed set listed differently on each side, or the same concept called team in one language and workspace in the other.

Structural detectors · shipped in v0.1.0

Detector Charge What it flags
large_function God Function Functions / methods / arrows past a body-line threshold (default 60). Escalates to high at 2× threshold.
large_file God File Files past a line threshold (default 300). Same severity ramp.
todo_density Unfinished Business Files dense with TODO / FIXME / XXX / HACK markers, with line ranges for each cluster.
direct_date Temporal Recklessness Direct Date.now() / new Date() calls in source files, with per-call line numbers.

Information architecture detectors

IA crimes surface deterministic evidence that names, locations or implementations of a product concept disagree. The checks run locally without an LLM or API key. Every finding cites concrete files, lines, and string literals; cross-file findings populate related_files and render as an "Also touches:" block in the human report.

Detector Charge What it flags
missing_agent_context Missing Agent Context Repos that declare a bin in package.json but ship no AGENTS.md, no CLAUDE.md, and no .claude/skills/*/SKILL.md.
route_metadata_drift Route Metadata Drift Route path, file location, default-export component, <title>, metadata.title, and nav labels describe the same destination with competing concept tokens (≥3-source quorum).
duplicated_navigation_source Duplicated Navigation Source A single internal destination (e.g. /settings/billing) appears in two or more nav-like sources with different non-empty labels.
concept_alias_drift Concept Alias Drift Multiple aliases from a seeded concept group (team / workspace / org; plan / subscription / tier) appear across the product surface, each in ≥2 distinct directories.
docs_code_drift Docs-Code Drift A markdown doc under docs/ (or a root-level *.md) contains a local link that does not resolve to a file on disk.

Example IA findings (from the bundled fixture)

# Concept Alias Drift
alias group: tenant
aliases found: account, organisation, team, workspace
"account" in 2 file(s): docs/billing.md, src/routes/account/subscription.tsx
"team" in 2 file(s): docs/teams.md, src/routes/team/index.tsx
"workspace" in 2 file(s): docs/teams.md, src/routes/workspace/members.tsx

# Route Metadata Drift
route path: /settings/billing
file: src/routes/settings/billing.tsx
component: PricingPage
metadata.title: Plans
<title>: Subscription
nav label in src/nav/registry.ts: Plans

# Duplicated Navigation Source
destination: /settings/billing
src/nav/registry.ts label: Plans
src/nav/sidebar.ts label: Billing

# Missing Agent Context
no AGENTS.md found at repo root
no CLAUDE.md found at repo root
no .claude/skills/*/SKILL.md present
package.json declares bin(s): messy — agents have no way to discover commands

# Docs-Code Drift
docs/teams.md:5 → ./setup.md (not found)

IA findings describe possible ambiguity. Check the cited files before deciding whether the difference is intentional. Long-form reference (quorum rules, false-positive notes, suggested fixes): docs/finding-types/ia.md.

Every finding includes evidence (raw facts the detector observed) and scores (severity, confidence, agent_risk).

FAQ

What is crimes?

crimes is an open-source CLI that scans a TypeScript, JavaScript, Python, Java, Kotlin or Swift repository for change risk and AI-agent risk, including duplicated business rules, ambiguous sources of truth, weakly tested hotspots, and patterns that confuse AI coding agents. In a polyglot repo it also reports disagreements between languages, such as a frontend calling an endpoint the backend does not serve. It runs locally with no cloud calls and outputs both a human report and a stable, versioned JSON contract.

How is crimes different from ESLint, Biome, Semgrep, or SonarQube?

crimes is not a linter, a security scanner, or a quality platform. ESLint and Biome catch style and correctness; Semgrep and CodeQL catch security issues; SonarQube wraps both with a dashboard. crimes ranks places where future edits may go wrong and supplies context for reviewing those edits.

Does crimes send my code to a server or LLM?

No. crimes runs entirely on your machine. There are no accounts, no telemetry, and no SaaS dashboard. Core detectors are deterministic AST and file-content analysis. It does not use an LLM.

Which languages does crimes support?

TypeScript and JavaScript (including JSX and TSX), Python, Java and Kotlin. A universal pack runs on every file in any language, so a Rust or Go repo still gets file-level and git-derived findings, and the report says those files had universal checks only rather than reading as clean. Since 0.15.0 a cross-language pack also reports disagreements between Python and TypeScript that neither language's own tooling can see, such as a frontend calling a path the backend does not serve, or a type listed differently on each side.

How do AI coding agents use crimes?

Run crimes init --agents to install the Claude Code and Codex skills, the Claude Code edit hook and a managed AGENTS.md section. The workflow is to run crimes context <file> --format json before editing, then crimes scan --changed --format json after editing, and treat any new severity: "high" finding as a blocker.

Why is the JSON output called the contract?

JSON is the versioned interface used by agents and CI. Every finding is a typed record with id, type, claim, charge, severity, confidence, evidence, scores, and suggested_actions. The top-level schema_version is bumped on any breaking change, so an AI agent or CI system can refuse to consume an unfamiliar shape rather than guess. The human terminal output is a renderer over the same schema.

Is crimes free?

Yes. crimes is MIT-licensed and published on npm as the unscoped package crimes. There is no paid tier, no hosted service, and no telemetry. Install with npm install -g crimes or run one-shot with npx crimes scan .

What's next

Read about released detectors, schema changes and commands on the GitHub Releases page. The forward roadmap is in docs/roadmap.md; the authoritative product spec is PRD.md.

Local, open-source, no cloud

crimes is MIT-licensed and runs entirely on your machine. No accounts, no telemetry, no SaaS dashboard. Analysis uses deterministic checks of your files and Git history.

Detectors live in packages/core, language parsing in packages/language-js, packages/language-py, packages/language-java, packages/language-kotlin and packages/language-swift, reporters in packages/reporter. See the contribution guide to add a detector or language pack.