react-doctor 0.5.8-dev.c2ce298 → 0.5.8-dev.c72b560

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/lsp.js CHANGED
@@ -6021,7 +6021,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
6021
6021
  * @since 2.0.0
6022
6022
  */
6023
6023
  const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
6024
- const setImmediate = "setImmediate" in globalThis ? (f) => {
6024
+ const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
6025
6025
  const timer = globalThis.setImmediate(f);
6026
6026
  return () => globalThis.clearImmediate(timer);
6027
6027
  } : (f) => {
@@ -6065,7 +6065,7 @@ var PriorityBuckets = class {
6065
6065
  var MixedScheduler = class {
6066
6066
  executionMode;
6067
6067
  setImmediate;
6068
- constructor(executionMode = "async", setImmediateFn = setImmediate) {
6068
+ constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
6069
6069
  this.executionMode = executionMode;
6070
6070
  this.setImmediate = setImmediateFn;
6071
6071
  }
@@ -6090,7 +6090,7 @@ var MixedSchedulerDispatcher = class {
6090
6090
  tasks = /* @__PURE__ */ new PriorityBuckets();
6091
6091
  running = void 0;
6092
6092
  setImmediate;
6093
- constructor(setImmediateFn = setImmediate) {
6093
+ constructor(setImmediateFn = setImmediate$1) {
6094
6094
  this.setImmediate = setImmediateFn;
6095
6095
  }
6096
6096
  /**
@@ -33730,6 +33730,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
33730
33730
  const SCORE_API_URL = "https://www.react.doctor/api/score";
33731
33731
  const FETCH_TIMEOUT_MS = 1e4;
33732
33732
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33733
+ const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33733
33734
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33734
33735
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33735
33736
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -33810,6 +33811,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
33810
33811
  const LINT_PHASE_TIMEOUT_MS = 3e5;
33811
33812
  const SCAN_TOTAL_DEADLINE_MS = 9e5;
33812
33813
  const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
33814
+ const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
33813
33815
  const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
33814
33816
  const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
33815
33817
  const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
@@ -35401,7 +35403,7 @@ const resolveScanConcurrency = (requested) => {
35401
35403
  if (!Number.isFinite(requested) || requested < 1) return 1;
35402
35404
  return Math.min(Math.floor(requested), 32);
35403
35405
  };
35404
- const readSystemFacts = () => ({
35406
+ const readSystemFacts$1 = () => ({
35405
35407
  availableCores: os.availableParallelism(),
35406
35408
  totalMemoryBytes: os.totalmem(),
35407
35409
  cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
@@ -35422,7 +35424,7 @@ const readSystemFacts = () => ({
35422
35424
  * `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
35423
35425
  * limited, and ceiling cases without mocking `os` or the filesystem.
35424
35426
  */
35425
- const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
35427
+ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
35426
35428
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
35427
35429
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
35428
35430
  return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
@@ -35613,6 +35615,12 @@ const BOOLEAN_FIELD_NAMES = [
35613
35615
  "adoptExistingLintConfig"
35614
35616
  ];
35615
35617
  const STRING_FIELD_NAMES = ["rootDir"];
35618
+ const STRING_ARRAY_FIELD_NAMES = [
35619
+ "projects",
35620
+ "textComponents",
35621
+ "rawTextWrapperComponents",
35622
+ "serverAuthFunctionNames"
35623
+ ];
35616
35624
  const SURFACE_CONTROL_FIELD_NAMES = [
35617
35625
  "includeTags",
35618
35626
  "excludeTags",
@@ -35714,6 +35722,7 @@ const validateConfigTypes = (config) => {
35714
35722
  const validated = { ...config };
35715
35723
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35716
35724
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
35725
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
35717
35726
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35718
35727
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35719
35728
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -36900,7 +36909,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
36900
36909
  }
36901
36910
  return true;
36902
36911
  };
36903
- const checkSecurityScan = (rootDirectory, options = {}) => {
36912
+ const yieldToEventLoop = () => new Promise((resolve) => {
36913
+ setImmediate(resolve);
36914
+ });
36915
+ const createSecurityScanSession = (rootDirectory, options) => {
36904
36916
  const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
36905
36917
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
36906
36918
  const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
@@ -36915,7 +36927,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
36915
36927
  committedFilesOnly: rule.committedFilesOnly === true
36916
36928
  }];
36917
36929
  });
36918
- if (enabledScanRules.length === 0) return [];
36930
+ if (enabledScanRules.length === 0) return null;
36919
36931
  const diagnostics = [];
36920
36932
  const seen = /* @__PURE__ */ new Set();
36921
36933
  const gitIgnoredCache = /* @__PURE__ */ new Map();
@@ -36927,15 +36939,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
36927
36939
  }
36928
36940
  return status === true;
36929
36941
  };
36930
- for (const file of collectSecurityScanFiles(rootDirectory)) for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
36931
- if (committedFilesOnly && isFileGitIgnored(file)) continue;
36932
- const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
36933
- const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
36934
- if (seen.has(key)) continue;
36935
- seen.add(key);
36936
- diagnostics.push(diagnostic);
36942
+ const scanFile = (file) => {
36943
+ for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
36944
+ if (committedFilesOnly && isFileGitIgnored(file)) continue;
36945
+ const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
36946
+ const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
36947
+ if (seen.has(key)) continue;
36948
+ seen.add(key);
36949
+ diagnostics.push(diagnostic);
36950
+ }
36951
+ };
36952
+ return {
36953
+ scanFile,
36954
+ diagnostics
36955
+ };
36956
+ };
36957
+ const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
36958
+ const session = createSecurityScanSession(rootDirectory, options);
36959
+ if (session === null) return [];
36960
+ let filesSinceYield = 0;
36961
+ for (const file of collectSecurityScanFiles(rootDirectory)) {
36962
+ session.scanFile(file);
36963
+ filesSinceYield += 1;
36964
+ if (filesSinceYield >= 16) {
36965
+ filesSinceYield = 0;
36966
+ await yieldToEventLoop();
36967
+ }
36937
36968
  }
36938
- return diagnostics;
36969
+ return session.diagnostics;
36939
36970
  };
36940
36971
  var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
36941
36972
  let p = process || {}, argv = p.argv || [], env = p.env || {};
@@ -37152,6 +37183,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37152
37183
  return [...seen].filter((pattern) => pattern.length > 0);
37153
37184
  };
37154
37185
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
37186
+ const readSystemFacts = () => ({
37187
+ availableCores: os.availableParallelism(),
37188
+ totalMemoryBytes: os.totalmem(),
37189
+ cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
37190
+ });
37191
+ /**
37192
+ * How many real deslop dead-code child processes may run at once, across the
37193
+ * concurrent per-project `runInspect` fibers of one CLI run. The cap is the
37194
+ * smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
37195
+ * workers that fit in available memory, floored at 1.
37196
+ *
37197
+ * On a roomy dev box / CI runner this resolves high enough that every
37198
+ * concurrently-scanned project still spawns its own worker (no serialization vs
37199
+ * the prior uncapped behavior); on a memory-constrained runner it collapses
37200
+ * toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
37201
+ * instead of oversubscribing memory with N simultaneous children — the global
37202
+ * cap the per-project spawn path lacked.
37203
+ *
37204
+ * Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
37205
+ * heavier dead-code worker. `facts` is injectable for tests.
37206
+ */
37207
+ const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
37208
+ const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
37209
+ const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
37210
+ return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
37211
+ };
37212
+ let availableSlots = -1;
37213
+ const waiters = [];
37214
+ const releaseSlot = () => {
37215
+ const nextWaiter = waiters.shift();
37216
+ if (nextWaiter !== void 0) nextWaiter();
37217
+ else availableSlots += 1;
37218
+ };
37219
+ /**
37220
+ * Runs `task` once a dead-code worker slot is free, releasing the slot when the
37221
+ * task settles (success or failure). With a high cap (roomy machine) every
37222
+ * caller proceeds immediately; with a low cap (constrained runner) callers
37223
+ * queue and run as slots free.
37224
+ *
37225
+ * `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
37226
+ * this caller is queued, the call rejects without acquiring a slot or running
37227
+ * `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
37228
+ * then spawn a child only to tear it down. A queued caller that aborts removes
37229
+ * its own waiter so a later release never hands a slot to a dead request.
37230
+ */
37231
+ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
37232
+ if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
37233
+ if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
37234
+ if (availableSlots > 0) availableSlots -= 1;
37235
+ else await new Promise((resolve, reject) => {
37236
+ const waiter = () => {
37237
+ abortSignal?.removeEventListener("abort", onAbort);
37238
+ resolve();
37239
+ };
37240
+ const onAbort = () => {
37241
+ const queuedIndex = waiters.indexOf(waiter);
37242
+ if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
37243
+ reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
37244
+ };
37245
+ waiters.push(waiter);
37246
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
37247
+ });
37248
+ try {
37249
+ return await task();
37250
+ } finally {
37251
+ releaseSlot();
37252
+ }
37253
+ };
37155
37254
  /**
37156
37255
  * Resolves a path to its canonical, symlink-free form, falling back to
37157
37256
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37442,14 +37541,17 @@ const checkDeadCode = async (options) => {
37442
37541
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37443
37542
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37444
37543
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37445
- const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37446
- rootDirectory,
37447
- entryPatterns,
37448
- tsConfigPath: resolveTsConfigPath(rootDirectory),
37449
- ignorePatterns,
37450
- deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37451
- parseConcurrency: options.parseConcurrency
37452
- }), options.workerTimeoutMs ?? 12e4, options.abortSignal));
37544
+ const spawnAndRun = () => {
37545
+ return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37546
+ rootDirectory,
37547
+ entryPatterns,
37548
+ tsConfigPath: resolveTsConfigPath(rootDirectory),
37549
+ ignorePatterns,
37550
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37551
+ parseConcurrency: options.parseConcurrency
37552
+ }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37553
+ };
37554
+ const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
37453
37555
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37454
37556
  const diagnostics = [];
37455
37557
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37850,43 +37952,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37850
37952
  * reason: GitInvocationFailed })` so the rest of the codebase
37851
37953
  * sees a single failure channel.
37852
37954
  */
37853
- const runCommand = (input) => scoped(gen(function* () {
37854
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37855
- cwd: input.directory,
37856
- env: input.env,
37857
- extendEnv: true
37858
- }));
37859
- const maxStdoutBytes = input.maxStdoutBytes;
37860
- const stdoutByteCount = yield* make$13(0);
37861
- const [stdout, stderr, status] = yield* all([
37862
- mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37863
- args: [...input.args],
37864
- directory: input.directory,
37865
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37866
- }) })) : void_)))))),
37867
- mkString(decodeText(handle.stderr)),
37868
- handle.exitCode
37869
- ], { concurrency: 3 });
37870
- return {
37871
- status,
37872
- stdout,
37873
- stderr
37874
- };
37875
- })).pipe(catchTag$1("PlatformError", (cause) => {
37876
- if (input.command !== "git") return succeed$2({
37955
+ const runCommand = (input) => {
37956
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37877
37957
  status: 127,
37878
37958
  stdout: "",
37879
37959
  stderr: String(cause)
37880
- });
37881
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37960
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37882
37961
  args: [...input.args],
37883
37962
  directory: input.directory,
37884
37963
  cause
37885
- }) });
37886
- }), withSpan("git.exec", { attributes: {
37887
- "git.command": input.command,
37888
- "git.subcommand": input.args[0] ?? ""
37889
- } }));
37964
+ }) }));
37965
+ return scoped(gen(function* () {
37966
+ if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37967
+ const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37968
+ if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
37969
+ const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37970
+ cwd: input.directory,
37971
+ env: input.env,
37972
+ extendEnv: true
37973
+ }));
37974
+ const maxStdoutBytes = input.maxStdoutBytes;
37975
+ const stdoutByteCount = yield* make$13(0);
37976
+ const [stdout, stderr, status] = yield* all([
37977
+ mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37978
+ args: [...input.args],
37979
+ directory: input.directory,
37980
+ cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37981
+ }) })) : void_)))))),
37982
+ mkString(decodeText(handle.stderr)),
37983
+ handle.exitCode
37984
+ ], { concurrency: 3 });
37985
+ return {
37986
+ status,
37987
+ stdout,
37988
+ stderr
37989
+ };
37990
+ })).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
37991
+ "git.command": input.command,
37992
+ "git.subcommand": input.args[0] ?? ""
37993
+ } }));
37994
+ };
37890
37995
  const runGit = (directory, args) => runCommand({
37891
37996
  command: "git",
37892
37997
  args,
@@ -37919,7 +38024,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37919
38024
  "rev-parse",
37920
38025
  "--verify",
37921
38026
  branch
37922
- ]).pipe(map$3((result) => result.status === 0));
38027
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
37923
38028
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37924
38029
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37925
38030
  "merge-base",
@@ -38133,7 +38238,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38133
38238
  ]);
38134
38239
  if (result.status !== 0) return null;
38135
38240
  return parseChangedLineRanges(result.stdout);
38136
- }).pipe(withSpan("Git.changedLineRanges"))
38241
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38137
38242
  });
38138
38243
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38139
38244
  /**
@@ -40610,7 +40715,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
40610
40715
  * diagnostics).
40611
40716
  * 2. beforeLint hook (e.g. CLI renders the project-detection block)
40612
40717
  * 3. environment checks (reduced-motion + pnpm hardening +
40613
- * expo/react-native + security scan), collected synchronously
40718
+ * expo/react-native), collected synchronously. The heavier
40719
+ * content-regex security scan is forked instead (like supply-chain
40720
+ * below) and joined before the concat, so its CPU overlaps lint
40721
+ * rather than blocking the event loop before it.
40614
40722
  * 4. The supply-chain check (Socket.dev) is forked onto a background
40615
40723
  * fiber so its ~100% network-bound time overlaps the ~100%
40616
40724
  * CPU/subprocess-bound lint pass below, collapsing two serial
@@ -40630,7 +40738,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
40630
40738
  * order, so terminal output is identical either way; supply-chain
40631
40739
  * rides alongside without a spinner.
40632
40740
  * 6. Join the supply-chain fiber, then assemble the diagnostics in a
40633
- * FIXED order (env, supply-chain, lint, dead-code) so the output is
40741
+ * FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
40634
40742
  * byte-identical regardless of which fiber settled first. The
40635
40743
  * viewer-permission fiber is joined later, during score-metadata
40636
40744
  * assembly (it feeds score metadata, not diagnostics). The per-element
@@ -40691,12 +40799,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40691
40799
  ...checkPnpmHardening(scanDirectory),
40692
40800
  ...checkReactServerComponentsAdvisory(scanDirectory, project),
40693
40801
  ...checkExpoProject(scanDirectory, project),
40694
- ...checkReactNativeProject(scanDirectory, project),
40695
- ...checkSecurityScan(scanDirectory, {
40696
- project,
40697
- ignoredTags: input.ignoredTags
40698
- })
40802
+ ...checkReactNativeProject(scanDirectory, project)
40699
40803
  ])));
40804
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
40805
+ project,
40806
+ ignoredTags: input.ignoredTags
40807
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
40700
40808
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
40701
40809
  const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
40702
40810
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
@@ -40832,9 +40940,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40832
40940
  else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
40833
40941
  const supplyChainResult = yield* join(supplyChainFiber);
40834
40942
  const supplyChainCollected = supplyChainResult.diagnostics;
40943
+ const securityScanCollected = yield* join(securityScanFiber);
40835
40944
  yield* reporterService.finalize;
40836
40945
  const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
40837
40946
  ...envCollected,
40947
+ ...securityScanCollected,
40838
40948
  ...supplyChainCollected,
40839
40949
  ...lintCollected,
40840
40950
  ...deadCodeCollected
@@ -42906,6 +43016,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
42906
43016
  const METRIC = {
42907
43017
  cliInvoked: "cli.invoked",
42908
43018
  cliError: "cli.error",
43019
+ cliEnvironmentError: "cli.env_error",
42909
43020
  projectDetected: "project.detected",
42910
43021
  projectPathSelected: "project.path_selected",
42911
43022
  projectConfigSelected: "project.config_selected",
@@ -42935,7 +43046,8 @@ const METRIC = {
42935
43046
  lspSessionStarted: "lsp.session.started",
42936
43047
  lspScanCompleted: "lsp.scan.completed",
42937
43048
  lspScanDuration: "lsp.scan.duration",
42938
- lspScanDiagnostics: "lsp.scan.diagnostics"
43049
+ lspScanDiagnostics: "lsp.scan.diagnostics",
43050
+ aiTrainingWarningShown: "ai.training.warning_shown"
42939
43051
  };
42940
43052
  //#endregion
42941
43053
  //#region src/cli/utils/to-category-key.ts
@@ -43252,5 +43364,5 @@ const startLanguageServer = () => {
43252
43364
  };
43253
43365
  //#endregion
43254
43366
  export { startLanguageServer };
43255
- !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]="28a9132e-0eb3-5487-9d97-46eb9456a794")}catch(e){}}();
43256
- //# debugId=28a9132e-0eb3-5487-9d97-46eb9456a794
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.c2ce298",
3
+ "version": "0.5.8-dev.c72b560",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -20,7 +20,7 @@
20
20
  "bugs": {
21
21
  "url": "https://github.com/millionco/react-doctor/issues"
22
22
  },
23
- "license": "MIT",
23
+ "license": "SEE LICENSE IN LICENSE",
24
24
  "author": "Million Software, Inc",
25
25
  "repository": {
26
26
  "type": "git",
@@ -34,7 +34,8 @@
34
34
  "bin/**",
35
35
  "dist/**/*.js",
36
36
  "dist/**/*.d.ts",
37
- "dist/skills/**"
37
+ "dist/skills/**",
38
+ "LICENSE"
38
39
  ],
39
40
  "type": "module",
40
41
  "sideEffects": false,
@@ -64,7 +65,7 @@
64
65
  "vscode-languageserver-textdocument": "^1.0.12",
65
66
  "vscode-uri": "^3.1.0",
66
67
  "deslop-js": "0.5.8",
67
- "oxlint-plugin-react-doctor": "0.5.8-dev.c2ce298"
68
+ "oxlint-plugin-react-doctor": "0.5.8-dev.c72b560"
68
69
  },
69
70
  "devDependencies": {
70
71
  "@types/babel__code-frame": "^7.27.0",
@@ -73,8 +74,8 @@
73
74
  "commander": "^14.0.3",
74
75
  "ora": "^9.4.0",
75
76
  "@react-doctor/api": "0.5.8",
76
- "@react-doctor/language-server": "0.5.8",
77
- "@react-doctor/core": "0.5.8"
77
+ "@react-doctor/core": "0.5.8",
78
+ "@react-doctor/language-server": "0.5.8"
78
79
  },
79
80
  "engines": {
80
81
  "node": "^20.19.0 || >=22.13.0"