workproof 0.1.0 → 0.1.2

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
@@ -1,5 +1,7 @@
1
1
  <p align="center"><img src="assets/wordmark.svg" width="480" alt="workproof"></p>
2
2
 
3
+ <p align="center">English | <a href="README.tr.md">Türkçe</a></p>
4
+
3
5
  <p align="center"><em>Your best work is in private repos. Prove it anyway.</em></p>
4
6
 
5
7
  <p align="center">
@@ -119,6 +121,7 @@ workproof verify <report.json> [--repo <dir>]...
119
121
  --repo <dir> repository to analyse (repeatable; several produce one combined report)
120
122
  --since / --until override the tenure window
121
123
  --sample <n> blame every n-th file (default: 1; 7 for very large repositories)
124
+ --max-commits <n> read only the newest n commits (escape hatch for enormous histories)
122
125
  --depth <n> directory depth for ownership (default: 2)
123
126
  --paths include directory paths
124
127
  --emails include author emails
@@ -127,7 +130,28 @@ workproof verify <report.json> [--repo <dir>]...
127
130
  --json print the JSON to stdout instead of writing files
128
131
  ```
129
132
 
130
- A `.mailmap` in the repository merges an author's several addresses.
133
+ A `.mailmap` in the repository merges an author's several addresses. Progress lines go to
134
+ stderr while history is read and files are blamed, so a long run is visibly alive; on a
135
+ history of hundreds of thousands of commits, `--max-commits` bounds the read and the report
136
+ records that it did.
137
+
138
+ ## Can it be gamed?
139
+
140
+ Partly, and the report is built so the gaming shows.
141
+
142
+ - Commit spam moves commit share and cadence, and nothing else. Surviving lines come from
143
+ `git blame` at HEAD, so a thousand empty commits add zero surviving lines, and the gap
144
+ between the two shares is printed side by side.
145
+ - Vendoring a library inflates lines added. The languages figure and the owned-directory
146
+ list (with `--paths`) show where those lines landed, and a reviewer sees a directory
147
+ named `vendor` or `node_modules` owning most of them.
148
+ - Rewriting history to change authorship changes the root commit or HEAD, so the
149
+ fingerprint and HEAD in an older report stop matching.
150
+ - The verifier runs against the same repository. A report that does not reproduce is worse
151
+ than no report, which is the incentive the tool relies on.
152
+
153
+ What it cannot catch: a genuinely large, low-value contribution. That is what references
154
+ are for.
131
155
 
132
156
  ## For candidates
133
157
 
package/README.tr.md ADDED
@@ -0,0 +1,188 @@
1
+ <p align="center"><img src="assets/wordmark.svg" width="480" alt="workproof"></p>
2
+
3
+ <p align="center"><a href="README.md">English</a> | Türkçe</p>
4
+
5
+ <p align="center"><em>En iyi işin özel repolarda. Yine de kanıtla.</em></p>
6
+
7
+ <p align="center">
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/github/stars/Bubblegunn/workproof?style=flat-square&color=111111" alt="stars">
10
+ <img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT">
11
+ </p>
12
+
13
+ workproof, bir git deposunu tek bir yazar için doğrulanabilir bir mühendislik raporuna
14
+ çevirir; hiçbir kod göstermeden. Paylaşamadığınız depoda çalıştırırsınız. Okuyan kişi altı
15
+ figür, her birinin arkasındaki tam komutu, her birinin neyi gösteremediğini ve bir hash alır.
16
+ Aynı depoya sahip herkes `verify` çalıştırıp sayıların yeniden üretilip üretilmediğini görür.
17
+
18
+ ## 30 saniye
19
+
20
+ ```
21
+ cd your-private-repo
22
+ npx workproof
23
+ ```
24
+
25
+ Bu, `workproof-report.md` (bir özgeçmişe, portfolyoya, vize başvurusuna yapıştırın) ve
26
+ `workproof-report.json` (araçlar ve doğrulama için) dosyalarını yazar. Birinin raporunu
27
+ kontrol etmek için:
28
+
29
+ ```
30
+ npx workproof verify workproof-report.json
31
+ ```
32
+
33
+ ## Bir rapor neye benzer
34
+
35
+ Bu, [langchain-ai/openwiki](https://github.com/langchain-ai/openwiki) deposunun `1e6d54c`
36
+ sürümünde bir bakımcı için 5 Eylül 2026'da `--author "Colin Francis" --sample 5` ile alınan
37
+ gerçek çıktıdır; yollar ve e-postalar gizli (varsayılanlar):
38
+
39
+ ```
40
+ ## openwiki
41
+
42
+ HEAD 1e6d54cdfeec · fingerprint 82aa401bbba056f1 · identities: Colin Francis
43
+
44
+ ### Tenure window
45
+ 2026-07-06 to 2026-09-03 (60 days)
46
+
47
+ ### Share of commits in tenure
48
+ 71 of 295 non-merge commits, 24.1%
49
+
50
+ ### Cadence
51
+ 9 active weeks of 9, 7.9 commits per active week, longest streak 9 weeks
52
+ 1 of 21 release tags in tenure
53
+
54
+ ### Footprint
55
+ 694 files touched
56
+ 16 directories with a commit share at or above the threshold (paths hidden; run with --paths)
57
+ languages by lines added: TypeScript 80.4%, JSON 9.6%, Markdown 7.0%, JavaScript 1.6%, YAML 1.4%
58
+
59
+ ### Tests and documentation
60
+ 393 of 657 test-file changes, 59.8%
61
+ 116 documents authored
62
+
63
+ ### Surviving lines at HEAD
64
+ 23,317 of 33,038 surviving lines, 70.6% (files 123/548, sample 1 in 5)
65
+ ```
66
+
67
+ Her figürün altında rapor iki satır daha basar: onu üreten git komutuyla `How:` ve
68
+ `What this cannot show:`. Son bölüm `Integrity`: rapor hash'i ve depo parmak izi.
69
+
70
+ İki payı birlikte okuyun. Bu kişi penceresindeki commit'lerin %24,1'ini ve hâlâ yaşayan
71
+ satırların %70,6'sını yazmış. Bir commit sayısı onu küçük bir katkıcı olarak adlandırırdı. Bu
72
+ boşluk, iki yönde de, bir raporun birinin işi hakkında söyleyebileceği en dürüst şeydir.
73
+
74
+ ## Ne ölçer
75
+
76
+ Altı figürün hepsi git'ten gelir, başka hiçbir şeyden değil.
77
+
78
+ | figür | ne | neyi gösteremez |
79
+ |---|---|---|
80
+ | Görev penceresi | yazarın ilk ve son commit'i, ya da `--since/--until` | ilk commit'ten önceki ya da sonuncudan sonraki iş |
81
+ | Commit payı | penceredeki merge dışı commit'ler içinde yazarın merge dışı commit'leri | neyin sağ kaldığı; bir yazım hatası ile bir alt sistem aynı sayılır |
82
+ | Tempo | aktif haftalar, aktif hafta başına commit, en uzun seri, penceredeki sürüm etiketleri ve yazarınkiler | bir commit'lik hafta ile kırk commit'lik hafta ikisi de aktif sayılır |
83
+ | Ayak izi | dokunulan dosyalar, commit payı eşiğin üstündeki dizinler, eklenen satıra göre diller | üretilmiş ve vendored dosyalar commit'leyeni şişirir |
84
+ | Testler ve dokümanlar | test dosyası değişikliklerinin payı, yazılan dokümanlar | test vakaları, kapsam ya da bir dokümanın kalitesi |
85
+ | Hayatta kalan satırlar | HEAD'de yaşayan satırların payı, deterministik dosya örneği üzerinde `git blame -w -M`, [surviving-lines](https://github.com/Bubblegunn/surviving-lines) ile | liyakat; bilerek silinen kod kimseye sayılmaz |
86
+
87
+ ## Doğrulama nasıl çalışır
88
+
89
+ - JSON deponun HEAD'ini, bir **parmak izi** (kök commit ve normalleştirilmiş remote adresinin
90
+ sha256'sı; depo adı verilmeden tanımlanır), kullanılan kimlik adlarını, `surviving-lines`
91
+ sürümünü, her parametreyi ve parametreler ile figürlerin bir **hash**'ini taşır.
92
+ - `workproof verify report.json` her figürü gösterdiğiniz depoda yeniden hesaplar ve bir
93
+ eşleşme tablosu basar. HEAD rapordan beri ilerlediyse bunu söyler ve hangi figürlerin
94
+ değiştiğini gösterir.
95
+ - Bir işe alım yöneticisinin iki şeye ihtiyacı vardır: rapor ve depoya okuma erişimi (ya da
96
+ şirket içinde tek komutu çalıştıracak bir çalışan). Depodan hiçbir şey çıkmaz.
97
+
98
+ ## Gizlilik
99
+
100
+ - Kod içeriği, asla. Araç `git log --numstat` ve `git blame` okur ve sayılar üretir.
101
+ - Varsayılan olarak dosya yolu yok. `--paths`, yapılandırılan `--depth` (varsayılan 2)
102
+ derinliğinde dizin adları ekler, dosya asla.
103
+ - Varsayılan olarak e-posta adresi yok. `--emails` ekler; onsuz, yazdığınız `--author`
104
+ bile saklanan parametrelerde `(email hidden)` ile değiştirilir.
105
+ - İsteğe bağlı anlatı (`--narrate`) figürleri, yalnızca figürleri, seçtiğiniz bir model uç
106
+ noktasına gönderir (`WORKPROOF_API_URL`, `WORKPROOF_API_KEY`, `WORKPROOF_MODEL`;
107
+ OpenAI uyumlu ya da Anthropic). Paragraf "Generated narrative (not verified)" başlığı
108
+ altına eklenir ve hash'in dışındadır.
109
+
110
+ ## Seçenekler
111
+
112
+ ```
113
+ workproof [options] [--repo <dir>]...
114
+ workproof verify <report.json> [--repo <dir>]...
115
+
116
+ --author <email|name> identity to report on (repeatable; default: git config user.email)
117
+ --repo <dir> repository to analyse (repeatable; several produce one combined report)
118
+ --since / --until override the tenure window
119
+ --sample <n> blame every n-th file (default: 1; 7 for very large repositories)
120
+ --max-commits <n> read only the newest n commits (escape hatch for enormous histories)
121
+ --depth <n> directory depth for ownership (default: 2)
122
+ --paths include directory paths
123
+ --emails include author emails
124
+ --narrate append a model-written paragraph
125
+ --out <basename> output basename (default: workproof-report)
126
+ --json print the JSON to stdout instead of writing files
127
+ ```
128
+
129
+ Depodaki bir `.mailmap`, bir yazarın birden çok adresini birleştirir. Geçmiş okunurken ve
130
+ dosyalar blame'lenirken ilerleme satırları stderr'e gider, uzun bir çalıştırma canlı
131
+ görünür; yüz binlerce commit'lik bir geçmişte `--max-commits` okumayı sınırlar ve rapor bunu
132
+ kaydeder.
133
+
134
+ ## Oyunlanabilir mi?
135
+
136
+ Kısmen; rapor, oyunun görünmesi için tasarlandı.
137
+
138
+ - Commit spam'i commit payını ve tempoyu oynatır, başka bir şeyi değil. Hayatta kalan satırlar
139
+ HEAD'deki `git blame`'den gelir; bin boş commit sıfır hayatta kalan satır ekler ve iki pay
140
+ yan yana basılır.
141
+ - Bir kütüphaneyi vendor'lamak eklenen satırları şişirir. Diller figürü ve (`--paths` ile)
142
+ sahiplenilen dizin listesi o satırların nereye düştüğünü gösterir; inceleyen, satırların
143
+ çoğunu `vendor` ya da `node_modules` adlı bir dizinin sahiplendiğini görür.
144
+ - Yazarlığı değiştirmek için geçmişi yeniden yazmak kök commit'i ya da HEAD'i değiştirir;
145
+ eski bir rapordaki parmak izi ve HEAD eşleşmez olur.
146
+ - Doğrulayıcı aynı depoya karşı çalışır. Yeniden üretilemeyen bir rapor, hiç rapor
147
+ olmamasından kötüdür; aracın dayandığı teşvik budur.
148
+
149
+ Yakalayamadığı: gerçekten büyük ama düşük değerli bir katkı. Referanslar bunun içindir.
150
+
151
+ ## Adaylar için
152
+
153
+ Gurur duyduğunuz ve gösteremediğiniz her depoda çalıştırın. Markdown'ı portfolyonuza, zaten
154
+ yazacağınız cümlenin ("frontend'i ben yaptım") yanına koyun ve sayılar cümleyi taşısın. JSON'u
155
+ saklayın; inceleyenin doğruladığı odur.
156
+
157
+ ## İşe alanlar için
158
+
159
+ JSON'u ve adayın eski şirketinden birinin üzerinde `npx workproof verify` çalıştırmasını
160
+ isteyin. Tablo ya yeniden üretilir ya üretilmez. Depo ilerlediyse araç hangi figürlerin
161
+ değiştiğini ve bunun neden beklendiğini söyler.
162
+
163
+ ## Vize ve göç kanıtı için
164
+
165
+ workproof, en güçlü işin özel depolarda olduğu ve "bana güven"in kanıt sayılmadığı bir
166
+ Birleşik Krallık Global Talent başvurusu için yapıldı. Bir rapor, yöntemi ekli bir ölçümdür,
167
+ bir tavsiye değil; orada bulunmuş insanların mektuplarıyla birlikte kullanın.
168
+
169
+ ## Bunu yapmaz
170
+
171
+ Hayatta kalmayı ve etkinliği ölçer; kaliteyi, incelemeyi, tasarımı ya da mentorluğu değil.
172
+ İnsanları sıralamaz. Referansların yerine geçmez. Hukuki bir belge değildir.
173
+
174
+ ## Nereden geliyor
175
+
176
+ Yöntem
177
+ [How to show engineering ownership when the repositories are private](https://efe-genc-portfolio.vercel.app/writing/showing-ownership-private-repositories/)
178
+ yazısında anlatılıyor. Blame örneklemesi workproof'un tek bağımlılığı olan
179
+ [surviving-lines](https://github.com/Bubblegunn/surviving-lines) paketidir.
180
+
181
+ ## Geliştirme
182
+
183
+ ```
184
+ npm ci
185
+ npm test # tsc build, then node:test over the compiled tests (fixture repositories built in a temp dir)
186
+ ```
187
+
188
+ MIT.
@@ -9,6 +9,12 @@ export interface Params {
9
9
  minCommits: number;
10
10
  paths: boolean;
11
11
  emails: boolean;
12
+ /** Escape hatch for enormous histories: read only the newest n commits. */
13
+ maxCommits?: number;
14
+ }
15
+ export interface AnalyseHooks {
16
+ /** Called with short status lines while history is read and files are blamed. */
17
+ progress?: (message: string) => void;
12
18
  }
13
19
  export interface RepoReport {
14
20
  name: string;
@@ -23,4 +29,4 @@ export interface RepoReport {
23
29
  }
24
30
  /** sha256 of the root commit and the normalised remote: identifies a repository without naming it. */
25
31
  export declare function fingerprint(root: string, remote: string): string;
26
- export declare function analyseRepo(cwd: string, params: Params): Promise<RepoReport>;
32
+ export declare function analyseRepo(cwd: string, params: Params, hooks?: AnalyseHooks): Promise<RepoReport>;
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { basename } from "node:path";
3
3
  import { createRequire } from "node:module";
4
- import { listCommits, listTags, rootCommit, headSha, remoteUrl } from "./git.js";
4
+ import { listCommits, listTags, rootCommit, headSha, remoteUrl, assertRepository } from "./git.js";
5
5
  import { resolveIdentity } from "./figures/identity.js";
6
6
  import { tenure, commitShare } from "./figures/commits.js";
7
7
  import { cadence } from "./figures/cadence.js";
@@ -22,8 +22,12 @@ const survivingVersion = () => {
22
22
  return "unknown";
23
23
  }
24
24
  };
25
- export async function analyseRepo(cwd, params) {
26
- const all = await listCommits(cwd, {});
25
+ export async function analyseRepo(cwd, params, hooks = {}) {
26
+ const say = hooks.progress ?? (() => { });
27
+ await assertRepository(cwd);
28
+ say(`${basename(cwd)}: reading history${params.maxCommits ? ` (newest ${params.maxCommits} commits)` : ""}...`);
29
+ const all = await listCommits(cwd, params.maxCommits ? { max: params.maxCommits } : {});
30
+ say(`${basename(cwd)}: ${all.length.toLocaleString("en-US")} commits read`);
27
31
  const id = await resolveIdentity(all, params.author, cwd);
28
32
  const t = tenure(all, id, { ...(params.since ? { since: params.since } : {}), ...(params.until ? { until: params.until } : {}) });
29
33
  const start = new Date(t.value.first + "T00:00:00Z");
@@ -41,8 +45,11 @@ export async function analyseRepo(cwd, params) {
41
45
  cadence(inTenure, tags, id, { first: t.value.first, last: t.value.last }),
42
46
  fp,
43
47
  testsAndDocs(inTenure, id),
44
- await survivingLines(cwd, id, { sample, version: survivingVersion() }),
45
48
  ];
49
+ say(`${basename(cwd)}: blaming files (1 in ${sample} sample)...`);
50
+ const surviving = await survivingLines(cwd, id, { sample, version: survivingVersion() });
51
+ say(`${basename(cwd)}: blamed ${surviving.value.filesSampled} of ${surviving.value.filesTotal} files`);
52
+ figures.push(surviving);
46
53
  return {
47
54
  name: basename(cwd),
48
55
  head: await headSha(cwd),
package/dist/src/cli.js CHANGED
@@ -13,6 +13,7 @@ Turn a git repository into a verifiable engineering report for one author, witho
13
13
  --repo <dir> repository to analyse (repeatable; default: current directory)
14
14
  --since / --until override the tenure window (dates git understands)
15
15
  --sample <n> blame every n-th file (default: 1, or 7 for very large repositories)
16
+ --max-commits <n> read only the newest n commits (escape hatch for enormous histories)
16
17
  --depth <n> directory depth for ownership (default: 2)
17
18
  --paths include directory paths in the report (off by default)
18
19
  --emails include author emails in the report (off by default)
@@ -52,6 +53,11 @@ export function parse(argv) {
52
53
  params.until = next();
53
54
  else if (a === "--sample")
54
55
  params.sample = Number(next());
56
+ else if (a === "--max-commits") {
57
+ params.maxCommits = Number(next());
58
+ if (!Number.isInteger(params.maxCommits) || params.maxCommits < 1)
59
+ throw new Error("--max-commits must be an integer >= 1");
60
+ }
55
61
  else if (a === "--depth")
56
62
  params.depth = Number(next());
57
63
  else if (a === "--paths")
@@ -78,7 +84,9 @@ export function parse(argv) {
78
84
  return { params, repos, out, json, doNarrate, verifyFile };
79
85
  }
80
86
  async function main() {
87
+ const started = Date.now();
81
88
  const { params, repos, out, json, doNarrate, verifyFile } = parse(process.argv.slice(2));
89
+ const progress = (m) => process.stderr.write(`${m}\n`);
82
90
  if (verifyFile) {
83
91
  const report = JSON.parse(await readFile(verifyFile, "utf8"));
84
92
  const result = await verifyReport(report, repos);
@@ -93,7 +101,7 @@ async function main() {
93
101
  const version = createRequire(import.meta.url)("../../package.json").version;
94
102
  const repositories = [];
95
103
  for (const dir of repos)
96
- repositories.push(await analyseRepo(dir, params));
104
+ repositories.push(await analyseRepo(dir, params, { progress }));
97
105
  const report = buildReport(repositories, params, { version, generatedAt: new Date().toISOString() });
98
106
  let narrative;
99
107
  if (doNarrate) {
@@ -110,7 +118,7 @@ async function main() {
110
118
  }
111
119
  await writeFile(`${out}.json`, JSON.stringify(report, null, 2));
112
120
  await writeFile(`${out}.md`, renderMarkdown(report, narrative));
113
- console.log(`wrote ${out}.md and ${out}.json`);
121
+ console.log(`wrote ${out}.md and ${out}.json in ${((Date.now() - started) / 1000).toFixed(1)}s`);
114
122
  }
115
123
  const entry = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
116
124
  if (entry === import.meta.url || entry.endsWith("/workproof")) {
@@ -1,13 +1,15 @@
1
- import { configuredEmail } from "../git.js";
1
+ import { configuredEmail, configuredName } from "../git.js";
2
2
  /**
3
3
  * Resolve the author to report on. `author` entries match mailmapped emails or
4
4
  * names, case-insensitively; with none given, the repository's configured
5
5
  * user.email is used.
6
6
  */
7
7
  export async function resolveIdentity(commits, author, cwd) {
8
- const wanted = (author && author.length ? author : [await configuredEmail(cwd)]).map((a) => a.toLowerCase()).filter(Boolean);
8
+ const explicit = author && author.length ? author : [];
9
+ // Without --author, try the configured email, then the configured name.
10
+ const wanted = (explicit.length ? explicit : [await configuredEmail(cwd), await configuredName(cwd)]).map((a) => a.toLowerCase()).filter(Boolean);
9
11
  if (!wanted.length)
10
- throw new Error("no author given and git config user.email is empty; pass --author");
12
+ throw new Error(`no author given and git config has no user.email or user.name; pass --author. ${authorsHint(commits)}`);
11
13
  const emails = new Set();
12
14
  const names = new Set();
13
15
  for (const c of commits) {
@@ -17,7 +19,15 @@ export async function resolveIdentity(commits, author, cwd) {
17
19
  }
18
20
  }
19
21
  if (!emails.size)
20
- throw new Error(`no commits by ${wanted.join(", ")} in this repository`);
22
+ throw new Error(`no commits by ${wanted.join(" or ")} in this repository. ${authorsHint(commits)}`);
21
23
  return { emails: [...emails].sort(), names: [...names].sort() };
22
24
  }
25
+ /** The most frequent author names, so the error tells the user what to pass. */
26
+ function authorsHint(commits) {
27
+ const counts = new Map();
28
+ for (const c of commits)
29
+ counts.set(c.name, (counts.get(c.name) ?? 0) + 1);
30
+ const top = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, n]) => `"${name}" (${n})`);
31
+ return top.length ? `Authors here: ${top.join(", ")}. Pass one with --author.` : "The repository has no commits.";
32
+ }
23
33
  export const isMine = (c, id) => id.emails.includes(c.email);
package/dist/src/git.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface Commit {
16
16
  export declare function listCommits(cwd: string, opts: {
17
17
  since?: string;
18
18
  until?: string;
19
+ max?: number;
19
20
  }): Promise<Commit[]>;
20
21
  export interface Tag {
21
22
  name: string;
@@ -26,4 +27,7 @@ export declare function listTags(cwd: string): Promise<Tag[]>;
26
27
  export declare const rootCommit: (cwd: string) => Promise<string>;
27
28
  export declare const headSha: (cwd: string) => Promise<string>;
28
29
  export declare function remoteUrl(cwd: string): Promise<string>;
30
+ export declare function configuredName(cwd: string): Promise<string>;
31
+ /** Throws a plain sentence when the directory is not inside a git repository. */
32
+ export declare function assertRepository(cwd: string): Promise<void>;
29
33
  export declare function configuredEmail(cwd: string): Promise<string>;
package/dist/src/git.js CHANGED
@@ -12,6 +12,8 @@ export async function listCommits(cwd, opts) {
12
12
  args.push(`--since=${opts.since}`);
13
13
  if (opts.until)
14
14
  args.push(`--until=${opts.until}`);
15
+ if (opts.max)
16
+ args.push(`--max-count=${opts.max}`);
15
17
  const out = await git(args, cwd);
16
18
  const commits = [];
17
19
  for (const block of out.split("\x1e")) {
@@ -66,6 +68,23 @@ export async function remoteUrl(cwd) {
66
68
  return "";
67
69
  }
68
70
  }
71
+ export async function configuredName(cwd) {
72
+ try {
73
+ return (await git(["config", "--get", "user.name"], cwd)).trim();
74
+ }
75
+ catch {
76
+ return "";
77
+ }
78
+ }
79
+ /** Throws a plain sentence when the directory is not inside a git repository. */
80
+ export async function assertRepository(cwd) {
81
+ try {
82
+ await git(["rev-parse", "--is-inside-work-tree"], cwd);
83
+ }
84
+ catch {
85
+ throw new Error(`${cwd} is not inside a git repository (use --repo to point at one)`);
86
+ }
87
+ }
69
88
  export async function configuredEmail(cwd) {
70
89
  try {
71
90
  return (await git(["config", "--get", "user.email"], cwd)).trim().toLowerCase();
@@ -1,5 +1,5 @@
1
1
  export { analyseRepo, fingerprint } from "./analyse.js";
2
- export type { Params, RepoReport } from "./analyse.js";
2
+ export type { Params, RepoReport, AnalyseHooks } from "./analyse.js";
3
3
  export { buildReport, renderMarkdown } from "./report.js";
4
4
  export type { Report } from "./report.js";
5
5
  export { verifyReport } from "./verify.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workproof",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",