@cejel/cejel 0.6.8 → 0.6.11

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
@@ -3,6 +3,35 @@
3
3
  [![release](https://img.shields.io/github/v/release/BargLabs/cejel)](https://github.com/BargLabs/cejel/releases/latest)
4
4
  [![license: AGPL-3.0-only](https://img.shields.io/badge/license-AGPL--3.0--only-blue.svg)](./LICENSE)
5
5
 
6
+ <p align="left"><img src="brand/cejel-icon.png" alt="Cejel logo" width="96"></p>
7
+
8
+ **Evidence about a repository that anyone can rerun.** Cejel reads a repository's own evidence
9
+ (tests that really assert something, committed secrets, CI gating) and writes a certificate
10
+ pinned to one commit. Whoever receives it, a customer, a buyer or an acquirer, can run the same
11
+ Cejel version on the same commit themselves, offline, and get the same result. No LLM, no signup.
12
+
13
+ Add it to a workflow; the result is posted to the job summary:
14
+
15
+ ```yaml
16
+ on: [pull_request]
17
+ jobs:
18
+ cejel:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0 # full history: A2 and B2 read commit history
24
+ - uses: BargLabs/cejel/action@v1
25
+ ```
26
+
27
+ Keep `fetch-depth: 0`. `actions/checkout` makes a shallow clone by default, and a shallow clone is
28
+ "Unrated: shallow clone", with no score and no verdict, under every rubric, the default included,
29
+ until the full history is checked out. A directory with no `.git` (an uploaded snapshot or an
30
+ archive) is "Unrated: no git history" under every rubric.
31
+
32
+ Or run it once from a shell: `npx @cejel/cejel@latest .` More on the Action under
33
+ [GitHub Action](#github-action). To read what comes out, see [Reading the result](#reading-the-result).
34
+
6
35
  Cejel (*"SEH-jel"*) — a trust certificate for your codebase.
7
36
 
8
37
  Free, offline, no-signup CLI that scores the engineering signals that tell you whether to
@@ -51,6 +80,48 @@ untouched holdout, every preregistered gate to pass, and a separately recorded p
51
80
  The [defect-class census](docs/defect-class-census.md) publishes the coverage-breadth
52
81
  boundary—which defect classes any shipped rule targets—and is explicitly not a recall claim.
53
82
 
83
+ ## Reading the result
84
+
85
+ The first page of `certificate.html` (and the block printed after the verdict in the terminal)
86
+ answers four questions in plain words, built only from `report.json`:
87
+
88
+ - **What holds:** the measured areas the report labels Verified, or Info (one label below, listed
89
+ as "Partly holds"), each with its score out of 4 and the strongest measurement it saw, for
90
+ example "Rated Verified, at 1.0 out of 4". Only a score of 4.0 is called the top score. A label
91
+ and a score are set separately, so a Verified area can have a low score; each area's card in the
92
+ certificate compares the two. An area Cejel did not examine, or examined with no measurement, is
93
+ never listed here. When the list is empty, one sentence says why: no overall result, no area
94
+ labelled Verified or Info, or both.
95
+ - **What is wrong:** every critical and warning-level finding, most serious first, naming the file
96
+ or area and what is wrong, and every area labelled Critical or Warning that has no critical or
97
+ warning finding of its own, with its score ("Scored 2.2 out of 4, with no single finding of its own"). A score made
98
+ up of several measurements is given with its number and level, for example "give 3.1 out of 4,
99
+ which Cejel counts as a warning".
100
+ - **What is not known:** what the report records it could not establish, from these fields: an
101
+ overall result it withheld, with its reason restated from a fixed table (a reason the table does
102
+ not know reads "Cejel could not rate the source here; see the certificate below"); areas that do
103
+ not apply, have no measurement, or have no score while a finding stands; areas Cejel could not
104
+ fully read (named in the report's skipped-content summary or by a scan or history limitation),
105
+ listed as "not read" with the cause the report ties to that area: files that could not be read,
106
+ Git LFS pointers, files left out under Cejel's size limit or rules, a scan limitation, or a
107
+ shallow clone; each scan and history limitation, reworded into plain words; files it did not
108
+ read; a label moved by imported evidence; and each limit a check recorded on what it examined.
109
+ Anything the report does not record is not listed, so an empty list does not mean nothing is
110
+ unknown.
111
+ - **What to check next:** one line for each wrong or not-known item, taken from a fixed table of
112
+ reviewed sentences. The line for an area not read, and for a scan limitation, follows its own
113
+ cause: `git lfs pull` for Git LFS pointers, making files readable only where they could not be
114
+ read, and no fix for a size limit or rule. An item with no table entry points to the section of
115
+ the certificate that holds it.
116
+
117
+ The first page also says what the result does not cover: Cejel reads files and history and does
118
+ not run the code or its tests. Under a prospective rubric such as
119
+ `witan-rubric-v27-prospective-2026-10-19`, the first page opens, above the verdict line, with the
120
+ same notice the certificate's header carries: the rubric is not calibrated, and its results carry
121
+ no precision or recall claim. Every line links to its source further down; the certificate below
122
+ the first page holds the evidence, unchanged apart from the anchors those links point to.
123
+ `summary.json` carries the same lists under `plainSummary`.
124
+
54
125
  ## Install
55
126
 
56
127
  No account, no key, no signup.
@@ -92,7 +163,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
92
163
  ```
93
164
 
94
165
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
95
- > 0.6.8 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
166
+ > 0.6.11 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
96
167
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
97
168
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
98
169
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM (the packages
@@ -116,9 +187,10 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
116
187
  npx @cejel/cejel@latest .
117
188
  ```
118
189
 
119
- > **Distribution note:** Cejel `0.6.8` is the coordinated release version for npm,
120
- > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. A GitLab CI
121
- > template and Code Quality export live in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
190
+ > **Distribution note:** Cejel `0.6.11` is the coordinated release version for npm,
191
+ > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. On GitLab, use the
192
+ > CI/CD Catalog component `gitlab.com/barglabs/cejel/cejel@0.6.11`; it, the include template and
193
+ > the Code Quality export are documented in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
122
194
 
123
195
  `npx` can reuse a stale cached package. Force the current npm release with the `@latest`
124
196
  specifier above, and check the version that will run before comparing certificates:
@@ -157,12 +229,41 @@ The npm package is scoped as `@cejel/cejel`; its executable remains the short co
157
229
  min-score: "2.5" # optional: fail the build below this
158
230
  ```
159
231
 
160
- `actions/checkout` fetches one commit by default. On a shallow clone, A2 and B2 see only that
161
- commit, and the certificate says so under "History limitations".
162
- Under the prospective v27 rubric (`--rubric-pin witan-rubric-v27-prospective-2026-10-19`), a
163
- shallow clone goes further. A2 abstains unless a confirmed critical secret finding stands or A2
164
- does not apply. B2 abstains, where it would be scored, when fewer than 12 commits are visible. Each
165
- abstention counts as 0 in the composite, so a shallow checkout can lower the v27 score.
232
+ `actions/checkout` fetches one commit by default, so clone in full with `fetch-depth: 0`. Under
233
+ every rubric, the calibrated default included, a shallow clone gets no composite score and no
234
+ verdict until the full history is checked out. The scan abstains (`insufficient_source`), still
235
+ reads and shows each criterion, and says what to do: run `git fetch --unshallow` (or check out with
236
+ full history, for example `fetch-depth: 0` in `actions/checkout`), then scan again. The
237
+ certificate, the badge and the Action's `verdict` output read "Unrated: shallow clone", and the
238
+ Action's `limited` output is `true`. With `--min-score` an abstained scan fails the check, as any
239
+ abstention does, and the message says how to fetch the history. If git cannot report the depth (git
240
+ is not installed, or cannot open the repository), Cejel reads it from the repository's `.git`
241
+ files; if those do not settle it either, the scan abstains the same way as "Unrated: history depth
242
+ unknown". Among the criteria shown on a shallow clone, A2's scans of earlier commits for `.env`
243
+ files and credentials read only the commits it holds, so A2 abstains unless a confirmed critical
244
+ secret finding stands, including where it would otherwise be marked not applicable, since that
245
+ decision also rests on earlier commits; B2 withholds its pull-request merge ratio, or abstains
246
+ where nothing else is left to score it on, saying why in its note. The certificate says so under
247
+ "History limitations". Until 2 November 2026 this held only under the opt-in prospective v27
248
+ rubric (`--rubric-pin witan-rubric-v27-prospective-2026-10-19`; v17 remains the default), and the
249
+ default rubric scored a shallow clone, on which A2 could not see a secret committed and later
250
+ deleted. A complete clone is scored exactly as before.
251
+
252
+ A directory that is not a git repository gets no composite score and no verdict under every
253
+ rubric, v27 included: "Unrated: no git history". That covers a directory with no `.git`, a
254
+ tarball, an archive export of a commit, and every snapshot uploaded to the HTTP or remote-MCP
255
+ server. A2 abstains, since no commit history was read, unless a confirmed critical secret finding
256
+ stands on the files. Scan a git clone with full history; an uploaded snapshot or archive carries no
257
+ history.
258
+
259
+ On a clone that is not shallow, A2 abstains under v27 where one of its reads of earlier commits failed:
260
+ for example on a partial clone made with `--filter`, which `fetch-depth: 0` and no `filter` avoid,
261
+ or where a read stops at one of Cejel's own limits: an output limit or the history secret scan's
262
+ cap of 5,000 historical files, which happen on some large complete histories and which a re-scan
263
+ of the same repository does not lift (#567), or the 30-second timeout, which a re-scan may lift.
264
+ It also abstains when it leaves a credential-shaped path in history unread because the path's name
265
+ contains control characters, and when git itself cannot be run on the scanning host. Each of these
266
+ abstentions counts as 0 in the composite, so they can lower the v27 score.
166
267
 
167
268
  For an executed public example that binds a release artifact to the same commit named by its
168
269
  Cejel certificate, see [Certified releases with GitHub build provenance](./docs/certified-release-example.md).
@@ -191,16 +292,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
191
292
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
192
293
  Microsoft Authenticode code-signing.
193
294
 
194
- **Docker / OCI.** The current container release is `0.6.8`:
295
+ **Docker / OCI.** The current container release is `0.6.11`:
195
296
 
196
297
  ```bash
197
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.8
298
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.11
198
299
  ```
199
300
 
200
301
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
201
302
 
202
303
  ```bash
203
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.8 .
304
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.11 .
204
305
  ```
205
306
 
206
307
  The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
@@ -213,7 +314,7 @@ The [Cejel OSS trust leaderboard](https://cejel.dev/leaderboard/) is hosted on c
213
314
  which is the single current board. This repository no longer distributes a second copy of
214
315
  its scores or certificates. The board records its producing package version, rubric,
215
316
  run date, pinned public revisions, and reproduction instructions for that particular run.
216
- The private Alfred transparency snapshot is not independently reproducible.
317
+ The board's row for Barg Labs' own private codebase is a transparency snapshot and is not independently reproducible.
217
318
 
218
319
  The site's History section preserves the **2026-08-18 withdrawal**: the earlier
219
320
  sealed-public-scorer reproducibility claim was false. The stale prospective-rubric copy
@@ -309,8 +410,8 @@ prints that boundary on every successful verification.
309
410
  - `--rubric-pin <version>` — Explicit opt-in only: pin an alternate rubric instead of the
310
411
  calibrated public default. It
311
412
  accepts the calibrated version or any published prospective rubric
312
- (`witan-rubric-v18-prospective-*` through `v26`) and fails closed, naming what was accepted, on
313
- anything else. `v26` (`witan-rubric-v26-prospective-2026-10-14`) is prospective and uncalibrated:
413
+ (`witan-rubric-v18-prospective-*` through `v27`) and fails closed, naming what was accepted, on
414
+ anything else. The GitHub Action takes the same value as its optional `rubric-pin` input. `v26` (`witan-rubric-v26-prospective-2026-10-14`) is prospective and uncalibrated:
314
415
  it is the next calibration candidate, inherits v25 complete, inherits neither v23 nor v24, and
315
416
  today scores exactly as v25 does. `v25` is prospective and uncalibrated: it inherits v22 and adds only credit for a
316
417
  health or readiness route declared inline in an application file (see "What counts as a health or
@@ -392,7 +493,7 @@ Three ways a scanner's output gets ingested:
392
493
  `--ingest scorecard.json`; auto-detected by its `checks` array.
393
494
  3. **Generic JSON** — for a tool that emits neither, map its output into the minimal shape
394
495
  below (or write a small adapter mirroring
395
- [`scorecard-adapter.ts`](../witan/src/scorecard-adapter.ts) if the format needs real
496
+ [`scorecard-adapter.ts`](./src/witan/scorecard-adapter.ts) if the format needs real
396
497
  parsing):
397
498
 
398
499
  ```json
@@ -416,7 +517,7 @@ Three ways a scanner's output gets ingested:
416
517
  }
417
518
  ```
418
519
 
419
- `dimension` is one of the Witan rubric criterion ids (`A1`-`A5`, `B1`-`B6`); `weight`
520
+ `dimension` is one of the rubric criterion ids (`A1`-`A5`, `B1`-`B6`); `weight`
420
521
  (0–1, default `0.5`) bounds how much this signal can move that dimension; `severity` is
421
522
  `critical` | `warning` | `info`; `location` is optional.
422
523
 
@@ -467,8 +568,50 @@ secrets.
467
568
  min-score: '2.5' # optional; omit to never fail the check
468
569
  ```
469
570
 
470
- Check out with `fetch-depth: 0`: the default depth of 1 truncates the history A2 and B2 read, and
471
- the certificate then lists a history limitation.
571
+ Check out with `fetch-depth: 0`. The default depth of 1 is a shallow clone, and under every
572
+ rubric, the default included, a shallow checkout gets no composite score and no verdict: the step
573
+ summary, the badge and the `verdict` output read "Unrated: shallow clone", the `limited` output is
574
+ `true` and `limitations` names the cause, and the scan says to run `git fetch --unshallow` (or check
575
+ out with full history, for example `fetch-depth: 0` in `actions/checkout`), then scan again. On a
576
+ shallow clone A2's scans of earlier commits read only the commits present and B2 withholds its
577
+ pull-request merge ratio, each with its reason. A `repo-path` that is not a git repository is
578
+ "Unrated: no git history" under every rubric. The Action does not pin a rubric, so it scores under
579
+ the default.
580
+
581
+ | Output | What it holds |
582
+ |---|---|
583
+ | `score` | The overall score (0.0–4.0), or empty when Cejel abstains. |
584
+ | `verdict` | The verdict label, or the abstention label (`Insufficient source`, `Insufficient evidence`, `Unrated: shallow clone`, `Unrated: history depth unknown`, `Unrated: no git history`). |
585
+ | `limited` | `true` when scan limitations qualify the result, or when the verdict is Unrated; otherwise `false`. |
586
+ | `limitation-count` | How many limitations qualify the certificate, counting an Unrated verdict as one. |
587
+ | `limitations` | A JSON array of the limitation messages; an Unrated verdict adds one naming its cause. |
588
+ | `artifact-dir` | Where this run's report artifacts were exported. |
589
+
590
+ | Input | Default | What it does |
591
+ |---|---|---|
592
+ | `repo-path` | the checkout (`$GITHUB_WORKSPACE`) | Path to the repository to score. |
593
+ | `min-score` | empty: never fail | Fails the step below this overall score (0–4), or when Cejel abstains. |
594
+ | `out-dir` | `.cejel` | Empty destination for this run's report artifacts. |
595
+ | `rubric-pin` | empty: the calibrated default | Explicit opt-in only: the same value as the CLI's `--rubric-pin`. An unknown id fails the step with the CLI's message. |
596
+
597
+ The scoring step is offline whatever the inputs; it never fetches history. To opt in to the
598
+ prospective v27 rubric, pin it and check out in full with `fetch-depth: 0`: v27 reads the commit
599
+ history, so a shallow checkout gets no full v27 result, as under the default. v17 remains the
600
+ calibrated default.
601
+
602
+ ```yaml
603
+ - uses: actions/checkout@v4
604
+ with:
605
+ fetch-depth: 0 # full history: A2 and B2 read commit history
606
+ - uses: BargLabs/cejel/action@v1
607
+ with:
608
+ rubric-pin: witan-rubric-v27-prospective-2026-10-19
609
+ ```
610
+
611
+ A prospective pin carries no precision/recall claim, and the certificate states that plainly
612
+ whenever one is used. Leave `rubric-pin` empty for the calibrated result every default scan
613
+ produces: an empty input passes no `--rubric-pin`, so the scan, the report and the outputs are
614
+ the same as without the input.
472
615
 
473
616
  ## Use with Claude
474
617
 
@@ -591,7 +734,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
591
734
  the host path with the repository OpenClaw should allow Cejel to read:
592
735
 
593
736
  ```bash
594
- npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.6.8"]}'
737
+ npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.6.11"]}'
595
738
  npx -y openclaw@latest mcp doctor cejel-oci --probe
596
739
  ```
597
740
 
@@ -754,7 +897,7 @@ scoring tool that has not been checked:
754
897
  the invariant it claimed to be. Rubric v3 removes that second path: every corpus row now
755
898
  passes through the same sealed public scorer, every external source is pinned to a commit,
756
899
  and a required guard re-scores every row and compares score, verdict, coverage, and
757
- evidence. The honest correction moved Alfred's Code score from 2.5 to 2.4 and A5 from 2.9
900
+ evidence. The honest correction moved our own private codebase's Code score from 2.5 to 2.4 and A5 from 2.9
758
901
  to 2.4. It is less flattering and more trustworthy.
759
902
  - **A tracked symlink could make evidence depend on files outside the checkout.** Repository
760
903
  discovery used a regular-file check that followed symlinks, so a tracked link could escape
@@ -5,11 +5,11 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-KJVFCZZ3.js";
8
+ } from "../chunk-4YOIR3E6.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
- } from "../chunk-WXPGEQBE.js";
12
- import "../chunk-NIWSYJ6N.js";
11
+ } from "../chunk-RRPQHEBU.js";
12
+ import "../chunk-2UMHJ23V.js";
13
13
  import {
14
14
  external_exports
15
15
  } from "../chunk-AMVP7L26.js";
@@ -8,8 +8,8 @@ import {
8
8
  // src/witan/git-exec.ts
9
9
  init_esm_shims();
10
10
  import { execFileSync } from "child_process";
11
- import { realpathSync } from "fs";
12
- import { dirname, resolve } from "path";
11
+ import { closeSync, lstatSync, openSync, readSync, realpathSync } from "fs";
12
+ import { dirname, join, resolve } from "path";
13
13
  var GIT_EXEC_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
14
14
  var GIT_EXEC_TIMEOUT_MS = 3e4;
15
15
  var HARDENED_GIT_ARGUMENTS = [
@@ -54,6 +54,59 @@ var HARDENED_GIT_ARGUMENTS = [
54
54
  "protocol.ssh.allow=never"
55
55
  ];
56
56
  var discoveryCeiling;
57
+ var GIT_FILE_PREFIX_BYTES = 4096;
58
+ function fsErrorCode(error) {
59
+ return typeof error === "object" && error !== null ? error.code : void 0;
60
+ }
61
+ var isDefinitelyAbsent = (error) => fsErrorCode(error) === "ENOENT" || fsErrorCode(error) === "ENOTDIR";
62
+ function isGitMetadataEntry(path) {
63
+ let link;
64
+ try {
65
+ link = lstatSync(path);
66
+ } catch (error) {
67
+ return !isDefinitelyAbsent(error);
68
+ }
69
+ if (link.isSymbolicLink()) return true;
70
+ if (link.isDirectory()) {
71
+ try {
72
+ lstatSync(join(path, "HEAD"));
73
+ return true;
74
+ } catch (error) {
75
+ return !isDefinitelyAbsent(error);
76
+ }
77
+ }
78
+ if (!link.isFile()) return false;
79
+ let descriptor;
80
+ try {
81
+ descriptor = openSync(path, "r");
82
+ const buffer = Buffer.alloc(GIT_FILE_PREFIX_BYTES);
83
+ const length = readSync(descriptor, buffer, 0, GIT_FILE_PREFIX_BYTES, 0);
84
+ return /^gitdir:/m.test(buffer.subarray(0, length).toString("utf8"));
85
+ } catch {
86
+ return true;
87
+ } finally {
88
+ if (descriptor !== void 0) closeSync(descriptor);
89
+ }
90
+ }
91
+ function hasVisibleGitMetadata(repoPath) {
92
+ return findVisibleGitMetadata(repoPath) !== void 0;
93
+ }
94
+ function findVisibleGitMetadata(repoPath) {
95
+ let current;
96
+ try {
97
+ current = realpathSync(repoPath);
98
+ } catch {
99
+ return void 0;
100
+ }
101
+ for (; ; ) {
102
+ if (discoveryCeiling !== void 0 && current === discoveryCeiling) return void 0;
103
+ const entry = join(current, ".git");
104
+ if (isGitMetadataEntry(entry)) return entry;
105
+ const parent = dirname(current);
106
+ if (parent === current) return void 0;
107
+ current = parent;
108
+ }
109
+ }
57
110
  function hardenedGitEnvironment(cwd) {
58
111
  const environment = {};
59
112
  for (const key of ["PATH", "HOME", "TZ"]) {
@@ -88,27 +141,32 @@ function errorField(error, key) {
88
141
  if (typeof error !== "object" || error === null) return void 0;
89
142
  return error[key];
90
143
  }
91
- function errorText(error) {
144
+ function stderrText(error) {
92
145
  const stderr = errorField(error, "stderr");
93
- const stderrText = typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString("utf8") : "";
94
- const message = error instanceof Error ? error.message : "";
95
- return `${stderrText}
96
- ${message}`;
146
+ return typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString("utf8") : "";
147
+ }
148
+ var GIT_NO_REPOSITORY_DIAGNOSTICS = [
149
+ /^fatal: not a git repository\b/i,
150
+ /^fatal: gitfile does not point to a valid repository: /,
151
+ /^fatal: not in a git directory$/,
152
+ /^fatal: this operation must be run in a work tree$/,
153
+ /^fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree\.$/,
154
+ /^fatal: needed a single revision$/i
155
+ ];
156
+ var GIT_UNBORN_BRANCH_DIAGNOSTIC = /^fatal: your current branch '' does not have any commits yet$/;
157
+ function isNoRepositoryDiagnostic(stderr) {
158
+ const line = stderr.split("\n").find((candidate) => candidate.startsWith("fatal: "))?.trimEnd();
159
+ if (line === void 0) return false;
160
+ if (GIT_NO_REPOSITORY_DIAGNOSTICS.some((pattern) => pattern.test(line))) return true;
161
+ const branchEmptied = line.replace(/^(fatal: your current branch )'[^'\n]*'/, "$1''");
162
+ return GIT_UNBORN_BRANCH_DIAGNOSTIC.test(branchEmptied);
97
163
  }
98
164
  function classifyGitFailure(error) {
99
165
  const code = errorField(error, "code");
100
- const signal = errorField(error, "signal");
101
- const text = errorText(error);
102
166
  if (code === "ENOENT") return "git_absent";
103
- if (code === "ENOBUFS" || /maxBuffer|ENOBUFS/i.test(text)) return "buffer_exceeded";
104
- if (code === "ETIMEDOUT" || signal === "SIGTERM" || /timed?\s*out|ETIMEDOUT/i.test(text)) {
105
- return "timeout";
106
- }
107
- if (/not a git repository|not inside a work tree|not in a git directory|unknown revision.*HEAD|ambiguous argument ['"]?HEAD|does not have any commits yet|needed a single revision/i.test(
108
- text
109
- )) {
110
- return "not_a_repo";
111
- }
167
+ if (code === "ENOBUFS" || code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "buffer_exceeded";
168
+ if (code === "ETIMEDOUT") return "timeout";
169
+ if (isNoRepositoryDiagnostic(stderrText(error))) return "not_a_repo";
112
170
  return "exec_failed";
113
171
  }
114
172
  function execGit(argv, options) {
@@ -154,6 +212,10 @@ function describeGitFailure(reason, operation, maxBufferBytes = GIT_EXEC_MAX_BUF
154
212
  }
155
213
 
156
214
  export {
215
+ GIT_EXEC_MAX_BUFFER_BYTES,
216
+ GIT_EXEC_TIMEOUT_MS,
217
+ hasVisibleGitMetadata,
218
+ findVisibleGitMetadata,
157
219
  execGit,
158
220
  isExpectedGitAbsence,
159
221
  describeGitFailure