@cejel/cejel 0.1.8 → 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 -26
- package/dist/{chunk-SFEPABAX.js → chunk-EFOUXVJE.js} +2432 -234
- package/dist/index.js +49 -10
- package/dist/mcp/index.js +2 -2
- package/package.json +10 -12
|
@@ -4077,7 +4077,10 @@ var WITAN_RUBRIC_VERSION_V3 = "witan-rubric-v3-2026-07-13";
|
|
|
4077
4077
|
var WITAN_RUBRIC_VERSION_V4 = "witan-rubric-v4-2026-07-18";
|
|
4078
4078
|
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
|
-
var
|
|
4080
|
+
var WITAN_RUBRIC_VERSION_V7 = "witan-rubric-v7-2026-07-21";
|
|
4081
|
+
var WITAN_RUBRIC_VERSION_V8 = "witan-rubric-v8-2026-07-21";
|
|
4082
|
+
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
4083
|
+
var WITAN_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V9;
|
|
4081
4084
|
var WITAN_TRADING_RUBRIC_VERSION_V0 = "witan-trading-rubric-v0-2026-07-01";
|
|
4082
4085
|
var WITAN_CRITERION_IDS = [
|
|
4083
4086
|
"A1",
|
|
@@ -4145,6 +4148,8 @@ var WITAN_REPO_ARCHETYPES = [
|
|
|
4145
4148
|
"monorepo",
|
|
4146
4149
|
"docs_only",
|
|
4147
4150
|
"binary_only",
|
|
4151
|
+
"generated_only",
|
|
4152
|
+
"non_cohesive_source",
|
|
4148
4153
|
"unrecognised_ecosystem",
|
|
4149
4154
|
"empty"
|
|
4150
4155
|
];
|
|
@@ -4360,6 +4365,51 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
4360
4365
|
}).strict()
|
|
4361
4366
|
}).strict();
|
|
4362
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
|
+
|
|
4380
|
+
// src/witan/coverage.ts
|
|
4381
|
+
var LOW_CONFIDENCE_COVERAGE_THRESHOLD = 0.5;
|
|
4382
|
+
function isMeasuredCriterionStatus(status) {
|
|
4383
|
+
return status !== "not_applicable" && status !== "insufficient_data";
|
|
4384
|
+
}
|
|
4385
|
+
function computeMeasuredCoverage(report) {
|
|
4386
|
+
const byCategory = [];
|
|
4387
|
+
const overall = { measured: 0, total: 0 };
|
|
4388
|
+
for (const criterion of report.criteria) {
|
|
4389
|
+
let bucket = byCategory.find((entry) => entry.category === criterion.category);
|
|
4390
|
+
if (!bucket) {
|
|
4391
|
+
bucket = { category: criterion.category, measured: 0, total: 0 };
|
|
4392
|
+
byCategory.push(bucket);
|
|
4393
|
+
}
|
|
4394
|
+
bucket.total += 1;
|
|
4395
|
+
overall.total += 1;
|
|
4396
|
+
if (isMeasuredCriterionStatus(criterion.status)) {
|
|
4397
|
+
bucket.measured += 1;
|
|
4398
|
+
overall.measured += 1;
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
const lowConfidence = [...byCategory, overall].some(
|
|
4402
|
+
(counts) => counts.total > 0 && counts.measured / counts.total < LOW_CONFIDENCE_COVERAGE_THRESHOLD
|
|
4403
|
+
);
|
|
4404
|
+
return { byCategory, overall, lowConfidence };
|
|
4405
|
+
}
|
|
4406
|
+
function shortCategoryLabel(category) {
|
|
4407
|
+
return category.replace(/_trust$/, "").replaceAll("_", " ");
|
|
4408
|
+
}
|
|
4409
|
+
function formatCoverageCounts(coverage) {
|
|
4410
|
+
return coverage.byCategory.map((entry) => `${shortCategoryLabel(entry.category)} ${entry.measured}/${entry.total}`).join(" \xB7 ");
|
|
4411
|
+
}
|
|
4412
|
+
|
|
4363
4413
|
// src/witan/external-findings.ts
|
|
4364
4414
|
var EXTERNAL_FINDINGS_DISPLAY_LIMIT = 10;
|
|
4365
4415
|
var SEVERITY_RANK = {
|
|
@@ -4413,6 +4463,87 @@ function collectExternalFindings(consumedSignals) {
|
|
|
4413
4463
|
});
|
|
4414
4464
|
}
|
|
4415
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
|
+
|
|
4416
4547
|
// src/witan/html.ts
|
|
4417
4548
|
function renderWitanHtmlReport(report) {
|
|
4418
4549
|
const codeCriteria = criteriaByCategory(report, "code_trust");
|
|
@@ -4424,6 +4555,7 @@ function renderWitanHtmlReport(report) {
|
|
|
4424
4555
|
const contributingSources = renderContributingSources(report.consumedSignals ?? []);
|
|
4425
4556
|
const externalSourceSummaries = summarizeExternalSources(report.consumedSignals ?? []);
|
|
4426
4557
|
const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
|
|
4558
|
+
const coverage = computeMeasuredCoverage(report);
|
|
4427
4559
|
return `<!doctype html>
|
|
4428
4560
|
<html lang="en">
|
|
4429
4561
|
<head>
|
|
@@ -4452,14 +4584,15 @@ function renderWitanHtmlReport(report) {
|
|
|
4452
4584
|
</section>
|
|
4453
4585
|
<aside class="score-panel" aria-label="Summary trust score">
|
|
4454
4586
|
<div class="score-badge">Summary</div>
|
|
4455
|
-
${report.verdict === "insufficient_source" ? `<div class="verdict"
|
|
4587
|
+
${report.verdict === "insufficient_source" ? `<div class="verdict">${escapeHtml(renderWitanAbstentionLabel(report))}</div>
|
|
4456
4588
|
<p class="insufficient-note">${escapeHtml(report.insufficientSourceReason)}</p>` : `<div class="score">${formatScore(report.overallScore)}</div>
|
|
4457
4589
|
<div class="score-unit">/ 4.0 overall</div>
|
|
4458
4590
|
<div class="verdict">${escapeHtml(renderVerdict(report.overallScore))}</div>
|
|
4459
4591
|
<div class="subscores">
|
|
4460
|
-
<span>Code ${
|
|
4461
|
-
<span>Process ${
|
|
4462
|
-
</div
|
|
4592
|
+
<span>Code ${renderCategoryScore(report.codeTrustScore, coverage, "code_trust")}</span>
|
|
4593
|
+
<span>Process ${renderCategoryScore(report.processTrustScore, coverage, "process_trust")}</span>
|
|
4594
|
+
</div>
|
|
4595
|
+
<div class="coverage-note">${escapeHtml(formatCoverageCounts(coverage))} measured${coverage.lowConfidence ? " \xB7 low confidence" : ""}</div>`}
|
|
4463
4596
|
</aside>
|
|
4464
4597
|
</div>
|
|
4465
4598
|
</header>
|
|
@@ -4536,7 +4669,7 @@ function renderNotApplicableItem(criterion) {
|
|
|
4536
4669
|
function renderCriterionCard(criterion) {
|
|
4537
4670
|
const evidence = [
|
|
4538
4671
|
...criterion.evidence.map(renderEvidencePointer),
|
|
4539
|
-
...criterion.findings.map((finding) => renderFindingEvidence(finding))
|
|
4672
|
+
...criterion.findings.map((finding) => renderFindingEvidence(criterion, finding))
|
|
4540
4673
|
];
|
|
4541
4674
|
const metrics = criterion.metrics.map(renderMetric);
|
|
4542
4675
|
return `<article class="criterion">
|
|
@@ -4557,19 +4690,26 @@ function renderCriterionCard(criterion) {
|
|
|
4557
4690
|
</article>`;
|
|
4558
4691
|
}
|
|
4559
4692
|
function renderMetric(metric2) {
|
|
4560
|
-
const max = metric2.max ? `/${formatMetricValue(metric2.max)}` : "";
|
|
4561
4693
|
const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
|
|
4562
|
-
|
|
4694
|
+
if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
|
|
4695
|
+
return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue2(metric2.max)}${unit} (capped; ${formatMetricValue2(metric2.value)} raw)</span>`;
|
|
4696
|
+
}
|
|
4697
|
+
const max = metric2.max ? `/${formatMetricValue2(metric2.max)}` : "";
|
|
4698
|
+
return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue2(metric2.value)}${max}${unit}</span>`;
|
|
4699
|
+
}
|
|
4700
|
+
function renderCategoryScore(score, coverage, category) {
|
|
4701
|
+
const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
|
|
4702
|
+
return categoryCoverage !== void 0 && categoryCoverage.measured === 0 || score === null ? "not measured" : formatScore(score);
|
|
4563
4703
|
}
|
|
4564
4704
|
function renderStatusChip(status) {
|
|
4565
|
-
return `<span class="status" data-status="${status}"
|
|
4705
|
+
return `<span class="status" data-status="${status}">dimension band: ${status}</span>`;
|
|
4566
4706
|
}
|
|
4567
4707
|
function renderEvidenceListItem(criterion, evidence) {
|
|
4568
4708
|
return `<li><strong>${escapeHtml(criterion.id)} - ${escapeHtml(criterion.title)}</strong><span>${renderEvidencePointer(evidence)}</span></li>`;
|
|
4569
4709
|
}
|
|
4570
4710
|
function renderOpenItems(criterion) {
|
|
4571
4711
|
const findingItems = criterion.findings.map(
|
|
4572
|
-
(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>`
|
|
4573
4713
|
);
|
|
4574
4714
|
if ((criterion.status === "unverified" || criterion.status === "insufficient_data") && criterion.evidence.length === 0 && criterion.findings.length === 0) {
|
|
4575
4715
|
const message = criterion.status === "insufficient_data" ? "Insufficient data \u2014 no measurable signal; excluded from composite (unmeasured, not inapplicable)." : "No concrete evidence supplied.";
|
|
@@ -4580,10 +4720,8 @@ function renderOpenItems(criterion) {
|
|
|
4580
4720
|
if (criterion.status === "verified") return [];
|
|
4581
4721
|
return findingItems;
|
|
4582
4722
|
}
|
|
4583
|
-
function renderFindingEvidence(finding) {
|
|
4584
|
-
return
|
|
4585
|
-
finding.evidence
|
|
4586
|
-
)})`;
|
|
4723
|
+
function renderFindingEvidence(criterion, finding) {
|
|
4724
|
+
return `finding severity ${escapeHtml(finding.severity)}: ${escapeHtml(renderFindingSummary(criterion, finding))} (${renderEvidencePointer(finding.evidence)})`;
|
|
4587
4725
|
}
|
|
4588
4726
|
function renderEvidencePointer(evidence) {
|
|
4589
4727
|
const target = evidence.url ?? renderPathEvidenceTarget(evidence);
|
|
@@ -4606,6 +4744,9 @@ function renderVerdict(score) {
|
|
|
4606
4744
|
return renderMachineVerdict(witanVerdictForScore(score));
|
|
4607
4745
|
}
|
|
4608
4746
|
function renderReportVerdict(report) {
|
|
4747
|
+
if (report.verdict === "insufficient_source") {
|
|
4748
|
+
return renderWitanAbstentionLabel(report);
|
|
4749
|
+
}
|
|
4609
4750
|
return renderMachineVerdict(report.verdict);
|
|
4610
4751
|
}
|
|
4611
4752
|
function renderMachineVerdict(verdict) {
|
|
@@ -4625,7 +4766,7 @@ function renderMachineVerdict(verdict) {
|
|
|
4625
4766
|
function formatScore(score) {
|
|
4626
4767
|
return score.toFixed(1);
|
|
4627
4768
|
}
|
|
4628
|
-
function
|
|
4769
|
+
function formatMetricValue2(value) {
|
|
4629
4770
|
return Number.isInteger(value) ? value.toString() : value.toFixed(2);
|
|
4630
4771
|
}
|
|
4631
4772
|
function formatDate(value) {
|
|
@@ -4701,6 +4842,7 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
4701
4842
|
.verdict { margin-top: 12px; font-family: var(--serif); font-size: 30px; color: var(--periwinkle); }
|
|
4702
4843
|
.insufficient-note { margin-top: 10px; color: var(--muted); font-size: 13px; line-height: 1.5; }
|
|
4703
4844
|
.subscores { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
4845
|
+
.coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
|
|
4704
4846
|
.source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
4705
4847
|
.trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
|
|
4706
4848
|
.external-findings-section { margin-top: 18px; }
|
|
@@ -4751,16 +4893,19 @@ var VERDICT_COLOR = {
|
|
|
4751
4893
|
Conditional: "yellow",
|
|
4752
4894
|
"At risk": "orange",
|
|
4753
4895
|
Unverified: "red",
|
|
4754
|
-
"Insufficient source": "lightgrey"
|
|
4896
|
+
"Insufficient source": "lightgrey",
|
|
4897
|
+
"Insufficient evidence": "lightgrey"
|
|
4755
4898
|
};
|
|
4756
4899
|
var VERDICT_SVG_FILL = {
|
|
4757
4900
|
Verified: "#2fc86a",
|
|
4758
4901
|
Conditional: "#dfb92f",
|
|
4759
4902
|
"At risk": "#e08a3c",
|
|
4760
4903
|
Unverified: "#d64545",
|
|
4761
|
-
"Insufficient source": "#8c8c8c"
|
|
4904
|
+
"Insufficient source": "#8c8c8c",
|
|
4905
|
+
"Insufficient evidence": "#8c8c8c"
|
|
4762
4906
|
};
|
|
4763
4907
|
function badgeMessage(report, verdict) {
|
|
4908
|
+
if (isWitanNoMeasurementAbstention(report)) return "unrated: no measurable evidence";
|
|
4764
4909
|
if (report.verdict === "insufficient_source") return "unrated: no source";
|
|
4765
4910
|
return `${formatScore2(report.overallScore)}/4.0 ${verdict.toLowerCase()}`;
|
|
4766
4911
|
}
|
|
@@ -4915,21 +5060,80 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
4915
5060
|
// src/witan/repo-signals.ts
|
|
4916
5061
|
import { execFileSync } from "child_process";
|
|
4917
5062
|
import { createHash as createHash2 } from "crypto";
|
|
4918
|
-
import {
|
|
4919
|
-
|
|
5063
|
+
import {
|
|
5064
|
+
closeSync,
|
|
5065
|
+
lstatSync,
|
|
5066
|
+
openSync,
|
|
5067
|
+
readFileSync,
|
|
5068
|
+
readSync,
|
|
5069
|
+
readdirSync,
|
|
5070
|
+
realpathSync,
|
|
5071
|
+
statSync
|
|
5072
|
+
} from "fs";
|
|
5073
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "path";
|
|
4920
5074
|
|
|
4921
5075
|
// src/witan/json-safe.ts
|
|
4922
5076
|
function stripBom(text) {
|
|
4923
5077
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
4924
5078
|
}
|
|
4925
5079
|
|
|
5080
|
+
// src/witan/rubric-version.ts
|
|
5081
|
+
var WITAN_RUBRIC_VERSION_V92 = "witan-rubric-v9-2026-07-22";
|
|
5082
|
+
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
5083
|
+
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
5084
|
+
var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
|
|
5085
|
+
var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
|
|
5086
|
+
var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
|
|
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";
|
|
5090
|
+
|
|
4926
5091
|
// src/witan/repo-signals.ts
|
|
4927
5092
|
function buildWitanInputFromRepo(options) {
|
|
4928
5093
|
assertRepoPathExists(options.repoPath);
|
|
4929
5094
|
const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5095
|
+
const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
|
|
4930
5096
|
const headSha = readGitHead(options.repoPath);
|
|
4931
5097
|
const repoFiles = listRepoFiles(options.repoPath);
|
|
4932
|
-
const
|
|
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;
|
|
5101
|
+
const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
|
|
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(
|
|
5103
|
+
options.repoPath,
|
|
5104
|
+
inventoryFiles,
|
|
5105
|
+
structuralArchetype,
|
|
5106
|
+
headSha,
|
|
5107
|
+
rubricVersion
|
|
5108
|
+
) : structuralArchetype;
|
|
5109
|
+
const gatedArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 ? applySemanticSourceRepresentationGate(
|
|
5110
|
+
options.repoPath,
|
|
5111
|
+
inventoryFiles,
|
|
5112
|
+
readableArchetype,
|
|
5113
|
+
rubricVersion
|
|
5114
|
+
) : readableArchetype;
|
|
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;
|
|
4933
5137
|
return {
|
|
4934
5138
|
productSlug: options.productSlug,
|
|
4935
5139
|
productDisplayName: options.productDisplayName,
|
|
@@ -4940,14 +5144,21 @@ function buildWitanInputFromRepo(options) {
|
|
|
4940
5144
|
generatedAt,
|
|
4941
5145
|
...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {},
|
|
4942
5146
|
archetype: archetype.archetype,
|
|
4943
|
-
...archetype.insufficientSourceReason ? { insufficientSourceReason: archetype.insufficientSourceReason } : {},
|
|
5147
|
+
...ignoredTargetReason ?? archetype.insufficientSourceReason ? { insufficientSourceReason: ignoredTargetReason ?? archetype.insufficientSourceReason } : {},
|
|
4944
5148
|
signals: [
|
|
4945
|
-
...
|
|
5149
|
+
...coreSignals,
|
|
4946
5150
|
...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
|
|
4947
5151
|
...options.additionalSignals ?? []
|
|
4948
5152
|
]
|
|
4949
5153
|
};
|
|
4950
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
|
+
}
|
|
4951
5162
|
var SOURCE_LANGUAGES = [
|
|
4952
5163
|
{
|
|
4953
5164
|
name: "JS/TS",
|
|
@@ -4967,7 +5178,7 @@ var SOURCE_LANGUAGES = [
|
|
|
4967
5178
|
{ name: "Dart", tier: "partial", extensions: ["dart"] },
|
|
4968
5179
|
{ name: "Elixir", tier: "partial", extensions: ["ex", "exs"] },
|
|
4969
5180
|
{ name: "Scala", tier: "partial", extensions: ["scala"] },
|
|
4970
|
-
{ name: "shell", tier: "unmodelled", extensions: ["sh", "bash", "zsh"] },
|
|
5181
|
+
{ name: "shell", tier: "unmodelled", extensions: ["sh", "bash", "zsh", "bat", "cmd"] },
|
|
4971
5182
|
{ name: "R", tier: "unmodelled", extensions: ["r"] },
|
|
4972
5183
|
{ name: "Lua", tier: "unmodelled", extensions: ["lua"] },
|
|
4973
5184
|
{ name: "Julia", tier: "unmodelled", extensions: ["jl"] },
|
|
@@ -4981,29 +5192,136 @@ var SOURCE_LANGUAGES = [
|
|
|
4981
5192
|
{ name: "Nim", tier: "unmodelled", extensions: ["nim"] },
|
|
4982
5193
|
{ name: "Zig", tier: "unmodelled", extensions: ["zig"] },
|
|
4983
5194
|
{ name: "F#", tier: "unmodelled", extensions: ["fs", "fsx"] },
|
|
4984
|
-
{ name: "Groovy", tier: "unmodelled", extensions: ["groovy", "gvy"] }
|
|
5195
|
+
{ name: "Groovy", tier: "unmodelled", extensions: ["groovy", "gvy"] },
|
|
5196
|
+
{ name: "Fortran", tier: "unmodelled", extensions: ["f", "for", "f77", "f90", "f95", "f03"] },
|
|
5197
|
+
{ name: "CUDA/HIP", tier: "unmodelled", extensions: ["cu", "cuh", "hip"] },
|
|
5198
|
+
{
|
|
5199
|
+
name: "Web templates/styles",
|
|
5200
|
+
tier: "unmodelled",
|
|
5201
|
+
extensions: [
|
|
5202
|
+
"html",
|
|
5203
|
+
"htm",
|
|
5204
|
+
"css",
|
|
5205
|
+
"scss",
|
|
5206
|
+
"sass",
|
|
5207
|
+
"less",
|
|
5208
|
+
"hbs",
|
|
5209
|
+
"handlebars",
|
|
5210
|
+
"variables",
|
|
5211
|
+
"overrides",
|
|
5212
|
+
"vue",
|
|
5213
|
+
"svelte",
|
|
5214
|
+
"astro",
|
|
5215
|
+
"erb",
|
|
5216
|
+
"haml",
|
|
5217
|
+
"slim",
|
|
5218
|
+
"erubis",
|
|
5219
|
+
"hamlit"
|
|
5220
|
+
]
|
|
5221
|
+
}
|
|
5222
|
+
];
|
|
5223
|
+
var SOURCE_LANGUAGES_V10 = [
|
|
5224
|
+
...SOURCE_LANGUAGES,
|
|
5225
|
+
{ name: "COBOL", tier: "unmodelled", extensions: ["cob", "cbl", "cpy"] },
|
|
5226
|
+
{ name: "MATLAB/Objective-C", tier: "unmodelled", extensions: ["m"] }
|
|
4985
5227
|
];
|
|
4986
5228
|
var SOURCE_EXTENSION_PATTERN = new RegExp(
|
|
4987
5229
|
`\\.(${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`,
|
|
4988
5230
|
"i"
|
|
4989
5231
|
);
|
|
4990
|
-
var
|
|
5232
|
+
var SOURCE_EXTENSION_PATTERN_V10 = new RegExp(
|
|
5233
|
+
`\\.(${SOURCE_LANGUAGES_V10.flatMap((language) => language.extensions).join("|")})$`,
|
|
5234
|
+
"i"
|
|
5235
|
+
);
|
|
5236
|
+
function isRecognizedSourcePath(path, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
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);
|
|
5238
|
+
}
|
|
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;
|
|
4991
5240
|
var DOCS_OR_MEDIA_EXTENSION_PATTERN = /\.(md|mdx|rst|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
|
|
4992
|
-
var
|
|
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;
|
|
5242
|
+
var DOC_DISTRIBUTION_COMPANION_PATTERN = /(^|\/)(LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CODE_OF_CONDUCT)(\.[A-Za-z0-9]+)?$/i;
|
|
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;
|
|
5245
|
+
var INDEPENDENT_CATALOG_PATH_PATTERN = /(^|\/)(solutions?|exercises?|challenges?|algorithms?)(\/|$)/i;
|
|
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;
|
|
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;
|
|
4993
5248
|
var INGEST_POINTER = "To assess a closed/bundled tool, ingest its scanner output via --ingest <sarif|scorecard>.";
|
|
4994
|
-
var
|
|
4995
|
-
|
|
5249
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V8 = 0.2;
|
|
5250
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V9 = 0.8;
|
|
5251
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V10 = 0.5;
|
|
5252
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V11 = 0.2;
|
|
5253
|
+
var GENERATED_SOURCE_MINIMUM = 8;
|
|
5254
|
+
var GENERATED_SOURCE_RATIO_THRESHOLD = 0.8;
|
|
5255
|
+
var GENERATED_AUTHORED_MAXIMUM = 3;
|
|
5256
|
+
var CATALOG_SOURCE_MINIMUM = 12;
|
|
5257
|
+
var CATALOG_SOURCE_RATIO_THRESHOLD = 0.9;
|
|
5258
|
+
var CATALOG_OUTSIDE_MAXIMUM = 2;
|
|
5259
|
+
function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
4996
5260
|
const totalFileCount = repoFiles.length;
|
|
4997
|
-
const
|
|
5261
|
+
const sourceFiles = repoFiles.filter((path) => isRecognizedSourcePath(path, rubricVersion));
|
|
5262
|
+
const sourceFileCount = sourceFiles.length;
|
|
4998
5263
|
if (totalFileCount === 0) {
|
|
4999
5264
|
return {
|
|
5000
5265
|
archetype: "empty",
|
|
5001
5266
|
sourceFileCount: 0,
|
|
5002
5267
|
totalFileCount: 0,
|
|
5268
|
+
abstentionKind: "empty",
|
|
5003
5269
|
insufficientSourceReason: "Repository has no tracked files \u2014 there is nothing to certify."
|
|
5004
5270
|
};
|
|
5005
5271
|
}
|
|
5006
|
-
const
|
|
5272
|
+
const generatedSourceFiles = sourceFiles.filter(
|
|
5273
|
+
(file) => GENERATED_OR_VENDOR_PATH_PATTERN.test(file)
|
|
5274
|
+
);
|
|
5275
|
+
if (sourceFileCount >= GENERATED_SOURCE_MINIMUM && generatedSourceFiles.length / sourceFileCount >= GENERATED_SOURCE_RATIO_THRESHOLD && sourceFileCount - generatedSourceFiles.length <= GENERATED_AUTHORED_MAXIMUM) {
|
|
5276
|
+
return {
|
|
5277
|
+
archetype: "generated_only",
|
|
5278
|
+
sourceFileCount,
|
|
5279
|
+
totalFileCount,
|
|
5280
|
+
abstentionKind: "generated_only",
|
|
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}`
|
|
5282
|
+
};
|
|
5283
|
+
}
|
|
5284
|
+
const catalogSourceFiles = sourceFiles.filter(
|
|
5285
|
+
(file) => INDEPENDENT_CATALOG_PATH_PATTERN.test(file)
|
|
5286
|
+
);
|
|
5287
|
+
if (sourceFileCount >= CATALOG_SOURCE_MINIMUM && catalogSourceFiles.length / sourceFileCount >= CATALOG_SOURCE_RATIO_THRESHOLD && sourceFileCount - catalogSourceFiles.length <= CATALOG_OUTSIDE_MAXIMUM) {
|
|
5288
|
+
return {
|
|
5289
|
+
archetype: "non_cohesive_source",
|
|
5290
|
+
sourceFileCount,
|
|
5291
|
+
totalFileCount,
|
|
5292
|
+
abstentionKind: "independent_catalog",
|
|
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}`
|
|
5294
|
+
};
|
|
5295
|
+
}
|
|
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) {
|
|
5297
|
+
const authoredSourceFiles = sourceFiles.filter(isAuthoredProductionPath);
|
|
5298
|
+
const documentationFiles = repoFiles.filter(
|
|
5299
|
+
(file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file)
|
|
5300
|
+
);
|
|
5301
|
+
if (sourceFileCount > 0 && authoredSourceFiles.length === 0 && documentationFiles.length > 0) {
|
|
5302
|
+
return {
|
|
5303
|
+
archetype: "docs_only",
|
|
5304
|
+
sourceFileCount,
|
|
5305
|
+
totalFileCount,
|
|
5306
|
+
abstentionKind: "docs_source_role",
|
|
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}`
|
|
5308
|
+
};
|
|
5309
|
+
}
|
|
5310
|
+
const artifactFiles = repoFiles.filter(
|
|
5311
|
+
(file) => ARTIFACT_DOMINANCE_EXTENSION_PATTERN.test(file)
|
|
5312
|
+
);
|
|
5313
|
+
const materialFiles = authoredSourceFiles.length + artifactFiles.length;
|
|
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) {
|
|
5315
|
+
return {
|
|
5316
|
+
archetype: "binary_only",
|
|
5317
|
+
sourceFileCount,
|
|
5318
|
+
totalFileCount,
|
|
5319
|
+
abstentionKind: "artifact_dominance",
|
|
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}`
|
|
5321
|
+
};
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5324
|
+
const unrecognisedSourceFiles = collectUnrecognisedSourceFiles(repoFiles, rubricVersion);
|
|
5007
5325
|
if (sourceFileCount === 0) {
|
|
5008
5326
|
const binaryFiles = repoFiles.filter((file) => BUNDLED_BINARY_EXTENSION_PATTERN.test(file));
|
|
5009
5327
|
if (binaryFiles.length > 0) {
|
|
@@ -5012,20 +5330,30 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
5012
5330
|
archetype: "binary_only",
|
|
5013
5331
|
sourceFileCount,
|
|
5014
5332
|
totalFileCount,
|
|
5333
|
+
abstentionKind: "binary_distribution",
|
|
5015
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}`
|
|
5016
5335
|
};
|
|
5017
5336
|
}
|
|
5018
5337
|
const docsFiles = repoFiles.filter((file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file));
|
|
5019
|
-
|
|
5338
|
+
const docsDistributionFiles = repoFiles.filter(
|
|
5339
|
+
(file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file) || DOC_DISTRIBUTION_COMPANION_PATTERN.test(file)
|
|
5340
|
+
);
|
|
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)) {
|
|
5020
5342
|
return {
|
|
5021
5343
|
archetype: "docs_only",
|
|
5022
5344
|
sourceFileCount,
|
|
5023
5345
|
totalFileCount,
|
|
5346
|
+
abstentionKind: "docs_distribution",
|
|
5024
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}`
|
|
5025
5348
|
};
|
|
5026
5349
|
}
|
|
5027
5350
|
if (unrecognisedSourceFiles.length > 0) {
|
|
5028
|
-
return unrecognisedEcosystemResult(
|
|
5351
|
+
return unrecognisedEcosystemResult(
|
|
5352
|
+
sourceFileCount,
|
|
5353
|
+
totalFileCount,
|
|
5354
|
+
unrecognisedSourceFiles,
|
|
5355
|
+
sourceDominanceThreshold(rubricVersion)
|
|
5356
|
+
);
|
|
5029
5357
|
}
|
|
5030
5358
|
return { archetype: "source", sourceFileCount, totalFileCount };
|
|
5031
5359
|
}
|
|
@@ -5037,25 +5365,285 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
5037
5365
|
};
|
|
5038
5366
|
}
|
|
5039
5367
|
const dominanceRatio = sourceFileCount / (sourceFileCount + unrecognisedSourceFiles.length);
|
|
5040
|
-
|
|
5368
|
+
const dominanceThreshold = sourceDominanceThreshold(rubricVersion);
|
|
5369
|
+
if (dominanceRatio >= dominanceThreshold) {
|
|
5041
5370
|
return {
|
|
5042
5371
|
archetype: isLikelyMonorepo(repoFiles) ? "monorepo" : "source",
|
|
5043
5372
|
sourceFileCount,
|
|
5044
5373
|
totalFileCount
|
|
5045
5374
|
};
|
|
5046
5375
|
}
|
|
5047
|
-
return unrecognisedEcosystemResult(
|
|
5376
|
+
return unrecognisedEcosystemResult(
|
|
5377
|
+
sourceFileCount,
|
|
5378
|
+
totalFileCount,
|
|
5379
|
+
unrecognisedSourceFiles,
|
|
5380
|
+
dominanceThreshold
|
|
5381
|
+
);
|
|
5048
5382
|
}
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5383
|
+
var READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13 = 0.8;
|
|
5384
|
+
var READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13 = 32;
|
|
5385
|
+
var READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13 = 256 * 1024;
|
|
5386
|
+
var SEMANTIC_AUXILIARY_SOURCE_MINIMUM_V14 = 20;
|
|
5387
|
+
var SEMANTIC_AUXILIARY_SOURCE_RATIO_V14 = 0.8;
|
|
5388
|
+
var SEMANTIC_METADATA_PREFIX_BYTES_V14 = 16 * 1024;
|
|
5389
|
+
var SEMANTIC_AUXILIARY_PATH_PATTERN_V14 = /(^|\/)(?:locales?|i18n|l10n|translations?|styles?|themes?)(\/|$)|\.(?:css|scss|sass|less|styl)$/i;
|
|
5390
|
+
var HTML_SOURCE_EXTENSION_PATTERN = /\.(?:html?|xhtml)$/i;
|
|
5391
|
+
var HTML_META_REFRESH_PATTERN = /<meta\b(?=[^>]*\bhttp-equiv\s*=\s*["']?\s*refresh\b)(?=[^>]*\bcontent\s*=\s*["'][^"']*\burl\s*=)[^>]*>/i;
|
|
5392
|
+
var RUBY_PACKAGE_RECIPE_CLASS_PATTERN = /\bclass\s+[A-Za-z0-9_:]+\s*<\s*(?:Formula|Cask)\b/;
|
|
5393
|
+
var RUBY_PACKAGE_RECIPE_URL_PATTERN = /^\s*url\s+["'][^"']+["']/m;
|
|
5394
|
+
var RUBY_PACKAGE_RECIPE_PATH_PATTERN = /(^|\/)(?:Formula|Casks?)(\/|$)/;
|
|
5395
|
+
function selectProportionalRepresentativeSourceFiles(files, rankKey, limit) {
|
|
5396
|
+
const eligibleFiles = [...new Set(files)];
|
|
5397
|
+
const slotCount = Math.min(Math.max(Math.floor(limit), 0), eligibleFiles.length);
|
|
5398
|
+
if (slotCount === 0) return [];
|
|
5399
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5400
|
+
for (const file of eligibleFiles) {
|
|
5401
|
+
const extension = extname(file).toLowerCase() || "(none)";
|
|
5402
|
+
const bucket = grouped.get(extension) ?? [];
|
|
5403
|
+
bucket.push(file);
|
|
5404
|
+
grouped.set(extension, bucket);
|
|
5405
|
+
}
|
|
5406
|
+
const groups = [...grouped.entries()].map(([extension, groupFiles]) => {
|
|
5407
|
+
const quota = groupFiles.length / eligibleFiles.length * slotCount;
|
|
5408
|
+
return {
|
|
5409
|
+
extension,
|
|
5410
|
+
files: [...groupFiles].sort(
|
|
5411
|
+
(left, right) => representativeSourceRank(rankKey, left).localeCompare(
|
|
5412
|
+
representativeSourceRank(rankKey, right)
|
|
5413
|
+
)
|
|
5414
|
+
),
|
|
5415
|
+
allocation: Math.floor(quota),
|
|
5416
|
+
remainder: quota - Math.floor(quota)
|
|
5417
|
+
};
|
|
5418
|
+
}).sort(
|
|
5419
|
+
(left, right) => right.files.length - left.files.length || left.extension.localeCompare(right.extension)
|
|
5420
|
+
);
|
|
5421
|
+
let remaining = slotCount - groups.reduce((sum, group) => sum + group.allocation, 0);
|
|
5422
|
+
const remainderOrder = [...groups].sort(
|
|
5423
|
+
(left, right) => right.remainder - left.remainder || right.files.length - left.files.length || left.extension.localeCompare(right.extension)
|
|
5424
|
+
);
|
|
5425
|
+
for (const group of remainderOrder) {
|
|
5426
|
+
if (remaining === 0) break;
|
|
5427
|
+
if (group.allocation >= group.files.length) continue;
|
|
5428
|
+
group.allocation += 1;
|
|
5429
|
+
remaining -= 1;
|
|
5430
|
+
}
|
|
5431
|
+
const selected = [];
|
|
5432
|
+
for (let offset = 0; selected.length < slotCount; offset += 1) {
|
|
5433
|
+
let added = false;
|
|
5434
|
+
for (const group of groups) {
|
|
5435
|
+
if (offset >= group.allocation) continue;
|
|
5436
|
+
const file = group.files[offset];
|
|
5437
|
+
if (!file) continue;
|
|
5438
|
+
selected.push(file);
|
|
5439
|
+
added = true;
|
|
5440
|
+
}
|
|
5441
|
+
if (!added) break;
|
|
5442
|
+
}
|
|
5443
|
+
return selected;
|
|
5444
|
+
}
|
|
5445
|
+
function isRepresentativeSourceText(contents) {
|
|
5446
|
+
if (contents.trim().length === 0) return false;
|
|
5447
|
+
if (contents.includes("\0") || contents.includes("\uFFFD")) return false;
|
|
5448
|
+
let disallowedControls = 0;
|
|
5449
|
+
for (const character of contents) {
|
|
5450
|
+
const code = character.charCodeAt(0);
|
|
5451
|
+
if (code < 32 && character !== "\n" && character !== "\r" && character !== " ") {
|
|
5452
|
+
disallowedControls += 1;
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
return disallowedControls / contents.length <= 0.01;
|
|
5456
|
+
}
|
|
5457
|
+
function representativeSourceRank(rankKey, file) {
|
|
5458
|
+
return createHash2("sha256").update(`${rankKey}:${file}`).digest("hex");
|
|
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);
|
|
5058
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
|
+
}
|
|
5487
|
+
function applyReadableSourceRepresentationGate(repoPath, repoFiles, classification, headSha, rubricVersion) {
|
|
5488
|
+
if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
|
|
5489
|
+
return classification;
|
|
5490
|
+
}
|
|
5491
|
+
const authoredSourceFiles = repoFiles.filter(
|
|
5492
|
+
(path) => isAuthoredProductionPath(path) && (isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path))
|
|
5493
|
+
);
|
|
5494
|
+
if (authoredSourceFiles.length === 0) return classification;
|
|
5495
|
+
const sample = selectProportionalRepresentativeSourceFiles(
|
|
5496
|
+
authoredSourceFiles,
|
|
5497
|
+
`witan-v13-readable-source:${headSha ?? "unversioned"}`,
|
|
5498
|
+
READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13
|
|
5499
|
+
);
|
|
5500
|
+
const sampledByExtension = /* @__PURE__ */ new Map();
|
|
5501
|
+
for (const file of sample) {
|
|
5502
|
+
const extension = extname(file).toLowerCase() || "(none)";
|
|
5503
|
+
const counts = sampledByExtension.get(extension) ?? { sampled: 0, readable: 0 };
|
|
5504
|
+
counts.sampled += 1;
|
|
5505
|
+
const contents = readRepresentativeSourceText(repoPath, file);
|
|
5506
|
+
if (contents !== null && isRepresentativeSourceText(contents)) {
|
|
5507
|
+
counts.readable += 1;
|
|
5508
|
+
}
|
|
5509
|
+
sampledByExtension.set(extension, counts);
|
|
5510
|
+
}
|
|
5511
|
+
const sourceCountByExtension = /* @__PURE__ */ new Map();
|
|
5512
|
+
for (const file of authoredSourceFiles) {
|
|
5513
|
+
const extension = extname(file).toLowerCase() || "(none)";
|
|
5514
|
+
sourceCountByExtension.set(extension, (sourceCountByExtension.get(extension) ?? 0) + 1);
|
|
5515
|
+
}
|
|
5516
|
+
const readableSourceFileCount = [...sourceCountByExtension.entries()].reduce(
|
|
5517
|
+
(total, [extension, sourceCount]) => {
|
|
5518
|
+
const sampled = sampledByExtension.get(extension);
|
|
5519
|
+
if (!sampled || sampled.sampled === 0) return total;
|
|
5520
|
+
return total + sourceCount * (sampled.readable / sampled.sampled);
|
|
5521
|
+
},
|
|
5522
|
+
0
|
|
5523
|
+
);
|
|
5524
|
+
const readableRatio = readableSourceFileCount / authoredSourceFiles.length;
|
|
5525
|
+
if (readableRatio >= READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13) return classification;
|
|
5526
|
+
const readableCountLabel = Number.isInteger(readableSourceFileCount) ? String(readableSourceFileCount) : readableSourceFileCount.toFixed(2);
|
|
5527
|
+
const readablePct = (readableRatio * 100).toFixed(1);
|
|
5528
|
+
const thresholdPct = (READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13 * 100).toFixed(0);
|
|
5529
|
+
return {
|
|
5530
|
+
archetype: "non_cohesive_source",
|
|
5531
|
+
sourceFileCount: classification.sourceFileCount,
|
|
5532
|
+
totalFileCount: classification.totalFileCount,
|
|
5533
|
+
abstentionKind: "readable_representation",
|
|
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}`
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
function applySemanticSourceRepresentationGate(repoPath, repoFiles, classification, rubricVersion) {
|
|
5538
|
+
if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
|
|
5539
|
+
return classification;
|
|
5540
|
+
}
|
|
5541
|
+
const authoredSourceFiles = repoFiles.filter(
|
|
5542
|
+
(path) => isAuthoredProductionPath(path) && (isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path))
|
|
5543
|
+
);
|
|
5544
|
+
if (authoredSourceFiles.length === 0) return classification;
|
|
5545
|
+
const auxiliaryFiles = authoredSourceFiles.filter(
|
|
5546
|
+
(path) => SEMANTIC_AUXILIARY_PATH_PATTERN_V14.test(path)
|
|
5547
|
+
);
|
|
5548
|
+
const packageMetadataFiles = authoredSourceFiles.filter(
|
|
5549
|
+
(path) => isRubyPackageRecipeMetadataSource(repoPath, path)
|
|
5550
|
+
);
|
|
5551
|
+
const redirectMetadataFiles = authoredSourceFiles.filter(
|
|
5552
|
+
(path) => isHtmlRedirectMetadataSource(repoPath, path)
|
|
5553
|
+
);
|
|
5554
|
+
const auxiliarySet = /* @__PURE__ */ new Set([
|
|
5555
|
+
...auxiliaryFiles,
|
|
5556
|
+
...packageMetadataFiles,
|
|
5557
|
+
...redirectMetadataFiles
|
|
5558
|
+
]);
|
|
5559
|
+
const representativeImplementationCount = authoredSourceFiles.filter(
|
|
5560
|
+
(path) => !auxiliarySet.has(path)
|
|
5561
|
+
).length;
|
|
5562
|
+
if (representativeImplementationCount === 0) {
|
|
5563
|
+
const detail = redirectMetadataFiles.length > 0 ? `${redirectMetadataFiles.length} redirect metadata file(s)` : packageMetadataFiles.length > 0 ? `${packageMetadataFiles.length} package metadata file(s)` : `${auxiliaryFiles.length} localization or style metadata file(s)`;
|
|
5564
|
+
return {
|
|
5565
|
+
archetype: "non_cohesive_source",
|
|
5566
|
+
sourceFileCount: classification.sourceFileCount,
|
|
5567
|
+
totalFileCount: classification.totalFileCount,
|
|
5568
|
+
abstentionKind: "semantic_auxiliary",
|
|
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}`
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
const auxiliaryCount = auxiliarySet.size;
|
|
5573
|
+
const auxiliaryRatio = auxiliaryCount / authoredSourceFiles.length;
|
|
5574
|
+
if (authoredSourceFiles.length >= SEMANTIC_AUXILIARY_SOURCE_MINIMUM_V14 && auxiliaryRatio >= SEMANTIC_AUXILIARY_SOURCE_RATIO_V14) {
|
|
5575
|
+
return {
|
|
5576
|
+
archetype: "non_cohesive_source",
|
|
5577
|
+
sourceFileCount: classification.sourceFileCount,
|
|
5578
|
+
totalFileCount: classification.totalFileCount,
|
|
5579
|
+
abstentionKind: "semantic_auxiliary",
|
|
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}`
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
return classification;
|
|
5584
|
+
}
|
|
5585
|
+
function isHtmlRedirectMetadataSource(repoPath, file) {
|
|
5586
|
+
if (!HTML_SOURCE_EXTENSION_PATTERN.test(file)) return false;
|
|
5587
|
+
const contents = readSemanticMetadataPrefix(repoPath, file);
|
|
5588
|
+
if (contents === null) return false;
|
|
5589
|
+
const operativeContents = contents.replace(/<!--[\s\S]*?-->/g, "");
|
|
5590
|
+
if (!HTML_META_REFRESH_PATTERN.test(operativeContents)) return false;
|
|
5591
|
+
return !/<(?:script|form|main|article|section|canvas|template)\b/i.test(operativeContents);
|
|
5592
|
+
}
|
|
5593
|
+
function isRubyPackageRecipeMetadataSource(repoPath, file) {
|
|
5594
|
+
if (!/\.rb$/i.test(file) || !RUBY_PACKAGE_RECIPE_PATH_PATTERN.test(file)) return false;
|
|
5595
|
+
const contents = readSemanticMetadataPrefix(repoPath, file);
|
|
5596
|
+
const operativeContents = contents?.replace(/^\s*#.*$/gm, "");
|
|
5597
|
+
return operativeContents !== void 0 && RUBY_PACKAGE_RECIPE_CLASS_PATTERN.test(operativeContents) && RUBY_PACKAGE_RECIPE_URL_PATTERN.test(operativeContents);
|
|
5598
|
+
}
|
|
5599
|
+
function readSemanticMetadataPrefix(repoPath, file) {
|
|
5600
|
+
const absolutePath = join(repoPath, file);
|
|
5601
|
+
if (!isRegularFile(absolutePath)) return null;
|
|
5602
|
+
let descriptor = null;
|
|
5603
|
+
try {
|
|
5604
|
+
descriptor = openSync(absolutePath, "r");
|
|
5605
|
+
const buffer = Buffer.allocUnsafe(SEMANTIC_METADATA_PREFIX_BYTES_V14);
|
|
5606
|
+
const bytesRead = readSync(descriptor, buffer, 0, SEMANTIC_METADATA_PREFIX_BYTES_V14, 0);
|
|
5607
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
5608
|
+
} catch {
|
|
5609
|
+
return null;
|
|
5610
|
+
} finally {
|
|
5611
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
function readRepresentativeSourceText(repoPath, file) {
|
|
5615
|
+
const absolutePath = join(repoPath, file);
|
|
5616
|
+
try {
|
|
5617
|
+
if (!isRegularFile(absolutePath)) return null;
|
|
5618
|
+
const size = statSync(absolutePath).size;
|
|
5619
|
+
if (size === 0 || size > READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13) return null;
|
|
5620
|
+
return readFileSync(absolutePath, "utf8");
|
|
5621
|
+
} catch {
|
|
5622
|
+
return null;
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
function sourceDominanceThreshold(rubricVersion) {
|
|
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) {
|
|
5627
|
+
return SOURCE_DOMINANCE_RATIO_THRESHOLD_V11;
|
|
5628
|
+
}
|
|
5629
|
+
if (rubricVersion === WITAN_RUBRIC_VERSION_V10) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V10;
|
|
5630
|
+
if (rubricVersion === WITAN_RUBRIC_VERSION_V92) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V9;
|
|
5631
|
+
return SOURCE_DOMINANCE_RATIO_THRESHOLD_V8;
|
|
5632
|
+
}
|
|
5633
|
+
function collectUnrecognisedSourceFiles(repoFiles, rubricVersion) {
|
|
5634
|
+
return repoFiles.filter((path) => isUnrecognisedSourcePath(path, rubricVersion));
|
|
5635
|
+
}
|
|
5636
|
+
function isUnrecognisedSourcePath(file, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
5637
|
+
const base = basename(file);
|
|
5638
|
+
if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
|
|
5639
|
+
if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
|
|
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)) {
|
|
5641
|
+
return false;
|
|
5642
|
+
}
|
|
5643
|
+
if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
|
|
5644
|
+
if (BUNDLED_BINARY_EXTENSION_PATTERN.test(base)) return false;
|
|
5645
|
+
if (isRecognizedSourcePath(base, rubricVersion)) return false;
|
|
5646
|
+
return true;
|
|
5059
5647
|
}
|
|
5060
5648
|
function extensionsByFileCountDescending(files) {
|
|
5061
5649
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -5068,16 +5656,17 @@ function extensionsByFileCountDescending(files) {
|
|
|
5068
5656
|
}
|
|
5069
5657
|
return [...counts.entries()].sort(([extA, countA], [extB, countB]) => countB - countA || extA.localeCompare(extB)).map(([ext]) => ext);
|
|
5070
5658
|
}
|
|
5071
|
-
function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles) {
|
|
5659
|
+
function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles, dominanceThreshold = SOURCE_DOMINANCE_RATIO_THRESHOLD_V9) {
|
|
5072
5660
|
const sample = extensionsByFileCountDescending(unrecognisedSourceFiles).slice(0, 6).join(", ");
|
|
5073
5661
|
const candidateCount = sourceFileCount + unrecognisedSourceFiles.length;
|
|
5074
5662
|
const dominancePct = candidateCount > 0 ? (sourceFileCount / candidateCount * 100).toFixed(1) : "0.0";
|
|
5075
|
-
const thresholdPct = (
|
|
5663
|
+
const thresholdPct = (dominanceThreshold * 100).toFixed(0);
|
|
5076
5664
|
const measuredClause = sourceFileCount > 0 ? `${sourceFileCount} of ${candidateCount} source-shaped file(s) (${dominancePct}%) are in a language Cejel reads \u2014 below the ${thresholdPct}% dominance threshold a score would need to be meaningful (${totalFileCount} tracked files in total; manifests, lockfiles, docs, media and bundled binaries are excluded from both sides of the ratio)` : `0 of ${totalFileCount} tracked file(s) matched a recognised source extension`;
|
|
5077
5665
|
return {
|
|
5078
5666
|
archetype: "unrecognised_ecosystem",
|
|
5079
5667
|
sourceFileCount,
|
|
5080
5668
|
totalFileCount,
|
|
5669
|
+
abstentionKind: "unrecognised_ecosystem",
|
|
5081
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}`
|
|
5082
5671
|
};
|
|
5083
5672
|
}
|
|
@@ -5098,6 +5687,32 @@ function assertRepoPathExists(repoPath) {
|
|
|
5098
5687
|
throw new Error(`Cejel: path is not a directory: ${repoPath}`);
|
|
5099
5688
|
}
|
|
5100
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
|
+
}
|
|
5101
5716
|
function buildNotApplicableSignal(criterionId, reason) {
|
|
5102
5717
|
return {
|
|
5103
5718
|
criterionId,
|
|
@@ -5108,14 +5723,35 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
5108
5723
|
notApplicable: true
|
|
5109
5724
|
};
|
|
5110
5725
|
}
|
|
5111
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles) {
|
|
5726
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, reviewableSourceProof) {
|
|
5112
5727
|
const signals = [];
|
|
5113
5728
|
const mono = resolveMonorepoContext(repoPath);
|
|
5114
|
-
const a1Signal = collectA1TestIntegrityEvidence(
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5729
|
+
const a1Signal = collectA1TestIntegrityEvidence(
|
|
5730
|
+
repoPath,
|
|
5731
|
+
repoFiles,
|
|
5732
|
+
useV27Detectors,
|
|
5733
|
+
useV33Detectors,
|
|
5734
|
+
useV36Detectors,
|
|
5735
|
+
reviewableSourceProof
|
|
5736
|
+
);
|
|
5737
|
+
const a2Signal = collectA2IsolationEvidence(
|
|
5738
|
+
repoPath,
|
|
5739
|
+
repoFiles,
|
|
5740
|
+
useV27Detectors,
|
|
5741
|
+
useV33Detectors,
|
|
5742
|
+
useV36Detectors,
|
|
5743
|
+
useV39Detectors,
|
|
5744
|
+
useV47Detectors
|
|
5745
|
+
);
|
|
5746
|
+
const a3Signal = collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors);
|
|
5747
|
+
const a4Signal = collectA4DependencyEvidence(
|
|
5748
|
+
repoPath,
|
|
5749
|
+
repoFiles,
|
|
5750
|
+
mono,
|
|
5751
|
+
useV27Detectors,
|
|
5752
|
+
useV47Detectors
|
|
5753
|
+
);
|
|
5754
|
+
const a5Signal = collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors);
|
|
5119
5755
|
const b1Signal = buildNotApplicableSignal(
|
|
5120
5756
|
"B1",
|
|
5121
5757
|
"Repository scans do not evaluate the dispatch-trace process dimension."
|
|
@@ -5127,7 +5763,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles) {
|
|
|
5127
5763
|
"B5",
|
|
5128
5764
|
"Repository scans do not evaluate the learning-trace process dimension."
|
|
5129
5765
|
);
|
|
5130
|
-
const b6Signal = collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles);
|
|
5766
|
+
const b6Signal = collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors);
|
|
5131
5767
|
for (const signal of [
|
|
5132
5768
|
a1Signal,
|
|
5133
5769
|
a2Signal,
|
|
@@ -5145,22 +5781,31 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles) {
|
|
|
5145
5781
|
}
|
|
5146
5782
|
return signals;
|
|
5147
5783
|
}
|
|
5148
|
-
function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
5784
|
+
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, reviewableSourceProof) {
|
|
5149
5785
|
const evidence = [];
|
|
5150
5786
|
const findings = [];
|
|
5151
|
-
const
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5787
|
+
const allTestFiles = findConcreteTestFiles(
|
|
5788
|
+
repoPath,
|
|
5789
|
+
repoFiles,
|
|
5790
|
+
reviewableSourceProof ? WITAN_RUBRIC_VERSION_V17 : void 0
|
|
5791
|
+
);
|
|
5156
5792
|
const testFiles = allTestFiles.slice(0, 8);
|
|
5157
5793
|
const sourceFiles = repoFiles.filter(isSourceFile);
|
|
5158
|
-
const runnerFiles = repoFiles.filter(
|
|
5159
|
-
|
|
5794
|
+
const runnerFiles = repoFiles.filter(
|
|
5795
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isTestRunnerConfig(repoPath, file)
|
|
5796
|
+
).slice(0, 6);
|
|
5797
|
+
const configuredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
|
|
5798
|
+
const allCoverageFiles = findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors);
|
|
5799
|
+
const coverageFiles = allCoverageFiles.slice(0, 4);
|
|
5160
5800
|
const packageJson = findRootPackageJson(repoFiles);
|
|
5161
5801
|
const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
5802
|
+
const packageJsonFiles = useV27Detectors ? repoFiles.filter(
|
|
5803
|
+
(file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
5804
|
+
) : packageJson ? [packageJson] : [];
|
|
5162
5805
|
const coveragePercent = readCoveragePercent(repoPath, coverageFiles);
|
|
5163
|
-
const ciWorkflows = repoFiles.filter(
|
|
5806
|
+
const ciWorkflows = repoFiles.filter(
|
|
5807
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
5808
|
+
);
|
|
5164
5809
|
const ciTestWorkflow = ciWorkflows.find(
|
|
5165
5810
|
(file) => fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN)
|
|
5166
5811
|
);
|
|
@@ -5179,7 +5824,9 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5179
5824
|
const usesLeanBuiltInTestRunner = packageJson != null && [...packageScripts.values()].some(
|
|
5180
5825
|
(script) => LEAN_TEST_RUNNER_SCRIPT_PATTERN.test(script)
|
|
5181
5826
|
) || testFiles.some((file) => fileContains(repoPath, file, LEAN_TEST_RUNNER_IMPORT_PATTERN));
|
|
5182
|
-
const hasHeavyTestDependency =
|
|
5827
|
+
const hasHeavyTestDependency = packageJsonFiles.some(
|
|
5828
|
+
(manifest) => packageJsonHasHeavyTestDependency(repoPath, manifest)
|
|
5829
|
+
);
|
|
5183
5830
|
const isLeanTestToolchain = usesLeanBuiltInTestRunner && !hasHeavyTestDependency;
|
|
5184
5831
|
for (const file of testFiles) {
|
|
5185
5832
|
evidence.push(evidenceForRelative(repoPath, file, "test_run", "Detected test file"));
|
|
@@ -5199,7 +5846,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5199
5846
|
);
|
|
5200
5847
|
}
|
|
5201
5848
|
const scheduledHealthWorkflow = ciWorkflows.find(
|
|
5202
|
-
(file) => fileContains(repoPath, file, SCHEDULE_TRIGGER_PATTERN) && fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN)
|
|
5849
|
+
(file) => fileContains(repoPath, file, SCHEDULE_TRIGGER_PATTERN) && fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN) && (!useV36Detectors || SCHEDULED_HEALTH_PATH_PATTERN.test(file) || fileContains(repoPath, file, SCHEDULED_HEALTH_INTENT_PATTERN))
|
|
5203
5850
|
);
|
|
5204
5851
|
if (scheduledHealthWorkflow) {
|
|
5205
5852
|
const isPublished = fileContains(
|
|
@@ -5230,9 +5877,16 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5230
5877
|
});
|
|
5231
5878
|
}
|
|
5232
5879
|
}
|
|
5233
|
-
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
|
+
}
|
|
5234
5883
|
if (testFiles.length === 0) {
|
|
5235
|
-
const fallback =
|
|
5884
|
+
const fallback = configuredRunnerFiles[0] ? evidenceForRelative(
|
|
5885
|
+
repoPath,
|
|
5886
|
+
configuredRunnerFiles[0],
|
|
5887
|
+
"test_run",
|
|
5888
|
+
"Configured test runner"
|
|
5889
|
+
) : useV33Detectors ? void 0 : evidence[0] ?? findings[0]?.evidence;
|
|
5236
5890
|
if (fallback) {
|
|
5237
5891
|
findings.push({
|
|
5238
5892
|
severity: "warning",
|
|
@@ -5240,7 +5894,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5240
5894
|
evidence: fallback
|
|
5241
5895
|
});
|
|
5242
5896
|
}
|
|
5243
|
-
} else if (coverageFiles.length === 0) {
|
|
5897
|
+
} else if (coverageFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
|
|
5244
5898
|
if (isLeanTestToolchain) {
|
|
5245
5899
|
const leanEvidenceFile = packageJson ?? testFiles[0];
|
|
5246
5900
|
if (leanEvidenceFile) {
|
|
@@ -5263,7 +5917,11 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5263
5917
|
});
|
|
5264
5918
|
}
|
|
5265
5919
|
}
|
|
5266
|
-
const nonHollowShare = measureNonHollowTestShare(
|
|
5920
|
+
const nonHollowShare = measureNonHollowTestShare(
|
|
5921
|
+
repoPath,
|
|
5922
|
+
allTestFiles,
|
|
5923
|
+
reviewableSourceProof !== void 0
|
|
5924
|
+
);
|
|
5267
5925
|
return {
|
|
5268
5926
|
criterionId: "A1",
|
|
5269
5927
|
positiveEvidence: evidence,
|
|
@@ -5311,27 +5969,131 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5311
5969
|
notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
|
|
5312
5970
|
};
|
|
5313
5971
|
}
|
|
5314
|
-
function
|
|
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
|
+
}
|
|
6036
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
|
|
5315
6037
|
const evidence = [];
|
|
5316
6038
|
const findings = [];
|
|
5317
6039
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
5318
|
-
const envExamples = repoFiles.filter((file) => isEnvTemplatePath(file));
|
|
6040
|
+
const envExamples = repoFiles.filter((file) => isEnvTemplatePath(file, useV47Detectors));
|
|
5319
6041
|
const migrationFiles = repoFiles.filter(
|
|
5320
6042
|
(file) => /(^|\/)(migrations?|drizzle|prisma)\//.test(file)
|
|
5321
6043
|
);
|
|
5322
|
-
|
|
5323
|
-
|
|
6044
|
+
let committedSecret;
|
|
6045
|
+
for (const path of repoFiles) {
|
|
6046
|
+
const authoredProductionPath = useV47Detectors ? isV47AuthoredProductionPath(path) : useV39Detectors ? isV39AuthoredProductionPath(path) : isAuthoredProductionPath(path);
|
|
6047
|
+
if (isIgnoredScanFile(path, useV47Detectors) || useV33Detectors && !authoredProductionPath) {
|
|
6048
|
+
continue;
|
|
6049
|
+
}
|
|
6050
|
+
const match = findCommittedSecretInFile(
|
|
6051
|
+
repoPath,
|
|
6052
|
+
path,
|
|
6053
|
+
useV36Detectors,
|
|
6054
|
+
useV39Detectors,
|
|
6055
|
+
useV47Detectors
|
|
6056
|
+
);
|
|
6057
|
+
if (!match) continue;
|
|
6058
|
+
committedSecret = { path, match };
|
|
6059
|
+
break;
|
|
6060
|
+
}
|
|
6061
|
+
const currentSecretFingerprintsByPath = /* @__PURE__ */ new Map();
|
|
6062
|
+
if (useV27Detectors) {
|
|
6063
|
+
for (const path of repoFiles.filter((candidate) => isCredentialHistoryPath(candidate))) {
|
|
6064
|
+
currentSecretFingerprintsByPath.set(
|
|
6065
|
+
path,
|
|
6066
|
+
new Set(
|
|
6067
|
+
findSecretFingerprintsInFile(
|
|
6068
|
+
repoPath,
|
|
6069
|
+
path,
|
|
6070
|
+
useV36Detectors,
|
|
6071
|
+
useV39Detectors,
|
|
6072
|
+
useV47Detectors
|
|
6073
|
+
)
|
|
6074
|
+
)
|
|
6075
|
+
);
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
6078
|
+
const historySecretScan = committedSecret && !useV27Detectors ? null : collectHistorySecretEvidence(
|
|
6079
|
+
repoPath,
|
|
6080
|
+
useV27Detectors,
|
|
6081
|
+
currentSecretFingerprintsByPath,
|
|
6082
|
+
useV33Detectors,
|
|
6083
|
+
useV36Detectors,
|
|
6084
|
+
useV39Detectors,
|
|
6085
|
+
useV47Detectors
|
|
5324
6086
|
);
|
|
5325
|
-
const historySecretScan = committedSecret ? null : collectHistorySecretEvidence(repoPath);
|
|
5326
6087
|
const hasConfirmedSecretFinding = committedSecret != null || historySecretScan?.evidence != null;
|
|
5327
6088
|
const cryptoHygiene = collectCryptoHygieneEvidence(
|
|
5328
6089
|
repoPath,
|
|
5329
|
-
repoFiles.filter(isImplementationFile).slice(0, 60)
|
|
6090
|
+
repoFiles.filter(isImplementationFile).slice(0, 60),
|
|
6091
|
+
useV39Detectors
|
|
5330
6092
|
);
|
|
5331
6093
|
const implFiles = repoFiles.filter(isImplementationFile);
|
|
5332
6094
|
const gitignoreHasEnvRule = gitignore != null && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m);
|
|
5333
6095
|
const committedEnvFilePath = repoFiles.find(
|
|
5334
|
-
(f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f)
|
|
6096
|
+
(f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f, useV47Detectors) && (!useV47Detectors || isV47AuthoredProductionPath(f)) && (!useV39Detectors || !hasTemplateOnlyEnvContent(repoPath, f))
|
|
5335
6097
|
);
|
|
5336
6098
|
const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath);
|
|
5337
6099
|
const dbLayerImportFile = migrationFiles.length === 0 ? implFiles.slice(0, 30).find((f) => fileContains(repoPath, f, DB_CLIENT_PATTERN)) : void 0;
|
|
@@ -5366,20 +6128,17 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5366
6128
|
} else if (dbLayerImportFile != null) {
|
|
5367
6129
|
evidence.push(evidenceForRelative(repoPath, dbLayerImportFile, "artifact", "DB client import"));
|
|
5368
6130
|
}
|
|
5369
|
-
const
|
|
5370
|
-
const
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
const
|
|
6131
|
+
const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
|
|
6132
|
+
const v47TenantStorageFiles = useV47Detectors ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
|
|
6133
|
+
const rlsMigration = useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
|
|
6134
|
+
const tenantScopePattern = useV39Detectors ? TENANT_SCOPE_PATTERN_V11 : useV36Detectors ? TENANT_SCOPE_PATTERN_V10 : TENANT_SCOPE_PATTERN;
|
|
6135
|
+
const tenantScopedFile = useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern));
|
|
6136
|
+
const rlsPolicyCount = useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
|
|
5374
6137
|
repoPath,
|
|
5375
6138
|
migrationFiles,
|
|
5376
6139
|
/create policy|enable row level security|force row level security/gi
|
|
5377
6140
|
);
|
|
5378
|
-
const tenantScopedMigrationFileCount = countFilesContaining(
|
|
5379
|
-
repoPath,
|
|
5380
|
-
migrationFiles,
|
|
5381
|
-
TENANT_SCOPE_PATTERN
|
|
5382
|
-
);
|
|
6141
|
+
const tenantScopedMigrationFileCount = useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
|
|
5383
6142
|
const gitignoreEnvFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
|
|
5384
6143
|
const envHandlingDepth = (envExamples.length > 0 ? 1 : 0) + (gitignoreEnvFile ? 1 : 0) + (countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0 ? 1 : 0);
|
|
5385
6144
|
const isMultiTenant = tenantScopedMigrationFileCount > 0;
|
|
@@ -5402,15 +6161,31 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5402
6161
|
);
|
|
5403
6162
|
}
|
|
5404
6163
|
if (committedSecret) {
|
|
5405
|
-
const isTestPath = isTestOrFixturePath(committedSecret);
|
|
6164
|
+
const isTestPath = isTestOrFixturePath(committedSecret.path);
|
|
6165
|
+
const isDefaultAdmin = committedSecret.match.kind === "default_admin";
|
|
5406
6166
|
findings.push({
|
|
5407
6167
|
severity: isTestPath ? "info" : "critical",
|
|
5408
|
-
summary: isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
|
|
6168
|
+
summary: isDefaultAdmin ? isTestPath ? `Default administrative credential in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production deployment default; verify.` : "An explicit default administrative credential appears committed in production configuration." : isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
|
|
6169
|
+
evidence: evidenceForRelativeAtLine(
|
|
6170
|
+
repoPath,
|
|
6171
|
+
committedSecret.path,
|
|
6172
|
+
"secret_scan",
|
|
6173
|
+
secretEvidenceLabel(
|
|
6174
|
+
isDefaultAdmin ? "Committed default administrative credential" : "Committed secret-shaped value",
|
|
6175
|
+
committedSecret.match
|
|
6176
|
+
),
|
|
6177
|
+
committedSecret.match.line
|
|
6178
|
+
)
|
|
6179
|
+
});
|
|
6180
|
+
} else if (committedEnvFilePath) {
|
|
6181
|
+
findings.push({
|
|
6182
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
6183
|
+
summary: "A non-template .env file is committed in the current repository tree; no secret-shaped value was detected.",
|
|
5409
6184
|
evidence: evidenceForRelative(
|
|
5410
6185
|
repoPath,
|
|
5411
|
-
|
|
6186
|
+
committedEnvFilePath,
|
|
5412
6187
|
"secret_scan",
|
|
5413
|
-
"Committed secret
|
|
6188
|
+
"Committed .env file (no confirmed secret value found)"
|
|
5414
6189
|
)
|
|
5415
6190
|
});
|
|
5416
6191
|
}
|
|
@@ -5424,7 +6199,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5424
6199
|
});
|
|
5425
6200
|
} else if (historySecretScan?.truncated) {
|
|
5426
6201
|
findings.push({
|
|
5427
|
-
severity: "warning",
|
|
6202
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
5428
6203
|
summary: "History secret scan reached the credential-path safety valve before scanning every matching blob.",
|
|
5429
6204
|
evidence: {
|
|
5430
6205
|
kind: "secret_scan",
|
|
@@ -5435,7 +6210,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5435
6210
|
});
|
|
5436
6211
|
} else if (historySecretScan?.envPathEvidence) {
|
|
5437
6212
|
findings.push({
|
|
5438
|
-
severity: "warning",
|
|
6213
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
5439
6214
|
summary: "A non-template .env file was tracked in git history; no secret-shaped value was detected.",
|
|
5440
6215
|
evidence: historySecretScan.envPathEvidence
|
|
5441
6216
|
});
|
|
@@ -5493,7 +6268,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5493
6268
|
metric(
|
|
5494
6269
|
"tenant_scope_ratio",
|
|
5495
6270
|
"Tenant-scoped schema ratio",
|
|
5496
|
-
countFilesContaining(repoPath, migrationFiles,
|
|
6271
|
+
countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
|
|
5497
6272
|
Math.max(migrationFiles.length, 1),
|
|
5498
6273
|
0.25,
|
|
5499
6274
|
"ratio",
|
|
@@ -5519,12 +6294,14 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5519
6294
|
notes: "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported."
|
|
5520
6295
|
};
|
|
5521
6296
|
}
|
|
5522
|
-
function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
6297
|
+
function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = false) {
|
|
5523
6298
|
const evidence = [];
|
|
5524
6299
|
const findings = [];
|
|
5525
6300
|
let hasSurface = false;
|
|
5526
6301
|
let insecureFound = false;
|
|
5527
|
-
const productionImplFiles = implFiles.filter(
|
|
6302
|
+
const productionImplFiles = implFiles.filter(
|
|
6303
|
+
(file) => useV39Detectors ? isV39AuthoredProductionPath(file) : isProductionSourcePath(file)
|
|
6304
|
+
);
|
|
5528
6305
|
const timingSafeFile = productionImplFiles.find(
|
|
5529
6306
|
(file) => fileContains(repoPath, file, TIMING_SAFE_COMPARE_PATTERN)
|
|
5530
6307
|
);
|
|
@@ -5555,8 +6332,9 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5555
6332
|
}
|
|
5556
6333
|
let insecureCompareFile = null;
|
|
5557
6334
|
let insecureCompareLine = null;
|
|
5558
|
-
|
|
5559
|
-
|
|
6335
|
+
const compareFiles = useV39Detectors ? productionImplFiles : implFiles;
|
|
6336
|
+
for (const file of compareFiles) {
|
|
6337
|
+
const line = findInsecureSecretCompareLine(repoPath, file, useV39Detectors);
|
|
5560
6338
|
if (line != null) {
|
|
5561
6339
|
insecureCompareFile = file;
|
|
5562
6340
|
insecureCompareLine = line;
|
|
@@ -5564,6 +6342,12 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5564
6342
|
}
|
|
5565
6343
|
}
|
|
5566
6344
|
if (insecureCompareFile) {
|
|
6345
|
+
const insecureCompareSourceLine = lineAt(
|
|
6346
|
+
repoPath,
|
|
6347
|
+
insecureCompareFile,
|
|
6348
|
+
insecureCompareLine ?? 0
|
|
6349
|
+
);
|
|
6350
|
+
const comparesDefaultSentinel = useV39Detectors && insecureCompareSourceLine !== null && DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN.test(insecureCompareSourceLine);
|
|
5567
6351
|
const isTestPath = isTestOrFixturePath(insecureCompareFile);
|
|
5568
6352
|
if (!isTestPath) {
|
|
5569
6353
|
hasSurface = true;
|
|
@@ -5571,7 +6355,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5571
6355
|
}
|
|
5572
6356
|
findings.push({
|
|
5573
6357
|
severity: isTestPath ? "info" : "warning",
|
|
5574
|
-
summary: isTestPath ? `A secret/HMAC/signature comparison via plain equality appears in a test/fixture file (${insecureCompareFile}) \u2014 likely a test assertion, not a production timing leak; verify.` : "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
|
|
6358
|
+
summary: isTestPath ? `A secret/HMAC/signature comparison via plain equality appears in a test/fixture file (${insecureCompareFile}) \u2014 likely a test assertion, not a production timing leak; verify.` : comparesDefaultSentinel ? "A supplied secret is compared with a configured default-secret sentinel using ordinary equality; verify whether this comparison belongs on a constant-time authentication path." : "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
|
|
5575
6359
|
evidence: evidenceForRelativeAtLine(
|
|
5576
6360
|
repoPath,
|
|
5577
6361
|
insecureCompareFile,
|
|
@@ -5581,8 +6365,9 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5581
6365
|
)
|
|
5582
6366
|
});
|
|
5583
6367
|
}
|
|
5584
|
-
const
|
|
5585
|
-
|
|
6368
|
+
const unsortedFiles = useV39Detectors ? productionImplFiles : implFiles;
|
|
6369
|
+
const unsortedSignFile = unsortedFiles.find(
|
|
6370
|
+
(file) => (useV39Detectors ? fileHasVariableUnsortedJsonHmac(repoPath, file) : fileContains(repoPath, file, UNSORTED_JSON_SIGN_PATTERN)) && !fileContains(repoPath, file, CANONICAL_SERIALIZE_PATTERN)
|
|
5586
6371
|
);
|
|
5587
6372
|
if (unsortedSignFile) {
|
|
5588
6373
|
const isTestPath = isTestOrFixturePath(unsortedSignFile);
|
|
@@ -5590,11 +6375,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5590
6375
|
hasSurface = true;
|
|
5591
6376
|
insecureFound = true;
|
|
5592
6377
|
}
|
|
5593
|
-
const unsortedSignLine = findFirstMatchingLine(
|
|
5594
|
-
repoPath,
|
|
5595
|
-
unsortedSignFile,
|
|
5596
|
-
UNSORTED_JSON_SIGN_PATTERN
|
|
5597
|
-
);
|
|
6378
|
+
const unsortedSignLine = useV39Detectors ? findUnsortedJsonHmacLine(repoPath, unsortedSignFile) : findFirstMatchingLine(repoPath, unsortedSignFile, UNSORTED_JSON_SIGN_PATTERN);
|
|
5598
6379
|
findings.push({
|
|
5599
6380
|
severity: isTestPath ? "info" : "warning",
|
|
5600
6381
|
summary: isTestPath ? `Signing over unsorted JSON serialization appears in a test/fixture file (${unsortedSignFile}) \u2014 likely a test assertion, not a production signing gap; verify.` : "A signature/HMAC appears computed directly over JSON.stringify output with no canonical key ordering in the same file \u2014 signatures may not verify across equivalent payloads.",
|
|
@@ -5609,36 +6390,53 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5609
6390
|
}
|
|
5610
6391
|
return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
|
|
5611
6392
|
}
|
|
5612
|
-
function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
5613
|
-
if (!isDeployableService(repoPath, repoFiles)) {
|
|
6393
|
+
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
6394
|
+
if (!isDeployableService(repoPath, repoFiles, useV27Detectors)) {
|
|
6395
|
+
const dockerApplicabilityNote = useV27Detectors ? "A Dockerfile without an explicit runtime start/service command is ambiguous and does not qualify." : "Dockerfile alone is ambiguous and does not qualify.";
|
|
5614
6396
|
return buildNotApplicableSignal(
|
|
5615
6397
|
"A3",
|
|
5616
|
-
|
|
6398
|
+
`No deployable-service surface detected \u2014 production-readiness not applicable to this library/CLI archetype. Signals checked: production server entrypoint (HTTP/RPC port binding in main/server/app files, outside examples/tests/demo dirs), deploy config (vercel.json, render.yaml, fly.toml, Procfile, app.yaml, serverless.yml, docker-compose, k8s/helm manifests), CI deploy job (fly deploy, kubectl apply, helm install/upgrade, docker push). ${dockerApplicabilityNote}`
|
|
5617
6399
|
);
|
|
5618
6400
|
}
|
|
5619
6401
|
const evidence = [];
|
|
5620
6402
|
const findings = [];
|
|
5621
6403
|
const packageJson = findRootPackageJson(repoFiles);
|
|
5622
6404
|
const scripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
5623
|
-
const workflows = repoFiles.filter(
|
|
6405
|
+
const workflows = repoFiles.filter(
|
|
6406
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
6407
|
+
);
|
|
5624
6408
|
const workflow = workflows[0];
|
|
5625
|
-
const deployConfigs = repoFiles.filter(
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
const
|
|
6409
|
+
const deployConfigs = repoFiles.filter(
|
|
6410
|
+
(file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isDeployConfig(file)
|
|
6411
|
+
);
|
|
6412
|
+
const releaseDeployConfigs = repoFiles.filter(
|
|
6413
|
+
(file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isExplicitDeployTarget(file)
|
|
6414
|
+
);
|
|
6415
|
+
const releaseDeployConfig = releaseDeployConfigs[0];
|
|
6416
|
+
const containerBuildConfig = deployConfigs.find((file) => /(^|\/)Dockerfile$/.test(file));
|
|
6417
|
+
const envTemplate = repoFiles.find(
|
|
6418
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isEnvTemplatePath(file)
|
|
6419
|
+
);
|
|
6420
|
+
const healthChecks = repoFiles.filter(
|
|
6421
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isHealthCheckSignalFile(repoPath, file)
|
|
6422
|
+
);
|
|
5629
6423
|
const healthCheck = healthChecks[0];
|
|
5630
6424
|
const errorBoundaries = repoFiles.filter(
|
|
5631
|
-
(file) => /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
|
|
6425
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
|
|
5632
6426
|
);
|
|
5633
6427
|
const errorBoundary = errorBoundaries[0];
|
|
5634
6428
|
const observabilityCount = countFilesContaining(
|
|
5635
6429
|
repoPath,
|
|
5636
|
-
repoFiles.filter(
|
|
6430
|
+
repoFiles.filter(
|
|
6431
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
|
|
6432
|
+
),
|
|
5637
6433
|
/sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
|
|
5638
6434
|
);
|
|
5639
6435
|
const rollbackSafetyCount = countFilesContaining(
|
|
5640
6436
|
repoPath,
|
|
5641
|
-
repoFiles.filter(
|
|
6437
|
+
repoFiles.filter(
|
|
6438
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)
|
|
6439
|
+
),
|
|
5642
6440
|
/rollback|roll back|migration safety|down migration|reversible|undo migration/i
|
|
5643
6441
|
);
|
|
5644
6442
|
if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
|
|
@@ -5647,9 +6445,23 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5647
6445
|
);
|
|
5648
6446
|
}
|
|
5649
6447
|
if (workflow) evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"));
|
|
5650
|
-
if (
|
|
6448
|
+
if (releaseDeployConfig)
|
|
6449
|
+
evidence.push(
|
|
6450
|
+
evidenceForRelative(
|
|
6451
|
+
repoPath,
|
|
6452
|
+
releaseDeployConfig,
|
|
6453
|
+
"prod_check",
|
|
6454
|
+
"Release deploy configuration"
|
|
6455
|
+
)
|
|
6456
|
+
);
|
|
6457
|
+
if (containerBuildConfig)
|
|
5651
6458
|
evidence.push(
|
|
5652
|
-
evidenceForRelative(
|
|
6459
|
+
evidenceForRelative(
|
|
6460
|
+
repoPath,
|
|
6461
|
+
containerBuildConfig,
|
|
6462
|
+
"prod_check",
|
|
6463
|
+
"Container build configuration"
|
|
6464
|
+
)
|
|
5653
6465
|
);
|
|
5654
6466
|
if (envTemplate)
|
|
5655
6467
|
evidence.push(evidenceForRelative(repoPath, envTemplate, "prod_check", "Environment template"));
|
|
@@ -5660,7 +6472,8 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5660
6472
|
if (errorBoundary)
|
|
5661
6473
|
evidence.push(evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary"));
|
|
5662
6474
|
if (evidence.length === 0) {
|
|
5663
|
-
const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles);
|
|
6475
|
+
const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles, useV27Detectors);
|
|
6476
|
+
const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles) : null;
|
|
5664
6477
|
if (serverEntrypoint) {
|
|
5665
6478
|
evidence.push(
|
|
5666
6479
|
evidenceForRelative(
|
|
@@ -5670,15 +6483,24 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5670
6483
|
"Production server entrypoint"
|
|
5671
6484
|
)
|
|
5672
6485
|
);
|
|
6486
|
+
} else if (runtimeContainer) {
|
|
6487
|
+
evidence.push(
|
|
6488
|
+
evidenceForRelative(
|
|
6489
|
+
repoPath,
|
|
6490
|
+
runtimeContainer,
|
|
6491
|
+
"prod_check",
|
|
6492
|
+
"Runtime container entrypoint"
|
|
6493
|
+
)
|
|
6494
|
+
);
|
|
5673
6495
|
}
|
|
5674
6496
|
}
|
|
5675
6497
|
if (evidence.length === 0) return null;
|
|
5676
|
-
if (!workflow &&
|
|
6498
|
+
if (!workflow && releaseDeployConfigs.length === 0) {
|
|
5677
6499
|
const firstEvidence = evidence[0];
|
|
5678
6500
|
if (!firstEvidence) return null;
|
|
5679
6501
|
findings.push({
|
|
5680
6502
|
severity: "info",
|
|
5681
|
-
summary: "
|
|
6503
|
+
summary: "A deployable service surface exists, but no CI or release-deployment automation was detected.",
|
|
5682
6504
|
evidence: firstEvidence
|
|
5683
6505
|
});
|
|
5684
6506
|
}
|
|
@@ -5736,7 +6558,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5736
6558
|
]
|
|
5737
6559
|
};
|
|
5738
6560
|
}
|
|
5739
|
-
function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
6561
|
+
function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors) {
|
|
5740
6562
|
const evidence = [];
|
|
5741
6563
|
const findings = [];
|
|
5742
6564
|
const manifests = repoFiles.filter(isDependencyManifest);
|
|
@@ -5785,7 +6607,9 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
|
5785
6607
|
);
|
|
5786
6608
|
}
|
|
5787
6609
|
if (!manifest && !hasLockfile) return null;
|
|
5788
|
-
const
|
|
6610
|
+
const packagedApplicationFiles = useV27Detectors ? findPackagedApplicationPremiseFiles(repoPath, repoFiles) : [];
|
|
6611
|
+
const deployableServiceFiles = useV47Detectors ? findDeployableServicePremiseFiles(repoPath, repoFiles, true) : [];
|
|
6612
|
+
const isAppOrService = (useV47Detectors ? deployableServiceFiles.length > 0 : isDeployableService(repoPath, repoFiles, useV27Detectors)) || packagedApplicationFiles.length > 0;
|
|
5789
6613
|
if (isAppOrService && !hasLockfile && manifest) {
|
|
5790
6614
|
findings.push({
|
|
5791
6615
|
severity: "critical",
|
|
@@ -5793,7 +6617,7 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
|
5793
6617
|
evidence: evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
|
|
5794
6618
|
});
|
|
5795
6619
|
}
|
|
5796
|
-
if (manifest && hasSuspiciousDependencies(repoPath, manifest)) {
|
|
6620
|
+
if (manifest && hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)) {
|
|
5797
6621
|
findings.push({
|
|
5798
6622
|
severity: "critical",
|
|
5799
6623
|
summary: "Dependency manifest contains obviously suspicious placeholder dependency names.",
|
|
@@ -5875,7 +6699,7 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
|
5875
6699
|
positiveEvidence: evidence,
|
|
5876
6700
|
findings,
|
|
5877
6701
|
metrics: isAppOrService ? appMetrics : libraryMetrics,
|
|
5878
|
-
notes: isAppOrService ?
|
|
6702
|
+
notes: isAppOrService ? `A4 scored against app/service norms (${packagedApplicationFiles.length > 0 ? "packaged application" : "deploy surface"} detected): pinned dependencies and a lockfile are required for reproducible installs.` : "A4 scored against library/CLI norms (no deploy surface detected \u2014 same archetype line as A3): declared version ranges are correct library behavior; a committed lockfile is credited but not required."
|
|
5879
6703
|
};
|
|
5880
6704
|
}
|
|
5881
6705
|
var LIBRARY_DEPENDENCY_COUNT_SANE_MAX = 120;
|
|
@@ -5892,15 +6716,19 @@ function hasDeclaredVersionConstraint(dependency) {
|
|
|
5892
6716
|
if (/[~^<>=!]/.test(trimmed)) return true;
|
|
5893
6717
|
return /^v?\d/.test(trimmed);
|
|
5894
6718
|
}
|
|
5895
|
-
function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
5896
|
-
const
|
|
6719
|
+
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
6720
|
+
const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
|
|
6721
|
+
const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
|
|
6722
|
+
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
6723
|
+
);
|
|
6724
|
+
const claimDoc = useV27Detectors ? claimSourceFiles[0] ?? reconciliationArtifacts[0] : repoFiles.find((file) => /^(?:README|readme)\.md$/.test(file)) ?? repoFiles.find((file) => /^docs\/[^/]+\.(?:md|mdx)$/.test(file)) ?? repoFiles.find((file) => /(^|\/)claim[_-]reality[_-]reconciliation\.md$/i.test(file));
|
|
5897
6725
|
if (!claimDoc) {
|
|
5898
6726
|
return buildNotApplicableSignal(
|
|
5899
6727
|
"A5",
|
|
5900
6728
|
"No README or docs found \u2014 nothing is claimed about this repo, so there is nothing for A5 to reconcile against."
|
|
5901
6729
|
);
|
|
5902
6730
|
}
|
|
5903
|
-
const implementationFiles = repoFiles
|
|
6731
|
+
const implementationFiles = findClaimImplementationFiles(repoFiles, useV27Detectors);
|
|
5904
6732
|
if (implementationFiles.length === 0) return null;
|
|
5905
6733
|
const implementationFile = implementationFiles[0];
|
|
5906
6734
|
if (!implementationFile) return null;
|
|
@@ -5913,10 +6741,21 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5913
6741
|
"Code presence for claim reconciliation"
|
|
5914
6742
|
)
|
|
5915
6743
|
];
|
|
6744
|
+
for (const artifact of reconciliationArtifacts.slice(0, 3)) {
|
|
6745
|
+
if (artifact === claimDoc) continue;
|
|
6746
|
+
evidence.push(
|
|
6747
|
+
evidenceForRelative(
|
|
6748
|
+
repoPath,
|
|
6749
|
+
artifact,
|
|
6750
|
+
"claim_reconciliation",
|
|
6751
|
+
"Dedicated claim-reality reconciliation artifact"
|
|
6752
|
+
)
|
|
6753
|
+
);
|
|
6754
|
+
}
|
|
5916
6755
|
const firstEvidence = evidence[0];
|
|
5917
6756
|
if (!firstEvidence) return null;
|
|
5918
|
-
const allDocFiles = repoFiles.filter(
|
|
5919
|
-
(file) => /^README\.md$/.test(file) || /^docs\/[^/]+\.(md|mdx)$/.test(file)
|
|
6757
|
+
const allDocFiles = useV27Detectors ? claimSourceFiles : repoFiles.filter(
|
|
6758
|
+
(file) => /^README\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
5920
6759
|
);
|
|
5921
6760
|
const boundedImplCount = Math.min(implementationFiles.length, 12);
|
|
5922
6761
|
const boundedDocCount = Math.min(allDocFiles.length, 8);
|
|
@@ -5941,7 +6780,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5941
6780
|
return {
|
|
5942
6781
|
criterionId: "A5",
|
|
5943
6782
|
positiveEvidence: evidence,
|
|
5944
|
-
findings: [
|
|
6783
|
+
findings: reconciliationArtifacts.length > 0 ? [] : [
|
|
5945
6784
|
{
|
|
5946
6785
|
// Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
|
|
5947
6786
|
// than a curated registry or reconciliation artifact. Downgraded to info when the repo
|
|
@@ -5953,12 +6792,11 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5953
6792
|
}
|
|
5954
6793
|
],
|
|
5955
6794
|
// Recalibrated (goal_cejel_launch_hardening_combined_2026-07-06, Phase 3 H1):
|
|
5956
|
-
// reconciliation_artifact_depth is
|
|
5957
|
-
//
|
|
5958
|
-
//
|
|
5959
|
-
//
|
|
5960
|
-
//
|
|
5961
|
-
// something a well-run external repo can actually earn; claim_source_depth's
|
|
6795
|
+
// reconciliation_artifact_depth is usually 0 on the generic-proxy path, so its prior 0.3
|
|
6796
|
+
// weight was a floor drag on external repos regardless of how well they otherwise document
|
|
6797
|
+
// and back claims. Weight remains 0.15 (still a real ding when a content-authenticated,
|
|
6798
|
+
// generically named reconciliation artifact is absent) and is redistributed to the two
|
|
6799
|
+
// metrics that every well-run external repo can earn; claim_source_depth's
|
|
5962
6800
|
// denominator lowered from 8 to 4 (README + a couple of top-level docs is already a
|
|
5963
6801
|
// reasonably documented repo, not a fraction of an inflated 8-file bar).
|
|
5964
6802
|
metrics: [
|
|
@@ -5984,7 +6822,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5984
6822
|
metric(
|
|
5985
6823
|
"reconciliation_artifact_depth",
|
|
5986
6824
|
"Reconciliation artifact depth",
|
|
5987
|
-
|
|
6825
|
+
reconciliationArtifacts.length,
|
|
5988
6826
|
3,
|
|
5989
6827
|
0.15,
|
|
5990
6828
|
"artifacts",
|
|
@@ -6167,7 +7005,7 @@ function collectB4AuditEvidence(repoPath, repoFiles, generatedAt) {
|
|
|
6167
7005
|
]
|
|
6168
7006
|
};
|
|
6169
7007
|
}
|
|
6170
|
-
function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
7008
|
+
function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors = false) {
|
|
6171
7009
|
const evidence = [];
|
|
6172
7010
|
const findings = [];
|
|
6173
7011
|
const docFiles = repoFiles.filter((file) => /\.(md|mdx)$/i.test(file));
|
|
@@ -6190,12 +7028,13 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
6190
7028
|
(file) => fileContains(repoPath, file, SQL_EXEC_PATTERN)
|
|
6191
7029
|
);
|
|
6192
7030
|
const ungatedEscalationFiles = executableFiles.filter((file) => {
|
|
7031
|
+
if (useV39Detectors && !fileHasExecutedPrivilegeEscalation(repoPath, file)) return false;
|
|
6193
7032
|
const hasEscalation = fileContains(repoPath, file, ROLE_MEMBERSHIP_GRANT_PATTERN) || fileContains(repoPath, file, SUPERUSER_ESCALATION_PATTERN);
|
|
6194
7033
|
if (!hasEscalation) return false;
|
|
6195
7034
|
return !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN);
|
|
6196
7035
|
});
|
|
6197
7036
|
const productionUngatedEscalationFiles = ungatedEscalationFiles.filter(
|
|
6198
|
-
(file) => !isTestOrFixturePath(file)
|
|
7037
|
+
(file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
|
|
6199
7038
|
);
|
|
6200
7039
|
if (humanGateDoc) {
|
|
6201
7040
|
evidence.push(
|
|
@@ -6237,11 +7076,10 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
6237
7076
|
)
|
|
6238
7077
|
);
|
|
6239
7078
|
}
|
|
6240
|
-
for (const file of
|
|
6241
|
-
const isTestPath = isTestOrFixturePath(file);
|
|
7079
|
+
for (const file of productionUngatedEscalationFiles.slice(0, 5)) {
|
|
6242
7080
|
findings.push({
|
|
6243
|
-
severity:
|
|
6244
|
-
summary:
|
|
7081
|
+
severity: "critical",
|
|
7082
|
+
summary: "Role-membership GRANT or SUPERUSER escalation executes in code with no documented human gate.",
|
|
6245
7083
|
evidence: evidenceForRelative(
|
|
6246
7084
|
repoPath,
|
|
6247
7085
|
file,
|
|
@@ -6300,7 +7138,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
6300
7138
|
1,
|
|
6301
7139
|
hasPrivilegedOpsSurface ? 0.3 : 0.4,
|
|
6302
7140
|
"clean",
|
|
6303
|
-
"Penalizes code that executes a role-membership GRANT or SUPERUSER escalation with no documented human gate (test/fixture
|
|
7141
|
+
"Penalizes code that executes a role-membership GRANT or SUPERUSER escalation with no documented human gate (test/fixture SQL is excluded from this production-code measurement)."
|
|
6304
7142
|
),
|
|
6305
7143
|
metric(
|
|
6306
7144
|
"protected_path_review_gate",
|
|
@@ -6320,41 +7158,70 @@ var CPP_TEST_FRAMEWORK_PATTERN = /<gtest\/gtest\.h>|<catch2\/|<doctest\/|<boost\
|
|
|
6320
7158
|
var CPP_TEST_MACRO_PATTERN = /\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(|\bCATCH_|BOOST_AUTO_TEST_CASE\b|BOOST_FIXTURE_TEST_CASE\b/;
|
|
6321
7159
|
var CPP_CONTENT_SCAN_LIMIT = 150;
|
|
6322
7160
|
var NON_PRODUCTION_DIR_PATTERN = /(^|\/)(__tests__|__mocks__|__fixtures__|tests?|fixtures?|testdata|e2e|cypress|examples?|samples?|demos?)(\/|$)/i;
|
|
6323
|
-
var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]
|
|
7161
|
+
var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]+$|_test\.go$|(^|\/)test_.*\.py$/i;
|
|
6324
7162
|
function isTestOrFixturePath(path) {
|
|
6325
7163
|
return NON_PRODUCTION_DIR_PATTERN.test(path) || NON_PRODUCTION_FILE_SUFFIX_PATTERN.test(path);
|
|
6326
7164
|
}
|
|
6327
7165
|
function isProductionSourcePath(path) {
|
|
6328
7166
|
return !isTestOrFixturePath(path);
|
|
6329
7167
|
}
|
|
6330
|
-
function
|
|
6331
|
-
return
|
|
6332
|
-
|
|
6333
|
-
|
|
7168
|
+
function isAuthoredProductionPath(path) {
|
|
7169
|
+
return isProductionSourcePath(path) && !GENERATED_OR_VENDOR_PATH_PATTERN.test(path);
|
|
7170
|
+
}
|
|
7171
|
+
var V39_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN = /(^|\/)(?:docs?|documentation|e2e[-_]?tests?)(\/|$)|(^|\/)(?:test|tests)[._-][^/]+$|(^|\/)[^/]*(?:appium|e2e[-_]?tests?)[^/]*$/i;
|
|
7172
|
+
function isV39AuthoredProductionPath(path) {
|
|
7173
|
+
return isAuthoredProductionPath(path) && !V39_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN.test(path);
|
|
7174
|
+
}
|
|
7175
|
+
var V47_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN = /(^|\/)(?:auto[-_]?gen(?:erated)?|generated|i18n|locales?|cookbooks?|lessons?)(\/|$)|\.(?:md|mdx|rst|adoc)$/i;
|
|
7176
|
+
function isV47AuthoredProductionPath(path) {
|
|
7177
|
+
return isV39AuthoredProductionPath(path) && !V47_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN.test(path);
|
|
7178
|
+
}
|
|
7179
|
+
var TEST_DIRECTORY_SOURCE_PATTERN = new RegExp(
|
|
7180
|
+
`(^|/)(?:__tests__|tests?|specs?|[^/]+(?:Tests|Specs))/(?:.*\\.)?(?:${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`
|
|
7181
|
+
);
|
|
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.
|
|
6334
7191
|
// Keep this root-anchored: nested files need an existing test-directory/suffix convention.
|
|
6335
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);
|
|
6336
7193
|
}
|
|
6337
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)$/;
|
|
6338
|
-
var
|
|
6339
|
-
var
|
|
7195
|
+
var NAME_SHAPED_TEST_FILE_PATTERN_V17 = /\.tftest\.hcl$|\.bats$|(^|\/)test[^/]*\.m$|_test\.m$/i;
|
|
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;
|
|
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;
|
|
6340
7198
|
var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
|
|
6341
7199
|
var CI_TYPECHECK_COMMAND_PATTERN = /\b(tsc|mypy|pyright)\b/i;
|
|
6342
7200
|
var SCHEDULE_TRIGGER_PATTERN = /(^|\n)\s*schedule:\s*\r?\n\s*-\s*cron:/;
|
|
6343
7201
|
var PUBLISHED_RESULT_MARKER_PATTERN = /actions\/deploy-pages|actions\/upload-pages-artifact|peaceiris\/actions-gh-pages|git\s+push\b|create-or-update-comment/i;
|
|
6344
7202
|
var EPHEMERAL_ARTIFACT_ONLY_PATTERN = /actions\/upload-artifact/i;
|
|
7203
|
+
var SCHEDULED_HEALTH_PATH_PATTERN = /(?:health|smoke|monitor|status|uptime|canary)/i;
|
|
7204
|
+
var SCHEDULED_HEALTH_INTENT_PATTERN = /\b(?:product[- ]?health|health check|smoke test|monitor(?:ing)?|uptime|canary|fault detection)\b/i;
|
|
6345
7205
|
var RLS_PATTERN = /row level security|enable rls|create policy|tenant[_-]?id|studio[_-]?id/i;
|
|
6346
7206
|
var DB_CLIENT_PATTERN = /\b(pg|mysql|mysql2|sqlite3|mongodb|mongoose|sequelize|drizzle|prisma|knex|typeorm|redis|ioredis|better-sqlite3|asyncpg|psycopg2?|sqlalchemy|pymongo|aiosqlite|database\/sql|gorm|sqlx|diesel|rusqlite|ActiveRecord|Sequel|PDO|JDBC|Hibernate)\b/i;
|
|
6347
7207
|
var ENV_READ_PATTERN = /process\.env\b|Deno\.env\b|os\.environ\b|getenv\s*\(|std::env::|ENV\[/;
|
|
6348
7208
|
var TENANT_SCOPE_PATTERN = /tenantId|tenant_id|studioId|studio_id|organizationId|org_id|workspaceId/i;
|
|
7209
|
+
var TENANT_SCOPE_PATTERN_V10 = /tenantId|tenant_id|studioId|studio_id|organizationId|organization_id|org_id|accountTenantId|account_tenant_id/i;
|
|
7210
|
+
var TENANT_SCOPE_PATTERN_V11 = /tenantId|tenant_id|accountTenantId|account_tenant_id/i;
|
|
6349
7211
|
var HUMAN_GATE_MARKER_PATTERN = /human[- ]executed|human[- ]gated|operator[- ]run|never agent-run|agents? (?:never|does not|do not) (?:hold|execute|run) (?:prod(?:uction)?\s+)?(?:admin|db) (?:credential|privilege)/i;
|
|
6350
7212
|
var ROLE_MEMBERSHIP_GRANT_PATTERN = /\bGRANT\s+(?!SELECT\b|INSERT\b|UPDATE\b|DELETE\b|USAGE\b|ALL\b|EXECUTE\b|TRIGGER\b|REFERENCES\b|CREATE\b|CONNECT\b|TEMP(?:ORARY)?\b)[A-Za-z_]\w*\s+TO\b/i;
|
|
6351
7213
|
var SUPERUSER_ESCALATION_PATTERN = /\b(ALTER|CREATE)\s+(ROLE|USER)\s+\w+[^;]*\bSUPERUSER\b/i;
|
|
6352
7214
|
var SQL_EXEC_PATTERN = /\.execute\s*\(|sql\.raw\s*\(/;
|
|
7215
|
+
var EXECUTED_ESCALATION_LITERAL_PATTERN = /(?:\.execute|sql\.raw)\s*\(\s*(?:[rubf]+)?["'`][^"'`]{0,500}(?:GRANT\s+(?!SELECT\b|INSERT\b|UPDATE\b|DELETE\b|USAGE\b|ALL\b|EXECUTE\b|TRIGGER\b|REFERENCES\b|CREATE\b|CONNECT\b|TEMP(?:ORARY)?\b)[A-Za-z_]\w*\s+TO\b|(?:ALTER|CREATE)\s+(?:ROLE|USER)\s+\w+[^;]*\bSUPERUSER\b)/i;
|
|
7216
|
+
function fileHasExecutedPrivilegeEscalation(repoPath, file) {
|
|
7217
|
+
return fileContains(repoPath, file, EXECUTED_ESCALATION_LITERAL_PATTERN);
|
|
7218
|
+
}
|
|
6353
7219
|
var GATED_PRIVILEGE_CHECK_PATTERN = /pg_has_role\s*\(|has_role\s*\(|is not a member of/i;
|
|
6354
7220
|
var SET_ROLE_PATTERN = /\bset\s+(local\s+)?role\b/i;
|
|
6355
7221
|
var REQUIRED_REVIEW_PATTERN = /required review|branch protection|protected branch/i;
|
|
6356
7222
|
var HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT = 5e3;
|
|
6357
|
-
var
|
|
7223
|
+
var SECRET_IDENTIFIER_KEYWORD_PATTERN = /secret|token|api[_-]?key|password|access[_-]?key/gi;
|
|
7224
|
+
var SINGLE_WHITESPACE_PATTERN = /\s/u;
|
|
6358
7225
|
var SECRET_VALUE_BRANDED_PATTERN = /^(?:sk-[A-Za-z0-9_-]{24,}|sk-ant-api\d{2}-[A-Za-z0-9_-]{80,}|ghp_[A-Za-z0-9_]{30,}|xox[baprs]-[A-Za-z0-9-]{24,}|AKIA[0-9A-Z]{16}|vcp_[A-Za-z0-9_:-]{24,})$/;
|
|
6359
7226
|
var GENERIC_SECRET_SHAPE_PATTERN = /^[A-Za-z0-9/+_-]{40,}$/;
|
|
6360
7227
|
var KNOWN_HEX_DIGEST_LENGTHS = /* @__PURE__ */ new Set([32, 40, 64, 128]);
|
|
@@ -6363,23 +7230,25 @@ var NEGATIVE_SPACE_SECTION_PATTERN = /^#{1,6}\s*(?:security\s+)?(?:limitations?|
|
|
|
6363
7230
|
var NEGATIVE_SPACE_PHRASE_PATTERN = /does not (?:protect|defend|guard|cover)s? against|not (?:covered|protected|defended) by this|out of scope for this (?:library|package|tool|sdk|project)/i;
|
|
6364
7231
|
var LEAN_TEST_RUNNER_SCRIPT_PATTERN = /\bnode\s+(?:--test\b|--experimental-test-coverage\b)/;
|
|
6365
7232
|
var LEAN_TEST_RUNNER_IMPORT_PATTERN = /from\s+['"]node:test['"]|require\(\s*['"]node:test['"]\s*\)/;
|
|
6366
|
-
var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:jest|mocha|ava|tape|jasmine|karma)(?:$|-|\/)/i;
|
|
7233
|
+
var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:(?:jest|mocha|ava|tape|jasmine|karma|vitest|cypress|playwright)(?:$|-|\/)|@(?:vitest|playwright)\/)/i;
|
|
6367
7234
|
var TIMING_SAFE_COMPARE_PATTERN = /\btiming[-_]?safe[-_]?(?:equal|compare)\b|crypto\.timingSafeEqual\s*\(/i;
|
|
6368
7235
|
var CANONICAL_SERIALIZE_PATTERN = /\bcanonicalize\s*\(|\bcanonicalJson\b|\bstableStringify\s*\(|json-stable-stringify|safe-stable-stringify|\bsortKeys\s*\(/i;
|
|
6369
7236
|
var SIGN_OR_HMAC_CALL_PATTERN = /\bcreateHmac\s*\(|\bcreateSign\s*\(|\.sign\s*\(/;
|
|
6370
7237
|
var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac|digest)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
|
|
7238
|
+
var DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN = /\b(?:secret|token|password|apiKey)\w*\s*(?:===|!==|==|!=)\s*[\w.]*default(?:Secret|Token|Password|ApiKey)\b/i;
|
|
6371
7239
|
var NUMERIC_SHAPED_OPERAND_PATTERN = /^-?\d+$|\.(?:length|len|size|count)\b|(?:length|index|idx|offset|count|size|len)$/i;
|
|
6372
|
-
function lineHasInsecureSecretCompare(line) {
|
|
7240
|
+
function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
7241
|
+
if (useV39Detectors && DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN.test(line)) return true;
|
|
6373
7242
|
const match = INSECURE_SECRET_COMPARE_LINE_PATTERN.exec(line);
|
|
6374
7243
|
const operand = match?.[2];
|
|
6375
7244
|
if (!operand) return false;
|
|
6376
7245
|
return !NUMERIC_SHAPED_OPERAND_PATTERN.test(operand.trim());
|
|
6377
7246
|
}
|
|
6378
|
-
function findInsecureSecretCompareLine(repoPath, file) {
|
|
7247
|
+
function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
|
|
6379
7248
|
const fullPath = join(repoPath, file);
|
|
6380
7249
|
if (!isRegularFile(fullPath)) return null;
|
|
6381
7250
|
const lines = readFileSync(fullPath, "utf8").split("\n");
|
|
6382
|
-
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line));
|
|
7251
|
+
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
|
|
6383
7252
|
return index === -1 ? null : index + 1;
|
|
6384
7253
|
}
|
|
6385
7254
|
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
@@ -6389,7 +7258,49 @@ function findFirstMatchingLine(repoPath, file, pattern) {
|
|
|
6389
7258
|
const index = lines.findIndex((line) => pattern.test(line));
|
|
6390
7259
|
return index === -1 ? null : index + 1;
|
|
6391
7260
|
}
|
|
7261
|
+
function lineAt(repoPath, file, line) {
|
|
7262
|
+
if (line < 1) return null;
|
|
7263
|
+
const fullPath = join(repoPath, file);
|
|
7264
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7265
|
+
return readFileSync(fullPath, "utf8").split("\n")[line - 1] ?? null;
|
|
7266
|
+
}
|
|
6392
7267
|
var UNSORTED_JSON_SIGN_PATTERN = /\.update\s*\(\s*JSON\.stringify\s*\(|\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7268
|
+
var UNSORTED_JSON_DIRECT_SIGN_PATTERN = /\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7269
|
+
var UNSORTED_JSON_CONSTRUCTOR_UPDATE_PATTERN = /\bcreate(?:Hmac|Sign)\s*\([^;\n]{0,256}\)\s*\.update\s*\(\s*JSON\.stringify\s*\(/;
|
|
7270
|
+
var JSON_STRINGIFY_ASSIGNMENT_PATTERN = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*JSON\.stringify\s*\(/g;
|
|
7271
|
+
function findUnsortedJsonHmacLine(repoPath, file) {
|
|
7272
|
+
const fullPath = join(repoPath, file);
|
|
7273
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7274
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
7275
|
+
const directSign = findFirstMatchingLine(repoPath, file, UNSORTED_JSON_DIRECT_SIGN_PATTERN);
|
|
7276
|
+
if (directSign !== null) return directSign;
|
|
7277
|
+
const directConstructorUpdate = findFirstMatchingLine(
|
|
7278
|
+
repoPath,
|
|
7279
|
+
file,
|
|
7280
|
+
UNSORTED_JSON_CONSTRUCTOR_UPDATE_PATTERN
|
|
7281
|
+
);
|
|
7282
|
+
if (directConstructorUpdate !== null) return directConstructorUpdate;
|
|
7283
|
+
JSON_STRINGIFY_ASSIGNMENT_PATTERN.lastIndex = 0;
|
|
7284
|
+
let match = JSON_STRINGIFY_ASSIGNMENT_PATTERN.exec(contents);
|
|
7285
|
+
while (match !== null) {
|
|
7286
|
+
const identifier = match[1];
|
|
7287
|
+
if (identifier) {
|
|
7288
|
+
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7289
|
+
const constructorUpdate = new RegExp(
|
|
7290
|
+
`\\bcreate(?:Hmac|Sign)\\s*\\([^;\\n]{0,256}\\)\\s*\\.update\\s*\\(\\s*${escaped}\\b`
|
|
7291
|
+
);
|
|
7292
|
+
const usageWindow = contents.slice(match.index, match.index + 2048);
|
|
7293
|
+
if (constructorUpdate.test(usageWindow)) {
|
|
7294
|
+
return contents.slice(0, match.index).split("\n").length;
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7297
|
+
match = JSON_STRINGIFY_ASSIGNMENT_PATTERN.exec(contents);
|
|
7298
|
+
}
|
|
7299
|
+
return null;
|
|
7300
|
+
}
|
|
7301
|
+
function fileHasVariableUnsortedJsonHmac(repoPath, file) {
|
|
7302
|
+
return findUnsortedJsonHmacLine(repoPath, file) !== null;
|
|
7303
|
+
}
|
|
6393
7304
|
var KILL_SWITCH_NAME_FRAGMENT = "kill[_-]?switch|emergency[_-]?stop|circuit[_-]?breaker|safety[_-]?(?:toggle|gate|switch)|governance[_-]?(?:toggle|gate)";
|
|
6394
7305
|
var KILL_SWITCH_NAME_PATTERN = new RegExp(`\\b(?:${KILL_SWITCH_NAME_FRAGMENT})\\b`, "i");
|
|
6395
7306
|
var KILL_SWITCH_FAIL_CLOSED_PATTERN = new RegExp(
|
|
@@ -6434,19 +7345,23 @@ function looksLikeSecretValue(value) {
|
|
|
6434
7345
|
return SECRET_VALUE_BRANDED_PATTERN.test(value) || isGenericSecretValue(value);
|
|
6435
7346
|
}
|
|
6436
7347
|
var PLACEHOLDER_SECRET_PATTERN = /(?:^|[-_])(?:your|example|sample|placeholder|dummy|changeme|replace(?:-?me)?|redacted)(?:$|[-_])|^x{3,}$|^\.\.\.|^\$\{[^}]+}$|^<[^>]+>$/i;
|
|
6437
|
-
var ENV_TEMPLATE_PATTERN = /(^|\/)\.env
|
|
6438
|
-
|
|
6439
|
-
|
|
7348
|
+
var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist)$/i;
|
|
7349
|
+
var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist|exemplo|ejemplo|exemple|esempio|beispiel|voorbeeld)$/i;
|
|
7350
|
+
function isEnvTemplatePath(path, useV47Detectors = false) {
|
|
7351
|
+
return (useV47Detectors ? ENV_TEMPLATE_PATTERN_V47 : ENV_TEMPLATE_PATTERN).test(path);
|
|
6440
7352
|
}
|
|
6441
7353
|
var HARD_EXCLUDED_PATH_PATTERN = /(^|\/)(?:\.git|\.venv|venv|site-packages|node_modules|dist|build|\.next|__pycache__|vendor|\.terraform|coverage)(?:\/|$)/;
|
|
6442
7354
|
var COVERAGE_PERCENT_PATTERN = /(?:statements|branches|lines|functions|fail_under)\s*[:=]\s*["']?(\d+(?:\.\d+)?)/gi;
|
|
6443
7355
|
function listRepoFiles(repoPath) {
|
|
6444
|
-
const gitFiles = listGitTrackedFiles(repoPath);
|
|
7356
|
+
const gitFiles = listGitTrackedFiles(repoPath, false);
|
|
6445
7357
|
if (gitFiles) return gitFiles;
|
|
6446
7358
|
const files = [];
|
|
6447
7359
|
visitRepoDir(repoPath, repoPath, files);
|
|
6448
7360
|
return files.filter((file) => !isHardExcludedPath(file)).sort();
|
|
6449
7361
|
}
|
|
7362
|
+
function listRepoInventory(repoPath, fallbackFiles) {
|
|
7363
|
+
return listGitTrackedFiles(repoPath, true) ?? [...fallbackFiles];
|
|
7364
|
+
}
|
|
6450
7365
|
function resolveMonorepoContext(repoPath) {
|
|
6451
7366
|
let top;
|
|
6452
7367
|
try {
|
|
@@ -6479,7 +7394,7 @@ function resolveMonorepoContext(repoPath) {
|
|
|
6479
7394
|
if (sharedFiles.length === 0) return null;
|
|
6480
7395
|
return { root: rootResolved, sharedFiles };
|
|
6481
7396
|
}
|
|
6482
|
-
function listGitTrackedFiles(repoPath) {
|
|
7397
|
+
function listGitTrackedFiles(repoPath, includeHardExcluded = false) {
|
|
6483
7398
|
try {
|
|
6484
7399
|
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
6485
7400
|
cwd: repoPath,
|
|
@@ -6493,7 +7408,7 @@ function listGitTrackedFiles(repoPath) {
|
|
|
6493
7408
|
}).trim();
|
|
6494
7409
|
if (!output) return [];
|
|
6495
7410
|
return output.split("\n").filter(
|
|
6496
|
-
(file) => file.length > 0 && !isHardExcludedPath(file) && isRegularFile(join(repoPath, file))
|
|
7411
|
+
(file) => file.length > 0 && (includeHardExcluded || !isHardExcludedPath(file)) && isRegularFile(join(repoPath, file))
|
|
6497
7412
|
).sort();
|
|
6498
7413
|
} catch {
|
|
6499
7414
|
return null;
|
|
@@ -6563,7 +7478,7 @@ var JS_TEST_ASSERTION_PATTERN = /(expect\(|assert\.|should\.|toEqual\(|toBe\(|as
|
|
|
6563
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*\(/;
|
|
6564
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*\(/;
|
|
6565
7480
|
var SUITE_LEVEL_SKIP_PATTERN = /\b(?:describe|suite|context)\.skip\s*\(/;
|
|
6566
|
-
function measureNonHollowTestShare(repoPath, files) {
|
|
7481
|
+
function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
6567
7482
|
let nonHollowCount = 0;
|
|
6568
7483
|
let ratedCount = 0;
|
|
6569
7484
|
for (const file of files) {
|
|
@@ -6574,7 +7489,7 @@ function measureNonHollowTestShare(repoPath, files) {
|
|
|
6574
7489
|
if (nonHollow) {
|
|
6575
7490
|
nonHollowCount += 1;
|
|
6576
7491
|
ratedCount += 1;
|
|
6577
|
-
} 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)) {
|
|
6578
7493
|
ratedCount += 1;
|
|
6579
7494
|
}
|
|
6580
7495
|
}
|
|
@@ -6898,43 +7813,639 @@ function parseJsonObject(contents) {
|
|
|
6898
7813
|
function isRecord(value) {
|
|
6899
7814
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6900
7815
|
}
|
|
6901
|
-
function isTestRunnerConfig(file) {
|
|
6902
|
-
return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini
|
|
7816
|
+
function isTestRunnerConfig(repoPath, file) {
|
|
7817
|
+
return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini)$/.test(file) || /(^|\/)phpunit\.xml(\.dist)?$/.test(file) || /(^|\/)pyproject\.toml$/.test(file) && fileContains(repoPath, file, /^\[tool\.pytest(?:\.|\])/m) || /(^|\/)CMakeLists\.txt$/.test(file) && fileContains(repoPath, file, /\b(?:enable_testing|add_test|ctest_add_tests)\s*\(/i) || /(^|\/)Makefile$/.test(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
|
|
7818
|
+
}
|
|
7819
|
+
function findCoverageConfigFiles(repoPath, repoFiles, authoredOnly = false) {
|
|
7820
|
+
return repoFiles.filter(
|
|
7821
|
+
(file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCoverageConfig(repoPath, file)
|
|
7822
|
+
);
|
|
7823
|
+
}
|
|
7824
|
+
function findConcreteTestFiles(repoPath, repoFiles, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
7825
|
+
const contentCppTests = collectContentBasedCppTestFiles(repoPath, repoFiles);
|
|
7826
|
+
return [
|
|
7827
|
+
...repoFiles.filter((file) => isTestFile(file, rubricVersion)),
|
|
7828
|
+
...contentCppTests.filter((file) => !isTestFile(file, rubricVersion))
|
|
7829
|
+
];
|
|
7830
|
+
}
|
|
7831
|
+
function findConfiguredTestRunnerFiles(repoPath, repoFiles) {
|
|
7832
|
+
const configured = repoFiles.filter(
|
|
7833
|
+
(file) => isAuthoredProductionPath(file) && isTestRunnerConfig(repoPath, file)
|
|
7834
|
+
);
|
|
7835
|
+
for (const packageJson of repoFiles.filter(
|
|
7836
|
+
(file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
7837
|
+
)) {
|
|
7838
|
+
if ([...readPackageScripts(join(repoPath, packageJson)).values()].some(
|
|
7839
|
+
(script) => TEST_RUNNER_PATTERN.test(script)
|
|
7840
|
+
)) {
|
|
7841
|
+
configured.push(packageJson);
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
configured.push(
|
|
7845
|
+
...repoFiles.filter((file) => isAuthoredProductionPath(file) && isCiWorkflow(file)).filter((file) => fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN))
|
|
7846
|
+
);
|
|
7847
|
+
return [...new Set(configured)].sort();
|
|
7848
|
+
}
|
|
7849
|
+
function findTenantStoragePremiseFiles(repoPath, repoFiles) {
|
|
7850
|
+
return repoFiles.filter((file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)).filter((file) => fileContains(repoPath, file, TENANT_SCOPE_PATTERN_V11));
|
|
7851
|
+
}
|
|
7852
|
+
function findRlsPolicyFiles(repoPath, repoFiles) {
|
|
7853
|
+
return repoFiles.filter((file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)).filter(
|
|
7854
|
+
(file) => fileContains(
|
|
7855
|
+
repoPath,
|
|
7856
|
+
file,
|
|
7857
|
+
/\b(?:create\s+policy|enable\s+row\s+level\s+security|force\s+row\s+level\s+security)\b/i
|
|
7858
|
+
)
|
|
7859
|
+
);
|
|
7860
|
+
}
|
|
7861
|
+
function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
|
|
7862
|
+
const explicitTargets = repoFiles.filter(
|
|
7863
|
+
(file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
|
|
7864
|
+
);
|
|
7865
|
+
const deployWorkflows = repoFiles.filter((file) => isAuthoredProductionPath(file) && isCiWorkflow(file)).filter((file) => fileContains(repoPath, file, CI_DEPLOY_JOB_PATTERN)).filter(
|
|
7866
|
+
(file) => !strictOperationalBoundary || !isDocumentationOnlyDeployWorkflow(repoPath, file)
|
|
7867
|
+
);
|
|
7868
|
+
const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles, true);
|
|
7869
|
+
const runtimeContainer = findRuntimeContainerEntrypointFile(repoPath, repoFiles);
|
|
7870
|
+
if (strictOperationalBoundary) {
|
|
7871
|
+
const runtimeFiles = [serverEntrypoint, runtimeContainer].filter(
|
|
7872
|
+
(file) => file != null && isV47OperationalApplicationPath(file)
|
|
7873
|
+
);
|
|
7874
|
+
const directApplicationTargets = explicitTargets.filter(
|
|
7875
|
+
(file) => !isInfrastructureOnlyDeployTarget(file)
|
|
7876
|
+
);
|
|
7877
|
+
const infrastructureTargets = runtimeFiles.length > 0 ? [...explicitTargets.filter(isInfrastructureOnlyDeployTarget), ...deployWorkflows] : [];
|
|
7878
|
+
return [...directApplicationTargets, ...runtimeFiles, ...infrastructureTargets];
|
|
7879
|
+
}
|
|
7880
|
+
return [
|
|
7881
|
+
...explicitTargets,
|
|
7882
|
+
...deployWorkflows,
|
|
7883
|
+
...serverEntrypoint && (!strictOperationalBoundary || isV47OperationalApplicationPath(serverEntrypoint)) ? [serverEntrypoint] : [],
|
|
7884
|
+
...runtimeContainer && (!strictOperationalBoundary || isV47OperationalApplicationPath(runtimeContainer)) ? [runtimeContainer] : []
|
|
7885
|
+
];
|
|
7886
|
+
}
|
|
7887
|
+
function isInfrastructureOnlyDeployTarget(file) {
|
|
7888
|
+
return /(^|\/)docker-compose\.ya?ml$/i.test(file) || /(^|\/)(?:k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /(^|\/)(?:charts?|helm)\/.+\.ya?ml$/i.test(file);
|
|
7889
|
+
}
|
|
7890
|
+
var V47_NON_OPERATIONAL_APPLICATION_PATH_PATTERN = /(^|\/)(?:cookbooks?|lessons?|tutorials?|docs?|documentation|examples?|samples?|demos?)(\/|$)/i;
|
|
7891
|
+
function isV47OperationalApplicationPath(path) {
|
|
7892
|
+
return !V47_NON_OPERATIONAL_APPLICATION_PATH_PATTERN.test(path);
|
|
7893
|
+
}
|
|
7894
|
+
function isDocumentationOnlyDeployWorkflow(repoPath, file) {
|
|
7895
|
+
if (/(?:docs?|pages?)/i.test(basename(file))) return true;
|
|
7896
|
+
return fileContains(
|
|
7897
|
+
repoPath,
|
|
7898
|
+
file,
|
|
7899
|
+
/\b(?:deploy[-_ ]?docs?|mkdocs|docusaurus|vitepress|gh-pages|github pages)\b/i
|
|
7900
|
+
);
|
|
7901
|
+
}
|
|
7902
|
+
function findPackagedApplicationPremiseFiles(repoPath, repoFiles) {
|
|
7903
|
+
return repoFiles.filter((file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)).filter((file) => packageJsonDescribesPackagedApplication(repoPath, file, repoFiles));
|
|
7904
|
+
}
|
|
7905
|
+
function findClaimSourceFiles(repoFiles) {
|
|
7906
|
+
return repoFiles.filter(
|
|
7907
|
+
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
7908
|
+
).sort((left, right) => {
|
|
7909
|
+
const leftRootReadme = /^(?:README|readme)\.md$/.test(left);
|
|
7910
|
+
const rightRootReadme = /^(?:README|readme)\.md$/.test(right);
|
|
7911
|
+
return Number(rightRootReadme) - Number(leftRootReadme) || left.localeCompare(right);
|
|
7912
|
+
});
|
|
7913
|
+
}
|
|
7914
|
+
function findClaimImplementationFiles(repoFiles, authoredOnly = false) {
|
|
7915
|
+
return repoFiles.filter(
|
|
7916
|
+
(file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file))
|
|
7917
|
+
);
|
|
7918
|
+
}
|
|
7919
|
+
function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
|
|
7920
|
+
return repoFiles.filter((file) => /(^|\/)claim[-_]reality[-_]reconciliation\.(?:md|mdx|json)$/i.test(file)).filter((file) => isAuthenticatedClaimRealityArtifact(repoPath, file));
|
|
7921
|
+
}
|
|
7922
|
+
function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
7923
|
+
const fullPath = join(repoPath, file);
|
|
7924
|
+
if (!isRegularFile(fullPath)) return false;
|
|
7925
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
7926
|
+
if (/\.json$/i.test(file)) {
|
|
7927
|
+
return parseJsonObject(contents)?.artifactKind === "claim_reality_reconciliation";
|
|
7928
|
+
}
|
|
7929
|
+
let inFence = false;
|
|
7930
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
7931
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
7932
|
+
inFence = !inFence;
|
|
7933
|
+
continue;
|
|
7934
|
+
}
|
|
7935
|
+
if (!inFence && /^#\s+Claim Reality Reconciliation\s*$/i.test(line)) return true;
|
|
7936
|
+
}
|
|
7937
|
+
return false;
|
|
7938
|
+
}
|
|
7939
|
+
function isCoverageConfig(repoPath, file) {
|
|
7940
|
+
if (/(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file)) {
|
|
7941
|
+
return true;
|
|
7942
|
+
}
|
|
7943
|
+
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7944
|
+
return fileContains(repoPath, file, /\bcoverage\s*:/i);
|
|
7945
|
+
}
|
|
7946
|
+
if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7947
|
+
return fileContains(
|
|
7948
|
+
repoPath,
|
|
7949
|
+
file,
|
|
7950
|
+
/\b(?:collectCoverage|coverageThreshold|coverageProvider|coverageDirectory)\s*:/
|
|
7951
|
+
);
|
|
7952
|
+
}
|
|
7953
|
+
if (/(^|\/)pyproject\.toml$/.test(file)) {
|
|
7954
|
+
return fileContains(repoPath, file, /^\[tool\.coverage(?:\.|\])/m);
|
|
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;
|
|
6903
7979
|
}
|
|
6904
|
-
function
|
|
6905
|
-
return
|
|
7980
|
+
function commandInvokesCoverageTool(command) {
|
|
7981
|
+
return normalizedCommandInvokesCoverageTool(normalizeCoverageCommandPrefixes(command), 0);
|
|
6906
7982
|
}
|
|
6907
|
-
function
|
|
6908
|
-
|
|
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
|
+
}
|
|
8223
|
+
return false;
|
|
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 };
|
|
6909
8388
|
}
|
|
6910
8389
|
function isCiWorkflow(file) {
|
|
6911
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);
|
|
6912
8391
|
}
|
|
6913
8392
|
function isDeployConfig(file) {
|
|
6914
|
-
return
|
|
8393
|
+
return /(^|\/)(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /(^|\/)Dockerfile$/.test(file) || /(^|\/)docker-compose\.ya?ml$/.test(file) || /(^|\/)k8s\//.test(file);
|
|
6915
8394
|
}
|
|
6916
8395
|
function isExplicitDeployTarget(file) {
|
|
6917
|
-
return
|
|
8396
|
+
return /(^|\/)(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /(^|\/)docker-compose\.ya?ml$/.test(file) || /(^|\/)Procfile$/.test(file) || /(^|\/)app\.ya?ml$/.test(file) || /(^|\/)serverless\.ya?ml$/.test(file) || /(^|\/)(k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /(^|\/)(charts?|helm)\/.+\.ya?ml$/i.test(file);
|
|
6918
8397
|
}
|
|
6919
8398
|
var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry|start|run)\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs)$/i;
|
|
8399
|
+
var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start|run)\.rb)$/i;
|
|
6920
8400
|
var SERVER_ENTRYPOINT_PATTERN = /\bapp\.listen\s*\(|\bserver\.listen\s*\(\s*(?:PORT|port|\d+)|http\.ListenAndServe\s*\(|http\.ListenAndServeTLS\s*\(|axum::Server(?:::|\.)|actix_web::HttpServer(?:::|\.)|uvicorn\.run\s*\(/;
|
|
8401
|
+
var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
|
|
8402
|
+
var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
|
|
8403
|
+
var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
|
|
8404
|
+
var NON_RUNTIME_CONTAINER_COMMAND_PATTERN = /\b(?:test|lint|build|compile|package|check)\b/i;
|
|
6921
8405
|
var CI_DEPLOY_JOB_PATTERN = /(?:^\s{2,4}deploy\s*:|\b(?:fly|heroku|vercel|netlify|railway|render)\s+deploy\b|kubectl\s+apply\b|helm\s+(?:install|upgrade|deploy)\b|docker\s+push\b)/im;
|
|
6922
|
-
function isDeployableService(repoPath, repoFiles) {
|
|
6923
|
-
if (repoFiles.some(
|
|
6924
|
-
|
|
6925
|
-
|
|
8406
|
+
function isDeployableService(repoPath, repoFiles, useV27Detectors) {
|
|
8407
|
+
if (repoFiles.some(
|
|
8408
|
+
(file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isExplicitDeployTarget(file)
|
|
8409
|
+
)) {
|
|
8410
|
+
return true;
|
|
8411
|
+
}
|
|
8412
|
+
if (detectCiDeployJob(repoPath, repoFiles, useV27Detectors)) return true;
|
|
8413
|
+
if (detectServerEntrypoint(repoPath, repoFiles, useV27Detectors)) return true;
|
|
8414
|
+
if (useV27Detectors && findRuntimeContainerEntrypointFile(repoPath, repoFiles)) return true;
|
|
6926
8415
|
return false;
|
|
6927
8416
|
}
|
|
6928
|
-
function detectCiDeployJob(repoPath, repoFiles) {
|
|
6929
|
-
const workflows = repoFiles.filter(
|
|
8417
|
+
function detectCiDeployJob(repoPath, repoFiles, authoredOnly = false) {
|
|
8418
|
+
const workflows = repoFiles.filter(
|
|
8419
|
+
(file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
8420
|
+
);
|
|
6930
8421
|
return workflows.some((file) => fileContains(repoPath, file, CI_DEPLOY_JOB_PATTERN));
|
|
6931
8422
|
}
|
|
6932
|
-
function detectServerEntrypoint(repoPath, repoFiles) {
|
|
6933
|
-
return findServerEntrypointFile(repoPath, repoFiles) !== null;
|
|
8423
|
+
function detectServerEntrypoint(repoPath, repoFiles, useV27Detectors) {
|
|
8424
|
+
return findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) !== null;
|
|
6934
8425
|
}
|
|
6935
|
-
function findServerEntrypointFile(repoPath, repoFiles) {
|
|
6936
|
-
const candidates = repoFiles.filter(
|
|
6937
|
-
|
|
8426
|
+
function findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) {
|
|
8427
|
+
const candidates = repoFiles.filter(
|
|
8428
|
+
(file) => (MAIN_ENTRYPOINT_FILE_PATTERN.test(file) || useV27Detectors && RACK_ENTRYPOINT_FILE_PATTERN.test(file)) && (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file))
|
|
8429
|
+
).slice(0, 30);
|
|
8430
|
+
return candidates.find(
|
|
8431
|
+
(file) => fileContains(repoPath, file, SERVER_ENTRYPOINT_PATTERN) || useV27Detectors && fileContains(repoPath, file, RACK_SERVER_ENTRYPOINT_PATTERN) || useV27Detectors && /\.ru$/i.test(file) && fileContains(repoPath, file, RACK_CONFIG_RUN_PATTERN)
|
|
8432
|
+
) ?? null;
|
|
8433
|
+
}
|
|
8434
|
+
function findRuntimeContainerEntrypointFile(repoPath, repoFiles) {
|
|
8435
|
+
return repoFiles.filter((file) => /(^|\/)Dockerfile$/i.test(file) && isAuthoredProductionPath(file)).find((file) => {
|
|
8436
|
+
const fullPath = join(repoPath, file);
|
|
8437
|
+
if (!isRegularFile(fullPath)) return false;
|
|
8438
|
+
const lines = readFileSync(fullPath, "utf8").split(/\r?\n/);
|
|
8439
|
+
const finalStageStart = lines.reduce(
|
|
8440
|
+
(last, line, index) => /^\s*FROM\b/i.test(line) ? index : last,
|
|
8441
|
+
0
|
|
8442
|
+
);
|
|
8443
|
+
const finalStageLines = lines.slice(finalStageStart);
|
|
8444
|
+
const entrypoint = [...finalStageLines].reverse().find((line) => /^\s*ENTRYPOINT\b/i.test(line));
|
|
8445
|
+
const command = [...finalStageLines].reverse().find((line) => /^\s*CMD\b/i.test(line));
|
|
8446
|
+
const effectiveCommand = [entrypoint, command].filter(Boolean).join(" ");
|
|
8447
|
+
return effectiveCommand.length > 0 && RUNTIME_CONTAINER_COMMAND_PATTERN.test(effectiveCommand) && !NON_RUNTIME_CONTAINER_COMMAND_PATTERN.test(effectiveCommand);
|
|
8448
|
+
}) ?? null;
|
|
6938
8449
|
}
|
|
6939
8450
|
function isHealthCheckSignalFile(repoPath, file) {
|
|
6940
8451
|
if (/docs\/|audit|architecture|adr/i.test(file)) return false;
|
|
@@ -6981,6 +8492,42 @@ function packageJsonHasHeavyTestDependency(repoPath, manifest) {
|
|
|
6981
8492
|
];
|
|
6982
8493
|
return names.some((name) => HEAVY_TEST_DEPENDENCY_NAME_PATTERN.test(name));
|
|
6983
8494
|
}
|
|
8495
|
+
function packageJsonDescribesPackagedApplication(repoPath, manifest, repoFiles) {
|
|
8496
|
+
const fullPath = join(repoPath, manifest);
|
|
8497
|
+
if (!isRegularFile(fullPath)) return false;
|
|
8498
|
+
const parsed = parseJsonObject(readFileSync(fullPath, "utf8"));
|
|
8499
|
+
if (!parsed) return false;
|
|
8500
|
+
const scripts = isRecord(parsed.scripts) ? Object.values(parsed.scripts) : [];
|
|
8501
|
+
const dependencies = {
|
|
8502
|
+
...isRecord(parsed.dependencies) ? parsed.dependencies : {},
|
|
8503
|
+
...isRecord(parsed.devDependencies) ? parsed.devDependencies : {}
|
|
8504
|
+
};
|
|
8505
|
+
const build = isRecord(parsed.build) ? parsed.build : null;
|
|
8506
|
+
const hasDesktopRuntime = Object.keys(dependencies).some(
|
|
8507
|
+
(name) => name.toLowerCase() === "electron" || /^@tauri-apps\/(?:api|plugin-)/i.test(name)
|
|
8508
|
+
);
|
|
8509
|
+
const hasPackagingCommand = scripts.some(
|
|
8510
|
+
(script) => typeof script === "string" && /\b(?:electron-builder|electron-packager|tauri\s+build)\b/i.test(script)
|
|
8511
|
+
);
|
|
8512
|
+
const hasApplicationMetadata = isNonEmptyString(parsed.productName) || isNonEmptyString(parsed.main) || build != null && isNonEmptyString(build.appId) || findTauriApplicationMetadata(repoPath, manifest, repoFiles);
|
|
8513
|
+
return hasDesktopRuntime && hasPackagingCommand && hasApplicationMetadata;
|
|
8514
|
+
}
|
|
8515
|
+
function findTauriApplicationMetadata(repoPath, manifest, repoFiles) {
|
|
8516
|
+
const manifestDirectory = dirname(manifest);
|
|
8517
|
+
const prefix = manifestDirectory === "." ? "" : `${manifestDirectory}/`;
|
|
8518
|
+
const candidates = [`${prefix}src-tauri/tauri.conf.json`, `${prefix}tauri.conf.json`];
|
|
8519
|
+
return candidates.some((file) => {
|
|
8520
|
+
if (!repoFiles.includes(file)) return false;
|
|
8521
|
+
const fullPath = join(repoPath, file);
|
|
8522
|
+
if (!isRegularFile(fullPath)) return false;
|
|
8523
|
+
const config = parseJsonObject(readFileSync(fullPath, "utf8"));
|
|
8524
|
+
const packageMetadata = isRecord(config?.package) ? config.package : null;
|
|
8525
|
+
return isNonEmptyString(config?.productName) || isNonEmptyString(config?.identifier) || packageMetadata != null && isNonEmptyString(packageMetadata.productName);
|
|
8526
|
+
});
|
|
8527
|
+
}
|
|
8528
|
+
function isNonEmptyString(value) {
|
|
8529
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
8530
|
+
}
|
|
6984
8531
|
function isImplementationFile(file) {
|
|
6985
8532
|
return /(^|\/)(src|app|lib|packages|cmd|include|source|Sources)\//.test(file) && /\.(ts|tsx|js|jsx|py|go|rs|java|rb|cpp|cc|cxx|c|h|hpp|kt|swift|php)$/.test(file);
|
|
6986
8533
|
}
|
|
@@ -7004,8 +8551,8 @@ function isSecurityPolicyOnlyAuditFile(file) {
|
|
|
7004
8551
|
function isFreshnessRatableAuditFile(file) {
|
|
7005
8552
|
return isAuditFile(file) && !isSecurityPolicyOnlyAuditFile(file);
|
|
7006
8553
|
}
|
|
7007
|
-
function isIgnoredScanFile(file) {
|
|
7008
|
-
return /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|README\.md|CHANGELOG\.md)$/.test(file) || isEnvTemplatePath(file);
|
|
8554
|
+
function isIgnoredScanFile(file, useV47Detectors = false) {
|
|
8555
|
+
return /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|README\.md|CHANGELOG\.md)$/.test(file) || isEnvTemplatePath(file, useV47Detectors);
|
|
7009
8556
|
}
|
|
7010
8557
|
function findRootPackageJson(repoFiles) {
|
|
7011
8558
|
return repoFiles.includes("package.json") ? "package.json" : null;
|
|
@@ -7022,46 +8569,315 @@ function fileContains(repoPath, file, pattern) {
|
|
|
7022
8569
|
if (!isRegularFile(fullPath)) return false;
|
|
7023
8570
|
return pattern.test(readFileSync(fullPath, "utf8"));
|
|
7024
8571
|
}
|
|
7025
|
-
function
|
|
8572
|
+
function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
7026
8573
|
const fullPath = join(repoPath, file);
|
|
7027
|
-
if (!isRegularFile(fullPath)) return
|
|
7028
|
-
|
|
8574
|
+
if (!isRegularFile(fullPath)) return null;
|
|
8575
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
8576
|
+
const isTestCredentialConfiguration = useV39Detectors && isLikelyTestCredentialConfiguration(file, contents);
|
|
8577
|
+
const secretScanContents = prepareCredentialScanContents(
|
|
8578
|
+
contents,
|
|
8579
|
+
file,
|
|
8580
|
+
useV36Detectors,
|
|
8581
|
+
useV39Detectors,
|
|
8582
|
+
useV47Detectors
|
|
8583
|
+
);
|
|
8584
|
+
const secret = findRealSecretAssignment(secretScanContents, /* @__PURE__ */ new Set(), {
|
|
8585
|
+
allowCredentialNamedDigest: useV36Detectors
|
|
8586
|
+
});
|
|
8587
|
+
if (secret) return secret;
|
|
8588
|
+
if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
|
|
8589
|
+
const weakCredential = findWeakExplicitEnvCredential(secretScanContents);
|
|
8590
|
+
if (weakCredential) return weakCredential;
|
|
8591
|
+
}
|
|
8592
|
+
return useV36Detectors && !isTestCredentialConfiguration ? findDefaultAdministrativeCredential(secretScanContents) : null;
|
|
8593
|
+
}
|
|
8594
|
+
function prepareCredentialScanContents(contents, file, useV36Detectors, useV39Detectors, useV47Detectors = false) {
|
|
8595
|
+
const withoutXamlEventNames = useV36Detectors && /\.xaml$/i.test(file) ? contents.replace(/\bTokenItem(?:Added|Removed)(?=\s*=)/gi, "EventHandler") : contents;
|
|
8596
|
+
const withoutDocumentationExamples = useV39Detectors ? stripCommentAndDocumentationExamples(withoutXamlEventNames) : withoutXamlEventNames;
|
|
8597
|
+
return useV47Detectors ? stripV47PublicIdentifiersAndNonCredentialValues(withoutDocumentationExamples, file) : withoutDocumentationExamples;
|
|
8598
|
+
}
|
|
8599
|
+
function stripV47PublicIdentifiersAndNonCredentialValues(contents, file) {
|
|
8600
|
+
const lines = contents.split(/\r?\n/);
|
|
8601
|
+
return lines.map((line, index) => {
|
|
8602
|
+
let sanitized = line.replace(
|
|
8603
|
+
/^(\s*(?:export\s+)?(?:PUBLIC_)?ALGOLIA_(?:PUBLIC_)?API_KEY\s*[:=]\s*)[^\s#]+/i,
|
|
8604
|
+
'$1""'
|
|
8605
|
+
);
|
|
8606
|
+
if (isPublicDocumentationSearchKeyContext(lines, index, file)) {
|
|
8607
|
+
sanitized = sanitized.replace(/(\bapiKey\s*[:=]\s*)(['"])[^'"]+\2/gi, "$1$2$2");
|
|
8608
|
+
}
|
|
8609
|
+
sanitized = sanitized.replace(
|
|
8610
|
+
/(\b[A-Za-z_][A-Za-z0-9_]*(?:token|api[_-]?key|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*)(['"]?)0x[0-9a-f]{40}\2/gi,
|
|
8611
|
+
'$1""'
|
|
8612
|
+
);
|
|
8613
|
+
sanitized = sanitized.replace(
|
|
8614
|
+
/(\b[A-Za-z_][A-Za-z0-9_]*(?:token|api[_-]?key|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*)(['"])([a-z][a-z0-9]*(?:_[a-z0-9]+){2,})\2/gi,
|
|
8615
|
+
(assignment, prefix, quote, value) => /\b(?:cost|input|output|context|model|limit|price|usage|audio)\b/i.test(
|
|
8616
|
+
value.replaceAll("_", " ")
|
|
8617
|
+
) ? `${prefix}${quote}${quote}` : assignment
|
|
8618
|
+
);
|
|
8619
|
+
return sanitized;
|
|
8620
|
+
}).join("\n");
|
|
7029
8621
|
}
|
|
7030
|
-
function
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
if (
|
|
7037
|
-
if (
|
|
8622
|
+
function isPublicDocumentationSearchKeyContext(lines, lineIndex, file) {
|
|
8623
|
+
const windowStart = Math.max(0, lineIndex - 6);
|
|
8624
|
+
const preceding = lines.slice(windowStart, lineIndex + 1);
|
|
8625
|
+
for (let index = preceding.length - 1; index >= 0; index -= 1) {
|
|
8626
|
+
const candidate = preceding[index] ?? "";
|
|
8627
|
+
if (/}/.test(candidate) && index < preceding.length - 1) return false;
|
|
8628
|
+
if (/\bdocsearch(?:Options)?\b/i.test(candidate)) return true;
|
|
8629
|
+
if (/\balgolia\b/i.test(candidate) && /(?:^|\/)(?:website|docs?)(?:\/|$)|(?:site[-_]?config|\.dumirc|docusaurus)/i.test(file)) {
|
|
8630
|
+
return true;
|
|
8631
|
+
}
|
|
7038
8632
|
}
|
|
7039
8633
|
return false;
|
|
7040
8634
|
}
|
|
7041
|
-
function
|
|
7042
|
-
|
|
8635
|
+
function stripCommentAndDocumentationExamples(contents) {
|
|
8636
|
+
let blockCommentEnd = null;
|
|
8637
|
+
return contents.split(/\r?\n/).map((line) => {
|
|
8638
|
+
const trimmed = line.trimStart();
|
|
8639
|
+
if (blockCommentEnd) {
|
|
8640
|
+
if (trimmed.includes(blockCommentEnd)) blockCommentEnd = null;
|
|
8641
|
+
return "";
|
|
8642
|
+
}
|
|
8643
|
+
if (trimmed.startsWith("/*")) {
|
|
8644
|
+
if (!trimmed.includes("*/")) blockCommentEnd = "*/";
|
|
8645
|
+
return "";
|
|
8646
|
+
}
|
|
8647
|
+
if (trimmed.startsWith("<!--")) {
|
|
8648
|
+
if (!trimmed.includes("-->")) blockCommentEnd = "-->";
|
|
8649
|
+
return "";
|
|
8650
|
+
}
|
|
8651
|
+
if (/^(?:\/\/|#|\*)/.test(trimmed)) return "";
|
|
8652
|
+
if (/\.description\s*\(/i.test(line) && /api[_-]?key|token|password|secret/i.test(line)) {
|
|
8653
|
+
return "";
|
|
8654
|
+
}
|
|
8655
|
+
return line;
|
|
8656
|
+
}).join("\n");
|
|
8657
|
+
}
|
|
8658
|
+
function isLikelyTestCredentialConfiguration(file, contents) {
|
|
8659
|
+
if (!/\.(?:ya?ml|json|toml|ini|conf)$/i.test(file) && !/(?:^|\/)docker-compose/i.test(file)) {
|
|
8660
|
+
return false;
|
|
8661
|
+
}
|
|
8662
|
+
const testMarkers = contents.match(/(?:^|[^A-Za-z])(?:tests?|testing|e2e|appium|fixture)(?:[^A-Za-z]|$)/gi)?.length ?? 0;
|
|
8663
|
+
return testMarkers >= 2;
|
|
8664
|
+
}
|
|
8665
|
+
function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
8666
|
+
const fullPath = join(repoPath, file);
|
|
8667
|
+
if (!isRegularFile(fullPath)) return false;
|
|
8668
|
+
const substantiveLines = readFileSync(fullPath, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*#/.test(line));
|
|
8669
|
+
if (substantiveLines.length === 0) return true;
|
|
8670
|
+
const assignments = substantiveLines.map(
|
|
8671
|
+
(line) => /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.*?)\s*$/.exec(line)?.[1] ?? null
|
|
8672
|
+
);
|
|
8673
|
+
if (assignments.some((value) => value === null)) return false;
|
|
8674
|
+
return assignments.every((raw) => {
|
|
8675
|
+
if (raw === null) return false;
|
|
8676
|
+
const value = raw.replace(/^['"]|['"]$/g, "").trim();
|
|
8677
|
+
return value.length === 0 || isPlaceholderSecretValue(value);
|
|
8678
|
+
});
|
|
8679
|
+
}
|
|
8680
|
+
function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8681
|
+
const fullPath = join(repoPath, file);
|
|
8682
|
+
if (!isRegularFile(fullPath)) return [];
|
|
8683
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
8684
|
+
const scanContents = prepareCredentialScanContents(
|
|
8685
|
+
contents,
|
|
8686
|
+
file,
|
|
8687
|
+
useV36Detectors,
|
|
8688
|
+
useV39Detectors,
|
|
8689
|
+
useV47Detectors
|
|
8690
|
+
);
|
|
8691
|
+
const fingerprints = findRealSecretAssignments(
|
|
8692
|
+
scanContents,
|
|
8693
|
+
/* @__PURE__ */ new Set(),
|
|
8694
|
+
Number.POSITIVE_INFINITY,
|
|
8695
|
+
{ allowCredentialNamedDigest: useV36Detectors }
|
|
8696
|
+
).map(({ valueFingerprint }) => valueFingerprint);
|
|
8697
|
+
if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
|
|
8698
|
+
const weakCredential = findWeakExplicitEnvCredential(scanContents);
|
|
8699
|
+
if (weakCredential) fingerprints.push(weakCredential.valueFingerprint);
|
|
8700
|
+
}
|
|
8701
|
+
return [...new Set(fingerprints)];
|
|
8702
|
+
}
|
|
8703
|
+
function findRealSecretAssignment(contents, excludedValueFingerprints = /* @__PURE__ */ new Set(), options = {}) {
|
|
8704
|
+
return findRealSecretAssignments(contents, excludedValueFingerprints, 1, options)[0] ?? null;
|
|
8705
|
+
}
|
|
8706
|
+
function findRealSecretAssignments(contents, excludedValueFingerprints = /* @__PURE__ */ new Set(), maximumMatches = Number.POSITIVE_INFINITY, options = {}) {
|
|
8707
|
+
const matches = [];
|
|
8708
|
+
SECRET_IDENTIFIER_KEYWORD_PATTERN.lastIndex = 0;
|
|
8709
|
+
let match = SECRET_IDENTIFIER_KEYWORD_PATTERN.exec(contents);
|
|
8710
|
+
let line = 1;
|
|
8711
|
+
let lineCursor = 0;
|
|
8712
|
+
while (match !== null) {
|
|
8713
|
+
const keyword = match[0];
|
|
8714
|
+
const keywordStart = match.index;
|
|
8715
|
+
while (lineCursor < keywordStart) {
|
|
8716
|
+
if (contents.charCodeAt(lineCursor) === 10) line += 1;
|
|
8717
|
+
lineCursor += 1;
|
|
8718
|
+
}
|
|
8719
|
+
let cursor = keywordStart + keyword.length;
|
|
8720
|
+
while (cursor < contents.length && isSecretIdentifierCharacter(contents.charCodeAt(cursor))) {
|
|
8721
|
+
cursor += 1;
|
|
8722
|
+
}
|
|
8723
|
+
const identifierEnd = cursor;
|
|
8724
|
+
let identifierStart = keywordStart;
|
|
8725
|
+
while (identifierStart > 0 && isSecretIdentifierCharacter(contents.charCodeAt(identifierStart - 1))) {
|
|
8726
|
+
identifierStart -= 1;
|
|
8727
|
+
}
|
|
8728
|
+
const identifier = contents.slice(identifierStart, identifierEnd);
|
|
8729
|
+
let nextSearchIndex = identifierEnd;
|
|
8730
|
+
while (cursor < contents.length && SINGLE_WHITESPACE_PATTERN.test(contents[cursor] ?? "")) {
|
|
8731
|
+
cursor += 1;
|
|
8732
|
+
}
|
|
8733
|
+
const delimiter = contents[cursor];
|
|
8734
|
+
if (delimiter === ":" || delimiter === "=") {
|
|
8735
|
+
cursor += 1;
|
|
8736
|
+
while (cursor < contents.length && SINGLE_WHITESPACE_PATTERN.test(contents[cursor] ?? "")) {
|
|
8737
|
+
cursor += 1;
|
|
8738
|
+
}
|
|
8739
|
+
if (contents[cursor] === "'" || contents[cursor] === '"') cursor += 1;
|
|
8740
|
+
const valueStart = cursor;
|
|
8741
|
+
while (cursor < contents.length) {
|
|
8742
|
+
const character = contents[cursor] ?? "";
|
|
8743
|
+
if (character === "'" || character === '"' || SINGLE_WHITESPACE_PATTERN.test(character)) {
|
|
8744
|
+
break;
|
|
8745
|
+
}
|
|
8746
|
+
cursor += 1;
|
|
8747
|
+
}
|
|
8748
|
+
if (cursor > valueStart) {
|
|
8749
|
+
const value = contents.slice(valueStart, cursor).replace(/[,;)]$/, "");
|
|
8750
|
+
nextSearchIndex = cursor;
|
|
8751
|
+
if (!isPlaceholderSecretValue(value) && (looksLikeSecretValue(value) || options.allowCredentialNamedDigest === true && isLikelyDigestOrHash(value) && isExplicitCredentialIdentifier(identifier))) {
|
|
8752
|
+
const valueFingerprint = createHash2("sha256").update(value).digest("hex");
|
|
8753
|
+
const characterClasses = [
|
|
8754
|
+
/[a-z]/.test(value) ? "lower" : "",
|
|
8755
|
+
/[A-Z]/.test(value) ? "upper" : "",
|
|
8756
|
+
/\d/.test(value) ? "digit" : "",
|
|
8757
|
+
/[^A-Za-z0-9]/.test(value) ? "symbol" : ""
|
|
8758
|
+
].filter(Boolean).join("+");
|
|
8759
|
+
if (!excludedValueFingerprints.has(valueFingerprint)) {
|
|
8760
|
+
matches.push({
|
|
8761
|
+
identifier,
|
|
8762
|
+
line,
|
|
8763
|
+
valueLength: value.length,
|
|
8764
|
+
characterClasses: characterClasses || "other",
|
|
8765
|
+
valueFingerprint
|
|
8766
|
+
});
|
|
8767
|
+
if (matches.length >= maximumMatches) return matches;
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
8772
|
+
SECRET_IDENTIFIER_KEYWORD_PATTERN.lastIndex = nextSearchIndex;
|
|
8773
|
+
match = SECRET_IDENTIFIER_KEYWORD_PATTERN.exec(contents);
|
|
8774
|
+
}
|
|
8775
|
+
return matches;
|
|
8776
|
+
}
|
|
8777
|
+
function isExplicitCredentialIdentifier(identifier) {
|
|
8778
|
+
return /^(?:github|gitlab|api|access)[_-]?(?:token|key)$/i.test(identifier);
|
|
8779
|
+
}
|
|
8780
|
+
function findWeakExplicitEnvCredential(contents) {
|
|
8781
|
+
const lines = contents.split(/\r?\n/);
|
|
8782
|
+
for (const [index, line] of lines.entries()) {
|
|
8783
|
+
if (/^\s*#/.test(line)) continue;
|
|
8784
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*(?:PASSWORD|PASSWD|PWD|SECRET(?:_KEY)?|API_KEY|TOKEN))\s*=\s*['"]?([^\s#'"]+)['"]?/i.exec(
|
|
8785
|
+
line
|
|
8786
|
+
);
|
|
8787
|
+
const identifier = match?.[1];
|
|
8788
|
+
const value = match?.[2];
|
|
8789
|
+
if (!identifier || !value || isPlaceholderSecretValue(value)) continue;
|
|
8790
|
+
const minimumLength = /(?:API_KEY|TOKEN)$/i.test(identifier) ? 12 : 6;
|
|
8791
|
+
if (value.length < minimumLength) continue;
|
|
8792
|
+
const characterClasses = [
|
|
8793
|
+
/[a-z]/.test(value) ? "lower" : "",
|
|
8794
|
+
/[A-Z]/.test(value) ? "upper" : "",
|
|
8795
|
+
/\d/.test(value) ? "digit" : "",
|
|
8796
|
+
/[^A-Za-z0-9]/.test(value) ? "symbol" : ""
|
|
8797
|
+
].filter(Boolean).join("+");
|
|
8798
|
+
return {
|
|
8799
|
+
identifier,
|
|
8800
|
+
line: index + 1,
|
|
8801
|
+
valueLength: value.length,
|
|
8802
|
+
characterClasses: characterClasses || "other",
|
|
8803
|
+
valueFingerprint: createHash2("sha256").update(value).digest("hex")
|
|
8804
|
+
};
|
|
8805
|
+
}
|
|
8806
|
+
return null;
|
|
8807
|
+
}
|
|
8808
|
+
function findDefaultAdministrativeCredential(contents) {
|
|
8809
|
+
const lines = contents.split(/\r?\n/);
|
|
8810
|
+
for (const [index, line] of lines.entries()) {
|
|
8811
|
+
const match = /\b([A-Za-z0-9_]*(?:ADMIN|ROOT)[A-Za-z0-9_]*(?:PASSWORD|PASSWD|PWD))\s*[:=]\s*["']?(admin|root|password)["']?(?:\s|$)/i.exec(
|
|
8812
|
+
line
|
|
8813
|
+
);
|
|
8814
|
+
const identifier = match?.[1];
|
|
8815
|
+
const value = match?.[2];
|
|
8816
|
+
if (!identifier || !value) continue;
|
|
8817
|
+
return {
|
|
8818
|
+
identifier,
|
|
8819
|
+
line: index + 1,
|
|
8820
|
+
valueLength: value.length,
|
|
8821
|
+
characterClasses: "lower",
|
|
8822
|
+
valueFingerprint: createHash2("sha256").update(value).digest("hex"),
|
|
8823
|
+
kind: "default_admin"
|
|
8824
|
+
};
|
|
8825
|
+
}
|
|
8826
|
+
return null;
|
|
8827
|
+
}
|
|
8828
|
+
function secretEvidenceLabel(prefix, match) {
|
|
8829
|
+
return `${prefix} (value redacted; length ${match.valueLength}; classes ${match.characterClasses})`;
|
|
8830
|
+
}
|
|
8831
|
+
function isSecretIdentifierCharacter(characterCode) {
|
|
8832
|
+
return characterCode >= 48 && characterCode <= 57 || characterCode >= 65 && characterCode <= 90 || characterCode === 95 || characterCode >= 97 && characterCode <= 122;
|
|
8833
|
+
}
|
|
8834
|
+
function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFingerprintsByPath, authoredProductionOnly = false, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8835
|
+
const historyEntries = readCredentialHistoryEntries(repoPath, useV36Detectors);
|
|
8836
|
+
const head = excludeHead ? readGitHead(repoPath) : null;
|
|
7043
8837
|
let scannedFiles = 0;
|
|
7044
8838
|
let envPathEvidence = null;
|
|
7045
8839
|
for (const entry of historyEntries) {
|
|
8840
|
+
if (head && entry.commit === head) continue;
|
|
8841
|
+
if (authoredProductionOnly && !(useV47Detectors ? isV47AuthoredProductionPath(entry.path) : useV39Detectors ? isV39AuthoredProductionPath(entry.path) : isAuthoredProductionPath(entry.path))) {
|
|
8842
|
+
continue;
|
|
8843
|
+
}
|
|
7046
8844
|
if (scannedFiles >= HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT) {
|
|
7047
8845
|
return { evidence: null, envPathEvidence, truncated: true };
|
|
7048
8846
|
}
|
|
7049
8847
|
scannedFiles += 1;
|
|
7050
8848
|
const contents = readGitBlob(repoPath, entry.commit, entry.path);
|
|
7051
8849
|
if (!contents) continue;
|
|
7052
|
-
|
|
8850
|
+
const scanContents = prepareCredentialScanContents(
|
|
8851
|
+
contents,
|
|
8852
|
+
entry.path,
|
|
8853
|
+
useV36Detectors,
|
|
8854
|
+
useV39Detectors,
|
|
8855
|
+
useV47Detectors
|
|
8856
|
+
);
|
|
8857
|
+
const currentFingerprints = currentSecretFingerprintsByPath.get(entry.path) ?? /* @__PURE__ */ new Set();
|
|
8858
|
+
let secretMatch = findRealSecretAssignment(scanContents, currentFingerprints, {
|
|
8859
|
+
allowCredentialNamedDigest: useV36Detectors
|
|
8860
|
+
});
|
|
8861
|
+
if (!secretMatch && useV39Detectors && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
|
|
8862
|
+
const weakCredential = findWeakExplicitEnvCredential(scanContents);
|
|
8863
|
+
if (weakCredential && !currentFingerprints.has(weakCredential.valueFingerprint)) {
|
|
8864
|
+
secretMatch = weakCredential;
|
|
8865
|
+
}
|
|
8866
|
+
}
|
|
8867
|
+
if (secretMatch) {
|
|
7053
8868
|
return {
|
|
7054
8869
|
evidence: {
|
|
7055
8870
|
kind: "secret_scan",
|
|
7056
|
-
label: "Committed secret in recent git history",
|
|
8871
|
+
label: secretEvidenceLabel("Committed secret in recent git history", secretMatch),
|
|
7057
8872
|
path: entry.path,
|
|
8873
|
+
line: secretMatch.line,
|
|
7058
8874
|
contentHash: entry.commit
|
|
7059
8875
|
},
|
|
7060
8876
|
envPathEvidence,
|
|
7061
8877
|
truncated: false
|
|
7062
8878
|
};
|
|
7063
8879
|
}
|
|
7064
|
-
if (!envPathEvidence && isEnvHistoryPath(entry.path)) {
|
|
8880
|
+
if (!envPathEvidence && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
|
|
7065
8881
|
envPathEvidence = {
|
|
7066
8882
|
kind: "secret_scan",
|
|
7067
8883
|
label: ".env file (not a template) tracked in git history; no confirmed secret value found",
|
|
@@ -7072,7 +8888,7 @@ function collectHistorySecretEvidence(repoPath) {
|
|
|
7072
8888
|
}
|
|
7073
8889
|
return { evidence: null, envPathEvidence, truncated: false };
|
|
7074
8890
|
}
|
|
7075
|
-
function readCredentialHistoryEntries(repoPath) {
|
|
8891
|
+
function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
7076
8892
|
const entries = [];
|
|
7077
8893
|
const seen = /* @__PURE__ */ new Set();
|
|
7078
8894
|
const appendEntry = (entry) => {
|
|
@@ -7113,18 +8929,18 @@ function readCredentialHistoryEntries(repoPath) {
|
|
|
7113
8929
|
}
|
|
7114
8930
|
const path = line.trim();
|
|
7115
8931
|
if (!currentCommit || !path) continue;
|
|
7116
|
-
if (!isCredentialHistoryPath(path)) continue;
|
|
8932
|
+
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
7117
8933
|
appendEntry({ commit: currentCommit, path });
|
|
7118
8934
|
}
|
|
7119
8935
|
} catch {
|
|
7120
8936
|
}
|
|
7121
|
-
for (const entry of readDeletedCredentialHistoryEntries(repoPath)) {
|
|
8937
|
+
for (const entry of readDeletedCredentialHistoryEntries(repoPath, useV36Detectors)) {
|
|
7122
8938
|
appendEntry(entry);
|
|
7123
8939
|
}
|
|
7124
8940
|
return entries;
|
|
7125
8941
|
}
|
|
7126
|
-
function readDeletedCredentialHistoryEntries(repoPath) {
|
|
7127
|
-
const deletedPaths = readDeletedCredentialHistoryPaths(repoPath);
|
|
8942
|
+
function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
8943
|
+
const deletedPaths = readDeletedCredentialHistoryPaths(repoPath, useV36Detectors);
|
|
7128
8944
|
const entries = [];
|
|
7129
8945
|
const seen = /* @__PURE__ */ new Set();
|
|
7130
8946
|
for (const path of deletedPaths) {
|
|
@@ -7137,7 +8953,7 @@ function readDeletedCredentialHistoryEntries(repoPath) {
|
|
|
7137
8953
|
}
|
|
7138
8954
|
return entries;
|
|
7139
8955
|
}
|
|
7140
|
-
function readDeletedCredentialHistoryPaths(repoPath) {
|
|
8956
|
+
function readDeletedCredentialHistoryPaths(repoPath, useV36Detectors = false) {
|
|
7141
8957
|
try {
|
|
7142
8958
|
const output = execFileSync(
|
|
7143
8959
|
"git",
|
|
@@ -7154,7 +8970,7 @@ function readDeletedCredentialHistoryPaths(repoPath) {
|
|
|
7154
8970
|
if (!line.startsWith("D ")) continue;
|
|
7155
8971
|
const path = line.slice(2).trim();
|
|
7156
8972
|
if (!path || seen.has(path)) continue;
|
|
7157
|
-
if (!isCredentialHistoryPath(path)) continue;
|
|
8973
|
+
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
7158
8974
|
seen.add(path);
|
|
7159
8975
|
paths.push(path);
|
|
7160
8976
|
}
|
|
@@ -7179,7 +8995,7 @@ function readHistoryCommitsForPath(repoPath, path) {
|
|
|
7179
8995
|
return [];
|
|
7180
8996
|
}
|
|
7181
8997
|
}
|
|
7182
|
-
function isCredentialHistoryPath(path) {
|
|
8998
|
+
function isCredentialHistoryPath(path, _useV36Detectors = false) {
|
|
7183
8999
|
if (isHardExcludedPath(path)) return false;
|
|
7184
9000
|
const lowerPath = path.toLowerCase();
|
|
7185
9001
|
const basename3 = lowerPath.split("/").at(-1) ?? lowerPath;
|
|
@@ -7188,10 +9004,11 @@ function isCredentialHistoryPath(path) {
|
|
|
7188
9004
|
}
|
|
7189
9005
|
return lowerPath.includes("secret") || lowerPath.includes("credential") || basename3.endsWith(".pem") || basename3.endsWith(".key") || basename3 === "id_rsa" || basename3.endsWith(".p12") || basename3.endsWith(".pfx");
|
|
7190
9006
|
}
|
|
7191
|
-
function isEnvHistoryPath(path) {
|
|
7192
|
-
if (isEnvTemplatePath(path)) return false;
|
|
9007
|
+
function isEnvHistoryPath(path, useV36Detectors = false, useV47Detectors = false) {
|
|
9008
|
+
if (isEnvTemplatePath(path, useV47Detectors)) return false;
|
|
9009
|
+
if (useV47Detectors && !isV47AuthoredProductionPath(path)) return false;
|
|
7193
9010
|
const basename3 = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
|
|
7194
|
-
return /\.env(?:\.|$)
|
|
9011
|
+
return (useV36Detectors ? /^\.env(?:\.|$)/ : /\.env(?:\.|$)/).test(basename3);
|
|
7195
9012
|
}
|
|
7196
9013
|
function readGitBlob(repoPath, commit, path) {
|
|
7197
9014
|
try {
|
|
@@ -7229,7 +9046,7 @@ function isFillerRunSecretValue(value) {
|
|
|
7229
9046
|
function isSyntheticFixtureSecretValue(value) {
|
|
7230
9047
|
return value.includes("abcdefghijklmnopqrstuvwxyz") || value.includes("0123456789abcdefghijklmnopqrstuvwxyz") || value.includes("abcdefghijklmnopqrstuvwxyz0123456789") || value.includes("abcdefghijklmnopqrst");
|
|
7231
9048
|
}
|
|
7232
|
-
function hasSuspiciousDependencies(repoPath, manifest) {
|
|
9049
|
+
function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false) {
|
|
7233
9050
|
if (!manifest.endsWith("package.json")) return false;
|
|
7234
9051
|
const fullPath = join(repoPath, manifest);
|
|
7235
9052
|
if (!isRegularFile(fullPath)) return false;
|
|
@@ -7238,9 +9055,15 @@ function hasSuspiciousDependencies(repoPath, manifest) {
|
|
|
7238
9055
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
7239
9056
|
const devDependencies = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
7240
9057
|
const names = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
|
|
7241
|
-
|
|
9058
|
+
if (names.some(
|
|
7242
9059
|
(name) => /^(todo|fake|placeholder|example)-|does-not-exist| hallucinated/i.test(name)
|
|
7243
|
-
)
|
|
9060
|
+
)) {
|
|
9061
|
+
return true;
|
|
9062
|
+
}
|
|
9063
|
+
if (!useV47Detectors || typeof parsed.name !== "string") return false;
|
|
9064
|
+
const placeholderIdentity = /^(?:example|sample|placeholder|todo|fake)(?:-|$)/i.test(parsed.name);
|
|
9065
|
+
const placeholderMetadata = typeof parsed.author === "string" && /^(?:your name|example|placeholder)$/i.test(parsed.author) || typeof parsed.description === "string" && /^(?:package description|example|placeholder|todo)$/i.test(parsed.description);
|
|
9066
|
+
return placeholderIdentity && placeholderMetadata;
|
|
7244
9067
|
}
|
|
7245
9068
|
function readRecentCommits(repoPath) {
|
|
7246
9069
|
try {
|
|
@@ -7822,7 +9645,12 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
7822
9645
|
);
|
|
7823
9646
|
consumedSignals.push(...summaries);
|
|
7824
9647
|
const finalScore = adjustment === 0 ? scored.score : adjustedScore;
|
|
7825
|
-
const finalStatus =
|
|
9648
|
+
const finalStatus = statusAfterInputAdjustment(
|
|
9649
|
+
scored.status,
|
|
9650
|
+
scored.score,
|
|
9651
|
+
adjustment,
|
|
9652
|
+
parsedInput.rubricVersion
|
|
9653
|
+
);
|
|
7826
9654
|
return {
|
|
7827
9655
|
id,
|
|
7828
9656
|
title: rubricCriterion.title,
|
|
@@ -7855,7 +9683,15 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
7855
9683
|
const overallScore = roundScore(
|
|
7856
9684
|
categoryOrder.reduce((sum, category) => sum + (categoryScoreMap[category] ?? 0), 0) / Math.max(categoryOrder.length, 1)
|
|
7857
9685
|
);
|
|
7858
|
-
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;
|
|
7859
9695
|
return WitanReportSchema.parse({
|
|
7860
9696
|
productSlug: parsedInput.productSlug,
|
|
7861
9697
|
productDisplayName: parsedInput.productDisplayName,
|
|
@@ -7875,7 +9711,7 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
7875
9711
|
// alter it (see classifyRepoArchetype in repo-signals.ts); per-criterion scoring above is
|
|
7876
9712
|
// unaffected by archetype, only presentation layers (badge/terminal/verdict) key off it.
|
|
7877
9713
|
...parsedInput.archetype ? { archetype: parsedInput.archetype } : {},
|
|
7878
|
-
...
|
|
9714
|
+
...insufficientSourceReason ? { insufficientSourceReason } : {}
|
|
7879
9715
|
});
|
|
7880
9716
|
}
|
|
7881
9717
|
function applySignalAdjustment(nativeScore, dimension, signals) {
|
|
@@ -7919,6 +9755,31 @@ function statusForScore(score) {
|
|
|
7919
9755
|
if (score > 0) return "critical";
|
|
7920
9756
|
return "critical";
|
|
7921
9757
|
}
|
|
9758
|
+
function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
|
|
9759
|
+
if (adjustment === 0) return nativeStatus;
|
|
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) {
|
|
9761
|
+
return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
|
|
9762
|
+
}
|
|
9763
|
+
const statusFloor = {
|
|
9764
|
+
verified: 3.5,
|
|
9765
|
+
info: 2.5,
|
|
9766
|
+
warning: 1.5,
|
|
9767
|
+
critical: 0
|
|
9768
|
+
};
|
|
9769
|
+
const severity = {
|
|
9770
|
+
verified: 0,
|
|
9771
|
+
info: 1,
|
|
9772
|
+
warning: 2,
|
|
9773
|
+
critical: 3
|
|
9774
|
+
};
|
|
9775
|
+
const floor = statusFloor[nativeStatus];
|
|
9776
|
+
const nativeSeverity = severity[nativeStatus];
|
|
9777
|
+
if (floor === void 0 || nativeSeverity === void 0) return nativeStatus;
|
|
9778
|
+
const adjustedSemanticStatus = statusForScore(
|
|
9779
|
+
roundScore(Math.max(0, Math.max(nativeScore, floor) - adjustment))
|
|
9780
|
+
);
|
|
9781
|
+
return (severity[adjustedSemanticStatus] ?? nativeSeverity) > nativeSeverity ? adjustedSemanticStatus : nativeStatus;
|
|
9782
|
+
}
|
|
7922
9783
|
function mergeSignalsByCriterion(signals) {
|
|
7923
9784
|
const merged = /* @__PURE__ */ new Map();
|
|
7924
9785
|
for (const signal of signals) {
|
|
@@ -7940,7 +9801,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
7940
9801
|
return merged;
|
|
7941
9802
|
}
|
|
7942
9803
|
function usesMetricScoring(rubricVersion) {
|
|
7943
|
-
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_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;
|
|
7944
9805
|
}
|
|
7945
9806
|
function unmeasuredStatus(rubricVersion) {
|
|
7946
9807
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -7981,13 +9842,31 @@ function scoreCriterion(signal, rubricVersion) {
|
|
|
7981
9842
|
}
|
|
7982
9843
|
if (usesMetricScoring(rubricVersion)) {
|
|
7983
9844
|
const measuredMetrics = metrics.length > 0 ? metrics : fallbackMetricsForEvidence(evidence, findings);
|
|
7984
|
-
const score = capScoreForFindings(
|
|
7985
|
-
|
|
9845
|
+
const score = capScoreForFindings(
|
|
9846
|
+
scoreMetrics(measuredMetrics),
|
|
9847
|
+
findings,
|
|
9848
|
+
rubricVersion,
|
|
9849
|
+
signal.criterionId
|
|
9850
|
+
);
|
|
9851
|
+
const status = statusForScoreAndFindings(
|
|
9852
|
+
signal.criterionId,
|
|
9853
|
+
score,
|
|
9854
|
+
findings,
|
|
9855
|
+
measuredMetrics,
|
|
9856
|
+
rubricVersion
|
|
9857
|
+
);
|
|
7986
9858
|
return {
|
|
7987
9859
|
score,
|
|
7988
9860
|
status,
|
|
7989
9861
|
evidence,
|
|
7990
|
-
findings: ensureFindingsExplainStatus(
|
|
9862
|
+
findings: ensureFindingsExplainStatus(
|
|
9863
|
+
signal.criterionId,
|
|
9864
|
+
score,
|
|
9865
|
+
status,
|
|
9866
|
+
findings,
|
|
9867
|
+
evidence,
|
|
9868
|
+
rubricVersion
|
|
9869
|
+
),
|
|
7991
9870
|
metrics: measuredMetrics
|
|
7992
9871
|
};
|
|
7993
9872
|
}
|
|
@@ -8050,29 +9929,261 @@ function scoreMetrics(metrics) {
|
|
|
8050
9929
|
if (totalWeight === 0) return 0;
|
|
8051
9930
|
return roundScore(weightedTotal / totalWeight * 4);
|
|
8052
9931
|
}
|
|
8053
|
-
function capScoreForFindings(score, findings) {
|
|
8054
|
-
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
|
+
}
|
|
8055
9938
|
if (findings.some((finding) => finding.severity === "warning")) return Math.min(score, 2.4);
|
|
8056
9939
|
if (findings.some((finding) => finding.severity === "info")) return Math.min(score, 3.4);
|
|
8057
9940
|
return score;
|
|
8058
9941
|
}
|
|
8059
|
-
function statusForScoreAndFindings(score, findings) {
|
|
9942
|
+
function statusForScoreAndFindings(criterionId, score, findings, metrics, rubricVersion) {
|
|
8060
9943
|
if (findings.some((finding) => finding.severity === "critical")) return "critical";
|
|
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);
|
|
9946
|
+
if (calibrated && calibrated !== "verified") return calibrated;
|
|
9947
|
+
if (calibrated === "verified" && !findings.some((finding) => finding.severity === "warning")) {
|
|
9948
|
+
return "verified";
|
|
9949
|
+
}
|
|
9950
|
+
}
|
|
8061
9951
|
if (findings.some((finding) => finding.severity === "warning")) return "warning";
|
|
8062
9952
|
if (score >= 3.5 && findings.length === 0) return "verified";
|
|
8063
9953
|
if (score >= 2.5) return "info";
|
|
8064
9954
|
if (score >= 1.5) return "warning";
|
|
8065
9955
|
return "critical";
|
|
8066
9956
|
}
|
|
8067
|
-
function
|
|
8068
|
-
|
|
9957
|
+
function calibratedCriterionStateV8(criterionId, metrics) {
|
|
9958
|
+
const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
|
|
9959
|
+
if (values.size !== metrics.length) return void 0;
|
|
9960
|
+
const value = (name) => values.get(name);
|
|
9961
|
+
const positive = (name) => (value(name) ?? 0) > 0;
|
|
9962
|
+
if (criterionId === "A1") {
|
|
9963
|
+
if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
|
|
9964
|
+
return void 0;
|
|
9965
|
+
}
|
|
9966
|
+
if (!positive("non_hollow_test_share")) return "critical";
|
|
9967
|
+
return positive("verification_script_ratio") ? "verified" : "warning";
|
|
9968
|
+
}
|
|
9969
|
+
if (criterionId === "A2") {
|
|
9970
|
+
if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
|
|
9971
|
+
if (!positive("secret_cleanliness")) return "critical";
|
|
9972
|
+
return (value("env_handling_depth") ?? 0) >= 2 ? "verified" : "warning";
|
|
9973
|
+
}
|
|
9974
|
+
if (criterionId === "A3") {
|
|
9975
|
+
if (!values.has("observability_depth") || !values.has("rollback_safety_depth")) {
|
|
9976
|
+
return void 0;
|
|
9977
|
+
}
|
|
9978
|
+
const coveredSurfaces = Number(positive("observability_depth")) + Number(positive("rollback_safety_depth"));
|
|
9979
|
+
return coveredSurfaces === 2 ? "verified" : coveredSurfaces === 1 ? "warning" : "critical";
|
|
9980
|
+
}
|
|
9981
|
+
if (criterionId === "A4") {
|
|
9982
|
+
if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
|
|
9983
|
+
return void 0;
|
|
9984
|
+
}
|
|
9985
|
+
const dependencyCountSanity = value("dependency_count_sanity");
|
|
9986
|
+
if (dependencyCountSanity !== void 0) {
|
|
9987
|
+
if (dependencyCountSanity <= 0) return "critical";
|
|
9988
|
+
if (dependencyCountSanity < 0.9) return "warning";
|
|
9989
|
+
}
|
|
9990
|
+
const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
|
|
9991
|
+
const hasLockfile = positive("lockfile_coverage");
|
|
9992
|
+
const hasAutomation = positive("dependency_automation_ratio");
|
|
9993
|
+
if (!hasVersionControl && !hasLockfile && !hasAutomation) return "critical";
|
|
9994
|
+
return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
|
|
9995
|
+
}
|
|
9996
|
+
if (criterionId === "B2") {
|
|
9997
|
+
if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
|
|
9998
|
+
return positive("pr_trace_primitives") && positive("pr_merge_ratio") ? "verified" : "warning";
|
|
9999
|
+
}
|
|
10000
|
+
if (criterionId === "B3") {
|
|
10001
|
+
if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
|
|
10002
|
+
const coveredSurfaces = Number(positive("ci_script_depth")) + Number(positive("default_branch_ci_depth"));
|
|
10003
|
+
return coveredSurfaces === 2 ? "verified" : coveredSurfaces === 1 ? "warning" : "critical";
|
|
10004
|
+
}
|
|
10005
|
+
if (criterionId === "B4") {
|
|
10006
|
+
if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
|
|
10007
|
+
return void 0;
|
|
10008
|
+
}
|
|
10009
|
+
if (!positive("audit_artifact_depth")) return "critical";
|
|
10010
|
+
return positive("audit_freshness_depth") ? "verified" : "warning";
|
|
10011
|
+
}
|
|
10012
|
+
if (criterionId === "B6") {
|
|
10013
|
+
if (!values.has("human_gate_documented") || !values.has("fail_closed_privilege_check")) {
|
|
10014
|
+
return void 0;
|
|
10015
|
+
}
|
|
10016
|
+
return positive("human_gate_documented") || positive("fail_closed_privilege_check") ? "verified" : "critical";
|
|
10017
|
+
}
|
|
10018
|
+
return void 0;
|
|
10019
|
+
}
|
|
10020
|
+
function calibratedCriterionStateV9(criterionId, metrics) {
|
|
10021
|
+
const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
|
|
10022
|
+
if (values.size !== metrics.length) return void 0;
|
|
10023
|
+
const value = (name) => values.get(name);
|
|
10024
|
+
const positive = (name) => (value(name) ?? 0) > 0;
|
|
10025
|
+
if (criterionId === "A1") {
|
|
10026
|
+
if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
|
|
10027
|
+
return void 0;
|
|
10028
|
+
}
|
|
10029
|
+
const nonHollowTests = value("non_hollow_test_share") ?? 0;
|
|
10030
|
+
if (nonHollowTests === 0) return "critical";
|
|
10031
|
+
return positive("verification_script_ratio") && nonHollowTests >= 3 ? "verified" : "warning";
|
|
10032
|
+
}
|
|
10033
|
+
if (criterionId === "A2") {
|
|
10034
|
+
if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
|
|
10035
|
+
if (!positive("secret_cleanliness")) return "critical";
|
|
10036
|
+
return (value("env_handling_depth") ?? 0) >= 2 ? "verified" : "warning";
|
|
10037
|
+
}
|
|
10038
|
+
if (criterionId === "A3") {
|
|
10039
|
+
if (!values.has("prod_readiness_primitives") || !values.has("prod_workflow_depth") || !values.has("observability_depth") || !values.has("rollback_safety_depth")) {
|
|
10040
|
+
return void 0;
|
|
10041
|
+
}
|
|
10042
|
+
const observability = value("observability_depth") ?? 0;
|
|
10043
|
+
const rollback = value("rollback_safety_depth") ?? 0;
|
|
10044
|
+
if (observability > 0 && rollback > 0) {
|
|
10045
|
+
return (value("prod_readiness_primitives") ?? 0) >= 4 && observability >= 2 && rollback >= 2 ? "verified" : "warning";
|
|
10046
|
+
}
|
|
10047
|
+
if (observability > 0 || rollback > 0) return "warning";
|
|
10048
|
+
return (value("prod_readiness_primitives") ?? 0) >= 2 && (value("prod_workflow_depth") ?? 0) >= 8 ? "warning" : "critical";
|
|
10049
|
+
}
|
|
10050
|
+
if (criterionId === "A4") {
|
|
10051
|
+
if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
|
|
10052
|
+
return void 0;
|
|
10053
|
+
}
|
|
10054
|
+
const dependencyCountSanity = value("dependency_count_sanity");
|
|
10055
|
+
if (dependencyCountSanity !== void 0) {
|
|
10056
|
+
if (dependencyCountSanity <= 0.25) return "critical";
|
|
10057
|
+
if (dependencyCountSanity < 0.5) return "warning";
|
|
10058
|
+
}
|
|
10059
|
+
const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
|
|
10060
|
+
const hasLockfile = positive("lockfile_coverage");
|
|
10061
|
+
const hasAutomation = positive("dependency_automation_ratio");
|
|
10062
|
+
if (!hasVersionControl && !hasLockfile && !hasAutomation) return "warning";
|
|
10063
|
+
return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
|
|
10064
|
+
}
|
|
10065
|
+
if (criterionId === "B2") {
|
|
10066
|
+
if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
|
|
10067
|
+
const primitives = value("pr_trace_primitives") ?? 0;
|
|
10068
|
+
const mergedOutcomes = value("pr_merge_ratio") ?? 0;
|
|
10069
|
+
if (mergedOutcomes === 0) return primitives <= 2 ? "critical" : "warning";
|
|
10070
|
+
return mergedOutcomes >= 4 ? "verified" : "warning";
|
|
10071
|
+
}
|
|
10072
|
+
if (criterionId === "B3") {
|
|
10073
|
+
if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
|
|
10074
|
+
const scriptDepth = value("ci_script_depth") ?? 0;
|
|
10075
|
+
const branchDepth = value("default_branch_ci_depth") ?? 0;
|
|
10076
|
+
if (scriptDepth === 0 && branchDepth === 0) return "critical";
|
|
10077
|
+
return scriptDepth >= 2 && branchDepth > 0 ? "verified" : "warning";
|
|
10078
|
+
}
|
|
10079
|
+
if (criterionId === "B4") {
|
|
10080
|
+
if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
|
|
10081
|
+
return void 0;
|
|
10082
|
+
}
|
|
10083
|
+
const artifacts = value("audit_artifact_depth") ?? 0;
|
|
10084
|
+
const freshness = value("audit_freshness_depth") ?? 0;
|
|
10085
|
+
if (artifacts === 0) return "critical";
|
|
10086
|
+
return artifacts >= 2 || freshness >= 2 ? "verified" : "warning";
|
|
10087
|
+
}
|
|
10088
|
+
if (criterionId === "B6") {
|
|
10089
|
+
const humanGate = value("human_gate_documented") ?? 0;
|
|
10090
|
+
const failClosed = value("fail_closed_privilege_check") ?? 0;
|
|
10091
|
+
if (humanGate > 0 || failClosed > 0) return "verified";
|
|
10092
|
+
if (positive("privilege_escalation_cleanliness") || positive("protected_path_review_gate")) {
|
|
10093
|
+
return "warning";
|
|
10094
|
+
}
|
|
10095
|
+
if (values.has("human_gate_documented") || values.has("fail_closed_privilege_check") || values.has("privilege_escalation_cleanliness") || values.has("protected_path_review_gate")) {
|
|
10096
|
+
return "critical";
|
|
10097
|
+
}
|
|
10098
|
+
}
|
|
10099
|
+
return void 0;
|
|
10100
|
+
}
|
|
10101
|
+
function calibratedCriterionStateV11(criterionId, metrics) {
|
|
10102
|
+
const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
|
|
10103
|
+
if (values.size !== metrics.length) return void 0;
|
|
10104
|
+
const value = (name) => values.get(name);
|
|
10105
|
+
const positive = (name) => (value(name) ?? 0) > 0;
|
|
10106
|
+
if (criterionId === "A1") {
|
|
10107
|
+
if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
|
|
10108
|
+
return void 0;
|
|
10109
|
+
}
|
|
10110
|
+
if (!positive("non_hollow_test_share")) return "critical";
|
|
10111
|
+
return positive("verification_script_ratio") ? "verified" : "warning";
|
|
10112
|
+
}
|
|
10113
|
+
if (criterionId === "A2") {
|
|
10114
|
+
if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
|
|
10115
|
+
if (!positive("secret_cleanliness")) return "critical";
|
|
10116
|
+
return positive("env_handling_depth") ? "verified" : "warning";
|
|
10117
|
+
}
|
|
10118
|
+
if (criterionId === "A3") {
|
|
10119
|
+
if (!values.has("prod_readiness_primitives") || !values.has("prod_workflow_depth") || !values.has("observability_depth") || !values.has("rollback_safety_depth")) {
|
|
10120
|
+
return void 0;
|
|
10121
|
+
}
|
|
10122
|
+
const primitives = value("prod_readiness_primitives") ?? 0;
|
|
10123
|
+
const workflow = value("prod_workflow_depth") ?? 0;
|
|
10124
|
+
const observability = value("observability_depth") ?? 0;
|
|
10125
|
+
const rollback = value("rollback_safety_depth") ?? 0;
|
|
10126
|
+
if (observability > 0 && rollback > 0) return primitives >= 3 ? "verified" : "warning";
|
|
10127
|
+
return primitives > 0 && workflow > 0 ? "warning" : "critical";
|
|
10128
|
+
}
|
|
10129
|
+
if (criterionId === "A4") {
|
|
10130
|
+
if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
|
|
10131
|
+
return void 0;
|
|
10132
|
+
}
|
|
10133
|
+
const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
|
|
10134
|
+
const hasLockfile = positive("lockfile_coverage");
|
|
10135
|
+
const hasAutomation = positive("dependency_automation_ratio");
|
|
10136
|
+
if (!hasVersionControl && !hasLockfile && !hasAutomation) return "critical";
|
|
10137
|
+
return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
|
|
10138
|
+
}
|
|
10139
|
+
if (criterionId === "B2") {
|
|
10140
|
+
if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
|
|
10141
|
+
if (!positive("pr_trace_primitives")) return "critical";
|
|
10142
|
+
return positive("pr_merge_ratio") ? "verified" : "warning";
|
|
10143
|
+
}
|
|
10144
|
+
if (criterionId === "B3") {
|
|
10145
|
+
if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
|
|
10146
|
+
const covered = Number(positive("ci_script_depth")) + Number(positive("default_branch_ci_depth"));
|
|
10147
|
+
return covered === 2 ? "verified" : covered === 1 ? "warning" : "critical";
|
|
10148
|
+
}
|
|
10149
|
+
if (criterionId === "B4") {
|
|
10150
|
+
if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
|
|
10151
|
+
return void 0;
|
|
10152
|
+
}
|
|
10153
|
+
if (!positive("audit_artifact_depth")) return "critical";
|
|
10154
|
+
return positive("audit_freshness_depth") ? "verified" : "warning";
|
|
10155
|
+
}
|
|
10156
|
+
if (criterionId === "B6") {
|
|
10157
|
+
const humanGate = positive("human_gate_documented");
|
|
10158
|
+
const failClosed = positive("fail_closed_privilege_check");
|
|
10159
|
+
const cleanAndReviewed = positive("privilege_escalation_cleanliness") && positive("protected_path_review_gate");
|
|
10160
|
+
if (humanGate || failClosed || cleanAndReviewed) return "verified";
|
|
10161
|
+
if (positive("privilege_escalation_cleanliness") || positive("protected_path_review_gate")) {
|
|
10162
|
+
return "warning";
|
|
10163
|
+
}
|
|
10164
|
+
if (values.has("human_gate_documented") || values.has("fail_closed_privilege_check") || values.has("privilege_escalation_cleanliness") || values.has("protected_path_review_gate")) {
|
|
10165
|
+
return "critical";
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
return void 0;
|
|
10169
|
+
}
|
|
10170
|
+
function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
|
|
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) {
|
|
10172
|
+
return [...findings];
|
|
10173
|
+
}
|
|
8069
10174
|
if (status !== "critical" && status !== "warning") return [...findings];
|
|
10175
|
+
if (findings.some(
|
|
10176
|
+
({ severity }) => severity === "critical" || status === "warning" && severity === "warning"
|
|
10177
|
+
)) {
|
|
10178
|
+
return [...findings];
|
|
10179
|
+
}
|
|
8070
10180
|
const anchor = evidence[0] ?? {
|
|
8071
10181
|
kind: "repository",
|
|
8072
10182
|
label: `${criterionId} metric-derived score`,
|
|
8073
10183
|
path: "."
|
|
8074
10184
|
};
|
|
8075
10185
|
return [
|
|
10186
|
+
...findings,
|
|
8076
10187
|
{
|
|
8077
10188
|
severity: status,
|
|
8078
10189
|
summary: `${criterionId} metric-derived score is ${score.toFixed(1)}/4.0, in the ${status} band \u2014 no single finding drove this; it reflects the combined metric weighting below.`,
|
|
@@ -8098,7 +10209,8 @@ function scoreRepoWithPublicCejel(options) {
|
|
|
8098
10209
|
productSlug: options.productSlug,
|
|
8099
10210
|
productDisplayName: options.productDisplayName,
|
|
8100
10211
|
repoPath: options.repoPath,
|
|
8101
|
-
...options.generatedAt ? { generatedAt: options.generatedAt } : {}
|
|
10212
|
+
...options.generatedAt ? { generatedAt: options.generatedAt } : {},
|
|
10213
|
+
...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {}
|
|
8102
10214
|
});
|
|
8103
10215
|
const inputSignals = resolvePublicIngestSignals(options);
|
|
8104
10216
|
return createWitanReport(input, inputSignals.length > 0 ? inputSignals : void 0);
|
|
@@ -8163,6 +10275,87 @@ function slugify(raw) {
|
|
|
8163
10275
|
return FALLBACK_SLUG;
|
|
8164
10276
|
}
|
|
8165
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
|
+
|
|
8166
10359
|
// src/summary.ts
|
|
8167
10360
|
var SEVERITY_RANK2 = {
|
|
8168
10361
|
critical: 0,
|
|
@@ -8172,11 +10365,16 @@ var SEVERITY_RANK2 = {
|
|
|
8172
10365
|
var TOP_FINDINGS_LIMIT = 5;
|
|
8173
10366
|
function buildWitanCliSummary(report) {
|
|
8174
10367
|
const allFindings = report.criteria.flatMap(
|
|
8175
|
-
(criterion) => criterion.findings.map((finding) =>
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
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
|
+
})
|
|
8180
10378
|
);
|
|
8181
10379
|
const sorted = [...allFindings].sort(
|
|
8182
10380
|
(a, b) => SEVERITY_RANK2[a.severity] - SEVERITY_RANK2[b.severity]
|
|
@@ -8203,7 +10401,7 @@ function buildWitanCliSummary(report) {
|
|
|
8203
10401
|
overallScore: null,
|
|
8204
10402
|
codeTrustScore: null,
|
|
8205
10403
|
processTrustScore: null,
|
|
8206
|
-
verdict:
|
|
10404
|
+
verdict: renderReportVerdict(report),
|
|
8207
10405
|
insufficientSourceReason: report.insufficientSourceReason
|
|
8208
10406
|
};
|
|
8209
10407
|
}
|
|
@@ -8221,7 +10419,7 @@ function runCejelScan(options) {
|
|
|
8221
10419
|
const identity = deriveProductIdentity(options.repoPath);
|
|
8222
10420
|
const report = scoreRepoWithPublicCejel({
|
|
8223
10421
|
productSlug: identity.productSlug,
|
|
8224
|
-
productDisplayName: identity.productDisplayName,
|
|
10422
|
+
productDisplayName: options.productDisplayName ?? identity.productDisplayName,
|
|
8225
10423
|
repoPath: options.repoPath,
|
|
8226
10424
|
ingestPatterns: options.ingestPatterns,
|
|
8227
10425
|
warnOnEmptyIngestMatch: options.warnOnEmptyIngestMatch
|