react-doctor 0.9.12 → 0.9.13

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 (52) hide show
  1. package/README.md +34 -1
  2. package/bin/react-doctor.js +1 -9
  3. package/dist/active-run-trace-dlIZeEvb.js +18 -0
  4. package/dist/active-scan-abort-registry-DwG6tSPy.js +28 -0
  5. package/dist/{build-section-divider-BsQvH-7I.js → build-section-divider-DELfSn3p.js} +10 -25
  6. package/dist/{ci-B4x4RjKD.js → ci-DqnZCnrL.js} +13 -12
  7. package/dist/cli.js +121 -41
  8. package/dist/{design-A48PNCf_.js → design-DqUidb1f.js} +4 -4
  9. package/dist/{devtools-DPRVRu3S.js → devtools-CC2PAZ85.js} +4 -4
  10. package/dist/{exit-gracefully-Bk531kS-.js → exit-gracefully-CrpeIbra.js} +15 -9
  11. package/dist/get-east-asian-width-Bv2TD3Xj.js +444 -0
  12. package/dist/{report-error-Pb2uO8EG.js → handle-error-BEYYtsCl.js} +18 -68
  13. package/dist/index.d.ts +53 -42
  14. package/dist/index.js +55119 -54689
  15. package/dist/{inspect-CwH84aSA.js → inspect-BjMaC5WV.js} +44 -33
  16. package/dist/{install-sFN_w425.js → install-DxLmASQ5.js} +9 -8
  17. package/dist/{install-doctor-script-DLayXS7q.js → install-doctor-script-4EWviJL8.js} +7 -449
  18. package/dist/{install-github-workflow-Goz-KFqb.js → install-github-workflow-BPznUbbo.js} +10 -6
  19. package/dist/{install-react-doctor-Bq7dfEBI.js → install-react-doctor-Baq9hjoF.js} +47 -45
  20. package/dist/{run-scan-app-DXBMW54J.js → jsx-runtime-DvynQ_wu.js} +281 -2759
  21. package/dist/path-format-DnHpmJwG.js +9 -0
  22. package/dist/{print-branded-header-D_J8Qdsj.js → print-branded-header-DSB6i3kk.js} +4 -4
  23. package/dist/project-analysis-worker.d.ts +1 -0
  24. package/dist/project-analysis-worker.js +51901 -0
  25. package/dist/prompt-runtime-scan-url-BibDt9O1.js +240 -0
  26. package/dist/{read-object-file-Hk79zI91.js → read-object-file-DfWpkle4.js} +4 -4
  27. package/dist/{record-metric-CsQXJewH.js → record-metric-Cckscwg_.js} +3257 -6019
  28. package/dist/report-error-C1YnoRNl.js +64 -0
  29. package/dist/{report-workflow-result-6n_wSqzA.js → report-workflow-result-IdGRoBFN.js} +5 -5
  30. package/dist/{resolve-scope-DP6rYlNO.js → resolve-scope-Bb6fljxd.js} +8 -10
  31. package/dist/{rules-BnIZ4Fq_.js → rules-BcFekwN4.js} +6 -6
  32. package/dist/run-scan-app-CKHynQx8.js +2511 -0
  33. package/dist/runtime-scan/browser-probe.iife.js +855 -0
  34. package/dist/runtime-scan-7Y_mbqEa.js +770 -0
  35. package/dist/sanitize-runtime-url-DzyBLbi3.js +76 -0
  36. package/dist/scan-DQCwBx6p.js +47 -0
  37. package/dist/semver-DFtnEmN-.js +3219 -0
  38. package/dist/{should-fail-scan-gate-DzZuTxim.js → should-fail-scan-gate-BYPsKIde.js} +160 -184
  39. package/dist/{should-skip-prompts-D1GWG2qN.js → should-skip-prompts-CKkbB3rC.js} +4 -4
  40. package/dist/should-use-tui-Crfoao-l.js +25 -0
  41. package/dist/skills/improve-threejs/SKILL.md +102 -0
  42. package/dist/skills/performance/SKILL.md +113 -0
  43. package/dist/skills/react-doctor/SKILL.md +6 -0
  44. package/dist/{unref-stdin-0a_0uwC6.js → telemetry-runtime-DKJyjz9o.js} +57 -33
  45. package/dist/unref-stdin-SK3WfwxS.js +12 -0
  46. package/dist/{version-Bo5QDp7F.js → version-CMkUAGmw.js} +4 -4
  47. package/dist/{why-BFYbtnm8.js → why-By3I6_Ex.js} +4 -4
  48. package/dist/with-run-span-DxFGyW7k.js +110 -0
  49. package/package.json +9 -10
  50. package/dist/chunk-q7NCDQ7-.js +0 -28
  51. package/dist/lsp.js +0 -81145
  52. package/dist/scan-CnnXz78m.js +0 -53
@@ -1,21 +1,25 @@
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]="00cf2dc8-65a5-523e-b3b1-8400324c9d87")}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-CsQXJewH.js";
4
- import { a as setActiveRunTrace, i as recordRunTraceId, o as getTelemetryContext } from "./unref-stdin-0a_0uwC6.js";
5
- import { n as buildFooterLinkLines, r as activeScanAbortRegistry, t as buildSectionDivider } from "./build-section-divider-BsQvH-7I.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-DLayXS7q.js";
7
- import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-Goz-KFqb.js";
8
- import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel, u as toForwardSlashes } from "./resolve-scope-DP6rYlNO.js";
9
- import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-6n_wSqzA.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]="40d3b0cf-26ca-5238-9651-4a1de64ef1f0")}catch(e){}}();
3
+ import { $ as Linter, A as SCAN_RESULT_CACHE_FILENAME, An as gen, At as discoverSupportedSubprojects, B as isNonInteractiveEnvironment, Bn as sync, Cn as error, Ct as collectSourceFileCountsByDirectory, Dn as Diagnostic, Dt as createNodeReadFileLinesSync, En as warn, Et as countOptInProjectRuleSelections, Fn as provide, G as isCiEnvironment, Gt as hashFileContents, Ht as groupBy, In as provideContext, J as Config, Jt as isPathInsideDirectory, K as isCiOrCodingAgentEnvironment, Kn as succeed, Kt as highlighter, Ln as provideService, Mn as map, Mt as filterPathsOutsideDirectories, Nn as orElseSucceed, Nt as filterSourceFiles, On as decodeUnknownSync, Ot as createOxlintSpawnSlots, Pn as promise, Q as LintPartialFailures, Qt as isScanComplete, Rn as runPromise, S as BASELINE_FILES_TEMP_DIR_PREFIX, Sn as Console, T as METRIC, Tn as log, Tt as computeDiagnosticDelta, U as ACTION_INPUT_ENVIRONMENT_VARIABLES, Ut as hasPublishedFixRecipe, Vn as try_, W as detectRunnerOs, Wn as mergeAll, X as Git, Xt as isReactDoctorError, Y as Files, Zn as ParentSpan, _ as VERSION, _n as shouldUseMaintainabilityLayer, _t as buildRuleSeverityControls, an as materializeSourceTree, ar as DIAGNOSTIC_CATEGORY_BUCKETS, at as Progress, bt as canonicalizeUserRuleKey, cn as resolveGithubActionsScoreMetadata, ct as Score, d as isJsonModeActive, dn as resolveScanConcurrency, dr as JSX_DUPLICATION_SOURCE_FILE_PATTERN, dt as SupplyChain, en as listSourceFiles, et as Maintainability, fn as resolveScanTarget, fr as JSX_FILE_PATTERN, gt as buildRuleDocsUrl, hr as SHARE_BASE_URL, in as mapWithConcurrency, it as PerFileLintCacheEnabled, jt as filterDiagnosticsForSurface, l as getRunId, ln as resolveLintBatchOrdering, lr as GIT_SHOW_MAX_BUFFER_BYTES, lt as SidecarLintCacheEnabled, mn as runInspect, mr as OXLINT_NODE_REQUIREMENT, mt as atomicWriteJson, n as recordDistribution, nn as listWorkspacePackages, nt as OxlintConcurrency, on as mergeReactDoctorConfigs, ot as Project, pn as restoreLegacyThrow, pr as MILLISECONDS_PER_SECOND, qt as isMonorepoRoot, r as toSpanAttributes, rt as OxlintSpawnSlots, sn as remainingDeadlineBudgetMs, sr as ENTERPRISE_CONTACT_URL, st as Reporter, t as recordCount, tn as listSourceFilesCooperative, tr as CI_URL, tt as NodeResolver, un as resolveReactDoctorCacheDir, ur as HTML_FILE_PATTERN, v as makeNoopConsole, vn as summarizeDiagnostics, vt as buildSkippedChecks, wt as computeConfigFingerprint, xn as yieldToEventLoop, xr as isFile, xt as classifyFileContext, yr as isDirectory, yt as calculateScore, z as CliInputError, zn as runSync, zt as getDiagnosticRuleIdentity } from "./record-metric-Cckscwg_.js";
4
+ import { t as getTelemetryContext } from "./telemetry-runtime-DKJyjz9o.js";
5
+ import { n as buildFooterLinkLines, t as buildSectionDivider } from "./build-section-divider-DELfSn3p.js";
6
+ import { t as activeScanAbortRegistry } from "./active-scan-abort-registry-DwG6tSPy.js";
7
+ import { t as toForwardSlashes } from "./path-format-DnHpmJwG.js";
8
+ import { C as isRecord, M as cliLogger, S as getPackageJsonPath, a as installReactDoctorScriptStep, c as spinner, k as runGit, n as findNearestPackageDirectory, o as isSpinnerSilent, s as setSpinnerSilent } from "./install-doctor-script-4EWviJL8.js";
9
+ import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-BPznUbbo.js";
10
+ import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel } from "./resolve-scope-Bb6fljxd.js";
11
+ import { n as resetSentryRunState, r as withRunSpan, t as recordSentryProjectContext } from "./with-run-span-DxFGyW7k.js";
12
+ import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-IdGRoBFN.js";
10
13
  import { createRequire } from "node:module";
11
14
  import { spawn } from "node:child_process";
12
15
  import * as path$1 from "node:path";
13
16
  import path from "node:path";
14
- import * as fs$1 from "node:fs";
17
+ import * as fs$2 from "node:fs";
15
18
  import fs, { mkdtempSync, rmSync } from "node:fs";
16
- import crypto, { randomUUID } from "node:crypto";
17
19
  import ts from "typescript";
20
+ import crypto, { randomUUID } from "node:crypto";
18
21
  import { tmpdir } from "node:os";
22
+ import * as fs$1 from "node:fs/promises";
19
23
  import { performance } from "node:perf_hooks";
20
24
  //#region src/cli/utils/apply-observability.ts
21
25
  /**
@@ -83,7 +87,10 @@ const buildSpinnerProgressHandle = (text) => {
83
87
  */
84
88
  const buildRuntimeLayers = (input) => {
85
89
  const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
86
- const deadCodeLayer = input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]);
90
+ const maintainabilityLayer = shouldUseMaintainabilityLayer({
91
+ shouldRunDuplicateJsx: input.shouldRunDeadCode,
92
+ userConfig: input.userConfig
93
+ }) ? Maintainability.layerNode : Maintainability.layerOf([]);
87
94
  const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
88
95
  const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
89
96
  const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
@@ -93,112 +100,11 @@ const buildRuntimeLayers = (input) => {
93
100
  config: input.userConfig,
94
101
  resolvedDirectory: input.directory,
95
102
  configSourceDirectory: input.configSourceDirectory
96
- }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
103
+ }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, maintainabilityLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
97
104
  const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll(baseLayers, succeed(OxlintConcurrency, input.oxlintConcurrency));
98
105
  return input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll(layersWithConcurrency, succeed(OxlintSpawnSlots, input.oxlintSpawnSlots));
99
106
  };
100
107
  //#endregion
101
- //#region src/cli/utils/with-run-span.ts
102
- /**
103
- * Ends the run span without letting a telemetry failure change the run's
104
- * outcome — the invariant every other emit site here already holds.
105
- *
106
- * The failure path is the reason this matters most: a throw from `end` there
107
- * would replace the user's original scan error, so a project-config mistake
108
- * could surface as an exporter stack trace, be reclassified as a crash, and get
109
- * reported as one.
110
- */
111
- const endSpanSafely = (span, exit) => {
112
- try {
113
- span.end(BigInt(Date.now()) * NANOSECONDS_PER_MILLISECOND, exit);
114
- } catch {}
115
- };
116
- /**
117
- * Clears the module-level run-scoped telemetry state — the current scanned
118
- * project and the active run trace. `inspect()` calls this at the start of every
119
- * run and again after a clean one, so a prior or just-finished scan can't attach
120
- * its project tags / trace to a later run or to a non-scan error (e.g.
121
- * inspectAction's post-loop finalize/handoff steps). A thrown scan error skips
122
- * the post-run reset, leaving the state for the command catch to attribute and
123
- * link the crash. Concurrent batch members (`concurrentScan`) never touch this
124
- * state — they neither write nor reset it. Safe to call when telemetry is off
125
- * (the refs are read only when an event is built).
126
- */
127
- const resetSentryRunState = () => {
128
- setSentryProjectInfo(null);
129
- setActiveRunTrace(null);
130
- };
131
- /**
132
- * Runs an inspect invocation inside a root span so each `react-doctor` run is a
133
- * first-class trace with timing and the run snapshot as attributes. The span is
134
- * handed to `run` so the scan's Effect spans parent under it and so the wide
135
- * event can be stamped onto it once the outcome is known.
136
- *
137
- * A no-op pass-through when telemetry is off (`--no-telemetry`, tests, or an
138
- * unconfigured build) — `run` receives `undefined` and no span is created, so
139
- * there's no added exit latency.
140
- *
141
- * The span is created against the shared telemetry context rather than a
142
- * freshly-provided layer, so it lives in the same tracer as the scan program's
143
- * spans; see `telemetry-runtime.ts` for why that matters. It is started and
144
- * ended manually because the body it wraps is a promise-returning imperative
145
- * function, not an Effect — the same shape the Sentry inactive span had.
146
- *
147
- * While the span runs, its trace context is recorded as the active run trace so
148
- * `reportErrorToSentry` can attach a crash thrown during the scan back to this
149
- * trace (errors surface in the command catch, after the span has ended).
150
- *
151
- * A `concurrentScan` (one member of the CLI's multi-project pool) still gets its
152
- * own root span, but skips recording the active run trace — the module-level
153
- * handle has single-scan semantics, and overlapping writers would link a crash
154
- * to an arbitrary sibling's trace.
155
- */
156
- const withRunSpan = async (run, options = {}) => {
157
- const telemetryContext = getTelemetryContext();
158
- if (telemetryContext === null) return run(void 0);
159
- const { tags } = buildSentryScope();
160
- const command = typeof tags.command === "string" ? tags.command : "inspect";
161
- let rootSpan;
162
- try {
163
- rootSpan = runSync(makeSpan(`react-doctor ${command}`, { attributes: toSpanAttributes(tags) }).pipe(provideContext(telemetryContext)));
164
- } catch {
165
- return run(void 0);
166
- }
167
- recordRunTraceId(rootSpan.traceId);
168
- if (options.concurrentScan !== true) setActiveRunTrace({
169
- traceId: rootSpan.traceId,
170
- spanId: rootSpan.spanId,
171
- sampled: rootSpan.sampled
172
- });
173
- try {
174
- const result = await run(rootSpan);
175
- endSpanSafely(rootSpan, void_);
176
- return result;
177
- } catch (error) {
178
- endSpanSafely(rootSpan, fail(error));
179
- throw error;
180
- }
181
- };
182
- /**
183
- * Records the scanned project (discovered in the `beforeLint` hook): remembers
184
- * it for the lazy Sentry error-capture path (`buildSentryScope` folds it into
185
- * exception events) and for per-emit metric attributes, and sets it on the run's
186
- * root span so the trace carries the project shape too. Always cheap — the span
187
- * attributes are skipped when `rootSpan` is absent (telemetry off), and storing
188
- * the info is a plain assignment.
189
- *
190
- * A `concurrentScan` only sets the span attributes: the module-level project ref
191
- * has single-scan semantics, and overlapping writers would stamp events and
192
- * metrics with an arbitrary sibling's project. Wide events keep full attribution
193
- * (they ride the span); per-emit metrics simply omit the project shape during a
194
- * concurrent batch (absent, never wrong).
195
- */
196
- const recordSentryProjectContext = (projectInfo, rootSpan, options = {}) => {
197
- if (options.concurrentScan !== true) setSentryProjectInfo(projectInfo);
198
- if (rootSpan === void 0) return;
199
- for (const [key, value] of Object.entries(toSpanAttributes(buildSentryProjectContext(projectInfo).tags))) rootSpan.attribute(key, value);
200
- };
201
- //#endregion
202
108
  //#region src/cli/utils/diagnostic-grouping.ts
203
109
  const buildRulePriorityMap = (scores) => {
204
110
  const rulePriority = /* @__PURE__ */ new Map();
@@ -309,7 +215,7 @@ const recordScanMetrics = (input) => {
309
215
  recordCount(METRIC.scanCompleted, 1, {
310
216
  mode: input.mode,
311
217
  lint: input.lint,
312
- deadCode: input.deadCode,
218
+ maintainability: input.deadCode,
313
219
  parallel: input.parallel,
314
220
  scoreOnly: input.scoreOnly,
315
221
  didLintFail: input.didLintFail,
@@ -349,7 +255,7 @@ const recordScanMetrics = (input) => {
349
255
  else if (!input.noScore && !input.didLintFail && !input.didDeadCodeFail) recordCount(METRIC.scoreUnavailable, 1, { mode: input.mode });
350
256
  if (input.baselineDegraded) recordCount(METRIC.baselineDegraded, 1);
351
257
  if (input.didLintFail) recordCount(METRIC.lintFailed, 1, { reasonKind: input.lintFailureReasonKind });
352
- if (input.didDeadCodeFail) recordCount(METRIC.deadCodeFailed, 1);
258
+ if (input.didDeadCodeFail) recordCount(METRIC.maintainabilityFailed, 1);
353
259
  for (const check of result.skippedChecks) recordCount(METRIC.scanCheckSkipped, 1, {
354
260
  check,
355
261
  reason: result.skippedCheckReasons?.[check] ?? null
@@ -493,10 +399,16 @@ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
493
399
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
494
400
  "oxlint/package.json",
495
401
  "oxlint-plugin-react-doctor/package.json",
496
- "deslop-js/package.json",
497
402
  "eslint-plugin-react-hooks/package.json"
498
403
  ];
499
404
  const bundledRequire = createRequire(import.meta.url);
405
+ const RULE_PLUGIN_CONTENT_FINGERPRINT = (() => {
406
+ try {
407
+ return `oxlint-plugin-react-doctor#fingerprint=${hashFileContents(bundledRequire.resolve("oxlint-plugin-react-doctor")) ?? "unreadable"}`;
408
+ } catch {
409
+ return "oxlint-plugin-react-doctor#fingerprint=unresolved";
410
+ }
411
+ })();
500
412
  const normalizeForStableJson = (value) => {
501
413
  if (value === null) return null;
502
414
  if (value === void 0) return void 0;
@@ -632,12 +544,12 @@ const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorC
632
544
  const readPersistedCache = (cacheFilePath) => {
633
545
  try {
634
546
  const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
635
- if (!isRecord(parsed) || parsed.version !== 6) return {
636
- version: 6,
547
+ if (!isRecord(parsed) || parsed.version !== 7) return {
548
+ version: 7,
637
549
  entries: []
638
550
  };
639
551
  if (!Array.isArray(parsed.entries)) return {
640
- version: 6,
552
+ version: 7,
641
553
  entries: []
642
554
  };
643
555
  const entries = [];
@@ -652,12 +564,12 @@ const readPersistedCache = (cacheFilePath) => {
652
564
  });
653
565
  }
654
566
  return {
655
- version: 6,
567
+ version: 7,
656
568
  entries
657
569
  };
658
570
  } catch {
659
571
  return {
660
- version: 6,
572
+ version: 7,
661
573
  entries: []
662
574
  };
663
575
  }
@@ -665,7 +577,7 @@ const readPersistedCache = (cacheFilePath) => {
665
577
  /**
666
578
  * The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
667
579
  * cache subsystem: this whole-repo scan cache plus core's per-file lint,
668
- * sidecar, and dead-code caches (their `Context.Reference` defaults read the
580
+ * sidecar caches (their `Context.Reference` defaults read the
669
581
  * same variable). Exported for the wide event's `cache.temperature`
670
582
  * derivation, which reports `"disabled"` instead of `"cold"` when the switch
671
583
  * is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
@@ -688,7 +600,7 @@ const fileFingerprint = (filePath) => {
688
600
  return null;
689
601
  }
690
602
  };
691
- const resolveToolchainFingerprint = (nodeBinaryPath) => {
603
+ const resolveScanResultToolchainFingerprint = (nodeBinaryPath) => {
692
604
  const fingerprints = [];
693
605
  if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
694
606
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
@@ -698,6 +610,7 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
698
610
  } catch {
699
611
  fingerprints.push(`${specifier}=missing`);
700
612
  }
613
+ fingerprints.push(RULE_PLUGIN_CONTENT_FINGERPRINT);
701
614
  return fingerprints;
702
615
  };
703
616
  const buildScanResultCacheKey = (input) => {
@@ -707,14 +620,14 @@ const buildScanResultCacheKey = (input) => {
707
620
  if (repositoryIdentity === null) return null;
708
621
  if (stringifyStableJson(input.userConfig) === null) return null;
709
622
  const cacheKeyJson = stringifyStableJson({
710
- schemaVersion: 6,
623
+ schemaVersion: 7,
711
624
  projectIdentity: resolveProjectIdentity(input.projectDirectory),
712
625
  headSha: repositoryIdentity.headSha,
713
626
  worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
714
627
  dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
715
628
  reactDoctorVersion: input.version,
716
629
  nodeVersion: process.version,
717
- toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
630
+ toolchainFingerprint: resolveScanResultToolchainFingerprint(input.nodeBinaryPath),
718
631
  configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
719
632
  hasConfigOverride: input.hasConfigOverride,
720
633
  configSourceDirectory: input.configSourceDirectory,
@@ -755,7 +668,7 @@ const createScanResultCache = (projectDirectory) => {
755
668
  for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
756
669
  for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
757
670
  atomicWriteJson(cacheFilePath, {
758
- version: 6,
671
+ version: 7,
759
672
  entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
760
673
  });
761
674
  };
@@ -790,8 +703,7 @@ const shouldBlockCi = (diagnostics, blockingLevel) => {
790
703
  /**
791
704
  * Lowercase, key-safe form of a rule category for the `diag.category.*`
792
705
  * telemetry attribute namespace (categories carry spaces / capitals, e.g.
793
- * "Performance" → `performance`, "Dead Code" → `dead_code`). Shared by the
794
- * CLI run event and the editor LSP wide event so both namespace identically.
706
+ * "Performance" → `performance`, "Dead Code" → `dead_code`).
795
707
  */
796
708
  const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
797
709
  //#endregion
@@ -821,12 +733,6 @@ const readEnvBoolean = (name) => {
821
733
  return value.toLowerCase() === "true" || value === "1";
822
734
  };
823
735
  const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
824
- const resolveDeadCodeReuseRatio = (result) => {
825
- if (result.deadCodeCacheHit === true) return 1;
826
- const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
827
- if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
828
- return result.deadCodeCacheHit === false ? 0 : null;
829
- };
830
736
  /**
831
737
  * One queryable cache temperature per scan, derived from the whole stack:
832
738
  *
@@ -834,8 +740,7 @@ const resolveDeadCodeReuseRatio = (result) => {
834
740
  * `wholeRepoCacheHit` flag from the CLI's cachedPayload
835
741
  * branch; no scan work ran).
836
742
  * - `"warm"` — any incremental reuse: per-file lint hits, sidecar
837
- * replays, a dead-code whole-result hit, or dead-code
838
- * summary-cache hits.
743
+ * replays.
839
744
  * - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
840
745
  * off-switch is on. Granular per-cache opt-outs
841
746
  * (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
@@ -844,7 +749,7 @@ const resolveDeadCodeReuseRatio = (result) => {
844
749
  *
845
750
  * `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
846
751
  * the subsystem reuse fractions known this run (lint hit ratio, sidecar
847
- * replay ratio, dead-code reuse), skipping subsystems that never consulted a
752
+ * replay ratio), skipping subsystems that never consulted a
848
753
  * cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
849
754
  * unweighted — the per-subsystem dims stay the precise signal; warmth is the
850
755
  * p50/p90-able summary. Emitted only on the success path.
@@ -857,11 +762,7 @@ const buildCacheAttributes = (input) => {
857
762
  warmth: 1
858
763
  });
859
764
  const result = input.result;
860
- const subsystemReuseRatios = [
861
- ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
862
- ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
863
- resolveDeadCodeReuseRatio(result)
864
- ];
765
+ const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
865
766
  let knownSubsystemCount = 0;
866
767
  let reuseRatioSum = 0;
867
768
  for (const reuseRatio of subsystemReuseRatios) {
@@ -1013,13 +914,7 @@ const buildOutcomeAttributes = (input) => {
1013
914
  sidecarTotalFiles: result.lintSidecarTotalFileCount,
1014
915
  sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
1015
916
  }),
1016
- ...withNamespace("deadCode", {
1017
- failed: input.didDeadCodeFail,
1018
- overlapped: input.deadCodeOverlapped,
1019
- cacheHit: result.deadCodeCacheHit,
1020
- summaryCacheHits: result.deadCodeSummaryCacheHits,
1021
- summaryCacheMisses: result.deadCodeSummaryCacheMisses
1022
- }),
917
+ ...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
1023
918
  ...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
1024
919
  ...withNamespace("securityScan", { failed: input.securityScanFailed }),
1025
920
  ...withNamespace("timing", {
@@ -1062,7 +957,7 @@ const buildScanAttributes = (input) => {
1062
957
  workerCount: input.workerCount,
1063
958
  maxDurationMs: input.maxDurationMs,
1064
959
  lint: input.lint,
1065
- deadCode: input.deadCode,
960
+ maintainability: input.deadCode,
1066
961
  supplyChain: input.supplyChain,
1067
962
  scoreOnly: input.scoreOnly,
1068
963
  noScore: input.noScore,
@@ -1073,6 +968,7 @@ const buildScanAttributes = (input) => {
1073
968
  hasCustomConfig: input.hasCustomConfig,
1074
969
  rulesConfigured: ruleKeys.length,
1075
970
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
971
+ projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
1076
972
  fileCount: input.result?.scannedFileCount,
1077
973
  nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
1078
974
  htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
@@ -1083,7 +979,7 @@ const buildScanAttributes = (input) => {
1083
979
  * Projects a scan into the namespaced attribute set for its root span — the
1084
980
  * canonical per-scan "wide event". Every attribute carries a dotted namespace
1085
981
  * that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
1086
- * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`, `cache.*`,
982
+ * verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
1087
983
  * `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
1088
984
  * tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
1089
985
  * in the Spans dataset. Pure and exported so the projection (outcome
@@ -1322,7 +1218,7 @@ const buildNoScoreMessage = (input) => {
1322
1218
  reason = input.disabledMessage ?? "Score disabled by --no-score.";
1323
1219
  break;
1324
1220
  case "analysis-incomplete":
1325
- reason = "Score not shown because lint or dead-code analysis could not complete.";
1221
+ reason = "Score not shown because lint or maintainability analysis could not complete.";
1326
1222
  break;
1327
1223
  case "api-unavailable":
1328
1224
  reason = "Score unavailable (could not reach the score API).";
@@ -1370,7 +1266,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
1370
1266
  const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
1371
1267
  const readPreviousRuleDumpFileNames = (directory) => {
1372
1268
  try {
1373
- const previous = JSON.parse(fs$1.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
1269
+ const previous = JSON.parse(fs$2.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
1374
1270
  return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
1375
1271
  } catch {
1376
1272
  return /* @__PURE__ */ new Set();
@@ -1378,10 +1274,10 @@ const readPreviousRuleDumpFileNames = (directory) => {
1378
1274
  };
1379
1275
  const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
1380
1276
  const directory = outputDirectory ? path$1.resolve(outputDirectory) : path$1.join(tmpdir(), `react-doctor-${randomUUID()}`);
1381
- if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$1.rmSync(path$1.join(directory, fileName), { force: true });
1382
- fs$1.mkdirSync(directory, { recursive: true });
1383
- for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$1.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
1384
- fs$1.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
1277
+ if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$2.rmSync(path$1.join(directory, fileName), { force: true });
1278
+ fs$2.mkdirSync(directory, { recursive: true });
1279
+ for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$2.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
1280
+ fs$2.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
1385
1281
  return directory;
1386
1282
  };
1387
1283
  //#endregion
@@ -1425,6 +1321,9 @@ const formatElapsedTime = (elapsedMilliseconds) => {
1425
1321
  return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
1426
1322
  };
1427
1323
  //#endregion
1324
+ //#region src/cli/utils/format-skipped-check-label.ts
1325
+ const formatSkippedCheckLabel = (skippedCheck) => skippedCheck === "dead-code" ? "maintainability" : skippedCheck;
1326
+ //#endregion
1428
1327
  //#region src/cli/utils/pluralize.ts
1429
1328
  const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
1430
1329
  //#endregion
@@ -1473,7 +1372,7 @@ const printHeadlessReport = (input) => gen(function* () {
1473
1372
  }
1474
1373
  if (input.skippedChecks.length > 0) {
1475
1374
  yield* log("");
1476
- yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.join(" and ")} checks failed.`));
1375
+ yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.map(formatSkippedCheckLabel).join(" and ")} checks failed.`));
1477
1376
  }
1478
1377
  });
1479
1378
  //#endregion
@@ -1673,12 +1572,39 @@ const renderAndRecordScan = async (input) => {
1673
1572
  didDeadCodeFail: input.payload.didDeadCodeFail,
1674
1573
  supplyChainOverlapTimedOut: input.payload.supplyChainOverlapTimedOut,
1675
1574
  securityScanFailed: input.payload.securityScanFailed,
1676
- deadCodeOverlapped: input.payload.deadCodeOverlapped,
1677
1575
  suppressedRuleCounts: input.payload.suppressedRuleCounts ?? []
1678
1576
  });
1679
1577
  return result;
1680
1578
  };
1681
1579
  //#endregion
1580
+ //#region src/cli/utils/copy-unchanged-baseline-sources.ts
1581
+ const copyUnchangedBaselineSources = async (input) => {
1582
+ const baseMaterializedFiles = new Set(input.baseMaterializedFiles.map(toForwardSlashes));
1583
+ const headChangedFiles = new Set(input.headChangedFiles.map(toForwardSlashes));
1584
+ const untrackedFiles = new Set(input.untrackedFiles.map(toForwardSlashes));
1585
+ const sourceDirectory = path$1.resolve(input.directory);
1586
+ const targetDirectory = path$1.resolve(input.tempDirectory);
1587
+ return (await mapWithConcurrency(input.sourceFiles.filter((filePath) => {
1588
+ const normalizedPath = toForwardSlashes(filePath);
1589
+ return !baseMaterializedFiles.has(normalizedPath) && !headChangedFiles.has(normalizedPath) && !untrackedFiles.has(normalizedPath);
1590
+ }), 32, async (filePath) => {
1591
+ input.signal?.throwIfAborted();
1592
+ if (input.deadlineEpochMs !== null && Date.now() >= input.deadlineEpochMs) return false;
1593
+ const sourcePath = path$1.resolve(sourceDirectory, filePath);
1594
+ const targetPath = path$1.resolve(targetDirectory, filePath);
1595
+ if (!isPathInsideDirectory(sourcePath, sourceDirectory) || !isPathInsideDirectory(targetPath, targetDirectory)) return false;
1596
+ try {
1597
+ if (!(await fs$1.lstat(sourcePath)).isFile()) return false;
1598
+ await fs$1.mkdir(path$1.dirname(targetPath), { recursive: true });
1599
+ await fs$1.copyFile(sourcePath, targetPath);
1600
+ return true;
1601
+ } catch {
1602
+ input.signal?.throwIfAborted();
1603
+ return false;
1604
+ }
1605
+ })).every(Boolean);
1606
+ };
1607
+ //#endregion
1682
1608
  //#region src/cli/utils/read-diagnostic-evidence.ts
1683
1609
  const SOURCE_EXTENSIONS = [
1684
1610
  ".ts",
@@ -2096,7 +2022,6 @@ const resolveMergeBaseRef = async (directory, ref) => {
2096
2022
  const silentConsole$1 = makeNoopConsole();
2097
2023
  const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
2098
2024
  const runBaselineComparison = async (input) => {
2099
- const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
2100
2025
  const baselineIncludePaths = filterPathsOutsideDirectories({
2101
2026
  rootDirectory: input.directory,
2102
2027
  relativePaths: input.options.includePaths,
@@ -2112,12 +2037,24 @@ const runBaselineComparison = async (input) => {
2112
2037
  relativePaths: input.headFiles,
2113
2038
  excludedDirectories: input.options.excludedProjectDirectories
2114
2039
  }) : void 0;
2040
+ const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
2041
+ if (remainingBaselineBudgetMs === 0) return null;
2042
+ const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
2043
+ const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
2044
+ let maintainabilitySourceFiles = [];
2045
+ if (input.options.deadCode) try {
2046
+ maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
2047
+ } catch (error) {
2048
+ if (baselineDeadlineSignal?.aborted) return null;
2049
+ throw error;
2050
+ }
2051
+ const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
2115
2052
  const snapshot = await materializeBaselineFiles({
2116
2053
  directory: input.directory,
2117
2054
  ref: input.baselineRef,
2118
- files: baselineIncludePaths,
2119
- baseFiles: baselineBaseFiles,
2120
- headFiles: baselineHeadFiles,
2055
+ files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
2056
+ baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
2057
+ headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
2121
2058
  tempDirectory: temporaryDirectory
2122
2059
  }).catch((error) => {
2123
2060
  rmSync(temporaryDirectory, {
@@ -2135,22 +2072,47 @@ const runBaselineComparison = async (input) => {
2135
2072
  }
2136
2073
  try {
2137
2074
  if (!snapshot.isComplete) return null;
2075
+ if (input.options.deadCode && !await copyUnchangedBaselineSources({
2076
+ directory: input.directory,
2077
+ sourceFiles: maintainabilitySourceFiles,
2078
+ baseMaterializedFiles: snapshot.materializedFiles,
2079
+ headChangedFiles: snapshot.headFiles,
2080
+ untrackedFiles: snapshot.untrackedFiles,
2081
+ tempDirectory: snapshot.tempDirectory,
2082
+ deadlineEpochMs: input.deadlineEpochMs,
2083
+ signal: input.oxlintRuntime.abortSignal
2084
+ })) return null;
2085
+ const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
2086
+ rootDirectory: input.directory,
2087
+ relativePaths: snapshot.baseFiles,
2088
+ excludedDirectories: input.options.excludedProjectDirectories
2089
+ });
2090
+ const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
2091
+ rootDirectory: input.directory,
2092
+ relativePaths: snapshot.headFiles,
2093
+ excludedDirectories: input.options.excludedProjectDirectories
2094
+ });
2138
2095
  const analyzedHeadFiles = new Set(input.headAnalyzedFiles.map(toForwardSlashes));
2139
- const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
2140
- const expectedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
2096
+ const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
2097
+ const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
2141
2098
  for (const filePath of baselineIncludePaths) {
2142
2099
  const normalizedFilePath = toForwardSlashes(filePath);
2143
2100
  if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
2144
2101
  }
2145
- if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
2102
+ if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
2103
+ const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
2104
+ const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
2105
+ const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
2106
+ 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)));
2107
+ const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
2146
2108
  const runtimeLayers = buildRuntimeLayers({
2147
2109
  directory: snapshot.tempDirectory,
2148
2110
  hasConfigOverride: true,
2149
2111
  userConfig: input.userConfig,
2150
2112
  configSourceDirectory: input.configSourceDirectory,
2151
2113
  projectInfoOverride: input.headProjectInfo,
2152
- shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath,
2153
- shouldRunDeadCode: false,
2114
+ shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
2115
+ shouldRunDeadCode: input.options.deadCode,
2154
2116
  shouldRunSupplyChain: input.options.supplyChain,
2155
2117
  shouldComputeScore: false,
2156
2118
  shouldShowProgressSpinners: false,
@@ -2159,7 +2121,8 @@ const runBaselineComparison = async (input) => {
2159
2121
  });
2160
2122
  const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
2161
2123
  directory: snapshot.tempDirectory,
2162
- includePaths: snapshot.materializedFiles,
2124
+ includePaths: baseIncludePaths,
2125
+ maintainabilityFocusPaths,
2163
2126
  customRulesOnly: input.options.customRulesOnly,
2164
2127
  respectInlineDisables: input.options.respectInlineDisables,
2165
2128
  warnings: input.options.warnings,
@@ -2168,7 +2131,7 @@ const runBaselineComparison = async (input) => {
2168
2131
  includedTags: input.options.includedTags,
2169
2132
  includeTagDefaults: input.options.includeTagDefaults,
2170
2133
  nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
2171
- runDeadCode: false,
2134
+ runDeadCode: input.options.deadCode,
2172
2135
  isCi: input.options.isCi,
2173
2136
  doctorVersion: VERSION,
2174
2137
  runId: getRunId(),
@@ -2176,10 +2139,16 @@ const runBaselineComparison = async (input) => {
2176
2139
  suppressScanSummary: true,
2177
2140
  supplyChainManifestChanged: input.options.supplyChainManifestChanged,
2178
2141
  deadlineEpochMs: input.deadlineEpochMs ?? void 0,
2179
- signal: input.oxlintRuntime.abortSignal
2142
+ signal: input.oxlintRuntime.abortSignal,
2143
+ excludedProjectDirectories: baselineExcludedProjectDirectories,
2144
+ retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
2180
2145
  }, {}).pipe(provide(runtimeLayers), provideService(PerFileLintCacheEnabled, false), provideService(SidecarLintCacheEnabled, false), provideService(Console, silentConsole$1))), { signal: input.oxlintRuntime.abortSignal });
2181
- if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
2182
- const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
2146
+ if (baseOutput.didLintFail || baseOutput.didDeadCodeFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
2147
+ const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
2148
+ rootDirectory: input.directory,
2149
+ relativePaths: snapshot.untrackedFiles,
2150
+ excludedDirectories: input.options.excludedProjectDirectories
2151
+ }).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
2183
2152
  const diagnosticDelta = computeDiagnosticDelta({
2184
2153
  headDiagnostics: input.headDiagnostics,
2185
2154
  baseDiagnostics: baseOutput.diagnostics,
@@ -2352,6 +2321,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
2352
2321
  directory,
2353
2322
  precomputedSourceFileCount: options.precomputedSourceFileCount,
2354
2323
  includePaths: options.includePaths,
2324
+ changedLineRanges: options.changedLineRanges ?? void 0,
2355
2325
  customRulesOnly: options.customRulesOnly,
2356
2326
  respectInlineDisables: options.respectInlineDisables,
2357
2327
  warnings: options.warnings,
@@ -2382,7 +2352,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
2382
2352
  else runSync(error(highlighter.error(lintFailureReason)));
2383
2353
  let inspectDiagnostics = output.diagnostics;
2384
2354
  let baselineDelta;
2385
- if (options.baseline && isDiffMode && !didLintFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
2355
+ if (options.baseline && isDiffMode && !didLintFail && !output.didDeadCodeFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
2386
2356
  const comparison = await runBaselineComparison({
2387
2357
  directory,
2388
2358
  options,
@@ -2535,13 +2505,13 @@ const resolveCursorBundledNode = () => {
2535
2505
  if (!localAppData) return null;
2536
2506
  const cursorAgentRoot = path$1.join(localAppData, "cursor-agent", "versions");
2537
2507
  try {
2538
- const versions = fs$1.readdirSync(cursorAgentRoot);
2508
+ const versions = fs$2.readdirSync(cursorAgentRoot);
2539
2509
  if (versions.length === 0) return null;
2540
2510
  const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
2541
2511
  for (const version of sortedVersions) try {
2542
2512
  const bundledNodePath = path$1.join(cursorAgentRoot, version, "node.exe");
2543
2513
  const entryScriptPath = path$1.join(cursorAgentRoot, version, "index.js");
2544
- if (fs$1.statSync(bundledNodePath).isFile() && fs$1.statSync(entryScriptPath).isFile()) return {
2514
+ if (fs$2.statSync(bundledNodePath).isFile() && fs$2.statSync(entryScriptPath).isFile()) return {
2545
2515
  command: bundledNodePath,
2546
2516
  args: [entryScriptPath]
2547
2517
  };
@@ -2555,15 +2525,15 @@ const resolveWindowsCmdEntryScript = (command) => {
2555
2525
  for (const directory of pathDirectories) {
2556
2526
  const cmdFilePath = path$1.join(directory, `${command}.cmd`);
2557
2527
  try {
2558
- if (!fs$1.statSync(cmdFilePath).isFile()) continue;
2528
+ if (!fs$2.statSync(cmdFilePath).isFile()) continue;
2559
2529
  } catch {
2560
2530
  continue;
2561
2531
  }
2562
2532
  try {
2563
- const entryScriptMatch = fs$1.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
2533
+ const entryScriptMatch = fs$2.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
2564
2534
  if (!entryScriptMatch) continue;
2565
2535
  const resolvedScriptPath = path$1.resolve(directory, entryScriptMatch[1]);
2566
- if (fs$1.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
2536
+ if (fs$2.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
2567
2537
  } catch {}
2568
2538
  }
2569
2539
  return null;
@@ -2889,7 +2859,7 @@ const buildCodeFrame = (input) => {
2889
2859
  const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
2890
2860
  let source;
2891
2861
  try {
2892
- source = fs$1.readFileSync(absolutePath, "utf8");
2862
+ source = fs$2.readFileSync(absolutePath, "utf8");
2893
2863
  } catch {
2894
2864
  return null;
2895
2865
  }
@@ -2910,7 +2880,7 @@ const buildCodeFrame = (input) => {
2910
2880
  const discoverWorkspacePackages = (rootDirectory) => {
2911
2881
  const hasRootPackageJson = isFile(path$1.join(rootDirectory, "package.json"));
2912
2882
  const packages = listWorkspacePackages(rootDirectory);
2913
- if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverReactSubprojects(rootDirectory);
2883
+ if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverSupportedSubprojects(rootDirectory);
2914
2884
  return packages;
2915
2885
  };
2916
2886
  const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
@@ -3002,6 +2972,12 @@ const validateIncludeUntrackedScope = (includeUntracked, scope) => {
3002
2972
  if (!includeUntracked || scope !== void 0 && scope !== "full") return;
3003
2973
  throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
3004
2974
  };
2975
+ const validateFilePathSelectionFlags = (flags) => {
2976
+ if (flags.staged || flags.changedFilesFrom !== void 0) throw new CliInputError("Cannot combine file path arguments with --staged or --changed-files-from. Use one file source.");
2977
+ if (usedDiffAlias(flags)) throw new CliInputError("Cannot combine file path arguments with --diff. File path arguments use the files scope.");
2978
+ if (usedScope(flags) && flags.scope !== "files") throw new CliInputError(`Cannot combine file path arguments with --scope ${flags.scope}. File path arguments use --scope files.`);
2979
+ if (flags.base !== void 0) throw new CliInputError("Cannot combine file path arguments with --base. The selected files do not need a Git base.");
2980
+ };
3005
2981
  const validateModeFlags = (flags) => {
3006
2982
  if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
3007
2983
  if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
@@ -3026,7 +3002,7 @@ const shouldFailScanGate = (input) => {
3026
3002
  return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
3027
3003
  };
3028
3004
  //#endregion
3029
- 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 };
3005
+ export { inspect as A, filterDiagnosticsByCategories as B, CLI_AGENT_BINARIES as C, collectProjectSourceFileCounts as D, buildHandoffPayload as E, formatElapsedTime as F, buildSortedRuleGroups as G, buildEmptyReportMessage as H, printFooter as I, formatFixRecipeLine as K, printDiagnosticsDump as L, printHeadlessReport as M, pluralize as N, deduplicateProjectScans as O, formatSkippedCheckLabel as P, formatDiagnosticSite as R, detectLaunchableAgents as S, launchCliAgent as T, hasLintHardFailure as U, buildNoScoreMessage as V, buildRulePriorityMap as W, resolveWorkspaceDeadCodeOwner as _, validateModeFlags as a, countUniqueScannedFiles as b, discoverWorkspacePackages as c, resolveProjectScan as d, resolveProjectChangedLineRanges as f, retryMissingProjectScores as g, resolveProjectRelativeDirectory as h, validateIncludeUntrackedScope as i, resolveMergeBaseRef as j, createInvocationInspect as k, selectProjects as l, resolveProjectSourceFilePaths as m, formatSkippedProjectsMessage as n, projectManifestChanged as o, resolveProjectDiffIncludePaths as p, formatLearnMoreLine as q, validateFilePathSelectionFlags as r, partitionProjectScanOutcomes as s, shouldFailScanGate as t, buildCodeFrame as u, resolveCliInspectOptions as v, copyToClipboard as w, setUpGitHubActions as x, filterScansForSurface as y, hasIncompleteScoreAnalysis as z };
3030
3006
 
3031
- //# sourceMappingURL=should-fail-scan-gate-DzZuTxim.js.map
3032
- //# debugId=00cf2dc8-65a5-523e-b3b1-8400324c9d87
3007
+ //# sourceMappingURL=should-fail-scan-gate-BYPsKIde.js.map
3008
+ //# debugId=40d3b0cf-26ca-5238-9651-4a1de64ef1f0