@cejel/cejel 0.6.7 → 0.6.10
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 +179 -17
- package/dist/calibration/llm-detector.js +5 -5
- package/dist/{chunk-VGBFZBZQ.js → chunk-2UMHJ23V.js} +81 -19
- package/dist/{chunk-RH6FKREX.js → chunk-6LVNNJTG.js} +1 -1
- package/dist/{chunk-FSMZV3Y6.js → chunk-AMVP7L26.js} +1 -1
- package/dist/{chunk-O3MHSK25.js → chunk-AWLKQUI4.js} +56 -6
- package/dist/{chunk-5PFYOIOS.js → chunk-RRPQHEBU.js} +2 -2
- package/dist/{chunk-TOQPQPDF.js → chunk-U2VDSTIT.js} +3 -3
- package/dist/chunk-YHJ7ICL4.js +23796 -0
- package/dist/index.js +163 -20
- package/dist/mcp/index.js +5 -5
- package/dist/packs/d-series/index.js +4 -4
- package/dist/packs/decision-contracts/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-ZZHQZW26.js +0 -11811
package/README.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
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
|
+
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.
|
|
166
|
+
> 0.6.10 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,7 +187,7 @@ 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.
|
|
190
|
+
> **Distribution note:** Cejel `0.6.10` is the coordinated release version for npm,
|
|
120
191
|
> standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. A GitLab CI
|
|
121
192
|
> template and Code Quality export live in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
|
|
122
193
|
|
|
@@ -157,8 +228,41 @@ The npm package is scoped as `@cejel/cejel`; its executable remains the short co
|
|
|
157
228
|
min-score: "2.5" # optional: fail the build below this
|
|
158
229
|
```
|
|
159
230
|
|
|
160
|
-
`actions/checkout` fetches one commit by default
|
|
161
|
-
|
|
231
|
+
`actions/checkout` fetches one commit by default, so clone in full with `fetch-depth: 0`. Under
|
|
232
|
+
every rubric, the calibrated default included, a shallow clone gets no composite score and no
|
|
233
|
+
verdict until the full history is checked out. The scan abstains (`insufficient_source`), still
|
|
234
|
+
reads and shows each criterion, and says what to do: run `git fetch --unshallow` (or check out with
|
|
235
|
+
full history, for example `fetch-depth: 0` in `actions/checkout`), then scan again. The
|
|
236
|
+
certificate, the badge and the Action's `verdict` output read "Unrated: shallow clone", and the
|
|
237
|
+
Action's `limited` output is `true`. With `--min-score` an abstained scan fails the check, as any
|
|
238
|
+
abstention does, and the message says how to fetch the history. If git cannot report the depth (git
|
|
239
|
+
is not installed, or cannot open the repository), Cejel reads it from the repository's `.git`
|
|
240
|
+
files; if those do not settle it either, the scan abstains the same way as "Unrated: history depth
|
|
241
|
+
unknown". Among the criteria shown on a shallow clone, A2's scans of earlier commits for `.env`
|
|
242
|
+
files and credentials read only the commits it holds, so A2 abstains unless a confirmed critical
|
|
243
|
+
secret finding stands, including where it would otherwise be marked not applicable, since that
|
|
244
|
+
decision also rests on earlier commits; B2 withholds its pull-request merge ratio, or abstains
|
|
245
|
+
where nothing else is left to score it on, saying why in its note. The certificate says so under
|
|
246
|
+
"History limitations". Until 2 November 2026 this held only under the opt-in prospective v27
|
|
247
|
+
rubric (`--rubric-pin witan-rubric-v27-prospective-2026-10-19`; v17 remains the default), and the
|
|
248
|
+
default rubric scored a shallow clone, on which A2 could not see a secret committed and later
|
|
249
|
+
deleted. A complete clone is scored exactly as before.
|
|
250
|
+
|
|
251
|
+
A directory that is not a git repository gets no composite score and no verdict under every
|
|
252
|
+
rubric, v27 included: "Unrated: no git history". That covers a directory with no `.git`, a
|
|
253
|
+
tarball, an archive export of a commit, and every snapshot uploaded to the HTTP or remote-MCP
|
|
254
|
+
server. A2 abstains, since no commit history was read, unless a confirmed critical secret finding
|
|
255
|
+
stands on the files. Scan a git clone with full history; an uploaded snapshot or archive carries no
|
|
256
|
+
history.
|
|
257
|
+
|
|
258
|
+
On a clone that is not shallow, A2 abstains under v27 where one of its reads of earlier commits failed:
|
|
259
|
+
for example on a partial clone made with `--filter`, which `fetch-depth: 0` and no `filter` avoid,
|
|
260
|
+
or where a read stops at one of Cejel's own limits: an output limit or the history secret scan's
|
|
261
|
+
cap of 5,000 historical files, which happen on some large complete histories and which a re-scan
|
|
262
|
+
of the same repository does not lift (#567), or the 30-second timeout, which a re-scan may lift.
|
|
263
|
+
It also abstains when it leaves a credential-shaped path in history unread because the path's name
|
|
264
|
+
contains control characters, and when git itself cannot be run on the scanning host. Each of these
|
|
265
|
+
abstentions counts as 0 in the composite, so they can lower the v27 score.
|
|
162
266
|
|
|
163
267
|
For an executed public example that binds a release artifact to the same commit named by its
|
|
164
268
|
Cejel certificate, see [Certified releases with GitHub build provenance](./docs/certified-release-example.md).
|
|
@@ -187,16 +291,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
|
|
|
187
291
|
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
188
292
|
Microsoft Authenticode code-signing.
|
|
189
293
|
|
|
190
|
-
**Docker / OCI.** The current container release is `0.6.
|
|
294
|
+
**Docker / OCI.** The current container release is `0.6.10`:
|
|
191
295
|
|
|
192
296
|
```bash
|
|
193
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.
|
|
297
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.10
|
|
194
298
|
```
|
|
195
299
|
|
|
196
300
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
197
301
|
|
|
198
302
|
```bash
|
|
199
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.
|
|
303
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.10 .
|
|
200
304
|
```
|
|
201
305
|
|
|
202
306
|
The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
|
|
@@ -209,7 +313,7 @@ The [Cejel OSS trust leaderboard](https://cejel.dev/leaderboard/) is hosted on c
|
|
|
209
313
|
which is the single current board. This repository no longer distributes a second copy of
|
|
210
314
|
its scores or certificates. The board records its producing package version, rubric,
|
|
211
315
|
run date, pinned public revisions, and reproduction instructions for that particular run.
|
|
212
|
-
The private
|
|
316
|
+
The board's row for Barg Labs' own private codebase is a transparency snapshot and is not independently reproducible.
|
|
213
317
|
|
|
214
318
|
The site's History section preserves the **2026-08-18 withdrawal**: the earlier
|
|
215
319
|
sealed-public-scorer reproducibility claim was false. The stale prospective-rubric copy
|
|
@@ -296,11 +400,17 @@ prints that boundary on every successful verification.
|
|
|
296
400
|
- `--ingest <file|glob>` — fold another scanner's output into the score (repeatable). Accepts
|
|
297
401
|
SARIF, OpenSSF Scorecard JSON, or the generic Cejel external-signal shape — format is
|
|
298
402
|
auto-detected. See "Aggregate your scanners" below.
|
|
403
|
+
- `--subject-repository <repo>` — name the scanned repository (`github.com/owner/repo` or a clone
|
|
404
|
+
URL) so an ingested input that declares another repository is recorded as mismatched. Cejel
|
|
405
|
+
never infers it from clone state.
|
|
406
|
+
- `--strict-ingest` — refuse, before scoring, an ingested input that declares another repository or
|
|
407
|
+
commit, was observed before the scanned commit existed, or duplicates another input. Without it,
|
|
408
|
+
the binding status is recorded and scoring is unchanged. See [input binding](docs/input-binding.md).
|
|
299
409
|
- `--rubric-pin <version>` — Explicit opt-in only: pin an alternate rubric instead of the
|
|
300
410
|
calibrated public default. It
|
|
301
411
|
accepts the calibrated version or any published prospective rubric
|
|
302
|
-
(`witan-rubric-v18-prospective-*` through `
|
|
303
|
-
anything else. `v26` (`witan-rubric-v26-prospective-2026-10-14`) is prospective and uncalibrated:
|
|
412
|
+
(`witan-rubric-v18-prospective-*` through `v27`) and fails closed, naming what was accepted, on
|
|
413
|
+
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:
|
|
304
414
|
it is the next calibration candidate, inherits v25 complete, inherits neither v23 nor v24, and
|
|
305
415
|
today scores exactly as v25 does. `v25` is prospective and uncalibrated: it inherits v22 and adds only credit for a
|
|
306
416
|
health or readiness route declared inline in an application file (see "What counts as a health or
|
|
@@ -382,7 +492,7 @@ Three ways a scanner's output gets ingested:
|
|
|
382
492
|
`--ingest scorecard.json`; auto-detected by its `checks` array.
|
|
383
493
|
3. **Generic JSON** — for a tool that emits neither, map its output into the minimal shape
|
|
384
494
|
below (or write a small adapter mirroring
|
|
385
|
-
[`scorecard-adapter.ts`](
|
|
495
|
+
[`scorecard-adapter.ts`](./src/witan/scorecard-adapter.ts) if the format needs real
|
|
386
496
|
parsing):
|
|
387
497
|
|
|
388
498
|
```json
|
|
@@ -406,7 +516,7 @@ Three ways a scanner's output gets ingested:
|
|
|
406
516
|
}
|
|
407
517
|
```
|
|
408
518
|
|
|
409
|
-
`dimension` is one of the
|
|
519
|
+
`dimension` is one of the rubric criterion ids (`A1`-`A5`, `B1`-`B6`); `weight`
|
|
410
520
|
(0–1, default `0.5`) bounds how much this signal can move that dimension; `severity` is
|
|
411
521
|
`critical` | `warning` | `info`; `location` is optional.
|
|
412
522
|
|
|
@@ -414,7 +524,17 @@ Three ways a scanner's output gets ingested:
|
|
|
414
524
|
versions rather than guessing. See the [generic ingest contract](docs/ingest-contract.md), its
|
|
415
525
|
[published JSON Schema](docs/schemas/cejel-ingest-v1.schema.json), and the
|
|
416
526
|
[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).
|
|
527
|
+
routing are documented in [format stability](docs/format-stability.md). Contract `1.1` adds
|
|
528
|
+
optional `subject`, `producer` and `observedAt` fields so the input can be bound to a revision.
|
|
529
|
+
|
|
530
|
+
Every ingested document is also recorded in `report.json` under `inputBinding`: the sha256 of its
|
|
531
|
+
bytes, the producer and subject its own format declares (Scorecard `repo.name`/`repo.commit`/`date`,
|
|
532
|
+
SARIF `versionControlProvenance` and `invocations`, generic `subject`/`observedAt`), and whether that
|
|
533
|
+
subject is the scanned HEAD (`matched`, `mismatched-repository`, `mismatched-commit`, `stale`,
|
|
534
|
+
`unbound`, `unverifiable`). By default this is a record, not a gate: it changes no score.
|
|
535
|
+
`--strict-ingest` refuses mismatched, stale and duplicate inputs instead. `cejel verify ... --input
|
|
536
|
+
<file>` checks a set of input files against the recorded digests, so a swapped input is detected.
|
|
537
|
+
See [input binding](docs/input-binding.md), which carries the conformance table the tests run.
|
|
418
538
|
|
|
419
539
|
## Displaying the trust badge
|
|
420
540
|
|
|
@@ -447,8 +567,50 @@ secrets.
|
|
|
447
567
|
min-score: '2.5' # optional; omit to never fail the check
|
|
448
568
|
```
|
|
449
569
|
|
|
450
|
-
Check out with `fetch-depth: 0
|
|
451
|
-
the
|
|
570
|
+
Check out with `fetch-depth: 0`. The default depth of 1 is a shallow clone, and under every
|
|
571
|
+
rubric, the default included, a shallow checkout gets no composite score and no verdict: the step
|
|
572
|
+
summary, the badge and the `verdict` output read "Unrated: shallow clone", the `limited` output is
|
|
573
|
+
`true` and `limitations` names the cause, and the scan says to run `git fetch --unshallow` (or check
|
|
574
|
+
out with full history, for example `fetch-depth: 0` in `actions/checkout`), then scan again. On a
|
|
575
|
+
shallow clone A2's scans of earlier commits read only the commits present and B2 withholds its
|
|
576
|
+
pull-request merge ratio, each with its reason. A `repo-path` that is not a git repository is
|
|
577
|
+
"Unrated: no git history" under every rubric. The Action does not pin a rubric, so it scores under
|
|
578
|
+
the default.
|
|
579
|
+
|
|
580
|
+
| Output | What it holds |
|
|
581
|
+
|---|---|
|
|
582
|
+
| `score` | The overall score (0.0–4.0), or empty when Cejel abstains. |
|
|
583
|
+
| `verdict` | The verdict label, or the abstention label (`Insufficient source`, `Insufficient evidence`, `Unrated: shallow clone`, `Unrated: history depth unknown`, `Unrated: no git history`). |
|
|
584
|
+
| `limited` | `true` when scan limitations qualify the result, or when the verdict is Unrated; otherwise `false`. |
|
|
585
|
+
| `limitation-count` | How many limitations qualify the certificate, counting an Unrated verdict as one. |
|
|
586
|
+
| `limitations` | A JSON array of the limitation messages; an Unrated verdict adds one naming its cause. |
|
|
587
|
+
| `artifact-dir` | Where this run's report artifacts were exported. |
|
|
588
|
+
|
|
589
|
+
| Input | Default | What it does |
|
|
590
|
+
|---|---|---|
|
|
591
|
+
| `repo-path` | the checkout (`$GITHUB_WORKSPACE`) | Path to the repository to score. |
|
|
592
|
+
| `min-score` | empty: never fail | Fails the step below this overall score (0–4), or when Cejel abstains. |
|
|
593
|
+
| `out-dir` | `.cejel` | Empty destination for this run's report artifacts. |
|
|
594
|
+
| `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. |
|
|
595
|
+
|
|
596
|
+
The scoring step is offline whatever the inputs; it never fetches history. To opt in to the
|
|
597
|
+
prospective v27 rubric, pin it and check out in full with `fetch-depth: 0`: v27 reads the commit
|
|
598
|
+
history, so a shallow checkout gets no full v27 result, as under the default. v17 remains the
|
|
599
|
+
calibrated default.
|
|
600
|
+
|
|
601
|
+
```yaml
|
|
602
|
+
- uses: actions/checkout@v4
|
|
603
|
+
with:
|
|
604
|
+
fetch-depth: 0 # full history: A2 and B2 read commit history
|
|
605
|
+
- uses: BargLabs/cejel/action@v1
|
|
606
|
+
with:
|
|
607
|
+
rubric-pin: witan-rubric-v27-prospective-2026-10-19
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
A prospective pin carries no precision/recall claim, and the certificate states that plainly
|
|
611
|
+
whenever one is used. Leave `rubric-pin` empty for the calibrated result every default scan
|
|
612
|
+
produces: an empty input passes no `--rubric-pin`, so the scan, the report and the outputs are
|
|
613
|
+
the same as without the input.
|
|
452
614
|
|
|
453
615
|
## Use with Claude
|
|
454
616
|
|
|
@@ -571,7 +733,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
|
|
|
571
733
|
the host path with the repository OpenClaw should allow Cejel to read:
|
|
572
734
|
|
|
573
735
|
```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.
|
|
736
|
+
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.10"]}'
|
|
575
737
|
npx -y openclaw@latest mcp doctor cejel-oci --probe
|
|
576
738
|
```
|
|
577
739
|
|
|
@@ -734,7 +896,7 @@ scoring tool that has not been checked:
|
|
|
734
896
|
the invariant it claimed to be. Rubric v3 removes that second path: every corpus row now
|
|
735
897
|
passes through the same sealed public scorer, every external source is pinned to a commit,
|
|
736
898
|
and a required guard re-scores every row and compares score, verdict, coverage, and
|
|
737
|
-
evidence. The honest correction moved
|
|
899
|
+
evidence. The honest correction moved our own private codebase's Code score from 2.5 to 2.4 and A5 from 2.9
|
|
738
900
|
to 2.4. It is less flattering and more trustworthy.
|
|
739
901
|
- **A tracked symlink could make evidence depend on files outside the checkout.** Repository
|
|
740
902
|
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-
|
|
8
|
+
} from "../chunk-YHJ7ICL4.js";
|
|
9
9
|
import {
|
|
10
10
|
listCejelLlmPackFiles
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-RRPQHEBU.js";
|
|
12
|
+
import "../chunk-2UMHJ23V.js";
|
|
13
13
|
import {
|
|
14
14
|
external_exports
|
|
15
|
-
} from "../chunk-
|
|
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-
|
|
21
|
+
} from "../chunk-AWLKQUI4.js";
|
|
22
22
|
|
|
23
23
|
// src/calibration/llm-detector.ts
|
|
24
24
|
init_esm_shims();
|
|
@@ -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-
|
|
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,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
|
|
144
|
+
function stderrText(error) {
|
|
92
145
|
const stderr = errorField(error, "stderr");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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" ||
|
|
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
|
-
}
|
|
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
|
|
@@ -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([
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
describeGitFailure,
|
|
6
6
|
execGit,
|
|
7
7
|
isExpectedGitAbsence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2UMHJ23V.js";
|
|
9
9
|
import {
|
|
10
10
|
init_esm_shims
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AWLKQUI4.js";
|
|
12
12
|
|
|
13
13
|
// src/packs/llm/files.ts
|
|
14
14
|
init_esm_shims();
|
|
@@ -5,19 +5,19 @@ import {
|
|
|
5
5
|
renderWitanBadgeSvg,
|
|
6
6
|
renderWitanHtmlReport,
|
|
7
7
|
runCejelScan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YHJ7ICL4.js";
|
|
9
9
|
import {
|
|
10
10
|
ZodFirstPartyTypeKind,
|
|
11
11
|
ZodOptional,
|
|
12
12
|
external_exports,
|
|
13
13
|
objectType
|
|
14
|
-
} from "./chunk-
|
|
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-
|
|
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({
|