@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 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`, and
93
- `cejel-Linux-x86_64`. The release also carries `SHA256SUMS`; each binary is executed against
94
- the source build and with networking denied before attachment. Releases from v0.1.6 also
95
- carry a Sigstore bundle containing GitHub's signed build-provenance attestation for all four
96
- binaries. Verify a downloaded binary with:
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-Darwin-arm64 -R BargLabs/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.0` (container releases are
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.0
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.0 .
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-BR2DO4QJ.js";
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(join(options.outDir, "certificate.html"), renderWitanHtmlReport(report), "utf8");
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-BR2DO4QJ.js";
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
- { uri: uri.href, mimeType: "text/html", text: renderWitanHtmlReport(lastScan.report) }
17100
+ {
17101
+ uri: uri.href,
17102
+ mimeType: "text/html",
17103
+ text: renderWitanHtmlReport(lastScan.report, { cliVersion: identity.version })
17104
+ }
17101
17105
  ]
17102
17106
  };
17103
17107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {