@starci/hfs 4.7.46 → 4.8.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.8.0 - 2026-10-27
4
+
5
+ - Removed: the Heritage family from the bundled Grammar catalog.
6
+ - Changed: the bundled canon pins name each @starci package by its folder in starci-harness (`source: packages/<name>/package.json`) at the versions there; `@starci/cli` is a registry-only pin.
7
+ - Removed: the specs that exercised the legacy runtime's own scripts (css-rules, grammar-knowledge, grammar-union-census, grammar-vendor-repaints, ui-proof-*); that code is not part of this package.
8
+ - Changed: the package lives in github.com/starci183/starci-harness under `packages/` and publishes from there; its bundled `runtime/` is committed source, edited in place (no generator, no `prepack`/`pretest` hook).
9
+ - Changed: owner ruling: the Grammar catalog has exactly two families, `core` and `offset-pop`. `grammarLintFor`,
10
+ `grammarFamilyFor`, `grammarContractFor`, `resolveGrammarFamily` and the code-pattern family binding
11
+ (`familyCanons.core`) answer by those ids; `starci app` scaffolds `sides.fe.grammar: core`; the core lint package is
12
+ `@starci/eslint-grammar-core` with `coreGrammarConfig`, and the knowledge folder is `knowledge/grammars/core`.
13
+ - Removed: `starci` and `starci-core` are not family ids and have no alias; a selection naming them is refused.
14
+
15
+ ## 4.7.47 - 2026-10-27
16
+
17
+ - Fixed: the rendered stack escapes every literal dollar once more, so a secret-reading command or healthcheck survives the second interpolation of docker stack deploy.
18
+
3
19
  ## 4.7.46 - 2026-10-26
4
20
 
5
21
  - Changed: owner ruling push-main-after-scoped-check reaches product apps: the generated pre-push hook passes a fast-forward main push when the hooks pass and the secret scan is clean, a v* tag must be annotated, and app CI runs on pushes to main.
package/README.md CHANGED
@@ -192,15 +192,10 @@ The upload authenticates with GitHub's OIDC token: the managed `ci.yml` job has
192
192
 
193
193
  ## Maintaining the bundle
194
194
 
195
- `runtime/` is a byte copy of the slot loader files, the pins, and the import closure of `scripts/hfs/check.mjs` and
196
- `scripts/hfs/architecture.mjs` (computed by `scripts/hfs/sync-runtime.mjs`, so a new import of the machine is bundled without
197
- editing a list), plus the catalog slice of every code `starci app check` can emit: its own and the machine's (`ARCHITECTURE_RULE_IDS`,
198
- derived from the machine's rule id lists). After changing any of those files, `knowledge/hfs/slots.yaml`,
199
- `knowledge/hfs/canon-pins.yaml`, `knowledge/patterns/fe/folder.yaml` or the catalog entries of those codes, run `starci release sync-runtime`;
200
- `starci release sync-runtime --check` fails on a stale copy. Bump `version` here and in the pin when the behaviour changes.
201
-
202
- The examples workflow (`.github/workflows/examples.yml`) runs `starci app lint` of this CLI at the root of every `examples/*`
203
- app with an `hfs.json` (each `examples/<app>` root) and fails on any finding or any tool that could not run. This includes the ESLint canons and repository checks also run by `starci app check`, plus stylelint over the frontend stylesheets.
195
+ `runtime/` holds the slot loader files, the pins, the import closure of `scripts/hfs/check.mjs` and
196
+ `scripts/hfs/architecture.mjs`, and the catalog slice of every code `starci app check` can emit. It is committed source of
197
+ this package, edited in place ([packages/README.md](../README.md) says how the bundled copies move together). Bump
198
+ `version` here and in the pin when the behaviour changes.
204
199
 
205
200
  ## Serving knowledge to other packages
206
201
 
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@starci/hfs",
3
- "version": "4.7.46",
3
+ "version": "4.8.0",
4
4
  "description": "The StarCi app command implementation for scaffolding, linting, checking and synchronizing one product repository.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "author": "starci183",
8
+ "homepage": "https://github.com/starci183/starci-harness/tree/main/packages/hfs#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/starci183/starci-harness.git",
12
+ "directory": "packages/hfs"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/starci183/starci-harness/issues"
16
+ },
7
17
  "private": false,
8
18
  "exports": {
9
19
  ".": "./src/main.mjs",
@@ -60,10 +70,6 @@
60
70
  "access": "public"
61
71
  },
62
72
  "scripts": {
63
- "sync": "node ../../scripts/hfs/sync-runtime.mjs",
64
- "sync:check": "node ../../scripts/hfs/sync-runtime.mjs --check",
65
- "prepack": "node prep-runtime.mjs",
66
- "pretest": "node prep-runtime.mjs",
67
73
  "test": "node --test \"src/*.spec.mjs\""
68
74
  }
69
75
  }
@@ -1,5 +1,5 @@
1
1
  schema: starci/knowledge-source@1
2
- id: grammar.starci.playbook
2
+ id: grammar.core.playbook
3
3
  title: StarCi Core — playbook
4
4
  purpose: |
5
5
  Which idioms a business shape asks for, and what a supplied reference is allowed to contribute to
@@ -12,7 +12,7 @@ purpose: |
12
12
  has yet shown them twice; `guidance` holds the rules every Core app built on them follows.
13
13
  appliesTo:
14
14
  - grammar
15
- family: starci
15
+ family: core
16
16
  provenance:
17
17
  publicEntry: "@starci/grammar/common"
18
18
  package: "@starci/grammar"
@@ -269,7 +269,7 @@ shapes:
269
269
  by presentation.groupIdentity.
270
270
  notes: |
271
271
  FE-RULE-OWNERSHIP in knowledge/patterns/fe/index.yaml separates universal app-shell semantics
272
- from these family APIs, values, shapes and effects. @starci/eslint-grammar-starci owns their
272
+ from these family APIs, values, shapes and effects. @starci/eslint-grammar-core owns their
273
273
  code checks. Inspect installed Grammar exports and the running shell when proving the recipe.
274
274
 
275
275
  readingSteps:
@@ -24,7 +24,7 @@ guidance:
24
24
  Do not treat this catalog as selecting a default Grammar family. Family choice belongs to the
25
25
  current design specification and its explicit sides.fe.grammar declaration.
26
26
  rationale: |
27
- Silent fallback to StarCi would invent product direction and override an absent/ambiguous design.
27
+ Silent fallback to a family would invent product direction and override an absent/ambiguous design.
28
28
  - id: verify-before-use
29
29
  requirement: |
30
30
  Before applying snapshot APIs or recorded gaps, inspect the selected repository's package
@@ -47,11 +47,11 @@ topics:
47
47
  Generated DNA snapshot of the `@starci/grammar/common` public entry: custom properties,
48
48
  the 110 renderers of its registry, their classes, stamped rule ids and recorded gaps. The DNA owns the observed package
49
49
  version and source inventory. Common supplies anatomy, not colour. The snapshot is drift-checked.
50
- - id: starci
51
- path: starci/index.yaml
52
- title: StarCi Core Grammar
50
+ - id: core
51
+ path: core/index.yaml
52
+ title: Core Grammar
53
53
  summary: |
54
- Historical snapshot of the StarCi Core family — DNA, idioms, playbook (including composing the
54
+ Historical snapshot of the core family — DNA, idioms, playbook (including composing the
55
55
  0.5.0 forms, overlays and navigation chrome) and family boundaries.
56
56
  - id: offset-pop
57
57
  path: offset-pop/index.yaml
@@ -61,16 +61,7 @@ topics:
61
61
  shadow, one pink decision accent with a text-safe accent for text — with DNA, idioms, a
62
62
  mobile-first playbook for playful learning or consumer apps, and family boundaries including two
63
63
  decisions flagged for the family owner.
64
- - id: heritage
65
- path: heritage/DNA.yaml
66
- title: Heritage Grammar
67
- summary: |
68
- Generated snapshot of the Heritage family's parity declaration, arrangement, scoped tokens and Common renderers.
69
64
  families:
70
- - id: heritage
71
- title: heritage
72
- contractRef: common
73
- packageSnapshot: heritage/DNA.yaml
74
65
  - id: common
75
66
  title: common
76
67
  componentContract:
@@ -85,18 +76,18 @@ families:
85
76
  (`defineGrammarFamily`), not a family that ships a palette, so `authority`, `reusableStyle`,
86
77
  `productComposition` and `index` have nothing to name here. Universal semantics, renderer anatomy,
87
78
  state and accessibility remain owned by knowledge/ui.
88
- - id: starci
89
- title: starci
79
+ - id: core
80
+ title: core
90
81
  contractRef: common
91
82
  lint:
92
- package: '@starci/eslint-grammar-starci'
93
- factory: starciGrammarConfig
83
+ package: '@starci/eslint-grammar-core'
84
+ factory: coreGrammarConfig
94
85
  prefix: starci-grammar
95
- authority: starci/family.yaml
96
- packageSnapshot: starci/DNA.yaml
97
- reusableStyle: starci/idioms.yaml
98
- productComposition: starci/playbook.yaml
99
- index: starci/index.yaml
86
+ authority: core/family.yaml
87
+ packageSnapshot: core/DNA.yaml
88
+ reusableStyle: core/idioms.yaml
89
+ productComposition: core/playbook.yaml
90
+ index: core/index.yaml
100
91
  - id: offset-pop
101
92
  title: offset-pop
102
93
  contractRef: common
@@ -289,14 +289,14 @@ guidance:
289
289
  presentation:
290
290
  font:
291
291
  ownership: FE-RULE-OWNERSHIP
292
- inherits: starci
292
+ inherits: core
293
293
  contract: |
294
294
  Offset Pop keeps Common typography renderers and props. Its font proof reads the shared Common
295
295
  rank-to-renderer recipes in the StarCi playbook; the Offset Pop stylesheet owns its family palette
296
296
  and display treatment.
297
297
  padding:
298
298
  ownership: FE-RULE-OWNERSHIP
299
- inherits: starci
299
+ inherits: core
300
300
  contract: |
301
301
  Offset Pop keeps the same Common spacing and component geometry. Its padding proof reads the
302
302
  shared Common recipes declared in the StarCi playbook; it supplies no replacement inset scale.
@@ -21,7 +21,7 @@ pins:
21
21
  install: registry
22
22
  side: fe
23
23
  source: packages/heroicons/package.json
24
- why: 'StarCi custom Heroicons-compatible cuts; eslint-grammar-starci (icon.mjs) admits @starci/heroicons/24/outline and /16/solid as icon sources, so an app that uses them gets this exact version.'
24
+ why: 'StarCi custom Heroicons-compatible cuts; eslint-grammar-core (icon.mjs) admits @starci/heroicons/24/outline and /16/solid as icon sources, so an app that uses them gets this exact version.'
25
25
  '@starci/event-stream':
26
26
  version: 0.1.0
27
27
  group: starci
@@ -30,61 +30,61 @@ pins:
30
30
  source: packages/event-stream/package.json
31
31
  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.'
32
32
  '@starci/grammar':
33
- version: 0.12.13
33
+ version: 0.13.0
34
34
  group: starci
35
35
  install: registry
36
36
  side: fe
37
37
  source: packages/grammar/package.json
38
38
  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.'
39
39
  '@starci/eslint-canon-be':
40
- version: 3.4.55
40
+ version: 3.4.57
41
41
  group: starci
42
42
  install: registry
43
43
  side: be
44
- source: packages/eslint/be/package.json
44
+ source: packages/eslint-canon-be/package.json
45
45
  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.'
46
46
  '@starci/eslint-canon-fe':
47
- version: 9.1.9
47
+ version: 9.2.0
48
48
  group: starci
49
49
  install: registry
50
50
  side: fe
51
- source: packages/eslint/fe/package.json
51
+ source: packages/eslint-canon-fe/package.json
52
52
  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.'
53
- '@starci/eslint-grammar-starci':
54
- version: 0.12.13
53
+ '@starci/eslint-grammar-core':
54
+ version: 0.13.0
55
55
  group: starci
56
56
  install: registry
57
57
  side: fe
58
- source: packages/eslint/grammar-starci/package.json
58
+ source: packages/eslint-grammar-core/package.json
59
59
  why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
60
60
  '@starci/eslint-grammar-offset-pop':
61
- version: 0.12.13
61
+ version: 0.13.0
62
62
  group: starci
63
63
  install: registry
64
64
  side: fe
65
- source: packages/eslint/grammar-offset-pop/package.json
65
+ source: packages/eslint-grammar-offset-pop/package.json
66
66
  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.'
67
67
  '@starci/stylelint-canon':
68
- version: 2.0.13
68
+ version: 2.1.0
69
69
  group: starci
70
70
  install: registry
71
71
  side: fe
72
- source: packages/stylelint/package.json
72
+ source: packages/stylelint-canon/package.json
73
73
  why: 'the front-end stylelint canon: no-class-selector refuses a compound class selector (`div.card`); the brand layer accepts `--font-sans` and `--font-mono`; status-contrast measures the HeroUI soft pairs per theme; brand-layer-shape reads the shared :root,.light,.dark block and the info soft pair; appTokens come from loadAppTokens(import.meta.url) for the managed one-line stylelint.config.mjs.'
74
74
  '@starci/tsconfig':
75
- version: 2.0.7
75
+ version: 2.0.8
76
76
  group: starci
77
77
  install: registry
78
78
  side: both
79
79
  source: packages/tsconfig/package.json
80
80
  '@starci/prettier-config':
81
- version: 1.0.4
81
+ version: 1.0.5
82
82
  group: starci
83
83
  install: registry
84
84
  side: both
85
85
  source: packages/prettier-config/package.json
86
86
  '@starci/jest-preset':
87
- version: 2.3.6
87
+ version: 2.3.7
88
88
  group: starci
89
89
  install: registry
90
90
  side: be
@@ -98,14 +98,13 @@ pins:
98
98
  source: packages/test-world/package.json
99
99
  why: 'the shared e2e library of every back end (R47, R48), a devDependency of every back end: the warm stack behind toxiproxy, the network-edge fakes, the Nest boot, the typed useTestWorld handle, useSandbox for contract specs and the outage lock. Kafka is real own infra (the one digest-pinned apache/kafka KRaft image, a broker listener and proxy per slot, slot-prefixed topics, consumer groups and client ids) and Postgres is schema-per-context (a schema and a login role per context in a shared database). Each worker has its own data slot (<ns>_w<k>) with its own databases, Keycloak realm, leased Redis DB, bucket/collection/topic prefixes, fakes host, toxiproxy proxies and outage lock; a run provisions min(jest workers, workers cap, default 2) slots, and state file protocol 2 pairs exactly with @starci/jest-preset (TEST_WORLD_PAIR_MISMATCH otherwise). A modules world boots real peer apps beside its modules, world.apps.<name>.during(fn) is the outage of a peer app, w.keycloak.clientSecret(client) answers the run-generated secret of a confidential realm client, world.keycloak.events/sessions read the realm user events and live sessions, world.infra.postgresql.connection(name) takes one database connection down while the others serve, and world.resolve and scope.resolve take any Nest token. Every path a declaration names (`stack`, seeds, the realm, Dockerfiles) resolves from the app root (the directory of hfs.json); the globalSetup registers the path aliases as TypeScript resolves them. Its CLI main is exported at ./cli for starci app stack.'
100
100
  '@starci/cli':
101
- version: 1.7.46
101
+ version: 1.7.47
102
102
  group: starci
103
103
  install: registry
104
104
  side: both
105
- source: packages/cli/package.json
106
105
  why: 'the only StarCi command package; product repositories install it for starci app, while @starci/hfs remains its pinned transitive implementation.'
107
106
  '@starci/hfs':
108
- version: 4.7.46
107
+ version: 4.8.0
109
108
  group: starci
110
109
  install: registry
111
110
  side: both
@@ -406,7 +405,7 @@ pins:
406
405
  version: 2.2.0
407
406
  group: ui
408
407
  side: fe
409
- why: 'the base of the one application icon set (fe-stack icons, with @starci/heroicons for the glyphs it lacks); eslint-grammar-starci (icon.mjs) admits @heroicons/react/24/outline and /16/solid.'
408
+ why: 'the base of the one application icon set (fe-stack icons, with @starci/heroicons for the glyphs it lacks); eslint-grammar-core (icon.mjs) admits @heroicons/react/24/outline and /16/solid.'
410
409
  '@dicebear/core':
411
410
  version: 10.4.0
412
411
  group: ui
@@ -251,7 +251,7 @@ concerns:
251
251
  why: the canon lint and format configuration.
252
252
  - id: grammar-lint
253
253
  tier: opt
254
- packages: ['@starci/eslint-grammar-starci', '@starci/eslint-grammar-offset-pop']
254
+ packages: ['@starci/eslint-grammar-core', '@starci/eslint-grammar-offset-pop']
255
255
  home: [fe.source-root-pinned]
256
256
  pattern: FE-RULE-OWNERSHIP
257
257
  forbids: []
@@ -29,7 +29,7 @@ gates:
29
29
  enforcerKinds:
30
30
  eslint-be: a rule of the plugin @starci/eslint-canon-be (id without the plugin prefix)
31
31
  eslint-fe: a universal rule of @starci/eslint-canon-fe (id without the plugin prefix)
32
- eslint-grammar-starci: a selected Grammar family rule sharing the common component API, resolved through the family canon binding (id without the plugin prefix; FE-RULE-OWNERSHIP)
32
+ eslint-grammar-core: a selected Grammar family rule sharing the common component API, resolved through the family canon binding (id without the plugin prefix; FE-RULE-OWNERSHIP)
33
33
  stylelint: a rule of the @starci/stylelint-canon package
34
34
  machine: a check of the architecture machine (scripts/hfs/architecture), which reads the import graph
35
35
  hfs: a check of the hfs CLI that reads the repository tree, configuration, contracts or catalogs
@@ -1273,49 +1273,49 @@ rules:
1273
1273
  - {kind: stylelint, id: breakpoint-scale}
1274
1274
  - {kind: stylelint, id: source-resolves}
1275
1275
  - {kind: eslint-fe, id: no-raw-brand-value}
1276
- - {kind: eslint-grammar-starci, id: no-fractional-step}
1276
+ - {kind: eslint-grammar-core, id: no-fractional-step}
1277
1277
  - {kind: eslint-fe, id: no-arbitrary-value}
1278
- - {kind: eslint-grammar-starci, id: no-hand-rolled-heading}
1279
- - {kind: eslint-grammar-starci, id: no-unresolved-token-class}
1280
- - {kind: eslint-grammar-starci, id: no-heading-tag-outside-heading-component}
1281
- - {kind: eslint-grammar-starci, id: status-text-uses-soft-foreground}
1278
+ - {kind: eslint-grammar-core, id: no-hand-rolled-heading}
1279
+ - {kind: eslint-grammar-core, id: no-unresolved-token-class}
1280
+ - {kind: eslint-grammar-core, id: no-heading-tag-outside-heading-component}
1281
+ - {kind: eslint-grammar-core, id: status-text-uses-soft-foreground}
1282
1282
  proves:
1283
1283
  - {path: packages/stylelint/source-resolves.spec.mjs, editions: [lite, full], codes: [FE_STYLE_SOURCE_UNRESOLVED]}
1284
1284
  - {path: packages/eslint/fe/brand-values.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1285
1285
  - {path: packages/eslint/fe/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1286
- - {path: packages/eslint/grammar-starci/status-colors.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1287
- - {path: packages/eslint/grammar-starci/typography.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1288
- - {path: packages/eslint/grammar-starci/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1286
+ - {path: packages/eslint/grammar-core/status-colors.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1287
+ - {path: packages/eslint/grammar-core/typography.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1288
+ - {path: packages/eslint/grammar-core/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1289
1289
  exemplifiedBy:
1290
1290
  - {path: packages/hfs/templates/fe/skeleton-lite/apps/web/src/app/globals.css, edition: lite}
1291
1291
  - {path: packages/hfs/templates/fe/skeleton/apps/app/src/app/globals.css, edition: full}
1292
1292
  - {path: packages/stylelint/source-resolves.spec.mjs, editions: [lite, full], codes: [FE_STYLE_SOURCE_UNRESOLVED]}
1293
1293
  - {path: packages/eslint/fe/brand-values.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1294
1294
  - {path: packages/eslint/fe/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1295
- - {path: packages/eslint/grammar-starci/status-colors.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1296
- - {path: packages/eslint/grammar-starci/typography.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1297
- - {path: packages/eslint/grammar-starci/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1295
+ - {path: packages/eslint/grammar-core/status-colors.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1296
+ - {path: packages/eslint/grammar-core/typography.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1297
+ - {path: packages/eslint/grammar-core/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1298
1298
  witness:
1299
1299
  - {path: packages/stylelint/source-resolves.spec.mjs, editions: [lite, full], codes: [FE_STYLE_SOURCE_UNRESOLVED]}
1300
1300
  - {path: packages/eslint/fe/brand-values.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1301
1301
  - {path: packages/eslint/fe/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1302
- - {path: packages/eslint/grammar-starci/status-colors.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1303
- - {path: packages/eslint/grammar-starci/typography.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1304
- - {path: packages/eslint/grammar-starci/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1302
+ - {path: packages/eslint/grammar-core/status-colors.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1303
+ - {path: packages/eslint/grammar-core/typography.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1304
+ - {path: packages/eslint/grammar-core/tokens.spec.mjs, editions: [lite, full], codes: [FE_STYLE_TOKEN_ONLY]}
1305
1305
  - id: R62
1306
1306
  code: "FE_NATIVE_FORM_CONTROL"
1307
1307
  editions: [lite, full]
1308
1308
  docs: [docs/architecture.md#r62, knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP]
1309
1309
  proves:
1310
- - {path: packages/eslint/grammar-starci/r62-structural-elements-coverage.spec.mjs, editions: [lite, full], codes: [FE_NATIVE_FORM_CONTROL]}
1310
+ - {path: packages/eslint/grammar-core/r62-structural-elements-coverage.spec.mjs, editions: [lite, full], codes: [FE_NATIVE_FORM_CONTROL]}
1311
1311
  - {path: packages/eslint/fe/r62-native-controls-coverage.spec.mjs, editions: [lite, full]}
1312
1312
  exemplifiedBy:
1313
- - {path: packages/eslint/grammar-starci/r62-structural-elements-coverage.spec.mjs, editions: [lite, full], codes: [FE_NATIVE_FORM_CONTROL]}
1313
+ - {path: packages/eslint/grammar-core/r62-structural-elements-coverage.spec.mjs, editions: [lite, full], codes: [FE_NATIVE_FORM_CONTROL]}
1314
1314
  - {path: packages/hfs/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx, edition: lite}
1315
1315
  - {path: packages/hfs/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/component.tsx, edition: full}
1316
1316
  - {path: packages/eslint/fe/r62-native-controls-coverage.spec.mjs, editions: [lite, full]}
1317
1317
  witness:
1318
- - {path: packages/eslint/grammar-starci/r62-structural-elements-coverage.spec.mjs, editions: [lite, full], codes: [FE_NATIVE_FORM_CONTROL]}
1318
+ - {path: packages/eslint/grammar-core/r62-structural-elements-coverage.spec.mjs, editions: [lite, full], codes: [FE_NATIVE_FORM_CONTROL]}
1319
1319
  - {path: packages/eslint/fe/r62-native-controls-coverage.spec.mjs, edition: lite, codes: [FE_NATIVE_FORM_CONTROL, FE_NATIVE_IMAGE]}
1320
1320
  - {path: packages/eslint/fe/r62-native-controls-coverage.spec.mjs, edition: full, codes: [FE_NATIVE_FORM_CONTROL, FE_NATIVE_IMAGE]}
1321
1321
  law: "Product tiers compose the selected Grammar renderers for form controls, images and structural elements that the Grammar owns; they never reproduce those renderers with raw HTML."
@@ -1325,7 +1325,7 @@ rules:
1325
1325
  enforcers:
1326
1326
  - {kind: eslint-fe, id: no-native-form-control}
1327
1327
  - {kind: eslint-fe, id: no-native-img}
1328
- - {kind: eslint-grammar-starci, id: no-raw-structural-element}
1328
+ - {kind: eslint-grammar-core, id: no-raw-structural-element}
1329
1329
  - {kind: eslint-fe, id: image-has-size}
1330
1330
 
1331
1331
  - id: R63
@@ -1878,7 +1878,7 @@ rules:
1878
1878
  - {kind: eslint-fe, id: base-props-atom}
1879
1879
  - {kind: eslint-fe, id: base-import-pair}
1880
1880
  - {kind: eslint-fe, id: no-data-status-shape}
1881
- - {kind: eslint-grammar-starci, id: slot-status-through-slotview}
1881
+ - {kind: eslint-grammar-core, id: slot-status-through-slotview}
1882
1882
  - {kind: eslint-fe, id: presentational-purity}
1883
1883
  - {kind: eslint-fe, id: connected-block-has-presentational-twin}
1884
1884
  - {kind: eslint-fe, id: no-resting-twin-component}
@@ -1891,18 +1891,18 @@ rules:
1891
1891
  editions: [lite, full]
1892
1892
  docs: [docs/architecture.md#r92, knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP]
1893
1893
  proves:
1894
- - {path: packages/eslint/grammar-starci/r92-slot-status-coverage.spec.mjs, editions: [lite, full], codes: [FE_COMPONENT_API]}
1894
+ - {path: packages/eslint/grammar-core/r92-slot-status-coverage.spec.mjs, editions: [lite, full], codes: [FE_COMPONENT_API]}
1895
1895
  - {path: packages/eslint/fe/r92-fetch-barrel.spec.mjs, editions: [lite, full]}
1896
1896
  - {path: packages/eslint/fe/r92-component-surface-coverage.spec.mjs, editions: [lite, full]}
1897
1897
  - {path: packages/eslint/fe/r56-r92-graph-coverage.spec.mjs, editions: [lite, full]}
1898
1898
  exemplifiedBy:
1899
- - {path: packages/eslint/grammar-starci/r92-slot-status-coverage.spec.mjs, editions: [lite, full], codes: [FE_COMPONENT_API]}
1899
+ - {path: packages/eslint/grammar-core/r92-slot-status-coverage.spec.mjs, editions: [lite, full], codes: [FE_COMPONENT_API]}
1900
1900
  - {path: packages/hfs/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/component.tsx, editions: [lite, full]}
1901
1901
  - {path: packages/eslint/fe/r92-fetch-barrel.spec.mjs, editions: [lite, full]}
1902
1902
  - {path: packages/eslint/fe/r92-component-surface-coverage.spec.mjs, editions: [lite, full]}
1903
1903
  - {path: packages/eslint/fe/r56-r92-graph-coverage.spec.mjs, editions: [lite, full]}
1904
1904
  witness:
1905
- - {path: packages/eslint/grammar-starci/r92-slot-status-coverage.spec.mjs, editions: [lite, full], codes: [FE_COMPONENT_API]}
1905
+ - {path: packages/eslint/grammar-core/r92-slot-status-coverage.spec.mjs, editions: [lite, full], codes: [FE_COMPONENT_API]}
1906
1906
  - {path: packages/eslint/fe/r92-fetch-barrel.spec.mjs, editions: [lite, full]}
1907
1907
  - {path: packages/eslint/fe/r92-component-surface-coverage.spec.mjs, editions: [lite, full]}
1908
1908
  - {path: packages/eslint/fe/r56-r92-graph-coverage.spec.mjs, editions: [lite, full]}
@@ -1913,29 +1913,29 @@ rules:
1913
1913
  gates: [pre-commit, settle, land, ci]
1914
1914
  failureCodes: ["FE_VENDOR_BOUNDARY", "ARCH_GRAMMAR_CONTRACT_INVALID", "ARCH_GRAMMAR_EXPORT_BYPASS"]
1915
1915
  enforcers:
1916
- - {kind: eslint-grammar-starci, id: no-vendor-icon-outside-icon-leaf}
1917
- - {kind: eslint-grammar-starci, id: heroicons-is-the-glyph-vendor}
1918
- - {kind: eslint-grammar-starci, id: no-off-scale-glyph-size}
1919
- - {kind: eslint-grammar-starci, id: no-icon-size-class}
1920
- - {kind: eslint-grammar-starci, id: destructive-menu-entry-needs-danger-tone}
1921
- - {kind: eslint-grammar-starci, id: no-decorative-icon-in-metric-cell}
1922
- - {kind: eslint-grammar-starci, id: vendor-primitive-has-named-owner}
1916
+ - {kind: eslint-grammar-core, id: no-vendor-icon-outside-icon-leaf}
1917
+ - {kind: eslint-grammar-core, id: heroicons-is-the-glyph-vendor}
1918
+ - {kind: eslint-grammar-core, id: no-off-scale-glyph-size}
1919
+ - {kind: eslint-grammar-core, id: no-icon-size-class}
1920
+ - {kind: eslint-grammar-core, id: destructive-menu-entry-needs-danger-tone}
1921
+ - {kind: eslint-grammar-core, id: no-decorative-icon-in-metric-cell}
1922
+ - {kind: eslint-grammar-core, id: vendor-primitive-has-named-owner}
1923
1923
  - {kind: eslint-fe, id: grammar-entry}
1924
1924
  - {kind: machine, id: grammar, at: scripts/hfs/architecture/frontend-grammar.mjs}
1925
1925
  editions: [lite, full]
1926
1926
  docs: ["docs/code-pattern-enforcement.md#r93-vendor-and-grammar-ownership", knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP]
1927
1927
  templateNa: "Vendor boundary violations and installed Grammar package contracts are checked against consumer ownership; no managed template scaffolds a vendor install or a forbidden import."
1928
1928
  proves:
1929
- - packages/eslint/grammar-starci/r93-vendor-coverage.spec.mjs
1929
+ - packages/eslint/grammar-core/r93-vendor-coverage.spec.mjs
1930
1930
  - packages/eslint/fe/r93-grammar-ownership.spec.mjs
1931
- - packages/eslint/grammar-starci/menu-entries.spec.mjs
1931
+ - packages/eslint/grammar-core/menu-entries.spec.mjs
1932
1932
  exemplifiedBy:
1933
- - {path: packages/eslint/grammar-starci/r93-vendor-coverage.spec.mjs, editions: [lite, full]}
1933
+ - {path: packages/eslint/grammar-core/r93-vendor-coverage.spec.mjs, editions: [lite, full]}
1934
1934
  - {path: packages/eslint/fe/r93-grammar-ownership.spec.mjs, editions: [lite, full]}
1935
- - {path: packages/eslint/grammar-starci/menu-entries.spec.mjs, editions: [lite, full]}
1935
+ - {path: packages/eslint/grammar-core/menu-entries.spec.mjs, editions: [lite, full]}
1936
1936
  witness:
1937
- - {path: packages/eslint/grammar-starci/menu-entries.spec.mjs, editions: [lite, full], codes: [FE_VENDOR_BOUNDARY]}
1938
- - {path: packages/eslint/grammar-starci/r93-vendor-coverage.spec.mjs, editions: [lite, full], codes: [FE_VENDOR_BOUNDARY]}
1937
+ - {path: packages/eslint/grammar-core/menu-entries.spec.mjs, editions: [lite, full], codes: [FE_VENDOR_BOUNDARY]}
1938
+ - {path: packages/eslint/grammar-core/r93-vendor-coverage.spec.mjs, editions: [lite, full], codes: [FE_VENDOR_BOUNDARY]}
1939
1939
  - {path: packages/eslint/fe/r93-grammar-ownership.spec.mjs, editions: [lite, full], codes: [ARCH_GRAMMAR_CONTRACT_INVALID, ARCH_GRAMMAR_EXPORT_BYPASS]}
1940
1940
  - id: R94
1941
1941
  code: "FE_SLOT_FILE_ROLE"
@@ -5006,20 +5006,20 @@ rules:
5006
5006
  enforcers:
5007
5007
  - {kind: eslint-fe, id: no-copy-in-call}
5008
5008
  - {kind: eslint-fe, id: no-alert}
5009
- - {kind: eslint-grammar-starci, id: no-inline-svg-outside-brand-mark-leaf}
5009
+ - {kind: eslint-grammar-core, id: no-inline-svg-outside-brand-mark-leaf}
5010
5010
  editions: [lite, full]
5011
5011
  docs: [docs/quality-bar.md#r286, knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP]
5012
5012
  exemplifiedBy:
5013
5013
  - {path: packages/eslint/fe/translation.spec.mjs, editions: [lite, full], codes: [FE_COPY_IN_CALL, FE_NO_ALERT]}
5014
- - {path: packages/eslint/grammar-starci/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
5014
+ - {path: packages/eslint/grammar-core/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
5015
5015
  - {path: packages/hfs/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx, editions: [lite, full]}
5016
5016
  - {path: packages/hfs/templates/fe/skeleton/apps/__app__/src/modules/toast, editions: [lite, full]}
5017
5017
  proves:
5018
5018
  - {path: packages/eslint/fe/translation.spec.mjs, editions: [lite, full], codes: [FE_COPY_IN_CALL, FE_NO_ALERT]}
5019
- - {path: packages/eslint/grammar-starci/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
5019
+ - {path: packages/eslint/grammar-core/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
5020
5020
  witness:
5021
5021
  - {path: packages/eslint/fe/translation.spec.mjs, editions: [lite, full], codes: [FE_COPY_IN_CALL, FE_NO_ALERT]}
5022
- - {path: packages/eslint/grammar-starci/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
5022
+ - {path: packages/eslint/grammar-core/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
5023
5023
 
5024
5024
  - id: R287
5025
5025
  code: "FE_CALLBACK_SHAPE"
@@ -5476,19 +5476,19 @@ rules:
5476
5476
  gates: [pre-commit, settle, land, ci]
5477
5477
  failureCodes: ["FE_APP_SHELL_FRAME"]
5478
5478
  enforcers:
5479
- - {kind: eslint-grammar-starci, id: app-shell-frame}
5479
+ - {kind: eslint-grammar-core, id: app-shell-frame}
5480
5480
  editions: [lite, full]
5481
5481
  docs:
5482
5482
  - knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP
5483
5483
  - {path: knowledge/ui/composition/app-shell.yaml, editions: [lite, full]}
5484
5484
  proves:
5485
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_FRAME]}
5485
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_FRAME]}
5486
5486
  exemplifiedBy:
5487
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_FRAME]}
5487
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_FRAME]}
5488
5488
  - {path: examples/full-app/fe/apps/app/src/features/layouts/ShopLayout/component.tsx, edition: full}
5489
5489
  - {path: examples/lite-app/fe/apps/web/src/features/layouts/WorkspaceLayout/component.tsx, edition: lite}
5490
5490
  witness:
5491
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_FRAME]}
5491
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_FRAME]}
5492
5492
  templateNa: "The app composes its shell from product destinations and identity; full and lite example shells provide the reference."
5493
5493
  - id: R314
5494
5494
  code: "FE_NAVBAR_CONTROL_SIZE"
@@ -5497,19 +5497,19 @@ rules:
5497
5497
  gates: [pre-commit, settle, land, ci]
5498
5498
  failureCodes: ["FE_NAVBAR_CONTROL_SIZE"]
5499
5499
  enforcers:
5500
- - {kind: eslint-grammar-starci, id: navbar-control-size}
5500
+ - {kind: eslint-grammar-core, id: navbar-control-size}
5501
5501
  editions: [lite, full]
5502
5502
  docs:
5503
5503
  - knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP
5504
5504
  - {path: knowledge/ui/composition/app-shell.yaml, editions: [lite, full]}
5505
5505
  proves:
5506
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CONTROL_SIZE]}
5506
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CONTROL_SIZE]}
5507
5507
  exemplifiedBy:
5508
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CONTROL_SIZE]}
5508
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CONTROL_SIZE]}
5509
5509
  - {path: examples/full-app/fe/packages/ecommerce-ui/src/branches/WorkspaceNavbar/component.tsx, edition: full}
5510
5510
  - {path: examples/lite-app/fe/apps/web/src/components/branches/Navbar/component.tsx, edition: lite}
5511
5511
  witness:
5512
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CONTROL_SIZE]}
5512
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CONTROL_SIZE]}
5513
5513
  templateNa: "The app composes its shell from product destinations and identity; full and lite example shells provide the reference."
5514
5514
  - id: R315
5515
5515
  code: "FE_NAVBAR_CLUSTER"
@@ -5518,19 +5518,19 @@ rules:
5518
5518
  gates: [pre-commit, settle, land, ci]
5519
5519
  failureCodes: ["FE_NAVBAR_CLUSTER"]
5520
5520
  enforcers:
5521
- - {kind: eslint-grammar-starci, id: navbar-cluster}
5521
+ - {kind: eslint-grammar-core, id: navbar-cluster}
5522
5522
  editions: [lite, full]
5523
5523
  docs:
5524
5524
  - knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP
5525
5525
  - {path: knowledge/ui/composition/app-shell.yaml, editions: [lite, full]}
5526
5526
  proves:
5527
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CLUSTER]}
5527
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CLUSTER]}
5528
5528
  exemplifiedBy:
5529
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CLUSTER]}
5529
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CLUSTER]}
5530
5530
  - {path: examples/full-app/fe/packages/ecommerce-ui/src/branches/WorkspaceNavbar/component.tsx, edition: full}
5531
5531
  - {path: examples/lite-app/fe/apps/web/src/components/branches/Navbar/component.tsx, edition: lite}
5532
5532
  witness:
5533
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CLUSTER]}
5533
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CLUSTER]}
5534
5534
  templateNa: "The app composes its shell from product destinations and identity; full and lite example shells provide the reference."
5535
5535
  - id: R316
5536
5536
  code: "FE_APP_SHELL_BOTTOM_TABS"
@@ -5539,19 +5539,19 @@ rules:
5539
5539
  gates: [pre-commit, settle, land, ci]
5540
5540
  failureCodes: ["FE_APP_SHELL_BOTTOM_TABS"]
5541
5541
  enforcers:
5542
- - {kind: eslint-grammar-starci, id: no-shell-bottom-tabs}
5542
+ - {kind: eslint-grammar-core, id: no-shell-bottom-tabs}
5543
5543
  editions: [lite, full]
5544
5544
  docs:
5545
5545
  - knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP
5546
5546
  - {path: knowledge/ui/composition/app-shell.yaml, editions: [lite, full]}
5547
5547
  proves:
5548
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_BOTTOM_TABS]}
5548
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_BOTTOM_TABS]}
5549
5549
  exemplifiedBy:
5550
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_BOTTOM_TABS]}
5550
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_BOTTOM_TABS]}
5551
5551
  - {path: examples/full-app/fe/apps/app/src/features/layouts/ShopLayout/component.tsx, edition: full}
5552
5552
  - {path: examples/lite-app/fe/apps/web/src/features/layouts/WorkspaceLayout/component.tsx, edition: lite}
5553
5553
  witness:
5554
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_BOTTOM_TABS]}
5554
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_APP_SHELL_BOTTOM_TABS]}
5555
5555
  templateNa: "The app composes its shell from product destinations and identity; full and lite example shells provide the reference."
5556
5556
  - id: R317
5557
5557
  code: "FE_NAVBAR_CREATE"
@@ -5560,19 +5560,19 @@ rules:
5560
5560
  gates: [pre-commit, settle, land, ci]
5561
5561
  failureCodes: ["FE_NAVBAR_CREATE"]
5562
5562
  enforcers:
5563
- - {kind: eslint-grammar-starci, id: no-navbar-create}
5563
+ - {kind: eslint-grammar-core, id: no-navbar-create}
5564
5564
  editions: [lite, full]
5565
5565
  docs:
5566
5566
  - knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP
5567
5567
  - {path: knowledge/ui/composition/app-shell.yaml, editions: [lite, full]}
5568
5568
  proves:
5569
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CREATE]}
5569
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CREATE]}
5570
5570
  exemplifiedBy:
5571
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CREATE]}
5571
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CREATE]}
5572
5572
  - {path: examples/full-app/fe/packages/ecommerce-ui/src/branches/WorkspaceNavbar/component.tsx, edition: full}
5573
5573
  - {path: examples/lite-app/fe/apps/web/src/components/branches/Navbar/component.tsx, edition: lite}
5574
5574
  witness:
5575
- - {path: packages/eslint/grammar-starci/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CREATE]}
5575
+ - {path: packages/eslint/grammar-core/app-shell.spec.mjs, editions: [lite, full], codes: [FE_NAVBAR_CREATE]}
5576
5576
  templateNa: "The app composes its shell from product destinations and identity; full and lite example shells provide the reference."
5577
5577
  - id: R318
5578
5578
  code: "FE_NESTED_VARIANT"
@@ -2130,7 +2130,7 @@ FE_APP_SHELL_BOTTOM_TABS:
2130
2130
  meaning_vi: "Khung ứng dụng tại <file> đặt điều hướng trong thanh tab dưới thay vì Sidebar drawer."
2131
2131
  causes_vi:
2132
2132
  - "Cấu trúc shell không theo canon ứng dụng."
2133
- nextStep_vi: "Dùng hàng mở drawer bên dưới navbar và Sidebar presentation=\"drawer\" (R316; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2133
+ nextStep_vi: "Dùng hàng mở drawer bên dưới navbar và Sidebar presentation=\"drawer\" (R316; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2134
2134
  owner: op-retry
2135
2135
  kind: check-finding
2136
2136
 
@@ -2140,7 +2140,7 @@ FE_APP_SHELL_FRAME:
2140
2140
  meaning_vi: "Khung ứng dụng tại <file> không giao bố cục và cuộn cho WorkspaceShell."
2141
2141
  causes_vi:
2142
2142
  - "Cấu trúc shell không theo canon ứng dụng."
2143
- nextStep_vi: "Ghép khung ứng dụng bằng WorkspaceShell viewport=\"fixed\" (R313; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2143
+ nextStep_vi: "Ghép khung ứng dụng bằng WorkspaceShell viewport=\"fixed\" (R313; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2144
2144
  owner: op-retry
2145
2145
  kind: check-finding
2146
2146
 
@@ -2237,7 +2237,7 @@ FE_COMPONENT_API:
2237
2237
  - "Component nhận className từ ngoài"
2238
2238
  - "Khối kết nối không có bản trình bày thuần"
2239
2239
  - "Trạng thái dữ liệu tự chế thay vì qua SlotView"
2240
- nextStep_vi: "Sửa chữ ký component theo luật drawing và tách phần trình bày; op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra (R92; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2240
+ nextStep_vi: "Sửa chữ ký component theo luật drawing và tách phần trình bày; op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra (R92; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2241
2241
  owner: op-retry
2242
2242
  kind: check-finding
2243
2243
 
@@ -2530,7 +2530,7 @@ FE_ICON_INLINE_SVG:
2530
2530
  causes_vi:
2531
2531
  - "Leaf Icon tự vẽ path hoặc SVG thay vì dùng Heroicons"
2532
2532
  - "Component vẽ SVG trực tiếp mà không phải leaf nhãn hiệu được khai báo"
2533
- nextStep_vi: "Thay glyph bằng Heroicons qua leaf Icon; nếu là nhãn hiệu, đặt nó trong leaf nhãn hiệu và khai báo đúng chủ của leaf (R286; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2533
+ nextStep_vi: "Thay glyph bằng Heroicons qua leaf Icon; nếu là nhãn hiệu, đặt nó trong leaf nhãn hiệu và khai báo đúng chủ của leaf (R286; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2534
2534
  owner: op-retry
2535
2535
  kind: check-finding
2536
2536
 
@@ -2561,7 +2561,7 @@ FE_NATIVE_FORM_CONTROL:
2561
2561
  meaning_vi: "`<tag>` thô ở `<file>`. Dùng renderer của grammar."
2562
2562
  causes_vi:
2563
2563
  - "Vi phạm luật R62: Không `<select>/<input>/<textarea>/<button>`, `<img>` thô và không thẻ cấu trúc thô (`<section>`, `<p>`, `<ul>`, `<h1>`...) ở tầng sản phẩm; dùng renderer grammar."
2564
- nextStep_vi: "Op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra và mục của luật trong tiêu chuẩn HFS; nợ cũ cần quyết định thiết kế nên op không thêm ngoại lệ, chỉ tách hoặc chuyển về đúng chủ (R62; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2564
+ nextStep_vi: "Op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra và mục của luật trong tiêu chuẩn HFS; nợ cũ cần quyết định thiết kế nên op không thêm ngoại lệ, chỉ tách hoặc chuyển về đúng chủ (R62; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2565
2565
  owner: op-retry
2566
2566
  kind: check-finding
2567
2567
 
@@ -2581,7 +2581,7 @@ FE_NAVBAR_CLUSTER:
2581
2581
  meaning_vi: "Cụm navbar tại <file> không giữ thứ tự ngôn ngữ, giao diện, thông báo và tài khoản."
2582
2582
  causes_vi:
2583
2583
  - "Cấu trúc shell không theo canon ứng dụng."
2584
- nextStep_vi: "Ghép IconButton ngôn ngữ, Switch giao diện, IconButton chuông và AvatarButton theo đúng thứ tự (R315; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2584
+ nextStep_vi: "Ghép IconButton ngôn ngữ, Switch giao diện, IconButton chuông và AvatarButton theo đúng thứ tự (R315; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2585
2585
  owner: op-retry
2586
2586
  kind: check-finding
2587
2587
 
@@ -2591,7 +2591,7 @@ FE_NAVBAR_CONTROL_SIZE:
2591
2591
  meaning_vi: "Điều khiển trong navbar tại <file> không khai báo cùng cỡ control của Grammar."
2592
2592
  causes_vi:
2593
2593
  - "Cấu trúc shell không theo canon ứng dụng."
2594
- nextStep_vi: "Đặt size=\"control\" trên từng điều khiển trong thanh (R314; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2594
+ nextStep_vi: "Đặt size=\"control\" trên từng điều khiển trong thanh (R314; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2595
2595
  owner: op-retry
2596
2596
  kind: check-finding
2597
2597
 
@@ -2601,7 +2601,7 @@ FE_NAVBAR_CREATE:
2601
2601
  meaning_vi: "Navbar tại <file> chứa thao tác tạo mới thuộc nội dung trang."
2602
2602
  causes_vi:
2603
2603
  - "Cấu trúc shell không theo canon ứng dụng."
2604
- nextStep_vi: "Đưa thao tác tạo mới về trang sở hữu nó (R317; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
2604
+ nextStep_vi: "Đưa thao tác tạo mới về trang sở hữu nó (R317; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
2605
2605
  owner: op-retry
2606
2606
  kind: check-finding
2607
2607
 
@@ -2953,8 +2953,8 @@ FE_STACK_FAMILY_LINT_MISSING:
2953
2953
  title_vi: "Front end dùng canon-fe 9 nhưng thiếu gói lint của họ Grammar đã chọn"
2954
2954
  meaning_vi: "hfs.json chọn sides.fe.grammar (starci hoặc offset-pop) và front end khai báo @starci/eslint-canon-fe 9 trở lên, nhưng không manifest nào khai báo gói lint của họ đó; từ canon-fe 9 các luật component Grammar nằm trong gói này, thiếu gói thì các luật ấy không chạy."
2955
2955
  causes_vi:
2956
- - "Nâng @starci/eslint-canon-fe lên 9 mà chưa thêm @starci/eslint-grammar-starci (Grammar StarCi) hoặc @starci/eslint-grammar-offset-pop (Grammar Offset Pop)."
2957
- nextStep_vi: "Thêm @starci/eslint-grammar-starci (hoặc @starci/eslint-grammar-offset-pop) cạnh @starci/eslint-canon-fe, ghép starciGrammarConfig({ hfs }) (hoặc offsetPopGrammarConfig({ hfs })) sau starciFeConfig({ hfs }) trong fe/eslint.config.mjs, hoặc chạy starci app sync."
2956
+ - "Nâng @starci/eslint-canon-fe lên 9 mà chưa thêm @starci/eslint-grammar-core (Grammar StarCi) hoặc @starci/eslint-grammar-offset-pop (Grammar Offset Pop)."
2957
+ nextStep_vi: "Thêm @starci/eslint-grammar-core (hoặc @starci/eslint-grammar-offset-pop) cạnh @starci/eslint-canon-fe, ghép coreGrammarConfig({ hfs }) (hoặc offsetPopGrammarConfig({ hfs })) sau starciFeConfig({ hfs }) trong fe/eslint.config.mjs, hoặc chạy starci app sync."
2958
2958
  owner: op-retry
2959
2959
  kind: check-finding
2960
2960
 
@@ -3011,7 +3011,7 @@ FE_STYLE_TOKEN_ONLY:
3011
3011
  - "@media dùng breakpoint ngoài thang của grammar"
3012
3012
  - "brand.css thiếu cặp mềm của một tông trạng thái ở sáng hoặc tối, chữ mềm dưới 3:1 trên nền nhạt hoặc nền trang, --foreground dưới 4.5:1, hoặc giá trị màu không phân giải được"
3013
3013
  - "Component tô chữ hoặc icon bằng text-success, text-danger, fill-warning, stroke-info (tông đặc) thay vì text-<tông>-soft-foreground"
3014
- nextStep_vi: "Op interface.implement bỏ CSS riêng, dùng component và token của grammar; giá trị màu thương hiệu chỉ khai ở modules/brand/brand.css cho cả sáng và tối, đủ cặp mềm cho mọi tông trạng thái; chữ và icon trạng thái dùng text-<tông>-soft-foreground (R61; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
3014
+ nextStep_vi: "Op interface.implement bỏ CSS riêng, dùng component và token của grammar; giá trị màu thương hiệu chỉ khai ở modules/brand/brand.css cho cả sáng và tối, đủ cặp mềm cho mọi tông trạng thái; chữ và icon trạng thái dùng text-<tông>-soft-foreground (R61; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
3015
3015
  owner: other-op:interface.implement
3016
3016
  kind: check-finding
3017
3017
 
@@ -3130,7 +3130,7 @@ FE_VENDOR_BOUNDARY:
3130
3130
  - "Dùng bộ glyph thứ hai ngoài họ heroicons (Phosphor, Lucide, Iconify...)"
3131
3131
  - "Dùng kích thước biểu tượng ngoài thang"
3132
3132
  - "Primitive thư viện ngoài dùng trực tiếp ở nhiều nơi"
3133
- nextStep_vi: "Dùng lá icon và chủ sở hữu primitive có sẵn; op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra (R93; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-starci/README.md)."
3133
+ nextStep_vi: "Dùng lá icon và chủ sở hữu primitive có sẵn; op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra (R93; FE-RULE-OWNERSHIP: knowledge/patterns/fe/index.yaml; packages/eslint/grammar-core/README.md)."
3134
3134
  owner: op-retry
3135
3135
  kind: check-finding
3136
3136
 
@@ -5007,7 +5007,7 @@ RT_GENERATED_BLOCK_STALE:
5007
5007
  RT_GENERATED_DRIFT:
5008
5008
  title: "Generated copy drifted"
5009
5009
  title_vi: "Bản sao sinh tự động bị lệch"
5010
- meaning_vi: "Một bản sao sinh tự động trong packages/hfs/runtime, packages/eslint/be/runtime hoặc packages/eslint/fe/runtime, packages/eslint/grammar-starci/runtime, packages/eslint/grammar-offset-pop/runtime khác với nội dung mà scripts/hfs/sync-runtime.mjs ghi ra từ nguồn duy nhất: thiếu, cũ, hoặc thừa tệp."
5010
+ meaning_vi: "Một bản sao sinh tự động trong packages/hfs/runtime, packages/eslint/be/runtime hoặc packages/eslint/fe/runtime, packages/eslint/grammar-core/runtime, packages/eslint/grammar-offset-pop/runtime khác với nội dung mà scripts/hfs/sync-runtime.mjs ghi ra từ nguồn duy nhất: thiếu, cũ, hoặc thừa tệp."
5011
5011
  causes_vi:
5012
5012
  - "Sửa một tệp nguồn được đóng gói mà quên chạy sync-runtime"
5013
5013
  - "Sửa tay một tệp trong thư mục runtime/ của gói"
@@ -12,7 +12,7 @@ import { fail } from './slot-errors.mjs';
12
12
 
13
13
  const HFS_RULES_FILE = 'knowledge/hfs/rules.yaml';
14
14
  const RULE_GATES = Object.freeze(['pre-commit', 'pre-push', 'settle', 'land', 'ci', 'sonar', 'runtime']);
15
- const ENFORCER_FAMILIES = Object.freeze(['eslint-be', 'eslint-fe', 'eslint-grammar-starci', 'stylelint', 'machine', 'hfs', 'work-validate', 'sonar', 'runtime']);
15
+ const ENFORCER_FAMILIES = Object.freeze(['eslint-be', 'eslint-fe', 'eslint-grammar-core', 'stylelint', 'machine', 'hfs', 'work-validate', 'sonar', 'runtime']);
16
16
  const RULE_KINDS = Object.freeze(['codemod', 'lint', 'check', 'design']);
17
17
  const FINDING_CODE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
18
18
  const ENFORCER_ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
@@ -346,7 +346,7 @@ export function loadRuleCatalog({ root = skillRoot, file = path.join(root, HFS_R
346
346
  /** The catalogued why code of a lint finding's rule id (`starci-be/<id>`, `starci-fe/<id>`), or undefined. */
347
347
  lintCode: (ruleId) => {
348
348
  const [plugin, id] = String(ruleId ?? '').split('/');
349
- const kind = (plugin === 'starci-be' && 'eslint-be') || (plugin === 'starci-fe' && 'eslint-fe') || (plugin === 'starci-grammar' && 'eslint-grammar-starci') || null;
349
+ const kind = (plugin === 'starci-be' && 'eslint-be') || (plugin === 'starci-fe' && 'eslint-fe') || (plugin === 'starci-grammar' && 'eslint-grammar-core') || null;
350
350
  return kind ? list.find((rule) => rule.enforcers.some((enforcer) => enforcer.kind === kind && enforcer.id === id))?.code : undefined;
351
351
  },
352
352
  /** The rules one enforcer judges, e.g. forEnforcer('eslint-be', 'error-home'). */
@@ -10,6 +10,7 @@ import path from 'node:path';
10
10
  import { parseYaml } from '../../engine/yaml.mjs';
11
11
  import { validateAgainstSchema } from './json-schema.mjs';
12
12
  import { insidePath, samePath, slash } from './path-key.mjs';
13
+ import { resolveGrammarFamily } from './grammar-lint.mjs';
13
14
  import { byCodeUnit } from './list.mjs';
14
15
  import { isLiteWorkRoot, workPathAllowed } from './work-edition.mjs';
15
16
  import { WORK_DIR_NAME } from './roots.mjs';
@@ -49,9 +50,15 @@ export const REFERENCE_APP_MANIFEST = `${EXAMPLES_ROOT}/full-app/hfs.json`;
49
50
  /** The example app whose installed tree an fe-kit peer link points at by default. */
50
51
  export const REFERENCE_EXAMPLE_APP = 'full-app';
51
52
 
52
- /** The grammar css families a product repository may declare (`[data-grammar-family="<id>"]`); the
53
- * runtime's own product maps to the core sheet. */
54
- export const GRAMMAR_FAMILIES = Object.freeze({ starci: 'core', nivo: 'nivo', 'offset-pop': 'offset-pop' });
53
+ /** The grammar css families a product repository may declare (`[data-grammar-family="<id>"]`), keyed by the
54
+ * canonical catalog id; the runtime's own product maps to the core sheet. */
55
+ export const GRAMMAR_FAMILIES = Object.freeze({ core: 'core', nivo: 'nivo', 'offset-pop': 'offset-pop' });
56
+
57
+ /** A css family key through the Grammar catalog's alias table (`starci` reads as `core`); an id the catalog does not declare is kept. */
58
+ export function grammarCssFamilyKey(family) {
59
+ if (typeof family !== 'string' || !family) return family;
60
+ try { return resolveGrammarFamily(family).id; } catch { return family; }
61
+ }
55
62
 
56
63
  /** A feature-folder segment that is only the product's own name - no useful leaf (display-names pathLabel). */
57
64
  export const PRODUCT_NAME_SEGMENT = /^(nivo|starci|mia)[-\w]*$/i;
@@ -4,14 +4,27 @@ import { parseYamlCached } from './yaml-cached.mjs';
4
4
 
5
5
  const CATALOG_FILE = new URL('../../knowledge/grammars/index.yaml', import.meta.url);
6
6
 
7
+ function readCatalog() {
8
+ return parseYamlCached(fs.readFileSync(CATALOG_FILE, 'utf8'));
9
+ }
10
+
7
11
  function selectedFamily(family) {
8
12
  if (typeof family !== 'string' || !family.trim()) throw new Error('sides.fe.grammar must explicitly name the selected Grammar family');
9
- const catalog = parseYamlCached(fs.readFileSync(CATALOG_FILE, 'utf8'));
13
+ const catalog = readCatalog();
10
14
  const matches = (catalog.families ?? []).filter(entry => entry.id === family);
11
15
  if (matches.length !== 1) throw new Error(`Grammar family ${family} has no declared lint contract in knowledge/grammars/index.yaml`);
12
16
  return { catalog, selected: matches[0] };
13
17
  }
14
18
 
19
+ /**
20
+ * Resolve a selection to its declared family id; only a catalog family id resolves, there are no aliases.
21
+ * @param {string} family - The id an app or caller names (sides.fe.grammar).
22
+ * @returns {{ id: string }} The declared family id.
23
+ */
24
+ export function resolveGrammarFamily(family) {
25
+ return Object.freeze({ id: selectedFamily(family).selected.id });
26
+ }
27
+
15
28
  /** The explicitly selected authored family entry; callers share the catalog lookup rather than inferring a family. */
16
29
  export function grammarFamilyFor(family) {
17
30
  return Object.freeze(selectedFamily(family).selected);
@@ -24,13 +37,13 @@ export function grammarLintFor(family) {
24
37
  throw new Error(`Grammar family ${family} has no declared lint contract in knowledge/grammars/index.yaml`);
25
38
  }
26
39
  const lint = selected.lint;
27
- if (lint.package === null) return Object.freeze({ family, package: null });
40
+ if (lint.package === null) return Object.freeze({ family: selected.id, package: null });
28
41
  if (typeof lint.package !== 'string' || !lint.package.startsWith('@starci/eslint-grammar-')
29
42
  || typeof lint.factory !== 'string' || !/^[a-zA-Z][a-zA-Z0-9]*$/.test(lint.factory)
30
43
  || typeof lint.prefix !== 'string' || !/^[a-z][a-z0-9-]*$/.test(lint.prefix)) {
31
44
  throw new Error(`Grammar family ${family} has an invalid lint contract in knowledge/grammars/index.yaml`);
32
45
  }
33
- return Object.freeze({ family, package: lint.package, factory: lint.factory, prefix: lint.prefix });
46
+ return Object.freeze({ family: selected.id, package: lint.package, factory: lint.factory, prefix: lint.prefix });
34
47
  }
35
48
 
36
49
  /** The selected family's shared component/export contract, resolved through its catalog reference. */
package/scaffold/app.mjs CHANGED
@@ -127,7 +127,7 @@ export const packageJsonText = value => `${JSON.stringify(value, null, 2)}\n`;
127
127
  const STARTER_SIDES = Object.freeze({
128
128
  be: Object.freeze({ apps: [{ name: 'core', kind: 'api' }, { name: 'cli', kind: 'cli' }], kinds: ['api', 'cli'], connections: [{ name: 'primary', envPrefix: 'PRIMARY_DB', owner: 'core', isolation: 'database' }] }),
129
129
  // fe: the landing and the product app, over the shared ui and i18n packages (both opt-in slots, enabled here).
130
- fe: Object.freeze({ grammar: 'starci', apps: [{ name: 'landing', kind: 'next' }, { name: 'app', kind: 'next' }], reads: ['be/contracts/'], optionalSlots: ['fe.package.ui', 'fe.package.i18n'] }),
130
+ fe: Object.freeze({ grammar: 'core', apps: [{ name: 'landing', kind: 'next' }, { name: 'app', kind: 'next' }], reads: ['be/contracts/'], optionalSlots: ['fe.package.ui', 'fe.package.i18n'] }),
131
131
  });
132
132
 
133
133
  /** The lite starter is the upgrade-safe subset: one API, one web app and one Supabase schema authority. */
@@ -138,7 +138,7 @@ const LITE_STARTER_SIDES = Object.freeze({
138
138
  connections: [{ name: 'primary', envPrefix: 'PRIMARY_DB', owner: 'api', isolation: 'schema', provider: 'supabase' }],
139
139
  reads: ['supabase/types/'],
140
140
  }),
141
- fe: Object.freeze({ grammar: 'starci', apps: [{ name: 'web', kind: 'next' }], reads: ['be/contracts/', 'supabase/types/'] }),
141
+ fe: Object.freeze({ grammar: 'core', apps: [{ name: 'web', kind: 'next' }], reads: ['be/contracts/', 'supabase/types/'] }),
142
142
  });
143
143
 
144
144
  /** The checked local Supabase auth posture of every newly scaffolded lite app. */
package/stack/render.mjs CHANGED
@@ -111,7 +111,9 @@ function dockerRender(ctx, config, overrideFile, options, env) {
111
111
  if (run.error) throw failure('STACK_RENDER_FAILED', `cannot run docker (${run.error.message})`);
112
112
  if (run.status !== 0) throw failure('STACK_RENDER_FAILED', run.stderr.trim().split('\n').slice(0, 6).join(' | '));
113
113
  if (hasPlaceholderDigest(run.stdout)) throw refusal('STACK_PLACEHOLDER_DIGEST', 'rendered stack still carries a placeholder digest');
114
- return run.stdout;
114
+ // docker stack config has already interpolated every variable, so a dollar left in its output is literal (a source `$`); docker stack
115
+ // deploy interpolates the written file again, so every dollar is escaped once more to survive that second pass.
116
+ return escapeDollar(run.stdout);
115
117
  }
116
118
 
117
119
  function releaseRecord(ctx, config, parts) {
package/sync/hygiene.mjs CHANGED
@@ -126,7 +126,7 @@ export function judge(cwd, files) {
126
126
 
127
127
  // The scripts/housekeeping/ledger-hygiene.mjs of the checkout under judgment: the root git names for `cwd`, which carries
128
128
  // that script only when the repository IS a full StarCi runtime checkout (packages/hfs/sync/ sits 3 directories
129
- // under such a root, the same computation scripts/hfs/sync-runtime.mjs uses). A product repository — or a
129
+ // under such a root). A product repository — or a
130
130
  // bare repo — has no such file, so the section is silently absent wherever this module happens to be installed.
131
131
  const ledgerHygieneScript = cwd => {
132
132
  try {
package/sync/index.mjs CHANGED
@@ -176,7 +176,7 @@ export async function loadPresets(root) {
176
176
 
177
177
  /**
178
178
  * The jest preset's Sonar exclusions as @starci/hfs ships them: `@starci/jest-preset/sonar-exclusions.cjs` (a require-free module),
179
- * copied byte for byte into packages/hfs/runtime by scripts/hfs/sync-runtime.mjs, so a fresh `starci app scaffold` (nothing installed
179
+ * carried byte for byte in packages/hfs/runtime, so a fresh `starci app scaffold` (nothing installed
180
180
  * yet) and an upgrade render the same list the app's installed preset answers. A missing copy is a refusal, never a render without it.
181
181
  */
182
182
  const BUNDLED_SONAR_EXCLUSIONS = new URL('../runtime/packages/jest-preset/sonar-exclusions.cjs', import.meta.url);