@cejel/cejel 0.6.1 → 0.6.3

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
@@ -92,7 +92,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
92
92
  ```
93
93
 
94
94
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
95
- > 0.6.1 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
95
+ > 0.6.3 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
96
96
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
97
97
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
98
98
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM (the packages
@@ -116,7 +116,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
116
116
  npx @cejel/cejel@latest .
117
117
  ```
118
118
 
119
- > **Distribution note:** Cejel `0.6.1` is the coordinated release version for npm,
119
+ > **Distribution note:** Cejel `0.6.3` is the coordinated release version for npm,
120
120
  > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. A GitLab CI
121
121
  > template and Code Quality export live in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
122
122
 
@@ -149,11 +149,17 @@ The npm package is scoped as `@cejel/cejel`; its executable remains the short co
149
149
  **GitHub Action** — score every PR and publish the badge:
150
150
 
151
151
  ```yaml
152
+ - uses: actions/checkout@v4
153
+ with:
154
+ fetch-depth: 0 # full history: A2 and B2 read commit history
152
155
  - uses: BargLabs/cejel/action@v1
153
156
  with:
154
157
  min-score: "2.5" # optional: fail the build below this
155
158
  ```
156
159
 
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
+
157
163
  For an executed public example that binds a release artifact to the same commit named by its
158
164
  Cejel certificate, see [Certified releases with GitHub build provenance](./docs/certified-release-example.md).
159
165
 
@@ -181,16 +187,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
181
187
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
182
188
  Microsoft Authenticode code-signing.
183
189
 
184
- **Docker / OCI.** The current container release is `0.6.1`:
190
+ **Docker / OCI.** The current container release is `0.6.3`:
185
191
 
186
192
  ```bash
187
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.1
193
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.3
188
194
  ```
189
195
 
190
196
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
191
197
 
192
198
  ```bash
193
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.1 .
199
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.3 .
194
200
  ```
195
201
 
196
202
  The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
@@ -429,11 +435,51 @@ a configurable `min-score` threshold. The scoring step makes no network calls an
429
435
  secrets.
430
436
 
431
437
  ```yaml
438
+ - uses: actions/checkout@v4
439
+ with:
440
+ fetch-depth: 0 # full history: A2 and B2 read commit history
432
441
  - uses: BargLabs/cejel/action@v1
433
442
  with:
434
443
  min-score: '2.5' # optional; omit to never fail the check
435
444
  ```
436
445
 
446
+ Check out with `fetch-depth: 0`: the default depth of 1 truncates the history A2 and B2 read, and
447
+ the certificate then lists a history limitation.
448
+
449
+ ## Use with Claude
450
+
451
+ Cejel ships as a Claude Code plugin in [`plugins/cejel/`](./plugins/cejel/). The plugin gives
452
+ Claude the `scan` tool below, started through `npx` and pinned to this release, plus a
453
+ `cejel-scan` skill. The skill tells Claude when a scan answers the question and how to read the
454
+ certificate. It also tells Claude to present a score as evidence for your own judgment, never as
455
+ an audit or a guarantee.
456
+
457
+ Install it from this repository's marketplace, inside Claude Code:
458
+
459
+ ```text
460
+ /plugin marketplace add BargLabs/cejel
461
+ /plugin install cejel@cejel
462
+ ```
463
+
464
+ From a shell, the same two steps are `claude plugin marketplace add BargLabs/cejel` and
465
+ `claude plugin install cejel@cejel`.
466
+
467
+ Scanning stays offline: the `scan` tool makes no network calls and writes no files. The first
468
+ time the server starts, `npx` fetches the pinned `@cejel/cejel` package from npm. When you name
469
+ a repository that isn't on your machine, Claude asks before cloning it with your `git`. Neither
470
+ fetch is part of the scan.
471
+
472
+ Example prompts that work on a public repository:
473
+
474
+ - "Clone https://github.com/BargLabs/cejel into a temporary folder and give me its Cejel trust
475
+ score."
476
+ - "Scan https://github.com/BargLabs/cejel with Cejel and explain the top findings and the criteria
477
+ they come from."
478
+ - "Run Cejel on https://github.com/BargLabs/cejel and save its trust badge to ./cejel-badge.svg."
479
+
480
+ Support: [GitHub issues](https://github.com/BargLabs/cejel/issues). Privacy policy:
481
+ <https://barglabs.ai/privacy>. Terms: <https://barglabs.ai/terms>.
482
+
437
483
  ## MCP server (for agents)
438
484
 
439
485
  The same package ships a second bin, `cejel-mcp` — a thin MCP (Model Context Protocol)
@@ -521,7 +567,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
521
567
  the host path with the repository OpenClaw should allow Cejel to read:
522
568
 
523
569
  ```bash
524
- 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.1"]}'
570
+ 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.3"]}'
525
571
  npx -y openclaw@latest mcp doctor cejel-oci --probe
526
572
  ```
527
573
 
@@ -5,7 +5,7 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-UBU3PP7P.js";
8
+ } from "../chunk-FA72HEWJ.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
11
  } from "../chunk-M6YYPJSN.js";
@@ -345,6 +345,10 @@ var WitanReportCommonSchema = external_exports.object({
345
345
  consumedSignals: external_exports.array(WitanConsumedSignalSummarySchema).optional(),
346
346
  archetype: WitanRepoArchetypeSchema.optional(),
347
347
  scanLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(16).optional(),
348
+ // Additive-optional (report format 1.5, issue #428): disclosures about the commit history the
349
+ // scan read, such as a shallow clone. Declared, never scored: unlike scanLimitations it gates
350
+ // nothing (no score, abstention, badge or --min-score effect). Absent when history is complete.
351
+ historyLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(4).optional(),
348
352
  contentReadSummary: WitanContentReadSummarySchema.optional(),
349
353
  // Additive-optional (report format 1.3): absent means a report produced before this field
350
354
  // existed, never "nothing withheld" — createWitanReport always sets it (possibly to []) on any
@@ -397,7 +401,7 @@ var WitanReportSchema = external_exports.preprocess((value) => {
397
401
  }, StrictWitanReportSchema);
398
402
  var WITAN_ATTESTATION_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
399
403
  var WITAN_ATTESTATION_PREDICATE_TYPE = "https://cejel.dev/attestations/scan/v1";
400
- var WITAN_REPORT_FORMAT_VERSION = "1.4";
404
+ var WITAN_REPORT_FORMAT_VERSION = "1.5";
401
405
  var WitanAttestationOutcomeSchema = external_exports.discriminatedUnion("status", [
402
406
  external_exports.object({
403
407
  status: external_exports.literal("scored"),
@@ -638,14 +642,14 @@ init_esm_shims();
638
642
  init_esm_shims();
639
643
  import { createHash } from "crypto";
640
644
  var WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS = Object.freeze({
641
- [WITAN_RUBRIC_VERSION_V17]: "sha256:eabdc78425373baf23f50c23b0b025bae4116461f3bc5ddc7d5b8496df03f653",
642
- [WITAN_RUBRIC_VERSION_V18]: "sha256:23cde3839b54875cf78eb8b4295779bc66327f7f5b331628a17839c2fb9562f1",
643
- [WITAN_RUBRIC_VERSION_V19]: "sha256:07e06ce68ba5ca365242f099e8860e1e466fa503dbcdac99986aab1e65c7c867",
644
- [WITAN_RUBRIC_VERSION_V20]: "sha256:4fb83b8972b76fb28cc6b59796f18ecfa16a37be52997c139f5cb14f60fd3717",
645
- [WITAN_RUBRIC_VERSION_V21]: "sha256:009f82a0ccaae4e9efcb1bb33776bf6cff2f268ddb21db0fb43f4b505563f645",
646
- [WITAN_RUBRIC_VERSION_V22]: "sha256:df8fcacf9a6f0d57c0ff00cafc51149e1bebafd3bec03f13316803b4ae27f0cc",
647
- [WITAN_RUBRIC_VERSION_V23]: "sha256:01ec9f9057f649f4bc737dc2f972bcec227237416cda5a4255db2c23d874ec5c",
648
- [WITAN_RUBRIC_VERSION_V24]: "sha256:4452f3b033552b7feb205048a07d6d851d1dc459a06f39befa86d22281e201fb"
645
+ [WITAN_RUBRIC_VERSION_V17]: "sha256:2da86acf64c39d2edb7824df46250ea23c396264266a2cd925953e9c6ad25858",
646
+ [WITAN_RUBRIC_VERSION_V18]: "sha256:60917d1d81efe7cda6f6adbfaa05293e2d9bf1db37b39d084e47f9011a996b67",
647
+ [WITAN_RUBRIC_VERSION_V19]: "sha256:e050ce8563de0736d9cfb6c23e2ba04725d88465d378a8632576269b9bc9f269",
648
+ [WITAN_RUBRIC_VERSION_V20]: "sha256:0433adc34b58c0fd5500a0bdeaa910c854eedbadbeaabb9045238d4ba9be8878",
649
+ [WITAN_RUBRIC_VERSION_V21]: "sha256:d3796b78a45ea3593f6abe71c77c2d00a0056d0f2d88476a01329dc342166aa6",
650
+ [WITAN_RUBRIC_VERSION_V22]: "sha256:932bc09cbe555c6058a95be581ccf7a286c43887f90d32698f5273b7623824bf",
651
+ [WITAN_RUBRIC_VERSION_V23]: "sha256:db97dc999d37e3c39c3bd3e4b1778c94eb9ad3176fb5c7d7264a7e510b162fc1",
652
+ [WITAN_RUBRIC_VERSION_V24]: "sha256:e313fab25ae1779a11e86b23696881cd6f8fe2a845609dc93e2ff6f3ff8c16d5"
649
653
  });
650
654
  function rubricBehaviourFingerprint(rubricVersion) {
651
655
  return WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS[rubricVersion];
@@ -2179,6 +2183,13 @@ function renderWitanHtmlReport(report, options = {}) {
2179
2183
  ).join("")}</ul>
2180
2184
  </section>` : ""}
2181
2185
 
2186
+ ${(report.historyLimitations?.length ?? 0) > 0 ? `<section class="scan-limitations-section evidence-section" aria-label="History limitations">
2187
+ <h2>History limitations</h2>
2188
+ <ul>${(report.historyLimitations ?? []).map(
2189
+ (limitation) => `<li class="scan-warning"><strong>History limitation</strong><span>${escapeHtml(limitation)}</span></li>`
2190
+ ).join("")}</ul>
2191
+ </section>` : ""}
2192
+
2182
2193
  ${renderContentReadSummary(report)}
2183
2194
 
2184
2195
  <section class="trust-grid" aria-label="Rubric criteria">
@@ -4800,14 +4811,14 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4800
4811
  const serverEntrypoint = v22PackageStartHttpEntrypoint ?? findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) ?? v20DirectHttpEntrypoint ?? null;
4801
4812
  const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, useV20ExplicitGaps) : null;
4802
4813
  const errorBoundaries = repoFiles.filter(
4803
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
4814
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationEvidencePath(file) && ERROR_BOUNDARY_FILENAME_PATTERN.test(file)
4804
4815
  );
4805
- const errorBoundaryFileReads = (file) => observabilityDepthReads(file) || (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)server\//.test(file) && /\.(?:mjs|cjs)$/.test(file);
4816
+ const errorBoundaryFileReads = (file) => isImplementationEvidencePath(file) && (observabilityDepthReads(file) || (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)server\//.test(file) && /\.(?:mjs|cjs)$/.test(file));
4806
4817
  const expressErrorMiddlewareFile = errorBoundaries.length > 0 ? void 0 : withContentReadSignal(
4807
4818
  "A3",
4808
4819
  "prod_readiness_primitives",
4809
4820
  () => repoFiles.find(
4810
- (file) => errorBoundaryFileReads(file) && fileContains(repoPath, file, EXPRESS_ERROR_MIDDLEWARE_PATTERN) && fileMatchesOutsideLineComments(repoPath, file, EXPRESS_MIDDLEWARE_REACHABLE_PATTERN)
4821
+ (file) => errorBoundaryFileReads(file) && fileMatchesOutsideComments(repoPath, file, EXPRESS_ERROR_MIDDLEWARE_PATTERN) && fileMatchesOutsideComments(repoPath, file, EXPRESS_MIDDLEWARE_REACHABLE_PATTERN)
4811
4822
  )
4812
4823
  );
4813
4824
  const errorBoundary = errorBoundaries[0] ?? expressErrorMiddlewareFile;
@@ -4886,7 +4897,11 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4886
4897
  errorBoundary,
4887
4898
  "prod_check",
4888
4899
  "Error boundary",
4889
- findFirstMatchingLine(repoPath, errorBoundary, EXPRESS_ERROR_MIDDLEWARE_PATTERN)
4900
+ findFirstMatchingLineOutsideComments(
4901
+ repoPath,
4902
+ errorBoundary,
4903
+ EXPRESS_ERROR_MIDDLEWARE_PATTERN
4904
+ )
4890
4905
  ) : evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary")
4891
4906
  );
4892
4907
  }
@@ -5425,7 +5440,7 @@ function collectB2PrTraceEvidence(repoPath, repoFiles) {
5425
5440
  (file) => /(^|\/)(pull_request_template|PULL_REQUEST_TEMPLATE)(\.md$|\/[^/]+\.md$)/.test(file)
5426
5441
  );
5427
5442
  const branchProtectionDoc = repoFiles.find(
5428
- (file) => /branch.*protection|review.*gate|CODEOWNERS/i.test(file)
5443
+ (file) => /branch.*protection|review.*gate/i.test(file) || GITHUB_CODEOWNERS_PATH_PATTERN.test(file)
5429
5444
  );
5430
5445
  const evidence = [
5431
5446
  ...workflows.slice(0, 3).map((file) => evidenceForRelative(repoPath, file, "ci_run", "Pull-request CI workflow")),
@@ -5678,32 +5693,36 @@ function collectB4AuditEvidence(repoPath, repoFiles, generatedAt, useV19CommitYe
5678
5693
  function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors = false, useV21ExecutedEscalations = false) {
5679
5694
  const evidence = [];
5680
5695
  const findings = [];
5681
- const docFiles = repoFiles.filter((file) => /\.(md|mdx)$/i.test(file));
5696
+ const policyDocFiles = repoFiles.filter(
5697
+ (file) => /\.(md|mdx)$/i.test(file) && isPolicyDocumentEvidencePath(file)
5698
+ );
5699
+ const isPolicyDocMatching = (file, pattern) => fileContains(repoPath, file, pattern) && !isCejelCertificateText(repoPath, file);
5682
5700
  const humanGateDoc = withContentReadSignal(
5683
5701
  "B6",
5684
5702
  "human_gate_documented",
5685
- () => docFiles.find((file) => fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN))
5703
+ () => policyDocFiles.find((file) => isPolicyDocMatching(file, HUMAN_GATE_MARKER_PATTERN))
5686
5704
  );
5687
5705
  const implFiles = repoFiles.filter(isImplementationFile);
5706
+ const implementationEvidenceFiles = implFiles.filter(isImplementationEvidencePath);
5688
5707
  const gatedPrivilegeCheckFile = withContentReadSignal(
5689
5708
  "B6",
5690
5709
  "fail_closed_privilege_check",
5691
- () => implFiles.find(
5692
- (file) => fileContains(repoPath, file, GATED_PRIVILEGE_CHECK_PATTERN) && fileContains(repoPath, file, SET_ROLE_PATTERN)
5710
+ () => implementationEvidenceFiles.find(
5711
+ (file) => fileMatchesOutsideComments(repoPath, file, GATED_PRIVILEGE_CHECK_PATTERN) && fileMatchesOutsideComments(repoPath, file, SET_ROLE_PATTERN)
5693
5712
  )
5694
5713
  );
5695
5714
  const killSwitchFile = withContentReadSignal(
5696
5715
  "B6",
5697
5716
  "kill_switch_fail_safe_present",
5698
- () => implFiles.find(
5699
- (file) => fileContains(repoPath, file, KILL_SWITCH_NAME_PATTERN) && fileContains(repoPath, file, KILL_SWITCH_FAIL_CLOSED_PATTERN)
5717
+ () => implementationEvidenceFiles.find(
5718
+ (file) => fileMatchesOutsideComments(repoPath, file, KILL_SWITCH_NAME_PATTERN) && fileMatchesOutsideComments(repoPath, file, KILL_SWITCH_FAIL_CLOSED_PATTERN)
5700
5719
  )
5701
5720
  );
5702
- const codeownersFile = repoFiles.find((file) => /(^|\/)CODEOWNERS$/.test(file));
5721
+ const codeownersFile = repoFiles.find((file) => GITHUB_CODEOWNERS_PATH_PATTERN.test(file));
5703
5722
  const reviewGateDoc = withContentReadSignal(
5704
5723
  "B6",
5705
5724
  "protected_path_review_gate",
5706
- () => docFiles.find((file) => fileContains(repoPath, file, REQUIRED_REVIEW_PATTERN))
5725
+ () => policyDocFiles.find((file) => isPolicyDocMatching(file, REQUIRED_REVIEW_PATTERN))
5707
5726
  );
5708
5727
  const protectedPathReviewGate = codeownersFile ?? reviewGateDoc;
5709
5728
  const { executableFiles, v21ExecutedEscalationFileSet, productionUngatedEscalationFiles } = withContentReadSignal("B6", "privilege_escalation_cleanliness", () => {
@@ -5781,7 +5800,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
5781
5800
  repoPath,
5782
5801
  protectedPathReviewGate,
5783
5802
  "artifact",
5784
- "CODEOWNERS/required-review gate on protected paths"
5803
+ protectedPathReviewGate === codeownersFile ? "CODEOWNERS file in a location GitHub reads (protected-path review gate)" : "Documented required-review/branch-protection policy"
5785
5804
  )
5786
5805
  )
5787
5806
  );
@@ -5872,7 +5891,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
5872
5891
  1,
5873
5892
  hasPrivilegedOpsSurface ? 0.2 : 0.6,
5874
5893
  "present",
5875
- "Credits a CODEOWNERS file or documented required-review/branch-protection policy \u2014 the general OSS-observable analogue of human-gating changes to sensitive paths."
5894
+ "Credits a CODEOWNERS file where GitHub reads it (root, .github/ or docs/) or a documented required-review/branch-protection policy (not a changelog, record, review note or report) \u2014 the general OSS-observable analogue of human-gating changes to sensitive paths."
5876
5895
  ),
5877
5896
  ...killSwitchMetrics
5878
5897
  ],
@@ -5918,6 +5937,19 @@ function isTestFile(file, rubricVersion = WITAN_RUBRIC_VERSION) {
5918
5937
  }
5919
5938
  var NAME_SHAPED_TEST_FILE_PATTERN = /^test(?:-[^/]+)?\.[cm]?[jt]sx?$|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)__tests__\/|(^|\/)test_[^/]*\.py$|(^|\/)tests?\.py$|_test\.go$|(^|\/)test_[^/]*\.(cpp|cc|cxx)$|_(test|tests)\.(cpp|cc|cxx)$|Tests?\.(java|kt)$|(_test|_spec)\.(rs|rb|php|swift|kt)$/;
5920
5939
  var NAME_SHAPED_TEST_FILE_PATTERN_V17 = /\.tftest\.hcl$|\.bats$|(^|\/)test[^/]*\.m$|_test\.m$/i;
5940
+ var CALIBRATION_RECORD_DIR_PATTERN = /(^|\/)calibrations?(\/|$)/i;
5941
+ function isImplementationEvidencePath(path) {
5942
+ return isProductionSourcePath(path) && !CALIBRATION_RECORD_DIR_PATTERN.test(path);
5943
+ }
5944
+ var CHANGELOG_FILE_PATTERN = /(^|\/)(?:CHANGELOG|CHANGES|HISTORY|NEWS|RELEASE[-_]?NOTES|RELEASES)(?:\.[^/]*)?$/i;
5945
+ var NON_POLICY_RECORD_DIR_PATTERN = /(^|\/)(?:\.changesets?|changelogs?|changelog\.d|experiments?|calibrations?|reviews|review[-_]notes?|reports)(\/|$)/i;
5946
+ function isPolicyDocumentEvidencePath(path) {
5947
+ return isProductionSourcePath(path) && !CHANGELOG_FILE_PATTERN.test(path) && !NON_POLICY_RECORD_DIR_PATTERN.test(path);
5948
+ }
5949
+ var CEJEL_CERTIFICATE_TEXT_PATTERN = /^# Cejel Trust Report\b[\s\S]*?^- Rubric: /m;
5950
+ function isCejelCertificateText(repoPath, file) {
5951
+ return fileContains(repoPath, file, CEJEL_CERTIFICATE_TEXT_PATTERN);
5952
+ }
5921
5953
  var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test|node\s+--test|node:test)\b/i;
5922
5954
  function hasCredibleTestScript(scripts) {
5923
5955
  const script = scripts.get("test");
@@ -5969,6 +6001,7 @@ function fileHasV21RawSqlEscalation(repoPath, file) {
5969
6001
  var GATED_PRIVILEGE_CHECK_PATTERN = /pg_has_role\s*\(|has_role\s*\(|is not a member of/i;
5970
6002
  var SET_ROLE_PATTERN = /\bset\s+(local\s+)?role\b/i;
5971
6003
  var REQUIRED_REVIEW_PATTERN = /required review|branch protection|protected branch/i;
6004
+ var GITHUB_CODEOWNERS_PATH_PATTERN = /^(?:\.github\/|docs\/)?CODEOWNERS$/;
5972
6005
  var HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT = 5e3;
5973
6006
  var V23_PEM_WIDENED_HISTORY_BLOB_LIMIT = 500;
5974
6007
  var SECRET_IDENTIFIER_KEYWORD_PATTERN = /secret|token|api[_-]?key|password|access[_-]?key/gi;
@@ -8027,6 +8060,7 @@ var HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN = /(^|\/)functions\/.*\.(?:ts|tsx|js|jsx|
8027
8060
  var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`](?:[\w${}./-]{0,60}\/[_-]?(?:(?:health|ready|live)z?|readiness|liveness|up)(?=["'`/])|(?:healthz|readiness|liveness)(?=["'`]))|@Get\s*\(\s*(["'`])health\1\s*\)/i;
8028
8061
  var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:_?err|_?error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*\)\s*(?:=>|\{)/i;
8029
8062
  var EXPRESS_MIDDLEWARE_REACHABLE_PATTERN = /\.use\s*\(|\bmodule\.exports\b|\bexport\s+(?:default\b|function\b|const\b|class\b)/;
8063
+ var ERROR_BOUNDARY_FILENAME_PATTERN = /(^|\/)(?:error-boundary|error)\.(?:tsx|jsx|ts|js)$/;
8030
8064
  var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
8031
8065
  var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
8032
8066
  var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
@@ -8221,11 +8255,22 @@ function fileContains(repoPath, file, pattern) {
8221
8255
  if (!isRegularFile(fullPath)) return false;
8222
8256
  return pattern.test(readRepoText(fullPath, "utf8"));
8223
8257
  }
8224
- function fileMatchesOutsideLineComments(repoPath, file, pattern) {
8258
+ var HASH_COMMENT_SOURCE_PATTERN = /\.(?:py|rb)$/i;
8259
+ function stripCodeComments(file, contents) {
8260
+ const withoutComments = contents.replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, "")).replace(/(?<!:)\/\/.*$/gm, "");
8261
+ return HASH_COMMENT_SOURCE_PATTERN.test(file) ? withoutComments.replace(/^[ \t]*#.*$/gm, "") : withoutComments;
8262
+ }
8263
+ function fileMatchesOutsideComments(repoPath, file, pattern) {
8225
8264
  const fullPath = join(repoPath, file);
8226
8265
  if (!isRegularFile(fullPath)) return false;
8227
- const withoutLineComments = readRepoText(fullPath, "utf8").replace(/(?<!:)\/\/.*$/gm, "");
8228
- return pattern.test(withoutLineComments);
8266
+ return pattern.test(stripCodeComments(file, readRepoText(fullPath, "utf8")));
8267
+ }
8268
+ function findFirstMatchingLineOutsideComments(repoPath, file, pattern) {
8269
+ const fullPath = join(repoPath, file);
8270
+ if (!isRegularFile(fullPath)) return null;
8271
+ const lines = stripCodeComments(file, readRepoText(fullPath, "utf8")).split("\n");
8272
+ const index = lines.findIndex((line) => pattern.test(line));
8273
+ return index === -1 ? null : index + 1;
8229
8274
  }
8230
8275
  var PEM_PRIVATE_KEY_ASSIGNMENT_PATTERN = /(?:["'])?([A-Za-z_$][\w$.-]*)(?:["'])?\s*[:=]\s*(?:\|[-+0-9]*\s*\r?\n[ \t]*)?["'`]?-----BEGIN((?: (?:RSA|DSA|EC|OPENSSH|ENCRYPTED))? PRIVATE KEY)-----([\s\S]{0,32768}?)-----END\2-----/g;
8231
8276
  var BARE_PEM_PRIVATE_KEY_PATTERN = /["'`]?-----BEGIN((?: (?:RSA|DSA|EC|OPENSSH|ENCRYPTED))? PRIVATE KEY)-----([\s\S]{0,32768}?)-----END\1-----/g;
@@ -9218,6 +9263,15 @@ function hasEnvPathInGitHistory(repoPath, scanLimitations) {
9218
9263
  // src/witan/public-scan.ts
9219
9264
  init_esm_shims();
9220
9265
 
9266
+ // src/witan/history-depth.ts
9267
+ init_esm_shims();
9268
+ var SHALLOW_HISTORY_LIMITATION = "This repository is a shallow clone (git rev-parse --is-shallow-repository reports true), so its commit history is truncated. A2 (credentials and .env files in earlier commits) and B2 (pull-request merge ratio over recent commits) read history and saw only the commits present; on a full clone their results may differ. Scores are computed exactly as for any other scan. Re-scan a full clone (for actions/checkout, set fetch-depth: 0).";
9269
+ function detectHistoryLimitations(repoPath) {
9270
+ const result = execGit(["rev-parse", "--is-shallow-repository"], { cwd: repoPath });
9271
+ if (!result.ok || result.stdout.trim() !== "true") return void 0;
9272
+ return [SHALLOW_HISTORY_LIMITATION];
9273
+ }
9274
+
9221
9275
  // src/witan/ingest.ts
9222
9276
  init_esm_shims();
9223
9277
  import { lstatSync as lstatSync3, opendirSync, readFileSync as readFileSync4, realpathSync as realpathSync3 } from "fs";
@@ -9755,12 +9809,14 @@ function scoreRepoWithPublicCejel(options) {
9755
9809
  ...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {}
9756
9810
  });
9757
9811
  const inputSignals = resolvePublicIngestSignals(options);
9758
- const report = createWitanReport(
9812
+ const scored = createWitanReport(
9759
9813
  input,
9760
9814
  inputSignals.length > 0 ? inputSignals : void 0,
9761
9815
  void 0,
9762
9816
  options.toolVersion
9763
9817
  );
9818
+ const historyLimitations = detectHistoryLimitations(options.repoPath);
9819
+ const report = historyLimitations ? { ...scored, historyLimitations } : scored;
9764
9820
  if (report.verdict !== "insufficient_source" || !isWitanNoMeasurementAbstention(report)) {
9765
9821
  return report;
9766
9822
  }
@@ -9982,6 +10038,7 @@ function buildWitanCliSummary(report) {
9982
10038
  externalFindingCount: allExternalFindings.length,
9983
10039
  topExternalFindings: allExternalFindings.slice(0, EXTERNAL_FINDINGS_DISPLAY_LIMIT),
9984
10040
  scanLimitations: [...report.scanLimitations ?? []],
10041
+ ...report.historyLimitations?.length ? { historyLimitations: [...report.historyLimitations] } : {},
9985
10042
  ...report.contentReadSummary ? { contentReadSummary: report.contentReadSummary } : {}
9986
10043
  };
9987
10044
  if (report.verdict === "insufficient_source") {