@cejel/cejel 0.2.1 → 0.2.2
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 +84 -11
- package/dist/{chunk-BR2DO4QJ.js → chunk-SNDRU52Z.js} +32 -6
- package/dist/index.js +6 -2
- package/dist/mcp/index.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,29 @@ chmod +x cejel
|
|
|
49
49
|
./cejel .
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
**Windows x86_64 (PowerShell).**
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
$asset = "cejel-Windows-x86_64.exe"
|
|
56
|
+
Invoke-WebRequest "https://github.com/BargLabs/cejel/releases/latest/download/$asset" -OutFile $asset
|
|
57
|
+
Invoke-WebRequest "https://github.com/BargLabs/cejel/releases/latest/download/SHA256SUMS" -OutFile SHA256SUMS
|
|
58
|
+
$expected = ((Select-String -Path SHA256SUMS -Pattern " $([regex]::Escape($asset))$").Line -split "\s+")[0]
|
|
59
|
+
$actual = (Get-FileHash -Algorithm SHA256 $asset).Hash.ToLowerInvariant()
|
|
60
|
+
if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
|
|
61
|
+
throw "SHA-256 verification failed for $asset"
|
|
62
|
+
}
|
|
63
|
+
.\cejel-Windows-x86_64.exe .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
|
|
67
|
+
> 0.2.2 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
|
|
68
|
+
> signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
|
|
69
|
+
> it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
|
|
70
|
+
> GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
|
|
71
|
+
> own-platform verification receipt covering `--version`, `--help`, a real scan,
|
|
72
|
+
> source/binary parity, and a network-denied scan. If your policy requires Authenticode,
|
|
73
|
+
> use the npm package or the OCI image until a human-approved signing path is available.
|
|
74
|
+
|
|
52
75
|
**Don't take the offline claim on trust — check it.** Turn your network off, then run the
|
|
53
76
|
binary. It will score your repository and write you a certificate anyway. That is the whole
|
|
54
77
|
product, and you can falsify it in ten seconds:
|
|
@@ -61,7 +84,17 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
|
|
|
61
84
|
**npm.**
|
|
62
85
|
|
|
63
86
|
```bash
|
|
64
|
-
npx @cejel/cejel .
|
|
87
|
+
npx @cejel/cejel@latest .
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **Distribution note:** Cejel `0.2.2` is the coordinated release version for npm,
|
|
91
|
+
> standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
|
|
92
|
+
|
|
93
|
+
`npx` can reuse a stale cached package. Force the current npm release with the `@latest`
|
|
94
|
+
specifier above, and check the version that will run before comparing certificates:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx @cejel/cejel@latest --version
|
|
65
98
|
```
|
|
66
99
|
|
|
67
100
|
If a root `package.json` still carries a template name, override only the certificate display
|
|
@@ -89,30 +122,30 @@ pnpm install && pnpm build
|
|
|
89
122
|
node dist/index.js .
|
|
90
123
|
```
|
|
91
124
|
|
|
92
|
-
Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`,
|
|
93
|
-
`cejel-Linux-x86_64`. The release also carries
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`,
|
|
126
|
+
`cejel-Linux-x86_64`, and `cejel-Windows-x86_64.exe`. The release also carries
|
|
127
|
+
`SHA256SUMS`, a per-binary SPDX SBOM, and an own-platform verification receipt. Each binary
|
|
128
|
+
is executed against the source build and with networking denied before attachment. Releases
|
|
129
|
+
from v0.1.6 also carry a Sigstore bundle containing GitHub's signed build-provenance
|
|
130
|
+
attestation for the release set. Verify a downloaded binary with:
|
|
97
131
|
|
|
98
132
|
```bash
|
|
99
|
-
gh attestation verify ./cejel-
|
|
133
|
+
gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
|
|
100
134
|
```
|
|
101
135
|
|
|
102
136
|
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
103
137
|
Microsoft Authenticode code-signing.
|
|
104
138
|
|
|
105
|
-
**Docker / OCI.** The current container release is `0.2.
|
|
106
|
-
versioned independently from npm-only patches):
|
|
139
|
+
**Docker / OCI.** The current container release is `0.2.2`:
|
|
107
140
|
|
|
108
141
|
```bash
|
|
109
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.2.
|
|
142
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.2.2
|
|
110
143
|
```
|
|
111
144
|
|
|
112
145
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
113
146
|
|
|
114
147
|
```bash
|
|
115
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.2.
|
|
148
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.2.2 .
|
|
116
149
|
```
|
|
117
150
|
|
|
118
151
|
The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
|
|
@@ -360,6 +393,46 @@ The server exposes one tool and two resources:
|
|
|
360
393
|
Like the CLI, scoring over MCP is fully offline: no network calls, no telemetry, no signup,
|
|
361
394
|
and the server writes no files.
|
|
362
395
|
|
|
396
|
+
## Install on OpenClaw
|
|
397
|
+
|
|
398
|
+
OpenClaw stores outbound MCP servers under `mcp.servers`. Add Cejel with the npm package's
|
|
399
|
+
shipped `cejel-mcp` bin:
|
|
400
|
+
|
|
401
|
+
```json
|
|
402
|
+
{
|
|
403
|
+
"mcp": {
|
|
404
|
+
"servers": {
|
|
405
|
+
"cejel": {
|
|
406
|
+
"command": "npx",
|
|
407
|
+
"args": ["-y", "--package=@cejel/cejel", "cejel-mcp"]
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
The equivalent OpenClaw command is:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
openclaw mcp add cejel --command npx --arg -y --arg --package=@cejel/cejel --arg cejel-mcp
|
|
418
|
+
openclaw mcp doctor cejel --probe
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The OCI image is an alternative when Docker is the preferred execution boundary. Replace
|
|
422
|
+
the host path with the repository OpenClaw should allow Cejel to read:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
openclaw mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.2.2"]}'
|
|
426
|
+
openclaw mcp doctor cejel-oci --probe
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Cejel is active in the Official MCP Registry as `io.github.BargLabs/cejel`. The explicit
|
|
430
|
+
OpenClaw configuration above works independently of catalog indexing.
|
|
431
|
+
|
|
432
|
+
Cejel scans code you point it at. This free adoption surface does **not** watch, intercept,
|
|
433
|
+
or govern an agent's runtime actions. Runtime-action governance is a separate boundary for
|
|
434
|
+
the future paid Agent Pack.
|
|
435
|
+
|
|
363
436
|
## What "offline" means here
|
|
364
437
|
|
|
365
438
|
Scoring a repo — `cejel scan .` itself, and the Action's scoring step — makes zero network
|
|
@@ -4544,7 +4544,7 @@ function formatMetricValue(value) {
|
|
|
4544
4544
|
}
|
|
4545
4545
|
|
|
4546
4546
|
// src/witan/html.ts
|
|
4547
|
-
function renderWitanHtmlReport(report) {
|
|
4547
|
+
function renderWitanHtmlReport(report, options = {}) {
|
|
4548
4548
|
const codeCriteria = criteriaByCategory(report, "code_trust");
|
|
4549
4549
|
const processCriteria = criteriaByCategory(report, "process_trust");
|
|
4550
4550
|
const verifiedEvidence = report.criteria.flatMap(
|
|
@@ -4555,6 +4555,7 @@ function renderWitanHtmlReport(report) {
|
|
|
4555
4555
|
const externalSourceSummaries = summarizeExternalSources(report.consumedSignals ?? []);
|
|
4556
4556
|
const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
|
|
4557
4557
|
const coverage = computeMeasuredCoverage(report);
|
|
4558
|
+
const gitHistoryUnavailable = !report.repo.headSha;
|
|
4558
4559
|
return `<!doctype html>
|
|
4559
4560
|
<html lang="en">
|
|
4560
4561
|
<head>
|
|
@@ -4577,6 +4578,7 @@ function renderWitanHtmlReport(report) {
|
|
|
4577
4578
|
<dl class="meta">
|
|
4578
4579
|
<div><dt>Date</dt><dd>${escapeHtml(formatDate(report.generatedAt))}</dd></div>
|
|
4579
4580
|
<div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
|
|
4581
|
+
<div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
|
|
4580
4582
|
<div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
|
|
4581
4583
|
${contributingSources.length > 0 ? `<div><dt>Sources</dt><dd>Incorporates findings from: ${escapeHtml(contributingSources.join(", "))}<ul class="source-counts">${externalSourceSummaries.map((s) => `<li>${escapeHtml(formatExternalSourceLine(s))}</li>`).join("")}</ul></dd></div>` : ""}
|
|
4582
4584
|
</dl>
|
|
@@ -4597,8 +4599,8 @@ function renderWitanHtmlReport(report) {
|
|
|
4597
4599
|
</header>
|
|
4598
4600
|
|
|
4599
4601
|
<section class="trust-grid" aria-label="Rubric criteria">
|
|
4600
|
-
${renderCriterionColumn("Code trust", codeCriteria)}
|
|
4601
|
-
${renderCriterionColumn("Process trust", processCriteria)}
|
|
4602
|
+
${renderCriterionColumn("Code trust", codeCriteria, gitHistoryUnavailable)}
|
|
4603
|
+
${renderCriterionColumn("Process trust", processCriteria, gitHistoryUnavailable)}
|
|
4602
4604
|
</section>
|
|
4603
4605
|
|
|
4604
4606
|
<section class="evidence-grid" aria-label="Evidence and gaps">
|
|
@@ -4641,7 +4643,7 @@ function renderExternalFindingItems(findings) {
|
|
|
4641
4643
|
function criteriaByCategory(report, category) {
|
|
4642
4644
|
return report.criteria.filter((criterion) => criterion.category === category);
|
|
4643
4645
|
}
|
|
4644
|
-
function renderCriterionColumn(title, criteria) {
|
|
4646
|
+
function renderCriterionColumn(title, criteria, gitHistoryUnavailable) {
|
|
4645
4647
|
const applicable = criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
4646
4648
|
const notApplicable = criteria.filter((criterion) => criterion.status === "not_applicable");
|
|
4647
4649
|
const notApplicableGroup = notApplicable.length > 0 ? `
|
|
@@ -4649,7 +4651,7 @@ function renderCriterionColumn(title, criteria) {
|
|
|
4649
4651
|
return `<section class="criteria-column">
|
|
4650
4652
|
<h2>${escapeHtml(title)}</h2>
|
|
4651
4653
|
<div class="criteria-list">
|
|
4652
|
-
${applicable.map(renderCriterionCard).join("")}
|
|
4654
|
+
${applicable.map((criterion) => renderCriterionCard(criterion, gitHistoryUnavailable)).join("")}
|
|
4653
4655
|
</div>${notApplicableGroup}
|
|
4654
4656
|
</section>`;
|
|
4655
4657
|
}
|
|
@@ -4665,12 +4667,14 @@ function renderNotApplicableItem(criterion) {
|
|
|
4665
4667
|
const reason = criterion.notes ?? "Not applicable to this repository.";
|
|
4666
4668
|
return `<li><span class="na-id">${escapeHtml(criterion.id)}</span> ${escapeHtml(criterion.title)} \u2014 <span class="na-reason">${escapeHtml(reason)}</span></li>`;
|
|
4667
4669
|
}
|
|
4668
|
-
function renderCriterionCard(criterion) {
|
|
4670
|
+
function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
4669
4671
|
const evidence = [
|
|
4670
4672
|
...criterion.evidence.map(renderEvidencePointer),
|
|
4671
4673
|
...criterion.findings.map((finding) => renderFindingEvidence(criterion, finding))
|
|
4672
4674
|
];
|
|
4673
4675
|
const metrics = criterion.metrics.map(renderMetric);
|
|
4676
|
+
const statusReconciliation = renderStatusReconciliation(criterion);
|
|
4677
|
+
const historyWarning = gitHistoryUnavailable && criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio") ? `<p class="scan-warning"><strong>Scan limitation:</strong> Git history was unavailable (for example, this may be a source tarball), so the recent PR merge ratio is not a measured repository outcome and its displayed zero may undercount ${escapeHtml(criterion.id)}. Re-scan a full Git clone for this signal.</p>` : "";
|
|
4674
4678
|
return `<article class="criterion">
|
|
4675
4679
|
<div class="criterion-top">
|
|
4676
4680
|
<div>
|
|
@@ -4680,6 +4684,8 @@ function renderCriterionCard(criterion) {
|
|
|
4680
4684
|
${renderStatusChip(criterion.status)}
|
|
4681
4685
|
</div>
|
|
4682
4686
|
<div class="criterion-score">${criterion.status === "not_applicable" ? "N/A" : criterion.status === "insufficient_data" ? "No data" : formatScore(criterion.score)}</div>
|
|
4687
|
+
${statusReconciliation}
|
|
4688
|
+
${historyWarning}
|
|
4683
4689
|
<ul class="criterion-metrics">
|
|
4684
4690
|
${metrics.length > 0 ? metrics.map((item) => `<li>${item}</li>`).join("") : "<li>No measured depth metrics supplied.</li>"}
|
|
4685
4691
|
</ul>
|
|
@@ -4688,6 +4694,20 @@ function renderCriterionCard(criterion) {
|
|
|
4688
4694
|
</ul>
|
|
4689
4695
|
</article>`;
|
|
4690
4696
|
}
|
|
4697
|
+
function renderStatusReconciliation(criterion) {
|
|
4698
|
+
if (criterion.status === "not_applicable" || criterion.status === "insufficient_data" || criterion.status === "unverified") {
|
|
4699
|
+
return "";
|
|
4700
|
+
}
|
|
4701
|
+
const numericBand = scoreBandForPresentation(criterion.score);
|
|
4702
|
+
if (numericBand === criterion.status) return "";
|
|
4703
|
+
return `<p class="status-explanation"><strong>Why the labels differ:</strong> the ${escapeHtml(criterion.status)} dimension band is calibrated from criterion evidence thresholds and findings, independently of the ${formatScore(criterion.score)}/4.0 weighted score (which falls in the ${numericBand} numeric band).</p>`;
|
|
4704
|
+
}
|
|
4705
|
+
function scoreBandForPresentation(score) {
|
|
4706
|
+
if (score >= 3.5) return "verified";
|
|
4707
|
+
if (score >= 2.5) return "info";
|
|
4708
|
+
if (score >= 1.5) return "warning";
|
|
4709
|
+
return "critical";
|
|
4710
|
+
}
|
|
4691
4711
|
function renderMetric(metric2) {
|
|
4692
4712
|
const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
|
|
4693
4713
|
if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
|
|
@@ -4860,6 +4880,12 @@ h2 { font-family: var(--serif); font-size: 30px; font-weight: 400; letter-spacin
|
|
|
4860
4880
|
.criterion-id { color: var(--periwinkle); font-family: var(--mono); font-size: 12px; margin-bottom: 4px; }
|
|
4861
4881
|
h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
|
|
4862
4882
|
.criterion-score { margin-top: 12px; color: var(--text); font-family: var(--mono); font-size: 24px; }
|
|
4883
|
+
.status-explanation, .scan-warning {
|
|
4884
|
+
margin: 10px 0 0; border-left: 3px solid var(--periwinkle); padding: 8px 10px;
|
|
4885
|
+
background: var(--periwinkle-weak); color: var(--muted); font-size: 12px; line-height: 1.5;
|
|
4886
|
+
}
|
|
4887
|
+
.scan-warning { border-left-color: var(--warn); background: var(--warn-weak); }
|
|
4888
|
+
.status-explanation strong, .scan-warning strong { color: var(--text); }
|
|
4863
4889
|
.criterion-metrics { display: grid; gap: 7px; margin-top: 10px; padding: 0; list-style: none; font-size: 12px; }
|
|
4864
4890
|
.criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
|
|
4865
4891
|
.criterion-metrics strong { color: var(--text); font-weight: 650; }
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
runCejelScan,
|
|
10
10
|
serializeWitanReport,
|
|
11
11
|
verifyWitanAttestationBinding
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SNDRU52Z.js";
|
|
13
13
|
|
|
14
14
|
// src/index.ts
|
|
15
15
|
import { createHash } from "crypto";
|
|
@@ -224,7 +224,11 @@ async function runWitanFreeCli(args) {
|
|
|
224
224
|
JSON.stringify(attestation, null, 2),
|
|
225
225
|
"utf8"
|
|
226
226
|
);
|
|
227
|
-
writeFileSync(
|
|
227
|
+
writeFileSync(
|
|
228
|
+
join(options.outDir, "certificate.html"),
|
|
229
|
+
renderWitanHtmlReport(report, { cliVersion: cliVersion() }),
|
|
230
|
+
"utf8"
|
|
231
|
+
);
|
|
228
232
|
writeFileSync(
|
|
229
233
|
join(options.outDir, "badge.json"),
|
|
230
234
|
JSON.stringify(renderWitanBadgeEndpoint(report), null, 2),
|
package/dist/mcp/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
renderWitanBadgeSvg,
|
|
11
11
|
renderWitanHtmlReport,
|
|
12
12
|
runCejelScan
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-SNDRU52Z.js";
|
|
14
14
|
|
|
15
15
|
// node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
16
16
|
var require_code = __commonJS({
|
|
@@ -17097,7 +17097,11 @@ function createCejelMcpServer(identity) {
|
|
|
17097
17097
|
if (!lastScan) throw new Error("No scan has run yet \u2014 call the scan tool first.");
|
|
17098
17098
|
return {
|
|
17099
17099
|
contents: [
|
|
17100
|
-
{
|
|
17100
|
+
{
|
|
17101
|
+
uri: uri.href,
|
|
17102
|
+
mimeType: "text/html",
|
|
17103
|
+
text: renderWitanHtmlReport(lastScan.report, { cliVersion: identity.version })
|
|
17104
|
+
}
|
|
17101
17105
|
]
|
|
17102
17106
|
};
|
|
17103
17107
|
}
|