@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.
- package/README.md +30 -71
- package/dist/{chunk-DVVBWBAA.js → chunk-PYI3OIBW.js} +871 -71
- package/dist/index.js +68 -3586
- package/dist/mcp/index.js +2 -2
- package/package.json +1 -1
|
@@ -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
|
|
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"
|
|
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>${
|
|
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 ? `/${
|
|
4605
|
-
return `<strong>${escapeHtml(metric2.label)}</strong><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}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ===
|
|
5015
|
-
const usesV33Detectors = rubricVersion ===
|
|
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
|
-
...
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
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)
|
|
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(
|
|
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
|
-
|
|
6927
|
-
|
|
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 [
|
|
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
|
|
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
|
-
...
|
|
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 !==
|
|
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 ===
|
|
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(
|
|
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"))
|
|
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 ===
|
|
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 ===
|
|
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 !==
|
|
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
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
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:
|
|
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
|