@hublo/sentinel 1.6.0-alpha.1 → 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.
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  registerAdapters,
7
7
  resolve,
8
8
  setDefaultRunner
9
- } from "./chunk-5267A6X3.js";
9
+ } from "./chunk-R7DS7VN4.js";
10
10
  import {
11
11
  BaseAdapter
12
- } from "./chunk-P7NYSMLS.js";
12
+ } from "./chunk-67QLOX6F.js";
13
13
  import "./chunk-JRZI5IIN.js";
14
14
  import "./chunk-7E6YTTAK.js";
15
15
  import "./chunk-4EIY7TTR.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sharedTestConfig
3
- } from "../../../chunk-5VNYQIFD.js";
3
+ } from "../../../chunk-EESVRILJ.js";
4
4
  import {
5
5
  decoratorMetadata,
6
6
  tsconfigAliases
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  jestExportConditions,
3
3
  sharedTestConfig
4
- } from "../../../chunk-5VNYQIFD.js";
4
+ } from "../../../chunk-EESVRILJ.js";
5
5
  import "../../../chunk-SWWQ7X7B.js";
6
6
  import "../../../chunk-QXFCZON7.js";
7
7
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sharedTestConfig
3
- } from "../../chunk-5VNYQIFD.js";
3
+ } from "../../chunk-EESVRILJ.js";
4
4
  import "../../chunk-SWWQ7X7B.js";
5
5
  import "../../chunk-QXFCZON7.js";
6
6
  export {
@@ -7,7 +7,7 @@ import {
7
7
  readTestAdoption,
8
8
  resolveVitest,
9
9
  suiteOf
10
- } from "./chunk-P7NYSMLS.js";
10
+ } from "./chunk-67QLOX6F.js";
11
11
  import "./chunk-JRZI5IIN.js";
12
12
  import "./chunk-O7REVMOC.js";
13
13
  import "./chunk-QXFCZON7.js";
@@ -160,9 +160,13 @@ repository. The migration of the monorepo onto the role is where they are revisi
160
160
  `PASSWORD`, `PRIVATE_?KEY`, `ACCESS_?KEY`, `API_?KEY`), so serviceapp's `facturationPassword` and a
161
161
  camel-case `apiKey` are seen. The scans read `.ts .tsx .mts .cts .js .jsx .mjs .cjs` (and `.svelte`,
162
162
  `.vue` for direct reads) across the whole repository, with test files left out of the reader scan:
163
- `.test.<ext>` and `.spec.<ext>` for each of those eight extensions. A value is read as dotenv reads
164
- it: an unquoted value ends at its first `#` (dotenv's README, "Comments begin where a `#` exists"),
165
- a quoted one at its closing quote, so `API_TOKEN= # filled by the vault` is blank.
163
+ `.test.<ext>` and `.spec.<ext>` for each of those eight extensions. A value is read from what is
164
+ written in the committed file: a quoted one ends at its closing quote, an unquoted one at a `#`
165
+ preceded by whitespace. So `API_TOKEN= # filled by the vault` and `API_TOKEN=` are blank,
166
+ `API_TOKEN=abc #def` is `abc`, and `API_TOKEN=abc#def` and `DATABASE_PASSWORD=#Pa55word!` are
167
+ filled. This deliberately differs from dotenv on a `#` with no whitespace before it: dotenv's
168
+ README says "Comments begin where a `#` exists", so dotenv loads `DATABASE_PASSWORD=#Pa55word!` as
169
+ blank, and a secret committed in clear text would go unreported by rule 6.
166
170
 
167
171
  ## In CI
168
172
 
@@ -227,9 +231,11 @@ the same single difference.
227
231
  - Rule 6 reads every committed env file, not only contracts, and no contract at all is a note
228
232
  rather than a refusal to run.
229
233
  - Every finding has a line, so an annotation lands on it.
230
- - A comment after a value is a comment, as in dotenv: `KEY= # note` is blank, not a committed
234
+ - A comment after a value, after whitespace, is a comment: `KEY= # note` is blank, not a committed
231
235
  secret. And a quoted value followed by one, `KEY="" # note`, no longer opens a multi-line value
232
- that hid every key after it in the file. The original read `KEY= # note` as filled.
236
+ that hid every key after it in the file. The original read `KEY= # note` as filled. A `#` with no
237
+ whitespace before it stays part of the value, as in the original and unlike dotenv:
238
+ `KEY=#Pa55word!` is filled (see Sentinel's defaults).
233
239
  - Test files are left out of the reader scan for every extension it reads; the original read
234
240
  `.ts` and `.tsx` only, and left out `.spec.ts` and `.test.ts`.
235
241
 
@@ -158,10 +158,12 @@ a **layer above the preset**, the way the planning team did:
158
158
  Order matters, sentinel's preset first, or your layer cannot override anything. `--inspect`
159
159
  reports the layer so it stays visible.
160
160
 
161
- A rule set directly in `.oxlintrc.json` is still reported as drift, and a layer is the place for
162
- it. Since 1.5.1 a re-`init` no longer deletes it, if the preset does not configure that rule: it
163
- is kept, in your order, and `--init` says so. A rule the preset does configure is dropped, as
164
- before, and named: set in the stub, it would override the preset and the baseline both.
161
+ A rule can also be set directly in `.oxlintrc.json`. Since 1.5.1 a re-`init` keeps it, in your
162
+ order, and says so, when the preset does not name that rule; `--inspect` lists it as added by
163
+ the module and does not call it drift (see
164
+ [What `--inspect` calls drift](#what---inspect-calls-drift-and-what-it-lists-as-yours)). A rule
165
+ the preset does name is dropped, as before, and named: set in the stub, it would override the
166
+ preset and the baseline both. To change one of those for your module, a layer is the place.
165
167
 
166
168
  Think of it as a queue rather than a fork: a rule promoted into the preset is deleted from the
167
169
  layer. It is the exact mirror of `.oxlintrc.baseline.json`, which holds rules **below** the
@@ -233,6 +235,85 @@ Two limits, both the preset's governance:
233
235
  rather than replace them. The planning layer's notes say overrides do not cross `extends`; that
234
236
  is not what 1.77.0 does.
235
237
 
238
+ ### What `--inspect` calls drift, and what it lists as yours
239
+
240
+ Since 1.5.1, `--inspect` (and `--status`) judge what you add to `.oxlintrc.json` (`rules`,
241
+ `plugins`, `jsPlugins`, `settings`, `overrides`) by the criterion a re-`init` keeps or drops it
242
+ by, so the two never disagree. It rests on how every
243
+ preset is built: each one turns every oxlint category off and names every rule it runs. A rule
244
+ the preset does not name is therefore off unless you turn it on, and setting it takes nothing
245
+ from the preset. Nobody maintains a list: "the preset names it" is read from the preset, by the
246
+ rule oxlint runs (so an alias counts as the rule it stands for).
247
+
248
+ **A rule the preset names is governed.** Whatever value you give it, a re-`init` drops it.
249
+ `--inspect` compares it with the preset:
250
+
251
+ | you set a rule the preset names... | `--inspect` |
252
+ | ----------------------------------------------------------------- | ---------------------------------------- |
253
+ | to the preset's own value and options (`deny` is `error`) | redundant: not drift, still conformant |
254
+ | to `off` | conflict: drift, not conformant |
255
+ | to `warn` where the preset says `error`, or the reverse | conflict: drift, not conformant |
256
+ | with other options (another `no-restricted-imports` pattern list) | conflict: drift, not conformant |
257
+ | under an alias (`eslint/no-var`, `typescript/no-restricted-...`) | the same as the name it stands for |
258
+ | stricter than the preset (`no-console: error` where it is `off`) | conflict: drift, not conformant, for now |
259
+
260
+ **A rule the preset does not name is added by the module.** A re-`init` keeps it, `--inspect`
261
+ does not call it drift, and lists it, with its value, so it stays visible:
262
+
263
+ | you set a rule the preset does not name... | `--inspect` |
264
+ | ------------------------------------------ | --------------------------------------------------------- |
265
+ | at any severity, with any options | added by the module: not drift, listed under `added` |
266
+ | to `off` | the same, listed, and the line says `off` changes nothing |
267
+ | in one of your `overrides` blocks | the same, listed with the block's `files` |
268
+
269
+ console, after its re-`init`:
270
+
271
+ ```
272
+ ✓ console (react) lint — ... added=2 ... conformant=true drift=[] ...
273
+ added by the module (kept by --init, not drift):
274
+ • import/no-cycle — "error": the preset does not name this rule
275
+ • oxc/no-barrel-file — ["error",{"threshold":0}]: the preset does not name this rule
276
+ ```
277
+
278
+ `--inspect --lint --json` carries the same list as `added: [{ rule, value, files?, reason }]`.
279
+
280
+ **The other blocks follow the re-`init` exactly**: what it keeps is added by the module, listed
281
+ under `addedConfig` ("config added by the module" on the console); what it drops as the
282
+ preset's is a collision, and drift.
283
+
284
+ | you add... | `--inspect` |
285
+ | ------------------------------------------------------------- | ---------------------------------------- |
286
+ | a `plugins` entry the preset does not enable | added, listed |
287
+ | a `plugins` entry the preset already enables | redundant: not drift, not listed |
288
+ | a `jsPlugins` entry under a name the preset does not register | added, listed |
289
+ | a `jsPlugins` entry under a name the preset registers | collision: drift (a re-`init` drops it) |
290
+ | a `settings` key the preset does not set | added, listed |
291
+ | a `settings` key the preset sets | collision: drift (a re-`init` drops it) |
292
+ | a key of an `overrides` block other than `files` and `rules` | added, listed with the block's `files` |
293
+ | a block without `files`, or a top-level key such as `env` | drift: a re-`init` does not read it back |
294
+
295
+ host-admin at monorepo#23342 shows both sides. Its planning `jsPlugins` entry and the planning
296
+ rules of its override blocks are additions, kept and listed. Its top-level `rules` set 46 rules
297
+ the react preset names, 41 of them at another value (the TanStack Query set at `error` where the
298
+ preset holds `warn`, `no-explicit-any`, `no-console`, the MUI barrel bans added to
299
+ `no-restricted-imports`, ...), and five of its ten blocks set `no-restricted-imports` or
300
+ `switch-exhaustiveness-check` (six entries): conflicts, drift. A re-`init` drops all 46 top-level ones
301
+ and the 12 override entries the preset governs, the redundant ones included (measured, `--dry-run`). Raising what the
302
+ preset governs belongs in a layer above the preset, where a re-`init` keeps it.
303
+
304
+ Three details:
305
+
306
+ - **Stricter is still a conflict, for now.** Raising a rule the preset governs, even one it
307
+ parks `off`, is reported as drift: the preset is where that decision is made, for every
308
+ module, and a layer above the preset is where a team makes it for itself.
309
+ - **An `overrides` block** is judged the same way, rule by rule. A governed rule in it is
310
+ compared with the preset's override for the same `files` when the preset has one that names
311
+ the rule, else with the preset's top-level value; a rule the preset names only in one of its
312
+ own overrides, set at another scope, is a conflict.
313
+ - **What stays drift**: any top-level key a re-`init` does not read back (`env`, `globals`,
314
+ `categories`, ...), and everything a module on a preset sentinel does not ship adds, since
315
+ there is nothing to compare it with.
316
+
236
317
  ## What a preset does not enforce
237
318
 
238
319
  Every unenforced rule carries a reason, readable with `sentinel --inspect --lint`:
@@ -290,8 +371,8 @@ things follow, and they are the point:
290
371
  predates the migration. A hold is never `off`;
291
372
  - the file is **regenerated, not accumulated**. Fix the last violation, re-run
292
373
  `sentinel --init --lint`, and the rule drops out and returns to the preset severity;
293
- - your own config stays `extends` + `ignorePatterns`, so a rule **you** change by hand is
294
- still reported as drift and is never confused with a hold sentinel made;
374
+ - your own config stays `extends` + `ignorePatterns`, so a preset rule **you** change by hand
375
+ is still reported as drift and is never confused with a hold sentinel made;
295
376
  - `--init` says what it held and how many violations each hold covers, so the debt is
296
377
  announced rather than discovered;
297
378
  - a hold is written under the **preset's** name for the rule (since 1.5.1). Oxlint reports
@@ -23,16 +23,16 @@ sentinel --run # no target -> every wired tar
23
23
  Read it by target, since that is what you pick first. `--report` and `--status` are deprecated
24
24
  everywhere (`--run --json` and `--inspect` answer them), and `--migrate` is refused everywhere.
25
25
 
26
- | Target | `--init` writes | `--run` does | `--inspect` shows |
27
- | ------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
28
- | `--lint` | stub + scripts + nx metadata, removes the ESLint config and the module's ESLint deps, then autofixes once | lints; `--fix` autofixes in the same pass | rule count, what is disabled or downgraded and **why**, drift |
29
- | `--format` | materializes the preset + scripts + nx metadata, removes the Prettier config and deps, then formats once | **checks**; `--fix` writes | effective options, every departure from the standard and why |
30
- | `--typescript` | writes/extends the tsconfig + `typecheck` script | typechecks | resolved options, what is deferred, drift |
31
- | `--build` | points the module's Vite config at sentinel's toolchain + scripts + nx metadata | builds | runner, config file, whose Vite, how many overrides, adoption |
32
- | `--dev` | adopts the BUILD too: one plan writes both `build` and `serve` | serves; long-running, so never swept by an unqualified `--run` | refuses, and sends you to `--inspect --build`: it is that config |
33
- | `--test` | the Vitest config + scripts + nx metadata, and migrates every spec file | runs Vitest | runner, which configs were found, adoption state, drift |
34
- | `--static-analysis` | the config stub + three scripts + nx metadata, and ignores the report directory at the repository root | measures and reports; never exits non-zero on a finding | runner, config file, resolved settings, what this module moved |
35
- | `--env` | refuses: it writes no config (an optional `env.config.mjs` at the repository root declares exceptions) | checks the whole REPOSITORY, from its git root only; exits 1 on a blocking finding at `SENTINEL_ENV_LEVEL=error`; an invalid level refuses the whole run, a bare root `--run` included | refuses: nothing to inspect |
26
+ | Target | `--init` writes | `--run` does | `--inspect` shows |
27
+ | ------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
28
+ | `--lint` | stub + scripts + nx metadata, removes the ESLint config and the module's ESLint deps, then autofixes once | lints; `--fix` autofixes in the same pass | rule count, what is disabled or downgraded and **why**, what the module adds, drift |
29
+ | `--format` | materializes the preset + scripts + nx metadata, removes the Prettier config and deps, then formats once | **checks**; `--fix` writes | effective options, every departure from the standard and why |
30
+ | `--typescript` | writes/extends the tsconfig + `typecheck` script | typechecks | resolved options, what is deferred, drift |
31
+ | `--build` | points the module's Vite config at sentinel's toolchain + scripts + nx metadata | builds | runner, config file, whose Vite, how many overrides, adoption |
32
+ | `--dev` | adopts the BUILD too: one plan writes both `build` and `serve` | serves; long-running, so never swept by an unqualified `--run` | refuses, and sends you to `--inspect --build`: it is that config |
33
+ | `--test` | the Vitest config + scripts + nx metadata, and migrates every spec file | runs Vitest | runner, which configs were found, adoption state, drift |
34
+ | `--static-analysis` | the config stub + three scripts + nx metadata, and ignores the report directory at the repository root | measures and reports; never exits non-zero on a finding | runner, config file, resolved settings, what this module moved |
35
+ | `--env` | refuses: it writes no config (an optional `env.config.mjs` at the repository root declares exceptions) | checks the whole REPOSITORY, from its git root only; exits 1 on a blocking finding at `SENTINEL_ENV_LEVEL=error`; an invalid level refuses the whole run, a bare root `--run` included | refuses: nothing to inspect |
36
36
 
37
37
  `--json` works for every verb, and stdout carries **only** the envelope, so `| jq` always
38
38
  parses. `--dry-run` applies to `--init` and writes nothing. Besides the config diffs, it says
@@ -141,10 +141,10 @@ Every cell is a test in `tests/e2e/env.e2e.test.ts`, against the built CLI, on g
141
141
  serviceapp-shaped repository (not nx, committed `variables/*.env`), the same with a package below
142
142
  its root, an nx workspace with a module, and a directory in no repository. `SENTINEL_ENV_LEVEL` and
143
143
  `GITHUB_ACTIONS` are pinned on every call, and "unset" removes the variable from the child's
144
- environment, which is a different input from the empty string. 57 cells: the 31 of the first draft,
145
- one more refused verb, 19 new ones, the rule 1 note, and the 5 of the first adopter's feedback
144
+ environment, which is a different input from the empty string. 58 cells: the 31 of the first draft,
145
+ one more refused verb, 19 new ones, the rule 1 note, the 5 of the first adopter's feedback
146
146
  (a refused config's row, in console and `--json`; test files of every extension; a comment after a
147
- value; the summary annotation past GitHub's cap).
147
+ value; the summary annotation past GitHub's cap), and a secret whose value starts with `#`.
148
148
 
149
149
  ### verb, from the repository root
150
150
 
@@ -205,6 +205,7 @@ fails nothing would be decoration; none is, and every new cell fails under at le
205
205
  | the rule 1 note, always emitted (unit + e2e) | 2 |
206
206
  | test files of every extension (suffixes reverted) | 2 |
207
207
  | a comment after a value (parser reverted) | 5 |
208
+ | a `#` with no whitespace before it (dotenv's rule) | 4 |
208
209
  | the refusal's `error` on the row (adapter reverted) | 2 |
209
210
  | no `undefined` key on the row (orchestrate reverted) | 1 |
210
211
  | a refusal names the kind found (define reverted) | 4 |
@@ -214,6 +215,9 @@ The `COPY` fix is unit (`env-checks-fixes`): reverted, its 3 tests fail and the
214
215
  The six feedback controls ran unit and e2e together over the six env suites (187 tests, 193 for
215
216
  the last one, once the summary cells existed): each failed only its own new cells, and no cell that existed before. The two
216
217
  summary-annotation guards (nothing at exactly 10, nothing at `info`) pass reverted, as guards do.
218
+ The `#` control restored dotenv's rule (an unquoted value ends at its first `#`) over the six env
219
+ suites, 203 tests: its 4 new cells fail, 199 pass. One existing cell changed, on purpose: it
220
+ asserted dotenv's reading of `B_TOKEN=#c` as blank, which is the reading this rule refuses.
217
221
 
218
222
  ### Against real repositories, from the packed tarball
219
223
 
@@ -251,7 +255,7 @@ Rule 8 against a real nx ran on the monorepo only (nx installed in a throwaway w
251
255
 
252
256
  ## The `--init` matrix for 1.5.1, 29 October 2026
253
257
 
254
- Six fixes, then a seventh (below) found by the alpha.1 validation. Found by re-initializing the monorepo's 63 adopted modules with 1.5.1-alpha.0. Every
258
+ Six fixes, then a seventh (below) found by the alpha.1 validation and an eighth found by the alpha.2 one. Found by re-initializing the monorepo's 63 adopted modules with 1.5.1-alpha.0. Every
255
259
  cell below is a test in this repo, against the built CLI unless it says unit, and every fix has a
256
260
  negative control: the fix reverted in the source, the cells run again, and the cells that must
257
261
  fail do. No existing cell changed: against the alpha, the test tree gains lines and loses none
@@ -395,6 +399,87 @@ Checked in the monorepo at 7177eb86038: none of the 66 committed `.oxlintrc*.jso
395
399
  team layers spells a governed rule under another name, so no adopted module carries the gap
396
400
  today.
397
401
 
402
+ ### 8. `--inspect` judges a module's own rules as a re-init does (`inspect-lint-module-additions`, unit `lint-module-rule-standing`)
403
+
404
+ Found by the alpha.2 sweep: `--init` kept console's `import/no-cycle` and `oxc/no-barrel-file`
405
+ ("kept 2 rule(s)"), and `--inspect` reported console's lint `status: failed, conformant: false,
406
+ drift: ["rules"]`, because any `rules` key was drift. The criterion is now the re-init's
407
+ (`governedRules`, by `canonicalRuleName`): it holds because every preset turns every oxlint
408
+ category off and names every rule it runs, checked first on all six shipped presets against
409
+ oxlint 1.77.0's seven categories (a unit cell), and on the binary: `no-const-assign`, a
410
+ correctness rule react does not name, fires under bare oxlint and not under console's stub.
411
+
412
+ | the module's `.oxlintrc.json` sets, react preset | `conformant`, `drift` | listed in `added` |
413
+ | ---------------------------------------------------------------------------------- | ---------------------------- | ---------------------- |
414
+ | console's two rules, which the preset does not name | true, `[]` | both, with values |
415
+ | an unnamed rule at `off` | true, `[]` | yes, "changes nothing" |
416
+ | an unnamed rule in an `overrides` block | true, `[]` | yes, with `files` |
417
+ | `no-var` at the preset's `error` (unit: also `deny`, `["error"]`, `eslint/no-var`) | true, `[]` | no |
418
+ | unit: `no-restricted-imports` at the preset's options | true, `[]` | no |
419
+ | `no-var` at `off`, at `warn` | false, `["rules"]` | no |
420
+ | `no-restricted-imports` with other options (unit: also as `typescript/...`) | false, `["rules"]` | no |
421
+ | `eslint/no-var: off` | false, `["rules"]` | no |
422
+ | `no-console: error`, stricter than the preset's `off` (a conflict for now) | false, `["rules"]` | no |
423
+ | unit: a rule the preset names only in its test override, set at the top level | false, `["rules"]` | no |
424
+ | the same rows inside an `overrides` block (unit; e2e: `no-var: off`) | the same, `["overrides"]` | the same |
425
+ | unit: the preset override's rule at its own value, for its own `files` | true, `[]` | no |
426
+ | unit: rules under a preset sentinel does not ship | drift `["rules"]`, as before | n/a |
427
+ | `--status`, console's two rules | `1/1 conformant` | |
428
+ | re-init of console's shape, then `--inspect` | kept 2, then true, `[]` | both |
429
+
430
+ Negative control, the source reverted (`git stash` of `src/`, new tests kept, rebuilt): 27 of
431
+ the 47 new cells fail, every cell that expects "not drift" or the `added` list. The 20 that pass
432
+ are the conflict cells, which were drift before too, the category check, the unshipped preset
433
+ and the block carrying `env`. No existing cell changed: `lint-adoption-read`,
434
+ `adoption-contract` and `lint.e2e` set `no-debugger: off` and `no-console: off` on nest, both
435
+ named there at another value, so they are conflicts and still drift.
436
+
437
+ On the monorepo, `--inspect --json` from each of the 63 modules of the alpha.2 sweep worktree,
438
+ with this branch's built CLI, nothing written (`git status` hash unchanged): console's lint row
439
+ goes `failed` to `ok`, `conformant` true, `drift` `[]`, its two rules listed, and the workspace
440
+ coverage footer of console's own run reads 4 of 4 adopted roles conformant instead of 3. The 62 other modules differ from the
441
+ alpha.2 run only by an empty `added: []` on their lint row.
442
+
443
+ **The other blocks a re-init reads back**, the same criterion, from `moduleOwnBlocks` itself (no
444
+ second definition): what it keeps is an addition, listed under `addedConfig`; what it drops as
445
+ the preset's is a collision, and drift. Found on host-admin at monorepo#23342, whose planning
446
+ `jsPlugins` entry a re-init keeps and `--inspect` called drift.
447
+
448
+ | the module's `.oxlintrc.json` adds, react preset | `conformant`, `drift` | listed in `addedConfig` |
449
+ | ------------------------------------------------------------------------------ | ---------------------- | ------------------------------------- |
450
+ | host-admin's planning jsPlugin (a path) and a named one | true, `[]` | yes |
451
+ | a `plugins` entry the preset does not enable | true, `[]` | yes |
452
+ | `plugins: ["import"]`, already enabled | true, `[]` | no |
453
+ | a `settings` key the preset does not set | true, `[]` | yes |
454
+ | an override block key beyond `files` and `rules` (`env`) | true, `[]` | yes, with `files` |
455
+ | a jsPlugin under a name the preset registers (`nx`), or the preset's own path | false, `["jsPlugins"]` | no |
456
+ | unit: a `settings` key the preset sets (synthetic preset: none ships settings) | drift `["settings"]` | no |
457
+ | unit: `plugins` not a list of names, `settings` not an object | drift | n/a |
458
+ | unit: an override block without `files`; a top-level `env` or `categories` | drift | n/a |
459
+ | an addition at `off` in an override block | true, `[]` | in `added`, without "changes nothing" |
460
+
461
+ host-admin's committed config at #23342, in a fixture copy with its planning plugin, before and
462
+ after this change: `drift: ["jsPlugins", "rules", "overrides"]` becomes `["rules", "overrides"]`,
463
+ with 18 rule additions and the jsPlugin listed. What remains is real: its top-level `rules` set
464
+ 46 rules the react preset names, 41 at another value (the TanStack Query set at `error`, the
465
+ `no-unsafe-*` set, `no-explicit-any`, `no-console`, `no-restricted-imports` with the MUI barrel
466
+ bans, ...), and 5 of its 10 blocks set `no-restricted-imports` or
467
+ `switch-exhaustiveness-check` (6 entries). A re-init's `--dry-run` on the same copy drops those
468
+ 46 rules and 12 governed override entries and keeps the jsPlugin and 4 blocks.
469
+
470
+ Negative control for this part, the source at 3060996 (`git stash` of `src/`, rebuilt): 15 of
471
+ the 64 cells fail, the 12 new ones that expect "not drift" or an `addedConfig` list, and the
472
+ three existing cells of 3060996 whose expectation this changes. The 49 that pass include the
473
+ collision cells and the unread shapes, drift before too. Four cells of 3060996 changed, all in
474
+ this PR's own new files and all asserting what this part reverses: the override block with `env`
475
+ (drift then, an addition now), the `settings: { team }` cell (replaced by `env` and `categories`,
476
+ which a re-init does not read back), and the two `readModuleAdditions` cells (it now returns
477
+ `{ rules, config }`).
478
+
479
+ The 63-module inspect, re-run with this build: identical to the run above for every module,
480
+ console included, except an empty `addedConfig: []` on each lint row; `git status` hash
481
+ unchanged, 63 of 63 exit 0.
482
+
398
483
  ### against the consumer, the 63 adopted modules
399
484
 
400
485
  The packed branch, through a pnpm `file:` override, on a fresh worktree of the monorepo at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.6.0-alpha.1",
3
+ "version": "1.6.0-alpha.2",
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",