@starci/hfs 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +25 -21
  3. package/bin/hfs.mjs +59 -37
  4. package/lint/run.mjs +70 -39
  5. package/package.json +2 -2
  6. package/runtime/engine/admission.mjs +3 -3
  7. package/runtime/engine/ledger-db.mjs +2 -2
  8. package/runtime/engine/machine-db.mjs +90 -9
  9. package/runtime/engine/migrations/machine/0002-worktrees-no-workflow-kind.sql +13 -0
  10. package/runtime/engine/migrations/runtime/0005-ended-workflow-views.sql +93 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +28 -9
  12. package/runtime/knowledge/hfs/peer-integrations.yaml +18 -0
  13. package/runtime/knowledge/hfs/slots.yaml +193 -128
  14. package/runtime/knowledge/patterns/fe/folder.yaml +36 -36
  15. package/runtime/modules/kernel/failure-codes.yaml +23 -32
  16. package/runtime/scripts/checks/architecture/backend.mjs +1 -1
  17. package/runtime/scripts/checks/architecture/config.mjs +31 -11
  18. package/runtime/scripts/checks/architecture/contracts.mjs +4 -4
  19. package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +7 -3
  20. package/runtime/scripts/checks/architecture/framework-pinned.mjs +5 -47
  21. package/runtime/scripts/checks/architecture/frontend.mjs +6 -4
  22. package/runtime/scripts/checks/architecture/hfs.mjs +104 -66
  23. package/runtime/scripts/checks/architecture/next-data.mjs +3 -2
  24. package/runtime/scripts/checks/architecture/registration.mjs +1 -1
  25. package/runtime/scripts/checks/architecture/symbols.mjs +13 -2
  26. package/runtime/scripts/checks/architecture/test-world-files.mjs +83 -45
  27. package/runtime/scripts/checks/architecture/typescript.mjs +45 -20
  28. package/runtime/scripts/checks/typescript-programs.mjs +2 -2
  29. package/runtime/scripts/lib/hfs-check.mjs +156 -141
  30. package/runtime/scripts/lib/hfs-path-findings.mjs +13 -2
  31. package/runtime/scripts/lib/hfs-rules/contract.mjs +15 -42
  32. package/runtime/scripts/lib/hfs-rules/frontend.mjs +37 -36
  33. package/runtime/scripts/lib/hfs-rules/peer-integrations.mjs +44 -0
  34. package/runtime/scripts/lib/hfs-rules/proof-commands.mjs +11 -8
  35. package/runtime/scripts/lib/hfs-slots.mjs +244 -61
  36. package/runtime/scripts/lib/hfs-view.mjs +9 -7
  37. package/runtime/scripts/lib/language.mjs +11 -1
  38. package/runtime/scripts/lib/safe-remove.mjs +95 -10
  39. package/scaffold/app.mjs +179 -0
  40. package/scaffold/service.mjs +26 -16
  41. package/sync/cli.mjs +1 -1
  42. package/sync/hygiene.mjs +11 -8
  43. package/sync/index.mjs +109 -111
  44. package/sync/managed.mjs +9 -8
  45. package/sync/sonar-key.mjs +20 -22
  46. package/templates/{be → app}/ci-workflows/github/workflows/ci.yml +4 -2
  47. package/templates/app/gitignore +6 -0
  48. package/templates/app/hooks/husky/pre-commit +25 -0
  49. package/templates/app/hooks/husky/pre-push +7 -0
  50. package/templates/app/package-scripts/package.json +22 -0
  51. package/templates/{be → app}/quality-config/sonar-project.properties +3 -2
  52. package/templates/app/skeleton/.editorconfig +15 -0
  53. package/templates/app/skeleton/.gitattributes +2 -0
  54. package/templates/app/skeleton/.nvmrc +1 -0
  55. package/templates/app/skeleton/.starciwork/features/index.yaml +7 -0
  56. package/templates/app/skeleton/.starciwork/workspace.yaml +9 -0
  57. package/templates/app/skeleton/README.md +36 -0
  58. package/templates/app/skeleton/scripts/codegen.mjs +4 -0
  59. package/templates/{fe → app}/tool-config/prettierignore +4 -1
  60. package/templates/be/skeleton/.sops.yaml +2 -0
  61. package/templates/be/skeleton/.starcistacks/application-stacks.yaml +10 -0
  62. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  63. package/templates/be/skeleton/apps/__app__/src/app.module.ts +27 -6
  64. package/templates/be/skeleton/apps/__app__/src/main.ts +4 -1
  65. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +2 -0
  66. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +11 -0
  67. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +25 -0
  68. package/templates/be/skeleton/src/modules/domain/identity/errors/identity.error.ts +16 -0
  69. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +11 -0
  70. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +8 -0
  71. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +7 -0
  72. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +14 -0
  73. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +2 -0
  74. package/templates/be/skeleton/src/modules/domain/identity/index.ts +6 -0
  75. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +11 -0
  76. package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +8 -1
  77. package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
  78. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +71 -23
  79. package/templates/be/skeleton/src/modules/platform/config/errors/config.error.ts +14 -16
  80. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  81. package/templates/be/skeleton/src/modules/platform/config/server.config.ts +2 -12
  82. package/templates/be/skeleton/src/modules/platform/errors/domain.error.ts +16 -7
  83. package/templates/be/skeleton/src/modules/platform/errors/errors/errors.error.ts +16 -0
  84. package/templates/be/skeleton/src/modules/platform/errors/errors.contracts.ts +33 -0
  85. package/templates/be/skeleton/src/modules/platform/errors/errors.decorators.ts +16 -0
  86. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +32 -0
  87. package/templates/be/skeleton/src/modules/platform/errors/errors.log-events.ts +2 -2
  88. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +9 -0
  89. package/templates/be/skeleton/src/modules/platform/errors/errors.module.ts +19 -0
  90. package/templates/be/skeleton/src/modules/platform/errors/errors.options.ts +7 -0
  91. package/templates/be/skeleton/src/modules/platform/errors/errors.service.spec.ts +94 -0
  92. package/templates/be/skeleton/src/modules/platform/errors/errors.service.ts +47 -0
  93. package/templates/be/skeleton/src/modules/platform/errors/http-status.policy.ts +13 -0
  94. package/templates/be/skeleton/src/modules/platform/errors/index.ts +4 -1
  95. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +11 -0
  96. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +19 -0
  97. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.ts +5 -0
  98. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -0
  99. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +10 -0
  100. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +9 -0
  101. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +13 -0
  102. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +17 -0
  103. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +7 -0
  104. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +13 -0
  105. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +31 -0
  106. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +68 -0
  107. package/templates/be/skeleton/src/modules/platform/i18n/bundle-message-catalog.service.spec.ts +66 -0
  108. package/templates/be/skeleton/src/modules/platform/i18n/bundle-message-catalog.service.ts +30 -0
  109. package/templates/be/skeleton/src/modules/platform/i18n/i18n.contracts.ts +18 -0
  110. package/templates/be/skeleton/src/modules/platform/i18n/i18n.decorators.ts +23 -0
  111. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +9 -0
  112. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module.ts +24 -0
  113. package/templates/be/skeleton/src/modules/platform/i18n/i18n.options.ts +7 -0
  114. package/templates/be/skeleton/src/modules/platform/i18n/i18n.port.ts +13 -0
  115. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +4 -0
  116. package/templates/be/skeleton/src/modules/platform/i18n/request-locale.service.spec.ts +45 -0
  117. package/templates/be/skeleton/src/modules/platform/i18n/request-locale.service.ts +19 -0
  118. package/templates/be/skeleton/src/modules/platform/logging/index.ts +1 -1
  119. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +85 -68
  120. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +14 -10
  121. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +13 -0
  122. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +4 -0
  123. package/templates/be/skeleton/src/modules/platform/logging/logging.port.ts +0 -1
  124. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +2 -0
  125. package/templates/be/skeleton/src/modules/platform/primitives/outcome.contracts.ts +25 -0
  126. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.ts +24 -0
  127. package/templates/fe/skeleton/apps/__app__/postcss.config.mjs +7 -0
  128. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/error.tsx +5 -17
  129. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +11 -22
  130. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/loading.tsx +6 -0
  131. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +3 -12
  132. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +6 -24
  133. package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +4 -18
  134. package/templates/fe/skeleton/apps/__app__/src/app/globals.css +5 -0
  135. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +28 -0
  136. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +35 -0
  137. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +21 -0
  138. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +17 -0
  139. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +26 -0
  140. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +15 -0
  141. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +25 -0
  142. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +15 -0
  143. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +27 -0
  144. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +8 -0
  145. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +32 -0
  146. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +8 -0
  147. package/templates/fe/skeleton/apps/__app__/src/modules/config/index.ts +12 -0
  148. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +2 -0
  149. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +6 -0
  150. package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/request.ts +1 -0
  151. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +4 -0
  152. package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/proxy.ts +1 -1
  153. package/sync/skeleton.mjs +0 -76
  154. package/templates/be/gitignore +0 -2
  155. package/templates/be/hooks/husky/pre-commit +0 -13
  156. package/templates/be/hooks/husky/pre-push +0 -6
  157. package/templates/be/package-scripts/package.json +0 -19
  158. package/templates/be/skeleton/scripts/.gitkeep +0 -0
  159. package/templates/be/skeleton/src/modules/platform/config/config.decorators.ts +0 -9
  160. package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +0 -20
  161. package/templates/be/tool-config/prettierignore +0 -8
  162. package/templates/fe/ci-workflows/github/workflows/ci.yml +0 -40
  163. package/templates/fe/gitignore +0 -3
  164. package/templates/fe/hooks/husky/pre-commit +0 -16
  165. package/templates/fe/hooks/husky/pre-push +0 -5
  166. package/templates/fe/package-scripts/package.json +0 -13
  167. package/templates/fe/parts/api-client.ts +0 -44
  168. package/templates/fe/parts/api-outcome.ts +0 -7
  169. package/templates/fe/quality-config/sonar-project.properties +0 -8
  170. package/templates/fe/skeleton/scripts/.gitkeep +0 -0
  171. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/client.ts +0 -1
  172. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/index.ts +0 -3
  173. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/outcome.ts +0 -1
  174. package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/index.ts +0 -4
  175. package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/navigation.ts +0 -5
  176. package/templates/fe/skeleton-shared/apps/__app__/next.config.ts +0 -12
  177. package/templates/fe/skeleton-shared/apps/__app__/src/modules/api/index.ts +0 -2
  178. package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/index.ts +0 -9
  179. package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/request.ts +0 -5
  180. package/templates/fe/skeleton-shared/apps/__app__/src/proxy.ts +0 -5
  181. package/templates/fe/skeleton-shared/packages/__family__-api/package.json +0 -12
  182. package/templates/fe/skeleton-shared/packages/__family__-api/src/client.ts +0 -1
  183. package/templates/fe/skeleton-shared/packages/__family__-api/src/index.ts +0 -3
  184. package/templates/fe/skeleton-shared/packages/__family__-api/src/outcome.ts +0 -1
  185. package/templates/fe/skeleton-shared/packages/__family__-api/tsconfig.json +0 -5
  186. package/templates/fe/skeleton-shared/packages/__family__-i18n/package.json +0 -18
  187. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/app.ts +0 -19
  188. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/index.ts +0 -2
  189. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/proxy.ts +0 -12
  190. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/request.ts +0 -15
  191. package/templates/fe/skeleton-shared/packages/__family__-i18n/tsconfig.json +0 -5
  192. package/templates/fe/tool-config/prettierrc +0 -1
  193. /package/templates/{be → app}/ci-workflows/github/workflows/e2e.yml +0 -0
  194. /package/templates/{be → app}/starciwork.gitignore +0 -0
  195. /package/templates/{be → app}/tool-config/prettierrc +0 -0
  196. /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/next.config.ts +0 -0
  197. /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/config.ts +0 -0
  198. /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/routing.ts +0 -0
@@ -1,13 +1,16 @@
1
1
  # HFS slot manifest (owner-approved 2026-09-29, decisions 1-10 in the owner decisions record).
2
- # The one place that says what may exist in a StarCi product repository and where. Every tracked path of every
2
+ # The one place that says what may exist in a StarCi product repository and where. A product is ONE app repository
3
+ # (hfs 4, manifest major 2): the app root holds the one package.json, lockfile and node_modules, the hfs.json of kind app,
4
+ # the CI, the hooks and .starciwork (slots of profile `app`); `be/` and `fe/` are its two sides, each laid out as the old
5
+ # standalone repository root (slots of profile be or fe, paths relative to the side folder). Every tracked path of every
3
6
  # repository must match exactly one slot. Checks, lint factories, the architecture machine, templates and the why
4
7
  # catalog READ this file through scripts/lib/hfs-slots.mjs; none of them hardcodes a path.
5
8
  # Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
6
- schema: starci/hfs-slots@1
7
- version: 1.0.0
9
+ schema: starci/hfs-slots@2
10
+ version: 2.0.0
8
11
 
9
12
  versioning:
10
- # MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 1).
13
+ # MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 2).
11
14
  patch: wording, why text, examples; no check result can change.
12
15
  minor: >-
13
16
  add a slot (always presence optional or opt-in), add an app kind or protocol slot, add a rule at severity warn,
@@ -24,7 +27,7 @@ versioning:
24
27
 
25
28
  # Field meaning, per slot.
26
29
  # id stable name, never changes meaning inside a major.
27
- # profiles be, fe, or both: the repository profiles the slot exists in.
30
+ # profiles app (the app root; alone), or be, fe or both: the sides the slot exists in (its path is relative to the side folder).
28
31
  # path pattern. <name> is one path segment (bound to the variable name), * and ? stay inside a segment,
29
32
  # ** spans directories, {a,b} alternates. A pattern ending in / is a directory: it owns everything
30
33
  # below it, except what a more specific slot owns. Otherwise it names files.
@@ -55,6 +58,12 @@ presenceValues: [required, optional, opt-in, forbidden]
55
58
  trackedValues: [tracked, ignored, external]
56
59
  testValues: [unit-beside, e2e, none]
57
60
 
61
+ # The two sides of an app: the folder of each is its profile name (be/, fe/). `reads` lists the only paths of the OTHER side a side
62
+ # may read (hfs.json sides.<side>.reads names a subset); nothing else crosses sides.
63
+ sides:
64
+ be: {reads: []}
65
+ fe: {reads: [be/contracts/]} # the back end's committed contract snapshots, the input of the front end's codegen
66
+
58
67
  appKinds:
59
68
  be: [api, worker, migrate, cli]
60
69
  fe: [next]
@@ -158,25 +167,26 @@ ruleParams:
158
167
 
159
168
  slots:
160
169
 
161
- # ----- repository root (both profiles) ---------------------------------------------------------------
162
- - id: repo.readme
163
- profiles: [be, fe]
164
- path: README.md
170
+ # ----- app root (profile app: the one repository of a product) --------------------------------------------
171
+ - id: app.declaration
172
+ profiles: [app]
173
+ path: hfs.json
165
174
  presence: required
166
175
  tracked: tracked
167
176
  tier: none
168
177
  tests: none
169
- rules: [HFS_README_*]
170
- - id: repo.declaration
171
- profiles: [be, fe]
172
- path: hfs.json
178
+ rules: [HFS_ARCH_CONFIG_UNREAD]
179
+ - id: app.readme
180
+ profiles: [app]
181
+ path: README.md
173
182
  presence: required
174
183
  tracked: tracked
175
184
  tier: none
176
185
  tests: none
177
- rules: [HFS_ARCH_CONFIG_UNREAD]
178
- - id: fe.package-manifest
179
- profiles: [fe]
186
+ rules: [HFS_README_*]
187
+ - id: app.package-manifest
188
+ profiles: [app]
189
+ # The one package.json of the app: every dependency of both sides and every script; npm workspaces only for fe/packages/*.
180
190
  path: package.json
181
191
  presence: required
182
192
  tracked: tracked
@@ -184,53 +194,184 @@ slots:
184
194
  tests: none
185
195
  managedBy: package-scripts # the `scripts` block only; the rest of the file is the repository's
186
196
  rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT]
187
- - id: fe.lockfile
188
- profiles: [fe]
197
+ - id: app.lockfile
198
+ profiles: [app]
189
199
  path: package-lock.json
190
200
  presence: required
191
201
  tracked: tracked
192
202
  tier: none
193
203
  tests: none
194
204
  rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
195
- - id: be.package-manifest
196
- profiles: [be]
197
- path: package.json
205
+ - id: app.git-meta
206
+ profiles: [app]
207
+ path: "{.gitignore,.gitattributes}"
198
208
  presence: required
199
209
  tracked: tracked
200
210
  tier: none
201
211
  tests: none
202
- managedBy: package-scripts # the `scripts` block only; the rest of the file is the repository's
203
- rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT]
204
- - id: be.lockfile
205
- profiles: [be]
206
- path: package-lock.json
212
+ rules: [HFS_GITIGNORE_BLOCK_DRIFT] # the marked block of .gitignore is rendered by hfs sync (templates/app/gitignore)
213
+ - id: app.tool-config
214
+ profiles: [app]
215
+ path: "{.editorconfig,.nvmrc}"
216
+ presence: required # plain dotfiles, the repository's own
217
+ tracked: tracked
218
+ tier: none
219
+ tests: none
220
+ - id: app.format-config
221
+ profiles: [app]
222
+ # The one formatter configuration of the app (prettier walks up from every file of both sides to it): .prettierrc names
223
+ # @starci/prettier-config, .prettierignore is the template.
224
+ path: "{.prettierrc,.prettierignore}"
207
225
  presence: required
208
226
  tracked: tracked
209
227
  tier: none
210
228
  tests: none
211
- rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
212
- - id: repo.git-meta
213
- profiles: [be, fe]
214
- path: "{.gitignore,.gitattributes}"
229
+ managedBy: tool-config
230
+ rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL]
231
+ - id: app.tool-config-optional
232
+ profiles: [app]
233
+ path: .npmrc
234
+ presence: optional
235
+ tracked: tracked
236
+ tier: none
237
+ tests: none
238
+ - id: app.tool-config-local
239
+ profiles: [app]
240
+ path: "{tsconfig.json,.eslintrc,.eslintrc.*,.eslintignore,eslint.config.*,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.*,.prettierrc.*,prettier.config.*,jest.config.*,lint-staged.config.*,.lintstagedrc*}"
241
+ presence: forbidden
242
+ tracked: external
243
+ tier: none
244
+ tests: none
245
+ goesTo: "nowhere at the app root: each side holds exactly its managed tool configuration (be.tool-config, fe.tool-config) and the root only the formatter (app.format-config)"
246
+ rules: [HFS_TOOL_CONFIG_LOCAL]
247
+ - id: app.quality-config
248
+ profiles: [app]
249
+ path: sonar-project.properties
215
250
  presence: required
216
251
  tracked: tracked
217
252
  tier: none
218
253
  tests: none
219
- rules: [HFS_GITIGNORE_BLOCK_DRIFT] # the marked block of .gitignore is rendered by hfs sync (templates/<profile>/gitignore)
220
- - id: repo.tool-config
221
- profiles: [be, fe]
222
- path: "{.editorconfig,.nvmrc}"
223
- presence: required # plain dotfiles, the repository's own
254
+ managedBy: quality-config
255
+ rules: [HFS_SONAR_CONFIG, HFS_MANAGED_FILE_DRIFT]
256
+ - id: app.hooks
257
+ profiles: [app]
258
+ path: ".husky/{pre-commit,pre-push}"
259
+ presence: required
224
260
  tracked: tracked
225
261
  tier: none
226
262
  tests: none
227
- - id: repo.tool-config-optional
228
- profiles: [be, fe]
229
- path: .npmrc
263
+ managedBy: hooks
264
+ rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
265
+ - id: app.ci
266
+ profiles: [app]
267
+ path: ".github/workflows/ci.yml"
268
+ presence: required
269
+ tracked: tracked
270
+ tier: none
271
+ tests: none
272
+ managedBy: ci-workflows
273
+ rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
274
+ - id: app.ci-e2e
275
+ profiles: [app]
276
+ path: ".github/workflows/e2e.yml"
277
+ presence: optional # on: workflow_dispatch only
278
+ tracked: tracked
279
+ tier: none
280
+ tests: none
281
+ managedBy: ci-workflows
282
+ rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
283
+ - id: app.github-meta
284
+ profiles: [app]
285
+ path: ".github/{CODEOWNERS,pull_request_template.md,ISSUE_TEMPLATE/**,dependabot.yml}"
286
+ presence: optional
287
+ tracked: tracked
288
+ tier: none
289
+ tests: none
290
+ - id: app.scripts
291
+ profiles: [app]
292
+ # The app's operational scripts only (`*.mjs`, `*.cjs`, `*.ps1`, `*.sh`), or nothing: an empty folder is `scripts/.gitkeep`, which
293
+ # `hfs scaffold app` writes. A spec or a test (BE_SPEC_PLACEMENT, FE_NO_TESTS), a check or lint source (`check-*`, `eslint-local-rules*`,
294
+ # a local eslint plugin: HFS_REPO_LOCAL_CHECK) and anything that re-implements a canon check is not an operational script and has no place here.
295
+ path: "scripts/{.gitkeep,*.mjs,*.cjs,*.ps1,*.sh}"
230
296
  presence: optional
231
297
  tracked: tracked
232
298
  tier: none
233
299
  tests: none
300
+ rules: [HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK] # one-off codemods and fix-* scripts are agent output, not tooling
301
+ - id: app.sides
302
+ profiles: [app]
303
+ # The two sides. Everything below be/ and fe/ is judged by the side's own slots (profiles be and fe) with the side folder as its root;
304
+ # this slot answers only for the side folders themselves.
305
+ path: "{be,fe}/"
306
+ presence: required
307
+ tracked: tracked
308
+ tier: none
309
+ tests: none
310
+ - id: app.starciwork
311
+ profiles: [app]
312
+ path: ".starciwork/"
313
+ presence: required
314
+ tracked: tracked
315
+ tier: none
316
+ tests: none
317
+ requires: [.gitignore, workspace.yaml, features/index.yaml]
318
+ allows: [workspace.yaml, "brand/**", shell/index.yaml, "features/<feature>/index.yaml",
319
+ "features/<feature>/<family>/<name>/index.yaml", "features/<feature>/br/<rule>/ac/<name>/index.yaml",
320
+ "features/<feature>/ui/<name>/assets/<approved-file>", "features/<feature>/uat/<name>/{index,accounts,fixtures}.yaml",
321
+ "features/<feature>/uat/<name>/{seed,cleanup}.sql", "_resources/{identities,environments,fixtures}/<slug>/resource.yaml"]
322
+ forbids: [evidence/, logs/, reports/, captures/, runs/, "kernel-*/", "*.sqlite*", _derived/, "work/node records"]
323
+ rules: [HFS_AGENT_DATA_TRACKED, HFS_WORK_NODE_RETIRED, HFS_IDENTITY_CUSTODY]
324
+ - id: app.build-output
325
+ profiles: [app]
326
+ path: "{**/node_modules/,coverage/,reports/,test-results/,**/*.tsbuildinfo}"
327
+ presence: optional
328
+ tracked: ignored
329
+ tier: none
330
+ tests: none
331
+ - id: app.tool-cache
332
+ profiles: [app]
333
+ path: "{.eslintcache,.turbo/,.scannerwork/,.sonar/,.jest-cache/,.cache/,.tools/}"
334
+ presence: forbidden
335
+ tracked: external
336
+ tier: none
337
+ tests: none
338
+ goesTo: "${STARCI_CACHE_HOME:-%LOCALAPPDATA%/StarCi/cache}/<repo>/<tool>/ (the canon presets set eslint --cache-location, jest cacheDirectory, turbo cacheDir, sonar.working.directory)"
339
+ - id: app.agent-output
340
+ profiles: [app]
341
+ path: "{report*.json,*.tmp.json,%*%,nul,.qwen*,.artifacts/,.tmp-*,*-lint.json,lf.json,lt.json,*.log,design-plans/,.gitmounts/,.dat,vi-flat.txt}"
342
+ presence: forbidden
343
+ tracked: external
344
+ tier: none
345
+ tests: none
346
+ goesTo: "agent scratchpad or the StarCi blob store (%LOCALAPPDATA%/StarCi/blobs), cited by {name, sha256}"
347
+ rules: [HFS_UNTRACKED_ROOT_ENTRY, HFS_AGENT_DATA_TRACKED]
348
+ - id: app.worktrees
349
+ profiles: [app]
350
+ path: "{.worktrees/,worktrees/,.starciwork/worktrees/}"
351
+ presence: forbidden
352
+ tracked: external
353
+ tier: none
354
+ tests: none
355
+ goesTo: "D:/starci-lanes/<project>/<lane>/ (outside every repository) for a lane; a runtime op worktree (.starciwork/worktrees/<op>) is git-excluded, never tracked, and removed when its op settles"
356
+ - id: app.plaintext-env
357
+ profiles: [app]
358
+ path: "{.env,.env.*,.secrets/,**/*.pem,**/*.key}"
359
+ presence: forbidden
360
+ tracked: external
361
+ tier: none
362
+ tests: none
363
+ goesTo: "be/.starcistacks/<env>/secrets/<slug>.enc; decrypted values only in memory or under %LOCALAPPDATA%/StarCi/secrets/<project>/<env>/ via *_FILE"
364
+ rules: [HFS_PLAINTEXT_SECRET]
365
+
366
+ # ----- side root (both sides): what the old standalone repository root held, less the app-root files ----------
367
+ - id: repo.side-root-forbidden
368
+ profiles: [be, fe]
369
+ path: "{package.json,package-lock.json,hfs.json,README.md,.gitignore,.gitattributes,.husky/,.github/,.starciwork/,sonar-project.properties,.prettierrc,.prettierignore,scripts/}"
370
+ presence: forbidden
371
+ tracked: external
372
+ tier: none
373
+ tests: none
374
+ goesTo: "the app root: one package.json, lockfile, hfs.json, README, git and CI files, hooks, formatter, Sonar configuration, scripts/ and .starciwork per app"
234
375
  - id: be.tool-config
235
376
  profiles: [be]
236
377
  # Managed files (hfs sync renders them, hfs check compares them): the whole tool configuration of a back end. Each is
@@ -239,9 +380,10 @@ slots:
239
380
  # specs import them); tsconfig.build.json puts an overlay preset after it and adds only the path-relative options a
240
381
  # preset cannot hold; the tests' own src/tests/tsconfig.json (the nearest config of a world, integration, e2e or contract
241
382
  # file, so typed lint and typecheck:tests both find it) extends it with the e2e preset; eslint.config.mjs is the
242
- # one-line starciBeConfig call; jest.config.js calls the @starci/jest-preset factory; .prettierrc names
243
- # @starci/prettier-config; .prettierignore is the template.
244
- path: "{tsconfig.json,tsconfig.build.json,src/tests/tsconfig.json,eslint.config.mjs,jest.config.js,.prettierrc,.prettierignore}"
383
+ # one-line starciBeConfig call; jest.config.js calls the @starci/jest-preset factory (the root scripts run it with
384
+ # --config be/jest.config.js). The formatter configuration is the app's (app.format-config); every preset resolves from the
385
+ # app root node_modules.
386
+ path: "{tsconfig.json,tsconfig.build.json,src/tests/tsconfig.json,eslint.config.mjs,jest.config.js}"
245
387
  presence: required
246
388
  tracked: tracked
247
389
  tier: none
@@ -270,9 +412,9 @@ slots:
270
412
  # reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/next.json and only
271
413
  # names the preset and nothing else (each app's own tsconfig.json, in fe.app.next, carries its aliases and include; there is
272
414
  # no test tsconfig: a front end has no tests, FE_NO_TESTS); eslint.config.mjs is the one-line starciFeConfig call;
273
- # stylelint.config.mjs is the one-line starciStylelintConfig call; .prettierrc names @starci/prettier-config;
274
- # .prettierignore is the template. A front end has no test configuration at all (FE_NO_TESTS).
275
- path: "{tsconfig.json,eslint.config.mjs,stylelint.config.mjs,.prettierrc,.prettierignore}"
415
+ # stylelint.config.mjs is the one-line starciStylelintConfig call. The formatter configuration is the app's (app.format-config).
416
+ # A front end has no test configuration at all (FE_NO_TESTS).
417
+ path: "{tsconfig.json,eslint.config.mjs,stylelint.config.mjs}"
276
418
  presence: required
277
419
  tracked: tracked
278
420
  tier: none
@@ -296,60 +438,6 @@ slots:
296
438
  tests: none
297
439
  goesTo: "nowhere: a front end has exactly the managed tool configuration (fe.tool-config); a change to a rule or a flag is proposed in the .claude runtime"
298
440
  rules: [HFS_TOOL_CONFIG_LOCAL]
299
- - id: repo.quality-config
300
- profiles: [be, fe]
301
- path: sonar-project.properties
302
- presence: required
303
- tracked: tracked
304
- tier: none
305
- tests: none
306
- managedBy: quality-config
307
- rules: [HFS_SONAR_CONFIG, HFS_MANAGED_FILE_DRIFT]
308
- - id: repo.hooks
309
- profiles: [be, fe]
310
- path: ".husky/{pre-commit,pre-push}"
311
- presence: required
312
- tracked: tracked
313
- tier: none
314
- tests: none
315
- managedBy: hooks
316
- rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
317
- - id: repo.ci
318
- profiles: [be, fe]
319
- path: ".github/workflows/ci.yml"
320
- presence: required
321
- tracked: tracked
322
- tier: none
323
- tests: none
324
- managedBy: ci-workflows
325
- rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
326
- - id: repo.ci-e2e
327
- profiles: [be]
328
- path: ".github/workflows/e2e.yml"
329
- presence: optional # on: workflow_dispatch only
330
- tracked: tracked
331
- tier: none
332
- tests: none
333
- managedBy: ci-workflows
334
- rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
335
- - id: repo.github-meta
336
- profiles: [be, fe]
337
- path: ".github/{CODEOWNERS,pull_request_template.md,ISSUE_TEMPLATE/**,dependabot.yml}"
338
- presence: optional
339
- tracked: tracked
340
- tier: none
341
- tests: none
342
- - id: repo.scripts
343
- profiles: [be, fe]
344
- # The repository's operational scripts only (`*.mjs`, `*.cjs`, `*.ps1`, `*.sh`), or nothing: an empty folder is `scripts/.gitkeep`, which hfs sync --init writes
345
- # when the folder is missing. A spec or a test (BE_SPEC_PLACEMENT, FE_NO_TESTS), a check or lint source (`check-*`, `eslint-local-rules*`, a local eslint
346
- # plugin: HFS_REPO_LOCAL_CHECK) and anything that re-implements a canon check is not an operational script and has no place here.
347
- path: "scripts/{.gitkeep,*.mjs,*.cjs,*.ps1,*.sh}"
348
- presence: optional
349
- tracked: tracked
350
- tier: none
351
- tests: none
352
- rules: [HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK] # one-off codemods and fix-* scripts are agent output, not tooling
353
441
  - id: repo.docs
354
442
  profiles: [be, fe]
355
443
  path: "docs/{adr,runbooks,guides}/**/*.md"
@@ -441,7 +529,7 @@ slots:
441
529
  tracked: external
442
530
  tier: none
443
531
  tests: none
444
- goesTo: ".starcistacks/<env>/secrets/<slug>.enc (BE repo); decrypted values only in memory or under %LOCALAPPDATA%/StarCi/secrets/<project>/<env>/ via *_FILE"
532
+ goesTo: "be/.starcistacks/<env>/secrets/<slug>.enc; decrypted values only in memory or under %LOCALAPPDATA%/StarCi/secrets/<project>/<env>/ via *_FILE"
445
533
  rules: [HFS_PLAINTEXT_SECRET]
446
534
  - id: be.root-e2e
447
535
  profiles: [be]
@@ -471,15 +559,6 @@ slots:
471
559
  tests: none
472
560
  why: emitted by `npm run contract:emit` (`hfs emit-contracts`, never written by hand) from the app's typed operation table `apps/<app>/src/operations.ts` (canon BE-OPERATIONS-1); CI fails when the emitted file differs
473
561
  rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
474
- - id: fe.contract.copy
475
- profiles: [fe]
476
- path: "apps/<app>/src/modules/api/contract/<be-app>.{graphql,json}"
477
- presence: opt-in
478
- tracked: tracked
479
- tier: none
480
- tests: none
481
- why: byte copy of the BE snapshot, refreshed by `npm run contract:pull`; the land gate compares hashes
482
- rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
483
562
 
484
563
  # ----- BE: apps (one slot per app kind; a new kind = a new slot, minor bump) ---------------------------
485
564
  - id: be.app.api
@@ -739,7 +818,7 @@ slots:
739
818
  presence: optional
740
819
  tracked: tracked
741
820
  tier: e2e
742
- allows: [global-setup.ts, global-teardown.ts, use-test-world.ts, fakes/] # jest globalSetup/globalTeardown default-export (Jest API); plus root files with a role suffix (test-world-files, R47)
821
+ allows: [global-setup.ts, global-teardown.ts, use-test-world.ts, "fakes/<provider>/server.ts", fakes/] # jest globalSetup/globalTeardown default-export (Jest API); fakes/<provider>/server.ts is the fake server of a provider, a literal file name BE_SOURCE_FORM reads as its role; plus root files with a role suffix (test-world-files, R47)
743
822
  tests: none
744
823
  why: >-
745
824
  the ONLY test infrastructure location, shared by integration and e2e: global-setup.ts starts the shared
@@ -827,20 +906,6 @@ slots:
827
906
  goesTo: "src/tests/world/ (the only test infrastructure location; e2e/<area>/ holds only *.e2e-spec.ts)"
828
907
 
829
908
  # ----- BE: work and stacks -----------------------------------------------------------------------------
830
- - id: be.starciwork
831
- profiles: [be]
832
- path: ".starciwork/"
833
- presence: required
834
- tracked: tracked
835
- tier: none
836
- tests: none
837
- requires: [.gitignore, workspace.yaml, features/index.yaml]
838
- allows: [workspace.yaml, "brand/**", shell/index.yaml, "features/<feature>/index.yaml",
839
- "features/<feature>/<family>/<name>/index.yaml", "features/<feature>/br/<rule>/ac/<name>/index.yaml",
840
- "features/<feature>/ui/<name>/assets/<approved-file>", "features/<feature>/uat/<name>/{index,accounts,fixtures}.yaml",
841
- "features/<feature>/uat/<name>/{seed,cleanup}.sql", "_resources/{identities,environments,fixtures}/<slug>/resource.yaml"]
842
- forbids: [evidence/, logs/, reports/, captures/, runs/, "kernel-*/", "*.sqlite*", _derived/, "work/node records"]
843
- rules: [HFS_AGENT_DATA_TRACKED, HFS_WORK_NODE_RETIRED, HFS_IDENTITY_CUSTODY]
844
909
  - id: be.starcistacks
845
910
  profiles: [be]
846
911
  path: ".starcistacks/"
@@ -864,13 +929,13 @@ slots:
864
929
  # ----- FE: apps ----------------------------------------------------------------------------------------
865
930
  - id: fe.app.next
866
931
  profiles: [fe]
867
- path: "apps/<app>/{package.json,next.config.ts,tsconfig.json,postcss.config.mjs}"
932
+ path: "apps/<app>/{next.config.ts,tsconfig.json,postcss.config.mjs}"
868
933
  appKind: next
869
934
  presence: required
870
935
  tracked: tracked
871
936
  tier: none
872
937
  minInstances: 1
873
- requires: [package.json, next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/,
938
+ requires: [next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/,
874
939
  "src/app/global-error.tsx", "src/app/[locale]/layout.tsx", "src/app/[locale]/error.tsx", "src/app/[locale]/not-found.tsx", "src/app/[locale]/loading.tsx"]
875
940
  tests: none
876
941
  rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS]
@@ -977,7 +1042,7 @@ slots:
977
1042
  # client.ts/outcome.ts (fe.transport.client/outcome) in a one-app repository, or the api package's
978
1043
  # (fe.package.api.client/outcome) when the repository shares it (FE_TRANSPORT_OWNER, machine).
979
1044
  requires: [index.ts]
980
- allows: [index.ts, client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/]
1045
+ allows: [index.ts, client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", __generated__/]
981
1046
  roles: {entry: index.ts, reader: "read-*.ts"} # a reader is a server module (FE_CLIENT_REACHES_SERVER)
982
1047
  tests: none
983
1048
  rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
@@ -1102,10 +1167,10 @@ slots:
1102
1167
  # Checks that read this manifest (all ship from .claude; none keeps its own path list)
1103
1168
  consumers:
1104
1169
  - scripts/lib/hfs-slots.mjs # the loader every consumer below goes through
1105
- - scripts/checks/hfs/slot-check.mjs # HFS_SLOT_* / tracked / external / managed drift
1170
+ - scripts/lib/hfs-check.mjs # hfs check: HFS_SLOT_* / tracked / external, the side view per side
1106
1171
  - scripts/checks/architecture.mjs # tiers, direction matrix, cycles, reachability
1107
1172
  - packages/eslint/be starciBeConfig({hfs}) # file globs for rules come from slots
1108
1173
  - packages/eslint/fe starciFeConfig({hfs})
1109
- - packages/stylelint-canon # colour and brand allowances from fe.modules.brand
1174
+ - packages/stylelint # @starci/stylelint-canon: colour and brand allowances from fe.modules.brand
1110
1175
  - packages/hfs/sync # renders managedBy templates (hfs sync) and judges them (hfs check)
1111
1176
  - modules/kernel/failure-codes.yaml # every rule id listed above has a Vietnamese why
@@ -17,7 +17,7 @@ provenance:
17
17
  - packages/grammar/src/**
18
18
  limitations: |
19
19
  Folder rules place code by responsibility. They do not prove rendered behavior or accessibility. Inspect the
20
- selected repository's actual source, manifests, aliases and lint and test configuration. Verify installed Grammar
20
+ selected app's actual source, manifests, aliases and lint configuration. Verify installed Grammar
21
21
  exports at @starci/grammar/common.
22
22
  rules:
23
23
  - id: FE-FOLDER-1
@@ -25,9 +25,9 @@ rules:
25
25
  kind: mandatory
26
26
  hfsRules: [R01, R02, R59, R64]
27
27
  requirement: |
28
- Every frontend repository is an apps/<app>/ monorepo even with one app, named by its role (for example web), and
29
- all product source lives under apps/<app>/src. A repository-root src/ does not exist. Each Next source root
30
- (apps/<app>/src) uses app/ for framework adapters; features/{pages,layouts,overlays} for route-facing product
28
+ The front end of every app (`fe/`) is an fe/apps/<app>/ monorepo even with one app, named by its role (for example
29
+ web), and all product source lives under fe/apps/<app>/src. An fe/src/ does not exist. Each Next source root
30
+ (fe/apps/<app>/src) uses app/ for framework adapters; features/{pages,layouts,overlays} for route-facing product
31
31
  composition; components/{blocks,composites,branches,leaves} for product visuals; hooks/<domain> for React hooks;
32
32
  and modules/<capability> for cohesive reusable technical and domain capabilities. Every app has the modules api,
33
33
  config, i18n and routes, and one brand.css under modules/brand when it sets brand tokens. No other empty role
@@ -63,47 +63,46 @@ rules:
63
63
  - instrumentation-client.mjs
64
64
  - next-env.d.ts
65
65
  frameworkPinnedRootExports:
66
- # Export names the framework mandates in a pinned root file, keyed by file stem (read by
67
- # scripts/checks/architecture/framework-pinned.mjs). FE_SOURCE_NAME_SHAPE accepts exactly these names in exactly
68
- # these files when they sit directly in a Next source root; everywhere else the name-shape rule is unchanged.
66
+ # Export names the framework mandates in a pinned root file, keyed by file stem: a naming review keeps exactly these
67
+ # names in exactly these files when they sit directly in a Next source root.
69
68
  middleware: [config, middleware, default]
70
69
  proxy: [config, proxy, default]
71
70
  instrumentation: [register, onRequestError]
72
71
  instrumentation-client: [onRouterTransitionStart]
73
72
  rationale: |
74
- One fixed source root per app keeps every repository the same shape while preventing app helpers, component-local
73
+ One fixed source root per app keeps every front end the same shape while preventing app helpers, component-local
75
74
  hooks and feature-local transport buckets from becoming undeclared owners.
76
75
  cases:
77
76
  - id: case-1
78
77
  when: "A routed authentication scenario"
79
- write: "apps/<app>/src/features/pages/AuthenticationPage/index.tsx"
78
+ write: "fe/apps/<app>/src/features/pages/AuthenticationPage/index.tsx"
80
79
  - id: case-2
81
80
  when: "A reusable API client, reader or session capability"
82
81
  write: >
83
- apps/<app>/src/modules/api/ or apps/<app>/src/modules/session/ with one explicit public index.ts entry
82
+ fe/apps/<app>/src/modules/api/ or fe/apps/<app>/src/modules/session/ with one explicit public index.ts entry
84
83
  - id: case-3
85
84
  when: "A reusable product visual without scenario ownership"
86
- write: "apps/<app>/src/components/{blocks,composites,branches,leaves}/<Name>/index.tsx"
85
+ write: "fe/apps/<app>/src/components/{blocks,composites,branches,leaves}/<Name>/index.tsx"
87
86
  - id: case-4
88
87
  when: "A React hook"
89
- write: "apps/<app>/src/hooks/<domain>/useAutoScroll.ts; built-in React hook calls may remain in visuals"
88
+ write: "fe/apps/<app>/src/hooks/<domain>/useAutoScroll.ts; built-in React hook calls may remain in visuals"
90
89
  - id: case-5
91
90
  kind: exception
92
91
  when: "Next.js only loads the file from the source root (proxy, instrumentation, instrumentation-client, next-env.d.ts)"
93
92
  write: >
94
- apps/<app>/src/proxy.ts beside apps/<app>/src/app/ as a thin adapter; locale routing logic lives in
95
- apps/<app>/src/modules/i18n/ and the adapter imports it; the names the framework mandates there
93
+ fe/apps/<app>/src/proxy.ts beside fe/apps/<app>/src/app/ as a thin adapter; locale routing logic lives in
94
+ fe/apps/<app>/src/modules/i18n/ and the adapter imports it; the names the framework mandates there
96
95
  (frameworkPinnedRootExports, for example export const config = { matcher }) keep their framework spelling
97
96
  - id: case-6
98
- when: "Any other file or folder directly under the source root (apps/<app>/src/i18n/request.ts, apps/<app>/src/config.ts), and a repository-root src/"
97
+ when: "Any other file or folder directly under the source root (fe/apps/<app>/src/i18n/request.ts, fe/apps/<app>/src/config.ts), and an fe/src/"
99
98
  write: >
100
- Move it to its owner, for example apps/<app>/src/modules/i18n/request.ts, and point framework configuration
101
- (apps/<app>/next.config.ts) at the new path
99
+ Move it to its owner, for example fe/apps/<app>/src/modules/i18n/request.ts, and point framework configuration
100
+ (fe/apps/<app>/next.config.ts) at the new path
102
101
  - id: case-7
103
102
  when: "The per-slot data-status recipe"
104
103
  write: >
105
- apps/<app>/src/components/composites/SlotView/index.tsx, apps/<app>/src/modules/slot/index.ts (Slot, toSlot,
106
- SlotLabels) and apps/<app>/src/hooks/slot/useSlotLabels.ts, one copy each (examples/shape-slot)
104
+ fe/apps/<app>/src/components/composites/SlotView/index.tsx, fe/apps/<app>/src/modules/slot/index.ts (Slot, toSlot,
105
+ SlotLabels) and fe/apps/<app>/src/hooks/slot/useSlotLabels.ts, one copy each (examples/shape-slot)
107
106
  verification:
108
107
  automated:
109
108
  - HFS_SLOT_UNDECLARED
@@ -126,12 +125,12 @@ rules:
126
125
  requirement: |
127
126
  A split unit (a connected block, and a feature page, layout or overlay) uses index.tsx for the connected X and
128
127
  sibling component.tsx for the pure XBase, its XBaseProps and xDefaultState, plus XState when the shape is its own union
129
- (a shape that is a domain status is typed with that status, never a second name for it). Only that index.tsx and the
130
- unit's specs import component.tsx. Pure blocks, composites, branches and leaves use index.tsx only and never gain an
128
+ (a shape that is a domain status is typed with that status, never a second name for it). Only that index.tsx imports
129
+ component.tsx. Pure blocks, composites, branches and leaves use index.tsx only and never gain an
131
130
  empty twin. classNames.ts is present only when the unit owns class strings. Budgets: component.tsx 300 lines,
132
131
  connected index.tsx 200 lines, at most 6 data hooks and 6 useState per connected unit.
133
132
  rationale: |
134
- Stable basenames make class, tests and the connected and presentational split discoverable without forwarding
133
+ Stable basenames make class names and the connected and presentational split discoverable without forwarding
135
134
  twins, and budgets keep a unit reviewable.
136
135
  cases:
137
136
  - id: case-1
@@ -210,7 +209,7 @@ rules:
210
209
  non-hook helpers of that domain (key builders, token readers). A file in hooks/ that is not a hook and is not the shared
211
210
  file is a finding. Server-side readers (fetch and map, no React) live in modules/api/<domain>/read-*.ts and are
212
211
  wrapped in React cache() when several blocks call them in one request. Generic client plumbing, the Outcome type,
213
- the contract copy and generated wire types live in modules/api; environment reading lives in modules/config; every
212
+ the generated wire types (from be/contracts/, through the root codegen) live in modules/api; environment reading lives in modules/config; every
214
213
  href builder lives in modules/routes; locale routing lives in modules/i18n; colour values live in modules/brand.
215
214
  A helper name defined twice across hook files is a finding.
216
215
  rationale: |
@@ -219,19 +218,19 @@ rules:
219
218
  cases:
220
219
  - id: case-1
221
220
  when: "Authentication request lifecycle"
222
- write: "apps/<app>/src/hooks/authentication/useAuthentication.ts"
221
+ write: "fe/apps/<app>/src/hooks/authentication/useAuthentication.ts"
223
222
  - id: case-2
224
223
  when: "A helper shared by the hooks of one domain"
225
- write: "apps/<app>/src/hooks/sales/sales.shared.ts, for example useSalesAccessToken's token reader"
224
+ write: "fe/apps/<app>/src/hooks/sales/sales.shared.ts, for example useSalesAccessToken's token reader"
226
225
  - id: case-3
227
226
  when: "A server read used by several blocks"
228
- write: "apps/<app>/src/modules/api/sales/read-sales-summary.ts, exported through modules/api/index.ts"
227
+ write: "fe/apps/<app>/src/modules/api/sales/read-sales-summary.ts, exported through modules/api/index.ts"
229
228
  - id: case-4
230
229
  when: "The API client"
231
- write: "apps/<app>/src/modules/api/client.ts and apps/<app>/src/modules/api/outcome.ts"
230
+ write: "fe/apps/<app>/src/modules/api/client.ts and fe/apps/<app>/src/modules/api/outcome.ts"
232
231
  - id: case-5
233
232
  when: "Intrinsic custom helper"
234
- write: "apps/<app>/src/hooks/ui/useAutoScroll.ts; direct useRef and useState calls may remain in their visual owner"
233
+ write: "fe/apps/<app>/src/hooks/ui/useAutoScroll.ts; direct useRef and useState calls may remain in their visual owner"
235
234
  verification:
236
235
  automated:
237
236
  - FE_HOOKS_ARE_HOOKS
@@ -248,21 +247,22 @@ rules:
248
247
  kind: mandatory
249
248
  hfsRules: [R63]
250
249
  requirement: |
251
- A shared package is packages/<pkg>/ with package.json (explicit exports), src/index.ts, tsconfig.json and colocated
252
- specs, built to dist. Grammar components live under packages/grammar/src/core/<tier>/<Name>/ with collocated class
253
- strings and specs; family entries and built-output proof stay at the package roots. See packages.yaml for the shared
254
- UI package shape.
250
+ A shared front-end package is fe/packages/<pkg>/ with package.json (explicit exports), src/index.ts and tsconfig.json,
251
+ built to dist, and no spec (the front end has no tests, FE_NO_TESTS). The design system is the runtime's own
252
+ `@starci/grammar` package (`.claude/packages/grammar`, installed from the npm registry): its components live under
253
+ packages/grammar/src/core/<tier>/<Name>/ of the runtime with collocated class strings and specs, and its family
254
+ entries and built-output proof stay at its package roots. See packages.yaml for the shared UI package shape.
255
255
  rationale: |
256
256
  Package layout separates primitives from app composition and keeps public entry verification against dist.
257
257
  cases:
258
258
  - id: case-1
259
- when: "A Grammar component"
259
+ when: "A Grammar component (runtime package)"
260
260
  write: "packages/grammar/src/core/<primitive|composite|branch|composition>/<Name>/index.tsx"
261
261
  - id: case-2
262
- when: "A Grammar component's class strings and spec"
262
+ when: "A Grammar component's class strings and spec (runtime package)"
263
263
  write: "sibling classNames.ts and index.spec.tsx"
264
264
  - id: case-3
265
- when: "Built-output proof"
265
+ when: "Grammar built-output proof (runtime package)"
266
266
  write: "common/index.test.mjs, core/index.test.mjs, package-boundary.test.mjs (node:test against dist/)"
267
267
  verification:
268
268
  automated:
@@ -296,7 +296,7 @@ rules:
296
296
  - id: case-4
297
297
  when: "A deployment constant"
298
298
  write: >
299
- Never. process.env.NEXT_PUBLIC_* is read only in apps/<app>/src/modules/config and reached through that module,
299
+ Never. process.env.NEXT_PUBLIC_* is read only in fe/apps/<app>/src/modules/config and reached through that module,
300
300
  with no localhost fallback; a missing value in production throws when the module loads.
301
301
  verification:
302
302
  automated: