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

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 (3) hide show
  1. package/dist/cli.js +98 -150
  2. package/dist/lsp.js +3 -4
  3. package/package.json +3 -3
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]="eb7a01bc-f737-5207-b430-b8fee7a1895f")}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]="bd31e7dc-cfca-56b2-a963-9d798b3cbbe5")}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";
@@ -45059,8 +45059,7 @@ const METRIC = {
45059
45059
  lspScanCompleted: "lsp.scan.completed",
45060
45060
  lspScanDuration: "lsp.scan.duration",
45061
45061
  lspScanDiagnostics: "lsp.scan.diagnostics",
45062
- aiTrainingWarningShown: "ai.training.warning_shown",
45063
- jsonOutUsed: "json.out_used"
45062
+ aiTrainingWarningShown: "ai.training.warning_shown"
45064
45063
  };
45065
45064
  //#endregion
45066
45065
  //#region src/cli/utils/noop-console.ts
@@ -45103,7 +45102,7 @@ const makeNoopConsole = () => ({
45103
45102
  });
45104
45103
  //#endregion
45105
45104
  //#region src/cli/utils/version.ts
45106
- const VERSION = "0.5.8-dev.a7ad969";
45105
+ const VERSION = "0.5.8-dev.b69f4a7";
45107
45106
  //#endregion
45108
45107
  //#region src/cli/utils/json-mode.ts
45109
45108
  let context = null;
@@ -45141,13 +45140,12 @@ const installSilentConsole = () => {
45141
45140
  "trace"
45142
45141
  ]) target[key] = source[key];
45143
45142
  };
45144
- const enableJsonMode = ({ compact, directory, outputFile }) => {
45143
+ const enableJsonMode = ({ compact, directory }) => {
45145
45144
  context = {
45146
45145
  compact,
45147
45146
  directory,
45148
45147
  startTime: performance$1.now(),
45149
- mode: "full",
45150
- outputFile: outputFile ?? null
45148
+ mode: "full"
45151
45149
  };
45152
45150
  installSilentConsole();
45153
45151
  };
@@ -45160,11 +45158,7 @@ const setJsonReportMode = (mode) => {
45160
45158
  };
45161
45159
  const writeJsonReport = (report) => {
45162
45160
  const serialized = context?.compact ? JSON.stringify(report) : JSON.stringify(report, null, 2);
45163
- if (context?.outputFile) {
45164
- const resolvedPath = Path.resolve(context.outputFile);
45165
- NFS.mkdirSync(Path.dirname(resolvedPath), { recursive: true });
45166
- NFS.writeFileSync(resolvedPath, `${serialized}\n`);
45167
- } else process.stdout.write(`${serialized}\n`);
45161
+ process.stdout.write(`${serialized}\n`);
45168
45162
  };
45169
45163
  const writeJsonErrorReport = (error, sentryEventId) => {
45170
45164
  if (!context) return;
@@ -45472,13 +45466,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45472
45466
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45473
45467
  * standard `SENTRY_RELEASE` override.
45474
45468
  */
45475
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.a7ad969`;
45469
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.b69f4a7`;
45476
45470
  /**
45477
45471
  * Deployment environment shown in Sentry's environment filter. Defaults to
45478
45472
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45479
45473
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45480
45474
  */
45481
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.a7ad969") ? "development" : "production");
45475
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.b69f4a7") ? "development" : "production");
45482
45476
  /**
45483
45477
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45484
45478
  * (set to `0` to disable tracing) and falls back to
@@ -52259,11 +52253,10 @@ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_
52259
52253
  const CLAUDE_AGENT = "claude-code";
52260
52254
  const CURSOR_AGENT = "cursor";
52261
52255
  const CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
52262
- const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.mjs";
52263
- const CLAUDE_HOOK_COMMAND = "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.mjs\"";
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\"";
52264
52258
  const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
52265
- const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.mjs";
52266
- const CURSOR_HOOK_COMMAND = "node .cursor/hooks/react-doctor.mjs";
52259
+ const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.sh";
52267
52260
  const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
52268
52261
  const CURSOR_HOOKS_SCHEMA_VERSION = 1;
52269
52262
  const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
@@ -52295,6 +52288,7 @@ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
52295
52288
  const writeHookScript = (filePath) => {
52296
52289
  ensureDirectoryExists(Path.dirname(filePath));
52297
52290
  NFS.writeFileSync(filePath, buildAgentHookScript());
52291
+ NFS.chmodSync(filePath, 493);
52298
52292
  };
52299
52293
  const hasClaudeHookCommand = (groups) => groups.some((group) => (group.hooks ?? []).some((hook) => hook.command === CLAUDE_HOOK_COMMAND));
52300
52294
  const installClaudeHook = (projectRoot) => {
@@ -52315,7 +52309,7 @@ const installClaudeHook = (projectRoot) => {
52315
52309
  writeHookScript(hookPath);
52316
52310
  return [settingsPath, hookPath];
52317
52311
  };
52318
- const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_COMMAND);
52312
+ const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_RELATIVE_PATH);
52319
52313
  const installCursorHook = (projectRoot) => {
52320
52314
  const configPath = Path.join(projectRoot, CURSOR_HOOKS_RELATIVE_PATH);
52321
52315
  const hookPath = Path.join(projectRoot, CURSOR_HOOK_RELATIVE_PATH);
@@ -52323,7 +52317,7 @@ const installCursorHook = (projectRoot) => {
52323
52317
  const hooks = { ...config.hooks ?? {} };
52324
52318
  const postToolUseHooks = [...hooks.postToolUse ?? []];
52325
52319
  if (!hasCursorHookCommand(postToolUseHooks)) postToolUseHooks.push({
52326
- command: CURSOR_HOOK_COMMAND,
52320
+ command: CURSOR_HOOK_RELATIVE_PATH,
52327
52321
  matcher: CURSOR_HOOK_MATCHER,
52328
52322
  timeout: 120
52329
52323
  });
@@ -52337,111 +52331,102 @@ const installCursorHook = (projectRoot) => {
52337
52331
  return [configPath, hookPath];
52338
52332
  };
52339
52333
  const buildAgentHookScript = () => [
52340
- "import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';",
52341
- "import { tmpdir } from 'node:os';",
52342
- "import { join, dirname } from 'node:path';",
52343
- "import { fileURLToPath } from 'node:url';",
52344
- "import { spawnSync } from 'node:child_process';",
52334
+ "#!/bin/sh",
52335
+ "set -u",
52345
52336
  "",
52346
- "const __filename = fileURLToPath(import.meta.url);",
52347
- "const __dirname = dirname(__filename);",
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\"",
52348
52341
  "",
52349
- "const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
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",
52350
52350
  "",
52351
- "const readStdin = () => {",
52352
- " try {",
52353
- " return readFileSync(0, 'utf8');",
52354
- " } catch {",
52355
- " return '';",
52356
- " }",
52357
- "};",
52351
+ "should_scan() {",
52352
+ " if ! command -v node >/dev/null 2>&1; then",
52353
+ " return 0",
52354
+ " fi",
52358
52355
  "",
52359
- "const shouldScan = (input) => {",
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') || '{}');",
52360
52362
  " const eventName = input.hook_event_name || input.eventName || input.event_name;",
52361
52363
  " if (eventName === 'PostToolBatch') {",
52362
52364
  " const toolCalls = Array.isArray(input.tool_calls) ? input.tool_calls : [];",
52363
- " return toolCalls.some((toolCall) => EDIT_TOOL_NAMES.has(toolCall.tool_name));",
52365
+ " process.exit(toolCalls.some((toolCall) => editToolNames.has(toolCall.tool_name)) ? 0 : 10);",
52364
52366
  " }",
52365
52367
  " const toolName = input.tool_name || input.toolName || input.tool;",
52366
- " return !toolName || EDIT_TOOL_NAMES.has(toolName);",
52367
- "};",
52368
+ " process.exit(!toolName || editToolNames.has(toolName) ? 0 : 10);",
52369
+ "} catch {",
52370
+ " process.exit(0);",
52371
+ "}",
52372
+ "NODE",
52373
+ "}",
52368
52374
  "",
52369
- "const runReactDoctor = (outputPath) => {",
52370
- " // Each candidate is a single shell command string (not an args array):",
52371
- " // `shell: true` is required to run the Windows `.cmd` shims, and an args",
52372
- " // array with `shell: true` trips Node's DEP0190. A missing command exits",
52373
- " // 127 via the shell (no ENOENT error), so fall through on that. With no",
52374
- " // runner found, exit 0 silently — stdout is parsed as the hook's JSON.",
52375
- " const commands = [",
52376
- " './node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score',",
52377
- " 'react-doctor --verbose --scope changed --blocking warning --no-score',",
52378
- " 'pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
52379
- " 'npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
52380
- " ];",
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",
52381
52380
  "",
52382
- " for (const command of commands) {",
52383
- " const result = spawnSync(command, { encoding: 'utf8', shell: true });",
52384
- " if (result.error?.code === 'ENOENT' || result.status === 127) continue;",
52385
- " try {",
52386
- " writeFileSync(outputPath, (result.stdout || '') + (result.stderr || ''));",
52387
- " } catch {}",
52388
- " return result.status;",
52389
- " }",
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",
52390
52385
  "",
52391
- " return 0;",
52392
- "};",
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",
52393
52390
  "",
52394
- "const cleanup = (...paths) => {",
52395
- " for (const path of paths) {",
52396
- " try { unlinkSync(path); } catch {}",
52397
- " }",
52398
- "};",
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",
52399
52395
  "",
52400
- "const main = () => {",
52401
- " let input;",
52402
- " try {",
52403
- " const stdinContent = readStdin();",
52404
- " input = JSON.parse(stdinContent || '{}');",
52405
- " } catch {",
52406
- " input = {};",
52407
- " }",
52396
+ " printf '%s\\n' 'react-doctor: command not found; skipping agent hook scan.'",
52397
+ " return 0",
52398
+ "}",
52408
52399
  "",
52409
- " if (!shouldScan(input)) {",
52410
- " process.exit(0);",
52411
- " }",
52400
+ "if ! should_scan; then",
52401
+ " exit 0",
52402
+ "fi",
52412
52403
  "",
52413
- " const projectRoot = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');",
52414
- " const outputPath = join(tmpdir(), `react-doctor-agent-hook-output-${process.pid}.txt`);",
52404
+ "if run_react_doctor > \"$output_file\" 2>&1; then",
52405
+ " exit 0",
52406
+ "fi",
52415
52407
  "",
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 = () => {",
52416
52413
  " try {",
52417
- " process.chdir(projectRoot);",
52414
+ " return JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
52418
52415
  " } catch {",
52419
- " process.exit(0);",
52420
- " }",
52421
- "",
52422
- " const scanResult = runReactDoctor(outputPath);",
52423
- " if (scanResult === 0) {",
52424
- " cleanup(outputPath);",
52425
- " process.exit(0);",
52426
- " }",
52427
- "",
52428
- " const scanOutput = readFileSync(outputPath, 'utf8').trim();",
52429
- " cleanup(outputPath);",
52430
- "",
52431
- " if (!scanOutput) {",
52432
- " process.exit(0);",
52433
- " }",
52434
- "",
52435
- " 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}`;",
52436
- "",
52437
- " if (input.hook_event_name === 'PostToolBatch') {",
52438
- " console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
52439
- " } else {",
52440
- " console.log(JSON.stringify({ additional_context: message }));",
52416
+ " return {};",
52441
52417
  " }",
52442
52418
  "};",
52443
- "",
52444
- "main();"
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
+ ""
52445
52430
  ].join("\n");
52446
52431
  const installReactDoctorAgentHooks = (options) => {
52447
52432
  const installedAgents = [];
@@ -53267,26 +53252,6 @@ const CLI_AGENT_AUTO_FLAGS = {
53267
53252
  codex: ["--yolo"],
53268
53253
  cursor: ["--force"]
53269
53254
  };
53270
- const resolveCursorBundledNode = () => {
53271
- if (!isWindows) return null;
53272
- const localAppData = process.env.LOCALAPPDATA;
53273
- if (!localAppData) return null;
53274
- const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
53275
- try {
53276
- const versions = NFS.readdirSync(cursorAgentRoot);
53277
- if (versions.length === 0) return null;
53278
- const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
53279
- for (const version of sortedVersions) try {
53280
- const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
53281
- const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
53282
- if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
53283
- command: bundledNodePath,
53284
- args: [entryScriptPath]
53285
- };
53286
- } catch {}
53287
- } catch {}
53288
- return null;
53289
- };
53290
53255
  const resolveWindowsCmdEntryScript = (command) => {
53291
53256
  if (!isWindows) return null;
53292
53257
  const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
@@ -53306,10 +53271,11 @@ const resolveWindowsCmdEntryScript = (command) => {
53306
53271
  }
53307
53272
  return null;
53308
53273
  };
53309
- const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
53274
+ const spawnAgent = (command, args, cwd, shell = false) => new Promise((resolve, reject) => {
53310
53275
  const child = spawn(command, [...args], {
53311
53276
  cwd,
53312
- stdio: "inherit"
53277
+ stdio: "inherit",
53278
+ shell
53313
53279
  });
53314
53280
  child.on("error", reject);
53315
53281
  child.on("close", (code) => resolve(code ?? 0));
@@ -53318,10 +53284,6 @@ const launchCliAgent = async (agentId, prompt, cwd) => {
53318
53284
  const binary = CLI_AGENT_BINARIES[agentId];
53319
53285
  const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
53320
53286
  if (isWindows) {
53321
- if (agentId === "cursor") {
53322
- const cursorResolution = resolveCursorBundledNode();
53323
- if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
53324
- }
53325
53287
  const entryScript = resolveWindowsCmdEntryScript(binary);
53326
53288
  if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
53327
53289
  }
@@ -54434,14 +54396,10 @@ const inspectAction = async (directory, flags) => {
54434
54396
  const isQuiet = isScoreOnly || isJsonMode;
54435
54397
  const requestedDirectory = Path.resolve(directory);
54436
54398
  const startTime = performance$1.now();
54437
- if (isJsonMode) {
54438
- enableJsonMode({
54439
- compact: Boolean(flags.jsonCompact),
54440
- directory: requestedDirectory,
54441
- outputFile: flags.jsonOut
54442
- });
54443
- if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
54444
- }
54399
+ if (isJsonMode) enableJsonMode({
54400
+ compact: Boolean(flags.jsonCompact),
54401
+ directory: requestedDirectory
54402
+ });
54445
54403
  recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
54446
54404
  try {
54447
54405
  validateModeFlags(flags);
@@ -55379,14 +55337,6 @@ const applyColorPreference = (argv, env = process.env) => {
55379
55337
  setColorEnabled(enabled);
55380
55338
  };
55381
55339
  //#endregion
55382
- //#region src/cli/utils/ensure-windows-utf8-console.ts
55383
- const ensureWindowsUtf8Console = () => {
55384
- if (process.platform !== "win32" || !process.stdout.isTTY) return;
55385
- try {
55386
- execFileSync("chcp.com", ["65001"], { stdio: "ignore" });
55387
- } catch {}
55388
- };
55389
- //#endregion
55390
55340
  //#region src/cli/utils/exit-gracefully.ts
55391
55341
  const exitGracefully = () => {
55392
55342
  try {
@@ -55529,7 +55479,6 @@ const ROOT_FLAG_SPEC = {
55529
55479
  "--category",
55530
55480
  "--changed-files-from",
55531
55481
  "--blocking",
55532
- "--json-out",
55533
55482
  "--output-dir",
55534
55483
  "--fail-on",
55535
55484
  "--project",
@@ -55720,7 +55669,7 @@ ${highlighter.dim("Learn more:")}
55720
55669
  ${highlighter.info(CANONICAL_GITHUB_URL)}
55721
55670
  `;
55722
55671
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
55723
- 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);
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);
55724
55673
  program.action(inspectAction);
55725
55674
  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));
55726
55675
  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);
@@ -55745,7 +55694,6 @@ if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
55745
55694
  process.exit(0);
55746
55695
  }
55747
55696
  applyColorPreference(strippedArgv);
55748
- ensureWindowsUtf8Console();
55749
55697
  const argv = normalizeHelpInvocation(strippedArgv, knownCommands);
55750
55698
  Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => program.parseAsync(argv)).then(() => flushSentry()).catch(async (error) => {
55751
55699
  const isUserError = isExpectedUserError(error);
@@ -55764,4 +55712,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
55764
55712
  export {};
55765
55713
 
55766
55714
  //# sourceMappingURL=cli.js.map
55767
- //# debugId=eb7a01bc-f737-5207-b430-b8fee7a1895f
55715
+ //# debugId=bd31e7dc-cfca-56b2-a963-9d798b3cbbe5
package/dist/lsp.js CHANGED
@@ -43047,8 +43047,7 @@ const METRIC = {
43047
43047
  lspScanCompleted: "lsp.scan.completed",
43048
43048
  lspScanDuration: "lsp.scan.duration",
43049
43049
  lspScanDiagnostics: "lsp.scan.diagnostics",
43050
- aiTrainingWarningShown: "ai.training.warning_shown",
43051
- jsonOutUsed: "json.out_used"
43050
+ aiTrainingWarningShown: "ai.training.warning_shown"
43052
43051
  };
43053
43052
  //#endregion
43054
43053
  //#region src/cli/utils/to-category-key.ts
@@ -43365,5 +43364,5 @@ const startLanguageServer = () => {
43365
43364
  };
43366
43365
  //#endregion
43367
43366
  export { startLanguageServer };
43368
- !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]="c5b6c740-461d-5f65-a993-9e6d35ad9113")}catch(e){}}();
43369
- //# debugId=c5b6c740-461d-5f65-a993-9e6d35ad9113
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.8-dev.a7ad969",
3
+ "version": "0.5.8-dev.b69f4a7",
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 <6",
63
+ "typescript": ">=5.0.4 <7",
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.a7ad969"
68
+ "oxlint-plugin-react-doctor": "0.5.8-dev.b69f4a7"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__code-frame": "^7.27.0",