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