Skip to content

crimes 0.30.0: Java, Kotlin and Swift, and honest about what it cannot see

JSON schema remains 0.8.0. Fingerprints of existing findings are unchanged. 0.30 adds Java, Kotlin and Swift language packs, makes reports say plainly when a language was not parsed, and gives pre-edit briefings the files that history says change together. It was built against a private estate of two Java backends, a Kotlin Android app and a Swift iOS app, and measured there.

Nothing you have recorded stops matching. Four things you may notice:

  • New findings in Java, Kotlin and Swift files. Previously those files got size and comment checks only. Expect large_function, swallowed_error, direct_date and weak_test_signal findings with .java, .kt and .swift detector ids, and real blast_radius and test_gap scores for those files.
  • A new coverage warning kind, files_universal_only. Source code in a language no pack parses (Go, Rust, C#, …) is listed, and crimes scan prints a partial: line. Consumers that switch on kind should tolerate the new value, as the schema already asks.
  • Unmeasured scores are absent, not 0. Findings on files no pack parses no longer carry test_gap, blast_radius or importer counts. These fields were already optional.
  • context status is target-specific. A target in an unparsed language is partial; a syntax error in a different file no longer makes every briefing partial. context and the Claude hook also stop at the enclosing git repository instead of walking up to an unrelated package.json.

The CLI now re-executes itself once under V8’s --liftoff-only (see Performance). Set CRIMES_NO_REEXEC=1 to disable it.

Java, Kotlin and Swift. Each parses with a vendored tree-sitter grammar on the WebAssembly runtime the Python pack already used. Java and Kotlin share one dependency index, so classes in the same package link to each other without imports, across the two languages, and Kotlin top-level and extension functions resolve. Swift links files through referenced types and extensions, since its imports name modules rather than files. Test discovery knows JUnit, kotlin.test, MockK, Kotest, XCTest and Swift Testing conventions. The four detectors are written once and configured per language. Calibration decisions from reading sampled findings against the code are recorded in calibration follow-ups: specific catch types are treated as deliberate recoveries, stopwatch reads are not clock dependencies, Compose and SwiftUI views get the React component budget, and benchmarks are not weak tests.

Briefings use history. crimes context adds up to four files that changed in the same commits as the target (at least twice in its last 40 commits), with the reason, and co-changed tests join likely tests. Imports cannot see the files a cross-layer change touches; history can, for any language.

Clock reads say what they are for. In JS and the new packs, each reading is classified as deciding a branch or only recording a value. A file whose readings only record ranks lower (0.6 of the intrinsic) and is capped at medium severity.

Quieter defaults.

  • Paths that both ESLint (flat config ignores) and Prettier exclude, in the same package directory, are skipped and listed as files_excluded_by_tooling. This reverses part of an earlier decision; on the four repositories that decision was measured on it still excludes nothing.
  • raster_should_be_vector skips photos, animated GIFs, spacers, Android density buckets, @2x/@3x variants and email templates, and reports a large icon set once.
  • config_drift no longer calls Vite’s import.meta.env built-ins or CI undocumented.

Also in this release (0.29.1–0.29.3, previously unreleased).

  • name_behavior_mismatch requires a word boundary on its prefixes and skips React components.
  • direct_date no longer counts new Date(value) as a clock read.
  • large_file gives data files a separate budget.
  • weak_test_signal reads the test body, not a destructured fixture argument, so Playwright tests with ({ page }) are no longer reported as asserting nothing.
  • The bundled skill asks agents to record confirmed findings as well as rejected ones.
  • The eval harness runs every agent in a disposable copy of the fixture.
  • crimes loads on Node 18.0–18.5 again. Since 0.16 the bundle imported isBuiltin from node:module, which arrived in 18.6, so on earlier 18.x every command failed before it ran. The packed 0.30.0 tarball was run on Node 18.0.0, 18.17, 20, 22, 24 and 26.

On the private estate, before → after this release:

Java backendSecond Java backendAndroidiOS
Source files parsedJS only → +2,333 JavaJS only → +2,575 Java→ 1,645 Kotlin→ 1,604 Swift
Findings1,596 → 1,608566 → 938425 → 391250 → 526

The noise reductions removed about 600 findings on the first repository before Java added its own.

Briefings. 82 files from 48 recent commits, each briefing taken at the commit’s parent: how often the briefing named a file the commit went on to change rose from 12% to 18%, 22% to 34% and 40% to 52% on three repositories, unchanged at 26% on the fourth. Where a test named for the target already existed, the briefing found it. Briefings took 4–10 s.

Performance. A Java briefing on a 3,400-file repository fell from 19 s to 9 s: each file is now parsed once (the per-analysis reuse budget rose to 128 MB), the Java walk materialises only the nodes it reads, and cycle detection runs once per graph. V8’s optimising WebAssembly compiler used unbounded memory on these grammars; under --liftoff-only a 1,604-file Swift scan peaked at 371 MB instead of 2.8 GB and ran faster (21 s → 13 s). Generated and vendored files are not parsed by the new packs.

Evals. The live round at 0.29.9 completed 120/120 cells. Eval fixtures are JavaScript and Python, so it re-pins the baseline rather than measuring the new packs. Ranking on the labelled scenarios is within 0.001 of 0.29.1.

  • Scan ranking on JVM and mobile code. The top of a full scan is still dominated by clock reads and commented-out code there, because the calibration since 0.24 ranks those signals above large, churning, untested files. Revisiting that is the first item for 0.31.
  • JSP and LESS are not parsed; on the estate above that is about 1,650 files, reported as undiscovered. They are the 0.31 milestone.
  • No eval fixture exercises Java, Kotlin or Swift yet.
  • Large repositories: a full n8n scan takes about 27 minutes.
  • Java 21 type patterns (case final Film film ->) do not parse with the current tree-sitter-java; affected files are reported as partial parses.
  • The published package grows by about 7.7 MB unpacked (1.3 MB packed) for the three grammars.

Upgrade by updating the npm package normally. See agent usage and language packs.