react-doctor 0.2.14-dev.ac3ca1a → 0.2.14-dev.b612664

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/README.md CHANGED
@@ -69,6 +69,8 @@ jobs:
69
69
 
70
70
  React Doctor scans the files changed in the pull request, emits inline annotations, blocks on error-level findings, and updates one sticky PR comment with the score and issue summary. The built-in GitHub token is used automatically; no secret or PAT is required. On forked PRs where GitHub withholds write permissions, the scan and annotations still run, but the sticky comment may be skipped.
71
71
 
72
+ **Permissions:** set `permissions: { contents: read, pull-requests: write }` so React Doctor can read the pull request's changed files for a changed-files-only scan and post the sticky summary comment. If `pull-requests: read` is unavailable (for example on fork PRs or with a restricted default token), the action degrades gracefully to a full-project scan instead of failing.
73
+
72
74
  [Add GitHub Action →](https://github.com/marketplace/actions/react-doctor)
73
75
 
74
76
  ### 4. Configure rules in `react-doctor.config.json`
@@ -6941,17 +6941,21 @@ var Reporter = class Reporter extends Context.Service()("react-doctor/Reporter")
6941
6941
  });
6942
6942
  }));
6943
6943
  };
6944
- const parseScoreResult = (value) => {
6945
- if (typeof value !== "object" || value === null) return null;
6946
- if (!("score" in value) || !("label" in value)) return null;
6947
- const scoreValue = Reflect.get(value, "score");
6948
- const labelValue = Reflect.get(value, "label");
6949
- if (typeof scoreValue !== "number" || typeof labelValue !== "string") return null;
6950
- return {
6951
- score: scoreValue,
6952
- label: labelValue
6953
- };
6954
- };
6944
+ const RulePrioritySchema = Schema.Struct({
6945
+ priority: Schema.NullOr(Schema.Number),
6946
+ tier: Schema.Literals([
6947
+ "P0",
6948
+ "P1",
6949
+ "P2",
6950
+ "P3"
6951
+ ])
6952
+ });
6953
+ const ScoreApiResponseSchema = Schema.Struct({
6954
+ score: Schema.Number,
6955
+ label: Schema.String,
6956
+ rules: Schema.optional(Schema.Record(Schema.String, RulePrioritySchema))
6957
+ });
6958
+ const parseScoreResult = (value) => Option.getOrNull(Schema.decodeUnknownOption(ScoreApiResponseSchema)(value));
6955
6959
  const stripFilePaths = (diagnostics) => diagnostics.map(({ filePath: _filePath, ...rest }) => rest);
6956
6960
  const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
6957
6961
  const describeFailure = (error) => {
@@ -7715,4 +7719,4 @@ const cliLogger = {
7715
7719
  //#endregion
7716
7720
  export { highlighter as A, discoverReactSubprojects as C, formatReactDoctorError as D, formatErrorChain as E, resolveScanTarget as F, restoreLegacyThrow as I, runInspect as L, isReactDoctorError as M, layerOtlp as N, getDiffInfo as O, listWorkspacePackages as P, toRelativePath as R, buildRulePromptUrl as S, filterSourceFiles as T, SKILL_NAME as _, DeadCode as a, buildJsonReport as b, Git as c, NodeResolver as d, OXLINT_NODE_REQUIREMENT as f, SHARE_BASE_URL as g, Reporter as h, Config as i, isMonorepoRoot as j, groupBy as k, LintPartialFailures as l, Project as m, cli_logger_exports as n, ENTERPRISE_CONTACT_URL as o, Progress as p, CANONICAL_GITHUB_URL as r, Files as s, cliLogger as t, Linter as u, Score as v, filterDiagnosticsForSurface as w, buildJsonReportError as x, StagedFiles as y };
7717
7721
 
7718
- //# sourceMappingURL=cli-logger-Cqq0L4Uo.js.map
7722
+ //# sourceMappingURL=cli-logger-BgVL1vBI.js.map
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
2
- import { A as highlighter, C as discoverReactSubprojects, D as formatReactDoctorError, E as formatErrorChain, F as resolveScanTarget, I as restoreLegacyThrow, L as runInspect, M as isReactDoctorError, N as layerOtlp, O as getDiffInfo, P as listWorkspacePackages, R as toRelativePath, S as buildRulePromptUrl, T as filterSourceFiles, _ as SKILL_NAME, a as DeadCode, b as buildJsonReport, c as Git, d as NodeResolver, f as OXLINT_NODE_REQUIREMENT, g as SHARE_BASE_URL, h as Reporter, i as Config, j as isMonorepoRoot, k as groupBy, l as LintPartialFailures, m as Project, o as ENTERPRISE_CONTACT_URL, p as Progress, r as CANONICAL_GITHUB_URL, s as Files, t as cliLogger, u as Linter, v as Score, w as filterDiagnosticsForSurface, x as buildJsonReportError, y as StagedFiles } from "./cli-logger-Cqq0L4Uo.js";
2
+ import { A as highlighter, C as discoverReactSubprojects, D as formatReactDoctorError, E as formatErrorChain, F as resolveScanTarget, I as restoreLegacyThrow, L as runInspect, M as isReactDoctorError, N as layerOtlp, O as getDiffInfo, P as listWorkspacePackages, R as toRelativePath, S as buildRulePromptUrl, T as filterSourceFiles, _ as SKILL_NAME, a as DeadCode, b as buildJsonReport, c as Git, d as NodeResolver, f as OXLINT_NODE_REQUIREMENT, g as SHARE_BASE_URL, h as Reporter, i as Config, j as isMonorepoRoot, k as groupBy, l as LintPartialFailures, m as Project, o as ENTERPRISE_CONTACT_URL, p as Progress, r as CANONICAL_GITHUB_URL, s as Files, t as cliLogger, u as Linter, v as Score, w as filterDiagnosticsForSurface, x as buildJsonReportError, y as StagedFiles } from "./cli-logger-BgVL1vBI.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { execFileSync, execSync } from "node:child_process";
5
5
  import path, { join } from "node:path";
@@ -6198,7 +6198,22 @@ const SEVERITY_ORDER = {
6198
6198
  warning: 1
6199
6199
  };
6200
6200
  const colorizeBySeverity = (text, severity) => severity === "error" ? highlighter.error(text) : highlighter.warn(text);
6201
- const sortByImportance = (diagnosticGroups) => diagnosticGroups.toSorted(([, diagnosticsA], [, diagnosticsB]) => {
6201
+ const buildRulePriorityMap = (scores) => {
6202
+ const rulePriority = /* @__PURE__ */ new Map();
6203
+ for (const score of scores) {
6204
+ if (!score?.rules) continue;
6205
+ for (const [ruleKey, info] of Object.entries(score.rules)) if (typeof info.priority === "number") rulePriority.set(ruleKey, info.priority);
6206
+ }
6207
+ return rulePriority;
6208
+ };
6209
+ const effectivePriority = (ruleKey, diagnostics, rulePriority) => {
6210
+ const known = rulePriority?.get(ruleKey);
6211
+ if (known !== void 0) return known;
6212
+ return diagnostics[0].severity === "error" ? 55 : 35;
6213
+ };
6214
+ const sortByImportance = (diagnosticGroups, rulePriority) => diagnosticGroups.toSorted(([ruleKeyA, diagnosticsA], [ruleKeyB, diagnosticsB]) => {
6215
+ const priorityDelta = effectivePriority(ruleKeyB, diagnosticsB, rulePriority) - effectivePriority(ruleKeyA, diagnosticsA, rulePriority);
6216
+ if (priorityDelta !== 0) return priorityDelta;
6202
6217
  const severityDelta = SEVERITY_ORDER[diagnosticsA[0].severity] - SEVERITY_ORDER[diagnosticsB[0].severity];
6203
6218
  if (severityDelta !== 0) return severityDelta;
6204
6219
  return diagnosticsB.length - diagnosticsA.length;
@@ -6235,14 +6250,20 @@ const buildCompactRuleGroupLine = (ruleKey, ruleDiagnostics, ruleNameColumnWidth
6235
6250
  return ` ${icon} ${siteCountBadge.length > 0 ? colorizeBySeverity(padRuleNameToColumn(ruleKey, ruleNameColumnWidth), firstDiagnostic.severity) : colorizeBySeverity(ruleKey, firstDiagnostic.severity)}${siteCountBadge.length > 0 ? ` ${highlighter.gray(siteCountBadge)}` : ""}`;
6236
6251
  };
6237
6252
  const getWorstSeverity = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "error" : "warning";
6238
- const buildCategoryDiagnosticGroups = (diagnostics) => {
6253
+ const categoryTopPriority = (categoryGroup, rulePriority) => {
6254
+ const [topRuleKey, topDiagnostics] = categoryGroup.ruleGroups[0];
6255
+ return effectivePriority(topRuleKey, topDiagnostics, rulePriority);
6256
+ };
6257
+ const buildCategoryDiagnosticGroups = (diagnostics, rulePriority) => {
6239
6258
  return [...groupBy(diagnostics, (diagnostic) => diagnostic.category).entries()].map(([category, categoryDiagnostics]) => {
6240
6259
  return {
6241
6260
  category,
6242
6261
  diagnostics: categoryDiagnostics,
6243
- ruleGroups: sortByImportance([...groupBy(categoryDiagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()])
6262
+ ruleGroups: sortByImportance([...groupBy(categoryDiagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()], rulePriority)
6244
6263
  };
6245
6264
  }).toSorted((categoryGroupA, categoryGroupB) => {
6265
+ const priorityDelta = categoryTopPriority(categoryGroupB, rulePriority) - categoryTopPriority(categoryGroupA, rulePriority);
6266
+ if (priorityDelta !== 0) return priorityDelta;
6246
6267
  const severityDelta = SEVERITY_ORDER[getWorstSeverity(categoryGroupA.diagnostics)] - SEVERITY_ORDER[getWorstSeverity(categoryGroupB.diagnostics)];
6247
6268
  if (severityDelta !== 0) return severityDelta;
6248
6269
  if (categoryGroupA.diagnostics.length !== categoryGroupB.diagnostics.length) return categoryGroupB.diagnostics.length - categoryGroupA.diagnostics.length;
@@ -6273,8 +6294,8 @@ const buildVerboseRuleGroupLines = (ruleKey, ruleDiagnostics, ruleNameColumnWidt
6273
6294
  lines.push("");
6274
6295
  return lines;
6275
6296
  };
6276
- const buildDefaultDiagnosticsLines = (diagnostics) => {
6277
- const categoryGroups = buildCategoryDiagnosticGroups(diagnostics);
6297
+ const buildDefaultDiagnosticsLines = (diagnostics, rulePriority) => {
6298
+ const categoryGroups = buildCategoryDiagnosticGroups(diagnostics, rulePriority);
6278
6299
  const lines = [];
6279
6300
  for (const categoryGroup of categoryGroups) lines.push(buildCompactCategoryLine(categoryGroup));
6280
6301
  lines.push("");
@@ -6286,11 +6307,11 @@ const buildDefaultDiagnosticsLines = (diagnostics) => {
6286
6307
  * single Effect.forEach over Console.log so failures or fiber
6287
6308
  * interruption produce predictable partial output.
6288
6309
  */
6289
- const printDiagnostics = (diagnostics, isVerbose, rootDirectory) => Effect.gen(function* () {
6310
+ const printDiagnostics = (diagnostics, isVerbose, rootDirectory, rulePriority) => Effect.gen(function* () {
6290
6311
  let lines;
6291
- if (!isVerbose) lines = buildDefaultDiagnosticsLines(diagnostics);
6312
+ if (!isVerbose) lines = buildDefaultDiagnosticsLines(diagnostics, rulePriority);
6292
6313
  else {
6293
- const sortedRuleGroups = sortByImportance([...groupBy(diagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()]);
6314
+ const sortedRuleGroups = sortByImportance([...groupBy(diagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()], rulePriority);
6294
6315
  const ruleNameColumnWidth = computeRuleNameColumnWidth(sortedRuleGroups.map(([ruleKey]) => ruleKey));
6295
6316
  lines = sortedRuleGroups.flatMap(([ruleKey, ruleDiagnostics]) => buildVerboseRuleGroupLines(ruleKey, ruleDiagnostics, ruleNameColumnWidth));
6296
6317
  }
@@ -6686,7 +6707,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
6686
6707
  const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
6687
6708
  //#endregion
6688
6709
  //#region src/cli/utils/version.ts
6689
- const VERSION = "0.2.14-dev.ac3ca1a";
6710
+ const VERSION = "0.2.14-dev.b612664";
6690
6711
  //#endregion
6691
6712
  //#region src/inspect.ts
6692
6713
  const silentConsole = makeNoopConsole();
@@ -6847,7 +6868,7 @@ const finalizeAndRender = (input) => Effect.gen(function* () {
6847
6868
  return buildResult();
6848
6869
  }
6849
6870
  yield* Console.log("");
6850
- yield* printDiagnostics([...surfaceDiagnostics], options.verbose, directory);
6871
+ yield* printDiagnostics([...surfaceDiagnostics], options.verbose, directory, buildRulePriorityMap([score]));
6851
6872
  if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
6852
6873
  if (demotedDiagnosticCount > 0) {
6853
6874
  yield* Console.log(highlighter.gray(` ${demotedDiagnosticCount} demoted from the ${options.outputSurface} surface (e.g. design cleanup) — run \`npx react-doctor@latest .\` locally for the full list.`));
@@ -7236,7 +7257,7 @@ const printMultiProjectSummary = (input) => Effect.gen(function* () {
7236
7257
  const surfaceDiagnostics = filterDiagnosticsForSurface(completedScans.flatMap((scan) => scan.result.diagnostics), "cli", userConfig);
7237
7258
  if (surfaceDiagnostics.length > 0) {
7238
7259
  yield* Console.log("");
7239
- yield* printDiagnostics(surfaceDiagnostics, verbose, "");
7260
+ yield* printDiagnostics(surfaceDiagnostics, verbose, "", buildRulePriorityMap(completedScans.map((scan) => scan.result.score)));
7240
7261
  }
7241
7262
  const aggregateScore = computeAggregateScore(completedScans);
7242
7263
  const totalSourceFileCount = completedScans.reduce((sum, scan) => sum + scan.result.project.sourceFileCount, 0);
@@ -7507,7 +7528,7 @@ const warnSetupPromptFailure = async (options, error) => {
7507
7528
  return;
7508
7529
  }
7509
7530
  try {
7510
- const { cliLogger } = await import("./cli-logger-Cqq0L4Uo.js").then((n) => n.n);
7531
+ const { cliLogger } = await import("./cli-logger-BgVL1vBI.js").then((n) => n.n);
7511
7532
  cliLogger.warn(message);
7512
7533
  } catch {}
7513
7534
  };
package/dist/index.d.ts CHANGED
@@ -349,9 +349,15 @@ interface ProjectInfo {
349
349
  }
350
350
  //#endregion
351
351
  //#region src/types/score.d.ts
352
+ type RuleTier = "P0" | "P1" | "P2" | "P3";
353
+ interface RulePriority {
354
+ readonly priority: number | null;
355
+ readonly tier: RuleTier;
356
+ }
352
357
  interface ScoreResult {
353
358
  score: number;
354
359
  label: string;
360
+ readonly rules?: Readonly<Record<string, RulePriority>>;
355
361
  } //#endregion
356
362
  //#region src/types/diagnose.d.ts
357
363
  interface DiagnoseOptions {
package/dist/index.js CHANGED
@@ -6970,17 +6970,21 @@ var Reporter = class Reporter extends Context.Service()("react-doctor/Reporter")
6970
6970
  });
6971
6971
  }));
6972
6972
  };
6973
- const parseScoreResult = (value) => {
6974
- if (typeof value !== "object" || value === null) return null;
6975
- if (!("score" in value) || !("label" in value)) return null;
6976
- const scoreValue = Reflect.get(value, "score");
6977
- const labelValue = Reflect.get(value, "label");
6978
- if (typeof scoreValue !== "number" || typeof labelValue !== "string") return null;
6979
- return {
6980
- score: scoreValue,
6981
- label: labelValue
6982
- };
6983
- };
6973
+ const RulePrioritySchema = Schema.Struct({
6974
+ priority: Schema.NullOr(Schema.Number),
6975
+ tier: Schema.Literals([
6976
+ "P0",
6977
+ "P1",
6978
+ "P2",
6979
+ "P3"
6980
+ ])
6981
+ });
6982
+ const ScoreApiResponseSchema = Schema.Struct({
6983
+ score: Schema.Number,
6984
+ label: Schema.String,
6985
+ rules: Schema.optional(Schema.Record(Schema.String, RulePrioritySchema))
6986
+ });
6987
+ const parseScoreResult = (value) => Option.getOrNull(Schema.decodeUnknownOption(ScoreApiResponseSchema)(value));
6984
6988
  const stripFilePaths = (diagnostics) => diagnostics.map(({ filePath: _filePath, ...rest }) => rest);
6985
6989
  const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
6986
6990
  const describeFailure = (error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.2.14-dev.ac3ca1a",
3
+ "version": "0.2.14-dev.b612664",
4
4
  "description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -58,7 +58,7 @@
58
58
  "oxlint": "^1.66.0",
59
59
  "prompts": "^2.4.2",
60
60
  "typescript": ">=5.0.4 <7",
61
- "oxlint-plugin-react-doctor": "0.2.14-dev.ac3ca1a"
61
+ "oxlint-plugin-react-doctor": "0.2.14-dev.b612664"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/prompts": "^2.4.9",