workproof 0.1.2 → 0.1.3
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 +58 -1
- package/dist/src/badge.d.ts +13 -0
- package/dist/src/badge.js +20 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +34 -9
- package/dist/src/figures/footprint.js +2 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/package.json +41 -10
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="https://img.shields.io/npm/v/workproof?style=flat-square&color=111111&label=npm" alt="npm">
|
|
9
|
+
<img src="https://img.shields.io/npm/dm/workproof?style=flat-square&color=111111" alt="npm downloads">
|
|
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">
|
|
9
12
|
<img src="https://img.shields.io/github/stars/Bubblegunn/workproof?style=flat-square&color=111111" alt="stars">
|
|
10
13
|
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT">
|
|
11
14
|
</p>
|
|
@@ -126,8 +129,10 @@ workproof verify <report.json> [--repo <dir>]...
|
|
|
126
129
|
--paths include directory paths
|
|
127
130
|
--emails include author emails
|
|
128
131
|
--narrate append a model-written paragraph
|
|
132
|
+
--badge also write <out>.badge.json, a shields.io endpoint document
|
|
129
133
|
--out <basename> output basename (default: workproof-report)
|
|
130
|
-
--json
|
|
134
|
+
--format <mode> write markdown, json, or both (default: both); json prints to stdout
|
|
135
|
+
--json same as --format json
|
|
131
136
|
```
|
|
132
137
|
|
|
133
138
|
A `.mailmap` in the repository merges an author's several addresses. Progress lines go to
|
|
@@ -135,6 +140,58 @@ stderr while history is read and files are blamed, so a long run is visibly aliv
|
|
|
135
140
|
history of hundreds of thousands of commits, `--max-commits` bounds the read and the report
|
|
136
141
|
records that it did.
|
|
137
142
|
|
|
143
|
+
## Badge
|
|
144
|
+
|
|
145
|
+
`--badge` writes `workproof-report.badge.json` next to the report, in the
|
|
146
|
+
[shields.io endpoint format](https://shields.io/badges/endpoint-badge):
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{ "schemaVersion": 1, "label": "workproof", "message": "70.6% surviving lines · 24.1% commits", "color": "1f3fbf" }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Commit it to a public repository (your portfolio, a gist) and point shields at the raw URL:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+

|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
A badge is a claim, not evidence. Keep the JSON report next to it; the report is what a
|
|
159
|
+
reader verifies, the badge is only how they find it.
|
|
160
|
+
|
|
161
|
+
## GitHub Action
|
|
162
|
+
|
|
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.
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
name: workproof
|
|
170
|
+
on:
|
|
171
|
+
pull_request:
|
|
172
|
+
permissions:
|
|
173
|
+
contents: read
|
|
174
|
+
pull-requests: write
|
|
175
|
+
jobs:
|
|
176
|
+
report:
|
|
177
|
+
runs-on: ubuntu-latest
|
|
178
|
+
steps:
|
|
179
|
+
- uses: actions/checkout@v4
|
|
180
|
+
with:
|
|
181
|
+
fetch-depth: 0
|
|
182
|
+
- uses: actions/setup-node@v4
|
|
183
|
+
with:
|
|
184
|
+
node-version: 22
|
|
185
|
+
- uses: Bubblegunn/workproof@main
|
|
186
|
+
with:
|
|
187
|
+
author: ada@example.com
|
|
188
|
+
sample: "1"
|
|
189
|
+
```
|
|
190
|
+
|
|
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
|
+
|
|
138
195
|
## Can it be gamed?
|
|
139
196
|
|
|
140
197
|
Partly, and the report is built so the gaming shows.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Report } from "./report.js";
|
|
2
|
+
/** shields.io endpoint document: https://shields.io/badges/endpoint-badge */
|
|
3
|
+
export interface Badge {
|
|
4
|
+
schemaVersion: 1;
|
|
5
|
+
label: string;
|
|
6
|
+
message: string;
|
|
7
|
+
color: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A badge for the first repository in the report. It is a claim, not evidence:
|
|
11
|
+
* the JSON report next to it is what a reader verifies.
|
|
12
|
+
*/
|
|
13
|
+
export declare function badgeFor(report: Report): Badge;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const pct = (x) => `${(x * 100).toFixed(1)}%`;
|
|
2
|
+
/**
|
|
3
|
+
* A badge for the first repository in the report. It is a claim, not evidence:
|
|
4
|
+
* the JSON report next to it is what a reader verifies.
|
|
5
|
+
*/
|
|
6
|
+
export function badgeFor(report) {
|
|
7
|
+
const repo = report.repositories[0];
|
|
8
|
+
if (!repo)
|
|
9
|
+
throw new Error("the report has no repositories");
|
|
10
|
+
const surviving = repo.figures.find((f) => f.id === "survivingLines");
|
|
11
|
+
const commits = repo.figures.find((f) => f.id === "commitShare");
|
|
12
|
+
if (!surviving || !commits)
|
|
13
|
+
throw new Error("the report has no surviving-lines or commit-share figure");
|
|
14
|
+
return {
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
label: "workproof",
|
|
17
|
+
message: `${pct(surviving.value.share)} surviving lines · ${pct(commits.value.share)} commits`,
|
|
18
|
+
color: "1f3fbf",
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import type { Params } from "./index.js";
|
|
3
|
+
type OutputFormat = "both" | "markdown" | "json";
|
|
3
4
|
interface Cli {
|
|
4
5
|
params: Params;
|
|
5
6
|
repos: string[];
|
|
6
7
|
out: string;
|
|
8
|
+
format: OutputFormat;
|
|
7
9
|
json: boolean;
|
|
8
10
|
doNarrate: boolean;
|
|
11
|
+
badge: boolean;
|
|
9
12
|
verifyFile: string | undefined;
|
|
10
13
|
}
|
|
11
14
|
export declare function parse(argv: string[]): Cli;
|
package/dist/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
|
-
import { analyseRepo, buildReport, renderMarkdown, verifyReport, narrate } from "./index.js";
|
|
6
|
+
import { analyseRepo, buildReport, renderMarkdown, verifyReport, narrate, badgeFor } from "./index.js";
|
|
7
7
|
const HELP = `usage: workproof [options] [--repo <dir>]...
|
|
8
8
|
workproof verify <report.json> [--repo <dir>]...
|
|
9
9
|
|
|
@@ -18,16 +18,20 @@ Turn a git repository into a verifiable engineering report for one author, witho
|
|
|
18
18
|
--paths include directory paths in the report (off by default)
|
|
19
19
|
--emails include author emails in the report (off by default)
|
|
20
20
|
--narrate append a model-written paragraph; needs WORKPROOF_API_URL, WORKPROOF_API_KEY, WORKPROOF_MODEL
|
|
21
|
+
--badge also write <out>.badge.json, a shields.io endpoint document
|
|
21
22
|
--out <basename> output basename (default: workproof-report)
|
|
22
|
-
--
|
|
23
|
+
--format <mode> output markdown, json, or both (default: both)
|
|
24
|
+
--json print the JSON to stdout instead of writing files (legacy alias)
|
|
23
25
|
-h, --help this text`;
|
|
24
26
|
export function parse(argv) {
|
|
25
27
|
const params = { depth: 2, threshold: 0.5, minCommits: 5, paths: false, emails: false };
|
|
26
28
|
const repos = [];
|
|
27
29
|
const authors = [];
|
|
28
30
|
let out = "workproof-report";
|
|
31
|
+
let format = "both";
|
|
29
32
|
let json = false;
|
|
30
33
|
let doNarrate = false;
|
|
34
|
+
let badge = false;
|
|
31
35
|
let verifyFile;
|
|
32
36
|
if (argv[0] === "verify") {
|
|
33
37
|
verifyFile = argv[1];
|
|
@@ -66,10 +70,20 @@ export function parse(argv) {
|
|
|
66
70
|
params.emails = true;
|
|
67
71
|
else if (a === "--narrate")
|
|
68
72
|
doNarrate = true;
|
|
73
|
+
else if (a === "--badge")
|
|
74
|
+
badge = true;
|
|
69
75
|
else if (a === "--out")
|
|
70
76
|
out = next();
|
|
71
|
-
else if (a === "--
|
|
77
|
+
else if (a === "--format") {
|
|
78
|
+
const value = next();
|
|
79
|
+
if (value !== "both" && value !== "markdown" && value !== "json")
|
|
80
|
+
throw new Error("--format must be markdown, json, or both");
|
|
81
|
+
format = value;
|
|
82
|
+
}
|
|
83
|
+
else if (a === "--json") {
|
|
72
84
|
json = true;
|
|
85
|
+
format = "json";
|
|
86
|
+
}
|
|
73
87
|
else if (a === "-h" || a === "--help") {
|
|
74
88
|
console.log(HELP);
|
|
75
89
|
process.exit(0);
|
|
@@ -81,11 +95,11 @@ export function parse(argv) {
|
|
|
81
95
|
params.author = authors;
|
|
82
96
|
if (!repos.length)
|
|
83
97
|
repos.push(process.cwd());
|
|
84
|
-
return { params, repos, out, json, doNarrate, verifyFile };
|
|
98
|
+
return { params, repos, out, format, json, doNarrate, badge, verifyFile };
|
|
85
99
|
}
|
|
86
100
|
async function main() {
|
|
87
101
|
const started = Date.now();
|
|
88
|
-
const { params, repos, out, json, doNarrate, verifyFile } = parse(process.argv.slice(2));
|
|
102
|
+
const { params, repos, out, format, json, doNarrate, badge, verifyFile } = parse(process.argv.slice(2));
|
|
89
103
|
const progress = (m) => process.stderr.write(`${m}\n`);
|
|
90
104
|
if (verifyFile) {
|
|
91
105
|
const report = JSON.parse(await readFile(verifyFile, "utf8"));
|
|
@@ -112,13 +126,24 @@ async function main() {
|
|
|
112
126
|
throw new Error("--narrate needs WORKPROOF_API_URL, WORKPROOF_API_KEY and WORKPROOF_MODEL");
|
|
113
127
|
narrative = await narrate(report, { url, key, model });
|
|
114
128
|
}
|
|
115
|
-
if (json) {
|
|
129
|
+
if (format === "json") {
|
|
116
130
|
console.log(JSON.stringify(report, null, 2));
|
|
117
131
|
return;
|
|
118
132
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
const written = [];
|
|
134
|
+
if (format === "markdown" || format === "both") {
|
|
135
|
+
await writeFile(`${out}.md`, renderMarkdown(report, narrative));
|
|
136
|
+
written.push(`${out}.md`);
|
|
137
|
+
}
|
|
138
|
+
if (format === "both") {
|
|
139
|
+
await writeFile(`${out}.json`, JSON.stringify(report, null, 2));
|
|
140
|
+
written.push(`${out}.json`);
|
|
141
|
+
}
|
|
142
|
+
if (badge) {
|
|
143
|
+
await writeFile(`${out}.badge.json`, JSON.stringify(badgeFor(report), null, 2));
|
|
144
|
+
written.push(`${out}.badge.json`);
|
|
145
|
+
}
|
|
146
|
+
console.log(`wrote ${written.join(" and ")} in ${((Date.now() - started) / 1000).toFixed(1)}s`);
|
|
122
147
|
}
|
|
123
148
|
const entry = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
|
|
124
149
|
if (entry === import.meta.url || entry.endsWith("/workproof")) {
|
|
@@ -2,7 +2,8 @@ import { isMine } from "./identity.js";
|
|
|
2
2
|
const LANG = {
|
|
3
3
|
ts: "TypeScript", tsx: "TypeScript", js: "JavaScript", jsx: "JavaScript", mjs: "JavaScript", cjs: "JavaScript",
|
|
4
4
|
py: "Python", cs: "C#", go: "Go", rs: "Rust", java: "Java", kt: "Kotlin", swift: "Swift", rb: "Ruby", php: "PHP",
|
|
5
|
-
c: "C", h: "C", cpp: "C++", hpp: "C++",
|
|
5
|
+
c: "C", h: "C", cpp: "C++", hpp: "C++", ex: "Elixir", exs: "Elixir", scala: "Scala", sc: "Scala", hs: "Haskell", lhs: "Haskell", lua: "Lua", r: "R", m: "Objective-C", mm: "Objective-C", zig: "Zig",
|
|
6
|
+
css: "CSS", scss: "CSS", html: "HTML", vue: "Vue", svelte: "Svelte",
|
|
6
7
|
sql: "SQL", sh: "Shell", yml: "YAML", yaml: "YAML", json: "JSON", md: "Markdown", mdx: "Markdown", tf: "Terraform", dart: "Dart",
|
|
7
8
|
};
|
|
8
9
|
export function languageOf(path) {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -6,3 +6,5 @@ export { verifyReport } from "./verify.js";
|
|
|
6
6
|
export type { VerifyRow } from "./verify.js";
|
|
7
7
|
export { narrate } from "./narrate.js";
|
|
8
8
|
export type { Figure, Identity } from "./figures/types.js";
|
|
9
|
+
export { badgeFor } from "./badge.js";
|
|
10
|
+
export type { Badge } from "./badge.js";
|
package/dist/src/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,25 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workproof",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Turn a private git repository into a verifiable engineering report for one author, without showing any code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
7
7
|
"types": "./dist/src/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"workproof": "./dist/src/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/src",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
11
22
|
"scripts": {
|
|
12
23
|
"build": "tsc -p tsconfig.json",
|
|
13
24
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
14
25
|
"test": "npm run build && node --test dist/test/figures.test.js dist/test/report.test.js dist/test/cli.test.js",
|
|
15
|
-
"prepublishOnly": "npm test"
|
|
26
|
+
"prepublishOnly": "npm test",
|
|
27
|
+
"examples": "npm run build && node examples/basic.mjs"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
16
31
|
},
|
|
17
|
-
"
|
|
18
|
-
|
|
32
|
+
"keywords": [
|
|
33
|
+
"git",
|
|
34
|
+
"portfolio",
|
|
35
|
+
"proof-of-work",
|
|
36
|
+
"career",
|
|
37
|
+
"blame",
|
|
38
|
+
"engineering-evidence"
|
|
39
|
+
],
|
|
19
40
|
"author": "Efe Genc",
|
|
20
41
|
"license": "MIT",
|
|
21
|
-
"repository": {
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/Bubblegunn/workproof.git"
|
|
45
|
+
},
|
|
22
46
|
"homepage": "https://github.com/Bubblegunn/workproof#readme",
|
|
23
|
-
"dependencies": {
|
|
24
|
-
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"surviving-lines": "^0.1.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
52
|
+
"@types/node": "^26.4.1",
|
|
53
|
+
"publint": "^0.3.24",
|
|
54
|
+
"typescript": "^7.0.2"
|
|
55
|
+
}
|
|
25
56
|
}
|