react-doctor 0.5.8-dev.f4e8e4b → 0.5.8-dev.f853efd

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]="6a2c86b6-c3a8-5e50-8e7b-daef840df200")}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]="89eea142-f3f2-5f35-a6b0-e9e10055d0cb")}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";
@@ -26,7 +26,7 @@ import tty from "node:tty";
26
26
  import { codeFrameColumns } from "@babel/code-frame";
27
27
  import Conf from "conf";
28
28
  import basePrompts from "prompts";
29
- import { SKILL_MANIFEST_FILE, detectInstalledSkillAgents, getSkillAgentConfig, getSkillAgentTypes, installSkillsFromSource } from "agent-install";
29
+ import { SKILL_MANIFEST_FILE, detectInstalledSkillAgents, getSkillAgentConfig, getSkillAgentTypes, installSkillsFromSource, isSkillAgentType } from "agent-install";
30
30
  import { generateCode, loadFile, writeFile } from "magicast";
31
31
  import { getConfigFromVariableDeclaration, getDefaultExportOptions } from "magicast/helpers";
32
32
  //#region \0rolldown/runtime.js
@@ -37679,7 +37679,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
37679
37679
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
37680
37680
  }
37681
37681
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
37682
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
37682
+ const canonicalizeRuleKey = (ruleKey) => {
37683
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
37684
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
37685
+ };
37683
37686
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
37684
37687
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
37685
37688
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -38765,6 +38768,12 @@ const BOOLEAN_FIELD_NAMES = [
38765
38768
  "adoptExistingLintConfig"
38766
38769
  ];
38767
38770
  const STRING_FIELD_NAMES = ["rootDir"];
38771
+ const STRING_ARRAY_FIELD_NAMES = [
38772
+ "projects",
38773
+ "textComponents",
38774
+ "rawTextWrapperComponents",
38775
+ "serverAuthFunctionNames"
38776
+ ];
38768
38777
  const SURFACE_CONTROL_FIELD_NAMES = [
38769
38778
  "includeTags",
38770
38779
  "excludeTags",
@@ -38866,6 +38875,7 @@ const validateConfigTypes = (config) => {
38866
38875
  const validated = { ...config };
38867
38876
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
38868
38877
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
38878
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
38869
38879
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
38870
38880
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
38871
38881
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -41163,7 +41173,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
41163
41173
  "rev-parse",
41164
41174
  "--verify",
41165
41175
  branch
41166
- ]).pipe(map$3((result) => result.status === 0));
41176
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
41167
41177
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
41168
41178
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
41169
41179
  "merge-base",
@@ -44839,6 +44849,7 @@ const NANOSECONDS_PER_SECOND = 1000000000n;
44839
44849
  const METRIC = {
44840
44850
  cliInvoked: "cli.invoked",
44841
44851
  cliError: "cli.error",
44852
+ cliEnvironmentError: "cli.env_error",
44842
44853
  projectDetected: "project.detected",
44843
44854
  projectPathSelected: "project.path_selected",
44844
44855
  projectConfigSelected: "project.config_selected",
@@ -44911,7 +44922,7 @@ const makeNoopConsole = () => ({
44911
44922
  });
44912
44923
  //#endregion
44913
44924
  //#region src/cli/utils/version.ts
44914
- const VERSION = "0.5.8-dev.f4e8e4b";
44925
+ const VERSION = "0.5.8-dev.f853efd";
44915
44926
  //#endregion
44916
44927
  //#region src/cli/utils/json-mode.ts
44917
44928
  let context = null;
@@ -45275,13 +45286,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45275
45286
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45276
45287
  * standard `SENTRY_RELEASE` override.
45277
45288
  */
45278
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.f4e8e4b`;
45289
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.f853efd`;
45279
45290
  /**
45280
45291
  * Deployment environment shown in Sentry's environment filter. Defaults to
45281
45292
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45282
45293
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45283
45294
  */
45284
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.f4e8e4b") ? "development" : "production");
45295
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.f853efd") ? "development" : "production");
45285
45296
  /**
45286
45297
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45287
45298
  * (set to `0` to disable tracing) and falls back to
@@ -49689,6 +49700,8 @@ const ONBOARDING_EVENT = "onboarding";
49689
49700
  const CI_PITCH_EVENT = "ci-pitch";
49690
49701
  const ACTION_UPGRADE_EVENT = "action-upgrade-v2";
49691
49702
  const SETUP_HINT_EVENT = "setup-hint";
49703
+ const HANDOFF_TARGET_PREFERENCE_ID = "handoff-target";
49704
+ const INSTALL_AGENTS_PREFERENCE_ID = "install-agents";
49692
49705
  const foldLegacyDecisions = (projects, legacy, eventId) => {
49693
49706
  for (const [hash, record] of Object.entries(legacy ?? {})) {
49694
49707
  const existing = projects[hash] ?? { rootDirectory: record.rootDirectory ?? "" };
@@ -49807,6 +49820,14 @@ const recordGate = (gate, target = {}, options = {}) => updateCliState((state) =
49807
49820
  }
49808
49821
  }
49809
49822
  })), options);
49823
+ const readPreference = (preference, target = {}, options = {}) => readCliState((state) => selectScope(state, preference, target.projectRoot)?.preferences?.[preference.id] ?? null, null, options);
49824
+ const writePreference = (preference, value, target = {}, options = {}) => updateCliState((state) => updateScope(state, preference, target.projectRoot, (scope) => ({
49825
+ ...scope,
49826
+ preferences: {
49827
+ ...scope.preferences,
49828
+ [preference.id]: value
49829
+ }
49830
+ })), options);
49810
49831
  const isMigrationPending = (migration, target = {}, options = {}) => {
49811
49832
  if (migration.scope === "project" && target.projectRoot === void 0) return false;
49812
49833
  return readCliState((state) => {
@@ -50344,7 +50365,7 @@ const readPackageJson = (projectRoot) => {
50344
50365
  return null;
50345
50366
  }
50346
50367
  };
50347
- const writeJsonFile$1 = (filePath, value) => {
50368
+ const writeJsonFile = (filePath, value) => {
50348
50369
  NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
50349
50370
  };
50350
50371
  const packageHasDependency = (projectRoot, dependencyName) => {
@@ -51083,6 +51104,35 @@ const materializeStagedFiles = async (directory, stagedFiles, tempDirectory) =>
51083
51104
  };
51084
51105
  };
51085
51106
  //#endregion
51107
+ //#region src/cli/utils/is-environment-error.ts
51108
+ const isNodeSystemError = (error) => error instanceof Error && typeof error.code === "string";
51109
+ const ENVIRONMENT_ERROR_CODES = new Set([
51110
+ "ENOSPC",
51111
+ "EIO",
51112
+ "EROFS",
51113
+ "EACCES",
51114
+ "EPERM",
51115
+ "ENOTDIR"
51116
+ ]);
51117
+ const isEnvironmentError = (error) => {
51118
+ if (!isNodeSystemError(error)) return false;
51119
+ if (error.code === "ENOENT") return error.syscall?.startsWith("spawn") ?? false;
51120
+ return error.code !== void 0 && ENVIRONMENT_ERROR_CODES.has(error.code);
51121
+ };
51122
+ const formatEnvironmentError = (error) => {
51123
+ if (!isNodeSystemError(error)) return error instanceof Error ? error.message : String(error);
51124
+ switch (error.code) {
51125
+ case "ENOSPC": return "No space left on device. Free up disk space and try again.";
51126
+ case "EIO": return "I/O error: the filesystem or disk may be failing. Check your system logs.";
51127
+ case "EROFS": return "Read-only filesystem: cannot write to this location.";
51128
+ case "EACCES":
51129
+ case "EPERM": return error.path ? `Permission denied accessing ${error.path}. Check file permissions and try again.` : "Permission denied. Check file permissions and try again.";
51130
+ case "ENOTDIR": return error.path ? `A file exists at ${error.path} or one of its parent paths where a directory was expected.` : "A file exists where a directory was expected.";
51131
+ case "ENOENT": return "Required command not found. Ensure the tool (e.g. git) is installed and on your PATH.";
51132
+ default: return error.message;
51133
+ }
51134
+ };
51135
+ //#endregion
51086
51136
  //#region src/cli/utils/handle-error.ts
51087
51137
  const OTLP_ENDPOINT_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_ENDPOINT";
51088
51138
  const OTLP_AUTH_HEADER_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_AUTH_HEADER";
@@ -51165,15 +51215,19 @@ const handleError = (error, options = {}) => {
51165
51215
  process.exitCode = 1;
51166
51216
  };
51167
51217
  /**
51168
- * Renderer for expected, user-actionable failures — a bad `--diff` value or
51169
- * a base branch that isn't fetched. Prints just the (already human-readable)
51170
- * message no "Something went wrong", prefilled issue, Discord link, or
51171
- * Sentry reference because there is no bug to report.
51218
+ * Renderer for expected, user-actionable failures — a bad `--diff` value,
51219
+ * a base branch that isn't fetched, or environment errors like disk-full or
51220
+ * permission-denied. Prints just the (already human-readable) message no
51221
+ * "Something went wrong", prefilled issue, Discord link, or Sentry reference
51222
+ * — because there is no bug to report.
51172
51223
  */
51173
51224
  const handleUserError = (error, options = {}) => {
51225
+ const isEnvError = isEnvironmentError(error);
51226
+ if (isEnvError) recordCount(METRIC.cliEnvironmentError, 1, { code: error.code ?? "unknown" });
51227
+ const message = isEnvError ? formatEnvironmentError(error) : formatErrorForReport(error);
51174
51228
  runSync(gen(function* () {
51175
51229
  yield* error$1("");
51176
- yield* error$1(highlighter.error(formatErrorForReport(error)));
51230
+ yield* error$1(highlighter.error(message));
51177
51231
  yield* error$1("");
51178
51232
  }));
51179
51233
  if (options.shouldExit !== false) process.exit(1);
@@ -51188,7 +51242,7 @@ const handleUserError = (error, options = {}) => {
51188
51242
  * `handleUserError` (a plain message — no "Something went wrong", prefilled
51189
51243
  * issue, Discord link, or Sentry reference), since there is no bug to report.
51190
51244
  *
51191
- * Three distinct shapes reach the CLI's catch blocks:
51245
+ * Four distinct shapes reach the CLI's catch blocks:
51192
51246
  *
51193
51247
  * - **Project-discovery failures** (`NoReactDependencyError`,
51194
51248
  * `ProjectNotFoundError`, `PackageJsonNotFoundError`, `NotADirectoryError`,
@@ -51201,12 +51255,19 @@ const handleUserError = (error, options = {}) => {
51201
51255
  * `--project` name.
51202
51256
  * - **Bad `--diff` input** (`GitBaseBranchInvalid` / `GitBaseBranchMissing`)
51203
51257
  * stays the tagged `ReactDoctorError`, so dispatch on the reason `_tag`.
51258
+ * - **Environment failures** (`ENOSPC`, `EIO`, `EROFS`, `EACCES`, `EPERM`,
51259
+ * `ENOTDIR`, plus a `spawn`-scoped `ENOENT` for a missing binary) — disk
51260
+ * full / failing / read-only, permission denied, or a path blocked by a
51261
+ * file. React Doctor cannot fix the user's environment; exit cleanly with an
51262
+ * actionable message instead of crashing. See `is-environment-error.ts` for
51263
+ * why the set stays narrow (codes that usually mean our bug keep reaching
51264
+ * Sentry).
51204
51265
  *
51205
51266
  * This composes the existing core narrowers rather than introducing a new
51206
51267
  * error-shape helper (AGENTS.md): it encodes CLI-layer reporting policy, not
51207
51268
  * knowledge of the `ReactDoctorError` shape.
51208
51269
  */
51209
- const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
51270
+ const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isEnvironmentError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
51210
51271
  //#endregion
51211
51272
  //#region src/cli/utils/build-handoff-payload.ts
51212
51273
  const buildHandoffPayload = (input) => {
@@ -51287,6 +51348,20 @@ const detectAvailableAgents = async () => {
51287
51348
  const detected = new Set([...detectPathAvailableAgents(), ...await detectInstalledSkillAgents()]);
51288
51349
  return getSkillAgentTypes().filter((agent) => agent !== "universal" && detected.has(agent));
51289
51350
  };
51351
+ const DEFAULT_INSTALL_AGENTS = [
51352
+ "claude-code",
51353
+ "cursor",
51354
+ "codex",
51355
+ "opencode"
51356
+ ];
51357
+ const computeDefaultSelectedAgents = (detectedAgents, rememberedAgents) => {
51358
+ const detected = new Set(detectedAgents);
51359
+ const remembered = rememberedAgents.filter((agent) => detected.has(agent));
51360
+ if (remembered.length > 0) return remembered;
51361
+ const defaults = DEFAULT_INSTALL_AGENTS.filter((agent) => detected.has(agent));
51362
+ if (defaults.length > 0) return defaults;
51363
+ return detectedAgents.length === 1 ? [...detectedAgents] : [];
51364
+ };
51290
51365
  //#endregion
51291
51366
  //#region src/cli/utils/install-doctor-script.ts
51292
51367
  const DOCTOR_SCRIPT_NAME = "doctor";
@@ -51368,7 +51443,7 @@ const installDoctorScript = (options) => {
51368
51443
  };
51369
51444
  })();
51370
51445
  const scriptStatus = scriptTarget.status;
51371
- if (scriptStatus === "created") writeJsonFile$1(packageJsonPath, {
51446
+ if (scriptStatus === "created") writeJsonFile(packageJsonPath, {
51372
51447
  ...packageJson,
51373
51448
  scripts: {
51374
51449
  ...isRecord$1(scripts) ? scripts : {},
@@ -51544,6 +51619,14 @@ const recordCiPromptDecision = (projectRoot, outcome, options = {}) => recordGat
51544
51619
  outcome
51545
51620
  }, options);
51546
51621
  //#endregion
51622
+ //#region src/cli/utils/handoff-target-preference.ts
51623
+ const HANDOFF_TARGET_PREFERENCE = {
51624
+ id: HANDOFF_TARGET_PREFERENCE_ID,
51625
+ scope: "global"
51626
+ };
51627
+ const readHandoffTarget = (options = {}) => readPreference(HANDOFF_TARGET_PREFERENCE, {}, options);
51628
+ const rememberHandoffTarget = (target, options = {}) => writePreference(HANDOFF_TARGET_PREFERENCE, target, {}, options);
51629
+ //#endregion
51547
51630
  //#region src/cli/utils/open-url.ts
51548
51631
  const resolveOpenCommand = (url) => {
51549
51632
  if (process$1.platform === "darwin") return {
@@ -51931,6 +52014,19 @@ const setUpGitHubActions = async (options) => {
51931
52014
  return didCreateWorkflow;
51932
52015
  };
51933
52016
  //#endregion
52017
+ //#region src/cli/utils/install-agents-preference.ts
52018
+ const INSTALL_AGENTS_PREFERENCE = {
52019
+ id: INSTALL_AGENTS_PREFERENCE_ID,
52020
+ scope: "global"
52021
+ };
52022
+ const PREFERENCE_SEPARATOR = ",";
52023
+ const readInstallAgents = (options = {}) => {
52024
+ const stored = readPreference(INSTALL_AGENTS_PREFERENCE, {}, options);
52025
+ if (stored === null) return [];
52026
+ return stored.split(PREFERENCE_SEPARATOR).map((entry) => entry.trim()).filter((entry) => isSkillAgentType(entry));
52027
+ };
52028
+ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_AGENTS_PREFERENCE, agents.join(PREFERENCE_SEPARATOR), {}, options);
52029
+ //#endregion
51934
52030
  //#region src/cli/utils/install-agent-hooks.ts
51935
52031
  const CLAUDE_AGENT = "claude-code";
51936
52032
  const CURSOR_AGENT = "cursor";
@@ -51941,20 +52037,34 @@ const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
51941
52037
  const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.sh";
51942
52038
  const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
51943
52039
  const CURSOR_HOOKS_SCHEMA_VERSION = 1;
51944
- const JSON_INDENT_SPACES$1 = 2;
51945
52040
  const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
51946
52041
  const readJsonFile = (filePath, fallback) => {
51947
52042
  if (!NFS.existsSync(filePath)) return fallback;
51948
52043
  const content = NFS.readFileSync(filePath, "utf8").trim();
51949
52044
  if (content.length === 0) return fallback;
51950
- return JSON.parse(content);
52045
+ try {
52046
+ return JSON.parse(content);
52047
+ } catch (error) {
52048
+ if (error instanceof SyntaxError) throw new CliInputError(`Could not parse ${filePath}: the file contains invalid JSON. Fix the syntax errors in this file and re-run the install command.`);
52049
+ throw error;
52050
+ }
51951
52051
  };
51952
- const writeJsonFile = (filePath, value) => {
51953
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
51954
- NFS.writeFileSync(filePath, `${JSON.stringify(value, null, JSON_INDENT_SPACES$1)}\n`);
52052
+ const ensureDirectoryExists = (directoryPath) => {
52053
+ try {
52054
+ NFS.mkdirSync(directoryPath, { recursive: true });
52055
+ } catch (error) {
52056
+ const code = error.code;
52057
+ if (code === "EACCES" || code === "EPERM") throw new CliInputError(`Could not create directory ${directoryPath}: permission denied. Ensure you have write permissions for this location and re-run the install command.`);
52058
+ if (code === "ENOTDIR" || code === "EEXIST") throw new CliInputError(`Could not create directory ${directoryPath}: a file exists at this path or one of its parent paths. Remove the conflicting file and re-run the install command.`);
52059
+ throw error;
52060
+ }
52061
+ };
52062
+ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
52063
+ ensureDirectoryExists(Path.dirname(filePath));
52064
+ writeJsonFile(filePath, value);
51955
52065
  };
51956
52066
  const writeHookScript = (filePath) => {
51957
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
52067
+ ensureDirectoryExists(Path.dirname(filePath));
51958
52068
  NFS.writeFileSync(filePath, buildAgentHookScript());
51959
52069
  NFS.chmodSync(filePath, 493);
51960
52070
  };
@@ -51970,7 +52080,7 @@ const installClaudeHook = (projectRoot) => {
51970
52080
  command: CLAUDE_HOOK_COMMAND
51971
52081
  }] });
51972
52082
  hooks.PostToolBatch = postToolBatchHooks;
51973
- writeJsonFile(settingsPath, {
52083
+ writeJsonFileWithDirectoryCheck(settingsPath, {
51974
52084
  ...settings,
51975
52085
  hooks
51976
52086
  });
@@ -51990,7 +52100,7 @@ const installCursorHook = (projectRoot) => {
51990
52100
  timeout: 120
51991
52101
  });
51992
52102
  hooks.postToolUse = postToolUseHooks;
51993
- writeJsonFile(configPath, {
52103
+ writeJsonFileWithDirectoryCheck(configPath, {
51994
52104
  ...config,
51995
52105
  version: config.version ?? CURSOR_HOOKS_SCHEMA_VERSION,
51996
52106
  hooks
@@ -52226,7 +52336,7 @@ const installPackageJsonHook = (options, strategy) => {
52226
52336
  parent = cloned;
52227
52337
  }
52228
52338
  parent[leafKey] = strategy.leafShape === "array" ? appendArrayCommand(parent[leafKey]) : appendStringCommand(parent[leafKey]);
52229
- writeJsonFile$1(packageJsonPath, nextPackageJson);
52339
+ writeJsonFile(packageJsonPath, nextPackageJson);
52230
52340
  removeLegacyManagedRunner(options.projectRoot);
52231
52341
  return {
52232
52342
  hookPath: packageJsonPath,
@@ -52809,6 +52919,9 @@ const runInstallReactDoctor = async (options = {}) => {
52809
52919
  const shouldUpgradeWorkflow = canUpgradeWorkflow && (Boolean(options.yes) || upgradePromptOutcome === "yes");
52810
52920
  if (upgradePromptOutcome === "no" && !options.dryRun) recordActionUpgradeDecision(projectRoot, "declined");
52811
52921
  if ((ciPromptOutcome === "yes" || ciPromptOutcome === "no") && !options.dryRun) recordCiPromptDecision(projectRoot, ciPromptOutcome === "yes" ? "accepted" : "declined");
52922
+ const rememberedAgents = options.lastSelectedAgents ?? readInstallAgents();
52923
+ const defaultSelectedAgents = computeDefaultSelectedAgents(detectedAgents, rememberedAgents);
52924
+ const usedRememberedAgents = rememberedAgents.some((agent) => detectedAgents.includes(agent));
52812
52925
  const selectedAgents = skipPrompts ? detectedAgents : (await prompt({
52813
52926
  type: "multiselect",
52814
52927
  name: "agents",
@@ -52816,12 +52929,13 @@ const runInstallReactDoctor = async (options = {}) => {
52816
52929
  choices: detectedAgents.map((agent) => ({
52817
52930
  title: getSkillAgentConfig(agent).displayName,
52818
52931
  value: agent,
52819
- selected: true
52932
+ selected: defaultSelectedAgents.includes(agent)
52820
52933
  })),
52821
52934
  instructions: false,
52822
52935
  min: 1
52823
52936
  }, promptOptions)).agents ?? [];
52824
52937
  if (selectedAgents.length === 0) return;
52938
+ if (!skipPrompts && !options.dryRun) rememberInstallAgents(selectedAgents);
52825
52939
  let dependencyResult;
52826
52940
  if (!options.dryRun) {
52827
52941
  await installReactDoctorSkillStep(sourceDir, selectedAgents, projectRoot);
@@ -52880,6 +52994,8 @@ const runInstallReactDoctor = async (options = {}) => {
52880
52994
  }
52881
52995
  recordCount(METRIC.installCompleted, 1, {
52882
52996
  agentsCount: selectedAgents.length,
52997
+ agentsDetected: detectedAgents.length,
52998
+ usedRememberedAgents,
52883
52999
  gitHook: shouldInstallGitHook,
52884
53000
  agentHooks: shouldInstallAgentHooks,
52885
53001
  workflow: didInstallWorkflow,
@@ -53078,29 +53194,34 @@ const handoffToAgent = async (input) => {
53078
53194
  outcome: "ci-suppressed",
53079
53195
  diagnosticsCount: input.diagnostics.length
53080
53196
  });
53197
+ const choices = [
53198
+ ...(await detectLaunchableAgents()).map((agentId) => ({
53199
+ title: getSkillAgentConfig(agentId).displayName,
53200
+ description: `Open ${CLI_AGENT_BINARIES[agentId]} here with the top issues as a prompt`,
53201
+ value: agentId
53202
+ })),
53203
+ {
53204
+ title: "Copy prompt to clipboard",
53205
+ description: "Paste into any agent or chat",
53206
+ value: CLIPBOARD_CHOICE
53207
+ },
53208
+ {
53209
+ title: "Skip",
53210
+ description: "Don't hand off",
53211
+ value: SKIP_CHOICE
53212
+ }
53213
+ ];
53214
+ const rememberedTarget = readHandoffTarget();
53215
+ const rememberedChoiceIndex = choices.findIndex((choice) => choice.value === rememberedTarget);
53216
+ const initial = rememberedChoiceIndex >= 0 ? rememberedChoiceIndex : 0;
53081
53217
  const { handoffTarget } = await prompts({
53082
53218
  type: "select",
53083
53219
  name: "handoffTarget",
53084
53220
  message: "What would you like to do next?",
53085
- choices: [
53086
- ...(await detectLaunchableAgents()).map((agentId) => ({
53087
- title: getSkillAgentConfig(agentId).displayName,
53088
- description: `Open ${CLI_AGENT_BINARIES[agentId]} here with the top issues as a prompt`,
53089
- value: agentId
53090
- })),
53091
- {
53092
- title: "Copy prompt to clipboard",
53093
- description: "Paste into any agent or chat",
53094
- value: CLIPBOARD_CHOICE
53095
- },
53096
- {
53097
- title: "Skip",
53098
- description: "Don't hand off",
53099
- value: SKIP_CHOICE
53100
- }
53101
- ],
53102
- initial: 0
53221
+ choices,
53222
+ initial
53103
53223
  }, { onCancel: () => true });
53224
+ if (handoffTarget !== void 0) rememberHandoffTarget(handoffTarget);
53104
53225
  let handoffOutcome = "launch";
53105
53226
  if (handoffTarget === void 0) handoffOutcome = "cancel";
53106
53227
  else if (handoffTarget === SKIP_CHOICE) handoffOutcome = "skip";
@@ -53108,7 +53229,9 @@ const handoffToAgent = async (input) => {
53108
53229
  recordCount(METRIC.agentHandoff, 1, {
53109
53230
  outcome: handoffOutcome,
53110
53231
  agent: handoffOutcome === "launch" ? handoffTarget : void 0,
53111
- diagnosticsCount: input.diagnostics.length
53232
+ diagnosticsCount: input.diagnostics.length,
53233
+ defaultRemembered: rememberedChoiceIndex >= 0,
53234
+ keptDefault: handoffTarget === choices[initial].value
53112
53235
  });
53113
53236
  if (handoffTarget === void 0 || handoffTarget === SKIP_CHOICE) return;
53114
53237
  const payload = buildHandoffPayload({
@@ -53652,7 +53775,7 @@ const warnDeprecatedDiff = (flags, userConfig) => {
53652
53775
  };
53653
53776
  const warnDiffUnavailable = (requested, isQuiet) => {
53654
53777
  if (isQuiet) return;
53655
- if (typeof requested.base === "string") cliLogger.warn(`Could not compute diff against "${requested.base}" (merge-base failed or HEAD has no history). Running full scan.`);
53778
+ if (typeof requested.base === "string") cliLogger.warn(`Could not compute diff against "${requested.base}" (git unavailable, ref not found, or merge-base failed). Running full scan.`);
53656
53779
  else cliLogger.warn("No feature branch or uncommitted changes detected. Running full scan.");
53657
53780
  cliLogger.break();
53658
53781
  };
@@ -54343,6 +54466,10 @@ const installAction = async (options, command) => {
54343
54466
  projectRoot: options.cwd ?? process.cwd()
54344
54467
  });
54345
54468
  } catch (error) {
54469
+ if (isExpectedUserError(error)) {
54470
+ handleUserError(error);
54471
+ return;
54472
+ }
54346
54473
  handleError(error, { sentryEventId: await reportErrorToSentry(error) });
54347
54474
  }
54348
54475
  };
@@ -55350,4 +55477,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
55350
55477
  export {};
55351
55478
 
55352
55479
  //# sourceMappingURL=cli.js.map
55353
- //# debugId=6a2c86b6-c3a8-5e50-8e7b-daef840df200
55480
+ //# debugId=89eea142-f3f2-5f35-a6b0-e9e10055d0cb
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]="33508ee6-c977-5b5f-8585-9939928ce74d")}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]="62d09923-e78d-5501-8799-859098af2aaa")}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";
@@ -34487,7 +34487,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34487
34487
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
34488
34488
  }
34489
34489
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34490
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34490
+ const canonicalizeRuleKey = (ruleKey) => {
34491
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
34492
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
34493
+ };
34491
34494
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34492
34495
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34493
34496
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -35577,6 +35580,12 @@ const BOOLEAN_FIELD_NAMES = [
35577
35580
  "adoptExistingLintConfig"
35578
35581
  ];
35579
35582
  const STRING_FIELD_NAMES = ["rootDir"];
35583
+ const STRING_ARRAY_FIELD_NAMES = [
35584
+ "projects",
35585
+ "textComponents",
35586
+ "rawTextWrapperComponents",
35587
+ "serverAuthFunctionNames"
35588
+ ];
35580
35589
  const SURFACE_CONTROL_FIELD_NAMES = [
35581
35590
  "includeTags",
35582
35591
  "excludeTags",
@@ -35678,6 +35687,7 @@ const validateConfigTypes = (config) => {
35678
35687
  const validated = { ...config };
35679
35688
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35680
35689
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
35690
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
35681
35691
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35682
35692
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35683
35693
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -37931,7 +37941,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37931
37941
  "rev-parse",
37932
37942
  "--verify",
37933
37943
  branch
37934
- ]).pipe(map$3((result) => result.status === 0));
37944
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
37935
37945
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37936
37946
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37937
37947
  "merge-base",
@@ -41469,4 +41479,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41469
41479
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41470
41480
 
41471
41481
  //# sourceMappingURL=index.js.map
41472
- //# debugId=33508ee6-c977-5b5f-8585-9939928ce74d
41482
+ //# debugId=62d09923-e78d-5501-8799-859098af2aaa
package/dist/lsp.js CHANGED
@@ -34546,7 +34546,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34546
34546
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
34547
34547
  }
34548
34548
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34549
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34549
+ const canonicalizeRuleKey = (ruleKey) => {
34550
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
34551
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
34552
+ };
34550
34553
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34551
34554
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34552
34555
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -35610,6 +35613,12 @@ const BOOLEAN_FIELD_NAMES = [
35610
35613
  "adoptExistingLintConfig"
35611
35614
  ];
35612
35615
  const STRING_FIELD_NAMES = ["rootDir"];
35616
+ const STRING_ARRAY_FIELD_NAMES = [
35617
+ "projects",
35618
+ "textComponents",
35619
+ "rawTextWrapperComponents",
35620
+ "serverAuthFunctionNames"
35621
+ ];
35613
35622
  const SURFACE_CONTROL_FIELD_NAMES = [
35614
35623
  "includeTags",
35615
35624
  "excludeTags",
@@ -35711,6 +35720,7 @@ const validateConfigTypes = (config) => {
35711
35720
  const validated = { ...config };
35712
35721
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35713
35722
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
35723
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
35714
35724
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35715
35725
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35716
35726
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -37916,7 +37926,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37916
37926
  "rev-parse",
37917
37927
  "--verify",
37918
37928
  branch
37919
- ]).pipe(map$3((result) => result.status === 0));
37929
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
37920
37930
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37921
37931
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37922
37932
  "merge-base",
@@ -42903,6 +42913,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
42903
42913
  const METRIC = {
42904
42914
  cliInvoked: "cli.invoked",
42905
42915
  cliError: "cli.error",
42916
+ cliEnvironmentError: "cli.env_error",
42906
42917
  projectDetected: "project.detected",
42907
42918
  projectPathSelected: "project.path_selected",
42908
42919
  projectConfigSelected: "project.config_selected",
@@ -43249,5 +43260,5 @@ const startLanguageServer = () => {
43249
43260
  };
43250
43261
  //#endregion
43251
43262
  export { startLanguageServer };
43252
- !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]="9ee72ccc-703c-588f-b7e4-c92657144721")}catch(e){}}();
43253
- //# debugId=9ee72ccc-703c-588f-b7e4-c92657144721
43263
+ !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]="1b85b637-9e6d-554c-a429-3652a14706b0")}catch(e){}}();
43264
+ //# debugId=1b85b637-9e6d-554c-a429-3652a14706b0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.8-dev.f4e8e4b",
3
+ "version": "0.5.8-dev.f853efd",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -63,8 +63,8 @@
63
63
  "vscode-languageserver": "^9.0.1",
64
64
  "vscode-languageserver-textdocument": "^1.0.12",
65
65
  "vscode-uri": "^3.1.0",
66
- "oxlint-plugin-react-doctor": "0.5.8-dev.f4e8e4b",
67
- "deslop-js": "0.5.8"
66
+ "deslop-js": "0.5.8",
67
+ "oxlint-plugin-react-doctor": "0.5.8-dev.f853efd"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",