Help us shape the future of Go! Take the 2026 Go Developer Survey

itos-cc

module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: AGPL-3.0

README

itos-cc

Code-quality measurements for TypeScript, Python, Kotlin, and Go, in one binary, with output meant for both people and coding agents.

Experimental. This is a proof of concept: commands, output, file formats, and exit codes may change in any release.

The aim is for coding agents to use it through itos, to find out where code is risky before and after they change it. IDEAS.md lists what is planned and what has not been verified.

Command Question it answers
itos-cc crap Which functions are complex and under-tested?
itos-cc dry Which functions are the same code with the names changed?
itos-cc mutate Would the tests notice if this code were wrong?
itos-cc scrap Which test files should an agent leave alone, table-drive, refactor, or split?
itos-cc units What functions and methods do the tools see?
itos-cc serve Live architecture graph for the viewer: what depends on what, and where is it risky?

The ideas come from Robert C. Martin's crapper, mutator, dryer, and scrap. This is a separate implementation in Go around one shared core, so every tool understands every language the same way.

Install

Download your platform's archive from the latest release: Linux (static) and macOS on amd64 and arm64, and Windows on amd64. Check it against checksums.txt and put itos-cc on your PATH:

sha256sum --ignore-missing -c checksums.txt
tar -xzf itos-cc-<version>-linux-amd64.tar.gz itos-cc
itos-cc version

Or build it with Go, which needs cgo and a C compiler (tree-sitter is C):

go install github.com/donvargax/itos-cc/cmd/itos-cc@latest

Build

Parsing uses tree-sitter, which needs cgo and a C compiler:

go build -o itos-cc ./cmd/itos-cc
go test ./...

Releases are built by CI (.github/workflows/ci.yml) on each platform's own runner when a vX.Y.Z tag is pushed, after the tests pass on Linux, macOS, and Windows. To cross-compile Linux and Windows binaries locally instead, use zig as the C compiler:

CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC="zig cc -target aarch64-linux-musl" \
  go build -ldflags='-linkmode=external -extldflags=-static' -o itos-cc ./cmd/itos-cc
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC="zig cc -target x86_64-windows-gnu" \
  go build -o itos-cc.exe ./cmd/itos-cc

macOS binaries need a macOS machine or CI runner: Go's macOS link step asks for a system library zig does not ship.

Use

Run from a project root. Paths are files, directories, or fragments of a path (itos-cc crap billing); --changed selects what git reports as added or modified. Every command takes -h.

itos-cc crap --top 20             # runs the tests with coverage first
itos-cc crap --use-existing-coverage
itos-cc dry --changed             # changed files against the whole project
itos-cc mutate src/billing/invoice.ts
itos-cc scrap --verbose
crap

CRAP = CC² × (1 − coverage)³ + CC. Coverage comes from each language's own tools, run per build root: go test -coverprofile, Vitest, Jest, or c8 (LCOV), coverage.py (LCOV), and JaCoCo or Kover XML for Kotlin. Bring your own with --coverage-command and --coverage-report. Python and TypeScript coverage is measured from the first line of the body, because loading a module executes every def and export const f = … line.

dry

Local names, field names, and literals are normalized away; called function names, operators, and the tree's shape stay. Functions are compared by the Jaccard similarity of their subtree fingerprints. The default threshold is 0.82.

mutate

Swaps operators (</<=, ==/!=, &&/||, +/-, …), deletes ! and unary -, and flips true/false and 0/1, one at a time, inside functions only. What keeps it fast:

  • Differential runs. Each function's source is hashed. Killed mutants of unchanged functions stay killed; survivors and changed functions rerun.
  • Coverage first. Mutants on lines no test executes are reported as uncovered and never run.
  • Narrow, fail-fast test runs. The file's own Go package (-failfast), vitest related / jest --findRelatedTests, pytest -x.
  • Parallel workers in private copies of the project, so the real tree is never modified while tests run. The baseline runs inside a worker, which proves the copy works before any mutant does.

Results go to .metrics/mutate/<file>.json, and a summary comment is kept at the end of each source file (--no-annotate turns it off). Exit codes: 0 all killed, 2 a baseline failed, 3 a mutant survived.

scrap

Finds the test cases of Vitest/Jest, pytest/unittest, Go testing (including t.Run and table loops), and JUnit/kotest, and scores each one on size (fixture text excluded), logic, mocking, and assertions (helpers that assert count as assertions). Similar examples are clustered with dry's fingerprints. Each file gets one action: LEAVE_ALONE, AUTO_TABLE_DRIVE, AUTO_REFACTOR, MANUAL_SPLIT, or REVIEW_FIRST, plus ranked recommendations with line ranges. Every run is compared with the previous snapshot, so rerunning after a refactor says whether it helped.

The architecture viewer

itos-cc serve watches one or more repositories and publishes their architecture over HTTP; viewer/ is a separate web app (TypeScript, React, React Flow, ELK) that draws it as a canvas you drill into: repositories → directories → modules → functions.

itos-cc serve . ../other-repo          # API on http://127.0.0.1:7070
cd viewer && npm install && npm run dev # viewer on http://localhost:5173

# or serve a built viewer from the same port
(cd viewer && npm run build) && itos-cc serve --ui viewer/dist .
  • A module is a file in TypeScript, Python, and Kotlin, and a package in Go. Directories with a single child collapse into one box, so a deep src/main/kotlin/com/acme tree is one click, not five.
  • Solid arrows are imports, rolled up to the boxes on the current level; boxes sit above what they depend on. Arrows in a dependency cycle are red at every level. Kotlin dependencies within a package count even without an import, and Python's importlib.import_module("x") counts as an import.
  • Dashed arrows are HTTP: a request (fetch, EventSource, axios, requests, httpx, http.Get, RestTemplate, WebClient, Ktor client) whose path matches a route another module serves (net/http, chi, gin, echo, Express, Fastify, Hono, Flask, FastAPI, Spring, Ktor). Matching runs across every repository served together, so a frontend in one repository points at the service it calls in another. Requests whose path is all parameters, such as ${base}/${path}, are too vague to place and are skipped.
  • Box color runs from red to green and combines CRAP with the mutation score; grey means not measured yet. A module is as risky as its worst function. A directory or repository is graded by the share of its functions that are risky, and by its overall kill rate, so one bad file does not paint a whole system red. The bar under each box splits its functions into risky, worth a look, low risk, and unmeasured. mut 100% · 6/53 means every mutant run was killed, in the 6 of 53 functions mutation-tested so far.
  • TypeScript imports through tsconfig.json aliases (paths, baseUrl, extends, and Vite-style references) resolve to project files.
  • Saving a file updates complexity, dependencies, and CRAP (live complexity with the last measured coverage) within a second. Rerunning crap, mutate, or dry updates their numbers. Functions edited since their last mutation run are marked stale.
  • Click a box for its functions, a function for its source, an arrow for the imports behind it. Double-click or Enter opens a box; Esc goes up.

API: GET /api/graph (JSON), GET /api/events (server-sent events carrying the graph version), GET /api/source?repo=&file= (only files in the graph). The server listens on localhost only.

.metrics

Every command writes a JSON snapshot under .metrics/: crap.json, dry.json, scrap.json, and mutate/. Commit them: a clone then has the numbers without rerunning, and mutation results are shared, so nobody reruns mutants that are already killed. Snapshots carry no timestamps and are sorted, so an unchanged result is an unchanged file. Raw coverage reports go to .metrics/coverage/, which ignores itself.

Layout

Package Role
lang Parsing, units (functions and methods), namespaces, complexity, and each language's syntax and mutation rules
project Finding source and test files
coverage Running coverage and reading LCOV, Go profiles, and JaCoCo
crap, dry, mutate, scrap The tools
metrics Snapshot files
graph, server The architecture graph and its live HTTP API
cmd/itos-cc The command line
viewer/ The web viewer, a separate npm project

Adding a language means one file in lang/ (grammar, units, decisions, syntax, mutation rules), test-framework rules in scrap/, and a coverage plan in coverage/.

License

GNU Affero General Public License v3.0.

Directories

Path Synopsis
cmd
itos-cc command
Command itos-cc measures code quality in TypeScript, Python, Kotlin, and Go.
Command itos-cc measures code quality in TypeScript, Python, Kotlin, and Go.
Package coverage loads coverage reports in the formats each language's tools write and answers how much of a line range the tests executed.
Package coverage loads coverage reports in the formats each language's tools write and answers how much of a line range the tests executed.
Package crap scores functions by the CRAP formula: complexity that tests do not exercise is risk.
Package crap scores functions by the CRAP formula: complexity that tests do not exercise is risk.
Package dry finds functions whose structure is close enough to be duplicates worth reviewing.
Package dry finds functions whose structure is close enough to be duplicates worth reviewing.
Package graph builds the architecture of one or more repositories as a tree with dependency edges: repositories contain directories, directories contain modules, modules contain functions.
Package graph builds the architecture of one or more repositories as a tree with dependency edges: repositories contain directories, directories contain modules, modules contain functions.
Package lang finds the units every tool measures: the functions and methods of a source file, named by namespace and function name.
Package lang finds the units every tool measures: the functions and methods of a source file, named by namespace and function name.
Package metrics writes the snapshots under .metrics/ that the tools share and that projects commit.
Package metrics writes the snapshots under .metrics/ that the tools share and that projects commit.
Package mutate runs mutation testing: it makes small changes to functions one at a time and checks that the tests notice.
Package mutate runs mutation testing: it makes small changes to functions one at a time and checks that the tests notice.
Package project finds the source and test files the tools analyze.
Package project finds the source and test files the tools analyze.
Package scrap measures the structure of test code the way crap measures production code: which tests are too big, too clever, too mocked, too weakly asserted, or repeated, and whether a test file is worth refactoring at all.
Package scrap measures the structure of test code the way crap measures production code: which tests are too big, too clever, too mocked, too weakly asserted, or repeated, and whether a test file is worth refactoring at all.
Package server publishes the architecture graph over HTTP for a viewer: the graph as JSON, a stream of change notifications, and the source of the files in the graph.
Package server publishes the architecture graph over HTTP for a viewer: the graph as JSON, a stream of change notifications, and the source of the files in the graph.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL