@cejel/cejel 0.1.8 → 0.1.9

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.
@@ -4077,7 +4077,10 @@ var WITAN_RUBRIC_VERSION_V3 = "witan-rubric-v3-2026-07-13";
4077
4077
  var WITAN_RUBRIC_VERSION_V4 = "witan-rubric-v4-2026-07-18";
4078
4078
  var WITAN_RUBRIC_VERSION_V5 = "witan-rubric-v5-2026-07-18";
4079
4079
  var WITAN_RUBRIC_VERSION_V6 = "witan-rubric-v6-2026-07-21";
4080
- var WITAN_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V6;
4080
+ var WITAN_RUBRIC_VERSION_V7 = "witan-rubric-v7-2026-07-21";
4081
+ var WITAN_RUBRIC_VERSION_V8 = "witan-rubric-v8-2026-07-21";
4082
+ var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
4083
+ var WITAN_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V9;
4081
4084
  var WITAN_TRADING_RUBRIC_VERSION_V0 = "witan-trading-rubric-v0-2026-07-01";
4082
4085
  var WITAN_CRITERION_IDS = [
4083
4086
  "A1",
@@ -4145,6 +4148,8 @@ var WITAN_REPO_ARCHETYPES = [
4145
4148
  "monorepo",
4146
4149
  "docs_only",
4147
4150
  "binary_only",
4151
+ "generated_only",
4152
+ "non_cohesive_source",
4148
4153
  "unrecognised_ecosystem",
4149
4154
  "empty"
4150
4155
  ];
@@ -4360,6 +4365,39 @@ var WitanAttestationStatementSchema = external_exports.object({
4360
4365
  }).strict()
4361
4366
  }).strict();
4362
4367
 
4368
+ // src/witan/coverage.ts
4369
+ var LOW_CONFIDENCE_COVERAGE_THRESHOLD = 0.5;
4370
+ function isMeasuredCriterionStatus(status) {
4371
+ return status !== "not_applicable" && status !== "insufficient_data";
4372
+ }
4373
+ function computeMeasuredCoverage(report) {
4374
+ const byCategory = [];
4375
+ const overall = { measured: 0, total: 0 };
4376
+ for (const criterion of report.criteria) {
4377
+ let bucket = byCategory.find((entry) => entry.category === criterion.category);
4378
+ if (!bucket) {
4379
+ bucket = { category: criterion.category, measured: 0, total: 0 };
4380
+ byCategory.push(bucket);
4381
+ }
4382
+ bucket.total += 1;
4383
+ overall.total += 1;
4384
+ if (isMeasuredCriterionStatus(criterion.status)) {
4385
+ bucket.measured += 1;
4386
+ overall.measured += 1;
4387
+ }
4388
+ }
4389
+ const lowConfidence = [...byCategory, overall].some(
4390
+ (counts) => counts.total > 0 && counts.measured / counts.total < LOW_CONFIDENCE_COVERAGE_THRESHOLD
4391
+ );
4392
+ return { byCategory, overall, lowConfidence };
4393
+ }
4394
+ function shortCategoryLabel(category) {
4395
+ return category.replace(/_trust$/, "").replaceAll("_", " ");
4396
+ }
4397
+ function formatCoverageCounts(coverage) {
4398
+ return coverage.byCategory.map((entry) => `${shortCategoryLabel(entry.category)} ${entry.measured}/${entry.total}`).join(" \xB7 ");
4399
+ }
4400
+
4363
4401
  // src/witan/external-findings.ts
4364
4402
  var EXTERNAL_FINDINGS_DISPLAY_LIMIT = 10;
4365
4403
  var SEVERITY_RANK = {
@@ -4424,6 +4462,7 @@ function renderWitanHtmlReport(report) {
4424
4462
  const contributingSources = renderContributingSources(report.consumedSignals ?? []);
4425
4463
  const externalSourceSummaries = summarizeExternalSources(report.consumedSignals ?? []);
4426
4464
  const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
4465
+ const coverage = computeMeasuredCoverage(report);
4427
4466
  return `<!doctype html>
4428
4467
  <html lang="en">
4429
4468
  <head>
@@ -4457,9 +4496,10 @@ function renderWitanHtmlReport(report) {
4457
4496
  <div class="score-unit">/ 4.0 overall</div>
4458
4497
  <div class="verdict">${escapeHtml(renderVerdict(report.overallScore))}</div>
4459
4498
  <div class="subscores">
4460
- <span>Code ${formatScore(report.codeTrustScore)}</span>
4461
- <span>Process ${formatScore(report.processTrustScore)}</span>
4462
- </div>`}
4499
+ <span>Code ${renderCategoryScore(report.codeTrustScore, coverage, "code_trust")}</span>
4500
+ <span>Process ${renderCategoryScore(report.processTrustScore, coverage, "process_trust")}</span>
4501
+ </div>
4502
+ <div class="coverage-note">${escapeHtml(formatCoverageCounts(coverage))} measured${coverage.lowConfidence ? " \xB7 low confidence" : ""}</div>`}
4463
4503
  </aside>
4464
4504
  </div>
4465
4505
  </header>
@@ -4557,10 +4597,17 @@ function renderCriterionCard(criterion) {
4557
4597
  </article>`;
4558
4598
  }
4559
4599
  function renderMetric(metric2) {
4560
- const max = metric2.max ? `/${formatMetricValue(metric2.max)}` : "";
4561
4600
  const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
4601
+ if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
4602
+ return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue(metric2.max)}${unit} (capped; ${formatMetricValue(metric2.value)} raw)</span>`;
4603
+ }
4604
+ const max = metric2.max ? `/${formatMetricValue(metric2.max)}` : "";
4562
4605
  return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue(metric2.value)}${max}${unit}</span>`;
4563
4606
  }
4607
+ function renderCategoryScore(score, coverage, category) {
4608
+ const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
4609
+ return categoryCoverage !== void 0 && categoryCoverage.measured === 0 || score === null ? "not measured" : formatScore(score);
4610
+ }
4564
4611
  function renderStatusChip(status) {
4565
4612
  return `<span class="status" data-status="${status}">${status}</span>`;
4566
4613
  }
@@ -4701,6 +4748,7 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
4701
4748
  .verdict { margin-top: 12px; font-family: var(--serif); font-size: 30px; color: var(--periwinkle); }
4702
4749
  .insufficient-note { margin-top: 10px; color: var(--muted); font-size: 13px; line-height: 1.5; }
4703
4750
  .subscores { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
4751
+ .coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
4704
4752
  .source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
4705
4753
  .trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
4706
4754
  .external-findings-section { margin-top: 18px; }
@@ -4915,21 +4963,59 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
4915
4963
  // src/witan/repo-signals.ts
4916
4964
  import { execFileSync } from "child_process";
4917
4965
  import { createHash as createHash2 } from "crypto";
4918
- import { lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
4919
- import { basename, join, relative, resolve, sep } from "path";
4966
+ import {
4967
+ closeSync,
4968
+ lstatSync,
4969
+ openSync,
4970
+ readFileSync,
4971
+ readSync,
4972
+ readdirSync,
4973
+ realpathSync,
4974
+ statSync
4975
+ } from "fs";
4976
+ import { basename, dirname, extname, join, relative, resolve, sep } from "path";
4920
4977
 
4921
4978
  // src/witan/json-safe.ts
4922
4979
  function stripBom(text) {
4923
4980
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
4924
4981
  }
4925
4982
 
4983
+ // src/witan/rubric-version.ts
4984
+ var WITAN_RUBRIC_VERSION_V92 = "witan-rubric-v9-2026-07-22";
4985
+ var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
4986
+ var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
4987
+ var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
4988
+ var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
4989
+ var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
4990
+ var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
4991
+
4926
4992
  // src/witan/repo-signals.ts
4927
4993
  function buildWitanInputFromRepo(options) {
4928
4994
  assertRepoPathExists(options.repoPath);
4929
4995
  const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4996
+ const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
4930
4997
  const headSha = readGitHead(options.repoPath);
4931
4998
  const repoFiles = listRepoFiles(options.repoPath);
4932
- const archetype = classifyRepoArchetype(repoFiles);
4999
+ const inventoryFiles = listRepoInventory(options.repoPath, repoFiles);
5000
+ const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
5001
+ const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? applyReadableSourceRepresentationGate(
5002
+ options.repoPath,
5003
+ inventoryFiles,
5004
+ structuralArchetype,
5005
+ headSha,
5006
+ rubricVersion
5007
+ ) : structuralArchetype;
5008
+ const archetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? applySemanticSourceRepresentationGate(
5009
+ options.repoPath,
5010
+ inventoryFiles,
5011
+ readableArchetype,
5012
+ rubricVersion
5013
+ ) : readableArchetype;
5014
+ const usesV8Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5015
+ const usesV33Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5016
+ const usesV36Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5017
+ const usesV39Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15;
5018
+ const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15;
4933
5019
  return {
4934
5020
  productSlug: options.productSlug,
4935
5021
  productDisplayName: options.productDisplayName,
@@ -4942,7 +5028,16 @@ function buildWitanInputFromRepo(options) {
4942
5028
  archetype: archetype.archetype,
4943
5029
  ...archetype.insufficientSourceReason ? { insufficientSourceReason: archetype.insufficientSourceReason } : {},
4944
5030
  signals: [
4945
- ...collectRepoSignals(options.repoPath, generatedAt, repoFiles),
5031
+ ...collectRepoSignals(
5032
+ options.repoPath,
5033
+ generatedAt,
5034
+ repoFiles,
5035
+ usesV8Detectors,
5036
+ usesV33Detectors,
5037
+ usesV36Detectors,
5038
+ usesV39Detectors,
5039
+ usesV47Detectors
5040
+ ),
4946
5041
  ...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
4947
5042
  ...options.additionalSignals ?? []
4948
5043
  ]
@@ -4967,7 +5062,7 @@ var SOURCE_LANGUAGES = [
4967
5062
  { name: "Dart", tier: "partial", extensions: ["dart"] },
4968
5063
  { name: "Elixir", tier: "partial", extensions: ["ex", "exs"] },
4969
5064
  { name: "Scala", tier: "partial", extensions: ["scala"] },
4970
- { name: "shell", tier: "unmodelled", extensions: ["sh", "bash", "zsh"] },
5065
+ { name: "shell", tier: "unmodelled", extensions: ["sh", "bash", "zsh", "bat", "cmd"] },
4971
5066
  { name: "R", tier: "unmodelled", extensions: ["r"] },
4972
5067
  { name: "Lua", tier: "unmodelled", extensions: ["lua"] },
4973
5068
  { name: "Julia", tier: "unmodelled", extensions: ["jl"] },
@@ -4981,20 +5076,73 @@ var SOURCE_LANGUAGES = [
4981
5076
  { name: "Nim", tier: "unmodelled", extensions: ["nim"] },
4982
5077
  { name: "Zig", tier: "unmodelled", extensions: ["zig"] },
4983
5078
  { name: "F#", tier: "unmodelled", extensions: ["fs", "fsx"] },
4984
- { name: "Groovy", tier: "unmodelled", extensions: ["groovy", "gvy"] }
5079
+ { name: "Groovy", tier: "unmodelled", extensions: ["groovy", "gvy"] },
5080
+ { name: "Fortran", tier: "unmodelled", extensions: ["f", "for", "f77", "f90", "f95", "f03"] },
5081
+ { name: "CUDA/HIP", tier: "unmodelled", extensions: ["cu", "cuh", "hip"] },
5082
+ {
5083
+ name: "Web templates/styles",
5084
+ tier: "unmodelled",
5085
+ extensions: [
5086
+ "html",
5087
+ "htm",
5088
+ "css",
5089
+ "scss",
5090
+ "sass",
5091
+ "less",
5092
+ "hbs",
5093
+ "handlebars",
5094
+ "variables",
5095
+ "overrides",
5096
+ "vue",
5097
+ "svelte",
5098
+ "astro",
5099
+ "erb",
5100
+ "haml",
5101
+ "slim",
5102
+ "erubis",
5103
+ "hamlit"
5104
+ ]
5105
+ }
5106
+ ];
5107
+ var SOURCE_LANGUAGES_V10 = [
5108
+ ...SOURCE_LANGUAGES,
5109
+ { name: "COBOL", tier: "unmodelled", extensions: ["cob", "cbl", "cpy"] },
5110
+ { name: "MATLAB/Objective-C", tier: "unmodelled", extensions: ["m"] }
4985
5111
  ];
4986
5112
  var SOURCE_EXTENSION_PATTERN = new RegExp(
4987
5113
  `\\.(${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`,
4988
5114
  "i"
4989
5115
  );
4990
- var BUNDLED_BINARY_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib)$/i;
5116
+ var SOURCE_EXTENSION_PATTERN_V10 = new RegExp(
5117
+ `\\.(${SOURCE_LANGUAGES_V10.flatMap((language) => language.extensions).join("|")})$`,
5118
+ "i"
5119
+ );
5120
+ function isRecognizedSourcePath(path, rubricVersion = WITAN_RUBRIC_VERSION) {
5121
+ return (rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? SOURCE_EXTENSION_PATTERN_V10 : SOURCE_EXTENSION_PATTERN).test(path);
5122
+ }
5123
+ var BUNDLED_BINARY_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps)$/i;
4991
5124
  var DOCS_OR_MEDIA_EXTENSION_PATTERN = /\.(md|mdx|rst|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
4992
- var KNOWN_NON_SOURCE_EXTENSION_PATTERN = /\.(json|jsonc|json5|ya?ml|toml|txt|lock|xml|csv|tsv|ini|cfg|conf|properties|env|sum|mod|graphql|gql|proto|editorconfig|gitignore|gitattributes|npmrc|nvmrc|dockerignore|log|txt\.license)$/i;
5125
+ var ARTIFACT_DOMINANCE_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
5126
+ var DOC_DISTRIBUTION_COMPANION_PATTERN = /(^|\/)(LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CODE_OF_CONDUCT)(\.[A-Za-z0-9]+)?$/i;
5127
+ var GENERATED_OR_VENDOR_PATH_PATTERN = /(^|\/)(vendor|vendors|dist|build|generated|gen|coverage|node_modules|site-packages|\.venv|venv|\.next|__pycache__|\.terraform|public\/assets|static\/assets)(\/|$)|(^|\/)[^/]+\.min\.(?:js|css)$/i;
5128
+ var INDEPENDENT_CATALOG_PATH_PATTERN = /(^|\/)(solutions?|exercises?|challenges?|algorithms?)(\/|$)/i;
5129
+ var KNOWN_NON_SOURCE_EXTENSION_PATTERN = /\.(json|jsonc|json5|ya?ml|toml|txt|lock|xml|csv|tsv|ini|cfg|conf|cnf|properties|env|sum|mod|graphql|gql|proto|sarif|editorconfig|gitignore|gitattributes|npmrc|nvmrc|dockerignore|prettierignore|prettierrc|log|stderr|snap|grit|po|mo|dbf|shp|shx|egg|pem|key|csr|ai|txt\.license|pf|in|case|common|fortls)$/i;
5130
+ var V13_NON_SOURCE_EXTENSION_PATTERN = /\.(?:\d+|class|py[co]|gz|bz2|xz|7z|rar|mat|npy|npz|h5|hdf5|onnx|pt|pth|ckpt|safetensors|crt|cer|der|p12|pfx|diff|patch|plist|pbxproj|xcworkspacedata|xcbuild|xcplayground|xcscheme|xcsettings|xcconfig|xcprivacy|xcstrings|csproj|fsproj|vbproj|sln|slnf|props|targets|projitems|shproj|pubxml|resw|resources|storyboard|xib|entitlements|modulemap|podspec|nuspec|appxmanifest|webmanifest|manifest|map|fls|aux|bbl|bib|ocm|tdb|db|sqlite|sqlcipher\d*|bin|dat|pdb|prj|resolved|sim|input|list|fig|drawio|puml|sketch|icns|flac|wav|mp3|mp4|mov|avi|mkv|nv21|xls[xb]?|mex[a-z0-9_]*|gitconfig|profile|yapf|config|desktop|typed|example|bak|_|test(?:_[a-z0-9]+)*_location_override|xamltest|tld(?:_[a-z0-9]+)?|xsd|cmake|gradle|mk|am|pro|upri|dockerfile|dockergen|makefile|alpine|android|debian|ubuntu\d+|cuda\d+_ubuntu\d+|manylinux[a-z0-9_]*)$/i;
4993
5131
  var INGEST_POINTER = "To assess a closed/bundled tool, ingest its scanner output via --ingest <sarif|scorecard>.";
4994
- var SOURCE_DOMINANCE_RATIO_THRESHOLD = 0.2;
4995
- function classifyRepoArchetype(repoFiles) {
5132
+ var SOURCE_DOMINANCE_RATIO_THRESHOLD_V8 = 0.2;
5133
+ var SOURCE_DOMINANCE_RATIO_THRESHOLD_V9 = 0.8;
5134
+ var SOURCE_DOMINANCE_RATIO_THRESHOLD_V10 = 0.5;
5135
+ var SOURCE_DOMINANCE_RATIO_THRESHOLD_V11 = 0.2;
5136
+ var GENERATED_SOURCE_MINIMUM = 8;
5137
+ var GENERATED_SOURCE_RATIO_THRESHOLD = 0.8;
5138
+ var GENERATED_AUTHORED_MAXIMUM = 3;
5139
+ var CATALOG_SOURCE_MINIMUM = 12;
5140
+ var CATALOG_SOURCE_RATIO_THRESHOLD = 0.9;
5141
+ var CATALOG_OUTSIDE_MAXIMUM = 2;
5142
+ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION) {
4996
5143
  const totalFileCount = repoFiles.length;
4997
- const sourceFileCount = repoFiles.filter((file) => SOURCE_EXTENSION_PATTERN.test(file)).length;
5144
+ const sourceFiles = repoFiles.filter((path) => isRecognizedSourcePath(path, rubricVersion));
5145
+ const sourceFileCount = sourceFiles.length;
4998
5146
  if (totalFileCount === 0) {
4999
5147
  return {
5000
5148
  archetype: "empty",
@@ -5003,7 +5151,55 @@ function classifyRepoArchetype(repoFiles) {
5003
5151
  insufficientSourceReason: "Repository has no tracked files \u2014 there is nothing to certify."
5004
5152
  };
5005
5153
  }
5006
- const unrecognisedSourceFiles = collectUnrecognisedSourceFiles(repoFiles);
5154
+ const generatedSourceFiles = sourceFiles.filter(
5155
+ (file) => GENERATED_OR_VENDOR_PATH_PATTERN.test(file)
5156
+ );
5157
+ if (sourceFileCount >= GENERATED_SOURCE_MINIMUM && generatedSourceFiles.length / sourceFileCount >= GENERATED_SOURCE_RATIO_THRESHOLD && sourceFileCount - generatedSourceFiles.length <= GENERATED_AUTHORED_MAXIMUM) {
5158
+ return {
5159
+ archetype: "generated_only",
5160
+ sourceFileCount,
5161
+ totalFileCount,
5162
+ insufficientSourceReason: `${generatedSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under generated, vendor, build, distribution, coverage, or bundled-asset paths; only ${sourceFileCount - generatedSourceFiles.length} authored source-shaped file(s) remain. Cejel abstains because generated/vendor output is not a reviewable implementation surface. ${INGEST_POINTER}`
5163
+ };
5164
+ }
5165
+ const catalogSourceFiles = sourceFiles.filter(
5166
+ (file) => INDEPENDENT_CATALOG_PATH_PATTERN.test(file)
5167
+ );
5168
+ if (sourceFileCount >= CATALOG_SOURCE_MINIMUM && catalogSourceFiles.length / sourceFileCount >= CATALOG_SOURCE_RATIO_THRESHOLD && sourceFileCount - catalogSourceFiles.length <= CATALOG_OUTSIDE_MAXIMUM) {
5169
+ return {
5170
+ archetype: "non_cohesive_source",
5171
+ sourceFileCount,
5172
+ totalFileCount,
5173
+ insufficientSourceReason: `${catalogSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under independent solution, exercise, challenge, or algorithm catalog paths; only ${sourceFileCount - catalogSourceFiles.length} source-shaped file(s) remain outside that catalog. Cejel abstains because the repository does not expose one cohesive product implementation to score. ${INGEST_POINTER}`
5174
+ };
5175
+ }
5176
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) {
5177
+ const authoredSourceFiles = sourceFiles.filter(isAuthoredProductionPath);
5178
+ const documentationFiles = repoFiles.filter(
5179
+ (file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file)
5180
+ );
5181
+ if (sourceFileCount > 0 && authoredSourceFiles.length === 0 && documentationFiles.length > 0) {
5182
+ return {
5183
+ archetype: "docs_only",
5184
+ sourceFileCount,
5185
+ totalFileCount,
5186
+ insufficientSourceReason: `${sourceFileCount} source-shaped file(s) are tests/fixtures around a documentation tree; no authored product source remains. Cejel abstains because documentation tests do not support a product-source headline score. ${INGEST_POINTER}`
5187
+ };
5188
+ }
5189
+ const artifactFiles = repoFiles.filter(
5190
+ (file) => ARTIFACT_DOMINANCE_EXTENSION_PATTERN.test(file)
5191
+ );
5192
+ const materialFiles = authoredSourceFiles.length + artifactFiles.length;
5193
+ if (artifactFiles.length >= 10 && (authoredSourceFiles.length <= 10 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && materialFiles > 0 && authoredSourceFiles.length / materialFiles <= 0.1) {
5194
+ return {
5195
+ archetype: "binary_only",
5196
+ sourceFileCount,
5197
+ totalFileCount,
5198
+ insufficientSourceReason: `${authoredSourceFiles.length} authored source-shaped file(s) accompany ${artifactFiles.length} packaged, font, image, or media artifact(s); the source is sparse tooling rather than a representative product implementation. Cejel abstains because incidental tooling cannot support a product-source headline score. ${INGEST_POINTER}`
5199
+ };
5200
+ }
5201
+ }
5202
+ const unrecognisedSourceFiles = collectUnrecognisedSourceFiles(repoFiles, rubricVersion);
5007
5203
  if (sourceFileCount === 0) {
5008
5204
  const binaryFiles = repoFiles.filter((file) => BUNDLED_BINARY_EXTENSION_PATTERN.test(file));
5009
5205
  if (binaryFiles.length > 0) {
@@ -5016,7 +5212,10 @@ function classifyRepoArchetype(repoFiles) {
5016
5212
  };
5017
5213
  }
5018
5214
  const docsFiles = repoFiles.filter((file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file));
5019
- if (docsFiles.length > 0 && docsFiles.length === totalFileCount) {
5215
+ const docsDistributionFiles = repoFiles.filter(
5216
+ (file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file) || DOC_DISTRIBUTION_COMPANION_PATTERN.test(file)
5217
+ );
5218
+ if (docsFiles.length > 0 && (docsDistributionFiles.length === totalFileCount || (rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && unrecognisedSourceFiles.length === 0)) {
5020
5219
  return {
5021
5220
  archetype: "docs_only",
5022
5221
  sourceFileCount,
@@ -5025,7 +5224,12 @@ function classifyRepoArchetype(repoFiles) {
5025
5224
  };
5026
5225
  }
5027
5226
  if (unrecognisedSourceFiles.length > 0) {
5028
- return unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles);
5227
+ return unrecognisedEcosystemResult(
5228
+ sourceFileCount,
5229
+ totalFileCount,
5230
+ unrecognisedSourceFiles,
5231
+ sourceDominanceThreshold(rubricVersion)
5232
+ );
5029
5233
  }
5030
5234
  return { archetype: "source", sourceFileCount, totalFileCount };
5031
5235
  }
@@ -5037,25 +5241,255 @@ function classifyRepoArchetype(repoFiles) {
5037
5241
  };
5038
5242
  }
5039
5243
  const dominanceRatio = sourceFileCount / (sourceFileCount + unrecognisedSourceFiles.length);
5040
- if (dominanceRatio >= SOURCE_DOMINANCE_RATIO_THRESHOLD) {
5244
+ const dominanceThreshold = sourceDominanceThreshold(rubricVersion);
5245
+ if (dominanceRatio >= dominanceThreshold) {
5041
5246
  return {
5042
5247
  archetype: isLikelyMonorepo(repoFiles) ? "monorepo" : "source",
5043
5248
  sourceFileCount,
5044
5249
  totalFileCount
5045
5250
  };
5046
5251
  }
5047
- return unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles);
5252
+ return unrecognisedEcosystemResult(
5253
+ sourceFileCount,
5254
+ totalFileCount,
5255
+ unrecognisedSourceFiles,
5256
+ dominanceThreshold
5257
+ );
5048
5258
  }
5049
- function collectUnrecognisedSourceFiles(repoFiles) {
5050
- return repoFiles.filter((file) => {
5051
- const base = basename(file);
5052
- if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
5053
- if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
5054
- if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
5055
- if (BUNDLED_BINARY_EXTENSION_PATTERN.test(base)) return false;
5056
- if (SOURCE_EXTENSION_PATTERN.test(base)) return false;
5057
- return true;
5058
- });
5259
+ var READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13 = 0.8;
5260
+ var READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13 = 32;
5261
+ var READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13 = 256 * 1024;
5262
+ var SEMANTIC_AUXILIARY_SOURCE_MINIMUM_V14 = 20;
5263
+ var SEMANTIC_AUXILIARY_SOURCE_RATIO_V14 = 0.8;
5264
+ var SEMANTIC_METADATA_PREFIX_BYTES_V14 = 16 * 1024;
5265
+ var SEMANTIC_AUXILIARY_PATH_PATTERN_V14 = /(^|\/)(?:locales?|i18n|l10n|translations?|styles?|themes?)(\/|$)|\.(?:css|scss|sass|less|styl)$/i;
5266
+ var HTML_SOURCE_EXTENSION_PATTERN = /\.(?:html?|xhtml)$/i;
5267
+ var HTML_META_REFRESH_PATTERN = /<meta\b(?=[^>]*\bhttp-equiv\s*=\s*["']?\s*refresh\b)(?=[^>]*\bcontent\s*=\s*["'][^"']*\burl\s*=)[^>]*>/i;
5268
+ var RUBY_PACKAGE_RECIPE_CLASS_PATTERN = /\bclass\s+[A-Za-z0-9_:]+\s*<\s*(?:Formula|Cask)\b/;
5269
+ var RUBY_PACKAGE_RECIPE_URL_PATTERN = /^\s*url\s+["'][^"']+["']/m;
5270
+ var RUBY_PACKAGE_RECIPE_PATH_PATTERN = /(^|\/)(?:Formula|Casks?)(\/|$)/;
5271
+ function selectProportionalRepresentativeSourceFiles(files, rankKey, limit) {
5272
+ const eligibleFiles = [...new Set(files)];
5273
+ const slotCount = Math.min(Math.max(Math.floor(limit), 0), eligibleFiles.length);
5274
+ if (slotCount === 0) return [];
5275
+ const grouped = /* @__PURE__ */ new Map();
5276
+ for (const file of eligibleFiles) {
5277
+ const extension = extname(file).toLowerCase() || "(none)";
5278
+ const bucket = grouped.get(extension) ?? [];
5279
+ bucket.push(file);
5280
+ grouped.set(extension, bucket);
5281
+ }
5282
+ const groups = [...grouped.entries()].map(([extension, groupFiles]) => {
5283
+ const quota = groupFiles.length / eligibleFiles.length * slotCount;
5284
+ return {
5285
+ extension,
5286
+ files: [...groupFiles].sort(
5287
+ (left, right) => representativeSourceRank(rankKey, left).localeCompare(
5288
+ representativeSourceRank(rankKey, right)
5289
+ )
5290
+ ),
5291
+ allocation: Math.floor(quota),
5292
+ remainder: quota - Math.floor(quota)
5293
+ };
5294
+ }).sort(
5295
+ (left, right) => right.files.length - left.files.length || left.extension.localeCompare(right.extension)
5296
+ );
5297
+ let remaining = slotCount - groups.reduce((sum, group) => sum + group.allocation, 0);
5298
+ const remainderOrder = [...groups].sort(
5299
+ (left, right) => right.remainder - left.remainder || right.files.length - left.files.length || left.extension.localeCompare(right.extension)
5300
+ );
5301
+ for (const group of remainderOrder) {
5302
+ if (remaining === 0) break;
5303
+ if (group.allocation >= group.files.length) continue;
5304
+ group.allocation += 1;
5305
+ remaining -= 1;
5306
+ }
5307
+ const selected = [];
5308
+ for (let offset = 0; selected.length < slotCount; offset += 1) {
5309
+ let added = false;
5310
+ for (const group of groups) {
5311
+ if (offset >= group.allocation) continue;
5312
+ const file = group.files[offset];
5313
+ if (!file) continue;
5314
+ selected.push(file);
5315
+ added = true;
5316
+ }
5317
+ if (!added) break;
5318
+ }
5319
+ return selected;
5320
+ }
5321
+ function isRepresentativeSourceText(contents) {
5322
+ if (contents.trim().length === 0) return false;
5323
+ if (contents.includes("\0") || contents.includes("\uFFFD")) return false;
5324
+ let disallowedControls = 0;
5325
+ for (const character of contents) {
5326
+ const code = character.charCodeAt(0);
5327
+ if (code < 32 && character !== "\n" && character !== "\r" && character !== " ") {
5328
+ disallowedControls += 1;
5329
+ }
5330
+ }
5331
+ return disallowedControls / contents.length <= 0.01;
5332
+ }
5333
+ function representativeSourceRank(rankKey, file) {
5334
+ return createHash2("sha256").update(`${rankKey}:${file}`).digest("hex");
5335
+ }
5336
+ function applyReadableSourceRepresentationGate(repoPath, repoFiles, classification, headSha, rubricVersion) {
5337
+ if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
5338
+ return classification;
5339
+ }
5340
+ const authoredSourceFiles = repoFiles.filter(
5341
+ (path) => isAuthoredProductionPath(path) && (isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path))
5342
+ );
5343
+ if (authoredSourceFiles.length === 0) return classification;
5344
+ const sample = selectProportionalRepresentativeSourceFiles(
5345
+ authoredSourceFiles,
5346
+ `witan-v13-readable-source:${headSha ?? "unversioned"}`,
5347
+ READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13
5348
+ );
5349
+ const sampledByExtension = /* @__PURE__ */ new Map();
5350
+ for (const file of sample) {
5351
+ const extension = extname(file).toLowerCase() || "(none)";
5352
+ const counts = sampledByExtension.get(extension) ?? { sampled: 0, readable: 0 };
5353
+ counts.sampled += 1;
5354
+ const contents = readRepresentativeSourceText(repoPath, file);
5355
+ if (contents !== null && isRepresentativeSourceText(contents)) {
5356
+ counts.readable += 1;
5357
+ }
5358
+ sampledByExtension.set(extension, counts);
5359
+ }
5360
+ const sourceCountByExtension = /* @__PURE__ */ new Map();
5361
+ for (const file of authoredSourceFiles) {
5362
+ const extension = extname(file).toLowerCase() || "(none)";
5363
+ sourceCountByExtension.set(extension, (sourceCountByExtension.get(extension) ?? 0) + 1);
5364
+ }
5365
+ const readableSourceFileCount = [...sourceCountByExtension.entries()].reduce(
5366
+ (total, [extension, sourceCount]) => {
5367
+ const sampled = sampledByExtension.get(extension);
5368
+ if (!sampled || sampled.sampled === 0) return total;
5369
+ return total + sourceCount * (sampled.readable / sampled.sampled);
5370
+ },
5371
+ 0
5372
+ );
5373
+ const readableRatio = readableSourceFileCount / authoredSourceFiles.length;
5374
+ if (readableRatio >= READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13) return classification;
5375
+ const readableCountLabel = Number.isInteger(readableSourceFileCount) ? String(readableSourceFileCount) : readableSourceFileCount.toFixed(2);
5376
+ const readablePct = (readableRatio * 100).toFixed(1);
5377
+ const thresholdPct = (READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13 * 100).toFixed(0);
5378
+ return {
5379
+ archetype: "non_cohesive_source",
5380
+ sourceFileCount: classification.sourceFileCount,
5381
+ totalFileCount: classification.totalFileCount,
5382
+ insufficientSourceReason: `Readable source representation covers ${readableCountLabel} of ${authoredSourceFiles.length} authored source-shaped file(s) (${readablePct}%); this is below the ${thresholdPct}% representation threshold. Cejel abstains because unreadable, compressed, binary-shaped, or opaque source families cannot support a representative headline score. ${INGEST_POINTER}`
5383
+ };
5384
+ }
5385
+ function applySemanticSourceRepresentationGate(repoPath, repoFiles, classification, rubricVersion) {
5386
+ if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
5387
+ return classification;
5388
+ }
5389
+ const authoredSourceFiles = repoFiles.filter(
5390
+ (path) => isAuthoredProductionPath(path) && (isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path))
5391
+ );
5392
+ if (authoredSourceFiles.length === 0) return classification;
5393
+ const auxiliaryFiles = authoredSourceFiles.filter(
5394
+ (path) => SEMANTIC_AUXILIARY_PATH_PATTERN_V14.test(path)
5395
+ );
5396
+ const packageMetadataFiles = authoredSourceFiles.filter(
5397
+ (path) => isRubyPackageRecipeMetadataSource(repoPath, path)
5398
+ );
5399
+ const redirectMetadataFiles = authoredSourceFiles.filter(
5400
+ (path) => isHtmlRedirectMetadataSource(repoPath, path)
5401
+ );
5402
+ const auxiliarySet = /* @__PURE__ */ new Set([
5403
+ ...auxiliaryFiles,
5404
+ ...packageMetadataFiles,
5405
+ ...redirectMetadataFiles
5406
+ ]);
5407
+ const representativeImplementationCount = authoredSourceFiles.filter(
5408
+ (path) => !auxiliarySet.has(path)
5409
+ ).length;
5410
+ if (representativeImplementationCount === 0) {
5411
+ const detail = redirectMetadataFiles.length > 0 ? `${redirectMetadataFiles.length} redirect metadata file(s)` : packageMetadataFiles.length > 0 ? `${packageMetadataFiles.length} package metadata file(s)` : `${auxiliaryFiles.length} localization or style metadata file(s)`;
5412
+ return {
5413
+ archetype: "non_cohesive_source",
5414
+ sourceFileCount: classification.sourceFileCount,
5415
+ totalFileCount: classification.totalFileCount,
5416
+ insufficientSourceReason: `${detail} provide no representative product implementation. Cejel abstains because auxiliary metadata or payload files cannot support a product-source headline score. ${INGEST_POINTER}`
5417
+ };
5418
+ }
5419
+ const auxiliaryCount = auxiliarySet.size;
5420
+ const auxiliaryRatio = auxiliaryCount / authoredSourceFiles.length;
5421
+ if (authoredSourceFiles.length >= SEMANTIC_AUXILIARY_SOURCE_MINIMUM_V14 && auxiliaryRatio >= SEMANTIC_AUXILIARY_SOURCE_RATIO_V14) {
5422
+ return {
5423
+ archetype: "non_cohesive_source",
5424
+ sourceFileCount: classification.sourceFileCount,
5425
+ totalFileCount: classification.totalFileCount,
5426
+ insufficientSourceReason: `${auxiliaryCount} of ${authoredSourceFiles.length} authored source-shaped file(s) are localization, style, package, or redirect auxiliary material. Cejel abstains because an auxiliary-dominated tree does not expose representative product implementation for a headline score. ${INGEST_POINTER}`
5427
+ };
5428
+ }
5429
+ return classification;
5430
+ }
5431
+ function isHtmlRedirectMetadataSource(repoPath, file) {
5432
+ if (!HTML_SOURCE_EXTENSION_PATTERN.test(file)) return false;
5433
+ const contents = readSemanticMetadataPrefix(repoPath, file);
5434
+ if (contents === null) return false;
5435
+ const operativeContents = contents.replace(/<!--[\s\S]*?-->/g, "");
5436
+ if (!HTML_META_REFRESH_PATTERN.test(operativeContents)) return false;
5437
+ return !/<(?:script|form|main|article|section|canvas|template)\b/i.test(operativeContents);
5438
+ }
5439
+ function isRubyPackageRecipeMetadataSource(repoPath, file) {
5440
+ if (!/\.rb$/i.test(file) || !RUBY_PACKAGE_RECIPE_PATH_PATTERN.test(file)) return false;
5441
+ const contents = readSemanticMetadataPrefix(repoPath, file);
5442
+ const operativeContents = contents?.replace(/^\s*#.*$/gm, "");
5443
+ return operativeContents !== void 0 && RUBY_PACKAGE_RECIPE_CLASS_PATTERN.test(operativeContents) && RUBY_PACKAGE_RECIPE_URL_PATTERN.test(operativeContents);
5444
+ }
5445
+ function readSemanticMetadataPrefix(repoPath, file) {
5446
+ const absolutePath = join(repoPath, file);
5447
+ if (!isRegularFile(absolutePath)) return null;
5448
+ let descriptor = null;
5449
+ try {
5450
+ descriptor = openSync(absolutePath, "r");
5451
+ const buffer = Buffer.allocUnsafe(SEMANTIC_METADATA_PREFIX_BYTES_V14);
5452
+ const bytesRead = readSync(descriptor, buffer, 0, SEMANTIC_METADATA_PREFIX_BYTES_V14, 0);
5453
+ return buffer.subarray(0, bytesRead).toString("utf8");
5454
+ } catch {
5455
+ return null;
5456
+ } finally {
5457
+ if (descriptor !== null) closeSync(descriptor);
5458
+ }
5459
+ }
5460
+ function readRepresentativeSourceText(repoPath, file) {
5461
+ const absolutePath = join(repoPath, file);
5462
+ try {
5463
+ if (!isRegularFile(absolutePath)) return null;
5464
+ const size = statSync(absolutePath).size;
5465
+ if (size === 0 || size > READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13) return null;
5466
+ return readFileSync(absolutePath, "utf8");
5467
+ } catch {
5468
+ return null;
5469
+ }
5470
+ }
5471
+ function sourceDominanceThreshold(rubricVersion) {
5472
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) {
5473
+ return SOURCE_DOMINANCE_RATIO_THRESHOLD_V11;
5474
+ }
5475
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V10) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V10;
5476
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V92) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V9;
5477
+ return SOURCE_DOMINANCE_RATIO_THRESHOLD_V8;
5478
+ }
5479
+ function collectUnrecognisedSourceFiles(repoFiles, rubricVersion) {
5480
+ return repoFiles.filter((path) => isUnrecognisedSourcePath(path, rubricVersion));
5481
+ }
5482
+ function isUnrecognisedSourcePath(file, rubricVersion = WITAN_RUBRIC_VERSION) {
5483
+ const base = basename(file);
5484
+ if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
5485
+ if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
5486
+ if ((rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && V13_NON_SOURCE_EXTENSION_PATTERN.test(base)) {
5487
+ return false;
5488
+ }
5489
+ if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
5490
+ if (BUNDLED_BINARY_EXTENSION_PATTERN.test(base)) return false;
5491
+ if (isRecognizedSourcePath(base, rubricVersion)) return false;
5492
+ return true;
5059
5493
  }
5060
5494
  function extensionsByFileCountDescending(files) {
5061
5495
  const counts = /* @__PURE__ */ new Map();
@@ -5068,11 +5502,11 @@ function extensionsByFileCountDescending(files) {
5068
5502
  }
5069
5503
  return [...counts.entries()].sort(([extA, countA], [extB, countB]) => countB - countA || extA.localeCompare(extB)).map(([ext]) => ext);
5070
5504
  }
5071
- function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles) {
5505
+ function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles, dominanceThreshold = SOURCE_DOMINANCE_RATIO_THRESHOLD_V9) {
5072
5506
  const sample = extensionsByFileCountDescending(unrecognisedSourceFiles).slice(0, 6).join(", ");
5073
5507
  const candidateCount = sourceFileCount + unrecognisedSourceFiles.length;
5074
5508
  const dominancePct = candidateCount > 0 ? (sourceFileCount / candidateCount * 100).toFixed(1) : "0.0";
5075
- const thresholdPct = (SOURCE_DOMINANCE_RATIO_THRESHOLD * 100).toFixed(0);
5509
+ const thresholdPct = (dominanceThreshold * 100).toFixed(0);
5076
5510
  const measuredClause = sourceFileCount > 0 ? `${sourceFileCount} of ${candidateCount} source-shaped file(s) (${dominancePct}%) are in a language Cejel reads \u2014 below the ${thresholdPct}% dominance threshold a score would need to be meaningful (${totalFileCount} tracked files in total; manifests, lockfiles, docs, media and bundled binaries are excluded from both sides of the ratio)` : `0 of ${totalFileCount} tracked file(s) matched a recognised source extension`;
5077
5511
  return {
5078
5512
  archetype: "unrecognised_ecosystem",
@@ -5108,14 +5542,34 @@ function buildNotApplicableSignal(criterionId, reason) {
5108
5542
  notApplicable: true
5109
5543
  };
5110
5544
  }
5111
- function collectRepoSignals(repoPath, generatedAt, repoFiles) {
5545
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
5112
5546
  const signals = [];
5113
5547
  const mono = resolveMonorepoContext(repoPath);
5114
- const a1Signal = collectA1TestIntegrityEvidence(repoPath, repoFiles);
5115
- const a2Signal = collectA2IsolationEvidence(repoPath, repoFiles);
5116
- const a3Signal = collectA3ProdReadinessEvidence(repoPath, repoFiles);
5117
- const a4Signal = collectA4DependencyEvidence(repoPath, repoFiles, mono);
5118
- const a5Signal = collectA5ClaimRealityEvidence(repoPath, repoFiles);
5548
+ const a1Signal = collectA1TestIntegrityEvidence(
5549
+ repoPath,
5550
+ repoFiles,
5551
+ useV27Detectors,
5552
+ useV33Detectors,
5553
+ useV36Detectors
5554
+ );
5555
+ const a2Signal = collectA2IsolationEvidence(
5556
+ repoPath,
5557
+ repoFiles,
5558
+ useV27Detectors,
5559
+ useV33Detectors,
5560
+ useV36Detectors,
5561
+ useV39Detectors,
5562
+ useV47Detectors
5563
+ );
5564
+ const a3Signal = collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors);
5565
+ const a4Signal = collectA4DependencyEvidence(
5566
+ repoPath,
5567
+ repoFiles,
5568
+ mono,
5569
+ useV27Detectors,
5570
+ useV47Detectors
5571
+ );
5572
+ const a5Signal = collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors);
5119
5573
  const b1Signal = buildNotApplicableSignal(
5120
5574
  "B1",
5121
5575
  "Repository scans do not evaluate the dispatch-trace process dimension."
@@ -5127,7 +5581,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles) {
5127
5581
  "B5",
5128
5582
  "Repository scans do not evaluate the learning-trace process dimension."
5129
5583
  );
5130
- const b6Signal = collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles);
5584
+ const b6Signal = collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors);
5131
5585
  for (const signal of [
5132
5586
  a1Signal,
5133
5587
  a2Signal,
@@ -5145,22 +5599,27 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles) {
5145
5599
  }
5146
5600
  return signals;
5147
5601
  }
5148
- function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
5602
+ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors) {
5149
5603
  const evidence = [];
5150
5604
  const findings = [];
5151
- const contentCppTests = collectContentBasedCppTestFiles(repoPath, repoFiles);
5152
- const allTestFiles = [
5153
- ...repoFiles.filter(isTestFile),
5154
- ...contentCppTests.filter((file) => !isTestFile(file))
5155
- ];
5605
+ const allTestFiles = findConcreteTestFiles(repoPath, repoFiles);
5156
5606
  const testFiles = allTestFiles.slice(0, 8);
5157
5607
  const sourceFiles = repoFiles.filter(isSourceFile);
5158
- const runnerFiles = repoFiles.filter(isTestRunnerConfig).slice(0, 6);
5159
- const coverageFiles = repoFiles.filter(isCoverageConfig).slice(0, 4);
5608
+ const runnerFiles = repoFiles.filter(
5609
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isTestRunnerConfig(repoPath, file)
5610
+ ).slice(0, 6);
5611
+ const configuredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
5612
+ const allCoverageFiles = findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors);
5613
+ const coverageFiles = allCoverageFiles.slice(0, 4);
5160
5614
  const packageJson = findRootPackageJson(repoFiles);
5161
5615
  const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
5616
+ const packageJsonFiles = useV27Detectors ? repoFiles.filter(
5617
+ (file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
5618
+ ) : packageJson ? [packageJson] : [];
5162
5619
  const coveragePercent = readCoveragePercent(repoPath, coverageFiles);
5163
- const ciWorkflows = repoFiles.filter(isCiWorkflow);
5620
+ const ciWorkflows = repoFiles.filter(
5621
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
5622
+ );
5164
5623
  const ciTestWorkflow = ciWorkflows.find(
5165
5624
  (file) => fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN)
5166
5625
  );
@@ -5179,7 +5638,9 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
5179
5638
  const usesLeanBuiltInTestRunner = packageJson != null && [...packageScripts.values()].some(
5180
5639
  (script) => LEAN_TEST_RUNNER_SCRIPT_PATTERN.test(script)
5181
5640
  ) || testFiles.some((file) => fileContains(repoPath, file, LEAN_TEST_RUNNER_IMPORT_PATTERN));
5182
- const hasHeavyTestDependency = packageJson != null && packageJsonHasHeavyTestDependency(repoPath, packageJson);
5641
+ const hasHeavyTestDependency = packageJsonFiles.some(
5642
+ (manifest) => packageJsonHasHeavyTestDependency(repoPath, manifest)
5643
+ );
5183
5644
  const isLeanTestToolchain = usesLeanBuiltInTestRunner && !hasHeavyTestDependency;
5184
5645
  for (const file of testFiles) {
5185
5646
  evidence.push(evidenceForRelative(repoPath, file, "test_run", "Detected test file"));
@@ -5199,7 +5660,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
5199
5660
  );
5200
5661
  }
5201
5662
  const scheduledHealthWorkflow = ciWorkflows.find(
5202
- (file) => fileContains(repoPath, file, SCHEDULE_TRIGGER_PATTERN) && fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN)
5663
+ (file) => fileContains(repoPath, file, SCHEDULE_TRIGGER_PATTERN) && fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN) && (!useV36Detectors || SCHEDULED_HEALTH_PATH_PATTERN.test(file) || fileContains(repoPath, file, SCHEDULED_HEALTH_INTENT_PATTERN))
5203
5664
  );
5204
5665
  if (scheduledHealthWorkflow) {
5205
5666
  const isPublished = fileContains(
@@ -5232,7 +5693,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
5232
5693
  }
5233
5694
  if (evidence.length === 0 && findings.length === 0) return null;
5234
5695
  if (testFiles.length === 0) {
5235
- const fallback = evidence[0] ?? findings[0]?.evidence;
5696
+ const fallback = configuredRunnerFiles[0] ? evidenceForRelative(
5697
+ repoPath,
5698
+ configuredRunnerFiles[0],
5699
+ "test_run",
5700
+ "Configured test runner"
5701
+ ) : useV33Detectors ? void 0 : evidence[0] ?? findings[0]?.evidence;
5236
5702
  if (fallback) {
5237
5703
  findings.push({
5238
5704
  severity: "warning",
@@ -5240,7 +5706,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
5240
5706
  evidence: fallback
5241
5707
  });
5242
5708
  }
5243
- } else if (coverageFiles.length === 0) {
5709
+ } else if (coverageFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
5244
5710
  if (isLeanTestToolchain) {
5245
5711
  const leanEvidenceFile = packageJson ?? testFiles[0];
5246
5712
  if (leanEvidenceFile) {
@@ -5311,27 +5777,67 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
5311
5777
  notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
5312
5778
  };
5313
5779
  }
5314
- function collectA2IsolationEvidence(repoPath, repoFiles) {
5780
+ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
5315
5781
  const evidence = [];
5316
5782
  const findings = [];
5317
5783
  const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
5318
- const envExamples = repoFiles.filter((file) => isEnvTemplatePath(file));
5784
+ const envExamples = repoFiles.filter((file) => isEnvTemplatePath(file, useV47Detectors));
5319
5785
  const migrationFiles = repoFiles.filter(
5320
5786
  (file) => /(^|\/)(migrations?|drizzle|prisma)\//.test(file)
5321
5787
  );
5322
- const committedSecret = repoFiles.find(
5323
- (file) => !isIgnoredScanFile(file) && fileContainsCommittedSecret(repoPath, file)
5788
+ let committedSecret;
5789
+ for (const path of repoFiles) {
5790
+ const authoredProductionPath = useV47Detectors ? isV47AuthoredProductionPath(path) : useV39Detectors ? isV39AuthoredProductionPath(path) : isAuthoredProductionPath(path);
5791
+ if (isIgnoredScanFile(path, useV47Detectors) || useV33Detectors && !authoredProductionPath) {
5792
+ continue;
5793
+ }
5794
+ const match = findCommittedSecretInFile(
5795
+ repoPath,
5796
+ path,
5797
+ useV36Detectors,
5798
+ useV39Detectors,
5799
+ useV47Detectors
5800
+ );
5801
+ if (!match) continue;
5802
+ committedSecret = { path, match };
5803
+ break;
5804
+ }
5805
+ const currentSecretFingerprintsByPath = /* @__PURE__ */ new Map();
5806
+ if (useV27Detectors) {
5807
+ for (const path of repoFiles.filter((candidate) => isCredentialHistoryPath(candidate))) {
5808
+ currentSecretFingerprintsByPath.set(
5809
+ path,
5810
+ new Set(
5811
+ findSecretFingerprintsInFile(
5812
+ repoPath,
5813
+ path,
5814
+ useV36Detectors,
5815
+ useV39Detectors,
5816
+ useV47Detectors
5817
+ )
5818
+ )
5819
+ );
5820
+ }
5821
+ }
5822
+ const historySecretScan = committedSecret && !useV27Detectors ? null : collectHistorySecretEvidence(
5823
+ repoPath,
5824
+ useV27Detectors,
5825
+ currentSecretFingerprintsByPath,
5826
+ useV33Detectors,
5827
+ useV36Detectors,
5828
+ useV39Detectors,
5829
+ useV47Detectors
5324
5830
  );
5325
- const historySecretScan = committedSecret ? null : collectHistorySecretEvidence(repoPath);
5326
5831
  const hasConfirmedSecretFinding = committedSecret != null || historySecretScan?.evidence != null;
5327
5832
  const cryptoHygiene = collectCryptoHygieneEvidence(
5328
5833
  repoPath,
5329
- repoFiles.filter(isImplementationFile).slice(0, 60)
5834
+ repoFiles.filter(isImplementationFile).slice(0, 60),
5835
+ useV39Detectors
5330
5836
  );
5331
5837
  const implFiles = repoFiles.filter(isImplementationFile);
5332
5838
  const gitignoreHasEnvRule = gitignore != null && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m);
5333
5839
  const committedEnvFilePath = repoFiles.find(
5334
- (f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f)
5840
+ (f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f, useV47Detectors) && (!useV47Detectors || isV47AuthoredProductionPath(f)) && (!useV39Detectors || !hasTemplateOnlyEnvContent(repoPath, f))
5335
5841
  );
5336
5842
  const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath);
5337
5843
  const dbLayerImportFile = migrationFiles.length === 0 ? implFiles.slice(0, 30).find((f) => fileContains(repoPath, f, DB_CLIENT_PATTERN)) : void 0;
@@ -5366,20 +5872,17 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
5366
5872
  } else if (dbLayerImportFile != null) {
5367
5873
  evidence.push(evidenceForRelative(repoPath, dbLayerImportFile, "artifact", "DB client import"));
5368
5874
  }
5369
- const rlsMigration = migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
5370
- const tenantScopedFile = migrationFiles.find(
5371
- (file) => fileContains(repoPath, file, TENANT_SCOPE_PATTERN)
5372
- );
5373
- const rlsPolicyCount = countPatternMatches(
5875
+ const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
5876
+ const v47TenantStorageFiles = useV47Detectors ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
5877
+ const rlsMigration = useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
5878
+ const tenantScopePattern = useV39Detectors ? TENANT_SCOPE_PATTERN_V11 : useV36Detectors ? TENANT_SCOPE_PATTERN_V10 : TENANT_SCOPE_PATTERN;
5879
+ const tenantScopedFile = useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern));
5880
+ const rlsPolicyCount = useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
5374
5881
  repoPath,
5375
5882
  migrationFiles,
5376
5883
  /create policy|enable row level security|force row level security/gi
5377
5884
  );
5378
- const tenantScopedMigrationFileCount = countFilesContaining(
5379
- repoPath,
5380
- migrationFiles,
5381
- TENANT_SCOPE_PATTERN
5382
- );
5885
+ const tenantScopedMigrationFileCount = useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
5383
5886
  const gitignoreEnvFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
5384
5887
  const envHandlingDepth = (envExamples.length > 0 ? 1 : 0) + (gitignoreEnvFile ? 1 : 0) + (countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0 ? 1 : 0);
5385
5888
  const isMultiTenant = tenantScopedMigrationFileCount > 0;
@@ -5402,15 +5905,31 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
5402
5905
  );
5403
5906
  }
5404
5907
  if (committedSecret) {
5405
- const isTestPath = isTestOrFixturePath(committedSecret);
5908
+ const isTestPath = isTestOrFixturePath(committedSecret.path);
5909
+ const isDefaultAdmin = committedSecret.match.kind === "default_admin";
5406
5910
  findings.push({
5407
5911
  severity: isTestPath ? "info" : "critical",
5408
- summary: isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
5912
+ summary: isDefaultAdmin ? isTestPath ? `Default administrative credential in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production deployment default; verify.` : "An explicit default administrative credential appears committed in production configuration." : isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
5913
+ evidence: evidenceForRelativeAtLine(
5914
+ repoPath,
5915
+ committedSecret.path,
5916
+ "secret_scan",
5917
+ secretEvidenceLabel(
5918
+ isDefaultAdmin ? "Committed default administrative credential" : "Committed secret-shaped value",
5919
+ committedSecret.match
5920
+ ),
5921
+ committedSecret.match.line
5922
+ )
5923
+ });
5924
+ } else if (committedEnvFilePath) {
5925
+ findings.push({
5926
+ severity: useV33Detectors ? "info" : "warning",
5927
+ summary: "A non-template .env file is committed in the current repository tree; no secret-shaped value was detected.",
5409
5928
  evidence: evidenceForRelative(
5410
5929
  repoPath,
5411
- committedSecret,
5930
+ committedEnvFilePath,
5412
5931
  "secret_scan",
5413
- "Committed secret-shaped value"
5932
+ "Committed .env file (no confirmed secret value found)"
5414
5933
  )
5415
5934
  });
5416
5935
  }
@@ -5424,7 +5943,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
5424
5943
  });
5425
5944
  } else if (historySecretScan?.truncated) {
5426
5945
  findings.push({
5427
- severity: "warning",
5946
+ severity: useV33Detectors ? "info" : "warning",
5428
5947
  summary: "History secret scan reached the credential-path safety valve before scanning every matching blob.",
5429
5948
  evidence: {
5430
5949
  kind: "secret_scan",
@@ -5435,7 +5954,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
5435
5954
  });
5436
5955
  } else if (historySecretScan?.envPathEvidence) {
5437
5956
  findings.push({
5438
- severity: "warning",
5957
+ severity: useV33Detectors ? "info" : "warning",
5439
5958
  summary: "A non-template .env file was tracked in git history; no secret-shaped value was detected.",
5440
5959
  evidence: historySecretScan.envPathEvidence
5441
5960
  });
@@ -5493,7 +6012,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
5493
6012
  metric(
5494
6013
  "tenant_scope_ratio",
5495
6014
  "Tenant-scoped schema ratio",
5496
- countFilesContaining(repoPath, migrationFiles, TENANT_SCOPE_PATTERN),
6015
+ countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
5497
6016
  Math.max(migrationFiles.length, 1),
5498
6017
  0.25,
5499
6018
  "ratio",
@@ -5519,12 +6038,14 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
5519
6038
  notes: "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported."
5520
6039
  };
5521
6040
  }
5522
- function collectCryptoHygieneEvidence(repoPath, implFiles) {
6041
+ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = false) {
5523
6042
  const evidence = [];
5524
6043
  const findings = [];
5525
6044
  let hasSurface = false;
5526
6045
  let insecureFound = false;
5527
- const productionImplFiles = implFiles.filter(isProductionSourcePath);
6046
+ const productionImplFiles = implFiles.filter(
6047
+ (file) => useV39Detectors ? isV39AuthoredProductionPath(file) : isProductionSourcePath(file)
6048
+ );
5528
6049
  const timingSafeFile = productionImplFiles.find(
5529
6050
  (file) => fileContains(repoPath, file, TIMING_SAFE_COMPARE_PATTERN)
5530
6051
  );
@@ -5555,8 +6076,9 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
5555
6076
  }
5556
6077
  let insecureCompareFile = null;
5557
6078
  let insecureCompareLine = null;
5558
- for (const file of implFiles) {
5559
- const line = findInsecureSecretCompareLine(repoPath, file);
6079
+ const compareFiles = useV39Detectors ? productionImplFiles : implFiles;
6080
+ for (const file of compareFiles) {
6081
+ const line = findInsecureSecretCompareLine(repoPath, file, useV39Detectors);
5560
6082
  if (line != null) {
5561
6083
  insecureCompareFile = file;
5562
6084
  insecureCompareLine = line;
@@ -5564,6 +6086,12 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
5564
6086
  }
5565
6087
  }
5566
6088
  if (insecureCompareFile) {
6089
+ const insecureCompareSourceLine = lineAt(
6090
+ repoPath,
6091
+ insecureCompareFile,
6092
+ insecureCompareLine ?? 0
6093
+ );
6094
+ const comparesDefaultSentinel = useV39Detectors && insecureCompareSourceLine !== null && DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN.test(insecureCompareSourceLine);
5567
6095
  const isTestPath = isTestOrFixturePath(insecureCompareFile);
5568
6096
  if (!isTestPath) {
5569
6097
  hasSurface = true;
@@ -5571,7 +6099,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
5571
6099
  }
5572
6100
  findings.push({
5573
6101
  severity: isTestPath ? "info" : "warning",
5574
- summary: isTestPath ? `A secret/HMAC/signature comparison via plain equality appears in a test/fixture file (${insecureCompareFile}) \u2014 likely a test assertion, not a production timing leak; verify.` : "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
6102
+ summary: isTestPath ? `A secret/HMAC/signature comparison via plain equality appears in a test/fixture file (${insecureCompareFile}) \u2014 likely a test assertion, not a production timing leak; verify.` : comparesDefaultSentinel ? "A supplied secret is compared with a configured default-secret sentinel using ordinary equality; verify whether this comparison belongs on a constant-time authentication path." : "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
5575
6103
  evidence: evidenceForRelativeAtLine(
5576
6104
  repoPath,
5577
6105
  insecureCompareFile,
@@ -5581,8 +6109,9 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
5581
6109
  )
5582
6110
  });
5583
6111
  }
5584
- const unsortedSignFile = implFiles.find(
5585
- (file) => fileContains(repoPath, file, UNSORTED_JSON_SIGN_PATTERN) && !fileContains(repoPath, file, CANONICAL_SERIALIZE_PATTERN)
6112
+ const unsortedFiles = useV39Detectors ? productionImplFiles : implFiles;
6113
+ const unsortedSignFile = unsortedFiles.find(
6114
+ (file) => (useV39Detectors ? fileHasVariableUnsortedJsonHmac(repoPath, file) : fileContains(repoPath, file, UNSORTED_JSON_SIGN_PATTERN)) && !fileContains(repoPath, file, CANONICAL_SERIALIZE_PATTERN)
5586
6115
  );
5587
6116
  if (unsortedSignFile) {
5588
6117
  const isTestPath = isTestOrFixturePath(unsortedSignFile);
@@ -5590,11 +6119,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
5590
6119
  hasSurface = true;
5591
6120
  insecureFound = true;
5592
6121
  }
5593
- const unsortedSignLine = findFirstMatchingLine(
5594
- repoPath,
5595
- unsortedSignFile,
5596
- UNSORTED_JSON_SIGN_PATTERN
5597
- );
6122
+ const unsortedSignLine = useV39Detectors ? findUnsortedJsonHmacLine(repoPath, unsortedSignFile) : findFirstMatchingLine(repoPath, unsortedSignFile, UNSORTED_JSON_SIGN_PATTERN);
5598
6123
  findings.push({
5599
6124
  severity: isTestPath ? "info" : "warning",
5600
6125
  summary: isTestPath ? `Signing over unsorted JSON serialization appears in a test/fixture file (${unsortedSignFile}) \u2014 likely a test assertion, not a production signing gap; verify.` : "A signature/HMAC appears computed directly over JSON.stringify output with no canonical key ordering in the same file \u2014 signatures may not verify across equivalent payloads.",
@@ -5609,36 +6134,53 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
5609
6134
  }
5610
6135
  return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
5611
6136
  }
5612
- function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
5613
- if (!isDeployableService(repoPath, repoFiles)) {
6137
+ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors) {
6138
+ if (!isDeployableService(repoPath, repoFiles, useV27Detectors)) {
6139
+ const dockerApplicabilityNote = useV27Detectors ? "A Dockerfile without an explicit runtime start/service command is ambiguous and does not qualify." : "Dockerfile alone is ambiguous and does not qualify.";
5614
6140
  return buildNotApplicableSignal(
5615
6141
  "A3",
5616
- "No deployable-service surface detected \u2014 production-readiness not applicable to this library/CLI archetype. Signals checked: production server entrypoint (HTTP/RPC port binding in main/server/app files, outside examples/tests/demo dirs), deploy config (vercel.json, render.yaml, fly.toml, Procfile, app.yaml, serverless.yml, docker-compose, k8s/helm manifests), CI deploy job (fly deploy, kubectl apply, helm install/upgrade, docker push). Dockerfile alone is ambiguous and does not qualify."
6142
+ `No deployable-service surface detected \u2014 production-readiness not applicable to this library/CLI archetype. Signals checked: production server entrypoint (HTTP/RPC port binding in main/server/app files, outside examples/tests/demo dirs), deploy config (vercel.json, render.yaml, fly.toml, Procfile, app.yaml, serverless.yml, docker-compose, k8s/helm manifests), CI deploy job (fly deploy, kubectl apply, helm install/upgrade, docker push). ${dockerApplicabilityNote}`
5617
6143
  );
5618
6144
  }
5619
6145
  const evidence = [];
5620
6146
  const findings = [];
5621
6147
  const packageJson = findRootPackageJson(repoFiles);
5622
6148
  const scripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
5623
- const workflows = repoFiles.filter(isCiWorkflow);
6149
+ const workflows = repoFiles.filter(
6150
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
6151
+ );
5624
6152
  const workflow = workflows[0];
5625
- const deployConfigs = repoFiles.filter(isDeployConfig);
5626
- const deployConfig = deployConfigs[0];
5627
- const envTemplate = repoFiles.find((file) => isEnvTemplatePath(file));
5628
- const healthChecks = repoFiles.filter((file) => isHealthCheckSignalFile(repoPath, file));
6153
+ const deployConfigs = repoFiles.filter(
6154
+ (file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isDeployConfig(file)
6155
+ );
6156
+ const releaseDeployConfigs = repoFiles.filter(
6157
+ (file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isExplicitDeployTarget(file)
6158
+ );
6159
+ const releaseDeployConfig = releaseDeployConfigs[0];
6160
+ const containerBuildConfig = deployConfigs.find((file) => /(^|\/)Dockerfile$/.test(file));
6161
+ const envTemplate = repoFiles.find(
6162
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isEnvTemplatePath(file)
6163
+ );
6164
+ const healthChecks = repoFiles.filter(
6165
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isHealthCheckSignalFile(repoPath, file)
6166
+ );
5629
6167
  const healthCheck = healthChecks[0];
5630
6168
  const errorBoundaries = repoFiles.filter(
5631
- (file) => /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
6169
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
5632
6170
  );
5633
6171
  const errorBoundary = errorBoundaries[0];
5634
6172
  const observabilityCount = countFilesContaining(
5635
6173
  repoPath,
5636
- repoFiles.filter(isImplementationFile),
6174
+ repoFiles.filter(
6175
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
6176
+ ),
5637
6177
  /sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
5638
6178
  );
5639
6179
  const rollbackSafetyCount = countFilesContaining(
5640
6180
  repoPath,
5641
- repoFiles.filter((file) => /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)),
6181
+ repoFiles.filter(
6182
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)
6183
+ ),
5642
6184
  /rollback|roll back|migration safety|down migration|reversible|undo migration/i
5643
6185
  );
5644
6186
  if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
@@ -5647,9 +6189,23 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
5647
6189
  );
5648
6190
  }
5649
6191
  if (workflow) evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"));
5650
- if (deployConfig)
6192
+ if (releaseDeployConfig)
6193
+ evidence.push(
6194
+ evidenceForRelative(
6195
+ repoPath,
6196
+ releaseDeployConfig,
6197
+ "prod_check",
6198
+ "Release deploy configuration"
6199
+ )
6200
+ );
6201
+ if (containerBuildConfig)
5651
6202
  evidence.push(
5652
- evidenceForRelative(repoPath, deployConfig, "prod_check", "Deploy configuration")
6203
+ evidenceForRelative(
6204
+ repoPath,
6205
+ containerBuildConfig,
6206
+ "prod_check",
6207
+ "Container build configuration"
6208
+ )
5653
6209
  );
5654
6210
  if (envTemplate)
5655
6211
  evidence.push(evidenceForRelative(repoPath, envTemplate, "prod_check", "Environment template"));
@@ -5660,7 +6216,8 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
5660
6216
  if (errorBoundary)
5661
6217
  evidence.push(evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary"));
5662
6218
  if (evidence.length === 0) {
5663
- const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles);
6219
+ const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles, useV27Detectors);
6220
+ const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles) : null;
5664
6221
  if (serverEntrypoint) {
5665
6222
  evidence.push(
5666
6223
  evidenceForRelative(
@@ -5670,15 +6227,24 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
5670
6227
  "Production server entrypoint"
5671
6228
  )
5672
6229
  );
6230
+ } else if (runtimeContainer) {
6231
+ evidence.push(
6232
+ evidenceForRelative(
6233
+ repoPath,
6234
+ runtimeContainer,
6235
+ "prod_check",
6236
+ "Runtime container entrypoint"
6237
+ )
6238
+ );
5673
6239
  }
5674
6240
  }
5675
6241
  if (evidence.length === 0) return null;
5676
- if (!workflow && !deployConfig) {
6242
+ if (!workflow && releaseDeployConfigs.length === 0) {
5677
6243
  const firstEvidence = evidence[0];
5678
6244
  if (!firstEvidence) return null;
5679
6245
  findings.push({
5680
6246
  severity: "info",
5681
- summary: "Production readiness signals exist, but no CI/deploy configuration was detected.",
6247
+ summary: "A deployable service surface exists, but no CI or release-deployment automation was detected.",
5682
6248
  evidence: firstEvidence
5683
6249
  });
5684
6250
  }
@@ -5736,7 +6302,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
5736
6302
  ]
5737
6303
  };
5738
6304
  }
5739
- function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
6305
+ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors) {
5740
6306
  const evidence = [];
5741
6307
  const findings = [];
5742
6308
  const manifests = repoFiles.filter(isDependencyManifest);
@@ -5785,7 +6351,9 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
5785
6351
  );
5786
6352
  }
5787
6353
  if (!manifest && !hasLockfile) return null;
5788
- const isAppOrService = isDeployableService(repoPath, repoFiles);
6354
+ const packagedApplicationFiles = useV27Detectors ? findPackagedApplicationPremiseFiles(repoPath, repoFiles) : [];
6355
+ const deployableServiceFiles = useV47Detectors ? findDeployableServicePremiseFiles(repoPath, repoFiles, true) : [];
6356
+ const isAppOrService = (useV47Detectors ? deployableServiceFiles.length > 0 : isDeployableService(repoPath, repoFiles, useV27Detectors)) || packagedApplicationFiles.length > 0;
5789
6357
  if (isAppOrService && !hasLockfile && manifest) {
5790
6358
  findings.push({
5791
6359
  severity: "critical",
@@ -5793,7 +6361,7 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
5793
6361
  evidence: evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
5794
6362
  });
5795
6363
  }
5796
- if (manifest && hasSuspiciousDependencies(repoPath, manifest)) {
6364
+ if (manifest && hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)) {
5797
6365
  findings.push({
5798
6366
  severity: "critical",
5799
6367
  summary: "Dependency manifest contains obviously suspicious placeholder dependency names.",
@@ -5875,7 +6443,7 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
5875
6443
  positiveEvidence: evidence,
5876
6444
  findings,
5877
6445
  metrics: isAppOrService ? appMetrics : libraryMetrics,
5878
- notes: isAppOrService ? "A4 scored against app/service norms (deploy surface detected): pinned dependencies and a lockfile are required for reproducible installs." : "A4 scored against library/CLI norms (no deploy surface detected \u2014 same archetype line as A3): declared version ranges are correct library behavior; a committed lockfile is credited but not required."
6446
+ notes: isAppOrService ? `A4 scored against app/service norms (${packagedApplicationFiles.length > 0 ? "packaged application" : "deploy surface"} detected): pinned dependencies and a lockfile are required for reproducible installs.` : "A4 scored against library/CLI norms (no deploy surface detected \u2014 same archetype line as A3): declared version ranges are correct library behavior; a committed lockfile is credited but not required."
5879
6447
  };
5880
6448
  }
5881
6449
  var LIBRARY_DEPENDENCY_COUNT_SANE_MAX = 120;
@@ -5892,15 +6460,19 @@ function hasDeclaredVersionConstraint(dependency) {
5892
6460
  if (/[~^<>=!]/.test(trimmed)) return true;
5893
6461
  return /^v?\d/.test(trimmed);
5894
6462
  }
5895
- function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
5896
- const claimDoc = repoFiles.find((file) => /(^|\/)(README|readme)\.md$/.test(file)) ?? repoFiles.find((file) => /(^|\/)docs\/.*\.(md|mdx)$/.test(file));
6463
+ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
6464
+ const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
6465
+ const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
6466
+ (file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
6467
+ );
6468
+ const claimDoc = useV27Detectors ? claimSourceFiles[0] ?? reconciliationArtifacts[0] : repoFiles.find((file) => /^(?:README|readme)\.md$/.test(file)) ?? repoFiles.find((file) => /^docs\/[^/]+\.(?:md|mdx)$/.test(file)) ?? repoFiles.find((file) => /(^|\/)claim[_-]reality[_-]reconciliation\.md$/i.test(file));
5897
6469
  if (!claimDoc) {
5898
6470
  return buildNotApplicableSignal(
5899
6471
  "A5",
5900
6472
  "No README or docs found \u2014 nothing is claimed about this repo, so there is nothing for A5 to reconcile against."
5901
6473
  );
5902
6474
  }
5903
- const implementationFiles = repoFiles.filter(isImplementationFile);
6475
+ const implementationFiles = findClaimImplementationFiles(repoFiles, useV27Detectors);
5904
6476
  if (implementationFiles.length === 0) return null;
5905
6477
  const implementationFile = implementationFiles[0];
5906
6478
  if (!implementationFile) return null;
@@ -5913,10 +6485,21 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
5913
6485
  "Code presence for claim reconciliation"
5914
6486
  )
5915
6487
  ];
6488
+ for (const artifact of reconciliationArtifacts.slice(0, 3)) {
6489
+ if (artifact === claimDoc) continue;
6490
+ evidence.push(
6491
+ evidenceForRelative(
6492
+ repoPath,
6493
+ artifact,
6494
+ "claim_reconciliation",
6495
+ "Dedicated claim-reality reconciliation artifact"
6496
+ )
6497
+ );
6498
+ }
5916
6499
  const firstEvidence = evidence[0];
5917
6500
  if (!firstEvidence) return null;
5918
- const allDocFiles = repoFiles.filter(
5919
- (file) => /^README\.md$/.test(file) || /^docs\/[^/]+\.(md|mdx)$/.test(file)
6501
+ const allDocFiles = useV27Detectors ? claimSourceFiles : repoFiles.filter(
6502
+ (file) => /^README\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
5920
6503
  );
5921
6504
  const boundedImplCount = Math.min(implementationFiles.length, 12);
5922
6505
  const boundedDocCount = Math.min(allDocFiles.length, 8);
@@ -5941,7 +6524,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
5941
6524
  return {
5942
6525
  criterionId: "A5",
5943
6526
  positiveEvidence: evidence,
5944
- findings: [
6527
+ findings: reconciliationArtifacts.length > 0 ? [] : [
5945
6528
  {
5946
6529
  // Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
5947
6530
  // than a curated registry or reconciliation artifact. Downgraded to info when the repo
@@ -5953,12 +6536,11 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
5953
6536
  }
5954
6537
  ],
5955
6538
  // Recalibrated (goal_cejel_launch_hardening_combined_2026-07-06, Phase 3 H1):
5956
- // reconciliation_artifact_depth is structurally 0 for every repo on this generic-proxy
5957
- // path (only Alfred itself has the dedicated claim-reality artifact), so its prior 0.3
5958
- // weight was a permanent floor drag on every external repo regardless of how well the
5959
- // repo otherwise documents and backs its claims. Weight lowered to 0.15 (still a real
5960
- // ding for lacking the artifact) and redistributed to the two metrics that measure
5961
- // something a well-run external repo can actually earn; claim_source_depth's
6539
+ // reconciliation_artifact_depth is usually 0 on the generic-proxy path, so its prior 0.3
6540
+ // weight was a floor drag on external repos regardless of how well they otherwise document
6541
+ // and back claims. Weight remains 0.15 (still a real ding when a content-authenticated,
6542
+ // generically named reconciliation artifact is absent) and is redistributed to the two
6543
+ // metrics that every well-run external repo can earn; claim_source_depth's
5962
6544
  // denominator lowered from 8 to 4 (README + a couple of top-level docs is already a
5963
6545
  // reasonably documented repo, not a fraction of an inflated 8-file bar).
5964
6546
  metrics: [
@@ -5984,7 +6566,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
5984
6566
  metric(
5985
6567
  "reconciliation_artifact_depth",
5986
6568
  "Reconciliation artifact depth",
5987
- 0,
6569
+ reconciliationArtifacts.length,
5988
6570
  3,
5989
6571
  0.15,
5990
6572
  "artifacts",
@@ -6167,7 +6749,7 @@ function collectB4AuditEvidence(repoPath, repoFiles, generatedAt) {
6167
6749
  ]
6168
6750
  };
6169
6751
  }
6170
- function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
6752
+ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors = false) {
6171
6753
  const evidence = [];
6172
6754
  const findings = [];
6173
6755
  const docFiles = repoFiles.filter((file) => /\.(md|mdx)$/i.test(file));
@@ -6190,12 +6772,13 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
6190
6772
  (file) => fileContains(repoPath, file, SQL_EXEC_PATTERN)
6191
6773
  );
6192
6774
  const ungatedEscalationFiles = executableFiles.filter((file) => {
6775
+ if (useV39Detectors && !fileHasExecutedPrivilegeEscalation(repoPath, file)) return false;
6193
6776
  const hasEscalation = fileContains(repoPath, file, ROLE_MEMBERSHIP_GRANT_PATTERN) || fileContains(repoPath, file, SUPERUSER_ESCALATION_PATTERN);
6194
6777
  if (!hasEscalation) return false;
6195
6778
  return !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN);
6196
6779
  });
6197
6780
  const productionUngatedEscalationFiles = ungatedEscalationFiles.filter(
6198
- (file) => !isTestOrFixturePath(file)
6781
+ (file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
6199
6782
  );
6200
6783
  if (humanGateDoc) {
6201
6784
  evidence.push(
@@ -6237,11 +6820,10 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
6237
6820
  )
6238
6821
  );
6239
6822
  }
6240
- for (const file of ungatedEscalationFiles.slice(0, 5)) {
6241
- const isTestPath = isTestOrFixturePath(file);
6823
+ for (const file of productionUngatedEscalationFiles.slice(0, 5)) {
6242
6824
  findings.push({
6243
- severity: isTestPath ? "info" : "critical",
6244
- summary: isTestPath ? `Role-membership GRANT or SUPERUSER escalation statement in a test/fixture file (${file}) \u2014 likely a test assertion, not a production escalation; verify.` : "Role-membership GRANT or SUPERUSER escalation executes in code with no documented human gate.",
6825
+ severity: "critical",
6826
+ summary: "Role-membership GRANT or SUPERUSER escalation executes in code with no documented human gate.",
6245
6827
  evidence: evidenceForRelative(
6246
6828
  repoPath,
6247
6829
  file,
@@ -6300,7 +6882,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
6300
6882
  1,
6301
6883
  hasPrivilegedOpsSurface ? 0.3 : 0.4,
6302
6884
  "clean",
6303
- "Penalizes code that executes a role-membership GRANT or SUPERUSER escalation with no documented human gate (test/fixture files are downgraded findings, not penalized here)."
6885
+ "Penalizes code that executes a role-membership GRANT or SUPERUSER escalation with no documented human gate (test/fixture SQL is excluded from this production-code measurement)."
6304
6886
  ),
6305
6887
  metric(
6306
6888
  "protected_path_review_gate",
@@ -6320,41 +6902,62 @@ var CPP_TEST_FRAMEWORK_PATTERN = /<gtest\/gtest\.h>|<catch2\/|<doctest\/|<boost\
6320
6902
  var CPP_TEST_MACRO_PATTERN = /\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(|\bCATCH_|BOOST_AUTO_TEST_CASE\b|BOOST_FIXTURE_TEST_CASE\b/;
6321
6903
  var CPP_CONTENT_SCAN_LIMIT = 150;
6322
6904
  var NON_PRODUCTION_DIR_PATTERN = /(^|\/)(__tests__|__mocks__|__fixtures__|tests?|fixtures?|testdata|e2e|cypress|examples?|samples?|demos?)(\/|$)/i;
6323
- var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]+$/i;
6905
+ var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]+$|_test\.go$|(^|\/)test_.*\.py$/i;
6324
6906
  function isTestOrFixturePath(path) {
6325
6907
  return NON_PRODUCTION_DIR_PATTERN.test(path) || NON_PRODUCTION_FILE_SUFFIX_PATTERN.test(path);
6326
6908
  }
6327
6909
  function isProductionSourcePath(path) {
6328
6910
  return !isTestOrFixturePath(path);
6329
6911
  }
6912
+ function isAuthoredProductionPath(path) {
6913
+ return isProductionSourcePath(path) && !GENERATED_OR_VENDOR_PATH_PATTERN.test(path);
6914
+ }
6915
+ var V39_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN = /(^|\/)(?:docs?|documentation|e2e[-_]?tests?)(\/|$)|(^|\/)(?:test|tests)[._-][^/]+$|(^|\/)[^/]*(?:appium|e2e[-_]?tests?)[^/]*$/i;
6916
+ function isV39AuthoredProductionPath(path) {
6917
+ return isAuthoredProductionPath(path) && !V39_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN.test(path);
6918
+ }
6919
+ var V47_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN = /(^|\/)(?:auto[-_]?gen(?:erated)?|generated|i18n|locales?|cookbooks?|lessons?)(\/|$)|\.(?:md|mdx|rst|adoc)$/i;
6920
+ function isV47AuthoredProductionPath(path) {
6921
+ return isV39AuthoredProductionPath(path) && !V47_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN.test(path);
6922
+ }
6923
+ var TEST_DIRECTORY_SOURCE_PATTERN = new RegExp(
6924
+ `(^|/)(?:__tests__|tests?|specs?|[^/]+(?:Tests|Specs))/(?:.*\\.)?(?:${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`
6925
+ );
6330
6926
  function isTestFile(file) {
6331
- return /(^|\/)(__tests__|test|tests|spec)\/.*\.(?:[cm]?[jt]sx?|py|go|rs|java|rb|cpp|cc|cxx)$/.test(
6332
- file
6333
- ) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
6927
+ return TEST_DIRECTORY_SOURCE_PATTERN.test(file) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
6334
6928
  // Keep this root-anchored: nested files need an existing test-directory/suffix convention.
6335
6929
  /^test(?:-[^/]+)?\.[cm]?[jt]sx?$/.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)test_.*\.py$/.test(file) || /_test\.go$/.test(file) || /(^|\/)test_.*\.(cpp|cc|cxx)$/.test(file) || /_(test|tests)\.(cpp|cc|cxx)$/.test(file) || /Tests?\.(java|kt)$/.test(file) || /(_test|_spec)\.(rs|rb|php|swift|kt)$/.test(file);
6336
6930
  }
6337
6931
  var NAME_SHAPED_TEST_FILE_PATTERN = /^test(?:-[^/]+)?\.[cm]?[jt]sx?$|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)__tests__\/|(^|\/)test_[^/]*\.py$|(^|\/)tests?\.py$|_test\.go$|(^|\/)test_[^/]*\.(cpp|cc|cxx)$|_(test|tests)\.(cpp|cc|cxx)$|Tests?\.(java|kt)$|(_test|_spec)\.(rs|rb|php|swift|kt)$/;
6338
- var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test)\b/i;
6339
- var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+test|npm\s+test|yarn\s+test|node\s+--test|node:test)\b/i;
6932
+ var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test|node\s+--test|node:test)\b/i;
6933
+ var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|dotnet\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+(?:run\s+)?test|npm\s+(?:run\s+)?test|yarn\s+(?:run\s+)?test|node\s+--test|node:test)\b|--target[=\s]+test\b/i;
6340
6934
  var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
6341
6935
  var CI_TYPECHECK_COMMAND_PATTERN = /\b(tsc|mypy|pyright)\b/i;
6342
6936
  var SCHEDULE_TRIGGER_PATTERN = /(^|\n)\s*schedule:\s*\r?\n\s*-\s*cron:/;
6343
6937
  var PUBLISHED_RESULT_MARKER_PATTERN = /actions\/deploy-pages|actions\/upload-pages-artifact|peaceiris\/actions-gh-pages|git\s+push\b|create-or-update-comment/i;
6344
6938
  var EPHEMERAL_ARTIFACT_ONLY_PATTERN = /actions\/upload-artifact/i;
6939
+ var SCHEDULED_HEALTH_PATH_PATTERN = /(?:health|smoke|monitor|status|uptime|canary)/i;
6940
+ var SCHEDULED_HEALTH_INTENT_PATTERN = /\b(?:product[- ]?health|health check|smoke test|monitor(?:ing)?|uptime|canary|fault detection)\b/i;
6345
6941
  var RLS_PATTERN = /row level security|enable rls|create policy|tenant[_-]?id|studio[_-]?id/i;
6346
6942
  var DB_CLIENT_PATTERN = /\b(pg|mysql|mysql2|sqlite3|mongodb|mongoose|sequelize|drizzle|prisma|knex|typeorm|redis|ioredis|better-sqlite3|asyncpg|psycopg2?|sqlalchemy|pymongo|aiosqlite|database\/sql|gorm|sqlx|diesel|rusqlite|ActiveRecord|Sequel|PDO|JDBC|Hibernate)\b/i;
6347
6943
  var ENV_READ_PATTERN = /process\.env\b|Deno\.env\b|os\.environ\b|getenv\s*\(|std::env::|ENV\[/;
6348
6944
  var TENANT_SCOPE_PATTERN = /tenantId|tenant_id|studioId|studio_id|organizationId|org_id|workspaceId/i;
6945
+ var TENANT_SCOPE_PATTERN_V10 = /tenantId|tenant_id|studioId|studio_id|organizationId|organization_id|org_id|accountTenantId|account_tenant_id/i;
6946
+ var TENANT_SCOPE_PATTERN_V11 = /tenantId|tenant_id|accountTenantId|account_tenant_id/i;
6349
6947
  var HUMAN_GATE_MARKER_PATTERN = /human[- ]executed|human[- ]gated|operator[- ]run|never agent-run|agents? (?:never|does not|do not) (?:hold|execute|run) (?:prod(?:uction)?\s+)?(?:admin|db) (?:credential|privilege)/i;
6350
6948
  var ROLE_MEMBERSHIP_GRANT_PATTERN = /\bGRANT\s+(?!SELECT\b|INSERT\b|UPDATE\b|DELETE\b|USAGE\b|ALL\b|EXECUTE\b|TRIGGER\b|REFERENCES\b|CREATE\b|CONNECT\b|TEMP(?:ORARY)?\b)[A-Za-z_]\w*\s+TO\b/i;
6351
6949
  var SUPERUSER_ESCALATION_PATTERN = /\b(ALTER|CREATE)\s+(ROLE|USER)\s+\w+[^;]*\bSUPERUSER\b/i;
6352
6950
  var SQL_EXEC_PATTERN = /\.execute\s*\(|sql\.raw\s*\(/;
6951
+ var EXECUTED_ESCALATION_LITERAL_PATTERN = /(?:\.execute|sql\.raw)\s*\(\s*(?:[rubf]+)?["'`][^"'`]{0,500}(?:GRANT\s+(?!SELECT\b|INSERT\b|UPDATE\b|DELETE\b|USAGE\b|ALL\b|EXECUTE\b|TRIGGER\b|REFERENCES\b|CREATE\b|CONNECT\b|TEMP(?:ORARY)?\b)[A-Za-z_]\w*\s+TO\b|(?:ALTER|CREATE)\s+(?:ROLE|USER)\s+\w+[^;]*\bSUPERUSER\b)/i;
6952
+ function fileHasExecutedPrivilegeEscalation(repoPath, file) {
6953
+ return fileContains(repoPath, file, EXECUTED_ESCALATION_LITERAL_PATTERN);
6954
+ }
6353
6955
  var GATED_PRIVILEGE_CHECK_PATTERN = /pg_has_role\s*\(|has_role\s*\(|is not a member of/i;
6354
6956
  var SET_ROLE_PATTERN = /\bset\s+(local\s+)?role\b/i;
6355
6957
  var REQUIRED_REVIEW_PATTERN = /required review|branch protection|protected branch/i;
6356
6958
  var HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT = 5e3;
6357
- var SECRET_ASSIGNMENT_PATTERN = /[A-Za-z0-9_]*(?:secret|token|api[_-]?key|password|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*['"]?([^'"\s]+)/gi;
6959
+ var SECRET_IDENTIFIER_KEYWORD_PATTERN = /secret|token|api[_-]?key|password|access[_-]?key/gi;
6960
+ var SINGLE_WHITESPACE_PATTERN = /\s/u;
6358
6961
  var SECRET_VALUE_BRANDED_PATTERN = /^(?:sk-[A-Za-z0-9_-]{24,}|sk-ant-api\d{2}-[A-Za-z0-9_-]{80,}|ghp_[A-Za-z0-9_]{30,}|xox[baprs]-[A-Za-z0-9-]{24,}|AKIA[0-9A-Z]{16}|vcp_[A-Za-z0-9_:-]{24,})$/;
6359
6962
  var GENERIC_SECRET_SHAPE_PATTERN = /^[A-Za-z0-9/+_-]{40,}$/;
6360
6963
  var KNOWN_HEX_DIGEST_LENGTHS = /* @__PURE__ */ new Set([32, 40, 64, 128]);
@@ -6363,23 +6966,25 @@ var NEGATIVE_SPACE_SECTION_PATTERN = /^#{1,6}\s*(?:security\s+)?(?:limitations?|
6363
6966
  var NEGATIVE_SPACE_PHRASE_PATTERN = /does not (?:protect|defend|guard|cover)s? against|not (?:covered|protected|defended) by this|out of scope for this (?:library|package|tool|sdk|project)/i;
6364
6967
  var LEAN_TEST_RUNNER_SCRIPT_PATTERN = /\bnode\s+(?:--test\b|--experimental-test-coverage\b)/;
6365
6968
  var LEAN_TEST_RUNNER_IMPORT_PATTERN = /from\s+['"]node:test['"]|require\(\s*['"]node:test['"]\s*\)/;
6366
- var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:jest|mocha|ava|tape|jasmine|karma)(?:$|-|\/)/i;
6969
+ var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:(?:jest|mocha|ava|tape|jasmine|karma|vitest|cypress|playwright)(?:$|-|\/)|@(?:vitest|playwright)\/)/i;
6367
6970
  var TIMING_SAFE_COMPARE_PATTERN = /\btiming[-_]?safe[-_]?(?:equal|compare)\b|crypto\.timingSafeEqual\s*\(/i;
6368
6971
  var CANONICAL_SERIALIZE_PATTERN = /\bcanonicalize\s*\(|\bcanonicalJson\b|\bstableStringify\s*\(|json-stable-stringify|safe-stable-stringify|\bsortKeys\s*\(/i;
6369
6972
  var SIGN_OR_HMAC_CALL_PATTERN = /\bcreateHmac\s*\(|\bcreateSign\s*\(|\.sign\s*\(/;
6370
6973
  var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac|digest)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
6974
+ var DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN = /\b(?:secret|token|password|apiKey)\w*\s*(?:===|!==|==|!=)\s*[\w.]*default(?:Secret|Token|Password|ApiKey)\b/i;
6371
6975
  var NUMERIC_SHAPED_OPERAND_PATTERN = /^-?\d+$|\.(?:length|len|size|count)\b|(?:length|index|idx|offset|count|size|len)$/i;
6372
- function lineHasInsecureSecretCompare(line) {
6976
+ function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
6977
+ if (useV39Detectors && DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN.test(line)) return true;
6373
6978
  const match = INSECURE_SECRET_COMPARE_LINE_PATTERN.exec(line);
6374
6979
  const operand = match?.[2];
6375
6980
  if (!operand) return false;
6376
6981
  return !NUMERIC_SHAPED_OPERAND_PATTERN.test(operand.trim());
6377
6982
  }
6378
- function findInsecureSecretCompareLine(repoPath, file) {
6983
+ function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
6379
6984
  const fullPath = join(repoPath, file);
6380
6985
  if (!isRegularFile(fullPath)) return null;
6381
6986
  const lines = readFileSync(fullPath, "utf8").split("\n");
6382
- const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line));
6987
+ const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
6383
6988
  return index === -1 ? null : index + 1;
6384
6989
  }
6385
6990
  function findFirstMatchingLine(repoPath, file, pattern) {
@@ -6389,7 +6994,49 @@ function findFirstMatchingLine(repoPath, file, pattern) {
6389
6994
  const index = lines.findIndex((line) => pattern.test(line));
6390
6995
  return index === -1 ? null : index + 1;
6391
6996
  }
6997
+ function lineAt(repoPath, file, line) {
6998
+ if (line < 1) return null;
6999
+ const fullPath = join(repoPath, file);
7000
+ if (!isRegularFile(fullPath)) return null;
7001
+ return readFileSync(fullPath, "utf8").split("\n")[line - 1] ?? null;
7002
+ }
6392
7003
  var UNSORTED_JSON_SIGN_PATTERN = /\.update\s*\(\s*JSON\.stringify\s*\(|\.sign\s*\([^)]*JSON\.stringify\s*\(/;
7004
+ var UNSORTED_JSON_DIRECT_SIGN_PATTERN = /\.sign\s*\([^)]*JSON\.stringify\s*\(/;
7005
+ var UNSORTED_JSON_CONSTRUCTOR_UPDATE_PATTERN = /\bcreate(?:Hmac|Sign)\s*\([^;\n]{0,256}\)\s*\.update\s*\(\s*JSON\.stringify\s*\(/;
7006
+ var JSON_STRINGIFY_ASSIGNMENT_PATTERN = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*JSON\.stringify\s*\(/g;
7007
+ function findUnsortedJsonHmacLine(repoPath, file) {
7008
+ const fullPath = join(repoPath, file);
7009
+ if (!isRegularFile(fullPath)) return null;
7010
+ const contents = readFileSync(fullPath, "utf8");
7011
+ const directSign = findFirstMatchingLine(repoPath, file, UNSORTED_JSON_DIRECT_SIGN_PATTERN);
7012
+ if (directSign !== null) return directSign;
7013
+ const directConstructorUpdate = findFirstMatchingLine(
7014
+ repoPath,
7015
+ file,
7016
+ UNSORTED_JSON_CONSTRUCTOR_UPDATE_PATTERN
7017
+ );
7018
+ if (directConstructorUpdate !== null) return directConstructorUpdate;
7019
+ JSON_STRINGIFY_ASSIGNMENT_PATTERN.lastIndex = 0;
7020
+ let match = JSON_STRINGIFY_ASSIGNMENT_PATTERN.exec(contents);
7021
+ while (match !== null) {
7022
+ const identifier = match[1];
7023
+ if (identifier) {
7024
+ const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7025
+ const constructorUpdate = new RegExp(
7026
+ `\\bcreate(?:Hmac|Sign)\\s*\\([^;\\n]{0,256}\\)\\s*\\.update\\s*\\(\\s*${escaped}\\b`
7027
+ );
7028
+ const usageWindow = contents.slice(match.index, match.index + 2048);
7029
+ if (constructorUpdate.test(usageWindow)) {
7030
+ return contents.slice(0, match.index).split("\n").length;
7031
+ }
7032
+ }
7033
+ match = JSON_STRINGIFY_ASSIGNMENT_PATTERN.exec(contents);
7034
+ }
7035
+ return null;
7036
+ }
7037
+ function fileHasVariableUnsortedJsonHmac(repoPath, file) {
7038
+ return findUnsortedJsonHmacLine(repoPath, file) !== null;
7039
+ }
6393
7040
  var KILL_SWITCH_NAME_FRAGMENT = "kill[_-]?switch|emergency[_-]?stop|circuit[_-]?breaker|safety[_-]?(?:toggle|gate|switch)|governance[_-]?(?:toggle|gate)";
6394
7041
  var KILL_SWITCH_NAME_PATTERN = new RegExp(`\\b(?:${KILL_SWITCH_NAME_FRAGMENT})\\b`, "i");
6395
7042
  var KILL_SWITCH_FAIL_CLOSED_PATTERN = new RegExp(
@@ -6434,19 +7081,23 @@ function looksLikeSecretValue(value) {
6434
7081
  return SECRET_VALUE_BRANDED_PATTERN.test(value) || isGenericSecretValue(value);
6435
7082
  }
6436
7083
  var PLACEHOLDER_SECRET_PATTERN = /(?:^|[-_])(?:your|example|sample|placeholder|dummy|changeme|replace(?:-?me)?|redacted)(?:$|[-_])|^x{3,}$|^\.\.\.|^\$\{[^}]+}$|^<[^>]+>$/i;
6437
- var ENV_TEMPLATE_PATTERN = /(^|\/)\.env\.(example|sample|template|dist)$/;
6438
- function isEnvTemplatePath(path) {
6439
- return ENV_TEMPLATE_PATTERN.test(path);
7084
+ var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist)$/i;
7085
+ var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist|exemplo|ejemplo|exemple|esempio|beispiel|voorbeeld)$/i;
7086
+ function isEnvTemplatePath(path, useV47Detectors = false) {
7087
+ return (useV47Detectors ? ENV_TEMPLATE_PATTERN_V47 : ENV_TEMPLATE_PATTERN).test(path);
6440
7088
  }
6441
7089
  var HARD_EXCLUDED_PATH_PATTERN = /(^|\/)(?:\.git|\.venv|venv|site-packages|node_modules|dist|build|\.next|__pycache__|vendor|\.terraform|coverage)(?:\/|$)/;
6442
7090
  var COVERAGE_PERCENT_PATTERN = /(?:statements|branches|lines|functions|fail_under)\s*[:=]\s*["']?(\d+(?:\.\d+)?)/gi;
6443
7091
  function listRepoFiles(repoPath) {
6444
- const gitFiles = listGitTrackedFiles(repoPath);
7092
+ const gitFiles = listGitTrackedFiles(repoPath, false);
6445
7093
  if (gitFiles) return gitFiles;
6446
7094
  const files = [];
6447
7095
  visitRepoDir(repoPath, repoPath, files);
6448
7096
  return files.filter((file) => !isHardExcludedPath(file)).sort();
6449
7097
  }
7098
+ function listRepoInventory(repoPath, fallbackFiles) {
7099
+ return listGitTrackedFiles(repoPath, true) ?? [...fallbackFiles];
7100
+ }
6450
7101
  function resolveMonorepoContext(repoPath) {
6451
7102
  let top;
6452
7103
  try {
@@ -6479,7 +7130,7 @@ function resolveMonorepoContext(repoPath) {
6479
7130
  if (sharedFiles.length === 0) return null;
6480
7131
  return { root: rootResolved, sharedFiles };
6481
7132
  }
6482
- function listGitTrackedFiles(repoPath) {
7133
+ function listGitTrackedFiles(repoPath, includeHardExcluded = false) {
6483
7134
  try {
6484
7135
  execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
6485
7136
  cwd: repoPath,
@@ -6493,7 +7144,7 @@ function listGitTrackedFiles(repoPath) {
6493
7144
  }).trim();
6494
7145
  if (!output) return [];
6495
7146
  return output.split("\n").filter(
6496
- (file) => file.length > 0 && !isHardExcludedPath(file) && isRegularFile(join(repoPath, file))
7147
+ (file) => file.length > 0 && (includeHardExcluded || !isHardExcludedPath(file)) && isRegularFile(join(repoPath, file))
6497
7148
  ).sort();
6498
7149
  } catch {
6499
7150
  return null;
@@ -6898,43 +7549,205 @@ function parseJsonObject(contents) {
6898
7549
  function isRecord(value) {
6899
7550
  return typeof value === "object" && value !== null && !Array.isArray(value);
6900
7551
  }
6901
- function isTestRunnerConfig(file) {
6902
- return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini|go\.mod|Cargo\.toml)$/.test(file) || /(^|\/)CMakeLists\.txt$/.test(file) || /(^|\/)Makefile$/.test(file) || /(^|\/)phpunit\.xml(\.dist)?$/.test(file);
7552
+ function isTestRunnerConfig(repoPath, file) {
7553
+ return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini)$/.test(file) || /(^|\/)phpunit\.xml(\.dist)?$/.test(file) || /(^|\/)pyproject\.toml$/.test(file) && fileContains(repoPath, file, /^\[tool\.pytest(?:\.|\])/m) || /(^|\/)CMakeLists\.txt$/.test(file) && fileContains(repoPath, file, /\b(?:enable_testing|add_test|ctest_add_tests)\s*\(/i) || /(^|\/)Makefile$/.test(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
6903
7554
  }
6904
- function isCoverageConfig(file) {
6905
- return /(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file) || fileContainsExtension(file, ["vitest.config.ts", "jest.config.js", "pyproject.toml"]);
7555
+ function findCoverageConfigFiles(repoPath, repoFiles, authoredOnly = false) {
7556
+ return repoFiles.filter(
7557
+ (file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCoverageConfig(repoPath, file)
7558
+ );
6906
7559
  }
6907
- function fileContainsExtension(file, names) {
6908
- return names.some((name) => file.endsWith(name));
7560
+ function findConcreteTestFiles(repoPath, repoFiles) {
7561
+ const contentCppTests = collectContentBasedCppTestFiles(repoPath, repoFiles);
7562
+ return [...repoFiles.filter(isTestFile), ...contentCppTests.filter((file) => !isTestFile(file))];
7563
+ }
7564
+ function findConfiguredTestRunnerFiles(repoPath, repoFiles) {
7565
+ const configured = repoFiles.filter(
7566
+ (file) => isAuthoredProductionPath(file) && isTestRunnerConfig(repoPath, file)
7567
+ );
7568
+ for (const packageJson of repoFiles.filter(
7569
+ (file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
7570
+ )) {
7571
+ if ([...readPackageScripts(join(repoPath, packageJson)).values()].some(
7572
+ (script) => TEST_RUNNER_PATTERN.test(script)
7573
+ )) {
7574
+ configured.push(packageJson);
7575
+ }
7576
+ }
7577
+ configured.push(
7578
+ ...repoFiles.filter((file) => isAuthoredProductionPath(file) && isCiWorkflow(file)).filter((file) => fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN))
7579
+ );
7580
+ return [...new Set(configured)].sort();
7581
+ }
7582
+ function findTenantStoragePremiseFiles(repoPath, repoFiles) {
7583
+ return repoFiles.filter((file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)).filter((file) => fileContains(repoPath, file, TENANT_SCOPE_PATTERN_V11));
7584
+ }
7585
+ function findRlsPolicyFiles(repoPath, repoFiles) {
7586
+ return repoFiles.filter((file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)).filter(
7587
+ (file) => fileContains(
7588
+ repoPath,
7589
+ file,
7590
+ /\b(?:create\s+policy|enable\s+row\s+level\s+security|force\s+row\s+level\s+security)\b/i
7591
+ )
7592
+ );
7593
+ }
7594
+ function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
7595
+ const explicitTargets = repoFiles.filter(
7596
+ (file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
7597
+ );
7598
+ const deployWorkflows = repoFiles.filter((file) => isAuthoredProductionPath(file) && isCiWorkflow(file)).filter((file) => fileContains(repoPath, file, CI_DEPLOY_JOB_PATTERN)).filter(
7599
+ (file) => !strictOperationalBoundary || !isDocumentationOnlyDeployWorkflow(repoPath, file)
7600
+ );
7601
+ const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles, true);
7602
+ const runtimeContainer = findRuntimeContainerEntrypointFile(repoPath, repoFiles);
7603
+ if (strictOperationalBoundary) {
7604
+ const runtimeFiles = [serverEntrypoint, runtimeContainer].filter(
7605
+ (file) => file != null && isV47OperationalApplicationPath(file)
7606
+ );
7607
+ const directApplicationTargets = explicitTargets.filter(
7608
+ (file) => !isInfrastructureOnlyDeployTarget(file)
7609
+ );
7610
+ const infrastructureTargets = runtimeFiles.length > 0 ? [...explicitTargets.filter(isInfrastructureOnlyDeployTarget), ...deployWorkflows] : [];
7611
+ return [...directApplicationTargets, ...runtimeFiles, ...infrastructureTargets];
7612
+ }
7613
+ return [
7614
+ ...explicitTargets,
7615
+ ...deployWorkflows,
7616
+ ...serverEntrypoint && (!strictOperationalBoundary || isV47OperationalApplicationPath(serverEntrypoint)) ? [serverEntrypoint] : [],
7617
+ ...runtimeContainer && (!strictOperationalBoundary || isV47OperationalApplicationPath(runtimeContainer)) ? [runtimeContainer] : []
7618
+ ];
7619
+ }
7620
+ function isInfrastructureOnlyDeployTarget(file) {
7621
+ return /(^|\/)docker-compose\.ya?ml$/i.test(file) || /(^|\/)(?:k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /(^|\/)(?:charts?|helm)\/.+\.ya?ml$/i.test(file);
7622
+ }
7623
+ var V47_NON_OPERATIONAL_APPLICATION_PATH_PATTERN = /(^|\/)(?:cookbooks?|lessons?|tutorials?|docs?|documentation|examples?|samples?|demos?)(\/|$)/i;
7624
+ function isV47OperationalApplicationPath(path) {
7625
+ return !V47_NON_OPERATIONAL_APPLICATION_PATH_PATTERN.test(path);
7626
+ }
7627
+ function isDocumentationOnlyDeployWorkflow(repoPath, file) {
7628
+ if (/(?:docs?|pages?)/i.test(basename(file))) return true;
7629
+ return fileContains(
7630
+ repoPath,
7631
+ file,
7632
+ /\b(?:deploy[-_ ]?docs?|mkdocs|docusaurus|vitepress|gh-pages|github pages)\b/i
7633
+ );
7634
+ }
7635
+ function findPackagedApplicationPremiseFiles(repoPath, repoFiles) {
7636
+ return repoFiles.filter((file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)).filter((file) => packageJsonDescribesPackagedApplication(repoPath, file, repoFiles));
7637
+ }
7638
+ function findClaimSourceFiles(repoFiles) {
7639
+ return repoFiles.filter(
7640
+ (file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
7641
+ ).sort((left, right) => {
7642
+ const leftRootReadme = /^(?:README|readme)\.md$/.test(left);
7643
+ const rightRootReadme = /^(?:README|readme)\.md$/.test(right);
7644
+ return Number(rightRootReadme) - Number(leftRootReadme) || left.localeCompare(right);
7645
+ });
7646
+ }
7647
+ function findClaimImplementationFiles(repoFiles, authoredOnly = false) {
7648
+ return repoFiles.filter(
7649
+ (file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file))
7650
+ );
7651
+ }
7652
+ function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
7653
+ return repoFiles.filter((file) => /(^|\/)claim[-_]reality[-_]reconciliation\.(?:md|mdx|json)$/i.test(file)).filter((file) => isAuthenticatedClaimRealityArtifact(repoPath, file));
7654
+ }
7655
+ function isAuthenticatedClaimRealityArtifact(repoPath, file) {
7656
+ const fullPath = join(repoPath, file);
7657
+ if (!isRegularFile(fullPath)) return false;
7658
+ const contents = readFileSync(fullPath, "utf8");
7659
+ if (/\.json$/i.test(file)) {
7660
+ return parseJsonObject(contents)?.artifactKind === "claim_reality_reconciliation";
7661
+ }
7662
+ let inFence = false;
7663
+ for (const line of contents.split(/\r?\n/)) {
7664
+ if (/^\s*(?:```|~~~)/.test(line)) {
7665
+ inFence = !inFence;
7666
+ continue;
7667
+ }
7668
+ if (!inFence && /^#\s+Claim Reality Reconciliation\s*$/i.test(line)) return true;
7669
+ }
7670
+ return false;
7671
+ }
7672
+ function isCoverageConfig(repoPath, file) {
7673
+ if (/(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file)) {
7674
+ return true;
7675
+ }
7676
+ if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
7677
+ return fileContains(repoPath, file, /\bcoverage\s*:/i);
7678
+ }
7679
+ if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
7680
+ return fileContains(
7681
+ repoPath,
7682
+ file,
7683
+ /\b(?:collectCoverage|coverageThreshold|coverageProvider|coverageDirectory)\s*:/
7684
+ );
7685
+ }
7686
+ if (/(^|\/)pyproject\.toml$/.test(file)) {
7687
+ return fileContains(repoPath, file, /^\[tool\.coverage(?:\.|\])/m);
7688
+ }
7689
+ return false;
6909
7690
  }
6910
7691
  function isCiWorkflow(file) {
6911
7692
  return /^\.github\/workflows\/.+\.ya?ml$/.test(file) || /^\.gitlab-ci\.ya?ml$/.test(file) || /^\.circleci\/config\.ya?ml$/.test(file) || /^azure-pipelines\.ya?ml$/.test(file) || /^Jenkinsfile$/.test(file);
6912
7693
  }
6913
7694
  function isDeployConfig(file) {
6914
- return /^(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /^Dockerfile$/.test(file) || /^docker-compose\.ya?ml$/.test(file) || /^k8s\//.test(file);
7695
+ return /(^|\/)(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /(^|\/)Dockerfile$/.test(file) || /(^|\/)docker-compose\.ya?ml$/.test(file) || /(^|\/)k8s\//.test(file);
6915
7696
  }
6916
7697
  function isExplicitDeployTarget(file) {
6917
- return /^(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /^docker-compose\.ya?ml$/.test(file) || /^Procfile$/.test(file) || /^app\.ya?ml$/.test(file) || /^serverless\.ya?ml$/.test(file) || /^(k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /^(charts?|helm)\/.+\.ya?ml$/i.test(file);
7698
+ return /(^|\/)(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /(^|\/)docker-compose\.ya?ml$/.test(file) || /(^|\/)Procfile$/.test(file) || /(^|\/)app\.ya?ml$/.test(file) || /(^|\/)serverless\.ya?ml$/.test(file) || /(^|\/)(k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /(^|\/)(charts?|helm)\/.+\.ya?ml$/i.test(file);
6918
7699
  }
6919
7700
  var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry|start|run)\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs)$/i;
7701
+ var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start|run)\.rb)$/i;
6920
7702
  var SERVER_ENTRYPOINT_PATTERN = /\bapp\.listen\s*\(|\bserver\.listen\s*\(\s*(?:PORT|port|\d+)|http\.ListenAndServe\s*\(|http\.ListenAndServeTLS\s*\(|axum::Server(?:::|\.)|actix_web::HttpServer(?:::|\.)|uvicorn\.run\s*\(/;
7703
+ var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
7704
+ var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
7705
+ var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
7706
+ var NON_RUNTIME_CONTAINER_COMMAND_PATTERN = /\b(?:test|lint|build|compile|package|check)\b/i;
6921
7707
  var CI_DEPLOY_JOB_PATTERN = /(?:^\s{2,4}deploy\s*:|\b(?:fly|heroku|vercel|netlify|railway|render)\s+deploy\b|kubectl\s+apply\b|helm\s+(?:install|upgrade|deploy)\b|docker\s+push\b)/im;
6922
- function isDeployableService(repoPath, repoFiles) {
6923
- if (repoFiles.some(isExplicitDeployTarget)) return true;
6924
- if (detectCiDeployJob(repoPath, repoFiles)) return true;
6925
- if (detectServerEntrypoint(repoPath, repoFiles)) return true;
7708
+ function isDeployableService(repoPath, repoFiles, useV27Detectors) {
7709
+ if (repoFiles.some(
7710
+ (file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isExplicitDeployTarget(file)
7711
+ )) {
7712
+ return true;
7713
+ }
7714
+ if (detectCiDeployJob(repoPath, repoFiles, useV27Detectors)) return true;
7715
+ if (detectServerEntrypoint(repoPath, repoFiles, useV27Detectors)) return true;
7716
+ if (useV27Detectors && findRuntimeContainerEntrypointFile(repoPath, repoFiles)) return true;
6926
7717
  return false;
6927
7718
  }
6928
- function detectCiDeployJob(repoPath, repoFiles) {
6929
- const workflows = repoFiles.filter(isCiWorkflow);
7719
+ function detectCiDeployJob(repoPath, repoFiles, authoredOnly = false) {
7720
+ const workflows = repoFiles.filter(
7721
+ (file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCiWorkflow(file)
7722
+ );
6930
7723
  return workflows.some((file) => fileContains(repoPath, file, CI_DEPLOY_JOB_PATTERN));
6931
7724
  }
6932
- function detectServerEntrypoint(repoPath, repoFiles) {
6933
- return findServerEntrypointFile(repoPath, repoFiles) !== null;
7725
+ function detectServerEntrypoint(repoPath, repoFiles, useV27Detectors) {
7726
+ return findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) !== null;
6934
7727
  }
6935
- function findServerEntrypointFile(repoPath, repoFiles) {
6936
- const candidates = repoFiles.filter((file) => MAIN_ENTRYPOINT_FILE_PATTERN.test(file) && isProductionSourcePath(file)).slice(0, 30);
6937
- return candidates.find((file) => fileContains(repoPath, file, SERVER_ENTRYPOINT_PATTERN)) ?? null;
7728
+ function findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) {
7729
+ const candidates = repoFiles.filter(
7730
+ (file) => (MAIN_ENTRYPOINT_FILE_PATTERN.test(file) || useV27Detectors && RACK_ENTRYPOINT_FILE_PATTERN.test(file)) && (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file))
7731
+ ).slice(0, 30);
7732
+ return candidates.find(
7733
+ (file) => fileContains(repoPath, file, SERVER_ENTRYPOINT_PATTERN) || useV27Detectors && fileContains(repoPath, file, RACK_SERVER_ENTRYPOINT_PATTERN) || useV27Detectors && /\.ru$/i.test(file) && fileContains(repoPath, file, RACK_CONFIG_RUN_PATTERN)
7734
+ ) ?? null;
7735
+ }
7736
+ function findRuntimeContainerEntrypointFile(repoPath, repoFiles) {
7737
+ return repoFiles.filter((file) => /(^|\/)Dockerfile$/i.test(file) && isAuthoredProductionPath(file)).find((file) => {
7738
+ const fullPath = join(repoPath, file);
7739
+ if (!isRegularFile(fullPath)) return false;
7740
+ const lines = readFileSync(fullPath, "utf8").split(/\r?\n/);
7741
+ const finalStageStart = lines.reduce(
7742
+ (last, line, index) => /^\s*FROM\b/i.test(line) ? index : last,
7743
+ 0
7744
+ );
7745
+ const finalStageLines = lines.slice(finalStageStart);
7746
+ const entrypoint = [...finalStageLines].reverse().find((line) => /^\s*ENTRYPOINT\b/i.test(line));
7747
+ const command = [...finalStageLines].reverse().find((line) => /^\s*CMD\b/i.test(line));
7748
+ const effectiveCommand = [entrypoint, command].filter(Boolean).join(" ");
7749
+ return effectiveCommand.length > 0 && RUNTIME_CONTAINER_COMMAND_PATTERN.test(effectiveCommand) && !NON_RUNTIME_CONTAINER_COMMAND_PATTERN.test(effectiveCommand);
7750
+ }) ?? null;
6938
7751
  }
6939
7752
  function isHealthCheckSignalFile(repoPath, file) {
6940
7753
  if (/docs\/|audit|architecture|adr/i.test(file)) return false;
@@ -6981,6 +7794,42 @@ function packageJsonHasHeavyTestDependency(repoPath, manifest) {
6981
7794
  ];
6982
7795
  return names.some((name) => HEAVY_TEST_DEPENDENCY_NAME_PATTERN.test(name));
6983
7796
  }
7797
+ function packageJsonDescribesPackagedApplication(repoPath, manifest, repoFiles) {
7798
+ const fullPath = join(repoPath, manifest);
7799
+ if (!isRegularFile(fullPath)) return false;
7800
+ const parsed = parseJsonObject(readFileSync(fullPath, "utf8"));
7801
+ if (!parsed) return false;
7802
+ const scripts = isRecord(parsed.scripts) ? Object.values(parsed.scripts) : [];
7803
+ const dependencies = {
7804
+ ...isRecord(parsed.dependencies) ? parsed.dependencies : {},
7805
+ ...isRecord(parsed.devDependencies) ? parsed.devDependencies : {}
7806
+ };
7807
+ const build = isRecord(parsed.build) ? parsed.build : null;
7808
+ const hasDesktopRuntime = Object.keys(dependencies).some(
7809
+ (name) => name.toLowerCase() === "electron" || /^@tauri-apps\/(?:api|plugin-)/i.test(name)
7810
+ );
7811
+ const hasPackagingCommand = scripts.some(
7812
+ (script) => typeof script === "string" && /\b(?:electron-builder|electron-packager|tauri\s+build)\b/i.test(script)
7813
+ );
7814
+ const hasApplicationMetadata = isNonEmptyString(parsed.productName) || isNonEmptyString(parsed.main) || build != null && isNonEmptyString(build.appId) || findTauriApplicationMetadata(repoPath, manifest, repoFiles);
7815
+ return hasDesktopRuntime && hasPackagingCommand && hasApplicationMetadata;
7816
+ }
7817
+ function findTauriApplicationMetadata(repoPath, manifest, repoFiles) {
7818
+ const manifestDirectory = dirname(manifest);
7819
+ const prefix = manifestDirectory === "." ? "" : `${manifestDirectory}/`;
7820
+ const candidates = [`${prefix}src-tauri/tauri.conf.json`, `${prefix}tauri.conf.json`];
7821
+ return candidates.some((file) => {
7822
+ if (!repoFiles.includes(file)) return false;
7823
+ const fullPath = join(repoPath, file);
7824
+ if (!isRegularFile(fullPath)) return false;
7825
+ const config = parseJsonObject(readFileSync(fullPath, "utf8"));
7826
+ const packageMetadata = isRecord(config?.package) ? config.package : null;
7827
+ return isNonEmptyString(config?.productName) || isNonEmptyString(config?.identifier) || packageMetadata != null && isNonEmptyString(packageMetadata.productName);
7828
+ });
7829
+ }
7830
+ function isNonEmptyString(value) {
7831
+ return typeof value === "string" && value.trim().length > 0;
7832
+ }
6984
7833
  function isImplementationFile(file) {
6985
7834
  return /(^|\/)(src|app|lib|packages|cmd|include|source|Sources)\//.test(file) && /\.(ts|tsx|js|jsx|py|go|rs|java|rb|cpp|cc|cxx|c|h|hpp|kt|swift|php)$/.test(file);
6986
7835
  }
@@ -7004,8 +7853,8 @@ function isSecurityPolicyOnlyAuditFile(file) {
7004
7853
  function isFreshnessRatableAuditFile(file) {
7005
7854
  return isAuditFile(file) && !isSecurityPolicyOnlyAuditFile(file);
7006
7855
  }
7007
- function isIgnoredScanFile(file) {
7008
- return /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|README\.md|CHANGELOG\.md)$/.test(file) || isEnvTemplatePath(file);
7856
+ function isIgnoredScanFile(file, useV47Detectors = false) {
7857
+ return /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|README\.md|CHANGELOG\.md)$/.test(file) || isEnvTemplatePath(file, useV47Detectors);
7009
7858
  }
7010
7859
  function findRootPackageJson(repoFiles) {
7011
7860
  return repoFiles.includes("package.json") ? "package.json" : null;
@@ -7022,46 +7871,315 @@ function fileContains(repoPath, file, pattern) {
7022
7871
  if (!isRegularFile(fullPath)) return false;
7023
7872
  return pattern.test(readFileSync(fullPath, "utf8"));
7024
7873
  }
7025
- function fileContainsCommittedSecret(repoPath, file) {
7874
+ function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
7026
7875
  const fullPath = join(repoPath, file);
7027
- if (!isRegularFile(fullPath)) return false;
7028
- return containsRealSecret(readFileSync(fullPath, "utf8"));
7876
+ if (!isRegularFile(fullPath)) return null;
7877
+ const contents = readFileSync(fullPath, "utf8");
7878
+ const isTestCredentialConfiguration = useV39Detectors && isLikelyTestCredentialConfiguration(file, contents);
7879
+ const secretScanContents = prepareCredentialScanContents(
7880
+ contents,
7881
+ file,
7882
+ useV36Detectors,
7883
+ useV39Detectors,
7884
+ useV47Detectors
7885
+ );
7886
+ const secret = findRealSecretAssignment(secretScanContents, /* @__PURE__ */ new Set(), {
7887
+ allowCredentialNamedDigest: useV36Detectors
7888
+ });
7889
+ if (secret) return secret;
7890
+ if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
7891
+ const weakCredential = findWeakExplicitEnvCredential(secretScanContents);
7892
+ if (weakCredential) return weakCredential;
7893
+ }
7894
+ return useV36Detectors && !isTestCredentialConfiguration ? findDefaultAdministrativeCredential(secretScanContents) : null;
7895
+ }
7896
+ function prepareCredentialScanContents(contents, file, useV36Detectors, useV39Detectors, useV47Detectors = false) {
7897
+ const withoutXamlEventNames = useV36Detectors && /\.xaml$/i.test(file) ? contents.replace(/\bTokenItem(?:Added|Removed)(?=\s*=)/gi, "EventHandler") : contents;
7898
+ const withoutDocumentationExamples = useV39Detectors ? stripCommentAndDocumentationExamples(withoutXamlEventNames) : withoutXamlEventNames;
7899
+ return useV47Detectors ? stripV47PublicIdentifiersAndNonCredentialValues(withoutDocumentationExamples, file) : withoutDocumentationExamples;
7900
+ }
7901
+ function stripV47PublicIdentifiersAndNonCredentialValues(contents, file) {
7902
+ const lines = contents.split(/\r?\n/);
7903
+ return lines.map((line, index) => {
7904
+ let sanitized = line.replace(
7905
+ /^(\s*(?:export\s+)?(?:PUBLIC_)?ALGOLIA_(?:PUBLIC_)?API_KEY\s*[:=]\s*)[^\s#]+/i,
7906
+ '$1""'
7907
+ );
7908
+ if (isPublicDocumentationSearchKeyContext(lines, index, file)) {
7909
+ sanitized = sanitized.replace(/(\bapiKey\s*[:=]\s*)(['"])[^'"]+\2/gi, "$1$2$2");
7910
+ }
7911
+ sanitized = sanitized.replace(
7912
+ /(\b[A-Za-z_][A-Za-z0-9_]*(?:token|api[_-]?key|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*)(['"]?)0x[0-9a-f]{40}\2/gi,
7913
+ '$1""'
7914
+ );
7915
+ sanitized = sanitized.replace(
7916
+ /(\b[A-Za-z_][A-Za-z0-9_]*(?:token|api[_-]?key|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*)(['"])([a-z][a-z0-9]*(?:_[a-z0-9]+){2,})\2/gi,
7917
+ (assignment, prefix, quote, value) => /\b(?:cost|input|output|context|model|limit|price|usage|audio)\b/i.test(
7918
+ value.replaceAll("_", " ")
7919
+ ) ? `${prefix}${quote}${quote}` : assignment
7920
+ );
7921
+ return sanitized;
7922
+ }).join("\n");
7029
7923
  }
7030
- function containsRealSecret(contents) {
7031
- SECRET_ASSIGNMENT_PATTERN.lastIndex = 0;
7032
- for (const match of contents.matchAll(SECRET_ASSIGNMENT_PATTERN)) {
7033
- const rawValue = match[1];
7034
- if (!rawValue) continue;
7035
- const value = rawValue.replace(/[,;)]$/, "");
7036
- if (isPlaceholderSecretValue(value)) continue;
7037
- if (looksLikeSecretValue(value)) return true;
7924
+ function isPublicDocumentationSearchKeyContext(lines, lineIndex, file) {
7925
+ const windowStart = Math.max(0, lineIndex - 6);
7926
+ const preceding = lines.slice(windowStart, lineIndex + 1);
7927
+ for (let index = preceding.length - 1; index >= 0; index -= 1) {
7928
+ const candidate = preceding[index] ?? "";
7929
+ if (/}/.test(candidate) && index < preceding.length - 1) return false;
7930
+ if (/\bdocsearch(?:Options)?\b/i.test(candidate)) return true;
7931
+ if (/\balgolia\b/i.test(candidate) && /(?:^|\/)(?:website|docs?)(?:\/|$)|(?:site[-_]?config|\.dumirc|docusaurus)/i.test(file)) {
7932
+ return true;
7933
+ }
7038
7934
  }
7039
7935
  return false;
7040
7936
  }
7041
- function collectHistorySecretEvidence(repoPath) {
7042
- const historyEntries = readCredentialHistoryEntries(repoPath);
7937
+ function stripCommentAndDocumentationExamples(contents) {
7938
+ let blockCommentEnd = null;
7939
+ return contents.split(/\r?\n/).map((line) => {
7940
+ const trimmed = line.trimStart();
7941
+ if (blockCommentEnd) {
7942
+ if (trimmed.includes(blockCommentEnd)) blockCommentEnd = null;
7943
+ return "";
7944
+ }
7945
+ if (trimmed.startsWith("/*")) {
7946
+ if (!trimmed.includes("*/")) blockCommentEnd = "*/";
7947
+ return "";
7948
+ }
7949
+ if (trimmed.startsWith("<!--")) {
7950
+ if (!trimmed.includes("-->")) blockCommentEnd = "-->";
7951
+ return "";
7952
+ }
7953
+ if (/^(?:\/\/|#|\*)/.test(trimmed)) return "";
7954
+ if (/\.description\s*\(/i.test(line) && /api[_-]?key|token|password|secret/i.test(line)) {
7955
+ return "";
7956
+ }
7957
+ return line;
7958
+ }).join("\n");
7959
+ }
7960
+ function isLikelyTestCredentialConfiguration(file, contents) {
7961
+ if (!/\.(?:ya?ml|json|toml|ini|conf)$/i.test(file) && !/(?:^|\/)docker-compose/i.test(file)) {
7962
+ return false;
7963
+ }
7964
+ const testMarkers = contents.match(/(?:^|[^A-Za-z])(?:tests?|testing|e2e|appium|fixture)(?:[^A-Za-z]|$)/gi)?.length ?? 0;
7965
+ return testMarkers >= 2;
7966
+ }
7967
+ function hasTemplateOnlyEnvContent(repoPath, file) {
7968
+ const fullPath = join(repoPath, file);
7969
+ if (!isRegularFile(fullPath)) return false;
7970
+ const substantiveLines = readFileSync(fullPath, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*#/.test(line));
7971
+ if (substantiveLines.length === 0) return true;
7972
+ const assignments = substantiveLines.map(
7973
+ (line) => /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.*?)\s*$/.exec(line)?.[1] ?? null
7974
+ );
7975
+ if (assignments.some((value) => value === null)) return false;
7976
+ return assignments.every((raw) => {
7977
+ if (raw === null) return false;
7978
+ const value = raw.replace(/^['"]|['"]$/g, "").trim();
7979
+ return value.length === 0 || isPlaceholderSecretValue(value);
7980
+ });
7981
+ }
7982
+ function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
7983
+ const fullPath = join(repoPath, file);
7984
+ if (!isRegularFile(fullPath)) return [];
7985
+ const contents = readFileSync(fullPath, "utf8");
7986
+ const scanContents = prepareCredentialScanContents(
7987
+ contents,
7988
+ file,
7989
+ useV36Detectors,
7990
+ useV39Detectors,
7991
+ useV47Detectors
7992
+ );
7993
+ const fingerprints = findRealSecretAssignments(
7994
+ scanContents,
7995
+ /* @__PURE__ */ new Set(),
7996
+ Number.POSITIVE_INFINITY,
7997
+ { allowCredentialNamedDigest: useV36Detectors }
7998
+ ).map(({ valueFingerprint }) => valueFingerprint);
7999
+ if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
8000
+ const weakCredential = findWeakExplicitEnvCredential(scanContents);
8001
+ if (weakCredential) fingerprints.push(weakCredential.valueFingerprint);
8002
+ }
8003
+ return [...new Set(fingerprints)];
8004
+ }
8005
+ function findRealSecretAssignment(contents, excludedValueFingerprints = /* @__PURE__ */ new Set(), options = {}) {
8006
+ return findRealSecretAssignments(contents, excludedValueFingerprints, 1, options)[0] ?? null;
8007
+ }
8008
+ function findRealSecretAssignments(contents, excludedValueFingerprints = /* @__PURE__ */ new Set(), maximumMatches = Number.POSITIVE_INFINITY, options = {}) {
8009
+ const matches = [];
8010
+ SECRET_IDENTIFIER_KEYWORD_PATTERN.lastIndex = 0;
8011
+ let match = SECRET_IDENTIFIER_KEYWORD_PATTERN.exec(contents);
8012
+ let line = 1;
8013
+ let lineCursor = 0;
8014
+ while (match !== null) {
8015
+ const keyword = match[0];
8016
+ const keywordStart = match.index;
8017
+ while (lineCursor < keywordStart) {
8018
+ if (contents.charCodeAt(lineCursor) === 10) line += 1;
8019
+ lineCursor += 1;
8020
+ }
8021
+ let cursor = keywordStart + keyword.length;
8022
+ while (cursor < contents.length && isSecretIdentifierCharacter(contents.charCodeAt(cursor))) {
8023
+ cursor += 1;
8024
+ }
8025
+ const identifierEnd = cursor;
8026
+ let identifierStart = keywordStart;
8027
+ while (identifierStart > 0 && isSecretIdentifierCharacter(contents.charCodeAt(identifierStart - 1))) {
8028
+ identifierStart -= 1;
8029
+ }
8030
+ const identifier = contents.slice(identifierStart, identifierEnd);
8031
+ let nextSearchIndex = identifierEnd;
8032
+ while (cursor < contents.length && SINGLE_WHITESPACE_PATTERN.test(contents[cursor] ?? "")) {
8033
+ cursor += 1;
8034
+ }
8035
+ const delimiter = contents[cursor];
8036
+ if (delimiter === ":" || delimiter === "=") {
8037
+ cursor += 1;
8038
+ while (cursor < contents.length && SINGLE_WHITESPACE_PATTERN.test(contents[cursor] ?? "")) {
8039
+ cursor += 1;
8040
+ }
8041
+ if (contents[cursor] === "'" || contents[cursor] === '"') cursor += 1;
8042
+ const valueStart = cursor;
8043
+ while (cursor < contents.length) {
8044
+ const character = contents[cursor] ?? "";
8045
+ if (character === "'" || character === '"' || SINGLE_WHITESPACE_PATTERN.test(character)) {
8046
+ break;
8047
+ }
8048
+ cursor += 1;
8049
+ }
8050
+ if (cursor > valueStart) {
8051
+ const value = contents.slice(valueStart, cursor).replace(/[,;)]$/, "");
8052
+ nextSearchIndex = cursor;
8053
+ if (!isPlaceholderSecretValue(value) && (looksLikeSecretValue(value) || options.allowCredentialNamedDigest === true && isLikelyDigestOrHash(value) && isExplicitCredentialIdentifier(identifier))) {
8054
+ const valueFingerprint = createHash2("sha256").update(value).digest("hex");
8055
+ const characterClasses = [
8056
+ /[a-z]/.test(value) ? "lower" : "",
8057
+ /[A-Z]/.test(value) ? "upper" : "",
8058
+ /\d/.test(value) ? "digit" : "",
8059
+ /[^A-Za-z0-9]/.test(value) ? "symbol" : ""
8060
+ ].filter(Boolean).join("+");
8061
+ if (!excludedValueFingerprints.has(valueFingerprint)) {
8062
+ matches.push({
8063
+ identifier,
8064
+ line,
8065
+ valueLength: value.length,
8066
+ characterClasses: characterClasses || "other",
8067
+ valueFingerprint
8068
+ });
8069
+ if (matches.length >= maximumMatches) return matches;
8070
+ }
8071
+ }
8072
+ }
8073
+ }
8074
+ SECRET_IDENTIFIER_KEYWORD_PATTERN.lastIndex = nextSearchIndex;
8075
+ match = SECRET_IDENTIFIER_KEYWORD_PATTERN.exec(contents);
8076
+ }
8077
+ return matches;
8078
+ }
8079
+ function isExplicitCredentialIdentifier(identifier) {
8080
+ return /^(?:github|gitlab|api|access)[_-]?(?:token|key)$/i.test(identifier);
8081
+ }
8082
+ function findWeakExplicitEnvCredential(contents) {
8083
+ const lines = contents.split(/\r?\n/);
8084
+ for (const [index, line] of lines.entries()) {
8085
+ if (/^\s*#/.test(line)) continue;
8086
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*(?:PASSWORD|PASSWD|PWD|SECRET(?:_KEY)?|API_KEY|TOKEN))\s*=\s*['"]?([^\s#'"]+)['"]?/i.exec(
8087
+ line
8088
+ );
8089
+ const identifier = match?.[1];
8090
+ const value = match?.[2];
8091
+ if (!identifier || !value || isPlaceholderSecretValue(value)) continue;
8092
+ const minimumLength = /(?:API_KEY|TOKEN)$/i.test(identifier) ? 12 : 6;
8093
+ if (value.length < minimumLength) continue;
8094
+ const characterClasses = [
8095
+ /[a-z]/.test(value) ? "lower" : "",
8096
+ /[A-Z]/.test(value) ? "upper" : "",
8097
+ /\d/.test(value) ? "digit" : "",
8098
+ /[^A-Za-z0-9]/.test(value) ? "symbol" : ""
8099
+ ].filter(Boolean).join("+");
8100
+ return {
8101
+ identifier,
8102
+ line: index + 1,
8103
+ valueLength: value.length,
8104
+ characterClasses: characterClasses || "other",
8105
+ valueFingerprint: createHash2("sha256").update(value).digest("hex")
8106
+ };
8107
+ }
8108
+ return null;
8109
+ }
8110
+ function findDefaultAdministrativeCredential(contents) {
8111
+ const lines = contents.split(/\r?\n/);
8112
+ for (const [index, line] of lines.entries()) {
8113
+ const match = /\b([A-Za-z0-9_]*(?:ADMIN|ROOT)[A-Za-z0-9_]*(?:PASSWORD|PASSWD|PWD))\s*[:=]\s*["']?(admin|root|password)["']?(?:\s|$)/i.exec(
8114
+ line
8115
+ );
8116
+ const identifier = match?.[1];
8117
+ const value = match?.[2];
8118
+ if (!identifier || !value) continue;
8119
+ return {
8120
+ identifier,
8121
+ line: index + 1,
8122
+ valueLength: value.length,
8123
+ characterClasses: "lower",
8124
+ valueFingerprint: createHash2("sha256").update(value).digest("hex"),
8125
+ kind: "default_admin"
8126
+ };
8127
+ }
8128
+ return null;
8129
+ }
8130
+ function secretEvidenceLabel(prefix, match) {
8131
+ return `${prefix} (value redacted; length ${match.valueLength}; classes ${match.characterClasses})`;
8132
+ }
8133
+ function isSecretIdentifierCharacter(characterCode) {
8134
+ return characterCode >= 48 && characterCode <= 57 || characterCode >= 65 && characterCode <= 90 || characterCode === 95 || characterCode >= 97 && characterCode <= 122;
8135
+ }
8136
+ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFingerprintsByPath, authoredProductionOnly = false, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
8137
+ const historyEntries = readCredentialHistoryEntries(repoPath, useV36Detectors);
8138
+ const head = excludeHead ? readGitHead(repoPath) : null;
7043
8139
  let scannedFiles = 0;
7044
8140
  let envPathEvidence = null;
7045
8141
  for (const entry of historyEntries) {
8142
+ if (head && entry.commit === head) continue;
8143
+ if (authoredProductionOnly && !(useV47Detectors ? isV47AuthoredProductionPath(entry.path) : useV39Detectors ? isV39AuthoredProductionPath(entry.path) : isAuthoredProductionPath(entry.path))) {
8144
+ continue;
8145
+ }
7046
8146
  if (scannedFiles >= HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT) {
7047
8147
  return { evidence: null, envPathEvidence, truncated: true };
7048
8148
  }
7049
8149
  scannedFiles += 1;
7050
8150
  const contents = readGitBlob(repoPath, entry.commit, entry.path);
7051
8151
  if (!contents) continue;
7052
- if (containsRealSecret(contents)) {
8152
+ const scanContents = prepareCredentialScanContents(
8153
+ contents,
8154
+ entry.path,
8155
+ useV36Detectors,
8156
+ useV39Detectors,
8157
+ useV47Detectors
8158
+ );
8159
+ const currentFingerprints = currentSecretFingerprintsByPath.get(entry.path) ?? /* @__PURE__ */ new Set();
8160
+ let secretMatch = findRealSecretAssignment(scanContents, currentFingerprints, {
8161
+ allowCredentialNamedDigest: useV36Detectors
8162
+ });
8163
+ if (!secretMatch && useV39Detectors && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
8164
+ const weakCredential = findWeakExplicitEnvCredential(scanContents);
8165
+ if (weakCredential && !currentFingerprints.has(weakCredential.valueFingerprint)) {
8166
+ secretMatch = weakCredential;
8167
+ }
8168
+ }
8169
+ if (secretMatch) {
7053
8170
  return {
7054
8171
  evidence: {
7055
8172
  kind: "secret_scan",
7056
- label: "Committed secret in recent git history",
8173
+ label: secretEvidenceLabel("Committed secret in recent git history", secretMatch),
7057
8174
  path: entry.path,
8175
+ line: secretMatch.line,
7058
8176
  contentHash: entry.commit
7059
8177
  },
7060
8178
  envPathEvidence,
7061
8179
  truncated: false
7062
8180
  };
7063
8181
  }
7064
- if (!envPathEvidence && isEnvHistoryPath(entry.path)) {
8182
+ if (!envPathEvidence && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
7065
8183
  envPathEvidence = {
7066
8184
  kind: "secret_scan",
7067
8185
  label: ".env file (not a template) tracked in git history; no confirmed secret value found",
@@ -7072,7 +8190,7 @@ function collectHistorySecretEvidence(repoPath) {
7072
8190
  }
7073
8191
  return { evidence: null, envPathEvidence, truncated: false };
7074
8192
  }
7075
- function readCredentialHistoryEntries(repoPath) {
8193
+ function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
7076
8194
  const entries = [];
7077
8195
  const seen = /* @__PURE__ */ new Set();
7078
8196
  const appendEntry = (entry) => {
@@ -7113,18 +8231,18 @@ function readCredentialHistoryEntries(repoPath) {
7113
8231
  }
7114
8232
  const path = line.trim();
7115
8233
  if (!currentCommit || !path) continue;
7116
- if (!isCredentialHistoryPath(path)) continue;
8234
+ if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
7117
8235
  appendEntry({ commit: currentCommit, path });
7118
8236
  }
7119
8237
  } catch {
7120
8238
  }
7121
- for (const entry of readDeletedCredentialHistoryEntries(repoPath)) {
8239
+ for (const entry of readDeletedCredentialHistoryEntries(repoPath, useV36Detectors)) {
7122
8240
  appendEntry(entry);
7123
8241
  }
7124
8242
  return entries;
7125
8243
  }
7126
- function readDeletedCredentialHistoryEntries(repoPath) {
7127
- const deletedPaths = readDeletedCredentialHistoryPaths(repoPath);
8244
+ function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false) {
8245
+ const deletedPaths = readDeletedCredentialHistoryPaths(repoPath, useV36Detectors);
7128
8246
  const entries = [];
7129
8247
  const seen = /* @__PURE__ */ new Set();
7130
8248
  for (const path of deletedPaths) {
@@ -7137,7 +8255,7 @@ function readDeletedCredentialHistoryEntries(repoPath) {
7137
8255
  }
7138
8256
  return entries;
7139
8257
  }
7140
- function readDeletedCredentialHistoryPaths(repoPath) {
8258
+ function readDeletedCredentialHistoryPaths(repoPath, useV36Detectors = false) {
7141
8259
  try {
7142
8260
  const output = execFileSync(
7143
8261
  "git",
@@ -7154,7 +8272,7 @@ function readDeletedCredentialHistoryPaths(repoPath) {
7154
8272
  if (!line.startsWith("D ")) continue;
7155
8273
  const path = line.slice(2).trim();
7156
8274
  if (!path || seen.has(path)) continue;
7157
- if (!isCredentialHistoryPath(path)) continue;
8275
+ if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
7158
8276
  seen.add(path);
7159
8277
  paths.push(path);
7160
8278
  }
@@ -7179,7 +8297,7 @@ function readHistoryCommitsForPath(repoPath, path) {
7179
8297
  return [];
7180
8298
  }
7181
8299
  }
7182
- function isCredentialHistoryPath(path) {
8300
+ function isCredentialHistoryPath(path, _useV36Detectors = false) {
7183
8301
  if (isHardExcludedPath(path)) return false;
7184
8302
  const lowerPath = path.toLowerCase();
7185
8303
  const basename3 = lowerPath.split("/").at(-1) ?? lowerPath;
@@ -7188,10 +8306,11 @@ function isCredentialHistoryPath(path) {
7188
8306
  }
7189
8307
  return lowerPath.includes("secret") || lowerPath.includes("credential") || basename3.endsWith(".pem") || basename3.endsWith(".key") || basename3 === "id_rsa" || basename3.endsWith(".p12") || basename3.endsWith(".pfx");
7190
8308
  }
7191
- function isEnvHistoryPath(path) {
7192
- if (isEnvTemplatePath(path)) return false;
8309
+ function isEnvHistoryPath(path, useV36Detectors = false, useV47Detectors = false) {
8310
+ if (isEnvTemplatePath(path, useV47Detectors)) return false;
8311
+ if (useV47Detectors && !isV47AuthoredProductionPath(path)) return false;
7193
8312
  const basename3 = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
7194
- return /\.env(?:\.|$)/.test(basename3);
8313
+ return (useV36Detectors ? /^\.env(?:\.|$)/ : /\.env(?:\.|$)/).test(basename3);
7195
8314
  }
7196
8315
  function readGitBlob(repoPath, commit, path) {
7197
8316
  try {
@@ -7229,7 +8348,7 @@ function isFillerRunSecretValue(value) {
7229
8348
  function isSyntheticFixtureSecretValue(value) {
7230
8349
  return value.includes("abcdefghijklmnopqrstuvwxyz") || value.includes("0123456789abcdefghijklmnopqrstuvwxyz") || value.includes("abcdefghijklmnopqrstuvwxyz0123456789") || value.includes("abcdefghijklmnopqrst");
7231
8350
  }
7232
- function hasSuspiciousDependencies(repoPath, manifest) {
8351
+ function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false) {
7233
8352
  if (!manifest.endsWith("package.json")) return false;
7234
8353
  const fullPath = join(repoPath, manifest);
7235
8354
  if (!isRegularFile(fullPath)) return false;
@@ -7238,9 +8357,15 @@ function hasSuspiciousDependencies(repoPath, manifest) {
7238
8357
  const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
7239
8358
  const devDependencies = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
7240
8359
  const names = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
7241
- return names.some(
8360
+ if (names.some(
7242
8361
  (name) => /^(todo|fake|placeholder|example)-|does-not-exist| hallucinated/i.test(name)
7243
- );
8362
+ )) {
8363
+ return true;
8364
+ }
8365
+ if (!useV47Detectors || typeof parsed.name !== "string") return false;
8366
+ const placeholderIdentity = /^(?:example|sample|placeholder|todo|fake)(?:-|$)/i.test(parsed.name);
8367
+ const placeholderMetadata = typeof parsed.author === "string" && /^(?:your name|example|placeholder)$/i.test(parsed.author) || typeof parsed.description === "string" && /^(?:package description|example|placeholder|todo)$/i.test(parsed.description);
8368
+ return placeholderIdentity && placeholderMetadata;
7244
8369
  }
7245
8370
  function readRecentCommits(repoPath) {
7246
8371
  try {
@@ -7822,7 +8947,12 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
7822
8947
  );
7823
8948
  consumedSignals.push(...summaries);
7824
8949
  const finalScore = adjustment === 0 ? scored.score : adjustedScore;
7825
- const finalStatus = adjustment === 0 ? scored.status : statusForScore(finalScore);
8950
+ const finalStatus = statusAfterInputAdjustment(
8951
+ scored.status,
8952
+ scored.score,
8953
+ adjustment,
8954
+ parsedInput.rubricVersion
8955
+ );
7826
8956
  return {
7827
8957
  id,
7828
8958
  title: rubricCriterion.title,
@@ -7919,6 +9049,31 @@ function statusForScore(score) {
7919
9049
  if (score > 0) return "critical";
7920
9050
  return "critical";
7921
9051
  }
9052
+ function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
9053
+ if (adjustment === 0) return nativeStatus;
9054
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V92 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15) {
9055
+ return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
9056
+ }
9057
+ const statusFloor = {
9058
+ verified: 3.5,
9059
+ info: 2.5,
9060
+ warning: 1.5,
9061
+ critical: 0
9062
+ };
9063
+ const severity = {
9064
+ verified: 0,
9065
+ info: 1,
9066
+ warning: 2,
9067
+ critical: 3
9068
+ };
9069
+ const floor = statusFloor[nativeStatus];
9070
+ const nativeSeverity = severity[nativeStatus];
9071
+ if (floor === void 0 || nativeSeverity === void 0) return nativeStatus;
9072
+ const adjustedSemanticStatus = statusForScore(
9073
+ roundScore(Math.max(0, Math.max(nativeScore, floor) - adjustment))
9074
+ );
9075
+ return (severity[adjustedSemanticStatus] ?? nativeSeverity) > nativeSeverity ? adjustedSemanticStatus : nativeStatus;
9076
+ }
7922
9077
  function mergeSignalsByCriterion(signals) {
7923
9078
  const merged = /* @__PURE__ */ new Map();
7924
9079
  for (const signal of signals) {
@@ -7940,7 +9095,7 @@ function mergeSignalsByCriterion(signals) {
7940
9095
  return merged;
7941
9096
  }
7942
9097
  function usesMetricScoring(rubricVersion) {
7943
- return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
9098
+ return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
7944
9099
  }
7945
9100
  function unmeasuredStatus(rubricVersion) {
7946
9101
  return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
@@ -7982,12 +9137,25 @@ function scoreCriterion(signal, rubricVersion) {
7982
9137
  if (usesMetricScoring(rubricVersion)) {
7983
9138
  const measuredMetrics = metrics.length > 0 ? metrics : fallbackMetricsForEvidence(evidence, findings);
7984
9139
  const score = capScoreForFindings(scoreMetrics(measuredMetrics), findings);
7985
- const status = statusForScoreAndFindings(score, findings);
9140
+ const status = statusForScoreAndFindings(
9141
+ signal.criterionId,
9142
+ score,
9143
+ findings,
9144
+ measuredMetrics,
9145
+ rubricVersion
9146
+ );
7986
9147
  return {
7987
9148
  score,
7988
9149
  status,
7989
9150
  evidence,
7990
- findings: ensureFindingsExplainStatus(signal.criterionId, score, status, findings, evidence),
9151
+ findings: ensureFindingsExplainStatus(
9152
+ signal.criterionId,
9153
+ score,
9154
+ status,
9155
+ findings,
9156
+ evidence,
9157
+ rubricVersion
9158
+ ),
7991
9159
  metrics: measuredMetrics
7992
9160
  };
7993
9161
  }
@@ -8056,23 +9224,251 @@ function capScoreForFindings(score, findings) {
8056
9224
  if (findings.some((finding) => finding.severity === "info")) return Math.min(score, 3.4);
8057
9225
  return score;
8058
9226
  }
8059
- function statusForScoreAndFindings(score, findings) {
9227
+ function statusForScoreAndFindings(criterionId, score, findings, metrics, rubricVersion) {
8060
9228
  if (findings.some((finding) => finding.severity === "critical")) return "critical";
9229
+ if (rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) {
9230
+ const calibrated = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? calibratedCriterionStateV11(criterionId, metrics) : rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 ? calibratedCriterionStateV9(criterionId, metrics) : calibratedCriterionStateV8(criterionId, metrics);
9231
+ if (calibrated && calibrated !== "verified") return calibrated;
9232
+ if (calibrated === "verified" && !findings.some((finding) => finding.severity === "warning")) {
9233
+ return "verified";
9234
+ }
9235
+ }
8061
9236
  if (findings.some((finding) => finding.severity === "warning")) return "warning";
8062
9237
  if (score >= 3.5 && findings.length === 0) return "verified";
8063
9238
  if (score >= 2.5) return "info";
8064
9239
  if (score >= 1.5) return "warning";
8065
9240
  return "critical";
8066
9241
  }
8067
- function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence) {
8068
- if (findings.length > 0) return [...findings];
9242
+ function calibratedCriterionStateV8(criterionId, metrics) {
9243
+ const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
9244
+ if (values.size !== metrics.length) return void 0;
9245
+ const value = (name) => values.get(name);
9246
+ const positive = (name) => (value(name) ?? 0) > 0;
9247
+ if (criterionId === "A1") {
9248
+ if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
9249
+ return void 0;
9250
+ }
9251
+ if (!positive("non_hollow_test_share")) return "critical";
9252
+ return positive("verification_script_ratio") ? "verified" : "warning";
9253
+ }
9254
+ if (criterionId === "A2") {
9255
+ if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
9256
+ if (!positive("secret_cleanliness")) return "critical";
9257
+ return (value("env_handling_depth") ?? 0) >= 2 ? "verified" : "warning";
9258
+ }
9259
+ if (criterionId === "A3") {
9260
+ if (!values.has("observability_depth") || !values.has("rollback_safety_depth")) {
9261
+ return void 0;
9262
+ }
9263
+ const coveredSurfaces = Number(positive("observability_depth")) + Number(positive("rollback_safety_depth"));
9264
+ return coveredSurfaces === 2 ? "verified" : coveredSurfaces === 1 ? "warning" : "critical";
9265
+ }
9266
+ if (criterionId === "A4") {
9267
+ if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
9268
+ return void 0;
9269
+ }
9270
+ const dependencyCountSanity = value("dependency_count_sanity");
9271
+ if (dependencyCountSanity !== void 0) {
9272
+ if (dependencyCountSanity <= 0) return "critical";
9273
+ if (dependencyCountSanity < 0.9) return "warning";
9274
+ }
9275
+ const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
9276
+ const hasLockfile = positive("lockfile_coverage");
9277
+ const hasAutomation = positive("dependency_automation_ratio");
9278
+ if (!hasVersionControl && !hasLockfile && !hasAutomation) return "critical";
9279
+ return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
9280
+ }
9281
+ if (criterionId === "B2") {
9282
+ if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
9283
+ return positive("pr_trace_primitives") && positive("pr_merge_ratio") ? "verified" : "warning";
9284
+ }
9285
+ if (criterionId === "B3") {
9286
+ if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
9287
+ const coveredSurfaces = Number(positive("ci_script_depth")) + Number(positive("default_branch_ci_depth"));
9288
+ return coveredSurfaces === 2 ? "verified" : coveredSurfaces === 1 ? "warning" : "critical";
9289
+ }
9290
+ if (criterionId === "B4") {
9291
+ if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
9292
+ return void 0;
9293
+ }
9294
+ if (!positive("audit_artifact_depth")) return "critical";
9295
+ return positive("audit_freshness_depth") ? "verified" : "warning";
9296
+ }
9297
+ if (criterionId === "B6") {
9298
+ if (!values.has("human_gate_documented") || !values.has("fail_closed_privilege_check")) {
9299
+ return void 0;
9300
+ }
9301
+ return positive("human_gate_documented") || positive("fail_closed_privilege_check") ? "verified" : "critical";
9302
+ }
9303
+ return void 0;
9304
+ }
9305
+ function calibratedCriterionStateV9(criterionId, metrics) {
9306
+ const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
9307
+ if (values.size !== metrics.length) return void 0;
9308
+ const value = (name) => values.get(name);
9309
+ const positive = (name) => (value(name) ?? 0) > 0;
9310
+ if (criterionId === "A1") {
9311
+ if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
9312
+ return void 0;
9313
+ }
9314
+ const nonHollowTests = value("non_hollow_test_share") ?? 0;
9315
+ if (nonHollowTests === 0) return "critical";
9316
+ return positive("verification_script_ratio") && nonHollowTests >= 3 ? "verified" : "warning";
9317
+ }
9318
+ if (criterionId === "A2") {
9319
+ if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
9320
+ if (!positive("secret_cleanliness")) return "critical";
9321
+ return (value("env_handling_depth") ?? 0) >= 2 ? "verified" : "warning";
9322
+ }
9323
+ if (criterionId === "A3") {
9324
+ if (!values.has("prod_readiness_primitives") || !values.has("prod_workflow_depth") || !values.has("observability_depth") || !values.has("rollback_safety_depth")) {
9325
+ return void 0;
9326
+ }
9327
+ const observability = value("observability_depth") ?? 0;
9328
+ const rollback = value("rollback_safety_depth") ?? 0;
9329
+ if (observability > 0 && rollback > 0) {
9330
+ return (value("prod_readiness_primitives") ?? 0) >= 4 && observability >= 2 && rollback >= 2 ? "verified" : "warning";
9331
+ }
9332
+ if (observability > 0 || rollback > 0) return "warning";
9333
+ return (value("prod_readiness_primitives") ?? 0) >= 2 && (value("prod_workflow_depth") ?? 0) >= 8 ? "warning" : "critical";
9334
+ }
9335
+ if (criterionId === "A4") {
9336
+ if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
9337
+ return void 0;
9338
+ }
9339
+ const dependencyCountSanity = value("dependency_count_sanity");
9340
+ if (dependencyCountSanity !== void 0) {
9341
+ if (dependencyCountSanity <= 0.25) return "critical";
9342
+ if (dependencyCountSanity < 0.5) return "warning";
9343
+ }
9344
+ const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
9345
+ const hasLockfile = positive("lockfile_coverage");
9346
+ const hasAutomation = positive("dependency_automation_ratio");
9347
+ if (!hasVersionControl && !hasLockfile && !hasAutomation) return "warning";
9348
+ return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
9349
+ }
9350
+ if (criterionId === "B2") {
9351
+ if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
9352
+ const primitives = value("pr_trace_primitives") ?? 0;
9353
+ const mergedOutcomes = value("pr_merge_ratio") ?? 0;
9354
+ if (mergedOutcomes === 0) return primitives <= 2 ? "critical" : "warning";
9355
+ return mergedOutcomes >= 4 ? "verified" : "warning";
9356
+ }
9357
+ if (criterionId === "B3") {
9358
+ if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
9359
+ const scriptDepth = value("ci_script_depth") ?? 0;
9360
+ const branchDepth = value("default_branch_ci_depth") ?? 0;
9361
+ if (scriptDepth === 0 && branchDepth === 0) return "critical";
9362
+ return scriptDepth >= 2 && branchDepth > 0 ? "verified" : "warning";
9363
+ }
9364
+ if (criterionId === "B4") {
9365
+ if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
9366
+ return void 0;
9367
+ }
9368
+ const artifacts = value("audit_artifact_depth") ?? 0;
9369
+ const freshness = value("audit_freshness_depth") ?? 0;
9370
+ if (artifacts === 0) return "critical";
9371
+ return artifacts >= 2 || freshness >= 2 ? "verified" : "warning";
9372
+ }
9373
+ if (criterionId === "B6") {
9374
+ const humanGate = value("human_gate_documented") ?? 0;
9375
+ const failClosed = value("fail_closed_privilege_check") ?? 0;
9376
+ if (humanGate > 0 || failClosed > 0) return "verified";
9377
+ if (positive("privilege_escalation_cleanliness") || positive("protected_path_review_gate")) {
9378
+ return "warning";
9379
+ }
9380
+ if (values.has("human_gate_documented") || values.has("fail_closed_privilege_check") || values.has("privilege_escalation_cleanliness") || values.has("protected_path_review_gate")) {
9381
+ return "critical";
9382
+ }
9383
+ }
9384
+ return void 0;
9385
+ }
9386
+ function calibratedCriterionStateV11(criterionId, metrics) {
9387
+ const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
9388
+ if (values.size !== metrics.length) return void 0;
9389
+ const value = (name) => values.get(name);
9390
+ const positive = (name) => (value(name) ?? 0) > 0;
9391
+ if (criterionId === "A1") {
9392
+ if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
9393
+ return void 0;
9394
+ }
9395
+ if (!positive("non_hollow_test_share")) return "critical";
9396
+ return positive("verification_script_ratio") ? "verified" : "warning";
9397
+ }
9398
+ if (criterionId === "A2") {
9399
+ if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
9400
+ if (!positive("secret_cleanliness")) return "critical";
9401
+ return positive("env_handling_depth") ? "verified" : "warning";
9402
+ }
9403
+ if (criterionId === "A3") {
9404
+ if (!values.has("prod_readiness_primitives") || !values.has("prod_workflow_depth") || !values.has("observability_depth") || !values.has("rollback_safety_depth")) {
9405
+ return void 0;
9406
+ }
9407
+ const primitives = value("prod_readiness_primitives") ?? 0;
9408
+ const workflow = value("prod_workflow_depth") ?? 0;
9409
+ const observability = value("observability_depth") ?? 0;
9410
+ const rollback = value("rollback_safety_depth") ?? 0;
9411
+ if (observability > 0 && rollback > 0) return primitives >= 3 ? "verified" : "warning";
9412
+ return primitives > 0 && workflow > 0 ? "warning" : "critical";
9413
+ }
9414
+ if (criterionId === "A4") {
9415
+ if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
9416
+ return void 0;
9417
+ }
9418
+ const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
9419
+ const hasLockfile = positive("lockfile_coverage");
9420
+ const hasAutomation = positive("dependency_automation_ratio");
9421
+ if (!hasVersionControl && !hasLockfile && !hasAutomation) return "critical";
9422
+ return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
9423
+ }
9424
+ if (criterionId === "B2") {
9425
+ if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
9426
+ if (!positive("pr_trace_primitives")) return "critical";
9427
+ return positive("pr_merge_ratio") ? "verified" : "warning";
9428
+ }
9429
+ if (criterionId === "B3") {
9430
+ if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
9431
+ const covered = Number(positive("ci_script_depth")) + Number(positive("default_branch_ci_depth"));
9432
+ return covered === 2 ? "verified" : covered === 1 ? "warning" : "critical";
9433
+ }
9434
+ if (criterionId === "B4") {
9435
+ if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
9436
+ return void 0;
9437
+ }
9438
+ if (!positive("audit_artifact_depth")) return "critical";
9439
+ return positive("audit_freshness_depth") ? "verified" : "warning";
9440
+ }
9441
+ if (criterionId === "B6") {
9442
+ const humanGate = positive("human_gate_documented");
9443
+ const failClosed = positive("fail_closed_privilege_check");
9444
+ const cleanAndReviewed = positive("privilege_escalation_cleanliness") && positive("protected_path_review_gate");
9445
+ if (humanGate || failClosed || cleanAndReviewed) return "verified";
9446
+ if (positive("privilege_escalation_cleanliness") || positive("protected_path_review_gate")) {
9447
+ return "warning";
9448
+ }
9449
+ if (values.has("human_gate_documented") || values.has("fail_closed_privilege_check") || values.has("privilege_escalation_cleanliness") || values.has("protected_path_review_gate")) {
9450
+ return "critical";
9451
+ }
9452
+ }
9453
+ return void 0;
9454
+ }
9455
+ function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
9456
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V92 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && findings.length > 0) {
9457
+ return [...findings];
9458
+ }
8069
9459
  if (status !== "critical" && status !== "warning") return [...findings];
9460
+ if (findings.some(
9461
+ ({ severity }) => severity === "critical" || status === "warning" && severity === "warning"
9462
+ )) {
9463
+ return [...findings];
9464
+ }
8070
9465
  const anchor = evidence[0] ?? {
8071
9466
  kind: "repository",
8072
9467
  label: `${criterionId} metric-derived score`,
8073
9468
  path: "."
8074
9469
  };
8075
9470
  return [
9471
+ ...findings,
8076
9472
  {
8077
9473
  severity: status,
8078
9474
  summary: `${criterionId} metric-derived score is ${score.toFixed(1)}/4.0, in the ${status} band \u2014 no single finding drove this; it reflects the combined metric weighting below.`,
@@ -8098,7 +9494,8 @@ function scoreRepoWithPublicCejel(options) {
8098
9494
  productSlug: options.productSlug,
8099
9495
  productDisplayName: options.productDisplayName,
8100
9496
  repoPath: options.repoPath,
8101
- ...options.generatedAt ? { generatedAt: options.generatedAt } : {}
9497
+ ...options.generatedAt ? { generatedAt: options.generatedAt } : {},
9498
+ ...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {}
8102
9499
  });
8103
9500
  const inputSignals = resolvePublicIngestSignals(options);
8104
9501
  return createWitanReport(input, inputSignals.length > 0 ? inputSignals : void 0);