react-doctor 0.9.11 → 0.9.12-dev.013f737

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 (50) hide show
  1. package/README.md +28 -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-sLcWe1hO.js → build-section-divider-CU-16tRj.js} +10 -25
  6. package/dist/{ci-DsrrD52i.js → ci-Dh7pP7KY.js} +13 -12
  7. package/dist/cli.js +84 -32
  8. package/dist/{design-CP25gyyg.js → design-BzrSnwUm.js} +4 -4
  9. package/dist/{devtools-DPRVRu3S.js → devtools-BiC-AP3B.js} +4 -4
  10. package/dist/{exit-gracefully-CVVkBxDS.js → exit-gracefully-D6bYEaJY.js} +15 -9
  11. package/dist/get-east-asian-width-Cglw13G7.js +444 -0
  12. package/dist/{report-error-CAEixMg1.js → handle-error-B-sefMQJ.js} +18 -68
  13. package/dist/index.d.ts +70 -61
  14. package/dist/index.js +55254 -54819
  15. package/dist/{inspect-D6y8oLtg.js → inspect-B91jqgDd.js} +26 -24
  16. package/dist/{install-afMFGeTB.js → install-DwdDJZLo.js} +9 -8
  17. package/dist/{install-doctor-script-Be7Mv3-l.js → install-doctor-script-C_02th1a.js} +7 -449
  18. package/dist/{install-github-workflow-Dev2F6Ea.js → install-github-workflow-CtrzHuoG.js} +6 -6
  19. package/dist/{install-react-doctor-DIPVunY4.js → install-react-doctor-Bxmp24zo.js} +47 -45
  20. package/dist/{run-scan-app-BGzlyiIB.js → jsx-runtime-DvQYHYIV.js} +281 -2726
  21. package/dist/{print-branded-header-D5jLdo4p.js → print-branded-header-ZI6bzOzV.js} +4 -4
  22. package/dist/project-analysis-worker.d.ts +1 -0
  23. package/dist/project-analysis-worker.js +51895 -0
  24. package/dist/prompt-runtime-scan-url-E76H6vtX.js +240 -0
  25. package/dist/{read-object-file-CEh1CqnD.js → read-object-file-DwgF3AUW.js} +4 -4
  26. package/dist/{record-metric-CJxSHfJD.js → record-metric-J1L5BmaF.js} +3180 -6009
  27. package/dist/report-error-CGlDARUr.js +64 -0
  28. package/dist/{report-workflow-result-C0jbKH8p.js → report-workflow-result-BEhF-Fk5.js} +5 -5
  29. package/dist/{resolve-scope-DS7rcRRz.js → resolve-scope-BryzWK4p.js} +6 -6
  30. package/dist/{rules-BJynymuU.js → rules-148RYdKo.js} +6 -6
  31. package/dist/run-scan-app-YPqscslC.js +2509 -0
  32. package/dist/runtime-scan/browser-probe.iife.js +855 -0
  33. package/dist/runtime-scan-DxEqnav4.js +770 -0
  34. package/dist/sanitize-runtime-url-Dy4tyhb1.js +76 -0
  35. package/dist/{scan-CgPRG6_z.js → scan-DjBH5dqT.js} +10 -22
  36. package/dist/semver-DFtnEmN-.js +3219 -0
  37. package/dist/{should-fail-scan-gate-4jf7ZRdL.js → should-fail-scan-gate-Nw22UN5m.js} +199 -195
  38. package/dist/{should-skip-prompts-C78r7Ww7.js → should-skip-prompts-C2oc3T4l.js} +4 -4
  39. package/dist/should-use-tui-BSvLC2-t.js +25 -0
  40. package/dist/skills/improve-threejs/SKILL.md +102 -0
  41. package/dist/skills/performance/SKILL.md +113 -0
  42. package/dist/skills/react-doctor/SKILL.md +6 -0
  43. package/dist/{unref-stdin-Dc8ipkaw.js → telemetry-runtime-DlBJxPXW.js} +57 -33
  44. package/dist/unref-stdin-BKT_s38a.js +12 -0
  45. package/dist/{version-DDrwsTas.js → version-wYwu6AvJ.js} +4 -4
  46. package/dist/{why-BRNrKY1b.js → why-CmDRbdLA.js} +4 -4
  47. package/dist/with-run-span--LC47bQS.js +110 -0
  48. package/package.json +9 -12
  49. package/dist/chunk-q7NCDQ7-.js +0 -28
  50. package/dist/lsp.js +0 -81114
@@ -1,23 +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]="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]="7decec60-ced4-543d-a6e9-381f8d779223")}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-J1L5BmaF.js";
4
+ import { t as getTelemetryContext } from "./telemetry-runtime-DlBJxPXW.js";
5
+ import { n as buildFooterLinkLines, t as buildSectionDivider } from "./build-section-divider-CU-16tRj.js";
6
+ import { t as activeScanAbortRegistry } from "./active-scan-abort-registry-DwG6tSPy.js";
7
+ 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-C_02th1a.js";
8
+ import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-CtrzHuoG.js";
9
+ import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel, u as toForwardSlashes } from "./resolve-scope-BryzWK4p.js";
10
+ import { n as resetSentryRunState, r as withRunSpan, t as recordSentryProjectContext } from "./with-run-span--LC47bQS.js";
11
+ import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-BEhF-Fk5.js";
10
12
  import { createRequire } from "node:module";
11
13
  import { spawn } from "node:child_process";
12
14
  import * as path$1 from "node:path";
13
15
  import path from "node:path";
14
- import * as fs$1 from "node:fs";
16
+ import * as fs$2 from "node:fs";
15
17
  import fs, { mkdtempSync, rmSync } from "node:fs";
16
- import crypto, { randomUUID } from "node:crypto";
17
18
  import ts from "typescript";
19
+ import crypto, { randomUUID } from "node:crypto";
18
20
  import { tmpdir } from "node:os";
21
+ import * as fs$1 from "node:fs/promises";
19
22
  import { performance } from "node:perf_hooks";
20
- import { codeFrameColumns } from "@babel/code-frame";
21
23
  //#region src/cli/utils/apply-observability.ts
22
24
  /**
23
25
  * Installs the tracing backend for the inspect program.
@@ -84,7 +86,10 @@ const buildSpinnerProgressHandle = (text) => {
84
86
  */
85
87
  const buildRuntimeLayers = (input) => {
86
88
  const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
87
- const deadCodeLayer = input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]);
89
+ const maintainabilityLayer = shouldUseMaintainabilityLayer({
90
+ shouldRunDuplicateJsx: input.shouldRunDeadCode,
91
+ userConfig: input.userConfig
92
+ }) ? Maintainability.layerNode : Maintainability.layerOf([]);
88
93
  const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
89
94
  const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
90
95
  const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
@@ -94,112 +99,11 @@ const buildRuntimeLayers = (input) => {
94
99
  config: input.userConfig,
95
100
  resolvedDirectory: input.directory,
96
101
  configSourceDirectory: input.configSourceDirectory
97
- }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
102
+ }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, maintainabilityLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
98
103
  const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll(baseLayers, succeed(OxlintConcurrency, input.oxlintConcurrency));
99
104
  return input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll(layersWithConcurrency, succeed(OxlintSpawnSlots, input.oxlintSpawnSlots));
100
105
  };
101
106
  //#endregion
102
- //#region src/cli/utils/with-run-span.ts
103
- /**
104
- * Ends the run span without letting a telemetry failure change the run's
105
- * outcome — the invariant every other emit site here already holds.
106
- *
107
- * The failure path is the reason this matters most: a throw from `end` there
108
- * would replace the user's original scan error, so a project-config mistake
109
- * could surface as an exporter stack trace, be reclassified as a crash, and get
110
- * reported as one.
111
- */
112
- const endSpanSafely = (span, exit) => {
113
- try {
114
- span.end(BigInt(Date.now()) * NANOSECONDS_PER_MILLISECOND, exit);
115
- } catch {}
116
- };
117
- /**
118
- * Clears the module-level run-scoped telemetry state — the current scanned
119
- * project and the active run trace. `inspect()` calls this at the start of every
120
- * run and again after a clean one, so a prior or just-finished scan can't attach
121
- * its project tags / trace to a later run or to a non-scan error (e.g.
122
- * inspectAction's post-loop finalize/handoff steps). A thrown scan error skips
123
- * the post-run reset, leaving the state for the command catch to attribute and
124
- * link the crash. Concurrent batch members (`concurrentScan`) never touch this
125
- * state — they neither write nor reset it. Safe to call when telemetry is off
126
- * (the refs are read only when an event is built).
127
- */
128
- const resetSentryRunState = () => {
129
- setSentryProjectInfo(null);
130
- setActiveRunTrace(null);
131
- };
132
- /**
133
- * Runs an inspect invocation inside a root span so each `react-doctor` run is a
134
- * first-class trace with timing and the run snapshot as attributes. The span is
135
- * handed to `run` so the scan's Effect spans parent under it and so the wide
136
- * event can be stamped onto it once the outcome is known.
137
- *
138
- * A no-op pass-through when telemetry is off (`--no-telemetry`, tests, or an
139
- * unconfigured build) — `run` receives `undefined` and no span is created, so
140
- * there's no added exit latency.
141
- *
142
- * The span is created against the shared telemetry context rather than a
143
- * freshly-provided layer, so it lives in the same tracer as the scan program's
144
- * spans; see `telemetry-runtime.ts` for why that matters. It is started and
145
- * ended manually because the body it wraps is a promise-returning imperative
146
- * function, not an Effect — the same shape the Sentry inactive span had.
147
- *
148
- * While the span runs, its trace context is recorded as the active run trace so
149
- * `reportErrorToSentry` can attach a crash thrown during the scan back to this
150
- * trace (errors surface in the command catch, after the span has ended).
151
- *
152
- * A `concurrentScan` (one member of the CLI's multi-project pool) still gets its
153
- * own root span, but skips recording the active run trace — the module-level
154
- * handle has single-scan semantics, and overlapping writers would link a crash
155
- * to an arbitrary sibling's trace.
156
- */
157
- const withRunSpan = async (run, options = {}) => {
158
- const telemetryContext = getTelemetryContext();
159
- if (telemetryContext === null) return run(void 0);
160
- const { tags } = buildSentryScope();
161
- const command = typeof tags.command === "string" ? tags.command : "inspect";
162
- let rootSpan;
163
- try {
164
- rootSpan = runSync(makeSpan(`react-doctor ${command}`, { attributes: toSpanAttributes(tags) }).pipe(provideContext(telemetryContext)));
165
- } catch {
166
- return run(void 0);
167
- }
168
- recordRunTraceId(rootSpan.traceId);
169
- if (options.concurrentScan !== true) setActiveRunTrace({
170
- traceId: rootSpan.traceId,
171
- spanId: rootSpan.spanId,
172
- sampled: rootSpan.sampled
173
- });
174
- try {
175
- const result = await run(rootSpan);
176
- endSpanSafely(rootSpan, void_);
177
- return result;
178
- } catch (error) {
179
- endSpanSafely(rootSpan, fail(error));
180
- throw error;
181
- }
182
- };
183
- /**
184
- * Records the scanned project (discovered in the `beforeLint` hook): remembers
185
- * it for the lazy Sentry error-capture path (`buildSentryScope` folds it into
186
- * exception events) and for per-emit metric attributes, and sets it on the run's
187
- * root span so the trace carries the project shape too. Always cheap — the span
188
- * attributes are skipped when `rootSpan` is absent (telemetry off), and storing
189
- * the info is a plain assignment.
190
- *
191
- * A `concurrentScan` only sets the span attributes: the module-level project ref
192
- * has single-scan semantics, and overlapping writers would stamp events and
193
- * metrics with an arbitrary sibling's project. Wide events keep full attribution
194
- * (they ride the span); per-emit metrics simply omit the project shape during a
195
- * concurrent batch (absent, never wrong).
196
- */
197
- const recordSentryProjectContext = (projectInfo, rootSpan, options = {}) => {
198
- if (options.concurrentScan !== true) setSentryProjectInfo(projectInfo);
199
- if (rootSpan === void 0) return;
200
- for (const [key, value] of Object.entries(toSpanAttributes(buildSentryProjectContext(projectInfo).tags))) rootSpan.attribute(key, value);
201
- };
202
- //#endregion
203
107
  //#region src/cli/utils/diagnostic-grouping.ts
204
108
  const buildRulePriorityMap = (scores) => {
205
109
  const rulePriority = /* @__PURE__ */ new Map();
@@ -310,7 +214,7 @@ const recordScanMetrics = (input) => {
310
214
  recordCount(METRIC.scanCompleted, 1, {
311
215
  mode: input.mode,
312
216
  lint: input.lint,
313
- deadCode: input.deadCode,
217
+ maintainability: input.deadCode,
314
218
  parallel: input.parallel,
315
219
  scoreOnly: input.scoreOnly,
316
220
  didLintFail: input.didLintFail,
@@ -350,7 +254,7 @@ const recordScanMetrics = (input) => {
350
254
  else if (!input.noScore && !input.didLintFail && !input.didDeadCodeFail) recordCount(METRIC.scoreUnavailable, 1, { mode: input.mode });
351
255
  if (input.baselineDegraded) recordCount(METRIC.baselineDegraded, 1);
352
256
  if (input.didLintFail) recordCount(METRIC.lintFailed, 1, { reasonKind: input.lintFailureReasonKind });
353
- if (input.didDeadCodeFail) recordCount(METRIC.deadCodeFailed, 1);
257
+ if (input.didDeadCodeFail) recordCount(METRIC.maintainabilityFailed, 1);
354
258
  for (const check of result.skippedChecks) recordCount(METRIC.scanCheckSkipped, 1, {
355
259
  check,
356
260
  reason: result.skippedCheckReasons?.[check] ?? null
@@ -494,10 +398,16 @@ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
494
398
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
495
399
  "oxlint/package.json",
496
400
  "oxlint-plugin-react-doctor/package.json",
497
- "deslop-js/package.json",
498
401
  "eslint-plugin-react-hooks/package.json"
499
402
  ];
500
403
  const bundledRequire = createRequire(import.meta.url);
404
+ const RULE_PLUGIN_CONTENT_FINGERPRINT = (() => {
405
+ try {
406
+ return `oxlint-plugin-react-doctor#fingerprint=${hashFileContents(bundledRequire.resolve("oxlint-plugin-react-doctor")) ?? "unreadable"}`;
407
+ } catch {
408
+ return "oxlint-plugin-react-doctor#fingerprint=unresolved";
409
+ }
410
+ })();
501
411
  const normalizeForStableJson = (value) => {
502
412
  if (value === null) return null;
503
413
  if (value === void 0) return void 0;
@@ -633,12 +543,12 @@ const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorC
633
543
  const readPersistedCache = (cacheFilePath) => {
634
544
  try {
635
545
  const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
636
- if (!isRecord(parsed) || parsed.version !== 6) return {
637
- version: 6,
546
+ if (!isRecord(parsed) || parsed.version !== 7) return {
547
+ version: 7,
638
548
  entries: []
639
549
  };
640
550
  if (!Array.isArray(parsed.entries)) return {
641
- version: 6,
551
+ version: 7,
642
552
  entries: []
643
553
  };
644
554
  const entries = [];
@@ -653,12 +563,12 @@ const readPersistedCache = (cacheFilePath) => {
653
563
  });
654
564
  }
655
565
  return {
656
- version: 6,
566
+ version: 7,
657
567
  entries
658
568
  };
659
569
  } catch {
660
570
  return {
661
- version: 6,
571
+ version: 7,
662
572
  entries: []
663
573
  };
664
574
  }
@@ -666,7 +576,7 @@ const readPersistedCache = (cacheFilePath) => {
666
576
  /**
667
577
  * The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
668
578
  * 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
579
+ * sidecar caches (their `Context.Reference` defaults read the
670
580
  * same variable). Exported for the wide event's `cache.temperature`
671
581
  * derivation, which reports `"disabled"` instead of `"cold"` when the switch
672
582
  * is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
@@ -689,7 +599,7 @@ const fileFingerprint = (filePath) => {
689
599
  return null;
690
600
  }
691
601
  };
692
- const resolveToolchainFingerprint = (nodeBinaryPath) => {
602
+ const resolveScanResultToolchainFingerprint = (nodeBinaryPath) => {
693
603
  const fingerprints = [];
694
604
  if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
695
605
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
@@ -699,6 +609,7 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
699
609
  } catch {
700
610
  fingerprints.push(`${specifier}=missing`);
701
611
  }
612
+ fingerprints.push(RULE_PLUGIN_CONTENT_FINGERPRINT);
702
613
  return fingerprints;
703
614
  };
704
615
  const buildScanResultCacheKey = (input) => {
@@ -708,14 +619,14 @@ const buildScanResultCacheKey = (input) => {
708
619
  if (repositoryIdentity === null) return null;
709
620
  if (stringifyStableJson(input.userConfig) === null) return null;
710
621
  const cacheKeyJson = stringifyStableJson({
711
- schemaVersion: 6,
622
+ schemaVersion: 7,
712
623
  projectIdentity: resolveProjectIdentity(input.projectDirectory),
713
624
  headSha: repositoryIdentity.headSha,
714
625
  worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
715
626
  dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
716
627
  reactDoctorVersion: input.version,
717
628
  nodeVersion: process.version,
718
- toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
629
+ toolchainFingerprint: resolveScanResultToolchainFingerprint(input.nodeBinaryPath),
719
630
  configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
720
631
  hasConfigOverride: input.hasConfigOverride,
721
632
  configSourceDirectory: input.configSourceDirectory,
@@ -756,7 +667,7 @@ const createScanResultCache = (projectDirectory) => {
756
667
  for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
757
668
  for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
758
669
  atomicWriteJson(cacheFilePath, {
759
- version: 6,
670
+ version: 7,
760
671
  entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
761
672
  });
762
673
  };
@@ -791,8 +702,7 @@ const shouldBlockCi = (diagnostics, blockingLevel) => {
791
702
  /**
792
703
  * Lowercase, key-safe form of a rule category for the `diag.category.*`
793
704
  * 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.
705
+ * "Performance" → `performance`, "Dead Code" → `dead_code`).
796
706
  */
797
707
  const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
798
708
  //#endregion
@@ -822,12 +732,6 @@ const readEnvBoolean = (name) => {
822
732
  return value.toLowerCase() === "true" || value === "1";
823
733
  };
824
734
  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
735
  /**
832
736
  * One queryable cache temperature per scan, derived from the whole stack:
833
737
  *
@@ -835,8 +739,7 @@ const resolveDeadCodeReuseRatio = (result) => {
835
739
  * `wholeRepoCacheHit` flag from the CLI's cachedPayload
836
740
  * branch; no scan work ran).
837
741
  * - `"warm"` — any incremental reuse: per-file lint hits, sidecar
838
- * replays, a dead-code whole-result hit, or dead-code
839
- * summary-cache hits.
742
+ * replays.
840
743
  * - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
841
744
  * off-switch is on. Granular per-cache opt-outs
842
745
  * (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
@@ -845,7 +748,7 @@ const resolveDeadCodeReuseRatio = (result) => {
845
748
  *
846
749
  * `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
847
750
  * the subsystem reuse fractions known this run (lint hit ratio, sidecar
848
- * replay ratio, dead-code reuse), skipping subsystems that never consulted a
751
+ * replay ratio), skipping subsystems that never consulted a
849
752
  * cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
850
753
  * unweighted — the per-subsystem dims stay the precise signal; warmth is the
851
754
  * p50/p90-able summary. Emitted only on the success path.
@@ -858,11 +761,7 @@ const buildCacheAttributes = (input) => {
858
761
  warmth: 1
859
762
  });
860
763
  const result = input.result;
861
- const subsystemReuseRatios = [
862
- ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
863
- ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
864
- resolveDeadCodeReuseRatio(result)
865
- ];
764
+ const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
866
765
  let knownSubsystemCount = 0;
867
766
  let reuseRatioSum = 0;
868
767
  for (const reuseRatio of subsystemReuseRatios) {
@@ -1014,13 +913,7 @@ const buildOutcomeAttributes = (input) => {
1014
913
  sidecarTotalFiles: result.lintSidecarTotalFileCount,
1015
914
  sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
1016
915
  }),
1017
- ...withNamespace("deadCode", {
1018
- failed: input.didDeadCodeFail,
1019
- overlapped: input.deadCodeOverlapped,
1020
- cacheHit: result.deadCodeCacheHit,
1021
- summaryCacheHits: result.deadCodeSummaryCacheHits,
1022
- summaryCacheMisses: result.deadCodeSummaryCacheMisses
1023
- }),
916
+ ...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
1024
917
  ...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
1025
918
  ...withNamespace("securityScan", { failed: input.securityScanFailed }),
1026
919
  ...withNamespace("timing", {
@@ -1063,7 +956,7 @@ const buildScanAttributes = (input) => {
1063
956
  workerCount: input.workerCount,
1064
957
  maxDurationMs: input.maxDurationMs,
1065
958
  lint: input.lint,
1066
- deadCode: input.deadCode,
959
+ maintainability: input.deadCode,
1067
960
  supplyChain: input.supplyChain,
1068
961
  scoreOnly: input.scoreOnly,
1069
962
  noScore: input.noScore,
@@ -1074,6 +967,7 @@ const buildScanAttributes = (input) => {
1074
967
  hasCustomConfig: input.hasCustomConfig,
1075
968
  rulesConfigured: ruleKeys.length,
1076
969
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
970
+ projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
1077
971
  fileCount: input.result?.scannedFileCount,
1078
972
  nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
1079
973
  htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
@@ -1084,7 +978,7 @@ const buildScanAttributes = (input) => {
1084
978
  * Projects a scan into the namespaced attribute set for its root span — the
1085
979
  * canonical per-scan "wide event". Every attribute carries a dotted namespace
1086
980
  * that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
1087
- * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`, `cache.*`,
981
+ * verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
1088
982
  * `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
1089
983
  * tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
1090
984
  * in the Spans dataset. Pure and exported so the projection (outcome
@@ -1323,7 +1217,7 @@ const buildNoScoreMessage = (input) => {
1323
1217
  reason = input.disabledMessage ?? "Score disabled by --no-score.";
1324
1218
  break;
1325
1219
  case "analysis-incomplete":
1326
- reason = "Score not shown because lint or dead-code analysis could not complete.";
1220
+ reason = "Score not shown because lint or maintainability analysis could not complete.";
1327
1221
  break;
1328
1222
  case "api-unavailable":
1329
1223
  reason = "Score unavailable (could not reach the score API).";
@@ -1371,7 +1265,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
1371
1265
  const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
1372
1266
  const readPreviousRuleDumpFileNames = (directory) => {
1373
1267
  try {
1374
- const previous = JSON.parse(fs$1.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
1268
+ const previous = JSON.parse(fs$2.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
1375
1269
  return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
1376
1270
  } catch {
1377
1271
  return /* @__PURE__ */ new Set();
@@ -1379,10 +1273,10 @@ const readPreviousRuleDumpFileNames = (directory) => {
1379
1273
  };
1380
1274
  const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
1381
1275
  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));
1276
+ if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$2.rmSync(path$1.join(directory, fileName), { force: true });
1277
+ fs$2.mkdirSync(directory, { recursive: true });
1278
+ for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$2.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
1279
+ fs$2.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
1386
1280
  return directory;
1387
1281
  };
1388
1282
  //#endregion
@@ -1426,6 +1320,9 @@ const formatElapsedTime = (elapsedMilliseconds) => {
1426
1320
  return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
1427
1321
  };
1428
1322
  //#endregion
1323
+ //#region src/cli/utils/format-skipped-check-label.ts
1324
+ const formatSkippedCheckLabel = (skippedCheck) => skippedCheck === "dead-code" ? "maintainability" : skippedCheck;
1325
+ //#endregion
1429
1326
  //#region src/cli/utils/pluralize.ts
1430
1327
  const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
1431
1328
  //#endregion
@@ -1474,7 +1371,7 @@ const printHeadlessReport = (input) => gen(function* () {
1474
1371
  }
1475
1372
  if (input.skippedChecks.length > 0) {
1476
1373
  yield* log("");
1477
- yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.join(" and ")} checks failed.`));
1374
+ yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.map(formatSkippedCheckLabel).join(" and ")} checks failed.`));
1478
1375
  }
1479
1376
  });
1480
1377
  //#endregion
@@ -1674,12 +1571,39 @@ const renderAndRecordScan = async (input) => {
1674
1571
  didDeadCodeFail: input.payload.didDeadCodeFail,
1675
1572
  supplyChainOverlapTimedOut: input.payload.supplyChainOverlapTimedOut,
1676
1573
  securityScanFailed: input.payload.securityScanFailed,
1677
- deadCodeOverlapped: input.payload.deadCodeOverlapped,
1678
1574
  suppressedRuleCounts: input.payload.suppressedRuleCounts ?? []
1679
1575
  });
1680
1576
  return result;
1681
1577
  };
1682
1578
  //#endregion
1579
+ //#region src/cli/utils/copy-unchanged-baseline-sources.ts
1580
+ const copyUnchangedBaselineSources = async (input) => {
1581
+ const baseMaterializedFiles = new Set(input.baseMaterializedFiles.map(toForwardSlashes));
1582
+ const headChangedFiles = new Set(input.headChangedFiles.map(toForwardSlashes));
1583
+ const untrackedFiles = new Set(input.untrackedFiles.map(toForwardSlashes));
1584
+ const sourceDirectory = path$1.resolve(input.directory);
1585
+ const targetDirectory = path$1.resolve(input.tempDirectory);
1586
+ return (await mapWithConcurrency(input.sourceFiles.filter((filePath) => {
1587
+ const normalizedPath = toForwardSlashes(filePath);
1588
+ return !baseMaterializedFiles.has(normalizedPath) && !headChangedFiles.has(normalizedPath) && !untrackedFiles.has(normalizedPath);
1589
+ }), 32, async (filePath) => {
1590
+ input.signal?.throwIfAborted();
1591
+ if (input.deadlineEpochMs !== null && Date.now() >= input.deadlineEpochMs) return false;
1592
+ const sourcePath = path$1.resolve(sourceDirectory, filePath);
1593
+ const targetPath = path$1.resolve(targetDirectory, filePath);
1594
+ if (!isPathInsideDirectory(sourcePath, sourceDirectory) || !isPathInsideDirectory(targetPath, targetDirectory)) return false;
1595
+ try {
1596
+ if (!(await fs$1.lstat(sourcePath)).isFile()) return false;
1597
+ await fs$1.mkdir(path$1.dirname(targetPath), { recursive: true });
1598
+ await fs$1.copyFile(sourcePath, targetPath);
1599
+ return true;
1600
+ } catch {
1601
+ input.signal?.throwIfAborted();
1602
+ return false;
1603
+ }
1604
+ })).every(Boolean);
1605
+ };
1606
+ //#endregion
1683
1607
  //#region src/cli/utils/read-diagnostic-evidence.ts
1684
1608
  const SOURCE_EXTENSIONS = [
1685
1609
  ".ts",
@@ -2097,7 +2021,6 @@ const resolveMergeBaseRef = async (directory, ref) => {
2097
2021
  const silentConsole$1 = makeNoopConsole();
2098
2022
  const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
2099
2023
  const runBaselineComparison = async (input) => {
2100
- const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
2101
2024
  const baselineIncludePaths = filterPathsOutsideDirectories({
2102
2025
  rootDirectory: input.directory,
2103
2026
  relativePaths: input.options.includePaths,
@@ -2113,12 +2036,24 @@ const runBaselineComparison = async (input) => {
2113
2036
  relativePaths: input.headFiles,
2114
2037
  excludedDirectories: input.options.excludedProjectDirectories
2115
2038
  }) : void 0;
2039
+ const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
2040
+ if (remainingBaselineBudgetMs === 0) return null;
2041
+ const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
2042
+ const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
2043
+ let maintainabilitySourceFiles = [];
2044
+ if (input.options.deadCode) try {
2045
+ maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
2046
+ } catch (error) {
2047
+ if (baselineDeadlineSignal?.aborted) return null;
2048
+ throw error;
2049
+ }
2050
+ const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
2116
2051
  const snapshot = await materializeBaselineFiles({
2117
2052
  directory: input.directory,
2118
2053
  ref: input.baselineRef,
2119
- files: baselineIncludePaths,
2120
- baseFiles: baselineBaseFiles,
2121
- headFiles: baselineHeadFiles,
2054
+ files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
2055
+ baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
2056
+ headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
2122
2057
  tempDirectory: temporaryDirectory
2123
2058
  }).catch((error) => {
2124
2059
  rmSync(temporaryDirectory, {
@@ -2136,22 +2071,47 @@ const runBaselineComparison = async (input) => {
2136
2071
  }
2137
2072
  try {
2138
2073
  if (!snapshot.isComplete) return null;
2074
+ if (input.options.deadCode && !await copyUnchangedBaselineSources({
2075
+ directory: input.directory,
2076
+ sourceFiles: maintainabilitySourceFiles,
2077
+ baseMaterializedFiles: snapshot.materializedFiles,
2078
+ headChangedFiles: snapshot.headFiles,
2079
+ untrackedFiles: snapshot.untrackedFiles,
2080
+ tempDirectory: snapshot.tempDirectory,
2081
+ deadlineEpochMs: input.deadlineEpochMs,
2082
+ signal: input.oxlintRuntime.abortSignal
2083
+ })) return null;
2084
+ const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
2085
+ rootDirectory: input.directory,
2086
+ relativePaths: snapshot.baseFiles,
2087
+ excludedDirectories: input.options.excludedProjectDirectories
2088
+ });
2089
+ const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
2090
+ rootDirectory: input.directory,
2091
+ relativePaths: snapshot.headFiles,
2092
+ excludedDirectories: input.options.excludedProjectDirectories
2093
+ });
2139
2094
  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));
2095
+ const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
2096
+ const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
2142
2097
  for (const filePath of baselineIncludePaths) {
2143
2098
  const normalizedFilePath = toForwardSlashes(filePath);
2144
2099
  if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
2145
2100
  }
2146
- if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
2101
+ if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
2102
+ const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
2103
+ const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
2104
+ const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
2105
+ 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)));
2106
+ const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
2147
2107
  const runtimeLayers = buildRuntimeLayers({
2148
2108
  directory: snapshot.tempDirectory,
2149
2109
  hasConfigOverride: true,
2150
2110
  userConfig: input.userConfig,
2151
2111
  configSourceDirectory: input.configSourceDirectory,
2152
2112
  projectInfoOverride: input.headProjectInfo,
2153
- shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath,
2154
- shouldRunDeadCode: false,
2113
+ shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
2114
+ shouldRunDeadCode: input.options.deadCode,
2155
2115
  shouldRunSupplyChain: input.options.supplyChain,
2156
2116
  shouldComputeScore: false,
2157
2117
  shouldShowProgressSpinners: false,
@@ -2160,7 +2120,8 @@ const runBaselineComparison = async (input) => {
2160
2120
  });
2161
2121
  const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
2162
2122
  directory: snapshot.tempDirectory,
2163
- includePaths: snapshot.materializedFiles,
2123
+ includePaths: baseIncludePaths,
2124
+ maintainabilityFocusPaths,
2164
2125
  customRulesOnly: input.options.customRulesOnly,
2165
2126
  respectInlineDisables: input.options.respectInlineDisables,
2166
2127
  warnings: input.options.warnings,
@@ -2169,7 +2130,7 @@ const runBaselineComparison = async (input) => {
2169
2130
  includedTags: input.options.includedTags,
2170
2131
  includeTagDefaults: input.options.includeTagDefaults,
2171
2132
  nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
2172
- runDeadCode: false,
2133
+ runDeadCode: input.options.deadCode,
2173
2134
  isCi: input.options.isCi,
2174
2135
  doctorVersion: VERSION,
2175
2136
  runId: getRunId(),
@@ -2177,10 +2138,16 @@ const runBaselineComparison = async (input) => {
2177
2138
  suppressScanSummary: true,
2178
2139
  supplyChainManifestChanged: input.options.supplyChainManifestChanged,
2179
2140
  deadlineEpochMs: input.deadlineEpochMs ?? void 0,
2180
- signal: input.oxlintRuntime.abortSignal
2141
+ signal: input.oxlintRuntime.abortSignal,
2142
+ excludedProjectDirectories: baselineExcludedProjectDirectories,
2143
+ retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
2181
2144
  }, {}).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));
2145
+ if (baseOutput.didLintFail || baseOutput.didDeadCodeFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
2146
+ const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
2147
+ rootDirectory: input.directory,
2148
+ relativePaths: snapshot.untrackedFiles,
2149
+ excludedDirectories: input.options.excludedProjectDirectories
2150
+ }).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
2184
2151
  const diagnosticDelta = computeDiagnosticDelta({
2185
2152
  headDiagnostics: input.headDiagnostics,
2186
2153
  baseDiagnostics: baseOutput.diagnostics,
@@ -2353,6 +2320,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
2353
2320
  directory,
2354
2321
  precomputedSourceFileCount: options.precomputedSourceFileCount,
2355
2322
  includePaths: options.includePaths,
2323
+ changedLineRanges: options.changedLineRanges ?? void 0,
2356
2324
  customRulesOnly: options.customRulesOnly,
2357
2325
  respectInlineDisables: options.respectInlineDisables,
2358
2326
  warnings: options.warnings,
@@ -2383,7 +2351,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
2383
2351
  else runSync(error(highlighter.error(lintFailureReason)));
2384
2352
  let inspectDiagnostics = output.diagnostics;
2385
2353
  let baselineDelta;
2386
- if (options.baseline && isDiffMode && !didLintFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
2354
+ if (options.baseline && isDiffMode && !didLintFail && !output.didDeadCodeFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
2387
2355
  const comparison = await runBaselineComparison({
2388
2356
  directory,
2389
2357
  options,
@@ -2536,13 +2504,13 @@ const resolveCursorBundledNode = () => {
2536
2504
  if (!localAppData) return null;
2537
2505
  const cursorAgentRoot = path$1.join(localAppData, "cursor-agent", "versions");
2538
2506
  try {
2539
- const versions = fs$1.readdirSync(cursorAgentRoot);
2507
+ const versions = fs$2.readdirSync(cursorAgentRoot);
2540
2508
  if (versions.length === 0) return null;
2541
2509
  const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
2542
2510
  for (const version of sortedVersions) try {
2543
2511
  const bundledNodePath = path$1.join(cursorAgentRoot, version, "node.exe");
2544
2512
  const entryScriptPath = path$1.join(cursorAgentRoot, version, "index.js");
2545
- if (fs$1.statSync(bundledNodePath).isFile() && fs$1.statSync(entryScriptPath).isFile()) return {
2513
+ if (fs$2.statSync(bundledNodePath).isFile() && fs$2.statSync(entryScriptPath).isFile()) return {
2546
2514
  command: bundledNodePath,
2547
2515
  args: [entryScriptPath]
2548
2516
  };
@@ -2556,15 +2524,15 @@ const resolveWindowsCmdEntryScript = (command) => {
2556
2524
  for (const directory of pathDirectories) {
2557
2525
  const cmdFilePath = path$1.join(directory, `${command}.cmd`);
2558
2526
  try {
2559
- if (!fs$1.statSync(cmdFilePath).isFile()) continue;
2527
+ if (!fs$2.statSync(cmdFilePath).isFile()) continue;
2560
2528
  } catch {
2561
2529
  continue;
2562
2530
  }
2563
2531
  try {
2564
- const entryScriptMatch = fs$1.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
2532
+ const entryScriptMatch = fs$2.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
2565
2533
  if (!entryScriptMatch) continue;
2566
2534
  const resolvedScriptPath = path$1.resolve(directory, entryScriptMatch[1]);
2567
- if (fs$1.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
2535
+ if (fs$2.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
2568
2536
  } catch {}
2569
2537
  }
2570
2538
  return null;
@@ -2829,6 +2797,45 @@ const resolveProjectScan = async (rootScanTarget, projectDirectory) => {
2829
2797
  };
2830
2798
  };
2831
2799
  //#endregion
2800
+ //#region src/cli/utils/highlight-code-line.ts
2801
+ 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;
2802
+ const highlightCodeLine = (codeLine) => codeLine.replace(CODE_TOKEN_PATTERN, (token) => {
2803
+ if (token.startsWith("/")) return highlighter.gray(token);
2804
+ if (token.startsWith("\"") || token.startsWith("'") || token.startsWith("`")) return highlighter.success(token);
2805
+ if (/^\d|^0[xob]/i.test(token)) return highlighter.warn(token);
2806
+ return highlighter.info(token);
2807
+ });
2808
+ //#endregion
2809
+ //#region src/cli/utils/render-code-frame.ts
2810
+ const SOURCE_LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
2811
+ const renderCodeFrame = ({ source, line, column, endLine, message, linesAbove, linesBelow, maximumLineLength }) => {
2812
+ const sourceLines = source.split(SOURCE_LINE_BREAK_PATTERN);
2813
+ if (line < 1 || line > sourceLines.length) return null;
2814
+ const offendingLine = sourceLines[line - 1];
2815
+ if (offendingLine === void 0 || offendingLine.length > maximumLineLength) return null;
2816
+ const lastMarkedLine = Math.min(Math.max(endLine ?? line, line), sourceLines.length);
2817
+ const firstDisplayedLine = Math.max(line - linesAbove, 1);
2818
+ const lastDisplayedLine = Math.min(lastMarkedLine + linesBelow, sourceLines.length);
2819
+ const lineNumberWidth = String(lastDisplayedLine).length;
2820
+ const hasCaret = lastMarkedLine === line && column !== void 0;
2821
+ const outputLines = [];
2822
+ if (message && !hasCaret) outputLines.push(`${" ".repeat(lineNumberWidth + 2)}${highlighter.error(message)}`);
2823
+ for (let displayedLineNumber = firstDisplayedLine; displayedLineNumber <= lastDisplayedLine; displayedLineNumber += 1) {
2824
+ const sourceLine = sourceLines[displayedLineNumber - 1] ?? "";
2825
+ const isMarked = displayedLineNumber >= line && displayedLineNumber <= lastMarkedLine;
2826
+ const gutter = ` ${String(displayedLineNumber).padStart(lineNumberWidth)} |`;
2827
+ const marker = isMarked ? highlighter.error(">") : " ";
2828
+ outputLines.push(`${marker}${highlighter.dim(gutter)}${sourceLine.length > 0 ? ` ${highlightCodeLine(sourceLine)}` : ""}`);
2829
+ if (isMarked && hasCaret) {
2830
+ const markerSpacing = sourceLine.slice(0, Math.max(column - 1, 0)).replace(/[^\t]/g, " ");
2831
+ const emptyGutter = gutter.replace(/\d/g, " ");
2832
+ const markerMessage = message ? ` ${highlighter.error(message)}` : "";
2833
+ outputLines.push(` ${highlighter.dim(emptyGutter)} ${markerSpacing}${highlighter.error("^")}${markerMessage}`);
2834
+ }
2835
+ }
2836
+ return outputLines.join("\n");
2837
+ };
2838
+ //#endregion
2832
2839
  //#region src/cli/utils/resolve-absolute-path.ts
2833
2840
  /**
2834
2841
  * Resolves a diagnostic's `filePath` (relative to its project root, or
@@ -2851,23 +2858,20 @@ const buildCodeFrame = (input) => {
2851
2858
  const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
2852
2859
  let source;
2853
2860
  try {
2854
- source = fs$1.readFileSync(absolutePath, "utf8");
2861
+ source = fs$2.readFileSync(absolutePath, "utf8");
2855
2862
  } catch {
2856
2863
  return null;
2857
2864
  }
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: {
2865
+ const endLine = input.endLine != null && input.endLine > input.line ? input.endLine : void 0;
2866
+ return renderCodeFrame({
2867
+ source,
2863
2868
  line: input.line,
2864
- column: input.column > 0 ? input.column : void 0
2865
- } };
2866
- return codeFrameColumns(source, location, {
2867
- highlightCode: true,
2869
+ column: endLine === void 0 && input.column > 0 ? input.column : void 0,
2870
+ endLine,
2871
+ message: input.message,
2868
2872
  linesAbove: 1,
2869
2873
  linesBelow: 1,
2870
- ...input.message ? { message: input.message } : {}
2874
+ maximumLineLength: 200
2871
2875
  });
2872
2876
  };
2873
2877
  //#endregion
@@ -2875,7 +2879,7 @@ const buildCodeFrame = (input) => {
2875
2879
  const discoverWorkspacePackages = (rootDirectory) => {
2876
2880
  const hasRootPackageJson = isFile(path$1.join(rootDirectory, "package.json"));
2877
2881
  const packages = listWorkspacePackages(rootDirectory);
2878
- if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverReactSubprojects(rootDirectory);
2882
+ if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverSupportedSubprojects(rootDirectory);
2879
2883
  return packages;
2880
2884
  };
2881
2885
  const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
@@ -2991,7 +2995,7 @@ const shouldFailScanGate = (input) => {
2991
2995
  return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
2992
2996
  };
2993
2997
  //#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 };
2998
+ 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
2999
 
2996
- //# sourceMappingURL=should-fail-scan-gate-4jf7ZRdL.js.map
2997
- //# debugId=1be0ae40-8cad-55a2-b447-69faa6b7cb0b
3000
+ //# sourceMappingURL=should-fail-scan-gate-Nw22UN5m.js.map
3001
+ //# debugId=7decec60-ced4-543d-a6e9-381f8d779223