@hublo/sentinel 1.6.0-alpha.0 → 1.6.0-alpha.2

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.
@@ -785,9 +785,9 @@ function resolveVitest(cwd) {
785
785
 
786
786
  // src/roles/test/adapters/vitest/vitest.adapter.ts
787
787
  import { spawnSync } from "node:child_process";
788
- import { existsSync as existsSync25, mkdtempSync as mkdtempSync3, readFileSync as readFileSync25, writeFileSync as writeFileSync2 } from "node:fs";
788
+ import { existsSync as existsSync25, mkdtempSync as mkdtempSync3, readFileSync as readFileSync26, writeFileSync as writeFileSync2 } from "node:fs";
789
789
  import { tmpdir as tmpdir3 } from "node:os";
790
- import { isAbsolute as isAbsolute2, join as join31, relative as relative7, resolve as resolve10 } from "node:path";
790
+ import { isAbsolute as isAbsolute2, join as join32, relative as relative7, resolve as resolve10 } from "node:path";
791
791
 
792
792
  // src/core/config/tool-args.ts
793
793
  import { existsSync as existsSync6 } from "node:fs";
@@ -5935,7 +5935,7 @@ function plan(context) {
5935
5935
 
5936
5936
  // src/roles/test/written-file-linter.ts
5937
5937
  import { existsSync as existsSync24 } from "node:fs";
5938
- import { join as join30 } from "node:path";
5938
+ import { join as join31 } from "node:path";
5939
5939
 
5940
5940
  // src/roles/lint/module-baseline.ts
5941
5941
  import { existsSync as existsSync20, readFileSync as readFileSync22 } from "node:fs";
@@ -6706,6 +6706,10 @@ function lintTarget() {
6706
6706
  return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
6707
6707
  }
6708
6708
 
6709
+ // src/roles/lint/read-adoption.ts
6710
+ import { readFileSync as readFileSync25 } from "node:fs";
6711
+ import { join as join29 } from "node:path";
6712
+
6709
6713
  // src/core/config/read-adoption.ts
6710
6714
  import { existsSync as existsSync22, readFileSync as readFileSync24 } from "node:fs";
6711
6715
  import { join as join28 } from "node:path";
@@ -6789,7 +6793,8 @@ function readAdoption(cwd, options) {
6789
6793
  const preset = normaliseExtends(parsed.extends).find((entry) => options.presetPattern.test(entry)) ?? null;
6790
6794
  if (preset === null) return NOT_ADOPTED2(target.path);
6791
6795
  const settings = options.settingsKey === null ? parsed : parsed[options.settingsKey];
6792
- const drift = presetOwnedKeys(settings, options.permitted, options.presetOwns?.(preset));
6796
+ const owned = presetOwnedKeys(settings, options.permitted, options.presetOwns?.(preset));
6797
+ const drift = options.refineDrift !== void 0 && settings !== void 0 ? options.refineDrift(owned, settings, preset) : owned;
6793
6798
  return {
6794
6799
  configFile: target.path,
6795
6800
  preset,
@@ -6800,24 +6805,2718 @@ function readAdoption(cwd, options) {
6800
6805
  };
6801
6806
  }
6802
6807
 
6803
- // src/roles/lint/read-adoption.ts
6804
- function readLintAdoption(cwd) {
6805
- return readAdoption(cwd, {
6806
- candidates: [LINT_CONFIG_FILE],
6807
- markers: ["@hublo/sentinel/oxlint/"],
6808
- fallback: LINT_CONFIG_FILE,
6809
- presetPattern: SENTINEL_LINT_PRESET,
6810
- // Drift for lint is any TOP-LEVEL key beyond `extends`, so the settings the shared
6811
- // reader inspects are the config's own root rather than a nested options object.
6812
- settingsKey: null,
6813
- permitted: PERMITTED_LOCAL_KEYS2
6814
- });
6808
+ // src/roles/lint/module-own-config.ts
6809
+ import { isDeepStrictEqual } from "node:util";
6810
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6811
+ function pluginKey(entry) {
6812
+ if (typeof entry === "string") return entry;
6813
+ if (isRecord(entry) && typeof entry.name === "string") return entry.name;
6814
+ if (isRecord(entry) && typeof entry.specifier === "string") return entry.specifier;
6815
+ return void 0;
6816
+ }
6817
+ function governedRuleNames(preset) {
6818
+ return /* @__PURE__ */ new Set([
6819
+ ...Object.keys(preset.rules),
6820
+ ...(preset.overrides ?? []).flatMap((override) => Object.keys(override.rules))
6821
+ ]);
6822
+ }
6823
+ function governedRules(preset) {
6824
+ const governed = /* @__PURE__ */ new Map();
6825
+ for (const name of governedRuleNames(preset)) {
6826
+ const canonical = canonicalRuleName(name);
6827
+ if (!governed.has(canonical)) governed.set(canonical, name);
6828
+ }
6829
+ return governed;
6830
+ }
6831
+ function moduleOwnBlocks(committed, preset) {
6832
+ const blocks = {};
6833
+ const kept = [];
6834
+ const dropped = [];
6835
+ const added = [];
6836
+ const collided = [];
6837
+ if (Array.isArray(committed.plugins)) {
6838
+ const own = committed.plugins.filter(
6839
+ (name) => typeof name === "string" && !preset.plugins.includes(name)
6840
+ );
6841
+ for (const name of own) added.push({ key: "plugins", entry: `plugins ${name}`, value: name });
6842
+ if (own.length > 0) {
6843
+ blocks.plugins = own;
6844
+ kept.push(
6845
+ `kept this module's own plugins (${own.join(", ")}): the preset does not enable them`
6846
+ );
6847
+ }
6848
+ }
6849
+ if (Array.isArray(committed.jsPlugins)) {
6850
+ const presetKeys = new Set(preset.jsPlugins.map((entry) => pluginKey(entry)));
6851
+ const own = [];
6852
+ for (const entry of committed.jsPlugins) {
6853
+ const key = pluginKey(entry);
6854
+ const named = { key: "jsPlugins", entry: `jsPlugins ${key ?? "?"}`, value: entry };
6855
+ if (key !== void 0 && presetKeys.has(key)) {
6856
+ dropped.push(named.entry);
6857
+ collided.push(named);
6858
+ } else {
6859
+ own.push(entry);
6860
+ added.push(named);
6861
+ }
6862
+ }
6863
+ if (own.length > 0) {
6864
+ blocks.jsPlugins = own;
6865
+ kept.push(
6866
+ `kept ${own.length} jsPlugin(s) this module added (${own.map((entry) => pluginKey(entry) ?? "?").join(", ")})`
6867
+ );
6868
+ }
6869
+ }
6870
+ if (isRecord(committed.settings)) {
6871
+ const presetSettings = preset.settings ?? {};
6872
+ const own = {};
6873
+ for (const [key, value] of Object.entries(committed.settings)) {
6874
+ const named = { key: "settings", entry: `settings.${key}`, value };
6875
+ if (key in presetSettings) {
6876
+ dropped.push(named.entry);
6877
+ collided.push(named);
6878
+ } else {
6879
+ own[key] = value;
6880
+ added.push(named);
6881
+ }
6882
+ }
6883
+ if (Object.keys(own).length > 0) {
6884
+ blocks.settings = own;
6885
+ kept.push(`kept this module's own settings (${Object.keys(own).join(", ")})`);
6886
+ }
6887
+ }
6888
+ if (Array.isArray(committed.overrides)) {
6889
+ const governed = governedRules(preset);
6890
+ const byFiles = /* @__PURE__ */ new Map();
6891
+ for (const block of committed.overrides) {
6892
+ if (!isRecord(block) || !Array.isArray(block.files)) continue;
6893
+ const files = JSON.stringify([...block.files].map(String).sort());
6894
+ const rules = {};
6895
+ for (const [rule, value] of Object.entries(isRecord(block.rules) ? block.rules : {})) {
6896
+ const presetSpelling = governed.get(canonicalRuleName(rule));
6897
+ if (presetSpelling === void 0) rules[rule] = value;
6898
+ else
6899
+ dropped.push(
6900
+ `overrides[${block.files.join(", ")}] ${describeRuleKey(rule, presetSpelling)}`
6901
+ );
6902
+ }
6903
+ const { rules: _rules, ...rest } = block;
6904
+ for (const [key, value] of Object.entries(rest)) {
6905
+ if (key === "files") continue;
6906
+ const files2 = block.files.map(String);
6907
+ added.push({
6908
+ key: "overrides",
6909
+ entry: `overrides[${files2.join(", ")}] ${key}`,
6910
+ value,
6911
+ files: files2
6912
+ });
6913
+ }
6914
+ const merged = byFiles.get(files);
6915
+ if (merged === void 0) {
6916
+ byFiles.set(files, { ...rest, ...Object.keys(rules).length > 0 ? { rules } : {} });
6917
+ continue;
6918
+ }
6919
+ const { files: _sameFiles, ...others } = rest;
6920
+ const mergedRules = { ...isRecord(merged.rules) ? merged.rules : {}, ...rules };
6921
+ Object.assign(
6922
+ merged,
6923
+ others,
6924
+ Object.keys(mergedRules).length > 0 ? { rules: mergedRules } : {}
6925
+ );
6926
+ }
6927
+ const own = [...byFiles.values()].filter((block) => Object.keys(block).length > 1);
6928
+ if (own.length > 0) {
6929
+ blocks.overrides = own;
6930
+ kept.push(
6931
+ `kept ${own.length} override block(s) this module set (${own.map((block) => block.files.join(", ")).join("; ")})`
6932
+ );
6933
+ }
6934
+ }
6935
+ return { blocks, kept, dropped, added, collided };
6936
+ }
6937
+ var SEVERITIES = {
6938
+ off: "off",
6939
+ allow: "off",
6940
+ "0": "off",
6941
+ warn: "warn",
6942
+ "1": "warn",
6943
+ error: "error",
6944
+ deny: "error",
6945
+ "2": "error"
6946
+ };
6947
+ function ruleSetting(value) {
6948
+ const [head, ...options] = Array.isArray(value) ? value : [value];
6949
+ if (typeof head !== "string" && typeof head !== "number") return void 0;
6950
+ const severity = SEVERITIES[String(head)];
6951
+ return severity === void 0 ? void 0 : [severity, ...options];
6952
+ }
6953
+ function isOffRuleValue(value) {
6954
+ return ruleSetting(value)?.[0] === "off";
6955
+ }
6956
+ function sameRuleSetting(module, preset) {
6957
+ const own = ruleSetting(module);
6958
+ return own !== void 0 && isDeepStrictEqual(own, ruleSetting(preset));
6959
+ }
6960
+ var filesKey = (files) => JSON.stringify(files.map(String).sort());
6961
+ function moduleRuleStanding(committed, preset) {
6962
+ const governed = governedRules(preset);
6963
+ const byCanonical = (rules) => new Map(Object.entries(rules).map(([rule, value]) => [canonicalRuleName(rule), value]));
6964
+ const topLevel = byCanonical(preset.rules);
6965
+ const scoped = new Map(
6966
+ (preset.overrides ?? []).map((override) => [
6967
+ filesKey(override.files),
6968
+ byCanonical(override.rules)
6969
+ ])
6970
+ );
6971
+ const standing = { added: [], conflicts: [] };
6972
+ const classify = (rules, key, files) => {
6973
+ for (const [rule, value] of Object.entries(rules)) {
6974
+ const canonical = canonicalRuleName(rule);
6975
+ const where = files === void 0 ? {} : { files };
6976
+ if (!governed.has(canonical)) {
6977
+ standing.added.push({ rule, ...where, value });
6978
+ continue;
6979
+ }
6980
+ const scope = files === void 0 ? void 0 : scoped.get(filesKey(files));
6981
+ const presetValue = scope?.has(canonical) ? scope.get(canonical) : topLevel.get(canonical);
6982
+ if (presetValue === void 0 || !sameRuleSetting(value, presetValue)) {
6983
+ standing.conflicts.push({ key, rule, ...where });
6984
+ }
6985
+ }
6986
+ };
6987
+ if (isRecord(committed.rules)) classify(committed.rules, "rules");
6988
+ if (Array.isArray(committed.overrides)) {
6989
+ for (const block of committed.overrides) {
6990
+ if (!isRecord(block) || !Array.isArray(block.files) || !isRecord(block.rules)) continue;
6991
+ classify(block.rules, "overrides", block.files.map(String));
6992
+ }
6993
+ }
6994
+ return standing;
6995
+ }
6996
+ function refineLintDrift(drift, committed, preset) {
6997
+ const ruleConflicts = new Set(
6998
+ moduleRuleStanding(committed, preset).conflicts.map((conflict) => conflict.key)
6999
+ );
7000
+ const collisions = new Set(moduleOwnBlocks(committed, preset).collided.map((entry) => entry.key));
7001
+ const readBack = (key) => {
7002
+ const value = committed[key];
7003
+ switch (key) {
7004
+ case "rules":
7005
+ case "settings":
7006
+ return isRecord(value);
7007
+ case "plugins":
7008
+ return Array.isArray(value) && value.every((name) => typeof name === "string");
7009
+ case "jsPlugins":
7010
+ return Array.isArray(value);
7011
+ case "overrides":
7012
+ return Array.isArray(value) && value.every(
7013
+ (block) => isRecord(block) && Array.isArray(block.files) && (block.rules === void 0 || isRecord(block.rules))
7014
+ );
7015
+ default:
7016
+ return false;
7017
+ }
7018
+ };
7019
+ return drift.filter(
7020
+ (key) => !readBack(key) || ruleConflicts.has(key) || collisions.has(key)
7021
+ );
7022
+ }
7023
+
7024
+ // src/roles/lint/presets/nest.json
7025
+ var nest_default = {
7026
+ plugins: ["typescript", "jest", "vitest", "import", "unicorn", "oxc", "node"],
7027
+ jsPlugins: [
7028
+ "eslint-plugin-no-barrel-files",
7029
+ {
7030
+ name: "nx",
7031
+ specifier: "@nx/eslint-plugin"
7032
+ }
7033
+ ],
7034
+ settings: null,
7035
+ rules: {
7036
+ "array-callback-return": [
7037
+ "error",
7038
+ {
7039
+ allowImplicit: false,
7040
+ checkForEach: false,
7041
+ allowVoid: false
7042
+ }
7043
+ ],
7044
+ complexity: ["error", 30],
7045
+ curly: ["error", "all"],
7046
+ "dot-notation": [
7047
+ "error",
7048
+ {
7049
+ allowKeywords: true,
7050
+ allowPattern: ""
7051
+ }
7052
+ ],
7053
+ eqeqeq: {
7054
+ severity: "warn",
7055
+ reason: "1 pre-existing violation(s) across one module, and they were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero."
7056
+ },
7057
+ "for-direction": "error",
7058
+ "func-style": {
7059
+ severity: "warn",
7060
+ reason: "58 pre-existing violation(s) across network and contract, and they were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero.",
7061
+ options: [
7062
+ "expression",
7063
+ {
7064
+ allowArrowFunctions: false,
7065
+ allowTypeAnnotation: false,
7066
+ overrides: {}
7067
+ }
7068
+ ]
7069
+ },
7070
+ "import/no-duplicates": [
7071
+ "error",
7072
+ {
7073
+ considerQueryString: true,
7074
+ "prefer-inline": true
7075
+ }
7076
+ ],
7077
+ "jest/expect-expect": {
7078
+ severity: "off",
7079
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7080
+ },
7081
+ "jest/no-alias-methods": {
7082
+ severity: "off",
7083
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7084
+ },
7085
+ "jest/no-commented-out-tests": {
7086
+ severity: "off",
7087
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7088
+ },
7089
+ "jest/no-deprecated-functions": "error",
7090
+ "jest/no-disabled-tests": {
7091
+ severity: "off",
7092
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7093
+ },
7094
+ "jest/no-done-callback": "error",
7095
+ "jest/no-export": "error",
7096
+ "jest/no-focused-tests": {
7097
+ severity: "off",
7098
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7099
+ },
7100
+ "jest/no-identical-title": {
7101
+ severity: "off",
7102
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7103
+ },
7104
+ "jest/no-interpolation-in-snapshots": {
7105
+ severity: "off",
7106
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7107
+ },
7108
+ "jest/no-jasmine-globals": "error",
7109
+ "jest/no-mocks-import": {
7110
+ severity: "off",
7111
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7112
+ },
7113
+ "jest/no-standalone-expect": {
7114
+ severity: "off",
7115
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7116
+ },
7117
+ "jest/no-test-prefixes": {
7118
+ severity: "off",
7119
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7120
+ },
7121
+ "jest/prefer-to-be": {
7122
+ severity: "off",
7123
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7124
+ },
7125
+ "jest/prefer-to-contain": {
7126
+ severity: "off",
7127
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7128
+ },
7129
+ "jest/prefer-to-have-length": {
7130
+ severity: "off",
7131
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7132
+ },
7133
+ "jest/valid-describe-callback": {
7134
+ severity: "off",
7135
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7136
+ },
7137
+ "jest/valid-expect": {
7138
+ severity: "off",
7139
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7140
+ },
7141
+ "jest/valid-expect-in-promise": {
7142
+ severity: "off",
7143
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7144
+ },
7145
+ "jest/valid-title": {
7146
+ severity: "off",
7147
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7148
+ },
7149
+ "no-async-promise-executor": "error",
7150
+ "no-barrel-files/no-barrel-files": {
7151
+ severity: "off",
7152
+ reason: "nest modules use barrel index.ts files as a deliberate structural pattern; the rule fired 127 times and every one was the architecture working as intended"
7153
+ },
7154
+ "no-case-declarations": "error",
7155
+ "no-compare-neg-zero": "error",
7156
+ "no-cond-assign": ["error", "except-parens"],
7157
+ "no-console": {
7158
+ severity: "warn",
7159
+ reason: "61 pre-existing violation(s) across network, db-client-management and others, and they were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero.",
7160
+ options: [
7161
+ {
7162
+ allow: ["warn", "error"]
7163
+ }
7164
+ ]
7165
+ },
7166
+ "no-constant-binary-expression": "error",
7167
+ "no-constant-condition": [
7168
+ "error",
7169
+ {
7170
+ checkLoops: "allExceptWhileTrue"
7171
+ }
7172
+ ],
7173
+ "no-control-regex": {
7174
+ severity: "off",
7175
+ reason: "the single occurrence is a regex whose whole purpose is stripping ASCII control characters before XML output, which the rule cannot distinguish from an accident"
7176
+ },
7177
+ "no-debugger": "error",
7178
+ "no-delete-var": "error",
7179
+ "no-dupe-else-if": "error",
7180
+ "no-dupe-keys": "error",
7181
+ "no-duplicate-case": "error",
7182
+ "no-else-return": [
7183
+ "error",
7184
+ {
7185
+ allowElseIf: true
7186
+ }
7187
+ ],
7188
+ "no-empty": [
7189
+ "error",
7190
+ {
7191
+ allowEmptyCatch: false
7192
+ }
7193
+ ],
7194
+ "no-empty-character-class": "error",
7195
+ "no-empty-pattern": [
7196
+ "error",
7197
+ {
7198
+ allowObjectPatternsAsParameters: false
7199
+ }
7200
+ ],
7201
+ "no-empty-static-block": "error",
7202
+ "no-ex-assign": "error",
7203
+ "no-extra-boolean-cast": ["error", {}],
7204
+ "no-fallthrough": [
7205
+ "error",
7206
+ {
7207
+ allowEmptyCase: false,
7208
+ reportUnusedFallthroughComment: false
7209
+ }
7210
+ ],
7211
+ "no-global-assign": [
7212
+ "error",
7213
+ {
7214
+ exceptions: []
7215
+ }
7216
+ ],
7217
+ "no-implicit-coercion": [
7218
+ "error",
7219
+ {
7220
+ allow: [],
7221
+ boolean: true,
7222
+ disallowTemplateShorthand: false,
7223
+ number: true,
7224
+ string: true
7225
+ }
7226
+ ],
7227
+ "no-invalid-regexp": ["error", {}],
7228
+ "no-irregular-whitespace": [
7229
+ "error",
7230
+ {
7231
+ skipComments: false,
7232
+ skipJSXText: false,
7233
+ skipRegExps: false,
7234
+ skipStrings: true,
7235
+ skipTemplates: false
7236
+ }
7237
+ ],
7238
+ "no-lonely-if": "error",
7239
+ "no-loss-of-precision": "error",
7240
+ "no-misleading-character-class": "error",
7241
+ "no-nonoctal-decimal-escape": "error",
7242
+ "no-prototype-builtins": "error",
7243
+ "no-regex-spaces": "error",
7244
+ "no-restricted-imports": [
7245
+ "error",
7246
+ {
7247
+ paths: [
7248
+ {
7249
+ name: "@front/theme",
7250
+ importNames: [
7251
+ "hubloTheme",
7252
+ "legacyHubloTheme",
7253
+ "customCommonColors",
7254
+ "customPalette",
7255
+ "breakpointsOptions",
7256
+ "breakpointsValues",
7257
+ "spacingConstants"
7258
+ ],
7259
+ message: "Use the MUI theme context or the public providers exposed by @front/theme."
7260
+ }
7261
+ ],
7262
+ patterns: [
7263
+ {
7264
+ group: ["@front/theme/*", "!@front/theme/testing"],
7265
+ message: "Deep imports from @front/theme are forbidden outside the theme lib."
7266
+ }
7267
+ ]
7268
+ }
7269
+ ],
7270
+ "no-self-assign": [
7271
+ "error",
7272
+ {
7273
+ props: true
7274
+ }
7275
+ ],
7276
+ "no-shadow-restricted-names": [
7277
+ "error",
7278
+ {
7279
+ reportGlobalThis: false
7280
+ }
7281
+ ],
7282
+ "no-sparse-arrays": "error",
7283
+ "no-unneeded-ternary": [
7284
+ "error",
7285
+ {
7286
+ defaultAssignment: true
7287
+ }
7288
+ ],
7289
+ "no-unsafe-finally": "error",
7290
+ "no-unsafe-optional-chaining": {
7291
+ severity: "warn",
7292
+ reason: "7 pre-existing violation(s) across the db-* libs, and they were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero.",
7293
+ options: [
7294
+ {
7295
+ disallowArithmeticOperators: false
7296
+ }
7297
+ ]
7298
+ },
7299
+ "no-unused-labels": "error",
7300
+ "no-unused-private-class-members": "error",
7301
+ "no-useless-backreference": "error",
7302
+ "no-useless-catch": "error",
7303
+ "no-useless-escape": [
7304
+ "error",
7305
+ {
7306
+ allowRegexCharacters: []
7307
+ }
7308
+ ],
7309
+ "no-useless-return": "error",
7310
+ "nx/enforce-module-boundaries": [
7311
+ "error",
7312
+ {
7313
+ enforceBuildableLibDependency: true,
7314
+ allow: [],
7315
+ depConstraints: [
7316
+ {
7317
+ sourceTag: "*",
7318
+ onlyDependOnLibsWithTags: ["*"]
7319
+ }
7320
+ ]
7321
+ }
7322
+ ],
7323
+ "prefer-arrow-callback": [
7324
+ "error",
7325
+ {
7326
+ allowNamedFunctions: false,
7327
+ allowUnboundThis: true
7328
+ }
7329
+ ],
7330
+ "require-yield": "error",
7331
+ "typescript/await-thenable": "error",
7332
+ "typescript/no-confusing-non-null-assertion": "error",
7333
+ "typescript/no-empty-function": {
7334
+ severity: "warn",
7335
+ reason: "pre-existing violations that were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero.",
7336
+ options: [
7337
+ {
7338
+ allow: []
7339
+ }
7340
+ ]
7341
+ },
7342
+ "typescript/no-explicit-any": "error",
7343
+ "typescript/no-floating-promises": [
7344
+ "error",
7345
+ {
7346
+ ignoreVoid: true
7347
+ }
7348
+ ],
7349
+ "typescript/no-import-type-side-effects": "error",
7350
+ "typescript/no-inferrable-types": "error",
7351
+ "typescript/no-non-null-assertion": "error",
7352
+ "typescript/no-unused-vars": {
7353
+ severity: "warn",
7354
+ reason: "pre-existing violations that were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero.",
7355
+ options: [
7356
+ {
7357
+ argsIgnorePattern: "^_",
7358
+ caughtErrors: "none"
7359
+ }
7360
+ ]
7361
+ },
7362
+ "typescript/return-await": {
7363
+ severity: "warn",
7364
+ reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
7365
+ options: ["in-try-catch"]
7366
+ },
7367
+ "typescript/switch-exhaustiveness-check": [
7368
+ "error",
7369
+ {
7370
+ considerDefaultExhaustiveForUnions: true
7371
+ }
7372
+ ],
7373
+ "use-isnan": [
7374
+ "error",
7375
+ {
7376
+ enforceForIndexOf: false,
7377
+ enforceForSwitchCase: true
7378
+ }
7379
+ ],
7380
+ "valid-typeof": [
7381
+ "error",
7382
+ {
7383
+ requireStringLiterals: false
7384
+ }
7385
+ ],
7386
+ "vitest/expect-expect": {
7387
+ severity: "warn",
7388
+ reason: "the modules already ran this at warn (confirmed in the bff-admin proof-of-concept config); adoption is like-for-like, so raising it to error is a separate decision"
7389
+ },
7390
+ "vitest/no-alias-methods": "error",
7391
+ "vitest/no-commented-out-tests": {
7392
+ severity: "warn",
7393
+ reason: "the modules already ran this at warn (confirmed in the bff-admin proof-of-concept config); adoption is like-for-like, so raising it to error is a separate decision"
7394
+ },
7395
+ "vitest/no-conditional-expect": {
7396
+ severity: "warn",
7397
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
7398
+ },
7399
+ "vitest/no-disabled-tests": {
7400
+ severity: "warn",
7401
+ reason: "the modules already ran this at warn (confirmed in the bff-admin proof-of-concept config); a skipped test is a deliberate act often paired with a ticket, so it reports rather than blocks"
7402
+ },
7403
+ "vitest/no-focused-tests": "error",
7404
+ "vitest/no-identical-title": "error",
7405
+ "vitest/no-interpolation-in-snapshots": "error",
7406
+ "vitest/no-mocks-import": "error",
7407
+ "vitest/no-standalone-expect": "error",
7408
+ "vitest/no-test-prefixes": "error",
7409
+ "vitest/prefer-to-be": "error",
7410
+ "vitest/prefer-to-contain": "error",
7411
+ "vitest/prefer-to-have-length": "error",
7412
+ "vitest/require-mock-type-parameters": {
7413
+ severity: "off",
7414
+ reason: "4 479 of the 5 255 findings this plugin reports over this repository, 85% of them. It asks for type parameters on vi.fn<\u2026>(), a convention this codebase never had; adopting it is its own piece of work and not part of reading the right plugin"
7415
+ },
7416
+ "vitest/require-to-throw-message": {
7417
+ severity: "warn",
7418
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
7419
+ },
7420
+ "vitest/valid-describe-callback": "error",
7421
+ "vitest/valid-expect": {
7422
+ severity: "warn",
7423
+ reason: "1 pre-existing violation(s) across bdd-tooling, and they were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero."
7424
+ },
7425
+ "vitest/valid-expect-in-promise": "error",
7426
+ "vitest/valid-title": "error",
7427
+ "vitest/warn-todo": {
7428
+ severity: "warn",
7429
+ reason: "a todo test is a reminder rather than a defect, so it reports and never blocks"
7430
+ }
7431
+ },
7432
+ uncovered: {
7433
+ "array-bracket-newline": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7434
+ "array-bracket-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7435
+ "block-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7436
+ "computed-property-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7437
+ "dot-location": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7438
+ "eol-last": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7439
+ "func-call-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7440
+ "import/order": "import ordering is FORMATTING, and the format role owns it: oxfmt's `sortImports` reproduces exactly these groups (builtin, external, internal, parent/index, sibling, alphabetical, blank line between) and sorts a file completely in ONE pass, because it rewrites the whole import block. A lint fixer cannot: it emits text edits over overlapping ranges, so this rule needed several runs to finish (five imports took four passes, measured) and sentinel deliberately runs autofix once. Enforced by `pnpm run format`, not by the linter, which is also why `eslint-plugin-import` is no longer hosted at all: its only other rule, no-duplicates, is native in oxlint.",
7441
+ "key-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7442
+ "keyword-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7443
+ "lines-between-class-members": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7444
+ "no-invalid-this": "no oxlint equivalent, and hosting does not help: the rule crashes on every file under oxlint's plugin API (sourceCode.getJSDocComment is not implemented). Its coverage moves to TypeScript's noImplicitThis, which the sentinel TypeScript role manages.",
7445
+ "no-multi-spaces": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7446
+ "no-octal": "no oxlint equivalent",
7447
+ "no-restricted-syntax": "no oxlint equivalent, and unlike the react tier this preset hosts no house-rule plugin, so architectural guards written as AST selectors are not enforced at all. They belong in sentinel's `hublo` plugin, next to the frontend ones: tell us which guards your service relies on. A per-module copy of a house rule drifts, which is the problem sentinel exists to remove.",
7448
+ "no-trailing-spaces": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7449
+ semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7450
+ "template-curly-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
7451
+ "typescript/member-ordering": "oxlint has no native rule for it, and the preset deliberately does NOT host @typescript-eslint/eslint-plugin: hosting a whole plugin for one rule costs a dependency, a load step and a pass over every file. Accepted as a loss rather than paid for, the same call as no-octal."
7452
+ },
7453
+ overrides: [
7454
+ {
7455
+ files: [
7456
+ "**/*.test.ts",
7457
+ "**/*.test.tsx",
7458
+ "**/*.spec.ts",
7459
+ "**/*.spec.tsx",
7460
+ "**/*.integration-spec.ts",
7461
+ "**/*.e2e-spec.ts"
7462
+ ],
7463
+ rules: {
7464
+ "no-restricted-properties": {
7465
+ severity: "warn",
7466
+ reason: "held at warn: over the 8 011 test files, 7 call Math.random(), 104 call Date.now(), 3 crypto.randomUUID(), 1 performance.now() and 1 process.hrtime, and 28 of the files that read the clock already install fake timers. A warning says it without turning a suite that was green yesterday red. Verified at error on a fixture carrying all nine forms: these six are reported, new Date('2026-01-01') is not, which is the point",
7467
+ options: [
7468
+ {
7469
+ object: "Math",
7470
+ property: "random",
7471
+ message: "No Math.random() in a test: use a deterministic fixture, or stub the random source."
7472
+ },
7473
+ {
7474
+ object: "Date",
7475
+ property: "now",
7476
+ message: "No Date.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
7477
+ },
7478
+ {
7479
+ object: "performance",
7480
+ property: "now",
7481
+ message: "No performance.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
7482
+ },
7483
+ {
7484
+ object: "process",
7485
+ property: "hrtime",
7486
+ message: "No process.hrtime in a test: use vi.useFakeTimers() with vi.setSystemTime()."
7487
+ },
7488
+ {
7489
+ object: "crypto",
7490
+ property: "randomUUID",
7491
+ message: "No crypto.randomUUID() in a test: use a fixed id, or stub the random source."
7492
+ },
7493
+ {
7494
+ object: "crypto",
7495
+ property: "getRandomValues",
7496
+ message: "No crypto.getRandomValues() in a test: use a fixed buffer, or stub the random source."
7497
+ }
7498
+ ]
7499
+ },
7500
+ "no-restricted-globals": {
7501
+ severity: "warn",
7502
+ reason: "held at warn: 68 test files call setTimeout and only 2 of them install fake timers, so waiting on the real clock is the second source of flakiness after reading it. The flush idiom await new Promise((resolve) => setTimeout(resolve, 0)) is inside that count and is usually harmless, which is exactly why this reports and never blocks",
7503
+ options: [
7504
+ {
7505
+ name: "setTimeout",
7506
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
7507
+ },
7508
+ {
7509
+ name: "setInterval",
7510
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
7511
+ }
7512
+ ]
7513
+ }
7514
+ }
7515
+ }
7516
+ ]
7517
+ };
7518
+
7519
+ // src/roles/lint/presets/node.json
7520
+ var node_default = {
7521
+ plugins: ["typescript", "oxc", "vitest"],
7522
+ jsPlugins: [],
7523
+ settings: null,
7524
+ rules: {
7525
+ "typescript/no-unused-expressions": {
7526
+ severity: "warn",
7527
+ reason: "3 pre-existing violations in apps/cloud/institution, never reported because the nx eslint target matched a couple of files per module. Held at warn so adoption stays a lateral move; the debt is visible in `--inspect` and the severity returns once it reaches zero. Parked in the node LAYER rather than the shared base on purpose: the base is the floor every preset stands on, and softening it would weaken presets that have no such debt.",
7528
+ options: [
7529
+ {
7530
+ allowShortCircuit: false,
7531
+ allowTaggedTemplates: false,
7532
+ allowTernary: false
7533
+ }
7534
+ ]
7535
+ },
7536
+ "vitest/expect-expect": {
7537
+ severity: "warn",
7538
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7539
+ },
7540
+ "vitest/no-alias-methods": {
7541
+ severity: "warn",
7542
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7543
+ },
7544
+ "vitest/no-commented-out-tests": {
7545
+ severity: "warn",
7546
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7547
+ },
7548
+ "vitest/no-conditional-expect": {
7549
+ severity: "warn",
7550
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7551
+ },
7552
+ "vitest/no-disabled-tests": {
7553
+ severity: "warn",
7554
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7555
+ },
7556
+ "vitest/no-focused-tests": {
7557
+ severity: "warn",
7558
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7559
+ },
7560
+ "vitest/no-identical-title": {
7561
+ severity: "warn",
7562
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7563
+ },
7564
+ "vitest/no-interpolation-in-snapshots": {
7565
+ severity: "warn",
7566
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7567
+ },
7568
+ "vitest/no-mocks-import": {
7569
+ severity: "warn",
7570
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7571
+ },
7572
+ "vitest/no-standalone-expect": {
7573
+ severity: "warn",
7574
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7575
+ },
7576
+ "vitest/no-test-prefixes": {
7577
+ severity: "warn",
7578
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7579
+ },
7580
+ "vitest/prefer-to-be": {
7581
+ severity: "warn",
7582
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7583
+ },
7584
+ "vitest/prefer-to-contain": {
7585
+ severity: "warn",
7586
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7587
+ },
7588
+ "vitest/prefer-to-have-length": {
7589
+ severity: "warn",
7590
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7591
+ },
7592
+ "vitest/require-mock-type-parameters": {
7593
+ severity: "off",
7594
+ reason: "4 479 of the 5 255 findings this plugin reports over this repository, 85% of them. It asks for type parameters on vi.fn<\u2026>(), a convention this codebase never had; adopting it is its own piece of work and not part of reading the right plugin"
7595
+ },
7596
+ "vitest/require-to-throw-message": {
7597
+ severity: "warn",
7598
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7599
+ },
7600
+ "vitest/valid-describe-callback": {
7601
+ severity: "warn",
7602
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7603
+ },
7604
+ "vitest/valid-expect": {
7605
+ severity: "warn",
7606
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7607
+ },
7608
+ "vitest/valid-expect-in-promise": {
7609
+ severity: "warn",
7610
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7611
+ },
7612
+ "vitest/valid-title": {
7613
+ severity: "warn",
7614
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7615
+ },
7616
+ "vitest/warn-todo": {
7617
+ severity: "warn",
7618
+ reason: "the backend tier runs tests and this preset carried no test rule at all, so the whole set ships at warn: it reports and never blocks a suite that was green yesterday"
7619
+ }
7620
+ },
7621
+ overrides: [
7622
+ {
7623
+ files: [
7624
+ "**/*.test.ts",
7625
+ "**/*.test.tsx",
7626
+ "**/*.spec.ts",
7627
+ "**/*.spec.tsx",
7628
+ "**/*.integration-spec.ts",
7629
+ "**/*.e2e-spec.ts"
7630
+ ],
7631
+ rules: {
7632
+ "no-restricted-properties": {
7633
+ severity: "warn",
7634
+ reason: "held at warn: over the 8 011 test files, 7 call Math.random(), 104 call Date.now(), 3 crypto.randomUUID(), 1 performance.now() and 1 process.hrtime, and 28 of the files that read the clock already install fake timers. A warning says it without turning a suite that was green yesterday red. Verified at error on a fixture carrying all nine forms: these six are reported, new Date('2026-01-01') is not, which is the point",
7635
+ options: [
7636
+ {
7637
+ object: "Math",
7638
+ property: "random",
7639
+ message: "No Math.random() in a test: use a deterministic fixture, or stub the random source."
7640
+ },
7641
+ {
7642
+ object: "Date",
7643
+ property: "now",
7644
+ message: "No Date.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
7645
+ },
7646
+ {
7647
+ object: "performance",
7648
+ property: "now",
7649
+ message: "No performance.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
7650
+ },
7651
+ {
7652
+ object: "process",
7653
+ property: "hrtime",
7654
+ message: "No process.hrtime in a test: use vi.useFakeTimers() with vi.setSystemTime()."
7655
+ },
7656
+ {
7657
+ object: "crypto",
7658
+ property: "randomUUID",
7659
+ message: "No crypto.randomUUID() in a test: use a fixed id, or stub the random source."
7660
+ },
7661
+ {
7662
+ object: "crypto",
7663
+ property: "getRandomValues",
7664
+ message: "No crypto.getRandomValues() in a test: use a fixed buffer, or stub the random source."
7665
+ }
7666
+ ]
7667
+ },
7668
+ "no-restricted-globals": {
7669
+ severity: "warn",
7670
+ reason: "held at warn: 68 test files call setTimeout and only 2 of them install fake timers, so waiting on the real clock is the second source of flakiness after reading it. The flush idiom await new Promise((resolve) => setTimeout(resolve, 0)) is inside that count and is usually harmless, which is exactly why this reports and never blocks",
7671
+ options: [
7672
+ {
7673
+ name: "setTimeout",
7674
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
7675
+ },
7676
+ {
7677
+ name: "setInterval",
7678
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
7679
+ }
7680
+ ]
7681
+ }
7682
+ }
7683
+ }
7684
+ ]
7685
+ };
7686
+
7687
+ // src/roles/lint/presets/react-lib.json
7688
+ var react_lib_default = {
7689
+ plugins: ["typescript", "react", "jsx-a11y", "jest", "vitest", "unicorn", "oxc", "nextjs"],
7690
+ jsPlugins: [
7691
+ "eslint-plugin-no-barrel-files",
7692
+ {
7693
+ name: "nx",
7694
+ specifier: "@nx/eslint-plugin"
7695
+ },
7696
+ {
7697
+ name: "jest-dom",
7698
+ specifier: "eslint-plugin-jest-dom"
7699
+ },
7700
+ {
7701
+ name: "storybook",
7702
+ specifier: "eslint-plugin-storybook"
7703
+ },
7704
+ "../plugins/hublo.js",
7705
+ {
7706
+ name: "@tanstack/query",
7707
+ specifier: "@tanstack/eslint-plugin-query"
7708
+ }
7709
+ ],
7710
+ settings: null,
7711
+ renames: {
7712
+ "@next/next": "nextjs"
7713
+ },
7714
+ rules: {
7715
+ "@tanstack/query/exhaustive-deps": {
7716
+ severity: "warn",
7717
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7718
+ },
7719
+ "@tanstack/query/infinite-query-property-order": {
7720
+ severity: "warn",
7721
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7722
+ },
7723
+ "@tanstack/query/mutation-property-order": {
7724
+ severity: "warn",
7725
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7726
+ },
7727
+ "@tanstack/query/no-rest-destructuring": {
7728
+ severity: "warn",
7729
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7730
+ },
7731
+ "@tanstack/query/no-unstable-deps": {
7732
+ severity: "warn",
7733
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7734
+ },
7735
+ "@tanstack/query/no-void-query-fn": {
7736
+ severity: "warn",
7737
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7738
+ },
7739
+ "@tanstack/query/prefer-query-options": {
7740
+ severity: "warn",
7741
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7742
+ },
7743
+ "@tanstack/query/stable-query-client": {
7744
+ severity: "warn",
7745
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
7746
+ },
7747
+ curly: ["error", "all"],
7748
+ "for-direction": "error",
7749
+ "hublo/no-regex-lookbehind": "error",
7750
+ "jest-dom/prefer-checked": "error",
7751
+ "jest-dom/prefer-empty": "error",
7752
+ "jest-dom/prefer-enabled-disabled": "error",
7753
+ "jest-dom/prefer-focus": "error",
7754
+ "jest-dom/prefer-in-document": "error",
7755
+ "jest-dom/prefer-required": "error",
7756
+ "jest-dom/prefer-to-have-attribute": "error",
7757
+ "jest-dom/prefer-to-have-class": "error",
7758
+ "jest-dom/prefer-to-have-style": "error",
7759
+ "jest-dom/prefer-to-have-text-content": "error",
7760
+ "jest-dom/prefer-to-have-value": "error",
7761
+ "jest/expect-expect": {
7762
+ severity: "off",
7763
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7764
+ },
7765
+ "jest/no-alias-methods": {
7766
+ severity: "off",
7767
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7768
+ },
7769
+ "jest/no-commented-out-tests": {
7770
+ severity: "off",
7771
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7772
+ },
7773
+ "jest/no-conditional-expect": {
7774
+ severity: "off",
7775
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7776
+ },
7777
+ "jest/no-deprecated-functions": "error",
7778
+ "jest/no-disabled-tests": {
7779
+ severity: "off",
7780
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7781
+ },
7782
+ "jest/no-done-callback": "error",
7783
+ "jest/no-export": "error",
7784
+ "jest/no-focused-tests": {
7785
+ severity: "off",
7786
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7787
+ },
7788
+ "jest/no-identical-title": {
7789
+ severity: "off",
7790
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7791
+ },
7792
+ "jest/no-interpolation-in-snapshots": {
7793
+ severity: "off",
7794
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7795
+ },
7796
+ "jest/no-jasmine-globals": "error",
7797
+ "jest/no-mocks-import": {
7798
+ severity: "off",
7799
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7800
+ },
7801
+ "jest/no-standalone-expect": {
7802
+ severity: "off",
7803
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7804
+ },
7805
+ "jest/no-test-prefixes": {
7806
+ severity: "off",
7807
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7808
+ },
7809
+ "jest/prefer-to-be": {
7810
+ severity: "off",
7811
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7812
+ },
7813
+ "jest/prefer-to-contain": {
7814
+ severity: "off",
7815
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7816
+ },
7817
+ "jest/prefer-to-have-length": {
7818
+ severity: "off",
7819
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7820
+ },
7821
+ "jest/valid-describe-callback": {
7822
+ severity: "off",
7823
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7824
+ },
7825
+ "jest/valid-expect": {
7826
+ severity: "off",
7827
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7828
+ },
7829
+ "jest/valid-expect-in-promise": {
7830
+ severity: "off",
7831
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7832
+ },
7833
+ "jest/valid-title": {
7834
+ severity: "off",
7835
+ reason: "the vitest plugin reports the same violation at the same offset, and this repository runs vitest: measured over apps and libs, 765 of this plugin\u2019s 769 findings are duplicates of the vitest ones. Its rules fire on jest-style files too, so nothing is lost by reading them once"
7836
+ },
7837
+ "jsx-a11y/anchor-is-valid": [
7838
+ "error",
7839
+ {
7840
+ components: ["Link"],
7841
+ specialLink: ["hrefLeft", "hrefRight"],
7842
+ aspects: ["invalidHref", "preferButton"]
7843
+ }
7844
+ ],
7845
+ "nextjs/google-font-display": {
7846
+ severity: "warn",
7847
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7848
+ },
7849
+ "nextjs/google-font-preconnect": {
7850
+ severity: "warn",
7851
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7852
+ },
7853
+ "nextjs/inline-script-id": "error",
7854
+ "nextjs/next-script-for-ga": {
7855
+ severity: "warn",
7856
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7857
+ },
7858
+ "nextjs/no-assign-module-variable": "error",
7859
+ "nextjs/no-async-client-component": {
7860
+ severity: "warn",
7861
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7862
+ },
7863
+ "nextjs/no-before-interactive-script-outside-document": {
7864
+ severity: "warn",
7865
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7866
+ },
7867
+ "nextjs/no-css-tags": {
7868
+ severity: "warn",
7869
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7870
+ },
7871
+ "nextjs/no-document-import-in-page": "error",
7872
+ "nextjs/no-duplicate-head": "error",
7873
+ "nextjs/no-head-element": {
7874
+ severity: "warn",
7875
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7876
+ },
7877
+ "nextjs/no-head-import-in-document": "error",
7878
+ "nextjs/no-html-link-for-pages": "error",
7879
+ "nextjs/no-img-element": {
7880
+ severity: "warn",
7881
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7882
+ },
7883
+ "nextjs/no-page-custom-font": {
7884
+ severity: "warn",
7885
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7886
+ },
7887
+ "nextjs/no-script-component-in-head": "error",
7888
+ "nextjs/no-styled-jsx-in-document": {
7889
+ severity: "warn",
7890
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7891
+ },
7892
+ "nextjs/no-sync-scripts": "error",
7893
+ "nextjs/no-title-in-document-head": {
7894
+ severity: "warn",
7895
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7896
+ },
7897
+ "nextjs/no-typos": {
7898
+ severity: "warn",
7899
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7900
+ },
7901
+ "nextjs/no-unwanted-polyfillio": {
7902
+ severity: "warn",
7903
+ reason: "Next's own recommended config sets this at warn, and the libs inherit it. Four of the ten front libs really do import `next` (`components` alone in 40 files), so the rule is not decoration here, but its severity is Next's call rather than ours."
7904
+ },
7905
+ "no-async-promise-executor": "error",
7906
+ "no-barrel-files/no-barrel-files": {
7907
+ severity: "warn",
7908
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first."
7909
+ },
7910
+ "no-case-declarations": "error",
7911
+ "no-compare-neg-zero": "error",
7912
+ "no-cond-assign": ["error", "except-parens"],
7913
+ "no-console": {
7914
+ severity: "warn",
7915
+ reason: "53 pre-existing violation(s) across libs/front/analytics, and they were never reported: the nx eslint target matched a couple of files per module (2 of 2888 on bff-admin), so the rule was configured and not enforced. Held at warn so adoption stays a lateral move rather than turning green modules red for code that predates it; the debt stays visible and countable via `--run --lint --json` and `--inspect`, and the severity is restored once it reaches zero.",
7916
+ options: [
7917
+ {
7918
+ allow: ["warn", "error"]
7919
+ }
7920
+ ]
7921
+ },
7922
+ "no-constant-binary-expression": "error",
7923
+ "no-constant-condition": [
7924
+ "error",
7925
+ {
7926
+ checkLoops: "allExceptWhileTrue"
7927
+ }
7928
+ ],
7929
+ "no-control-regex": "error",
7930
+ "no-debugger": "error",
7931
+ "no-delete-var": "error",
7932
+ "no-dupe-else-if": "error",
7933
+ "no-duplicate-case": "error",
7934
+ "no-empty": [
7935
+ "error",
7936
+ {
7937
+ allowEmptyCatch: false
7938
+ }
7939
+ ],
7940
+ "no-empty-character-class": "error",
7941
+ "no-empty-pattern": [
7942
+ "error",
7943
+ {
7944
+ allowObjectPatternsAsParameters: false
7945
+ }
7946
+ ],
7947
+ "no-empty-static-block": "error",
7948
+ "no-ex-assign": "error",
7949
+ "no-extra-boolean-cast": ["error", {}],
7950
+ "no-fallthrough": [
7951
+ "error",
7952
+ {
7953
+ allowEmptyCase: false,
7954
+ reportUnusedFallthroughComment: false
7955
+ }
7956
+ ],
7957
+ "no-global-assign": [
7958
+ "error",
7959
+ {
7960
+ exceptions: []
7961
+ }
7962
+ ],
7963
+ "no-invalid-regexp": ["error", {}],
7964
+ "no-irregular-whitespace": [
7965
+ "error",
7966
+ {
7967
+ skipComments: false,
7968
+ skipJSXText: false,
7969
+ skipRegExps: false,
7970
+ skipStrings: true,
7971
+ skipTemplates: false
7972
+ }
7973
+ ],
7974
+ "no-loss-of-precision": "error",
7975
+ "no-misleading-character-class": "error",
7976
+ "no-nonoctal-decimal-escape": "error",
7977
+ "no-prototype-builtins": "error",
7978
+ "no-regex-spaces": "error",
7979
+ "no-restricted-properties": [
7980
+ "error",
7981
+ {
7982
+ object: "i18n",
7983
+ property: "changeLanguage",
7984
+ message: "This does not work in Nextjs. Please use Link or router instead. See https://nextjs.org/docs/advanced-features/i18n-routing#transition-between-locales"
7985
+ }
7986
+ ],
7987
+ "no-self-assign": [
7988
+ "error",
7989
+ {
7990
+ props: true
7991
+ }
7992
+ ],
7993
+ "no-shadow-restricted-names": [
7994
+ "error",
7995
+ {
7996
+ reportGlobalThis: false
7997
+ }
7998
+ ],
7999
+ "no-sparse-arrays": "error",
8000
+ "no-unsafe-finally": "error",
8001
+ "no-unsafe-optional-chaining": [
8002
+ "error",
8003
+ {
8004
+ disallowArithmeticOperators: false
8005
+ }
8006
+ ],
8007
+ "no-unused-labels": "error",
8008
+ "no-unused-private-class-members": "error",
8009
+ "no-useless-backreference": "error",
8010
+ "no-useless-catch": "error",
8011
+ "no-useless-escape": [
8012
+ "error",
8013
+ {
8014
+ allowRegexCharacters: []
8015
+ }
8016
+ ],
8017
+ "nx/enforce-module-boundaries": [
8018
+ "error",
8019
+ {
8020
+ enforceBuildableLibDependency: true,
8021
+ allow: [],
8022
+ depConstraints: [
8023
+ {
8024
+ sourceTag: "*",
8025
+ onlyDependOnLibsWithTags: ["*"]
8026
+ }
8027
+ ]
8028
+ }
8029
+ ],
8030
+ "react-hooks/exhaustive-deps": {
8031
+ severity: "warn",
8032
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first."
8033
+ },
8034
+ "react-hooks/rules-of-hooks": "error",
8035
+ "react/jsx-curly-brace-presence": {
8036
+ severity: "warn",
8037
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first.",
8038
+ options: [
8039
+ {
8040
+ props: "never",
8041
+ children: "never"
8042
+ }
8043
+ ]
8044
+ },
8045
+ "react/jsx-no-useless-fragment": [
8046
+ "error",
8047
+ {
8048
+ allowExpressions: true
8049
+ }
8050
+ ],
8051
+ "require-yield": "error",
8052
+ "typescript/consistent-type-imports": [
8053
+ "error",
8054
+ {
8055
+ fixStyle: "inline-type-imports"
8056
+ }
8057
+ ],
8058
+ "typescript/no-empty-function": [
8059
+ "error",
8060
+ {
8061
+ allow: []
8062
+ }
8063
+ ],
8064
+ "typescript/no-explicit-any": "error",
8065
+ "typescript/no-floating-promises": [
8066
+ "error",
8067
+ {
8068
+ ignoreVoid: true
8069
+ }
8070
+ ],
8071
+ "typescript/no-inferrable-types": "error",
8072
+ "typescript/no-non-null-assertion": {
8073
+ severity: "warn",
8074
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first."
8075
+ },
8076
+ "typescript/no-unused-vars": [
8077
+ "error",
8078
+ {
8079
+ argsIgnorePattern: "^_",
8080
+ caughtErrors: "none"
8081
+ }
8082
+ ],
8083
+ "typescript/no-useless-constructor": {
8084
+ severity: "warn",
8085
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first."
8086
+ },
8087
+ "typescript/return-await": {
8088
+ severity: "warn",
8089
+ reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
8090
+ options: ["in-try-catch"]
8091
+ },
8092
+ "use-isnan": [
8093
+ "error",
8094
+ {
8095
+ enforceForIndexOf: false,
8096
+ enforceForSwitchCase: true
8097
+ }
8098
+ ],
8099
+ "valid-typeof": [
8100
+ "error",
8101
+ {
8102
+ requireStringLiterals: false
8103
+ }
8104
+ ],
8105
+ "vitest/expect-expect": [
8106
+ "error",
8107
+ {
8108
+ assertFunctionNames: [
8109
+ "expect",
8110
+ "expect*",
8111
+ "waitForElementToBeRemoved",
8112
+ "screen.findBy*",
8113
+ "result.findBy*"
8114
+ ]
8115
+ }
8116
+ ],
8117
+ "vitest/no-alias-methods": "error",
8118
+ "vitest/no-commented-out-tests": {
8119
+ severity: "warn",
8120
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first."
8121
+ },
8122
+ "vitest/no-conditional-expect": "error",
8123
+ "vitest/no-disabled-tests": {
8124
+ severity: "warn",
8125
+ reason: "the front libs already run this at warn today (read with `eslint --print-config`), so keeping it there is a like-for-like migration. Raising it to error would be a NEW enforcement decision, and it needs its own count of violations first."
8126
+ },
8127
+ "vitest/no-focused-tests": "error",
8128
+ "vitest/no-identical-title": "error",
8129
+ "vitest/no-interpolation-in-snapshots": "error",
8130
+ "vitest/no-mocks-import": "error",
8131
+ "vitest/no-standalone-expect": "error",
8132
+ "vitest/no-test-prefixes": "error",
8133
+ "vitest/prefer-to-be": "error",
8134
+ "vitest/prefer-to-contain": "error",
8135
+ "vitest/prefer-to-have-length": "error",
8136
+ "vitest/require-mock-type-parameters": {
8137
+ severity: "off",
8138
+ reason: "4 479 of the 5 255 findings this plugin reports over this repository, 85% of them. It asks for type parameters on vi.fn<\u2026>(), a convention this codebase never had; adopting it is its own piece of work and not part of reading the right plugin"
8139
+ },
8140
+ "vitest/require-to-throw-message": {
8141
+ severity: "warn",
8142
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8143
+ },
8144
+ "vitest/valid-describe-callback": "error",
8145
+ "vitest/valid-expect": "error",
8146
+ "vitest/valid-expect-in-promise": "error",
8147
+ "vitest/valid-title": "error",
8148
+ "vitest/warn-todo": {
8149
+ severity: "warn",
8150
+ reason: "a todo test is a reminder rather than a defect, so it reports and never blocks"
8151
+ }
8152
+ },
8153
+ overrides: [
8154
+ {
8155
+ files: ["**/*.stories.ts", "**/*.stories.tsx", "**/*.stories.js", "**/*.stories.jsx"],
8156
+ rules: {
8157
+ "storybook/await-interactions": "error",
8158
+ "storybook/context-in-play-function": "error",
8159
+ "storybook/default-exports": "error",
8160
+ "storybook/hierarchy-separator": {
8161
+ severity: "warn",
8162
+ reason: "the storybook plugin sets this severity for story files; adoption is like-for-like"
8163
+ },
8164
+ "storybook/no-redundant-story-name": {
8165
+ severity: "warn",
8166
+ reason: "the storybook plugin sets this severity for story files; adoption is like-for-like"
8167
+ },
8168
+ "storybook/no-renderer-packages": "error",
8169
+ "storybook/prefer-pascal-case": {
8170
+ severity: "warn",
8171
+ reason: "the storybook plugin sets this severity for story files; adoption is like-for-like"
8172
+ },
8173
+ "storybook/story-exports": "error",
8174
+ "storybook/use-storybook-expect": "error",
8175
+ "storybook/use-storybook-testing-library": "error",
8176
+ "react-hooks/rules-of-hooks": {
8177
+ severity: "off",
8178
+ reason: "off for story files in the module's own config, and it has to be: a story's render function is not a React component and the rule cannot tell, so it reports every useState in a story. Measured on libs/front/components: 7 errors, none of them real."
8179
+ }
8180
+ }
8181
+ },
8182
+ {
8183
+ files: [
8184
+ "**/*.test.ts",
8185
+ "**/*.test.tsx",
8186
+ "**/*.spec.ts",
8187
+ "**/*.spec.tsx",
8188
+ "**/*.integration-spec.ts",
8189
+ "**/*.e2e-spec.ts"
8190
+ ],
8191
+ rules: {
8192
+ "no-restricted-properties": {
8193
+ severity: "warn",
8194
+ reason: "held at warn: over the 8 011 test files, 7 call Math.random(), 104 call Date.now(), 3 crypto.randomUUID(), 1 performance.now() and 1 process.hrtime, and 28 of the files that read the clock already install fake timers. A warning says it without turning a suite that was green yesterday red. Verified at error on a fixture carrying all nine forms: these six are reported, new Date('2026-01-01') is not, which is the point",
8195
+ options: [
8196
+ {
8197
+ object: "Math",
8198
+ property: "random",
8199
+ message: "No Math.random() in a test: use a deterministic fixture, or stub the random source."
8200
+ },
8201
+ {
8202
+ object: "Date",
8203
+ property: "now",
8204
+ message: "No Date.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
8205
+ },
8206
+ {
8207
+ object: "performance",
8208
+ property: "now",
8209
+ message: "No performance.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
8210
+ },
8211
+ {
8212
+ object: "process",
8213
+ property: "hrtime",
8214
+ message: "No process.hrtime in a test: use vi.useFakeTimers() with vi.setSystemTime()."
8215
+ },
8216
+ {
8217
+ object: "crypto",
8218
+ property: "randomUUID",
8219
+ message: "No crypto.randomUUID() in a test: use a fixed id, or stub the random source."
8220
+ },
8221
+ {
8222
+ object: "crypto",
8223
+ property: "getRandomValues",
8224
+ message: "No crypto.getRandomValues() in a test: use a fixed buffer, or stub the random source."
8225
+ }
8226
+ ]
8227
+ },
8228
+ "no-restricted-globals": {
8229
+ severity: "warn",
8230
+ reason: "held at warn: 68 test files call setTimeout and only 2 of them install fake timers, so waiting on the real clock is the second source of flakiness after reading it. The flush idiom await new Promise((resolve) => setTimeout(resolve, 0)) is inside that count and is usually harmless, which is exactly why this reports and never blocks",
8231
+ options: [
8232
+ {
8233
+ name: "setTimeout",
8234
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
8235
+ },
8236
+ {
8237
+ name: "setInterval",
8238
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
8239
+ }
8240
+ ]
8241
+ }
8242
+ }
8243
+ }
8244
+ ],
8245
+ uncovered: {
8246
+ "no-octal": "no oxlint equivalent",
8247
+ "no-restricted-syntax": "no oxlint equivalent; the house rules it encoded ship as the local `hublo` plugin instead, so this tier hosts `../plugins/hublo.js` too. It enforces exactly one of them: `hublo/no-regex-lookbehind`, because a library is where that bug is WRITTEN and it reaches an app tier only by being imported (HUB-43431 shipped from libs/front/components into the worker app). The palette selectors stay app-tier only.",
8248
+ semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between."
8249
+ }
8250
+ };
8251
+
8252
+ // src/roles/lint/presets/react.json
8253
+ var react_default = {
8254
+ plugins: ["typescript", "react", "jsx-a11y", "jest", "vitest", "import", "unicorn", "oxc"],
8255
+ jsPlugins: [
8256
+ "eslint-plugin-no-barrel-files",
8257
+ {
8258
+ name: "nx",
8259
+ specifier: "@nx/eslint-plugin"
8260
+ },
8261
+ {
8262
+ name: "@tanstack/query",
8263
+ specifier: "@tanstack/eslint-plugin-query"
8264
+ },
8265
+ {
8266
+ name: "styled-components-a11y",
8267
+ specifier: "eslint-plugin-styled-components-a11y"
8268
+ },
8269
+ {
8270
+ name: "react-refresh",
8271
+ specifier: "eslint-plugin-react-refresh"
8272
+ },
8273
+ "../plugins/hublo.js",
8274
+ {
8275
+ name: "@tanstack/router",
8276
+ specifier: "@tanstack/eslint-plugin-router"
8277
+ }
8278
+ ],
8279
+ settings: null,
8280
+ rules: {
8281
+ "@tanstack/query/exhaustive-deps": {
8282
+ severity: "warn",
8283
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8284
+ },
8285
+ "@tanstack/query/infinite-query-property-order": {
8286
+ severity: "warn",
8287
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8288
+ },
8289
+ "@tanstack/query/mutation-property-order": {
8290
+ severity: "warn",
8291
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8292
+ },
8293
+ "@tanstack/query/no-rest-destructuring": {
8294
+ severity: "warn",
8295
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8296
+ },
8297
+ "@tanstack/query/no-unstable-deps": {
8298
+ severity: "warn",
8299
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8300
+ },
8301
+ "@tanstack/query/no-void-query-fn": {
8302
+ severity: "warn",
8303
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8304
+ },
8305
+ "@tanstack/query/prefer-query-options": {
8306
+ severity: "warn",
8307
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8308
+ },
8309
+ "@tanstack/query/stable-query-client": {
8310
+ severity: "warn",
8311
+ reason: "held at warn across the react tier: the plugin is now a dependency of sentinel, so the rule runs everywhere instead of being named by a preset that could not load it. Measured over 8 784 files of console, host-admin, libs/front/api and libs/front/components, the eight rules report 806 findings, of which 796 are prefer-query-options and 10 are the correctness ones (exhaustive-deps 9, no-rest-destructuring 1). A module that wants them to block decides that for itself, and pays the 10 fixes"
8312
+ },
8313
+ "@tanstack/router/create-route-property-order": {
8314
+ severity: "warn",
8315
+ reason: "held at warn on the APP tier only: routes live in applications, and libs/front holds 0 of the 227 files that call createFileRoute, the same reason hublo/require-reload-document is here and not in react-lib. Measured over the three fronts, 7 133 files and 226 route files, the two rules report 3 findings, all create-route-property-order on host-admin. The order is not cosmetic: TanStack Router infers types through it"
8316
+ },
8317
+ "@tanstack/router/route-param-names": {
8318
+ severity: "warn",
8319
+ reason: "held at warn on the APP tier only: routes live in applications, and libs/front holds 0 of the 227 files that call createFileRoute, the same reason hublo/require-reload-document is here and not in react-lib. Measured over the three fronts, 7 133 files and 226 route files, the two rules report 3 findings, all create-route-property-order on host-admin. The order is not cosmetic: TanStack Router infers types through it"
8320
+ },
8321
+ curly: ["error", "all"],
8322
+ "for-direction": "error",
8323
+ "hublo/no-palette-common": {
8324
+ severity: "off",
8325
+ reason: "the house rule finds 3 real violations; they are fixed as their own change rather than inside a linter swap, so the rule ships visible-but-off and can be switched on once they are"
8326
+ },
8327
+ "hublo/no-regex-lookbehind": "error",
8328
+ "hublo/require-reload-document": "error",
8329
+ "jest/expect-expect": {
8330
+ severity: "off",
8331
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8332
+ },
8333
+ "jest/no-alias-methods": {
8334
+ severity: "off",
8335
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8336
+ },
8337
+ "jest/no-commented-out-tests": {
8338
+ severity: "off",
8339
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8340
+ },
8341
+ "jest/no-conditional-expect": {
8342
+ severity: "off",
8343
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8344
+ },
8345
+ "jest/no-deprecated-functions": {
8346
+ severity: "off",
8347
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8348
+ },
8349
+ "jest/no-disabled-tests": {
8350
+ severity: "off",
8351
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8352
+ },
8353
+ "jest/no-done-callback": {
8354
+ severity: "off",
8355
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8356
+ },
8357
+ "jest/no-export": {
8358
+ severity: "off",
8359
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8360
+ },
8361
+ "jest/no-focused-tests": {
8362
+ severity: "off",
8363
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8364
+ },
8365
+ "jest/no-identical-title": {
8366
+ severity: "off",
8367
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8368
+ },
8369
+ "jest/no-interpolation-in-snapshots": {
8370
+ severity: "off",
8371
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8372
+ },
8373
+ "jest/no-jasmine-globals": {
8374
+ severity: "off",
8375
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8376
+ },
8377
+ "jest/no-mocks-import": {
8378
+ severity: "off",
8379
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8380
+ },
8381
+ "jest/no-standalone-expect": {
8382
+ severity: "off",
8383
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8384
+ },
8385
+ "jest/no-test-prefixes": {
8386
+ severity: "off",
8387
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8388
+ },
8389
+ "jest/prefer-to-be": {
8390
+ severity: "off",
8391
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8392
+ },
8393
+ "jest/prefer-to-contain": {
8394
+ severity: "off",
8395
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8396
+ },
8397
+ "jest/prefer-to-have-length": {
8398
+ severity: "off",
8399
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8400
+ },
8401
+ "jest/valid-describe-callback": {
8402
+ severity: "off",
8403
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8404
+ },
8405
+ "jest/valid-expect": {
8406
+ severity: "off",
8407
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8408
+ },
8409
+ "jest/valid-expect-in-promise": {
8410
+ severity: "off",
8411
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8412
+ },
8413
+ "jest/valid-title": {
8414
+ severity: "off",
8415
+ reason: "pre-existing test-suite debt; enabling it on adoption would surface a wall of findings and make a lateral move look like a regression"
8416
+ },
8417
+ "jsx-a11y/alt-text": "error",
8418
+ "jsx-a11y/anchor-has-content": "error",
8419
+ "jsx-a11y/anchor-is-valid": "error",
8420
+ "jsx-a11y/aria-activedescendant-has-tabindex": "error",
8421
+ "jsx-a11y/aria-props": "error",
8422
+ "jsx-a11y/aria-proptypes": "error",
8423
+ "jsx-a11y/aria-role": "error",
8424
+ "jsx-a11y/aria-unsupported-elements": "error",
8425
+ "jsx-a11y/autocomplete-valid": "error",
8426
+ "jsx-a11y/click-events-have-key-events": "error",
8427
+ "jsx-a11y/heading-has-content": "error",
8428
+ "jsx-a11y/html-has-lang": "error",
8429
+ "jsx-a11y/iframe-has-title": "error",
8430
+ "jsx-a11y/img-redundant-alt": {
8431
+ severity: "off",
8432
+ reason: 'one real finding, a decorative image whose title is already rendered as a heading beside it, so the fix is alt="". A genuine accessibility issue rather than a false positive: parked only so swapping linters stays a lateral move, and worth switching back on as soon as it is fixed'
8433
+ },
8434
+ "jsx-a11y/interactive-supports-focus": [
8435
+ "error",
8436
+ {
8437
+ tabbable: [
8438
+ "button",
8439
+ "checkbox",
8440
+ "link",
8441
+ "progressbar",
8442
+ "searchbox",
8443
+ "slider",
8444
+ "spinbutton",
8445
+ "switch",
8446
+ "textbox"
8447
+ ]
8448
+ }
8449
+ ],
8450
+ "jsx-a11y/label-has-associated-control": "error",
8451
+ "jsx-a11y/media-has-caption": "error",
8452
+ "jsx-a11y/mouse-events-have-key-events": "error",
8453
+ "jsx-a11y/no-access-key": "error",
8454
+ "jsx-a11y/no-autofocus": "error",
8455
+ "jsx-a11y/no-distracting-elements": "error",
8456
+ "jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
8457
+ "jsx-a11y/no-noninteractive-element-interactions": [
8458
+ "error",
8459
+ {
8460
+ body: ["onError", "onLoad"],
8461
+ iframe: ["onError", "onLoad"],
8462
+ img: ["onError", "onLoad"]
8463
+ }
8464
+ ],
8465
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
8466
+ "jsx-a11y/no-noninteractive-tabindex": "error",
8467
+ "jsx-a11y/no-redundant-roles": "error",
8468
+ "jsx-a11y/no-static-element-interactions": "error",
8469
+ "jsx-a11y/role-has-required-aria-props": "error",
8470
+ "jsx-a11y/role-supports-aria-props": "error",
8471
+ "jsx-a11y/scope": "error",
8472
+ "jsx-a11y/tabindex-no-positive": "error",
8473
+ "no-async-promise-executor": "error",
8474
+ "no-barrel-files/no-barrel-files": {
8475
+ severity: "warn",
8476
+ reason: "the modules already ran this at warn (confirmed in the host-admin proof-of-concept config); nest disables it outright as deliberate architecture, react keeps it visible"
8477
+ },
8478
+ "no-case-declarations": "error",
8479
+ "no-compare-neg-zero": "error",
8480
+ "no-cond-assign": ["error", "except-parens"],
8481
+ "no-console": {
8482
+ severity: "off",
8483
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8484
+ },
8485
+ "no-constant-binary-expression": "error",
8486
+ "no-constant-condition": [
8487
+ "error",
8488
+ {
8489
+ checkLoops: "allExceptWhileTrue"
8490
+ }
8491
+ ],
8492
+ "no-control-regex": "error",
8493
+ "no-debugger": "error",
8494
+ "no-delete-var": "error",
8495
+ "no-dupe-else-if": "error",
8496
+ "no-duplicate-case": "error",
8497
+ "no-empty": [
8498
+ "error",
8499
+ {
8500
+ allowEmptyCatch: false
8501
+ }
8502
+ ],
8503
+ "no-empty-character-class": "error",
8504
+ "no-empty-pattern": [
8505
+ "error",
8506
+ {
8507
+ allowObjectPatternsAsParameters: false
8508
+ }
8509
+ ],
8510
+ "no-empty-static-block": "error",
8511
+ "no-ex-assign": "error",
8512
+ "no-extra-boolean-cast": ["error", {}],
8513
+ "no-fallthrough": [
8514
+ "error",
8515
+ {
8516
+ allowEmptyCase: false,
8517
+ reportUnusedFallthroughComment: false
8518
+ }
8519
+ ],
8520
+ "no-global-assign": [
8521
+ "error",
8522
+ {
8523
+ exceptions: []
8524
+ }
8525
+ ],
8526
+ "no-invalid-regexp": ["error", {}],
8527
+ "no-irregular-whitespace": [
8528
+ "error",
8529
+ {
8530
+ skipComments: false,
8531
+ skipJSXText: false,
8532
+ skipRegExps: false,
8533
+ skipStrings: true,
8534
+ skipTemplates: false
8535
+ }
8536
+ ],
8537
+ "no-loss-of-precision": "error",
8538
+ "no-misleading-character-class": "error",
8539
+ "no-nonoctal-decimal-escape": "error",
8540
+ "no-prototype-builtins": "error",
8541
+ "no-regex-spaces": "error",
8542
+ "no-restricted-imports": [
8543
+ "error",
8544
+ {
8545
+ patterns: [
8546
+ {
8547
+ group: ["next/*"],
8548
+ message: "Importing from next/* is not allowed in the admin project."
8549
+ },
8550
+ {
8551
+ group: ["storybook/*"],
8552
+ message: "Importing from storybook/* is not allowed in the admin project."
8553
+ },
8554
+ {
8555
+ group: ["@nestjs/*"],
8556
+ message: "Importing from @nestjs/* is not allowed in the admin project."
8557
+ },
8558
+ {
8559
+ group: ["next-i18next"],
8560
+ message: "Importing from next-i18next is not allowed in the admin project."
8561
+ },
8562
+ {
8563
+ group: ["@hublo/style/colors", "@hublo/style/colors/*"],
8564
+ message: "Importing runtime colors directly is not allowed. Use the MUI theme, theme-derived CSS variables, or an explicit product-specific mapping."
8565
+ }
8566
+ ]
8567
+ }
8568
+ ],
8569
+ "no-self-assign": [
8570
+ "error",
8571
+ {
8572
+ props: true
8573
+ }
8574
+ ],
8575
+ "no-shadow-restricted-names": [
8576
+ "error",
8577
+ {
8578
+ reportGlobalThis: false
8579
+ }
8580
+ ],
8581
+ "no-sparse-arrays": "error",
8582
+ "no-unexpected-multiline": "error",
8583
+ "no-unsafe-finally": "error",
8584
+ "no-unsafe-optional-chaining": {
8585
+ severity: "off",
8586
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8587
+ },
8588
+ "no-unused-labels": "error",
8589
+ "no-unused-private-class-members": "error",
8590
+ "no-useless-backreference": "error",
8591
+ "no-useless-catch": "error",
8592
+ "no-useless-escape": [
8593
+ "error",
8594
+ {
8595
+ allowRegexCharacters: []
8596
+ }
8597
+ ],
8598
+ "nx/enforce-module-boundaries": [
8599
+ "error",
8600
+ {
8601
+ enforceBuildableLibDependency: true,
8602
+ allow: [],
8603
+ depConstraints: [
8604
+ {
8605
+ sourceTag: "*",
8606
+ onlyDependOnLibsWithTags: ["*"]
8607
+ }
8608
+ ]
8609
+ }
8610
+ ],
8611
+ "react-hooks/exhaustive-deps": {
8612
+ severity: "warn",
8613
+ reason: "the modules already ran this at warn (confirmed in the host-admin proof-of-concept config); its suggested fix can change behaviour, so it reports and a human decides"
8614
+ },
8615
+ "react-hooks/rules-of-hooks": "error",
8616
+ "react-refresh/only-export-components": {
8617
+ severity: "warn",
8618
+ reason: "the modules already ran this at warn (confirmed in the host-admin proof-of-concept config); it guards fast-refresh ergonomics, not correctness",
8619
+ options: [
8620
+ {
8621
+ allowConstantExport: true
8622
+ }
8623
+ ]
8624
+ },
8625
+ "react/jsx-curly-brace-presence": {
8626
+ severity: "off",
8627
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8628
+ },
8629
+ "require-yield": "error",
8630
+ "styled-components-a11y/alt-text": "error",
8631
+ "styled-components-a11y/anchor-has-content": "error",
8632
+ "styled-components-a11y/anchor-is-valid": "error",
8633
+ "styled-components-a11y/aria-activedescendant-has-tabindex": "error",
8634
+ "styled-components-a11y/aria-props": "error",
8635
+ "styled-components-a11y/aria-proptypes": "error",
8636
+ "styled-components-a11y/aria-role": "error",
8637
+ "styled-components-a11y/aria-unsupported-elements": "error",
8638
+ "styled-components-a11y/autocomplete-valid": "error",
8639
+ "styled-components-a11y/click-events-have-key-events": "error",
8640
+ "styled-components-a11y/control-has-associated-label": {
8641
+ severity: "off",
8642
+ reason: "off in the host-admin proof-of-concept config, with its element and role ignore lists; it fires on composed styled components where the label lives in a wrapper the rule cannot see",
8643
+ options: [
8644
+ {
8645
+ ignoreElements: ["audio", "canvas", "embed", "input", "textarea", "tr", "video"],
8646
+ ignoreRoles: [
8647
+ "grid",
8648
+ "listbox",
8649
+ "menu",
8650
+ "menubar",
8651
+ "radiogroup",
8652
+ "row",
8653
+ "tablist",
8654
+ "toolbar",
8655
+ "tree",
8656
+ "treegrid"
8657
+ ],
8658
+ includeRoles: ["alert", "dialog"]
8659
+ }
8660
+ ]
8661
+ },
8662
+ "styled-components-a11y/heading-has-content": "error",
8663
+ "styled-components-a11y/html-has-lang": "error",
8664
+ "styled-components-a11y/iframe-has-title": "error",
8665
+ "styled-components-a11y/img-redundant-alt": "error",
8666
+ "styled-components-a11y/interactive-supports-focus": [
8667
+ "error",
8668
+ {
8669
+ tabbable: [
8670
+ "button",
8671
+ "checkbox",
8672
+ "link",
8673
+ "progressbar",
8674
+ "searchbox",
8675
+ "slider",
8676
+ "spinbutton",
8677
+ "switch",
8678
+ "textbox"
8679
+ ]
8680
+ }
8681
+ ],
8682
+ "styled-components-a11y/label-has-associated-control": "error",
8683
+ "styled-components-a11y/label-has-for": {
8684
+ severity: "off",
8685
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8686
+ },
8687
+ "styled-components-a11y/media-has-caption": "error",
8688
+ "styled-components-a11y/mouse-events-have-key-events": "error",
8689
+ "styled-components-a11y/no-access-key": "error",
8690
+ "styled-components-a11y/no-autofocus": "error",
8691
+ "styled-components-a11y/no-distracting-elements": "error",
8692
+ "styled-components-a11y/no-interactive-element-to-noninteractive-role": "error",
8693
+ "styled-components-a11y/no-noninteractive-element-interactions": [
8694
+ "error",
8695
+ {
8696
+ body: ["onError", "onLoad"],
8697
+ iframe: ["onError", "onLoad"],
8698
+ img: ["onError", "onLoad"]
8699
+ }
8700
+ ],
8701
+ "styled-components-a11y/no-noninteractive-element-to-interactive-role": "error",
8702
+ "styled-components-a11y/no-noninteractive-tabindex": "error",
8703
+ "styled-components-a11y/no-redundant-roles": "error",
8704
+ "styled-components-a11y/no-static-element-interactions": "error",
8705
+ "styled-components-a11y/role-has-required-aria-props": "error",
8706
+ "styled-components-a11y/role-supports-aria-props": "error",
8707
+ "styled-components-a11y/scope": "error",
8708
+ "styled-components-a11y/tabindex-no-positive": "error",
8709
+ "typescript/await-thenable": "error",
8710
+ "typescript/no-array-delete": "error",
8711
+ "typescript/no-base-to-string": "error",
8712
+ "typescript/no-duplicate-type-constituents": "error",
8713
+ "typescript/no-empty-function": {
8714
+ severity: "off",
8715
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8716
+ },
8717
+ "typescript/no-explicit-any": {
8718
+ severity: "off",
8719
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8720
+ },
8721
+ "typescript/no-floating-promises": [
8722
+ "error",
8723
+ {
8724
+ ignoreVoid: true
8725
+ }
8726
+ ],
8727
+ "typescript/no-for-in-array": "error",
8728
+ "typescript/no-implied-eval": "error",
8729
+ "typescript/no-inferrable-types": "error",
8730
+ "typescript/no-misused-promises": [
8731
+ "error",
8732
+ {
8733
+ checksVoidReturn: {
8734
+ attributes: false
8735
+ }
8736
+ }
8737
+ ],
8738
+ "typescript/no-non-null-assertion": {
8739
+ severity: "warn",
8740
+ reason: "kept at the severity the proof of concept measured; no module config set it, so raising it to error would be a NEW enforcement decision rather than a like-for-like migration, and it needs its own count of violations first"
8741
+ },
8742
+ "typescript/no-redundant-type-constituents": "error",
8743
+ "typescript/no-require-imports": "error",
8744
+ "typescript/no-unnecessary-type-assertion": {
8745
+ severity: "off",
8746
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8747
+ },
8748
+ "typescript/no-unsafe-argument": {
8749
+ severity: "off",
8750
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8751
+ },
8752
+ "typescript/no-unsafe-assignment": {
8753
+ severity: "off",
8754
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8755
+ },
8756
+ "typescript/no-unsafe-call": {
8757
+ severity: "off",
8758
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8759
+ },
8760
+ "typescript/no-unsafe-enum-comparison": {
8761
+ severity: "off",
8762
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8763
+ },
8764
+ "typescript/no-unsafe-member-access": {
8765
+ severity: "off",
8766
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8767
+ },
8768
+ "typescript/no-unsafe-return": {
8769
+ severity: "off",
8770
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8771
+ },
8772
+ "typescript/no-unsafe-unary-minus": "error",
8773
+ "typescript/no-unused-vars": {
8774
+ severity: "off",
8775
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8776
+ },
8777
+ "typescript/only-throw-error": {
8778
+ severity: "off",
8779
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8780
+ },
8781
+ "typescript/prefer-promise-reject-errors": "error",
8782
+ "typescript/require-await": {
8783
+ severity: "off",
8784
+ reason: "pre-existing debt measured during the proof of concept; parked so adoption stays non-breaking"
8785
+ },
8786
+ "typescript/restrict-plus-operands": "error",
8787
+ "typescript/restrict-template-expressions": "error",
8788
+ "typescript/return-await": {
8789
+ severity: "warn",
8790
+ reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
8791
+ options: ["in-try-catch"]
8792
+ },
8793
+ "typescript/switch-exhaustiveness-check": {
8794
+ severity: "warn",
8795
+ reason: "kept at the severity the proof of concept measured; no module config set it, so raising it to error would be a NEW enforcement decision rather than a like-for-like migration, and it needs its own count of violations first"
8796
+ },
8797
+ "typescript/unbound-method": "error",
8798
+ "use-isnan": [
8799
+ "error",
8800
+ {
8801
+ enforceForIndexOf: false,
8802
+ enforceForSwitchCase: true
8803
+ }
8804
+ ],
8805
+ "valid-typeof": [
8806
+ "error",
8807
+ {
8808
+ requireStringLiterals: false
8809
+ }
8810
+ ],
8811
+ "vitest/expect-expect": {
8812
+ severity: "warn",
8813
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8814
+ },
8815
+ "vitest/no-alias-methods": {
8816
+ severity: "warn",
8817
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8818
+ },
8819
+ "vitest/no-commented-out-tests": {
8820
+ severity: "warn",
8821
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8822
+ },
8823
+ "vitest/no-conditional-expect": {
8824
+ severity: "warn",
8825
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8826
+ },
8827
+ "vitest/no-disabled-tests": {
8828
+ severity: "warn",
8829
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8830
+ },
8831
+ "vitest/no-focused-tests": {
8832
+ severity: "warn",
8833
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8834
+ },
8835
+ "vitest/no-identical-title": {
8836
+ severity: "warn",
8837
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8838
+ },
8839
+ "vitest/no-interpolation-in-snapshots": {
8840
+ severity: "warn",
8841
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8842
+ },
8843
+ "vitest/no-mocks-import": {
8844
+ severity: "warn",
8845
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8846
+ },
8847
+ "vitest/no-standalone-expect": {
8848
+ severity: "warn",
8849
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8850
+ },
8851
+ "vitest/no-test-prefixes": {
8852
+ severity: "warn",
8853
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8854
+ },
8855
+ "vitest/prefer-to-be": {
8856
+ severity: "warn",
8857
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8858
+ },
8859
+ "vitest/prefer-to-contain": {
8860
+ severity: "warn",
8861
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8862
+ },
8863
+ "vitest/prefer-to-have-length": {
8864
+ severity: "warn",
8865
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8866
+ },
8867
+ "vitest/require-mock-type-parameters": {
8868
+ severity: "off",
8869
+ reason: "4 479 of the 5 255 findings this plugin reports over this repository, 85% of them. It asks for type parameters on vi.fn<\u2026>(), a convention this codebase never had; adopting it is its own piece of work and not part of reading the right plugin"
8870
+ },
8871
+ "vitest/require-to-throw-message": {
8872
+ severity: "warn",
8873
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8874
+ },
8875
+ "vitest/valid-describe-callback": {
8876
+ severity: "warn",
8877
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8878
+ },
8879
+ "vitest/valid-expect": {
8880
+ severity: "warn",
8881
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8882
+ },
8883
+ "vitest/valid-expect-in-promise": {
8884
+ severity: "warn",
8885
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8886
+ },
8887
+ "vitest/valid-title": {
8888
+ severity: "warn",
8889
+ reason: "new to this preset and the test suite predates it, so it reports and never blocks. The reason the jest equivalent was parked was that enabling it on adoption would make a lateral move look like a regression; a warning does not"
8890
+ },
8891
+ "vitest/warn-todo": {
8892
+ severity: "warn",
8893
+ reason: "a todo test is a reminder rather than a defect, so it reports and never blocks"
8894
+ }
8895
+ },
8896
+ uncovered: {
8897
+ "import/order": "import ordering is FORMATTING, and the format role owns it: oxfmt's `sortImports` reproduces exactly these groups (builtin, external, internal, parent/index, sibling, alphabetical, blank line between) and sorts a file completely in ONE pass, because it rewrites the whole import block. A lint fixer cannot: it emits text edits over overlapping ranges, so this rule needed several runs to finish (five imports took four passes, measured) and sentinel deliberately runs autofix once. Enforced by `pnpm run format`, not by the linter, which is also why `eslint-plugin-import` is no longer hosted at all: its only other rule, no-duplicates, is native in oxlint.",
8898
+ "no-octal": "no oxlint equivalent",
8899
+ "no-restricted-syntax": "no oxlint equivalent as configuration, so the AST selectors this repo used are shipped as real rules in the local `hublo` plugin instead, which this tier hosts (`../plugins/hublo.js`). Three are hosted today: the eight palette selectors as `hublo/no-palette-common`, the lookbehind selector as `hublo/no-regex-lookbehind`, and the TanStack Router one as `hublo/require-reload-document`, carried here from an ESLint selector in the monorepo (PR #22043) because ADR 0042 makes this the layer that outlives it. App tier only: measured on main, `libs/front` calls `redirect(` nowhere, while host-admin does in 93 files and console in 2. Any selector NOT in that plugin is not enforced here; propose it and it joins the plugin rather than a per-module copy.",
8900
+ semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between."
8901
+ },
8902
+ overrides: [
8903
+ {
8904
+ files: [
8905
+ "**/*.test.ts",
8906
+ "**/*.test.tsx",
8907
+ "**/*.spec.ts",
8908
+ "**/*.spec.tsx",
8909
+ "**/*.integration-spec.ts",
8910
+ "**/*.e2e-spec.ts"
8911
+ ],
8912
+ rules: {
8913
+ "no-restricted-properties": {
8914
+ severity: "warn",
8915
+ reason: "held at warn: over the 8 011 test files, 7 call Math.random(), 104 call Date.now(), 3 crypto.randomUUID(), 1 performance.now() and 1 process.hrtime, and 28 of the files that read the clock already install fake timers. A warning says it without turning a suite that was green yesterday red. Verified at error on a fixture carrying all nine forms: these six are reported, new Date('2026-01-01') is not, which is the point",
8916
+ options: [
8917
+ {
8918
+ object: "Math",
8919
+ property: "random",
8920
+ message: "No Math.random() in a test: use a deterministic fixture, or stub the random source."
8921
+ },
8922
+ {
8923
+ object: "Date",
8924
+ property: "now",
8925
+ message: "No Date.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
8926
+ },
8927
+ {
8928
+ object: "performance",
8929
+ property: "now",
8930
+ message: "No performance.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
8931
+ },
8932
+ {
8933
+ object: "process",
8934
+ property: "hrtime",
8935
+ message: "No process.hrtime in a test: use vi.useFakeTimers() with vi.setSystemTime()."
8936
+ },
8937
+ {
8938
+ object: "crypto",
8939
+ property: "randomUUID",
8940
+ message: "No crypto.randomUUID() in a test: use a fixed id, or stub the random source."
8941
+ },
8942
+ {
8943
+ object: "crypto",
8944
+ property: "getRandomValues",
8945
+ message: "No crypto.getRandomValues() in a test: use a fixed buffer, or stub the random source."
8946
+ }
8947
+ ]
8948
+ },
8949
+ "no-restricted-globals": {
8950
+ severity: "warn",
8951
+ reason: "held at warn: 68 test files call setTimeout and only 2 of them install fake timers, so waiting on the real clock is the second source of flakiness after reading it. The flush idiom await new Promise((resolve) => setTimeout(resolve, 0)) is inside that count and is usually harmless, which is exactly why this reports and never blocks",
8952
+ options: [
8953
+ {
8954
+ name: "setTimeout",
8955
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
8956
+ },
8957
+ {
8958
+ name: "setInterval",
8959
+ message: "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
8960
+ }
8961
+ ]
8962
+ }
8963
+ }
8964
+ }
8965
+ ]
8966
+ };
8967
+
8968
+ // src/roles/lint/presets/shared.json
8969
+ var shared_default = {
8970
+ rules: {
8971
+ "no-var": "error",
8972
+ "prefer-const": [
8973
+ "error",
8974
+ {
8975
+ destructuring: "any",
8976
+ ignoreReadBeforeAssign: false
8977
+ }
8978
+ ],
8979
+ "prefer-rest-params": "error",
8980
+ "prefer-spread": "error",
8981
+ "typescript/ban-ts-comment": "error",
8982
+ "typescript/no-array-constructor": "error",
8983
+ "typescript/no-duplicate-enum-values": "error",
8984
+ "typescript/no-empty-object-type": "error",
8985
+ "typescript/no-extra-non-null-assertion": "error",
8986
+ "typescript/no-misused-new": "error",
8987
+ "typescript/no-namespace": "error",
8988
+ "typescript/no-non-null-asserted-optional-chain": "error",
8989
+ "typescript/no-this-alias": "error",
8990
+ "typescript/no-unnecessary-type-constraint": "error",
8991
+ "typescript/no-unsafe-declaration-merging": "error",
8992
+ "typescript/no-unsafe-function-type": "error",
8993
+ "typescript/no-unused-expressions": [
8994
+ "error",
8995
+ {
8996
+ allowShortCircuit: false,
8997
+ allowTaggedTemplates: false,
8998
+ allowTernary: false
8999
+ }
9000
+ ],
9001
+ "typescript/no-wrapper-object-types": "error",
9002
+ "typescript/prefer-as-const": "error",
9003
+ "typescript/prefer-namespace-keyword": "error",
9004
+ "typescript/triple-slash-reference": "error"
9005
+ }
9006
+ };
9007
+
9008
+ // src/roles/lint/presets/svelte.json
9009
+ var svelte_default = {
9010
+ plugins: ["typescript", "oxc"],
9011
+ jsPlugins: [],
9012
+ settings: null,
9013
+ rules: {
9014
+ "constructor-super": "error",
9015
+ "for-direction": "error",
9016
+ "getter-return": [
9017
+ "error",
9018
+ {
9019
+ allowImplicit: false
9020
+ }
9021
+ ],
9022
+ "no-async-promise-executor": "error",
9023
+ "no-case-declarations": "error",
9024
+ "no-class-assign": "error",
9025
+ "no-compare-neg-zero": "error",
9026
+ "no-cond-assign": ["error", "except-parens"],
9027
+ "no-const-assign": "error",
9028
+ "no-constant-binary-expression": "error",
9029
+ "no-constant-condition": [
9030
+ "error",
9031
+ {
9032
+ checkLoops: "allExceptWhileTrue"
9033
+ }
9034
+ ],
9035
+ "no-control-regex": "error",
9036
+ "no-debugger": "error",
9037
+ "no-delete-var": "error",
9038
+ "no-dupe-class-members": "error",
9039
+ "no-dupe-else-if": "error",
9040
+ "no-dupe-keys": "error",
9041
+ "no-duplicate-case": "error",
9042
+ "no-empty": [
9043
+ "error",
9044
+ {
9045
+ allowEmptyCatch: false
9046
+ }
9047
+ ],
9048
+ "no-empty-character-class": "error",
9049
+ "no-empty-pattern": [
9050
+ "error",
9051
+ {
9052
+ allowObjectPatternsAsParameters: false
9053
+ }
9054
+ ],
9055
+ "no-empty-static-block": "error",
9056
+ "no-ex-assign": "error",
9057
+ "no-extra-boolean-cast": ["error", {}],
9058
+ "no-fallthrough": [
9059
+ "error",
9060
+ {
9061
+ allowEmptyCase: false,
9062
+ reportUnusedFallthroughComment: false
9063
+ }
9064
+ ],
9065
+ "no-func-assign": "error",
9066
+ "no-global-assign": [
9067
+ "error",
9068
+ {
9069
+ exceptions: []
9070
+ }
9071
+ ],
9072
+ "no-import-assign": "error",
9073
+ "no-invalid-regexp": ["error", {}],
9074
+ "no-irregular-whitespace": [
9075
+ "error",
9076
+ {
9077
+ skipComments: false,
9078
+ skipJSXText: false,
9079
+ skipRegExps: false,
9080
+ skipStrings: true,
9081
+ skipTemplates: false
9082
+ }
9083
+ ],
9084
+ "no-loss-of-precision": "error",
9085
+ "no-misleading-character-class": "error",
9086
+ "no-new-native-nonconstructor": "error",
9087
+ "no-nonoctal-decimal-escape": "error",
9088
+ "no-obj-calls": "error",
9089
+ "no-prototype-builtins": "error",
9090
+ "no-redeclare": [
9091
+ "error",
9092
+ {
9093
+ builtinGlobals: true
9094
+ }
9095
+ ],
9096
+ "no-regex-spaces": "error",
9097
+ "no-self-assign": [
9098
+ "error",
9099
+ {
9100
+ props: true
9101
+ }
9102
+ ],
9103
+ "no-setter-return": "error",
9104
+ "no-shadow-restricted-names": [
9105
+ "error",
9106
+ {
9107
+ reportGlobalThis: false
9108
+ }
9109
+ ],
9110
+ "no-sparse-arrays": "error",
9111
+ "no-this-before-super": "error",
9112
+ "no-undef": {
9113
+ severity: "off",
9114
+ reason: "unusable under oxlint for Svelte, and not for want of configuration. Svelte 5's runes ($state, $props, $derived, $effect) are compiler globals, and `$store` auto-subscription invents an identifier per store ($_, $page, $currentAdmin, \u2026), so the names cannot be enumerated. eslint-plugin-svelte's PARSER supplies both; oxlint has no Svelte parser, so it reported 1641 undefined identifiers on hr-management, none of them real. TypeScript and svelte-check already report genuinely undefined identifiers, which is why react and nest never enabled this rule either."
9115
+ },
9116
+ "no-unreachable": "error",
9117
+ "no-unsafe-finally": "error",
9118
+ "no-unsafe-negation": [
9119
+ "error",
9120
+ {
9121
+ enforceForOrderingRelations: false
9122
+ }
9123
+ ],
9124
+ "no-unsafe-optional-chaining": [
9125
+ "error",
9126
+ {
9127
+ disallowArithmeticOperators: false
9128
+ }
9129
+ ],
9130
+ "no-unused-labels": "error",
9131
+ "no-unused-private-class-members": "error",
9132
+ "no-useless-backreference": "error",
9133
+ "no-useless-catch": "error",
9134
+ "no-useless-escape": [
9135
+ "error",
9136
+ {
9137
+ allowRegexCharacters: []
9138
+ }
9139
+ ],
9140
+ "no-with": "error",
9141
+ "prefer-rest-params": {
9142
+ severity: "warn",
9143
+ reason: "error in the module's eslint config, but never actually reported: `eslint src/lib/analytics/useAnalytics.js` exits clean today while oxlint finds 3 real violations in it. Held at warn so adoption does not turn a green module red, and restored to error once they are fixed. Not auto-fixable, verified with --fix."
9144
+ },
9145
+ "prefer-spread": {
9146
+ severity: "warn",
9147
+ reason: "error in the module's eslint config, but never actually reported: `eslint src/lib/analytics/useAnalytics.js` exits clean today while oxlint finds 3 real violations in it. Held at warn so adoption does not turn a green module red, and restored to error once they are fixed. Not auto-fixable, verified with --fix."
9148
+ },
9149
+ "require-yield": "error",
9150
+ "typescript/no-explicit-any": {
9151
+ severity: "warn",
9152
+ reason: "libs/front/ui enforces it and apps/front/hr-management turns it off; enforcing it would newly fail hr-management, so it reports for both instead of breaking one"
9153
+ },
9154
+ "typescript/no-require-imports": "error",
9155
+ "typescript/no-unused-vars": {
9156
+ severity: "warn",
9157
+ reason: "the two modules disagree: libs/front/ui runs it at error with no ignore patterns, apps/front/hr-management at warn with the ^_ patterns kept here (both read from eslint --print-config). Holding it at error would newly fail hr-management, so it reports for both and keeps the patterns hr-management already relies on",
9158
+ options: [
9159
+ {
9160
+ varsIgnorePattern: "^_",
9161
+ argsIgnorePattern: "^_",
9162
+ destructuredArrayIgnorePattern: "^_"
9163
+ }
9164
+ ]
9165
+ },
9166
+ "use-isnan": [
9167
+ "error",
9168
+ {
9169
+ enforceForIndexOf: false,
9170
+ enforceForSwitchCase: true
9171
+ }
9172
+ ],
9173
+ "valid-typeof": [
9174
+ "error",
9175
+ {
9176
+ requireStringLiterals: false
9177
+ }
9178
+ ]
9179
+ },
9180
+ uncovered: {
9181
+ "svelte/comment-directive": "oxlint cannot parse Svelte templates, so the eslint-disable comments inside markup are neither read nor honoured",
9182
+ "svelte/system": "the plugin internal rule that reports Svelte parse errors; it needs the Svelte parser",
9183
+ "svelte/no-at-debug-tags": "reads {@debug} in the template",
9184
+ "svelte/no-at-html-tags": "reads {@html} in the template, the XSS guard; the highest-value loss in this list",
9185
+ "svelte/no-dupe-else-if-blocks": "reads {#if}/{:else if} chains in the template",
9186
+ "svelte/no-dupe-style-properties": "reads the style block",
9187
+ "svelte/no-dynamic-slot-name": "reads <slot> in the template",
9188
+ "svelte/no-inner-declarations": "the Svelte-aware variant; the core rule it shadows is not a substitute inside markup",
9189
+ "svelte/no-not-function-handler": "reads on:event handlers in the template",
9190
+ "svelte/no-object-in-text-mustaches": "reads {expression} interpolation in the template",
9191
+ "svelte/no-shorthand-style-property-overrides": "reads the style block",
9192
+ "svelte/no-unknown-style-directive-property": "reads style: directives in the template",
9193
+ "svelte/no-unused-svelte-ignore": "reads svelte-ignore comments in the template",
9194
+ "svelte/valid-compile": "runs the Svelte compiler and reports its warnings",
9195
+ "no-dupe-args": "no oxlint equivalent (TypeScript rejects duplicate parameter names anyway)",
9196
+ "no-octal": "no oxlint equivalent"
9197
+ }
9198
+ };
9199
+
9200
+ // src/roles/lint/presets/tools.json
9201
+ var tools_default = {
9202
+ plugins: ["typescript", "oxc", "unicorn", "import", "node"],
9203
+ jsPlugins: [],
9204
+ settings: null,
9205
+ rules: {
9206
+ "for-direction": "error",
9207
+ "no-async-promise-executor": "error",
9208
+ "no-case-declarations": "error",
9209
+ "no-compare-neg-zero": "error",
9210
+ "no-cond-assign": ["error", "except-parens"],
9211
+ "no-constant-binary-expression": [
9212
+ "error",
9213
+ {
9214
+ checkRelationalComparisons: false
9215
+ }
9216
+ ],
9217
+ "no-constant-condition": [
9218
+ "error",
9219
+ {
9220
+ checkLoops: "allExceptWhileTrue"
9221
+ }
9222
+ ],
9223
+ "no-control-regex": "error",
9224
+ "no-debugger": "error",
9225
+ "no-delete-var": "error",
9226
+ "no-dupe-else-if": "error",
9227
+ "no-duplicate-case": "error",
9228
+ "no-empty": [
9229
+ "error",
9230
+ {
9231
+ allowEmptyCatch: false
9232
+ }
9233
+ ],
9234
+ "no-empty-character-class": "error",
9235
+ "no-empty-pattern": [
9236
+ "error",
9237
+ {
9238
+ allowObjectPatternsAsParameters: false
9239
+ }
9240
+ ],
9241
+ "no-empty-static-block": "error",
9242
+ "no-ex-assign": "error",
9243
+ "no-extra-boolean-cast": ["error", {}],
9244
+ "no-fallthrough": [
9245
+ "error",
9246
+ {
9247
+ allowEmptyCase: false,
9248
+ reportUnusedFallthroughComment: false
9249
+ }
9250
+ ],
9251
+ "no-global-assign": [
9252
+ "error",
9253
+ {
9254
+ exceptions: []
9255
+ }
9256
+ ],
9257
+ "no-invalid-regexp": ["error", {}],
9258
+ "no-irregular-whitespace": [
9259
+ "error",
9260
+ {
9261
+ skipComments: false,
9262
+ skipJSXText: false,
9263
+ skipRegExps: false,
9264
+ skipStrings: true,
9265
+ skipTemplates: false
9266
+ }
9267
+ ],
9268
+ "no-loss-of-precision": "error",
9269
+ "no-misleading-character-class": [
9270
+ "error",
9271
+ {
9272
+ allowEscape: false
9273
+ }
9274
+ ],
9275
+ "no-nonoctal-decimal-escape": "error",
9276
+ "no-prototype-builtins": "error",
9277
+ "no-regex-spaces": "error",
9278
+ "no-self-assign": [
9279
+ "error",
9280
+ {
9281
+ props: true
9282
+ }
9283
+ ],
9284
+ "no-shadow-restricted-names": [
9285
+ "error",
9286
+ {
9287
+ reportGlobalThis: true
9288
+ }
9289
+ ],
9290
+ "no-sparse-arrays": "error",
9291
+ "no-unassigned-vars": "error",
9292
+ "no-unsafe-finally": "error",
9293
+ "no-unsafe-optional-chaining": [
9294
+ "error",
9295
+ {
9296
+ disallowArithmeticOperators: false
9297
+ }
9298
+ ],
9299
+ "no-unused-labels": "error",
9300
+ "no-unused-private-class-members": "error",
9301
+ "no-useless-assignment": "error",
9302
+ "no-useless-backreference": "error",
9303
+ "no-useless-catch": "error",
9304
+ "no-useless-escape": [
9305
+ "error",
9306
+ {
9307
+ allowRegexCharacters: []
9308
+ }
9309
+ ],
9310
+ "preserve-caught-error": [
9311
+ "error",
9312
+ {
9313
+ requireCatchParameter: false
9314
+ }
9315
+ ],
9316
+ "require-yield": "error",
9317
+ "typescript/no-explicit-any": "error",
9318
+ "typescript/no-require-imports": "error",
9319
+ "typescript/no-unused-vars": [
9320
+ "error",
9321
+ {
9322
+ argsIgnorePattern: "^_",
9323
+ varsIgnorePattern: "^_"
9324
+ }
9325
+ ],
9326
+ "use-isnan": [
9327
+ "error",
9328
+ {
9329
+ enforceForIndexOf: false,
9330
+ enforceForSwitchCase: true
9331
+ }
9332
+ ],
9333
+ "valid-typeof": [
9334
+ "error",
9335
+ {
9336
+ requireStringLiterals: false
9337
+ }
9338
+ ]
9339
+ },
9340
+ uncovered: {
9341
+ "no-octal": "no oxlint equivalent"
9342
+ }
9343
+ };
9344
+
9345
+ // src/roles/lint/preset-data.ts
9346
+ var SHARED_RULES = shared_default.rules;
9347
+ var PRESET_FILES = {
9348
+ nest: nest_default,
9349
+ node: node_default,
9350
+ react: react_default,
9351
+ svelte: svelte_default,
9352
+ tools: tools_default
9353
+ };
9354
+ var VARIANT_FILES = {
9355
+ "react-lib": react_lib_default
9356
+ };
9357
+ var LINT_PRESETS = Object.keys(PRESET_FILES);
9358
+ var GENERATED_LINT_PRESETS = [
9359
+ ...Object.keys(PRESET_FILES),
9360
+ ...Object.keys(VARIANT_FILES)
9361
+ ];
9362
+ function hasLintPreset(preset) {
9363
+ return preset in PRESET_FILES;
9364
+ }
9365
+ function presetFile(preset) {
9366
+ return VARIANT_FILES[preset] ?? PRESET_FILES[preset] ?? node_default;
9367
+ }
9368
+ function sharedRules() {
9369
+ return SHARED_RULES;
9370
+ }
9371
+ function presetRules(preset) {
9372
+ return presetFile(preset).rules;
9373
+ }
9374
+ function uncoveredFor(preset) {
9375
+ return Object.entries(presetFile(preset).uncovered ?? {}).map(([rule, reason]) => ({ rule, reason })).sort((left, right) => left.rule.localeCompare(right.rule));
9376
+ }
9377
+
9378
+ // src/roles/lint/rule-data.ts
9379
+ var sharedRules2 = sharedRules();
9380
+ function isDecision(value) {
9381
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9382
+ }
9383
+ function severityOf(value) {
9384
+ if (isDecision(value)) return value.severity;
9385
+ if (Array.isArray(value)) return String(value[0]);
9386
+ return value;
9387
+ }
9388
+ function validate(preset, rules) {
9389
+ for (const [rule, value] of Object.entries(rules)) {
9390
+ const severity = severityOf(value);
9391
+ if (!["off", "warn", "error"].includes(severity)) {
9392
+ throw new Error(
9393
+ `${preset}: rule "${rule}" has severity "${severity}"; expected off, warn or error`
9394
+ );
9395
+ }
9396
+ if (severity === "error") continue;
9397
+ const reason = isDecision(value) ? value.reason.trim() : "";
9398
+ if (reason === "") {
9399
+ throw new Error(
9400
+ `${preset}: rule "${rule}" is set to "${severity}" without a reason. Every rule that is not enforced must say why, so --inspect can tell a developer what is not running. Use { "severity": "${severity}", "reason": "..." }.`
9401
+ );
9402
+ }
9403
+ }
9404
+ }
9405
+ function rejectTwoSpellings(label, rules) {
9406
+ const seen = /* @__PURE__ */ new Map();
9407
+ for (const rule of Object.keys(rules)) {
9408
+ const canonical = canonicalRuleName(rule);
9409
+ const other = seen.get(canonical);
9410
+ if (other !== void 0) {
9411
+ throw new Error(
9412
+ `${label}: "${other}" and "${rule}" are the same rule to oxlint (${canonical}). Configure it once, under one name: oxlint keeps both and picks one by hash order.`
9413
+ );
9414
+ }
9415
+ seen.set(canonical, rule);
9416
+ }
9417
+ }
9418
+ function layered(preset) {
9419
+ const base = sharedRules();
9420
+ const layer = presetRules(preset);
9421
+ validate("base", base);
9422
+ validate(preset, layer);
9423
+ return { ...base, ...layer };
9424
+ }
9425
+ function toRuleMap(label, rules) {
9426
+ validate(label, rules);
9427
+ rejectTwoSpellings(label, rules);
9428
+ const entries = Object.entries(rules).map(([rule, value]) => {
9429
+ if (!isDecision(value)) return [rule, value];
9430
+ const options = value.options ?? [];
9431
+ return [rule, options.length > 0 ? [value.severity, ...options] : value.severity];
9432
+ });
9433
+ return Object.fromEntries(entries);
9434
+ }
9435
+ function rulesFor(preset) {
9436
+ return toRuleMap(preset, layered(preset));
9437
+ }
9438
+ function disabledFor(preset) {
9439
+ return decisionsAt(preset, "off");
9440
+ }
9441
+ function downgradedFor(preset) {
9442
+ return decisionsAt(preset, "warn");
9443
+ }
9444
+ function decisionsAt(preset, severity) {
9445
+ return Object.entries(layered(preset)).filter(([, value]) => isDecision(value) && value.severity === severity).map(([rule, value]) => ({ rule, reason: value.reason })).sort((left, right) => left.rule.localeCompare(right.rule));
9446
+ }
9447
+
9448
+ // src/roles/lint/presets.ts
9449
+ var hasRuleData = (name) => GENERATED_LINT_PRESETS.includes(name);
9450
+ function lintPresetFor(preset) {
9451
+ const file = presetFile(preset);
9452
+ const resolved = hasRuleData(preset) ? preset : "node";
9453
+ return {
9454
+ plugins: file.plugins,
9455
+ jsPlugins: file.jsPlugins,
9456
+ ...file.settings === null ? {} : { settings: file.settings },
9457
+ ...file.renames ? { renames: file.renames } : {},
9458
+ ...file.overrides ? {
9459
+ overrides: file.overrides.map((override, index) => ({
9460
+ files: override.files,
9461
+ rules: toRuleMap(`${preset} override ${index}`, override.rules)
9462
+ }))
9463
+ } : {},
9464
+ rules: rulesFor(resolved)
9465
+ };
9466
+ }
9467
+
9468
+ // src/roles/lint/read-adoption.ts
9469
+ function shippedPreset(presetPath2) {
9470
+ const variant = presetNameFromPath(presetPath2);
9471
+ return variant !== void 0 && GENERATED_LINT_PRESETS.includes(variant) ? lintPresetFor(variant) : void 0;
9472
+ }
9473
+ function readLintAdoption(cwd) {
9474
+ return readAdoption(cwd, {
9475
+ candidates: [LINT_CONFIG_FILE],
9476
+ markers: ["@hublo/sentinel/oxlint/"],
9477
+ fallback: LINT_CONFIG_FILE,
9478
+ presetPattern: SENTINEL_LINT_PRESET,
9479
+ // Drift for lint is read from the TOP-LEVEL keys beyond `extends`, so the settings the
9480
+ // shared reader inspects are the config's own root rather than a nested options object.
9481
+ settingsKey: null,
9482
+ permitted: PERMITTED_LOCAL_KEYS2,
9483
+ // `rules` and `overrides` are drift only when they CONFLICT with the preset, by the
9484
+ // criterion a re-init keeps or drops them by: a rule the preset does not name is the
9485
+ // module's addition, kept and listed by `--inspect`, not drift. A preset sentinel does not
9486
+ // ship cannot be compared with, so its module keeps the allowlist answer.
9487
+ refineDrift: (drift, config, presetPath2) => {
9488
+ const preset = shippedPreset(presetPath2);
9489
+ return preset !== void 0 ? refineLintDrift(drift, config, preset) : [...drift];
9490
+ }
9491
+ });
9492
+ }
9493
+ var NO_ADDITIONS = { rules: [], config: [] };
9494
+ function readModuleAdditions(cwd) {
9495
+ const adoption = readLintAdoption(cwd);
9496
+ const preset = adoption.adopted ? shippedPreset(adoption.preset) : void 0;
9497
+ if (preset === void 0 || adoption.configFile === null) return NO_ADDITIONS;
9498
+ try {
9499
+ const config = parseJsonc(
9500
+ readFileSync25(join29(cwd, adoption.configFile), "utf8"),
9501
+ adoption.configFile
9502
+ );
9503
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
9504
+ return NO_ADDITIONS;
9505
+ }
9506
+ const committed = config;
9507
+ return {
9508
+ rules: moduleRuleStanding(committed, preset).added,
9509
+ config: moduleOwnBlocks(committed, preset).added
9510
+ };
9511
+ } catch {
9512
+ return NO_ADDITIONS;
9513
+ }
6815
9514
  }
6816
9515
 
6817
9516
  // src/roles/lint/resolve-oxlint.ts
6818
9517
  import { existsSync as existsSync23 } from "node:fs";
6819
9518
  import { createRequire as createRequire6 } from "node:module";
6820
- import { delimiter as delimiter2, dirname as dirname13, join as join29 } from "node:path";
9519
+ import { delimiter as delimiter2, dirname as dirname13, join as join30 } from "node:path";
6821
9520
  import { fileURLToPath } from "node:url";
6822
9521
  var PACKAGE_OF = {
6823
9522
  oxlint: "oxlint",
@@ -6842,14 +9541,14 @@ function tsgolintShim(cwd) {
6842
9541
  for (const owner of ["oxlint-tsgolint", "oxlint"]) {
6843
9542
  try {
6844
9543
  const packageDir = dirname13(require3.resolve(`${owner}/package.json`));
6845
- candidates.push(join29(packageDir, "node_modules", ".bin", "tsgolint"));
6846
- candidates.push(join29(packageDir, "..", ".bin", "tsgolint"));
9544
+ candidates.push(join30(packageDir, "node_modules", ".bin", "tsgolint"));
9545
+ candidates.push(join30(packageDir, "..", ".bin", "tsgolint"));
6847
9546
  } catch {
6848
9547
  }
6849
9548
  }
6850
9549
  try {
6851
9550
  const ownRoot = dirname13(require3.resolve("@hublo/sentinel/package.json"));
6852
- candidates.push(join29(ownRoot, "node_modules", ".bin", "tsgolint"));
9551
+ candidates.push(join30(ownRoot, "node_modules", ".bin", "tsgolint"));
6853
9552
  } catch {
6854
9553
  candidates.push(resolveBin(dirname13(fileURLToPath(import.meta.url)), "tsgolint") ?? "");
6855
9554
  }
@@ -6867,7 +9566,7 @@ function writtenFileLinter(cwd) {
6867
9566
  return { bin: oxlint, args: ["--fix"], origin: "this module's own oxlint config" };
6868
9567
  }
6869
9568
  }
6870
- const config = ESLINT_CONFIG_FILES.map((name) => join30(cwd, name)).find((path) => existsSync24(path));
9569
+ const config = ESLINT_CONFIG_FILES.map((name) => join31(cwd, name)).find((path) => existsSync24(path));
6871
9570
  const eslint = resolveBin(cwd, "eslint");
6872
9571
  if (config !== void 0 && eslint !== void 0) {
6873
9572
  return {
@@ -6889,7 +9588,7 @@ var FORMATTABLE = /\.(m|c)?(j|t)sx?$|\.json$/;
6889
9588
  var FORMAT_BATCH = 25;
6890
9589
  function readIfPossible(path) {
6891
9590
  try {
6892
- return readFileSync25(path, "utf8");
9591
+ return readFileSync26(path, "utf8");
6893
9592
  } catch {
6894
9593
  return void 0;
6895
9594
  }
@@ -7015,12 +9714,12 @@ var VitestAdapter = class _VitestAdapter extends BaseAdapter {
7015
9714
  const linter = writtenFileLinter(ctx.cwd);
7016
9715
  if (linter === void 0) return;
7017
9716
  const workspaceRoot = findWorkspaceRoot(ctx.cwd) ?? ctx.cwd;
7018
- const fromRoot = (file) => relative7(workspaceRoot, join31(ctx.cwd, file));
9717
+ const fromRoot = (file) => relative7(workspaceRoot, join32(ctx.cwd, file));
7019
9718
  let unsettled = 0;
7020
9719
  const reverted = [];
7021
9720
  for (let at = 0; at < written.length; at += FORMAT_BATCH) {
7022
9721
  const batch = written.slice(at, at + FORMAT_BATCH);
7023
- const before = new Map(batch.map((file) => [file, readIfPossible(join31(ctx.cwd, file))]));
9722
+ const before = new Map(batch.map((file) => [file, readIfPossible(join32(ctx.cwd, file))]));
7024
9723
  const pass = spawnSync(linter.bin, [...linter.args, ...batch.map(fromRoot)], {
7025
9724
  cwd: workspaceRoot,
7026
9725
  encoding: "utf8"
@@ -7029,11 +9728,11 @@ var VitestAdapter = class _VitestAdapter extends BaseAdapter {
7029
9728
  for (const file of batch) {
7030
9729
  const was = before.get(file);
7031
9730
  if (was === void 0) continue;
7032
- const now = readIfPossible(join31(ctx.cwd, file));
9731
+ const now = readIfPossible(join32(ctx.cwd, file));
7033
9732
  if (now === void 0 || now === was) continue;
7034
9733
  const lost = lineLostByTheFixer(was, now);
7035
9734
  if (lost === void 0) continue;
7036
- writeFileSync2(join31(ctx.cwd, file), was);
9735
+ writeFileSync2(join32(ctx.cwd, file), was);
7037
9736
  reverted.push(`${file}: \`${lost.slice(0, 80)}\``);
7038
9737
  }
7039
9738
  }
@@ -7112,7 +9811,7 @@ var VitestAdapter = class _VitestAdapter extends BaseAdapter {
7112
9811
  metrics: { ..._VitestAdapter.NOTHING_TO_REPORT, error: "vitest not available" }
7113
9812
  };
7114
9813
  }
7115
- const out = join31(mkdtempSync3(join31(tmpdir3(), "sentinel-test-report-")), "vitest.json");
9814
+ const out = join32(mkdtempSync3(join32(tmpdir3(), "sentinel-test-report-")), "vitest.json");
7116
9815
  const { cwd: runFrom, options, env } = fromWorkspaceRoot(ctx.cwd, [], []);
7117
9816
  const result = spawnSync(bin, ["run", ...options, "--reporter=json", `--outputFile=${out}`], {
7118
9817
  cwd: runFrom,
@@ -7136,7 +9835,7 @@ var VitestAdapter = class _VitestAdapter extends BaseAdapter {
7136
9835
  }
7137
9836
  let parsed;
7138
9837
  try {
7139
- parsed = JSON.parse(readFileSync25(out, "utf8"));
9838
+ parsed = JSON.parse(readFileSync26(out, "utf8"));
7140
9839
  } catch {
7141
9840
  return {
7142
9841
  ok: false,
@@ -7298,11 +9997,21 @@ export {
7298
9997
  presetNameFromPath,
7299
9998
  ESLINT_CONFIG_FILES,
7300
9999
  lintTarget,
10000
+ LINT_PRESETS,
10001
+ hasLintPreset,
10002
+ uncoveredFor,
10003
+ disabledFor,
10004
+ downgradedFor,
10005
+ governedRules,
10006
+ moduleOwnBlocks,
10007
+ isOffRuleValue,
10008
+ lintPresetFor,
7301
10009
  presetOwnedKeys,
7302
10010
  localOnlyKeys,
7303
10011
  resolveConfigTarget,
7304
10012
  readAdoption,
7305
10013
  readLintAdoption,
10014
+ readModuleAdditions,
7306
10015
  resolveOxlint,
7307
10016
  canRunTypeAware,
7308
10017
  oxlintPath,