@cejel/cejel 0.6.8 → 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
@@ -157,12 +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".
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.
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.
166
202
 
167
203
  For an executed public example that binds a release artifact to the same commit named by its
168
204
  Cejel certificate, see [Certified releases with GitHub build provenance](./docs/certified-release-example.md).
@@ -213,7 +249,7 @@ The [Cejel OSS trust leaderboard](https://cejel.dev/leaderboard/) is hosted on c
213
249
  which is the single current board. This repository no longer distributes a second copy of
214
250
  its scores or certificates. The board records its producing package version, rubric,
215
251
  run date, pinned public revisions, and reproduction instructions for that particular run.
216
- 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.
217
253
 
218
254
  The site's History section preserves the **2026-08-18 withdrawal**: the earlier
219
255
  sealed-public-scorer reproducibility claim was false. The stale prospective-rubric copy
@@ -392,7 +428,7 @@ Three ways a scanner's output gets ingested:
392
428
  `--ingest scorecard.json`; auto-detected by its `checks` array.
393
429
  3. **Generic JSON** — for a tool that emits neither, map its output into the minimal shape
394
430
  below (or write a small adapter mirroring
395
- [`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
396
432
  parsing):
397
433
 
398
434
  ```json
@@ -416,7 +452,7 @@ Three ways a scanner's output gets ingested:
416
452
  }
417
453
  ```
418
454
 
419
- `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`
420
456
  (0–1, default `0.5`) bounds how much this signal can move that dimension; `severity` is
421
457
  `critical` | `warning` | `info`; `location` is optional.
422
458
 
@@ -468,7 +504,10 @@ secrets.
468
504
  ```
469
505
 
470
506
  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.
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.
472
511
 
473
512
  ## Use with Claude
474
513
 
@@ -754,7 +793,7 @@ scoring tool that has not been checked:
754
793
  the invariant it claimed to be. Rubric v3 removes that second path: every corpus row now
755
794
  passes through the same sealed public scorer, every external source is pinned to a commit,
756
795
  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
796
+ evidence. The honest correction moved our own private codebase's Code score from 2.5 to 2.4 and A5 from 2.9
758
797
  to 2.4. It is less flattering and more trustworthy.
759
798
  - **A tracked symlink could make evidence depend on files outside the checkout.** Repository
760
799
  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-SNRQAKKE.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
- } from "../chunk-WXPGEQBE.js";
12
- import "../chunk-NIWSYJ6N.js";
11
+ } from "../chunk-UMGZWGOI.js";
12
+ import "../chunk-D7MWEPPX.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,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,7 +5,7 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "./chunk-KJVFCZZ3.js";
8
+ } from "./chunk-SNRQAKKE.js";
9
9
  import {
10
10
  ZodFirstPartyTypeKind,
11
11
  ZodOptional,