@svelte-vitals/core 0.29.0 → 0.31.0

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +491 -25
  2. package/dist/index.js +1477 -138
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -17,6 +17,33 @@ interface Detection {
17
17
  presence: Presence;
18
18
  value: Value;
19
19
  }
20
+ /**
21
+ * One compiled SvelteKit alias entry, in the order Kit builds them (`get_config_aliases` in
22
+ * `@sveltejs/kit/src/exports/vite/utils.js`): `$lib` first, then `kit.alias` in declaration
23
+ * order. Resolution takes the FIRST matching entry, exactly as Vite's alias plugin does, so
24
+ * **position is precedence** — the list is never sorted and a longer `find` never wins on
25
+ * length alone.
26
+ */
27
+ interface KitAlias {
28
+ /** The alias key, with any trailing `/*` removed. */
29
+ find: string;
30
+ /**
31
+ * The project-relative target: posixified, with any trailing `/*` and any trailing slashes
32
+ * removed. `null` when the config's value is not a string literal — such an entry still
33
+ * matches (holding its position and its mode) but resolves to undefined, so a specifier we
34
+ * cannot resolve stays unresolved instead of falling through to a later entry.
35
+ */
36
+ replacement: string | null;
37
+ /**
38
+ * How `find` matches a specifier, mirroring Kit's three compiled entry shapes:
39
+ * - `prefix` — `spec === find` or `spec.startsWith(find + '/')`; a plain key.
40
+ * - `contents` — `spec.startsWith(find + '/')` only; from a `key/*` key, which Kit
41
+ * documents as matching "the contents of a directory, not the directory itself".
42
+ * - `exact` — `spec === find` only; a plain key whose `key/*` form is ALSO declared, which
43
+ * is how Kit stops the plain key from swallowing the nested specifiers.
44
+ */
45
+ match: 'prefix' | 'contents' | 'exact';
46
+ }
20
47
  /** Project-wide facts precomputed by the runtime layer for project-scope rules (design §10). */
21
48
  interface Project {
22
49
  hasRobotsTxt: boolean;
@@ -36,6 +63,24 @@ interface Project {
36
63
  file?: string;
37
64
  line?: number;
38
65
  };
66
+ /**
67
+ * Set when the project configures a non-empty `kit.paths.base` — read from the `sveltekit()`
68
+ * Vite plugin config, else `svelte.config.{js,ts}` (correctness/base-path-navigation).
69
+ * `value` is the literal base when statically resolvable, unset when the config computes it
70
+ * (e.g. `dev ? '' : '/repo'`). `file` is the config path relative to the analyzed root (posix).
71
+ * Absent means the app is served at the root — the rule stays silent.
72
+ */
73
+ kitPathsBase?: {
74
+ value?: string;
75
+ file: string;
76
+ };
77
+ /**
78
+ * The project's compiled SvelteKit alias entries, in Kit's own order (`$lib` first), read from
79
+ * `svelte.config.{js,ts}`. Absent means no config was read — resolution then falls back to
80
+ * `$lib` → `src/lib`, which is what this analyzer assumed unconditionally before. A collected
81
+ * list is never empty: `$lib` is always prepended.
82
+ */
83
+ kitAliases?: KitAlias[];
39
84
  }
40
85
  declare const defaultProject: Project;
41
86
  /** A concrete, agent-actionable remediation for a finding (design §10, issue #18). */
@@ -69,10 +114,29 @@ interface Result {
69
114
  }
70
115
  type Scope = 'route' | 'project' | 'component';
71
116
  type Category = 'seo' | 'performance' | 'correctness' | 'security' | 'architecture';
117
+ /**
118
+ * Every category, as a runtime list — for validating a user-supplied category
119
+ * name and naming the known ones in the error. One definition so a category
120
+ * added to `Category` can't be accepted by one validator and rejected by
121
+ * another. Not an ordering: reporters keep their own display order.
122
+ */
123
+ declare const CATEGORIES: readonly Category[];
72
124
  /** How dynamic (`{data.title}`) values are treated by scoring (design §4, §12). */
73
125
  type TreatDynamicAs = 'pass' | 'warn' | 'fail';
74
- /** Per-rule override: disable, or change severity. */
75
- type RuleSetting = 'off' | Severity;
126
+ /** Resolved option values handed to a rule at check time. */
127
+ type RuleOptions = Record<string, unknown>;
128
+ /**
129
+ * Object form of a rule setting. `severity` omitted keeps the rule's built-in
130
+ * severity — the common case when only a threshold is being moved.
131
+ * `{ severity: 'off', … }` disables the rule and any `options` beside it are
132
+ * inert (equivalent to the bare `'off'` string, not an error).
133
+ */
134
+ interface RuleSettingObject {
135
+ severity?: Severity | 'off';
136
+ options?: RuleOptions;
137
+ }
138
+ /** Per-rule override: disable, change severity, and/or set options. */
139
+ type RuleSetting = 'off' | Severity | RuleSettingObject;
76
140
  /**
77
141
  * Scoped rule override (design 2026-07-18), applied to results after analysis.
78
142
  * An entry matches a finding when any `route` glob matches its route id or any
@@ -122,7 +186,21 @@ interface Runtime {
122
186
  readFile(path: string): Promise<string>;
123
187
  /** Whether a path exists. */
124
188
  exists(path: string): Promise<boolean>;
125
- /** Glob relative to `cwd`, returning paths (adapter decides abs/rel convention). */
189
+ /**
190
+ * Paths matching `pattern`, relative to `cwd`.
191
+ *
192
+ * **Dot files and dot directories are excluded**, and an adapter must keep it that way: the
193
+ * directory-shaped Architecture rules derive their directory set from these paths, and one of them
194
+ * enumerates a parent's children exhaustively, so a `.server/` appearing here would be reported as
195
+ * an undeclared name. Both shipped adapters pass `dot: false`.
196
+ *
197
+ * **Every returned path is a file, never a directory**, and an adapter must keep that true too:
198
+ * `architecture/reserved-directory-names`' unit test takes a directory's immediate children from
199
+ * this same inventory and asks whether one of them is a file named after the directory, so an
200
+ * adapter that let a directory through here would let a bare `Card/Card` satisfy that test as if it
201
+ * were an entry file. Both shipped adapters get this for free from their glob library's default,
202
+ * which returns files only unless asked to include directories.
203
+ */
126
204
  glob(pattern: string, cwd: string): Promise<string[]>;
127
205
  /** Join path segments without depending on `node:path`. */
128
206
  join(...parts: string[]): string;
@@ -297,6 +375,26 @@ interface SuppressionDirective {
297
375
  /** Rule ids suppressed on that line; undefined = suppress every rule on that line. */
298
376
  ruleIds?: string[];
299
377
  }
378
+ /** An `<input type="checkbox">` / `<input type="radio">` element carrying a `bind:value`
379
+ * directive — `bind:value` observes the DOM `value` property, which checkbox/radio
380
+ * interaction never changes, so the bound state silently never updates
381
+ * (correctness/checkable-bind-value). */
382
+ interface CheckableBindValueFact {
383
+ /** Which checkable input type was flagged — selects the message wording. */
384
+ kind: 'checkbox' | 'radio';
385
+ /** 1-based source line, or 0 if unknown. */
386
+ line: number;
387
+ }
388
+ /** A root-relative navigation literal — broken when the app is served under `kit.paths.base`
389
+ * (correctness/base-path-navigation). Shared by the component and Kit-module channels. */
390
+ interface BasePathLinkFact {
391
+ /** Which navigation surface it was written on — selects the message wording. */
392
+ kind: 'href' | 'goto' | 'redirect';
393
+ /** The literal path as written, e.g. '/about'. */
394
+ path: string;
395
+ /** 1-based source line, or 0 if unknown. */
396
+ line: number;
397
+ }
300
398
  /** Reactivity/correctness + security + architecture facts parsed from one `.svelte` component. */
301
399
  interface ComponentFacts {
302
400
  /** Source file the component came from. */
@@ -313,10 +411,17 @@ interface ComponentFacts {
313
411
  propCount: number;
314
412
  /** Module specifiers of every `import` in the instance + module scripts (performance/heavy-import). */
315
413
  imports: string[];
316
- /** Module specifiers of every `import`, each with its source line (performance/heavy-import). */
414
+ /**
415
+ * Module specifiers of every `import`, each with its source line (performance/heavy-import,
416
+ * architecture/route-component-import). `type` marks a declaration that contributes **no runtime
417
+ * value binding** — either `import type …`, or one whose every specifier is inline-typed
418
+ * (`import { type A } from …`). A specifier-less side-effect import is not marked: it still loads
419
+ * the module. Optional, so existing external constructors of `ComponentFacts` are unaffected.
420
+ */
317
421
  importSpans: {
318
422
  source: string;
319
423
  line: number;
424
+ type?: true;
320
425
  }[];
321
426
  /** Value `import * as X from '<bare pkg>'` namespace imports (type-only excluded) — performance/namespace-import. */
322
427
  namespaceImports: {
@@ -351,6 +456,11 @@ interface ComponentFacts {
351
456
  type: string;
352
457
  line: number;
353
458
  }[];
459
+ /** `<input type="checkbox">` / `<input type="radio">` elements bound with `bind:value`
460
+ * instead of `bind:checked`/`bind:group` (correctness/checkable-bind-value). */
461
+ checkableBindValues: CheckableBindValueFact[];
462
+ /** Root-relative `<a href>` and `goto()` literals in this component (correctness/base-path-navigation). */
463
+ basePathLinks: BasePathLinkFact[];
354
464
  /** `$effect` calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (correctness/orphan-effect). */
355
465
  orphanEffects: OrphanEffectFact[];
356
466
  /** Svelte lifecycle/context calls guaranteed to run outside component initialisation — module scope in `.svelte.ts`/`.svelte.js` or `<script module>` (correctness/orphan-lifecycle). */
@@ -393,6 +503,21 @@ declare function emptyComponentFacts(file: string): ComponentFacts;
393
503
  */
394
504
  declare function collectComponentFacts(rt: Runtime, cwd: string): Promise<ComponentFacts[]>;
395
505
 
506
+ /**
507
+ * Every file under `src/`, as project-relative paths, sorted. Paths only — nothing is
508
+ * read, so this is the cheaper of the two passes over `src/` (the component collector
509
+ * already walks the same tree and reads every `.svelte`).
510
+ *
511
+ * Directory-shaped rules derive their directory set from these paths' ancestor prefixes
512
+ * rather than globbing a second time; see `architecture/unit-entry-file`. The list is
513
+ * sorted so anything that picks "the first file under a directory" is deterministic.
514
+ *
515
+ * Two properties of the result the directory-shaped rules depend on: a directory containing no file
516
+ * at any depth does not appear among these paths' ancestor prefixes and so does not exist as far as
517
+ * those rules are concerned, and dot directories never appear at all (see `Runtime.glob`).
518
+ */
519
+ declare function collectSourceFiles(rt: Runtime, cwd: string): Promise<string[]>;
520
+
396
521
  /**
397
522
  * Facts parsed from one SvelteKit route/hooks file for the SSR shared-state rules
398
523
  * (the security kit-module rules). Collected by `collectKitModuleFacts` (static/CLI + vite build mode).
@@ -438,6 +563,8 @@ interface KitModuleFacts {
438
563
  line: number;
439
564
  inHandler: boolean;
440
565
  }[];
566
+ /** Root-relative `redirect()` literals in this Kit module (correctness/base-path-navigation). */
567
+ basePathLinks: BasePathLinkFact[];
441
568
  /** Set when this file disables SSR via `export const ssr = false` (inline or same-file alias export) — the declaration's line (seo/ssr-disabled). */
442
569
  ssrDisabled?: {
443
570
  line: number;
@@ -457,19 +584,21 @@ interface KitModuleFacts {
457
584
 
458
585
  /**
459
586
  * Resolve an import specifier to a repo-relative `.svelte.ts`/`.svelte.js` path, or
460
- * undefined when it cannot be a runes module: `$lib/` maps to `src/lib/`, `./`/`../`
461
- * resolve against the importing file's directory; bare packages, other aliases, and
587
+ * undefined when it cannot be a runes module: delegates to `resolveRepoLocalPath` with
588
+ * `aliases` (defaulting to `$lib` `src/lib` when omitted), so a project's declared
589
+ * `kit.alias`/`kit.files.lib` resolve here exactly as they do at rule time; `./`/`../`
590
+ * resolve against the importing file's directory; bare packages, unmatched aliases, and
462
591
  * a relative specifier whose `..` segments escape the repo root are skipped. An
463
592
  * extensionless `…/x.svelte` specifier canonicalises to `….svelte.ts` (security/shared-state-import also
464
593
  * tries the `.js` sibling when matching).
465
594
  */
466
- declare function resolveRunesModuleSpecifier(spec: string, importerFile: string): string | undefined;
595
+ declare function resolveRunesModuleSpecifier(spec: string, importerFile: string, aliases?: readonly KitAlias[]): string | undefined;
467
596
  /**
468
597
  * Parse one SvelteKit route/hooks file's SSR shared-state facts (the security kit-module rules). Uses
469
598
  * the shared wrap parser (`parseModuleProgram`), so reported lines subtract the
470
599
  * 1-line wrap prefix; suppressions are scanned on the unwrapped source.
471
600
  */
472
- declare function parseKitModuleFacts(source: string, filename: string): Omit<KitModuleFacts, 'file' | 'kind'>;
601
+ declare function parseKitModuleFacts(source: string, filename: string, aliases?: readonly KitAlias[]): Omit<KitModuleFacts, 'file' | 'kind'>;
473
602
 
474
603
  /** Fallback facts for a Kit file that fails to read or parse (dev tooling must never throw). */
475
604
  declare function emptyKitModuleFacts(file: string, kind: KitModuleFacts['kind']): KitModuleFacts;
@@ -479,8 +608,11 @@ declare function emptyKitModuleFacts(file: string, kind: KitModuleFacts['kind'])
479
608
  * `src/hooks.server`. `src/lib/server/**` is deliberately NOT scanned — legitimate
480
609
  * module singletons (DB connections, clients) live there (design). A file that
481
610
  * fails to read or parse contributes empty facts instead of aborting the scan.
611
+ *
612
+ * `aliases` is the project's compiled alias list (`Project.kitAliases`); omitted,
613
+ * specifiers resolve through `$lib` → `src/lib` only.
482
614
  */
483
- declare function collectKitModuleFacts(rt: Runtime, cwd: string): Promise<KitModuleFacts[]>;
615
+ declare function collectKitModuleFacts(rt: Runtime, cwd: string, aliases?: readonly KitAlias[]): Promise<KitModuleFacts[]>;
484
616
 
485
617
  /**
486
618
  * The `build: { minify: false }` override, when present as a literal: returns the
@@ -491,6 +623,84 @@ declare function findMinifyDisabled(source: string): {
491
623
  line: number;
492
624
  } | undefined;
493
625
 
626
+ /** What a Vite config says about SvelteKit's own configuration. */
627
+ type ViteKitConfigResult =
628
+ /** No `sveltekit()` call, or one with no argument — `svelte.config` still applies. */
629
+ {
630
+ kind: 'no-plugin-config';
631
+ }
632
+ /** `sveltekit(<something we can't resolve>)` — the effective config is unknowable AND
633
+ * `svelte.config` is provably ignored, so the caller must stay quiet. */
634
+ | {
635
+ kind: 'unresolvable';
636
+ }
637
+ /** `sveltekit({…})` resolved. `base` is unset when the config declares no non-empty base. */
638
+ | {
639
+ kind: 'resolved';
640
+ base?: {
641
+ value?: string;
642
+ };
643
+ };
644
+ /** `kit.alias` and `kit.files.lib` as written, before Kit compiles them into ordered entries. */
645
+ type RawKitAliases = {
646
+ /**
647
+ * `kit.alias` entries in declaration order, `value: null` where the config's value is not a
648
+ * string literal. **Undefined means the key set is unknowable** — a spread or a computed key
649
+ * puts an unknown key at a known position, and an unknown key could shadow anything after it,
650
+ * with no `find` to record that with. The caller then discards every user entry.
651
+ */
652
+ entries?: {
653
+ key: string;
654
+ value: string | null;
655
+ }[];
656
+ /**
657
+ * `kit.files.lib`, in three distinct states: **absent** (`undefined`) — there is no `lib`
658
+ * property, or `files` itself does not resolve to an object literal; a **literal** (the
659
+ * string) — `files.lib` is a string literal; **present but unreadable** (`null`) — the `lib`
660
+ * property exists but its value is not statically a string (e.g. a computed expression). The
661
+ * `null` state must not collapse into "absent": the caller cannot fall back to `src/lib`
662
+ * without risking a wrong answer, because the project may have moved `$lib` to something this
663
+ * parser simply couldn't read.
664
+ */
665
+ filesLib?: string | null;
666
+ };
667
+ /** `kit.alias` and `kit.files.lib` from a `svelte.config.{js,ts}` source. */
668
+ declare function findKitAliasesInSvelteConfig(source: string): RawKitAliases;
669
+ /**
670
+ * The project's compiled alias list, following SvelteKit's config precedence: options passed to
671
+ * the `sveltekit()` Vite plugin make `svelte.config` irrelevant (Kit logs "svelte.config.js is
672
+ * ignored when options are passed via your Vite config"), so aliases are read from
673
+ * `svelte.config` only when the Vite config carries no plugin config. Reading `kit.alias` out of
674
+ * a plugin config is deliberately not done — that costs reach, not correctness, and such a
675
+ * project keeps the resolver's default `$lib` behaviour. Undefined means "no config was read".
676
+ */
677
+ declare function resolveKitAliases(viteConfig: {
678
+ source: string;
679
+ } | undefined, svelteConfig: {
680
+ source: string;
681
+ } | undefined): KitAlias[] | undefined;
682
+ /** `kit.paths.base` from a `svelte.config.{js,ts}` source. */
683
+ declare function findKitPathsBaseInSvelteConfig(source: string): {
684
+ value?: string;
685
+ } | undefined;
686
+ /** SvelteKit config passed to the `sveltekit()` plugin in a Vite config source (since Kit 2.62). */
687
+ declare function findKitPathsBaseInViteConfig(source: string): ViteKitConfigResult;
688
+ /**
689
+ * The project's effective `kit.paths.base`, following SvelteKit's precedence: the `sveltekit()`
690
+ * plugin config when it carries one, otherwise `svelte.config`. `file` is the config the base
691
+ * came from (as passed in by the caller). Undefined means "no base path" — the gate stays shut.
692
+ */
693
+ declare function resolveKitPathsBase(viteConfig: {
694
+ file: string;
695
+ source: string;
696
+ } | undefined, svelteConfig: {
697
+ file: string;
698
+ source: string;
699
+ } | undefined): {
700
+ value?: string;
701
+ file: string;
702
+ } | undefined;
703
+
494
704
  /** A template fragment's child node relevant to value classification: literal text or a `{expr}`. */
495
705
  type TextOrExpr = AST.Text | AST.ExpressionTag;
496
706
  /**
@@ -529,6 +739,184 @@ declare function attrTextOf(attr: AST.Attribute): string | undefined;
529
739
  declare const ROBOTS_SOURCE_PATHS: readonly ["static/robots.txt", "src/routes/robots.txt/+server.ts", "src/routes/robots.txt/+server.js"];
530
740
  /** Locations that satisfy the sitemap.xml project rule (seo/sitemap-xml). */
531
741
  declare const SITEMAP_SOURCE_PATHS: readonly ["static/sitemap.xml", "src/routes/sitemap.xml/+server.ts", "src/routes/sitemap.xml/+server.js"];
742
+ /** Vite's own config resolution order — only the first existing file is the one Vite loads. */
743
+ declare const VITE_CONFIG_FILES: readonly ["vite.config.js", "vite.config.mjs", "vite.config.ts", "vite.config.cjs", "vite.config.mts", "vite.config.cts"];
744
+ /** SvelteKit's config resolution order (`@sveltejs/kit` checks js before ts). */
745
+ declare const SVELTE_CONFIG_FILES: readonly ["svelte.config.js", "svelte.config.ts"];
746
+
747
+ /** The severity a setting selects: `'off'`, an explicit severity, or undefined (leave the built-in). */
748
+ declare function settingSeverity(setting: RuleSetting | undefined): Severity | 'off' | undefined;
749
+ /** The options a setting carries, or undefined for the string forms. */
750
+ declare function settingOptions(setting: RuleSetting | undefined): RuleOptions | undefined;
751
+ /** Drop rules disabled via config (design §6). */
752
+ declare function selectRules(rules: Rule[], config: Config): Rule[];
753
+ /** Apply per-rule severity overrides to results (design §6). */
754
+ declare function applyRuleSeverities(results: Result[], config: Config): Result[];
755
+ /** An override entry with its globs compiled once. Build with `compileOverrides`. */
756
+ interface CompiledOverride {
757
+ routes: RegExp[];
758
+ files: RegExp[];
759
+ rules: Record<string, RuleSetting>;
760
+ }
761
+ /**
762
+ * Compile every override entry's globs to RegExp, once. Callers that match many
763
+ * targets (every component, every route) must hoist this out of their loop.
764
+ */
765
+ declare function compileOverrides(config: Config): CompiledOverride[];
766
+ /**
767
+ * Whether an override entry applies to a target. THE single definition of that
768
+ * question — the result post-pass and in-run option resolution both call it.
769
+ * Sharing this matcher is necessary but not sufficient for a severity override
770
+ * and an option override to select the same files: each caller must also pass
771
+ * the same `target` (route and, critically, `file`) the other path effectively
772
+ * matches against. See Finding 1, docs/superpowers/specs/2026-07-26-rule-options-design.md.
773
+ */
774
+ declare function overrideMatches(o: CompiledOverride, target: {
775
+ route?: string;
776
+ file?: string;
777
+ }): boolean;
778
+ /**
779
+ * Apply route-/file-scoped overrides to results (design 2026-07-18). An entry
780
+ * matches when any `route` glob matches the finding's route id or any `files`
781
+ * glob matches its location (OR). `'off'` removes a matched result entirely —
782
+ * passing seeds included, so scoring and "checks passed" counts behave as if
783
+ * the rule never ran there. A severity value rewrites the result's severity.
784
+ * Entries are evaluated in order (later entries win); within one entry, a
785
+ * rule-id key beats a category key only when it specifies a `severity` — an
786
+ * options-only rule-id key (no `severity`) contributes its options but leaves
787
+ * the category key's severity in force, rather than shadowing it (design
788
+ * 2026-07-26, Finding 2 / second review Finding E).
789
+ */
790
+ declare function applyOverrides(results: Result[], config: Config): Result[];
791
+
792
+ /**
793
+ * Per-rule options: their declaration, resolution, and validation (design
794
+ * 2026-07-26). Deliberately does not import `rule.ts` — `rule.ts` imports
795
+ * `RuleOptionsSpec` from here, so taking `Rule` as a parameter would cycle.
796
+ * Callers pass the id and the spec instead.
797
+ */
798
+
799
+ /**
800
+ * One configurable option. `kind` decides the merge semantics, so no rule
801
+ * writes merge code of its own: `integer` replaces, and the two collection
802
+ * kinds ADD to the built-in default (never replace — see the design doc).
803
+ */
804
+ type RuleOptionSpec = {
805
+ kind: 'integer';
806
+ default: number;
807
+ min?: number;
808
+ max?: number;
809
+ } | {
810
+ kind: 'string-list';
811
+ default: readonly string[];
812
+ } | {
813
+ kind: 'string-map';
814
+ default: Readonly<Record<string, string>>;
815
+ };
816
+ /** A rule's configurable options, keyed by option name. */
817
+ type RuleOptionsSpec = Record<string, RuleOptionSpec>;
818
+ /**
819
+ * Typed reads of a resolved options object. `RuleOptions` values are `unknown`
820
+ * (the map is open-ended by design), so without these every rule would carry
821
+ * its own `o.max as number` cast and the "resolution guarantees the declared
822
+ * kind" invariant would live in a dozen places instead of one. `resolveRuleOptions`
823
+ * always seeds every declared key from the spec default and validation rejects a
824
+ * wrongly-typed value up front, so a mismatch here means a rule read a key it
825
+ * never declared — the `fallback` keeps that a wrong number rather than a crash.
826
+ */
827
+ declare function intOption(options: RuleOptions, key: string, fallback?: number): number;
828
+ /** As `intOption`, for a `string-list` option. */
829
+ declare function listOption(options: RuleOptions, key: string): string[];
830
+ /** As `intOption`, for a `string-map` option. */
831
+ declare function mapOption(options: RuleOptions, key: string): Record<string, string>;
832
+ /**
833
+ * Whether any config layer so much as mentions `ruleId` — its `rules` entry, or any `overrides`
834
+ * entry's.
835
+ *
836
+ * A rule that is inert until declared can return early on `false` instead of resolving options once
837
+ * per target and discarding the result. That waste is not hypothetical: the three directory-shaped
838
+ * Architecture rules resolve per directory, so an unconfigured project pays it for every directory
839
+ * under `src/` three times over, on every dev-server save. Measured 2026-07-30 over a synthetic tree
840
+ * of 1,523 directories: 5.4 ms per analysis, for rules that are off by default and therefore produce
841
+ * nothing.
842
+ *
843
+ * Deliberately conservative. It asks only whether the rule is *mentioned*, not whether the mention
844
+ * resolves to a non-empty value, so a `'off'` severity with no options still answers `true` and the
845
+ * caller does its normal work. A cheaper-but-wrong version of this would make a rule skip work it
846
+ * owed; this one can only ever fail to save time.
847
+ */
848
+ declare function isMentionedAnywhere(config: Config, ruleId: string): boolean;
849
+ /**
850
+ * Effective options for a rule at a target: built-in defaults, then
851
+ * `config.rules[ruleId].options`, then every matching `config.overrides` entry
852
+ * in order. Integers take the last value; lists and maps accumulate.
853
+ *
854
+ * `target` omitted skips overrides entirely (project-scoped rules). Callers
855
+ * resolving many targets should hoist `compileOverrides(config)` and pass it as
856
+ * `compiled` — otherwise every call recompiles the globs.
857
+ */
858
+ declare function resolveRuleOptions(ruleId: string, spec: RuleOptionsSpec | undefined, config: Config, target?: {
859
+ route?: string;
860
+ file?: string;
861
+ }, compiled?: CompiledOverride[]): RuleOptions;
862
+ /**
863
+ * Problems with a user-supplied options object, as human-readable sentences
864
+ * (empty = valid). Callers treat any result as fatal: a typo that silently
865
+ * leaves the config inert is the failure this exists to prevent.
866
+ *
867
+ * `baseline`, when given, is the already-resolved value this `options` layer
868
+ * is being merged onto — built-in defaults merged with any earlier layer(s)
869
+ * (e.g. the global `config.rules[id].options`, when `options` is an
870
+ * `overrides[]` entry). The min/max cross-check below compares against it
871
+ * instead of the spec's own default, so a layer that only sets one side of a
872
+ * range is checked against what it actually inherits (design 2026-07-26
873
+ * review, Finding A). Omit it to check `options` against the spec defaults
874
+ * alone, as when validating the global layer itself. A `baseline` that is
875
+ * only partially resolved (missing `min` or `max`) is treated as "can't
876
+ * determine that side" rather than silently comparing against `undefined` —
877
+ * see the `typeof` guard below.
878
+ *
879
+ * `skipRangeCheck`, when true, skips the min/max cross-check entirely
880
+ * regardless of `baseline`. A caller sets this when it statically cannot
881
+ * rule out that some *other* config layer narrows the opposite side of the
882
+ * range at the same target — see the CLI's and the Vite plugin's
883
+ * `overrides[]` validation (design 2026-07-26 review, Finding A, third
884
+ * pass).
885
+ */
886
+ declare function validateRuleOptions(ruleId: string, spec: RuleOptionsSpec | undefined, options: RuleOptions, baseline?: RuleOptions, skipRangeCheck?: boolean): string[];
887
+ /**
888
+ * Whether `validateRuleOptions` should skip the min/max cross-check for
889
+ * `overrides[selfIndex].rules[key]` — the whole decision, so the CLI's
890
+ * config-file loader and the Vite plugin can't drift apart on it (they held
891
+ * line-for-line copies of it before).
892
+ *
893
+ * An entry that sets both sides, or neither, is judged against its baseline as
894
+ * usual. An entry that sets only one side is skipped when some *other* entry
895
+ * sets the opposite side, since the two may co-apply at a shared target and be
896
+ * valid there — see `otherOverrideNarrowsOppositeSide` for why that is
897
+ * conservative by necessity and what it lets through.
898
+ */
899
+ declare function shouldSkipRangeCheck(overrides: readonly unknown[], selfIndex: number, key: string, setting: unknown): boolean;
900
+ /**
901
+ * Problems with one user-supplied rule setting — the bare severity string or the
902
+ * object form — as human-readable sentences prefixed with `label` (empty = valid).
903
+ * THE single definition of what a setting may look like: the CLI's config-file
904
+ * loader and the Vite plugin both funnel through it, so a config file and the
905
+ * equivalent plugin option are accepted or rejected identically. Callers treat any
906
+ * result as fatal, on the same reasoning as an unknown rule id — a typo that
907
+ * silently leaves the config inert is the failure being prevented.
908
+ *
909
+ * `label` names the setting in the message (e.g. `rules.seo/title-length`,
910
+ * `overrides[0].rules.architecture`); `ruleId` is the key options messages quote.
911
+ * `allowOptions` is false for a category key: a category may carry a severity, but
912
+ * options are rule-specific and meaningless there. `baseline` and `skipRangeCheck`
913
+ * are passed through to `validateRuleOptions`.
914
+ */
915
+ declare function validateRuleSetting(label: string, ruleId: string, setting: unknown, spec: RuleOptionsSpec | undefined, opts: {
916
+ allowOptions: boolean;
917
+ baseline?: RuleOptions;
918
+ skipRangeCheck?: boolean;
919
+ }): string[];
532
920
 
533
921
  /** Input given to every rule. Mode-independent: rules see only ResolvedHead[] (design §8, §10). */
534
922
  interface RuleContext {
@@ -541,6 +929,12 @@ interface RuleContext {
541
929
  components?: ComponentFacts[];
542
930
  /** Per-file SvelteKit route/hooks facts for the SSR shared-state rules (static/CLI + vite build mode only). */
543
931
  kitModules?: KitModuleFacts[];
932
+ /**
933
+ * Every file under `src/`, as project-relative paths, for directory-shaped Architecture rules
934
+ * (static/CLI + vite build mode only). Sorted — see `collectSourceFiles`, which is what both
935
+ * adapters use to build it.
936
+ */
937
+ sourceFiles?: string[];
544
938
  project: Project;
545
939
  config: Config;
546
940
  }
@@ -556,6 +950,8 @@ interface Rule {
556
950
  rationale: string;
557
951
  /** Canonical remediation template, shared by findings and explain_rule (issue #24). */
558
952
  fix?: Fix;
953
+ /** Configurable options for this rule; absent means the rule takes none. */
954
+ options?: RuleOptionsSpec;
559
955
  /**
560
956
  * Evaluate the resolved heads. A single rule may return one Result per route,
561
957
  * so it always returns an array. Project-scoped rules return a single element.
@@ -751,6 +1147,15 @@ declare const correctnessStalePropDerivation: Rule;
751
1147
  */
752
1148
  declare const correctnessNonreactiveBuiltinState: Rule;
753
1149
 
1150
+ /**
1151
+ * correctness/checkable-bind-value — bind:value binds the DOM value property. A
1152
+ * checkbox/radio's user interaction toggles checkedness, which bind:value never observes, so
1153
+ * the bound state is frozen at its initial value and silently never updates in production.
1154
+ * bind:checked (single checkbox) / bind:group (checkbox list, radio group) are the correct
1155
+ * bindings.
1156
+ */
1157
+ declare const correctnessCheckableBindValue: Rule;
1158
+
754
1159
  declare const correctnessOrphanEffect: Rule;
755
1160
 
756
1161
  /**
@@ -761,6 +1166,14 @@ declare const correctnessOrphanEffect: Rule;
761
1166
  */
762
1167
  declare const correctnessOrphanLifecycle: Rule;
763
1168
 
1169
+ /**
1170
+ * correctness/base-path-navigation — root-relative navigation literals in a project that sets
1171
+ * `kit.paths.base`. A custom check because it is gated on a PROJECT fact and its own facts live
1172
+ * on BOTH the component channel (`<a href>`, `goto()`) and the Kit-module channel (`redirect()`).
1173
+ * With no base path configured the rule emits nothing at all — the gate is the whole point.
1174
+ */
1175
+ declare const correctnessBasePathNavigation: Rule;
1176
+
764
1177
  /**
765
1178
  * correctness/server-browser-global — browser globals read in server-executed MODULE code: module scope of
766
1179
  * runes modules / `<script module>`, and Kit route/hooks files (top level, handler
@@ -786,6 +1199,59 @@ declare const architectureComponentSize: Rule;
786
1199
 
787
1200
  declare const architecturePropCount: Rule;
788
1201
 
1202
+ /**
1203
+ * architecture/private-scope-import — a unit inside a declared private scope must not be
1204
+ * imported from outside that scope (design 2026-07-28). L3: the scopes are declared by the
1205
+ * project via the `scopes` option and never inferred, so the rule is inert until then.
1206
+ *
1207
+ * Findings are reported at the import site, not at the imported unit: `--diff` filters
1208
+ * results to the files that changed, and the author of the violation edited the importer.
1209
+ */
1210
+ declare const architecturePrivateScopeImport: Rule;
1211
+
1212
+ /**
1213
+ * architecture/unit-entry-file — a directory declared to be a unit must contain a file named
1214
+ * after it (design 2026-07-28). L3: the declarations come from the project's own `units`,
1215
+ * `pascalCaseUnits` and `exclude` options and are never inferred, so the rule is inert until then.
1216
+ *
1217
+ * The directory set is every ancestor path prefix of every file, so a directory holding only
1218
+ * subdirectories is checked too. Violations report at a file inside the directory rather than at
1219
+ * the directory, because `filterToChangedFiles` keeps only locations git lists as changed.
1220
+ */
1221
+ declare const architectureUnitEntryFile: Rule;
1222
+
1223
+ /**
1224
+ * architecture/directory-naming — a directory must be named in the casing its location declares
1225
+ * (design 2026-07-29). L3: the declarations come from the project's own `directories` and `exclude`
1226
+ * options and are never inferred, so the rule is inert until then.
1227
+ *
1228
+ * Violations report at a file inside the directory rather than at the directory, because
1229
+ * `filterToChangedFiles` keeps only locations git lists as changed and git never lists a directory.
1230
+ *
1231
+ * There are no pass results. `architecture/unit-entry-file` emits one per conforming unit and can
1232
+ * afford to, because it keys the pass on the unit's entry file — a `.svelte` path already present as
1233
+ * a score key. This rule's subject is the directory itself, with no such pre-existing key, and
1234
+ * `computeScore` seeds every distinct `route` at 100 and averages: a pass per directory would add
1235
+ * hundreds of 100s from one `'src/routes/**'` declaration and dilute every real finding.
1236
+ */
1237
+ declare const architectureDirectoryNaming: Rule;
1238
+
1239
+ /**
1240
+ * architecture/reserved-directory-names — a directory's immediate subdirectories may only take names
1241
+ * the project declared for that position (design 2026-07-29). L3: inert until a scope is declared.
1242
+ *
1243
+ * Two option maps, differing in what their key names. A `scopes` key names the parent directly. A
1244
+ * `unitScopes` key names a root, and the rule governs the children of whichever directories beneath
1245
+ * it are units — the shape a glob cannot reach, because units nest to arbitrary depth.
1246
+ *
1247
+ * There are no pass results. `computeScore` seeds every distinct `route` at 100 and averages, and the
1248
+ * subject here is a directory with no pre-existing score key, so a pass per directory would add
1249
+ * hundreds of 100s from one broad declaration and dilute every real finding.
1250
+ */
1251
+ declare const architectureReservedDirectoryNames: Rule;
1252
+
1253
+ declare const architectureRouteComponentImport: Rule;
1254
+
789
1255
  declare const performanceHeavyImport: Rule;
790
1256
 
791
1257
  declare const performanceNamespaceImport: Rule;
@@ -824,6 +1290,15 @@ declare const performanceStateRaw: Rule;
824
1290
 
825
1291
  declare const allRules: Rule[];
826
1292
 
1293
+ /** One configurable option of a rule, flattened for explain_rule's consumers. */
1294
+ interface RuleOptionInfo {
1295
+ name: string;
1296
+ /** `integer` replaces the default; `string-list`/`string-map` are ADDED to it. */
1297
+ kind: RuleOptionSpec['kind'];
1298
+ default: number | readonly string[] | Readonly<Record<string, string>>;
1299
+ min?: number;
1300
+ max?: number;
1301
+ }
827
1302
  interface RuleInfo {
828
1303
  id: string;
829
1304
  title: string;
@@ -832,6 +1307,12 @@ interface RuleInfo {
832
1307
  rationale: string;
833
1308
  docsUrl: string;
834
1309
  fix?: Fix;
1310
+ /**
1311
+ * The rule's configurable options, omitted when it takes none. An agent that
1312
+ * judges a finding to be a threshold disagreement rather than a defect needs
1313
+ * to know the knob exists and what it is called before it can suggest one.
1314
+ */
1315
+ options?: RuleOptionInfo[];
835
1316
  }
836
1317
  /** Look up a rule's static metadata for the MCP explain_rule tool (issue #24). Rule ids are matched exactly (case-sensitive, e.g. "seo/ssr-disabled"). */
837
1318
  declare function explainRule(id: string): RuleInfo | undefined;
@@ -1100,19 +1581,4 @@ declare function escapeHtml(s: string): string;
1100
1581
  */
1101
1582
  declare function safeHref(url: string): string | null;
1102
1583
 
1103
- /** Drop rules disabled via config (design §6). */
1104
- declare function selectRules(rules: Rule[], config: Config): Rule[];
1105
- /** Apply per-rule severity overrides to results (design §6). */
1106
- declare function applyRuleSeverities(results: Result[], config: Config): Result[];
1107
- /**
1108
- * Apply route-/file-scoped overrides to results (design 2026-07-18). An entry
1109
- * matches when any `route` glob matches the finding's route id or any `files`
1110
- * glob matches its location (OR). `'off'` removes a matched result entirely —
1111
- * passing seeds included, so scoring and "checks passed" counts behave as if
1112
- * the rule never ran there. A severity value rewrites the result's severity.
1113
- * Entries are evaluated in order (later entries win); within one entry a
1114
- * rule-id key beats a category key.
1115
- */
1116
- declare function applyOverrides(results: Result[], config: Config): Result[];
1117
-
1118
- export { APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, CHILD_NODE_KEYS, type Category, type Classification, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type KitModuleFacts, type OrphanEffectFact, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type RouteBadge, type Rule, type RuleContext, type RuleInfo, type RuleOverride, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type SuppressionDirective, type TreatDynamicAs, type Value, allRules, applyOverrides, applyRuleSeverities, architectureComponentSize, architecturePropCount, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, computeHealth, computeScore, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findMinifyDisabled, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, lineOf, linkRule, noColorPalette, parseComponentFacts, parseKitModuleFacts, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, summarize, textFromNodes, valueFromNodes };
1584
+ export { APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, CATEGORIES, CHILD_NODE_KEYS, type Category, type Classification, type CompiledOverride, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type KitAlias, type KitModuleFacts, type OrphanEffectFact, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type RawKitAliases, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type RouteBadge, type Rule, type RuleContext, type RuleInfo, type RuleOptionInfo, type RuleOptionSpec, type RuleOptions, type RuleOptionsSpec, type RuleOverride, type RuleSetting, type RuleSettingObject, type Runtime, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type SuppressionDirective, type TreatDynamicAs, VITE_CONFIG_FILES, type Value, type ViteKitConfigResult, allRules, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, compileOverrides, computeHealth, computeScore, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, summarize, textFromNodes, validateRuleOptions, validateRuleSetting, valueFromNodes };