@starci/eslint-canon-fe 9.7.0 → 9.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.8.1 - 2026-10-31
4
+
5
+ - Changed: the bundled canon pins name @starci/grammar, eslint-grammar-core and eslint-grammar-offset-pop 0.19.0, eslint-canon-be 3.11.2, eslint-canon-fe 9.8.1, stylelint-canon 2.4.0 and hfs 4.16.1 (RELEASE_VERSION_NOT_BUMPED: 9.8.0 is on npm).
6
+
7
+ ## 9.8.0 - 2026-10-31
8
+
9
+ - Added: `no-spacing-utility` (R350, `FE_SPACING_UTILITY`): no padding, margin, gap or space utility (variants and arbitrary values included) in an app class string; the message names the role prop to use. It ships in the closed counting cohort (`COUNTING` of `src/lib/config.ts`) at warn for this release.
10
+ - Changed: `nested-control-variant` refuses the pill (`variant="secondary"`) on `Tabs` wherever it is set, and no longer refuses `variant="primary"` inside a surface: tabs are the underline bar everywhere.
11
+
3
12
  ## 9.7.0 - 2026-10-31
4
13
 
5
14
  - Added: R349 `no-hand-built-panes` (`FE_HAND_BUILT_PANES`, surface rules, slot-free): a multi-pane frame is a `PaneGrid`, so a vertical Grammar `Divider` used as a column seam (among the plain columns of a flex row, or anywhere inside a `SurfaceCard composition="joined"`) and a joined card that holds a flex row of two or more plain columns are refused.
package/README.md CHANGED
@@ -38,7 +38,7 @@ configuration for source that has not adopted the product composition laws.
38
38
 
39
39
  The [surface composition law](docs/surface-rules.md) (R318) holds ANATOMY-2 and PADDING-0 for every
40
40
  family that publishes the shared HeroUI surface and field API: `nested-control-variant` refuses static
41
- variants on layer-aware fields and primary Tabs inside painted Grammar surfaces (`FE_NESTED_VARIANT`),
41
+ variants on layer-aware fields and the pill (`variant="secondary"`) on Tabs, which are the underline bar (`FE_NESTED_VARIANT`),
42
42
  `no-app-surface-paint` keeps surface paint in Grammar (`FE_GRAMMAR_LAYOUT_ONLY`) and
43
43
  `no-list-panel-padding` refuses horizontal padding wrappers around Grammar lists and section captions
44
44
  within the nearest painted surface, a `Pane` or a `PaneSection` included (`FE_LIST_PANEL_PADDING`), and
@@ -46,6 +46,12 @@ within the nearest painted surface, a `Pane` or a `PaneSection` included (`FE_LI
46
46
  columns inside a joined card, where `PaneGrid` owns both (`FE_HAND_BUILT_PANES`). The R65 budget law's
47
47
  `no-app-relative-clock` leaves live relative time and countdowns to Grammar's shared clock.
48
48
 
49
+ The spacing law (R350) leaves every distance to Grammar: `no-spacing-utility` refuses a padding, margin, gap
50
+ or space utility (`p-*`, `px-*`, `m*-*`, `gap-*`, `space-*`, arbitrary values and variants included) in an app class
51
+ string, where a role prop names the relation instead (`Stack`/`Cluster` `gap`, `Grid` `kind`, `PaneSection`, `Page`;
52
+ `FE_SPACING_UTILITY`). It runs in the counting cohort (`COUNTING` of `src/lib/config.ts`): `starci app lint` reports
53
+ and counts every finding at warn and fails on none, while `starci app codemod spacing-roles` migrates the app.
54
+
49
55
  The [button intent law](docs/button-intent.md) (CTA-6) requires every app Button and IconButton to
50
56
  declare intent and leave variants to Grammar. It contains destructive confirms in their same-file
51
57
  dialog, counts mutually exclusive render paths separately and reserves commit for the view's
package/dist/index.d.ts CHANGED
@@ -37,9 +37,10 @@ export declare const rules: Record<string, Rule>;
37
37
  * The levels this canon asks for, as the plugin's own opinion.
38
38
  *
39
39
  * Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
40
- * to warn teaches every later author that the boundary is optional and is not a supported rollout.
40
+ * to warn teaches every later author that the boundary is optional and is not a supported rollout. The one
41
+ * exception is the closed counting cohort of lib/config.ts (`COUNTING`), an owner-ruled warn for one release.
41
42
  */
42
- export declare const recommended: Record<string, "error">;
43
+ export declare const recommended: Record<string, "error" | "warn">;
43
44
  /** Flat-config options that make inline directives ineffective rather than merely forbidden. */
44
45
  export { linterOptions } from "./lib/config.js";
45
46
  export { loadHfs } from "./lib/hfs.js";
package/dist/index.js CHANGED
@@ -45,7 +45,8 @@ import { recommended as translationRecommended, rules as translationRules } from
45
45
  import { recommended as typeSafetyRecommended, rules as typeSafetyRules } from "./type-safety.js";
46
46
  import { recommended as projectGraphRecommended, rules as projectGraphRules } from "./project-graph.js";
47
47
  import { recommended as sonarParityRecommended, rules as sonarParityRules } from "./sonar-parity.js";
48
- import { buildFeConfig, buildPlainSourceConfig } from "./lib/config.js";
48
+ import { recommended as spacingRecommended, rules as spacingRules } from "./spacing.js";
49
+ import { buildFeConfig, buildPlainSourceConfig, COUNTING } from "./lib/config.js";
49
50
  import { why } from "./lib/why.js";
50
51
  import { recommended as uiRulingsRecommended, rules as uiRulingsRules } from "./ui-rulings.js";
51
52
  /** Each law's contribution, kept separate so a duplicate name is detectable rather than silent. */
@@ -89,6 +90,7 @@ const CONTRIBUTIONS = [
89
90
  { law: "transport", rules: transportRules, recommended: transportRecommended },
90
91
  { law: "type-safety", rules: typeSafetyRules, recommended: typeSafetyRecommended },
91
92
  { law: "sonar-parity", rules: sonarParityRules, recommended: sonarParityRecommended },
93
+ { law: "spacing", rules: spacingRules, recommended: spacingRecommended },
92
94
  ];
93
95
  /** Every gathered law. */
94
96
  export const lawOwners = CONTRIBUTIONS.map((entry) => entry.law);
@@ -120,7 +122,8 @@ export const rules = Object.fromEntries(CONTRIBUTIONS.flatMap((entry) => Object.
120
122
  * The levels this canon asks for, as the plugin's own opinion.
121
123
  *
122
124
  * Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
123
- * to warn teaches every later author that the boundary is optional and is not a supported rollout.
125
+ * to warn teaches every later author that the boundary is optional and is not a supported rollout. The one
126
+ * exception is the closed counting cohort of lib/config.ts (`COUNTING`), an owner-ruled warn for one release.
124
127
  */
125
128
  export const recommended = Object.fromEntries(CONTRIBUTIONS.flatMap((entry) => Object.entries(entry.recommended)));
126
129
  /** Flat-config options that make inline directives ineffective rather than merely forbidden. */
@@ -223,7 +226,7 @@ export const reactHooksRules = (reactHooks) => {
223
226
  */
224
227
  export const starciFeConfig = ({ hfs } = {}) => {
225
228
  const published = Object.keys(rules).map((name) => `starci-fe/${name}`);
226
- const notRequested = published.filter((name) => recommended[name] !== "error");
229
+ const notRequested = published.filter((name) => recommended[name] !== (name in COUNTING ? "warn" : "error"));
227
230
  if (notRequested.length > 0) {
228
231
  throw new Error(`starciFeConfig: these published rules are not requested at error: ${notRequested.join(", ")}`);
229
232
  }
@@ -27,6 +27,15 @@ export declare const IGNORED: readonly string[];
27
27
  * and the Sonar rules that need the checker: S1874 (a deprecated API), S6551 (an object turned into text) and S7503 (an `async` function that never awaits).
28
28
  */
29
29
  export declare const BORROWED: Readonly<Record<string, "error">>;
30
+ /**
31
+ * The counting cohort: a rule the owner ruled to ship at `warn` for one release while a codemod moves apps onto the API
32
+ * it asks for, each with the HFS rule id it enforces. `starci app lint` reports and counts its findings and never fails
33
+ * on them; the next cohort moves the rule to error and deletes its entry. No other rule may be below error.
34
+ */
35
+ export declare const COUNTING: Readonly<Record<string, {
36
+ readonly rule: string;
37
+ readonly ruling: string;
38
+ }>>;
30
39
  /**
31
40
  * The flat config of one front-end repository.
32
41
  *
@@ -40,6 +40,14 @@ export const BORROWED = Object.freeze({
40
40
  "@typescript-eslint/require-await": "error",
41
41
  });
42
42
  const levelOf = (setting) => (Array.isArray(setting) ? setting[0] : setting);
43
+ /**
44
+ * The counting cohort: a rule the owner ruled to ship at `warn` for one release while a codemod moves apps onto the API
45
+ * it asks for, each with the HFS rule id it enforces. `starci app lint` reports and counts its findings and never fails
46
+ * on them; the next cohort moves the rule to error and deletes its entry. No other rule may be below error.
47
+ */
48
+ export const COUNTING = Object.freeze({
49
+ "starci-fe/no-spacing-utility": { rule: "R350", ruling: "spacing-roles" },
50
+ });
43
51
  /**
44
52
  * The flat config of one front-end repository.
45
53
  *
@@ -60,9 +68,9 @@ export const buildFeConfig = ({ hfs, plugin, source, reactHooks, tsPlugin, sonar
60
68
  throw new Error(`starciFeConfig lints the fe side of an app (its eslint.config.mjs lives in fe/); this view is ${hfs.side ? `the ${hfs.side} side` : "the app root"}`);
61
69
  if (Object.keys(source).length === 0)
62
70
  throw new Error("starciFeConfig received an empty source recommendation - a config with no rules is not adoption");
63
- const weak = Object.entries(source).filter(([, setting]) => levelOf(setting) !== "error").map(([name]) => name);
71
+ const weak = Object.entries(source).filter(([name, setting]) => levelOf(setting) !== (name in COUNTING ? "warn" : "error")).map(([name]) => name);
64
72
  if (weak.length > 0)
65
- throw new Error(`starciFeConfig refuses source rules not at error: ${weak.join(", ")}`);
73
+ throw new Error(`starciFeConfig refuses source rules not at error (or not at warn in the counting cohort): ${weak.join(", ")}`);
66
74
  const settings = { starci: { hfs } };
67
75
  return [
68
76
  { ignores: [...IGNORED] },
@@ -21,6 +21,8 @@ export type Comment = TSESTree.Comment;
21
21
  export type Rules = Readonly<Record<string, Rule<string, readonly unknown[]>>>;
22
22
  /** The level a law asks for each of its rules, by `starci-fe/<name>`. */
23
23
  export type Recommended = Readonly<Record<string, "error">>;
24
+ /** The level of a rule in its counting cohort (lib/config.ts `COUNTING`): reported and counted, never failing. */
25
+ export type CountingRecommended = Readonly<Record<string, "warn">>;
24
26
  /**
25
27
  * A rule with its message ids and options inferred from the literal: `defineRule({ meta: { messages: { a: "..." } }, ... })`
26
28
  * types `context.report({ messageId })` against `a`. It returns the object it is given.
package/dist/lib/why.d.ts CHANGED
@@ -32,6 +32,11 @@ export declare const why: {
32
32
  en: string;
33
33
  fix: string;
34
34
  };
35
+ "no-spacing-utility": {
36
+ code: string;
37
+ en: string;
38
+ fix: string;
39
+ };
35
40
  "no-hand-built-panes": {
36
41
  code: string;
37
42
  en: string;
package/dist/lib/why.js CHANGED
@@ -32,6 +32,11 @@ export const why = {
32
32
  en: "`<file>` insets a horizontal Grammar divider with wrapper padding.",
33
33
  fix: "Move horizontal padding to the sibling content bands so the divider reaches both surface edges.",
34
34
  },
35
+ "no-spacing-utility": {
36
+ code: "FE_SPACING_UTILITY",
37
+ en: "`<file>` writes a padding, margin, gap or space utility (`<what>`): a distance the Grammar owns.",
38
+ fix: "Name the relation instead: a Stack or Cluster gap role, a Grid kind, or the inset of the Grammar box that holds the content.",
39
+ },
35
40
  "no-hand-built-panes": {
36
41
  code: "FE_HAND_BUILT_PANES",
37
42
  en: "`<file>` draws a multi-pane frame by hand: a vertical Divider between columns of a flex row, or a flex row of columns inside a joined SurfaceCard.",
@@ -0,0 +1,18 @@
1
+ import { type CountingRecommended } from "./lib/rule.js";
2
+ /** The role a Tailwind gap step names, as the spacing-roles codemod maps it; a step with no role is a judgement. */
3
+ export declare const GAP_STEP_ROLE: Readonly<Record<string, string>>;
4
+ /** The utility a class names once its variants (`sm:`, `hover:`, `[&>*]:`) and importance (`!`) are dropped. */
5
+ export declare const baseUtility: (cls: string) => string;
6
+ /** Every spacing utility of a class string, in the order written. */
7
+ export declare const spacingUtilities: (text: string) => string[];
8
+ /** No padding, margin, gap or space utility in an app class string: the relation is named, the step is the Grammar's. */
9
+ export declare const noSpacingUtility: import("./lib/rule.js").Rule<"utility", []>;
10
+ /** The rules of the spacing law. */
11
+ export declare const rules: {
12
+ "no-spacing-utility": import("./lib/rule.js").Rule<"utility", []>;
13
+ };
14
+ /**
15
+ * The rule runs in its counting cohort (lib/config.ts `COUNTING`): every finding is reported and counted by `starci app
16
+ * lint`, none fails the gate, while `starci app codemod spacing-roles` moves the app onto role props.
17
+ */
18
+ export declare const recommended: CountingRecommended;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Spacing is the Grammar's (HFS R350): an app names the relation between the blocks it places through a role prop
3
+ * (`<Stack gap="section">`, `<Cluster gap="inline">`, `<Grid kind="cards">`, `PaneSection`, `Page`), and the Grammar
4
+ * resolves the role to its step. A padding, margin, gap or space utility in an app class string is a second owner of
5
+ * a distance the Grammar already owns.
6
+ */
7
+ import { classTextVisitors } from "./lib/class-text.js";
8
+ import { exemptFreestyle } from "./lib/freestyle.js";
9
+ import { defineRule } from "./lib/rule.js";
10
+ /** The spacing utility families: padding, margin, gap and the space-between utilities, each side and axis. */
11
+ const SPACING_UTILITY = /^-?(?:p|px|py|pt|pb|ps|pe|pl|pr|m|mx|my|mt|mb|ms|me|ml|mr|gap|gap-x|gap-y|space-x|space-y)-(.+)$/;
12
+ /** The role a Tailwind gap step names, as the spacing-roles codemod maps it; a step with no role is a judgement. */
13
+ export const GAP_STEP_ROLE = Object.freeze({
14
+ "0": "seam (a joined face or a divider)",
15
+ "0.5": "identity",
16
+ "1": "identity",
17
+ "1.5": "identity",
18
+ "2": "inline",
19
+ "2.5": "inline",
20
+ "3": "row",
21
+ "4": "section",
22
+ "5": "section",
23
+ "6": "region",
24
+ "8": "region",
25
+ });
26
+ /** The utility a class names once its variants (`sm:`, `hover:`, `[&>*]:`) and importance (`!`) are dropped. */
27
+ export const baseUtility = (cls) => {
28
+ let depth = 0;
29
+ let start = 0;
30
+ for (let index = 0; index < cls.length; index += 1) {
31
+ const char = cls[index];
32
+ if (char === "[" || char === "(")
33
+ depth += 1;
34
+ else if (char === "]" || char === ")")
35
+ depth -= 1;
36
+ else if (char === ":" && depth === 0)
37
+ start = index + 1;
38
+ }
39
+ return cls.slice(start).replace(/^!|!$/g, "");
40
+ };
41
+ /** Every spacing utility of a class string, in the order written. */
42
+ export const spacingUtilities = (text) => text.split(/\s+/).filter((cls) => SPACING_UTILITY.test(baseUtility(cls)));
43
+ /** What to write instead of one spacing utility. */
44
+ const instead = (cls) => {
45
+ const base = baseUtility(cls);
46
+ const match = SPACING_UTILITY.exec(base);
47
+ const family = base.replace(/^-/, "").split("-")[0] ?? "";
48
+ if (family === "gap" || family === "space") {
49
+ const role = GAP_STEP_ROLE[match?.[1] ?? ""];
50
+ return role === undefined
51
+ ? "the role of the relation on a Stack, Cluster or Grid gap"
52
+ : `gap="${role}" on the Stack or Cluster that holds these siblings`;
53
+ }
54
+ if (family.startsWith("m"))
55
+ return "the parent's gap (a Stack or Cluster role); the Grammar alone places a margin";
56
+ return "the inset of the Grammar box that holds this content (SurfaceCard, PaneSection, Page, Dialog); a box of your own takes none";
57
+ };
58
+ /** No padding, margin, gap or space utility in an app class string: the relation is named, the step is the Grammar's. */
59
+ export const noSpacingUtility = exemptFreestyle(defineRule({
60
+ meta: {
61
+ type: "suggestion",
62
+ docs: { description: "Spacing is the Grammar's: an app names the relation through a role prop, never a spacing utility." },
63
+ schema: [],
64
+ messages: {
65
+ utility: "`{{cls}}` writes a distance the Grammar owns. Use {{instead}} (R350 `FE_SPACING_UTILITY`).",
66
+ },
67
+ },
68
+ create(context) {
69
+ return classTextVisitors(context, (node, text) => {
70
+ if (!text)
71
+ return;
72
+ for (const cls of spacingUtilities(text))
73
+ context.report({ node, messageId: "utility", data: { cls, instead: instead(cls) } });
74
+ });
75
+ },
76
+ }));
77
+ /** The rules of the spacing law. */
78
+ export const rules = { "no-spacing-utility": noSpacingUtility };
79
+ /**
80
+ * The rule runs in its counting cohort (lib/config.ts `COUNTING`): every finding is reported and counted by `starci app
81
+ * lint`, none fails the gate, while `starci app codemod spacing-roles` moves the app onto role props.
82
+ */
83
+ export const recommended = { "starci-fe/no-spacing-utility": "warn" };
@@ -289,11 +289,11 @@ const inSectionCaption = (context, node) => {
289
289
  export const nestedControlVariant = defineRule({
290
290
  meta: {
291
291
  type: "problem",
292
- docs: { description: "ANATOMY-2: Grammar context supplies field variants; nested Tabs cannot force primary." },
292
+ docs: { description: "ANATOMY-2: Grammar context supplies field variants; Tabs are the underline bar, never the pill." },
293
293
  schema: [],
294
294
  messages: {
295
295
  variant: "FE_NESTED_VARIANT: app fields cannot set a static variant. Omit variant so Grammar's surface context supplies the appropriate default.",
296
- tabs: "FE_NESTED_VARIANT: Tabs inside a Grammar surface cannot force variant=\"primary\". Omit variant so Grammar supplies secondary.",
296
+ tabs: "FE_NESTED_VARIANT: Tabs are the underline bar, their labels text on the content edge; variant=\"secondary\" is the pill, a filter's shape. Omit variant, or filter a collection with a selectable TagGroup.",
297
297
  },
298
298
  },
299
299
  create(context) {
@@ -308,9 +308,9 @@ export const nestedControlVariant = defineRule({
308
308
  context.report({ node: variant, messageId: "variant" });
309
309
  }
310
310
  else if (name[0] === "Tabs") {
311
- const primary = node.attributes.find((entry) => propStrings(context, entry, "variant", "variant").some((value) => value.text === "primary"));
312
- if (primary && [...surfaceAncestors(context, node.parent)].some((ancestor) => isSurface(context, ancestor, node)))
313
- context.report({ node: primary, messageId: "tabs" });
311
+ const pill = node.attributes.find((entry) => propStrings(context, entry, "variant", "variant").some((value) => value.text === "secondary"));
312
+ if (pill)
313
+ context.report({ node: pill, messageId: "tabs" });
314
314
  }
315
315
  },
316
316
  };
@@ -4,7 +4,7 @@ Law module: `surface-rules.mjs`. HFS rule: R318. Canon: ANATOMY-2 and PADDING-0.
4
4
 
5
5
  ## `starci-fe/nested-control-variant`
6
6
 
7
- Finding code: `FE_NESTED_VARIANT`. Grammar context supplies field variants on the page background and inside painted surfaces. App code omits every static `variant` on layer-aware fields, including fields rendered by imported or helper components. Named import aliases, namespace imports, static constants and props spreads preserve Grammar ownership. Explicit primary `Tabs` inside a painted surface is also refused; its default follows the surface context.
7
+ Finding code: `FE_NESTED_VARIANT`. Grammar context supplies field variants on the page background and inside painted surfaces. App code omits every static `variant` on layer-aware fields, including fields rendered by imported or helper components. Named import aliases, namespace imports, static constants and props spreads preserve Grammar ownership. `Tabs` are the underline bar everywhere, their labels text on the content edge, so the pill (`variant="secondary"`) is refused wherever it is set: a pill is a filter's shape, and a filter over a collection is a selectable `TagGroup`.
8
8
 
9
9
  Invalid:
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/eslint-canon-fe",
3
- "version": "9.7.0",
3
+ "version": "9.8.1",
4
4
  "description": "Universal front-end lint laws for TypeScript and Next.js, independent of the selected Grammar family.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,42 +31,42 @@ pins:
31
31
  source: packages/event-stream/package.json
32
32
  why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
33
33
  '@starci/grammar':
34
- version: 0.17.0
34
+ version: 0.19.0
35
35
  group: starci
36
36
  install: registry
37
37
  side: fe
38
38
  source: packages/grammar/package.json
39
39
  why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
40
40
  '@starci/eslint-canon-be':
41
- version: 3.11.0
41
+ version: 3.11.2
42
42
  group: starci
43
43
  install: registry
44
44
  side: be
45
45
  source: packages/eslint-canon-be/package.json
46
46
  why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
47
47
  '@starci/eslint-canon-fe':
48
- version: 9.7.0
48
+ version: 9.8.1
49
49
  group: starci
50
50
  install: registry
51
51
  side: fe
52
52
  source: packages/eslint-canon-fe/package.json
53
53
  why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
54
54
  '@starci/eslint-grammar-core':
55
- version: 0.17.0
55
+ version: 0.19.0
56
56
  group: starci
57
57
  install: registry
58
58
  side: fe
59
59
  source: packages/eslint-grammar-core/package.json
60
60
  why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
61
61
  '@starci/eslint-grammar-offset-pop':
62
- version: 0.17.0
62
+ version: 0.19.0
63
63
  group: starci
64
64
  install: registry
65
65
  side: fe
66
66
  source: packages/eslint-grammar-offset-pop/package.json
67
67
  why: 'the Offset Pop Grammar component API lint, versioned with @starci/grammar; its shared component contract comes from the StarCi family lint without copying rules.'
68
68
  '@starci/stylelint-canon':
69
- version: 2.3.0
69
+ version: 2.4.0
70
70
  group: starci
71
71
  install: registry
72
72
  side: fe
@@ -106,7 +106,7 @@ pins:
106
106
  source: packages/ai-router/package.json
107
107
  why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
108
108
  '@starci/hfs':
109
- version: 4.15.0
109
+ version: 4.16.1
110
110
  group: starci
111
111
  install: registry
112
112
  side: both