react-doctor 0.5.8-dev.b69f4a7 → 0.5.8-dev.b6d1a87

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.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
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]="bd31e7dc-cfca-56b2-a963-9d798b3cbbe5")}catch(e){}}();
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]="1e81c0a2-fd08-5f5b-ba28-bd5f7587e0f4")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -40522,6 +40522,11 @@ const toCanonicalPath = (filePath) => {
40522
40522
  };
40523
40523
  const DEAD_CODE_PLUGIN = "deslop";
40524
40524
  const DEAD_CODE_CATEGORY = "Maintainability";
40525
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
40526
+ "react-doctor",
40527
+ "eslint-plugin-react-doctor",
40528
+ "oxlint-plugin-react-doctor"
40529
+ ]);
40525
40530
  const DEAD_CODE_WORKER_SCRIPT = `
40526
40531
  const inputChunks = [];
40527
40532
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -40827,6 +40832,7 @@ const checkDeadCode = async (options) => {
40827
40832
  });
40828
40833
  }
40829
40834
  for (const unusedDependency of result.unusedDependencies) {
40835
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
40830
40836
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
40831
40837
  diagnostics.push({
40832
40838
  filePath: "package.json",
@@ -45059,7 +45065,8 @@ const METRIC = {
45059
45065
  lspScanCompleted: "lsp.scan.completed",
45060
45066
  lspScanDuration: "lsp.scan.duration",
45061
45067
  lspScanDiagnostics: "lsp.scan.diagnostics",
45062
- aiTrainingWarningShown: "ai.training.warning_shown"
45068
+ aiTrainingWarningShown: "ai.training.warning_shown",
45069
+ jsonOutUsed: "json.out_used"
45063
45070
  };
45064
45071
  //#endregion
45065
45072
  //#region src/cli/utils/noop-console.ts
@@ -45102,7 +45109,7 @@ const makeNoopConsole = () => ({
45102
45109
  });
45103
45110
  //#endregion
45104
45111
  //#region src/cli/utils/version.ts
45105
- const VERSION = "0.5.8-dev.b69f4a7";
45112
+ const VERSION = "0.5.8-dev.b6d1a87";
45106
45113
  //#endregion
45107
45114
  //#region src/cli/utils/json-mode.ts
45108
45115
  let context = null;
@@ -45140,12 +45147,13 @@ const installSilentConsole = () => {
45140
45147
  "trace"
45141
45148
  ]) target[key] = source[key];
45142
45149
  };
45143
- const enableJsonMode = ({ compact, directory }) => {
45150
+ const enableJsonMode = ({ compact, directory, outputFile }) => {
45144
45151
  context = {
45145
45152
  compact,
45146
45153
  directory,
45147
45154
  startTime: performance$1.now(),
45148
- mode: "full"
45155
+ mode: "full",
45156
+ outputFile: outputFile ?? null
45149
45157
  };
45150
45158
  installSilentConsole();
45151
45159
  };
@@ -45158,7 +45166,11 @@ const setJsonReportMode = (mode) => {
45158
45166
  };
45159
45167
  const writeJsonReport = (report) => {
45160
45168
  const serialized = context?.compact ? JSON.stringify(report) : JSON.stringify(report, null, 2);
45161
- process.stdout.write(`${serialized}\n`);
45169
+ if (context?.outputFile) {
45170
+ const resolvedPath = Path.resolve(context.outputFile);
45171
+ NFS.mkdirSync(Path.dirname(resolvedPath), { recursive: true });
45172
+ NFS.writeFileSync(resolvedPath, `${serialized}\n`);
45173
+ } else process.stdout.write(`${serialized}\n`);
45162
45174
  };
45163
45175
  const writeJsonErrorReport = (error, sentryEventId) => {
45164
45176
  if (!context) return;
@@ -45466,13 +45478,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45466
45478
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45467
45479
  * standard `SENTRY_RELEASE` override.
45468
45480
  */
45469
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.b69f4a7`;
45481
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.b6d1a87`;
45470
45482
  /**
45471
45483
  * Deployment environment shown in Sentry's environment filter. Defaults to
45472
45484
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45473
45485
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45474
45486
  */
45475
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.b69f4a7") ? "development" : "production");
45487
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.b6d1a87") ? "development" : "production");
45476
45488
  /**
45477
45489
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45478
45490
  * (set to `0` to disable tracing) and falls back to
@@ -50556,6 +50568,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
50556
50568
  `if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
50557
50569
  "rm -f \"$react_doctor_output\";",
50558
50570
  "else",
50571
+ "cat \"$react_doctor_output\" >&2;",
50559
50572
  "rm -f \"$react_doctor_output\";",
50560
50573
  `printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
50561
50574
  "fi"
@@ -52253,10 +52266,11 @@ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_
52253
52266
  const CLAUDE_AGENT = "claude-code";
52254
52267
  const CURSOR_AGENT = "cursor";
52255
52268
  const CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
52256
- const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.sh";
52257
- const CLAUDE_HOOK_COMMAND = "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.sh\"";
52269
+ const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.mjs";
52270
+ const CLAUDE_HOOK_COMMAND = "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.mjs\"";
52258
52271
  const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
52259
- const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.sh";
52272
+ const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.mjs";
52273
+ const CURSOR_HOOK_COMMAND = "node .cursor/hooks/react-doctor.mjs";
52260
52274
  const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
52261
52275
  const CURSOR_HOOKS_SCHEMA_VERSION = 1;
52262
52276
  const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
@@ -52288,7 +52302,6 @@ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
52288
52302
  const writeHookScript = (filePath) => {
52289
52303
  ensureDirectoryExists(Path.dirname(filePath));
52290
52304
  NFS.writeFileSync(filePath, buildAgentHookScript());
52291
- NFS.chmodSync(filePath, 493);
52292
52305
  };
52293
52306
  const hasClaudeHookCommand = (groups) => groups.some((group) => (group.hooks ?? []).some((hook) => hook.command === CLAUDE_HOOK_COMMAND));
52294
52307
  const installClaudeHook = (projectRoot) => {
@@ -52309,7 +52322,7 @@ const installClaudeHook = (projectRoot) => {
52309
52322
  writeHookScript(hookPath);
52310
52323
  return [settingsPath, hookPath];
52311
52324
  };
52312
- const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_RELATIVE_PATH);
52325
+ const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_COMMAND);
52313
52326
  const installCursorHook = (projectRoot) => {
52314
52327
  const configPath = Path.join(projectRoot, CURSOR_HOOKS_RELATIVE_PATH);
52315
52328
  const hookPath = Path.join(projectRoot, CURSOR_HOOK_RELATIVE_PATH);
@@ -52317,7 +52330,7 @@ const installCursorHook = (projectRoot) => {
52317
52330
  const hooks = { ...config.hooks ?? {} };
52318
52331
  const postToolUseHooks = [...hooks.postToolUse ?? []];
52319
52332
  if (!hasCursorHookCommand(postToolUseHooks)) postToolUseHooks.push({
52320
- command: CURSOR_HOOK_RELATIVE_PATH,
52333
+ command: CURSOR_HOOK_COMMAND,
52321
52334
  matcher: CURSOR_HOOK_MATCHER,
52322
52335
  timeout: 120
52323
52336
  });
@@ -52331,102 +52344,111 @@ const installCursorHook = (projectRoot) => {
52331
52344
  return [configPath, hookPath];
52332
52345
  };
52333
52346
  const buildAgentHookScript = () => [
52334
- "#!/bin/sh",
52335
- "set -u",
52347
+ "import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';",
52348
+ "import { tmpdir } from 'node:os';",
52349
+ "import { join, dirname } from 'node:path';",
52350
+ "import { fileURLToPath } from 'node:url';",
52351
+ "import { spawnSync } from 'node:child_process';",
52336
52352
  "",
52337
- "input_file=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-agent-hook.XXXXXX\")",
52338
- "output_file=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-agent-hook-output.XXXXXX\")",
52339
- "trap 'rm -f \"$input_file\" \"$output_file\"' EXIT",
52340
- "cat > \"$input_file\"",
52353
+ "const __filename = fileURLToPath(import.meta.url);",
52354
+ "const __dirname = dirname(__filename);",
52341
52355
  "",
52342
- "script_dir=$(CDPATH= cd \"$(dirname \"$0\")\" && pwd)",
52343
- "project_root=${CLAUDE_PROJECT_DIR:-}",
52344
- "if [ -z \"$project_root\" ]; then",
52345
- " project_root=$(CDPATH= cd \"$script_dir/../..\" && pwd)",
52346
- "fi",
52347
- "if ! cd \"$project_root\"; then",
52348
- " exit 0",
52349
- "fi",
52356
+ "const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
52350
52357
  "",
52351
- "should_scan() {",
52352
- " if ! command -v node >/dev/null 2>&1; then",
52353
- " return 0",
52354
- " fi",
52358
+ "const readStdin = () => {",
52359
+ " try {",
52360
+ " return readFileSync(0, 'utf8');",
52361
+ " } catch {",
52362
+ " return '';",
52363
+ " }",
52364
+ "};",
52355
52365
  "",
52356
- " node - \"$input_file\" <<'NODE'",
52357
- "const fs = require('node:fs');",
52358
- "const inputPath = process.argv[2];",
52359
- "const editToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
52360
- "try {",
52361
- " const input = JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
52366
+ "const shouldScan = (input) => {",
52362
52367
  " const eventName = input.hook_event_name || input.eventName || input.event_name;",
52363
52368
  " if (eventName === 'PostToolBatch') {",
52364
52369
  " const toolCalls = Array.isArray(input.tool_calls) ? input.tool_calls : [];",
52365
- " process.exit(toolCalls.some((toolCall) => editToolNames.has(toolCall.tool_name)) ? 0 : 10);",
52370
+ " return toolCalls.some((toolCall) => EDIT_TOOL_NAMES.has(toolCall.tool_name));",
52366
52371
  " }",
52367
52372
  " const toolName = input.tool_name || input.toolName || input.tool;",
52368
- " process.exit(!toolName || editToolNames.has(toolName) ? 0 : 10);",
52369
- "} catch {",
52370
- " process.exit(0);",
52371
- "}",
52372
- "NODE",
52373
- "}",
52373
+ " return !toolName || EDIT_TOOL_NAMES.has(toolName);",
52374
+ "};",
52374
52375
  "",
52375
- "run_react_doctor() {",
52376
- " if [ -x ./node_modules/.bin/react-doctor ]; then",
52377
- " ./node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score",
52378
- " return",
52379
- " fi",
52376
+ "const runReactDoctor = (outputPath) => {",
52377
+ " // Each candidate is a single shell command string (not an args array):",
52378
+ " // `shell: true` is required to run the Windows `.cmd` shims, and an args",
52379
+ " // array with `shell: true` trips Node's DEP0190. A missing command exits",
52380
+ " // 127 via the shell (no ENOENT error), so fall through on that. With no",
52381
+ " // runner found, exit 0 silently — stdout is parsed as the hook's JSON.",
52382
+ " const commands = [",
52383
+ " './node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score',",
52384
+ " 'react-doctor --verbose --scope changed --blocking warning --no-score',",
52385
+ " 'pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
52386
+ " 'npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
52387
+ " ];",
52380
52388
  "",
52381
- " if command -v react-doctor >/dev/null 2>&1; then",
52382
- " react-doctor --verbose --scope changed --blocking warning --no-score",
52383
- " return",
52384
- " fi",
52389
+ " for (const command of commands) {",
52390
+ " const result = spawnSync(command, { encoding: 'utf8', shell: true });",
52391
+ " if (result.error?.code === 'ENOENT' || result.status === 127) continue;",
52392
+ " try {",
52393
+ " writeFileSync(outputPath, (result.stdout || '') + (result.stderr || ''));",
52394
+ " } catch {}",
52395
+ " return result.status;",
52396
+ " }",
52385
52397
  "",
52386
- " if command -v pnpm >/dev/null 2>&1; then",
52387
- " pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score",
52388
- " return",
52389
- " fi",
52398
+ " return 0;",
52399
+ "};",
52390
52400
  "",
52391
- " if command -v npx >/dev/null 2>&1; then",
52392
- " npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score",
52393
- " return",
52394
- " fi",
52401
+ "const cleanup = (...paths) => {",
52402
+ " for (const path of paths) {",
52403
+ " try { unlinkSync(path); } catch {}",
52404
+ " }",
52405
+ "};",
52395
52406
  "",
52396
- " printf '%s\\n' 'react-doctor: command not found; skipping agent hook scan.'",
52397
- " return 0",
52398
- "}",
52407
+ "const main = () => {",
52408
+ " let input;",
52409
+ " try {",
52410
+ " const stdinContent = readStdin();",
52411
+ " input = JSON.parse(stdinContent || '{}');",
52412
+ " } catch {",
52413
+ " input = {};",
52414
+ " }",
52399
52415
  "",
52400
- "if ! should_scan; then",
52401
- " exit 0",
52402
- "fi",
52416
+ " if (!shouldScan(input)) {",
52417
+ " process.exit(0);",
52418
+ " }",
52403
52419
  "",
52404
- "if run_react_doctor > \"$output_file\" 2>&1; then",
52405
- " exit 0",
52406
- "fi",
52420
+ " const projectRoot = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');",
52421
+ " const outputPath = join(tmpdir(), `react-doctor-agent-hook-output-${process.pid}.txt`);",
52407
52422
  "",
52408
- "node - \"$input_file\" \"$output_file\" <<'NODE'",
52409
- "const fs = require('node:fs');",
52410
- "const inputPath = process.argv[2];",
52411
- "const outputPath = process.argv[3];",
52412
- "const readInput = () => {",
52413
52423
  " try {",
52414
- " return JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
52424
+ " process.chdir(projectRoot);",
52415
52425
  " } catch {",
52416
- " return {};",
52426
+ " process.exit(0);",
52427
+ " }",
52428
+ "",
52429
+ " const scanResult = runReactDoctor(outputPath);",
52430
+ " if (scanResult === 0) {",
52431
+ " cleanup(outputPath);",
52432
+ " process.exit(0);",
52433
+ " }",
52434
+ "",
52435
+ " const scanOutput = readFileSync(outputPath, 'utf8').trim();",
52436
+ " cleanup(outputPath);",
52437
+ "",
52438
+ " if (!scanOutput) {",
52439
+ " process.exit(0);",
52440
+ " }",
52441
+ "",
52442
+ " const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
52443
+ "",
52444
+ " if (input.hook_event_name === 'PostToolBatch') {",
52445
+ " console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
52446
+ " } else {",
52447
+ " console.log(JSON.stringify({ additional_context: message }));",
52417
52448
  " }",
52418
52449
  "};",
52419
- "const input = readInput();",
52420
- "const scanOutput = fs.readFileSync(outputPath, 'utf8').trim();",
52421
- "if (!scanOutput) process.exit(0);",
52422
- "const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
52423
- "if (input.hook_event_name === 'PostToolBatch') {",
52424
- " console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
52425
- "} else {",
52426
- " console.log(JSON.stringify({ additional_context: message }));",
52427
- "}",
52428
- "NODE",
52429
- ""
52450
+ "",
52451
+ "main();"
52430
52452
  ].join("\n");
52431
52453
  const installReactDoctorAgentHooks = (options) => {
52432
52454
  const installedAgents = [];
@@ -52487,6 +52509,7 @@ const buildReactDoctorHookBlock = () => [
52487
52509
  "if react_doctor_scan_staged_files > \"$react_doctor_output\" 2>&1; then",
52488
52510
  " rm -f \"$react_doctor_output\"",
52489
52511
  "else",
52512
+ " cat \"$react_doctor_output\" >&2",
52490
52513
  " rm -f \"$react_doctor_output\"",
52491
52514
  ` printf '%s\\n' "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2`,
52492
52515
  "fi",
@@ -53252,6 +53275,26 @@ const CLI_AGENT_AUTO_FLAGS = {
53252
53275
  codex: ["--yolo"],
53253
53276
  cursor: ["--force"]
53254
53277
  };
53278
+ const resolveCursorBundledNode = () => {
53279
+ if (!isWindows) return null;
53280
+ const localAppData = process.env.LOCALAPPDATA;
53281
+ if (!localAppData) return null;
53282
+ const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
53283
+ try {
53284
+ const versions = NFS.readdirSync(cursorAgentRoot);
53285
+ if (versions.length === 0) return null;
53286
+ const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
53287
+ for (const version of sortedVersions) try {
53288
+ const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
53289
+ const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
53290
+ if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
53291
+ command: bundledNodePath,
53292
+ args: [entryScriptPath]
53293
+ };
53294
+ } catch {}
53295
+ } catch {}
53296
+ return null;
53297
+ };
53255
53298
  const resolveWindowsCmdEntryScript = (command) => {
53256
53299
  if (!isWindows) return null;
53257
53300
  const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
@@ -53271,11 +53314,10 @@ const resolveWindowsCmdEntryScript = (command) => {
53271
53314
  }
53272
53315
  return null;
53273
53316
  };
53274
- const spawnAgent = (command, args, cwd, shell = false) => new Promise((resolve, reject) => {
53317
+ const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
53275
53318
  const child = spawn(command, [...args], {
53276
53319
  cwd,
53277
- stdio: "inherit",
53278
- shell
53320
+ stdio: "inherit"
53279
53321
  });
53280
53322
  child.on("error", reject);
53281
53323
  child.on("close", (code) => resolve(code ?? 0));
@@ -53284,6 +53326,10 @@ const launchCliAgent = async (agentId, prompt, cwd) => {
53284
53326
  const binary = CLI_AGENT_BINARIES[agentId];
53285
53327
  const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
53286
53328
  if (isWindows) {
53329
+ if (agentId === "cursor") {
53330
+ const cursorResolution = resolveCursorBundledNode();
53331
+ if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
53332
+ }
53287
53333
  const entryScript = resolveWindowsCmdEntryScript(binary);
53288
53334
  if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
53289
53335
  }
@@ -54396,10 +54442,14 @@ const inspectAction = async (directory, flags) => {
54396
54442
  const isQuiet = isScoreOnly || isJsonMode;
54397
54443
  const requestedDirectory = Path.resolve(directory);
54398
54444
  const startTime = performance$1.now();
54399
- if (isJsonMode) enableJsonMode({
54400
- compact: Boolean(flags.jsonCompact),
54401
- directory: requestedDirectory
54402
- });
54445
+ if (isJsonMode) {
54446
+ enableJsonMode({
54447
+ compact: Boolean(flags.jsonCompact),
54448
+ directory: requestedDirectory,
54449
+ outputFile: flags.jsonOut
54450
+ });
54451
+ if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
54452
+ }
54403
54453
  recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
54404
54454
  try {
54405
54455
  validateModeFlags(flags);
@@ -55337,6 +55387,14 @@ const applyColorPreference = (argv, env = process.env) => {
55337
55387
  setColorEnabled(enabled);
55338
55388
  };
55339
55389
  //#endregion
55390
+ //#region src/cli/utils/ensure-windows-utf8-console.ts
55391
+ const ensureWindowsUtf8Console = () => {
55392
+ if (process.platform !== "win32" || !process.stdout.isTTY) return;
55393
+ try {
55394
+ execFileSync("chcp.com", ["65001"], { stdio: "ignore" });
55395
+ } catch {}
55396
+ };
55397
+ //#endregion
55340
55398
  //#region src/cli/utils/exit-gracefully.ts
55341
55399
  const exitGracefully = () => {
55342
55400
  try {
@@ -55479,6 +55537,7 @@ const ROOT_FLAG_SPEC = {
55479
55537
  "--category",
55480
55538
  "--changed-files-from",
55481
55539
  "--blocking",
55540
+ "--json-out",
55482
55541
  "--output-dir",
55483
55542
  "--fail-on",
55484
55543
  "--project",
@@ -55669,7 +55728,7 @@ ${highlighter.dim("Learn more:")}
55669
55728
  ${highlighter.info(CANONICAL_GITHUB_URL)}
55670
55729
  `;
55671
55730
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
55672
- const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
55731
+ const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
55673
55732
  program.action(inspectAction);
55674
55733
  program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
55675
55734
  program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
@@ -55694,6 +55753,7 @@ if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
55694
55753
  process.exit(0);
55695
55754
  }
55696
55755
  applyColorPreference(strippedArgv);
55756
+ ensureWindowsUtf8Console();
55697
55757
  const argv = normalizeHelpInvocation(strippedArgv, knownCommands);
55698
55758
  Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => program.parseAsync(argv)).then(() => flushSentry()).catch(async (error) => {
55699
55759
  const isUserError = isExpectedUserError(error);
@@ -55712,4 +55772,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
55712
55772
  export {};
55713
55773
 
55714
55774
  //# sourceMappingURL=cli.js.map
55715
- //# debugId=bd31e7dc-cfca-56b2-a963-9d798b3cbbe5
55775
+ //# debugId=1e81c0a2-fd08-5f5b-ba28-bd5f7587e0f4
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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]="9807b1bf-1e62-5421-ad35-d1f4129c1538")}catch(e){}}();
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]="faa2d232-c423-58f8-97f4-ba8d41a199d7")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -37290,6 +37290,11 @@ const toCanonicalPath = (filePath) => {
37290
37290
  };
37291
37291
  const DEAD_CODE_PLUGIN = "deslop";
37292
37292
  const DEAD_CODE_CATEGORY = "Maintainability";
37293
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
37294
+ "react-doctor",
37295
+ "eslint-plugin-react-doctor",
37296
+ "oxlint-plugin-react-doctor"
37297
+ ]);
37293
37298
  const DEAD_CODE_WORKER_SCRIPT = `
37294
37299
  const inputChunks = [];
37295
37300
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -37595,6 +37600,7 @@ const checkDeadCode = async (options) => {
37595
37600
  });
37596
37601
  }
37597
37602
  for (const unusedDependency of result.unusedDependencies) {
37603
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
37598
37604
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
37599
37605
  diagnostics.push({
37600
37606
  filePath: "package.json",
@@ -41645,4 +41651,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41645
41651
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41646
41652
 
41647
41653
  //# sourceMappingURL=index.js.map
41648
- //# debugId=9807b1bf-1e62-5421-ad35-d1f4129c1538
41654
+ //# debugId=faa2d232-c423-58f8-97f4-ba8d41a199d7
package/dist/lsp.js CHANGED
@@ -37275,6 +37275,11 @@ const toCanonicalPath = (filePath) => {
37275
37275
  };
37276
37276
  const DEAD_CODE_PLUGIN = "deslop";
37277
37277
  const DEAD_CODE_CATEGORY = "Maintainability";
37278
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
37279
+ "react-doctor",
37280
+ "eslint-plugin-react-doctor",
37281
+ "oxlint-plugin-react-doctor"
37282
+ ]);
37278
37283
  const DEAD_CODE_WORKER_SCRIPT = `
37279
37284
  const inputChunks = [];
37280
37285
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -37580,6 +37585,7 @@ const checkDeadCode = async (options) => {
37580
37585
  });
37581
37586
  }
37582
37587
  for (const unusedDependency of result.unusedDependencies) {
37588
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
37583
37589
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
37584
37590
  diagnostics.push({
37585
37591
  filePath: "package.json",
@@ -43047,7 +43053,8 @@ const METRIC = {
43047
43053
  lspScanCompleted: "lsp.scan.completed",
43048
43054
  lspScanDuration: "lsp.scan.duration",
43049
43055
  lspScanDiagnostics: "lsp.scan.diagnostics",
43050
- aiTrainingWarningShown: "ai.training.warning_shown"
43056
+ aiTrainingWarningShown: "ai.training.warning_shown",
43057
+ jsonOutUsed: "json.out_used"
43051
43058
  };
43052
43059
  //#endregion
43053
43060
  //#region src/cli/utils/to-category-key.ts
@@ -43364,5 +43371,5 @@ const startLanguageServer = () => {
43364
43371
  };
43365
43372
  //#endregion
43366
43373
  export { startLanguageServer };
43367
- !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]="b3c6b2e5-253e-5be2-aacb-bd57bbb3e5e6")}catch(e){}}();
43368
- //# debugId=b3c6b2e5-253e-5be2-aacb-bd57bbb3e5e6
43374
+ !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]="47940f68-872d-59ed-bdd2-40ef7848566c")}catch(e){}}();
43375
+ //# debugId=47940f68-872d-59ed-bdd2-40ef7848566c
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.8-dev.b69f4a7",
3
+ "version": "0.5.8-dev.b6d1a87",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -60,12 +60,12 @@
60
60
  "magicast": "^0.5.3",
61
61
  "oxlint": ">=1.66.0 <1.67.0",
62
62
  "prompts": "^2.4.2",
63
- "typescript": ">=5.0.4 <7",
63
+ "typescript": ">=5.0.4 <6",
64
64
  "vscode-languageserver": "^9.0.1",
65
65
  "vscode-languageserver-textdocument": "^1.0.12",
66
66
  "vscode-uri": "^3.1.0",
67
67
  "deslop-js": "0.5.8",
68
- "oxlint-plugin-react-doctor": "0.5.8-dev.b69f4a7"
68
+ "oxlint-plugin-react-doctor": "0.5.8-dev.b6d1a87"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__code-frame": "^7.27.0",