@cejel/cejel 0.6.7 → 0.7.0-rc.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 CHANGED
@@ -3,6 +3,30 @@
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
+ Or run it once from a shell: `npx @cejel/cejel@latest .` More on the Action under
28
+ [GitHub Action](#github-action).
29
+
6
30
  Cejel (*"SEH-jel"*) — a trust certificate for your codebase.
7
31
 
8
32
  Free, offline, no-signup CLI that scores the engineering signals that tell you whether to
@@ -92,7 +116,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
92
116
  ```
93
117
 
94
118
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
95
- > 0.6.7 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
119
+ > 0.6.8 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
96
120
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
97
121
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
98
122
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM (the packages
@@ -116,7 +140,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
116
140
  npx @cejel/cejel@latest .
117
141
  ```
118
142
 
119
- > **Distribution note:** Cejel `0.6.7` is the coordinated release version for npm,
143
+ > **Distribution note:** Cejel `0.6.8` is the coordinated release version for npm,
120
144
  > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. A GitLab CI
121
145
  > template and Code Quality export live in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
122
146
 
@@ -157,8 +181,24 @@ The npm package is scoped as `@cejel/cejel`; its executable remains the short co
157
181
  min-score: "2.5" # optional: fail the build below this
158
182
  ```
159
183
 
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".
184
+ `actions/checkout` fetches one commit by default, so clone in full with `fetch-depth: 0`. On a
185
+ shallow clone the criteria that read commit history see only the commits it holds: A2's scans of
186
+ earlier commits for `.env` files and credentials read only those, and B2 withholds its
187
+ pull-request merge ratio, or abstains where nothing else is left to score it on, saying why in its
188
+ note. The certificate says so under "History limitations".
189
+ Under the opt-in prospective v27 rubric (`--rubric-pin witan-rubric-v27-prospective-2026-10-19`;
190
+ v17 remains the default), A2 abstains on a shallow clone unless a confirmed critical secret finding
191
+ stands, including where it would otherwise be marked not applicable, since that decision also rests
192
+ on earlier commits. That abstention is left out of the composite, but the verdict is capped at the
193
+ band the composite reaches with it counted as 0.
194
+ A2 also abstains under v27 on any clone where one of its reads of earlier commits failed:
195
+ for example on a partial clone made with `--filter`, which `fetch-depth: 0` and no `filter` avoid,
196
+ or where a read stops at one of Cejel's own limits: an output limit or the history secret scan's
197
+ cap of 5,000 historical files, which happen on some large complete histories and which a re-scan
198
+ of the same repository does not lift (#567), or the 30-second timeout, which a re-scan may lift.
199
+ It also abstains when it leaves a credential-shaped path in history unread because the path's name
200
+ contains control characters, and when git itself cannot be run on the scanning host. Each of these
201
+ abstentions counts as 0 in the composite, so they can lower the v27 score.
162
202
 
163
203
  For an executed public example that binds a release artifact to the same commit named by its
164
204
  Cejel certificate, see [Certified releases with GitHub build provenance](./docs/certified-release-example.md).
@@ -187,16 +227,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
187
227
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
188
228
  Microsoft Authenticode code-signing.
189
229
 
190
- **Docker / OCI.** The current container release is `0.6.7`:
230
+ **Docker / OCI.** The current container release is `0.6.8`:
191
231
 
192
232
  ```bash
193
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.7
233
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.8
194
234
  ```
195
235
 
196
236
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
197
237
 
198
238
  ```bash
199
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.7 .
239
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.8 .
200
240
  ```
201
241
 
202
242
  The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
@@ -209,7 +249,7 @@ The [Cejel OSS trust leaderboard](https://cejel.dev/leaderboard/) is hosted on c
209
249
  which is the single current board. This repository no longer distributes a second copy of
210
250
  its scores or certificates. The board records its producing package version, rubric,
211
251
  run date, pinned public revisions, and reproduction instructions for that particular run.
212
- The private Alfred transparency snapshot is not independently reproducible.
252
+ The board's row for Barg Labs' own private codebase is a transparency snapshot and is not independently reproducible.
213
253
 
214
254
  The site's History section preserves the **2026-08-18 withdrawal**: the earlier
215
255
  sealed-public-scorer reproducibility claim was false. The stale prospective-rubric copy
@@ -296,6 +336,12 @@ prints that boundary on every successful verification.
296
336
  - `--ingest <file|glob>` — fold another scanner's output into the score (repeatable). Accepts
297
337
  SARIF, OpenSSF Scorecard JSON, or the generic Cejel external-signal shape — format is
298
338
  auto-detected. See "Aggregate your scanners" below.
339
+ - `--subject-repository <repo>` — name the scanned repository (`github.com/owner/repo` or a clone
340
+ URL) so an ingested input that declares another repository is recorded as mismatched. Cejel
341
+ never infers it from clone state.
342
+ - `--strict-ingest` — refuse, before scoring, an ingested input that declares another repository or
343
+ commit, was observed before the scanned commit existed, or duplicates another input. Without it,
344
+ the binding status is recorded and scoring is unchanged. See [input binding](docs/input-binding.md).
299
345
  - `--rubric-pin <version>` — Explicit opt-in only: pin an alternate rubric instead of the
300
346
  calibrated public default. It
301
347
  accepts the calibrated version or any published prospective rubric
@@ -382,7 +428,7 @@ Three ways a scanner's output gets ingested:
382
428
  `--ingest scorecard.json`; auto-detected by its `checks` array.
383
429
  3. **Generic JSON** — for a tool that emits neither, map its output into the minimal shape
384
430
  below (or write a small adapter mirroring
385
- [`scorecard-adapter.ts`](../witan/src/scorecard-adapter.ts) if the format needs real
431
+ [`scorecard-adapter.ts`](./src/witan/scorecard-adapter.ts) if the format needs real
386
432
  parsing):
387
433
 
388
434
  ```json
@@ -406,7 +452,7 @@ Three ways a scanner's output gets ingested:
406
452
  }
407
453
  ```
408
454
 
409
- `dimension` is one of the Witan rubric criterion ids (`A1`-`A5`, `B1`-`B6`); `weight`
455
+ `dimension` is one of the rubric criterion ids (`A1`-`A5`, `B1`-`B6`); `weight`
410
456
  (0–1, default `0.5`) bounds how much this signal can move that dimension; `severity` is
411
457
  `critical` | `warning` | `info`; `location` is optional.
412
458
 
@@ -414,7 +460,17 @@ Three ways a scanner's output gets ingested:
414
460
  versions rather than guessing. See the [generic ingest contract](docs/ingest-contract.md), its
415
461
  [published JSON Schema](docs/schemas/cejel-ingest-v1.schema.json), and the
416
462
  [synthetic CI heal-log mapping](docs/heal-log-mapping.md). Output-field stability and version
417
- routing are documented in [format stability](docs/format-stability.md).
463
+ routing are documented in [format stability](docs/format-stability.md). Contract `1.1` adds
464
+ optional `subject`, `producer` and `observedAt` fields so the input can be bound to a revision.
465
+
466
+ Every ingested document is also recorded in `report.json` under `inputBinding`: the sha256 of its
467
+ bytes, the producer and subject its own format declares (Scorecard `repo.name`/`repo.commit`/`date`,
468
+ SARIF `versionControlProvenance` and `invocations`, generic `subject`/`observedAt`), and whether that
469
+ subject is the scanned HEAD (`matched`, `mismatched-repository`, `mismatched-commit`, `stale`,
470
+ `unbound`, `unverifiable`). By default this is a record, not a gate: it changes no score.
471
+ `--strict-ingest` refuses mismatched, stale and duplicate inputs instead. `cejel verify ... --input
472
+ <file>` checks a set of input files against the recorded digests, so a swapped input is detected.
473
+ See [input binding](docs/input-binding.md), which carries the conformance table the tests run.
418
474
 
419
475
  ## Displaying the trust badge
420
476
 
@@ -448,7 +504,10 @@ secrets.
448
504
  ```
449
505
 
450
506
  Check out with `fetch-depth: 0`: the default depth of 1 truncates the history A2 and B2 read, and
451
- the certificate then lists a history limitation.
507
+ the certificate then lists a history limitation. On a shallow clone A2's scans of earlier commits
508
+ read only the commits present and B2 withholds its pull-request merge ratio, each with its reason;
509
+ under the opt-in `witan-rubric-v27-prospective-2026-10-19` rubric, A2 abstains and the verdict is
510
+ capped where A2 could not be read.
452
511
 
453
512
  ## Use with Claude
454
513
 
@@ -571,7 +630,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
571
630
  the host path with the repository OpenClaw should allow Cejel to read:
572
631
 
573
632
  ```bash
574
- 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.7"]}'
633
+ 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"]}'
575
634
  npx -y openclaw@latest mcp doctor cejel-oci --probe
576
635
  ```
577
636
 
@@ -734,7 +793,7 @@ scoring tool that has not been checked:
734
793
  the invariant it claimed to be. Rubric v3 removes that second path: every corpus row now
735
794
  passes through the same sealed public scorer, every external source is pinned to a commit,
736
795
  and a required guard re-scores every row and compares score, verdict, coverage, and
737
- evidence. The honest correction moved Alfred's Code score from 2.5 to 2.4 and A5 from 2.9
796
+ evidence. The honest correction moved our own private codebase's Code score from 2.5 to 2.4 and A5 from 2.9
738
797
  to 2.4. It is less flattering and more trustworthy.
739
798
  - **A tracked symlink could make evidence depend on files outside the checkout.** Repository
740
799
  discovery used a regular-file check that followed symlinks, so a tracked link could escape
@@ -5,20 +5,20 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-ZZHQZW26.js";
8
+ } from "../chunk-SNRQAKKE.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
- } from "../chunk-5PFYOIOS.js";
12
- import "../chunk-VGBFZBZQ.js";
11
+ } from "../chunk-UMGZWGOI.js";
12
+ import "../chunk-D7MWEPPX.js";
13
13
  import {
14
14
  external_exports
15
- } from "../chunk-FSMZV3Y6.js";
15
+ } from "../chunk-AMVP7L26.js";
16
16
  import {
17
17
  init_esm_shims,
18
18
  readRepoText,
19
19
  recordContentSkip,
20
20
  recordFilesystemSkip
21
- } from "../chunk-O3MHSK25.js";
21
+ } from "../chunk-AWLKQUI4.js";
22
22
 
23
23
  // src/calibration/llm-detector.ts
24
24
  init_esm_shims();
@@ -7,7 +7,7 @@ import {
7
7
  __filename,
8
8
  __require,
9
9
  init_esm_shims
10
- } from "./chunk-O3MHSK25.js";
10
+ } from "./chunk-AWLKQUI4.js";
11
11
 
12
12
  // node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/typescript.js
13
13
  var require_typescript = __commonJS({
@@ -4,7 +4,7 @@ const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  __export,
6
6
  init_esm_shims
7
- } from "./chunk-O3MHSK25.js";
7
+ } from "./chunk-AWLKQUI4.js";
8
8
 
9
9
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
10
10
  var external_exports = {};
@@ -62,7 +62,11 @@ var MAX_REPOSITORY_CONTENT_BYTES = 512e3;
62
62
  init_esm_shims();
63
63
  import { closeSync, lstatSync, openSync, readFileSync, readSync } from "fs";
64
64
  import { resolve } from "path";
65
- var READ_FAILURE_REASONS = /* @__PURE__ */ new Set(["unreadable", "denied_path"]);
65
+ var READ_FAILURE_REASONS = /* @__PURE__ */ new Set([
66
+ "unreadable",
67
+ "denied_path",
68
+ "lfs_pointer"
69
+ ]);
66
70
  function isReadFailureReason(reason) {
67
71
  return READ_FAILURE_REASONS.has(reason);
68
72
  }
@@ -131,7 +135,10 @@ function summaryFor(session) {
131
135
  tooLarge: session.counts.too_large,
132
136
  excludedByExtension: session.counts.excluded_by_extension,
133
137
  deniedPath: session.counts.denied_path,
134
- nonRegularFile: session.counts.non_regular_file
138
+ nonRegularFile: session.counts.non_regular_file,
139
+ // Present only when non-zero, so every report without an LFS pointer is byte-identical to one
140
+ // produced before this reason existed (report format 1.6).
141
+ ...session.counts.lfs_pointer > 0 ? { lfsPointer: session.counts.lfs_pointer } : {}
135
142
  };
136
143
  const sortedErrnos = Object.fromEntries(
137
144
  [...session.unreadableByErrno.entries()].sort(([left], [right]) => left.localeCompare(right))
@@ -143,7 +150,7 @@ function summaryFor(session) {
143
150
  affectedCriteria: [...session.affectedCriteria].sort()
144
151
  };
145
152
  }
146
- function trackContentReads(_repoPath, collect) {
153
+ function trackContentReads(_repoPath, collect, options = {}) {
147
154
  const existing = activeSession;
148
155
  if (existing) {
149
156
  return {
@@ -162,8 +169,10 @@ function trackContentReads(_repoPath, collect) {
162
169
  too_large: 0,
163
170
  excluded_by_extension: 0,
164
171
  denied_path: 0,
165
- non_regular_file: 0
172
+ non_regular_file: 0,
173
+ lfs_pointer: 0
166
174
  },
175
+ lfsPointerAbstention: options.lfsPointerAbstention === true,
167
176
  unreadableByErrno: /* @__PURE__ */ new Map(),
168
177
  deduplicatedSkips: /* @__PURE__ */ new Set(),
169
178
  affectedCriteria: /* @__PURE__ */ new Set(),
@@ -218,6 +227,20 @@ function withContentReadSignals(criterionId, signalIds, collect) {
218
227
  session.signals = previous;
219
228
  }
220
229
  }
230
+ function withoutContentReadAttribution(collect) {
231
+ const session = activeSession;
232
+ if (!session) return collect();
233
+ const previousCriterion = session.criterion;
234
+ const previousSignals = session.signals;
235
+ session.criterion = void 0;
236
+ session.signals = void 0;
237
+ try {
238
+ return collect();
239
+ } finally {
240
+ session.criterion = previousCriterion;
241
+ session.signals = previousSignals;
242
+ }
243
+ }
221
244
  function recordContentSkip(path2, reason, deduplicate = false) {
222
245
  recordSkip(path2, reason, void 0, false, deduplicate);
223
246
  }
@@ -287,12 +310,33 @@ function readRepoText(path2, encoding = "utf8") {
287
310
  recordFilesystemSkip(path2, error);
288
311
  return "";
289
312
  }
313
+ let contents;
290
314
  try {
291
- return readFileSync(path2, encoding);
315
+ contents = readFileSync(path2, encoding);
292
316
  } catch (error) {
293
317
  recordFilesystemSkip(path2, error);
294
318
  return "";
295
319
  }
320
+ if (isGitLfsPointerText(contents)) {
321
+ recordSkip(path2, "lfs_pointer", void 0, activeSession?.lfsPointerAbstention === true, true);
322
+ }
323
+ return contents;
324
+ }
325
+ var GIT_LFS_POINTER_MAX_BYTES = 1024;
326
+ var GIT_LFS_POINTER_VERSION_LINE = /^version https:\/\/(?:git-lfs\.github\.com\/spec\/v1|hawser\.github\.com\/spec\/v1)\n/;
327
+ var GIT_LFS_POINTER_OID_LINE = /^oid sha256:[0-9a-f]{64}$/m;
328
+ var GIT_LFS_POINTER_SIZE_LINE = /^size [0-9]+$/m;
329
+ var GIT_LFS_POINTER_KEY_LINE = /^[a-z0-9.-]+ \S[^\n]*$/;
330
+ function isGitLfsPointerText(contents) {
331
+ if (isLfGitLfsPointerText(contents)) return true;
332
+ return contents.length < 2 * GIT_LFS_POINTER_MAX_BYTES && contents.includes("\r\n") && !/\r(?!\n)/.test(contents) && !/(?:^|[^\r])\n/.test(contents) && isLfGitLfsPointerText(contents.replaceAll("\r\n", "\n"));
333
+ }
334
+ function isLfGitLfsPointerText(contents) {
335
+ if (contents.length >= GIT_LFS_POINTER_MAX_BYTES || !contents.startsWith("version ")) return false;
336
+ if (!GIT_LFS_POINTER_VERSION_LINE.test(contents)) return false;
337
+ const lines = contents.replace(/\n$/, "").split("\n");
338
+ if (!lines.slice(1).every((line) => GIT_LFS_POINTER_KEY_LINE.test(line))) return false;
339
+ return GIT_LFS_POINTER_OID_LINE.test(contents) && GIT_LFS_POINTER_SIZE_LINE.test(contents);
296
340
  }
297
341
  function readRepoTextPrefix(path2, byteLimit) {
298
342
  try {
@@ -310,7 +354,11 @@ function readRepoTextPrefix(path2, byteLimit) {
310
354
  descriptor = openSync(path2, "r");
311
355
  const buffer = Buffer.allocUnsafe(byteLimit);
312
356
  const bytesRead = readSync(descriptor, buffer, 0, byteLimit, 0);
313
- return buffer.subarray(0, bytesRead).toString("utf8");
357
+ const contents = buffer.subarray(0, bytesRead).toString("utf8");
358
+ if (bytesRead < byteLimit && isGitLfsPointerText(contents)) {
359
+ recordSkip(path2, "lfs_pointer", void 0, activeSession?.lfsPointerAbstention === true, true);
360
+ }
361
+ return contents;
314
362
  } catch (error) {
315
363
  recordFilesystemSkip(path2, error);
316
364
  return "";
@@ -340,11 +388,13 @@ export {
340
388
  withContentReadCriterion,
341
389
  withContentReadSignal,
342
390
  withContentReadSignals,
391
+ withoutContentReadAttribution,
343
392
  recordContentSkip,
344
393
  registerWithheldRepoPath,
345
394
  withheldRepoPaths,
346
395
  abstainSignalOnWithheldPaths,
347
396
  recordFilesystemSkip,
348
397
  readRepoText,
398
+ isGitLfsPointerText,
349
399
  readRepoTextPrefix
350
400
  };
@@ -3,13 +3,13 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  init_esm_shims
6
- } from "./chunk-O3MHSK25.js";
6
+ } from "./chunk-AWLKQUI4.js";
7
7
 
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,55 @@ 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
+ let current;
93
+ try {
94
+ current = realpathSync(repoPath);
95
+ } catch {
96
+ return false;
97
+ }
98
+ for (; ; ) {
99
+ if (discoveryCeiling !== void 0 && current === discoveryCeiling) return false;
100
+ if (isGitMetadataEntry(join(current, ".git"))) return true;
101
+ const parent = dirname(current);
102
+ if (parent === current) return false;
103
+ current = parent;
104
+ }
105
+ }
57
106
  function hardenedGitEnvironment(cwd) {
58
107
  const environment = {};
59
108
  for (const key of ["PATH", "HOME", "TZ"]) {
@@ -88,27 +137,32 @@ function errorField(error, key) {
88
137
  if (typeof error !== "object" || error === null) return void 0;
89
138
  return error[key];
90
139
  }
91
- function errorText(error) {
140
+ function stderrText(error) {
92
141
  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}`;
142
+ return typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString("utf8") : "";
143
+ }
144
+ var GIT_NO_REPOSITORY_DIAGNOSTICS = [
145
+ /^fatal: not a git repository\b/i,
146
+ /^fatal: gitfile does not point to a valid repository: /,
147
+ /^fatal: not in a git directory$/,
148
+ /^fatal: this operation must be run in a work tree$/,
149
+ /^fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree\.$/,
150
+ /^fatal: needed a single revision$/i
151
+ ];
152
+ var GIT_UNBORN_BRANCH_DIAGNOSTIC = /^fatal: your current branch '' does not have any commits yet$/;
153
+ function isNoRepositoryDiagnostic(stderr) {
154
+ const line = stderr.split("\n").find((candidate) => candidate.startsWith("fatal: "))?.trimEnd();
155
+ if (line === void 0) return false;
156
+ if (GIT_NO_REPOSITORY_DIAGNOSTICS.some((pattern) => pattern.test(line))) return true;
157
+ const branchEmptied = line.replace(/^(fatal: your current branch )'[^'\n]*'/, "$1''");
158
+ return GIT_UNBORN_BRANCH_DIAGNOSTIC.test(branchEmptied);
97
159
  }
98
160
  function classifyGitFailure(error) {
99
161
  const code = errorField(error, "code");
100
- const signal = errorField(error, "signal");
101
- const text = errorText(error);
102
162
  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
- }
163
+ if (code === "ENOBUFS" || code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "buffer_exceeded";
164
+ if (code === "ETIMEDOUT") return "timeout";
165
+ if (isNoRepositoryDiagnostic(stderrText(error))) return "not_a_repo";
112
166
  return "exec_failed";
113
167
  }
114
168
  function execGit(argv, options) {
@@ -154,6 +208,9 @@ function describeGitFailure(reason, operation, maxBufferBytes = GIT_EXEC_MAX_BUF
154
208
  }
155
209
 
156
210
  export {
211
+ GIT_EXEC_MAX_BUFFER_BYTES,
212
+ GIT_EXEC_TIMEOUT_MS,
213
+ hasVisibleGitMetadata,
157
214
  execGit,
158
215
  isExpectedGitAbsence,
159
216
  describeGitFailure
@@ -5,19 +5,19 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "./chunk-ZZHQZW26.js";
8
+ } from "./chunk-SNRQAKKE.js";
9
9
  import {
10
10
  ZodFirstPartyTypeKind,
11
11
  ZodOptional,
12
12
  external_exports,
13
13
  objectType
14
- } from "./chunk-FSMZV3Y6.js";
14
+ } from "./chunk-AMVP7L26.js";
15
15
  import {
16
16
  __commonJS,
17
17
  __export,
18
18
  __toESM,
19
19
  init_esm_shims
20
- } from "./chunk-O3MHSK25.js";
20
+ } from "./chunk-AWLKQUI4.js";
21
21
 
22
22
  // node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
23
23
  var require_code = __commonJS({