@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 +50 -11
- package/dist/calibration/llm-detector.js +3 -3
- package/dist/{chunk-NIWSYJ6N.js → chunk-D7MWEPPX.js} +75 -18
- package/dist/{chunk-KNTBED5T.js → chunk-LBJFDK4R.js} +1 -1
- package/dist/{chunk-KJVFCZZ3.js → chunk-SNRQAKKE.js} +13503 -11417
- package/dist/{chunk-WXPGEQBE.js → chunk-UMGZWGOI.js} +1 -1
- package/dist/index.js +5 -3
- package/dist/mcp/index.js +3 -3
- package/dist/packs/d-series/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
[](https://github.com/BargLabs/cejel/releases/latest)
|
|
4
4
|
[](./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
|
|
161
|
-
commit
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
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`](
|
|
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
|
|
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
|
|
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-
|
|
8
|
+
} from "../chunk-SNRQAKKE.js";
|
|
9
9
|
import {
|
|
10
10
|
listCejelLlmPackFiles
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
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
|
|
140
|
+
function stderrText(error) {
|
|
92
141
|
const stderr = errorField(error, "stderr");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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" ||
|
|
104
|
-
if (code === "ETIMEDOUT"
|
|
105
|
-
|
|
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
|