Every Grasp Score comes from the method on this page. I publish all of it because I expect people to check my work; that is the point of a measurement.
Jason Gorman named comprehension debt in September 2025, and Addy Osmani's writing carried it into the mainstream the following spring: the widening gap between how much code exists and how much of it any living person still understands. The Comprehension Index is the measurement for it, and the Grasp Score is the number this instrument produces. Where cyclomatic complexity and its descendants score the code, this scores the people's grasp of it: who wrote what still ships, who is still here, and what keeps changing with no living author behind it.
Grasp analyzes commit history metadata (authors, timestamps, which files changed, co-author trailers) and, where API access exists, pull-request records (open/merge times, review events). Free scans clone with git's --filter=blob:none, so file contents are never transferred at all; the git protocol itself withholds them. No source code is read, stored, or transmitted, by me or by the server.
How it computes: deterministic arithmetic over that metadata, with no AI anywhere in the engine: no language models, no inference, nothing generative. The same scan on the same day returns the same number, the evidence bundle lets anyone re-derive it, and only deterministic arithmetic allows that kind of audit.
Scores are as of a date. "Active" means active in the 90 days before the scan, measured against the calendar rather than the repository's own last commit. That distinction matters more than it sounds. Anchored to the repository, every contributor to a long-abandoned project counts as current, because they were all active in the 90 days before it died. Measured against today, an abandoned repository scores near zero (the honest answer). Every report carries its scan date, the days since the last commit and a dormant flag when nothing has landed inside the window, so a low score on an archived project reads as its status rather than a problem to fix.
| Term | Exact meaning |
|---|---|
| Active human | A human author (bots and agents excluded) with a commit in the last 90 days. A cliff, and weak evidence of leaving: 80% of curl's and 82% of Django's regular contributors have gone quiet that long and returned. That is why the far side is "inactive" rather than "departed", why the headline weighs recency as a curve instead, and why self-hosted scans can set their own window. |
| Living knowledge | The share of recent work (3-year window, lines changed: additions plus deletions, so pruning is visible) held by its authors weighted by how recently they were here: full weight the day a person commits, half weight after 180 quiet days, on a smooth curve. Taken across every scored line rather than averaged over modules, it is the Grasp Score, so no tree layout can move it. The binary 90-day version is published beside it as living_knowledge_cliff with its 60/90/180 band. |
| AI-attributed minimum | Source-file work from commits authored or co-authored by identifiable AI agents and bots. It overlaps living knowledge rather than subtracting from it: an active human who ships agent-assisted work and attributes it honestly still counts as living, and the same lines count here too. A low minimum. By how much. |
| Machine identity | Counts as an agent only on bot-shaped evidence: a [bot] marker, a service address, or a known bot domain. Names that are also human names (Claude, Devin, Jules) need that corroboration first; names that are never human (Copilot, Stainless) stand alone. |
| Inactive share | Work by human authors with no commits in the active window. Inactive is the honest word: it measures current silence, not permanent departure. In open source this includes drive-by contributors, overstating attrition versus a company roster. |
| Bus factor | Number of active humans each holding ≥10% of a module's human-authored work. |
| Heat | Commits touching the module in the last 180 days. |
| Risk (0–100) | The unknown share (1 − living knowledge) at 55%, plus up to 25% when the module is hot and 20% when ownership is thin: unknown code that still changes, held by few. The weights are judgement, not physics; the formula is versioned. One caution: a module freshly rewritten by one person scores low here because its author is present, while its bus factor of 1 records that it lives in one head. The two failure shapes are separate columns on purpose; read both. |
Automation bots (CI, dependency updaters, release tooling), lockfiles, build output, and binary assets are excluded. Modules below a minimum activity threshold are omitted from a report's tables, to keep them from filling with two-line directories; their lines still count toward the score at the top.
Every signal below ships in every scan. Each exists because a review found the score silent about something it should confess; together they are the error bars.
| Signal | Read it as |
|---|---|
| living_knowledge_band + score_stability | The cliff variant at 60/90/180-day activity windows, labelled stable, moderate or volatile; the label travels with the headline. Read the width before the middle value. |
| living_knowledge_cliff | The headline with the 90-day cliff in place of the curve: fully in the window or not at all. Published beside the headline for continuity; the promotion study is at /decay-study. |
| successor_shadow | Living stewardship of the steering-and-follow-up surface, the candidate for the day authorship stops being the right proxy. Promoted never or later, and only through a published study. |
| window_line_coverage | How much of the recorded history the window holds; under half is flagged, and a thin window flatters. |
| largest_commit_share / largest_commit_file_share | How much of the window one commit carries, by lines and by breadth of files touched. Both flagged past their thresholds. |
| largest_file_share (per module) | How much of a module's weight sits in its single heaviest file; a module that is mostly one generated file says so. |
| revisits_per_file / coupled_modules (per module) | How often files are re-touched, and how many other modules move with this one. Informational, never score-bearing. |
| cochange_clusters | Module groups that change as one thing, clustered deterministically from co-change evidence, never from reading imports. |
| quarterly_series | Commits, trailer share and new-author influx per quarter; the raw material of the diligence manipulation screen, which states deltas, never verdicts. |
| signed_commit_share | How much of the window even carries a commit signature. Presence only, never verification. |
| applied_commits / top_appliers | Patch-workflow visibility: commits applied by a human who is not their author. Appliers count as present; fractional line credit was measured and deferred. |
| release_cadence | Tags shipped in the last year, days since the last tag, and the all-time count, read from the repository's own tags. A project can be quiet in its code and current in its releases, and a dead one is neither; last-commit age alone cannot tell those apart. Lifecycle context only; it never enters the score. |
| drive_by_share | How much of the inactive weight belongs to people who appear exactly once in the recorded history, so the drive-by long tail is subtractable from true maintainer churn. The score never removes it: a one-commit author's work is still work nobody present wrote. |
| composition + living_knowledge_by_bucket + source_only | The source/test/doc/config mix, living share of each bucket, and the headline recomputed over hand-written source alone. |
| ai_signal_coverage | The share of commits carrying any attribution signal: the ceiling on what AI detection could ever see here. |
| identities_merged / people_named | How many identities were coalesced, and whether this scan named real people; outputs are pseudonymous by default, and the merge audit is emitted only in named mode. |
| external_share (with declared domains) | How much of the work belongs to contractors and agencies, and how much of that external work has gone quiet. |
| ai_followup_30d_share / steering_share | Human follow-up on AI-touched files within thirty days, per scan and per module, an AI touch nobody ever revisits counting against it; and authorship of steering artifacts (rules files, agent instructions, ADRs, specs). High AI share with low follow-up is attributed machine work nobody returns to. |
Generated and configuration files (JSON, YAML, TOML, XML, SQL, protobuf stubs, migrations, fixtures, snapshots) count at 0.3 weight rather than 1.0, because a 5,000-line generated schema is not five thousand lines of understanding and counting it equally lets one machine-written file decide a repository's score. The weight is partial rather than zero because someone still has to own that configuration. Build output, images, fonts, lockfiles and minified bundles are excluded entirely. The discount bounds the class, not the individual file, which is why each module reports its heaviest file's share. The class rule has a named cost: a hand-written Helm chart prices at 0.3 alongside machine-dumped YAML, because file metadata cannot tell them apart without reading contents; infrastructure languages with their own extensions (Terraform's HCL among them) are not in the class and count in full.
Tests and documentation count in full: real work real people must understand, a departed test author is genuine comprehension debt, and discounting them would need a coefficient nobody could defend. But a repository is often mostly not source (four percent in crewAI, where documentation carries eighty), so every scan publishes its composition and a source-only living figure beside the headline; a score over a repository that is four-fifths prose is substantially a statement about that prose.
The AI minimum is the exception, measured against source only, because "what share of this code was written by a machine" is a claim about code. Gin reads as far more machine-written across every artifact than across its source, the difference being a maintainer co-authoring release notes with an agent; both numbers ship, and only the source figure means what the column says.
Attribution overlaps; it never subtracts. A commit
with an AI co-author trailer counts toward the AI minimum, and its human author, if still active,
counts toward living knowledge at the same time. The alternative, treating any attributed
commit as wholly machine work, prices honesty as a defect: measured on the most diligently
attributing repository in this benchmark, the honest team would score 37 points below an
identical team that strips its trailers, and a metric of unnoticed risk must never hand its
best-behaved subjects its worst numbers. The same principle sets the split: credit divides
equally among the humans on a commit, machine co-authors never dilute that share, and on
generated pipelines the engineers named in the pipeline's own trailers hold the living
knowledge, with the AI minimum beside the figure keeping it honest. Notebooks count at the
generated weight for the same class of reason: an .ipynb diff is mostly
machine-serialised output cells, and LangChain carries half a million such lines that would
otherwise be a fifth of its score.
Line-weighted (full clones) weights authorship by lines added: it matches "what share of the code by volume has a living author," but verbose and generated authorship dominate it. Touch-weighted (blobless free scans) counts each file-touching commit once, closer to how comprehension forms, though a 3,000-line generated commit counts the same as a one-line fix. The two proxies err in opposite directions, so the whole benchmark is computed both ways as a self-check; any conclusion that flips when the weighting changes is not published. Scores are comparable only within a mode, and every report states its mode. Neither mode can see difficulty, and no metadata metric can: a 15-line concurrency primitive can carry more correctness than 3,000 lines of boilerplate, and telling them apart requires reading the code, which this engine never does. Touch-weighting is also the language-neutral mode, since line counts inherit a language's verbosity; fleet scans rank estates touch-weighted for that reason.
Both are windowed churn views, defaulting to 3 years. The window matters, measured on the cliff variant: curl reads 96 at 3 years and 79 at 30, the same repo in the same health, because decades of long-rewritten code count against it; unwindowed counting quietly turns the metric into an age detector, so the window stays and every report states it. The window also has a size, reported as coverage: on a slow-moving project it can hold a few percent of the recorded history, the excluded majority skews toward authors long gone, and a thin window therefore flatters. A thin-window score reads as a statement about recent work, paired with the survivorship view for the whole tree.
Diligence engagements add a third, windowless survivorship view: git-blame attribution of every line in the current tree, answering who wrote the code as it exists today. A line written fifteen years ago that still ships counts fully, credited to its author's current status. Blame follows code that moves, within a file and between files, so extraction credits the author rather than the mover. Churn shows where risk is accumulating now; survivorship shows the standing inventory. The two are labeled and never mixed.
Sixteen numbers in this engine were chosen rather than derived: the 90-day activity window, the 180-day heat window, the three-year lookback, the 200-line module minimum, the 0.3 weight on generated files, the 40% container test and the three-level depth cap that goes with it, the 10% key-person share, the commit count at which heat saturates, the two coefficients in the risk formula, the one-third breadth threshold at which a commit is priced as touches, the half-of-history flag on thin windows, the 180-day half-life of the recency curve, the 30-day follow-up window on AI-touched files, and the single-appearance cutoff that marks a drive-by contributor. None has been validated against an outcome. Nobody has shown that a module below some score causes more incidents, or that onboarding takes longer where living authorship is thin. That validation needs data pairing repository history with incidents and ramp times, which I do not have and which no public dataset provides. Inventing a proxy, counting bug-fix commits and calling them incidents, would produce something that looks like evidence and is not, so I have not done it.
What can be tested is whether the conclusions are artifacts of those numbers. Recomputed on the cliff variant at 30, 60, 90, 180 and 365-day activity windows, the gradient's ends hold at every setting, AI vendor SDKs lowest and modern human-led projects highest, while the middle groups swap places, so their ordering is not a finding and I say so.
Individual repositories move further on that variant: seven of the 22 shift by more than 40 points across the sweep. Flask, with one active contributor, scores 0% at sixty days and 76% at ninety, the same repository in the same week; curl, with 52, stays between 92% and 95% wherever the line goes. Stability is mostly a function of how many people are currently active, and a repository held up by one or two cannot produce a stable cliff number however carefully the rest is computed. That is why the headline prices presence as a curve rather than a cliff: one person's ninetieth quiet day moves the score by a step, not a plunge. The cliff score is still published beside it at 60, 90 and 180 days as a band, every scan labels itself stable, moderate or volatile from that width, and the width is read before the middle value. The measurements that decided the curve's promotion are at /decay-study.
Modules are directories, the only structure a repository reliably provides.
Taking the top-level directory fails on any project that keeps its code under one roof. Applied to
LangChain it puts 99% of the lines in a single module, and a ranking with one row cannot point
anywhere. Naming the usual containers instead (src, packages,
apps) only moves the problem to the next project that uses a different word, and
writes one team's layout into everybody's score. The test here is structural: a directory holding
more than 40% of the weighted tree is a container rather than a module, and the split moves inside
it, to a depth of three. A monorepo divides at its packages, a flat project is left alone, and no
directory name appears in the rule.
Two consequences: module boundaries follow layout, so module tables are comparable within a project and not between projects, and the score at the top of a report is computed over every scored line rather than by averaging the rows beneath it, which makes it arithmetically independent of where these boundaries fall; the minimum module size governs which rows are worth printing, nothing more.
Developers change employers, and their commits change address with them. Counted naively one person becomes two: the older address falls outside the activity window, so their own surviving work is scored as written by someone who left, and their remaining share drops below the key-person threshold. Both errors make a repository look worse than it is, and a measure of unnoticed risk must not manufacture the risk it reports.
Addresses sharing the same numeric GitHub user id
merge first: the id survives every rename, the one identity evidence here that cannot be wrong
about a person. A curated .mailmap is authoritative; only one repository in this
benchmark ships one, so nearly everywhere else the rule applies: addresses merge when they
share a full name of two or more tokens. A single token is not evidence: one "bob" is not
another. Machine identities never merge, so an agent under three addresses stays three
machines rather than becoming one contributor. The canonical address is the most recently
used, every merge is auditable in named mode, and a mailmap entry overrides any merge that is
wrong. The next heuristics were measured rather than argued: localpart matching recovered at
most one identity per benchmark repository, and diminutive matching (Rob for Robert) yielded
about one candidate per repository while offering to merge a Dave Smith with a David Smith;
both were rejected as collision risk. Name changes are not recoverable from git; the mailmap
and, for client work, the roster are the answers, and unmade merges only make a repository
look worse than it is. Co-author trailers count as presence: a person appearing only in
trailers registers as active, joins the contributor counts, and shares commit credit
equally.
Published so it can be checked and corrected. Agent names that are never human names count on their own: copilot, cursoragent, chatgpt, openhands, codegen, stainless, speakeasy, codeium, tabnine, sourcegraph-cody, swe-agent, gemini-code-assist, amazonq, qodo, factory-droid, sweep, openapi-generator, swagger-codegen. Names that are also human names need corroborating bot evidence before anyone is called a machine: claude, devin, codex, aider, jules, cursor. Review bots: ellipsis-dev, greptile, coderabbit, sourcery-ai, codeant, bugbot, qodana. Build and dependency automation, excluded from scoring rather than counted: github-actions, dependabot, renovate, semantic-release, snyk, release-please, mergify, pre-commit.ci, allcontributors. Anything else carrying a platform bot marker counts as machine authorship, a standalone word "Bot" in a display name counts as automation (the word boundary is what keeps Talbot and Abbot human), and GitHub's own action@github.com identity counts as automation in both its singular and plural forms. Missing one? Tell me.
Where pull-request records are available, Grasp joins PR timing with diff sizes to measure approval latency versus change size, merges with no recorded human review, merges reviewed only by AI bots, and comment-free approvals. Coverage is labeled on the output: full (every PR, with API credentials), sampled (size-stratified under public rate limits), or partial. One warning I attach to all of it: the review record cannot see pairing, pre-coordinated work, or review done outside the platform. These are process signals, not verdicts on individuals.
Living authorship is the right comprehension proxy in 2026 because authorship is still where comprehension mostly comes from. As agent-written code becomes the default, that assumption expires: in a repo where agents write most lines, low human authorship becomes true of healthy teams too, and comprehension migrates into review, steering, and specification. Two distortions grow together as this happens. Authorship share compresses toward zero even for well-run teams, while the AI-attribution minimum undercounts more (squash merges strip trailers), overstating what remains. I am explicit about the consequence: authorship share will eventually retire as the headline number, and engagement share will replace it. The engine collects the successor signals rather than promising them, and the named candidate is already computed on every scan as successor_shadow: living stewardship of the steering and follow-up surface, the share of the specs, rules and AI-follow-up work held by people still present. It rides as a shadow the way the recency curve once did, accumulating history against the day its promotion study is worth running, and it is promoted the only way anything is promoted here, through a published study or not at all. Baselines will move with the industry, since scores mean the most against same-era peers. The formula is versioned and will change. The question it answers, who still understands this system, will not.
This is a diagnostic for organisations: where does comprehension risk sit, which modules deserve attention, what does an acquisition actually contain. It is not a performance metric and must never become one; no number here says anything about the worth, competence, or effort of an individual, and a per-developer target is a misuse of the instrument.
The reason is Goodhart's law: when a measure becomes a target it stops measuring, and this one is public, deterministic arithmetic. Most known plays leave artifacts the scan itself publishes, in the signals table above. One play is named here precisely because nothing detects it: a script making tiny periodic commits to hold an author "active". Detecting mechanical cadence was tried and rejected because it flagged genuine work; if someone is drip-committing to move a score, the score has already been turned into a target, and that is the failure, not the script. The product is shaped against that failure. People are pseudonymised in every output by default, real names appear only under an explicit flag, and each scan records on its face whether it was named, so a report that targets a person had to be built to do so. In a small team an alias is thin cover, since module shapes and timestamps identify people to anyone who works beside them; the alias raises the cost of casual targeting, and the contract, not the alias, carries the prohibition. Some features will never exist here because their only honest use is surveillance: per-person productivity trend lines, hour-of-day commit analysis, individual decline alerts, exportable per-engineer scorecards. All four are trivial to build from data this engine already holds, which is why refusing them in print matters; if they ever appear, this paragraph is the receipt. The documented default for any engagement is that the scanned team sees its own report: a metric read in the open is a diagnostic, the same metric held in a drawer is a file being built. Diligence is the exception, because a seller who knows a scan is coming has a motive: every scan ships a quarterly series of commits, trailer share, and new-author influx (quarterly_series), so an attribution pattern that breaks from its own history before a transaction is a question a human gets to ask. The series states deltas; it never issues verdicts.
This instrument reads git first and the hosting platform second. The forge engagement layer reads the pull-request record git loses, reviewers, approvals, participants and the platform's own verification, so review-only contributors appear at all, with presence evidence authorship cannot provide; it runs sampled without credentials and fully with a token, and feeds the diligence manipulation screen. On request the same layer reads the issue record, who opens, comments, and is assigned, on the same terms as reviews: presence evidence only, never a per-person activity feed, no thread contents read or stored. A comment costs seconds where a commit costs work, so triage presence is the cheapest evidence here to manufacture and is emitted as its own labeled source with its own coverage statement, for the reader to weigh. A provided roster becomes the identity and presence authority, because the roster knows about leave and departure and git does not; fleet scans join presence across the whole estate, so an engineer who lives in the shared library stops reading as inactive risk in every service that depends on it. The oldest admission on this page, that no threshold is calibrated against outcomes, stands open as an invitation: calibration against real incident and onboarding data awaits an organisation willing to share it, and that study will be published like the others. Every formula decision here is made in public rather than shipped silently, and each carries its measurements. The recency curve was promoted to the headline at the v0.21 re-baselining, the versioned break the deferral named, with /decay-study carrying both the original study and the promotion record. Fractional applier credit was measured twice and deferred twice: crediting only present appliers moves Django up thirteen points, while crediting appliers as fractional authors moves it down 2.7 at even a tenth of a person, because the appliers of any three-year window are substantially gone; the two designs misprice applying in opposite directions, which is the strongest form a deferral takes. A coupling term in the risk formula was likewise measured twice and deferred twice: raw co-change breadth promoted the test harnesses that co-change with everything (mean rank correlation 0.99, and the movers were evals and e2e directories), and a source-gated variant demoted the two highest-risk root modules in the benchmark on reweighting alone. Coupling stays printed beside risk rather than priced inside it, until an evidence source exists that can tell a load-bearing core from a brushed directory without reading imports. That condition now has a named candidate: a dependency index (SCIP or LSIF) generated on the client's side and supplied the way a roster is, consumed as hashed graph edges only, no symbol text retained; the client's indexer reads source, this instrument never does, and if that overlay arrives, coupling reopens through the same delta gate. Two smaller measurements belong in this record too. Repricing Helm chart templates to full weight was measured and found untestable: the benchmark carries no chart surface at all, so the carve-out waits for an estate that does rather than shipping unmeasured. And the module depth cap is configurable (GRASP_MODULE_DEPTH, default three) because deep enterprise monorepos nest real service boundaries further down; measured across this benchmark, no tree has a dominant chain past depth three, so the default changes nothing here, and the org score cannot move with the setting by construction. Beyond the forge sit external ticket systems, incident tooling and org charts, and those remain not claimed: an engineer who triages incidents in a tracker the forge cannot see holds real comprehension this instrument cannot see either, and says so rather than pretending otherwise; the refusal list bounds every extension the forge layer will ever get.
The AI minimum is far lower than the truth. Most machine authorship is never marked at all: work with an agent in your editor, commit normally, and git records a human and nothing else. Even the most agent-heavy repositories here carry an attribution-capable signal on a quarter to a half of their commits, curl on under one in twenty-five, and much of that spread is squash conventions injecting trailers rather than agent use. No agent-name list stays complete, though an unrecognized bot account still counts as machine authorship rather than vanishing. Coverage is reported beside the minimum; read any AI figure as attributable, not actual, and expect the gap to widen.
A single commit can carry a small repository. Git cannot distinguish a thousand lines someone reasoned about from a thousand a formatter rewrote, and every heuristic I tested flagged genuine refactoring as machine noise. So no guessing: the largest-commit share is reported, flagged above a fifth, and cross-checked by the touch-weighted view.
Wide commits are priced as touches. A formatter run, a license pass, or the deletion of a deprecated tree touches most files while demanding little comprehension per line; one trailing-newline commit across 146 files moved a low-activity repository three points in testing. A commit touching more than a third of the window's files counts one changed line per file, so its author registers work without inheriting a subsystem for reformatting it. The breadth figure is reported and flagged either way.
The record itself is trusted. Git accepts whatever author name and date a committer supplies, so a scan measures history as recorded, not as sworn: a determined insider can manufacture apparent activity, and a pasted co-author trailer is the cheapest way to manufacture credit and presence at once. The signed-commit share bounds this without claiming verification: where signing is enforced a forged author cannot carry the real author's signature, and where it is not the scan says the record is unsigned. Acceptable for public benchmarking, where forging in plain sight carries its own cost; a stated assumption for diligence, where the cross-check is the platform's record of who pushed what and when.
Merge policy limits comparability, less than assumed. Squashing collapses a pull request into one commit by one author, but platform defaults preserve co-author trailers in the squashed message: 27% to 39% of squash commits on this benchmark's heaviest squashers carry them with no developer discipline involved, and credit splitting recovered 959 contributors on the 97%-squashed LangChain. What squashing reliably erases is review-only participation, which the forge engagement layer reads back from the platform. Every report states the squash rate and flags rates above half; read a heavily squashed repository's concentration as an upper bound and its AI attribution as a lower one.
Community-curated repositories get their own banner. Link lists and docs collections are built by thousands of people contributing one line each, once, so a low authorship score there describes the contribution model rather than decay. Above 70% content, the report says so and points to the active-maintainer count; the score is never adjusted to compensate, the interpretation is disclosed instead.
Committers count for presence, not for credit. Patch workflows route work through a maintainer who applies what they did not author: a quarter of curl's recent commits, 1,868 by its lead maintainer, whose applied throughput every scan reports. Applying counts as presence, dated by commit time, so an engineer who rebase-merges an old branch is present today while cherry-picked old code never reads as newly known; that timestamp choice was measured on the benchmark and moves no score. Line credit was measured twice, in opposite directions, and deferred; the boundary section below carries those numbers.
Standing caveats. Living authorship is a proxy: people understand code they didn't write, and write code they don't understand. Identity resolution from git emails is heuristic. An unrecorded navigator in pair or mob programming is invisible: no repository metadata can recover a person who never appears in it, and only trailers, the forge record, or a roster can. Open-source "departed" includes drive-by contributors, overstating attrition against a company roster; the drive_by_share signal reports how much of the inactive weight is that long tail, so it is subtracted by the reader, never by the score. Blobless free scans truncate contributor history to the window. Merge commits are excluded from authorship counts, which across this benchmark leaves out at most about three percent of a repository's commits and usually none. Every score is a potential indicator telling you where to look, not a diagnosis. Believe a number is wrong? Tell me. I correct verified errors, and this page and every report are versioned.