@cejel/cejel 0.1.9 → 0.2.0

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.
@@ -4079,8 +4079,7 @@ var WITAN_RUBRIC_VERSION_V5 = "witan-rubric-v5-2026-07-18";
4079
4079
  var WITAN_RUBRIC_VERSION_V6 = "witan-rubric-v6-2026-07-21";
4080
4080
  var WITAN_RUBRIC_VERSION_V7 = "witan-rubric-v7-2026-07-21";
4081
4081
  var WITAN_RUBRIC_VERSION_V8 = "witan-rubric-v8-2026-07-21";
4082
- var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
4083
- var WITAN_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V9;
4082
+ var WITAN_RUBRIC_VERSION = "witan-rubric-v17-2026-07-24";
4084
4083
  var WITAN_TRADING_RUBRIC_VERSION_V0 = "witan-trading-rubric-v0-2026-07-01";
4085
4084
  var WITAN_CRITERION_IDS = [
4086
4085
  "A1",
@@ -4365,6 +4364,18 @@ var WitanAttestationStatementSchema = external_exports.object({
4365
4364
  }).strict()
4366
4365
  }).strict();
4367
4366
 
4367
+ // src/witan/abstention.ts
4368
+ var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a measurable signal. Cejel abstains rather than publish a numeric zero for an entirely unmeasured repository.";
4369
+ var WITAN_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.";
4370
+ function isWitanNoMeasurementAbstention(report) {
4371
+ return report.verdict === "insufficient_source" && report.insufficientSourceReason === WITAN_NO_MEASUREMENT_REASON && report.criteria.every(
4372
+ ({ status }) => status === "not_applicable" || status === "insufficient_data"
4373
+ );
4374
+ }
4375
+ function renderWitanAbstentionLabel(report) {
4376
+ return isWitanNoMeasurementAbstention(report) ? "Insufficient evidence" : "Insufficient source";
4377
+ }
4378
+
4368
4379
  // src/witan/coverage.ts
4369
4380
  var LOW_CONFIDENCE_COVERAGE_THRESHOLD = 0.5;
4370
4381
  function isMeasuredCriterionStatus(status) {
@@ -4451,6 +4462,87 @@ function collectExternalFindings(consumedSignals) {
4451
4462
  });
4452
4463
  }
4453
4464
 
4465
+ // src/witan/finding-presentation.ts
4466
+ var METRIC_DERIVED_FINDING_PATTERN = /^[A-Z]\d+ metric-derived score is (\d+\.\d)\/4\.0, in the (critical|warning) band — no single finding drove this; it reflects the combined metric weighting below\.$/;
4467
+ var REMEDIATION_BY_METRIC = {
4468
+ test_to_source_ratio: "add concrete regression tests for the implementation surface",
4469
+ coverage_percent: "configure coverage and publish a measured threshold or report",
4470
+ verification_script_ratio: "add explicit test, lint, and typecheck verification commands",
4471
+ non_hollow_test_share: "replace skipped or placeholder tests with live assertions",
4472
+ env_handling_depth: "document and enforce safe environment-secret handling",
4473
+ secret_cleanliness: "remove committed secrets and rotate any exposed credentials",
4474
+ tenant_scope_ratio: "apply tenant scoping consistently across data access",
4475
+ rls_policy_count: "add database row-level security policies where the data model requires them",
4476
+ crypto_comparison_hygiene: "use constant-time comparison for signature or MAC checks",
4477
+ prod_readiness_primitives: "add the missing deployment-readiness controls",
4478
+ observability_depth: "add health checks, structured logging, and error boundaries",
4479
+ rollback_safety_depth: "document and test rollback or recovery procedures",
4480
+ pinned_dependency_ratio: "pin application dependencies that determine the deployed artifact",
4481
+ lockfile_coverage: "commit the package-manager lockfile used by CI and deployment",
4482
+ dependency_automation_ratio: "enable automated dependency updates and an audit command",
4483
+ declared_version_range_ratio: "declare an explicit compatible version for every dependency",
4484
+ dependency_count_sanity: "review and reduce unnecessary direct dependencies",
4485
+ claim_match_rate: "align documented product claims with observable repository evidence",
4486
+ claim_source_depth: "document concrete, checkable product claims",
4487
+ reconciliation_artifact_depth: "publish a claim-to-evidence reconciliation record",
4488
+ pr_merge_ratio: "preserve merged pull-request history in the scanned clone",
4489
+ pr_trace_primitives: "add pull-request templates and outcome-trace records",
4490
+ ci_script_depth: "run the repository verification commands in CI",
4491
+ default_branch_ci_depth: "run CI on the default branch and pull requests",
4492
+ prod_workflow_depth: "add a production or release verification workflow",
4493
+ audit_artifact_depth: "publish changelog, incident, security, or audit records",
4494
+ audit_freshness_depth: "keep the audit trail current",
4495
+ human_gate_documented: "document which privileged operations require human approval",
4496
+ protected_path_review_gate: "require review for changes to protected paths",
4497
+ fail_closed_privilege_check: "make privileged-operation checks fail closed",
4498
+ kill_switch_fail_safe_present: "add a fail-safe kill switch for privileged automation",
4499
+ privilege_escalation_cleanliness: "remove unsafe privilege-escalation patterns"
4500
+ };
4501
+ function renderFindingSummary(criterion, finding) {
4502
+ const match = METRIC_DERIVED_FINDING_PATTERN.exec(finding.summary);
4503
+ if (!match) return finding.summary;
4504
+ const score = match[1] ?? criterion.score.toFixed(1);
4505
+ const band = match[2] ?? criterion.status;
4506
+ const drivers = selectLowestDrivers(criterion.metrics);
4507
+ if (drivers.length === 0) {
4508
+ return `${criterion.id} dimension band is ${band} at ${score}/4.0. To improve this dimension, add stronger measurable evidence for ${criterion.title.toLowerCase()}.`;
4509
+ }
4510
+ const measurements = drivers.map(formatMetricMember).join("; ");
4511
+ const actions = Array.from(
4512
+ new Set(
4513
+ drivers.map(
4514
+ (metric2) => REMEDIATION_BY_METRIC[metric2.name] ?? `raise the measured ${metric2.label.toLowerCase()}`
4515
+ )
4516
+ )
4517
+ ).join("; ");
4518
+ return `${criterion.id} dimension band is ${band} at ${score}/4.0. Lowest contributing measurements: ${measurements}. To improve: ${actions}.`;
4519
+ }
4520
+ function selectLowestDrivers(metrics) {
4521
+ return [...metrics].filter((metric2) => metric2.max !== void 0).sort((left, right) => {
4522
+ const leftRatio = normalizedMetricValue(left);
4523
+ const rightRatio = normalizedMetricValue(right);
4524
+ if (leftRatio !== rightRatio) return leftRatio - rightRatio;
4525
+ if (left.weight !== right.weight) return right.weight - left.weight;
4526
+ return left.name.localeCompare(right.name);
4527
+ }).slice(0, 2);
4528
+ }
4529
+ function normalizedMetricValue(metric2) {
4530
+ if (metric2.max === void 0) return 1;
4531
+ return Math.min(metric2.value / metric2.max, 1);
4532
+ }
4533
+ function formatMetricMember(metric2) {
4534
+ const unit = metric2.unit ? ` ${metric2.unit}` : "";
4535
+ const maximum = metric2.max;
4536
+ if (maximum === void 0) return `${metric2.label} ${formatMetricValue(metric2.value)}${unit}`;
4537
+ if (metric2.kind === "saturating_count" && metric2.value > maximum) {
4538
+ return `${metric2.label} ${formatMetricValue(maximum)}${unit} capped (${formatMetricValue(metric2.value)} raw)`;
4539
+ }
4540
+ return `${metric2.label} ${formatMetricValue(metric2.value)}/${formatMetricValue(maximum)}${unit}`;
4541
+ }
4542
+ function formatMetricValue(value) {
4543
+ return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
4544
+ }
4545
+
4454
4546
  // src/witan/html.ts
4455
4547
  function renderWitanHtmlReport(report) {
4456
4548
  const codeCriteria = criteriaByCategory(report, "code_trust");
@@ -4491,7 +4583,7 @@ function renderWitanHtmlReport(report) {
4491
4583
  </section>
4492
4584
  <aside class="score-panel" aria-label="Summary trust score">
4493
4585
  <div class="score-badge">Summary</div>
4494
- ${report.verdict === "insufficient_source" ? `<div class="verdict">Insufficient source</div>
4586
+ ${report.verdict === "insufficient_source" ? `<div class="verdict">${escapeHtml(renderWitanAbstentionLabel(report))}</div>
4495
4587
  <p class="insufficient-note">${escapeHtml(report.insufficientSourceReason)}</p>` : `<div class="score">${formatScore(report.overallScore)}</div>
4496
4588
  <div class="score-unit">/ 4.0 overall</div>
4497
4589
  <div class="verdict">${escapeHtml(renderVerdict(report.overallScore))}</div>
@@ -4576,7 +4668,7 @@ function renderNotApplicableItem(criterion) {
4576
4668
  function renderCriterionCard(criterion) {
4577
4669
  const evidence = [
4578
4670
  ...criterion.evidence.map(renderEvidencePointer),
4579
- ...criterion.findings.map((finding) => renderFindingEvidence(finding))
4671
+ ...criterion.findings.map((finding) => renderFindingEvidence(criterion, finding))
4580
4672
  ];
4581
4673
  const metrics = criterion.metrics.map(renderMetric);
4582
4674
  return `<article class="criterion">
@@ -4599,24 +4691,24 @@ function renderCriterionCard(criterion) {
4599
4691
  function renderMetric(metric2) {
4600
4692
  const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
4601
4693
  if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
4602
- return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue(metric2.max)}${unit} (capped; ${formatMetricValue(metric2.value)} raw)</span>`;
4694
+ return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue2(metric2.max)}${unit} (capped; ${formatMetricValue2(metric2.value)} raw)</span>`;
4603
4695
  }
4604
- const max = metric2.max ? `/${formatMetricValue(metric2.max)}` : "";
4605
- return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue(metric2.value)}${max}${unit}</span>`;
4696
+ const max = metric2.max ? `/${formatMetricValue2(metric2.max)}` : "";
4697
+ return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue2(metric2.value)}${max}${unit}</span>`;
4606
4698
  }
4607
4699
  function renderCategoryScore(score, coverage, category) {
4608
4700
  const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
4609
4701
  return categoryCoverage !== void 0 && categoryCoverage.measured === 0 || score === null ? "not measured" : formatScore(score);
4610
4702
  }
4611
4703
  function renderStatusChip(status) {
4612
- return `<span class="status" data-status="${status}">${status}</span>`;
4704
+ return `<span class="status" data-status="${status}">dimension band: ${status}</span>`;
4613
4705
  }
4614
4706
  function renderEvidenceListItem(criterion, evidence) {
4615
4707
  return `<li><strong>${escapeHtml(criterion.id)} - ${escapeHtml(criterion.title)}</strong><span>${renderEvidencePointer(evidence)}</span></li>`;
4616
4708
  }
4617
4709
  function renderOpenItems(criterion) {
4618
4710
  const findingItems = criterion.findings.map(
4619
- (finding) => `<li><strong>${escapeHtml(criterion.id)} - ${escapeHtml(criterion.title)}</strong><span>${escapeHtml(finding.severity)}: ${escapeHtml(finding.summary)} (${renderEvidencePointer(finding.evidence)})</span></li>`
4711
+ (finding) => `<li><strong>${escapeHtml(criterion.id)} - ${escapeHtml(criterion.title)}</strong><span>finding severity ${escapeHtml(finding.severity)}: ${escapeHtml(renderFindingSummary(criterion, finding))} (${renderEvidencePointer(finding.evidence)})</span></li>`
4620
4712
  );
4621
4713
  if ((criterion.status === "unverified" || criterion.status === "insufficient_data") && criterion.evidence.length === 0 && criterion.findings.length === 0) {
4622
4714
  const message = criterion.status === "insufficient_data" ? "Insufficient data \u2014 no measurable signal; excluded from composite (unmeasured, not inapplicable)." : "No concrete evidence supplied.";
@@ -4627,10 +4719,8 @@ function renderOpenItems(criterion) {
4627
4719
  if (criterion.status === "verified") return [];
4628
4720
  return findingItems;
4629
4721
  }
4630
- function renderFindingEvidence(finding) {
4631
- return `${escapeHtml(finding.severity)}: ${escapeHtml(finding.summary)} (${renderEvidencePointer(
4632
- finding.evidence
4633
- )})`;
4722
+ function renderFindingEvidence(criterion, finding) {
4723
+ return `finding severity ${escapeHtml(finding.severity)}: ${escapeHtml(renderFindingSummary(criterion, finding))} (${renderEvidencePointer(finding.evidence)})`;
4634
4724
  }
4635
4725
  function renderEvidencePointer(evidence) {
4636
4726
  const target = evidence.url ?? renderPathEvidenceTarget(evidence);
@@ -4653,6 +4743,9 @@ function renderVerdict(score) {
4653
4743
  return renderMachineVerdict(witanVerdictForScore(score));
4654
4744
  }
4655
4745
  function renderReportVerdict(report) {
4746
+ if (report.verdict === "insufficient_source") {
4747
+ return renderWitanAbstentionLabel(report);
4748
+ }
4656
4749
  return renderMachineVerdict(report.verdict);
4657
4750
  }
4658
4751
  function renderMachineVerdict(verdict) {
@@ -4672,7 +4765,7 @@ function renderMachineVerdict(verdict) {
4672
4765
  function formatScore(score) {
4673
4766
  return score.toFixed(1);
4674
4767
  }
4675
- function formatMetricValue(value) {
4768
+ function formatMetricValue2(value) {
4676
4769
  return Number.isInteger(value) ? value.toString() : value.toFixed(2);
4677
4770
  }
4678
4771
  function formatDate(value) {
@@ -4799,16 +4892,19 @@ var VERDICT_COLOR = {
4799
4892
  Conditional: "yellow",
4800
4893
  "At risk": "orange",
4801
4894
  Unverified: "red",
4802
- "Insufficient source": "lightgrey"
4895
+ "Insufficient source": "lightgrey",
4896
+ "Insufficient evidence": "lightgrey"
4803
4897
  };
4804
4898
  var VERDICT_SVG_FILL = {
4805
4899
  Verified: "#2fc86a",
4806
4900
  Conditional: "#dfb92f",
4807
4901
  "At risk": "#e08a3c",
4808
4902
  Unverified: "#d64545",
4809
- "Insufficient source": "#8c8c8c"
4903
+ "Insufficient source": "#8c8c8c",
4904
+ "Insufficient evidence": "#8c8c8c"
4810
4905
  };
4811
4906
  function badgeMessage(report, verdict) {
4907
+ if (isWitanNoMeasurementAbstention(report)) return "unrated: no measurable evidence";
4812
4908
  if (report.verdict === "insufficient_source") return "unrated: no source";
4813
4909
  return `${formatScore2(report.overallScore)}/4.0 ${verdict.toLowerCase()}`;
4814
4910
  }
@@ -4981,13 +5077,15 @@ function stripBom(text) {
4981
5077
  }
4982
5078
 
4983
5079
  // src/witan/rubric-version.ts
4984
- var WITAN_RUBRIC_VERSION_V92 = "witan-rubric-v9-2026-07-22";
5080
+ var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
4985
5081
  var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
4986
5082
  var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
4987
5083
  var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
4988
5084
  var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
4989
5085
  var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
4990
5086
  var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
5087
+ var WITAN_RUBRIC_VERSION_V16 = "witan-rubric-v16-2026-07-24";
5088
+ var WITAN_RUBRIC_VERSION_V17 = "witan-rubric-v17-2026-07-24";
4991
5089
 
4992
5090
  // src/witan/repo-signals.ts
4993
5091
  function buildWitanInputFromRepo(options) {
@@ -4997,25 +5095,44 @@ function buildWitanInputFromRepo(options) {
4997
5095
  const headSha = readGitHead(options.repoPath);
4998
5096
  const repoFiles = listRepoFiles(options.repoPath);
4999
5097
  const inventoryFiles = listRepoInventory(options.repoPath, repoFiles);
5098
+ const ignoredTargetReason = repoFiles.length === 0 ? explainIgnoredScanTarget(options.repoPath) : void 0;
5099
+ const usesV17Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V17;
5000
5100
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
5001
- const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? applyReadableSourceRepresentationGate(
5101
+ const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 ? applyReadableSourceRepresentationGate(
5002
5102
  options.repoPath,
5003
5103
  inventoryFiles,
5004
5104
  structuralArchetype,
5005
5105
  headSha,
5006
5106
  rubricVersion
5007
5107
  ) : structuralArchetype;
5008
- const archetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? applySemanticSourceRepresentationGate(
5108
+ const gatedArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 ? applySemanticSourceRepresentationGate(
5009
5109
  options.repoPath,
5010
5110
  inventoryFiles,
5011
5111
  readableArchetype,
5012
5112
  rubricVersion
5013
5113
  ) : readableArchetype;
5014
- const usesV8Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5015
- const usesV33Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5016
- const usesV36Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5017
- const usesV39Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5018
- const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15;
5114
+ const usesV8Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17;
5115
+ const usesV33Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17;
5116
+ const usesV36Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17;
5117
+ const usesV39Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17;
5118
+ const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17;
5119
+ const reviewableSourceProof = usesV17Detectors ? buildReviewableSourceProof(options.repoPath, inventoryFiles, headSha, rubricVersion) : void 0;
5120
+ const coreSignals = collectRepoSignals(
5121
+ options.repoPath,
5122
+ generatedAt,
5123
+ repoFiles,
5124
+ usesV8Detectors,
5125
+ usesV33Detectors,
5126
+ usesV36Detectors,
5127
+ usesV39Detectors,
5128
+ usesV47Detectors,
5129
+ reviewableSourceProof
5130
+ );
5131
+ const archetype = usesV17Detectors && reviewableSourceProof?.passes === true && isV17RescuableAbstention(gatedArchetype.abstentionKind) && coreSignals.some(isMeasuredCriterionSignal) ? {
5132
+ archetype: isLikelyMonorepo(inventoryFiles) ? "monorepo" : "source",
5133
+ sourceFileCount: gatedArchetype.sourceFileCount,
5134
+ totalFileCount: gatedArchetype.totalFileCount
5135
+ } : gatedArchetype;
5019
5136
  return {
5020
5137
  productSlug: options.productSlug,
5021
5138
  productDisplayName: options.productDisplayName,
@@ -5026,23 +5143,21 @@ function buildWitanInputFromRepo(options) {
5026
5143
  generatedAt,
5027
5144
  ...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {},
5028
5145
  archetype: archetype.archetype,
5029
- ...archetype.insufficientSourceReason ? { insufficientSourceReason: archetype.insufficientSourceReason } : {},
5146
+ ...ignoredTargetReason ?? archetype.insufficientSourceReason ? { insufficientSourceReason: ignoredTargetReason ?? archetype.insufficientSourceReason } : {},
5030
5147
  signals: [
5031
- ...collectRepoSignals(
5032
- options.repoPath,
5033
- generatedAt,
5034
- repoFiles,
5035
- usesV8Detectors,
5036
- usesV33Detectors,
5037
- usesV36Detectors,
5038
- usesV39Detectors,
5039
- usesV47Detectors
5040
- ),
5148
+ ...coreSignals,
5041
5149
  ...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
5042
5150
  ...options.additionalSignals ?? []
5043
5151
  ]
5044
5152
  };
5045
5153
  }
5154
+ function isV17RescuableAbstention(kind) {
5155
+ return kind === "artifact_dominance" || kind === "docs_source_role" || kind === "readable_representation" || kind === "semantic_auxiliary";
5156
+ }
5157
+ function isMeasuredCriterionSignal(signal) {
5158
+ if (signal.notApplicable === true) return false;
5159
+ return (signal.metrics?.length ?? 0) > 0 || (signal.positiveEvidence?.length ?? 0) > 0 || (signal.findings?.length ?? 0) > 0;
5160
+ }
5046
5161
  var SOURCE_LANGUAGES = [
5047
5162
  {
5048
5163
  name: "JS/TS",
@@ -5118,13 +5233,14 @@ var SOURCE_EXTENSION_PATTERN_V10 = new RegExp(
5118
5233
  "i"
5119
5234
  );
5120
5235
  function isRecognizedSourcePath(path, rubricVersion = WITAN_RUBRIC_VERSION) {
5121
- return (rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? SOURCE_EXTENSION_PATTERN_V10 : SOURCE_EXTENSION_PATTERN).test(path);
5236
+ return (rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 ? SOURCE_EXTENSION_PATTERN_V10 : SOURCE_EXTENSION_PATTERN).test(path);
5122
5237
  }
5123
5238
  var BUNDLED_BINARY_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps)$/i;
5124
5239
  var DOCS_OR_MEDIA_EXTENSION_PATTERN = /\.(md|mdx|rst|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
5125
5240
  var ARTIFACT_DOMINANCE_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
5126
5241
  var DOC_DISTRIBUTION_COMPANION_PATTERN = /(^|\/)(LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CODE_OF_CONDUCT)(\.[A-Za-z0-9]+)?$/i;
5127
5242
  var GENERATED_OR_VENDOR_PATH_PATTERN = /(^|\/)(vendor|vendors|dist|build|generated|gen|coverage|node_modules|site-packages|\.venv|venv|\.next|__pycache__|\.terraform|public\/assets|static\/assets)(\/|$)|(^|\/)[^/]+\.min\.(?:js|css)$/i;
5243
+ var V17_BUILD_OR_DEPENDENCY_CACHE_PATH_PATTERN = /(^|\/)(?:target|_build|\.build|DerivedData|Pods|Carthage\/Build|\.cache|\.gradle|\.m2|\.yarn\/cache|\.pnpm-store|\.dart_tool|\.tox|\.pytest_cache|\.ruff_cache|\.mypy_cache|\.turbo|\.parcel-cache|\.angular\/cache|\.svelte-kit|\.nuxt|bazel-(?:bin|out|testlogs)|buck-out)(\/|$)/i;
5128
5244
  var INDEPENDENT_CATALOG_PATH_PATTERN = /(^|\/)(solutions?|exercises?|challenges?|algorithms?)(\/|$)/i;
5129
5245
  var KNOWN_NON_SOURCE_EXTENSION_PATTERN = /\.(json|jsonc|json5|ya?ml|toml|txt|lock|xml|csv|tsv|ini|cfg|conf|cnf|properties|env|sum|mod|graphql|gql|proto|sarif|editorconfig|gitignore|gitattributes|npmrc|nvmrc|dockerignore|prettierignore|prettierrc|log|stderr|snap|grit|po|mo|dbf|shp|shx|egg|pem|key|csr|ai|txt\.license|pf|in|case|common|fortls)$/i;
5130
5246
  var V13_NON_SOURCE_EXTENSION_PATTERN = /\.(?:\d+|class|py[co]|gz|bz2|xz|7z|rar|mat|npy|npz|h5|hdf5|onnx|pt|pth|ckpt|safetensors|crt|cer|der|p12|pfx|diff|patch|plist|pbxproj|xcworkspacedata|xcbuild|xcplayground|xcscheme|xcsettings|xcconfig|xcprivacy|xcstrings|csproj|fsproj|vbproj|sln|slnf|props|targets|projitems|shproj|pubxml|resw|resources|storyboard|xib|entitlements|modulemap|podspec|nuspec|appxmanifest|webmanifest|manifest|map|fls|aux|bbl|bib|ocm|tdb|db|sqlite|sqlcipher\d*|bin|dat|pdb|prj|resolved|sim|input|list|fig|drawio|puml|sketch|icns|flac|wav|mp3|mp4|mov|avi|mkv|nv21|xls[xb]?|mex[a-z0-9_]*|gitconfig|profile|yapf|config|desktop|typed|example|bak|_|test(?:_[a-z0-9]+)*_location_override|xamltest|tld(?:_[a-z0-9]+)?|xsd|cmake|gradle|mk|am|pro|upri|dockerfile|dockergen|makefile|alpine|android|debian|ubuntu\d+|cuda\d+_ubuntu\d+|manylinux[a-z0-9_]*)$/i;
@@ -5148,6 +5264,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5148
5264
  archetype: "empty",
5149
5265
  sourceFileCount: 0,
5150
5266
  totalFileCount: 0,
5267
+ abstentionKind: "empty",
5151
5268
  insufficientSourceReason: "Repository has no tracked files \u2014 there is nothing to certify."
5152
5269
  };
5153
5270
  }
@@ -5159,6 +5276,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5159
5276
  archetype: "generated_only",
5160
5277
  sourceFileCount,
5161
5278
  totalFileCount,
5279
+ abstentionKind: "generated_only",
5162
5280
  insufficientSourceReason: `${generatedSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under generated, vendor, build, distribution, coverage, or bundled-asset paths; only ${sourceFileCount - generatedSourceFiles.length} authored source-shaped file(s) remain. Cejel abstains because generated/vendor output is not a reviewable implementation surface. ${INGEST_POINTER}`
5163
5281
  };
5164
5282
  }
@@ -5170,10 +5288,11 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5170
5288
  archetype: "non_cohesive_source",
5171
5289
  sourceFileCount,
5172
5290
  totalFileCount,
5291
+ abstentionKind: "independent_catalog",
5173
5292
  insufficientSourceReason: `${catalogSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under independent solution, exercise, challenge, or algorithm catalog paths; only ${sourceFileCount - catalogSourceFiles.length} source-shaped file(s) remain outside that catalog. Cejel abstains because the repository does not expose one cohesive product implementation to score. ${INGEST_POINTER}`
5174
5293
  };
5175
5294
  }
5176
- if (rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) {
5295
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) {
5177
5296
  const authoredSourceFiles = sourceFiles.filter(isAuthoredProductionPath);
5178
5297
  const documentationFiles = repoFiles.filter(
5179
5298
  (file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file)
@@ -5183,6 +5302,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5183
5302
  archetype: "docs_only",
5184
5303
  sourceFileCount,
5185
5304
  totalFileCount,
5305
+ abstentionKind: "docs_source_role",
5186
5306
  insufficientSourceReason: `${sourceFileCount} source-shaped file(s) are tests/fixtures around a documentation tree; no authored product source remains. Cejel abstains because documentation tests do not support a product-source headline score. ${INGEST_POINTER}`
5187
5307
  };
5188
5308
  }
@@ -5190,11 +5310,12 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5190
5310
  (file) => ARTIFACT_DOMINANCE_EXTENSION_PATTERN.test(file)
5191
5311
  );
5192
5312
  const materialFiles = authoredSourceFiles.length + artifactFiles.length;
5193
- if (artifactFiles.length >= 10 && (authoredSourceFiles.length <= 10 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && materialFiles > 0 && authoredSourceFiles.length / materialFiles <= 0.1) {
5313
+ if (artifactFiles.length >= 10 && (authoredSourceFiles.length <= 10 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) && materialFiles > 0 && authoredSourceFiles.length / materialFiles <= 0.1) {
5194
5314
  return {
5195
5315
  archetype: "binary_only",
5196
5316
  sourceFileCount,
5197
5317
  totalFileCount,
5318
+ abstentionKind: "artifact_dominance",
5198
5319
  insufficientSourceReason: `${authoredSourceFiles.length} authored source-shaped file(s) accompany ${artifactFiles.length} packaged, font, image, or media artifact(s); the source is sparse tooling rather than a representative product implementation. Cejel abstains because incidental tooling cannot support a product-source headline score. ${INGEST_POINTER}`
5199
5320
  };
5200
5321
  }
@@ -5208,6 +5329,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5208
5329
  archetype: "binary_only",
5209
5330
  sourceFileCount,
5210
5331
  totalFileCount,
5332
+ abstentionKind: "binary_distribution",
5211
5333
  insufficientSourceReason: `${sourceFileCount} source file(s) found among ${totalFileCount} tracked file(s); repo appears to be a binary/bundled-distribution tree (e.g. ${sample ? basename(sample) : "a packaged artifact"}) \u2014 cejel rates source, not binaries. ${INGEST_POINTER}`
5212
5334
  };
5213
5335
  }
@@ -5215,11 +5337,12 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5215
5337
  const docsDistributionFiles = repoFiles.filter(
5216
5338
  (file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file) || DOC_DISTRIBUTION_COMPANION_PATTERN.test(file)
5217
5339
  );
5218
- if (docsFiles.length > 0 && (docsDistributionFiles.length === totalFileCount || (rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && unrecognisedSourceFiles.length === 0)) {
5340
+ if (docsFiles.length > 0 && (docsDistributionFiles.length === totalFileCount || (rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) && unrecognisedSourceFiles.length === 0)) {
5219
5341
  return {
5220
5342
  archetype: "docs_only",
5221
5343
  sourceFileCount,
5222
5344
  totalFileCount,
5345
+ abstentionKind: "docs_distribution",
5223
5346
  insufficientSourceReason: `${sourceFileCount} source file(s) found among ${totalFileCount} tracked file(s); repo appears to be a docs/distribution tree (README/markdown/media), not a source tree \u2014 cejel rates source, not docs. ${INGEST_POINTER}`
5224
5347
  };
5225
5348
  }
@@ -5333,6 +5456,33 @@ function isRepresentativeSourceText(contents) {
5333
5456
  function representativeSourceRank(rankKey, file) {
5334
5457
  return createHash2("sha256").update(`${rankKey}:${file}`).digest("hex");
5335
5458
  }
5459
+ function buildReviewableSourceProof(repoPath, repoFiles, headSha, rubricVersion) {
5460
+ const sourceShapedFiles = repoFiles.filter(
5461
+ (path) => isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path)
5462
+ );
5463
+ const eligibleSourceFiles = sourceShapedFiles.filter(
5464
+ (path) => !GENERATED_OR_VENDOR_PATH_PATTERN.test(path) && !V17_BUILD_OR_DEPENDENCY_CACHE_PATH_PATTERN.test(path) && (SOURCE_EXTENSION_PATTERN.test(path) || /\.m$/i.test(path))
5465
+ );
5466
+ const sample = selectProportionalRepresentativeSourceFiles(
5467
+ eligibleSourceFiles,
5468
+ `witan-v17-reviewable-source:${headSha ?? "unversioned"}`,
5469
+ READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13
5470
+ );
5471
+ const readablePaths = sample.filter((file) => {
5472
+ const contents = readRepresentativeSourceText(repoPath, file);
5473
+ return contents !== null && isRepresentativeSourceText(contents);
5474
+ });
5475
+ const ratableShare = sourceShapedFiles.length === 0 ? 0 : eligibleSourceFiles.length / sourceShapedFiles.length;
5476
+ const readableShare = sample.length === 0 ? 0 : readablePaths.length / sample.length;
5477
+ return {
5478
+ sourceShapedFileCount: sourceShapedFiles.length,
5479
+ eligibleSourceFileCount: eligibleSourceFiles.length,
5480
+ sampledFileCount: sample.length,
5481
+ readableSampledFileCount: readablePaths.length,
5482
+ ...readablePaths[0] ? { firstReadablePath: readablePaths[0] } : {},
5483
+ passes: eligibleSourceFiles.length > 0 && ratableShare >= 0.2 && readablePaths.length > 0 && (readableShare >= 0.75 || readablePaths.length >= 8)
5484
+ };
5485
+ }
5336
5486
  function applyReadableSourceRepresentationGate(repoPath, repoFiles, classification, headSha, rubricVersion) {
5337
5487
  if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
5338
5488
  return classification;
@@ -5379,6 +5529,7 @@ function applyReadableSourceRepresentationGate(repoPath, repoFiles, classificati
5379
5529
  archetype: "non_cohesive_source",
5380
5530
  sourceFileCount: classification.sourceFileCount,
5381
5531
  totalFileCount: classification.totalFileCount,
5532
+ abstentionKind: "readable_representation",
5382
5533
  insufficientSourceReason: `Readable source representation covers ${readableCountLabel} of ${authoredSourceFiles.length} authored source-shaped file(s) (${readablePct}%); this is below the ${thresholdPct}% representation threshold. Cejel abstains because unreadable, compressed, binary-shaped, or opaque source families cannot support a representative headline score. ${INGEST_POINTER}`
5383
5534
  };
5384
5535
  }
@@ -5413,6 +5564,7 @@ function applySemanticSourceRepresentationGate(repoPath, repoFiles, classificati
5413
5564
  archetype: "non_cohesive_source",
5414
5565
  sourceFileCount: classification.sourceFileCount,
5415
5566
  totalFileCount: classification.totalFileCount,
5567
+ abstentionKind: "semantic_auxiliary",
5416
5568
  insufficientSourceReason: `${detail} provide no representative product implementation. Cejel abstains because auxiliary metadata or payload files cannot support a product-source headline score. ${INGEST_POINTER}`
5417
5569
  };
5418
5570
  }
@@ -5423,6 +5575,7 @@ function applySemanticSourceRepresentationGate(repoPath, repoFiles, classificati
5423
5575
  archetype: "non_cohesive_source",
5424
5576
  sourceFileCount: classification.sourceFileCount,
5425
5577
  totalFileCount: classification.totalFileCount,
5578
+ abstentionKind: "semantic_auxiliary",
5426
5579
  insufficientSourceReason: `${auxiliaryCount} of ${authoredSourceFiles.length} authored source-shaped file(s) are localization, style, package, or redirect auxiliary material. Cejel abstains because an auxiliary-dominated tree does not expose representative product implementation for a headline score. ${INGEST_POINTER}`
5427
5580
  };
5428
5581
  }
@@ -5469,11 +5622,11 @@ function readRepresentativeSourceText(repoPath, file) {
5469
5622
  }
5470
5623
  }
5471
5624
  function sourceDominanceThreshold(rubricVersion) {
5472
- if (rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) {
5625
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) {
5473
5626
  return SOURCE_DOMINANCE_RATIO_THRESHOLD_V11;
5474
5627
  }
5475
5628
  if (rubricVersion === WITAN_RUBRIC_VERSION_V10) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V10;
5476
- if (rubricVersion === WITAN_RUBRIC_VERSION_V92) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V9;
5629
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V9) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V9;
5477
5630
  return SOURCE_DOMINANCE_RATIO_THRESHOLD_V8;
5478
5631
  }
5479
5632
  function collectUnrecognisedSourceFiles(repoFiles, rubricVersion) {
@@ -5483,7 +5636,7 @@ function isUnrecognisedSourcePath(file, rubricVersion = WITAN_RUBRIC_VERSION) {
5483
5636
  const base = basename(file);
5484
5637
  if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
5485
5638
  if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
5486
- if ((rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && V13_NON_SOURCE_EXTENSION_PATTERN.test(base)) {
5639
+ if ((rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) && V13_NON_SOURCE_EXTENSION_PATTERN.test(base)) {
5487
5640
  return false;
5488
5641
  }
5489
5642
  if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
@@ -5512,6 +5665,7 @@ function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognis
5512
5665
  archetype: "unrecognised_ecosystem",
5513
5666
  sourceFileCount,
5514
5667
  totalFileCount,
5668
+ abstentionKind: "unrecognised_ecosystem",
5515
5669
  insufficientSourceReason: `Cejel does not yet read this repository's dominant source language(s) (${sample}) \u2014 ${measuredClause}. Cejel abstains from a verdict rather than score a repository whose recognised source is incidental rather than dominant; the Criterion Profile and Measured coverage below show exactly which dimensions were and were not measured. ${INGEST_POINTER}`
5516
5670
  };
5517
5671
  }
@@ -5532,6 +5686,32 @@ function assertRepoPathExists(repoPath) {
5532
5686
  throw new Error(`Cejel: path is not a directory: ${repoPath}`);
5533
5687
  }
5534
5688
  }
5689
+ function explainIgnoredScanTarget(repoPath) {
5690
+ let top;
5691
+ try {
5692
+ top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
5693
+ cwd: repoPath,
5694
+ encoding: "utf8",
5695
+ stdio: ["ignore", "pipe", "ignore"]
5696
+ }).trim();
5697
+ } catch {
5698
+ return void 0;
5699
+ }
5700
+ if (!top) return void 0;
5701
+ const topResolved = realpathSync(top);
5702
+ const targetResolved = realpathSync(repoPath);
5703
+ const target = relative(topResolved, targetResolved).split(sep).join("/");
5704
+ if (!target || target.startsWith("../")) return void 0;
5705
+ try {
5706
+ execFileSync("git", ["check-ignore", "--quiet", "--", `${target}/`], {
5707
+ cwd: topResolved,
5708
+ stdio: "ignore"
5709
+ });
5710
+ } catch {
5711
+ return void 0;
5712
+ }
5713
+ return `The requested scan path (${target}) is excluded by this repository's Git ignore rules, so Cejel found no tracked source to evaluate. Scan a tracked project or intentionally add the path to version control; Cejel abstains rather than score an ignored or vendored working tree.`;
5714
+ }
5535
5715
  function buildNotApplicableSignal(criterionId, reason) {
5536
5716
  return {
5537
5717
  criterionId,
@@ -5542,7 +5722,7 @@ function buildNotApplicableSignal(criterionId, reason) {
5542
5722
  notApplicable: true
5543
5723
  };
5544
5724
  }
5545
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
5725
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, reviewableSourceProof) {
5546
5726
  const signals = [];
5547
5727
  const mono = resolveMonorepoContext(repoPath);
5548
5728
  const a1Signal = collectA1TestIntegrityEvidence(
@@ -5550,7 +5730,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
5550
5730
  repoFiles,
5551
5731
  useV27Detectors,
5552
5732
  useV33Detectors,
5553
- useV36Detectors
5733
+ useV36Detectors,
5734
+ reviewableSourceProof
5554
5735
  );
5555
5736
  const a2Signal = collectA2IsolationEvidence(
5556
5737
  repoPath,
@@ -5599,10 +5780,14 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
5599
5780
  }
5600
5781
  return signals;
5601
5782
  }
5602
- function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors) {
5783
+ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, reviewableSourceProof) {
5603
5784
  const evidence = [];
5604
5785
  const findings = [];
5605
- const allTestFiles = findConcreteTestFiles(repoPath, repoFiles);
5786
+ const allTestFiles = findConcreteTestFiles(
5787
+ repoPath,
5788
+ repoFiles,
5789
+ reviewableSourceProof ? WITAN_RUBRIC_VERSION_V17 : void 0
5790
+ );
5606
5791
  const testFiles = allTestFiles.slice(0, 8);
5607
5792
  const sourceFiles = repoFiles.filter(isSourceFile);
5608
5793
  const runnerFiles = repoFiles.filter(
@@ -5691,7 +5876,9 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
5691
5876
  });
5692
5877
  }
5693
5878
  }
5694
- if (evidence.length === 0 && findings.length === 0) return null;
5879
+ if (evidence.length === 0 && findings.length === 0 && configuredRunnerFiles.length === 0) {
5880
+ return reviewableSourceProof?.passes === true && reviewableSourceProof.firstReadablePath ? buildA1AuthenticatedAbsenceSignal(repoPath, reviewableSourceProof) : null;
5881
+ }
5695
5882
  if (testFiles.length === 0) {
5696
5883
  const fallback = configuredRunnerFiles[0] ? evidenceForRelative(
5697
5884
  repoPath,
@@ -5729,7 +5916,11 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
5729
5916
  });
5730
5917
  }
5731
5918
  }
5732
- const nonHollowShare = measureNonHollowTestShare(repoPath, allTestFiles);
5919
+ const nonHollowShare = measureNonHollowTestShare(
5920
+ repoPath,
5921
+ allTestFiles,
5922
+ reviewableSourceProof !== void 0
5923
+ );
5733
5924
  return {
5734
5925
  criterionId: "A1",
5735
5926
  positiveEvidence: evidence,
@@ -5777,6 +5968,70 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
5777
5968
  notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
5778
5969
  };
5779
5970
  }
5971
+ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
5972
+ const sourcePath = proof.firstReadablePath;
5973
+ if (!sourcePath) {
5974
+ throw new Error("Cejel invariant: a passing reviewable-source proof must have a readable path");
5975
+ }
5976
+ const anchor = evidenceForRelative(
5977
+ repoPath,
5978
+ sourcePath,
5979
+ "artifact",
5980
+ `Reviewable source anchor (${proof.readableSampledFileCount}/${proof.sampledFileCount} sampled files readable; ${proof.eligibleSourceFileCount}/${proof.sourceShapedFileCount} source-shaped files criterion-ratable)`
5981
+ );
5982
+ return {
5983
+ criterionId: "A1",
5984
+ positiveEvidence: [anchor],
5985
+ findings: [
5986
+ {
5987
+ severity: "critical",
5988
+ summary: WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY,
5989
+ evidence: anchor
5990
+ }
5991
+ ],
5992
+ metrics: [
5993
+ metric(
5994
+ "test_to_source_ratio",
5995
+ "Test-to-source file ratio",
5996
+ 0,
5997
+ proof.eligibleSourceFileCount,
5998
+ 0.3,
5999
+ "ratio",
6000
+ "Measures how much concrete test surface exists relative to criterion-ratable implementation source.",
6001
+ "saturating_count"
6002
+ ),
6003
+ metric(
6004
+ "coverage_percent",
6005
+ "Static coverage percentage",
6006
+ 0,
6007
+ 100,
6008
+ 0.3,
6009
+ "percent",
6010
+ "Uses a static coverage report value or configured threshold when present, without running tests."
6011
+ ),
6012
+ metric(
6013
+ "verification_script_ratio",
6014
+ "Verification script ratio",
6015
+ 0,
6016
+ 4,
6017
+ 0.25,
6018
+ "ratio",
6019
+ "Measures explicit test/lint/typecheck verification commands plus test runner configuration.",
6020
+ "saturating_count"
6021
+ ),
6022
+ metric(
6023
+ "non_hollow_test_share",
6024
+ "Non-hollow test share",
6025
+ 0,
6026
+ 1,
6027
+ 0.15,
6028
+ "ratio",
6029
+ "No concrete test files were present to establish non-hollow test behavior."
6030
+ )
6031
+ ],
6032
+ notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
6033
+ };
6034
+ }
5780
6035
  function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
5781
6036
  const evidence = [];
5782
6037
  const findings = [];
@@ -6923,12 +7178,20 @@ function isV47AuthoredProductionPath(path) {
6923
7178
  var TEST_DIRECTORY_SOURCE_PATTERN = new RegExp(
6924
7179
  `(^|/)(?:__tests__|tests?|specs?|[^/]+(?:Tests|Specs))/(?:.*\\.)?(?:${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`
6925
7180
  );
6926
- function isTestFile(file) {
6927
- return TEST_DIRECTORY_SOURCE_PATTERN.test(file) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
7181
+ var TEST_DIRECTORY_SOURCE_PATTERN_V17 = new RegExp(
7182
+ `(^|/)(?:__tests__|tests?|specs?|[^/]+(?:Tests|Specs))/(?:.*\\.)?(?:${[
7183
+ ...SOURCE_LANGUAGES_V10.flatMap((language) => language.extensions),
7184
+ "bats"
7185
+ ].join("|")})$`,
7186
+ "i"
7187
+ );
7188
+ function isTestFile(file, rubricVersion = WITAN_RUBRIC_VERSION) {
7189
+ return rubricVersion === WITAN_RUBRIC_VERSION_V17 && (TEST_DIRECTORY_SOURCE_PATTERN_V17.test(file) || /\.tftest\.hcl$/i.test(file) || /\.bats$/i.test(file) || /(^|\/)test[^/]*\.m$/i.test(file) || /_test\.m$/i.test(file)) || TEST_DIRECTORY_SOURCE_PATTERN.test(file) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
6928
7190
  // Keep this root-anchored: nested files need an existing test-directory/suffix convention.
6929
7191
  /^test(?:-[^/]+)?\.[cm]?[jt]sx?$/.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)test_.*\.py$/.test(file) || /_test\.go$/.test(file) || /(^|\/)test_.*\.(cpp|cc|cxx)$/.test(file) || /_(test|tests)\.(cpp|cc|cxx)$/.test(file) || /Tests?\.(java|kt)$/.test(file) || /(_test|_spec)\.(rs|rb|php|swift|kt)$/.test(file);
6930
7192
  }
6931
7193
  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)$/;
7194
+ var NAME_SHAPED_TEST_FILE_PATTERN_V17 = /\.tftest\.hcl$|\.bats$|(^|\/)test[^/]*\.m$|_test\.m$/i;
6932
7195
  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;
6933
7196
  var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|dotnet\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+(?:run\s+)?test|npm\s+(?:run\s+)?test|yarn\s+(?:run\s+)?test|node\s+--test|node:test)\b|--target[=\s]+test\b/i;
6934
7197
  var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
@@ -7214,7 +7477,7 @@ var JS_TEST_ASSERTION_PATTERN = /(expect\(|assert\.|should\.|toEqual\(|toBe\(|as
7214
7477
  var CPP_TEST_ASSERTION_PATTERN = /\bEXPECT_|ASSERT_EQ\b|ASSERT_TRUE\b|ASSERT_FALSE\b|\bREQUIRE\s*\(|\bCHECK\s*\(|\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(/;
7215
7478
  var MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN = /\.assert[A-Z_]\w*\s*\(|\bassert[A-Z]\w*\s*\(|\bpytest\.raises\b|\bself\.fail\s*\(|\bt\.(?:Error|Errorf|Fatal|Fatalf|Fail|FailNow)\b|\brequire\.[A-Z]\w*\s*\(|\b(?:debug_)?assert(?:_eq|_ne)?!\s*\(|\bassert_\w+\b|\$this->assert\w+\s*\(/;
7216
7479
  var SUITE_LEVEL_SKIP_PATTERN = /\b(?:describe|suite|context)\.skip\s*\(/;
7217
- function measureNonHollowTestShare(repoPath, files) {
7480
+ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
7218
7481
  let nonHollowCount = 0;
7219
7482
  let ratedCount = 0;
7220
7483
  for (const file of files) {
@@ -7225,7 +7488,7 @@ function measureNonHollowTestShare(repoPath, files) {
7225
7488
  if (nonHollow) {
7226
7489
  nonHollowCount += 1;
7227
7490
  ratedCount += 1;
7228
- } else if (NAME_SHAPED_TEST_FILE_PATTERN.test(file)) {
7491
+ } else if (NAME_SHAPED_TEST_FILE_PATTERN.test(file) || useV17Detectors && NAME_SHAPED_TEST_FILE_PATTERN_V17.test(file)) {
7229
7492
  ratedCount += 1;
7230
7493
  }
7231
7494
  }
@@ -7557,9 +7820,12 @@ function findCoverageConfigFiles(repoPath, repoFiles, authoredOnly = false) {
7557
7820
  (file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCoverageConfig(repoPath, file)
7558
7821
  );
7559
7822
  }
7560
- function findConcreteTestFiles(repoPath, repoFiles) {
7823
+ function findConcreteTestFiles(repoPath, repoFiles, rubricVersion = WITAN_RUBRIC_VERSION) {
7561
7824
  const contentCppTests = collectContentBasedCppTestFiles(repoPath, repoFiles);
7562
- return [...repoFiles.filter(isTestFile), ...contentCppTests.filter((file) => !isTestFile(file))];
7825
+ return [
7826
+ ...repoFiles.filter((file) => isTestFile(file, rubricVersion)),
7827
+ ...contentCppTests.filter((file) => !isTestFile(file, rubricVersion))
7828
+ ];
7563
7829
  }
7564
7830
  function findConfiguredTestRunnerFiles(repoPath, repoFiles) {
7565
7831
  const configured = repoFiles.filter(
@@ -7686,8 +7952,439 @@ function isCoverageConfig(repoPath, file) {
7686
7952
  if (/(^|\/)pyproject\.toml$/.test(file)) {
7687
7953
  return fileContains(repoPath, file, /^\[tool\.coverage(?:\.|\])/m);
7688
7954
  }
7955
+ if (/(^|\/)package\.json$/.test(file)) {
7956
+ return packageJsonHasCoverageTooling(repoPath, file);
7957
+ }
7958
+ return false;
7959
+ }
7960
+ function packageJsonHasCoverageTooling(repoPath, file) {
7961
+ const fullPath = join(repoPath, file);
7962
+ if (!isRegularFile(fullPath)) return false;
7963
+ const parsed = parseJsonObject(readFileSync(fullPath, "utf8"));
7964
+ if (!parsed) return false;
7965
+ const scripts = parsed.scripts;
7966
+ if (typeof scripts === "object" && scripts !== null && !Array.isArray(scripts)) {
7967
+ for (const command of Object.values(scripts)) {
7968
+ if (typeof command === "string" && commandInvokesCoverageTool(command)) {
7969
+ return true;
7970
+ }
7971
+ }
7972
+ }
7973
+ for (const key of ["nyc", "c8", "istanbul"]) {
7974
+ const config = parsed[key];
7975
+ if (typeof config === "object" && config !== null && !Array.isArray(config)) return true;
7976
+ }
7689
7977
  return false;
7690
7978
  }
7979
+ function commandInvokesCoverageTool(command) {
7980
+ return normalizedCommandInvokesCoverageTool(normalizeCoverageCommandPrefixes(command), 0);
7981
+ }
7982
+ function normalizedCommandInvokesCoverageTool(normalized, depth) {
7983
+ const segments = normalized.splitShellOperators ? splitShellCommandSegments(normalized.segment) : [normalized.segment];
7984
+ return segments.some((rawSegment) => {
7985
+ const candidate = normalizeCoverageCommandPrefixes(rawSegment);
7986
+ if (/^(?:nyc|c8|istanbul)(?:\s|$)/iu.test(candidate.segment)) return true;
7987
+ return depth < 4 && candidate.splitShellOperators && candidate.segment !== rawSegment.trim() && normalizedCommandInvokesCoverageTool(candidate, depth + 1);
7988
+ });
7989
+ }
7990
+ function splitShellCommandSegments(command) {
7991
+ const segments = [];
7992
+ let start = 0;
7993
+ let quote = null;
7994
+ let escaped = false;
7995
+ for (let index = 0; index < command.length; index += 1) {
7996
+ const character = command[index];
7997
+ if (escaped) {
7998
+ escaped = false;
7999
+ continue;
8000
+ }
8001
+ if (character === "\\" && quote !== "'") {
8002
+ escaped = true;
8003
+ continue;
8004
+ }
8005
+ if (quote) {
8006
+ if (character === quote) quote = null;
8007
+ continue;
8008
+ }
8009
+ if (character === "'" || character === '"') {
8010
+ quote = character;
8011
+ continue;
8012
+ }
8013
+ if (character === "#" && (index === 0 || /[\s;|&()]/u.test(command[index - 1] ?? ""))) {
8014
+ segments.push(command.slice(start, index));
8015
+ const newline = command.indexOf("\n", index + 1);
8016
+ if (newline < 0) return segments;
8017
+ index = newline;
8018
+ start = newline + 1;
8019
+ continue;
8020
+ }
8021
+ const isBoundary = character === ";" || character === "|" || character === "&" || character === "\n";
8022
+ if (!isBoundary) continue;
8023
+ segments.push(command.slice(start, index));
8024
+ if ((character === "|" || character === "&") && command[index + 1] === character) index += 1;
8025
+ start = index + 1;
8026
+ }
8027
+ segments.push(command.slice(start));
8028
+ return segments;
8029
+ }
8030
+ function normalizeCoverageCommandPrefixes(rawSegment) {
8031
+ let segment = rawSegment.trim();
8032
+ let unwrapQuotedShellCommand = false;
8033
+ let splitShellOperators = true;
8034
+ for (let index = 0; index < 8; index += 1) {
8035
+ const before = segment;
8036
+ segment = stripOuterShellGroup(segment);
8037
+ segment = segment.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+)\s+)*/u, "");
8038
+ segment = segment.replace(
8039
+ /^(?:npx|npm\s+exec|pnpm\s+(?:exec|dlx)|yarn\s+(?:exec|dlx))(?:\s+--(?:no-install|yes))*(?:\s+--)?\s+/iu,
8040
+ ""
8041
+ );
8042
+ segment = segment.replace(/^(?:command(?:\s+-p)?(?:\s+--)?|time(?:\s+-p)?)\s+/iu, "");
8043
+ const explicitShellCommand = extractExplicitShellCommand(segment);
8044
+ if (explicitShellCommand !== null) {
8045
+ segment = explicitShellCommand;
8046
+ unwrapQuotedShellCommand = false;
8047
+ splitShellOperators = true;
8048
+ }
8049
+ const environmentPrefix = stripEnvironmentCommandPrefix(segment);
8050
+ segment = environmentPrefix.segment;
8051
+ unwrapQuotedShellCommand ||= environmentPrefix.unwrapQuotedShellCommand;
8052
+ if (environmentPrefix.splitShellOperators !== null) {
8053
+ splitShellOperators = environmentPrefix.splitShellOperators;
8054
+ }
8055
+ if (segment === before) break;
8056
+ }
8057
+ return {
8058
+ segment: unwrapQuotedShellCommand ? segment.replace(/^(["'])(.*)\1$/u, "$2").trim() : segment,
8059
+ splitShellOperators
8060
+ };
8061
+ }
8062
+ function extractExplicitShellCommand(segment) {
8063
+ const shellExecutable = /^(?:(?:\/(?:usr\/)?bin\/)?(?<shell>(?:ba|da|k|z)?sh))(?=\s|$)/iu.exec(
8064
+ segment
8065
+ );
8066
+ if (!shellExecutable) return null;
8067
+ const shell = shellExecutable.groups?.shell?.toLowerCase();
8068
+ if (!shell) return null;
8069
+ let offset = shellExecutable[0].length;
8070
+ let noExecute = false;
8071
+ let dumpStrings = false;
8072
+ for (let optionCount = 0; optionCount < 32; optionCount += 1) {
8073
+ const option = scanShellWord(segment, offset);
8074
+ if (!option || !option.word.startsWith("-") && !option.word.startsWith("+")) return null;
8075
+ offset = option.end;
8076
+ if (option.word === "--") return null;
8077
+ const zshLongOption = shell === "zsh" ? /^(?<sign>--|\+-)(?<name>[A-Za-z][A-Za-z_-]*)$/u.exec(option.word) : null;
8078
+ if (zshLongOption) {
8079
+ const optionName = zshLongOption.groups?.name?.replaceAll("-", "");
8080
+ if (!optionName) return null;
8081
+ const resolvedSetOption = resolveSupportedShellSetOption(shell, optionName);
8082
+ if (!resolvedSetOption && !isSupportedZshLongInvocationOption(optionName)) return null;
8083
+ if (resolvedSetOption?.name === "noexec") {
8084
+ noExecute = zshLongOption.groups?.sign === "--" !== resolvedSetOption.inverted;
8085
+ }
8086
+ continue;
8087
+ }
8088
+ if (option.word === "--rcfile" || option.word === "--init-file") {
8089
+ const operand = scanShellWord(segment, offset);
8090
+ if (shell !== "bash" || !operand) return null;
8091
+ offset = operand.end;
8092
+ continue;
8093
+ }
8094
+ if (shell === "bash" && /^--(?:debug|debugger|login|noediting|noprofile|norc|posix|protected|restricted|verbose)$/u.test(
8095
+ option.word
8096
+ )) {
8097
+ continue;
8098
+ }
8099
+ const cluster = parseSupportedShellOptionCluster(shell, option.word);
8100
+ if (!cluster) return null;
8101
+ if (cluster.changesNoExecute) noExecute = cluster.sign === "-";
8102
+ if (cluster.enablesDumpStrings) dumpStrings = true;
8103
+ if (cluster.operandKind) {
8104
+ const operand = scanShellWord(segment, offset);
8105
+ const resolvedSetOperand = operand && cluster.operandKind === "set" ? resolveSupportedShellSetOption(shell, operand.word) : null;
8106
+ const operandIsZshInvocationOption = shell === "zsh" && operand !== null && cluster.operandKind === "set" && isSupportedZshLongInvocationOption(operand.word);
8107
+ const operandIsSupported = cluster.operandKind === "set" ? resolvedSetOperand !== null || operandIsZshInvocationOption : shell === "bash" && operand && BASH_SHOPT_OPTIONS.has(operand.word);
8108
+ if (!operand || !operandIsSupported) return null;
8109
+ if (resolvedSetOperand?.name === "noexec") {
8110
+ noExecute = cluster.sign === "-" !== resolvedSetOperand.inverted;
8111
+ }
8112
+ offset = operand.end;
8113
+ }
8114
+ if (cluster.hasCommand) {
8115
+ return noExecute || dumpStrings ? null : readShellWord(segment, offset);
8116
+ }
8117
+ }
8118
+ return null;
8119
+ }
8120
+ var BASH_SET_OPTIONS = /* @__PURE__ */ new Set([
8121
+ "allexport",
8122
+ "braceexpand",
8123
+ "emacs",
8124
+ "errexit",
8125
+ "errtrace",
8126
+ "functrace",
8127
+ "hashall",
8128
+ "histexpand",
8129
+ "history",
8130
+ "ignoreeof",
8131
+ "keyword",
8132
+ "monitor",
8133
+ "noclobber",
8134
+ "noexec",
8135
+ "noglob",
8136
+ "nolog",
8137
+ "notify",
8138
+ "nounset",
8139
+ "onecmd",
8140
+ "physical",
8141
+ "pipefail",
8142
+ "posix",
8143
+ "privileged",
8144
+ "verbose",
8145
+ "vi",
8146
+ "xtrace"
8147
+ ]);
8148
+ var BASH_SHOPT_OPTIONS = /* @__PURE__ */ new Set([
8149
+ "autocd",
8150
+ "cdable_vars",
8151
+ "cdspell",
8152
+ "checkhash",
8153
+ "checkjobs",
8154
+ "checkwinsize",
8155
+ "cmdhist",
8156
+ "complete_fullquote",
8157
+ "direxpand",
8158
+ "dirspell",
8159
+ "dotglob",
8160
+ "execfail",
8161
+ "expand_aliases",
8162
+ "extdebug",
8163
+ "extglob",
8164
+ "extquote",
8165
+ "failglob",
8166
+ "force_fignore",
8167
+ "globasciiranges",
8168
+ "globskipdots",
8169
+ "globstar",
8170
+ "gnu_errfmt",
8171
+ "histappend",
8172
+ "histreedit",
8173
+ "histverify",
8174
+ "hostcomplete",
8175
+ "huponexit",
8176
+ "inherit_errexit",
8177
+ "interactive_comments",
8178
+ "lastpipe",
8179
+ "lithist",
8180
+ "localvar_inherit",
8181
+ "localvar_unset",
8182
+ "login_shell",
8183
+ "mailwarn",
8184
+ "nocaseglob",
8185
+ "nocasematch",
8186
+ "no_empty_cmd_completion",
8187
+ "nullglob",
8188
+ "progcomp",
8189
+ "progcomp_alias",
8190
+ "promptvars",
8191
+ "restricted_shell",
8192
+ "shift_verbose",
8193
+ "sourcepath",
8194
+ "varredir_close",
8195
+ "xpg_echo"
8196
+ ]);
8197
+ var POSIX_SET_OPTIONS = /* @__PURE__ */ new Set([
8198
+ "allexport",
8199
+ "errexit",
8200
+ "ignoreeof",
8201
+ "noclobber",
8202
+ "noexec",
8203
+ "noglob",
8204
+ "nounset",
8205
+ "verbose",
8206
+ "vi",
8207
+ "xtrace"
8208
+ ]);
8209
+ var ZSH_LONG_INVOCATION_OPTIONS = /* @__PURE__ */ new Set([
8210
+ "interactive",
8211
+ "login",
8212
+ "privileged",
8213
+ "shinstdin",
8214
+ "stdin"
8215
+ ]);
8216
+ function isSupportedShellSetOption(shell, option) {
8217
+ if (shell === "bash") return BASH_SET_OPTIONS.has(option);
8218
+ if (shell === "sh" || shell === "dash") return POSIX_SET_OPTIONS.has(option);
8219
+ if (shell === "ksh" || shell === "zsh") {
8220
+ return POSIX_SET_OPTIONS.has(option) || option === "pipefail";
8221
+ }
8222
+ return false;
8223
+ }
8224
+ function normalizeShellSetOption(shell, option) {
8225
+ if (shell === "zsh") return option.toLowerCase().replaceAll("_", "");
8226
+ return option;
8227
+ }
8228
+ function isSupportedZshLongInvocationOption(option) {
8229
+ const normalized = normalizeShellSetOption("zsh", option);
8230
+ if (ZSH_LONG_INVOCATION_OPTIONS.has(normalized)) return true;
8231
+ return normalized.startsWith("no") && !normalized.startsWith("nono") && ZSH_LONG_INVOCATION_OPTIONS.has(normalized.slice(2));
8232
+ }
8233
+ function resolveSupportedShellSetOption(shell, option) {
8234
+ const normalized = normalizeShellSetOption(shell, option);
8235
+ if (shell === "zsh" && normalized === "exec") {
8236
+ return { name: "noexec", inverted: true };
8237
+ }
8238
+ if (isSupportedShellSetOption(shell, normalized)) {
8239
+ return { name: normalized, inverted: false };
8240
+ }
8241
+ if (shell === "zsh" && normalized.startsWith("no") && !normalized.startsWith("nono") && isSupportedShellSetOption(shell, normalized.slice(2))) {
8242
+ return { name: normalized.slice(2), inverted: true };
8243
+ }
8244
+ return null;
8245
+ }
8246
+ function parseSupportedShellOptionCluster(shell, option) {
8247
+ if (!/^[+-][A-Za-z]+$/u.test(option)) return null;
8248
+ const sign = option[0];
8249
+ let letters = option.slice(1);
8250
+ let operandKind = null;
8251
+ const operandLetter = letters.at(-1);
8252
+ if (operandLetter === "o" || operandLetter === "O") {
8253
+ if (operandLetter === "O" && shell !== "bash") return null;
8254
+ letters = letters.slice(0, -1);
8255
+ if (letters.includes("c")) return null;
8256
+ operandKind = operandLetter === "o" ? "set" : "shopt";
8257
+ }
8258
+ const supportedLetters = shell === "bash" ? "abcefhkmnptuvxBCEHPTilrsD" : shell === "sh" ? "abCcefimnsuvx" : shell === "dash" ? "abCcefilmnsuvx" : "acefilmnpsuvx";
8259
+ if (![...letters].every((letter) => supportedLetters.includes(letter))) return null;
8260
+ return {
8261
+ sign,
8262
+ hasCommand: letters.includes("c"),
8263
+ changesNoExecute: letters.includes("n"),
8264
+ enablesDumpStrings: shell === "bash" && letters.includes("D"),
8265
+ operandKind
8266
+ };
8267
+ }
8268
+ function scanShellWord(value, start) {
8269
+ let result = "";
8270
+ let quote = null;
8271
+ let escaped = false;
8272
+ let index = start;
8273
+ while (index < value.length && /\s/u.test(value[index] ?? "")) index += 1;
8274
+ const wordStart = index;
8275
+ for (; index < value.length; index += 1) {
8276
+ const character = value[index];
8277
+ if (escaped) {
8278
+ if (character !== "\n") result += character;
8279
+ escaped = false;
8280
+ continue;
8281
+ }
8282
+ if (character === "\\" && quote !== "'") {
8283
+ const nextCharacter = value[index + 1];
8284
+ const escapesNext = quote !== '"' || nextCharacter === "$" || nextCharacter === "`" || nextCharacter === '"' || nextCharacter === "\\" || nextCharacter === "\n";
8285
+ if (escapesNext) {
8286
+ escaped = true;
8287
+ } else {
8288
+ result += character;
8289
+ }
8290
+ continue;
8291
+ }
8292
+ if (quote) {
8293
+ if (character === quote) {
8294
+ quote = null;
8295
+ } else {
8296
+ result += character;
8297
+ }
8298
+ continue;
8299
+ }
8300
+ if (character === "'" || character === '"') {
8301
+ quote = character;
8302
+ continue;
8303
+ }
8304
+ if (/\s/u.test(character ?? "")) break;
8305
+ result += character;
8306
+ }
8307
+ if (escaped || quote || index === wordStart) return null;
8308
+ return { word: result, end: index };
8309
+ }
8310
+ function readShellWord(value, start) {
8311
+ return scanShellWord(value, start)?.word ?? null;
8312
+ }
8313
+ function stripOuterShellGroup(segment) {
8314
+ if (!segment.startsWith("(") || segment.startsWith("((")) return segment;
8315
+ let depth = 0;
8316
+ let quote = null;
8317
+ let escaped = false;
8318
+ for (let index = 0; index < segment.length; index += 1) {
8319
+ const character = segment[index];
8320
+ if (escaped) {
8321
+ escaped = false;
8322
+ continue;
8323
+ }
8324
+ if (character === "\\" && quote !== "'") {
8325
+ escaped = true;
8326
+ continue;
8327
+ }
8328
+ if (quote) {
8329
+ if (character === quote) quote = null;
8330
+ continue;
8331
+ }
8332
+ if (character === "'" || character === '"') {
8333
+ quote = character;
8334
+ continue;
8335
+ }
8336
+ if (character === "(") {
8337
+ depth += 1;
8338
+ continue;
8339
+ }
8340
+ if (character !== ")") continue;
8341
+ depth -= 1;
8342
+ if (depth < 0) return segment;
8343
+ if (depth === 0) {
8344
+ if (segment.slice(index + 1).trim().length > 0) return segment;
8345
+ return segment.slice(1, index).trim();
8346
+ }
8347
+ }
8348
+ return segment;
8349
+ }
8350
+ function stripEnvironmentCommandPrefix(segment) {
8351
+ const wrapper = /^(cross-env(?:-shell)?|env)(?:\s|$)/iu.exec(segment);
8352
+ if (!wrapper) {
8353
+ return { segment, unwrapQuotedShellCommand: false, splitShellOperators: null };
8354
+ }
8355
+ let remainder = segment.slice(wrapper[0].length).trimStart();
8356
+ let unwrapQuotedShellCommand = wrapper[1]?.toLowerCase() === "cross-env-shell";
8357
+ let splitShellOperators = true;
8358
+ while (remainder.length > 0) {
8359
+ const splitString = /^(?:-S|--split-string)(?:=|\s+)(?:"([^"]*)"|'([^']*)'|([^\s]+))(?:\s+|$)/u.exec(remainder);
8360
+ if (splitString) {
8361
+ const splitCommand = splitString[1] ?? splitString[2] ?? splitString[3] ?? "";
8362
+ remainder = `${splitCommand} ${remainder.slice(splitString[0].length)}`.trim();
8363
+ unwrapQuotedShellCommand = false;
8364
+ splitShellOperators = false;
8365
+ continue;
8366
+ }
8367
+ const assignment = /^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+)(?:\s+|$)/u.exec(
8368
+ remainder
8369
+ );
8370
+ if (assignment) {
8371
+ remainder = remainder.slice(assignment[0].length).trimStart();
8372
+ continue;
8373
+ }
8374
+ const optionWithValue = /^(?:-u|--unset|-C|--chdir)(?:=|\s+)(?:"[^"]*"|'[^']*'|[^\s]+)(?:\s+|$)/u.exec(remainder);
8375
+ if (optionWithValue) {
8376
+ remainder = remainder.slice(optionWithValue[0].length).trimStart();
8377
+ continue;
8378
+ }
8379
+ const flag = /^(?:--ignore-environment|-i|--)(?:\s+|$)/u.exec(remainder);
8380
+ if (flag) {
8381
+ remainder = remainder.slice(flag[0].length).trimStart();
8382
+ continue;
8383
+ }
8384
+ break;
8385
+ }
8386
+ return { segment: remainder, unwrapQuotedShellCommand, splitShellOperators };
8387
+ }
7691
8388
  function isCiWorkflow(file) {
7692
8389
  return /^\.github\/workflows\/.+\.ya?ml$/.test(file) || /^\.gitlab-ci\.ya?ml$/.test(file) || /^\.circleci\/config\.ya?ml$/.test(file) || /^azure-pipelines\.ya?ml$/.test(file) || /^Jenkinsfile$/.test(file);
7693
8390
  }
@@ -8985,7 +9682,15 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
8985
9682
  const overallScore = roundScore(
8986
9683
  categoryOrder.reduce((sum, category) => sum + (categoryScoreMap[category] ?? 0), 0) / Math.max(categoryOrder.length, 1)
8987
9684
  );
8988
- const abstained = parsedInput.insufficientSourceReason != null;
9685
+ const hasMeasuredCriterion = criteria.some(
9686
+ ({ status }) => status !== "not_applicable" && status !== "insufficient_data"
9687
+ );
9688
+ const isFreeCoreRubric = rubric.length === WITAN_RUBRIC.length && rubric.every(
9689
+ ({ id, category }, index) => id === WITAN_RUBRIC[index]?.id && category === WITAN_RUBRIC[index]?.category
9690
+ );
9691
+ const noMeasurementAbstention = (parsedInput.rubricVersion === WITAN_RUBRIC_VERSION_V16 || parsedInput.rubricVersion === WITAN_RUBRIC_VERSION_V17) && isFreeCoreRubric && !hasMeasuredCriterion;
9692
+ const insufficientSourceReason = parsedInput.insufficientSourceReason ?? (noMeasurementAbstention ? WITAN_NO_MEASUREMENT_REASON : void 0);
9693
+ const abstained = insufficientSourceReason != null;
8989
9694
  return WitanReportSchema.parse({
8990
9695
  productSlug: parsedInput.productSlug,
8991
9696
  productDisplayName: parsedInput.productDisplayName,
@@ -9005,7 +9710,7 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
9005
9710
  // alter it (see classifyRepoArchetype in repo-signals.ts); per-criterion scoring above is
9006
9711
  // unaffected by archetype, only presentation layers (badge/terminal/verdict) key off it.
9007
9712
  ...parsedInput.archetype ? { archetype: parsedInput.archetype } : {},
9008
- ...parsedInput.insufficientSourceReason ? { insufficientSourceReason: parsedInput.insufficientSourceReason } : {}
9713
+ ...insufficientSourceReason ? { insufficientSourceReason } : {}
9009
9714
  });
9010
9715
  }
9011
9716
  function applySignalAdjustment(nativeScore, dimension, signals) {
@@ -9051,7 +9756,7 @@ function statusForScore(score) {
9051
9756
  }
9052
9757
  function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
9053
9758
  if (adjustment === 0) return nativeStatus;
9054
- if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V92 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15) {
9759
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17) {
9055
9760
  return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
9056
9761
  }
9057
9762
  const statusFloor = {
@@ -9095,7 +9800,7 @@ function mergeSignalsByCriterion(signals) {
9095
9800
  return merged;
9096
9801
  }
9097
9802
  function usesMetricScoring(rubricVersion) {
9098
- return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
9803
+ return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
9099
9804
  }
9100
9805
  function unmeasuredStatus(rubricVersion) {
9101
9806
  return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
@@ -9136,7 +9841,12 @@ function scoreCriterion(signal, rubricVersion) {
9136
9841
  }
9137
9842
  if (usesMetricScoring(rubricVersion)) {
9138
9843
  const measuredMetrics = metrics.length > 0 ? metrics : fallbackMetricsForEvidence(evidence, findings);
9139
- const score = capScoreForFindings(scoreMetrics(measuredMetrics), findings);
9844
+ const score = capScoreForFindings(
9845
+ scoreMetrics(measuredMetrics),
9846
+ findings,
9847
+ rubricVersion,
9848
+ signal.criterionId
9849
+ );
9140
9850
  const status = statusForScoreAndFindings(
9141
9851
  signal.criterionId,
9142
9852
  score,
@@ -9218,16 +9928,20 @@ function scoreMetrics(metrics) {
9218
9928
  if (totalWeight === 0) return 0;
9219
9929
  return roundScore(weightedTotal / totalWeight * 4);
9220
9930
  }
9221
- function capScoreForFindings(score, findings) {
9222
- if (findings.some((finding) => finding.severity === "critical")) return 1.4;
9931
+ function capScoreForFindings(score, findings, rubricVersion, criterionId) {
9932
+ if (findings.some((finding) => finding.severity === "critical")) {
9933
+ const criticalFindings = findings.filter(({ severity }) => severity === "critical");
9934
+ const authenticatedA1Absence = rubricVersion === WITAN_RUBRIC_VERSION_V17 && criterionId === "A1" && criticalFindings.length === 1 && criticalFindings[0]?.summary === WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY;
9935
+ return authenticatedA1Absence ? Math.min(score, 1.4) : 1.4;
9936
+ }
9223
9937
  if (findings.some((finding) => finding.severity === "warning")) return Math.min(score, 2.4);
9224
9938
  if (findings.some((finding) => finding.severity === "info")) return Math.min(score, 3.4);
9225
9939
  return score;
9226
9940
  }
9227
9941
  function statusForScoreAndFindings(criterionId, score, findings, metrics, rubricVersion) {
9228
9942
  if (findings.some((finding) => finding.severity === "critical")) return "critical";
9229
- if (rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) {
9230
- const calibrated = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? calibratedCriterionStateV11(criterionId, metrics) : rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 ? calibratedCriterionStateV9(criterionId, metrics) : calibratedCriterionStateV8(criterionId, metrics);
9943
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) {
9944
+ const calibrated = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 ? calibratedCriterionStateV11(criterionId, metrics) : rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 ? calibratedCriterionStateV9(criterionId, metrics) : calibratedCriterionStateV8(criterionId, metrics);
9231
9945
  if (calibrated && calibrated !== "verified") return calibrated;
9232
9946
  if (calibrated === "verified" && !findings.some((finding) => finding.severity === "warning")) {
9233
9947
  return "verified";
@@ -9453,7 +10167,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
9453
10167
  return void 0;
9454
10168
  }
9455
10169
  function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
9456
- if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V92 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && findings.length > 0) {
10170
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && findings.length > 0) {
9457
10171
  return [...findings];
9458
10172
  }
9459
10173
  if (status !== "critical" && status !== "warning") return [...findings];
@@ -9560,6 +10274,87 @@ function slugify(raw) {
9560
10274
  return FALLBACK_SLUG;
9561
10275
  }
9562
10276
 
10277
+ // src/finding-presentation.ts
10278
+ var METRIC_DERIVED_FINDING_PATTERN2 = /^[A-Z]\d+ metric-derived score is (\d+\.\d)\/4\.0, in the (critical|warning) band — no single finding drove this; it reflects the combined metric weighting below\.$/;
10279
+ var REMEDIATION_BY_METRIC2 = {
10280
+ test_to_source_ratio: "add concrete regression tests for the implementation surface",
10281
+ coverage_percent: "configure coverage and publish a measured threshold or report",
10282
+ verification_script_ratio: "add explicit test, lint, and typecheck verification commands",
10283
+ non_hollow_test_share: "replace skipped or placeholder tests with live assertions",
10284
+ env_handling_depth: "document and enforce safe environment-secret handling",
10285
+ secret_cleanliness: "remove committed secrets and rotate any exposed credentials",
10286
+ tenant_scope_ratio: "apply tenant scoping consistently across data access",
10287
+ rls_policy_count: "add database row-level security policies where the data model requires them",
10288
+ crypto_comparison_hygiene: "use constant-time comparison for signature or MAC checks",
10289
+ prod_readiness_primitives: "add the missing deployment-readiness controls",
10290
+ observability_depth: "add health checks, structured logging, and error boundaries",
10291
+ rollback_safety_depth: "document and test rollback or recovery procedures",
10292
+ pinned_dependency_ratio: "pin application dependencies that determine the deployed artifact",
10293
+ lockfile_coverage: "commit the package-manager lockfile used by CI and deployment",
10294
+ dependency_automation_ratio: "enable automated dependency updates and an audit command",
10295
+ declared_version_range_ratio: "declare an explicit compatible version for every dependency",
10296
+ dependency_count_sanity: "review and reduce unnecessary direct dependencies",
10297
+ claim_match_rate: "align documented product claims with observable repository evidence",
10298
+ claim_source_depth: "document concrete, checkable product claims",
10299
+ reconciliation_artifact_depth: "publish a claim-to-evidence reconciliation record",
10300
+ pr_merge_ratio: "preserve merged pull-request history in the scanned clone",
10301
+ pr_trace_primitives: "add pull-request templates and outcome-trace records",
10302
+ ci_script_depth: "run the repository verification commands in CI",
10303
+ default_branch_ci_depth: "run CI on the default branch and pull requests",
10304
+ prod_workflow_depth: "add a production or release verification workflow",
10305
+ audit_artifact_depth: "publish changelog, incident, security, or audit records",
10306
+ audit_freshness_depth: "keep the audit trail current",
10307
+ human_gate_documented: "document which privileged operations require human approval",
10308
+ protected_path_review_gate: "require review for changes to protected paths",
10309
+ fail_closed_privilege_check: "make privileged-operation checks fail closed",
10310
+ kill_switch_fail_safe_present: "add a fail-safe kill switch for privileged automation",
10311
+ privilege_escalation_cleanliness: "remove unsafe privilege-escalation patterns"
10312
+ };
10313
+ function renderFindingSummary2(criterion, finding) {
10314
+ const match = METRIC_DERIVED_FINDING_PATTERN2.exec(finding.summary);
10315
+ if (!match) return finding.summary;
10316
+ const score = match[1] ?? criterion.score.toFixed(1);
10317
+ const band = match[2] ?? criterion.status;
10318
+ const drivers = selectLowestDrivers2(criterion.metrics);
10319
+ if (drivers.length === 0) {
10320
+ return `${criterion.id} dimension band is ${band} at ${score}/4.0. To improve this dimension, add stronger measurable evidence for ${criterion.title.toLowerCase()}.`;
10321
+ }
10322
+ const measurements = drivers.map(formatMetricMember2).join("; ");
10323
+ const actions = Array.from(
10324
+ new Set(
10325
+ drivers.map(
10326
+ (metric2) => REMEDIATION_BY_METRIC2[metric2.name] ?? `raise the measured ${metric2.label.toLowerCase()}`
10327
+ )
10328
+ )
10329
+ ).join("; ");
10330
+ return `${criterion.id} dimension band is ${band} at ${score}/4.0. Lowest contributing measurements: ${measurements}. To improve: ${actions}.`;
10331
+ }
10332
+ function selectLowestDrivers2(metrics) {
10333
+ return [...metrics].filter((metric2) => metric2.max !== void 0).sort((left, right) => {
10334
+ const leftRatio = normalizedMetricValue2(left);
10335
+ const rightRatio = normalizedMetricValue2(right);
10336
+ if (leftRatio !== rightRatio) return leftRatio - rightRatio;
10337
+ if (left.weight !== right.weight) return right.weight - left.weight;
10338
+ return left.name.localeCompare(right.name);
10339
+ }).slice(0, 2);
10340
+ }
10341
+ function normalizedMetricValue2(metric2) {
10342
+ if (metric2.max === void 0) return 1;
10343
+ return Math.min(metric2.value / metric2.max, 1);
10344
+ }
10345
+ function formatMetricMember2(metric2) {
10346
+ const unit = metric2.unit ? ` ${metric2.unit}` : "";
10347
+ const maximum = metric2.max;
10348
+ if (maximum === void 0) return `${metric2.label} ${formatMetricValue3(metric2.value)}${unit}`;
10349
+ if (metric2.kind === "saturating_count" && metric2.value > maximum) {
10350
+ return `${metric2.label} ${formatMetricValue3(maximum)}${unit} capped (${formatMetricValue3(metric2.value)} raw)`;
10351
+ }
10352
+ return `${metric2.label} ${formatMetricValue3(metric2.value)}/${formatMetricValue3(maximum)}${unit}`;
10353
+ }
10354
+ function formatMetricValue3(value) {
10355
+ return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
10356
+ }
10357
+
9563
10358
  // src/summary.ts
9564
10359
  var SEVERITY_RANK2 = {
9565
10360
  critical: 0,
@@ -9569,11 +10364,16 @@ var SEVERITY_RANK2 = {
9569
10364
  var TOP_FINDINGS_LIMIT = 5;
9570
10365
  function buildWitanCliSummary(report) {
9571
10366
  const allFindings = report.criteria.flatMap(
9572
- (criterion) => criterion.findings.map((finding) => ({
9573
- criterionId: criterion.id,
9574
- severity: finding.severity,
9575
- summary: finding.summary
9576
- }))
10367
+ (criterion) => criterion.findings.map((finding) => {
10368
+ const displaySummary = renderFindingSummary2(criterion, finding);
10369
+ return {
10370
+ criterionId: criterion.id,
10371
+ severity: finding.severity,
10372
+ dimensionBand: criterion.status,
10373
+ summary: finding.summary,
10374
+ ...displaySummary !== finding.summary ? { displaySummary } : {}
10375
+ };
10376
+ })
9577
10377
  );
9578
10378
  const sorted = [...allFindings].sort(
9579
10379
  (a, b) => SEVERITY_RANK2[a.severity] - SEVERITY_RANK2[b.severity]
@@ -9600,7 +10400,7 @@ function buildWitanCliSummary(report) {
9600
10400
  overallScore: null,
9601
10401
  codeTrustScore: null,
9602
10402
  processTrustScore: null,
9603
- verdict: "Insufficient source",
10403
+ verdict: renderReportVerdict(report),
9604
10404
  insufficientSourceReason: report.insufficientSourceReason
9605
10405
  };
9606
10406
  }
@@ -9618,7 +10418,7 @@ function runCejelScan(options) {
9618
10418
  const identity = deriveProductIdentity(options.repoPath);
9619
10419
  const report = scoreRepoWithPublicCejel({
9620
10420
  productSlug: identity.productSlug,
9621
- productDisplayName: identity.productDisplayName,
10421
+ productDisplayName: options.productDisplayName ?? identity.productDisplayName,
9622
10422
  repoPath: options.repoPath,
9623
10423
  ingestPatterns: options.ingestPatterns,
9624
10424
  warnOnEmptyIngestMatch: options.warnOnEmptyIngestMatch