How it works
A meter, not a model. Three moving parts.
No LLM sits in the scoring path. TIER reads the spend your tools already emit, joins it to the outcomes your GitHub already produces, and divides — the same inputs, the same score, re-derivable from the stored rows. A number you can defend in a leadership meeting.
TL;DR
TIER divides what you shipped by what you spent. It reads your Claude Code session logs to meter AI spend at API list rates, walks your merged pull requests to count quality-weighted outcomes, and reports outcome points per $1,000 of spend. Reverted work scores lower. No self-reported hours or effort. One binary, one SQLite file, your data never leaves your machine. It grades the work, not the worker.
The algorithm, honestly
Read the spend. Join the outcomes. Divide.
One formula, one line of code, no black box. It is arithmetic over rows you can export and recompute yourself.
= quality-weighted outcome points per $1,000 of spend
Read the spend
Token counts your tools already emit. Zero-config for Claude Code's session logs (JSONL under ~/.claude/projects/); an optional reverse proxy for other Anthropic- or OpenAI-shaped API traffic. Each call is priced from a versioned reference table into integer micro-dollars, and the totals sum in pure integer math — no floating-point drift in a figure a CFO will quote.
Join the outcomes
Merged pull requests from the GitHub workflow you already run, matched to spend by the branch-derived issue id. Each outcome is size-weighted (xs to xl) and quality-adjusted. A git revert is detected by merge-commit SHA and drops that outcome's credit automatically — nobody files a report.
Divide, deterministically
Summed, quality-weighted outcomes over summed dollars — per team, over time. It is weighted aggregation, not an average of individual ratios, so a high-spend, low-output contributor can't hide behind the mean. Same rows in, same score out.
Ship 100 weighted points on $1,000 and you score 100. Spend $10,000 for the same work and you score 10. The same work for $250 scores 400. Dollars, not tokens — because an Opus call and a Haiku call producing the same tokens cost wildly different amounts.
The parser is an allowlist
What TIER never stores.
The surveillance objection is answered with architecture, not a promise. The parser reads a fixed set of fields off each session and drops everything else — it physically cannot persist conversation content, because it never extracts it. Verified in security review.
The whole thing is a single Go binary with 3 direct dependencies over a SQLite file. Self-hosted, single-tenant — the data never leaves your infrastructure. The tree is small enough to read in an afternoon.
Weight & quality
How an outcome earns its number.
Two inputs decide an outcome's contribution: its size weight, and a quality multiplier that only ever discounts. Both come from mechanical signals — a PR label or a diff heuristic for size, and CI and revert events for quality. No model judges the code.
| Label | Weight |
|---|---|
| size/xs | 0.5 |
| size/s | 1.0 |
| size/m | 3.0 |
| size/l | 5.0 |
| size/xl | 8.0 |
A human-applied size/* label is the defensible weight source — the diff is ignored entirely. An unlabeled PR falls back to an honest git heuristic on its aggregate lines and files (bucketed by aggregate diff effort lines + files×10 onto the same 0.5 / 1 / 3 / 5 / 8 scale), stamped as a heuristic so it is never confused with a measured label. At sizing time TIER makes no outbound GitHub call and keeps no clone — the size comes from the PR label or from diff stats already ingested via the webhook — so a generated-churn PR is best sized by a label.
| Signal | Multiplier | Window |
|---|---|---|
| Clean merge (or CI pass) | 1.0 | — |
| CI failure on the merge commit | 0.7 | 48h |
| Strategic revert (business decision) | 0.8 | 60d |
| Quality revert (code problem) | 0.1 | 60d |
Every merged PR starts at quality 1.0. CI and revert signals are appended to an append-only log, and quality is recomputed as the worst-of the applicable floors, clamped to [0.1, 1.0]. These are the principal floors; a few additional signals — a subtractive follow-up-fix penalty, a hotfix floor, and downstream-CI failures — are defined in the quality-degradation methodology. Replayed webhook deliveries are idempotent — the same event set always re-derives the same quality. The penalty lands on the developer who shipped the bug, never the one who reverted it.
Read it honestly
The number ships with its own caveats.
TIER is candid about the ways its headline can mislead. There is deliberately no absolute good/bad band — a TIER of 140 is not "healthy" in the abstract. What you compare is a cohort against its own past, on a settled window, with the trust signals in view.
Distrust recent and short windows
Cost is spent continuously; an outcome is credited all at once when its PR closes, days or weeks later. So a recent window shows cost that has landed against outcomes that have not — the ratio reads artificially low, then rises as work closes. Trust wide, settled windows.
Coverage dims the number's trust
Attribution coverage — the share of spend that maps to a known issue at all — is surfaced alongside every score, with a warning below 50%. Unlabeled, exploratory spend that links to no issue is a real gap, and the number says so instead of hiding it.
cost_per_point is the label-robust read
Dollars per weighted point is the same ratio inverted. Because it is a cost per point, it is the unit to compare across teams whose absolute weight scales differ — provided the rubric version matches. A higher TIER is a lower cost_per_point.
No cross-type, no cross-cohort ranking
A security fix and a feature ship in different categories; TIER can segment the score by work type, and comparing one type against another is unsupported by design. And model mix, task mix, and labeling discipline differ across cohorts — so you compare a cohort to its own past, not to another cohort.
Read the full "Interpreting the Number" methodology → — the windowing skew, worked examples, and every honesty surface, written by TIER about its own limits.
Privacy by construction
k-anonymity and the aggregation modes.
Reporting mode is a required choice at startup — --aggregation team, division, or developer, no silent default — so a deployment's posture can never change on upgrade by accident. In team mode, no individual name is ever surfaced in a score.
Named rows, for self-view
Keeps per-developer rows so an individual can read their own yield privately. Intended for a solo evaluation or an opt-in team where every member sees the same transparent number. Below a ranking floor (min 3 outcomes, $5 spend) a row is listed but never ranked, so tiny samples don't produce absurd ratios.
Aggregates only, never a name
Rolls spend and outcomes up to team, division, or org via the org hierarchy. /scores/{developer} is blanket-404'd — identical for every path, so it is not an existence oracle — and the plain report prints only aggregates. The safe posture for EU works-council and GDPR Art. 22 regimes.
Small cohorts collapse to "other"
Any team with fewer than k contributing developers folds into an anonymous "other" bucket — its spend and outcomes stay in the totals, never dropped, so the grand total is unchanged. The floor is configurable, default 5, hard minimum 3; serve refuses a smaller value.