@cejel/cejel 0.6.3 → 0.6.5

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.
@@ -532,6 +532,8 @@ var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
532
532
  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
+ 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";
535
537
  var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
536
538
  var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
537
539
  WITAN_RUBRIC_VERSION_V18,
@@ -540,7 +542,9 @@ var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
540
542
  WITAN_RUBRIC_VERSION_V21,
541
543
  WITAN_RUBRIC_VERSION_V22,
542
544
  WITAN_RUBRIC_VERSION_V23,
543
- WITAN_RUBRIC_VERSION_V24
545
+ WITAN_RUBRIC_VERSION_V24,
546
+ WITAN_RUBRIC_VERSION_V25,
547
+ WITAN_RUBRIC_VERSION_V26
544
548
  ]);
545
549
  var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
546
550
  WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
@@ -566,6 +570,8 @@ var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a meas
566
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.";
567
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.";
568
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.";
569
575
  function isWitanNoMeasurementAbstention(report) {
570
576
  return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
571
577
  ({ status }) => status === "not_applicable" || status === "insufficient_data"
@@ -642,14 +648,21 @@ init_esm_shims();
642
648
  init_esm_shims();
643
649
  import { createHash } from "crypto";
644
650
  var WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS = Object.freeze({
645
- [WITAN_RUBRIC_VERSION_V17]: "sha256:2da86acf64c39d2edb7824df46250ea23c396264266a2cd925953e9c6ad25858",
646
- [WITAN_RUBRIC_VERSION_V18]: "sha256:60917d1d81efe7cda6f6adbfaa05293e2d9bf1db37b39d084e47f9011a996b67",
647
- [WITAN_RUBRIC_VERSION_V19]: "sha256:e050ce8563de0736d9cfb6c23e2ba04725d88465d378a8632576269b9bc9f269",
648
- [WITAN_RUBRIC_VERSION_V20]: "sha256:0433adc34b58c0fd5500a0bdeaa910c854eedbadbeaabb9045238d4ba9be8878",
649
- [WITAN_RUBRIC_VERSION_V21]: "sha256:d3796b78a45ea3593f6abe71c77c2d00a0056d0f2d88476a01329dc342166aa6",
650
- [WITAN_RUBRIC_VERSION_V22]: "sha256:932bc09cbe555c6058a95be581ccf7a286c43887f90d32698f5273b7623824bf",
651
- [WITAN_RUBRIC_VERSION_V23]: "sha256:db97dc999d37e3c39c3bd3e4b1778c94eb9ad3176fb5c7d7264a7e510b162fc1",
652
- [WITAN_RUBRIC_VERSION_V24]: "sha256:e313fab25ae1779a11e86b23696881cd6f8fe2a845609dc93e2ff6f3ff8c16d5"
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"
653
666
  });
654
667
  function rubricBehaviourFingerprint(rubricVersion) {
655
668
  return WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS[rubricVersion];
@@ -717,7 +730,7 @@ var WITAN_RUBRIC = [
717
730
 
718
731
  // src/witan/scoring.ts
719
732
  function usesV17DetectorClosure(rubricVersion) {
720
- 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_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;
721
734
  }
722
735
  var MAX_SIGNAL_ADJUSTMENT = 0.8;
723
736
  function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC, toolVersion) {
@@ -888,7 +901,7 @@ function statusForScore(score) {
888
901
  }
889
902
  function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
890
903
  if (adjustment === 0) return nativeStatus;
891
- 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_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) {
892
905
  return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
893
906
  }
894
907
  const statusFloor = {
@@ -945,7 +958,7 @@ function mergeSignalsByCriterion(signals) {
945
958
  return merged;
946
959
  }
947
960
  function usesMetricScoring(rubricVersion) {
948
- 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_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;
949
962
  }
950
963
  function unmeasuredStatus(rubricVersion) {
951
964
  return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
@@ -1333,7 +1346,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
1333
1346
  return void 0;
1334
1347
  }
1335
1348
  function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
1336
- 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_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) {
1337
1350
  return [...findings];
1338
1351
  }
1339
1352
  if (status !== "critical" && status !== "warning") return [...findings];
@@ -1479,7 +1492,7 @@ var CERTIFICATE_GLOSSARY = [
1479
1492
  {
1480
1493
  key: "production-readiness-basic-checks",
1481
1494
  term: "Production-readiness basic checks",
1482
- 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.",
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.",
1483
1496
  metricNames: ["prod_readiness_primitives"]
1484
1497
  },
1485
1498
  {
@@ -1611,7 +1624,7 @@ var CERTIFICATE_GLOSSARY = [
1611
1624
  {
1612
1625
  key: "protected-path-review-gate",
1613
1626
  term: "Protected-path review gate",
1614
- definition: "A binary check for a CODEOWNERS file or documentation of required review or branch protection. This is the repository-visible proxy for human review of sensitive-path changes.",
1627
+ definition: "A binary check for a CODEOWNERS file in a location GitHub reads (the repository root, .github/ or docs/), or a document stating a required-review or branch-protection policy. A changelog, an experiment or calibration record, a review note or a report does not count. This is the repository-visible proxy for human review of sensitive-path changes.",
1615
1628
  metricNames: ["protected_path_review_gate"]
1616
1629
  }
1617
1630
  ];
@@ -1710,7 +1723,7 @@ var BINARY_METRIC_COPY = {
1710
1723
  },
1711
1724
  protected_path_review_gate: {
1712
1725
  positive: "present \u2014 CODEOWNERS or a documented required-review policy was detected",
1713
- negative: "missing \u2014 no CODEOWNERS or documented required-review policy detected"
1726
+ negative: "missing \u2014 no CODEOWNERS file in a location GitHub reads, and no documented required-review or branch-protection policy detected"
1714
1727
  }
1715
1728
  };
1716
1729
  var CONDITIONAL_METRIC_COPY = {
@@ -1958,6 +1971,17 @@ function buildWithheldPathsGapSentence(report) {
1958
1971
  const reportsOnWhatItRead = signals.length === 1 ? "that signal reports on what it read" : "these signals report on what they read";
1959
1972
  return `${count} ${entryWord} withheld (${reasonLabel}); ${signalList} would have read ${pronoun}. Under this rubric ${reportsOnWhatItRead}; the prospective v23 rubric abstains instead.`;
1960
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
+ }
1961
1985
  function describeEstablishedDimensions(report, measured, applicable) {
1962
1986
  const total = report.criteria.length;
1963
1987
  const counts = formatMeasuredDimensionCounts(computeMeasuredCoverage(report));
@@ -2016,6 +2040,7 @@ function buildRelyingPartySummary(report) {
2016
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.`
2017
2041
  );
2018
2042
  }
2043
+ gaps.push(...settlingEvidenceDisclosures(report));
2019
2044
  const withheldPathsSentence = buildWithheldPathsGapSentence(report);
2020
2045
  if (withheldPathsSentence !== void 0) {
2021
2046
  gaps.push(withheldPathsSentence);
@@ -2892,7 +2917,7 @@ import {
2892
2917
  realpathSync,
2893
2918
  statSync
2894
2919
  } from "fs";
2895
- import { basename, dirname, extname, join, relative, resolve, sep } from "path";
2920
+ import { basename, dirname, extname, join, posix, relative, resolve, sep } from "path";
2896
2921
 
2897
2922
  // src/witan/json-safe.ts
2898
2923
  init_esm_shims();
@@ -2902,7 +2927,7 @@ function stripBom(text) {
2902
2927
 
2903
2928
  // src/witan/repo-signals.ts
2904
2929
  function usesV17DetectorClosure2(rubricVersion) {
2905
- 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;
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;
2906
2931
  }
2907
2932
  var LEGACY_INVENTORY_ABSENCE_CONTEXT = {
2908
2933
  rubricGeneration: "legacy",
@@ -2917,13 +2942,28 @@ var INVENTORY_SCAN_PATTERN_SETS = {
2917
2942
  id: "cejel.core-a1.concrete-test-files.v1",
2918
2943
  count: 16
2919
2944
  },
2920
- a1CoverageConfigurationV1: {
2921
- id: "cejel.core-a1.coverage-configuration.v1",
2922
- count: 10
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
2923
2963
  },
2924
- a1CoverageConfigurationV2: {
2925
- id: "cejel.core-a1.coverage-configuration.v2",
2926
- count: 14
2964
+ a1CoverageConfigurationV4: {
2965
+ id: "cejel.core-a1.coverage-configuration.v4",
2966
+ count: 25
2927
2967
  },
2928
2968
  a2CurrentSecret: {
2929
2969
  id: "cejel.core-a2.current-secret-shape.v1",
@@ -2944,6 +2984,11 @@ var INVENTORY_SCAN_PATTERN_SETS = {
2944
2984
  a5ClaimRealityArtifact: {
2945
2985
  id: "cejel.core-a5.claim-reality-artifact.v1",
2946
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
2947
2992
  }
2948
2993
  };
2949
2994
  function inventoryScanDerivation(inventoryCount, patternSet, matchCount) {
@@ -3149,15 +3194,17 @@ function buildWitanInputFromRepoUntracked(options) {
3149
3194
  rubricGeneration: usesV17Detectors ? "current" : "legacy",
3150
3195
  inventoryProvenance: fileInventory.repoFileInventoryScope === "tracked-scan-eligible" ? "tracked-scan-eligible" : "directory-walk"
3151
3196
  };
3152
- 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;
3153
- 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;
3154
- 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;
3155
- const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
3156
- const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
3157
- const usesV23CommandCoverage = rubricVersion === WITAN_RUBRIC_VERSION_V23;
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;
3158
3203
  const usesV23PemPrivateKeyGrammar = rubricVersion === WITAN_RUBRIC_VERSION_V23;
3159
3204
  const usesV23WithheldPathAbstention = rubricVersion === WITAN_RUBRIC_VERSION_V23;
3160
3205
  const usesV24SecretContentContext = rubricVersion === WITAN_RUBRIC_VERSION_V24;
3206
+ const usesV25InlineHealthRoute = rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
3207
+ const usesV26SettlingEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
3161
3208
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
3162
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(
3163
3210
  options.repoPath,
@@ -3193,10 +3240,12 @@ function buildWitanInputFromRepoUntracked(options) {
3193
3240
  usesV20A3ExplicitGaps,
3194
3241
  usesV21ExecutedEscalations,
3195
3242
  usesV22PackageStartEntrypoint,
3196
- usesV23CommandCoverage,
3243
+ usesV23StrictCoverageAndRecipeRunners,
3197
3244
  usesV23PemPrivateKeyGrammar,
3198
3245
  usesV23WithheldPathAbstention,
3199
3246
  usesV24SecretContentContext,
3247
+ usesV25InlineHealthRoute,
3248
+ usesV26SettlingEvidence,
3200
3249
  reviewableSourceProof,
3201
3250
  inventoryAbsenceContext,
3202
3251
  scanLimitations
@@ -3811,7 +3860,7 @@ function buildNotApplicableSignal(criterionId, reason) {
3811
3860
  notApplicable: true
3812
3861
  };
3813
3862
  }
3814
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
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()) {
3815
3864
  const signals = [];
3816
3865
  const mono = resolveMonorepoContext(repoPath, scanLimitations);
3817
3866
  const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
@@ -3823,7 +3872,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3823
3872
  useV27Detectors,
3824
3873
  useV33Detectors,
3825
3874
  useV36Detectors,
3826
- useV23CommandCoverage,
3875
+ useV23StrictCoverageAndRecipeRunners,
3827
3876
  reviewableSourceProof,
3828
3877
  inventoryAbsenceContext
3829
3878
  )
@@ -3842,7 +3891,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3842
3891
  useV24SecretContentContext,
3843
3892
  useV18NativeRls,
3844
3893
  inventoryAbsenceContext,
3845
- scanLimitations
3894
+ scanLimitations,
3895
+ useV26SettlingEvidence
3846
3896
  )
3847
3897
  );
3848
3898
  const a3Signal = collectCriterion(
@@ -3854,7 +3904,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3854
3904
  useV20A3ExplicitGaps,
3855
3905
  useV22PackageStartEntrypoint,
3856
3906
  useV23WithheldPathAbstention,
3857
- inventoryAbsenceContext
3907
+ inventoryAbsenceContext,
3908
+ useV25InlineHealthRoute
3858
3909
  )
3859
3910
  );
3860
3911
  const a4Signal = collectCriterion(
@@ -3875,7 +3926,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3875
3926
  repoFiles,
3876
3927
  useV27Detectors,
3877
3928
  useV23WithheldPathAbstention,
3878
- inventoryAbsenceContext
3929
+ inventoryAbsenceContext,
3930
+ useV26SettlingEvidence
3879
3931
  )
3880
3932
  );
3881
3933
  const b1Signal = buildNotApplicableSignal(
@@ -3927,7 +3979,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3927
3979
  }
3928
3980
  return signals;
3929
3981
  }
3930
- function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV23CommandCoverage, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3982
+ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV23StrictCoverageAndRecipeRunners, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3931
3983
  const evidence = [];
3932
3984
  const findings = [];
3933
3985
  const allTestFiles = findConcreteTestFiles(
@@ -3938,10 +3990,10 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
3938
3990
  const testFiles = allTestFiles.slice(0, 8);
3939
3991
  const sourceFiles = repoFiles.filter(isSourceFile);
3940
3992
  const runnerFiles = repoFiles.filter(
3941
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) || useV23CommandCoverage && isRecipeTestEntry(repoPath, file))
3993
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) || useV23StrictCoverageAndRecipeRunners && isRecipeTestEntry(repoPath, file))
3942
3994
  ).slice(0, 6);
3943
3995
  const baseConfiguredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
3944
- const configuredRunnerFiles = useV23CommandCoverage ? [
3996
+ const configuredRunnerFiles = useV23StrictCoverageAndRecipeRunners ? [
3945
3997
  .../* @__PURE__ */ new Set([
3946
3998
  ...baseConfiguredRunnerFiles,
3947
3999
  ...repoFiles.filter((file) => isRecipeTestEntry(repoPath, file))
@@ -3950,11 +4002,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
3950
4002
  const packageJson = findRootPackageJson(repoFiles);
3951
4003
  const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
3952
4004
  const { coverageAnalysis, coveragePercent } = withContentReadSignal("A1", "coverage_percent", () => {
3953
- const analysis = useV23CommandCoverage ? analyzeCoverageConfiguration(repoPath, repoFiles, useV27Detectors) : {
3954
- coverageFiles: findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors),
3955
- unresolvedFiles: [],
3956
- hasCoverageCommand: packageScripts.has("coverage")
3957
- };
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
+ );
3958
4011
  return {
3959
4012
  coverageAnalysis: analysis,
3960
4013
  coveragePercent: readCoveragePercent(repoPath, analysis.coverageFiles.slice(0, 4))
@@ -4106,7 +4159,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
4106
4159
  evidence: firstEvidence,
4107
4160
  context: inventoryAbsenceContext,
4108
4161
  inventoryCount: repoFiles.length,
4109
- patternSet: useV23CommandCoverage ? INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV2 : INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV1,
4162
+ patternSet: useV23StrictCoverageAndRecipeRunners ? INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV4 : INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV3,
4110
4163
  matchCount: 0
4111
4164
  })
4112
4165
  );
@@ -4253,7 +4306,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
4253
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.`
4254
4307
  };
4255
4308
  }
4256
- 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) {
4257
4310
  const evidence = [];
4258
4311
  const findings = [];
4259
4312
  const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
@@ -4539,7 +4592,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
4539
4592
  evidence: historySecretScan.envPathEvidence
4540
4593
  });
4541
4594
  }
4542
- const hasTenantWithoutRlsGap = rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
4595
+ const hasTenantWithoutRlsGap = !useV26SettlingEvidence && rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
4543
4596
  if (hasTenantWithoutRlsGap) {
4544
4597
  const gapEvidence = rlsMigration ?? tenantScopedFile ?? tenantWithoutRlsPremiseFiles[0];
4545
4598
  if (gapEvidence) {
@@ -4565,6 +4618,28 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
4565
4618
  );
4566
4619
  }
4567
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
+ }
4568
4643
  evidence.push(...cryptoHygiene.evidence);
4569
4644
  findings.push(...cryptoHygiene.findings);
4570
4645
  if (evidence.length === 0 && findings.length === 0) return null;
@@ -4652,7 +4727,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
4652
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.",
4653
4728
  ...abstainsSecretCleanliness ? [
4654
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."
4655
- ] : []
4730
+ ] : [],
4731
+ ...tenantIsolationUnsettled ? [WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE] : []
4656
4732
  ].join(" ")
4657
4733
  };
4658
4734
  }
@@ -4752,7 +4828,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
4752
4828
  }
4753
4829
  return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
4754
4830
  }
4755
- function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
4831
+ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV25InlineHealthRoute = false) {
4756
4832
  const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
4757
4833
  const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
4758
4834
  const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
@@ -4790,8 +4866,16 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4790
4866
  )
4791
4867
  );
4792
4868
  const healthCheck = healthChecks[0];
4793
- const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && (isImplementationFile(file) || HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN.test(file));
4869
+ const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && (isImplementationFile(file) || HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN.test(file) || HEALTH_ROUTE_ENTRYPOINT_HOME_PATTERN.test(file));
4794
4870
  const observabilityDepthReads = (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file);
4871
+ const inlineHealthRouteFile = useV25InlineHealthRoute && healthChecks.length === 0 ? withContentReadSignal(
4872
+ "A3",
4873
+ "prod_readiness_primitives",
4874
+ () => repoFiles.find(
4875
+ (file) => healthReadinessRouteReads(file) && fileMatchesOutsideComments(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
4876
+ )
4877
+ ) : void 0;
4878
+ const hasHealthOrReadinessSignal = healthChecks.length > 0 || inlineHealthRouteFile !== void 0;
4795
4879
  const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
4796
4880
  hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
4797
4881
  (file) => healthReadinessRouteReads(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
@@ -4855,12 +4939,28 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4855
4939
  /rollback|roll back|migration safety|down migration|reversible|undo migration/i
4856
4940
  )
4857
4941
  );
4942
+ const rootOnlyScriptsNote = rootOnlyPackageScriptsDisclosure(repoFiles, packageJson);
4858
4943
  if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
4859
4944
  evidence.push(
4860
4945
  evidenceForRelative(repoPath, packageJson, "prod_check", "Build or typecheck script")
4861
4946
  );
4862
4947
  }
4863
- if (workflow) evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"));
4948
+ if (workflow) {
4949
+ evidence.push(
4950
+ evidenceForRelative(
4951
+ repoPath,
4952
+ workflow,
4953
+ "ci_run",
4954
+ ciWorkflowCitationLabel(
4955
+ "CI workflow",
4956
+ "counted",
4957
+ workflows.length,
4958
+ 1,
4959
+ unreadCiWorkflowReasons((file) => !useV27Detectors || isAuthoredProductionPath(file))
4960
+ )
4961
+ )
4962
+ );
4963
+ }
4864
4964
  if (releaseDeployConfig)
4865
4965
  evidence.push(
4866
4966
  evidenceForRelative(
@@ -4889,6 +4989,24 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4889
4989
  evidenceForRelative(repoPath, healthCheck, "prod_check", "Health/readiness signal")
4890
4990
  )
4891
4991
  );
4992
+ } else if (inlineHealthRouteFile) {
4993
+ withContentReadSignal(
4994
+ "A3",
4995
+ "prod_readiness_primitives",
4996
+ () => evidence.push(
4997
+ evidenceForRelativeAtLine(
4998
+ repoPath,
4999
+ inlineHealthRouteFile,
5000
+ "prod_check",
5001
+ "Health/readiness route",
5002
+ findFirstMatchingLineOutsideComments(
5003
+ repoPath,
5004
+ inlineHealthRouteFile,
5005
+ V20_HEALTH_OR_READINESS_ROUTE_PATTERN
5006
+ )
5007
+ )
5008
+ )
5009
+ );
4892
5010
  }
4893
5011
  if (errorBoundary) {
4894
5012
  evidence.push(
@@ -4928,8 +5046,13 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4928
5046
  }
4929
5047
  if (evidence.length === 0) return null;
4930
5048
  if (useV20ExplicitGaps) {
4931
- const hasBuildOrTypecheck = scripts.has("build") || scripts.has("typecheck");
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
+ });
4932
5054
  if (packageJson && !hasBuildOrTypecheck) {
5055
+ const workspaceRead = workspaceManifests.read.length;
4933
5056
  findings.push({
4934
5057
  severity: "info",
4935
5058
  summary: "A deployable service package manifest declares neither a build nor a typecheck script.",
@@ -4937,7 +5060,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
4937
5060
  repoPath,
4938
5061
  packageJson,
4939
5062
  "prod_check",
4940
- "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"
4941
5064
  )
4942
5065
  });
4943
5066
  } else if (runtimeContainer) {
@@ -5016,7 +5139,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
5016
5139
  workflows.length > 0,
5017
5140
  deployConfigs.length > 0,
5018
5141
  envTemplate,
5019
- healthChecks.length > 0,
5142
+ hasHealthOrReadinessSignal,
5020
5143
  errorBoundary !== void 0
5021
5144
  ].filter(Boolean).length,
5022
5145
  6,
@@ -5033,9 +5156,10 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
5033
5156
  { label: "CI workflow", count: workflows.length > 0 ? 1 : 0 },
5034
5157
  { label: "deployment configuration", count: deployConfigs.length > 0 ? 1 : 0 },
5035
5158
  { label: "environment template", count: envTemplate ? 1 : 0 },
5036
- { label: "health or readiness signal", count: healthChecks.length > 0 ? 1 : 0 },
5159
+ { label: "health or readiness signal", count: hasHealthOrReadinessSignal ? 1 : 0 },
5037
5160
  { label: "error boundary", count: errorBoundary !== void 0 ? 1 : 0 }
5038
- ]
5161
+ ],
5162
+ ...rootOnlyScriptsNote ? { condition: rootOnlyScriptsNote } : {}
5039
5163
  }
5040
5164
  ),
5041
5165
  metric(
@@ -5278,7 +5402,7 @@ function hasDeclaredVersionConstraint(dependency) {
5278
5402
  if (/[~^<>=!]/.test(trimmed)) return true;
5279
5403
  return /^v?\d/.test(trimmed);
5280
5404
  }
5281
- 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) {
5282
5406
  const claimImplementationReads = claimImplementationFileReads(useV27Detectors);
5283
5407
  abstainSignalOnWithheldPaths(
5284
5408
  "A5",
@@ -5372,10 +5496,40 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, use
5372
5496
  )
5373
5497
  );
5374
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;
5375
5507
  return {
5376
5508
  criterionId: "A5",
5377
5509
  positiveEvidence: evidence,
5378
- 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
+ ] : [
5379
5533
  inventoryAbsenceFinding({
5380
5534
  // Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
5381
5535
  // than a curated registry or reconciliation artifact. Downgraded to info when the repo
@@ -5422,16 +5576,19 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, use
5422
5576
  "Credits multiple claim-bearing documents without judging unverified prose as truth.",
5423
5577
  "saturating_count"
5424
5578
  ),
5425
- metric(
5426
- "reconciliation_artifact_depth",
5427
- "Reconciliation artifact depth",
5428
- reconciliationArtifacts.length,
5429
- 3,
5430
- 0.15,
5431
- "artifacts",
5432
- "Requires a specific claim-reality artifact to reach full depth."
5433
- )
5434
- ]
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 } : {}
5435
5592
  };
5436
5593
  }
5437
5594
  function collectB2PrTraceEvidence(repoPath, repoFiles) {
@@ -5442,8 +5599,16 @@ function collectB2PrTraceEvidence(repoPath, repoFiles) {
5442
5599
  const branchProtectionDoc = repoFiles.find(
5443
5600
  (file) => /branch.*protection|review.*gate/i.test(file) || GITHUB_CODEOWNERS_PATH_PATTERN.test(file)
5444
5601
  );
5602
+ const citedWorkflows = workflows.slice(0, 3);
5603
+ const workflowLabel = ciWorkflowCitationLabel(
5604
+ "Pull-request CI workflow",
5605
+ "counted",
5606
+ workflows.length,
5607
+ citedWorkflows.length,
5608
+ unreadCiWorkflowReasons(() => true)
5609
+ );
5445
5610
  const evidence = [
5446
- ...workflows.slice(0, 3).map((file) => evidenceForRelative(repoPath, file, "ci_run", "Pull-request CI workflow")),
5611
+ ...citedWorkflows.map((file) => evidenceForRelative(repoPath, file, "ci_run", workflowLabel)),
5447
5612
  ...prTemplate ? [evidenceForRelative(repoPath, prTemplate, "pull_request", "Pull request template")] : [],
5448
5613
  ...branchProtectionDoc ? [
5449
5614
  evidenceForRelative(
@@ -5516,11 +5681,35 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
5516
5681
  if (packageJson && scripts.has("lint")) {
5517
5682
  evidence.push(evidenceForRelative(repoPath, packageJson, "ci_run", "Lint script"));
5518
5683
  }
5684
+ const unreadWorkflows = unreadCiWorkflowReasons(() => true);
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
+ );
5519
5694
  if (workflow) {
5520
5695
  withContentReadSignals(
5521
5696
  "B3",
5522
5697
  ["ci_script_depth", "default_branch_ci_depth"],
5523
- () => evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"))
5698
+ () => evidence.push(
5699
+ evidenceForRelative(
5700
+ repoPath,
5701
+ workflow,
5702
+ "ci_run",
5703
+ ciWorkflowCitationLabel(
5704
+ "CI workflow",
5705
+ "read",
5706
+ workflowsRead,
5707
+ 1,
5708
+ unreadWorkflows,
5709
+ delegatedSteps
5710
+ )
5711
+ )
5712
+ )
5524
5713
  );
5525
5714
  } else if (rootWorkflows[0] && mono) {
5526
5715
  const rootWorkflow = rootWorkflows[0];
@@ -5528,7 +5717,19 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
5528
5717
  "B3",
5529
5718
  ["ci_script_depth", "default_branch_ci_depth"],
5530
5719
  () => evidence.push(
5531
- evidenceForRelative(mono.root, rootWorkflow, "ci_run", "CI workflow (monorepo root)")
5720
+ evidenceForRelative(
5721
+ mono.root,
5722
+ rootWorkflow,
5723
+ "ci_run",
5724
+ ciWorkflowCitationLabel(
5725
+ "CI workflow (monorepo root)",
5726
+ "read",
5727
+ workflowsRead,
5728
+ 1,
5729
+ unreadWorkflows,
5730
+ delegatedSteps
5731
+ )
5732
+ )
5532
5733
  )
5533
5734
  );
5534
5735
  }
@@ -5540,9 +5741,16 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
5540
5741
  CI_TYPECHECK_COMMAND_PATTERN,
5541
5742
  CI_BUILD_COMMAND_PATTERN
5542
5743
  ];
5543
- const scriptDepth = ["test", "lint", "typecheck", "build"].filter(
5544
- (s) => s === "test" ? hasCredibleTestScript(scripts) : scripts.has(s)
5545
- ).length;
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;
5546
5754
  const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
5547
5755
  "B3",
5548
5756
  "ci_script_depth",
@@ -5575,7 +5783,7 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
5575
5783
  components: [
5576
5784
  ...["test", "lint", "typecheck", "build"].map((name) => ({
5577
5785
  label: `package script: ${name === "typecheck" ? "type-check" : name}`,
5578
- count: (name === "test" ? hasCredibleTestScript(scripts) : scripts.has(name)) ? 1 : 0
5786
+ count: declaresScript(name) ? 1 : 0
5579
5787
  })),
5580
5788
  ...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
5581
5789
  label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
@@ -5585,7 +5793,8 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
5585
5793
  label: `monorepo-root CI command: ${["test", "lint", "type-check", "build"][index]}`,
5586
5794
  count: rootCategoryHits[index] ? 1 : 0
5587
5795
  })) : []
5588
- ]
5796
+ ],
5797
+ ...rootOnlyScriptsNote ? { condition: rootOnlyScriptsNote } : {}
5589
5798
  }
5590
5799
  ),
5591
5800
  metric(
@@ -6018,7 +6227,7 @@ var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:(?:jest|mocha|ava|tape|jasmine|kar
6018
6227
  var TIMING_SAFE_COMPARE_PATTERN = /\btiming[-_]?safe[-_]?(?:equal|compare)\b|crypto\.timingSafeEqual\s*\(/i;
6019
6228
  var CANONICAL_SERIALIZE_PATTERN = /\bcanonicalize\s*\(|\bcanonicalJson\b|\bstableStringify\s*\(|json-stable-stringify|safe-stable-stringify|\bsortKeys\s*\(/i;
6020
6229
  var SIGN_OR_HMAC_CALL_PATTERN = /\bcreateHmac\s*\(|\bcreateSign\s*\(|\.sign\s*\(/;
6021
- var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac|digest)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
6230
+ var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
6022
6231
  var DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN = /\b(?:secret|token|password|apiKey)\w*\s*(?:===|!==|==|!=)\s*[\w.]*default(?:Secret|Token|Password|ApiKey)\b/i;
6023
6232
  var NUMERIC_SHAPED_OPERAND_PATTERN = /^-?\d+$|\.(?:length|len|size|count)\b|(?:length|index|idx|offset|count|size|len)$/i;
6024
6233
  function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
@@ -6031,9 +6240,124 @@ function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
6031
6240
  function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
6032
6241
  const fullPath = join(repoPath, file);
6033
6242
  if (!isRegularFile(fullPath)) return null;
6034
- const lines = readRepoText(fullPath, "utf8").split("\n");
6243
+ const contents = readRepoText(fullPath, "utf8");
6244
+ const lines = contents.split("\n");
6035
6245
  const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
6036
- return index === -1 ? null : index + 1;
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;
6037
6361
  }
6038
6362
  function findFirstMatchingLine(repoPath, file, pattern) {
6039
6363
  const fullPath = join(repoPath, file);
@@ -6412,6 +6736,166 @@ var JS_TEST_ASSERTION_PATTERN = /(expect\(|assert\.|should\.|toEqual\(|toBe\(|as
6412
6736
  var CPP_TEST_ASSERTION_PATTERN = /\bEXPECT_|ASSERT_EQ\b|ASSERT_TRUE\b|ASSERT_FALSE\b|\bREQUIRE\s*\(|\bCHECK\s*\(|\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(/;
6413
6737
  var MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN = /\.assert[A-Z_]\w*\s*\(|\bassert[A-Z]\w*\s*\(|\bpytest\.raises\b|\bself\.fail\s*\(|\bt\.(?:Error|Errorf|Fatal|Fatalf|Fail|FailNow)\b|\brequire\.[A-Z]\w*\s*\(|\b(?:debug_)?assert(?:_eq|_ne)?!\s*\(|\bassert_\w+\b|\$this->assert\w+\s*\(/;
6414
6738
  var SUITE_LEVEL_SKIP_PATTERN = /\b(?:describe|suite|context)\.skip\s*\(/;
6739
+ function hasLiveAvaAssertion(contents) {
6740
+ const uncommented = stripJavaScriptComments(contents);
6741
+ const imports = [...uncommented.matchAll(/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s*['"]ava['"]|\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\s*\(\s*['"]ava['"]\s*\)/g)];
6742
+ if (!imports.length) return false;
6743
+ let quote = "";
6744
+ let escaped = false;
6745
+ const characters = uncommented.split("");
6746
+ for (let i = 0; i < characters.length; i += 1) {
6747
+ const ch = characters[i];
6748
+ if (quote) {
6749
+ characters[i] = ch === "\n" ? "\n" : " ";
6750
+ if (escaped) escaped = false;
6751
+ else if (ch === "\\") escaped = true;
6752
+ else if (ch === quote) quote = "";
6753
+ } else if (ch === "'" || ch === '"' || ch === "`") {
6754
+ quote = ch;
6755
+ characters[i] = " ";
6756
+ }
6757
+ }
6758
+ const code = characters.join("");
6759
+ const imported = imports.find((candidate) => code[candidate.index] === uncommented[candidate.index]);
6760
+ const runner = imported?.[1] ?? imported?.[2];
6761
+ if (!runner) return false;
6762
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6763
+ const calls = new RegExp(`\\b${escape(runner)}((?:\\.\\w+)*)\\s*\\(`, "g");
6764
+ const closeAt = (open, left, right) => {
6765
+ let depth = 1;
6766
+ for (let i = open + 1; i < code.length; i += 1) {
6767
+ if (code[i] === left) depth += 1;
6768
+ else if (code[i] === right && --depth === 0) return i;
6769
+ }
6770
+ return -1;
6771
+ };
6772
+ for (const call of code.matchAll(calls)) {
6773
+ const modifiers = call[1].split(".").filter(Boolean);
6774
+ if (modifiers.some((modifier) => !["serial", "only", "failing"].includes(modifier))) continue;
6775
+ const open = call.index + call[0].length - 1;
6776
+ const close = closeAt(open, "(", ")");
6777
+ if (close < 0) continue;
6778
+ const args = code.slice(open + 1, close);
6779
+ const callback = /(?:\basync\s+)?(?:\(\s*([A-Za-z_$][\w$]*)\s*\)|(?<![\w$])([A-Za-z_$][\w$]*))\s*=>|\bfunction(?:\s+\w+)?\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/.exec(args);
6780
+ const parameter = callback?.[1] ?? callback?.[2] ?? callback?.[3];
6781
+ if (!callback || !parameter) continue;
6782
+ let start = open + 1 + callback.index + callback[0].length;
6783
+ while (/\s/.test(code[start] ?? "") && start < close) start += 1;
6784
+ const end = code[start] === "{" ? closeAt(start, "{", "}") : close;
6785
+ if (end < 0 || end > close) continue;
6786
+ const assertion = new RegExp(`\\b${escape(parameter)}\\.(?:assert|pass|fail|truthy|falsy|true|false|is|not|deepEqual|notDeepEqual|throws|throwsAsync|notThrows|notThrowsAsync|regex|notRegex|snapshot|like)\\s*\\(`);
6787
+ const body = code.slice(start, end);
6788
+ const active = body.split("");
6789
+ let maskedThrough = -1;
6790
+ for (const nested of body.matchAll(/=>\s*|\bfunction(?:\s+\w+)?\s*\([^)]*\)\s*/g)) {
6791
+ if (nested.index < maskedThrough) continue;
6792
+ const begin = nested.index + nested[0].length;
6793
+ let stop = begin;
6794
+ if (body[begin] === "{") {
6795
+ stop = closeAt(start + begin, "{", "}") - start + 1;
6796
+ } else {
6797
+ let depth = 0;
6798
+ while (stop < body.length) {
6799
+ const ch = body[stop];
6800
+ if (depth === 0 && /[;,)]/.test(ch)) break;
6801
+ if (ch === "(" || ch === "[") depth += 1;
6802
+ else if (ch === ")" || ch === "]") depth -= 1;
6803
+ stop += 1;
6804
+ }
6805
+ }
6806
+ if (stop <= begin) continue;
6807
+ for (let i = nested.index; i < Math.min(stop, active.length); i += 1) active[i] = " ";
6808
+ maskedThrough = stop;
6809
+ }
6810
+ if (assertion.test(active.join(""))) return true;
6811
+ }
6812
+ return false;
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
+ }
6415
6899
  function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
6416
6900
  let nonHollowCount = 0;
6417
6901
  let ratedCount = 0;
@@ -6419,11 +6903,14 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
6419
6903
  const fullPath = join(repoPath, file);
6420
6904
  if (!isRegularFile(fullPath)) continue;
6421
6905
  const contents = readRepoText(fullPath, "utf8");
6422
- const nonHollow = (JS_TEST_ASSERTION_PATTERN.test(contents) || CPP_TEST_ASSERTION_PATTERN.test(contents) || MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN.test(contents)) && !SUITE_LEVEL_SKIP_PATTERN.test(contents);
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;
6423
6910
  if (nonHollow) {
6424
6911
  nonHollowCount += 1;
6425
6912
  ratedCount += 1;
6426
- } 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)) {
6427
6914
  ratedCount += 1;
6428
6915
  }
6429
6916
  }
@@ -6930,6 +7417,354 @@ function deriveRlsPolicyScopeInventory(repoPath, repoFiles) {
6930
7417
  storageFiles
6931
7418
  };
6932
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
+ }
6933
7768
  function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
6934
7769
  const explicitTargets = repoFiles.filter(
6935
7770
  (file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
@@ -7019,10 +7854,190 @@ function isAuthenticatedClaimRealityArtifact(repoPath, file) {
7019
7854
  }
7020
7855
  return false;
7021
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
+ }
7022
8016
  function isCoverageConfig(repoPath, file) {
7023
8017
  if (/(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file)) {
7024
8018
  return true;
7025
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
+ }
7026
8041
  if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
7027
8042
  return fileContains(repoPath, file, /\bcoverage\s*:/i);
7028
8043
  }
@@ -7054,19 +8069,76 @@ function packageJsonHasCoverageTooling(repoPath, file) {
7054
8069
  }
7055
8070
  }
7056
8071
  }
7057
- for (const key of ["nyc", "c8", "istanbul"]) {
7058
- const config = parsed[key];
7059
- if (typeof config === "object" && config !== null && !Array.isArray(config)) return true;
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
+ }
7060
8134
  }
7061
8135
  return false;
7062
8136
  }
7063
8137
  function isV23CoverageConfig(repoPath, file) {
7064
8138
  if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
7065
- const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
7066
- const property = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents)?.[1]?.trim();
7067
- if (!property || /^(?:false\b|null\b|undefined\b)/i.test(property)) return false;
7068
- if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
7069
- return true;
8139
+ return vitestCoveragePropertyCollects(
8140
+ stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"))
8141
+ );
7070
8142
  }
7071
8143
  if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
7072
8144
  const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
@@ -7126,6 +8198,10 @@ function packageJsonHasStaticCoverageTooling(repoPath, file) {
7126
8198
  if (!isRegularFile(fullPath)) return false;
7127
8199
  const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
7128
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
+ }
7129
8205
  return ["nyc", "c8", "istanbul"].some((key) => {
7130
8206
  const config = parsed[key];
7131
8207
  return typeof config === "object" && config !== null && !Array.isArray(config);
@@ -7207,6 +8283,19 @@ function analyzeCoverageConfiguration(repoPath, repoFiles, authoredOnly = false)
7207
8283
  detectorDecisions
7208
8284
  };
7209
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
+ }
7210
8299
  function stripJavaScriptComments(contents) {
7211
8300
  let output = "";
7212
8301
  let quote = null;
@@ -7577,14 +8666,89 @@ function coverageFlagCommandState(words) {
7577
8666
  if (executable === "vitest" || executable === "bnt") {
7578
8667
  return flagState(args, ["--coverage"], ["--no-coverage"]);
7579
8668
  }
8669
+ if (isPythonModuleRunner(executable, args)) {
8670
+ return flagState(args.slice(2), ["--cov"], ["--no-cov"]);
8671
+ }
8672
+ if (executable === "go") {
8673
+ if (args[0]?.toLowerCase() !== "test") return "none";
8674
+ const end = args.findIndex((arg, index) => index > 0 && /^--?args$/i.test(arg));
8675
+ return goTestCoverageState(end < 0 ? args.slice(1) : args.slice(1, end));
8676
+ }
8677
+ if (executable === "dotnet") {
8678
+ if (args[0]?.toLowerCase() !== "test") return "none";
8679
+ const end = args.indexOf("--", 1);
8680
+ return dotnetTestCoverageState(end < 0 ? args.slice(1) : args.slice(1, end));
8681
+ }
8682
+ if (executable === "cargo") {
8683
+ const subcommand = args[0]?.startsWith("+") ? args.slice(1) : args;
8684
+ if (subcommand[0]?.toLowerCase() !== "llvm-cov") return "none";
8685
+ if (["report", "clean", "show-env", "run"].includes(subcommand[1]?.toLowerCase() ?? "")) {
8686
+ return "none";
8687
+ }
8688
+ const end = subcommand.indexOf("--");
8689
+ const owned = end < 0 ? subcommand.slice(1) : subcommand.slice(1, end);
8690
+ return owned.some((arg) => arg.toLowerCase() === "--no-run") ? "none" : "collects";
8691
+ }
7580
8692
  return "none";
7581
8693
  }
8694
+ function isPythonModuleRunner(executable, args) {
8695
+ return /^(?:python[0-9.]*|py)$/.test(executable) && args[0] === "-m" && (args[1] === "pytest" || args[1] === "py.test");
8696
+ }
8697
+ function goTestCoverageState(args) {
8698
+ let enabled = false;
8699
+ let disabled = false;
8700
+ let unresolved = false;
8701
+ for (const raw of args) {
8702
+ const [name, assigned] = raw.toLowerCase().replace(/^--/u, "-").split("=", 2);
8703
+ if (name === "-covermode" || name === "-coverpkg" || name === "-coverprofile") {
8704
+ enabled = true;
8705
+ } else if (name === "-cover") {
8706
+ if (assigned === void 0 || /^(?:true|t|1)$/u.test(assigned)) enabled = true;
8707
+ else if (/^(?:false|f|0)$/u.test(assigned)) disabled = true;
8708
+ else unresolved = true;
8709
+ }
8710
+ }
8711
+ if (unresolved || enabled && disabled) return "unresolved";
8712
+ return enabled ? "collects" : "none";
8713
+ }
8714
+ function dotnetTestCoverageState(args) {
8715
+ let enabled = false;
8716
+ let disabled = false;
8717
+ let unresolved = false;
8718
+ for (let index = 0; index < args.length; index += 1) {
8719
+ const lower = (args[index] ?? "").toLowerCase();
8720
+ const collect = /^--collect(?:[:=]([\s\S]*))?$/u.exec(lower);
8721
+ if (collect) {
8722
+ const value = (collect[1] ?? args[index += 1]?.toLowerCase() ?? "").trim();
8723
+ if (/\$|\{|\}/u.test(value)) unresolved = true;
8724
+ else if (value === "xplat code coverage" || value === "code coverage") enabled = true;
8725
+ continue;
8726
+ }
8727
+ const property = /^(?:[/-]p|--property):([\s\S]*)$/u.exec(lower);
8728
+ if (!property) continue;
8729
+ for (const assignment of (property[1] ?? "").split(/[;,]/u)) {
8730
+ const [name, value] = assignment.split("=", 2).map((part) => part.trim());
8731
+ if (name !== "collectcoverage" || value === void 0) continue;
8732
+ if (value === "true") enabled = true;
8733
+ else if (value === "false") disabled = true;
8734
+ else unresolved = true;
8735
+ }
8736
+ }
8737
+ if (unresolved || enabled && disabled) return "unresolved";
8738
+ return enabled ? "collects" : "none";
8739
+ }
7582
8740
  function isRecognizedCoverageFlagRunner(words) {
7583
8741
  const command = unwrapPackageManagerExecutable(words);
7584
8742
  const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
7585
8743
  if (["bnt", "vitest", "jest", "pytest", "py.test"].includes(executable)) return true;
7586
8744
  if (executable === "node") return command.slice(1).includes("--test");
7587
- return (executable === "bun" || executable === "deno") && command[1]?.toLowerCase() === "test";
8745
+ if (isPythonModuleRunner(executable, command.slice(1))) return true;
8746
+ const subcommand = command[1]?.toLowerCase();
8747
+ if (executable === "go" || executable === "dotnet") return subcommand === "test";
8748
+ if (executable === "cargo") {
8749
+ return (subcommand?.startsWith("+") ? command[2] : command[1])?.toLowerCase() === "llvm-cov";
8750
+ }
8751
+ return (executable === "bun" || executable === "deno") && subcommand === "test";
7588
8752
  }
7589
8753
  function flagState(args, enablingNames, disablingNames) {
7590
8754
  let enabled = false;
@@ -8057,8 +9221,9 @@ var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start
8057
9221
  var SERVER_ENTRYPOINT_PATTERN = /\bapp\.listen\s*\(|\bserver\.listen\s*\(\s*(?:PORT|port|\d+)|http\.ListenAndServe\s*\(|http\.ListenAndServeTLS\s*\(|axum::Server(?:::|\.)|actix_web::HttpServer(?:::|\.)|uvicorn\.run\s*\(/;
8058
9222
  var V20_DIRECT_HTTP_SERVER_PATTERN = /\b(?:http|https)\.createServer\s*\([\s\S]{0,1500}?\)\.listen\s*\(\s*(?:PORT|port|\d+)/;
8059
9223
  var HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN = /(^|\/)functions\/.*\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|cpp|cc|cxx|c|h|hpp|kt|swift|php)$/;
9224
+ var HEALTH_ROUTE_ENTRYPOINT_HOME_PATTERN = /^(?:(?:server|index|app|main)\.(?:ts|tsx|js|jsx|mjs|cjs)|(?:server|backend|api)\/.*\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php))$/;
8060
9225
  var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`](?:[\w${}./-]{0,60}\/[_-]?(?:(?:health|ready|live)z?|readiness|liveness|up)(?=["'`/])|(?:healthz|readiness|liveness)(?=["'`]))|@Get\s*\(\s*(["'`])health\1\s*\)/i;
8061
- var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:_?err|_?error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*\)\s*(?:=>|\{)/i;
9226
+ var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:_?err|_?error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,?\s*\)\s*(?:=>|\{)/i;
8062
9227
  var EXPRESS_MIDDLEWARE_REACHABLE_PATTERN = /\.use\s*\(|\bmodule\.exports\b|\bexport\s+(?:default\b|function\b|const\b|class\b)/;
8063
9228
  var ERROR_BOUNDARY_FILENAME_PATTERN = /(^|\/)(?:error-boundary|error)\.(?:tsx|jsx|ts|js)$/;
8064
9229
  var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
@@ -8243,6 +9408,231 @@ function isV24IgnoredScanFile(file) {
8243
9408
  function findRootPackageJson(repoFiles) {
8244
9409
  return repoFiles.includes("package.json") ? "package.json" : null;
8245
9410
  }
9411
+ var WORKSPACE_PATTERN_LIMIT = 64;
9412
+ var WORKSPACE_PACKAGE_MANIFEST_LIMIT = 200;
9413
+ var NO_DECLARED_WORKSPACE_MANIFESTS = { read: [], declared: 0 };
9414
+ function findDeclaredWorkspaceManifests(repoPath, repoFiles, rootPackageJson) {
9415
+ const patterns = [];
9416
+ if (rootPackageJson) {
9417
+ const declared = readPackageJsonWorkspacePatterns(join(repoPath, rootPackageJson));
9418
+ if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9419
+ patterns.push(...declared);
9420
+ }
9421
+ if (repoFiles.includes("pnpm-workspace.yaml")) {
9422
+ const declared = readPnpmWorkspacePatterns(join(repoPath, "pnpm-workspace.yaml"));
9423
+ if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9424
+ patterns.push(...declared);
9425
+ }
9426
+ if (patterns.length === 0 || patterns.length > WORKSPACE_PATTERN_LIMIT) {
9427
+ return NO_DECLARED_WORKSPACE_MANIFESTS;
9428
+ }
9429
+ const include = [];
9430
+ const exclude = [];
9431
+ for (const pattern of patterns) {
9432
+ const negated = pattern.startsWith("!");
9433
+ const regex = workspaceGlobToRegExp(negated ? pattern.slice(1) : pattern);
9434
+ if (negated) {
9435
+ if (regex === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9436
+ exclude.push(regex);
9437
+ } else if (regex !== null) {
9438
+ include.push(regex);
9439
+ }
9440
+ }
9441
+ const matched = repoFiles.filter((file) => {
9442
+ if (file === rootPackageJson || !file.endsWith("/package.json")) return false;
9443
+ if (!isAuthoredProductionPath(file)) return false;
9444
+ const dir = file.slice(0, -"/package.json".length);
9445
+ return include.some((regex) => regex.test(dir)) && !exclude.some((regex) => regex.test(dir));
9446
+ }).sort();
9447
+ return { read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT), declared: matched.length };
9448
+ }
9449
+ function readPackageJsonWorkspacePatterns(packageJsonPath) {
9450
+ if (!isRegularFile(packageJsonPath)) return [];
9451
+ const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
9452
+ const workspaces = parsed?.workspaces;
9453
+ if (workspaces === void 0) return [];
9454
+ const list = isRecord(workspaces) ? workspaces.packages : workspaces;
9455
+ if (list === void 0 && isRecord(workspaces)) return [];
9456
+ if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) return null;
9457
+ return list;
9458
+ }
9459
+ function readPnpmWorkspacePatterns(yamlPath) {
9460
+ if (!isRegularFile(yamlPath)) return [];
9461
+ const patterns = [];
9462
+ let inPackages = false;
9463
+ for (const line of readRepoText(yamlPath, "utf8").split(/\r?\n/)) {
9464
+ if (!inPackages) {
9465
+ const key = /^packages\s*:(.*)$/.exec(line);
9466
+ if (!key) continue;
9467
+ const rest = stripYamlLineComment(key[1] ?? "").trim();
9468
+ if (rest === "") {
9469
+ inPackages = true;
9470
+ continue;
9471
+ }
9472
+ const flow = /^\[(.*)\]$/.exec(rest);
9473
+ if (!flow) return null;
9474
+ if ((flow[1] ?? "").trim() === "") return [];
9475
+ for (const item2 of (flow[1] ?? "").split(",")) {
9476
+ const scalar = yamlScalar(item2.trim());
9477
+ if (scalar === null) return null;
9478
+ patterns.push(scalar);
9479
+ }
9480
+ return patterns;
9481
+ }
9482
+ if (/^\s*(?:#.*)?$/.test(line)) continue;
9483
+ const item = /^\s*-\s+(.*)$/.exec(line);
9484
+ if (item) {
9485
+ const scalar = yamlScalar((item[1] ?? "").trim());
9486
+ if (scalar === null) return null;
9487
+ patterns.push(scalar);
9488
+ continue;
9489
+ }
9490
+ if (/^\S/.test(line)) break;
9491
+ return null;
9492
+ }
9493
+ return patterns;
9494
+ }
9495
+ function stripYamlLineComment(text) {
9496
+ return /^\s*#/.test(text) ? "" : text.replace(/\s+#.*$/, "");
9497
+ }
9498
+ function yamlScalar(raw) {
9499
+ const single = /^'((?:[^']|'')*)'\s*(?:#.*)?$/.exec(raw);
9500
+ if (single) return (single[1] ?? "").replace(/''/g, "'");
9501
+ const double = /^"([^"\\]*)"\s*(?:#.*)?$/.exec(raw);
9502
+ if (double) return double[1] ?? "";
9503
+ const plain = stripYamlLineComment(raw).trim();
9504
+ return plain === "" || /^["'[{&*|>]/.test(plain) ? null : plain;
9505
+ }
9506
+ function workspaceGlobToRegExp(pattern) {
9507
+ const normalised = pattern.trim().replace(/^\.\//, "").replace(/\/+$/, "");
9508
+ if (normalised === "" || normalised.startsWith("/") || /(^|\/)\.\.?(\/|$)/.test(normalised) || /[[\]{}()!+@\\]/.test(normalised)) {
9509
+ return null;
9510
+ }
9511
+ const segments = normalised.split("/").filter((segment, index, all) => !(segment === "**" && all[index - 1] === "**"));
9512
+ const anySegment = "(?!\\.)[^/]+";
9513
+ if (segments.length === 1 && segments[0] === "**") return new RegExp(`^${anySegment}(?:/${anySegment})*$`);
9514
+ let source = "";
9515
+ segments.forEach((segment, index) => {
9516
+ if (segment === "**") {
9517
+ source += index === 0 ? `(?:${anySegment}/)*` : `(?:/${anySegment})*`;
9518
+ return;
9519
+ }
9520
+ if (index > 0 && !(index === 1 && segments[0] === "**")) source += "/";
9521
+ const body = segment.split("").map(
9522
+ (char) => char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.^$|]/g, "\\$&")
9523
+ ).join("");
9524
+ source += /^[*?]/.test(segment) ? `(?!\\.)${body}` : body;
9525
+ });
9526
+ return new RegExp(`^${source}$`);
9527
+ }
9528
+ function ciWorkflowCitationLabel(baseLabel, verb, considered, cited, unread, delegated = NO_DELEGATED_CI_STEPS) {
9529
+ const unreadTotal = [...unread.values()].reduce((sum, count) => sum + count, 0);
9530
+ const { crossRepositoryReusableWorkflows: crossRepository, compositeActions } = delegated;
9531
+ const delegatedTotal = crossRepository + compositeActions;
9532
+ if (considered <= cited && unreadTotal === 0 && delegatedTotal === 0) return baseLabel;
9533
+ const parts = [
9534
+ `${considered} workflow file${considered === 1 ? "" : "s"} ${verb}`,
9535
+ `${cited} cited`
9536
+ ];
9537
+ if (unreadTotal > 0) {
9538
+ const reasons = [...unread.keys()].sort().join(", ");
9539
+ parts.push(`${unreadTotal} not read (${reasons})`);
9540
+ }
9541
+ if (delegatedTotal === 0) return `${baseLabel} (${parts.join("; ")})`;
9542
+ const delegatedCount = `${delegatedTotal} delegated step${delegatedTotal === 1 ? "" : "s"} not read`;
9543
+ const kinds = [
9544
+ ...crossRepository > 0 ? [
9545
+ `${crossRepository} reusable workflow${crossRepository === 1 ? " in another repository" : "s in other repositories"}`
9546
+ ] : [],
9547
+ ...compositeActions > 0 ? [`${compositeActions} local action${compositeActions === 1 ? "" : "s"}`] : []
9548
+ ];
9549
+ const full = `${baseLabel} (${[...parts, `${delegatedCount} (${kinds.join(", ")})`].join("; ")})`;
9550
+ return full.length <= EVIDENCE_LABEL_MAX_LENGTH ? full : `${baseLabel} (${[...parts, delegatedCount].join("; ")})`;
9551
+ }
9552
+ var EVIDENCE_LABEL_MAX_LENGTH = 180;
9553
+ var NO_DELEGATED_CI_STEPS = {
9554
+ crossRepositoryReusableWorkflows: 0,
9555
+ compositeActions: 0
9556
+ };
9557
+ function sumDelegatedCiSteps(left, right) {
9558
+ return {
9559
+ crossRepositoryReusableWorkflows: left.crossRepositoryReusableWorkflows + right.crossRepositoryReusableWorkflows,
9560
+ compositeActions: left.compositeActions + right.compositeActions
9561
+ };
9562
+ }
9563
+ var CROSS_REPOSITORY_REUSABLE_WORKFLOW_PATTERN = /^[^./\s][^/\s]*\/[^/\s]+\/\.github\/workflows\/[^@\s]+\.ya?ml@\S+$/u;
9564
+ function countDelegatedCiSteps(root, workflows) {
9565
+ let crossRepositoryReusableWorkflows = 0;
9566
+ let compositeActions = 0;
9567
+ for (const file of workflows) {
9568
+ if (!/^\.github\/workflows\//.test(file)) continue;
9569
+ const fullPath = join(root, file);
9570
+ if (!isRegularFile(fullPath)) continue;
9571
+ for (const uses of extractWorkflowUsesValues(readRepoText(fullPath, "utf8"))) {
9572
+ if (CROSS_REPOSITORY_REUSABLE_WORKFLOW_PATTERN.test(uses)) {
9573
+ crossRepositoryReusableWorkflows += 1;
9574
+ continue;
9575
+ }
9576
+ if (!uses.startsWith("./") || /^\.\/\.github\/workflows\//.test(uses)) continue;
9577
+ const directory = uses.slice(2).replace(/\/+$/u, "");
9578
+ if (directory.length === 0 || directory.split("/").includes("..")) continue;
9579
+ if (isRegularFile(join(root, directory, "action.yml")) || isRegularFile(join(root, directory, "action.yaml"))) {
9580
+ compositeActions += 1;
9581
+ }
9582
+ }
9583
+ }
9584
+ return { crossRepositoryReusableWorkflows, compositeActions };
9585
+ }
9586
+ function extractWorkflowUsesValues(contents) {
9587
+ const values = [];
9588
+ let blockScalarIndentation = null;
9589
+ for (const line of contents.split(/\r?\n/u)) {
9590
+ const indentation = /^\s*/u.exec(line)?.[0].length ?? 0;
9591
+ if (blockScalarIndentation !== null) {
9592
+ if (line.trim().length === 0 || indentation > blockScalarIndentation) continue;
9593
+ blockScalarIndentation = null;
9594
+ }
9595
+ const match = /^\s*(?:-\s*)?uses\s*:\s*(.*)$/u.exec(line);
9596
+ if (match) {
9597
+ const value = (match[1] ?? "").replace(/\s+#.*$/u, "").trim();
9598
+ const quoted = /^(?:"(.*)"|'(.*)')$/u.exec(value);
9599
+ values.push(quoted ? quoted[1] ?? quoted[2] ?? "" : value);
9600
+ continue;
9601
+ }
9602
+ if (/:\s*[|>][-+0-9]*\s*(?:#.*)?$/u.test(line)) blockScalarIndentation = indentation;
9603
+ }
9604
+ return values;
9605
+ }
9606
+ function unreadCiWorkflowReasons(includes) {
9607
+ const unread = /* @__PURE__ */ new Map();
9608
+ for (const { path, reasons } of withheldRepoPaths()) {
9609
+ if (!isCiWorkflow(path) || !includes(path)) continue;
9610
+ const reason = [...reasons].sort()[0];
9611
+ if (reason !== void 0) unread.set(reason, (unread.get(reason) ?? 0) + 1);
9612
+ }
9613
+ return unread;
9614
+ }
9615
+ function rootOnlyPackageScriptsDisclosure(repoFiles, rootPackageJson) {
9616
+ const others = repoFiles.filter(
9617
+ (file) => file !== rootPackageJson && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
9618
+ ).length;
9619
+ if (others === 0) return null;
9620
+ const present = `${others}${rootPackageJson ? " other" : ""} package.json file${others === 1 ? "" : "s"} present`;
9621
+ return `${rootPackageJson ? "" : "no root package.json found; "}package scripts for this metric are read from the root package.json only; ${present}`;
9622
+ }
9623
+ function workspacePackageScriptsDisclosure(repoFiles, rootPackageJson, workspace) {
9624
+ const readCount = workspace.read.length;
9625
+ 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"}`;
9626
+ const read = new Set(workspace.read);
9627
+ const others = repoFiles.filter(
9628
+ (file) => file !== rootPackageJson && !read.has(file) && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
9629
+ ).length;
9630
+ return [
9631
+ ...rootPackageJson ? [] : ["no root package.json found"],
9632
+ `package scripts for this metric are read from ${rootPackageJson ? `the root package.json and ${workspaceRead}` : workspaceRead}`,
9633
+ ...others > 0 ? [`${others} other package.json file${others === 1 ? "" : "s"} present`] : []
9634
+ ].join("; ");
9635
+ }
8246
9636
  function isRegularFile(path) {
8247
9637
  try {
8248
9638
  return lstatSync(path).isFile();