@cejel/cejel 0.6.5 → 0.6.7

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.5 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
95
+ > 0.6.7 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.5` is the coordinated release version for npm,
119
+ > **Distribution note:** Cejel `0.6.7` 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
 
@@ -187,16 +187,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
187
187
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
188
188
  Microsoft Authenticode code-signing.
189
189
 
190
- **Docker / OCI.** The current container release is `0.6.5`:
190
+ **Docker / OCI.** The current container release is `0.6.7`:
191
191
 
192
192
  ```bash
193
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.5
193
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.7
194
194
  ```
195
195
 
196
196
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
197
197
 
198
198
  ```bash
199
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.5 .
199
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.7 .
200
200
  ```
201
201
 
202
202
  The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
@@ -571,7 +571,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
571
571
  the host path with the repository OpenClaw should allow Cejel to read:
572
572
 
573
573
  ```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.5"]}'
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.7"]}'
575
575
  npx -y openclaw@latest mcp doctor cejel-oci --probe
576
576
  ```
577
577
 
@@ -5,11 +5,11 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-7XQVE76B.js";
8
+ } from "../chunk-ZZHQZW26.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
- } from "../chunk-M6YYPJSN.js";
12
- import "../chunk-63XQDYQ5.js";
11
+ } from "../chunk-5PFYOIOS.js";
12
+ import "../chunk-VGBFZBZQ.js";
13
13
  import {
14
14
  external_exports
15
15
  } from "../chunk-FSMZV3Y6.js";
@@ -5,7 +5,7 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-63XQDYQ5.js";
8
+ } from "./chunk-VGBFZBZQ.js";
9
9
  import {
10
10
  init_esm_shims
11
11
  } from "./chunk-O3MHSK25.js";
@@ -5,7 +5,7 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "./chunk-7XQVE76B.js";
8
+ } from "./chunk-ZZHQZW26.js";
9
9
  import {
10
10
  ZodFirstPartyTypeKind,
11
11
  ZodOptional,
@@ -8,7 +8,8 @@ import {
8
8
  // src/witan/git-exec.ts
9
9
  init_esm_shims();
10
10
  import { execFileSync } from "child_process";
11
- import { resolve } from "path";
11
+ import { realpathSync } from "fs";
12
+ import { dirname, resolve } from "path";
12
13
  var GIT_EXEC_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
13
14
  var GIT_EXEC_TIMEOUT_MS = 3e4;
14
15
  var HARDENED_GIT_ARGUMENTS = [
@@ -52,6 +53,7 @@ var HARDENED_GIT_ARGUMENTS = [
52
53
  "-c",
53
54
  "protocol.ssh.allow=never"
54
55
  ];
56
+ var discoveryCeiling;
55
57
  function hardenedGitEnvironment(cwd) {
56
58
  const environment = {};
57
59
  for (const key of ["PATH", "HOME", "TZ"]) {
@@ -78,7 +80,8 @@ function hardenedGitEnvironment(cwd) {
78
80
  // still excluded below.
79
81
  GIT_CONFIG_COUNT: "1",
80
82
  GIT_CONFIG_KEY_0: "safe.directory",
81
- GIT_CONFIG_VALUE_0: cwd
83
+ GIT_CONFIG_VALUE_0: cwd,
84
+ ...discoveryCeiling === void 0 ? {} : { GIT_CEILING_DIRECTORIES: discoveryCeiling }
82
85
  };
83
86
  }
84
87
  function errorField(error, key) {
@@ -5,7 +5,7 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-63XQDYQ5.js";
8
+ } from "./chunk-VGBFZBZQ.js";
9
9
  import {
10
10
  external_exports
11
11
  } from "./chunk-FSMZV3Y6.js";
@@ -572,6 +572,7 @@ var WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source i
572
572
  var WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the complete tracked inventory contains no concrete test file, configured test runner, CI test command, or coverage configuration.";
573
573
  var WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE = "Whether this repository's claims are reconciled against its implementation cannot be settled from static evidence; Cejel makes no finding on it.";
574
574
  var WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE = "Whether tenant isolation is enforced outside the database cannot be settled from static evidence; Cejel makes no finding on it.";
575
+ var WITAN_A3_BUILD_STEP_UNSETTLED_DISCLOSURE = "Whether this package has a build or type-check step outside the files read cannot be settled from static evidence; Cejel makes no finding on it.";
575
576
  function isWitanNoMeasurementAbstention(report) {
576
577
  return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
577
578
  ({ status }) => status === "not_applicable" || status === "insufficient_data"
@@ -1973,6 +1974,7 @@ function buildWithheldPathsGapSentence(report) {
1973
1974
  }
1974
1975
  var SETTLING_EVIDENCE_DISCLOSURES = [
1975
1976
  { criterionId: "A2", sentence: WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE },
1977
+ { criterionId: "A3", sentence: WITAN_A3_BUILD_STEP_UNSETTLED_DISCLOSURE },
1976
1978
  { criterionId: "A5", sentence: WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE }
1977
1979
  ];
1978
1980
  function settlingEvidenceDisclosures(report) {
@@ -3205,6 +3207,7 @@ function buildWitanInputFromRepoUntracked(options) {
3205
3207
  const usesV24SecretContentContext = rubricVersion === WITAN_RUBRIC_VERSION_V24;
3206
3208
  const usesV25InlineHealthRoute = rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
3207
3209
  const usesV26SettlingEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
3210
+ const usesV26BuildStepEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
3208
3211
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
3209
3212
  const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure2(rubricVersion) ? applyReadableSourceRepresentationGate(
3210
3213
  options.repoPath,
@@ -3246,6 +3249,7 @@ function buildWitanInputFromRepoUntracked(options) {
3246
3249
  usesV24SecretContentContext,
3247
3250
  usesV25InlineHealthRoute,
3248
3251
  usesV26SettlingEvidence,
3252
+ usesV26BuildStepEvidence,
3249
3253
  reviewableSourceProof,
3250
3254
  inventoryAbsenceContext,
3251
3255
  scanLimitations
@@ -3860,7 +3864,7 @@ function buildNotApplicableSignal(criterionId, reason) {
3860
3864
  notApplicable: true
3861
3865
  };
3862
3866
  }
3863
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23StrictCoverageAndRecipeRunners, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, useV25InlineHealthRoute, useV26SettlingEvidence, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
3867
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23StrictCoverageAndRecipeRunners, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, useV25InlineHealthRoute, useV26SettlingEvidence, useV26BuildStepEvidence, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
3864
3868
  const signals = [];
3865
3869
  const mono = resolveMonorepoContext(repoPath, scanLimitations);
3866
3870
  const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
@@ -3905,7 +3909,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3905
3909
  useV22PackageStartEntrypoint,
3906
3910
  useV23WithheldPathAbstention,
3907
3911
  inventoryAbsenceContext,
3908
- useV25InlineHealthRoute
3912
+ useV25InlineHealthRoute,
3913
+ useV26BuildStepEvidence
3909
3914
  )
3910
3915
  );
3911
3916
  const a4Signal = collectCriterion(
@@ -4828,7 +4833,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
4828
4833
  }
4829
4834
  return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
4830
4835
  }
4831
- function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV25InlineHealthRoute = false) {
4836
+ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV25InlineHealthRoute = false, useV26BuildStepEvidence = false) {
4832
4837
  const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
4833
4838
  const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
4834
4839
  const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
@@ -5045,13 +5050,22 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
5045
5050
  }
5046
5051
  }
5047
5052
  if (evidence.length === 0) return null;
5053
+ let buildStepSettlement;
5048
5054
  if (useV20ExplicitGaps) {
5049
5055
  const workspaceManifests = packageJson ? findDeclaredWorkspaceManifests(repoPath, repoFiles, packageJson) : NO_DECLARED_WORKSPACE_MANIFESTS;
5050
5056
  const hasBuildOrTypecheck = scripts.has("build") || scripts.has("typecheck") || workspaceManifests.declared > workspaceManifests.read.length || workspaceManifests.read.some((file) => {
5051
5057
  const workspaceScripts = readPackageScripts(join(repoPath, file));
5052
5058
  return workspaceScripts.has("build") || workspaceScripts.has("typecheck");
5053
5059
  });
5054
- if (packageJson && !hasBuildOrTypecheck) {
5060
+ buildStepSettlement = useV26BuildStepEvidence && packageJson ? settleV26BuildStepEvidence(
5061
+ repoPath,
5062
+ repoFiles,
5063
+ packageJson,
5064
+ workspaceManifests,
5065
+ workflows,
5066
+ (file) => !useV27Detectors || isAuthoredProductionPath(file)
5067
+ ) : void 0;
5068
+ if (packageJson && !hasBuildOrTypecheck && (buildStepSettlement === void 0 || buildStepSettlement === "fires")) {
5055
5069
  const workspaceRead = workspaceManifests.read.length;
5056
5070
  findings.push({
5057
5071
  severity: "info",
@@ -5192,7 +5206,8 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
5192
5206
  "Counts static rollback or migration-safety artifacts.",
5193
5207
  "saturating_count"
5194
5208
  )
5195
- ]
5209
+ ],
5210
+ ...buildStepSettlement === "unsettled" ? { notes: WITAN_A3_BUILD_STEP_UNSETTLED_DISCLOSURE } : {}
5196
5211
  };
5197
5212
  }
5198
5213
  function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
@@ -8408,22 +8423,7 @@ function packageScriptReferences(command, scripts) {
8408
8423
  const names = /* @__PURE__ */ new Set();
8409
8424
  let dynamic = false;
8410
8425
  for (const segment of splitShellCommandSegments(command)) {
8411
- for (const match of segment.matchAll(
8412
- /\bpnpm\s+run\s+\/((?:\\.|[^/])+)\/([dgimsuvy]*)/giu
8413
- )) {
8414
- const source = match[1];
8415
- if (!source) continue;
8416
- try {
8417
- const flags = (match[2] ?? "").replace(/[dgy]/g, "");
8418
- const selector = new RegExp(source, flags);
8419
- for (const name of scripts.keys()) {
8420
- if (selector.test(name)) names.add(name);
8421
- selector.lastIndex = 0;
8422
- }
8423
- } catch {
8424
- dynamic = true;
8425
- }
8426
- }
8426
+ if (/\bpnpm\s+run\s+\//iu.test(segment)) dynamic = true;
8427
8427
  for (const match of segment.matchAll(
8428
8428
  /\b(?:npm|pnpm|yarn)\s+(?:run\s+)?([A-Za-z0-9][A-Za-z0-9:._-]*)/giu
8429
8429
  )) {
@@ -9411,40 +9411,51 @@ function findRootPackageJson(repoFiles) {
9411
9411
  var WORKSPACE_PATTERN_LIMIT = 64;
9412
9412
  var WORKSPACE_PACKAGE_MANIFEST_LIMIT = 200;
9413
9413
  var NO_DECLARED_WORKSPACE_MANIFESTS = { read: [], declared: 0 };
9414
+ var UNREADABLE_WORKSPACE_DECLARATION = {
9415
+ read: [],
9416
+ declared: 0,
9417
+ unreadable: true
9418
+ };
9414
9419
  function findDeclaredWorkspaceManifests(repoPath, repoFiles, rootPackageJson) {
9415
9420
  const patterns = [];
9416
9421
  if (rootPackageJson) {
9417
9422
  const declared = readPackageJsonWorkspacePatterns(join(repoPath, rootPackageJson));
9418
- if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9423
+ if (declared === null) return UNREADABLE_WORKSPACE_DECLARATION;
9419
9424
  patterns.push(...declared);
9420
9425
  }
9421
9426
  if (repoFiles.includes("pnpm-workspace.yaml")) {
9422
9427
  const declared = readPnpmWorkspacePatterns(join(repoPath, "pnpm-workspace.yaml"));
9423
- if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9428
+ if (declared === null) return UNREADABLE_WORKSPACE_DECLARATION;
9424
9429
  patterns.push(...declared);
9425
9430
  }
9426
- if (patterns.length === 0 || patterns.length > WORKSPACE_PATTERN_LIMIT) {
9427
- return NO_DECLARED_WORKSPACE_MANIFESTS;
9428
- }
9431
+ if (patterns.length === 0) return NO_DECLARED_WORKSPACE_MANIFESTS;
9432
+ if (patterns.length > WORKSPACE_PATTERN_LIMIT) return UNREADABLE_WORKSPACE_DECLARATION;
9429
9433
  const include = [];
9430
9434
  const exclude = [];
9435
+ let unparsedPattern = false;
9431
9436
  for (const pattern of patterns) {
9432
9437
  const negated = pattern.startsWith("!");
9433
- const regex = workspaceGlobToRegExp(negated ? pattern.slice(1) : pattern);
9438
+ const matcher = workspaceGlobMatcher(negated ? pattern.slice(1) : pattern);
9434
9439
  if (negated) {
9435
- if (regex === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9436
- exclude.push(regex);
9437
- } else if (regex !== null) {
9438
- include.push(regex);
9440
+ if (matcher === null) return UNREADABLE_WORKSPACE_DECLARATION;
9441
+ exclude.push(matcher);
9442
+ } else if (matcher !== null) {
9443
+ include.push(matcher);
9444
+ } else {
9445
+ unparsedPattern = true;
9439
9446
  }
9440
9447
  }
9441
9448
  const matched = repoFiles.filter((file) => {
9442
9449
  if (file === rootPackageJson || !file.endsWith("/package.json")) return false;
9443
9450
  if (!isAuthoredProductionPath(file)) return false;
9444
9451
  const dir = file.slice(0, -"/package.json".length);
9445
- return include.some((regex) => regex.test(dir)) && !exclude.some((regex) => regex.test(dir));
9452
+ return include.some((matches) => matches(dir)) && !exclude.some((matches) => matches(dir));
9446
9453
  }).sort();
9447
- return { read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT), declared: matched.length };
9454
+ return {
9455
+ read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT),
9456
+ declared: matched.length,
9457
+ ...unparsedPattern ? { unreadable: true } : {}
9458
+ };
9448
9459
  }
9449
9460
  function readPackageJsonWorkspacePatterns(packageJsonPath) {
9450
9461
  if (!isRegularFile(packageJsonPath)) return [];
@@ -9503,27 +9514,69 @@ function yamlScalar(raw) {
9503
9514
  const plain = stripYamlLineComment(raw).trim();
9504
9515
  return plain === "" || /^["'[{&*|>]/.test(plain) ? null : plain;
9505
9516
  }
9506
- function workspaceGlobToRegExp(pattern) {
9517
+ var WORKSPACE_GLOB_MAX_LENGTH = 256;
9518
+ var WORKSPACE_GLOB_MAX_SEGMENTS = 32;
9519
+ function workspaceGlobMatcher(pattern) {
9520
+ if (pattern.length > WORKSPACE_GLOB_MAX_LENGTH) return null;
9507
9521
  const normalised = pattern.trim().replace(/^\.\//, "").replace(/\/+$/, "");
9508
9522
  if (normalised === "" || normalised.startsWith("/") || /(^|\/)\.\.?(\/|$)/.test(normalised) || /[[\]{}()!+@\\]/.test(normalised)) {
9509
9523
  return null;
9510
9524
  }
9511
- const segments = normalised.split("/").filter((segment, index, all) => !(segment === "**" && all[index - 1] === "**"));
9512
- const anySegment = "(?!\\.)[^/]+";
9513
- if (segments.length === 1 && segments[0] === "**") return new RegExp(`^${anySegment}(?:/${anySegment})*$`);
9514
- let source = "";
9515
- segments.forEach((segment, index) => {
9516
- if (segment === "**") {
9517
- source += index === 0 ? `(?:${anySegment}/)*` : `(?:/${anySegment})*`;
9518
- return;
9525
+ const split = normalised.split("/");
9526
+ if (split.length > WORKSPACE_GLOB_MAX_SEGMENTS) return null;
9527
+ const segments = split.filter((segment, index, all) => !(segment === "**" && all[index - 1] === "**"));
9528
+ const isAnySegment = (part) => part !== "" && !part.startsWith(".");
9529
+ if (segments.length === 1 && segments[0] === "**") {
9530
+ return (dir) => dir.split("/").every(isAnySegment);
9531
+ }
9532
+ return (dir) => {
9533
+ let reachable = withGlobstarSkips(segments, [true, ...segments.map(() => false)]);
9534
+ for (const part of dir.split("/")) {
9535
+ const next = segments.map(() => false).concat(false);
9536
+ segments.forEach((segment, index) => {
9537
+ if (!reachable[index]) return;
9538
+ if (segment === "**") {
9539
+ if (isAnySegment(part)) next[index] = true;
9540
+ } else if (matchesWildcardSegment(segment, part)) {
9541
+ next[index + 1] = true;
9542
+ }
9543
+ });
9544
+ reachable = withGlobstarSkips(segments, next);
9545
+ if (!reachable.includes(true)) return false;
9519
9546
  }
9520
- if (index > 0 && !(index === 1 && segments[0] === "**")) source += "/";
9521
- const body = segment.split("").map(
9522
- (char) => char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.^$|]/g, "\\$&")
9523
- ).join("");
9524
- source += /^[*?]/.test(segment) ? `(?!\\.)${body}` : body;
9547
+ return reachable[segments.length] === true;
9548
+ };
9549
+ }
9550
+ function withGlobstarSkips(segments, reachable) {
9551
+ segments.forEach((segment, index) => {
9552
+ if (segment === "**" && reachable[index]) reachable[index + 1] = true;
9525
9553
  });
9526
- return new RegExp(`^${source}$`);
9554
+ return reachable;
9555
+ }
9556
+ function matchesWildcardSegment(glob, part) {
9557
+ if ((glob.startsWith("*") || glob.startsWith("?")) && part.startsWith(".")) return false;
9558
+ let g = 0;
9559
+ let p = 0;
9560
+ let starGlob = -1;
9561
+ let starPart = 0;
9562
+ while (p < part.length) {
9563
+ if (g < glob.length && glob[g] === "*") {
9564
+ starGlob = g;
9565
+ starPart = p;
9566
+ g += 1;
9567
+ } else if (g < glob.length && (glob[g] === "?" || glob[g] === part[p])) {
9568
+ g += 1;
9569
+ p += 1;
9570
+ } else if (starGlob !== -1) {
9571
+ g = starGlob + 1;
9572
+ starPart += 1;
9573
+ p = starPart;
9574
+ } else {
9575
+ return false;
9576
+ }
9577
+ }
9578
+ while (g < glob.length && glob[g] === "*") g += 1;
9579
+ return g === glob.length;
9527
9580
  }
9528
9581
  function ciWorkflowCitationLabel(baseLabel, verb, considered, cited, unread, delegated = NO_DELEGATED_CI_STEPS) {
9529
9582
  const unreadTotal = [...unread.values()].reduce((sum, count) => sum + count, 0);
@@ -9612,6 +9665,263 @@ function unreadCiWorkflowReasons(includes) {
9612
9665
  }
9613
9666
  return unread;
9614
9667
  }
9668
+ var BUILD_STEP_EXECUTABLES = /* @__PURE__ */ new Set([
9669
+ "babel",
9670
+ "esbuild",
9671
+ "ncc",
9672
+ "rollup",
9673
+ "svelte-check",
9674
+ "swc",
9675
+ "tsc",
9676
+ "tsup",
9677
+ "vue-tsc",
9678
+ "webpack",
9679
+ "webpack-cli"
9680
+ ]);
9681
+ var BUILD_STEP_SUBCOMMANDS = /* @__PURE__ */ new Map([
9682
+ ["bun", "build"],
9683
+ ["nest", "build"],
9684
+ ["next", "build"],
9685
+ ["ng", "build"],
9686
+ ["parcel", "build"],
9687
+ ["vite", "build"]
9688
+ ]);
9689
+ var BUILD_STEP_TASK_RUNNERS = /* @__PURE__ */ new Set([
9690
+ "bun",
9691
+ "gmake",
9692
+ "just",
9693
+ "lerna",
9694
+ "make",
9695
+ "npm",
9696
+ "nx",
9697
+ "pnpm",
9698
+ "turbo",
9699
+ "yarn"
9700
+ ]);
9701
+ var BUILD_STEP_TARGET_NAME_PATTERN = /^(?:build|typecheck)$/u;
9702
+ var BUILD_STEP_TARGET_ARGUMENT_PATTERN = /^(?:[^\s:]+:)?(?:build|typecheck)$/u;
9703
+ var TYPESCRIPT_SOURCE_PATTERN = /\.(?:ts|tsx|mts|cts)$/u;
9704
+ var TYPESCRIPT_DECLARATION_PATTERN = /\.d\.(?:ts|mts|cts)$/u;
9705
+ var TSCONFIG_PATTERN = /(^|\/)tsconfig[^/]*\.json$/u;
9706
+ var BUILD_STEP_DOCKERFILE_PATTERN = /(^|\/)(?:dockerfile|containerfile)(?:\.[^/]*)?$|[^/]\.dockerfile$/iu;
9707
+ function settleV26BuildStepEvidence(repoPath, repoFiles, packageJson, workspaceManifests, workflows, includesWorkflow) {
9708
+ const nestedPackageDirs = repoFiles.filter((file) => file !== packageJson && /(^|\/)package\.json$/u.test(file)).map((file) => file.slice(0, -"package.json".length));
9709
+ const hasSomethingToBuild = repoFiles.some(
9710
+ (file) => isAuthoredProductionPath(file) && (TYPESCRIPT_SOURCE_PATTERN.test(file) && !TYPESCRIPT_DECLARATION_PATTERN.test(file) || TSCONFIG_PATTERN.test(file)) && !nestedPackageDirs.some((dir) => file.startsWith(dir))
9711
+ );
9712
+ if (!hasSomethingToBuild) return "nothing-to-build";
9713
+ let unsettled = false;
9714
+ const commandRunsStep = (command) => {
9715
+ const state = classifyBuildStepCommand(command);
9716
+ if (state === "unparseable") unsettled = true;
9717
+ return state === "step";
9718
+ };
9719
+ for (const manifest of [packageJson, ...workspaceManifests.read]) {
9720
+ const scripts = readBuildStepPackageScripts(join(repoPath, manifest));
9721
+ if (scripts === null) {
9722
+ unsettled = true;
9723
+ continue;
9724
+ }
9725
+ for (const [name, command] of scripts) {
9726
+ if (BUILD_STEP_TARGET_NAME_PATTERN.test(name) || commandRunsStep(command)) return "step-found";
9727
+ }
9728
+ }
9729
+ for (const workflow of workflows) {
9730
+ const fullPath = join(repoPath, workflow);
9731
+ if (!/^\.github\/workflows\//u.test(workflow) || !isRegularFile(fullPath)) {
9732
+ unsettled = true;
9733
+ continue;
9734
+ }
9735
+ if (extractCiRunCommands(readRepoText(fullPath, "utf8")).some(commandRunsStep)) {
9736
+ return "step-found";
9737
+ }
9738
+ }
9739
+ for (const file of repoFiles.filter((path) => BUILD_STEP_DOCKERFILE_PATTERN.test(path))) {
9740
+ const fullPath = join(repoPath, file);
9741
+ if (!isRegularFile(fullPath)) {
9742
+ unsettled = true;
9743
+ continue;
9744
+ }
9745
+ const run = dockerfileRunCommands(readRepoText(fullPath, "utf8"));
9746
+ if (run.unparseable) unsettled = true;
9747
+ if (run.commands.some(commandRunsStep)) return "step-found";
9748
+ }
9749
+ for (const file of repoFiles.filter(isRecipeFile)) {
9750
+ const fullPath = join(repoPath, file);
9751
+ if (!isRegularFile(fullPath)) {
9752
+ unsettled = true;
9753
+ continue;
9754
+ }
9755
+ for (const [name, target] of readRecipeTargets(readRepoText(fullPath, "utf8"))) {
9756
+ if (BUILD_STEP_TARGET_NAME_PATTERN.test(name)) return "step-found";
9757
+ if (target.commands.some((command) => commandRunsStep(command.replace(/\\$/u, "")))) {
9758
+ return "step-found";
9759
+ }
9760
+ }
9761
+ }
9762
+ if (workspaceManifests.declared > workspaceManifests.read.length || workspaceManifests.unreadable) {
9763
+ unsettled = true;
9764
+ }
9765
+ if (unreadCiWorkflowReasons(includesWorkflow).size > 0) unsettled = true;
9766
+ const delegated = countDelegatedCiSteps(repoPath, workflows);
9767
+ if (delegated.crossRepositoryReusableWorkflows + delegated.compositeActions > 0) unsettled = true;
9768
+ if (withheldRepoPaths().some(
9769
+ ({ path }) => BUILD_STEP_DOCKERFILE_PATTERN.test(path) || isRecipeFile(path) || /(^|\/)package\.json$/u.test(path) && isAuthoredProductionPath(path)
9770
+ )) {
9771
+ unsettled = true;
9772
+ }
9773
+ return unsettled ? "unsettled" : "fires";
9774
+ }
9775
+ function readBuildStepPackageScripts(packageJsonPath) {
9776
+ if (!isRegularFile(packageJsonPath)) return null;
9777
+ const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
9778
+ if (parsed === null) return null;
9779
+ if (parsed.scripts === void 0) return /* @__PURE__ */ new Map();
9780
+ if (!isRecord(parsed.scripts)) return null;
9781
+ const entries = Object.entries(parsed.scripts);
9782
+ if (!entries.every((entry) => typeof entry[1] === "string")) return null;
9783
+ return new Map(entries);
9784
+ }
9785
+ function dockerfileRunCommands(contents) {
9786
+ if (/^#\s*escape\s*=/imu.test(contents)) return { commands: [], unparseable: true };
9787
+ const lines = contents.split(/\r?\n/u);
9788
+ const commands = [];
9789
+ let unparseable = false;
9790
+ for (let index = 0; index < lines.length; index += 1) {
9791
+ const match = /^\s*RUN(?:\s+(.*))?$/iu.exec(lines[index] ?? "");
9792
+ if (!match) continue;
9793
+ const chunks = [match[1] ?? ""];
9794
+ while (endsWithLineContinuation(chunks) && index + 1 < lines.length) {
9795
+ index += 1;
9796
+ const next = lines[index] ?? "";
9797
+ if (/^\s*#/u.test(next)) continue;
9798
+ dropLineContinuation(chunks);
9799
+ chunks.push(" ", next.trim());
9800
+ }
9801
+ let instruction = chunks.join("");
9802
+ instruction = instruction.replace(/^(?:--[A-Za-z][\w-]*(?:=\S*)?\s+)*/u, "").trim();
9803
+ if (instruction === "" || /<<-?\s*["']?[A-Za-z_]/u.test(instruction)) {
9804
+ unparseable = true;
9805
+ continue;
9806
+ }
9807
+ if (!instruction.startsWith("[")) {
9808
+ commands.push(instruction);
9809
+ continue;
9810
+ }
9811
+ const execForm = dockerfileExecFormCommand(instruction);
9812
+ if (execForm === null) unparseable = true;
9813
+ else commands.push(execForm);
9814
+ }
9815
+ return { commands, unparseable };
9816
+ }
9817
+ function endsWithLineContinuation(chunks) {
9818
+ for (let c = chunks.length - 1; c >= 0; c -= 1) {
9819
+ const chunk = chunks[c] ?? "";
9820
+ for (let i = chunk.length - 1; i >= 0; i -= 1) {
9821
+ const character = chunk.charAt(i);
9822
+ if (/\s/u.test(character)) continue;
9823
+ return character === "\\";
9824
+ }
9825
+ }
9826
+ return false;
9827
+ }
9828
+ function dropLineContinuation(chunks) {
9829
+ while (chunks.length > 0) {
9830
+ const chunk = chunks[chunks.length - 1] ?? "";
9831
+ let i = chunk.length - 1;
9832
+ while (i >= 0 && /\s/u.test(chunk.charAt(i))) i -= 1;
9833
+ if (i < 0) {
9834
+ chunks.pop();
9835
+ continue;
9836
+ }
9837
+ chunks[chunks.length - 1] = chunk.slice(0, i);
9838
+ return;
9839
+ }
9840
+ }
9841
+ function dockerfileExecFormCommand(instruction) {
9842
+ let parsed;
9843
+ try {
9844
+ parsed = JSON.parse(instruction);
9845
+ } catch {
9846
+ return null;
9847
+ }
9848
+ if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every((part) => typeof part === "string")) {
9849
+ return null;
9850
+ }
9851
+ return parsed.map(
9852
+ (part) => /^[A-Za-z0-9_@%+=:,./-]+$/u.test(part) ? part : `'${part.replaceAll("'", "'\\''")}'`
9853
+ ).join(" ");
9854
+ }
9855
+ function classifyBuildStepCommand(command) {
9856
+ return buildStepCommandState(command, 0);
9857
+ }
9858
+ function buildStepCommandState(command, depth) {
9859
+ if (depth > 4) return "unparseable";
9860
+ const states = splitShellCommandSegments(command).map(
9861
+ (segment) => buildStepSegmentState(segment, depth)
9862
+ );
9863
+ if (states.includes("step")) return "step";
9864
+ return states.includes("unparseable") ? "unparseable" : "none";
9865
+ }
9866
+ function buildStepSegmentState(raw, depth) {
9867
+ let segment = stripUnbalancedShellParentheses(raw.trim());
9868
+ for (; ; ) {
9869
+ const keyword = /^(?:if|then|else|elif|do|while|until|exec|!|\{)(?:\s+|$)/u.exec(segment);
9870
+ if (!keyword) break;
9871
+ segment = segment.slice(keyword[0].length);
9872
+ }
9873
+ if (segment === "" || /^(?:fi|done|esac|\})$/u.test(segment)) return "none";
9874
+ const normalized = normalizeCoverageCommandPrefixes(segment);
9875
+ if (normalized.splitShellOperators && splitShellCommandSegments(normalized.segment).length > 1) {
9876
+ return buildStepCommandState(normalized.segment, depth + 1);
9877
+ }
9878
+ if (/\b(?:npm|pnpm|yarn|bun)\s+run\s+(?:\$|["']\$|`)/u.test(normalized.segment)) {
9879
+ return "unparseable";
9880
+ }
9881
+ const words = strictShellWords(normalized.segment);
9882
+ if (words === null) return "unparseable";
9883
+ return words.length === 0 ? "none" : buildStepWordsState(words);
9884
+ }
9885
+ function buildStepWordsState(words) {
9886
+ const first = words[0] === "$(MAKE)" || words[0] === "${MAKE}" ? "make" : words[0] ?? "";
9887
+ if (isUnresolvedCommandWord(first)) return "unparseable";
9888
+ const runner = buildStepCommandName(first);
9889
+ if (BUILD_STEP_TASK_RUNNERS.has(runner) && words.slice(1).some((word) => BUILD_STEP_TARGET_ARGUMENT_PATTERN.test(word))) {
9890
+ return "step";
9891
+ }
9892
+ const command = runner === "bunx" ? words.slice(1) : runner === "bun" && words[1] === "x" ? words.slice(2) : unwrapPackageManagerExecutable(words);
9893
+ const executable = command[0] ?? "";
9894
+ if (isUnresolvedCommandWord(executable)) return "unparseable";
9895
+ const name = buildStepCommandName(executable);
9896
+ if (BUILD_STEP_EXECUTABLES.has(name)) return "step";
9897
+ const subcommand = BUILD_STEP_SUBCOMMANDS.get(name);
9898
+ return subcommand !== void 0 && command.slice(1).includes(subcommand) ? "step" : "none";
9899
+ }
9900
+ function isUnresolvedCommandWord(word) {
9901
+ return word.startsWith("$") || word.includes("`");
9902
+ }
9903
+ function buildStepCommandName(word) {
9904
+ return basename(word).replace(/\.(?:cmd|exe)$/iu, "").toLowerCase();
9905
+ }
9906
+ function stripUnbalancedShellParentheses(segment) {
9907
+ const opens = segment.split("(").length - 1;
9908
+ const closes = segment.split(")").length - 1;
9909
+ if (opens > closes) return segment.replace(/^\(+\s*/u, "");
9910
+ if (closes > opens) return segment.replace(/\s*\)+$/u, "");
9911
+ return segment;
9912
+ }
9913
+ function strictShellWords(segment) {
9914
+ const words = [];
9915
+ let offset = 0;
9916
+ for (; ; ) {
9917
+ while (offset < segment.length && /\s/u.test(segment[offset] ?? "")) offset += 1;
9918
+ if (offset >= segment.length) return words;
9919
+ const scanned = scanShellWord(segment, offset);
9920
+ if (!scanned) return null;
9921
+ if (scanned.word !== "") words.push(scanned.word);
9922
+ offset = scanned.end;
9923
+ }
9924
+ }
9615
9925
  function rootOnlyPackageScriptsDisclosure(repoFiles, rootPackageJson) {
9616
9926
  const others = repoFiles.filter(
9617
9927
  (file) => file !== rootPackageJson && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  serveCejelMcpOverStdio
6
- } from "./chunk-35TRRAEC.js";
6
+ } from "./chunk-TOQPQPDF.js";
7
7
  import {
8
8
  CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
9
9
  CERTIFICATE_GLOSSARY,
@@ -29,8 +29,8 @@ import {
29
29
  runCejelScan,
30
30
  serializeWitanReport,
31
31
  verifyWitanAttestationBinding
32
- } from "./chunk-7XQVE76B.js";
33
- import "./chunk-63XQDYQ5.js";
32
+ } from "./chunk-ZZHQZW26.js";
33
+ import "./chunk-VGBFZBZQ.js";
34
34
  import {
35
35
  external_exports
36
36
  } from "./chunk-FSMZV3Y6.js";
@@ -41,8 +41,8 @@ import {
41
41
  // src/index.ts
42
42
  init_esm_shims();
43
43
  import { createHash as createHash3 } from "crypto";
44
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
45
- import { dirname, join, resolve as resolve2 } from "path";
44
+ import { existsSync, readFileSync, realpathSync } from "fs";
45
+ import { dirname, join as join2, resolve as resolve3 } from "path";
46
46
  import { fileURLToPath, pathToFileURL } from "url";
47
47
 
48
48
  // src/issuance/index.ts
@@ -578,6 +578,68 @@ function resolvedToLocalTree(env, cwd) {
578
578
  return first !== void 0 && first === resolve(cwd, "node_modules", ".bin");
579
579
  }
580
580
 
581
+ // src/output-files.ts
582
+ init_esm_shims();
583
+ import { closeSync, constants, lstatSync, mkdirSync, openSync, writeFileSync } from "fs";
584
+ import { join, resolve as resolve2 } from "path";
585
+ var O_NOFOLLOW = constants.O_NOFOLLOW ?? 0;
586
+ function isSymbolicLink(path) {
587
+ try {
588
+ return lstatSync(path).isSymbolicLink();
589
+ } catch (error) {
590
+ if (error.code === "ENOENT") return false;
591
+ throw error;
592
+ }
593
+ }
594
+ function refusal(what, subject) {
595
+ return new Error(
596
+ `Cejel: refusing to write ${what}: ${subject} is a symbolic link. Nothing was written; remove the link or choose another output path.`
597
+ );
598
+ }
599
+ function refuseSymlinkedDirectory(dir, what) {
600
+ if (isSymbolicLink(resolve2(dir))) throw refusal(what, `output directory ${dir}`);
601
+ }
602
+ function refuseSymlinkedFile(path, what) {
603
+ if (isSymbolicLink(resolve2(path))) throw refusal(what, path);
604
+ }
605
+ function refuseSymlinkedOutputs(dir, names, what) {
606
+ refuseSymlinkedDirectory(dir, what);
607
+ for (const name of names) refuseSymlinkedFile(join(dir, name), what);
608
+ }
609
+ function writeOutputFileNoFollow(path, contents, what) {
610
+ let fd;
611
+ try {
612
+ fd = openSync(
613
+ path,
614
+ constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | O_NOFOLLOW,
615
+ 438
616
+ );
617
+ } catch (error) {
618
+ if (error.code === "ELOOP") {
619
+ throw new Error(
620
+ `Cejel: refusing to write ${what}: ${path} is a symbolic link (the open was refused without following it).`
621
+ );
622
+ }
623
+ throw error;
624
+ }
625
+ try {
626
+ writeFileSync(fd, contents, "utf8");
627
+ } finally {
628
+ closeSync(fd);
629
+ }
630
+ }
631
+ function writeOutputFile(path, contents, what) {
632
+ refuseSymlinkedFile(path, what);
633
+ writeOutputFileNoFollow(path, contents, what);
634
+ }
635
+ function writeOutputFiles(dir, files, what) {
636
+ const names = files.map((file) => file.name);
637
+ refuseSymlinkedOutputs(dir, names, what);
638
+ mkdirSync(dir, { recursive: true });
639
+ refuseSymlinkedDirectory(dir, what);
640
+ for (const file of files) writeOutputFileNoFollow(join(dir, file.name), file.contents, what);
641
+ }
642
+
581
643
  // src/terminal.ts
582
644
  init_esm_shims();
583
645
  function renderMinScoreAbstentionFailure(summary, minScore) {
@@ -601,19 +663,22 @@ function renderMinScoreCoverageFailure(coverage, minScore) {
601
663
  return `Cejel: cannot evaluate the required minimum ${minScore.toFixed(1)}/4.0 because fewer than half of the applicable dimensions were measured in at least one score (${formatCoverageSummary(coverage)}). Resolve the reported evidence gaps and re-scan.
602
664
  `;
603
665
  }
666
+ function stripTerminalControls(text) {
667
+ return text.replace(/[\t\n]/g, " ").replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
668
+ }
604
669
  function renderTerminalCertificate(summary, report) {
605
670
  const abstained = summary.verdict === "Insufficient source" || summary.verdict === "Insufficient evidence";
606
671
  if (!abstained && (summary.overallScore === null || summary.codeTrustScore === null || summary.processTrustScore === null)) {
607
672
  throw new Error("A scored Cejel summary must carry numeric headline scores.");
608
673
  }
609
674
  const lines = abstained ? [
610
- `Cejel Trust Certificate \u2014 ${summary.productDisplayName}`,
675
+ `Cejel Trust Certificate \u2014 ${stripTerminalControls(summary.productDisplayName)}`,
611
676
  "",
612
677
  `${summary.verdict} to certify.`,
613
- ` ${summary.insufficientSourceReason ?? "No ratable source or measurable evidence was found."}`,
678
+ ` ${summary.insufficientSourceReason === void 0 ? "No ratable source or measurable evidence was found." : stripTerminalControls(summary.insufficientSourceReason)}`,
614
679
  ""
615
680
  ] : [
616
- `Cejel Trust Certificate \u2014 ${summary.productDisplayName}`,
681
+ `Cejel Trust Certificate \u2014 ${stripTerminalControls(summary.productDisplayName)}`,
617
682
  "",
618
683
  `Overall: ${formatScore(summary.overallScore)}/4.0 (${summary.verdict})`,
619
684
  ` Code trust: ${formatScore(summary.codeTrustScore)}/4.0`,
@@ -629,7 +694,7 @@ function renderTerminalCertificate(summary, report) {
629
694
  2,
630
695
  0,
631
696
  `Scope: ${relyingPartySummary.scope}`,
632
- `What was examined: ${relyingPartySummary.examined}`,
697
+ `What was examined: ${stripTerminalControls(relyingPartySummary.examined)}`,
633
698
  `What was established: ${relyingPartySummary.established}`,
634
699
  `What was not established: ${relyingPartySummary.notEstablished}`,
635
700
  `What to do next: ${relyingPartySummary.next}`,
@@ -639,14 +704,14 @@ function renderTerminalCertificate(summary, report) {
639
704
  if (summary.scanLimitations.length > 0) {
640
705
  lines.push(
641
706
  "LIMITED EVIDENCE \u2014 the score and verdict are qualified by scan limitations:",
642
- ...summary.scanLimitations.map((limitation) => ` - ${limitation}`),
707
+ ...summary.scanLimitations.map((limitation) => ` - ${stripTerminalControls(limitation)}`),
643
708
  ""
644
709
  );
645
710
  }
646
711
  if (summary.historyLimitations?.length) {
647
712
  lines.push(
648
713
  "HISTORY LIMITATION \u2014 scored as usual, but some criteria read truncated history:",
649
- ...summary.historyLimitations.map((limitation) => ` - ${limitation}`),
714
+ ...summary.historyLimitations.map((limitation) => ` - ${stripTerminalControls(limitation)}`),
650
715
  ""
651
716
  );
652
717
  }
@@ -664,9 +729,11 @@ function renderTerminalCertificate(summary, report) {
664
729
  lines.push("");
665
730
  }
666
731
  if (summary.contributingSources.length > 0) {
667
- lines.push(`Incorporates findings from: ${summary.contributingSources.join(", ")}`);
732
+ lines.push(
733
+ `Incorporates findings from: ${summary.contributingSources.map(stripTerminalControls).join(", ")}`
734
+ );
668
735
  for (const source of summary.externalSources) {
669
- lines.push(` ${formatExternalSourceLine(source)}`);
736
+ lines.push(` ${stripTerminalControls(formatExternalSourceLine(source))}`);
670
737
  }
671
738
  lines.push("");
672
739
  }
@@ -697,7 +764,7 @@ function renderTerminalCertificate(summary, report) {
697
764
  );
698
765
  for (const finding of summary.topFindings) {
699
766
  lines.push(
700
- ` [finding severity: ${finding.severity}] ${finding.criterionId} [dimension band: ${finding.dimensionBand}]: ${finding.displaySummary ?? finding.summary}`
767
+ ` [finding severity: ${finding.severity}] ${finding.criterionId} [dimension band: ${finding.dimensionBand}]: ${stripTerminalControls(finding.displaySummary ?? finding.summary)}`
701
768
  );
702
769
  }
703
770
  const remaining = summary.findingCount - summary.topFindings.length;
@@ -714,7 +781,7 @@ function renderTerminalCertificate(summary, report) {
714
781
  );
715
782
  for (const finding of summary.topExternalFindings) {
716
783
  lines.push(
717
- ` [${finding.severity}] ${finding.label} \u2192 ${finding.dimension}: ${finding.ruleId} \u2014 ${finding.message}${finding.location ? ` (${finding.location})` : ""}`
784
+ ` [${finding.severity}] ${stripTerminalControls(finding.label)} \u2192 ${finding.dimension}: ${stripTerminalControls(finding.ruleId)} \u2014 ${stripTerminalControls(finding.message)}${finding.location ? ` (${stripTerminalControls(finding.location)})` : ""}`
718
785
  );
719
786
  }
720
787
  const remaining = summary.externalFindingCount - summary.topExternalFindings.length;
@@ -739,6 +806,15 @@ function formatScore(score) {
739
806
 
740
807
  // src/index.ts
741
808
  var DEFAULT_OUT_DIR = ".cejel";
809
+ var SCAN_OUTPUT_FILES = [
810
+ "report.json",
811
+ "attestation.json",
812
+ "certificate.html",
813
+ "badge.json",
814
+ "badge.svg",
815
+ "summary.json"
816
+ ];
817
+ var ISSUANCE_OUTPUT_FILE = "issuance.json";
742
818
  var ISSUER_SIGNERS_FILE = "issuer-signers";
743
819
  var ISSUER_REVOCATIONS_FILE = "issuer-revocations";
744
820
  var SECURITY_FILE_CANDIDATES = ["../docs/security/", "../../docs/security/"];
@@ -951,6 +1027,7 @@ async function runWitanCli(args, rubricVersion) {
951
1027
  return 0;
952
1028
  }
953
1029
  const effectiveRubricVersion = options.rubricPin ?? rubricVersion;
1030
+ refuseSymlinkedOutputs(options.outDir, SCAN_OUTPUT_FILES, "scan output");
954
1031
  const { report, summary, generatedAt } = runCejelScan({
955
1032
  repoPath: options.repoPath,
956
1033
  ...options.productName ? { productName: options.productName } : {},
@@ -965,29 +1042,25 @@ async function runWitanCli(args, rubricVersion) {
965
1042
  generatedAt,
966
1043
  ...options.runAttempt ? { githubRunAttempt: options.runAttempt } : {}
967
1044
  });
968
- mkdirSync(options.outDir, { recursive: true });
969
- writeFileSync(join(options.outDir, "report.json"), serializeWitanReport(report), "utf8");
970
- writeFileSync(
971
- join(options.outDir, "attestation.json"),
972
- JSON.stringify(attestation, null, 2),
973
- "utf8"
974
- );
975
- writeFileSync(
976
- join(options.outDir, "certificate.html"),
977
- renderWitanHtmlReport(report, {
978
- cliVersion: cliVersion(),
979
- generatedAt,
980
- ...options.runAttempt ? { runAttempt: options.runAttempt } : {}
981
- }),
982
- "utf8"
983
- );
984
- writeFileSync(
985
- join(options.outDir, "badge.json"),
986
- JSON.stringify(renderWitanBadgeEndpoint(report), null, 2),
987
- "utf8"
1045
+ writeOutputFiles(
1046
+ options.outDir,
1047
+ [
1048
+ { name: "report.json", contents: serializeWitanReport(report) },
1049
+ { name: "attestation.json", contents: JSON.stringify(attestation, null, 2) },
1050
+ {
1051
+ name: "certificate.html",
1052
+ contents: renderWitanHtmlReport(report, {
1053
+ cliVersion: cliVersion(),
1054
+ generatedAt,
1055
+ ...options.runAttempt ? { runAttempt: options.runAttempt } : {}
1056
+ })
1057
+ },
1058
+ { name: "badge.json", contents: JSON.stringify(renderWitanBadgeEndpoint(report), null, 2) },
1059
+ { name: "badge.svg", contents: renderWitanBadgeSvg(report) },
1060
+ { name: "summary.json", contents: JSON.stringify(summary, null, 2) }
1061
+ ],
1062
+ "scan output"
988
1063
  );
989
- writeFileSync(join(options.outDir, "badge.svg"), renderWitanBadgeSvg(report), "utf8");
990
- writeFileSync(join(options.outDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
991
1064
  if (!options.quiet) {
992
1065
  process.stdout.write(renderTerminalCertificate(summary, report));
993
1066
  process.stdout.write(
@@ -1097,8 +1170,8 @@ function parseExportArgs(args) {
1097
1170
  return {
1098
1171
  command: "export",
1099
1172
  format,
1100
- reportPath: resolve2(reportPath),
1101
- outPath: resolve2(outPath)
1173
+ reportPath: resolve3(reportPath),
1174
+ outPath: resolve3(outPath)
1102
1175
  };
1103
1176
  }
1104
1177
  function runExport(invocation) {
@@ -1116,8 +1189,12 @@ ${members.map((member) => ` - ${member}`).join("\n")}
1116
1189
  return 1;
1117
1190
  }
1118
1191
  const result = exportGitLabCodeQuality(reportResult.data);
1119
- writeFileSync(invocation.outPath, `${JSON.stringify(result.entries, null, 2)}
1120
- `, "utf8");
1192
+ writeOutputFile(
1193
+ invocation.outPath,
1194
+ `${JSON.stringify(result.entries, null, 2)}
1195
+ `,
1196
+ "export output"
1197
+ );
1121
1198
  process.stderr.write(renderGitLabCodeQualityFooter(result));
1122
1199
  return 0;
1123
1200
  }
@@ -1132,8 +1209,8 @@ function parseVerifyArgs(args) {
1132
1209
  if (arg === "--signers" || arg === "--revocations") {
1133
1210
  const value = args[index + 1];
1134
1211
  if (!value) throw new Error(`Missing value for ${arg}`);
1135
- if (arg === "--signers") signersPath = resolve2(value);
1136
- else revocationsPath = resolve2(value);
1212
+ if (arg === "--signers") signersPath = resolve3(value);
1213
+ else revocationsPath = resolve3(value);
1137
1214
  index += 1;
1138
1215
  continue;
1139
1216
  }
@@ -1145,10 +1222,10 @@ function parseVerifyArgs(args) {
1145
1222
  if (!reportPath || !attestationPath) throw new Error(VERIFY_USAGE());
1146
1223
  return {
1147
1224
  command: "verify",
1148
- reportPath: resolve2(reportPath),
1149
- attestationPath: resolve2(attestationPath),
1150
- ...issuancePath ? { issuancePath: resolve2(issuancePath) } : {},
1151
- ...issuanceSignaturePath ? { issuanceSignaturePath: resolve2(issuanceSignaturePath) } : {},
1225
+ reportPath: resolve3(reportPath),
1226
+ attestationPath: resolve3(attestationPath),
1227
+ ...issuancePath ? { issuancePath: resolve3(issuancePath) } : {},
1228
+ ...issuanceSignaturePath ? { issuanceSignaturePath: resolve3(issuanceSignaturePath) } : {},
1152
1229
  ...signersPath ? { signersPath } : {},
1153
1230
  ...revocationsPath ? { revocationsPath } : {}
1154
1231
  };
@@ -1174,15 +1251,15 @@ function parseIssueArgs(args) {
1174
1251
  if (arg === void 0) continue;
1175
1252
  switch (arg) {
1176
1253
  case "--report":
1177
- reportPath = resolve2(takeValue(arg, index + 1));
1254
+ reportPath = resolve3(takeValue(arg, index + 1));
1178
1255
  index += 1;
1179
1256
  continue;
1180
1257
  case "--attestation":
1181
- attestationPath = resolve2(takeValue(arg, index + 1));
1258
+ attestationPath = resolve3(takeValue(arg, index + 1));
1182
1259
  index += 1;
1183
1260
  continue;
1184
1261
  case "--key":
1185
- keyPath = resolve2(takeValue(arg, index + 1));
1262
+ keyPath = resolve3(takeValue(arg, index + 1));
1186
1263
  index += 1;
1187
1264
  continue;
1188
1265
  case "--engagement-ref":
@@ -1191,7 +1268,7 @@ function parseIssueArgs(args) {
1191
1268
  continue;
1192
1269
  case "--out":
1193
1270
  case "--out-dir":
1194
- outDir = resolve2(takeValue(arg, index + 1));
1271
+ outDir = resolve3(takeValue(arg, index + 1));
1195
1272
  index += 1;
1196
1273
  continue;
1197
1274
  case "--product-name":
@@ -1235,7 +1312,7 @@ function parseIssueArgs(args) {
1235
1312
  throw new Error("--product-name and --name cannot be used together");
1236
1313
  }
1237
1314
  return {
1238
- repoPath: resolve2(repoPath ?? "."),
1315
+ repoPath: resolve3(repoPath ?? "."),
1239
1316
  reportPath,
1240
1317
  attestationPath,
1241
1318
  keyPath,
@@ -1405,6 +1482,7 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1405
1482
  throw new Error(`Cejel: could not read --key file ${options.keyPath}: ${message}`);
1406
1483
  }
1407
1484
  const issuerKey = parseIssuerPublicKey(keyContents);
1485
+ refuseSymlinkedOutputs(options.outDir, [ISSUANCE_OUTPUT_FILE], "issuance output");
1408
1486
  const reproduction = runCejelScan({
1409
1487
  repoPath: options.repoPath,
1410
1488
  ...options.productName ? { productName: options.productName } : {},
@@ -1439,9 +1517,12 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1439
1517
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1440
1518
  engagementRef: options.engagementRef
1441
1519
  });
1442
- mkdirSync(options.outDir, { recursive: true });
1443
- const issuancePath = join(options.outDir, "issuance.json");
1444
- writeFileSync(issuancePath, serializeWitanIssuance(statement), "utf8");
1520
+ const issuancePath = join2(options.outDir, ISSUANCE_OUTPUT_FILE);
1521
+ writeOutputFiles(
1522
+ options.outDir,
1523
+ [{ name: ISSUANCE_OUTPUT_FILE, contents: serializeWitanIssuance(statement) }],
1524
+ "issuance output"
1525
+ );
1445
1526
  process.stdout.write(
1446
1527
  [
1447
1528
  `Cejel: reproduced report.json byte for byte (sha256:${reportSha256}).`,
@@ -1588,7 +1669,7 @@ function parseArgs(args) {
1588
1669
  throw new Error("--product-name and --name cannot be used together");
1589
1670
  }
1590
1671
  return {
1591
- repoPath: resolve2(repoPath ?? "."),
1672
+ repoPath: resolve3(repoPath ?? "."),
1592
1673
  outDir,
1593
1674
  ...minScore != null ? { minScore } : {},
1594
1675
  ...productName ? { productName } : {},
package/dist/mcp/index.js CHANGED
@@ -3,9 +3,9 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  serveCejelMcpOverStdio
6
- } from "../chunk-35TRRAEC.js";
7
- import "../chunk-7XQVE76B.js";
8
- import "../chunk-63XQDYQ5.js";
6
+ } from "../chunk-TOQPQPDF.js";
7
+ import "../chunk-ZZHQZW26.js";
8
+ import "../chunk-VGBFZBZQ.js";
9
9
  import "../chunk-FSMZV3Y6.js";
10
10
  import {
11
11
  init_esm_shims
@@ -3,10 +3,10 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  listCejelLlmPackFiles
6
- } from "../../chunk-M6YYPJSN.js";
6
+ } from "../../chunk-5PFYOIOS.js";
7
7
  import {
8
8
  execGit
9
- } from "../../chunk-63XQDYQ5.js";
9
+ } from "../../chunk-VGBFZBZQ.js";
10
10
  import {
11
11
  require_typescript
12
12
  } from "../../chunk-RH6FKREX.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {