workproof 0.1.3 → 0.2.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 +322 -101
- package/README.tr.md +270 -77
- package/dist/src/analyse.d.ts +39 -2
- package/dist/src/analyse.js +98 -12
- package/dist/src/attest.d.ts +62 -0
- package/dist/src/attest.js +72 -0
- package/dist/src/badge.js +4 -4
- package/dist/src/canonical.d.ts +9 -0
- package/dist/src/canonical.js +27 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +110 -8
- package/dist/src/exclusions.d.ts +24 -0
- package/dist/src/exclusions.js +80 -0
- package/dist/src/figures/authorship.d.ts +53 -0
- package/dist/src/figures/authorship.js +190 -0
- package/dist/src/figures/footprint.d.ts +5 -1
- package/dist/src/figures/footprint.js +14 -9
- package/dist/src/figures/surviving.d.ts +38 -2
- package/dist/src/figures/surviving.js +82 -11
- package/dist/src/git.d.ts +24 -2
- package/dist/src/git.js +70 -13
- package/dist/src/index.d.ts +8 -4
- package/dist/src/index.js +6 -3
- package/dist/src/report.d.ts +5 -1
- package/dist/src/report.js +35 -13
- package/dist/src/schema.d.ts +4 -0
- package/dist/src/schema.js +66 -0
- package/dist/src/summary.d.ts +11 -0
- package/dist/src/summary.js +47 -0
- package/dist/src/verify.d.ts +29 -3
- package/dist/src/verify.js +38 -6
- package/package.json +32 -8
- package/schema/report.schema.json +74 -0
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
|
-
|
|
19
|
-
hash
|
|
20
|
-
|
|
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
|
|
30
|
-
and `workproof-report.json` (for tools, and for verification).
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
###
|
|
63
|
-
|
|
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/
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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 |
|
|
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 |
|
|
87
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 |
|
|
88
|
-
| Footprint | files touched, directories at or above a commit-share threshold, languages by lines added |
|
|
89
|
-
|
|
|
90
|
-
|
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
the
|
|
98
|
-
|
|
99
|
-
and
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
--
|
|
126
|
-
--
|
|
127
|
-
--
|
|
128
|
-
--
|
|
129
|
-
--
|
|
130
|
-
--
|
|
131
|
-
--
|
|
132
|
-
--
|
|
133
|
-
--
|
|
134
|
-
--
|
|
135
|
-
--
|
|
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
|
|
140
|
-
|
|
141
|
-
records
|
|
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 ·
|
|
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
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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@
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
|
222
|
-
`npx workproof verify` on it. The
|
|
223
|
-
repository has moved on, the tool says which
|
|
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
|
|
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
|
|