@starci/hfs 1.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 (74) hide show
  1. package/README.md +38 -0
  2. package/bin/hfs.mjs +100 -0
  3. package/package.json +28 -0
  4. package/runtime/engine/runtime-root.mjs +32 -0
  5. package/runtime/engine/yaml.mjs +161 -0
  6. package/runtime/knowledge/hfs/canon-pins.yaml +212 -0
  7. package/runtime/knowledge/hfs/slots.yaml +842 -0
  8. package/runtime/modules/kernel/failure-codes.yaml +169 -0
  9. package/runtime/scripts/lib/glob.mjs +23 -0
  10. package/runtime/scripts/lib/hfs-check.mjs +305 -0
  11. package/runtime/scripts/lib/hfs-slots.mjs +675 -0
  12. package/runtime/scripts/lib/path-key.mjs +15 -0
  13. package/sync/cli.mjs +15 -0
  14. package/sync/hygiene.mjs +92 -0
  15. package/sync/index.mjs +224 -0
  16. package/sync/skeleton.mjs +54 -0
  17. package/sync/sonar-key.mjs +45 -0
  18. package/templates/be/e2e.yml +21 -0
  19. package/templates/be/gitignore +2 -0
  20. package/templates/be/pre-commit +8 -0
  21. package/templates/be/skeleton/apps/__app__/src/__app__.composition.spec.ts +31 -0
  22. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +7 -0
  23. package/templates/be/skeleton/apps/__app__/src/app.module.ts +23 -0
  24. package/templates/be/skeleton/apps/__app__/src/main.ts +20 -0
  25. package/templates/be/skeleton/src/features/system-health/index.ts +1 -0
  26. package/templates/be/skeleton/src/features/system-health/system-health.module.ts +6 -0
  27. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.spec.ts +13 -0
  28. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +18 -0
  29. package/templates/be/skeleton/src/features/system-health/transport/http/system-health-http.module.ts +10 -0
  30. package/templates/be/skeleton/src/modules/platform/config/env-source.spec.ts +36 -0
  31. package/templates/be/skeleton/src/modules/platform/config/env-source.ts +40 -0
  32. package/templates/be/skeleton/src/modules/platform/config/errors/config.error.ts +21 -0
  33. package/templates/be/skeleton/src/modules/platform/config/index.ts +5 -0
  34. package/templates/be/skeleton/src/modules/platform/config/server.config.spec.ts +19 -0
  35. package/templates/be/skeleton/src/modules/platform/config/server.config.ts +15 -0
  36. package/templates/be/skeleton/src/modules/platform/config/server.options.ts +8 -0
  37. package/templates/be/skeleton/src/modules/platform/errors/domain-error.spec.ts +15 -0
  38. package/templates/be/skeleton/src/modules/platform/errors/domain-error.ts +11 -0
  39. package/templates/be/skeleton/src/modules/platform/errors/error.filter.spec.ts +38 -0
  40. package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +24 -0
  41. package/templates/be/skeleton/src/modules/platform/errors/index.ts +2 -0
  42. package/templates/be/skeleton/src/modules/platform/logging/index.ts +5 -0
  43. package/templates/be/skeleton/src/modules/platform/logging/json-logger.spec.ts +33 -0
  44. package/templates/be/skeleton/src/modules/platform/logging/json-logger.ts +35 -0
  45. package/templates/be/skeleton/src/modules/platform/logging/log-id.ts +9 -0
  46. package/templates/be/skeleton/src/modules/platform/logging/logger.port.ts +19 -0
  47. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +11 -0
  48. package/templates/be/sonar-project.properties +10 -0
  49. package/templates/be/starciwork.gitignore +39 -0
  50. package/templates/common/ci.yml +50 -0
  51. package/templates/common/codecov.yml +13 -0
  52. package/templates/common/gitignore.base +33 -0
  53. package/templates/common/pre-push +5 -0
  54. package/templates/fe/e2e.yml +22 -0
  55. package/templates/fe/gitignore +3 -0
  56. package/templates/fe/pre-commit +7 -0
  57. package/templates/fe/skeleton/apps/__app__/next.config.ts +11 -0
  58. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/error.tsx +22 -0
  59. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +31 -0
  60. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +15 -0
  61. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +27 -0
  62. package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +24 -0
  63. package/templates/fe/skeleton/apps/__app__/src/app/globals.css +1 -0
  64. package/templates/fe/skeleton/apps/__app__/src/app/health/live/route.spec.ts +10 -0
  65. package/templates/fe/skeleton/apps/__app__/src/app/health/live/route.ts +5 -0
  66. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +8 -0
  67. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +19 -0
  68. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages.spec.ts +27 -0
  69. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/navigation.ts +5 -0
  70. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +13 -0
  71. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.spec.ts +10 -0
  72. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +9 -0
  73. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +10 -0
  74. package/templates/fe/sonar-project.properties +11 -0
@@ -0,0 +1,842 @@
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
3
+ # repository must match exactly one slot. Checks, lint factories, the architecture machine, templates and the why
4
+ # catalog READ this file through scripts/lib/hfs-slots.mjs; none of them hardcodes a path.
5
+ # 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
8
+
9
+ versioning:
10
+ # MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 1).
11
+ patch: wording, why text, examples; no check result can change.
12
+ minor: >-
13
+ add a slot (always presence optional or opt-in), add an app kind or protocol slot, add a rule at severity warn,
14
+ tighten a budget that no pinned repo currently exceeds. A minor never changes an existing slot's path,
15
+ presence, imports or tests. Every repo pinned to the same MAJOR stays green without edits.
16
+ major: >-
17
+ change or remove an existing slot, make an optional slot required, promote a rule warn to error, change the
18
+ direction matrix. Needs owner approval and a migration lane per repo. There is no compatibility window: the old
19
+ MAJOR is refused the day the new one lands (owner ruling: no backward compatibility).
20
+ retire: >-
21
+ a slot is never edited in place; it gets retiredIn (a major) and a successor slot id. After that major a
22
+ path matching a retired slot is HFS_SLOT_RETIRED (error).
23
+ pins: knowledge/hfs/canon-pins.yaml carries the exact versions of every @starci/* package and framework this MAJOR supports.
24
+
25
+ # Field meaning, per slot.
26
+ # id stable name, never changes meaning inside a major.
27
+ # profiles be, fe, or both: the repository profiles the slot exists in.
28
+ # path pattern. <name> is one path segment (bound to the variable name), * and ? stay inside a segment,
29
+ # ** spans directories, {a,b} alternates. A pattern ending in / is a directory: it owns everything
30
+ # below it, except what a more specific slot owns. Otherwise it names files.
31
+ # presence required | optional | opt-in (legal only when hfs.json lists it or declares an app of its kind) | forbidden.
32
+ # A slot is optional in the sense of the requirements list when its presence is not required.
33
+ # tracked tracked (committed) | ignored (may exist, must be gitignored) | external (must not exist in the tree;
34
+ # goesTo says where it lives).
35
+ # tier node of the import direction matrix (tiers below); none = takes no part in import checks;
36
+ # inherit = the tier of the enclosing owner.
37
+ # owner true when every directory matching the slot is an owner: the unit cross-owner imports and cycles use.
38
+ # requires files or directories that must exist inside each instance; <var> is replaced by the bound name,
39
+ # a leading / means repository root; a trailing / means directory.
40
+ # requiredInstances variable -> names that must exist as instances (config, logging ... of platform).
41
+ # minInstances at least this many instances (one feature, one api app).
42
+ # requiredWhen connections: required only when hfs.json declares a database connection.
43
+ # allows / forbids names inside the instance; the loader exposes them, slot-check enforces them.
44
+ # tests unit-beside | e2e | none.
45
+ # budget size budgets (lines per file, files per slot, exports).
46
+ # appKind the app kind of hfs.json apps[].kind whose directory this slot describes.
47
+ # managedBy template id that hfs sync renders; a difference is HFS_MANAGED_FILE_DRIFT.
48
+ # rules rule ids (the catalog lands with knowledge/hfs/rules.yaml).
49
+ # since / retiredIn / successor lifecycle.
50
+ presenceValues: [required, optional, opt-in, forbidden]
51
+ trackedValues: [tracked, ignored, external]
52
+ testValues: [unit-beside, e2e, none]
53
+
54
+ appKinds:
55
+ be: [api, worker, migrate, cli]
56
+ fe: [next]
57
+
58
+ # ---------------------------------------------------------------------------------------------------------
59
+ # Tiers and the dependency direction matrix (owner granularity; type-only imports count; no cycles anywhere)
60
+ # mayImport lists the tiers a file of that tier may import. Importing inside the same owner is always allowed.
61
+ # ---------------------------------------------------------------------------------------------------------
62
+ tiers:
63
+ be:
64
+ app: {mayImport: [feature, domain, platform, integrations, package]}
65
+ feature: {mayImport: [domain, platform, integrations, package]} # never another feature
66
+ domain: {mayImport: [domain, platform, integrations, package], acyclic: true}
67
+ integrations: {mayImport: [platform, package]}
68
+ platform: {mayImport: [platform, package], acyclic: true}
69
+ e2e: {mayImport: [e2e, app, feature, domain, platform, integrations, fixtures, package]}
70
+ fixtures: {mayImport: [domain, platform, integrations, package]} # public contracts only
71
+ package: {mayImport: [package]}
72
+ fe:
73
+ route: {mayImport: [feature, foundation, modules, transport, package]} # app/** adapters
74
+ feature: {mayImport: [components, hooks, foundation, modules, transport, package]}
75
+ # A component never reaches data: config, routes, i18n and types are foundation; every other module and the api
76
+ # transport are reached through a hook (owner decision 2026-09-29).
77
+ components: {mayImport: [components, hooks, foundation, package], lowerLayerOnly: true} # blocks, composites, branches, leaves
78
+ # Across domains a hook is imported only through the other domain's public index (hooks/<domain>/index.ts), never deep.
79
+ hooks: {mayImport: [hooks, foundation, modules, transport, package]}
80
+ foundation: {mayImport: [foundation, package], acyclic: true} # modules/{config,i18n,routes,types}
81
+ modules: {mayImport: [foundation, modules, transport, package], acyclic: true}
82
+ transport: {mayImport: [foundation, modules, package]} # modules/api: the one fetch
83
+ e2e: {mayImport: [e2e, package]} # black box: no app source
84
+ package: {mayImport: [package]}
85
+ crossOwner: every import across owners targets the owner's public entry (index.ts or index.tsx); never export *.
86
+ crossApp: apps never import each other; shared code is a packages/<pkg> slot.
87
+
88
+ # Parameters the lint factories and checks read per profile (through ruleParams(profile) in scripts/lib/hfs-slots.mjs).
89
+ ruleParams:
90
+ be:
91
+ # BE_MODULE_SHAPE: the only modules that may be @Global(); directories under src/modules/platform/.
92
+ globalModules: [src/modules/platform/config/, src/modules/platform/logging/, src/modules/platform/database/]
93
+ # HFS_SIZE_GROWTH: a file above soft may not grow against its parent commit; a new file stays within soft.
94
+ fileLines: {soft: 500, hardGrowth: true}
95
+ # HFS_DUPLICATE_BLOCK: a token-normalised block of at least this many source lines that appears in two owners.
96
+ duplicateBlockLines: 25
97
+ fe:
98
+ # Slot budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
99
+ fileLines: {soft: 500, hardGrowth: true}
100
+ # FE_TRANSPORT_OWNER: the one module per app that may call fetch.
101
+ clientModule: "apps/<app>/src/modules/api/client.ts"
102
+ duplicateBlockLines: 25
103
+
104
+ slots:
105
+
106
+ # ----- repository root (both profiles) ---------------------------------------------------------------
107
+ - id: repo.readme
108
+ profiles: [be, fe]
109
+ path: README.md
110
+ presence: required
111
+ tracked: tracked
112
+ tier: none
113
+ tests: none
114
+ rules: [HFS_README_*]
115
+ - id: repo.declaration
116
+ profiles: [be, fe]
117
+ path: hfs.json
118
+ presence: required
119
+ tracked: tracked
120
+ tier: none
121
+ tests: none
122
+ rules: [HFS_ARCH_CONFIG_UNREAD]
123
+ - id: repo.package-manifest
124
+ profiles: [be, fe]
125
+ path: "{package.json,package-lock.json}"
126
+ presence: required
127
+ tracked: tracked
128
+ tier: none
129
+ tests: none
130
+ rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT]
131
+ - id: repo.git-meta
132
+ profiles: [be, fe]
133
+ path: "{.gitignore,.gitattributes}"
134
+ presence: required
135
+ tracked: tracked
136
+ tier: none
137
+ tests: none
138
+ managedBy: gitignore-base
139
+ rules: [HFS_GITIGNORE_BLOCK_DRIFT]
140
+ - id: repo.tool-config
141
+ profiles: [be, fe]
142
+ path: "{tsconfig.json,eslint.config.mjs,.editorconfig,.nvmrc,.prettierignore}"
143
+ presence: required # each file is a thin extends of a canon package
144
+ tracked: tracked
145
+ tier: none
146
+ tests: none
147
+ managedBy: tool-config
148
+ rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT, HFS_TS_STRICT]
149
+ - id: repo.tool-config-optional
150
+ profiles: [be, fe]
151
+ path: "{tsconfig.build.json,tsconfig.e2e.json,.npmrc,jest.config.e2e.js}"
152
+ presence: optional
153
+ tracked: tracked
154
+ tier: none
155
+ tests: none
156
+ managedBy: tool-config
157
+ rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
158
+ - id: be.tool-config
159
+ profiles: [be]
160
+ path: "{nest-cli.json,jest.config.js}"
161
+ presence: required
162
+ tracked: tracked
163
+ tier: none
164
+ tests: none
165
+ managedBy: tool-config
166
+ rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
167
+ - id: fe.tool-config
168
+ profiles: [fe]
169
+ path: "{vitest.config.ts,vitest.setup.ts,playwright.config.ts,turbo.json}"
170
+ presence: optional
171
+ tracked: tracked
172
+ tier: none
173
+ tests: none
174
+ managedBy: tool-config
175
+ rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
176
+ - id: repo.quality-config
177
+ profiles: [be, fe]
178
+ path: "{sonar-project.properties,codecov.yml}"
179
+ presence: required
180
+ tracked: tracked
181
+ tier: none
182
+ tests: none
183
+ managedBy: quality-config
184
+ rules: [HFS_SONAR_CONFIG, HFS_MANAGED_FILE_DRIFT]
185
+ - id: repo.hooks
186
+ profiles: [be, fe]
187
+ path: ".husky/{pre-commit,pre-push}"
188
+ presence: required
189
+ tracked: tracked
190
+ tier: none
191
+ tests: none
192
+ managedBy: husky
193
+ rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
194
+ - id: repo.ci
195
+ profiles: [be, fe]
196
+ path: ".github/workflows/ci.yml"
197
+ presence: required
198
+ tracked: tracked
199
+ tier: none
200
+ tests: none
201
+ managedBy: ci-workflows
202
+ rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
203
+ - id: repo.ci-e2e
204
+ profiles: [be, fe]
205
+ path: ".github/workflows/e2e.yml"
206
+ presence: optional # on: workflow_dispatch only
207
+ tracked: tracked
208
+ tier: none
209
+ tests: none
210
+ managedBy: ci-workflows
211
+ rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
212
+ - id: repo.github-meta
213
+ profiles: [be, fe]
214
+ path: ".github/{CODEOWNERS,pull_request_template.md,ISSUE_TEMPLATE/**,dependabot.yml}"
215
+ presence: optional
216
+ tracked: tracked
217
+ tier: none
218
+ tests: none
219
+ - id: repo.scripts
220
+ profiles: [be, fe]
221
+ path: "scripts/<name>.mjs"
222
+ presence: optional
223
+ tracked: tracked
224
+ tier: none
225
+ tests: none
226
+ rules: [HFS_SCRIPT_ONE_OFF] # one-off codemods and fix-* scripts are agent output, not tooling
227
+ - id: repo.docs
228
+ profiles: [be, fe]
229
+ path: "docs/{adr,runbooks,guides}/**/*.md"
230
+ presence: opt-in
231
+ tracked: tracked
232
+ tier: none
233
+ tests: none
234
+ rules: [HFS_ROOT_MARKDOWN_FORBIDDEN]
235
+ - id: repo.docs-media
236
+ profiles: [be, fe]
237
+ path: "docs/**/*.{png,svg,webp}"
238
+ presence: opt-in
239
+ tracked: tracked
240
+ tier: none
241
+ tests: none
242
+ budget: {bytes: 512000}
243
+ - id: repo.packages
244
+ profiles: [be, fe]
245
+ path: "packages/<pkg>/"
246
+ presence: opt-in
247
+ tracked: tracked
248
+ tier: package
249
+ owner: true
250
+ requires: [package.json, src/index.ts, tsconfig.json]
251
+ tests: unit-beside
252
+ rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
253
+ - id: repo.app-image
254
+ profiles: [be, fe]
255
+ path: "apps/<app>/Dockerfile"
256
+ presence: optional
257
+ tracked: tracked
258
+ tier: none
259
+ tests: none
260
+ - id: repo.dockerignore
261
+ profiles: [be, fe]
262
+ path: .dockerignore
263
+ presence: optional
264
+ tracked: tracked
265
+ tier: none
266
+ tests: none
267
+
268
+ # ----- never tracked: build output the tools need in place ---------------------------------------------
269
+ - id: repo.build-output
270
+ profiles: [be, fe]
271
+ path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,test-results/,playwright-report/,**/next-env.d.ts,**/*.tsbuildinfo}"
272
+ presence: optional
273
+ tracked: ignored
274
+ tier: none
275
+ tests: none
276
+ - id: repo.generated
277
+ profiles: [be, fe]
278
+ path: "{**/__generated__/,apps/*/src/schema.gql}"
279
+ presence: optional
280
+ tracked: ignored
281
+ tier: none
282
+ tests: none
283
+ why: produced by `npm run codegen` (prebuild and pretypecheck); never committed
284
+
285
+ # ----- never in the working tree: caches, scratch, agent output ----------------------------------------
286
+ - id: repo.tool-cache
287
+ profiles: [be, fe]
288
+ path: "{.eslintcache,.turbo/,.scannerwork/,.sonar/,.jest-cache/,.cache/,.tools/}"
289
+ presence: forbidden
290
+ tracked: external
291
+ tier: none
292
+ tests: none
293
+ goesTo: "${STARCI_CACHE_HOME:-%LOCALAPPDATA%/StarCi/cache}/<repo>/<tool>/ (the canon presets set eslint --cache-location, jest cacheDirectory, turbo cacheDir, sonar.working.directory)"
294
+ - id: repo.agent-output
295
+ profiles: [be, fe]
296
+ path: "{report*.json,*.tmp.json,%*%,nul,.qwen*,.artifacts/,.tmp-*,*-lint.json,lf.json,lt.json,*.log,design-plans/,.gitmounts/,.dat,vi-flat.txt}"
297
+ presence: forbidden
298
+ tracked: external
299
+ tier: none
300
+ tests: none
301
+ goesTo: "agent scratchpad or the StarCi blob store (%LOCALAPPDATA%/StarCi/blobs), cited by {name, sha256}"
302
+ rules: [HFS_UNTRACKED_ROOT_ENTRY, HFS_AGENT_DATA_TRACKED]
303
+ - id: repo.worktrees
304
+ profiles: [be, fe]
305
+ path: "{.worktrees/,worktrees/,.starciwork/worktrees/}"
306
+ presence: forbidden
307
+ tracked: external
308
+ tier: none
309
+ tests: none
310
+ goesTo: "D:/starci-lanes/<project>/<lane>/ (outside every repository)"
311
+ - id: repo.plaintext-env
312
+ profiles: [be, fe]
313
+ path: "{.env,.env.*,.secrets/,**/*.pem,**/*.key}"
314
+ presence: forbidden
315
+ tracked: external
316
+ tier: none
317
+ tests: none
318
+ goesTo: ".starcistacks/<env>/secrets/<slug>.enc (BE repo); decrypted values only in memory or under %LOCALAPPDATA%/StarCi/secrets/<project>/<env>/ via *_FILE"
319
+ rules: [HFS_PLAINTEXT_SECRET]
320
+ - id: be.root-e2e
321
+ profiles: [be]
322
+ path: "e2e/"
323
+ presence: forbidden
324
+ tracked: external
325
+ tier: none
326
+ tests: none
327
+ goesTo: "src/tests/e2e/ for specs; operational probes move to scripts/ or are deleted"
328
+
329
+ # ----- contract snapshots (the committed machine-emitted files, because they are a cross-repo interface) --
330
+ - id: be.contract.graphql
331
+ profiles: [be]
332
+ path: "contracts/<app>/schema.graphql"
333
+ presence: opt-in
334
+ tracked: tracked
335
+ tier: none
336
+ tests: none
337
+ why: emitted by `npm run contract:emit`; CI fails when the emitted file differs (a contract, not a build artefact)
338
+ rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
339
+ - id: be.contract.openapi
340
+ profiles: [be]
341
+ path: "contracts/<app>/openapi.json"
342
+ presence: opt-in
343
+ tracked: tracked
344
+ tier: none
345
+ tests: none
346
+ rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
347
+ - id: fe.contract.copy
348
+ profiles: [fe]
349
+ path: "apps/<app>/src/modules/api/contract/<be-app>.{graphql,json}"
350
+ presence: opt-in
351
+ tracked: tracked
352
+ tier: none
353
+ tests: none
354
+ why: byte copy of the BE snapshot, refreshed by `npm run contract:pull`; the land gate compares hashes
355
+ rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
356
+
357
+ # ----- BE: apps (one slot per app kind; a new kind = a new slot, minor bump) ---------------------------
358
+ - id: be.app.api
359
+ profiles: [be]
360
+ path: "apps/<app>/src/"
361
+ appKind: api
362
+ presence: required # at least one api app
363
+ tracked: tracked
364
+ tier: app
365
+ owner: true
366
+ minInstances: 1
367
+ requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
368
+ allows: [main.ts, app.module.ts, "<app>.composition.spec.ts", "<app>.options.ts"]
369
+ tests: unit-beside # the composition spec boots the REAL AppModule with stubbed options
370
+ budget: {app.module.ts: 250, main.ts: 80}
371
+ rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED]
372
+ - id: be.app.worker
373
+ profiles: [be]
374
+ path: "apps/<app>/src/"
375
+ appKind: worker # composes transport/schedule and transport/message modules only
376
+ presence: opt-in
377
+ tracked: tracked
378
+ tier: app
379
+ owner: true
380
+ requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
381
+ tests: unit-beside
382
+ rules: [BE_APP_COMPOSITION_ONLY, BE_BACKGROUND_UNOWNED]
383
+ - id: be.app.migrate
384
+ profiles: [be]
385
+ path: "apps/<app>/src/"
386
+ appKind: migrate # the ONLY process that runs migrations (one run per connection, before api/worker start)
387
+ presence: required
388
+ requiredWhen: connections # required as soon as hfs.json declares a connection
389
+ tracked: tracked
390
+ tier: app
391
+ owner: true
392
+ requires: [main.ts, "<app>.composition.spec.ts"]
393
+ tests: unit-beside
394
+ rules: [BE_SCHEMA_AUTHORITY, BE_ENTRYPOINT_ONLY_IN_APPS]
395
+ - id: be.app.cli
396
+ profiles: [be]
397
+ path: "apps/<app>/src/"
398
+ appKind: cli
399
+ presence: opt-in
400
+ tracked: tracked
401
+ tier: app
402
+ owner: true
403
+ requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
404
+ tests: unit-beside
405
+
406
+ # ----- BE: features ------------------------------------------------------------------------------------
407
+ - id: be.feature
408
+ profiles: [be]
409
+ path: "src/features/<feature>/"
410
+ presence: required
411
+ tracked: tracked
412
+ tier: feature
413
+ owner: true
414
+ minInstances: 1
415
+ requires: [index.ts, "<feature>.module.ts", application/]
416
+ allows: [index.ts, "<feature>.module.ts", application/, transport/, messages/] # nothing else at feature root
417
+ tests: unit-beside
418
+ budget: {files: 250, indexExports: 60} # a feature above 250 source files must be split by product capability
419
+ rules: [BE_FEATURE_SHAPE, BE_FEATURE_IMPORTS_FEATURE, BE_PUBLIC_SURFACE, BE_FEATURE_NOT_COMPOSED]
420
+ - id: be.feature.application
421
+ profiles: [be]
422
+ path: "src/features/<feature>/application/"
423
+ presence: required
424
+ tracked: tracked
425
+ tier: feature
426
+ allows: ["<action>.use-case.ts", "<action>.contracts.ts", "<action>.{command,query,handler}.ts", "*.spec.ts"]
427
+ forbids: [graphql/, http/, message/, schedule/, transport/] # no protocol names under application/
428
+ tests: unit-beside
429
+ rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT]
430
+ - id: be.feature.application.support
431
+ profiles: [be]
432
+ path: "src/features/<feature>/application/support/"
433
+ presence: optional # helpers and types local to ONE feature; same tier as application, never a shared home
434
+ tracked: tracked
435
+ tier: feature # feature tier: another feature cannot import it (features never import features)
436
+ allows: ["<name>.ts", "<name>.types.ts", "*.spec.ts"]
437
+ forbids: [graphql/, http/, message/, schedule/, transport/]
438
+ tests: unit-beside # a <name>.spec.ts beside every <name>.ts
439
+ rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_FEATURE_IMPORTS_FEATURE]
440
+ since: 1.0.0
441
+ - id: be.transport.http
442
+ profiles: [be]
443
+ path: "src/features/<feature>/transport/http/"
444
+ presence: optional
445
+ tracked: tracked
446
+ tier: feature
447
+ requires: ["<feature>-http.module.ts"]
448
+ allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts", "*.spec.ts"]
449
+ tests: unit-beside
450
+ rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED]
451
+ - id: be.transport.graphql
452
+ profiles: [be]
453
+ path: "src/features/<feature>/transport/graphql/"
454
+ presence: optional
455
+ tracked: tracked
456
+ tier: feature
457
+ requires: ["<feature>-graphql.module.ts"]
458
+ allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts", "*.spec.ts"]
459
+ tests: unit-beside
460
+ rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED]
461
+ - id: be.transport.message
462
+ profiles: [be]
463
+ path: "src/features/<feature>/transport/message/"
464
+ presence: opt-in # queue worker: consumed only by a worker app
465
+ tracked: tracked
466
+ tier: feature
467
+ requires: ["<feature>-message.module.ts"]
468
+ allows: ["<event>.consumer.ts", "<event>.message.ts", "*.spec.ts"]
469
+ tests: unit-beside
470
+ rules: [BE_BACKGROUND_UNOWNED]
471
+ - id: be.transport.schedule
472
+ profiles: [be]
473
+ path: "src/features/<feature>/transport/schedule/"
474
+ presence: opt-in # cron job, sweep or outbox publisher: consumed only by a worker app
475
+ tracked: tracked
476
+ tier: feature
477
+ requires: ["<feature>-schedule.module.ts"]
478
+ allows: ["<job>.job.ts", "*.spec.ts"]
479
+ tests: unit-beside
480
+ rules: [BE_BACKGROUND_UNOWNED]
481
+ - id: be.transport.websocket
482
+ profiles: [be]
483
+ path: "src/features/<feature>/transport/websocket/"
484
+ presence: opt-in
485
+ tracked: tracked
486
+ tier: feature
487
+ requires: ["<feature>-websocket.module.ts"]
488
+ allows: ["<channel>.gateway.ts", "dto/*.ts", "*.spec.ts"]
489
+ tests: unit-beside
490
+ since: 1.0.0
491
+ - id: be.feature.transport.cli
492
+ profiles: [be]
493
+ path: "src/features/<feature>/transport/cli/"
494
+ presence: opt-in # a command line entry: calls application use cases only, composed by an app of kind cli
495
+ tracked: tracked
496
+ tier: feature
497
+ requires: ["<feature>-cli.module.ts"]
498
+ allows: ["<command>.command.ts", "dto/*.ts", "*.spec.ts"]
499
+ tests: unit-beside
500
+ rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_FEATURE_NOT_COMPOSED, BE_ENTRYPOINT_ONLY_IN_APPS]
501
+ since: 1.0.0
502
+
503
+ # ----- BE: modules (three tiers, closed set) -----------------------------------------------------------
504
+ - id: be.domain
505
+ profiles: [be]
506
+ path: "src/modules/domain/<capability>/"
507
+ presence: optional
508
+ tracked: tracked
509
+ tier: domain
510
+ owner: true
511
+ requires: [index.ts]
512
+ allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.contracts.ts", "*.spec.ts"]
513
+ forbids: [testing/, exceptions/, utils/, helpers/, shared/]
514
+ tests: unit-beside
515
+ budget: {indexExports: 60}
516
+ rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_PUBLIC_SURFACE, BE_ERROR_HOME, BE_CONFIG_OWNER]
517
+ - id: be.errors
518
+ profiles: [be]
519
+ path: "src/modules/{domain,integrations}/<capability>/errors/"
520
+ presence: optional
521
+ tracked: tracked
522
+ tier: inherit
523
+ allows: ["<capability>.error.ts", "<name>.error.ts", "*.spec.ts"] # one family per capability, extends platform/errors DomainError
524
+ tests: unit-beside
525
+ rules: [BE_ERROR_HOME]
526
+ - id: be.domain.messages
527
+ profiles: [be]
528
+ path: "src/modules/{domain,integrations}/<capability>/messages/"
529
+ presence: optional # a capability that raises no user-facing text carries none
530
+ tracked: tracked
531
+ tier: inherit
532
+ requires: [index.ts]
533
+ allows: ["<capability>.messages.ts", "*.spec.ts"] # one keyed catalog per capability, vi and en, read through the platform/i18n MessageCatalog port
534
+ tests: unit-beside
535
+ rules: [BE_USER_COPY_LITERAL]
536
+ since: 1.0.0
537
+ - id: be.feature.messages
538
+ profiles: [be]
539
+ path: "src/features/<feature>/messages/"
540
+ presence: optional
541
+ tracked: tracked
542
+ tier: feature
543
+ requires: [index.ts]
544
+ allows: ["<feature>.messages.ts", "*.spec.ts"] # one keyed catalog per feature, vi and en, read through the platform/i18n MessageCatalog port
545
+ tests: unit-beside
546
+ rules: [BE_USER_COPY_LITERAL]
547
+ since: 1.0.0
548
+ - id: be.persistence
549
+ profiles: [be]
550
+ path: "src/modules/{domain,platform}/<capability>/persistence/"
551
+ presence: optional
552
+ tracked: tracked
553
+ tier: inherit
554
+ requires: [connection.ts] # export const CONNECTION = "<name from hfs.json connections>"
555
+ allows: ["entities/<table>.entity.ts", "migrations/<yyyyMMddHHmmss>-<kebab-name>.ts", "<name>.repository.ts", index.ts, "*.spec.ts"]
556
+ tests: unit-beside
557
+ rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_REPOSITORY]
558
+ - id: be.platform
559
+ profiles: [be]
560
+ path: "src/modules/platform/<capability>/"
561
+ presence: required
562
+ tracked: tracked
563
+ tier: platform
564
+ owner: true
565
+ # config = EnvSource reader only; logging = LoggerPort + adapter; errors = DomainError base + one filter + GraphQL
566
+ # formatError with masking; primitives = isRecord, hashing, ids, cookies; clock = the injected Clock port
567
+ # (now()), with a FakeClock test double from @starci/jest-preset; i18n = the MessageCatalog port, a keyed
568
+ # lookup with named interpolation over the vi/en catalogs a capability or feature carries in its messages/,
569
+ # resolved through the RequestLocale provider (request: stored user preference, else Accept-Language, else
570
+ # vi; non-request: recipient's stored preference, else vi - see knowledge/patterns/be/messages.yaml);
571
+ # database when a connection exists.
572
+ requiredInstances: {capability: [config, logging, errors, primitives, clock, i18n]}
573
+ requires: [index.ts]
574
+ forbids: [common/, utils/, shared/, testing/] # primitives live in platform/primitives
575
+ tests: unit-beside
576
+ rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
577
+ - id: be.integrations
578
+ profiles: [be]
579
+ path: "src/modules/integrations/<provider>/"
580
+ presence: optional
581
+ tracked: tracked
582
+ tier: integrations
583
+ owner: true
584
+ requires: [index.ts, "<provider>.config.ts"]
585
+ tests: unit-beside
586
+ rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
587
+ - id: be.integrations.model
588
+ profiles: [be]
589
+ path: "src/modules/integrations/<provider>/model/"
590
+ presence: opt-in
591
+ tracked: tracked
592
+ tier: integrations
593
+ tests: unit-beside
594
+ why: an ML or LLM model client is an integration; weights and datasets are never in git
595
+
596
+ # ----- BE: tests ---------------------------------------------------------------------------------------
597
+ - id: be.tests.e2e
598
+ profiles: [be]
599
+ path: "src/tests/e2e/<area>/*.e2e-spec.ts"
600
+ presence: optional
601
+ tracked: tracked
602
+ tier: e2e
603
+ tests: e2e
604
+ rules: [HFS_CI_E2E_AUTOMATIC, BE_TEST_TOPOLOGY, BE_E2E_FLOW]
605
+ - id: be.tests.e2e-live
606
+ profiles: [be]
607
+ path: "src/tests/e2e/live/<area>/*.e2e-spec.ts"
608
+ presence: opt-in
609
+ tracked: tracked
610
+ tier: e2e
611
+ tests: e2e
612
+ why: run only by test:e2e:live (E2E_LIVE=1); excluded from plain test:e2e by folder, never by file name
613
+ - id: be.tests.e2e-setup
614
+ profiles: [be]
615
+ path: "src/tests/e2e/setup/"
616
+ presence: optional
617
+ tracked: tracked
618
+ tier: e2e
619
+ tests: none
620
+ why: boots the REAL AppModule and overrides providers; no per-lane hand-assembled modules
621
+ - id: be.tests.fixtures
622
+ profiles: [be]
623
+ path: "src/tests/fixtures/"
624
+ presence: optional
625
+ tracked: tracked
626
+ tier: fixtures
627
+ tests: unit-beside
628
+ why: typed data builders and typed mock<T>() doubles; importable by unit specs and e2e; never imports a feature
629
+
630
+ # ----- BE: work and stacks -----------------------------------------------------------------------------
631
+ - id: be.starciwork
632
+ profiles: [be]
633
+ path: ".starciwork/"
634
+ presence: required
635
+ tracked: tracked
636
+ tier: none
637
+ tests: none
638
+ requires: [.gitignore, workspace.yaml, features/index.yaml]
639
+ allows: [workspace.yaml, "brand/**", shell/index.yaml, "features/<feature>/index.yaml",
640
+ "features/<feature>/<family>/<name>/index.yaml", "features/<feature>/br/<rule>/ac/<name>/index.yaml",
641
+ "features/<feature>/ui/<name>/assets/<approved-file>", "features/<feature>/uat/<name>/{index,accounts,fixtures}.yaml",
642
+ "features/<feature>/uat/<name>/{seed,cleanup}.sql", "_resources/{identities,environments,fixtures}/<slug>/resource.yaml"]
643
+ forbids: [evidence/, logs/, reports/, captures/, runs/, "kernel-*/", "*.sqlite*", _derived/, "work/node records"]
644
+ rules: [HFS_AGENT_DATA_TRACKED, HFS_WORK_NODE_RETIRED, HFS_IDENTITY_CUSTODY]
645
+ - id: be.starcistacks
646
+ profiles: [be]
647
+ path: ".starcistacks/"
648
+ presence: required
649
+ tracked: tracked
650
+ tier: none
651
+ tests: none
652
+ requires: [application-stacks.yaml]
653
+ allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,k8s,terraform}/**",
654
+ "<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/seeds/**"]
655
+ forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
656
+ rules: [HFS_STACKS_SHAPE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
657
+ - id: be.sops
658
+ profiles: [be]
659
+ path: .sops.yaml
660
+ presence: required
661
+ tracked: tracked
662
+ tier: none
663
+ tests: none
664
+ managedBy: sops-policy
665
+
666
+ # ----- FE: apps ----------------------------------------------------------------------------------------
667
+ - id: fe.app.next
668
+ profiles: [fe]
669
+ path: "apps/<app>/{package.json,next.config.ts,tsconfig.json,postcss.config.mjs}"
670
+ appKind: next
671
+ presence: required
672
+ tracked: tracked
673
+ tier: none
674
+ minInstances: 1
675
+ requires: [package.json, next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/, src/modules/api/,
676
+ "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"]
677
+ tests: none
678
+ rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS]
679
+ - id: fe.app-optional
680
+ profiles: [fe]
681
+ path: "apps/<app>/{vitest.config.ts,public/}"
682
+ presence: optional
683
+ tracked: tracked
684
+ tier: none
685
+ tests: none
686
+ - id: fe.route
687
+ profiles: [fe]
688
+ path: "apps/<app>/src/app/"
689
+ presence: required
690
+ tracked: tracked
691
+ tier: route
692
+ allows: ["[locale]/**/{page,layout,template,loading,error,not-found}.tsx", "[locale]/**/route.ts", global-error.tsx, "page.tsx (redirect only)",
693
+ "health/{live,ready}/route.ts", globals.css, "icon.*", robots.ts, sitemap.ts]
694
+ tests: none
695
+ rules: [FE_ROUTE_FILES_THIN, FE_CLIENT_BOUNDARY, FE_OWNER_REACHABLE]
696
+ - id: fe.source-root-pinned
697
+ profiles: [fe]
698
+ path: "apps/<app>/src/{proxy.ts,instrumentation.ts,instrumentation-client.ts}"
699
+ presence: optional
700
+ tracked: tracked
701
+ tier: route
702
+ tests: none
703
+ rules: [FE_NEXT_CONVENTIONS] # middleware.ts is refused on Next >= 16
704
+ - id: fe.feature
705
+ profiles: [fe]
706
+ path: "apps/<app>/src/features/{pages,layouts,overlays}/<name>/"
707
+ presence: required
708
+ tracked: tracked
709
+ tier: feature
710
+ owner: true
711
+ requires: [index.tsx]
712
+ allows: [index.tsx, component.tsx, classNames.ts, "*.spec.tsx", "*.spec.ts"]
713
+ tests: unit-beside
714
+ budget: {component.tsx: 300, index.tsx: 200}
715
+ rules: [FE_SIZE_BUDGET, FE_OWNER_REACHABLE, FE_I18N_LITERAL]
716
+ - id: fe.components
717
+ profiles: [fe]
718
+ path: "apps/<app>/src/components/{blocks,composites,branches,leaves}/<name>/"
719
+ presence: optional
720
+ tracked: tracked
721
+ tier: components
722
+ owner: true
723
+ layers: [blocks, composites, branches, leaves] # a layer imports only the layers after it
724
+ requires: [index.tsx]
725
+ allows: [index.tsx, component.tsx, classNames.ts, "*.spec.tsx", "*.spec.ts"]
726
+ tests: unit-beside
727
+ budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
728
+ rules: [FE_CONNECTED_BLOCK_RENDER_PAIR, FE_I18N_LITERAL, FE_NATIVE_FORM_CONTROL, FE_CLIENT_BOUNDARY]
729
+ - id: fe.hooks
730
+ profiles: [fe]
731
+ path: "apps/<app>/src/hooks/<domain>/"
732
+ presence: optional
733
+ tracked: tracked
734
+ tier: hooks
735
+ owner: true # a domain is an owner: other domains import it through index.ts only
736
+ requires: [index.ts]
737
+ allows: [index.ts, "use<name>.ts", "<domain>.shared.ts", "*.spec.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
738
+ tests: unit-beside
739
+ budget: {file: 200}
740
+ rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
741
+ - id: fe.modules
742
+ profiles: [fe]
743
+ path: "apps/<app>/src/modules/<capability>/"
744
+ presence: required
745
+ tracked: tracked
746
+ tier: modules
747
+ owner: true
748
+ requiredInstances: {capability: [api, config, i18n, routes]}
749
+ requires: [index.ts]
750
+ tests: unit-beside
751
+ budget: {file: 400}
752
+ rules: [FE_ENV_OWNER, FE_TRANSPORT_OWNER]
753
+ - id: fe.modules.api
754
+ profiles: [fe]
755
+ path: "apps/<app>/src/modules/api/"
756
+ presence: required
757
+ tracked: tracked
758
+ tier: transport
759
+ requires: [index.ts, client.ts, outcome.ts] # client.ts is the only fetch; outcome.ts the only result vocabulary
760
+ allows: [client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/, "*.spec.ts"]
761
+ tests: unit-beside
762
+ rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
763
+ - id: fe.modules.config
764
+ profiles: [fe]
765
+ path: "apps/<app>/src/modules/config/"
766
+ presence: required
767
+ tracked: tracked
768
+ tier: foundation
769
+ tests: unit-beside
770
+ why: the only reader of process.env and NEXT_PUBLIC_*; fails fast, no localhost fallback
771
+ - id: fe.modules.i18n
772
+ profiles: [fe]
773
+ path: "apps/<app>/src/modules/i18n/"
774
+ presence: required
775
+ tracked: tracked
776
+ tier: foundation
777
+ requires: [config.ts, routing.ts, navigation.ts, request.ts, messages/]
778
+ tests: unit-beside
779
+ rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
780
+ - id: fe.modules.brand
781
+ profiles: [fe]
782
+ path: "apps/<app>/src/modules/brand/brand.css"
783
+ presence: optional
784
+ tracked: tracked
785
+ tier: none
786
+ tests: none
787
+ why: the ONLY app file that may hold colour values; sets grammar brand tokens only, light and dark
788
+ rules: [FE_STYLE_TOKEN_ONLY]
789
+ - id: fe.modules.routes
790
+ profiles: [fe]
791
+ path: "apps/<app>/src/modules/routes/"
792
+ presence: required
793
+ tracked: tracked
794
+ tier: foundation
795
+ tests: unit-beside
796
+ why: every href builder; FE_HREF_RESOLVES checks each against app/
797
+ - id: fe.modules.types
798
+ profiles: [fe]
799
+ path: "apps/<app>/src/modules/types/"
800
+ presence: optional
801
+ tracked: tracked
802
+ tier: foundation
803
+ tests: unit-beside
804
+ why: shared types with no runtime behaviour; a component may import them
805
+ - id: fe.package.ui
806
+ profiles: [fe]
807
+ path: "packages/<family>-ui/"
808
+ presence: opt-in
809
+ tracked: tracked
810
+ tier: package
811
+ owner: true
812
+ requires: [package.json, src/index.ts, tsconfig.json]
813
+ tests: unit-beside
814
+ why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
815
+ rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
816
+ - id: fe.e2e
817
+ profiles: [fe]
818
+ path: "e2e/<area>/*.e2e-spec.ts"
819
+ presence: optional
820
+ tracked: tracked
821
+ tier: e2e
822
+ tests: e2e
823
+ requires: [/playwright.config.ts, /tsconfig.e2e.json]
824
+ rules: [FE_E2E_SHAPE, HFS_CI_E2E_AUTOMATIC]
825
+ - id: fe.e2e-support
826
+ profiles: [fe]
827
+ path: "e2e/{support,fixtures}/"
828
+ presence: optional
829
+ tracked: tracked
830
+ tier: e2e
831
+ tests: none
832
+
833
+ # Checks that read this manifest (all ship from .claude; none keeps its own path list)
834
+ consumers:
835
+ - scripts/lib/hfs-slots.mjs # the loader every consumer below goes through
836
+ - scripts/checks/hfs/slot-check.mjs # HFS_SLOT_* / tracked / external / managed drift
837
+ - scripts/checks/architecture.mjs # tiers, direction matrix, cycles, reachability
838
+ - packages/eslint/be starciBeConfig({hfs}) # file globs for rules come from slots
839
+ - packages/eslint/fe starciFeConfig({hfs})
840
+ - packages/stylelint-canon # colour and brand allowances from fe.modules.brand
841
+ - scripts/hfs/sync.mjs # renders managedBy templates
842
+ - modules/kernel/failure-codes.yaml # every rule id listed above has a Vietnamese why