Skip to content

Releases: Bubblegunn/workproof

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 10:29

0.5.0 (2026-09-21)

Bot detection was partly guesswork, and the guesswork ran on the expensive side. Two definitions
existed: exclusions.ts, which decides what leaves every denominator, and identity.ts, which
suggests that two addresses might be the same person. They could drift apart, and one of them matched
on name prefixes, so any address beginning with dependabot, renovate, github-actions or
greenkeeper was a bot. dependabot.fan@gmail.com and renovate-reviewer@corp.com were bots too.

There is now one isBotIdentity, used by both, matching on the commit email rather than the author
name, and every pattern in it points at a real address: the GitHub [bot]@users.noreply.github.com
form, bot@renovateapp.com, gitlab-bot@gitlab.com and teabot@gitea.io. The four prefixes are
gone.

Measured before merging: both definitions over every commit in six repositories, 572 commits, 30
excluded by the old exclusions.ts rule and 30 by the new one, zero disagreements in either file. No
figure in any existing report moves. The version is a minor rather than a patch because what leaves a
denominator is user-visible behaviour, and for someone whose repository holds a bot this library did
not recognise before, or recognised only by name, the numbers can change.

One narrowing comes with it, and the README says so rather than leaving it to be found: exclusions.ts
no longer excludes on the author name, so a bot whose name ends in [bot] but whose address is
outside the recognised forms is counted as a contributor. That deflates the subject's share rather
than inflating it, which is the safe direction for an ownership claim.

The release gate and the release script disagreed about whether this file may carry an entry for a
version that is not out yet.
release.mjs required one; release-gate.mjs forbade one on every
push. Both could not hold, so a contributor's credit could only be written at release time and never
on the day their work merged. The gate now recognises two states and says which one it found, and
this entry exists on the day it describes.

From outside the project, all three by @shivam-070208: the shared
bot identity, the evidence-backed forge patterns and the tests for both directions
(#26, closes
#10); the release-gate fix above, with a
shared version comparator so the two scripts cannot drift again
(#29, closes
#28); and the tests for the pack allowlist,
which had been enforced with nothing that would notice if it stopped enforcing. Reading an empty
packed-file list left all 63 tests passing, and now it fails two of them
(#33, closes
#30); and the fix for npm pack --dry-run --json, which returns an array on npm 11 and an object keyed by package name on npm 12, so the gate
died on npm 12 where it used to read the packed file list
(#35, closes
#31). Each of the four arrived with the mutation
that fails its own test already run and pasted.

What's Changed

Everything else

  • feat(bot): unify bot identity detection by @shivam-070208 in #26
  • fix: the CHANGELOG cannot carry an unreleased entry, and the gate says so by @Bubblegunn in #27
  • fix(release): align release gate with changelog state by @shivam-070208 in #29
  • docs: restore the 0.5.0 entry the gate used to forbid by @Bubblegunn in #32
  • test(release): cover npm pack allowlist enforcement by @shivam-070208 in #33
  • docs: credit #33 on the day it merged, and normalise its spacing by @Bubblegunn in #34
  • fix(release): support npm pack output shapes by @shivam-070208 in #35
  • docs: credit #35 on the day it merged by @Bubblegunn in #36
  • fix(release): keep npm's stderr out of the version comparison by @Bubblegunn in #38
  • fix(release): give the registry longer than two minutes to serve a new version by @Bubblegunn in #39

New Contributors

Full Changelog: v0.4.2...v0.5.0

v0.4.2

Choose a tag to compare

@github-actions github-actions released this 12 Sep 21:14

0.4.2 (2026-09-12)

The report in the README did not reproduce, on a tool whose whole claim is that it does. The front
page shows a real report on langchain-ai/openwiki at 1e6d54c and invites the reader to recompute
it. Running the stated command at the stated commit with the current build gave different numbers, and
a different fingerprint:

figure README said actually computes
share of commits 71 of 233, 30.5% 71 of 235, 30.2%
test file changes 393 of 656, 59.9% 393 of 657, 59.8%
excluded lines 2.2% 2.6%
absence factor 1 of 71 authors 1 of 73

The block was generated on 5 September, before 0.4.0 and 0.4.1 changed what the report covers, and
nothing in CI compared it against a fresh run, so it drifted in silence. For this tool that is the
worst inconsistency available: a reader who accepted the invitation would have found the published
figures disagreeing with the tool that produced them, with no way to tell a stale document from a
dishonest one. Regenerated at the same commit in both languages, with the prose that cites the shares
updated. The fingerprint still differs per run by design, because its key is random unless
--fingerprint-key is passed.

Nothing in src/, bin/ or the package manifest changed since 0.4.1, so a report you generate is
byte for byte what 0.4.1 generated and one written before this release still verifies.

CITATION.cff and both READMEs now cite the archived release by DOI, so a figure quoted in a document
can name the version that produced it.

Full Changelog: v0.4.1...v0.4.2

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 05 Sep 21:10

0.4.1 (2026-09-05)

A person who signs Weiß on one machine and WEISS on another was two people, and half their commits fell outside every figure. JavaScript's toLowerCase is Unicode's simple case fold and leaves ß alone; the full fold maps it to ss (CaseFolding.txt, status F), which the identity fold now does. The same fold normalises to NFKC, so a name typed in fullwidth Latin letters, which is what a Japanese or Korean keyboard produces without switching modes, matches the ASCII spelling. This changes which commits are counted, not how a name is printed: the report still prints the name git holds.

An Arabic or Hebrew name no longer turns the plain-language paragraph right to left. Names and repository names are wrapped in the U+2068 and U+2069 isolates UAX #9 defines, in the Markdown report only. The JSON keeps the exact string git holds, so the hash is unchanged and a report written before this release still verifies.

The README, in both languages, adds a section on names that are not ASCII and states three choices that are deliberate rather than overlooked: en-US number formatting for people, which cannot reach the hash because that is taken over canonical JSON; ISO weeks for cadence, though CLDR gives Monday to only seven of the twenty most populous countries; and mapping identities by address, because git matches a .mailmap name ignoring case for ASCII letters only.

Full Changelog: v0.4.0...v0.4.1

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 20:11

0.4.0 (2026-09-05)

The hash was not reproducible on another machine, and that is the one thing this tool exists to promise. It covered two values that belong to the computer rather than to the repository: the local directory's name, and the git version string. Both were reproduced with real runs. The same repository copied to a second folder and measured with the same command produced two different hashes; so did two reports identical except for git --version. Anyone who cloned a repository under a different name, or simply had a different git, could not verify a report they had been given.

Both fields stay in the report, because they explain why two runs might legitimately differ, and both are now outside the hash. What the hash covers is what a stranger can reproduce: the head, the fingerprint, the identity, the exclusion counts, every figure, and the flags the caller chose, including the blame flags, the ignore-revs file and the seed. A test pins the exclusions in both directions, and CI recomputes the same fixture's hash on Linux, macOS and Windows so this cannot decay.

The README now places these figures in the literature that already exists around them: Kalliamvakou, Gousios and Blincoe on the traps in mining a forge, Bird and colleagues on ownership as a defect predictor rather than a measure of credit, and Spinellis and Gurov on how long lines live. None of that work measures a person's share of surviving code, and a first measurement of how far it diverges from commit share now sits in the surviving-lines repository.

Every existing report's hash changes. A report made before this release will not verify against one made after, the same way the local-weeks change did in 0.3.0. Regenerate any report you are relying on.

Full Changelog: v0.3.1...v0.4.0

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 05 Sep 19:24

0.3.1 (2026-09-05)

A report now says when someone else in the repository looks like the same person. Anyone who has committed from a laptop and a work machine is two authors to git, so the report described half their work and said nothing about the other half, which is a silently wrong number in a tool whose whole claim is that its numbers can be checked. Three signals raise it: the same name on another address, a GitHub noreply login that matches the subject's address or name, and the same address name on another domain. Role addresses such as dev@ and bot addresses are ignored. The disclosure appears in the Markdown above the figures, as identity.possiblySplit in the JSON, and as a warning on stderr during the run; the addresses themselves stay out of the document unless --emails is on, as everywhere else. It is a disclosure and not a correction: only the author knows whether two addresses are one person. No figure, hash or verification changed, and a report made before this release still verifies.

Full Changelog: v0.3.0...v0.3.1

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 17:11

0.3.0 (2026-09-05)

A repository whose paths are stored decomposed now produces a report instead of failing. Git precomposes command-line arguments on macOS, so a path read out of the tree and handed back as a pathspec matched nothing, ending the run with fatal: no such path café.ts in HEAD and no report. core.precomposeunicode=false joins the pinned settings, so the path sent is the path git stored. A checkout authored on Linux carrying Korean, French, Turkish, Vietnamese, Portuguese or Spanish filenames was affected; figures for ASCII repositories are byte-identical.

Weeks and tenure days are the author's, not UTC's. Each commit records its own offset in %aI, and that was discarded, so a commit made on Monday morning in Auckland landed in the previous UTC week: four commits inside two local weeks were reported as three active weeks with a three-week streak. Calendar figures now read the author's own clock, while anything ordering commits in time still uses the instant. This changes published figures and therefore report hashes for any repository whose commits cross a UTC day boundary; on this project's portfolio repository the tenure start moved from 29 July to 30 July, the day the work was actually done. A report produced before this release will not verify against one produced after it.

An author matches across Unicode normalization and Turkish casing. A name typed precomposed did not match the same name stored decomposed, and the error then listed an author that looked identical to what had just been typed. "İ".toLowerCase() is "i" followed by a combining dot and "I".toLowerCase() is "i" rather than "ı", so --author "İSMAİL YILMAZ" matched nothing, including itself. Names and addresses are now folded to NFC with the four dotted and dotless i forms collapsed, and the error says the comparison was made on the folded form. Two names differing only in dotted and dotless i now match each other, which is the deliberate cost.

Full Changelog: v0.2.0...v0.3.0

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 16:12

0.2.0 (2026-09-05)

Every repository section of the Markdown report now opens with a plain-language paragraph for the person who will read the report and does not write software. It is assembled from the figures by a fixed rule with no model call, so it is deterministic, states no opinion about quality, keeps commit share and surviving lines apart, and ends with the command that recomputes it. It is derived from the hashed figures rather than added to them, so the hash is unchanged.

verify now prints what it proves and what it does not: that the figures were recomputed from the repository and the document was not edited, and not that the repository is honest history or that the work was good. When figures differ it says that a difference is not proof of dishonesty, because every figure except tenure is computed at HEAD and HEAD moves.

npm run bench:adversarial runs the gaming instead of describing it: commit padding, whitespace churn, an unmarked generated file, the same file marked linguist-generated, and a co-author trailer against its own control, each applied to a copy of the fixture with every gaming commit dated inside the existing tenure so the window does not move. Sixty padding commits take cadence from 1.0 to 21.0 a week and add one surviving line while the median commit halves; re-indenting every line moves nothing because blame runs with -w; 2,000 machine-written lines under a name the lists miss take surviving lines from 51.6% to 99.3%, and marking the file removes them again; the trailer moves only the co-authored count. The README carries the table and test/adversarial.test.ts pins the last two findings.

Verification that means something: workproof check validates the report against a published JSON Schema and recomputes the hash offline over RFC 8785 canonical JSON; verify runs it first and refuses to recompute figures for an edited document, then compares the repository fingerprint (a mismatch stops before figures) and HEAD. attest writes an in-toto statement whose subject is the report hash, signs it with an SSH key (--local) into a DSSE envelope, and the Action signs it keylessly with Sigstore when attest: "true".

Honest denominators: bot commits leave every figure; lock files, snapshots, minified assets, generated outputs and vendored directories (built-in lists plus linguist-generated and linguist-vendored in .gitattributes) leave every count and the blame sample; the report prints how much was excluded. --no-exclusions, --exclude, --seed, --copies and --ignore-revs-file are new, and .git-blame-ignore-revs at the root is honoured automatically. Every git call runs with pinned diff settings and the report records the git version, blame flags, ignore-revs file and seed.

Seven new figures, each with its bias in its limits: files authored (degree of authorship), major-contributor components, commit size, co-authored commits, absence factor, AI-assisted commits and survival by cohort. Two labels stop overclaiming: "documents authored" is now "documents created" (oldest commit is the author's) and the test figure is titled test-file changes. The badge reads surviving lines and tenure days.

Privacy: the fingerprint is keyed (HMAC-SHA256 under a per-report key printed once and stored nowhere; --fingerprint-key reuses one) and GitHub noreply addresses are never written. Reports carry schemaVersion: 2; 0.1.x reports fail check by design.

The Action writes the Markdown report to the job summary and uploads the report files as the workproof-report artifact, so a run leaves something readable behind even without a pull request to comment on. npm run release now also moves the v0 tag that uses: Bubblegunn/workproof@v0 resolves to, and the release workflow starts on full version tags only, so the moving tag cannot start a second publish.

Full Changelog: v0.1.3...v0.2.0

v0.1.3

Choose a tag to compare

@github-actions github-actions released this 05 Sep 08:19

0.1.3 (2026-09-05)

--badge writes a shields.io endpoint document next to the report; a composite GitHub Action (Bubblegunn/workproof@main) runs the report on a pull request and posts one sticky comment with the six figures.

From outside the project, both by @edwardsong08: the language map now covers Elixir, Scala, Haskell, Lua, R, Objective-C and Zig (#3), and --format markdown|json|both lets a pipeline ask for one file instead of two, with --json kept as an alias (#9, closes #1).

What's Changed

Everything else

New Contributors

Full Changelog: v0.1.2...v0.1.3