workproof 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,16 +8,15 @@
8
8
  <img src="https://img.shields.io/npm/v/workproof?style=flat-square&color=111111&label=npm" alt="npm">
9
9
  <img src="https://img.shields.io/npm/dm/workproof?style=flat-square&color=111111" alt="npm downloads">
10
10
  <img src="https://img.shields.io/github/actions/workflow/status/Bubblegunn/workproof/ci.yml?style=flat-square&color=111111&label=ci" alt="ci">
11
- <img src="https://img.shields.io/bundlephobia/minzip/workproof?style=flat-square&color=111111" alt="minzipped size">
12
11
  <img src="https://img.shields.io/github/stars/Bubblegunn/workproof?style=flat-square&color=111111" alt="stars">
13
12
  <img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT">
14
13
  </p>
15
14
 
16
15
  workproof turns a git repository into a verifiable engineering report for one author,
17
- without showing any code. You run it in the repository you cannot share. The reader
18
- gets six figures, the exact command behind each one, what each one cannot show, and a
19
- hash. Anyone with the same repository can rerun `verify` and see whether the numbers
20
- reproduce.
16
+ without showing any code. You run it in the repository you cannot share. The reader gets
17
+ thirteen figures, the exact command behind each one, what each one cannot show, and a
18
+ hash that anyone can recompute offline. It is not a productivity metric: it measures what
19
+ survived and what was touched, and it prints its own limits under every number.
21
20
 
22
21
  ## 30 seconds
23
22
 
@@ -26,119 +25,279 @@ cd your-private-repo
26
25
  npx workproof
27
26
  ```
28
27
 
29
- That writes `workproof-report.md` (paste it into a résumé, a portfolio, a visa application)
30
- and `workproof-report.json` (for tools, and for verification). To check someone's report:
28
+ That writes `workproof-report.md` (paste it into a résumé, a portfolio, a visa
29
+ application) and `workproof-report.json` (for tools, and for verification). This is the
30
+ real transcript of the run behind the example below, on a clone of
31
+ [langchain-ai/openwiki](https://github.com/langchain-ai/openwiki) at `1e6d54c`:
31
32
 
32
33
  ```
33
- npx workproof verify workproof-report.json
34
+ $ npx workproof --author "Colin Francis" --sample 5
35
+ fingerprint key 9dc900a6227a1faaaa17d774565afbf3 (keep it to compare reports or to verify the fingerprint; it is not stored)
36
+ openwiki: reading history...
37
+ openwiki: 369 commits read
38
+ openwiki: blaming files (1 in 5 sample)...
39
+ openwiki: blamed 123 of 547 files
40
+ wrote workproof-report.md and workproof-report.json in 2.7s
41
+
42
+ $ npx workproof check workproof-report.json
43
+ schema ok
44
+ hash ok 63d4fd1373b06b090086a653d52dfed234edcf35645bce3a34137a0447049355
45
+
46
+ $ npx workproof verify workproof-report.json --fingerprint-key 9dc900a6227a1faaaa17d774565afbf3
47
+ schema ok
48
+ hash ok 63d4fd1373b06b090086a653d52dfed234edcf35645bce3a34137a0447049355
49
+ openwiki: fingerprint ok
50
+ all figures reproduce
34
51
  ```
35
52
 
53
+ `check` needs the JSON and nothing else. `verify` needs the repository.
54
+
36
55
  ## What a report looks like
37
56
 
38
- This is real output for one maintainer of [langchain-ai/openwiki](https://github.com/langchain-ai/openwiki)
39
- at `1e6d54c`, run on 5 September 2026 with `--author "Colin Francis" --sample 5`, paths and
40
- emails hidden (the defaults):
57
+ Real output for one maintainer of openwiki at `1e6d54c`, run on 5 September 2026 with
58
+ `--author "Colin Francis" --sample 5`, paths and emails hidden (the defaults). Under every
59
+ figure the full report also prints `How:` with the git command and `What this cannot show:`;
60
+ they are cut here for length.
41
61
 
42
62
  ```
43
63
  ## openwiki
44
64
 
45
- HEAD 1e6d54cdfeec · fingerprint 82aa401bbba056f1 · identities: Colin Francis
65
+ HEAD 1e6d54cdfeec · fingerprint a1b19a27aab4a577 · identities: Colin Francis
66
+
67
+ excluded 64 bot commits and 1 generated, vendored or lock file (2.2% of lines added)
46
68
 
47
69
  ### Tenure window
48
70
  2026-07-06 to 2026-09-03 (60 days)
49
71
 
50
72
  ### Share of commits in tenure
51
- 71 of 295 non-merge commits, 24.1%
73
+ 71 of 233 non-merge commits, 30.5%
52
74
 
53
75
  ### Cadence
54
76
  9 active weeks of 9, 7.9 commits per active week, longest streak 9 weeks
55
77
  1 of 21 release tags in tenure
56
78
 
57
79
  ### Footprint
58
- 694 files touched
80
+ 693 files touched
59
81
  16 directories with a commit share at or above the threshold (paths hidden; run with --paths)
60
- languages by lines added: TypeScript 80.4%, JSON 9.6%, Markdown 7.0%, JavaScript 1.6%, YAML 1.4%
82
+ languages by lines added: TypeScript 81.4%, JSON 9.7%, Markdown 7.1%, JavaScript 1.6%, YAML 0.2%
83
+
84
+ ### Test-file changes and documents created
85
+ 393 of 656 test-file changes, 59.9%
86
+ 101 documents created
87
+
88
+ ### Files authored
89
+ 455 of 555 files alive at HEAD, 82.0% (degree of authorship)
90
+
91
+ ### Major-contributor components
92
+ major contributor in 59 of 65 directories (at least 5% of commits)
93
+
94
+ ### Commit size
95
+ median 166 lines, 90th percentile 9,234, 6 commits over 10,000 lines
96
+
97
+ ### Co-authored commits
98
+ 119 commits by others naming the author in a Co-authored-by trailer
99
+
100
+ ### Absence factor
101
+ 4 authors cover half the commits; the author ranks 1 of 71 by commit count
61
102
 
62
- ### Tests and documentation
63
- 393 of 657 test-file changes, 59.8%
64
- 116 documents authored
103
+ ### AI-assisted commits
104
+ 1 commit declares an AI tool in a trailer, 1.4% of the author's commits
65
105
 
66
106
  ### Surviving lines at HEAD
67
- 23,317 of 33,038 surviving lines, 70.6% (files 123/548, sample 1 in 5)
107
+ 23,317 of 33,038 surviving lines, 70.6% (files 123/547, sample 1 in 5)
108
+
109
+ ### Survival by cohort
110
+ 2026: 23,317 lines
68
111
  ```
69
112
 
70
- Under every figure the report prints two more lines: `How:` with the git command that
71
- produced it, and `What this cannot show:`. The last section is `Integrity`: the report
72
- hash and the repository fingerprint.
113
+ Read the first line and the two shares together. Sixty-four bot commits left the
114
+ denominator before anything was counted, which moved this person's commit share from the
115
+ 24.1% an older version reported to 30.5%. They wrote 30.5% of the human commits in their
116
+ window and 70.6% of the lines that are still alive. A commit count alone would have called
117
+ them one contributor among seventy-one. That gap, in either direction, is usually the most
118
+ honest thing a report can say about someone's work.
119
+
120
+ ## When someone reformatted the code
73
121
 
74
- Read the two shares together. This person wrote 24.1% of the commits in their window and
75
- 70.6% of the lines that are still alive. A commit count would have called them a minor
76
- contributor. That gap, in either direction, is usually the most honest thing a report can
77
- say about someone's work.
122
+ A repository-wide formatter run rewrites lines it did not write. Plain `git blame` then
123
+ credits every one of them to whoever ran the formatter. workproof reads
124
+ `.git-blame-ignore-revs` at the root by default, so those commits are skipped and the
125
+ lines keep their real author; `-w` handles whitespace-only changes on its own, so the
126
+ file matters when a formatter changed quotes, line breaks or trailing commas.
127
+
128
+ [guidance-ai/guidance](https://github.com/guidance-ai/guidance) at `21b1d90` (21 May 2026)
129
+ lists its black and ruff runs in that file. Two runs per author on 5 September 2026 with
130
+ `--sample 1`; the second passes an empty `--ignore-revs-file`, which is the only way to
131
+ turn the default off:
132
+
133
+ | author | honouring `.git-blame-ignore-revs` (default) | ignoring it |
134
+ |---|---|---|
135
+ | Harsha Nori, who ran black over the tree in April 2024 | 2,185 of 49,267 surviving lines, 4.4% | 2,476 of 49,267, 5.0% |
136
+ | Scott Lundberg, who wrote most of what black reformatted | 12,368 of 49,267, 25.1% | 12,152 of 49,267, 24.7% |
137
+
138
+ Without the file, 291 lines of other people's code would have counted for the person who
139
+ ran the formatter, 216 of them taken from one author. The report prints the file it used
140
+ in every `How:` line and under `environment.ignoreRevs` in the JSON, so two reports are
141
+ only comparable when that line matches:
142
+
143
+ ```
144
+ How: `git blame --line-porcelain -w -M --ignore-revs-file .git-blame-ignore-revs HEAD -- <file> over a deterministic 1-in-1 file sample (surviving-lines 0.1.1: FNV-1a on path); generated, vendored and lock files excluded`
145
+ ```
146
+
147
+ ## In plain language
148
+
149
+ Each repository section of the Markdown report opens with a paragraph a non-engineer can
150
+ read, assembled from the figures by a fixed rule with no model call, so it is deterministic
151
+ and says nothing the numbers do not:
152
+
153
+ > Ada worked in app from 5 January 2026 to 19 January 2026, a span of 15 days. They made 3
154
+ > of the 3 changes recorded in that period (100%), and 16 of the 31 lines of code still in
155
+ > the project today are theirs (52%). The second number is the one that lasts: it counts the
156
+ > work that survived everything written since. 5 of the 8 files in the project were started
157
+ > by them (63%). They were active in 3 of the 3 weeks in that period. Before any of this was
158
+ > counted, 1 automated change and 2 machine-written or copied files were removed, so none of
159
+ > them inflate the figures.
160
+
161
+ It states no opinion about quality, keeps the two shares apart, and ends with the command
162
+ that recomputes it. `--narrate` is a different thing: that one asks a model, is labelled as
163
+ unverified in the report, and is not part of the hash. So is this paragraph, which is
164
+ derived from the hashed figures rather than added to them.
78
165
 
79
166
  ## What it measures
80
167
 
81
- All six figures come from git and nothing else.
168
+ Every figure comes from git and nothing else. Bot commits and generated, vendored, lock and
169
+ snapshot files are removed before any of them is computed (see Gaming and bias).
82
170
 
83
171
  | figure | what it is | what it cannot show |
84
172
  |---|---|---|
85
173
  | Tenure window | first to last commit by the author, or `--since/--until` | work before the first commit or after the last |
86
- | Share of commits | non-merge commits by the author over all non-merge commits in the window | what survived; a typo and a subsystem count the same |
87
- | Cadence | active weeks, commits per active week, longest streak, release tags in tenure and the author's | a week with one commit and a week with forty both count as active |
88
- | Footprint | files touched, directories at or above a commit-share threshold, languages by lines added | generated and vendored files inflate whoever committed them |
89
- | Tests and docs | share of test-file changes, documents authored | test cases, coverage, or the quality of a document |
90
- | Surviving lines | share of lines alive at HEAD, `git blame -w -M` over a deterministic file sample, via [surviving-lines](https://github.com/Bubblegunn/surviving-lines) | merit; code deleted on purpose counts for nobody |
174
+ | Share of commits | non-merge commits by the author over all human non-merge commits in the window | what survived; a typo and a subsystem count the same |
175
+ | Cadence | active weeks, commits per active week, longest streak, release tags in tenure and the author's | a week with one commit and a week with forty both count as active; weeks are the author's own, read from the offset each commit records |
176
+ | Footprint | files touched, directories at or above a commit-share threshold, languages by lines added | anything the exclusion lists miss still counts |
177
+ | Test-file changes and documents created | share of test-file changes; `.md`, `.mdx`, `.rst` files whose oldest commit is the author's | test cases, coverage, or the quality of a document |
178
+ | Files authored | files alive at HEAD where the author's degree of authorship (Avelino et al.) is at least 3.293 and above 75% of the file's maximum | coefficients fitted on other systems; first authorship outweighs later rewrites |
179
+ | Major-contributor components | directories where the author has at least 5% of the commits (Bird et al.) | commit exposure treats a typo and a subsystem alike |
180
+ | Commit size | median and 90th percentile of added plus deleted lines per commit; commits over 10,000 lines | size is not value; imports and reformats dominate the 90th percentile |
181
+ | Co-authored commits | other people's commits naming the author in a `Co-authored-by` trailer | trailers are written by the merger and can be absent or wrong |
182
+ | Absence factor | the smallest set of authors covering half the commits (CHAOSS), and the author's rank | several unmerged emails count as several people |
183
+ | AI-assisted commits | author commits whose trailers or name declare Claude, Cursor, Copilot, Codex, Gemini, ChatGPT, Aider, Devin or Windsurf | a missing trailer is not evidence of unassisted work |
184
+ | Surviving lines | share of lines alive at HEAD, one `git blame -w -M` pass over a deterministic file sample, honouring `.git-blame-ignore-revs` | merit; code deleted on purpose counts for nobody |
185
+ | Survival by cohort | the author's surviving lines by the year of the commit that last touched them | newer cohorts have had less time to die |
91
186
 
92
187
  ## How verification works
93
188
 
94
- - The JSON carries the repository's HEAD, a **fingerprint** (sha256 of the root commit and
95
- the normalised remote URL, so the repository is identified without being named), the
96
- identity names used, the `surviving-lines` version, every parameter, and a **hash** of
97
- the parameters and figures.
98
- - `workproof verify report.json` recomputes every figure in the repository you point it at
99
- and prints a match table. If HEAD moved since the report, it says so and shows which
100
- figures changed.
101
- - A hiring manager needs two things: the report and read access to the repository (or a
102
- colleague inside the company who will run one command). Nothing leaves the repository.
189
+ Three commands, in increasing order of what the reader needs to have.
190
+
191
+ - `workproof check report.json` needs only the file. It validates the document against
192
+ [schema/report.schema.json](schema/report.schema.json) and recomputes the hash: sha256
193
+ over the [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) canonical JSON of the
194
+ parameters and figures. An edited figure prints `hash mismatch: report says X, content
195
+ hashes to Y` and exits 1. No git, no network.
196
+ - `workproof verify report.json` needs the repository. It runs `check`, compares the
197
+ fingerprint (with `--fingerprint-key`; without it the comparison is skipped and says so),
198
+ compares HEAD, then recomputes every figure and prints what differs. A report from another
199
+ repository stops at the fingerprint. If HEAD moved since the report, it says so and shows
200
+ which figures changed.
201
+ - `workproof attest report.json` writes `report.intoto.json`, an
202
+ [in-toto](https://in-toto.io) v1 Statement whose subject is the report hash and whose
203
+ predicate carries the tool version, parameters, HEAD, keyed fingerprint, git version and
204
+ exclusion counts, and nothing else: no figures, no remote, no paths, no emails.
205
+ `--local ~/.ssh/id_ed25519` signs it with `ssh-keygen -Y sign` in the `workproof`
206
+ namespace and writes the detached signature plus a DSSE envelope. Anyone with your public
207
+ key checks it with:
208
+
209
+ ```
210
+ ssh-keygen -Y verify -f allowed_signers -I you@example.com -n workproof \
211
+ -s workproof-report.intoto.json.sig < workproof-report.intoto.json
212
+ ```
213
+
214
+ where `allowed_signers` is one line, `you@example.com ssh-ed25519 AAAA...`, and the key is
215
+ the one GitHub serves at `https://github.com/<you>.keys`.
216
+
217
+ What `verify` proves: every figure was recomputed from that repository at that moment and
218
+ came out the same, and the document was not edited after it was written. What it does not
219
+ prove: that the repository is honest history, that the figures measure anything worth
220
+ measuring, or that the work was good. A repository whose history was rewritten before the
221
+ report was made reproduces perfectly. The command prints both halves of that after its
222
+ result, so a reader who never opens this README still sees the limit.
223
+
224
+ A figure that differs is not proof of dishonesty either. Every figure except tenure is
225
+ computed at HEAD, and HEAD moves; `verify` prints the HEAD line first for that reason.
226
+
227
+ In the GitHub Action, `attest: "true"` signs the same statement keylessly with
228
+ [Sigstore](https://www.sigstore.dev): cosign is installed from a SHA-pinned action and
229
+ `cosign attest-blob` writes `workproof-report.sigstore.json`. A reader verifies it with:
230
+
231
+ ```
232
+ cosign verify-blob-attestation workproof-report.json \
233
+ --bundle workproof-report.sigstore.json \
234
+ --type https://workproof.dev/attestation/v1 \
235
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com \
236
+ --certificate-identity-regexp '^https://github.com/<owner>/<repo>/'
237
+ ```
238
+
239
+ What that proves: this exact JSON was produced by a workflow run in that repository, at
240
+ that commit, and has not changed since. What it does not prove: that the figures are
241
+ right (run `verify` for that) or that the workflow was honest about which repository it
242
+ checked out. Two warnings before you turn it on. The Fulcio certificate names the
243
+ repository the workflow ran in, and the Rekor transparency log is public and permanent. For
244
+ private code the documented pattern is a small public repository that holds the report and
245
+ runs the attest step, so the private repository's name never reaches the log.
103
246
 
104
247
  ## Privacy
105
248
 
106
- - No code content, ever. The tool reads `git log --numstat` and `git blame`, and emits
107
- counts.
249
+ - No code content, ever. The tool reads `git log --numstat` and `git blame` and emits counts.
108
250
  - No file paths by default. `--paths` adds directory names at the configured `--depth`
109
251
  (default 2), never files.
110
- - No email addresses by default. `--emails` adds them; without it, even the `--author`
111
- you typed is replaced by `(email hidden)` in the stored parameters.
112
- - The optional narrative (`--narrate`) sends the figures, and only the figures, to a
113
- model endpoint you choose (`WORKPROOF_API_URL`, `WORKPROOF_API_KEY`, `WORKPROOF_MODEL`;
114
- OpenAI-compatible or Anthropic). The paragraph is appended under
115
- "Generated narrative (not verified)" and is excluded from the hash.
252
+ - No email addresses by default. `--emails` adds them; without it, even the `--author` you
253
+ typed is replaced by `(email hidden)` in the stored parameters. GitHub noreply addresses
254
+ (`<id>+<login>@users.noreply.github.com`) are never written, with or without the flag,
255
+ because the login is in the local part.
256
+ - Non-subject names are never written. The absence-factor figure carries counts only.
257
+ - The fingerprint is `HMAC-SHA256(key, root commit + remote)` under a 16-byte key generated
258
+ per report. The key is printed once and stored nowhere, so a public repository cannot be
259
+ looked up from its fingerprint. Pass `--fingerprint-key` to reuse a key across reports of
260
+ the same repository.
261
+ - The optional narrative (`--narrate`) sends the figures, and only the figures, to a model
262
+ endpoint you choose (`WORKPROOF_API_URL`, `WORKPROOF_API_KEY`, `WORKPROOF_MODEL`;
263
+ OpenAI-compatible or Anthropic). The paragraph is appended under "Generated narrative
264
+ (not verified)" and is excluded from the hash.
116
265
 
117
266
  ## Options
118
267
 
119
268
  ```
120
269
  workproof [options] [--repo <dir>]...
121
- workproof verify <report.json> [--repo <dir>]...
122
-
123
- --author <email|name> identity to report on (repeatable; default: git config user.email)
124
- --repo <dir> repository to analyse (repeatable; several produce one combined report)
125
- --since / --until override the tenure window
126
- --sample <n> blame every n-th file (default: 1; 7 for very large repositories)
127
- --max-commits <n> read only the newest n commits (escape hatch for enormous histories)
128
- --depth <n> directory depth for ownership (default: 2)
129
- --paths include directory paths
130
- --emails include author emails
131
- --narrate append a model-written paragraph
132
- --badge also write <out>.badge.json, a shields.io endpoint document
133
- --out <basename> output basename (default: workproof-report)
134
- --format <mode> write markdown, json, or both (default: both); json prints to stdout
135
- --json same as --format json
270
+ workproof check <report.json>
271
+ workproof verify <report.json> [--repo <dir>]... [--fingerprint-key <hex>]
272
+ workproof attest <report.json> [--local <ssh-key>]
273
+
274
+ --author <email|name> identity to report on (repeatable; default: git config user.email)
275
+ --repo <dir> repository to analyse (repeatable; several produce one combined report)
276
+ --since / --until override the tenure window
277
+ --sample <n> blame every n-th file (default: 1; 7 for very large repositories)
278
+ --seed <text> salt for the blame file sample
279
+ --exclude <glob> also drop files matching the glob (repeatable)
280
+ --no-exclusions count bot commits and generated, vendored, lock and snapshot files
281
+ --copies pass -C to git blame so copied lines follow their origin
282
+ --ignore-revs-file <f> blame ignore-revs file (default: .git-blame-ignore-revs at the root)
283
+ --fingerprint-key <hex> reuse a fingerprint key so two reports of one repository match
284
+ --max-commits <n> read only the newest n commits (escape hatch for enormous histories)
285
+ --depth <n> directory depth for ownership (default: 2)
286
+ --paths include directory paths
287
+ --emails include author emails
288
+ --narrate append a model-written paragraph
289
+ --badge also write <out>.badge.json, a shields.io endpoint document
290
+ --out <basename> output basename (default: workproof-report)
291
+ --format <mode> write markdown, json, or both (default: both); json prints to stdout
292
+ --json same as --format json
136
293
  ```
137
294
 
138
295
  A `.mailmap` in the repository merges an author's several addresses. Progress lines go to
139
- stderr while history is read and files are blamed, so a long run is visibly alive; on a
140
- history of hundreds of thousands of commits, `--max-commits` bounds the read and the report
141
- records that it did.
296
+ stderr while history is read and files are blamed. Every git call runs with
297
+ `diff.renames=true`, `diff.algorithm=myers`, `diff.indentHeuristic=true` and
298
+ `core.autocrlf=false`, and the report records the git version, the blame flags, the
299
+ ignore-revs file and the seed, so two machines with different defaults agree, and `verify`
300
+ can tell an environment drift from an edit.
142
301
 
143
302
  ## Badge
144
303
 
@@ -146,7 +305,7 @@ records that it did.
146
305
  [shields.io endpoint format](https://shields.io/badges/endpoint-badge):
147
306
 
148
307
  ```json
149
- { "schemaVersion": 1, "label": "workproof", "message": "70.6% surviving lines · 24.1% commits", "color": "1f3fbf" }
308
+ { "schemaVersion": 1, "label": "workproof", "message": "70.6% surviving lines · 60 days", "color": "1f3fbf" }
150
309
  ```
151
310
 
152
311
  Commit it to a public repository (your portfolio, a gist) and point shields at the raw URL:
@@ -160,10 +319,12 @@ reader verifies, the badge is only how they find it.
160
319
 
161
320
  ## GitHub Action
162
321
 
163
- The repository ships a composite action that runs workproof on a checkout and posts one
164
- sticky comment on the pull request with the six figures. `fetch-depth: 0` is required, or
165
- the history the figures come from is missing; `author` is required because a GitHub login
166
- does not map reliably onto commit identities.
322
+ The repository ships a composite action that runs workproof on a checkout, runs `check`,
323
+ writes the in-toto statement, puts the Markdown report in the job summary, uploads the
324
+ report files as one artifact, and posts one sticky comment on the pull request with the
325
+ headline figures. `fetch-depth: 0` is required, or the history the figures come from is
326
+ missing; `author` is required because a GitHub login does not map reliably onto commit
327
+ identities.
167
328
 
168
329
  ```yaml
169
330
  name: workproof
@@ -172,6 +333,7 @@ on:
172
333
  permissions:
173
334
  contents: read
174
335
  pull-requests: write
336
+ id-token: write # only for attest: "true"
175
337
  jobs:
176
338
  report:
177
339
  runs-on: ubuntu-latest
@@ -182,51 +344,108 @@ jobs:
182
344
  - uses: actions/setup-node@v4
183
345
  with:
184
346
  node-version: 22
185
- - uses: Bubblegunn/workproof@main
347
+ - uses: Bubblegunn/workproof@v0
186
348
  with:
187
349
  author: ada@example.com
188
350
  sample: "1"
351
+ attest: "false"
189
352
  ```
190
353
 
191
- The comment is updated in place on later pushes (it carries a marker), and the JSON report
192
- stays in the workspace as `workproof-report.json` for an `upload-artifact` step if you want
193
- the evidence kept with the run. Set `comment: "false"` to only produce the files.
194
-
195
- ## Can it be gamed?
196
-
197
- Partly, and the report is built so the gaming shows.
198
-
199
- - Commit spam moves commit share and cadence, and nothing else. Surviving lines come from
200
- `git blame` at HEAD, so a thousand empty commits add zero surviving lines, and the gap
201
- between the two shares is printed side by side.
202
- - Vendoring a library inflates lines added. The languages figure and the owned-directory
203
- list (with `--paths`) show where those lines landed, and a reviewer sees a directory
204
- named `vendor` or `node_modules` owning most of them.
205
- - Rewriting history to change authorship changes the root commit or HEAD, so the
206
- fingerprint and HEAD in an older report stop matching.
207
- - The verifier runs against the same repository. A report that does not reproduce is worse
208
- than no report, which is the incentive the tool relies on.
209
-
210
- What it cannot catch: a genuinely large, low-value contribution. That is what references
211
- are for.
354
+ `@v0` is a moving tag the maintainer points at the newest 0.x release; pin a commit SHA if
355
+ you want the action to never change under you. Inputs reach the shell through environment
356
+ variables, never by interpolation into the script. The comment is updated in place on later
357
+ pushes (it carries a marker). The full Markdown report appears on the run's summary page,
358
+ and `workproof-report.md`, `.json`, `.intoto.json` and `.predicate.json` are uploaded as the
359
+ `workproof-report` artifact, so a report survives the workspace and can be downloaded from
360
+ the run. Set `comment: "false"` to skip the comment and keep the summary and the artifact.
361
+
362
+ ## Gaming and bias
363
+
364
+ Partly gameable, and built so the gaming shows. `npm run bench:adversarial` applies four
365
+ strategies to copies of the same fixture repository, dates every gaming commit inside the
366
+ subject's existing tenure so the window itself does not move, and recomputes every figure
367
+ with identical parameters:
368
+
369
+ | figure | baseline | commit padding | whitespace churn | generated file | generated, marked | Bob alone | Bob credits Ada |
370
+ |---|---|---|---|---|---|---|---|
371
+ | commits per week | 1.0 | **21.0** | 1.3 | 1.3 | 1.3 | 1.0 | 1.0 |
372
+ | median commit | 4 lines | **2 lines** | 4 lines | 4 lines | 2 lines | 4 lines | 4 lines |
373
+ | surviving lines | 51.6% | 53.1% | 51.6% | **99.3%** | 53.1% | 6.9% | 6.9% |
374
+ | surviving count | 16 | 17 | 16 | **2,016** | 17 | 16 | 16 |
375
+ | co-authored | 0 | 0 | 0 | 0 | 0 | 0 | **8** |
376
+ | excluded files | 2 | 2 | 2 | 2 | **3** | 2 | 2 |
377
+
378
+ Sixty padding commits take the cadence from 1.0 to 21.0 a week and buy one surviving line,
379
+ 16 to 17, while the median commit falls from 4 lines to 2: loud in the rows that count
380
+ commits, silent in the row that counts code, and the median is the tell in the same report.
381
+ Re-indenting every source line moves nothing, because blame runs with `-w`.
382
+
383
+ The generated file is the real hole, and the table says so: 2,000 machine-written lines
384
+ under a name the built-in lists do not match take surviving lines from 51.6% to 99.3%. The
385
+ same file marked `linguist-generated` in `.gitattributes` leaves every count and shows up
386
+ in the excluded column instead. The defence is a repository convention, not a detector.
387
+
388
+ The co-author trailer buys nothing. Bob's eight commits move the subject's shares down
389
+ either way because Bob wrote that code; naming the subject as co-author changes the
390
+ co-authored count and no other row. `test/adversarial.test.ts` pins the last two of those
391
+ so they cannot regress quietly.
392
+
393
+ These are the thirteen things a reader should know before trusting a number.
394
+
395
+ 1. Commit spam moves commit share and cadence, and nothing else. Surviving lines come from
396
+ `git blame` at HEAD, so a thousand empty commits add zero surviving lines, and the two
397
+ shares are printed side by side.
398
+ 2. Vendoring a library inflates lines added. Vendored directories, lock files, snapshots,
399
+ minified assets and generated outputs are excluded by built-in lists and by
400
+ `linguist-generated` or `linguist-vendored` in `.gitattributes`, and the report prints how
401
+ much was excluded. Anything the lists miss still counts; `--exclude` covers it, and the
402
+ glob is recorded in the parameters.
403
+ 3. Bot commits (`dependabot[bot]`, `renovate[bot]`, GitHub app identities) leave every
404
+ denominator. A bot with a human-looking name is not detected; there is no heuristic.
405
+ 4. A reformat commit takes every line it touched unless it is listed in
406
+ `.git-blame-ignore-revs`. The report says which file was used, or that none was.
407
+ 5. `--since`, `--until`, `--sample`, `--seed` and `--exclude` are all ways to choose a
408
+ flattering window or sample. Every one of them is stored in the parameters and hashed.
409
+ 6. Rewriting history to change authorship changes the root commit or HEAD, so the fingerprint
410
+ and HEAD in an older report stop matching.
411
+ 7. Degree of authorship uses coefficients fitted on other systems. First authorship weighs
412
+ more than later rewrites, so a file rewritten from scratch by someone else can stay with
413
+ its creator.
414
+ 8. `Co-authored-by` trailers are written by whoever merges. They can be missing, wrong, or
415
+ added by a squash-merge UI, and pairing without a trailer is invisible.
416
+ 9. AI-assisted means a trailer or author name said so. The absence of a trailer is not
417
+ evidence of unassisted work; blame credits the human for every line, so authorship no
418
+ longer implies comprehension. These commits are never excluded from any other figure.
419
+ 10. The absence factor counts email addresses. An author with several unmerged addresses
420
+ counts as several people; add a `.mailmap`.
421
+ 11. Documents created counts files whose oldest commit is the author's. A one-line README
422
+ and a design document count the same.
423
+ 12. Test-file changes are file changes matching test paths, not test cases, assertions or
424
+ coverage.
425
+ 13. The verifier runs against the same repository. A report that does not reproduce is worse
426
+ than no report, which is the incentive the tool relies on. What no figure catches is a
427
+ genuinely large, low-value contribution. That is what references are for.
212
428
 
213
429
  ## For candidates
214
430
 
215
431
  Run it in each repository you are proud of and cannot show. Put the Markdown in your
216
- portfolio next to the sentence you would have written anyway ("I built the frontend"),
217
- and let the numbers carry the sentence. Keep the JSON; it is what a reviewer verifies.
432
+ portfolio next to the sentence you would have written anyway ("I built the frontend"), and
433
+ let the numbers carry the sentence. Keep the JSON and the fingerprint key; the JSON is what
434
+ a reviewer verifies.
218
435
 
219
436
  ## For hiring managers
220
437
 
221
- Ask for the JSON and for someone inside the candidate's former company to run
222
- `npx workproof verify` on it. The table either reproduces or it does not. If the
223
- repository has moved on, the tool says which figures changed and why that is expected.
438
+ Ask for the JSON. `npx workproof check` tells you in a second whether it was edited. Then
439
+ ask someone inside the candidate's former company to run `npx workproof verify` on it. The
440
+ table either reproduces or it does not. If the repository has moved on, the tool says which
441
+ figures changed and why that is expected.
224
442
 
225
443
  ## For visa and immigration evidence
226
444
 
227
445
  workproof was built for a UK Global Talent application, where the strongest work was in
228
446
  private repositories and "trust me" is not evidence. A report is a measurement with its
229
- method attached, not an endorsement; pair it with letters from people who were there.
447
+ method attached, not an endorsement; pair it with letters from people who were there, and
448
+ with an attestation if the reader cannot reach the repository.
230
449
 
231
450
  ## What it does not do
232
451
 
@@ -237,8 +456,10 @@ not rank people. It does not replace references. It is not a legal document.
237
456
 
238
457
  The method is written up in
239
458
  [How to show engineering ownership when the repositories are private](https://efe-genc-portfolio.vercel.app/writing/showing-ownership-private-repositories/).
240
- The blame sampling is [surviving-lines](https://github.com/Bubblegunn/surviving-lines),
241
- workproof's only dependency.
459
+ The sampler and glob helpers are [surviving-lines](https://github.com/Bubblegunn/surviving-lines),
460
+ workproof's only dependency. Degree of authorship follows Avelino, Hora and Valente
461
+ (2016), major contributors follow Bird et al. (2011), and the absence factor is the CHAOSS
462
+ Contributor Absence Factor.
242
463
 
243
464
  ## Development
244
465