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.
Upgrading
Section titled “Upgrading”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_dateandweak_test_signalfindings with.java,.ktand.swiftdetector ids, and realblast_radiusandtest_gapscores for those files. - A new coverage warning kind,
files_universal_only. Source code in a language no pack parses (Go, Rust, C#, …) is listed, andcrimes scanprints apartial:line. Consumers that switch onkindshould 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_radiusor importer counts. These fields were already optional. contextstatus is target-specific. A target in an unparsed language ispartial; a syntax error in a different file no longer makes every briefingpartial.contextand the Claude hook also stop at the enclosing git repository instead of walking up to an unrelatedpackage.json.
The CLI now re-executes itself once under V8’s --liftoff-only (see
Performance). Set CRIMES_NO_REEXEC=1 to disable it.
What changes
Section titled “What changes”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 asfiles_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_vectorskips photos, animated GIFs, spacers, Android density buckets,@2x/@3xvariants and email templates, and reports a large icon set once.config_driftno longer calls Vite’simport.meta.envbuilt-ins orCIundocumented.
Also in this release (0.29.1–0.29.3, previously unreleased).
name_behavior_mismatchrequires a word boundary on its prefixes and skips React components.direct_dateno longer countsnew Date(value)as a clock read.large_filegives data files a separate budget.weak_test_signalreads 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
isBuiltinfromnode: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.
Measured
Section titled “Measured”On the private estate, before → after this release:
| Java backend | Second Java backend | Android | iOS | |
|---|---|---|---|---|
| Source files parsed | JS only → +2,333 Java | JS only → +2,575 Java | → 1,645 Kotlin | → 1,604 Swift |
| Findings | 1,596 → 1,608 | 566 → 938 | 425 → 391 | 250 → 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.
Still unsettled
Section titled “Still unsettled”- 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.