@starci/eslint-grammar-core 0.15.34 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/index.js +4 -0
  4. package/dist/lib/why.js +15 -0
  5. package/dist/mascot.d.ts +28 -0
  6. package/dist/mascot.js +166 -0
  7. package/dist/surface-depth.d.ts +7 -0
  8. package/dist/surface-depth.js +92 -0
  9. package/docs/mascot.md +60 -0
  10. package/docs/surface-depth.md +40 -0
  11. package/package.json +2 -2
  12. package/runtime/bundle.json +74 -0
  13. package/runtime/engine/by-code-unit.mjs +6 -6
  14. package/runtime/engine/model-config.mjs +98 -76
  15. package/runtime/engine/orca-config.mjs +22 -17
  16. package/runtime/engine/release-config.mjs +22 -18
  17. package/runtime/engine/removed-vocabulary.mjs +21 -20
  18. package/runtime/engine/resources-config.mjs +42 -35
  19. package/runtime/engine/runtime-root.mjs +11 -13
  20. package/runtime/engine/secrets.mjs +170 -102
  21. package/runtime/engine/sonar-config.mjs +16 -13
  22. package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
  23. package/runtime/knowledge/hfs/rules.yaml +95 -6
  24. package/runtime/knowledge/hfs/slots.yaml +2 -2
  25. package/runtime/knowledge/patterns/fe/ui.yaml +13 -1
  26. package/runtime/scripts/hfs/allows.mjs +37 -38
  27. package/runtime/scripts/hfs/architecture/config.mjs +302 -268
  28. package/runtime/scripts/hfs/architecture/side-boundary.mjs +2 -5
  29. package/runtime/scripts/hfs/architecture/type-context.mjs +255 -250
  30. package/runtime/scripts/hfs/architecture/typescript.mjs +358 -318
  31. package/runtime/scripts/hfs/declaration-shape.mjs +140 -103
  32. package/runtime/scripts/hfs/declaration-slots.mjs +64 -61
  33. package/runtime/scripts/hfs/edition-slots.mjs +43 -40
  34. package/runtime/scripts/hfs/param-names.mjs +6 -11
  35. package/runtime/scripts/hfs/rule-catalog.mjs +353 -257
  36. package/runtime/scripts/hfs/rule-params-shape.mjs +60 -47
  37. package/runtime/scripts/hfs/slot-app-view.mjs +69 -74
  38. package/runtime/scripts/hfs/slot-classify.mjs +42 -38
  39. package/runtime/scripts/hfs/slot-errors.mjs +8 -7
  40. package/runtime/scripts/hfs/slot-imports.mjs +70 -63
  41. package/runtime/scripts/hfs/slot-manifest-shape.mjs +74 -52
  42. package/runtime/scripts/hfs/slot-match.mjs +122 -111
  43. package/runtime/scripts/hfs/slot-path.mjs +0 -2
  44. package/runtime/scripts/hfs/slot-required.mjs +65 -59
  45. package/runtime/scripts/hfs/slot-semantic-problems.mjs +138 -112
  46. package/runtime/scripts/hfs/slot-side-problems.mjs +23 -19
  47. package/runtime/scripts/hfs/slots.mjs +175 -152
  48. package/runtime/scripts/hfs/typescript-programs.mjs +66 -69
  49. package/runtime/scripts/hfs/view.mjs +38 -40
  50. package/runtime/scripts/lib/glob.mjs +23 -14
  51. package/runtime/scripts/lib/grammar-lint.mjs +33 -37
  52. package/runtime/scripts/lib/i18n.mjs +32 -35
  53. package/runtime/scripts/lib/json-clone.mjs +45 -36
  54. package/runtime/scripts/lib/json.mjs +26 -14
  55. package/runtime/scripts/lib/list.mjs +9 -14
  56. package/runtime/scripts/lib/path-key.mjs +53 -38
  57. package/runtime/scripts/lib/read-text.mjs +30 -17
  58. package/runtime/scripts/lib/regex.mjs +1 -2
  59. package/runtime/scripts/lib/source-phrases.mjs +13 -13
  60. package/runtime/scripts/lib/ts-analysis.mjs +28 -26
  61. package/runtime/scripts/lib/ts-ast.mjs +36 -42
  62. package/runtime/scripts/lib/walk.mjs +33 -27
  63. package/runtime/scripts/lib/yaml-cached.mjs +4 -4
@@ -51,7 +51,7 @@ versioning:
51
51
  # 3.10.0 is a minor (2026-10-29): it ADDS the lite be.platform.database.entities slot, the entity registry (slot fields role and importedBy), so the
52
52
  # tier matrix and the public-entry rules admit exactly its <c>Entities imports and its deep importers (persistence.md BE-PERSISTENCE-3).
53
53
  # 3.11.0 (ruling:lite-official-postgres-image, 2026-10-29) is, like 3.9.0, a change for lite only: it ADDS the managed app.db.image slot
54
- # (db/Dockerfile, written by `starci app sync --write`), narrows ruleParams.db.extensions to the extensions that image carries, and gives
54
+ # (db/Dockerfile, written by `starci app sync --execute`), narrows ruleParams.db.extensions to the extensions that image carries, and gives
55
55
  # @nestjs/schedule and cron one owner, platform/jobs (R335).
56
56
  # 3.13.0 (rulings lite-pgmq-bus-bullmq-jobs and lite-db-image-in-stacks, 2026-10-29) is a change for lite only: ruleParams.db.extensions
57
57
  # admits pgmq, the event bus, ruleParams.db.queueOutbox is retired with the pg_cron half of R258, the lite event-bus functions replace
@@ -940,7 +940,7 @@ slots:
940
940
 
941
941
  # ----- app root: the database of a lite app (ruling:lite-self-hosted-postgres-passport-minio): a self-hosted PostgreSQL in the app's
942
942
  # stack (the `db` service, built from the managed .starcistacks/images/db/Dockerfile: the official postgres image plus pgvector and pgmq, ruling:lite-db-image-in-stacks), migrated by
943
- # `starci app db migrate --url-env <NAME>`. A full app keeps its TypeORM migrations under be/ and has no such folder.
943
+ # `starci app db migrate --db-url-env <NAME>`. A full app keeps its TypeORM migrations under be/ and has no such folder.
944
944
  - id: app.db
945
945
  profiles: [app]
946
946
  editions: [lite]
@@ -24,7 +24,7 @@ rules:
24
24
  - id: FE-UI
25
25
  title: Compose the selected Grammar
26
26
  kind: mandatory
27
- hfsRules: [R93, R244, R313, R314, R315, R316, R317, R318, R319, R320]
27
+ hfsRules: [R93, R244, R313, R314, R315, R316, R317, R318, R319, R320, R338, R339, R340]
28
28
  requirement: |-
29
29
  Components compose the selected Grammar through its published entry and props; application CSS carries
30
30
  layout only (css.yaml). A missing visual capability is added to that Grammar before the app consumes it.
@@ -59,6 +59,15 @@ rules:
59
59
  (owner:agent-avatar-initials-bot-badge). Apps neither declare nor import an avatar-generation
60
60
  library and draw no avatar visuals; the family owns the renderer API (the StarCi recipe is
61
61
  groupIdentity in its playbook).
62
+ - id: case-4
63
+ date: "2026-10-30"
64
+ source: owner
65
+ when: A screen shows generated character art (a mascot)
66
+ write: |-
67
+ Render it with the selected Grammar's Mascot at a step of its size scale (sm, md, lg, or hero for a split
68
+ screen), passing the asset slot it was requested under (assetSlot, and assetSha256 once delivered); a scene
69
+ or illustration is framed by MediaFrame. Generated art under /images/ never renders through a bare Image or
70
+ img (R338, R339).
62
71
  verification:
63
72
  automated:
64
73
  - FE_VENDOR_BOUNDARY
@@ -81,5 +90,8 @@ rules:
81
90
  - FE_SINGLE_COMMIT_PER_RENDER
82
91
  - FE_NO_COMMIT_IN_COLLECTION_ITEM
83
92
  - FE_CONTROL_HEIGHT
93
+ - FE_GENERATED_ART_OUTSIDE_FRAME
94
+ - FE_MASCOT_ASSET_SLOT_MISSING
95
+ - FE_RAISED_SURFACE_IN_SURFACE
84
96
  manual: []
85
97
  relatedExamples: [full-app-shell, lite-app-shell]
@@ -1,7 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { braceVariants, globExpression } from '../lib/glob.mjs';
3
3
  import { declaredLogicOf } from './manifest-shape.mjs';
4
-
5
4
  /**
6
5
  * What a slot's `requires`, `allows` and `forbids` entries (knowledge/hfs/slots.yaml) say about a file, read through the
7
6
  * resolver of scripts/hfs/slots.mjs. The machine checks that judge "this folder holds only what its slot allows"
@@ -14,65 +13,65 @@ import { declaredLogicOf } from './manifest-shape.mjs';
14
13
  */
15
14
  const VAR = /<([A-Za-z][A-Za-z0-9-]*)>/g;
16
15
  const SPEC = /\.spec\.ts$/;
17
-
18
16
  /**
19
17
  * Whether `relative` is the unit spec `<stem>.spec.ts` of a measured file beside it that the slot names: a file the slot declares in
20
18
  * `logic`, or a `requires`/`allows` file of a logic role (ruleParams.be.logicRoles), in a slot whose coverage is required and whose tests
21
19
  * are unit-beside. The slot admits that spec without listing it, as unit-test-colocated (R47) accepts it.
22
20
  */
23
21
  function isMeasuredSpec(resolver, classified, slot, relative, named) {
24
- if (!SPEC.test(relative) || slot.coverage !== 'required' || slot.tests !== 'unit-beside') return false;
25
- const subject = relative.replace(SPEC, '.ts');
26
- if (declaredLogicOf(slot).includes(subject)) return true;
27
- const role = /\.([a-z0-9-]+)\.ts$/.exec(subject)?.[1];
28
- // An app resolver keeps no rule parameters of its own: a path on a side reads that side's.
29
- const params = classified.side ? resolver.sides?.[classified.side]?.ruleParams() : resolver.ruleParams?.();
30
- return (params?.logicRoles ?? []).includes(role) && named.some(matcher => matcher.expression.test(subject));
22
+ if (!SPEC.test(relative) || slot.coverage !== 'required' || slot.tests !== 'unit-beside')
23
+ return false;
24
+ const subject = relative.replace(SPEC, '.ts');
25
+ if (declaredLogicOf(slot).includes(subject))
26
+ return true;
27
+ const role = /\.([a-z0-9-]+)\.ts$/.exec(subject)?.[1];
28
+ // An app resolver keeps no rule parameters of its own: a path on a side reads that side's.
29
+ const params = classified.side ? resolver.sides?.[classified.side]?.ruleParams() : resolver.ruleParams?.();
30
+ return (params?.logicRoles ?? []).includes(role) && named.some((matcher) => matcher.expression.test(subject));
31
31
  }
32
-
33
32
  function matchersOf(entries, bindings) {
34
- return (entries ?? []).flatMap(entry => {
35
- const directory = entry.endsWith('/');
36
- const body = directory ? entry.slice(0, -1) : entry;
37
- const filled = body.replace(VAR, (whole, name) => bindings[name] ?? '*');
38
- return braceVariants(filled).map(variant => ({ entry, directory, expression: globExpression(directory ? `${variant}/**` : variant) }));
39
- });
33
+ return (entries ?? []).flatMap((entry) => {
34
+ const directory = entry.endsWith('/');
35
+ const body = directory ? entry.slice(0, -1) : entry;
36
+ const filled = body.replace(VAR, (_whole, name) => bindings[name] ?? '*');
37
+ return braceVariants(filled).map((variant) => ({ entry, directory, expression: globExpression(directory ? `${variant}/**` : variant) }));
38
+ });
40
39
  }
41
-
42
40
  /** The path of `file` below the instance root of the slot that owns it. */
43
41
  export function relativeToRoot(file, root) {
44
- return root && file.startsWith(`${root}/`) ? file.slice(root.length + 1) : path.posix.basename(file);
42
+ return root && file.startsWith(`${root}/`) ? file.slice(root.length + 1) : path.posix.basename(file);
45
43
  }
46
-
47
44
  /**
48
45
  * Whether the slot that owns `file` names it in `requires` or `allows` (and does not forbid it); null when no enabled slot
49
46
  * owns it. Unlike allowsFile it judges a slot that lists only `requires` too, so a folder closed by its slot (an app's
50
47
  * src/) is read from the slot and never from a check's own list.
51
48
  */
52
49
  export function slotAdmitsFile(resolver, file) {
53
- const classified = resolver.classifyPath(file);
54
- if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
55
- const slot = resolver.slot(classified.slot);
56
- const relative = relativeToRoot(classified.path, classified.root);
57
- const named = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings);
58
- const hit = named.some(matcher => matcher.expression.test(relative)) || isMeasuredSpec(resolver, classified, slot, relative, named);
59
- const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
60
- return hit && !forbidden;
50
+ const classified = resolver.classifyPath(file);
51
+ if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot)
52
+ return null;
53
+ const slot = resolver.slot(classified.slot);
54
+ const relative = relativeToRoot(classified.path, classified.root);
55
+ const named = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings);
56
+ const hit = named.some(matcher => matcher.expression.test(relative)) || isMeasuredSpec(resolver, classified, slot, relative, named);
57
+ const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
58
+ return hit && !forbidden;
61
59
  }
62
-
63
60
  /**
64
61
  * {slot, root, relative, allowed, entry?, forbiddenBy?} for a repository-relative file, or null when no enabled slot owns
65
62
  * it or the slot names no `allows`. `allowed` is true when the relative path matches a `requires` or `allows` entry.
66
63
  */
67
64
  export function allowsFile(resolver, file) {
68
- const classified = resolver.classifyPath(file);
69
- if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
70
- const slot = resolver.slot(classified.slot);
71
- if (!slot?.allows?.length) return null;
72
- const relative = relativeToRoot(classified.path, classified.root);
73
- const named = matchersOf([...(slot.requires ?? []), ...slot.allows], classified.bindings);
74
- const hit = named.find(matcher => matcher.expression.test(relative));
75
- const spec = !hit && isMeasuredSpec(resolver, classified, slot, relative, named);
76
- const forbidden = matchersOf(slot.forbids, classified.bindings).find(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
77
- return { slot: slot.id, root: classified.root, relative, allowed: (Boolean(hit) || spec) && !forbidden, entry: hit?.entry, forbiddenBy: forbidden?.entry, allows: slot.allows };
65
+ const classified = resolver.classifyPath(file);
66
+ if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot)
67
+ return null;
68
+ const slot = resolver.slot(classified.slot);
69
+ if (!slot?.allows?.length)
70
+ return null;
71
+ const relative = relativeToRoot(classified.path, classified.root);
72
+ const named = matchersOf([...(slot.requires ?? []), ...slot.allows], classified.bindings);
73
+ const hit = named.find(matcher => matcher.expression.test(relative));
74
+ const spec = !hit && isMeasuredSpec(resolver, classified, slot, relative, named);
75
+ const forbidden = matchersOf(slot.forbids, classified.bindings).find(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
76
+ return { slot: slot.id, root: classified.root, relative, allowed: (Boolean(hit) || spec) && !forbidden, entry: hit?.entry, forbiddenBy: forbidden?.entry, allows: slot.allows };
78
77
  }