@cejel/cejel 0.6.4 → 0.6.6
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 +14 -12
- package/dist/calibration/llm-detector.js +3 -3
- package/dist/{chunk-M6YYPJSN.js → chunk-5PFYOIOS.js} +1 -1
- package/dist/{chunk-HC2WASRS.js → chunk-BG4ZRBQ5.js} +1 -1
- package/dist/{chunk-4YK23CST.js → chunk-JNTTFAHQ.js} +1333 -101
- package/dist/{chunk-63XQDYQ5.js → chunk-VGBFZBZQ.js} +5 -2
- package/dist/index.js +18 -13
- package/dist/mcp/index.js +3 -3
- package/dist/packs/d-series/index.js +2 -2
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
describeGitFailure,
|
|
6
6
|
execGit,
|
|
7
7
|
isExpectedGitAbsence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VGBFZBZQ.js";
|
|
9
9
|
import {
|
|
10
10
|
external_exports
|
|
11
11
|
} from "./chunk-FSMZV3Y6.js";
|
|
@@ -533,6 +533,7 @@ var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
|
|
|
533
533
|
var WITAN_RUBRIC_VERSION_V23 = "witan-rubric-v23-prospective-2026-09-06";
|
|
534
534
|
var WITAN_RUBRIC_VERSION_V24 = "witan-rubric-v24-prospective-2026-09-15";
|
|
535
535
|
var WITAN_RUBRIC_VERSION_V25 = "witan-rubric-v25-prospective-2026-10-12";
|
|
536
|
+
var WITAN_RUBRIC_VERSION_V26 = "witan-rubric-v26-prospective-2026-10-14";
|
|
536
537
|
var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
|
|
537
538
|
var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
538
539
|
WITAN_RUBRIC_VERSION_V18,
|
|
@@ -542,7 +543,8 @@ var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
|
542
543
|
WITAN_RUBRIC_VERSION_V22,
|
|
543
544
|
WITAN_RUBRIC_VERSION_V23,
|
|
544
545
|
WITAN_RUBRIC_VERSION_V24,
|
|
545
|
-
WITAN_RUBRIC_VERSION_V25
|
|
546
|
+
WITAN_RUBRIC_VERSION_V25,
|
|
547
|
+
WITAN_RUBRIC_VERSION_V26
|
|
546
548
|
]);
|
|
547
549
|
var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
|
|
548
550
|
WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
|
|
@@ -568,6 +570,8 @@ var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a meas
|
|
|
568
570
|
var WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the tracked scan-eligible inventory matched no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
569
571
|
var WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the bounded directory walk matched no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
570
572
|
var WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the complete tracked inventory contains no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
573
|
+
var WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE = "Whether this repository's claims are reconciled against its implementation cannot be settled from static evidence; Cejel makes no finding on it.";
|
|
574
|
+
var WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE = "Whether tenant isolation is enforced outside the database cannot be settled from static evidence; Cejel makes no finding on it.";
|
|
571
575
|
function isWitanNoMeasurementAbstention(report) {
|
|
572
576
|
return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
|
|
573
577
|
({ status }) => status === "not_applicable" || status === "insufficient_data"
|
|
@@ -644,15 +648,21 @@ init_esm_shims();
|
|
|
644
648
|
init_esm_shims();
|
|
645
649
|
import { createHash } from "crypto";
|
|
646
650
|
var WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS = Object.freeze({
|
|
647
|
-
[WITAN_RUBRIC_VERSION_V17]: "sha256:
|
|
648
|
-
[WITAN_RUBRIC_VERSION_V18]: "sha256:
|
|
649
|
-
[WITAN_RUBRIC_VERSION_V19]: "sha256:
|
|
650
|
-
[WITAN_RUBRIC_VERSION_V20]: "sha256:
|
|
651
|
-
[WITAN_RUBRIC_VERSION_V21]: "sha256:
|
|
652
|
-
[WITAN_RUBRIC_VERSION_V22]: "sha256:
|
|
653
|
-
[WITAN_RUBRIC_VERSION_V23]: "sha256:
|
|
654
|
-
[WITAN_RUBRIC_VERSION_V24]: "sha256:
|
|
655
|
-
[WITAN_RUBRIC_VERSION_V25]: "sha256:
|
|
651
|
+
[WITAN_RUBRIC_VERSION_V17]: "sha256:5cd48f8c642300ae8736a09d4a24ce3a4f8885f6e099b5f27f99d950e0c944b4",
|
|
652
|
+
[WITAN_RUBRIC_VERSION_V18]: "sha256:4a5d17c84e265ec20cf54216d2e794dec1d299d859c69ac8ef1ab317fc479e2f",
|
|
653
|
+
[WITAN_RUBRIC_VERSION_V19]: "sha256:73dc0282a9ea2a481db8e86a9a5dc227a8b108356c358de029901f2dc38cef79",
|
|
654
|
+
[WITAN_RUBRIC_VERSION_V20]: "sha256:b5f4254a6056ff792d79a2d85892cdf05484eabded85ba13251a2c8fd5ac6336",
|
|
655
|
+
[WITAN_RUBRIC_VERSION_V21]: "sha256:f599d06354adb45af018639594cb6f44e7cb40445b84a0dcdd4438b8d7d069d0",
|
|
656
|
+
[WITAN_RUBRIC_VERSION_V22]: "sha256:34fb6ac5d1f6faf0912aa4d00924837eeee543614d7971247d005312b89928e1",
|
|
657
|
+
[WITAN_RUBRIC_VERSION_V23]: "sha256:ce24235861ce0f0dc4dafd22059a78d31f348e5c741e4056475867c29cc8b814",
|
|
658
|
+
[WITAN_RUBRIC_VERSION_V24]: "sha256:b221907d60d209cd3d57437892ae3dd873f4fa221ad8743954a30c8575d4e77c",
|
|
659
|
+
[WITAN_RUBRIC_VERSION_V25]: "sha256:0f2638a5088f95a610a0e12012442f1067af3e985b955f96ec71853d549693b9",
|
|
660
|
+
// v25's closure plus v26 item 6 (docs/calibration/v26/ruling-item-6.md and its amendment 1).
|
|
661
|
+
// The corpus holds no declared claim-reconciliation artifact, so the v25 A5 finding no longer
|
|
662
|
+
// fires on it and, by amendment 1, A5 is scored without reconciliation_artifact_depth. The A5
|
|
663
|
+
// component and the report components of the specimens whose code-trust score that moves differ
|
|
664
|
+
// from v25's. A2 does not move on this corpus.
|
|
665
|
+
[WITAN_RUBRIC_VERSION_V26]: "sha256:4530317d56fc73eb89287e344ad626e2b9a96908ef12eb9e9c13b839bd599dae"
|
|
656
666
|
});
|
|
657
667
|
function rubricBehaviourFingerprint(rubricVersion) {
|
|
658
668
|
return WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS[rubricVersion];
|
|
@@ -720,7 +730,7 @@ var WITAN_RUBRIC = [
|
|
|
720
730
|
|
|
721
731
|
// src/witan/scoring.ts
|
|
722
732
|
function usesV17DetectorClosure(rubricVersion) {
|
|
723
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
733
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
724
734
|
}
|
|
725
735
|
var MAX_SIGNAL_ADJUSTMENT = 0.8;
|
|
726
736
|
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC, toolVersion) {
|
|
@@ -891,7 +901,7 @@ function statusForScore(score) {
|
|
|
891
901
|
}
|
|
892
902
|
function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
|
|
893
903
|
if (adjustment === 0) return nativeStatus;
|
|
894
|
-
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V25 && rubricVersion !== WITAN_RUBRIC_VERSION_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
904
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V25 && rubricVersion !== WITAN_RUBRIC_VERSION_V26 && rubricVersion !== WITAN_RUBRIC_VERSION_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
895
905
|
return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
|
|
896
906
|
}
|
|
897
907
|
const statusFloor = {
|
|
@@ -948,7 +958,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
948
958
|
return merged;
|
|
949
959
|
}
|
|
950
960
|
function usesMetricScoring(rubricVersion) {
|
|
951
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
961
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
952
962
|
}
|
|
953
963
|
function unmeasuredStatus(rubricVersion) {
|
|
954
964
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -1336,7 +1346,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
|
|
|
1336
1346
|
return void 0;
|
|
1337
1347
|
}
|
|
1338
1348
|
function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
|
|
1339
|
-
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V25 && rubricVersion !== WITAN_RUBRIC_VERSION_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23 && findings.length > 0) {
|
|
1349
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V25 && rubricVersion !== WITAN_RUBRIC_VERSION_V26 && rubricVersion !== WITAN_RUBRIC_VERSION_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23 && findings.length > 0) {
|
|
1340
1350
|
return [...findings];
|
|
1341
1351
|
}
|
|
1342
1352
|
if (status !== "critical" && status !== "warning") return [...findings];
|
|
@@ -1482,7 +1492,7 @@ var CERTIFICATE_GLOSSARY = [
|
|
|
1482
1492
|
{
|
|
1483
1493
|
key: "production-readiness-basic-checks",
|
|
1484
1494
|
term: "Production-readiness basic checks",
|
|
1485
|
-
definition: "A count of six named checks: a build or type-check command, a CI workflow, deployment configuration, an environment template, a health or readiness signal, and an error boundary. In the calibrated rubric the health or readiness signal is credited only for a dedicated health or readiness file (such as health.ts or readiness.ts), a Dockerfile HEALTHCHECK, or Kubernetes liveness or readiness probes. A health route declared inline in an application file does not count there. The prospective witan-rubric-v25 also
|
|
1495
|
+
definition: "A count of six named checks: a build or type-check command, a CI workflow, deployment configuration, an environment template, a health or readiness signal, and an error boundary. In the calibrated rubric the health or readiness signal is credited only for a dedicated health or readiness file (such as health.ts or readiness.ts), a Dockerfile HEALTHCHECK, or Kubernetes liveness or readiness probes. A health route declared inline in an application file does not count there. The prospective witan-rubric-v25 and witan-rubric-v26 also credit such an inline route.",
|
|
1486
1496
|
metricNames: ["prod_readiness_primitives"]
|
|
1487
1497
|
},
|
|
1488
1498
|
{
|
|
@@ -1961,6 +1971,17 @@ function buildWithheldPathsGapSentence(report) {
|
|
|
1961
1971
|
const reportsOnWhatItRead = signals.length === 1 ? "that signal reports on what it read" : "these signals report on what they read";
|
|
1962
1972
|
return `${count} ${entryWord} withheld (${reasonLabel}); ${signalList} would have read ${pronoun}. Under this rubric ${reportsOnWhatItRead}; the prospective v23 rubric abstains instead.`;
|
|
1963
1973
|
}
|
|
1974
|
+
var SETTLING_EVIDENCE_DISCLOSURES = [
|
|
1975
|
+
{ criterionId: "A2", sentence: WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE },
|
|
1976
|
+
{ criterionId: "A5", sentence: WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE }
|
|
1977
|
+
];
|
|
1978
|
+
function settlingEvidenceDisclosures(report) {
|
|
1979
|
+
return SETTLING_EVIDENCE_DISCLOSURES.filter(
|
|
1980
|
+
({ criterionId, sentence }) => report.criteria.some(
|
|
1981
|
+
(criterion) => criterion.id === criterionId && criterion.status !== "not_applicable" && criterion.notes?.includes(sentence) === true
|
|
1982
|
+
)
|
|
1983
|
+
).map(({ sentence }) => sentence);
|
|
1984
|
+
}
|
|
1964
1985
|
function describeEstablishedDimensions(report, measured, applicable) {
|
|
1965
1986
|
const total = report.criteria.length;
|
|
1966
1987
|
const counts = formatMeasuredDimensionCounts(computeMeasuredCoverage(report));
|
|
@@ -2019,6 +2040,7 @@ function buildRelyingPartySummary(report) {
|
|
|
2019
2040
|
`${contentEntries(excludedByExtension, "was", "were")} not read because ${excludedByExtension === 1 ? "its file type is" : "their file types are"} outside what Cejel reads. This is a fixed limit of the tool, not something the repository needs to fix.`
|
|
2020
2041
|
);
|
|
2021
2042
|
}
|
|
2043
|
+
gaps.push(...settlingEvidenceDisclosures(report));
|
|
2022
2044
|
const withheldPathsSentence = buildWithheldPathsGapSentence(report);
|
|
2023
2045
|
if (withheldPathsSentence !== void 0) {
|
|
2024
2046
|
gaps.push(withheldPathsSentence);
|
|
@@ -2895,7 +2917,7 @@ import {
|
|
|
2895
2917
|
realpathSync,
|
|
2896
2918
|
statSync
|
|
2897
2919
|
} from "fs";
|
|
2898
|
-
import { basename, dirname, extname, join, relative, resolve, sep } from "path";
|
|
2920
|
+
import { basename, dirname, extname, join, posix, relative, resolve, sep } from "path";
|
|
2899
2921
|
|
|
2900
2922
|
// src/witan/json-safe.ts
|
|
2901
2923
|
init_esm_shims();
|
|
@@ -2905,7 +2927,7 @@ function stripBom(text) {
|
|
|
2905
2927
|
|
|
2906
2928
|
// src/witan/repo-signals.ts
|
|
2907
2929
|
function usesV17DetectorClosure2(rubricVersion) {
|
|
2908
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
2930
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
2909
2931
|
}
|
|
2910
2932
|
var LEGACY_INVENTORY_ABSENCE_CONTEXT = {
|
|
2911
2933
|
rubricGeneration: "legacy",
|
|
@@ -2920,13 +2942,28 @@ var INVENTORY_SCAN_PATTERN_SETS = {
|
|
|
2920
2942
|
id: "cejel.core-a1.concrete-test-files.v1",
|
|
2921
2943
|
count: 16
|
|
2922
2944
|
},
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2945
|
+
// v1 (10 branches, every rubric but v23) and v2 (14, v23) were retired on 2026-10-14
|
|
2946
|
+
// (goal_cejel_v26_coverage_config_recognition_2026-10-14), when v23's command-flag recognition
|
|
2947
|
+
// and eleven documented configuration locations shipped under every rubric. Branches, counted
|
|
2948
|
+
// the way v2 counted runners (one runner-flag branch however many runners it knows):
|
|
2949
|
+
// configuration (19): nyc/c8/coverage/codecov .json|.yml|.yaml; .coveragerc; lcov.info;
|
|
2950
|
+
// .coveralls.yml; vitest.config coverage with disabled values; jest.config coverage keys;
|
|
2951
|
+
// pyproject [tool.coverage]; package.json nyc/c8/istanbul objects; .codecov.yml|.yaml;
|
|
2952
|
+
// .nycrc family; .c8rc family; setup.cfg/tox.ini [coverage:run|report]; package.json
|
|
2953
|
+
// "jest" coverage keys; vite.config test.coverage; .simplecov; phpunit <coverage>; JaCoCo
|
|
2954
|
+
// in pom.xml; JaCoCo in build.gradle(.kts); tarpaulin.toml.
|
|
2955
|
+
// commands (6): CI run steps; reachable package scripts; reachable Make/just recipes; tracked
|
|
2956
|
+
// shell wrappers; coverage-tool invocation; runner coverage flags.
|
|
2957
|
+
// v4 is those 25. v3 adds the four v17 heuristics v23 replaced rather than kept: a bare
|
|
2958
|
+
// `coverage:` key in vitest.config, a jest.config coverage key of any value, a package script
|
|
2959
|
+
// anywhere invoking nyc/c8/istanbul, and a script named `coverage`.
|
|
2960
|
+
a1CoverageConfigurationV3: {
|
|
2961
|
+
id: "cejel.core-a1.coverage-configuration.v3",
|
|
2962
|
+
count: 29
|
|
2926
2963
|
},
|
|
2927
|
-
|
|
2928
|
-
id: "cejel.core-a1.coverage-configuration.
|
|
2929
|
-
count:
|
|
2964
|
+
a1CoverageConfigurationV4: {
|
|
2965
|
+
id: "cejel.core-a1.coverage-configuration.v4",
|
|
2966
|
+
count: 25
|
|
2930
2967
|
},
|
|
2931
2968
|
a2CurrentSecret: {
|
|
2932
2969
|
id: "cejel.core-a2.current-secret-shape.v1",
|
|
@@ -2947,6 +2984,11 @@ var INVENTORY_SCAN_PATTERN_SETS = {
|
|
|
2947
2984
|
a5ClaimRealityArtifact: {
|
|
2948
2985
|
id: "cejel.core-a5.claim-reality-artifact.v1",
|
|
2949
2986
|
count: 3
|
|
2987
|
+
},
|
|
2988
|
+
// v26 item 6: the one path the repository itself declared, looked up in the inventory.
|
|
2989
|
+
a5DeclaredClaimArtifactPath: {
|
|
2990
|
+
id: "cejel.core-a5.declared-claim-artifact-path.v1",
|
|
2991
|
+
count: 1
|
|
2950
2992
|
}
|
|
2951
2993
|
};
|
|
2952
2994
|
function inventoryScanDerivation(inventoryCount, patternSet, matchCount) {
|
|
@@ -3152,16 +3194,17 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
3152
3194
|
rubricGeneration: usesV17Detectors ? "current" : "legacy",
|
|
3153
3195
|
inventoryProvenance: fileInventory.repoFileInventoryScope === "tracked-scan-eligible" ? "tracked-scan-eligible" : "directory-walk"
|
|
3154
3196
|
};
|
|
3155
|
-
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
3156
|
-
const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
3157
|
-
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
3158
|
-
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
3159
|
-
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
3160
|
-
const
|
|
3197
|
+
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3198
|
+
const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3199
|
+
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3200
|
+
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3201
|
+
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3202
|
+
const usesV23StrictCoverageAndRecipeRunners = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
3161
3203
|
const usesV23PemPrivateKeyGrammar = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
3162
3204
|
const usesV23WithheldPathAbstention = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
3163
3205
|
const usesV24SecretContentContext = rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3164
|
-
const usesV25InlineHealthRoute = rubricVersion === WITAN_RUBRIC_VERSION_V25;
|
|
3206
|
+
const usesV25InlineHealthRoute = rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3207
|
+
const usesV26SettlingEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3165
3208
|
const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
|
|
3166
3209
|
const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure2(rubricVersion) ? applyReadableSourceRepresentationGate(
|
|
3167
3210
|
options.repoPath,
|
|
@@ -3197,11 +3240,12 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
3197
3240
|
usesV20A3ExplicitGaps,
|
|
3198
3241
|
usesV21ExecutedEscalations,
|
|
3199
3242
|
usesV22PackageStartEntrypoint,
|
|
3200
|
-
|
|
3243
|
+
usesV23StrictCoverageAndRecipeRunners,
|
|
3201
3244
|
usesV23PemPrivateKeyGrammar,
|
|
3202
3245
|
usesV23WithheldPathAbstention,
|
|
3203
3246
|
usesV24SecretContentContext,
|
|
3204
3247
|
usesV25InlineHealthRoute,
|
|
3248
|
+
usesV26SettlingEvidence,
|
|
3205
3249
|
reviewableSourceProof,
|
|
3206
3250
|
inventoryAbsenceContext,
|
|
3207
3251
|
scanLimitations
|
|
@@ -3816,7 +3860,7 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
3816
3860
|
notApplicable: true
|
|
3817
3861
|
};
|
|
3818
3862
|
}
|
|
3819
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint,
|
|
3863
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23StrictCoverageAndRecipeRunners, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, useV25InlineHealthRoute, useV26SettlingEvidence, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
3820
3864
|
const signals = [];
|
|
3821
3865
|
const mono = resolveMonorepoContext(repoPath, scanLimitations);
|
|
3822
3866
|
const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
|
|
@@ -3828,7 +3872,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3828
3872
|
useV27Detectors,
|
|
3829
3873
|
useV33Detectors,
|
|
3830
3874
|
useV36Detectors,
|
|
3831
|
-
|
|
3875
|
+
useV23StrictCoverageAndRecipeRunners,
|
|
3832
3876
|
reviewableSourceProof,
|
|
3833
3877
|
inventoryAbsenceContext
|
|
3834
3878
|
)
|
|
@@ -3847,7 +3891,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3847
3891
|
useV24SecretContentContext,
|
|
3848
3892
|
useV18NativeRls,
|
|
3849
3893
|
inventoryAbsenceContext,
|
|
3850
|
-
scanLimitations
|
|
3894
|
+
scanLimitations,
|
|
3895
|
+
useV26SettlingEvidence
|
|
3851
3896
|
)
|
|
3852
3897
|
);
|
|
3853
3898
|
const a3Signal = collectCriterion(
|
|
@@ -3881,7 +3926,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3881
3926
|
repoFiles,
|
|
3882
3927
|
useV27Detectors,
|
|
3883
3928
|
useV23WithheldPathAbstention,
|
|
3884
|
-
inventoryAbsenceContext
|
|
3929
|
+
inventoryAbsenceContext,
|
|
3930
|
+
useV26SettlingEvidence
|
|
3885
3931
|
)
|
|
3886
3932
|
);
|
|
3887
3933
|
const b1Signal = buildNotApplicableSignal(
|
|
@@ -3933,7 +3979,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3933
3979
|
}
|
|
3934
3980
|
return signals;
|
|
3935
3981
|
}
|
|
3936
|
-
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors,
|
|
3982
|
+
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV23StrictCoverageAndRecipeRunners, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
3937
3983
|
const evidence = [];
|
|
3938
3984
|
const findings = [];
|
|
3939
3985
|
const allTestFiles = findConcreteTestFiles(
|
|
@@ -3944,10 +3990,10 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3944
3990
|
const testFiles = allTestFiles.slice(0, 8);
|
|
3945
3991
|
const sourceFiles = repoFiles.filter(isSourceFile);
|
|
3946
3992
|
const runnerFiles = repoFiles.filter(
|
|
3947
|
-
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) ||
|
|
3993
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) || useV23StrictCoverageAndRecipeRunners && isRecipeTestEntry(repoPath, file))
|
|
3948
3994
|
).slice(0, 6);
|
|
3949
3995
|
const baseConfiguredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
|
|
3950
|
-
const configuredRunnerFiles =
|
|
3996
|
+
const configuredRunnerFiles = useV23StrictCoverageAndRecipeRunners ? [
|
|
3951
3997
|
.../* @__PURE__ */ new Set([
|
|
3952
3998
|
...baseConfiguredRunnerFiles,
|
|
3953
3999
|
...repoFiles.filter((file) => isRecipeTestEntry(repoPath, file))
|
|
@@ -3956,11 +4002,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3956
4002
|
const packageJson = findRootPackageJson(repoFiles);
|
|
3957
4003
|
const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
3958
4004
|
const { coverageAnalysis, coveragePercent } = withContentReadSignal("A1", "coverage_percent", () => {
|
|
3959
|
-
const
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
4005
|
+
const commandAnalysis = analyzeCoverageConfiguration(repoPath, repoFiles, useV27Detectors);
|
|
4006
|
+
const analysis = useV23StrictCoverageAndRecipeRunners ? commandAnalysis : unionWithLegacyCoverageRecognition(
|
|
4007
|
+
commandAnalysis,
|
|
4008
|
+
findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors),
|
|
4009
|
+
packageScripts.has("coverage")
|
|
4010
|
+
);
|
|
3964
4011
|
return {
|
|
3965
4012
|
coverageAnalysis: analysis,
|
|
3966
4013
|
coveragePercent: readCoveragePercent(repoPath, analysis.coverageFiles.slice(0, 4))
|
|
@@ -4112,7 +4159,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4112
4159
|
evidence: firstEvidence,
|
|
4113
4160
|
context: inventoryAbsenceContext,
|
|
4114
4161
|
inventoryCount: repoFiles.length,
|
|
4115
|
-
patternSet:
|
|
4162
|
+
patternSet: useV23StrictCoverageAndRecipeRunners ? INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV4 : INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV3,
|
|
4116
4163
|
matchCount: 0
|
|
4117
4164
|
})
|
|
4118
4165
|
);
|
|
@@ -4259,7 +4306,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
|
|
|
4259
4306
|
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files. Verification-script signals (lint/typecheck/coverage/CI commands) are independent of test-file presence and are still credited above where detected.`
|
|
4260
4307
|
};
|
|
4261
4308
|
}
|
|
4262
|
-
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV23PemPrivateKeyGrammar, useV24SecretContentContext, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
|
|
4309
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV23PemPrivateKeyGrammar, useV24SecretContentContext, useV18NativeRls, inventoryAbsenceContext, scanLimitations, useV26SettlingEvidence = false) {
|
|
4263
4310
|
const evidence = [];
|
|
4264
4311
|
const findings = [];
|
|
4265
4312
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
@@ -4545,7 +4592,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
4545
4592
|
evidence: historySecretScan.envPathEvidence
|
|
4546
4593
|
});
|
|
4547
4594
|
}
|
|
4548
|
-
const hasTenantWithoutRlsGap = rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
|
|
4595
|
+
const hasTenantWithoutRlsGap = !useV26SettlingEvidence && rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
|
|
4549
4596
|
if (hasTenantWithoutRlsGap) {
|
|
4550
4597
|
const gapEvidence = rlsMigration ?? tenantScopedFile ?? tenantWithoutRlsPremiseFiles[0];
|
|
4551
4598
|
if (gapEvidence) {
|
|
@@ -4571,6 +4618,28 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
4571
4618
|
);
|
|
4572
4619
|
}
|
|
4573
4620
|
}
|
|
4621
|
+
let tenantIsolationUnsettled = false;
|
|
4622
|
+
if (useV26SettlingEvidence) {
|
|
4623
|
+
const uncoveredTables = withContentReadSignals(
|
|
4624
|
+
"A2",
|
|
4625
|
+
["rls_policy_count", "tenant_scope_ratio"],
|
|
4626
|
+
() => findUncoveredTenantTables(repoPath, repoFiles)
|
|
4627
|
+
);
|
|
4628
|
+
for (const table of uncoveredTables) {
|
|
4629
|
+
findings.push({
|
|
4630
|
+
severity: "warning",
|
|
4631
|
+
summary: `Table \`${table.name}\` carries a tenant column and no row-level-security policy, while this repository enforces tenant isolation with row-level security elsewhere.`,
|
|
4632
|
+
evidence: evidenceForRelativeAtLine(
|
|
4633
|
+
repoPath,
|
|
4634
|
+
table.file,
|
|
4635
|
+
"artifact",
|
|
4636
|
+
"Tenant table without a row-level-security policy",
|
|
4637
|
+
table.line
|
|
4638
|
+
)
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
tenantIsolationUnsettled = uncoveredTables.length === 0 && tenantWithoutRlsPremiseFiles.length > 0;
|
|
4642
|
+
}
|
|
4574
4643
|
evidence.push(...cryptoHygiene.evidence);
|
|
4575
4644
|
findings.push(...cryptoHygiene.findings);
|
|
4576
4645
|
if (evidence.length === 0 && findings.length === 0) return null;
|
|
@@ -4658,7 +4727,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
4658
4727
|
useV18NativeRls ? "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported. Positive multi-tenancy credit is derived only from this repository\u2019s own CREATE POLICY USING/WITH CHECK clauses; no fixed tenant-token vocabulary can create isolation credit. A conservative schema premise is retained only to fail closed when a tenant-shaped schema has zero RLS policies." : "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported.",
|
|
4659
4728
|
...abstainsSecretCleanliness ? [
|
|
4660
4729
|
"Cejel abstained on secret cleanliness: the current-tree scan matched a secret-shaped value it could not classify from content as either a credential or an example. The metric is withheld rather than scored; every other A2 signal is unaffected."
|
|
4661
|
-
] : []
|
|
4730
|
+
] : [],
|
|
4731
|
+
...tenantIsolationUnsettled ? [WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE] : []
|
|
4662
4732
|
].join(" ")
|
|
4663
4733
|
};
|
|
4664
4734
|
}
|
|
@@ -4976,8 +5046,13 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4976
5046
|
}
|
|
4977
5047
|
if (evidence.length === 0) return null;
|
|
4978
5048
|
if (useV20ExplicitGaps) {
|
|
4979
|
-
const
|
|
5049
|
+
const workspaceManifests = packageJson ? findDeclaredWorkspaceManifests(repoPath, repoFiles, packageJson) : NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
5050
|
+
const hasBuildOrTypecheck = scripts.has("build") || scripts.has("typecheck") || workspaceManifests.declared > workspaceManifests.read.length || workspaceManifests.read.some((file) => {
|
|
5051
|
+
const workspaceScripts = readPackageScripts(join(repoPath, file));
|
|
5052
|
+
return workspaceScripts.has("build") || workspaceScripts.has("typecheck");
|
|
5053
|
+
});
|
|
4980
5054
|
if (packageJson && !hasBuildOrTypecheck) {
|
|
5055
|
+
const workspaceRead = workspaceManifests.read.length;
|
|
4981
5056
|
findings.push({
|
|
4982
5057
|
severity: "info",
|
|
4983
5058
|
summary: "A deployable service package manifest declares neither a build nor a typecheck script.",
|
|
@@ -4985,7 +5060,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4985
5060
|
repoPath,
|
|
4986
5061
|
packageJson,
|
|
4987
5062
|
"prod_check",
|
|
4988
|
-
"Deployable service manifest without a build or typecheck script"
|
|
5063
|
+
workspaceRead > 0 ? `Deployable service manifest without a build or typecheck script (${workspaceRead} declared workspace package.json file${workspaceRead === 1 ? "" : "s"} read)` : "Deployable service manifest without a build or typecheck script"
|
|
4989
5064
|
)
|
|
4990
5065
|
});
|
|
4991
5066
|
} else if (runtimeContainer) {
|
|
@@ -5327,7 +5402,7 @@ function hasDeclaredVersionConstraint(dependency) {
|
|
|
5327
5402
|
if (/[~^<>=!]/.test(trimmed)) return true;
|
|
5328
5403
|
return /^v?\d/.test(trimmed);
|
|
5329
5404
|
}
|
|
5330
|
-
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
5405
|
+
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV26SettlingEvidence = false) {
|
|
5331
5406
|
const claimImplementationReads = claimImplementationFileReads(useV27Detectors);
|
|
5332
5407
|
abstainSignalOnWithheldPaths(
|
|
5333
5408
|
"A5",
|
|
@@ -5421,10 +5496,40 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, use
|
|
|
5421
5496
|
)
|
|
5422
5497
|
);
|
|
5423
5498
|
}
|
|
5499
|
+
const claimArtifactDeclarations = useV26SettlingEvidence && reconciliationArtifacts.length === 0 ? withContentReadSignal(
|
|
5500
|
+
"A5",
|
|
5501
|
+
"reconciliation_artifact_depth",
|
|
5502
|
+
() => scanClaimArtifactDeclarations(repoPath, repoFiles)
|
|
5503
|
+
) : void 0;
|
|
5504
|
+
const declaredAbsentArtifact = claimArtifactDeclarations?.firstAbsent;
|
|
5505
|
+
const reconciliationUnsettled = useV26SettlingEvidence && reconciliationArtifacts.length === 0 && declaredAbsentArtifact === void 0;
|
|
5506
|
+
const omitReconciliationArtifactDepth = claimArtifactDeclarations !== void 0 && !claimArtifactDeclarations.declared;
|
|
5424
5507
|
return {
|
|
5425
5508
|
criterionId: "A5",
|
|
5426
5509
|
positiveEvidence: evidence,
|
|
5427
|
-
findings: reconciliationArtifacts.length > 0 ? [] : [
|
|
5510
|
+
findings: reconciliationArtifacts.length > 0 ? [] : useV26SettlingEvidence ? declaredAbsentArtifact === void 0 ? [] : [
|
|
5511
|
+
inventoryAbsenceFinding({
|
|
5512
|
+
// Same severity rule as the finding it replaces: documented negative space is
|
|
5513
|
+
// itself claim-reality discipline.
|
|
5514
|
+
severity: negativeSpaceDoc ? "info" : "warning",
|
|
5515
|
+
summaries: {
|
|
5516
|
+
legacy: `This repository declares a claim-reconciliation artifact at \`${declaredAbsentArtifact.path}\`, and that path is not present in the tracked scan-eligible inventory.`,
|
|
5517
|
+
currentTracked: `This repository declares a claim-reconciliation artifact at \`${declaredAbsentArtifact.path}\`, and that path is not present in the tracked scan-eligible inventory.`,
|
|
5518
|
+
currentDirectoryWalk: `This repository declares a claim-reconciliation artifact at \`${declaredAbsentArtifact.path}\`, and that path is not present in the bounded directory walk.`
|
|
5519
|
+
},
|
|
5520
|
+
evidence: evidenceForRelativeAtLine(
|
|
5521
|
+
repoPath,
|
|
5522
|
+
declaredAbsentArtifact.declaringFile,
|
|
5523
|
+
"claim_reconciliation",
|
|
5524
|
+
"Declares a claim-reconciliation artifact that is absent",
|
|
5525
|
+
declaredAbsentArtifact.line
|
|
5526
|
+
),
|
|
5527
|
+
context: inventoryAbsenceContext,
|
|
5528
|
+
inventoryCount: repoFiles.length,
|
|
5529
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a5DeclaredClaimArtifactPath,
|
|
5530
|
+
matchCount: 0
|
|
5531
|
+
})
|
|
5532
|
+
] : [
|
|
5428
5533
|
inventoryAbsenceFinding({
|
|
5429
5534
|
// Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
|
|
5430
5535
|
// than a curated registry or reconciliation artifact. Downgraded to info when the repo
|
|
@@ -5471,16 +5576,19 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, use
|
|
|
5471
5576
|
"Credits multiple claim-bearing documents without judging unverified prose as truth.",
|
|
5472
5577
|
"saturating_count"
|
|
5473
5578
|
),
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5579
|
+
...omitReconciliationArtifactDepth ? [] : [
|
|
5580
|
+
metric(
|
|
5581
|
+
"reconciliation_artifact_depth",
|
|
5582
|
+
"Reconciliation artifact depth",
|
|
5583
|
+
reconciliationArtifacts.length,
|
|
5584
|
+
3,
|
|
5585
|
+
0.15,
|
|
5586
|
+
"artifacts",
|
|
5587
|
+
"Requires a specific claim-reality artifact to reach full depth."
|
|
5588
|
+
)
|
|
5589
|
+
]
|
|
5590
|
+
],
|
|
5591
|
+
...reconciliationUnsettled ? { notes: WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE } : {}
|
|
5484
5592
|
};
|
|
5485
5593
|
}
|
|
5486
5594
|
function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
@@ -5575,6 +5683,14 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5575
5683
|
}
|
|
5576
5684
|
const unreadWorkflows = unreadCiWorkflowReasons(() => true);
|
|
5577
5685
|
const workflowsRead = workflows.length + rootWorkflows.length;
|
|
5686
|
+
const delegatedSteps = withContentReadSignal(
|
|
5687
|
+
"B3",
|
|
5688
|
+
"default_branch_ci_depth",
|
|
5689
|
+
() => sumDelegatedCiSteps(
|
|
5690
|
+
countDelegatedCiSteps(repoPath, workflows),
|
|
5691
|
+
mono ? countDelegatedCiSteps(mono.root, rootWorkflows) : NO_DELEGATED_CI_STEPS
|
|
5692
|
+
)
|
|
5693
|
+
);
|
|
5578
5694
|
if (workflow) {
|
|
5579
5695
|
withContentReadSignals(
|
|
5580
5696
|
"B3",
|
|
@@ -5584,7 +5700,14 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5584
5700
|
repoPath,
|
|
5585
5701
|
workflow,
|
|
5586
5702
|
"ci_run",
|
|
5587
|
-
ciWorkflowCitationLabel(
|
|
5703
|
+
ciWorkflowCitationLabel(
|
|
5704
|
+
"CI workflow",
|
|
5705
|
+
"read",
|
|
5706
|
+
workflowsRead,
|
|
5707
|
+
1,
|
|
5708
|
+
unreadWorkflows,
|
|
5709
|
+
delegatedSteps
|
|
5710
|
+
)
|
|
5588
5711
|
)
|
|
5589
5712
|
)
|
|
5590
5713
|
);
|
|
@@ -5603,7 +5726,8 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5603
5726
|
"read",
|
|
5604
5727
|
workflowsRead,
|
|
5605
5728
|
1,
|
|
5606
|
-
unreadWorkflows
|
|
5729
|
+
unreadWorkflows,
|
|
5730
|
+
delegatedSteps
|
|
5607
5731
|
)
|
|
5608
5732
|
)
|
|
5609
5733
|
)
|
|
@@ -5617,10 +5741,16 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5617
5741
|
CI_TYPECHECK_COMMAND_PATTERN,
|
|
5618
5742
|
CI_BUILD_COMMAND_PATTERN
|
|
5619
5743
|
];
|
|
5620
|
-
const
|
|
5621
|
-
const
|
|
5622
|
-
|
|
5623
|
-
|
|
5744
|
+
const workspaceManifests = findDeclaredWorkspaceManifests(repoPath, repoFiles, packageJson);
|
|
5745
|
+
const scriptSources = [
|
|
5746
|
+
scripts,
|
|
5747
|
+
...workspaceManifests.read.map((file) => readPackageScripts(join(repoPath, file)))
|
|
5748
|
+
];
|
|
5749
|
+
const declaresScript = (name) => scriptSources.some(
|
|
5750
|
+
(source) => name === "test" ? hasCredibleTestScript(source) : source.has(name)
|
|
5751
|
+
);
|
|
5752
|
+
const rootOnlyScriptsNote = workspaceManifests.read.length > 0 ? workspacePackageScriptsDisclosure(repoFiles, packageJson, workspaceManifests) : rootOnlyPackageScriptsDisclosure(repoFiles, packageJson);
|
|
5753
|
+
const scriptDepth = ["test", "lint", "typecheck", "build"].filter(declaresScript).length;
|
|
5624
5754
|
const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
|
|
5625
5755
|
"B3",
|
|
5626
5756
|
"ci_script_depth",
|
|
@@ -5653,7 +5783,7 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5653
5783
|
components: [
|
|
5654
5784
|
...["test", "lint", "typecheck", "build"].map((name) => ({
|
|
5655
5785
|
label: `package script: ${name === "typecheck" ? "type-check" : name}`,
|
|
5656
|
-
count: (name
|
|
5786
|
+
count: declaresScript(name) ? 1 : 0
|
|
5657
5787
|
})),
|
|
5658
5788
|
...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
|
|
5659
5789
|
label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
@@ -6110,9 +6240,124 @@ function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
|
6110
6240
|
function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
|
|
6111
6241
|
const fullPath = join(repoPath, file);
|
|
6112
6242
|
if (!isRegularFile(fullPath)) return null;
|
|
6113
|
-
const
|
|
6243
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
6244
|
+
const lines = contents.split("\n");
|
|
6114
6245
|
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
|
|
6115
|
-
|
|
6246
|
+
const keyedDigestLine = findKeyedHmacDigestCompareLine(file, contents);
|
|
6247
|
+
if (keyedDigestLine === null) return index === -1 ? null : index + 1;
|
|
6248
|
+
return index === -1 ? keyedDigestLine : Math.min(index + 1, keyedDigestLine);
|
|
6249
|
+
}
|
|
6250
|
+
var KEYED_HMAC_GRAMMARS = [
|
|
6251
|
+
[
|
|
6252
|
+
/\.(?:ts|tsx|js|jsx)$/,
|
|
6253
|
+
{
|
|
6254
|
+
keyed: /\bcreateHmac\s*\(|\bsubtle\s*\.\s*sign\s*\(\s*(?:['"]HMAC['"]|\{\s*name\s*:\s*['"]HMAC['"])/,
|
|
6255
|
+
produces: /\.\s*digest\s*\(|\bsubtle\s*\.\s*sign\s*\(/,
|
|
6256
|
+
objectDigestCall: String.raw`\.\s*digest\s*\(`,
|
|
6257
|
+
equality: "===|!==",
|
|
6258
|
+
hashComments: false
|
|
6259
|
+
}
|
|
6260
|
+
],
|
|
6261
|
+
[
|
|
6262
|
+
/\.py$/,
|
|
6263
|
+
{
|
|
6264
|
+
keyed: /\bhmac\s*\.\s*(?:new|digest)\s*\(/,
|
|
6265
|
+
produces: /\.\s*(?:hex)?digest\s*\(/,
|
|
6266
|
+
objectDigestCall: String.raw`\.\s*(?:hex)?digest\s*\(`,
|
|
6267
|
+
equality: "==|!=",
|
|
6268
|
+
hashComments: true
|
|
6269
|
+
}
|
|
6270
|
+
],
|
|
6271
|
+
[
|
|
6272
|
+
/\.go$/,
|
|
6273
|
+
{
|
|
6274
|
+
keyed: /\bhmac\s*\.\s*New\s*\(/,
|
|
6275
|
+
produces: /\.\s*Sum\s*\(/,
|
|
6276
|
+
objectDigestCall: String.raw`\.\s*Sum\s*\(`,
|
|
6277
|
+
equality: "==|!=",
|
|
6278
|
+
hashComments: false
|
|
6279
|
+
}
|
|
6280
|
+
],
|
|
6281
|
+
[
|
|
6282
|
+
/\.rb$/,
|
|
6283
|
+
{
|
|
6284
|
+
keyed: /\bOpenSSL\s*::\s*HMAC\s*\.\s*(?:new|(?:hex|base64)?digest)\b/,
|
|
6285
|
+
produces: /\.\s*(?:hex|base64)?digest\b/,
|
|
6286
|
+
objectDigestCall: String.raw`\.\s*(?:hex|base64)?digest\b`,
|
|
6287
|
+
equality: "==|!=",
|
|
6288
|
+
hashComments: true
|
|
6289
|
+
}
|
|
6290
|
+
]
|
|
6291
|
+
];
|
|
6292
|
+
var VARIABLE_ASSIGNMENT_PATTERN = /(?<![\w$.])([A-Za-z_$][\w$]*)(?:\s*:\s*[A-Za-z_$][\w$.<>[\]]*)?\s*:?=(?![=>])/g;
|
|
6293
|
+
var ASSIGNED_EXPRESSION_MAX_CHARS = 512;
|
|
6294
|
+
function assignedExpressionEnd(code, start) {
|
|
6295
|
+
const limit = Math.min(code.length, start + ASSIGNED_EXPRESSION_MAX_CHARS);
|
|
6296
|
+
let depth = 0;
|
|
6297
|
+
for (let i = start; i < limit; i += 1) {
|
|
6298
|
+
const ch = code[i];
|
|
6299
|
+
if (ch === "(" || ch === "[" || ch === "{") depth += 1;
|
|
6300
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
6301
|
+
if (depth === 0) return i;
|
|
6302
|
+
depth -= 1;
|
|
6303
|
+
} else if (depth === 0 && (ch === ";" || ch === ",")) return i;
|
|
6304
|
+
else if (depth === 0 && ch === "\n" && !/^\s*\./.test(code.slice(i + 1, i + 65))) return i;
|
|
6305
|
+
}
|
|
6306
|
+
return limit;
|
|
6307
|
+
}
|
|
6308
|
+
var escapeIdentifier = (identifier) => identifier.replace(/\$/g, "\\$");
|
|
6309
|
+
function findKeyedHmacDigestCompareLine(file, contents) {
|
|
6310
|
+
const grammar = KEYED_HMAC_GRAMMARS.find(([extension]) => extension.test(file))?.[1];
|
|
6311
|
+
if (!grammar || !/digest/i.test(contents) || !grammar.produces.test(contents)) return null;
|
|
6312
|
+
const { commentFree, code } = maskCommentsAndStrings(contents, grammar.hashComments);
|
|
6313
|
+
const startsInCode = (pattern, from, to) => {
|
|
6314
|
+
for (const match of commentFree.slice(from, to).matchAll(new RegExp(pattern.source, "g"))) {
|
|
6315
|
+
if (code[from + match.index] === commentFree[from + match.index]) return true;
|
|
6316
|
+
}
|
|
6317
|
+
return false;
|
|
6318
|
+
};
|
|
6319
|
+
const boundAt = /* @__PURE__ */ new Map();
|
|
6320
|
+
const hmacValues = [];
|
|
6321
|
+
const hmacObjects = [];
|
|
6322
|
+
for (const assignment of code.matchAll(VARIABLE_ASSIGNMENT_PATTERN)) {
|
|
6323
|
+
const name = assignment[1];
|
|
6324
|
+
const start = assignment.index + assignment[0].length;
|
|
6325
|
+
const end = assignedExpressionEnd(code, start);
|
|
6326
|
+
const keyed = startsInCode(grammar.keyed, start, end);
|
|
6327
|
+
let bound = false;
|
|
6328
|
+
if (keyed && startsInCode(grammar.produces, start, end)) {
|
|
6329
|
+
hmacValues.push(name);
|
|
6330
|
+
bound = true;
|
|
6331
|
+
} else if (keyed) {
|
|
6332
|
+
hmacObjects.push(name);
|
|
6333
|
+
} else {
|
|
6334
|
+
bound = hmacValues.some(
|
|
6335
|
+
(value) => startsInCode(new RegExp(`(?<![\\w$.])${escapeIdentifier(value)}(?![\\w$])`), start, end)
|
|
6336
|
+
) || hmacObjects.some(
|
|
6337
|
+
(object) => startsInCode(
|
|
6338
|
+
new RegExp(`(?<![\\w$.])${escapeIdentifier(object)}\\s*${grammar.objectDigestCall}`),
|
|
6339
|
+
start,
|
|
6340
|
+
end
|
|
6341
|
+
)
|
|
6342
|
+
);
|
|
6343
|
+
}
|
|
6344
|
+
if (bound && !boundAt.has(name)) boundAt.set(name, assignment.index);
|
|
6345
|
+
}
|
|
6346
|
+
if (boundAt.size === 0) return null;
|
|
6347
|
+
const comparison = new RegExp(
|
|
6348
|
+
`(?<![\\w$.])([A-Za-z_$][\\w$]*)\\s*(?:${grammar.equality})(?!=)\\s*(?!['"\`]|undefined\\b|null\\b|None\\b|nil\\b)([\\w.]+(?:\\s*[-+]\\s*\\d+)?)`,
|
|
6349
|
+
"g"
|
|
6350
|
+
);
|
|
6351
|
+
for (const match of commentFree.matchAll(comparison)) {
|
|
6352
|
+
const at = match.index;
|
|
6353
|
+
const name = match[1];
|
|
6354
|
+
if (code[at] !== commentFree[at] || !/digest/i.test(name)) continue;
|
|
6355
|
+
const assignedAt = boundAt.get(name);
|
|
6356
|
+
if (assignedAt === void 0 || assignedAt > at) continue;
|
|
6357
|
+
if (NUMERIC_SHAPED_OPERAND_PATTERN.test(match[2].trim())) continue;
|
|
6358
|
+
return contents.slice(0, at).split("\n").length;
|
|
6359
|
+
}
|
|
6360
|
+
return null;
|
|
6116
6361
|
}
|
|
6117
6362
|
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
6118
6363
|
const fullPath = join(repoPath, file);
|
|
@@ -6566,6 +6811,91 @@ function hasLiveAvaAssertion(contents) {
|
|
|
6566
6811
|
}
|
|
6567
6812
|
return false;
|
|
6568
6813
|
}
|
|
6814
|
+
var DOTNET_ASSERTION_PATTERN = /\b(?:Classic|Collection|String)?Assert\.[A-Z]\w*\s*(?:<[^<>()]*>\s*)?\(/;
|
|
6815
|
+
var XCTEST_ASSERTION_PATTERN = /\bXCT(?:Assert\w*|Fail|Unwrap)\s*\(/;
|
|
6816
|
+
var SCALATEST_IMPORT_PATTERN = /\bimport\s+org\.scalatest\b/;
|
|
6817
|
+
var SCALATEST_ASSERTION_PATTERN = /\b(?:assert|assertResult)\s*\(|\b(?:assertThrows|intercept)\s*\[/;
|
|
6818
|
+
var SCALATEST_MATCHERS_IMPORT_PATTERN = /\bimport\s+org\.scalatest\.(?:matchers\b|(?:Should|Must)?Matchers\b|_|\*|\{[^}]*\b(?:Should|Must)?Matchers\b)/;
|
|
6819
|
+
var SCALATEST_MATCHER_PATTERN = /\b(?:should|must)(?:Be|Equal)\b|\b(?:should|must)\s+(?:not\s+)?(?:be|equal|have|contain|include|startWith|endWith|matchPattern)\b|\b(?:should|must)\s*===/;
|
|
6820
|
+
var KOTEST_MATCHERS_IMPORT_PATTERN = /\bimport\s+io\.kotest\.(?:matchers|assertions)\b/;
|
|
6821
|
+
var KOTEST_MATCHER_PATTERN = /\bshould[A-Z]\w*\b/;
|
|
6822
|
+
var TAPE_OR_TAP_BINDING_PATTERN = /\bfrom\s*['"]tape?['"]|\brequire\s*\(\s*['"]tape?['"]\s*\)/g;
|
|
6823
|
+
var TAPE_OR_TAP_ASSERTION_PATTERN = /\bt\.(?:ok|notOk|notok|true|false|assert|error|ifError|ifErr|iferror|equal|equals|isEqual|strictEqual|strictEquals|is|notEqual|notEquals|isNotEqual|doesNotEqual|isInequal|notStrictEqual|notStrictEquals|isNot|not|looseEqual|looseEquals|notLooseEqual|notLooseEquals|deepEqual|deepEquals|isEquivalent|same|notDeepEqual|notDeepEquals|notEquivalent|notDeeply|notSame|deepLooseEqual|notDeepLooseEqual|strictSame|strictNotSame|has|notHas|hasStrict|notHasStrict|match|notMatch|doesNotMatch|matchOnly|notMatchOnly|matchStrict|notMatchStrict|matchOnlyStrict|notMatchOnlyStrict|type|throws|doesNotThrow|rejects|resolves|resolveMatch|emits|pass|fail)\s*\(/;
|
|
6824
|
+
var DOTNET_IGNORED_CLASS_PATTERN = /\[[^[\]]*\b(?:NUnit\.Framework\.|Microsoft\.VisualStudio\.TestTools\.UnitTesting\.)?Ignore(?:Attribute)?\b[^[\]]*\](?:\s*\[[^[\]]*\])*\s*(?:(?:public|internal|private|protected|sealed|abstract|static|partial|file)\s+)*class\b/;
|
|
6825
|
+
var SCALATEST_IGNORED_CLASS_PATTERN = /@(?:org\.scalatest\.)?Ignore\b(?:\s*@[\w.]+(?:\([^)]*\))?)*\s*(?:(?:final|abstract|sealed|private|protected)\s+)*class\b/;
|
|
6826
|
+
var KOTEST_IMPORT_PATTERN = /\bimport\s+io\.kotest\b/;
|
|
6827
|
+
var KOTEST_IGNORED_CLASS_PATTERN = /@(?:io\.kotest\.core\.annotation\.)?Ignored\b(?:\s*@[\w.]+(?:\([^)]*\))?)*\s*(?:(?:public|internal|private|open|abstract)\s+)*class\b/;
|
|
6828
|
+
var SUITE_SKIP_NAME_SHAPED_TEST_FILE_PATTERN = /Tests?\.cs$|(?:Spec|Test|Suite)\.scala$/;
|
|
6829
|
+
function maskCommentsAndStrings(contents, hashComments = false) {
|
|
6830
|
+
const commentFree = contents.split("");
|
|
6831
|
+
const code = contents.split("");
|
|
6832
|
+
const blank = (target, from, to) => {
|
|
6833
|
+
for (let i2 = from; i2 < to; i2 += 1) if (target[i2] !== "\n") target[i2] = " ";
|
|
6834
|
+
};
|
|
6835
|
+
let i = 0;
|
|
6836
|
+
while (i < contents.length) {
|
|
6837
|
+
const ch = contents[i];
|
|
6838
|
+
const next = contents[i + 1];
|
|
6839
|
+
let end = i + 1;
|
|
6840
|
+
if (hashComments ? ch === "#" : ch === "/" && next === "/") {
|
|
6841
|
+
end = contents.indexOf("\n", i);
|
|
6842
|
+
if (end < 0) end = contents.length;
|
|
6843
|
+
blank(commentFree, i, end);
|
|
6844
|
+
blank(code, i, end);
|
|
6845
|
+
} else if (!hashComments && ch === "/" && next === "*") {
|
|
6846
|
+
let depth = 1;
|
|
6847
|
+
end = i + 2;
|
|
6848
|
+
while (end < contents.length && depth > 0) {
|
|
6849
|
+
if (contents.startsWith("/*", end)) {
|
|
6850
|
+
depth += 1;
|
|
6851
|
+
end += 2;
|
|
6852
|
+
} else if (contents.startsWith("*/", end)) {
|
|
6853
|
+
depth -= 1;
|
|
6854
|
+
end += 2;
|
|
6855
|
+
} else end += 1;
|
|
6856
|
+
}
|
|
6857
|
+
blank(commentFree, i, end);
|
|
6858
|
+
blank(code, i, end);
|
|
6859
|
+
} else if (contents.startsWith('"""', i) || hashComments && contents.startsWith("'''", i)) {
|
|
6860
|
+
const close = contents.indexOf(contents.slice(i, i + 3), i + 3);
|
|
6861
|
+
end = close < 0 ? contents.length : close + 3;
|
|
6862
|
+
blank(code, i, end);
|
|
6863
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
6864
|
+
const verbatim = ch === '"' && contents[i - 1] === "@";
|
|
6865
|
+
end = i + 1;
|
|
6866
|
+
while (end < contents.length) {
|
|
6867
|
+
const c = contents[end];
|
|
6868
|
+
if (verbatim && c === '"' && contents[end + 1] === '"') {
|
|
6869
|
+
end += 2;
|
|
6870
|
+
continue;
|
|
6871
|
+
}
|
|
6872
|
+
if (!verbatim && c === "\\") {
|
|
6873
|
+
end += 2;
|
|
6874
|
+
continue;
|
|
6875
|
+
}
|
|
6876
|
+
end += 1;
|
|
6877
|
+
if (c === ch) break;
|
|
6878
|
+
}
|
|
6879
|
+
blank(code, i, end);
|
|
6880
|
+
}
|
|
6881
|
+
i = Math.max(end, i + 1);
|
|
6882
|
+
}
|
|
6883
|
+
return { commentFree: commentFree.join(""), code: code.join("") };
|
|
6884
|
+
}
|
|
6885
|
+
function hasLiveFrameworkAssertion({ commentFree, code }) {
|
|
6886
|
+
const body = code.replace(/^[ \t]*import\b.*$/gm, (line) => " ".repeat(line.length));
|
|
6887
|
+
if (DOTNET_ASSERTION_PATTERN.test(body) || XCTEST_ASSERTION_PATTERN.test(body)) return true;
|
|
6888
|
+
if (SCALATEST_IMPORT_PATTERN.test(code) && SCALATEST_ASSERTION_PATTERN.test(body)) return true;
|
|
6889
|
+
if (SCALATEST_MATCHERS_IMPORT_PATTERN.test(code) && SCALATEST_MATCHER_PATTERN.test(body)) return true;
|
|
6890
|
+
if (KOTEST_MATCHERS_IMPORT_PATTERN.test(code) && KOTEST_MATCHER_PATTERN.test(body)) return true;
|
|
6891
|
+
const tapeBound = [...commentFree.matchAll(TAPE_OR_TAP_BINDING_PATTERN)].some(
|
|
6892
|
+
(binding) => code[binding.index] === commentFree[binding.index]
|
|
6893
|
+
);
|
|
6894
|
+
return tapeBound && TAPE_OR_TAP_ASSERTION_PATTERN.test(body);
|
|
6895
|
+
}
|
|
6896
|
+
function hasFrameworkSuiteLevelSkip({ code }) {
|
|
6897
|
+
return DOTNET_IGNORED_CLASS_PATTERN.test(code) || SCALATEST_IMPORT_PATTERN.test(code) && SCALATEST_IGNORED_CLASS_PATTERN.test(code) || KOTEST_IMPORT_PATTERN.test(code) && KOTEST_IGNORED_CLASS_PATTERN.test(code);
|
|
6898
|
+
}
|
|
6569
6899
|
function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
6570
6900
|
let nonHollowCount = 0;
|
|
6571
6901
|
let ratedCount = 0;
|
|
@@ -6573,11 +6903,14 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
|
6573
6903
|
const fullPath = join(repoPath, file);
|
|
6574
6904
|
if (!isRegularFile(fullPath)) continue;
|
|
6575
6905
|
const contents = readRepoText(fullPath, "utf8");
|
|
6576
|
-
|
|
6906
|
+
let maskedSource;
|
|
6907
|
+
const masked = () => maskedSource ??= maskCommentsAndStrings(contents);
|
|
6908
|
+
const frameworkSuiteSkipped = /\bIgnored?\b/.test(contents) && hasFrameworkSuiteLevelSkip(masked());
|
|
6909
|
+
const nonHollow = (JS_TEST_ASSERTION_PATTERN.test(contents) || CPP_TEST_ASSERTION_PATTERN.test(contents) || MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN.test(contents) || hasLiveAvaAssertion(contents) || hasLiveFrameworkAssertion(masked())) && !SUITE_LEVEL_SKIP_PATTERN.test(contents) && !frameworkSuiteSkipped;
|
|
6577
6910
|
if (nonHollow) {
|
|
6578
6911
|
nonHollowCount += 1;
|
|
6579
6912
|
ratedCount += 1;
|
|
6580
|
-
} else if (NAME_SHAPED_TEST_FILE_PATTERN.test(file) || useV17Detectors && NAME_SHAPED_TEST_FILE_PATTERN_V17.test(file)) {
|
|
6913
|
+
} else if (NAME_SHAPED_TEST_FILE_PATTERN.test(file) || useV17Detectors && NAME_SHAPED_TEST_FILE_PATTERN_V17.test(file) || frameworkSuiteSkipped && SUITE_SKIP_NAME_SHAPED_TEST_FILE_PATTERN.test(file)) {
|
|
6581
6914
|
ratedCount += 1;
|
|
6582
6915
|
}
|
|
6583
6916
|
}
|
|
@@ -7084,6 +7417,354 @@ function deriveRlsPolicyScopeInventory(repoPath, repoFiles) {
|
|
|
7084
7417
|
storageFiles
|
|
7085
7418
|
};
|
|
7086
7419
|
}
|
|
7420
|
+
var DATA_LAYER_PATH_PATTERN = /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i;
|
|
7421
|
+
var RLS_STATEMENT_PHRASE_PATTERN = /\b(?:enable|disable)\s+row\s+level\s+security\b|\bcreate\s+(?:or\s+replace\s+)?policy\b/i;
|
|
7422
|
+
var SQL_TABLE_REMOVAL_PHRASE_PATTERN = /\bdrop\s+table\b|\brename\s+to\b/i;
|
|
7423
|
+
var SQL_CREATE_TABLE_HEAD = /^create\s+(?:(?:global|local)\s+)?(?:(?:temporary|temp|unlogged)\s+)?table\s+(?:if\s+not\s+exists\s+)?/i;
|
|
7424
|
+
var SQL_ALTER_TABLE_HEAD = /^alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?/i;
|
|
7425
|
+
var SQL_DROP_TABLE_HEAD = /^drop\s+table\s+(?:if\s+exists\s+)?/i;
|
|
7426
|
+
var SQL_RENAME_TO_CLAUSE = /^\s+rename\s+to\s+/i;
|
|
7427
|
+
var SQL_CREATE_POLICY_HEAD = /^create\s+(?:or\s+replace\s+)?policy\s+/i;
|
|
7428
|
+
var SQL_RLS_TOGGLE_PATTERN = /\b(enable|disable)\s+row\s+level\s+security\b/gi;
|
|
7429
|
+
var SQL_PLAIN_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
7430
|
+
var SQL_TABLE_CONSTRAINT_PATTERN = /^(?:constraint|primary|foreign|unique|check|exclude|like)\b/i;
|
|
7431
|
+
var MAX_UNCOVERED_TENANT_TABLES = 3;
|
|
7432
|
+
function compareMigrationPaths(left, right) {
|
|
7433
|
+
const leftChunks = left.match(/\d+|\D+/g) ?? [];
|
|
7434
|
+
const rightChunks = right.match(/\d+|\D+/g) ?? [];
|
|
7435
|
+
for (let index = 0; index < Math.min(leftChunks.length, rightChunks.length); index += 1) {
|
|
7436
|
+
const a = leftChunks[index];
|
|
7437
|
+
const b = rightChunks[index];
|
|
7438
|
+
if (a === b) continue;
|
|
7439
|
+
if (/^\d/.test(a) && /^\d/.test(b)) {
|
|
7440
|
+
const x = a.replace(/^0+(?=\d)/, "");
|
|
7441
|
+
const y = b.replace(/^0+(?=\d)/, "");
|
|
7442
|
+
if (x.length !== y.length) return x.length - y.length;
|
|
7443
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
7444
|
+
return a.length - b.length;
|
|
7445
|
+
}
|
|
7446
|
+
return a < b ? -1 : 1;
|
|
7447
|
+
}
|
|
7448
|
+
return leftChunks.length - rightChunks.length;
|
|
7449
|
+
}
|
|
7450
|
+
function blankSqlNonCode(text) {
|
|
7451
|
+
const out = text.split("");
|
|
7452
|
+
let dynamic = false;
|
|
7453
|
+
const blank = (from, to) => {
|
|
7454
|
+
for (let index2 = from; index2 < to; index2 += 1) {
|
|
7455
|
+
if (out[index2] !== "\n" && out[index2] !== "\r") out[index2] = " ";
|
|
7456
|
+
}
|
|
7457
|
+
};
|
|
7458
|
+
const noteBody = (from, to) => {
|
|
7459
|
+
const body = text.slice(from, to);
|
|
7460
|
+
if (RLS_STATEMENT_PHRASE_PATTERN.test(body) || SQL_TABLE_REMOVAL_PHRASE_PATTERN.test(body)) {
|
|
7461
|
+
dynamic = true;
|
|
7462
|
+
}
|
|
7463
|
+
};
|
|
7464
|
+
let index = 0;
|
|
7465
|
+
while (index < text.length) {
|
|
7466
|
+
const character = text[index];
|
|
7467
|
+
const next = text[index + 1];
|
|
7468
|
+
if (character === "-" && next === "-") {
|
|
7469
|
+
const newline = text.indexOf("\n", index);
|
|
7470
|
+
const end = newline === -1 ? text.length : newline;
|
|
7471
|
+
blank(index, end);
|
|
7472
|
+
index = end;
|
|
7473
|
+
} else if (character === "/" && next === "*") {
|
|
7474
|
+
let depth = 1;
|
|
7475
|
+
let cursor = index + 2;
|
|
7476
|
+
while (cursor < text.length && depth > 0) {
|
|
7477
|
+
if (text[cursor] === "/" && text[cursor + 1] === "*") {
|
|
7478
|
+
depth += 1;
|
|
7479
|
+
cursor += 2;
|
|
7480
|
+
} else if (text[cursor] === "*" && text[cursor + 1] === "/") {
|
|
7481
|
+
depth -= 1;
|
|
7482
|
+
cursor += 2;
|
|
7483
|
+
} else {
|
|
7484
|
+
cursor += 1;
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
blank(index, cursor);
|
|
7488
|
+
index = cursor;
|
|
7489
|
+
} else if (character === "'") {
|
|
7490
|
+
const escapes = index > 0 && /[Ee]/.test(text[index - 1] ?? "") && !/[A-Za-z0-9_$]/.test(text[index - 2] ?? "");
|
|
7491
|
+
let cursor = index + 1;
|
|
7492
|
+
while (cursor < text.length) {
|
|
7493
|
+
if (escapes && text[cursor] === "\\") {
|
|
7494
|
+
cursor += 2;
|
|
7495
|
+
} else if (text[cursor] === "'") {
|
|
7496
|
+
if (text[cursor + 1] !== "'") break;
|
|
7497
|
+
cursor += 2;
|
|
7498
|
+
} else {
|
|
7499
|
+
cursor += 1;
|
|
7500
|
+
}
|
|
7501
|
+
}
|
|
7502
|
+
noteBody(index + 1, cursor);
|
|
7503
|
+
blank(index + 1, cursor);
|
|
7504
|
+
index = cursor + 1;
|
|
7505
|
+
} else if (character === '"') {
|
|
7506
|
+
let cursor = index + 1;
|
|
7507
|
+
while (cursor < text.length) {
|
|
7508
|
+
if (text[cursor] === '"') {
|
|
7509
|
+
if (text[cursor + 1] !== '"') break;
|
|
7510
|
+
cursor += 2;
|
|
7511
|
+
} else {
|
|
7512
|
+
cursor += 1;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
index = cursor + 1;
|
|
7516
|
+
} else if (character === "$" && !/[A-Za-z0-9_$]/.test(text[index - 1] ?? "")) {
|
|
7517
|
+
const tag = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(text.slice(index, index + 66))?.[0];
|
|
7518
|
+
if (tag === void 0) {
|
|
7519
|
+
index += 1;
|
|
7520
|
+
continue;
|
|
7521
|
+
}
|
|
7522
|
+
const close = text.indexOf(tag, index + tag.length);
|
|
7523
|
+
const end = close === -1 ? text.length : close;
|
|
7524
|
+
noteBody(index + tag.length, end);
|
|
7525
|
+
blank(index + tag.length, end);
|
|
7526
|
+
index = close === -1 ? text.length : close + tag.length;
|
|
7527
|
+
} else {
|
|
7528
|
+
index += 1;
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
return { code: out.join(""), dynamic };
|
|
7532
|
+
}
|
|
7533
|
+
function splitSqlStatements(code) {
|
|
7534
|
+
const statements = [];
|
|
7535
|
+
let start = 0;
|
|
7536
|
+
let quoted = false;
|
|
7537
|
+
for (let index = 0; index <= code.length; index += 1) {
|
|
7538
|
+
const character = code[index];
|
|
7539
|
+
if (character === '"') quoted = !quoted;
|
|
7540
|
+
if (index === code.length || character === ";" && !quoted) {
|
|
7541
|
+
const raw = code.slice(start, index);
|
|
7542
|
+
const lead = raw.length - raw.trimStart().length;
|
|
7543
|
+
if (raw.trim() !== "") statements.push({ text: raw.slice(lead), start: start + lead });
|
|
7544
|
+
start = index + 1;
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
return statements;
|
|
7548
|
+
}
|
|
7549
|
+
function readSqlIdentifier(text, from) {
|
|
7550
|
+
let index = from;
|
|
7551
|
+
while (/\s/.test(text[index] ?? "")) index += 1;
|
|
7552
|
+
if (text[index] === '"') {
|
|
7553
|
+
let cursor = index + 1;
|
|
7554
|
+
while (cursor < text.length) {
|
|
7555
|
+
if (text[cursor] === '"') {
|
|
7556
|
+
if (text[cursor + 1] !== '"') break;
|
|
7557
|
+
cursor += 2;
|
|
7558
|
+
} else {
|
|
7559
|
+
cursor += 1;
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
if (cursor >= text.length) return null;
|
|
7563
|
+
const inner = text.slice(index + 1, cursor).replace(/""/g, '"');
|
|
7564
|
+
return { value: SQL_PLAIN_IDENTIFIER_PATTERN.test(inner) ? inner : null, end: cursor + 1 };
|
|
7565
|
+
}
|
|
7566
|
+
const bare = /^[^\s(),;.]+/.exec(text.slice(index))?.[0];
|
|
7567
|
+
if (bare === void 0) return null;
|
|
7568
|
+
return { value: SQL_PLAIN_IDENTIFIER_PATTERN.test(bare) ? bare : null, end: index + bare.length };
|
|
7569
|
+
}
|
|
7570
|
+
function readSqlTableName(text, from) {
|
|
7571
|
+
const segments = [];
|
|
7572
|
+
let normalisable = true;
|
|
7573
|
+
let index = from;
|
|
7574
|
+
for (; ; ) {
|
|
7575
|
+
const identifier = readSqlIdentifier(text, index);
|
|
7576
|
+
if (identifier === null) return { segments: null, end: index };
|
|
7577
|
+
if (identifier.value === null) normalisable = false;
|
|
7578
|
+
else segments.push(identifier.value.toLowerCase());
|
|
7579
|
+
index = identifier.end;
|
|
7580
|
+
let cursor = index;
|
|
7581
|
+
while (/\s/.test(text[cursor] ?? "")) cursor += 1;
|
|
7582
|
+
if (text[cursor] !== ".") break;
|
|
7583
|
+
index = cursor + 1;
|
|
7584
|
+
}
|
|
7585
|
+
return {
|
|
7586
|
+
segments: normalisable && segments.length >= 1 && segments.length <= 2 ? segments : null,
|
|
7587
|
+
end: index
|
|
7588
|
+
};
|
|
7589
|
+
}
|
|
7590
|
+
function sqlColumnListHasTenantColumn(text, open) {
|
|
7591
|
+
let depth = 0;
|
|
7592
|
+
let quoted = false;
|
|
7593
|
+
let elementStart = open + 1;
|
|
7594
|
+
const elements = [];
|
|
7595
|
+
for (let index = open; index < text.length; index += 1) {
|
|
7596
|
+
const character = text[index];
|
|
7597
|
+
if (character === '"') quoted = !quoted;
|
|
7598
|
+
if (quoted) continue;
|
|
7599
|
+
if (character === "(") depth += 1;
|
|
7600
|
+
else if (character === ")") {
|
|
7601
|
+
depth -= 1;
|
|
7602
|
+
if (depth === 0) {
|
|
7603
|
+
elements.push(text.slice(elementStart, index));
|
|
7604
|
+
break;
|
|
7605
|
+
}
|
|
7606
|
+
} else if (character === "," && depth === 1) {
|
|
7607
|
+
elements.push(text.slice(elementStart, index));
|
|
7608
|
+
elementStart = index + 1;
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7611
|
+
return elements.some((element) => {
|
|
7612
|
+
const trimmed = element.trim();
|
|
7613
|
+
if (trimmed === "" || SQL_TABLE_CONSTRAINT_PATTERN.test(trimmed)) return false;
|
|
7614
|
+
const column = readSqlIdentifier(trimmed, 0)?.value;
|
|
7615
|
+
return column != null && TENANT_SCOPE_PATTERN_V11.test(column);
|
|
7616
|
+
});
|
|
7617
|
+
}
|
|
7618
|
+
function lineAtOffset(text, offset) {
|
|
7619
|
+
let line = 1;
|
|
7620
|
+
for (let index = 0; index < offset && index < text.length; index += 1) {
|
|
7621
|
+
if (text[index] === "\n") line += 1;
|
|
7622
|
+
}
|
|
7623
|
+
return line;
|
|
7624
|
+
}
|
|
7625
|
+
function findUncoveredTenantTables(repoPath, repoFiles) {
|
|
7626
|
+
const events = [];
|
|
7627
|
+
const nameEvents = [];
|
|
7628
|
+
const tenantTables = [];
|
|
7629
|
+
let order = 0;
|
|
7630
|
+
const dataLayerFiles = repoFiles.filter((file) => DATA_LAYER_PATH_PATTERN.test(file)).sort(compareMigrationPaths);
|
|
7631
|
+
for (const file of dataLayerFiles) {
|
|
7632
|
+
const fullPath = join(repoPath, file);
|
|
7633
|
+
if (!isRegularFile(fullPath)) continue;
|
|
7634
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7635
|
+
if (!/\.sql$/i.test(file)) {
|
|
7636
|
+
if (RLS_STATEMENT_PHRASE_PATTERN.test(contents)) return [];
|
|
7637
|
+
continue;
|
|
7638
|
+
}
|
|
7639
|
+
const { code, dynamic } = blankSqlNonCode(contents);
|
|
7640
|
+
if (dynamic) return [];
|
|
7641
|
+
const dir = posix.dirname(file);
|
|
7642
|
+
for (const statement of splitSqlStatements(code)) {
|
|
7643
|
+
order += 1;
|
|
7644
|
+
const createTable = SQL_CREATE_TABLE_HEAD.exec(statement.text);
|
|
7645
|
+
if (createTable) {
|
|
7646
|
+
const { segments, end } = readSqlTableName(statement.text, createTable[0].length);
|
|
7647
|
+
const open = statement.text.slice(end).search(/\S/);
|
|
7648
|
+
if (segments === null || open === -1 || statement.text[end + open] !== "(") continue;
|
|
7649
|
+
if (sqlColumnListHasTenantColumn(statement.text, end + open)) {
|
|
7650
|
+
tenantTables.push({
|
|
7651
|
+
key: segments[segments.length - 1],
|
|
7652
|
+
segments,
|
|
7653
|
+
file,
|
|
7654
|
+
line: lineAtOffset(contents, statement.start),
|
|
7655
|
+
order,
|
|
7656
|
+
dir
|
|
7657
|
+
});
|
|
7658
|
+
}
|
|
7659
|
+
continue;
|
|
7660
|
+
}
|
|
7661
|
+
const dropTable = SQL_DROP_TABLE_HEAD.exec(statement.text);
|
|
7662
|
+
if (dropTable) {
|
|
7663
|
+
let cursor = dropTable[0].length;
|
|
7664
|
+
for (; ; ) {
|
|
7665
|
+
const { segments, end } = readSqlTableName(statement.text, cursor);
|
|
7666
|
+
if (segments === null) return [];
|
|
7667
|
+
nameEvents.push({ key: segments[segments.length - 1], order, dir });
|
|
7668
|
+
const comma = /^\s*,/.exec(statement.text.slice(end));
|
|
7669
|
+
if (!comma) break;
|
|
7670
|
+
cursor = end + comma[0].length;
|
|
7671
|
+
}
|
|
7672
|
+
continue;
|
|
7673
|
+
}
|
|
7674
|
+
const alterTable = SQL_ALTER_TABLE_HEAD.exec(statement.text);
|
|
7675
|
+
if (alterTable) {
|
|
7676
|
+
const { segments: renamed, end: renamedEnd } = readSqlTableName(
|
|
7677
|
+
statement.text,
|
|
7678
|
+
alterTable[0].length
|
|
7679
|
+
);
|
|
7680
|
+
const renameTo = renamed === null ? null : SQL_RENAME_TO_CLAUSE.exec(statement.text.slice(renamedEnd));
|
|
7681
|
+
if (renamed !== null && renameTo) {
|
|
7682
|
+
const { segments: to } = readSqlTableName(statement.text, renamedEnd + renameTo[0].length);
|
|
7683
|
+
if (to === null) return [];
|
|
7684
|
+
nameEvents.push({
|
|
7685
|
+
key: renamed[renamed.length - 1],
|
|
7686
|
+
to: to[to.length - 1],
|
|
7687
|
+
order,
|
|
7688
|
+
dir
|
|
7689
|
+
});
|
|
7690
|
+
continue;
|
|
7691
|
+
}
|
|
7692
|
+
const toggles = [...statement.text.matchAll(SQL_RLS_TOGGLE_PATTERN)];
|
|
7693
|
+
if (toggles.length === 0) {
|
|
7694
|
+
if (SQL_TABLE_REMOVAL_PHRASE_PATTERN.test(statement.text)) return [];
|
|
7695
|
+
continue;
|
|
7696
|
+
}
|
|
7697
|
+
const { segments } = readSqlTableName(statement.text, alterTable[0].length);
|
|
7698
|
+
if (segments === null) return [];
|
|
7699
|
+
const key = segments[segments.length - 1];
|
|
7700
|
+
for (const toggle of toggles) {
|
|
7701
|
+
order += 1;
|
|
7702
|
+
events.push({ key, covers: toggle[1]?.toLowerCase() === "enable", order, dir });
|
|
7703
|
+
}
|
|
7704
|
+
continue;
|
|
7705
|
+
}
|
|
7706
|
+
const createPolicy = SQL_CREATE_POLICY_HEAD.exec(statement.text);
|
|
7707
|
+
if (createPolicy) {
|
|
7708
|
+
const policyName = readSqlIdentifier(statement.text, createPolicy[0].length);
|
|
7709
|
+
const on = policyName ? /^\s+on\s+/i.exec(statement.text.slice(policyName.end)) : null;
|
|
7710
|
+
if (!policyName || !on) return [];
|
|
7711
|
+
const { segments } = readSqlTableName(statement.text, policyName.end + on[0].length);
|
|
7712
|
+
if (segments === null) return [];
|
|
7713
|
+
events.push({ key: segments[segments.length - 1], covers: true, order, dir });
|
|
7714
|
+
continue;
|
|
7715
|
+
}
|
|
7716
|
+
if (RLS_STATEMENT_PHRASE_PATTERN.test(statement.text)) return [];
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
if (!events.some((event) => event.covers)) return [];
|
|
7720
|
+
const followTable = (table) => {
|
|
7721
|
+
const spans = [];
|
|
7722
|
+
let key = table.key;
|
|
7723
|
+
let from = Number.NEGATIVE_INFINITY;
|
|
7724
|
+
for (; ; ) {
|
|
7725
|
+
const current = key;
|
|
7726
|
+
if (nameEvents.some((event) => event.key === current && event.dir !== table.dir)) return null;
|
|
7727
|
+
const next = nameEvents.find(
|
|
7728
|
+
(event) => event.key === current && event.dir === table.dir && event.order > Math.max(from, table.order)
|
|
7729
|
+
);
|
|
7730
|
+
if (next === void 0) {
|
|
7731
|
+
spans.push({ key, from, until: Number.POSITIVE_INFINITY });
|
|
7732
|
+
return spans;
|
|
7733
|
+
}
|
|
7734
|
+
if (next.to === void 0) return null;
|
|
7735
|
+
spans.push({ key, from, until: next.order });
|
|
7736
|
+
key = next.to;
|
|
7737
|
+
from = next.order;
|
|
7738
|
+
}
|
|
7739
|
+
};
|
|
7740
|
+
const isCovered = (table, spans) => {
|
|
7741
|
+
const forTable = events.filter(
|
|
7742
|
+
(event) => spans.some(
|
|
7743
|
+
(span) => span.key === event.key && (event.dir !== table.dir || event.order > span.from && event.order < span.until)
|
|
7744
|
+
)
|
|
7745
|
+
);
|
|
7746
|
+
const lastCover = forTable.filter((event) => event.covers).at(-1);
|
|
7747
|
+
const lastDisable = forTable.filter((event) => !event.covers).at(-1);
|
|
7748
|
+
if (lastCover === void 0) return false;
|
|
7749
|
+
if (lastDisable === void 0) return true;
|
|
7750
|
+
if (lastCover.dir !== lastDisable.dir) return true;
|
|
7751
|
+
return lastCover.order > lastDisable.order;
|
|
7752
|
+
};
|
|
7753
|
+
const uncovered = /* @__PURE__ */ new Map();
|
|
7754
|
+
for (const table of tenantTables) {
|
|
7755
|
+
const spans = followTable(table);
|
|
7756
|
+
const current = spans?.at(-1)?.key;
|
|
7757
|
+
if (spans === null || current === void 0 || uncovered.has(current)) continue;
|
|
7758
|
+
if (isCovered(table, spans)) continue;
|
|
7759
|
+
uncovered.set(current, {
|
|
7760
|
+
name: [...table.segments.slice(0, -1), current].join("."),
|
|
7761
|
+
file: table.file,
|
|
7762
|
+
line: table.line,
|
|
7763
|
+
order: table.order
|
|
7764
|
+
});
|
|
7765
|
+
}
|
|
7766
|
+
return [...uncovered.values()].sort((left, right) => left.order - right.order).slice(0, MAX_UNCOVERED_TENANT_TABLES).map(({ name, file, line }) => ({ name, file, line }));
|
|
7767
|
+
}
|
|
7087
7768
|
function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
|
|
7088
7769
|
const explicitTargets = repoFiles.filter(
|
|
7089
7770
|
(file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
|
|
@@ -7173,10 +7854,190 @@ function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
|
7173
7854
|
}
|
|
7174
7855
|
return false;
|
|
7175
7856
|
}
|
|
7857
|
+
var DECLARED_CLAIM_ARTIFACT_BASENAME_PATTERN = /^claims?[-_.]?(?:reality[-_.]?)?(?:reconciliation|register|ledger)\.(?:md|mdx|json|ya?ml)$/i;
|
|
7858
|
+
var DECLARED_CLAIM_ARTIFACT_PATH_PATTERN = /^[A-Za-z0-9._/-]{1,160}$/;
|
|
7859
|
+
var MARKDOWN_FENCE_PATTERN = /^\s{0,3}(`{3,}|~{3,})/;
|
|
7860
|
+
var MARKDOWN_INLINE_LINK_PATTERN = /(!?)\[[^\]]*\]\(\s*<?([^\s)>]+)>?[^)]*\)/g;
|
|
7861
|
+
var MARKDOWN_REFERENCE_DEFINITION_PATTERN = /^\s{0,3}\[[^\]]+\]:\s*<?([^\s>]+)>?/;
|
|
7862
|
+
var MARKDOWN_CODE_SPAN_PATTERN = /`([^`]+)`/g;
|
|
7863
|
+
function isClaimArtifactDeclaringDocument(file) {
|
|
7864
|
+
return isClaimSourceFile(file) || /^CONTRIBUTING\.md$/i.test(file);
|
|
7865
|
+
}
|
|
7866
|
+
function isClaimArtifactDeclaringWorkflow(file) {
|
|
7867
|
+
return /^\.github\/workflows\/[^/]+\.ya?ml$/.test(file);
|
|
7868
|
+
}
|
|
7869
|
+
function normaliseDeclaredClaimArtifactPath(candidate, baseDir) {
|
|
7870
|
+
if (!DECLARED_CLAIM_ARTIFACT_PATH_PATTERN.test(candidate)) return null;
|
|
7871
|
+
const joined = candidate.startsWith("/") ? posix.normalize(candidate.slice(1)) : posix.normalize(posix.join(baseDir, candidate));
|
|
7872
|
+
const path = joined.replace(/\/+$/, "");
|
|
7873
|
+
if (path === "" || path === "." || path === ".." || path.startsWith("../") || path.startsWith("/")) {
|
|
7874
|
+
return null;
|
|
7875
|
+
}
|
|
7876
|
+
return DECLARED_CLAIM_ARTIFACT_BASENAME_PATTERN.test(posix.basename(path)) ? path : null;
|
|
7877
|
+
}
|
|
7878
|
+
function markdownLinkTarget(target) {
|
|
7879
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//") || target.startsWith("#")) {
|
|
7880
|
+
return null;
|
|
7881
|
+
}
|
|
7882
|
+
const withoutSuffix = target.replace(/[?#].*$/, "");
|
|
7883
|
+
try {
|
|
7884
|
+
return decodeURIComponent(withoutSuffix);
|
|
7885
|
+
} catch {
|
|
7886
|
+
return null;
|
|
7887
|
+
}
|
|
7888
|
+
}
|
|
7889
|
+
function markdownClaimArtifactDeclarations(file, contents) {
|
|
7890
|
+
const baseDir = posix.dirname(file);
|
|
7891
|
+
const declarations = [];
|
|
7892
|
+
let fence = null;
|
|
7893
|
+
let inHtmlComment = false;
|
|
7894
|
+
contents.split(/\r?\n/).forEach((rawLine, index) => {
|
|
7895
|
+
const marker = MARKDOWN_FENCE_PATTERN.exec(rawLine)?.[1];
|
|
7896
|
+
if (fence !== null) {
|
|
7897
|
+
if (marker !== void 0 && marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
7898
|
+
return;
|
|
7899
|
+
}
|
|
7900
|
+
if (marker !== void 0) {
|
|
7901
|
+
fence = marker;
|
|
7902
|
+
return;
|
|
7903
|
+
}
|
|
7904
|
+
let line = rawLine;
|
|
7905
|
+
if (inHtmlComment) {
|
|
7906
|
+
const close = line.indexOf("-->");
|
|
7907
|
+
if (close === -1) return;
|
|
7908
|
+
line = line.slice(close + 3);
|
|
7909
|
+
inHtmlComment = false;
|
|
7910
|
+
}
|
|
7911
|
+
line = line.replace(/<!--[\s\S]*?-->/g, " ");
|
|
7912
|
+
const open = line.indexOf("<!--");
|
|
7913
|
+
if (open !== -1) {
|
|
7914
|
+
line = line.slice(0, open);
|
|
7915
|
+
inHtmlComment = true;
|
|
7916
|
+
}
|
|
7917
|
+
const lineNumber = index + 1;
|
|
7918
|
+
for (const match of line.matchAll(MARKDOWN_CODE_SPAN_PATTERN)) {
|
|
7919
|
+
const span = (match[1] ?? "").trim().replace(/^\.\//, "");
|
|
7920
|
+
const candidates = [
|
|
7921
|
+
normaliseDeclaredClaimArtifactPath(span, "."),
|
|
7922
|
+
normaliseDeclaredClaimArtifactPath(span, baseDir)
|
|
7923
|
+
].filter((path) => path !== null);
|
|
7924
|
+
if (candidates.length > 0) declarations.push({ candidates: [...new Set(candidates)], line: lineNumber });
|
|
7925
|
+
}
|
|
7926
|
+
const prose = line.replace(MARKDOWN_CODE_SPAN_PATTERN, " ");
|
|
7927
|
+
const targets = [];
|
|
7928
|
+
for (const match of prose.matchAll(MARKDOWN_INLINE_LINK_PATTERN)) {
|
|
7929
|
+
if (match[1] === "!") continue;
|
|
7930
|
+
targets.push(match[2] ?? "");
|
|
7931
|
+
}
|
|
7932
|
+
const reference = MARKDOWN_REFERENCE_DEFINITION_PATTERN.exec(prose);
|
|
7933
|
+
if (reference?.[1]) targets.push(reference[1]);
|
|
7934
|
+
for (const target of targets) {
|
|
7935
|
+
const decoded = markdownLinkTarget(target);
|
|
7936
|
+
const path = decoded === null ? null : normaliseDeclaredClaimArtifactPath(decoded, baseDir);
|
|
7937
|
+
if (path !== null) declarations.push({ candidates: [path], line: lineNumber });
|
|
7938
|
+
}
|
|
7939
|
+
});
|
|
7940
|
+
return declarations;
|
|
7941
|
+
}
|
|
7942
|
+
var WORKFLOW_WRITE_OPERATOR_PATTERN = />|(?:^|[\s|;&(])(?:tee|-o|--out[\w-]*)(?=[\s=]|$)/;
|
|
7943
|
+
var WORKFLOW_ARTIFACT_ACTION_PATTERN = /^\s*(?:-\s+)?uses\s*:\s*["']?actions\/(?:upload|download)-artifact(?:[/@]|["']?\s*$)/i;
|
|
7944
|
+
function workflowClaimArtifactDeclarations(contents) {
|
|
7945
|
+
if (/^\s*(?:-\s+)?working-directory\s*:/m.test(contents)) return [];
|
|
7946
|
+
const declarations = [];
|
|
7947
|
+
let stepsIndent = null;
|
|
7948
|
+
let stepItemIndent = null;
|
|
7949
|
+
let step = null;
|
|
7950
|
+
let writeContinues = false;
|
|
7951
|
+
const closeStep = () => {
|
|
7952
|
+
if (step !== null && !step.movesArtifact) declarations.push(...step.declarations);
|
|
7953
|
+
step = null;
|
|
7954
|
+
writeContinues = false;
|
|
7955
|
+
};
|
|
7956
|
+
contents.split(/\r?\n/).forEach((rawLine, index) => {
|
|
7957
|
+
const line = rawLine.replace(/(^|\s)#.*$/, "");
|
|
7958
|
+
if (line.trim() === "") return;
|
|
7959
|
+
const indent = line.length - line.trimStart().length;
|
|
7960
|
+
if (stepsIndent !== null && (indent < stepsIndent || indent === stepsIndent && !line.trimStart().startsWith("- "))) {
|
|
7961
|
+
closeStep();
|
|
7962
|
+
stepsIndent = null;
|
|
7963
|
+
}
|
|
7964
|
+
const stepsKey = /^(\s*)steps\s*:\s*$/.exec(line);
|
|
7965
|
+
if (stepsKey) {
|
|
7966
|
+
closeStep();
|
|
7967
|
+
stepsIndent = stepsKey[1]?.length ?? 0;
|
|
7968
|
+
stepItemIndent = null;
|
|
7969
|
+
return;
|
|
7970
|
+
}
|
|
7971
|
+
if (stepsIndent === null) return;
|
|
7972
|
+
if (line.trimStart().startsWith("- ") && (stepItemIndent === null || indent <= stepItemIndent)) {
|
|
7973
|
+
closeStep();
|
|
7974
|
+
stepItemIndent = indent;
|
|
7975
|
+
}
|
|
7976
|
+
step ??= { declarations: [], movesArtifact: false };
|
|
7977
|
+
if (WORKFLOW_ARTIFACT_ACTION_PATTERN.test(line)) step.movesArtifact = true;
|
|
7978
|
+
const writeAt = writeContinues ? 0 : line.search(WORKFLOW_WRITE_OPERATOR_PATTERN);
|
|
7979
|
+
writeContinues = writeAt !== -1 && /\\\s*$/.test(line);
|
|
7980
|
+
const read = writeAt === -1 ? line : line.slice(0, writeAt);
|
|
7981
|
+
for (const token of read.split(/[\s'"`=,;:()[\]{}<>|&]+/)) {
|
|
7982
|
+
const path = normaliseDeclaredClaimArtifactPath(token.replace(/^\.\//, ""), ".");
|
|
7983
|
+
if (path !== null) step.declarations.push({ candidates: [path], line: index + 1 });
|
|
7984
|
+
}
|
|
7985
|
+
});
|
|
7986
|
+
closeStep();
|
|
7987
|
+
return declarations;
|
|
7988
|
+
}
|
|
7989
|
+
function scanClaimArtifactDeclarations(repoPath, repoFiles) {
|
|
7990
|
+
const declaringFiles = repoFiles.filter((file) => isClaimArtifactDeclaringDocument(file) || isClaimArtifactDeclaringWorkflow(file)).sort();
|
|
7991
|
+
let declared = false;
|
|
7992
|
+
if (declaringFiles.length === 0) return { declared };
|
|
7993
|
+
const known = [...repoFiles, ...withheldRepoPaths().map(({ path }) => path)].map(
|
|
7994
|
+
(path) => path.toLowerCase()
|
|
7995
|
+
);
|
|
7996
|
+
const isPresent = (path) => {
|
|
7997
|
+
const lower = path.toLowerCase();
|
|
7998
|
+
return known.some((file) => file === lower || file.startsWith(`${lower}/`));
|
|
7999
|
+
};
|
|
8000
|
+
for (const file of declaringFiles) {
|
|
8001
|
+
const fullPath = join(repoPath, file);
|
|
8002
|
+
if (!isRegularFile(fullPath)) continue;
|
|
8003
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8004
|
+
const declarations = isClaimArtifactDeclaringWorkflow(file) ? workflowClaimArtifactDeclarations(contents) : markdownClaimArtifactDeclarations(file, contents);
|
|
8005
|
+
for (const declaration of declarations) {
|
|
8006
|
+
declared = true;
|
|
8007
|
+
if (declaration.candidates.some(isPresent)) continue;
|
|
8008
|
+
const [path] = declaration.candidates;
|
|
8009
|
+
if (path !== void 0) {
|
|
8010
|
+
return { declared, firstAbsent: { path, declaringFile: file, line: declaration.line } };
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
8014
|
+
return { declared };
|
|
8015
|
+
}
|
|
7176
8016
|
function isCoverageConfig(repoPath, file) {
|
|
7177
8017
|
if (/(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file)) {
|
|
7178
8018
|
return true;
|
|
7179
8019
|
}
|
|
8020
|
+
if (/(^|\/)\.codecov\.ya?ml$/.test(file) || /(^|\/)\.nycrc(?:\.json|\.ya?ml)?$/.test(file) || /(^|\/)\.c8rc(?:\.json)?$/.test(file) || /(^|\/)\.?tarpaulin\.toml$/.test(file)) {
|
|
8021
|
+
return true;
|
|
8022
|
+
}
|
|
8023
|
+
if (/(^|\/)(?:setup\.cfg|tox\.ini)$/.test(file)) {
|
|
8024
|
+
return fileContains(repoPath, file, /^\s*\[coverage:(?:run|report)\]\s*$/m);
|
|
8025
|
+
}
|
|
8026
|
+
if (/(^|\/)vite\.config\.[cm]?[jt]s$/.test(file)) {
|
|
8027
|
+
return viteConfigCollectsCoverage(repoPath, file);
|
|
8028
|
+
}
|
|
8029
|
+
if (/(^|\/)\.simplecov$/.test(file)) {
|
|
8030
|
+
return fileContains(repoPath, file, /^(?!\s*#).*\bSimpleCov\b/m);
|
|
8031
|
+
}
|
|
8032
|
+
if (/(^|\/)phpunit\.xml(?:\.dist)?$/.test(file)) {
|
|
8033
|
+
return /<coverage(?=[\s>/])/.test(stripXmlComments(readRepoText(join(repoPath, file), "utf8")));
|
|
8034
|
+
}
|
|
8035
|
+
if (/(^|\/)pom\.xml$/.test(file)) {
|
|
8036
|
+
return pomAppliesJacoco(readRepoText(join(repoPath, file), "utf8"));
|
|
8037
|
+
}
|
|
8038
|
+
if (/(^|\/)build\.gradle(?:\.kts)?$/.test(file)) {
|
|
8039
|
+
return gradleAppliesJacoco(readRepoText(join(repoPath, file), "utf8"));
|
|
8040
|
+
}
|
|
7180
8041
|
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7181
8042
|
return fileContains(repoPath, file, /\bcoverage\s*:/i);
|
|
7182
8043
|
}
|
|
@@ -7208,19 +8069,76 @@ function packageJsonHasCoverageTooling(repoPath, file) {
|
|
|
7208
8069
|
}
|
|
7209
8070
|
}
|
|
7210
8071
|
}
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
8072
|
+
return packageJsonHasStaticCoverageTooling(repoPath, file);
|
|
8073
|
+
}
|
|
8074
|
+
function vitestCoveragePropertyCollects(contents) {
|
|
8075
|
+
const property = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents)?.[1]?.trim();
|
|
8076
|
+
if (!property || /^(?:false\b|null\b|undefined\b)/i.test(property)) return false;
|
|
8077
|
+
if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
|
|
8078
|
+
return true;
|
|
8079
|
+
}
|
|
8080
|
+
function viteConfigCollectsCoverage(repoPath, file) {
|
|
8081
|
+
const directory = dirname(file);
|
|
8082
|
+
for (const extension of ["ts", "mts", "cts", "js", "mjs", "cjs"]) {
|
|
8083
|
+
const sibling = join(repoPath, directory, `vitest.config.${extension}`);
|
|
8084
|
+
if (!isRegularFile(sibling)) continue;
|
|
8085
|
+
const vitestConfig = stripJavaScriptComments(readRepoText(sibling, "utf8"));
|
|
8086
|
+
if (!/["']\.\/vite\.config(?:\.[cm]?[jt]s)?["']/.test(vitestConfig)) return false;
|
|
8087
|
+
}
|
|
8088
|
+
const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
|
|
8089
|
+
const testProperty = /(?:^|[{,])\s*["']?test["']?\s*:\s*\{/m.exec(contents);
|
|
8090
|
+
if (!testProperty) return false;
|
|
8091
|
+
const block = balancedBraceBlock(contents, testProperty.index + testProperty[0].length - 1);
|
|
8092
|
+
return vitestCoveragePropertyCollects(block);
|
|
8093
|
+
}
|
|
8094
|
+
function balancedBraceBlock(contents, open) {
|
|
8095
|
+
let depth = 0;
|
|
8096
|
+
let quote = null;
|
|
8097
|
+
for (let index = open; index < contents.length; index += 1) {
|
|
8098
|
+
const character = contents[index];
|
|
8099
|
+
if (quote) {
|
|
8100
|
+
if (character === "\\") index += 1;
|
|
8101
|
+
else if (character === quote) quote = null;
|
|
8102
|
+
continue;
|
|
8103
|
+
}
|
|
8104
|
+
if (character === '"' || character === "'" || character === "`") quote = character;
|
|
8105
|
+
else if (character === "{") depth += 1;
|
|
8106
|
+
else if (character === "}") {
|
|
8107
|
+
depth -= 1;
|
|
8108
|
+
if (depth === 0) return contents.slice(open, index + 1);
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
return contents.slice(open);
|
|
8112
|
+
}
|
|
8113
|
+
function stripXmlComments(contents) {
|
|
8114
|
+
return contents.replace(/<!--[\s\S]*?-->/g, "");
|
|
8115
|
+
}
|
|
8116
|
+
function pomAppliesJacoco(contents) {
|
|
8117
|
+
const pom = stripXmlComments(contents).replace(/<pluginManagement>[\s\S]*?<\/pluginManagement>/g, "");
|
|
8118
|
+
if (/<jacoco\.skip>\s*true\s*<\/jacoco\.skip>/i.test(pom)) return false;
|
|
8119
|
+
for (const plugin of pom.matchAll(/<plugin>([\s\S]*?)<\/plugin>/g)) {
|
|
8120
|
+
const body = plugin[1] ?? "";
|
|
8121
|
+
if (!/<artifactId>\s*jacoco-maven-plugin\s*<\/artifactId>/.test(body)) continue;
|
|
8122
|
+
if (/<skip>\s*true\s*<\/skip>/i.test(body)) continue;
|
|
8123
|
+
return true;
|
|
8124
|
+
}
|
|
8125
|
+
return false;
|
|
8126
|
+
}
|
|
8127
|
+
function gradleAppliesJacoco(contents) {
|
|
8128
|
+
const script = stripJavaScriptComments(contents);
|
|
8129
|
+
if (/\bapply\s*\(?\s*plugin\s*[:=]\s*["']jacoco["']/.test(script)) return true;
|
|
8130
|
+
for (const block of script.matchAll(/\bplugins\s*\{([^{}]*)\}/g)) {
|
|
8131
|
+
if (/(?:^|[\s;])(?:id\s*\(?\s*["']jacoco["']\s*\)?|jacoco)[ \t]*(?:$|[\s;])/m.test(block[1] ?? "")) {
|
|
8132
|
+
return true;
|
|
8133
|
+
}
|
|
7214
8134
|
}
|
|
7215
8135
|
return false;
|
|
7216
8136
|
}
|
|
7217
8137
|
function isV23CoverageConfig(repoPath, file) {
|
|
7218
8138
|
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
|
|
7223
|
-
return true;
|
|
8139
|
+
return vitestCoveragePropertyCollects(
|
|
8140
|
+
stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"))
|
|
8141
|
+
);
|
|
7224
8142
|
}
|
|
7225
8143
|
if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7226
8144
|
const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
|
|
@@ -7280,6 +8198,10 @@ function packageJsonHasStaticCoverageTooling(repoPath, file) {
|
|
|
7280
8198
|
if (!isRegularFile(fullPath)) return false;
|
|
7281
8199
|
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
7282
8200
|
if (!parsed) return false;
|
|
8201
|
+
const jest = parsed.jest;
|
|
8202
|
+
if (isRecord(jest) && (jest.collectCoverage === true || isRecord(jest.coverageThreshold) || typeof jest.coverageProvider === "string")) {
|
|
8203
|
+
return true;
|
|
8204
|
+
}
|
|
7283
8205
|
return ["nyc", "c8", "istanbul"].some((key) => {
|
|
7284
8206
|
const config = parsed[key];
|
|
7285
8207
|
return typeof config === "object" && config !== null && !Array.isArray(config);
|
|
@@ -7361,6 +8283,19 @@ function analyzeCoverageConfiguration(repoPath, repoFiles, authoredOnly = false)
|
|
|
7361
8283
|
detectorDecisions
|
|
7362
8284
|
};
|
|
7363
8285
|
}
|
|
8286
|
+
function unionWithLegacyCoverageRecognition(analysis, legacyFiles, hasLegacyCoverageScript) {
|
|
8287
|
+
const legacy = new Set(legacyFiles);
|
|
8288
|
+
const coverageFiles = [
|
|
8289
|
+
...legacyFiles,
|
|
8290
|
+
...analysis.coverageFiles.filter((file) => !legacy.has(file))
|
|
8291
|
+
];
|
|
8292
|
+
return {
|
|
8293
|
+
coverageFiles,
|
|
8294
|
+
unresolvedFiles: analysis.unresolvedFiles.filter((file) => !legacy.has(file)),
|
|
8295
|
+
hasCoverageCommand: hasLegacyCoverageScript || analysis.hasCoverageCommand,
|
|
8296
|
+
detectorDecisions: analysis.detectorDecisions
|
|
8297
|
+
};
|
|
8298
|
+
}
|
|
7364
8299
|
function stripJavaScriptComments(contents) {
|
|
7365
8300
|
let output = "";
|
|
7366
8301
|
let quote = null;
|
|
@@ -7473,22 +8408,7 @@ function packageScriptReferences(command, scripts) {
|
|
|
7473
8408
|
const names = /* @__PURE__ */ new Set();
|
|
7474
8409
|
let dynamic = false;
|
|
7475
8410
|
for (const segment of splitShellCommandSegments(command)) {
|
|
7476
|
-
|
|
7477
|
-
/\bpnpm\s+run\s+\/((?:\\.|[^/])+)\/([dgimsuvy]*)/giu
|
|
7478
|
-
)) {
|
|
7479
|
-
const source = match[1];
|
|
7480
|
-
if (!source) continue;
|
|
7481
|
-
try {
|
|
7482
|
-
const flags = (match[2] ?? "").replace(/[dgy]/g, "");
|
|
7483
|
-
const selector = new RegExp(source, flags);
|
|
7484
|
-
for (const name of scripts.keys()) {
|
|
7485
|
-
if (selector.test(name)) names.add(name);
|
|
7486
|
-
selector.lastIndex = 0;
|
|
7487
|
-
}
|
|
7488
|
-
} catch {
|
|
7489
|
-
dynamic = true;
|
|
7490
|
-
}
|
|
7491
|
-
}
|
|
8411
|
+
if (/\bpnpm\s+run\s+\//iu.test(segment)) dynamic = true;
|
|
7492
8412
|
for (const match of segment.matchAll(
|
|
7493
8413
|
/\b(?:npm|pnpm|yarn)\s+(?:run\s+)?([A-Za-z0-9][A-Za-z0-9:._-]*)/giu
|
|
7494
8414
|
)) {
|
|
@@ -7731,14 +8651,89 @@ function coverageFlagCommandState(words) {
|
|
|
7731
8651
|
if (executable === "vitest" || executable === "bnt") {
|
|
7732
8652
|
return flagState(args, ["--coverage"], ["--no-coverage"]);
|
|
7733
8653
|
}
|
|
8654
|
+
if (isPythonModuleRunner(executable, args)) {
|
|
8655
|
+
return flagState(args.slice(2), ["--cov"], ["--no-cov"]);
|
|
8656
|
+
}
|
|
8657
|
+
if (executable === "go") {
|
|
8658
|
+
if (args[0]?.toLowerCase() !== "test") return "none";
|
|
8659
|
+
const end = args.findIndex((arg, index) => index > 0 && /^--?args$/i.test(arg));
|
|
8660
|
+
return goTestCoverageState(end < 0 ? args.slice(1) : args.slice(1, end));
|
|
8661
|
+
}
|
|
8662
|
+
if (executable === "dotnet") {
|
|
8663
|
+
if (args[0]?.toLowerCase() !== "test") return "none";
|
|
8664
|
+
const end = args.indexOf("--", 1);
|
|
8665
|
+
return dotnetTestCoverageState(end < 0 ? args.slice(1) : args.slice(1, end));
|
|
8666
|
+
}
|
|
8667
|
+
if (executable === "cargo") {
|
|
8668
|
+
const subcommand = args[0]?.startsWith("+") ? args.slice(1) : args;
|
|
8669
|
+
if (subcommand[0]?.toLowerCase() !== "llvm-cov") return "none";
|
|
8670
|
+
if (["report", "clean", "show-env", "run"].includes(subcommand[1]?.toLowerCase() ?? "")) {
|
|
8671
|
+
return "none";
|
|
8672
|
+
}
|
|
8673
|
+
const end = subcommand.indexOf("--");
|
|
8674
|
+
const owned = end < 0 ? subcommand.slice(1) : subcommand.slice(1, end);
|
|
8675
|
+
return owned.some((arg) => arg.toLowerCase() === "--no-run") ? "none" : "collects";
|
|
8676
|
+
}
|
|
7734
8677
|
return "none";
|
|
7735
8678
|
}
|
|
8679
|
+
function isPythonModuleRunner(executable, args) {
|
|
8680
|
+
return /^(?:python[0-9.]*|py)$/.test(executable) && args[0] === "-m" && (args[1] === "pytest" || args[1] === "py.test");
|
|
8681
|
+
}
|
|
8682
|
+
function goTestCoverageState(args) {
|
|
8683
|
+
let enabled = false;
|
|
8684
|
+
let disabled = false;
|
|
8685
|
+
let unresolved = false;
|
|
8686
|
+
for (const raw of args) {
|
|
8687
|
+
const [name, assigned] = raw.toLowerCase().replace(/^--/u, "-").split("=", 2);
|
|
8688
|
+
if (name === "-covermode" || name === "-coverpkg" || name === "-coverprofile") {
|
|
8689
|
+
enabled = true;
|
|
8690
|
+
} else if (name === "-cover") {
|
|
8691
|
+
if (assigned === void 0 || /^(?:true|t|1)$/u.test(assigned)) enabled = true;
|
|
8692
|
+
else if (/^(?:false|f|0)$/u.test(assigned)) disabled = true;
|
|
8693
|
+
else unresolved = true;
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
if (unresolved || enabled && disabled) return "unresolved";
|
|
8697
|
+
return enabled ? "collects" : "none";
|
|
8698
|
+
}
|
|
8699
|
+
function dotnetTestCoverageState(args) {
|
|
8700
|
+
let enabled = false;
|
|
8701
|
+
let disabled = false;
|
|
8702
|
+
let unresolved = false;
|
|
8703
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
8704
|
+
const lower = (args[index] ?? "").toLowerCase();
|
|
8705
|
+
const collect = /^--collect(?:[:=]([\s\S]*))?$/u.exec(lower);
|
|
8706
|
+
if (collect) {
|
|
8707
|
+
const value = (collect[1] ?? args[index += 1]?.toLowerCase() ?? "").trim();
|
|
8708
|
+
if (/\$|\{|\}/u.test(value)) unresolved = true;
|
|
8709
|
+
else if (value === "xplat code coverage" || value === "code coverage") enabled = true;
|
|
8710
|
+
continue;
|
|
8711
|
+
}
|
|
8712
|
+
const property = /^(?:[/-]p|--property):([\s\S]*)$/u.exec(lower);
|
|
8713
|
+
if (!property) continue;
|
|
8714
|
+
for (const assignment of (property[1] ?? "").split(/[;,]/u)) {
|
|
8715
|
+
const [name, value] = assignment.split("=", 2).map((part) => part.trim());
|
|
8716
|
+
if (name !== "collectcoverage" || value === void 0) continue;
|
|
8717
|
+
if (value === "true") enabled = true;
|
|
8718
|
+
else if (value === "false") disabled = true;
|
|
8719
|
+
else unresolved = true;
|
|
8720
|
+
}
|
|
8721
|
+
}
|
|
8722
|
+
if (unresolved || enabled && disabled) return "unresolved";
|
|
8723
|
+
return enabled ? "collects" : "none";
|
|
8724
|
+
}
|
|
7736
8725
|
function isRecognizedCoverageFlagRunner(words) {
|
|
7737
8726
|
const command = unwrapPackageManagerExecutable(words);
|
|
7738
8727
|
const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
|
|
7739
8728
|
if (["bnt", "vitest", "jest", "pytest", "py.test"].includes(executable)) return true;
|
|
7740
8729
|
if (executable === "node") return command.slice(1).includes("--test");
|
|
7741
|
-
|
|
8730
|
+
if (isPythonModuleRunner(executable, command.slice(1))) return true;
|
|
8731
|
+
const subcommand = command[1]?.toLowerCase();
|
|
8732
|
+
if (executable === "go" || executable === "dotnet") return subcommand === "test";
|
|
8733
|
+
if (executable === "cargo") {
|
|
8734
|
+
return (subcommand?.startsWith("+") ? command[2] : command[1])?.toLowerCase() === "llvm-cov";
|
|
8735
|
+
}
|
|
8736
|
+
return (executable === "bun" || executable === "deno") && subcommand === "test";
|
|
7742
8737
|
}
|
|
7743
8738
|
function flagState(args, enablingNames, disablingNames) {
|
|
7744
8739
|
let enabled = false;
|
|
@@ -8398,9 +9393,170 @@ function isV24IgnoredScanFile(file) {
|
|
|
8398
9393
|
function findRootPackageJson(repoFiles) {
|
|
8399
9394
|
return repoFiles.includes("package.json") ? "package.json" : null;
|
|
8400
9395
|
}
|
|
8401
|
-
|
|
9396
|
+
var WORKSPACE_PATTERN_LIMIT = 64;
|
|
9397
|
+
var WORKSPACE_PACKAGE_MANIFEST_LIMIT = 200;
|
|
9398
|
+
var NO_DECLARED_WORKSPACE_MANIFESTS = { read: [], declared: 0 };
|
|
9399
|
+
function findDeclaredWorkspaceManifests(repoPath, repoFiles, rootPackageJson) {
|
|
9400
|
+
const patterns = [];
|
|
9401
|
+
if (rootPackageJson) {
|
|
9402
|
+
const declared = readPackageJsonWorkspacePatterns(join(repoPath, rootPackageJson));
|
|
9403
|
+
if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9404
|
+
patterns.push(...declared);
|
|
9405
|
+
}
|
|
9406
|
+
if (repoFiles.includes("pnpm-workspace.yaml")) {
|
|
9407
|
+
const declared = readPnpmWorkspacePatterns(join(repoPath, "pnpm-workspace.yaml"));
|
|
9408
|
+
if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9409
|
+
patterns.push(...declared);
|
|
9410
|
+
}
|
|
9411
|
+
if (patterns.length === 0 || patterns.length > WORKSPACE_PATTERN_LIMIT) {
|
|
9412
|
+
return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9413
|
+
}
|
|
9414
|
+
const include = [];
|
|
9415
|
+
const exclude = [];
|
|
9416
|
+
for (const pattern of patterns) {
|
|
9417
|
+
const negated = pattern.startsWith("!");
|
|
9418
|
+
const matcher = workspaceGlobMatcher(negated ? pattern.slice(1) : pattern);
|
|
9419
|
+
if (negated) {
|
|
9420
|
+
if (matcher === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9421
|
+
exclude.push(matcher);
|
|
9422
|
+
} else if (matcher !== null) {
|
|
9423
|
+
include.push(matcher);
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
const matched = repoFiles.filter((file) => {
|
|
9427
|
+
if (file === rootPackageJson || !file.endsWith("/package.json")) return false;
|
|
9428
|
+
if (!isAuthoredProductionPath(file)) return false;
|
|
9429
|
+
const dir = file.slice(0, -"/package.json".length);
|
|
9430
|
+
return include.some((matches) => matches(dir)) && !exclude.some((matches) => matches(dir));
|
|
9431
|
+
}).sort();
|
|
9432
|
+
return { read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT), declared: matched.length };
|
|
9433
|
+
}
|
|
9434
|
+
function readPackageJsonWorkspacePatterns(packageJsonPath) {
|
|
9435
|
+
if (!isRegularFile(packageJsonPath)) return [];
|
|
9436
|
+
const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
|
|
9437
|
+
const workspaces = parsed?.workspaces;
|
|
9438
|
+
if (workspaces === void 0) return [];
|
|
9439
|
+
const list = isRecord(workspaces) ? workspaces.packages : workspaces;
|
|
9440
|
+
if (list === void 0 && isRecord(workspaces)) return [];
|
|
9441
|
+
if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) return null;
|
|
9442
|
+
return list;
|
|
9443
|
+
}
|
|
9444
|
+
function readPnpmWorkspacePatterns(yamlPath) {
|
|
9445
|
+
if (!isRegularFile(yamlPath)) return [];
|
|
9446
|
+
const patterns = [];
|
|
9447
|
+
let inPackages = false;
|
|
9448
|
+
for (const line of readRepoText(yamlPath, "utf8").split(/\r?\n/)) {
|
|
9449
|
+
if (!inPackages) {
|
|
9450
|
+
const key = /^packages\s*:(.*)$/.exec(line);
|
|
9451
|
+
if (!key) continue;
|
|
9452
|
+
const rest = stripYamlLineComment(key[1] ?? "").trim();
|
|
9453
|
+
if (rest === "") {
|
|
9454
|
+
inPackages = true;
|
|
9455
|
+
continue;
|
|
9456
|
+
}
|
|
9457
|
+
const flow = /^\[(.*)\]$/.exec(rest);
|
|
9458
|
+
if (!flow) return null;
|
|
9459
|
+
if ((flow[1] ?? "").trim() === "") return [];
|
|
9460
|
+
for (const item2 of (flow[1] ?? "").split(",")) {
|
|
9461
|
+
const scalar = yamlScalar(item2.trim());
|
|
9462
|
+
if (scalar === null) return null;
|
|
9463
|
+
patterns.push(scalar);
|
|
9464
|
+
}
|
|
9465
|
+
return patterns;
|
|
9466
|
+
}
|
|
9467
|
+
if (/^\s*(?:#.*)?$/.test(line)) continue;
|
|
9468
|
+
const item = /^\s*-\s+(.*)$/.exec(line);
|
|
9469
|
+
if (item) {
|
|
9470
|
+
const scalar = yamlScalar((item[1] ?? "").trim());
|
|
9471
|
+
if (scalar === null) return null;
|
|
9472
|
+
patterns.push(scalar);
|
|
9473
|
+
continue;
|
|
9474
|
+
}
|
|
9475
|
+
if (/^\S/.test(line)) break;
|
|
9476
|
+
return null;
|
|
9477
|
+
}
|
|
9478
|
+
return patterns;
|
|
9479
|
+
}
|
|
9480
|
+
function stripYamlLineComment(text) {
|
|
9481
|
+
return /^\s*#/.test(text) ? "" : text.replace(/\s+#.*$/, "");
|
|
9482
|
+
}
|
|
9483
|
+
function yamlScalar(raw) {
|
|
9484
|
+
const single = /^'((?:[^']|'')*)'\s*(?:#.*)?$/.exec(raw);
|
|
9485
|
+
if (single) return (single[1] ?? "").replace(/''/g, "'");
|
|
9486
|
+
const double = /^"([^"\\]*)"\s*(?:#.*)?$/.exec(raw);
|
|
9487
|
+
if (double) return double[1] ?? "";
|
|
9488
|
+
const plain = stripYamlLineComment(raw).trim();
|
|
9489
|
+
return plain === "" || /^["'[{&*|>]/.test(plain) ? null : plain;
|
|
9490
|
+
}
|
|
9491
|
+
var WORKSPACE_GLOB_MAX_LENGTH = 256;
|
|
9492
|
+
var WORKSPACE_GLOB_MAX_SEGMENTS = 32;
|
|
9493
|
+
function workspaceGlobMatcher(pattern) {
|
|
9494
|
+
if (pattern.length > WORKSPACE_GLOB_MAX_LENGTH) return null;
|
|
9495
|
+
const normalised = pattern.trim().replace(/^\.\//, "").replace(/\/+$/, "");
|
|
9496
|
+
if (normalised === "" || normalised.startsWith("/") || /(^|\/)\.\.?(\/|$)/.test(normalised) || /[[\]{}()!+@\\]/.test(normalised)) {
|
|
9497
|
+
return null;
|
|
9498
|
+
}
|
|
9499
|
+
const split = normalised.split("/");
|
|
9500
|
+
if (split.length > WORKSPACE_GLOB_MAX_SEGMENTS) return null;
|
|
9501
|
+
const segments = split.filter((segment, index, all) => !(segment === "**" && all[index - 1] === "**"));
|
|
9502
|
+
const isAnySegment = (part) => part !== "" && !part.startsWith(".");
|
|
9503
|
+
if (segments.length === 1 && segments[0] === "**") {
|
|
9504
|
+
return (dir) => dir.split("/").every(isAnySegment);
|
|
9505
|
+
}
|
|
9506
|
+
return (dir) => {
|
|
9507
|
+
let reachable = withGlobstarSkips(segments, [true, ...segments.map(() => false)]);
|
|
9508
|
+
for (const part of dir.split("/")) {
|
|
9509
|
+
const next = segments.map(() => false).concat(false);
|
|
9510
|
+
segments.forEach((segment, index) => {
|
|
9511
|
+
if (!reachable[index]) return;
|
|
9512
|
+
if (segment === "**") {
|
|
9513
|
+
if (isAnySegment(part)) next[index] = true;
|
|
9514
|
+
} else if (matchesWildcardSegment(segment, part)) {
|
|
9515
|
+
next[index + 1] = true;
|
|
9516
|
+
}
|
|
9517
|
+
});
|
|
9518
|
+
reachable = withGlobstarSkips(segments, next);
|
|
9519
|
+
if (!reachable.includes(true)) return false;
|
|
9520
|
+
}
|
|
9521
|
+
return reachable[segments.length] === true;
|
|
9522
|
+
};
|
|
9523
|
+
}
|
|
9524
|
+
function withGlobstarSkips(segments, reachable) {
|
|
9525
|
+
segments.forEach((segment, index) => {
|
|
9526
|
+
if (segment === "**" && reachable[index]) reachable[index + 1] = true;
|
|
9527
|
+
});
|
|
9528
|
+
return reachable;
|
|
9529
|
+
}
|
|
9530
|
+
function matchesWildcardSegment(glob, part) {
|
|
9531
|
+
if ((glob.startsWith("*") || glob.startsWith("?")) && part.startsWith(".")) return false;
|
|
9532
|
+
let g = 0;
|
|
9533
|
+
let p = 0;
|
|
9534
|
+
let starGlob = -1;
|
|
9535
|
+
let starPart = 0;
|
|
9536
|
+
while (p < part.length) {
|
|
9537
|
+
if (g < glob.length && glob[g] === "*") {
|
|
9538
|
+
starGlob = g;
|
|
9539
|
+
starPart = p;
|
|
9540
|
+
g += 1;
|
|
9541
|
+
} else if (g < glob.length && (glob[g] === "?" || glob[g] === part[p])) {
|
|
9542
|
+
g += 1;
|
|
9543
|
+
p += 1;
|
|
9544
|
+
} else if (starGlob !== -1) {
|
|
9545
|
+
g = starGlob + 1;
|
|
9546
|
+
starPart += 1;
|
|
9547
|
+
p = starPart;
|
|
9548
|
+
} else {
|
|
9549
|
+
return false;
|
|
9550
|
+
}
|
|
9551
|
+
}
|
|
9552
|
+
while (g < glob.length && glob[g] === "*") g += 1;
|
|
9553
|
+
return g === glob.length;
|
|
9554
|
+
}
|
|
9555
|
+
function ciWorkflowCitationLabel(baseLabel, verb, considered, cited, unread, delegated = NO_DELEGATED_CI_STEPS) {
|
|
8402
9556
|
const unreadTotal = [...unread.values()].reduce((sum, count) => sum + count, 0);
|
|
8403
|
-
|
|
9557
|
+
const { crossRepositoryReusableWorkflows: crossRepository, compositeActions } = delegated;
|
|
9558
|
+
const delegatedTotal = crossRepository + compositeActions;
|
|
9559
|
+
if (considered <= cited && unreadTotal === 0 && delegatedTotal === 0) return baseLabel;
|
|
8404
9560
|
const parts = [
|
|
8405
9561
|
`${considered} workflow file${considered === 1 ? "" : "s"} ${verb}`,
|
|
8406
9562
|
`${cited} cited`
|
|
@@ -8409,7 +9565,70 @@ function ciWorkflowCitationLabel(baseLabel, verb, considered, cited, unread) {
|
|
|
8409
9565
|
const reasons = [...unread.keys()].sort().join(", ");
|
|
8410
9566
|
parts.push(`${unreadTotal} not read (${reasons})`);
|
|
8411
9567
|
}
|
|
8412
|
-
return `${baseLabel} (${parts.join("; ")})`;
|
|
9568
|
+
if (delegatedTotal === 0) return `${baseLabel} (${parts.join("; ")})`;
|
|
9569
|
+
const delegatedCount = `${delegatedTotal} delegated step${delegatedTotal === 1 ? "" : "s"} not read`;
|
|
9570
|
+
const kinds = [
|
|
9571
|
+
...crossRepository > 0 ? [
|
|
9572
|
+
`${crossRepository} reusable workflow${crossRepository === 1 ? " in another repository" : "s in other repositories"}`
|
|
9573
|
+
] : [],
|
|
9574
|
+
...compositeActions > 0 ? [`${compositeActions} local action${compositeActions === 1 ? "" : "s"}`] : []
|
|
9575
|
+
];
|
|
9576
|
+
const full = `${baseLabel} (${[...parts, `${delegatedCount} (${kinds.join(", ")})`].join("; ")})`;
|
|
9577
|
+
return full.length <= EVIDENCE_LABEL_MAX_LENGTH ? full : `${baseLabel} (${[...parts, delegatedCount].join("; ")})`;
|
|
9578
|
+
}
|
|
9579
|
+
var EVIDENCE_LABEL_MAX_LENGTH = 180;
|
|
9580
|
+
var NO_DELEGATED_CI_STEPS = {
|
|
9581
|
+
crossRepositoryReusableWorkflows: 0,
|
|
9582
|
+
compositeActions: 0
|
|
9583
|
+
};
|
|
9584
|
+
function sumDelegatedCiSteps(left, right) {
|
|
9585
|
+
return {
|
|
9586
|
+
crossRepositoryReusableWorkflows: left.crossRepositoryReusableWorkflows + right.crossRepositoryReusableWorkflows,
|
|
9587
|
+
compositeActions: left.compositeActions + right.compositeActions
|
|
9588
|
+
};
|
|
9589
|
+
}
|
|
9590
|
+
var CROSS_REPOSITORY_REUSABLE_WORKFLOW_PATTERN = /^[^./\s][^/\s]*\/[^/\s]+\/\.github\/workflows\/[^@\s]+\.ya?ml@\S+$/u;
|
|
9591
|
+
function countDelegatedCiSteps(root, workflows) {
|
|
9592
|
+
let crossRepositoryReusableWorkflows = 0;
|
|
9593
|
+
let compositeActions = 0;
|
|
9594
|
+
for (const file of workflows) {
|
|
9595
|
+
if (!/^\.github\/workflows\//.test(file)) continue;
|
|
9596
|
+
const fullPath = join(root, file);
|
|
9597
|
+
if (!isRegularFile(fullPath)) continue;
|
|
9598
|
+
for (const uses of extractWorkflowUsesValues(readRepoText(fullPath, "utf8"))) {
|
|
9599
|
+
if (CROSS_REPOSITORY_REUSABLE_WORKFLOW_PATTERN.test(uses)) {
|
|
9600
|
+
crossRepositoryReusableWorkflows += 1;
|
|
9601
|
+
continue;
|
|
9602
|
+
}
|
|
9603
|
+
if (!uses.startsWith("./") || /^\.\/\.github\/workflows\//.test(uses)) continue;
|
|
9604
|
+
const directory = uses.slice(2).replace(/\/+$/u, "");
|
|
9605
|
+
if (directory.length === 0 || directory.split("/").includes("..")) continue;
|
|
9606
|
+
if (isRegularFile(join(root, directory, "action.yml")) || isRegularFile(join(root, directory, "action.yaml"))) {
|
|
9607
|
+
compositeActions += 1;
|
|
9608
|
+
}
|
|
9609
|
+
}
|
|
9610
|
+
}
|
|
9611
|
+
return { crossRepositoryReusableWorkflows, compositeActions };
|
|
9612
|
+
}
|
|
9613
|
+
function extractWorkflowUsesValues(contents) {
|
|
9614
|
+
const values = [];
|
|
9615
|
+
let blockScalarIndentation = null;
|
|
9616
|
+
for (const line of contents.split(/\r?\n/u)) {
|
|
9617
|
+
const indentation = /^\s*/u.exec(line)?.[0].length ?? 0;
|
|
9618
|
+
if (blockScalarIndentation !== null) {
|
|
9619
|
+
if (line.trim().length === 0 || indentation > blockScalarIndentation) continue;
|
|
9620
|
+
blockScalarIndentation = null;
|
|
9621
|
+
}
|
|
9622
|
+
const match = /^\s*(?:-\s*)?uses\s*:\s*(.*)$/u.exec(line);
|
|
9623
|
+
if (match) {
|
|
9624
|
+
const value = (match[1] ?? "").replace(/\s+#.*$/u, "").trim();
|
|
9625
|
+
const quoted = /^(?:"(.*)"|'(.*)')$/u.exec(value);
|
|
9626
|
+
values.push(quoted ? quoted[1] ?? quoted[2] ?? "" : value);
|
|
9627
|
+
continue;
|
|
9628
|
+
}
|
|
9629
|
+
if (/:\s*[|>][-+0-9]*\s*(?:#.*)?$/u.test(line)) blockScalarIndentation = indentation;
|
|
9630
|
+
}
|
|
9631
|
+
return values;
|
|
8413
9632
|
}
|
|
8414
9633
|
function unreadCiWorkflowReasons(includes) {
|
|
8415
9634
|
const unread = /* @__PURE__ */ new Map();
|
|
@@ -8428,6 +9647,19 @@ function rootOnlyPackageScriptsDisclosure(repoFiles, rootPackageJson) {
|
|
|
8428
9647
|
const present = `${others}${rootPackageJson ? " other" : ""} package.json file${others === 1 ? "" : "s"} present`;
|
|
8429
9648
|
return `${rootPackageJson ? "" : "no root package.json found; "}package scripts for this metric are read from the root package.json only; ${present}`;
|
|
8430
9649
|
}
|
|
9650
|
+
function workspacePackageScriptsDisclosure(repoFiles, rootPackageJson, workspace) {
|
|
9651
|
+
const readCount = workspace.read.length;
|
|
9652
|
+
const workspaceRead = workspace.declared > readCount ? `the first ${readCount} of ${workspace.declared} declared workspace package.json files` : `${readCount} declared workspace package.json file${readCount === 1 ? "" : "s"}`;
|
|
9653
|
+
const read = new Set(workspace.read);
|
|
9654
|
+
const others = repoFiles.filter(
|
|
9655
|
+
(file) => file !== rootPackageJson && !read.has(file) && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
9656
|
+
).length;
|
|
9657
|
+
return [
|
|
9658
|
+
...rootPackageJson ? [] : ["no root package.json found"],
|
|
9659
|
+
`package scripts for this metric are read from ${rootPackageJson ? `the root package.json and ${workspaceRead}` : workspaceRead}`,
|
|
9660
|
+
...others > 0 ? [`${others} other package.json file${others === 1 ? "" : "s"} present`] : []
|
|
9661
|
+
].join("; ");
|
|
9662
|
+
}
|
|
8431
9663
|
function isRegularFile(path) {
|
|
8432
9664
|
try {
|
|
8433
9665
|
return lstatSync(path).isFile();
|