@starci/eslint-canon-fe 8.0.8 → 8.0.9

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 (117) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +5 -4
  3. package/client-boundary.mjs +4 -2
  4. package/comments.mjs +1 -1
  5. package/docs/next-conventions.md +1 -1
  6. package/docs/translation.md +1 -1
  7. package/docs/transport.md +2 -2
  8. package/{shape-slot.mjs → drawing.mjs} +5 -5
  9. package/file-layout.mjs +8 -6
  10. package/icon.mjs +1 -23
  11. package/index.mjs +2 -2
  12. package/lib/ast.mjs +14 -11
  13. package/lib/config.mjs +1 -1
  14. package/lib/scope.mjs +6 -2
  15. package/lib/why.mjs +113 -110
  16. package/naming.mjs +4 -4
  17. package/package.json +3 -1
  18. package/project-graph.mjs +1 -1
  19. package/runtime/engine/config.mjs +494 -0
  20. package/runtime/engine/invalid-config.mjs +15 -0
  21. package/runtime/engine/orca-config.mjs +27 -0
  22. package/runtime/knowledge/hfs/canon-pins.yaml +160 -29
  23. package/runtime/knowledge/hfs/slots.yaml +912 -120
  24. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  25. package/runtime/knowledge/sonar-gate.yaml +52 -18
  26. package/runtime/modules/models/registry.yaml +591 -0
  27. package/runtime/modules/models/runtimes.yaml +701 -0
  28. package/runtime/packages/hfs/templates/app/package-scripts/package.json +3 -10
  29. package/runtime/scripts/api/git/config-get.mjs +10 -0
  30. package/runtime/scripts/api/git/lib.mjs +16 -26
  31. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  32. package/runtime/scripts/api/git/remote.mjs +5 -0
  33. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  34. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  35. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  36. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  37. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  38. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  39. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  40. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  41. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  42. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  43. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  44. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  45. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  46. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  47. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  48. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  49. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  50. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  51. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
  52. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  53. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  54. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  55. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  56. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  57. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  58. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  59. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  60. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  61. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  62. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  63. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  65. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  66. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  67. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  68. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  69. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  70. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  71. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  72. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  73. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  74. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  75. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  76. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  77. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  78. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  79. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  80. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  81. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  82. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  83. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  84. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  85. package/runtime/scripts/hfs/param-names.mjs +18 -0
  86. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  87. package/runtime/scripts/hfs/project-graph.mjs +3 -3
  88. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  89. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  90. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  91. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  92. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  93. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  94. package/runtime/scripts/hfs/slots.mjs +78 -108
  95. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  96. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  97. package/runtime/scripts/hfs/view.mjs +3 -2
  98. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  99. package/runtime/scripts/lib/git.mjs +36 -0
  100. package/runtime/scripts/lib/i18n.mjs +86 -0
  101. package/runtime/scripts/lib/json.mjs +13 -3
  102. package/runtime/scripts/lib/language.mjs +24 -5
  103. package/runtime/scripts/lib/list.mjs +20 -0
  104. package/runtime/scripts/lib/path-key.mjs +57 -2
  105. package/runtime/scripts/lib/read-text.mjs +34 -0
  106. package/runtime/scripts/lib/regex.mjs +4 -0
  107. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  108. package/runtime/scripts/lib/sops-envelope.mjs +58 -0
  109. package/runtime/scripts/lib/stack-services.mjs +7 -31
  110. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  111. package/runtime/scripts/lib/walk.mjs +4 -3
  112. package/size-and-state-budget.mjs +1 -1
  113. package/tokens.mjs +10 -18
  114. package/translation.mjs +1 -1
  115. package/transport.mjs +2 -2
  116. package/runtime/scripts/api/sops/decrypt.mjs +0 -16
  117. package/runtime/scripts/lib/test-secrets.mjs +0 -116
package/lib/why.mjs CHANGED
@@ -1,279 +1,282 @@
1
1
  /**
2
- * The Vietnamese "why" of each rule the HFS catalogue assigns to this canon.
2
+ * The "why" of each rule the HFS catalogue assigns to this canon.
3
3
  *
4
4
  * WHERE IT IS SURFACED. An ESLint message is written for the developer at the terminal, in English,
5
5
  * and says what is wrong in the vocabulary of the rule. The agent that reads a failed land gate needs
6
6
  * the catalogue's sentence instead: the finding code (the key in `modules/kernel/failure-codes.yaml`)
7
- * and a Vietnamese headline and next step it can quote to the owner. This map is the canon's half of
8
- * that contract: `code` is the finding code, `vi` the headline with `<file>` / `<what>` placeholders
9
- * the reader fills from the ESLint location, `fixVi` the one sentence "what do I do now".
7
+ * and a headline and next step it can quote to the owner. This map is the canon's half of that
8
+ * contract: `code` is the finding code, `en` the English source of the headline with `<file>` /
9
+ * `<what>` placeholders the reader fills from the ESLint location, `fix` the one sentence "what do I
10
+ * do now". The owner reads both in Vietnamese through the declared message catalog: the `en` and
11
+ * `fix` strings are the keys of `modules/i18n/messages/canon.yaml`, so `translator('vi')(entry.en)` and
12
+ * `translator('vi')(entry.fix)` hand back the Vietnamese of the same sentence.
10
13
  *
11
- * Every rule that carries a catalogue id (R18, R22, R49-R59, R60-R62, R65-R67) has an entry;
14
+ * Every rule that carries a catalogue id (R18, R22, R49-R59, R60-R62, R65) has an entry;
12
15
  * the twin test refuses a rule of those laws with no entry and an entry for a rule that does not exist.
13
16
  */
14
17
 
15
- /** @typedef {{ code: string, vi: string, fixVi: string }} Why */
18
+ /** @typedef {{ code: string, en: string, fix: string }} Why */
16
19
 
17
20
  /** @type {Record<string, Why>} */
18
21
  export const why = {
19
22
  "no-env-outside-config": {
20
23
  code: "FE_ENV_OWNER",
21
- vi: "`<file>` đọc biến môi trường. Chỉ `modules/config` được đọc env.",
22
- fixVi: "Đọc giá trị từ export có kiểu của `modules/config` thay vì `process.env`.",
24
+ en: "`<file>` reads an environment variable. Only `modules/config` may read env.",
25
+ fix: "Read the value from a typed export of `modules/config` instead of `process.env`.",
23
26
  },
24
27
  "no-hardcoded-endpoint-fallback": {
25
28
  code: "FE_ENV_OWNER",
26
- vi: "`<file>` có giá trị dự phòng cứng cho địa chỉ hoặc bí mật (`?? \"http://localhost…\"`). Thiếu biến ở production phải ném lỗi khi nạp cấu hình, không được lặng lẽ trỏ về máy dev.",
27
- fixVi: "Xóa giá trị dự phòng; để `modules/config` ném lỗi khi thiếu biến ở production.",
29
+ en: "`<file>` carries a hardcoded fallback for an address or a secret (`?? \"http://localhost…\"`). A variable missing in production must throw at config load, not silently point at a dev machine.",
30
+ fix: "Delete the fallback; let `modules/config` throw when a variable is missing in production.",
28
31
  },
29
32
  "fetch-only-in-api-client": {
30
33
  code: "FE_TRANSPORT_OWNER",
31
- vi: "`fetch` (hoặc `Request`, `EventSource`, `sendBeacon`, thư viện HTTP khác) ở `<file>` nằm ngoài client duy nhất (`modules/api/client.ts`, hoặc `src/client.ts` của gói api dùng chung). Mỗi repo chỉ có đúng một đường truyền.",
32
- fixVi: "Gọi client của repo và nhận `Outcome<T>`; không tự gọi `fetch`.",
34
+ en: "`fetch` (or `Request`, `EventSource`, `sendBeacon`, another HTTP library) in `<file>` sits outside the one client (`modules/api/client.ts`, or `src/client.ts` of the shared api package). A repo has exactly one transport.",
35
+ fix: "Call the repo's client and receive `Outcome<T>`; do not call `fetch` yourself.",
33
36
  },
34
37
  "client-fetch-has-signal": {
35
38
  code: "FE_TRANSPORT_OWNER",
36
- vi: "`fetch` ở `<file>` không có `signal`. Client bắt buộc có timeout và `AbortSignal`.",
37
- fixVi: "Truyền `signal` (`AbortSignal.timeout(...)` kết hợp với tín hiệu của người gọi).",
39
+ en: "`fetch` in `<file>` has no `signal`. The client must carry a timeout and an `AbortSignal`.",
40
+ fix: "Pass `signal` (`AbortSignal.timeout(...)` combined with the caller's signal).",
38
41
  },
39
42
  "no-shared-transport-state": {
40
43
  code: "FE_TRANSPORT_OWNER",
41
- vi: "`<file>` giữ trạng thái dùng chung dạng `let`/`var` trong tầng API. Token và locale không được nằm trong singleton.",
42
- fixVi: "Truyền credential và locale bằng tham số hoặc context.",
44
+ en: "`<file>` holds shared state as `let`/`var` in the API layer. Token and locale must not sit in a singleton.",
45
+ fix: "Pass credential and locale as parameters or through context.",
43
46
  },
44
47
  "client-maps-auth-to-refused": {
45
48
  code: "FE_HTTP_STATUS_COLLAPSE",
46
- vi: "Client ở `<file>` không có nhánh so sánh `response.status` với 401 và 403 rồi trả `{ kind: \"refused\" }`, nên trạng thái \"cần đăng nhập\" không bao giờ đạt được.",
47
- fixVi: "Thêm nhánh `response.status === 401 || response.status === 403` trả `{ ok: false, kind: \"refused\" }` ngay trong client.",
49
+ en: "The client in `<file>` has no branch comparing `response.status` with 401 and 403 and returning `{ kind: \"refused\" }`, so the \"sign-in required\" state is never reached.",
50
+ fix: "Add a `response.status === 401 || response.status === 403` branch returning `{ ok: false, kind: \"refused\" }` right inside the client.",
48
51
  },
49
52
  "no-failure-collapse": {
50
53
  code: "FE_HTTP_STATUS_COLLAPSE",
51
- vi: "`<file>` gộp mọi thất bại (phản hồi HTTP lỗi hoặc `Outcome` thất bại) thành một nhánh hoặc một giá trị rỗng, nên lý do bị mất.",
52
- fixVi: "Giữ lý do: với `Outcome` thì rẽ nhánh theo mã lý do; với phản hồi HTTP thì đổi mã trạng thái thành `Outcome` (`refused` cho 401/403, `not-found`, `invalid`, `unavailable`). Không dùng nguyên văn lỗi của server làm lý do.",
54
+ en: "`<file>` merges every failure (an error HTTP response or a failed `Outcome`) into one branch or one empty value, so the reason is lost.",
55
+ fix: "Keep the reason: for an `Outcome` branch on the reason code; for an HTTP response turn the status code into an `Outcome` (`refused` for 401/403, `not-found`, `invalid`, `unavailable`). Do not use the server's raw error as the reason.",
53
56
  },
54
57
  "no-hand-typed-wire": {
55
58
  code: "FE_WIRE_GENERATED",
56
- vi: "`<file>` tự gõ kiểu wire hoặc ép kiểu phản hồi. Dùng kiểu sinh từ `be/contracts/`.",
57
- fixVi: "Chạy codegen từ ảnh chụp hợp đồng của be (`be/contracts/`) và nhập kiểu sinh ra; đưa tài liệu GraphQL vào tệp `.graphql`.",
59
+ en: "`<file>` hand-types a wire type or casts a response. Use the types generated from `be/contracts/`.",
60
+ fix: "Run codegen from the be contract snapshot (`be/contracts/`) and import the generated types; put GraphQL documents in `.graphql` files.",
58
61
  },
59
62
  "use-client-only-at-boundary": {
60
63
  code: "FE_CLIENT_BOUNDARY",
61
- vi: "`\"use client\"` ở `<file>` (`<slot>` không được là client component).",
62
- fixVi: "Đặt `\"use client\"` ở `index.tsx` của block có tương tác, leaf, hoặc `error.tsx`/`global-error.tsx`; giữ layout và page là server component.",
64
+ en: "`\"use client\"` in `<file>` (`<slot>` may not be a client component).",
65
+ fix: "Put `\"use client\"` on the `index.tsx` of an interactive block, a leaf, or `error.tsx`/`global-error.tsx`; keep layout and page as server components.",
63
66
  },
64
67
  "hooks-folder-holds-hooks-only": {
65
68
  code: "FE_HOOKS_ARE_HOOKS",
66
- vi: "`<file>` trong `hooks/` không phải React hook. Reader server về `modules/api`, helper về `<domain>.shared.ts`.",
67
- fixVi: "Mỗi tệp `use*.ts` một hook; helper dùng chung vào `<domain>.shared.ts`; reader server vào `modules/api/<domain>/read-*.ts`.",
69
+ en: "`<file>` inside `hooks/` is not a React hook. A server reader goes to `modules/api`, a helper to `<domain>.shared.ts`.",
70
+ fix: "One hook per `use*.ts` file; a shared helper goes in `<domain>.shared.ts`; a server reader goes in `modules/api/<domain>/read-*.ts`.",
68
71
  },
69
72
  "no-middleware-file": {
70
73
  code: "FE_NEXT_CONVENTIONS",
71
- vi: "`<file>` dùng tên `middleware`. Từ Next 16 bộ chặn request là `proxy.ts` và xuất `proxy`.",
72
- fixVi: "Đổi tên tệp thành `proxy.ts` và đổi export thành `proxy`.",
74
+ en: "`<file>` uses the name `middleware`. From Next 16 the request gate is `proxy.ts` exporting `proxy`.",
75
+ fix: "Rename the file to `proxy.ts` and rename the export to `proxy`.",
73
76
  },
74
77
  "locale-segment-is-locale": {
75
78
  code: "FE_I18N_PLACEMENT",
76
- vi: "Tệp `<file>` nằm dưới segment ngôn ngữ không phải `[locale]`. Chỉ có một mẫu: `next-intl`, `[locale]`, mặc định `vi`.",
77
- fixVi: "Đổi tên thư mục segment thành `[locale]`.",
79
+ en: "File `<file>` sits under a locale segment that is not `[locale]`. There is one pattern: `next-intl`, `[locale]`, default `vi`.",
80
+ fix: "Rename the segment directory to `[locale]`.",
78
81
  },
79
82
  "no-second-i18n-stack": {
80
83
  code: "FE_I18N_PLACEMENT",
81
- vi: "`<file>` nhập một thư viện i18n thứ hai. App chỉ dùng `next-intl` qua `modules/i18n`.",
82
- fixVi: "Chuyển sang `next-intl`; xóa thư viện còn lại.",
84
+ en: "`<file>` imports a second i18n library. The app uses only `next-intl` through `modules/i18n`.",
85
+ fix: "Switch to `next-intl`; delete the other library.",
83
86
  },
84
87
  "html-lang-from-locale": {
85
88
  code: "FE_I18N_LITERAL",
86
- vi: "`<html lang>` ở `<file>` là chữ cứng. Thuộc tính `lang` phải lấy từ segment `[locale]`.",
87
- fixVi: "Dùng `lang={locale}` từ tham số của layout `[locale]`.",
89
+ en: "`<html lang>` in `<file>` is a hardcoded string. The `lang` attribute must come from the `[locale]` segment.",
90
+ fix: "Use `lang={locale}` from the parameter of the `[locale]` layout.",
88
91
  },
89
92
  "no-hardcoded-copy": {
90
93
  code: "FE_I18N_LITERAL",
91
- vi: "Chữ `\"<text>\"` ở `<file>:<line>` không đi qua `t()`.",
92
- fixVi: "Chuyển câu vào `modules/i18n/messages/<locale>.json` và đọc bằng `t(\"khóa\")`; không có ngoại lệ và không có `vn-ok`.",
94
+ en: "The text `\"<text>\"` in `<file>:<line>` does not go through `t()`.",
95
+ fix: "Move the sentence into `modules/i18n/messages/<locale>.json` and read it with `t(\"key\")`; no exceptions and no `vn-ok`.",
93
96
  },
94
97
  "no-raw-brand-value": {
95
98
  code: "FE_STYLE_TOKEN_ONLY",
96
- vi: "`<file>` dùng giá trị màu/độ dài thô (`<what>`). Màu và khoảng cách chỉ đến từ token grammar; màu thương hiệu chỉ ở `brand.css`.",
97
- fixVi: "Dùng lớp hoặc biến token của grammar; giá trị màu chỉ khai trong `modules/brand/brand.css`.",
99
+ en: "`<file>` uses a raw colour/length value (`<what>`). Colour and spacing come only from grammar tokens; brand colour lives only in `brand.css`.",
100
+ fix: "Use a grammar token class or variable; a colour value is declared only in `modules/brand/brand.css`.",
98
101
  },
99
102
  "status-text-uses-soft-foreground": {
100
103
  code: "FE_STYLE_TOKEN_ONLY",
101
- vi: "`<file>` tô chữ hoặc icon bằng tông trạng thái đặc (`<what>`, ví dụ `text-success`). Tông đặc là màu nền; dùng làm màu chữ nó đọc dưới 4.5:1 trên nền trang.",
102
- fixVi: "Đổi thành cặp mềm: `text-<tông>-soft-foreground` (kèm `bg-<tông>-soft` nếu nằm trên nền nhạt); tông đặc chỉ dùng với `bg-<tông>` và `text-<tông>-foreground`.",
104
+ en: "`<file>` paints text or an icon with a solid status tone (`<what>`, e.g. `text-success`). A solid tone is a background colour; used as a text colour it reads below 4.5:1 on the page background.",
105
+ fix: "Switch to the soft pair: `text-<tone>-soft-foreground` (with `bg-<tone>-soft` when it sits on a light surface); a solid tone is used only with `bg-<tone>` and `text-<tone>-foreground`.",
103
106
  },
104
107
  "no-native-form-control": {
105
108
  code: "FE_NATIVE_FORM_CONTROL",
106
- vi: "`<tag>` thô ở `<file>`. Dùng renderer của grammar.",
107
- fixVi: "Thay bằng thành phần của grammar; nếu chưa có, thêm vào grammar thay vì vẽ tại chỗ.",
109
+ en: "Raw `<tag>` in `<file>`. Use the grammar renderer.",
110
+ fix: "Replace it with the grammar component; if none exists yet, add it to grammar instead of drawing it in place.",
108
111
  },
109
112
  "component-line-budget": {
110
113
  code: "FE_SIZE_AND_STATE_BUDGET",
111
- vi: "`<file>` dài `<n>` dòng, vượt ngưỡng 300 dòng của một component.",
112
- fixVi: "Tách phần vẽ khỏi phần dữ liệu và tách từng khu vực thành đơn vị riêng.",
114
+ en: "`<file>` is `<n>` lines long, over the 300-line budget of a component.",
115
+ fix: "Split the drawing part from the data part and each area into its own unit.",
113
116
  },
114
117
  "unit-hook-budget": {
115
118
  code: "FE_SIZE_AND_STATE_BUDGET",
116
- vi: "`<unit>` có `<n>` hook dữ liệu / `<m>` useState, vượt ngưỡng 6.",
117
- fixVi: "Gom trạng thái đổi cùng nhau vào reducer hoặc hook riêng; tách mỗi vùng dữ liệu thành block kết nối riêng.",
119
+ en: "`<unit>` has `<n>` data hooks / `<m>` useState, over the budget of 6.",
120
+ fix: "Fold state that changes together into a reducer or a dedicated hook; split each data area into its own connection block.",
118
121
  },
119
122
  "no-hand-rolled-polling": {
120
123
  code: "FE_SIZE_AND_STATE_BUDGET",
121
- vi: "`<unit>` tự viết vòng poll (`setInterval` hoặc `setTimeout` tự gọi lại).",
122
- fixVi: "Làm tươi qua hook dữ liệu (`refreshInterval`) hoặc socket, mỗi resource đúng một cơ chế.",
124
+ en: "`<unit>` hand-writes a poll loop (`setInterval` or a self-rescheduling `setTimeout`).",
125
+ fix: "Refresh through a data hook (`refreshInterval`) or a socket, one mechanism per resource.",
123
126
  },
124
127
  "no-inline-lint-config": {
125
128
  code: "HFS_INLINE_SUPPRESSION",
126
- vi: "Có chú thích tắt luật ở `<file>:<line>`. HFS không cho tắt tại chỗ — sửa code, hoặc đề xuất đổi luật.",
127
- fixVi: "Xóa `eslint-disable`, `@ts-ignore`, `@ts-expect-error` hoặc `vn-ok` và sửa nguyên nhân.",
129
+ en: "A comment disabling a rule sits at `<file>:<line>`. HFS does not allow in-place suppression - fix the code, or propose a rule change.",
130
+ fix: "Remove `eslint-disable`, `@ts-ignore`, `@ts-expect-error` or `vn-ok` and fix the cause.",
128
131
  },
129
132
  "no-double-cast": {
130
133
  code: "FE_TYPE_ESCAPE",
131
- vi: "`<file>` ép kiểu qua `unknown` (`as unknown as T`): trình biên dịch quên mọi thứ nó biết ngay tại chỗ dữ liệu đi vào.",
132
- fixVi: "Thu hẹp từ `unknown` bằng type guard hoặc parser; không ép kiểu.",
134
+ en: "`<file>` casts through `unknown` (`as unknown as T`): the compiler forgets everything it knows right where the data enters.",
135
+ fix: "Narrow from `unknown` with a type guard or a parser; do not cast.",
133
136
  },
134
137
  "no-type-assertion": {
135
138
  code: "FE_TYPE_ESCAPE",
136
- vi: "`<file>` dùng `as T` hoặc `<T>x`: một lời khẳng định trình biên dịch không kiểm được. Sai thì lỗi rơi vào trình duyệt của người dùng.",
137
- fixVi: "Thu hẹp bằng type guard, `in`, discriminant hoặc parser tại nơi dữ liệu đi vào; dùng `satisfies` nếu chỉ muốn kiểm một literal.",
139
+ en: "`<file>` uses `as T` or `<T>x`: an assertion the compiler cannot check. If it is wrong, the error lands in the user's browser.",
140
+ fix: "Narrow with a type guard, `in`, a discriminant or a parser where the data enters; use `satisfies` when you only mean to check a literal.",
138
141
  },
139
142
  "no-non-null-assertion": {
140
143
  code: "FE_TYPE_ESCAPE",
141
- vi: "`<file>` dùng `x!`: khẳng định giá trị có mặt mà không chứng minh.",
142
- fixVi: "Xử lý nhánh vắng mặt (`if`, `??`, return sớm) hoặc sửa kiểu để giá trị không thể vắng.",
144
+ en: "`<file>` uses `x!`: asserting a value is present without proving it.",
145
+ fix: "Handle the absent branch (`if`, `??`, an early return) or fix the type so the value cannot be absent.",
143
146
  },
144
147
  "no-explicit-any": {
145
148
  code: "FE_TYPE_ESCAPE",
146
- vi: "`<file>` dùng `any`: tắt kiểm kiểu cho giá trị đó và mọi thứ suy ra từ nó.",
147
- fixVi: "Dùng kiểu thật, generic, hoặc `unknown` rồi thu hẹp tại chỗ dùng.",
149
+ en: "`<file>` uses `any`: it switches off type checking for that value and for everything inferred from it.",
150
+ fix: "Use a real type, a generic, or `unknown` narrowed at the use site.",
148
151
  },
149
152
  "list-item-has-key": {
150
153
  code: "FE_LIST_KEY",
151
- vi: "Phần tử trả từ `.map` ở `<file>` không có `key`, nên React nhận diện hàng theo vị trí và hàng bị đổi state khi xoá hoặc sắp xếp lại.",
152
- fixVi: "Thêm `key` lấy từ id của dữ liệu; với fragment dùng `<Fragment key={...}>`.",
154
+ en: "An element returned from `.map` in `<file>` has no `key`, so React identifies the row by position and the row loses state on delete or reorder.",
155
+ fix: "Add a `key` taken from the data's id; for a fragment use `<Fragment key={...}>`.",
153
156
  },
154
157
  "no-index-key": {
155
158
  code: "FE_LIST_KEY",
156
- vi: "`key` ở `<file>` lấy từ chỉ số của `.map` hoặc giá trị sinh ngẫu nhiên; chỉ số là vị trí, không phải danh tính.",
157
- fixVi: "Dùng id mà dữ liệu mang (`key={row.id}`); không dùng index, `Math.random`, `Date.now`.",
159
+ en: "A `key` in `<file>` comes from the `.map` index or a randomly generated value; an index is a position, not an identity.",
160
+ fix: "Use the id the data carries (`key={row.id}`); never the index, `Math.random`, `Date.now`.",
158
161
  },
159
162
  "no-inline-literal-prop-in-list": {
160
163
  code: "FE_LIST_KEY",
161
- vi: "Component trong `.map` ở `<file>` nhận object hoặc array literal làm prop: mỗi hàng mỗi lần render một giá trị mới, memo không bao giờ trúng.",
162
- fixVi: "Nâng hằng số ra ngoài component hoặc dựng một lần trước `.map`.",
164
+ en: "A component in a `.map` in `<file>` receives an object or array literal as a prop: every row gets a new value every render, so memo never hits.",
165
+ fix: "Hoist the constant outside the component or build it once before `.map`.",
163
166
  },
164
167
  "effect-subscription-needs-cleanup": {
165
168
  code: "FE_EFFECT_CLEANUP",
166
- vi: "`<what>` ở `<file>` (timer, frame, listener, observer, socket hoặc subscription) được effect hoặc `subscribe` khởi động nhưng cleanup trả về không giải phóng đúng handle/target/listener đó.",
167
- fixVi: "Giữ handle và trả cleanup gọi `clearTimeout(id)`, `cancelAnimationFrame(id)`, `removeEventListener` cùng type và listener, `.disconnect()`, `.close()` hoặc `.unsubscribe()` trên đúng đối tượng.",
169
+ en: "`<what>` in `<file>` (timer, frame, listener, observer, socket or subscription) is started by an effect or `subscribe` but the returned cleanup does not release that same handle/target/listener.",
170
+ fix: "Keep the handle and return a cleanup calling `clearTimeout(id)`, `cancelAnimationFrame(id)`, `removeEventListener` with the same type and listener, `.disconnect()`, `.close()` or `.unsubscribe()` on the right object.",
168
171
  },
169
172
  "no-data-fetch-in-effect": {
170
173
  code: "FE_EFFECT_FETCH",
171
- vi: "`useEffect` ở `<file>` khởi động một promise (`await`, `fetch`, `.then`, `void load()`, `mutate()`/`refresh()` gọi thẳng): không cache, không gộp request, không trạng thái loading/lỗi, không huỷ. Độ tươi của dữ liệu đến từ key của SWR, không đến từ effect.",
172
- fixVi: "Đọc phía client bằng SWR gọi client của app (`hooks/`) với key theo thứ thay đổi; gọi `mutate()` từ sự kiện làm dữ liệu đổi (handler, message socket); phía route dùng server reader `modules/api/<domain>/read-*.ts`.",
174
+ en: "`useEffect` in `<file>` starts a promise (`await`, `fetch`, `.then`, `void load()`, `mutate()`/`refresh()` called directly): no cache, no request merging, no loading/error state, no abort. Freshness of data comes from the SWR key, not from an effect.",
175
+ fix: "Read on the client with SWR calling the app's client (`hooks/`) keyed by what changes; call `mutate()` from the event that changes the data (handler, socket message); on the route use the server reader `modules/api/<domain>/read-*.ts`.",
173
176
  },
174
177
  "no-empty-catch": {
175
178
  code: "FE_SWALLOWED_ERROR",
176
- vi: "`catch` hoặc `.catch` ở `<file>` không làm gì: lỗi biến mất, không thông báo, không dấu vết.",
177
- fixVi: "Trả một outcome có kiểu mang nguyên nhân, hiển thị trạng thái lỗi, hoặc ném lại.",
179
+ en: "A `catch` or `.catch` in `<file>` does nothing: the error vanishes, no notice, no trace.",
180
+ fix: "Return a typed outcome carrying the cause, show an error state, or rethrow.",
178
181
  },
179
182
  "no-console": {
180
183
  code: "FE_CONSOLE_CALL",
181
- vi: "`console.<method>` ở `<file>`: không vào pipeline log nào và lộ chi tiết nội bộ cho người dùng.",
182
- fixVi: "Trả outcome có kiểu, hiển thị trạng thái lỗi, hoặc để lỗi tới `error.tsx`.",
184
+ en: "`console.<method>` in `<file>`: it reaches no log pipeline and leaks internal detail to the user.",
185
+ fix: "Return a typed outcome, show an error state, or let the error reach `error.tsx`.",
183
186
  },
184
187
  "page-exports-metadata": {
185
188
  code: "FE_PAGE_METADATA_MISSING",
186
- vi: "`<file>` là `page.tsx` nhưng không export `metadata` hoặc `generateMetadata`; mọi trang trong nhánh mang cùng một tiêu đề.",
187
- fixVi: "Export `metadata` (hoặc `generateMetadata` khi tiêu đề lấy từ dữ liệu) với tiêu đề và mô tả lấy từ catalog thông điệp.",
189
+ en: "`<file>` is `page.tsx` but exports no `metadata` or `generateMetadata`; every page in the branch carries the same title.",
190
+ fix: "Export `metadata` (or `generateMetadata` when the title comes from data) with a title and description taken from the message catalog.",
188
191
  },
189
192
  "no-null-suspense-fallback": {
190
193
  code: "FE_SUSPENSE_NULL_FALLBACK",
191
- vi: "`<Suspense>` ở `<file>` có `fallback` rỗng: người dùng thấy khoảng trống thay vì trạng thái đang tải.",
192
- fixVi: "Truyền skeleton (trạng thái loading) của đúng thứ đang tải làm `fallback`.",
194
+ en: "`<Suspense>` in `<file>` has an empty `fallback`: the user sees a blank instead of a loading state.",
195
+ fix: "Pass the skeleton (loading state) of the very thing loading as the `fallback`.",
193
196
  },
194
197
  "navigation-from-intl": {
195
198
  code: "FE_I18N_NAVIGATION",
196
- vi: "`<file>` nhập `Link`, `useRouter`, `usePathname` hoặc `redirect` từ Next, không biết locale: đường dẫn mất tiền tố `[locale]`.",
197
- fixVi: "Nhập từ `modules/i18n/navigation`, được next-intl dựng từ `routing.ts`.",
199
+ en: "`<file>` imports `Link`, `useRouter`, `usePathname` or `redirect` from Next, which does not know the locale: the path loses the `[locale]` prefix.",
200
+ fix: "Import from `modules/i18n/navigation`, built by next-intl from `routing.ts`.",
198
201
  },
199
202
  "no-native-anchor": {
200
203
  code: "FE_I18N_NAVIGATION",
201
- vi: "`<a>` ở `<file>` trỏ route nội bộ (tải lại cả trang, mất locale, mất prefetch) hoặc mở tab mới mà không có `rel`.",
202
- fixVi: "Dùng `Link` từ `modules/i18n/navigation` với href dựng bởi `modules/routes`; liên kết `_blank` thêm `rel=\"noopener noreferrer\"`.",
204
+ en: "`<a>` in `<file>` points at an internal route (reloads the whole page, loses the locale, loses prefetch) or opens a new tab without `rel`.",
205
+ fix: "Use `Link` from `modules/i18n/navigation` with an href built by `modules/routes`; a `_blank` link adds `rel=\"noopener noreferrer\"`.",
203
206
  },
204
207
  "use-intl-formatter": {
205
208
  code: "FE_I18N_FORMATTER",
206
- vi: "`<file>` định dạng số, tiền hoặc ngày bằng `toLocale*String`, `new Intl.*`, `toFixed`, ký hiệu tiền dán vào template hoặc thư viện ngày, thay vì formatter của next-intl.",
207
- fixVi: "Dùng `useFormatter()` (hoặc `getFormatter()` ở server): `number(...)`, `dateTime(...)`, `relativeTime(...)`.",
209
+ en: "`<file>` formats a number, money or a date with `toLocale*String`, `new Intl.*`, `toFixed`, a currency sign pasted into a template or a date library, instead of the next-intl formatter.",
210
+ fix: "Use `useFormatter()` (or `getFormatter()` on the server): `number(...)`, `dateTime(...)`, `relativeTime(...)`.",
208
211
  },
209
212
  "no-hardcoded-route": {
210
213
  code: "FE_ROUTE_HARDCODED",
211
- vi: "`<file>` viết thẳng đường dẫn route ở nơi gọi; route đổi chỗ thì bản sao trỏ vào 404.",
212
- fixVi: "Dựng href bằng hàm của `modules/routes` và dùng đúng hàm đó ở mọi nơi.",
214
+ en: "`<file>` writes a route path inline at the call site; when the route moves, the copy points at a 404.",
215
+ fix: "Build the href with a function of `modules/routes` and use that same function everywhere.",
213
216
  },
214
217
  "client-no-server-import": {
215
218
  code: "FE_CLIENT_SERVER_IMPORT",
216
- vi: "Client component ở `<file>` nhập mã chỉ tồn tại ở server (`server-only`, `next/headers`, module Node, server reader).",
217
- fixVi: "Đọc dữ liệu ở server component hoặc server reader rồi truyền xuống bằng props; hoặc dùng SWR gọi client của app.",
219
+ en: "A client component in `<file>` imports code that exists only on the server (`server-only`, `next/headers`, a Node module, a server reader).",
220
+ fix: "Read the data in a server component or a server reader and pass it down as props; or use SWR calling the app's client.",
218
221
  },
219
222
  "server-module-marks-server-only": {
220
223
  code: "FE_SERVER_ONLY_MARK",
221
- vi: "Module `<file>` nhập API chỉ có ở server (`next/headers`, `next/server`, `next-intl/server`, module Node hoặc module đã là server-only) nhưng không mở đầu bằng `import \"server-only\"`.",
222
- fixVi: "Đặt `import \"server-only\"` làm câu lệnh đầu tiên của tệp; tệp route (`page`, `layout`, `route`, `proxy`) là server component sẵn nên không cần.",
224
+ en: "Module `<file>` imports a server-only API (`next/headers`, `next/server`, `next-intl/server`, a Node module or an already server-only module) but does not open with `import \"server-only\"`.",
225
+ fix: "Make `import \"server-only\"` the file's first statement; route files (`page`, `layout`, `route`, `proxy`) are server components already and need none.",
223
226
  },
224
227
  "web-storage-only-in-modules": {
225
228
  code: "FE_STORAGE_OUTSIDE_MODULES",
226
- vi: "`<file>` dùng `localStorage`/`sessionStorage` ngoài `modules/`; storage không có ở server và ném lỗi khi đầy hạn mức.",
227
- fixVi: "Đặt đọc/ghi sau một hook hoặc module trong `modules/` có bảo vệ (`typeof window`, try/catch) rồi gọi từ block.",
229
+ en: "`<file>` uses `localStorage`/`sessionStorage` outside `modules/`; storage does not exist on the server and throws when over quota.",
230
+ fix: "Put the read/write behind a hook or a module in `modules/` with a guard (`typeof window`, try/catch) and call it from the block.",
228
231
  },
229
232
  "no-dangerous-html": {
230
233
  code: "FE_DANGEROUS_HTML",
231
- vi: "`dangerouslySetInnerHTML` trên `<tag>` ở `<file>`: chuỗi bất kỳ trở thành HTML chạy được.",
232
- fixVi: "Render nội dung thành phần tử; chỉ `<script>` mang JSON-LD hoặc mã theme từ hằng số của app mới được dùng.",
234
+ en: "`dangerouslySetInnerHTML` on `<tag>` in `<file>`: any string becomes runnable HTML.",
235
+ fix: "Render content as elements; only a `<script>` carrying JSON-LD or theme code from an app constant may use it.",
233
236
  },
234
237
  "response-cookie-attributes": {
235
238
  code: "FE_COOKIE_ATTRIBUTES",
236
- vi: "`<file>` ghi cookie qua cookie phản hồi của Next mà không nêu `httpOnly` cố định, thiếu `secure` hoặc `sameSite` không phải `lax`/`strict`.",
237
- fixVi: "Truyền một hằng số options `as const` của module sở hữu cookie: `httpOnly: true` (chỉ `false` cho cookie tùy chọn script cần đọc), `secure` từ `modules/config`, `sameSite: \"lax\"` hoặc `\"strict\"`.",
239
+ en: "`<file>` writes a cookie through the Next response cookie without stating fixed `httpOnly`, missing `secure` or a `sameSite` that is not `lax`/`strict`.",
240
+ fix: "Pass one `as const` options constant of the module owning the cookie: `httpOnly: true` (`false` only for a cookie an optional script must read), `secure` from `modules/config`, `sameSite: \"lax\"` or `\"strict\"`.",
238
241
  },
239
242
  "props-fields-readonly": {
240
243
  code: "FE_PROPS_MUTABLE",
241
- vi: "Kiểu props của component ở `<file>` có field hoặc collection không `readonly`, nên component có thể ghi vào thứ nó được truyền.",
242
- fixVi: "Đánh dấu `readonly` cho mọi field và index signature, viết collection là `readonly T[]`, `readonly [A, B]` hoặc `ReadonlyArray<T>`.",
244
+ en: "A props type of a component in `<file>` has a field or collection that is not `readonly`, so the component could write into what it is passed.",
245
+ fix: "Mark every field and index signature `readonly`, and write collections as `readonly T[]`, `readonly [A, B]` or `ReadonlyArray<T>`.",
243
246
  },
244
247
  "no-native-img": {
245
248
  code: "FE_NATIVE_IMAGE",
246
- vi: "`<img>` thô ở `<file>`: tải ảnh gốc, không giữ chỗ nên trang nhảy khi ảnh tải xong.",
247
- fixVi: "Dùng `Image` của `next/image` với `width` và `height` (hoặc `fill` và `sizes`) và `alt` từ catalog.",
249
+ en: "Raw `<img>` in `<file>`: it loads the original image and holds no space, so the page jumps when the image arrives.",
250
+ fix: "Use `Image` of `next/image` with `width` and `height` (or `fill` and `sizes`) and an `alt` from the catalog.",
248
251
  },
249
252
  "image-has-size": {
250
253
  code: "FE_NATIVE_IMAGE",
251
- vi: "`Image` ở `<file>` thiếu `width` và `height` (hoặc `fill` mà thiếu `sizes`): trình duyệt không giữ chỗ được.",
252
- fixVi: "Cho đủ `width` và `height`, hoặc `fill` trong khung có kích thước kèm `sizes`.",
254
+ en: "`Image` in `<file>` lacks `width` and `height` (or has `fill` without `sizes`): the browser cannot hold the space.",
255
+ fix: "Give both `width` and `height`, or `fill` inside a sized frame with `sizes`.",
253
256
  },
254
257
  "outcome-kinds-exhaustive": {
255
258
  code: "FE_OUTCOME_KIND_UNHANDLED",
256
- vi: "`switch` trên `kind` ở `<file>` có nhánh `ok` nhưng thiếu một trong refused, invalid, not-found, unavailable.",
257
- fixVi: "Viết đủ năm nhánh của `Outcome<T>`, mỗi nhánh một màn; không dựa vào `default`.",
259
+ en: "A `switch` on `kind` in `<file>` has an `ok` branch but misses one of refused, invalid, not-found, unavailable.",
260
+ fix: "Write all five branches of `Outcome<T>`, one screen each; do not lean on `default`.",
258
261
  },
259
262
  "one-outcome-union": {
260
263
  code: "FE_HTTP_STATUS_COLLAPSE",
261
- vi: "`<file>` khai báo thêm một union kết quả (`ok` hoặc `kind`) ngoài `outcome.ts`. Repo chỉ có một `Outcome<T>`.",
262
- fixVi: "Dùng `Outcome<T>` của `modules/api/outcome.ts` (hoặc gói api dùng chung); thêm chi tiết nghiệp vụ qua tham số thứ hai thay vì khai báo union mới.",
264
+ en: "`<file>` declares another result union (`ok` or `kind`) outside `outcome.ts`. A repo has only one `Outcome<T>`.",
265
+ fix: "Use `Outcome<T>` of `modules/api/outcome.ts` (or the shared api package); add business detail through the second parameter instead of declaring a new union.",
263
266
  },
264
267
  "i18n-stack-in-one-module": {
265
268
  code: "FE_I18N_PLACEMENT",
266
- vi: "`<file>` tự dựng một tầng của stack next-intl (`defineRouting`, `createNavigation`, `getRequestConfig`, `createMiddleware`). Stack chỉ được viết một lần cho cả repo.",
267
- fixVi: "Gọi factory `createAppI18n` của gói i18n dùng chung trong `modules/i18n/index.ts` và nhập kết quả; repo một app thì chỉ viết stack trong `modules/i18n` của app đó.",
269
+ en: "`<file>` builds a layer of the next-intl stack itself (`defineRouting`, `createNavigation`, `getRequestConfig`, `createMiddleware`). The stack is written once for the whole repo.",
270
+ fix: "Call the `createAppI18n` factory of the shared i18n package inside `modules/i18n/index.ts` and import the result; a single-app repo writes the stack only in that app's `modules/i18n`.",
268
271
  },
269
272
  "no-raw-structural-element": {
270
273
  code: "FE_NATIVE_FORM_CONTROL",
271
- vi: "`<tag>` thô ở `<file>`. Cấu trúc trang và chữ được ghép từ component của grammar, không viết HTML thô.",
272
- fixVi: "Thay bằng component grammar tương ứng (`Heading`, `Text`, `SurfaceCard`, ...); nếu grammar chưa có, thêm vào grammar hoặc gói ui thay vì vẽ tại chỗ.",
274
+ en: "Raw `<tag>` in `<file>`. Page structure and text are composed from grammar components, not hand-written HTML.",
275
+ fix: "Replace it with the matching grammar component (`Heading`, `Text`, `SurfaceCard`, ...); if grammar lacks it, add it to grammar or the ui package instead of drawing it in place.",
273
276
  },
274
277
  "file-size-growth": {
275
278
  code: "HFS_SIZE_GROWTH",
276
- vi: "`<file>` vượt ngân sách dòng: file mới đã dài quá mức, hoặc file cũ dài hơn bản ở commit cha. Mỗi file phải nhỏ để một người đọc hết và một bài test phủ hết.",
277
- fixVi: "Tách phần mới sang file riêng theo trách nhiệm; một file đã vượt ngân sách chỉ được giữ nguyên hoặc ngắn lại.",
279
+ en: "`<file>` exceeds the line budget: a new file already too long, or an existing file longer than at the parent commit. Every file must be small enough for one person to read through and one test to cover.",
280
+ fix: "Split the new part into its own file by responsibility; a file already over budget may only stay or shrink.",
278
281
  },
279
282
  }
package/naming.mjs CHANGED
@@ -111,13 +111,13 @@ export const noDirectConstAlias = {
111
111
  // -- NAMING-3 --------------------------------------------------------------------------------------
112
112
 
113
113
  /** Letters that exist in Vietnamese and not in English, in the forms a path can carry. */
114
- const SECOND_LANGUAGE_PATH = /[àáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ]/i
114
+ const SECOND_LANGUAGE_PATH = /[\u00e0\u00e1\u00e2\u00e3\u00e8\u00e9\u00ea\u00ec\u00ed\u00f2\u00f3\u00f4\u00f5\u00f9\u00fa\u00fd\u0103\u0111\u0129\u0169\u01a1\u01b0\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9]/i
115
115
 
116
116
  /**
117
117
  * Second-language words spelled in ASCII, as a route or folder segment writes them.
118
118
  *
119
- * A path cannot carry diacritics, so `cấp phát` reaches the filesystem as `cap-phat` and every
120
- * accent-based check passes it. The segments below are the ones this product actually produced.
119
+ * A path cannot carry diacritics, so a name written with them reaches the filesystem unaccented
120
+ * (`cap-phat` below) and every accent-based check passes it. The segments below are the ones this product actually produced.
121
121
  * A list is the honest instrument here: guessing at Vietnamese-shaped ASCII would refuse `dang`
122
122
  * in `dangerous` and `cap` in `capacity`, and a rule that fires on English words is one a
123
123
  * repository turns off.
@@ -145,7 +145,7 @@ const segmentsOf = (filename) =>
145
145
  * quotes, so this is not only an authoring question: the product's own URLs stop being readable to
146
146
  * anybody outside one language.
147
147
  */
148
- export const noSecondLanguageInPath = {
148
+ const noSecondLanguageInPath = {
149
149
  meta: {
150
150
  type: "problem",
151
151
  docs: { description: "File and route names are written in the repository's one shared language." },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/eslint-canon-fe",
3
- "version": "8.0.8",
3
+ "version": "8.0.9",
4
4
  "description": "The StarCi front-end lint canon: every rule its laws publish, gathered as one flat-config plugin and attached by one factory.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -31,6 +31,8 @@
31
31
  "access": "public"
32
32
  },
33
33
  "scripts": {
34
+ "prepack": "node ../../hfs/prep-runtime.mjs",
35
+ "pretest": "node ../../hfs/prep-runtime.mjs",
34
36
  "test": "node --test \"*.spec.mjs\""
35
37
  },
36
38
  "keywords": [
package/project-graph.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  * One graph per lint run: `scripts/hfs/project-graph.mjs` (a byte copy in this package's runtime/) builds the machine's module
5
5
  * graph and slot manifest once for the repository, caches it, and each rule below reports the findings of its own codes on
6
6
  * the file ESLint is visiting, on the line the machine names. There is no second graph and no second judge: the list of
7
- * rules and their codes is `LINT_ENFORCERS` of the machine (scripts/hfs/architecture/surface.mjs); `hfs check` keeps the
7
+ * rules and their codes is `LINT_ENFORCERS` of the machine (scripts/hfs/architecture/surface.mjs); `starci app check` keeps the
8
8
  * findings that have no TypeScript file to sit on. A graph that cannot be built stops the run with the machine's message.
9
9
  */
10
10
  import { LINT_ENFORCERS } from "./runtime/scripts/hfs/architecture/surface.mjs"