@cejel/cejel 0.6.4 → 0.6.6

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