@hublo/sentinel 1.1.5 → 1.1.7

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.
@@ -9,6 +9,7 @@ import {
9
9
  dispatch,
10
10
  ensureWorkspacePrep,
11
11
  findWorkspaceRoot,
12
+ inspectWorkspacePrep,
12
13
  palette,
13
14
  readNxProjectName,
14
15
  readOwnVersion,
@@ -16,7 +17,7 @@ import {
16
17
  registerAdapters,
17
18
  resolve,
18
19
  resolveBin
19
- } from "../chunk-RLVLDC5H.js";
20
+ } from "../chunk-W73ISYMG.js";
20
21
 
21
22
  // bin/sentinel.ts
22
23
  import { program } from "commander";
@@ -438,6 +439,10 @@ function renderStatusSummary(items, p) {
438
439
  }
439
440
 
440
441
  // src/cli/run-verb.ts
442
+ function workspacePrep(cwd2) {
443
+ const root = findWorkspaceRoot(cwd2);
444
+ return root ? inspectWorkspacePrep(root) : [];
445
+ }
441
446
  async function runVerb(ctx) {
442
447
  const { modules, scope } = resolveContext(ctx.cwd, { module: ctx.module, ci: ctx.ci });
443
448
  const out = palette(process.stdout);
@@ -482,7 +487,15 @@ async function runVerb(ctx) {
482
487
  });
483
488
  const summary = generateSummaries(results, ctx.targets);
484
489
  if (ctx.json) {
485
- const base = ctx.verb === "status" || ctx.verb === "inspect" ? { ...summary, coverage: statusCoverage(summary.results) } : summary;
490
+ const base = ctx.verb === "status" || ctx.verb === "inspect" ? {
491
+ ...summary,
492
+ coverage: statusCoverage(summary.results),
493
+ // The root-level changes sentinel maintains, each with its justification checked
494
+ // NOW rather than described in a comment. They are the only thing sentinel does
495
+ // that nobody can see afterwards, and every one of them is temporary. See
496
+ // `inspectWorkspacePrep`.
497
+ ...ctx.verb === "inspect" ? { workspace: workspacePrep(ctx.cwd) } : {}
498
+ } : summary;
486
499
  const payload = ctx.toolArgs.length > 0 ? { ...base, toolArgs: ctx.toolArgs } : base;
487
500
  process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
488
501
  } else if (ctx.verb === "status" || ctx.verb === "inspect" && modules.length > 1) {
@@ -493,6 +506,19 @@ async function runVerb(ctx) {
493
506
  process.stdout.write(renderSummary(item, out) + "\n");
494
507
  }
495
508
  }
509
+ if (!ctx.json && ctx.verb === "inspect") {
510
+ const prep = workspacePrep(ctx.cwd);
511
+ if (prep.length > 0) {
512
+ process.stdout.write(`
513
+ sentinel maintains these at the workspace root:
514
+ `);
515
+ for (const entry of prep) {
516
+ process.stdout.write(` ${out.strong(entry.rule)}
517
+ ${entry.reason}
518
+ `);
519
+ }
520
+ }
521
+ }
496
522
  process.stderr.write(
497
523
  err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms
498
524
  `)
@@ -110,20 +110,6 @@ function readNxProjectName(dir) {
110
110
  }
111
111
  }
112
112
 
113
- // src/shared/color.ts
114
- import { styleText } from "util";
115
- function palette(stream) {
116
- const paint = (format, text) => styleText(format, text, { stream });
117
- return {
118
- ok: (text) => paint(["green", "bold"], text),
119
- fail: (text) => paint(["red", "bold"], text),
120
- warn: (text) => paint("yellow", text),
121
- strong: (text) => paint("bold", text),
122
- dim: (text) => paint("dim", text),
123
- accent: (text) => paint("cyan", text)
124
- };
125
- }
126
-
127
113
  // src/core/domain.ts
128
114
  var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
129
115
  var TARGETS = [
@@ -138,6 +124,20 @@ var TARGETS = [
138
124
  ];
139
125
  var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
140
126
 
127
+ // src/shared/color.ts
128
+ import { styleText } from "util";
129
+ function palette(stream) {
130
+ const paint = (format, text) => styleText(format, text, { stream });
131
+ return {
132
+ ok: (text) => paint(["green", "bold"], text),
133
+ fail: (text) => paint(["red", "bold"], text),
134
+ warn: (text) => paint("yellow", text),
135
+ strong: (text) => paint("bold", text),
136
+ dim: (text) => paint("dim", text),
137
+ accent: (text) => paint("cyan", text)
138
+ };
139
+ }
140
+
141
141
  // src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
142
142
  import { spawnSync } from "child_process";
143
143
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
@@ -458,6 +458,40 @@ function ensureWorkspacePrep(opts) {
458
458
  opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
459
459
  ].filter((message) => message !== void 0);
460
460
  }
461
+ function inspectWorkspacePrep(root) {
462
+ const entries = [];
463
+ let pkg = {};
464
+ const pkgPath = join6(root, "package.json");
465
+ if (existsSync5(pkgPath)) {
466
+ try {
467
+ pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
468
+ } catch {
469
+ pkg = {};
470
+ }
471
+ }
472
+ const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
473
+ if (pinned !== void 0) {
474
+ const stillForks = declaredNativeTs(pkg);
475
+ entries.push({
476
+ rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
477
+ reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
478
+ });
479
+ }
480
+ const own = readOwnPackage().name;
481
+ const yamlPath = join6(root, WORKSPACE_YAML);
482
+ if (existsSync5(yamlPath)) {
483
+ const yaml = readFileSync4(yamlPath, "utf8");
484
+ const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
485
+ if (listed) {
486
+ const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
487
+ entries.push({
488
+ rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
489
+ reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
490
+ });
491
+ }
492
+ }
493
+ return entries;
494
+ }
461
495
 
462
496
  // src/shared/resolve-bin.ts
463
497
  import { existsSync as existsSync6 } from "fs";
@@ -514,6 +548,7 @@ var PERMITTED_LOCAL_KEYS = [
514
548
  "experimentalOperatorPosition"
515
549
  ];
516
550
  var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
551
+ var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
517
552
  var PRETTIER_CONFIG_FILES = [
518
553
  ".prettierrc",
519
554
  ".prettierrc.json",
@@ -906,6 +941,22 @@ var OxfmtAdapter = class extends BaseAdapter {
906
941
  appliesTo(_preset) {
907
942
  return true;
908
943
  }
944
+ /**
945
+ * The preset this module COMMITTED to, read from the `$sentinel` block it writes.
946
+ *
947
+ * Every other role implements this and format did not, so it was the only one answering from
948
+ * dependency detection alone. On an adopted Nest module whose own `.oxfmtrc.json` records
949
+ * `"preset": "nest"`, `--inspect` reported `flavour=node` — the module knew, and the role did
950
+ * not pass it on. Same command, four rows, two different answers about what the module is.
951
+ *
952
+ * It reads the RECORD rather than re-deriving: the block is what `--init` wrote, so it is the
953
+ * module's declaration, and a re-derivation from hoisted dependencies is exactly the wrong
954
+ * answer this exists to avoid.
955
+ */
956
+ declaredPreset(cwd) {
957
+ const declared = readFormatAdoption(cwd).preset;
958
+ return declared !== null && PRESET_NAMES.includes(declared) ? declared : void 0;
959
+ }
909
960
  plan(context) {
910
961
  if (!hasSourceFiles(context.cwd, FORMATTABLE_EXTENSIONS)) {
911
962
  return {
@@ -926,7 +977,9 @@ var OxfmtAdapter = class extends BaseAdapter {
926
977
  const localKeys = Object.keys(local).filter((key) => !ALWAYS_LOCAL_KEYS.includes(key)).sort();
927
978
  const inherited = inheritedIgnorePatterns(findWorkspaceRoot(context.cwd));
928
979
  const declaredIgnores = Array.isArray(local.ignorePatterns) ? local.ignorePatterns.filter((v) => typeof v === "string") : [];
929
- const ignorePatterns = [.../* @__PURE__ */ new Set([...declaredIgnores, ...inherited])].sort();
980
+ const ignorePatterns = [
981
+ .../* @__PURE__ */ new Set([...declaredIgnores, ...inherited, ...FORMAT_DEFAULT_IGNORES])
982
+ ].sort();
930
983
  if (ignorePatterns.length > 0) local.ignorePatterns = ignorePatterns;
931
984
  const unchanged = existing.adopted && existing.conformant;
932
985
  const version = unchanged && existing.presetVersion ? existing.presetVersion : own.version;
@@ -1264,8 +1317,8 @@ function registerFormat() {
1264
1317
 
1265
1318
  // src/roles/lint/adapters/oxlint/oxlint.adapter.ts
1266
1319
  import { spawnSync as spawnSync2 } from "child_process";
1267
- import { existsSync as existsSync16, readFileSync as readFileSync15, rmSync, writeFileSync as writeFileSync2 } from "fs";
1268
- import { join as join19 } from "path";
1320
+ import { existsSync as existsSync17, readFileSync as readFileSync16, rmSync, writeFileSync as writeFileSync2 } from "fs";
1321
+ import { join as join20 } from "path";
1269
1322
 
1270
1323
  // src/core/config/deferred-rules.ts
1271
1324
  function deferredRuleNames(rules) {
@@ -1279,6 +1332,14 @@ var PRESET_DIR = "./node_modules/@hublo/sentinel/oxlint";
1279
1332
  function presetPath(preset) {
1280
1333
  return `${PRESET_DIR}/${preset}.json`;
1281
1334
  }
1335
+ function isSentinelPreset(entry) {
1336
+ return entry.includes("@hublo/sentinel/oxlint/");
1337
+ }
1338
+ function extendsWithPreset(current, variant) {
1339
+ const own = presetPath(variant);
1340
+ const others = current.filter((entry) => entry !== own && !isSentinelPreset(entry));
1341
+ return [own, ...new Set(others)];
1342
+ }
1282
1343
  function presetVariant(preset, cwd) {
1283
1344
  const normalised = cwd.replaceAll("\\", "/");
1284
1345
  return preset === "react" && /(^|\/)libs\//.test(normalised) ? "react-lib" : preset;
@@ -1298,7 +1359,7 @@ var ESLINT_CONFIG_FILES = [
1298
1359
  "eslint.config.ts"
1299
1360
  ];
1300
1361
  function lintTarget() {
1301
- return { cache: true, inputs: ["default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
1362
+ return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
1302
1363
  }
1303
1364
 
1304
1365
  // src/roles/lint/presets/base.json
@@ -1642,8 +1703,8 @@ var nest_default = {
1642
1703
  },
1643
1704
  "typescript/return-await": {
1644
1705
  severity: "warn",
1645
- 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. The `never` option matches what no-return-await meant, so this is like-for-like, but it now runs over every file and finds real violations (9 on bff-admin, 5 on host-admin, measured with --type-aware). Held at warn so adoption does not turn a green module red, and raised to error once they are fixed. Switching to the stricter `in-try-catch`, which typescript-eslint recommends because `return await` inside try IS needed for correct error handling, is a separate decision with its own count.",
1646
- options: ["never"]
1706
+ 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.",
1707
+ options: ["in-try-catch"]
1647
1708
  },
1648
1709
  "typescript/switch-exhaustiveness-check": [
1649
1710
  "error",
@@ -2034,8 +2095,8 @@ var react_lib_default = {
2034
2095
  },
2035
2096
  "typescript/return-await": {
2036
2097
  severity: "warn",
2037
- reason: "replaces the deprecated core `no-return-await`. The `never` option is what makes it mean the same thing. Held at warn to match the app tier, where running it over every file found real violations the old rule never reported.",
2038
- options: ["never"]
2098
+ 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.",
2099
+ options: ["in-try-catch"]
2039
2100
  },
2040
2101
  "use-isnan": [
2041
2102
  "error",
@@ -2608,8 +2669,8 @@ var react_default = {
2608
2669
  "typescript/restrict-template-expressions": "error",
2609
2670
  "typescript/return-await": {
2610
2671
  severity: "warn",
2611
- 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. The `never` option matches what no-return-await meant, so this is like-for-like, but it now runs over every file and finds real violations (9 on bff-admin, 5 on host-admin, measured with --type-aware). Held at warn so adoption does not turn a green module red, and raised to error once they are fixed. Switching to the stricter `in-try-catch`, which typescript-eslint recommends because `return await` inside try IS needed for correct error handling, is a separate decision with its own count.",
2612
- options: ["never"]
2672
+ 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.",
2673
+ options: ["in-try-catch"]
2613
2674
  },
2614
2675
  "typescript/switch-exhaustiveness-check": {
2615
2676
  severity: "warn",
@@ -3070,6 +3131,10 @@ function downgradedRulesFor(preset) {
3070
3131
  return downgradedFor(preset);
3071
3132
  }
3072
3133
 
3134
+ // src/roles/lint/extra-layers.ts
3135
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
3136
+ import { isAbsolute as isAbsolute2, join as join13, resolve as resolve3 } from "path";
3137
+
3073
3138
  // src/roles/lint/module-baseline.ts
3074
3139
  var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
3075
3140
  var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
@@ -3078,10 +3143,7 @@ function holdsFrom(errorCounts) {
3078
3143
  return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
3079
3144
  }
3080
3145
  function renderBaseline(holds, version) {
3081
- const entries = holds.map(
3082
- ({ rule, count }) => ` // ${count} violation${count === 1 ? "" : "s"} when this module adopted
3083
- ${JSON.stringify(rule)}: "warn"`
3084
- ).join(",\n");
3146
+ const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
3085
3147
  return [
3086
3148
  "{",
3087
3149
  ` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
@@ -3089,9 +3151,15 @@ function renderBaseline(holds, version) {
3089
3151
  " // and returns to `error` on its own.",
3090
3152
  " //",
3091
3153
  " // These rules are held at `warn` because this module ALREADY violated them when it",
3092
- " // adopted. They still run and still report; they just cannot fail the build for code",
3093
- " // that was there before the migration. Fix them and re-run `--init --lint` to get the",
3094
- " // preset severity back.",
3154
+ " // adopted. They still run and still report, so the build cannot fail on them.",
3155
+ " //",
3156
+ " // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
3157
+ " // violations, so a NEW violation of one of these rules is covered too, and will only",
3158
+ " // warn. That is the cost of adopting without a red build, and the reason to clear this",
3159
+ " // file rather than live with it.",
3160
+ " //",
3161
+ " // `sentinel --inspect --lint` counts what is left. Fix them and re-run `--init --lint`",
3162
+ " // to get the preset severity back.",
3095
3163
  ' "rules": {',
3096
3164
  entries,
3097
3165
  " }",
@@ -3111,6 +3179,44 @@ function describeHolds(holds) {
3111
3179
  return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
3112
3180
  }
3113
3181
 
3182
+ // src/roles/lint/extra-layers.ts
3183
+ function isSentinelPreset2(entry) {
3184
+ return entry.includes("@hublo/sentinel/oxlint/");
3185
+ }
3186
+ function ruleCount(cwd, specifier) {
3187
+ const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
3188
+ if (!existsSync11(path)) return void 0;
3189
+ try {
3190
+ const parsed = parseJsonc(
3191
+ readFileSync10(path, "utf8"),
3192
+ specifier
3193
+ );
3194
+ return Object.keys(parsed.rules ?? {}).length;
3195
+ } catch {
3196
+ return void 0;
3197
+ }
3198
+ }
3199
+ function extraLayers(cwd, extendsList) {
3200
+ return extendsList.filter((entry) => !isSentinelPreset2(entry) && entry !== LINT_BASELINE_SPECIFIER).map((entry) => {
3201
+ const count = ruleCount(cwd, entry);
3202
+ return {
3203
+ rule: entry,
3204
+ reason: count === void 0 ? "this module extends it, but it could not be read from here" : `${count} rule(s) on top of the preset, applied after it`
3205
+ };
3206
+ });
3207
+ }
3208
+ function committedExtendsList(cwd, configFile) {
3209
+ try {
3210
+ const parsed = parseJsonc(
3211
+ readFileSync10(join13(cwd, configFile), "utf8"),
3212
+ configFile
3213
+ );
3214
+ return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
3215
+ } catch {
3216
+ return [];
3217
+ }
3218
+ }
3219
+
3114
3220
  // src/roles/lint/parse-diagnostics.ts
3115
3221
  function ruleId(code) {
3116
3222
  const inner = /\(([^)]+)\)/.exec(code);
@@ -3161,8 +3267,8 @@ function errorCountsByConfigRule(stdout) {
3161
3267
  }
3162
3268
 
3163
3269
  // src/core/config/read-adoption.ts
3164
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3165
- import { join as join14 } from "path";
3270
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
3271
+ import { join as join15 } from "path";
3166
3272
 
3167
3273
  // src/core/config/owned-keys.ts
3168
3274
  function presetOwnedKeys(config, permitted, presetSets) {
@@ -3177,12 +3283,12 @@ function localOnlyKeys(config, permitted, presetSets) {
3177
3283
  }
3178
3284
 
3179
3285
  // src/core/config/resolve-config-target.ts
3180
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
3181
- import { join as join13 } from "path";
3286
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3287
+ import { join as join14 } from "path";
3182
3288
  function readExtends(absolutePath) {
3183
3289
  let parsed;
3184
3290
  try {
3185
- parsed = parseJsonc(readFileSync10(absolutePath, "utf8"), absolutePath);
3291
+ parsed = parseJsonc(readFileSync11(absolutePath, "utf8"), absolutePath);
3186
3292
  } catch {
3187
3293
  return [];
3188
3294
  }
@@ -3196,8 +3302,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
3196
3302
  let existing;
3197
3303
  let existingExtendsSomething = false;
3198
3304
  for (const candidate of candidates) {
3199
- const absolutePath = join13(moduleDir, candidate);
3200
- if (!existsSync11(absolutePath)) continue;
3305
+ const absolutePath = join14(moduleDir, candidate);
3306
+ if (!existsSync12(absolutePath)) continue;
3201
3307
  const chain = readExtends(absolutePath);
3202
3308
  if (existing === void 0) {
3203
3309
  existing = candidate;
@@ -3230,12 +3336,12 @@ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
3230
3336
  });
3231
3337
  function readAdoption(cwd, options) {
3232
3338
  const target = resolveConfigTarget(cwd, options);
3233
- if (target.reason === "none" || !existsSync12(join14(cwd, target.path))) {
3339
+ if (target.reason === "none" || !existsSync13(join15(cwd, target.path))) {
3234
3340
  return NOT_ADOPTED2(target.reason === "none" ? null : target.path);
3235
3341
  }
3236
3342
  let parsed;
3237
3343
  try {
3238
- parsed = parseJsonc(readFileSync11(join14(cwd, target.path), "utf8"), target.path);
3344
+ parsed = parseJsonc(readFileSync12(join15(cwd, target.path), "utf8"), target.path);
3239
3345
  } catch (error) {
3240
3346
  const reason = error instanceof Error ? error.message : String(error);
3241
3347
  return NOT_ADOPTED2(target.path, `${target.path} could not be parsed (${reason})`);
@@ -3269,9 +3375,9 @@ function readLintAdoption(cwd) {
3269
3375
  }
3270
3376
 
3271
3377
  // src/roles/lint/resolve-oxlint.ts
3272
- import { existsSync as existsSync13 } from "fs";
3378
+ import { existsSync as existsSync14 } from "fs";
3273
3379
  import { createRequire as createRequire3 } from "module";
3274
- import { delimiter as delimiter2, dirname as dirname5, join as join15 } from "path";
3380
+ import { delimiter as delimiter2, dirname as dirname5, join as join16 } from "path";
3275
3381
  import { fileURLToPath as fileURLToPath2 } from "url";
3276
3382
  var PACKAGE_OF = {
3277
3383
  oxlint: "oxlint",
@@ -3296,30 +3402,30 @@ function tsgolintShim(cwd) {
3296
3402
  for (const owner of ["oxlint-tsgolint", "oxlint"]) {
3297
3403
  try {
3298
3404
  const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
3299
- candidates.push(join15(packageDir, "node_modules", ".bin", "tsgolint"));
3300
- candidates.push(join15(packageDir, "..", ".bin", "tsgolint"));
3405
+ candidates.push(join16(packageDir, "node_modules", ".bin", "tsgolint"));
3406
+ candidates.push(join16(packageDir, "..", ".bin", "tsgolint"));
3301
3407
  } catch {
3302
3408
  }
3303
3409
  }
3304
3410
  try {
3305
3411
  const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
3306
- candidates.push(join15(ownRoot, "node_modules", ".bin", "tsgolint"));
3412
+ candidates.push(join16(ownRoot, "node_modules", ".bin", "tsgolint"));
3307
3413
  } catch {
3308
3414
  candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
3309
3415
  }
3310
- return candidates.find((candidate) => candidate !== "" && existsSync13(candidate));
3416
+ return candidates.find((candidate) => candidate !== "" && existsSync14(candidate));
3311
3417
  }
3312
3418
  function oxlintSearchPath(cwd) {
3313
3419
  return binSearchPath(cwd);
3314
3420
  }
3315
3421
 
3316
3422
  // src/roles/lint/adapters/oxlint/plan.ts
3317
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
3318
- import { join as join18 } from "path";
3423
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
3424
+ import { join as join19 } from "path";
3319
3425
 
3320
3426
  // src/roles/lint/eslint-ignores.ts
3321
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
3322
- import { join as join16 } from "path";
3427
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
3428
+ import { join as join17 } from "path";
3323
3429
  var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
3324
3430
  var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
3325
3431
  var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
@@ -3332,11 +3438,11 @@ function readRootEslintIgnores(root) {
3332
3438
  return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
3333
3439
  }
3334
3440
  function readEslintIgnores(cwd) {
3335
- const config = ESLINT_CONFIG_FILES.map((name) => join16(cwd, name)).find((path) => existsSync14(path));
3441
+ const config = ESLINT_CONFIG_FILES.map((name) => join17(cwd, name)).find((path) => existsSync15(path));
3336
3442
  if (!config) return { patterns: [], unresolved: [] };
3337
3443
  let source;
3338
3444
  try {
3339
- source = readFileSync12(config, "utf8");
3445
+ source = readFileSync13(config, "utf8");
3340
3446
  } catch {
3341
3447
  return { patterns: [], unresolved: [] };
3342
3448
  }
@@ -3390,8 +3496,8 @@ function lintPresetFor(preset) {
3390
3496
  }
3391
3497
 
3392
3498
  // src/roles/lint/rename-suppressions.ts
3393
- import { readdirSync as readdirSync2, readFileSync as readFileSync13, statSync } from "fs";
3394
- import { join as join17, relative as relative2 } from "path";
3499
+ import { readdirSync as readdirSync2, readFileSync as readFileSync14, statSync } from "fs";
3500
+ import { join as join18, relative as relative2 } from "path";
3395
3501
  var SOURCE_EXTENSIONS = [
3396
3502
  ".ts",
3397
3503
  ".tsx",
@@ -3438,7 +3544,7 @@ function* sourceFiles(dir) {
3438
3544
  return;
3439
3545
  }
3440
3546
  for (const entry of entries) {
3441
- const full = join17(dir, entry);
3547
+ const full = join18(dir, entry);
3442
3548
  let isDirectory;
3443
3549
  try {
3444
3550
  isDirectory = statSync(full).isDirectory();
@@ -3458,7 +3564,7 @@ function findSuppressionRenames(cwd, renames) {
3458
3564
  for (const file of sourceFiles(cwd)) {
3459
3565
  let content;
3460
3566
  try {
3461
- content = readFileSync13(file, "utf8");
3567
+ content = readFileSync14(file, "utf8");
3462
3568
  } catch {
3463
3569
  continue;
3464
3570
  }
@@ -3498,7 +3604,17 @@ var DEFAULT_IGNORE_PATTERNS = [
3498
3604
  "**/dist/**",
3499
3605
  "**/node_modules/**",
3500
3606
  "**/coverage/**",
3501
- "**/*.mock.ts",
3607
+ // `**/*.mock.ts` used to sit here and has been removed. It was the only entry in this list
3608
+ // with no reason written next to it, and it does not meet the bar the paragraph above sets:
3609
+ // a mock is not build output, not vendored and not generated. It is hand-written TypeScript
3610
+ // that ships with the tests, and lint rules have as much to say about it as about any other
3611
+ // source file. Ignoring it quietly narrowed what an adopted module checks — career lost
3612
+ // `src/test/router.mock.ts`, which its previous ESLint config did lint.
3613
+ //
3614
+ // Measured before removing, because this is the one change that can turn a green module red:
3615
+ // career has 1 such file and it reports nothing; the 15 planning modules have none. 222 exist
3616
+ // repo-wide, and modules adopting later meet them through the baseline, which is exactly what
3617
+ // the baseline is for.
3502
3618
  // Tool configuration is not source. A repo's shared ESLint config ignores these, and every
3503
3619
  // module inherits that; adoption replaces the config chain, so without them the first thing
3504
3620
  // an adopted module lints is its own jest setup file. Found on libs/front/components, where
@@ -3561,7 +3677,7 @@ function plan(context) {
3561
3677
  ].filter((pattern) => !defaults.has(bare(pattern)));
3562
3678
  const carried = [.../* @__PURE__ */ new Set([...DEFAULT_IGNORE_PATTERNS, ...moduleOwn])];
3563
3679
  const stub = {
3564
- extends: [presetPath(variant)],
3680
+ extends: extendsWithPreset(committedExtends(context.cwd), variant),
3565
3681
  ...carried.length > 0 ? { ignorePatterns: carried } : {}
3566
3682
  };
3567
3683
  const operations = [
@@ -3576,7 +3692,7 @@ function plan(context) {
3576
3692
  keys: removableDeps
3577
3693
  });
3578
3694
  }
3579
- const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync15(join18(context.cwd, name)));
3695
+ const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync16(join19(context.cwd, name)));
3580
3696
  for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
3581
3697
  operations.push(
3582
3698
  ...nxTargetOperations({
@@ -3671,10 +3787,21 @@ function lintScripts(cwd) {
3671
3787
  }
3672
3788
  return rewritten;
3673
3789
  }
3790
+ function committedExtends(cwd) {
3791
+ try {
3792
+ const parsed = parseJsonc(
3793
+ readFileSync15(join19(cwd, LINT_CONFIG_FILE), "utf8"),
3794
+ LINT_CONFIG_FILE
3795
+ );
3796
+ return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
3797
+ } catch {
3798
+ return [];
3799
+ }
3800
+ }
3674
3801
  function committedIgnorePatterns(cwd) {
3675
3802
  try {
3676
3803
  const parsed = parseJsonc(
3677
- readFileSync14(join18(cwd, LINT_CONFIG_FILE), "utf8"),
3804
+ readFileSync15(join19(cwd, LINT_CONFIG_FILE), "utf8"),
3678
3805
  LINT_CONFIG_FILE
3679
3806
  );
3680
3807
  return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
@@ -3686,7 +3813,7 @@ function moduleEslintDependencies(cwd) {
3686
3813
  const isEslintPackage = (name) => name === "eslint" || name === "@types/eslint" || name === "typescript-eslint" || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-plugin-") || name.startsWith("eslint-config-") || name.startsWith("@eslint/");
3687
3814
  let manifest;
3688
3815
  try {
3689
- manifest = JSON.parse(readFileSync14(join18(cwd, "package.json"), "utf8"));
3816
+ manifest = JSON.parse(readFileSync15(join19(cwd, "package.json"), "utf8"));
3690
3817
  } catch {
3691
3818
  return [];
3692
3819
  }
@@ -3805,20 +3932,20 @@ var OxlintAdapter = class extends BaseAdapter {
3805
3932
  * build the developer can see, rather than a silent half-adoption they cannot.
3806
3933
  */
3807
3934
  writeModuleBaseline(ctx, oxlint, env) {
3808
- const configPath = join19(ctx.cwd, LINT_CONFIG_FILE);
3809
- const baselinePath = join19(ctx.cwd, LINT_BASELINE_FILE);
3810
- if (!existsSync16(configPath)) return;
3935
+ const configPath = join20(ctx.cwd, LINT_CONFIG_FILE);
3936
+ const baselinePath = join20(ctx.cwd, LINT_BASELINE_FILE);
3937
+ if (!existsSync17(configPath)) return;
3811
3938
  let config;
3812
3939
  try {
3813
3940
  config = parseJsonc(
3814
- readFileSync15(configPath, "utf8"),
3941
+ readFileSync16(configPath, "utf8"),
3815
3942
  LINT_CONFIG_FILE
3816
3943
  );
3817
3944
  } catch {
3818
3945
  return;
3819
3946
  }
3820
3947
  const current = Array.isArray(config.extends) ? config.extends : [];
3821
- const measurePath = join19(ctx.cwd, LINT_MEASURE_FILE);
3948
+ const measurePath = join20(ctx.cwd, LINT_MEASURE_FILE);
3822
3949
  let measured;
3823
3950
  try {
3824
3951
  writeFileSync2(
@@ -3866,7 +3993,7 @@ var OxlintAdapter = class extends BaseAdapter {
3866
3993
  }
3867
3994
  }
3868
3995
  async run(ctx) {
3869
- if (!existsSync16(join19(ctx.cwd, LINT_CONFIG_FILE))) {
3996
+ if (!existsSync17(join20(ctx.cwd, LINT_CONFIG_FILE))) {
3870
3997
  process.stderr.write(
3871
3998
  `sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
3872
3999
  `
@@ -3950,7 +4077,12 @@ var OxlintAdapter = class extends BaseAdapter {
3950
4077
  downgraded: downgradedRulesFor(presetVariant(ctx.preset, ctx.cwd)).map((entry) => ({
3951
4078
  rule: entry.rule,
3952
4079
  reason: entry.reason
3953
- }))
4080
+ })),
4081
+ // What this module enforces BEYOND the preset. Not drift, and not covered by anything
4082
+ // else here: the stub still holds only `extends` and `ignorePatterns`, so a module with
4083
+ // a team layer reports `conformant=true drift=[]` and used to say nothing at all about
4084
+ // the extra rules it runs. See `extra-layers` for why that state is legitimate.
4085
+ layers: extraLayers(ctx.cwd, committedExtendsList(ctx.cwd, LINT_CONFIG_FILE))
3954
4086
  };
3955
4087
  }
3956
4088
  /** How many rules the extended preset enforces, read from the preset on disk. */
@@ -3981,7 +4113,7 @@ var OxlintAdapter = class extends BaseAdapter {
3981
4113
  let stub;
3982
4114
  try {
3983
4115
  stub = parseJsonc(
3984
- readFileSync15(join19(cwd, LINT_CONFIG_FILE), "utf8"),
4116
+ readFileSync16(join20(cwd, LINT_CONFIG_FILE), "utf8"),
3985
4117
  LINT_CONFIG_FILE
3986
4118
  );
3987
4119
  } catch {
@@ -3991,7 +4123,7 @@ var OxlintAdapter = class extends BaseAdapter {
3991
4123
  for (const entry of stub.extends ?? []) {
3992
4124
  try {
3993
4125
  const preset = parseJsonc(
3994
- readFileSync15(join19(cwd, entry), "utf8"),
4126
+ readFileSync16(join20(cwd, entry), "utf8"),
3995
4127
  entry
3996
4128
  );
3997
4129
  for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
@@ -4061,14 +4193,14 @@ var OxlintAdapter = class extends BaseAdapter {
4061
4193
  let parsed;
4062
4194
  try {
4063
4195
  parsed = parseJsonc(
4064
- readFileSync15(join19(cwd, LINT_CONFIG_FILE), "utf8"),
4196
+ readFileSync16(join20(cwd, LINT_CONFIG_FILE), "utf8"),
4065
4197
  LINT_CONFIG_FILE
4066
4198
  );
4067
4199
  } catch {
4068
4200
  return void 0;
4069
4201
  }
4070
4202
  const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
4071
- return targets.find((target) => !existsSync16(join19(cwd, target)));
4203
+ return targets.find((target) => !existsSync17(join20(cwd, target)));
4072
4204
  }
4073
4205
  /** Announce what is not enforced, so reduced coverage is never silent. */
4074
4206
  announceDisabled(preset) {
@@ -4100,9 +4232,9 @@ function registerLint() {
4100
4232
 
4101
4233
  // src/roles/typescript/adapters/tsc/tsc.adapter.ts
4102
4234
  import { spawnSync as spawnSync3 } from "child_process";
4103
- import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
4235
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
4104
4236
  import { createRequire as createRequire4 } from "module";
4105
- import { join as join21 } from "path";
4237
+ import { join as join22 } from "path";
4106
4238
 
4107
4239
  // src/roles/typescript/presets/base.json
4108
4240
  var base_default3 = {
@@ -4283,8 +4415,8 @@ function readTsconfigAdoption(cwd) {
4283
4415
  }
4284
4416
 
4285
4417
  // src/roles/typescript/adapters/tsc/plan.ts
4286
- import { readFileSync as readFileSync16 } from "fs";
4287
- import { join as join20 } from "path";
4418
+ import { readFileSync as readFileSync17 } from "fs";
4419
+ import { join as join21 } from "path";
4288
4420
 
4289
4421
  // src/roles/typescript/typecheck-script.ts
4290
4422
  var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
@@ -4306,7 +4438,10 @@ function keepsForeignChecker(existing) {
4306
4438
 
4307
4439
  // src/roles/typescript/adapters/tsc/plan.ts
4308
4440
  var INSTALL_NOTE = "run `pnpm install` to fetch @hublo/sentinel (added to the module devDependencies) so `extends` and the typecheck script resolve";
4309
- var TYPECHECK_TARGET = { cache: true, inputs: ["default", "{projectRoot}/tsconfig.json"] };
4441
+ var TYPECHECK_TARGET = {
4442
+ cache: true,
4443
+ inputs: ["default", "^default", "{projectRoot}/tsconfig.json"]
4444
+ };
4310
4445
  function typecheckScripts(cwd) {
4311
4446
  return {
4312
4447
  // Reads the nx target too, not only the npm script: five modules in this repo keep a
@@ -4372,7 +4507,7 @@ function planAdoption(context) {
4372
4507
  };
4373
4508
  }
4374
4509
  const existing = parseJsonc(
4375
- readFileSync16(join20(context.cwd, target.path), "utf8"),
4510
+ readFileSync17(join21(context.cwd, target.path), "utf8"),
4376
4511
  target.path
4377
4512
  );
4378
4513
  const extendsChain = composeExtends(existing.extends, preset);
@@ -4510,7 +4645,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
4510
4645
  let chain;
4511
4646
  try {
4512
4647
  const parsed = parseJsonc(
4513
- readFileSync17(join21(cwd, target.path), "utf8"),
4648
+ readFileSync18(join22(cwd, target.path), "utf8"),
4514
4649
  target.path
4515
4650
  );
4516
4651
  chain = parsed.extends;
@@ -4523,7 +4658,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
4523
4658
  );
4524
4659
  if (preset === void 0) return void 0;
4525
4660
  try {
4526
- createRequire4(join21(cwd, "noop.js")).resolve(preset);
4661
+ createRequire4(join22(cwd, "noop.js")).resolve(preset);
4527
4662
  return void 0;
4528
4663
  } catch {
4529
4664
  return preset;
@@ -4619,7 +4754,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
4619
4754
  referencedProjects(cwd, config) {
4620
4755
  try {
4621
4756
  const parsed = parseJsonc(
4622
- readFileSync17(join21(cwd, config), "utf8"),
4757
+ readFileSync18(join22(cwd, config), "utf8"),
4623
4758
  config
4624
4759
  );
4625
4760
  const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
@@ -4635,7 +4770,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
4635
4770
  * check.
4636
4771
  */
4637
4772
  typecheckTarget(cwd) {
4638
- if (existsSync17(join21(cwd, "tsconfig.json"))) return "tsconfig.json";
4773
+ if (existsSync18(join22(cwd, "tsconfig.json"))) return "tsconfig.json";
4639
4774
  const target = resolveTsconfigTarget(cwd);
4640
4775
  return target.reason === "none" ? null : target.path;
4641
4776
  }
@@ -4838,8 +4973,8 @@ function replaceLines(current, replacements) {
4838
4973
  }
4839
4974
 
4840
4975
  // src/core/apply-plan.ts
4841
- import { existsSync as existsSync18, readFileSync as readFileSync18, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
4842
- import { resolve as resolve3, sep } from "path";
4976
+ import { existsSync as existsSync19, readFileSync as readFileSync19, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
4977
+ import { resolve as resolve4, sep } from "path";
4843
4978
  import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
4844
4979
 
4845
4980
  // src/shared/deep-merge.ts
@@ -4849,15 +4984,15 @@ function isPlainObject(value) {
4849
4984
 
4850
4985
  // src/core/apply-plan.ts
4851
4986
  function resolveWithinRoot(cwd, relativePath) {
4852
- const root = resolve3(cwd);
4853
- const absolutePath = resolve3(root, relativePath);
4987
+ const root = resolve4(cwd);
4988
+ const absolutePath = resolve4(root, relativePath);
4854
4989
  if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {
4855
4990
  throw new Error(`Refusing to write outside the module root: "${relativePath}".`);
4856
4991
  }
4857
4992
  return absolutePath;
4858
4993
  }
4859
4994
  function readIfExists(absolutePath) {
4860
- return existsSync18(absolutePath) ? readFileSync18(absolutePath, "utf8") : void 0;
4995
+ return existsSync19(absolutePath) ? readFileSync19(absolutePath, "utf8") : void 0;
4861
4996
  }
4862
4997
  function* leaves(value, prefix = []) {
4863
4998
  for (const [key, keyValue] of Object.entries(value)) {
@@ -4975,8 +5110,8 @@ function applyPlan(cwd, plan2) {
4975
5110
  }
4976
5111
 
4977
5112
  // src/core/config/preset-evidence.ts
4978
- import { existsSync as existsSync19, readdirSync as readdirSync3, readFileSync as readFileSync19 } from "fs";
4979
- import { join as join22 } from "path";
5113
+ import { existsSync as existsSync20, readdirSync as readdirSync3, readFileSync as readFileSync20 } from "fs";
5114
+ import { join as join23 } from "path";
4980
5115
  var PATH_SIGNALS = [
4981
5116
  {
4982
5117
  preset: "nest",
@@ -4990,11 +5125,11 @@ var DEPENDENCY_SIGNALS = [
4990
5125
  { preset: "nest", pattern: /^@nestjs\// }
4991
5126
  ];
4992
5127
  function dependencyNames(cwd) {
4993
- const path = join22(cwd, "package.json");
4994
- if (!existsSync19(path)) return [];
5128
+ const path = join23(cwd, "package.json");
5129
+ if (!existsSync20(path)) return [];
4995
5130
  try {
4996
5131
  const manifest = parseJsonc(
4997
- readFileSync19(path, "utf8"),
5132
+ readFileSync20(path, "utf8"),
4998
5133
  path
4999
5134
  );
5000
5135
  return [
@@ -5017,7 +5152,7 @@ function declaresJsx(cwd) {
5017
5152
  for (const name of entries) {
5018
5153
  try {
5019
5154
  const config = parseJsonc(
5020
- readFileSync19(join22(cwd, name), "utf8"),
5155
+ readFileSync20(join23(cwd, name), "utf8"),
5021
5156
  name
5022
5157
  );
5023
5158
  if (config.compilerOptions?.jsx !== void 0) return true;
@@ -5113,13 +5248,26 @@ async function dispatch(opts) {
5113
5248
  const detected = resolveFlavour(opts);
5114
5249
  const adapter = resolve(opts.target, detected, opts.runner);
5115
5250
  const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
5116
- const contradiction = presetContradiction(preset, opts.cwd);
5117
- if (contradiction !== void 0) {
5118
- process.stderr.write(`sentinel (${opts.target}): ${contradiction}
5251
+ if (opts.preset !== void 0) {
5252
+ const contradiction = presetContradiction(opts.preset, opts.cwd);
5253
+ if (contradiction !== void 0) {
5254
+ process.stderr.write(`sentinel (${opts.target}): ${contradiction}
5119
5255
  `);
5120
- return 1;
5256
+ return 1;
5257
+ }
5258
+ }
5259
+ let effective = preset;
5260
+ if (opts.preset === void 0 && adapter.declaredPreset?.(opts.cwd) === void 0) {
5261
+ const [signal] = presetSignals(opts.cwd);
5262
+ if (signal !== void 0 && signal.preset !== preset) {
5263
+ effective = signal.preset;
5264
+ process.stderr.write(
5265
+ ` detected "${preset}" from dependencies, but ${signal.evidence}, so adopting as "${signal.preset}". Pass --preset to override.
5266
+ `
5267
+ );
5268
+ }
5121
5269
  }
5122
- const context = { cwd: opts.cwd, preset };
5270
+ const context = { cwd: opts.cwd, preset: effective };
5123
5271
  const plan2 = await adapter.plan(context);
5124
5272
  if (plan2.blocked) {
5125
5273
  process.stderr.write(`sentinel (${opts.target}): ${plan2.blocked}
@@ -5164,17 +5312,18 @@ export {
5164
5312
  readOwnVersion,
5165
5313
  readProjectPackageJson,
5166
5314
  readNxProjectName,
5315
+ VERBS,
5316
+ TARGETS,
5317
+ PRESET_NAMES,
5167
5318
  WORKSPACE_ROOT_MARKER,
5168
5319
  findWorkspaceRoot,
5169
5320
  ensureWorkspacePrep,
5321
+ inspectWorkspacePrep,
5170
5322
  palette,
5171
5323
  resolveBin,
5172
- VERBS,
5173
- TARGETS,
5174
- PRESET_NAMES,
5175
5324
  registerAdapters,
5176
5325
  describeFramework,
5177
5326
  detectFramework,
5178
5327
  dispatch
5179
5328
  };
5180
- //# sourceMappingURL=chunk-RLVLDC5H.js.map
5329
+ //# sourceMappingURL=chunk-W73ISYMG.js.map
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-RLVLDC5H.js";
10
+ } from "./chunk-W73ISYMG.js";
11
11
  export {
12
12
  BaseAdapter,
13
13
  all,
package/oxlint/nest.json CHANGED
@@ -327,7 +327,7 @@
327
327
  ],
328
328
  "typescript/return-await": [
329
329
  "warn",
330
- "never"
330
+ "in-try-catch"
331
331
  ],
332
332
  "typescript/switch-exhaustiveness-check": [
333
333
  "error",
@@ -319,7 +319,7 @@
319
319
  "typescript/no-useless-constructor": "warn",
320
320
  "typescript/return-await": [
321
321
  "warn",
322
- "never"
322
+ "in-try-catch"
323
323
  ],
324
324
  "use-isnan": [
325
325
  "error",
package/oxlint/react.json CHANGED
@@ -438,7 +438,7 @@
438
438
  "typescript/restrict-template-expressions": "error",
439
439
  "typescript/return-await": [
440
440
  "warn",
441
- "never"
441
+ "in-try-catch"
442
442
  ],
443
443
  "typescript/switch-exhaustiveness-check": "warn",
444
444
  "typescript/unbound-method": "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
5
5
  "type": "module",
6
6
  "license": "MIT",