Skip to content
history-metrics.jsonl

history-metrics.jsonl

Change history measured at the commit a model describes, written by codegraph-metrics --history: one record per file, from one walk over the whole commit graph, and one per measured entity, from one git blame per file. It is a side-car beside the model, <model>.history-metrics.jsonl, and never a key inside it: a model describes the code at one commit, and its bytes must not change because time passed. city and serve join the two in memory.

One JSON object per line, discriminated on t. Section order is contractual and enforced by the reader:

header → f* → e* → eof

Files are ordered by path and entities by natural key, both strictly ascending: the order is the uniqueness check, and what makes two runs over one repository byte-identical. An entity is named by its natural key {lang, module, symbol, disambiguator?}, never by a rendered id; the CLI renders it with core’s own renderId to join (ids are rendered, never parsed). Field names are those of the Zod record schemas in packages/scm/src/history-metrics.ts.

header

First record of a history-metrics side-car.

FieldTypeMeaning
t"header"
kind"codegraph.history-metrics/1"self-identification: a model file must be refusable as “not a side-car”
generatedBy.name, .version, .bigCodeAnalysisstringscodegraph-metrics, its version, and the big-code-analysis version whose definitions the numbers follow
langstringthe model’s language; an entity of another language is refused
rootstring, optionalthe model’s root, as its header spells it
commithex stringthe commit measured at: the model’s repository.commit, else HEAD at the time of the run, or --ref
asOfintegerthat commit’s committer time, Unix seconds — every window’s “now”; never the wall clock
windowDays.long, .recentintegersthe two windows every *_long and *_recent measure counts back over: 365 and 90

f — one file

One file of the model, measured from the commit walk.

FieldTypeMeaning
t"f"
pathnon-empty stringthe model’s file path, relative to its root, as its anchors spell it
vcsRecord<string, number>the file-level measures below; absence means “not measured”, never zero

e — one entity

One measured entity, from the blame of its file.

FieldTypeMeaning
t"e"
key.lang, .module, .symbol, .disambiguator?stringsthe entity’s natural key, restated
vcsRecord<string, number>the entity-level measures below, over the lines that survive at the commit

A file whose bytes at the commit differ from what the extractor read keeps its f record and has no e records: its anchors and the blamed lines would describe different text. The run says how many.

eof

Trailer: the file is complete, and this is what it carries.

{"t":"eof","counts":{"files":N,"entities":N}} — both integers ≥ 0, and both must match what was read.

Measures

Windows count back from asOf. Blame credits a line to the commit that last touched it, so an entity’s lines_* are surviving lines — a different quantity from a file’s churn_* (added plus deleted), hence a different name; the two never share a key. risk_score is ordinal: rank by it, never read it.

MeasureUnitLevelMeaning
age_daysdaysfile, entitydays since the first commit in the long window (capped at it)
authors_longauthorsfile, entitydistinct authors in the long window (mailmap-canonical, bots excluded)
authors_recentauthorsfile, entitydistinct authors in the recent window
bug_fix_commitscommitsfile, entitylong-window commits whose message reads as a bug fix
burstratiofile, entitycommits_recent / commits_long: how much of the activity is recent
change_entropy_longbitsfilechange entropy (Hassan, 2009): how scattered the commits it took part in were
change_entropy_recentbitsfilechange entropy over the recent window
churn_longlinesfilelines added plus deleted in the long window
churn_recentlinesfilelines added plus deleted in the recent window
cochange_entropy_longbitsfileco-change entropy: how widely its changes ripple to other files
cochange_entropy_recentbitsfileco-change entropy over the recent window
commits_longcommitsfile, entitydistinct commits in the long window (an entity: among its surviving lines)
commits_recentcommitsfile, entitydistinct commits in the recent window
last_modified_daysdaysfile, entitydays since the most recent commit in the long window
lines_longlinesentityits lines surviving at the commit, last changed in the long window (blame)
lines_recentlinesentityits surviving lines last changed in the recent window
ownership_top_sharesharefile, entitythe top author’s share of the long window’s edits, 0..1
revert_commitscommitsfile, entitylong-window commits that revert or roll back
risk_scorerankfile, entitybig-code-analysis’s weighted risk score — ordinal: rank by it
security_fix_commitscommitsfile, entitylong-window commits whose message reads as a security fix

The list is HISTORY_MEASURES in @codegraph/scm, pinned equal to the Rust lists by a test. Bug-fix, security and revert counts are labelled heuristics over commit messages, as the fix and revert flags of history.jsonl are. Each measure’s paper, how the walk and the blame differ, the classifier’s keywords and the risk formula: Metrics catalogue.

Excerpt

The committed fixture, fixtures/history-metrics/expected/: a repository the Rust test builds commit by commit with pinned authors and dates, so every number is hand-countable. One record of each type:

{"t":"header","kind":"codegraph.history-metrics/1","generatedBy":{"name":"codegraph-metrics","version":"0.2.0","bigCodeAnalysis":"2.2.0"},"lang":"java","root":"src","commit":"dfa563f5ce9e4a8f1d8e898eb7ae89d5168c8229","asOf":1779357600,"windowDays":{"long":365,"recent":90}}
{"t":"f","path":"com/acme/Shop.java","vcs":{"age_days":131,"authors_long":3,"authors_recent":2,"bug_fix_commits":1,"burst":0.6667,"change_entropy_long":0,"change_entropy_recent":0,"churn_long":15,"churn_recent":4,"cochange_entropy_long":0,"cochange_entropy_recent":0,"commits_long":3,"commits_recent":2,"last_modified_days":1,"ownership_top_share":0.3333,"revert_commits":0,"risk_score":1.5817,"security_fix_commits":0}}
{"t":"e","key":{"lang":"java","module":"com.acme","symbol":"Shop.add(int)"},"vcs":{"age_days":131,"authors_long":2,"authors_recent":1,"bug_fix_commits":1,"burst":0.5,"commits_long":2,"commits_recent":1,"last_modified_days":20,"lines_long":3,"lines_recent":1,"ownership_top_share":0.5,"revert_commits":0,"risk_score":0.9511,"security_fix_commits":0}}
{"t":"eof","counts":{"files":1,"entities":3}}

Reading it

city and serve read <model>.history-metrics.jsonl beside each model — or the file --history-metrics FILE names — and offer two metric forms on every channel and on --carry:

FormReads
history:<measure>the building’s own type: its blame
history-file:<measure>the file the type is written in

Asking for one with no side-car, or at a level the measure lacks, is a usage error that says what to run or type instead:

codegraph: churn_long is measured per file only: use history-file:churn_long

The decoder is strict: a missing header, a record after the trailer, a file after the entity records, disorder or repetition inside a section, a non-number, trailer counts that do not match, or an entity of another language than the header’s are refused with the line number; encode(decode(x)) is x for every file the pass wrote. Why history stays a side-car: Time as structure.

Last updated on