react-doctor 0.9.11 → 0.9.12-dev.8c2f03a

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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/bin/react-doctor.js +1 -9
  3. package/dist/{build-section-divider-sLcWe1hO.js → build-section-divider-DuozMmwU.js} +9 -10
  4. package/dist/{ci-DsrrD52i.js → ci-Bwd2LJZC.js} +12 -12
  5. package/dist/cli.js +29 -30
  6. package/dist/{design-CP25gyyg.js → design-Q4Umr_4T.js} +4 -4
  7. package/dist/{devtools-DPRVRu3S.js → devtools-D8NTVj81.js} +4 -4
  8. package/dist/{exit-gracefully-CVVkBxDS.js → exit-gracefully-vUI1H1zC.js} +6 -6
  9. package/dist/index.d.ts +36 -59
  10. package/dist/index.js +54539 -54310
  11. package/dist/{inspect-D6y8oLtg.js → inspect-RxIsB8bi.js} +20 -20
  12. package/dist/{install-afMFGeTB.js → install-CI0nvC_v.js} +8 -8
  13. package/dist/{install-doctor-script-Be7Mv3-l.js → install-doctor-script-BAmTZygh.js} +4 -4
  14. package/dist/{install-github-workflow-Dev2F6Ea.js → install-github-workflow-Dg2sosDx.js} +6 -6
  15. package/dist/{install-react-doctor-DIPVunY4.js → install-react-doctor-CIJcnTik.js} +8 -8
  16. package/dist/{print-branded-header-D5jLdo4p.js → print-branded-header-kwnSGrfR.js} +4 -4
  17. package/dist/project-analysis-worker.d.ts +1 -0
  18. package/dist/project-analysis-worker.js +51900 -0
  19. package/dist/{read-object-file-CEh1CqnD.js → read-object-file-m5uVvcBp.js} +4 -4
  20. package/dist/{record-metric-CJxSHfJD.js → record-metric-DospZkfP.js} +3093 -6149
  21. package/dist/{report-error-CAEixMg1.js → report-error-CAp5cil9.js} +5 -5
  22. package/dist/{report-workflow-result-C0jbKH8p.js → report-workflow-result-D0GvvJtT.js} +5 -5
  23. package/dist/{resolve-scope-DS7rcRRz.js → resolve-scope-eXawiGEH.js} +6 -6
  24. package/dist/{rules-BJynymuU.js → rules-C_xYDSz8.js} +6 -6
  25. package/dist/{run-scan-app-BGzlyiIB.js → run-scan-app-CeNk17wq.js} +189 -145
  26. package/dist/{scan-CgPRG6_z.js → scan-DIwsgV6M.js} +8 -8
  27. package/dist/semver-DFtnEmN-.js +3219 -0
  28. package/dist/{should-fail-scan-gate-4jf7ZRdL.js → should-fail-scan-gate-BWTNIU5h.js} +186 -91
  29. package/dist/{should-skip-prompts-C78r7Ww7.js → should-skip-prompts-oK3MwWyB.js} +4 -4
  30. package/dist/{unref-stdin-Dc8ipkaw.js → unref-stdin-BGZVK4sJ.js} +6 -12
  31. package/dist/{version-DDrwsTas.js → version-9-2T2YQf.js} +4 -4
  32. package/dist/{why-BRNrKY1b.js → why-BOiuB_qy.js} +4 -4
  33. package/package.json +6 -12
  34. package/dist/chunk-q7NCDQ7-.js +0 -28
  35. package/dist/lsp.js +0 -81114
@@ -1,23 +1,23 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1be0ae40-8cad-55a2-b447-69faa6b7cb0b")}catch(e){}}();
3
- import { $n as orElseSucceed, An as resolveReactDoctorCacheDir, Bt as computeConfigFingerprint, Cn as mapWithConcurrency, Ct as SHARE_BASE_URL, Dt as SidecarLintCacheEnabled, E as NANOSECONDS_PER_MILLISECOND, Et as Score, Ft as buildSkippedChecks, G as isCiOrCodingAgentEnvironment, Gn as log, Gt as discoverReactSubprojects, H as ACTION_INPUT_ENVIRONMENT_VARIABLES, Hn as Console, Ht as createNodeReadFileLinesSync, It as calculateScore, Jn as decodeUnknownSync, Jt as filterSourceFiles, Kn as warn, Kt as filterDiagnosticsForSurface, Ln as summarizeDiagnostics, Lt as canonicalizeUserRuleKey, Mn as resolveScanTarget, Mt as atomicWriteJson, Nn as restoreLegacyThrow, On as resolveGithubActionsScoreMetadata, Pn as runInspect, Pt as buildRuleDocsUrl, Qn as map, R as CliInputError, St as Reporter, Tn as mergeReactDoctorConfigs, U as detectRunnerOs, Un as error, Ut as createOxlintSpawnSlots, Vn as yieldToEventLoop, Vt as computeDiagnosticDelta, W as isCiEnvironment, X as CI_URL, Xn as gen, Zn as makeSpan, _ as makeNoopConsole, _r as ParentSpan, _t as OxlintConcurrency, an as hasPublishedFixRecipe, ar as runSync, at as Files, bn as listSourceFiles, bt as Progress, c as setSentryProjectInfo, cn as highlighter, ct as Git, d as isJsonModeActive, dn as isFile, er as promise, et as Config, fn as isMonorepoRoot, fr as succeed, ft as LintPartialFailures, g as VERSION, gn as isReactDoctorError, gr as void_, gt as OXLINT_NODE_REQUIREMENT, hr as fail, ht as NodeResolver, in as groupBy, ir as runPromise, it as ENTERPRISE_CONTACT_URL, jn as resolveScanConcurrency, k as SCAN_RESULT_CACHE_FILENAME, kn as resolveLintBatchOrdering, kt as SupplyChain, l as getRunId, ln as isDirectory, lt as HTML_FILE_PATTERN, mt as MILLISECONDS_PER_SECOND, n as recordDistribution, nr as provideContext, o as buildSentryScope, or as sync, pt as Linter, qn as Diagnostic, qt as filterPathsOutsideDirectories, r as toSpanAttributes, rr as provideService, rt as DeadCode, s as buildSentryProjectContext, sn as hashFileContents, sr as try_, st as GIT_SHOW_MAX_BUFFER_BYTES, t as recordCount, tn as getDiagnosticRuleIdentity, tr as provide, tt as DIAGNOSTIC_CATEGORY_BUCKETS, ur as mergeAll, ut as JSX_FILE_PATTERN, vn as isScanComplete, vt as OxlintSpawnSlots, w as METRIC, wn as materializeSourceTree, x as BASELINE_FILES_TEMP_DIR_PREFIX, xn as listWorkspacePackages, xt as Project, yt as PerFileLintCacheEnabled, z as isNonInteractiveEnvironment, zt as collectSourceFileCountsByDirectory } from "./record-metric-CJxSHfJD.js";
4
- import { a as setActiveRunTrace, i as recordRunTraceId, o as getTelemetryContext } from "./unref-stdin-Dc8ipkaw.js";
5
- import { n as buildFooterLinkLines, r as activeScanAbortRegistry, t as buildSectionDivider } from "./build-section-divider-sLcWe1hO.js";
6
- import { D as getPackageJsonPath, L as cliLogger, O as isRecord, P as runGit, a as installReactDoctorScriptStep, c as spinner, n as findNearestPackageDirectory, o as isSpinnerSilent, s as setSpinnerSilent } from "./install-doctor-script-Be7Mv3-l.js";
7
- import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-Dev2F6Ea.js";
8
- import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel, u as toForwardSlashes } from "./resolve-scope-DS7rcRRz.js";
9
- import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-C0jbKH8p.js";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="73ec5086-9c15-5ede-ba70-0f5b26bd0ed2")}catch(e){}}();
3
+ import { $ as Maintainability, $t as listSourceFiles, An as makeSpan, At as filterDiagnosticsForSurface, Bn as try_, Ct as computeConfigFingerprint, Dn as decodeUnknownSync, Dt as createOxlintSpawnSlots, E as NANOSECONDS_PER_MILLISECOND, En as Diagnostic, Et as createNodeReadFileLinesSync, Fn as provideContext, G as isCiOrCodingAgentEnvironment, Gn as succeed, Gt as highlighter, H as ACTION_INPUT_ENVIRONMENT_VARIABLES, Ht as hasPublishedFixRecipe, In as provideService, J as Files, Jn as fail, Kt as isMonorepoRoot, Ln as runPromise, Mn as orElseSucceed, Mt as filterSourceFiles, Nn as promise, Pn as provide, Q as Linter, R as CliInputError, Rn as runSync, Rt as getDiagnosticRuleIdentity, Sn as error, St as collectSourceFileCountsByDirectory, Tn as warn, Tt as countOptInProjectRuleSelections, U as detectRunnerOs, Un as mergeAll, Vt as groupBy, W as isCiEnvironment, Wt as hashFileContents, Xn as ParentSpan, Y as Git, Yn as void_, Yt as isReactDoctorError, Z as LintPartialFailures, Zt as isScanComplete, _ as makeNoopConsole, _n as summarizeDiagnostics, _t as buildSkippedChecks, an as mergeReactDoctorConfigs, at as Project, bn as yieldToEventLoop, br as isFile, bt as classifyFileContext, c as setSentryProjectInfo, cn as resolveLintBatchOrdering, cr as GIT_SHOW_MAX_BUFFER_BYTES, ct as SidecarLintCacheEnabled, d as isJsonModeActive, dn as resolveScanTarget, dr as JSX_FILE_PATTERN, en as listSourceFilesCooperative, er as CI_URL, et as NodeResolver, fn as restoreLegacyThrow, fr as MILLISECONDS_PER_SECOND, g as VERSION, gn as shouldUseMaintainabilityLayer, gt as buildRuleSeverityControls, ht as buildRuleDocsUrl, in as materializeSourceTree, ir as DIAGNOSTIC_CATEGORY_BUCKETS, it as Progress, jn as map, jt as filterPathsOutsideDirectories, k as SCAN_RESULT_CACHE_FILENAME, kn as gen, kt as discoverReactSubprojects, l as getRunId, ln as resolveReactDoctorCacheDir, lr as HTML_FILE_PATTERN, mr as SHARE_BASE_URL, n as recordDistribution, nt as OxlintSpawnSlots, o as buildSentryScope, on as remainingDeadlineBudgetMs, or as ENTERPRISE_CONTACT_URL, ot as Reporter, pn as runInspect, pr as OXLINT_NODE_REQUIREMENT, pt as atomicWriteJson, q as Config, qt as isPathInsideDirectory, r as toSpanAttributes, rn as mapWithConcurrency, rt as PerFileLintCacheEnabled, s as buildSentryProjectContext, sn as resolveGithubActionsScoreMetadata, st as Score, t as recordCount, tn as listWorkspacePackages, tt as OxlintConcurrency, un as resolveScanConcurrency, ur as JSX_DUPLICATION_SOURCE_FILE_PATTERN, ut as SupplyChain, vr as isDirectory, vt as calculateScore, w as METRIC, wn as log, wt as computeDiagnosticDelta, x as BASELINE_FILES_TEMP_DIR_PREFIX, xn as Console, yt as canonicalizeUserRuleKey, z as isNonInteractiveEnvironment, zn as sync } from "./record-metric-DospZkfP.js";
4
+ import { a as setActiveRunTrace, i as recordRunTraceId, o as getTelemetryContext } from "./unref-stdin-BGZVK4sJ.js";
5
+ import { n as buildFooterLinkLines, r as activeScanAbortRegistry, t as buildSectionDivider } from "./build-section-divider-DuozMmwU.js";
6
+ import { D as getPackageJsonPath, L as cliLogger, O as isRecord, P as runGit, a as installReactDoctorScriptStep, c as spinner, n as findNearestPackageDirectory, o as isSpinnerSilent, s as setSpinnerSilent } from "./install-doctor-script-BAmTZygh.js";
7
+ import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-Dg2sosDx.js";
8
+ import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel, u as toForwardSlashes } from "./resolve-scope-eXawiGEH.js";
9
+ import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-D0GvvJtT.js";
10
10
  import { createRequire } from "node:module";
11
11
  import { spawn } from "node:child_process";
12
12
  import * as path$1 from "node:path";
13
13
  import path from "node:path";
14
- import * as fs$1 from "node:fs";
14
+ import * as fs$2 from "node:fs";
15
15
  import fs, { mkdtempSync, rmSync } from "node:fs";
16
- import crypto, { randomUUID } from "node:crypto";
17
16
  import ts from "typescript";
17
+ import crypto, { randomUUID } from "node:crypto";
18
18
  import { tmpdir } from "node:os";
19
+ import * as fs$1 from "node:fs/promises";
19
20
  import { performance } from "node:perf_hooks";
20
- import { codeFrameColumns } from "@babel/code-frame";
21
21
  //#region src/cli/utils/apply-observability.ts
22
22
  /**
23
23
  * Installs the tracing backend for the inspect program.
@@ -84,7 +84,10 @@ const buildSpinnerProgressHandle = (text) => {
84
84
  */
85
85
  const buildRuntimeLayers = (input) => {
86
86
  const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
87
- const deadCodeLayer = input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]);
87
+ const maintainabilityLayer = shouldUseMaintainabilityLayer({
88
+ shouldRunDuplicateJsx: input.shouldRunDeadCode,
89
+ userConfig: input.userConfig
90
+ }) ? Maintainability.layerNode : Maintainability.layerOf([]);
88
91
  const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
89
92
  const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
90
93
  const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
@@ -94,7 +97,7 @@ const buildRuntimeLayers = (input) => {
94
97
  config: input.userConfig,
95
98
  resolvedDirectory: input.directory,
96
99
  configSourceDirectory: input.configSourceDirectory
97
- }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
100
+ }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, maintainabilityLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
98
101
  const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll(baseLayers, succeed(OxlintConcurrency, input.oxlintConcurrency));
99
102
  return input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll(layersWithConcurrency, succeed(OxlintSpawnSlots, input.oxlintSpawnSlots));
100
103
  };
@@ -310,7 +313,7 @@ const recordScanMetrics = (input) => {
310
313
  recordCount(METRIC.scanCompleted, 1, {
311
314
  mode: input.mode,
312
315
  lint: input.lint,
313
- deadCode: input.deadCode,
316
+ maintainability: input.deadCode,
314
317
  parallel: input.parallel,
315
318
  scoreOnly: input.scoreOnly,
316
319
  didLintFail: input.didLintFail,
@@ -350,7 +353,7 @@ const recordScanMetrics = (input) => {
350
353
  else if (!input.noScore && !input.didLintFail && !input.didDeadCodeFail) recordCount(METRIC.scoreUnavailable, 1, { mode: input.mode });
351
354
  if (input.baselineDegraded) recordCount(METRIC.baselineDegraded, 1);
352
355
  if (input.didLintFail) recordCount(METRIC.lintFailed, 1, { reasonKind: input.lintFailureReasonKind });
353
- if (input.didDeadCodeFail) recordCount(METRIC.deadCodeFailed, 1);
356
+ if (input.didDeadCodeFail) recordCount(METRIC.maintainabilityFailed, 1);
354
357
  for (const check of result.skippedChecks) recordCount(METRIC.scanCheckSkipped, 1, {
355
358
  check,
356
359
  reason: result.skippedCheckReasons?.[check] ?? null
@@ -494,7 +497,6 @@ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
494
497
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
495
498
  "oxlint/package.json",
496
499
  "oxlint-plugin-react-doctor/package.json",
497
- "deslop-js/package.json",
498
500
  "eslint-plugin-react-hooks/package.json"
499
501
  ];
500
502
  const bundledRequire = createRequire(import.meta.url);
@@ -633,12 +635,12 @@ const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorC
633
635
  const readPersistedCache = (cacheFilePath) => {
634
636
  try {
635
637
  const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
636
- if (!isRecord(parsed) || parsed.version !== 6) return {
637
- version: 6,
638
+ if (!isRecord(parsed) || parsed.version !== 7) return {
639
+ version: 7,
638
640
  entries: []
639
641
  };
640
642
  if (!Array.isArray(parsed.entries)) return {
641
- version: 6,
643
+ version: 7,
642
644
  entries: []
643
645
  };
644
646
  const entries = [];
@@ -653,12 +655,12 @@ const readPersistedCache = (cacheFilePath) => {
653
655
  });
654
656
  }
655
657
  return {
656
- version: 6,
658
+ version: 7,
657
659
  entries
658
660
  };
659
661
  } catch {
660
662
  return {
661
- version: 6,
663
+ version: 7,
662
664
  entries: []
663
665
  };
664
666
  }
@@ -666,7 +668,7 @@ const readPersistedCache = (cacheFilePath) => {
666
668
  /**
667
669
  * The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
668
670
  * cache subsystem: this whole-repo scan cache plus core's per-file lint,
669
- * sidecar, and dead-code caches (their `Context.Reference` defaults read the
671
+ * sidecar caches (their `Context.Reference` defaults read the
670
672
  * same variable). Exported for the wide event's `cache.temperature`
671
673
  * derivation, which reports `"disabled"` instead of `"cold"` when the switch
672
674
  * is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
@@ -708,7 +710,7 @@ const buildScanResultCacheKey = (input) => {
708
710
  if (repositoryIdentity === null) return null;
709
711
  if (stringifyStableJson(input.userConfig) === null) return null;
710
712
  const cacheKeyJson = stringifyStableJson({
711
- schemaVersion: 6,
713
+ schemaVersion: 7,
712
714
  projectIdentity: resolveProjectIdentity(input.projectDirectory),
713
715
  headSha: repositoryIdentity.headSha,
714
716
  worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
@@ -756,7 +758,7 @@ const createScanResultCache = (projectDirectory) => {
756
758
  for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
757
759
  for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
758
760
  atomicWriteJson(cacheFilePath, {
759
- version: 6,
761
+ version: 7,
760
762
  entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
761
763
  });
762
764
  };
@@ -791,8 +793,7 @@ const shouldBlockCi = (diagnostics, blockingLevel) => {
791
793
  /**
792
794
  * Lowercase, key-safe form of a rule category for the `diag.category.*`
793
795
  * telemetry attribute namespace (categories carry spaces / capitals, e.g.
794
- * "Performance" → `performance`, "Dead Code" → `dead_code`). Shared by the
795
- * CLI run event and the editor LSP wide event so both namespace identically.
796
+ * "Performance" → `performance`, "Dead Code" → `dead_code`).
796
797
  */
797
798
  const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
798
799
  //#endregion
@@ -822,12 +823,6 @@ const readEnvBoolean = (name) => {
822
823
  return value.toLowerCase() === "true" || value === "1";
823
824
  };
824
825
  const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
825
- const resolveDeadCodeReuseRatio = (result) => {
826
- if (result.deadCodeCacheHit === true) return 1;
827
- const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
828
- if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
829
- return result.deadCodeCacheHit === false ? 0 : null;
830
- };
831
826
  /**
832
827
  * One queryable cache temperature per scan, derived from the whole stack:
833
828
  *
@@ -835,8 +830,7 @@ const resolveDeadCodeReuseRatio = (result) => {
835
830
  * `wholeRepoCacheHit` flag from the CLI's cachedPayload
836
831
  * branch; no scan work ran).
837
832
  * - `"warm"` — any incremental reuse: per-file lint hits, sidecar
838
- * replays, a dead-code whole-result hit, or dead-code
839
- * summary-cache hits.
833
+ * replays.
840
834
  * - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
841
835
  * off-switch is on. Granular per-cache opt-outs
842
836
  * (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
@@ -845,7 +839,7 @@ const resolveDeadCodeReuseRatio = (result) => {
845
839
  *
846
840
  * `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
847
841
  * the subsystem reuse fractions known this run (lint hit ratio, sidecar
848
- * replay ratio, dead-code reuse), skipping subsystems that never consulted a
842
+ * replay ratio), skipping subsystems that never consulted a
849
843
  * cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
850
844
  * unweighted — the per-subsystem dims stay the precise signal; warmth is the
851
845
  * p50/p90-able summary. Emitted only on the success path.
@@ -858,11 +852,7 @@ const buildCacheAttributes = (input) => {
858
852
  warmth: 1
859
853
  });
860
854
  const result = input.result;
861
- const subsystemReuseRatios = [
862
- ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
863
- ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
864
- resolveDeadCodeReuseRatio(result)
865
- ];
855
+ const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
866
856
  let knownSubsystemCount = 0;
867
857
  let reuseRatioSum = 0;
868
858
  for (const reuseRatio of subsystemReuseRatios) {
@@ -1014,13 +1004,7 @@ const buildOutcomeAttributes = (input) => {
1014
1004
  sidecarTotalFiles: result.lintSidecarTotalFileCount,
1015
1005
  sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
1016
1006
  }),
1017
- ...withNamespace("deadCode", {
1018
- failed: input.didDeadCodeFail,
1019
- overlapped: input.deadCodeOverlapped,
1020
- cacheHit: result.deadCodeCacheHit,
1021
- summaryCacheHits: result.deadCodeSummaryCacheHits,
1022
- summaryCacheMisses: result.deadCodeSummaryCacheMisses
1023
- }),
1007
+ ...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
1024
1008
  ...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
1025
1009
  ...withNamespace("securityScan", { failed: input.securityScanFailed }),
1026
1010
  ...withNamespace("timing", {
@@ -1063,7 +1047,7 @@ const buildScanAttributes = (input) => {
1063
1047
  workerCount: input.workerCount,
1064
1048
  maxDurationMs: input.maxDurationMs,
1065
1049
  lint: input.lint,
1066
- deadCode: input.deadCode,
1050
+ maintainability: input.deadCode,
1067
1051
  supplyChain: input.supplyChain,
1068
1052
  scoreOnly: input.scoreOnly,
1069
1053
  noScore: input.noScore,
@@ -1074,6 +1058,7 @@ const buildScanAttributes = (input) => {
1074
1058
  hasCustomConfig: input.hasCustomConfig,
1075
1059
  rulesConfigured: ruleKeys.length,
1076
1060
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
1061
+ projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
1077
1062
  fileCount: input.result?.scannedFileCount,
1078
1063
  nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
1079
1064
  htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
@@ -1084,7 +1069,7 @@ const buildScanAttributes = (input) => {
1084
1069
  * Projects a scan into the namespaced attribute set for its root span — the
1085
1070
  * canonical per-scan "wide event". Every attribute carries a dotted namespace
1086
1071
  * that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
1087
- * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`, `cache.*`,
1072
+ * verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
1088
1073
  * `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
1089
1074
  * tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
1090
1075
  * in the Spans dataset. Pure and exported so the projection (outcome
@@ -1323,7 +1308,7 @@ const buildNoScoreMessage = (input) => {
1323
1308
  reason = input.disabledMessage ?? "Score disabled by --no-score.";
1324
1309
  break;
1325
1310
  case "analysis-incomplete":
1326
- reason = "Score not shown because lint or dead-code analysis could not complete.";
1311
+ reason = "Score not shown because lint or maintainability analysis could not complete.";
1327
1312
  break;
1328
1313
  case "api-unavailable":
1329
1314
  reason = "Score unavailable (could not reach the score API).";
@@ -1371,7 +1356,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
1371
1356
  const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
1372
1357
  const readPreviousRuleDumpFileNames = (directory) => {
1373
1358
  try {
1374
- const previous = JSON.parse(fs$1.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
1359
+ const previous = JSON.parse(fs$2.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
1375
1360
  return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
1376
1361
  } catch {
1377
1362
  return /* @__PURE__ */ new Set();
@@ -1379,10 +1364,10 @@ const readPreviousRuleDumpFileNames = (directory) => {
1379
1364
  };
1380
1365
  const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
1381
1366
  const directory = outputDirectory ? path$1.resolve(outputDirectory) : path$1.join(tmpdir(), `react-doctor-${randomUUID()}`);
1382
- if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$1.rmSync(path$1.join(directory, fileName), { force: true });
1383
- fs$1.mkdirSync(directory, { recursive: true });
1384
- for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$1.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
1385
- fs$1.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
1367
+ if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$2.rmSync(path$1.join(directory, fileName), { force: true });
1368
+ fs$2.mkdirSync(directory, { recursive: true });
1369
+ for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$2.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
1370
+ fs$2.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
1386
1371
  return directory;
1387
1372
  };
1388
1373
  //#endregion
@@ -1426,6 +1411,9 @@ const formatElapsedTime = (elapsedMilliseconds) => {
1426
1411
  return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
1427
1412
  };
1428
1413
  //#endregion
1414
+ //#region src/cli/utils/format-skipped-check-label.ts
1415
+ const formatSkippedCheckLabel = (skippedCheck) => skippedCheck === "dead-code" ? "maintainability" : skippedCheck;
1416
+ //#endregion
1429
1417
  //#region src/cli/utils/pluralize.ts
1430
1418
  const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
1431
1419
  //#endregion
@@ -1474,7 +1462,7 @@ const printHeadlessReport = (input) => gen(function* () {
1474
1462
  }
1475
1463
  if (input.skippedChecks.length > 0) {
1476
1464
  yield* log("");
1477
- yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.join(" and ")} checks failed.`));
1465
+ yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.map(formatSkippedCheckLabel).join(" and ")} checks failed.`));
1478
1466
  }
1479
1467
  });
1480
1468
  //#endregion
@@ -1674,12 +1662,39 @@ const renderAndRecordScan = async (input) => {
1674
1662
  didDeadCodeFail: input.payload.didDeadCodeFail,
1675
1663
  supplyChainOverlapTimedOut: input.payload.supplyChainOverlapTimedOut,
1676
1664
  securityScanFailed: input.payload.securityScanFailed,
1677
- deadCodeOverlapped: input.payload.deadCodeOverlapped,
1678
1665
  suppressedRuleCounts: input.payload.suppressedRuleCounts ?? []
1679
1666
  });
1680
1667
  return result;
1681
1668
  };
1682
1669
  //#endregion
1670
+ //#region src/cli/utils/copy-unchanged-baseline-sources.ts
1671
+ const copyUnchangedBaselineSources = async (input) => {
1672
+ const baseMaterializedFiles = new Set(input.baseMaterializedFiles.map(toForwardSlashes));
1673
+ const headChangedFiles = new Set(input.headChangedFiles.map(toForwardSlashes));
1674
+ const untrackedFiles = new Set(input.untrackedFiles.map(toForwardSlashes));
1675
+ const sourceDirectory = path$1.resolve(input.directory);
1676
+ const targetDirectory = path$1.resolve(input.tempDirectory);
1677
+ return (await mapWithConcurrency(input.sourceFiles.filter((filePath) => {
1678
+ const normalizedPath = toForwardSlashes(filePath);
1679
+ return !baseMaterializedFiles.has(normalizedPath) && !headChangedFiles.has(normalizedPath) && !untrackedFiles.has(normalizedPath);
1680
+ }), 32, async (filePath) => {
1681
+ input.signal?.throwIfAborted();
1682
+ if (input.deadlineEpochMs !== null && Date.now() >= input.deadlineEpochMs) return false;
1683
+ const sourcePath = path$1.resolve(sourceDirectory, filePath);
1684
+ const targetPath = path$1.resolve(targetDirectory, filePath);
1685
+ if (!isPathInsideDirectory(sourcePath, sourceDirectory) || !isPathInsideDirectory(targetPath, targetDirectory)) return false;
1686
+ try {
1687
+ if (!(await fs$1.lstat(sourcePath)).isFile()) return false;
1688
+ await fs$1.mkdir(path$1.dirname(targetPath), { recursive: true });
1689
+ await fs$1.copyFile(sourcePath, targetPath);
1690
+ return true;
1691
+ } catch {
1692
+ input.signal?.throwIfAborted();
1693
+ return false;
1694
+ }
1695
+ })).every(Boolean);
1696
+ };
1697
+ //#endregion
1683
1698
  //#region src/cli/utils/read-diagnostic-evidence.ts
1684
1699
  const SOURCE_EXTENSIONS = [
1685
1700
  ".ts",
@@ -2097,7 +2112,6 @@ const resolveMergeBaseRef = async (directory, ref) => {
2097
2112
  const silentConsole$1 = makeNoopConsole();
2098
2113
  const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
2099
2114
  const runBaselineComparison = async (input) => {
2100
- const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
2101
2115
  const baselineIncludePaths = filterPathsOutsideDirectories({
2102
2116
  rootDirectory: input.directory,
2103
2117
  relativePaths: input.options.includePaths,
@@ -2113,12 +2127,24 @@ const runBaselineComparison = async (input) => {
2113
2127
  relativePaths: input.headFiles,
2114
2128
  excludedDirectories: input.options.excludedProjectDirectories
2115
2129
  }) : void 0;
2130
+ const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
2131
+ if (remainingBaselineBudgetMs === 0) return null;
2132
+ const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
2133
+ const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
2134
+ let maintainabilitySourceFiles = [];
2135
+ if (input.options.deadCode) try {
2136
+ maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
2137
+ } catch (error) {
2138
+ if (baselineDeadlineSignal?.aborted) return null;
2139
+ throw error;
2140
+ }
2141
+ const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
2116
2142
  const snapshot = await materializeBaselineFiles({
2117
2143
  directory: input.directory,
2118
2144
  ref: input.baselineRef,
2119
- files: baselineIncludePaths,
2120
- baseFiles: baselineBaseFiles,
2121
- headFiles: baselineHeadFiles,
2145
+ files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
2146
+ baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
2147
+ headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
2122
2148
  tempDirectory: temporaryDirectory
2123
2149
  }).catch((error) => {
2124
2150
  rmSync(temporaryDirectory, {
@@ -2136,22 +2162,47 @@ const runBaselineComparison = async (input) => {
2136
2162
  }
2137
2163
  try {
2138
2164
  if (!snapshot.isComplete) return null;
2165
+ if (input.options.deadCode && !await copyUnchangedBaselineSources({
2166
+ directory: input.directory,
2167
+ sourceFiles: maintainabilitySourceFiles,
2168
+ baseMaterializedFiles: snapshot.materializedFiles,
2169
+ headChangedFiles: snapshot.headFiles,
2170
+ untrackedFiles: snapshot.untrackedFiles,
2171
+ tempDirectory: snapshot.tempDirectory,
2172
+ deadlineEpochMs: input.deadlineEpochMs,
2173
+ signal: input.oxlintRuntime.abortSignal
2174
+ })) return null;
2175
+ const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
2176
+ rootDirectory: input.directory,
2177
+ relativePaths: snapshot.baseFiles,
2178
+ excludedDirectories: input.options.excludedProjectDirectories
2179
+ });
2180
+ const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
2181
+ rootDirectory: input.directory,
2182
+ relativePaths: snapshot.headFiles,
2183
+ excludedDirectories: input.options.excludedProjectDirectories
2184
+ });
2139
2185
  const analyzedHeadFiles = new Set(input.headAnalyzedFiles.map(toForwardSlashes));
2140
- const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
2141
- const expectedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
2186
+ const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
2187
+ const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
2142
2188
  for (const filePath of baselineIncludePaths) {
2143
2189
  const normalizedFilePath = toForwardSlashes(filePath);
2144
2190
  if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
2145
2191
  }
2146
- if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
2192
+ if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
2193
+ const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
2194
+ const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
2195
+ const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
2196
+ const baselineExcludedProjectDirectories = input.options.excludedProjectDirectories.map((excludedDirectory) => path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, input.directory)).map((excludedDirectory) => path.resolve(snapshot.tempDirectory, path.relative(input.directory, excludedDirectory)));
2197
+ const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
2147
2198
  const runtimeLayers = buildRuntimeLayers({
2148
2199
  directory: snapshot.tempDirectory,
2149
2200
  hasConfigOverride: true,
2150
2201
  userConfig: input.userConfig,
2151
2202
  configSourceDirectory: input.configSourceDirectory,
2152
2203
  projectInfoOverride: input.headProjectInfo,
2153
- shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath,
2154
- shouldRunDeadCode: false,
2204
+ shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
2205
+ shouldRunDeadCode: input.options.deadCode,
2155
2206
  shouldRunSupplyChain: input.options.supplyChain,
2156
2207
  shouldComputeScore: false,
2157
2208
  shouldShowProgressSpinners: false,
@@ -2160,7 +2211,8 @@ const runBaselineComparison = async (input) => {
2160
2211
  });
2161
2212
  const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
2162
2213
  directory: snapshot.tempDirectory,
2163
- includePaths: snapshot.materializedFiles,
2214
+ includePaths: baseIncludePaths,
2215
+ maintainabilityFocusPaths,
2164
2216
  customRulesOnly: input.options.customRulesOnly,
2165
2217
  respectInlineDisables: input.options.respectInlineDisables,
2166
2218
  warnings: input.options.warnings,
@@ -2169,7 +2221,7 @@ const runBaselineComparison = async (input) => {
2169
2221
  includedTags: input.options.includedTags,
2170
2222
  includeTagDefaults: input.options.includeTagDefaults,
2171
2223
  nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
2172
- runDeadCode: false,
2224
+ runDeadCode: input.options.deadCode,
2173
2225
  isCi: input.options.isCi,
2174
2226
  doctorVersion: VERSION,
2175
2227
  runId: getRunId(),
@@ -2177,10 +2229,16 @@ const runBaselineComparison = async (input) => {
2177
2229
  suppressScanSummary: true,
2178
2230
  supplyChainManifestChanged: input.options.supplyChainManifestChanged,
2179
2231
  deadlineEpochMs: input.deadlineEpochMs ?? void 0,
2180
- signal: input.oxlintRuntime.abortSignal
2232
+ signal: input.oxlintRuntime.abortSignal,
2233
+ excludedProjectDirectories: baselineExcludedProjectDirectories,
2234
+ retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
2181
2235
  }, {}).pipe(provide(runtimeLayers), provideService(PerFileLintCacheEnabled, false), provideService(SidecarLintCacheEnabled, false), provideService(Console, silentConsole$1))), { signal: input.oxlintRuntime.abortSignal });
2182
- if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
2183
- const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
2236
+ if (baseOutput.didLintFail || baseOutput.didDeadCodeFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
2237
+ const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
2238
+ rootDirectory: input.directory,
2239
+ relativePaths: snapshot.untrackedFiles,
2240
+ excludedDirectories: input.options.excludedProjectDirectories
2241
+ }).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
2184
2242
  const diagnosticDelta = computeDiagnosticDelta({
2185
2243
  headDiagnostics: input.headDiagnostics,
2186
2244
  baseDiagnostics: baseOutput.diagnostics,
@@ -2353,6 +2411,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
2353
2411
  directory,
2354
2412
  precomputedSourceFileCount: options.precomputedSourceFileCount,
2355
2413
  includePaths: options.includePaths,
2414
+ changedLineRanges: options.changedLineRanges ?? void 0,
2356
2415
  customRulesOnly: options.customRulesOnly,
2357
2416
  respectInlineDisables: options.respectInlineDisables,
2358
2417
  warnings: options.warnings,
@@ -2383,7 +2442,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
2383
2442
  else runSync(error(highlighter.error(lintFailureReason)));
2384
2443
  let inspectDiagnostics = output.diagnostics;
2385
2444
  let baselineDelta;
2386
- if (options.baseline && isDiffMode && !didLintFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
2445
+ if (options.baseline && isDiffMode && !didLintFail && !output.didDeadCodeFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
2387
2446
  const comparison = await runBaselineComparison({
2388
2447
  directory,
2389
2448
  options,
@@ -2536,13 +2595,13 @@ const resolveCursorBundledNode = () => {
2536
2595
  if (!localAppData) return null;
2537
2596
  const cursorAgentRoot = path$1.join(localAppData, "cursor-agent", "versions");
2538
2597
  try {
2539
- const versions = fs$1.readdirSync(cursorAgentRoot);
2598
+ const versions = fs$2.readdirSync(cursorAgentRoot);
2540
2599
  if (versions.length === 0) return null;
2541
2600
  const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
2542
2601
  for (const version of sortedVersions) try {
2543
2602
  const bundledNodePath = path$1.join(cursorAgentRoot, version, "node.exe");
2544
2603
  const entryScriptPath = path$1.join(cursorAgentRoot, version, "index.js");
2545
- if (fs$1.statSync(bundledNodePath).isFile() && fs$1.statSync(entryScriptPath).isFile()) return {
2604
+ if (fs$2.statSync(bundledNodePath).isFile() && fs$2.statSync(entryScriptPath).isFile()) return {
2546
2605
  command: bundledNodePath,
2547
2606
  args: [entryScriptPath]
2548
2607
  };
@@ -2556,15 +2615,15 @@ const resolveWindowsCmdEntryScript = (command) => {
2556
2615
  for (const directory of pathDirectories) {
2557
2616
  const cmdFilePath = path$1.join(directory, `${command}.cmd`);
2558
2617
  try {
2559
- if (!fs$1.statSync(cmdFilePath).isFile()) continue;
2618
+ if (!fs$2.statSync(cmdFilePath).isFile()) continue;
2560
2619
  } catch {
2561
2620
  continue;
2562
2621
  }
2563
2622
  try {
2564
- const entryScriptMatch = fs$1.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
2623
+ const entryScriptMatch = fs$2.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
2565
2624
  if (!entryScriptMatch) continue;
2566
2625
  const resolvedScriptPath = path$1.resolve(directory, entryScriptMatch[1]);
2567
- if (fs$1.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
2626
+ if (fs$2.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
2568
2627
  } catch {}
2569
2628
  }
2570
2629
  return null;
@@ -2829,6 +2888,45 @@ const resolveProjectScan = async (rootScanTarget, projectDirectory) => {
2829
2888
  };
2830
2889
  };
2831
2890
  //#endregion
2891
+ //#region src/cli/utils/highlight-code-line.ts
2892
+ const CODE_TOKEN_PATTERN = /\/\/.*|\/\*.*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|type|typeof|undefined|var|void|while|with|yield)\b|\b(?:0[xob][\da-f]+|\d+(?:\.\d+)?)\b/giu;
2893
+ const highlightCodeLine = (codeLine) => codeLine.replace(CODE_TOKEN_PATTERN, (token) => {
2894
+ if (token.startsWith("/")) return highlighter.gray(token);
2895
+ if (token.startsWith("\"") || token.startsWith("'") || token.startsWith("`")) return highlighter.success(token);
2896
+ if (/^\d|^0[xob]/i.test(token)) return highlighter.warn(token);
2897
+ return highlighter.info(token);
2898
+ });
2899
+ //#endregion
2900
+ //#region src/cli/utils/render-code-frame.ts
2901
+ const SOURCE_LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
2902
+ const renderCodeFrame = ({ source, line, column, endLine, message, linesAbove, linesBelow, maximumLineLength }) => {
2903
+ const sourceLines = source.split(SOURCE_LINE_BREAK_PATTERN);
2904
+ if (line < 1 || line > sourceLines.length) return null;
2905
+ const offendingLine = sourceLines[line - 1];
2906
+ if (offendingLine === void 0 || offendingLine.length > maximumLineLength) return null;
2907
+ const lastMarkedLine = Math.min(Math.max(endLine ?? line, line), sourceLines.length);
2908
+ const firstDisplayedLine = Math.max(line - linesAbove, 1);
2909
+ const lastDisplayedLine = Math.min(lastMarkedLine + linesBelow, sourceLines.length);
2910
+ const lineNumberWidth = String(lastDisplayedLine).length;
2911
+ const hasCaret = lastMarkedLine === line && column !== void 0;
2912
+ const outputLines = [];
2913
+ if (message && !hasCaret) outputLines.push(`${" ".repeat(lineNumberWidth + 2)}${highlighter.error(message)}`);
2914
+ for (let displayedLineNumber = firstDisplayedLine; displayedLineNumber <= lastDisplayedLine; displayedLineNumber += 1) {
2915
+ const sourceLine = sourceLines[displayedLineNumber - 1] ?? "";
2916
+ const isMarked = displayedLineNumber >= line && displayedLineNumber <= lastMarkedLine;
2917
+ const gutter = ` ${String(displayedLineNumber).padStart(lineNumberWidth)} |`;
2918
+ const marker = isMarked ? highlighter.error(">") : " ";
2919
+ outputLines.push(`${marker}${highlighter.dim(gutter)}${sourceLine.length > 0 ? ` ${highlightCodeLine(sourceLine)}` : ""}`);
2920
+ if (isMarked && hasCaret) {
2921
+ const markerSpacing = sourceLine.slice(0, Math.max(column - 1, 0)).replace(/[^\t]/g, " ");
2922
+ const emptyGutter = gutter.replace(/\d/g, " ");
2923
+ const markerMessage = message ? ` ${highlighter.error(message)}` : "";
2924
+ outputLines.push(` ${highlighter.dim(emptyGutter)} ${markerSpacing}${highlighter.error("^")}${markerMessage}`);
2925
+ }
2926
+ }
2927
+ return outputLines.join("\n");
2928
+ };
2929
+ //#endregion
2832
2930
  //#region src/cli/utils/resolve-absolute-path.ts
2833
2931
  /**
2834
2932
  * Resolves a diagnostic's `filePath` (relative to its project root, or
@@ -2851,23 +2949,20 @@ const buildCodeFrame = (input) => {
2851
2949
  const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
2852
2950
  let source;
2853
2951
  try {
2854
- source = fs$1.readFileSync(absolutePath, "utf8");
2952
+ source = fs$2.readFileSync(absolutePath, "utf8");
2855
2953
  } catch {
2856
2954
  return null;
2857
2955
  }
2858
- if ((source.split("\n", input.line)[input.line - 1] ?? "").length > 200) return null;
2859
- const location = input.endLine != null && input.endLine > input.line ? {
2860
- start: { line: input.line },
2861
- end: { line: input.endLine }
2862
- } : { start: {
2956
+ const endLine = input.endLine != null && input.endLine > input.line ? input.endLine : void 0;
2957
+ return renderCodeFrame({
2958
+ source,
2863
2959
  line: input.line,
2864
- column: input.column > 0 ? input.column : void 0
2865
- } };
2866
- return codeFrameColumns(source, location, {
2867
- highlightCode: true,
2960
+ column: endLine === void 0 && input.column > 0 ? input.column : void 0,
2961
+ endLine,
2962
+ message: input.message,
2868
2963
  linesAbove: 1,
2869
2964
  linesBelow: 1,
2870
- ...input.message ? { message: input.message } : {}
2965
+ maximumLineLength: 200
2871
2966
  });
2872
2967
  };
2873
2968
  //#endregion
@@ -2991,7 +3086,7 @@ const shouldFailScanGate = (input) => {
2991
3086
  return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
2992
3087
  };
2993
3088
  //#endregion
2994
- export { resolveMergeBaseRef as A, buildEmptyReportMessage as B, copyToClipboard as C, deduplicateProjectScans as D, collectProjectSourceFileCounts as E, printDiagnosticsDump as F, formatLearnMoreLine as G, buildRulePriorityMap as H, formatDiagnosticSite as I, hasIncompleteScoreAnalysis as L, pluralize as M, formatElapsedTime as N, createInvocationInspect as O, printFooter as P, filterDiagnosticsByCategories as R, CLI_AGENT_BINARIES as S, buildHandoffPayload as T, buildSortedRuleGroups as U, hasLintHardFailure as V, formatFixRecipeLine as W, resolveCliInspectOptions as _, projectManifestChanged as a, setUpGitHubActions as b, selectProjects as c, resolveProjectChangedLineRanges as d, resolveProjectDiffIncludePaths as f, resolveWorkspaceDeadCodeOwner as g, retryMissingProjectScores as h, validateModeFlags as i, printHeadlessReport as j, inspect as k, buildCodeFrame as l, resolveProjectRelativeDirectory as m, formatSkippedProjectsMessage as n, partitionProjectScanOutcomes as o, resolveProjectSourceFilePaths as p, validateIncludeUntrackedScope as r, discoverWorkspacePackages as s, shouldFailScanGate as t, resolveProjectScan as u, filterScansForSurface as v, launchCliAgent as w, detectLaunchableAgents as x, countUniqueScannedFiles as y, buildNoScoreMessage as z };
3089
+ export { resolveMergeBaseRef as A, buildNoScoreMessage as B, copyToClipboard as C, deduplicateProjectScans as D, collectProjectSourceFileCounts as E, printFooter as F, formatFixRecipeLine as G, hasLintHardFailure as H, printDiagnosticsDump as I, formatLearnMoreLine as K, formatDiagnosticSite as L, pluralize as M, formatSkippedCheckLabel as N, createInvocationInspect as O, formatElapsedTime as P, hasIncompleteScoreAnalysis as R, CLI_AGENT_BINARIES as S, buildHandoffPayload as T, buildRulePriorityMap as U, buildEmptyReportMessage as V, buildSortedRuleGroups as W, resolveCliInspectOptions as _, projectManifestChanged as a, setUpGitHubActions as b, selectProjects as c, resolveProjectChangedLineRanges as d, resolveProjectDiffIncludePaths as f, resolveWorkspaceDeadCodeOwner as g, retryMissingProjectScores as h, validateModeFlags as i, printHeadlessReport as j, inspect as k, buildCodeFrame as l, resolveProjectRelativeDirectory as m, formatSkippedProjectsMessage as n, partitionProjectScanOutcomes as o, resolveProjectSourceFilePaths as p, validateIncludeUntrackedScope as r, discoverWorkspacePackages as s, shouldFailScanGate as t, resolveProjectScan as u, filterScansForSurface as v, launchCliAgent as w, detectLaunchableAgents as x, countUniqueScannedFiles as y, filterDiagnosticsByCategories as z };
2995
3090
 
2996
- //# sourceMappingURL=should-fail-scan-gate-4jf7ZRdL.js.map
2997
- //# debugId=1be0ae40-8cad-55a2-b447-69faa6b7cb0b
3091
+ //# sourceMappingURL=should-fail-scan-gate-BWTNIU5h.js.map
3092
+ //# debugId=73ec5086-9c15-5ede-ba70-0f5b26bd0ed2
@@ -1,10 +1,10 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a532e2d3-42f9-5e38-9c59-ee0e8063b1fd")}catch(e){}}();
3
- import { z as isNonInteractiveEnvironment } from "./record-metric-CJxSHfJD.js";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4a59485c-bb47-54f4-8b34-a6dd3a7d7831")}catch(e){}}();
3
+ import { z as isNonInteractiveEnvironment } from "./record-metric-DospZkfP.js";
4
4
  //#region src/cli/utils/should-skip-prompts.ts
5
5
  const shouldSkipPrompts = (input = {}) => Boolean(input.yes) || Boolean(input.json) || isNonInteractiveEnvironment() || !process.stdin.isTTY;
6
6
  //#endregion
7
7
  export { shouldSkipPrompts as t };
8
8
 
9
- //# sourceMappingURL=should-skip-prompts-C78r7Ww7.js.map
10
- //# debugId=a532e2d3-42f9-5e38-9c59-ee0e8063b1fd
9
+ //# sourceMappingURL=should-skip-prompts-oK3MwWyB.js.map
10
+ //# debugId=4a59485c-bb47-54f4-8b34-a6dd3a7d7831