workproof 0.3.1 → 0.4.1
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 +74 -0
- package/README.tr.md +43 -0
- package/dist/src/figures/identity.d.ts +13 -0
- package/dist/src/figures/identity.js +16 -2
- package/dist/src/report.d.ts +0 -1
- package/dist/src/report.js +31 -3
- package/dist/src/summary.js +5 -2
- package/dist/src/text.d.ts +20 -0
- package/dist/src/text.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -175,6 +175,48 @@ so it never merges them for you: pass each address to `--author`, or write a `.m
|
|
|
175
175
|
The addresses themselves stay out of the document unless you pass `--emails`, the same rule the
|
|
176
176
|
rest of the report follows.
|
|
177
177
|
|
|
178
|
+
## Names that are not ASCII
|
|
179
|
+
|
|
180
|
+
The identity fold decides which commits are yours, so a gap in it is a missing figure rather than
|
|
181
|
+
an ugly one. Two gaps were closed against Unicode 17.0, and both changed counts:
|
|
182
|
+
|
|
183
|
+
- `Weiß` and `WEISS` are one person. JavaScript's `toLowerCase` is Unicode's simple case fold and
|
|
184
|
+
leaves ß alone, so the two spellings never met and half of one person's commits fell outside
|
|
185
|
+
every figure. The full fold maps ß to ss
|
|
186
|
+
([CaseFolding.txt](https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), status `F`),
|
|
187
|
+
which is what the tool does now.
|
|
188
|
+
- A name typed in fullwidth Latin letters, which is what a Japanese or Korean keyboard produces
|
|
189
|
+
without switching modes, is the same name typed in ASCII. Matching normalises to NFKC, so the
|
|
190
|
+
two forms meet, along with the fi ligature and the compatibility ideographs.
|
|
191
|
+
|
|
192
|
+
Turkish dotted and dotless i and decomposed accents were already folded. The fold is for matching
|
|
193
|
+
only. The report always prints the name git holds.
|
|
194
|
+
|
|
195
|
+
Display is separate. An Arabic or Hebrew name is the first strong character of the plain-language
|
|
196
|
+
paragraph, and without an isolate it turns the whole paragraph right to left: the English after it
|
|
197
|
+
reverses and the figures land in the wrong order. Names and repository names are wrapped in U+2068
|
|
198
|
+
and U+2069 in the Markdown report, the isolates [UAX #9](https://www.unicode.org/reports/tr9/)
|
|
199
|
+
defines for this. They are invisible, they are added only where a strong right-to-left character
|
|
200
|
+
appears, and they are never written into the JSON, so the hash over a report is unchanged and a
|
|
201
|
+
report written before this still verifies.
|
|
202
|
+
|
|
203
|
+
Three things here are deliberate:
|
|
204
|
+
|
|
205
|
+
- Numbers are printed in the `en-US` format wherever a person reads them, so `12,345` is twelve
|
|
206
|
+
thousand for every reader. The hash is taken over the RFC 8785 canonical JSON, where numbers
|
|
207
|
+
are not formatted at all, so this cannot affect `workproof verify`. It is about the document:
|
|
208
|
+
two reports are meant to be read side by side, and a thousands separator that follows the
|
|
209
|
+
reader's machine makes that harder for no gain.
|
|
210
|
+
- Weeks are ISO weeks, starting on Monday. That is not the local week for most of the world: of
|
|
211
|
+
the twenty most populous countries, CLDR gives Monday to seven, Sunday to eleven and Saturday
|
|
212
|
+
to two, which `Intl.Locale.prototype.getWeekInfo` reports in Node 24. Cadence counts weeks a
|
|
213
|
+
person was active, and that figure is only worth reading next to another report, so it uses one
|
|
214
|
+
definition everywhere rather than the reader's. The days inside a week are already the author's
|
|
215
|
+
own local dates, which is what decides whether a Sunday commit belongs to this week or the next.
|
|
216
|
+
- A `.mailmap` name is matched by git, not by this tool. git compares those names ignoring case
|
|
217
|
+
for ASCII letters only, verified on git 2.50.1: an entry written `josÉ Álvarez` matches a commit
|
|
218
|
+
by `JOSÉ ÁLVAREZ`, and one written `josé álvarez` does not. Map by address wherever you can.
|
|
219
|
+
|
|
178
220
|
## In plain language
|
|
179
221
|
|
|
180
222
|
Each repository section of the Markdown report opens with a paragraph a non-engineer can
|
|
@@ -229,6 +271,16 @@ Three commands, in increasing order of what the reader needs to have.
|
|
|
229
271
|
compares HEAD, then recomputes every figure and prints what differs. A report from another
|
|
230
272
|
repository stops at the fingerprint. If HEAD moved since the report, it says so and shows
|
|
231
273
|
which figures changed.
|
|
274
|
+
The hash is reproducible **across operating systems, git versions, locales and directory
|
|
275
|
+
names**, for the same repository at the same commit with the same command and the same
|
|
276
|
+
`--fingerprint-key`. It is not reproducible across a rewritten history, a different sample
|
|
277
|
+
or a different seed, and it is not meant to be: those change the measurement. Two fields
|
|
278
|
+
in the report are deliberately outside the hash, because a verifier on another machine
|
|
279
|
+
cannot reproduce them: the local directory name, and the git version. They stay in the
|
|
280
|
+
document, where they explain why two runs might legitimately differ. Before 0.4.0 both
|
|
281
|
+
were inside it, so the same repository in a folder with a different name hashed
|
|
282
|
+
differently; a test now pins all of this, and CI checks it on Linux, macOS and Windows.
|
|
283
|
+
|
|
232
284
|
- `workproof attest report.json` writes `report.intoto.json`, an
|
|
233
285
|
[in-toto](https://in-toto.io) v1 Statement whose subject is the report hash and whose
|
|
234
286
|
predicate carries the tool version, parameters, HEAD, keyed fingerprint, git version and
|
|
@@ -483,6 +535,28 @@ with an attestation if the reader cannot reach the repository.
|
|
|
483
535
|
It measures survivorship and activity, not quality, review, design or mentoring. It does
|
|
484
536
|
not rank people. It does not replace references. It is not a legal document.
|
|
485
537
|
|
|
538
|
+
## What the research says about numbers like these
|
|
539
|
+
|
|
540
|
+
Repository-derived figures have a literature, and most of it is a warning. Kalliamvakou,
|
|
541
|
+
Gousios and Blincoe catalogued the traps in mining a forge, including that most repositories
|
|
542
|
+
are personal and inactive and that a large share of merged pull requests are not recorded as
|
|
543
|
+
merged ([MSR 2014](https://doi.org/10.1145/2597073.2597074), extended in [EMSE
|
|
544
|
+
2015](https://doi.org/10.1007/s10664-015-9393-5)). Bird and colleagues related ownership
|
|
545
|
+
concentration to defects rather than to credit, which is a different question from this one
|
|
546
|
+
and is not evidence that a high share means good work. On survival, Spinellis, Louridas and
|
|
547
|
+
Kechagia put the median line lifespan at about 2.4 years across 89 repositories ([PeerJ CS
|
|
548
|
+
7:e372, 2021](https://doi.org/10.7717/peerj-cs.372)), and Gurov found over half of all lines
|
|
549
|
+
are never deleted in 120 TypeScript repositories ([arXiv
|
|
550
|
+
2606.04993](https://arxiv.org/abs/2606.04993)).
|
|
551
|
+
|
|
552
|
+
None of that work measures a person's share of the surviving code, which is what the
|
|
553
|
+
surviving-lines figure here reports. A first measurement of how far that share diverges from
|
|
554
|
+
commit share, over twelve repositories, is in
|
|
555
|
+
[surviving-lines/research](https://github.com/Bubblegunn/surviving-lines/tree/main/research):
|
|
556
|
+
the same person tops both in 25 of 36 runs, and among substantial contributors the two
|
|
557
|
+
differ by a median of 7.5 percentage points. That is the honest size of the effect this
|
|
558
|
+
report exists to expose, measured rather than asserted.
|
|
559
|
+
|
|
486
560
|
## Where it comes from
|
|
487
561
|
|
|
488
562
|
The method is written up in
|
package/README.tr.md
CHANGED
|
@@ -115,6 +115,49 @@ sayılmadan paydadan çıktı; bu, aynı kişinin eski sürümde %24,1 görünen
|
|
|
115
115
|
adlandırırdı. Bu boşluk, iki yönde de, bir raporun birinin işi hakkında söyleyebileceği en
|
|
116
116
|
dürüst şeydir.
|
|
117
117
|
|
|
118
|
+
## ASCII olmayan isimler
|
|
119
|
+
|
|
120
|
+
Kimlik katlaması hangi commit'lerin size ait olduğuna karar verir; oradaki bir boşluk çirkin bir
|
|
121
|
+
görüntü değil, eksik bir figür demektir. Unicode 17.0'a bakılarak iki boşluk kapatıldı ve ikisi de
|
|
122
|
+
sayıları değiştirdi:
|
|
123
|
+
|
|
124
|
+
- `Weiß` ile `WEISS` tek kişidir. JavaScript'in `toLowerCase`'i Unicode'un basit harf katlamasıdır
|
|
125
|
+
ve ß'ye dokunmaz; iki yazım hiç buluşmuyordu ve bir kişinin commit'lerinin yarısı bütün
|
|
126
|
+
figürlerin dışında kalıyordu. Tam katlama ß'yi ss'ye eşler
|
|
127
|
+
([CaseFolding.txt](https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), durum `F`)
|
|
128
|
+
ve araç artık bunu yapıyor.
|
|
129
|
+
- Tam genişlikli Latin harfleriyle yazılan bir isim, ki Japonca ya da Korece bir klavyede mod
|
|
130
|
+
değiştirmeden yazılan budur, ASCII ile yazılan aynı isimdir. Eşleştirme NFKC'ye normalleştirir;
|
|
131
|
+
fi bağlaması ve uyumluluk ideografları da bununla birlikte katlanır.
|
|
132
|
+
|
|
133
|
+
Türkçe noktalı ve noktasız i ile ayrışık aksanlar zaten katlanıyordu. Katlama yalnız eşleştirme
|
|
134
|
+
içindir; rapor her zaman git'in tuttuğu ismi yazar.
|
|
135
|
+
|
|
136
|
+
Görüntü ayrı bir konu. Arapça ya da İbranice bir isim, sade dil paragrafının ilk güçlü
|
|
137
|
+
karakteridir ve yalıtıcı olmadan bütün paragrafı sağdan sola çevirir: ardındaki İngilizce ters
|
|
138
|
+
döner, sayılar yanlış sırada görünür. Markdown raporunda isimler ve depo adları U+2068 ile U+2069
|
|
139
|
+
arasına alınıyor; [UAX #9](https://www.unicode.org/reports/tr9/) bu yalıtıcıları tam olarak bunun
|
|
140
|
+
için tanımlar. Görünmezler, yalnız güçlü sağdan sola karakter geçtiğinde eklenirler ve JSON'a hiç
|
|
141
|
+
yazılmazlar; yani rapor özeti değişmez ve bu değişiklikten önce yazılmış bir rapor hâlâ doğrulanır.
|
|
142
|
+
|
|
143
|
+
Üç şey bilerek böyle:
|
|
144
|
+
|
|
145
|
+
- Sayılar, insanın okuduğu her yerde `en-US` biçiminde yazılır; `12,345` her okuyucu için on iki
|
|
146
|
+
bindir. Özet, sayıların hiç biçimlendirilmediği RFC 8785 kanonik JSON üzerinden alınır, yani bu
|
|
147
|
+
seçim `workproof verify`'ı etkileyemez. Mesele belgedir: iki rapor yan yana okunmak için yazılır
|
|
148
|
+
ve okuyucunun makinesine göre değişen bir binlik ayıracı bunu boşuna zorlaştırır.
|
|
149
|
+
- Haftalar ISO haftasıdır, pazartesi başlar. Dünyanın çoğu için yerel hafta bu değildir: en
|
|
150
|
+
kalabalık yirmi ülkeden CLDR'ye göre yedisinde pazartesi, on birinde pazar, ikisinde cumartesi
|
|
151
|
+
başlar; Node 24'te bunu `Intl.Locale.prototype.getWeekInfo` söyler. Cadence, bir kişinin etkin
|
|
152
|
+
olduğu haftaları sayar ve bu figür ancak başka bir raporun yanında anlamlıdır, o yüzden
|
|
153
|
+
okuyucunun tanımını değil her yerde aynı tanımı kullanır. Haftanın içindeki günler zaten yazarın
|
|
154
|
+
kendi yerel tarihleridir; bir pazar commit'inin bu haftaya mı yoksa sonrakine mi düştüğüne o
|
|
155
|
+
karar verir.
|
|
156
|
+
- `.mailmap` isimlerini bu araç değil git eşleştirir. git bu isimleri yalnız ASCII harfleri için
|
|
157
|
+
büyük küçük harf farkını yok sayarak karşılaştırır; git 2.50.1 üzerinde doğrulandı:
|
|
158
|
+
`josÉ Álvarez` yazılmış bir satır `JOSÉ ÁLVAREZ` imzalı bir commit ile eşleşiyor,
|
|
159
|
+
`josé álvarez` yazılmış olan eşleşmiyor. Mümkün olan her yerde adresle eşleyin.
|
|
160
|
+
|
|
118
161
|
## Ne ölçer
|
|
119
162
|
|
|
120
163
|
Her figür git'ten gelir, başka hiçbir şeyden değil. Bot commit'leri ile üretilmiş, vendored,
|
|
@@ -13,6 +13,19 @@ import type { Identity } from "./types.js";
|
|
|
13
13
|
* turn English "I" into "ı", so the four i forms collapse to one instead. The cost is that two
|
|
14
14
|
* names differing only in dotted and dotless i match each other; the benefit is that a Turkish
|
|
15
15
|
* name matches itself in any case, and the error message prints the folded form that was tried.
|
|
16
|
+
*
|
|
17
|
+
* The German sharp s. `"WEISS".toLowerCase()` is "weiss" and `"Weiß".toLowerCase()` is "weiß",
|
|
18
|
+
* because JavaScript's `toLowerCase` is Unicode's simple case fold. The full fold maps ß to ss
|
|
19
|
+
* (CaseFolding.txt, status F, https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), so
|
|
20
|
+
* ß and capital ẞ are mapped before it runs. Without this, one person who signs both ways was two
|
|
21
|
+
* people, and half their commits were left out of every figure.
|
|
22
|
+
*
|
|
23
|
+
* Compatibility spellings. A name typed in fullwidth Latin letters, which is what a Japanese or
|
|
24
|
+
* Korean keyboard produces without switching modes, is the same name typed in ASCII; NFKC is what
|
|
25
|
+
* says so. It folds the fi ligature and the compatibility ideographs with it.
|
|
26
|
+
*
|
|
27
|
+
* This fold decides which commits are counted, so a gap in it is a wrong figure rather than a
|
|
28
|
+
* cosmetic problem. It is never a display form: the report prints the name git holds.
|
|
16
29
|
*/
|
|
17
30
|
export declare function foldIdentity(s: string): string;
|
|
18
31
|
/**
|
|
@@ -12,14 +12,28 @@ import { configuredEmail, configuredName } from "../git.js";
|
|
|
12
12
|
* turn English "I" into "ı", so the four i forms collapse to one instead. The cost is that two
|
|
13
13
|
* names differing only in dotted and dotless i match each other; the benefit is that a Turkish
|
|
14
14
|
* name matches itself in any case, and the error message prints the folded form that was tried.
|
|
15
|
+
*
|
|
16
|
+
* The German sharp s. `"WEISS".toLowerCase()` is "weiss" and `"Weiß".toLowerCase()` is "weiß",
|
|
17
|
+
* because JavaScript's `toLowerCase` is Unicode's simple case fold. The full fold maps ß to ss
|
|
18
|
+
* (CaseFolding.txt, status F, https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), so
|
|
19
|
+
* ß and capital ẞ are mapped before it runs. Without this, one person who signs both ways was two
|
|
20
|
+
* people, and half their commits were left out of every figure.
|
|
21
|
+
*
|
|
22
|
+
* Compatibility spellings. A name typed in fullwidth Latin letters, which is what a Japanese or
|
|
23
|
+
* Korean keyboard produces without switching modes, is the same name typed in ASCII; NFKC is what
|
|
24
|
+
* says so. It folds the fi ligature and the compatibility ideographs with it.
|
|
25
|
+
*
|
|
26
|
+
* This fold decides which commits are counted, so a gap in it is a wrong figure rather than a
|
|
27
|
+
* cosmetic problem. It is never a display form: the report prints the name git holds.
|
|
15
28
|
*/
|
|
16
29
|
export function foldIdentity(s) {
|
|
17
30
|
return s
|
|
18
|
-
.normalize("
|
|
31
|
+
.normalize("NFKC")
|
|
19
32
|
.replace(/[\u0130\u0131Ii]/g, "i")
|
|
20
33
|
.replace(/\u0307/g, "")
|
|
34
|
+
.replace(/[\u00df\u1e9e]/g, "ss")
|
|
21
35
|
.toLowerCase()
|
|
22
|
-
.normalize("
|
|
36
|
+
.normalize("NFKC")
|
|
23
37
|
.trim();
|
|
24
38
|
}
|
|
25
39
|
/**
|
package/dist/src/report.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ export interface Report {
|
|
|
10
10
|
/** sha256 of the RFC 8785 canonical JSON of { params, repositories }. */
|
|
11
11
|
hash: string;
|
|
12
12
|
}
|
|
13
|
-
/** The hash a report with these parameters and repositories must carry. */
|
|
14
13
|
export declare const hashOf: (params: unknown, repositories: unknown) => string;
|
|
15
14
|
export declare function buildReport(repositories: RepoReport[], params: Params, meta: {
|
|
16
15
|
version: string;
|
package/dist/src/report.js
CHANGED
|
@@ -2,8 +2,36 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { canonicalize } from "./canonical.js";
|
|
3
3
|
import { publicEmail } from "./analyse.js";
|
|
4
4
|
import { plainSummary } from "./summary.js";
|
|
5
|
+
import { isolate } from "./text.js";
|
|
5
6
|
/** The hash a report with these parameters and repositories must carry. */
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* What of a repository the hash covers: everything a stranger with the same repository at
|
|
9
|
+
* the same commit can reproduce, and nothing else.
|
|
10
|
+
*
|
|
11
|
+
* Two fields are recorded in the report and deliberately left out. `name` is the local
|
|
12
|
+
* directory's basename, so the same repository cloned to a different folder hashed
|
|
13
|
+
* differently. `environment.git` is the local git version string, so two people with the
|
|
14
|
+
* same repository and different git hashed differently. Both were reproduced with real
|
|
15
|
+
* runs before 0.4.0. They stay in the document because they explain why two runs might
|
|
16
|
+
* legitimately differ; they are not something a verifier can be asked to reproduce.
|
|
17
|
+
*
|
|
18
|
+
* The blame flags, the ignore-revs file and the seed stay in, because those are the
|
|
19
|
+
* question the caller asked, not the machine they asked it on.
|
|
20
|
+
*/
|
|
21
|
+
function forHash(repositories) {
|
|
22
|
+
if (!Array.isArray(repositories))
|
|
23
|
+
return repositories;
|
|
24
|
+
return repositories.map((repo) => {
|
|
25
|
+
if (repo === null || typeof repo !== "object")
|
|
26
|
+
return repo;
|
|
27
|
+
const { name: _name, environment, ...rest } = repo;
|
|
28
|
+
if (environment === null || typeof environment !== "object")
|
|
29
|
+
return rest;
|
|
30
|
+
const { git: _git, ...restEnvironment } = environment;
|
|
31
|
+
return { ...rest, environment: restEnvironment };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export const hashOf = (params, repositories) => createHash("sha256").update(canonicalize({ params, repositories: forHash(repositories) })).digest("hex");
|
|
7
35
|
/**
|
|
8
36
|
* What of the parameters the report may carry: never the fingerprint key, never a GitHub
|
|
9
37
|
* noreply login, and without --emails no author given as an email address.
|
|
@@ -72,9 +100,9 @@ export function renderMarkdown(report, narrative) {
|
|
|
72
100
|
``,
|
|
73
101
|
];
|
|
74
102
|
for (const repo of report.repositories) {
|
|
75
|
-
out.push(`## ${repo.name}`, ``, `HEAD \`${repo.head.slice(0, 12)}\` · fingerprint \`${repo.fingerprint.slice(0, 16)}\` · identities: ${repo.identity.names.join(", ")}${repo.identity.emails.length ? ` (${repo.identity.emails.join(", ")})` : ""}`, ``, ...(repo.identity.possiblySplit
|
|
103
|
+
out.push(`## ${isolate(repo.name)}`, ``, `HEAD \`${repo.head.slice(0, 12)}\` · fingerprint \`${repo.fingerprint.slice(0, 16)}\` · identities: ${repo.identity.names.map(isolate).join(", ")}${repo.identity.emails.length ? ` (${repo.identity.emails.join(", ")})` : ""}`, ``, ...(repo.identity.possiblySplit
|
|
76
104
|
? [
|
|
77
|
-
`**${plural(repo.identity.possiblySplit.names.length, "other identity", "other identities")} in this repository look${repo.identity.possiblySplit.names.length === 1 ? "s" : ""} like the same person and ${repo.identity.possiblySplit.names.length === 1 ? "was" : "were"} not counted here**: ${repo.identity.possiblySplit.names.join(", ")}${repo.identity.possiblySplit.emails?.length ? ` (${repo.identity.possiblySplit.emails.join(", ")})` : ""}. ${repo.identity.possiblySplit.reasons.map((r, i) => (i === 0 ? r.charAt(0).toUpperCase() + r.slice(1) : r)).join("; ")}. Every figure below therefore describes only the work under the identities named above. If they are the same person, re-run with each address passed to \`--author\`, or merge them in a \`.mailmap\`.`,
|
|
105
|
+
`**${plural(repo.identity.possiblySplit.names.length, "other identity", "other identities")} in this repository look${repo.identity.possiblySplit.names.length === 1 ? "s" : ""} like the same person and ${repo.identity.possiblySplit.names.length === 1 ? "was" : "were"} not counted here**: ${repo.identity.possiblySplit.names.map(isolate).join(", ")}${repo.identity.possiblySplit.emails?.length ? ` (${repo.identity.possiblySplit.emails.join(", ")})` : ""}. ${repo.identity.possiblySplit.reasons.map((r, i) => (i === 0 ? r.charAt(0).toUpperCase() + r.slice(1) : r)).join("; ")}. Every figure below therefore describes only the work under the identities named above. If they are the same person, re-run with each address passed to \`--author\`, or merge them in a \`.mailmap\`.`,
|
|
78
106
|
``,
|
|
79
107
|
]
|
|
80
108
|
: []), repo.excluded.enabled
|
package/dist/src/summary.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isolate } from "./text.js";
|
|
1
2
|
const pct = (x) => `${Math.round(x * 100)}%`;
|
|
2
3
|
const n = (x) => x.toLocaleString("en-US");
|
|
3
4
|
const value = (repo, id) => repo.figures.find((f) => f.id === id)?.value;
|
|
@@ -10,7 +11,9 @@ function longDate(iso) {
|
|
|
10
11
|
return `${d} ${MONTHS[m - 1]} ${y}`;
|
|
11
12
|
}
|
|
12
13
|
export function plainSummary(repo) {
|
|
13
|
-
|
|
14
|
+
// Isolated so a right-to-left name at the head of the paragraph cannot reverse the sentence
|
|
15
|
+
// around it. Presentation only: the data, and the hash over it, keep the name git holds.
|
|
16
|
+
const who = isolate(repo.identity.names[0] ?? "This author");
|
|
14
17
|
const tenure = value(repo, "tenure");
|
|
15
18
|
const commits = value(repo, "commitShare");
|
|
16
19
|
const surviving = value(repo, "survivingLines");
|
|
@@ -19,7 +22,7 @@ export function plainSummary(repo) {
|
|
|
19
22
|
const absence = value(repo, "absenceFactor");
|
|
20
23
|
const sentences = [];
|
|
21
24
|
if (tenure) {
|
|
22
|
-
sentences.push(`${who} worked in ${repo.name} from ${longDate(tenure.first)} to ${longDate(tenure.last)}, a span of ${n(tenure.days)} days.`);
|
|
25
|
+
sentences.push(`${who} worked in ${isolate(repo.name)} from ${longDate(tenure.first)} to ${longDate(tenure.last)}, a span of ${n(tenure.days)} days.`);
|
|
23
26
|
}
|
|
24
27
|
if (commits && surviving) {
|
|
25
28
|
sentences.push(`They made ${n(commits.author)} of the ${n(commits.total)} changes recorded in that period (${pct(commits.share)}), and ${n(surviving.lines)} of the ${n(surviving.linesAttributed)} lines of code still in the project today are theirs (${pct(surviving.share)}).`);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text that has to survive being embedded in someone else's sentence.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a name, a repository name or any other borrowed string in the first strong isolate, so it
|
|
6
|
+
* cannot reorder the sentence it is dropped into.
|
|
7
|
+
*
|
|
8
|
+
* The plain-language paragraph puts the author's name at the start of a sentence and the
|
|
9
|
+
* repository's name in the middle of it. An Arabic or Hebrew name there is the first strong
|
|
10
|
+
* character of the paragraph, which under the bidirectional algorithm makes the whole paragraph
|
|
11
|
+
* right to left: the English words after it reverse, and the numbers land in the wrong order.
|
|
12
|
+
* U+2068 and U+2069 are what UAX #9 provides for embedding text of unknown direction
|
|
13
|
+
* (https://www.unicode.org/reports/tr9/, section 2.7). They are default-ignorable, so they take
|
|
14
|
+
* no width, and they are added only to strings that carry strong right-to-left characters, so
|
|
15
|
+
* every other report is byte for byte what it was.
|
|
16
|
+
*
|
|
17
|
+
* This is presentation only. The report's data, and therefore its hash, keeps the exact string
|
|
18
|
+
* git holds: `workproof verify` on a report written before this change still passes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isolate(s: string): string;
|
package/dist/src/text.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text that has to survive being embedded in someone else's sentence.
|
|
3
|
+
*/
|
|
4
|
+
/** Scripts written right to left, whose first strong character sets a paragraph's direction. */
|
|
5
|
+
const RTL = /[\p{sc=Arabic}\p{sc=Hebrew}\p{sc=Syriac}\p{sc=Thaana}\p{sc=Nko}\p{sc=Samaritan}\p{sc=Mandaic}\p{sc=Adlam}]/u;
|
|
6
|
+
/**
|
|
7
|
+
* Wrap a name, a repository name or any other borrowed string in the first strong isolate, so it
|
|
8
|
+
* cannot reorder the sentence it is dropped into.
|
|
9
|
+
*
|
|
10
|
+
* The plain-language paragraph puts the author's name at the start of a sentence and the
|
|
11
|
+
* repository's name in the middle of it. An Arabic or Hebrew name there is the first strong
|
|
12
|
+
* character of the paragraph, which under the bidirectional algorithm makes the whole paragraph
|
|
13
|
+
* right to left: the English words after it reverse, and the numbers land in the wrong order.
|
|
14
|
+
* U+2068 and U+2069 are what UAX #9 provides for embedding text of unknown direction
|
|
15
|
+
* (https://www.unicode.org/reports/tr9/, section 2.7). They are default-ignorable, so they take
|
|
16
|
+
* no width, and they are added only to strings that carry strong right-to-left characters, so
|
|
17
|
+
* every other report is byte for byte what it was.
|
|
18
|
+
*
|
|
19
|
+
* This is presentation only. The report's data, and therefore its hash, keeps the exact string
|
|
20
|
+
* git holds: `workproof verify` on a report written before this change still passes.
|
|
21
|
+
*/
|
|
22
|
+
export function isolate(s) {
|
|
23
|
+
if (!RTL.test(s) || /\p{Bidi_Control}/u.test(s))
|
|
24
|
+
return s;
|
|
25
|
+
return `${s}`;
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workproof",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Turn a private git repository into a verifiable engineering report for one author, without showing any code: thirteen figures from git, a hash anyone can recompute offline, verify, and an in-toto attestation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|