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
Install
crimes is published on
npm and requires
Node.js 18 or newer.
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
-
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.
-
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.
-
After editing, re-scan what you touched:
crimes scan --changed --format json. Diff the
findings against the pre-edit run.
-
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.
-
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
name: crimes
on:
pull_request:
push:
branches: [main]
jobs:
crimes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
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?
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)
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 path: /settings/billing
file: src/routes/settings/billing.tsx
component: PricingPage
metadata.title: Plans
<title>: Subscription
nav label in src/nav/registry.ts: Plans
destination: /settings/billing
src/nav/registry.ts label: Plans
src/nav/sidebar.ts label: Billing
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/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 .
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.