Skip to content

`crimes@0.11.0` — Triage as the front door (Release B)

Draft release notes for the GitHub Release tagged v0.11.0. The body below is what should go in the Releases page when you cut the tag — that triggers .github/workflows/release.yml and publishes to npm via Trusted Publishing.

crimes@0.11.0 is a minor release that adds the crimes triage command as the recommended front door for handling existing findings — crimes baseline save stays in place as the escape hatch. Silenced triage entries and baseline entries now resurface automatically in crimes scan when their file is in the branch diff, so a one-time decision doesn’t become permanent amnesia. crimes init --agents ships a Claude Code PreToolUse Edit hook so the pre-edit briefing happens without the agent having to remember to call crimes context. The human renderer’s secondary scores read as prose (blast top-quartile (11 importers) instead of 0.72); the JSON contract is unchanged on those numerics.

The schema bumps "0.1.0""0.2.0" to add two new required fields on every Finding: effort and fix_shape. Existing field shapes, names, and semantics are unchanged.

Changes requiring agent attention:

  1. schema_version is now "0.2.0". Consumers that hard-checked === "0.1.0" must accept the new string.
  2. Finding.effort and Finding.fix_shape are required. Both are detector-supplied with defaults; consumers that ignored unknown fields under 0.1.0 see them appear for free.
  3. .crimes/baseline.json and .crimes/suppressions.json files written by older crimes versions still load — the loader tolerates the "0.1.0" schema_version on those files, which keeps teams from having to rewrite committed state on the same PR they upgrade.

Design rationale: docs/superpowers/specs/2026-05-20-release-b-triage-design.md.


Every report (ScanReport, ContextReport, DiffReport, BaselineCheckReport, VerdictReport, ExplainReport, HotspotsReport, on-disk Baseline + Suppressions, plus the new on-disk Triage) now carries schema_version: "0.2.0". The top-of-file docs/json-schema.md migration note documents exactly what changed.

Agent code that pinned to the old string:

if (report.schema_version !== "0.1.0") throw new Error("unknown schema");

…must switch to accepting both:

if (report.schema_version !== "0.1.0" && report.schema_version !== "0.2.0") {
throw new Error("unknown schema");
}

No existing field changed shape, name, or semantics. The bump exists specifically to add the two new required fields below.

New required Finding fields: effort + fix_shape

Section titled “New required Finding fields: effort + fix_shape”
interface Finding {
// ...existing fields...
/** Estimated effort to address. */
effort: "quick" | "small" | "medium" | "large";
/** One-line description of the shape of the fix (≤120 chars). */
fix_shape: string;
}

The effort ladder:

  • quick — ≤1-line change.
  • small — under one hour of work.
  • medium — fits within one PR.
  • large — needs design.

fix_shape is a one-line description of the shape of the fix (e.g. "extract orchestration; move pure helpers to a sibling module"), not a complete patch. ≤120 chars, single line, non-empty. Detector defaults live in packages/core/src/detector-defaults.ts. A detector that omits either field falls back to the per-type default, then to medium + "refactor to remove this signal; add a test that pins the fix".

.crimes/baseline.json and .crimes/suppressions.json committed under crimes@0.10.x carry schema_version: "0.1.0". The 0.11.0 loaders accept either "0.1.0" or "0.2.0" on those files, so a team upgrading to 0.11.0 doesn’t have to rewrite committed state in the same PR. The first crimes ignore / crimes baseline save after the upgrade rewrites the file with "0.2.0".

.crimes/triage.json is new in this release — there’s nothing to migrate.


Per-finding interactive walk over current findings, top-of-rank first. Each prompt asks for a disposition, reason, and owner, then stamps the date from the system clock. Five dispositions:

DispositionDefault crimes scanResurfaces on touched files?
fix-nowshown ( prefix)n/a
fix-this-PRshown ( prefix)n/a
needs-designhiddenyes — “still intentional?”
wont-fixhiddenyes — “still intentional?”
scaffoldinghiddenyes — “still intentional?”

Each disposition writes to .crimes/triage.json immediately (incremental persistence) so a SIGINT or terminal crash mid-walk does not lose progress. The file is intended to be committed and is hand-reviewable in git diff.

Sample interactive flow:

$ crimes triage
Triaging 47 findings against .crimes/triage.json.
Keys: f fix-now · p fix-this-PR · d needs-design · w wont-fix · s scaffolding · k skip · q quit
[1/47] 🚨 src/billing/invoice.ts
God Function · generateInvoice() — 214 lines, 6 awaits, 3 tables
Risk: churn high · test gap top-quartile · blast 0.72
Fix shape: extract orchestration; move pure helpers to a sibling module
Effort: medium
Disposition? w
Reason (one line): legacy billing, full rewrite Q3 — see ADR-014
Owner [@amayfield]:
✓ wont-fix · saved.
[2/47] …

Flags:

FlagWhat it does
--apply <file>Non-interactive — read dispositions from a JSON file (same shape as on disk).
--listShow the current triage entries and exit; no scan, no prompts.
--clear <fingerprint>Remove a single entry by fingerprint.
--retriage <target>Re-open the disposition prompt for matching entries (fingerprint, file path, or glob).
--owner <handle>Default owner for new dispositions written this run.
--allInclude non-domain tier findings in the interactive walk.
--format human|jsonOutput format for the summary line on exit.

crimes triage refuses to start the interactive walk in CI or a non-TTY (exits 2 with a hint to use --apply). --apply merges by fingerprint — applied entries overwrite existing fingerprints; unmentioned fingerprints stay untouched.

.crimes/triage.json on-disk shape: docs/json-schema.md. Source: packages/core/src/triage.ts, packages/cli/src/commands/triage.ts.

crimes scan now re-runs the relevant detector on files that match .crimes/triage.json or .crimes/baseline.json entries when those files appear in the branch diff against config.triage.resurfaceBase. Findings that still match the stored fingerprint surface with annotations:

interface Finding {
/** True when this finding matches an entry in .crimes/triage.json. */
previously_triaged?: true;
previous_triage?: {
disposition: "fix-now" | "fix-this-PR" | "needs-design" | "wont-fix" | "scaffolding";
reason: string;
owner: string;
date: string; // YYYY-MM-DD
};
/** True when this finding matches an entry in .crimes/baseline.json. */
previously_baselined?: true;
previous_baseline?: {
date?: string;
reason?: string;
};
}

The human renderer shows a “You’re editing files you previously triaged — was this still intentional?” block before the main “Top files by risk” header, with one row per resurfaced finding and a crimes triage --retriage <file> hint. Baseline resurfacings render in a sibling block.

config.triage.resurfaceBase defaults to "main". Set it to "" (empty string) in crimes.config.json to disable resurfacing entirely:

{
"triage": {
"resurfaceBase": ""
}
}

Resurfacing is also skipped silently when the directory isn’t a git repo, when HEAD resolves to the same ref as <resurfaceBase>, or when the re-detect finds zero matching findings for the stored fingerprint (i.e. the user already fixed it — we don’t nag).

Interaction with scopeTiers.nonDomain: resurfacing crosses tiers. A triaged finding in a non-domain file (under scripts/**, **/__tests__/**, etc.) still resurfaces when that file is touched on the branch — the non-domain footer is a display tier, not a “don’t care” tier. The crimes triage interactive walk visits domain-tier findings only by default; crimes triage --all includes non-domain.

New crimes scan flags:

FlagDefaultEffect
--show-triagedoffInclude silenced dispositions in the output (annotated with hidden_triage).
--gate-needs-designoffWhen --fail-on is set, count needs-design findings toward the gate.
--gate-resurfacedoffWhen --fail-on is set, count resurfaced findings on touched files toward the gate.

Default gate semantics: wont-fix, scaffolding, and needs-design are excluded from --fail-on because the user already triaged them. Resurfaced findings are surfaced as a reminder, not a block. Opt in with the flags above when your team wants stricter gating.

Source: packages/core/src/resurface.ts, packages/core/src/triage-filter.ts, packages/core/src/scan.ts.

Bundled into a single schema_version bump ("0.1.0""0.2.0") so consumers cross the boundary once. Both fields are required on output; both come from a detector-defaults map keyed on detector type with a generic medium + “refactor to remove this signal; add a test that pins the fix” fallback.

crimes scan --all and crimes explain <id> show both fields in human output. crimes context per-finding block shows fix_shape under the finding header. crimes triage shows both fields next to the finding when prompting for a disposition — the brief explicitly calls this out as the load-bearing UX, since the user is deciding how much work this is before they pick a disposition.

Source: packages/core/src/finding.ts, packages/core/src/detector-defaults.ts, packages/core/src/detector.ts.

crimes init --agents now writes hook configuration alongside the existing SKILL.md files:

PathBehaviour
.claude/skills/crimes/SKILL.mdUnchanged (since 0.9.0).
.agents/skills/crimes/SKILL.mdUnchanged (since 0.9.0).
.claude/settings.local.jsonNew. Merge-write of a PreToolUse Edit hook.
.agents/settings.local.jsonNew (placeholder). Codex doesn’t honour PreToolUse yet.

The Claude hook merged into .claude/settings.local.json:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "npx -y crimes context \"$CLAUDE_TOOL_INPUT_file_path\" --format json 2>/dev/null || true",
"timeout": 8000
}
]
}
]
}
}

Merge semantics:

  1. If .claude/settings.local.json doesn’t exist, create with just the hook.
  2. If it exists and already contains a crimes hook (detected by the crimes context substring in command), skip (idempotent).
  3. If it exists with other hooks, parse, append, write back with 2-space indent + trailing newline.
  4. If it exists with malformed JSON, exit 2 with a clear message. --force overwrites only the crimes hook entry.

--no-hooks opts out of writing either settings file (SKILL.md files still ship). crimes init without --agents is unaffected.

.agents/settings.local.json mirrors the Claude shape with $CODEX_TOOL_INPUT_file_path. Codex doesn’t honour PreToolUse hooks as of 0.11.0; the file is a forward-looking stub with a top-of-file _note (or sibling README) documenting that it’s safe to delete.

Source: packages/cli/src/commands/init.ts, packages/cli/src/hook-templates.ts.

The scan per-file Risk: line and the context per-finding score block now print interpretive prose instead of bare decimals:

Before (scan default view, file header):

Risk: churn high · test gap top-quartile · blast 0.72

After:

Risk: churn high (24 commits in 90d) · test gap top-quartile · blast top-quartile (11 importers)

Before (crimes context per-finding):

scores: severity 0.86 · confidence 0.88 · blast 0.72 · churn 0.64 · test gap 1.00 · agent risk 0.91

After:

scores: severity 0.86 · confidence 0.88 · agent risk 0.91
blast top-quartile · imported by 11 files (top 5% of fan-in)
churn 24 commits over 90d · last touched 2 days ago
test gap top-quartile · no test file imports this module

JSON numerics in Finding.scores are unchangedblast_radius, churn, test_gap, recency, and agent_risk still ship as 0–1 floats rounded to two decimals. This is a renderer-only change. Fallback for missing inputs (git unavailable, no clues.churn data) shows the raw quartile label only; never a bare decimal.

Source: packages/reporter/src/human/score-format.ts, packages/reporter/src/human/scan.ts, packages/reporter/src/human/context.ts.

The human renderers for crimes diff, crimes baseline check, crimes verdict, and the per-finding Charge: / Also touches: lines now share one glyph vocabulary:

GlyphMeaning
new (also doubles as the terminal logo)
fixed
unchanged
§charge marker on a finding header
related-files block
verdict: worse
verdict: cleaner
verdict: mixed

JSON output is untouched — these marks are presentation-only. Every glyph is suppressed in --no-color mode, so piped output, CI logs, and accessibility-conscious terminals stay plain ASCII.

Source: packages/reporter/src/human/shared.ts.

Agent-skill guidance: JSON for decisions, human for readbacks

Section titled “Agent-skill guidance: JSON for decisions, human for readbacks”

Both shipped SKILL.md files (.claude/skills/crimes/SKILL.md and .agents/skills/crimes/SKILL.md), the crimes init skill template, and docs/agent-usage.md gained a short rule:

Use --format json when you need to plan, gate, compare, or make decisions. When the user wants to see the results — or you are summarising findings back in chat — rerun without --format json and paste the relevant human-readable readout instead of paraphrasing the JSON in your own voice.

The human report already renders severity glyphs, file groupings, evidence, feedback hints, suppressions, and gate status. The skill guidance now says so explicitly so agents stop re-rendering the findings prose-style in chat.

Source: .claude/skills/crimes/SKILL.md, .agents/skills/crimes/SKILL.md, docs/agent-usage.md, packages/cli/src/commands/init.ts.


  • No new detectors. Detector count unchanged at 48. Detector taxonomy stays frozen — Release B is workflow + schema fields + renderer, not new signal.
  • No crimes baseline deprecation. baseline save stays as the escape hatch; the brief is explicit that triage is the front door, not the replacement.
  • No --quick-wins / --budget <minutes> filter flags. effort is added to make these trivial in a follow-up, but they’re explicitly out of scope for this release.
  • No crimes ask / LLM-assisted modes. Still deferred to v1+ per PRD §26.

Terminal window
npm install -g crimes@0.11.0
crimes --version # 0.11.0
crimes triage # new — the recommended front door
crimes scan . --format json | jq '.findings[0] | {effort, fix_shape}'
crimes init --agents # writes PreToolUse hook by default
crimes init --agents --no-hooks # opts out of the hook

JSON consumer migration:

// Before:
if (report.schema_version !== "0.1.0") throw new Error("unknown schema");
// After:
if (report.schema_version !== "0.1.0" && report.schema_version !== "0.2.0") {
throw new Error("unknown schema");
}

Finding.effort and Finding.fix_shape are required from 0.2.0; existing consumers that ignored unknown fields see them appear for free. No existing field changed shape, name, or semantics. Triage and resurface annotations (triaged, hidden_triage, previously_triaged, previous_triage, previously_baselined, previous_baseline) are optional; check docs/json-schema.md for the full shape.

Existing .crimes/baseline.json and .crimes/suppressions.json files carrying schema_version: "0.1.0" continue to load — the loader tolerates either string. The first crimes ignore / crimes baseline save after the upgrade rewrites the file with "0.2.0".