@starci/skills 1.1.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 (321) hide show
  1. package/INDEX.md +74 -0
  2. package/INDEX.vi.md +75 -0
  3. package/README.md +44 -0
  4. package/README.vi.md +43 -0
  5. package/SKILL.md +135 -0
  6. package/SKILL.vi.md +128 -0
  7. package/alias/INDEX.md +104 -0
  8. package/alias/INDEX.vi.md +104 -0
  9. package/alias/alias.json +297 -0
  10. package/bin/starci-skills.mjs +219 -0
  11. package/knowledge/INDEX.md +22 -0
  12. package/knowledge/INDEX.vi.md +22 -0
  13. package/knowledge/grammars/starci/DNA.md +146 -0
  14. package/knowledge/grammars/starci/DNA.vi.md +146 -0
  15. package/knowledge/grammars/starci/INDEX.md +25 -0
  16. package/knowledge/grammars/starci/INDEX.vi.md +25 -0
  17. package/knowledge/grammars/starci/family.md +50 -0
  18. package/knowledge/grammars/starci/family.vi.md +50 -0
  19. package/knowledge/grammars/starci/idioms.md +120 -0
  20. package/knowledge/grammars/starci/idioms.vi.md +121 -0
  21. package/knowledge/grammars/starci/playbook.md +36 -0
  22. package/knowledge/grammars/starci/playbook.vi.md +35 -0
  23. package/knowledge/patterns/be/INDEX.md +29 -0
  24. package/knowledge/patterns/be/INDEX.vi.md +29 -0
  25. package/knowledge/patterns/be/comment.md +80 -0
  26. package/knowledge/patterns/be/comment.vi.md +80 -0
  27. package/knowledge/patterns/be/error.md +86 -0
  28. package/knowledge/patterns/be/error.vi.md +86 -0
  29. package/knowledge/patterns/be/folder.md +87 -0
  30. package/knowledge/patterns/be/folder.vi.md +86 -0
  31. package/knowledge/patterns/be/function.md +80 -0
  32. package/knowledge/patterns/be/function.vi.md +80 -0
  33. package/knowledge/patterns/be/imports.md +79 -0
  34. package/knowledge/patterns/be/imports.vi.md +79 -0
  35. package/knowledge/patterns/be/naming.md +87 -0
  36. package/knowledge/patterns/be/naming.vi.md +87 -0
  37. package/knowledge/patterns/be/test.md +79 -0
  38. package/knowledge/patterns/be/test.vi.md +79 -0
  39. package/knowledge/patterns/be/typing.md +73 -0
  40. package/knowledge/patterns/be/typing.vi.md +73 -0
  41. package/knowledge/patterns/fe/INDEX.md +29 -0
  42. package/knowledge/patterns/fe/INDEX.vi.md +29 -0
  43. package/knowledge/patterns/fe/comment.md +70 -0
  44. package/knowledge/patterns/fe/comment.vi.md +70 -0
  45. package/knowledge/patterns/fe/error.md +61 -0
  46. package/knowledge/patterns/fe/error.vi.md +61 -0
  47. package/knowledge/patterns/fe/folder.md +98 -0
  48. package/knowledge/patterns/fe/folder.vi.md +95 -0
  49. package/knowledge/patterns/fe/function.md +70 -0
  50. package/knowledge/patterns/fe/function.vi.md +70 -0
  51. package/knowledge/patterns/fe/imports.md +87 -0
  52. package/knowledge/patterns/fe/imports.vi.md +87 -0
  53. package/knowledge/patterns/fe/naming.md +77 -0
  54. package/knowledge/patterns/fe/naming.vi.md +77 -0
  55. package/knowledge/patterns/fe/test.md +73 -0
  56. package/knowledge/patterns/fe/test.vi.md +73 -0
  57. package/knowledge/patterns/fe/typing.md +67 -0
  58. package/knowledge/patterns/fe/typing.vi.md +67 -0
  59. package/knowledge/ui/INDEX.md +101 -0
  60. package/knowledge/ui/INDEX.vi.md +101 -0
  61. package/knowledge/ui/composition/INDEX.md +60 -0
  62. package/knowledge/ui/composition/INDEX.vi.md +63 -0
  63. package/knowledge/ui/composition/accent.md +73 -0
  64. package/knowledge/ui/composition/accent.vi.md +74 -0
  65. package/knowledge/ui/composition/action.md +55 -0
  66. package/knowledge/ui/composition/action.vi.md +56 -0
  67. package/knowledge/ui/composition/coverage.md +37 -0
  68. package/knowledge/ui/composition/coverage.vi.md +37 -0
  69. package/knowledge/ui/composition/cta.md +79 -0
  70. package/knowledge/ui/composition/cta.vi.md +79 -0
  71. package/knowledge/ui/composition/feedback.md +55 -0
  72. package/knowledge/ui/composition/feedback.vi.md +56 -0
  73. package/knowledge/ui/composition/hierarchy.md +83 -0
  74. package/knowledge/ui/composition/hierarchy.vi.md +82 -0
  75. package/knowledge/ui/composition/layout.md +91 -0
  76. package/knowledge/ui/composition/layout.vi.md +93 -0
  77. package/knowledge/ui/composition/responsive.md +67 -0
  78. package/knowledge/ui/composition/responsive.vi.md +67 -0
  79. package/knowledge/ui/composition/state.md +105 -0
  80. package/knowledge/ui/composition/state.vi.md +106 -0
  81. package/knowledge/ui/presentation/INDEX.md +94 -0
  82. package/knowledge/ui/presentation/INDEX.vi.md +69 -0
  83. package/knowledge/ui/presentation/boundary.md +170 -0
  84. package/knowledge/ui/presentation/boundary.vi.md +169 -0
  85. package/knowledge/ui/presentation/font.md +155 -0
  86. package/knowledge/ui/presentation/font.vi.md +156 -0
  87. package/knowledge/ui/presentation/gap.md +181 -0
  88. package/knowledge/ui/presentation/gap.vi.md +181 -0
  89. package/knowledge/ui/presentation/margin.md +168 -0
  90. package/knowledge/ui/presentation/margin.vi.md +171 -0
  91. package/knowledge/ui/presentation/measure.md +174 -0
  92. package/knowledge/ui/presentation/measure.vi.md +178 -0
  93. package/knowledge/ui/presentation/overflow.md +142 -0
  94. package/knowledge/ui/presentation/overflow.vi.md +142 -0
  95. package/knowledge/ui/presentation/padding.md +293 -0
  96. package/knowledge/ui/presentation/padding.vi.md +292 -0
  97. package/knowledge/ui/presentation/surface.md +166 -0
  98. package/knowledge/ui/presentation/surface.vi.md +166 -0
  99. package/knowledge/ui/presentation/text-flow.md +123 -0
  100. package/knowledge/ui/presentation/text-flow.vi.md +123 -0
  101. package/knowledge/ui/presentation/tone.md +114 -0
  102. package/knowledge/ui/presentation/tone.vi.md +113 -0
  103. package/knowledge/ui/proof/INDEX.md +55 -0
  104. package/knowledge/ui/proof/INDEX.vi.md +55 -0
  105. package/knowledge/ui/proof/accessibility.md +67 -0
  106. package/knowledge/ui/proof/accessibility.vi.md +68 -0
  107. package/knowledge/ui/proof/contrast.md +58 -0
  108. package/knowledge/ui/proof/contrast.vi.md +57 -0
  109. package/knowledge/ui/proof/focus.md +76 -0
  110. package/knowledge/ui/proof/focus.vi.md +76 -0
  111. package/knowledge/ui/proof/motion.md +63 -0
  112. package/knowledge/ui/proof/motion.vi.md +64 -0
  113. package/knowledge/ui/proof/render-truth.md +63 -0
  114. package/knowledge/ui/proof/render-truth.vi.md +63 -0
  115. package/operators/INDEX.md +199 -0
  116. package/operators/INDEX.vi.md +199 -0
  117. package/operators/architecture-decide/errors.json +115 -0
  118. package/operators/architecture-decide/operator.json +20 -0
  119. package/operators/architecture-decide/operator.md +133 -0
  120. package/operators/architecture-decide/operator.vi.md +130 -0
  121. package/operators/architecture-decide/self-test.mjs +221 -0
  122. package/operators/architecture-decide/validate.mjs +112 -0
  123. package/operators/backend-source-apply/errors.json +66 -0
  124. package/operators/backend-source-apply/operator.json +20 -0
  125. package/operators/backend-source-apply/operator.md +189 -0
  126. package/operators/backend-source-apply/operator.vi.md +185 -0
  127. package/operators/backend-source-apply/self-test.mjs +257 -0
  128. package/operators/backend-source-apply/validate.mjs +240 -0
  129. package/operators/business-decide/errors.json +78 -0
  130. package/operators/business-decide/operator.json +20 -0
  131. package/operators/business-decide/operator.md +176 -0
  132. package/operators/business-decide/operator.vi.md +174 -0
  133. package/operators/business-decide/self-test.mjs +225 -0
  134. package/operators/business-decide/validate.mjs +277 -0
  135. package/operators/content-generate/errors.json +106 -0
  136. package/operators/content-generate/operator.json +21 -0
  137. package/operators/content-generate/operator.md +155 -0
  138. package/operators/content-generate/operator.vi.md +155 -0
  139. package/operators/content-generate/self-test.mjs +288 -0
  140. package/operators/content-generate/validate.mjs +196 -0
  141. package/operators/errors.json +178 -0
  142. package/operators/frontend-direction-decide/errors.json +135 -0
  143. package/operators/frontend-direction-decide/operator.json +21 -0
  144. package/operators/frontend-direction-decide/operator.md +167 -0
  145. package/operators/frontend-direction-decide/operator.vi.md +167 -0
  146. package/operators/frontend-direction-decide/self-test.mjs +226 -0
  147. package/operators/frontend-direction-decide/validate.mjs +146 -0
  148. package/operators/frontend-presentation-resolve/errors.json +42 -0
  149. package/operators/frontend-presentation-resolve/operator.json +19 -0
  150. package/operators/frontend-presentation-resolve/operator.md +142 -0
  151. package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
  152. package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
  153. package/operators/frontend-presentation-resolve/validate.mjs +143 -0
  154. package/operators/frontend-source-apply/errors.json +30 -0
  155. package/operators/frontend-source-apply/operator.json +20 -0
  156. package/operators/frontend-source-apply/operator.md +127 -0
  157. package/operators/frontend-source-apply/operator.vi.md +126 -0
  158. package/operators/frontend-source-apply/self-test.mjs +214 -0
  159. package/operators/frontend-source-apply/validate.mjs +133 -0
  160. package/operators/frontend-surface-audit/errors.json +5 -0
  161. package/operators/frontend-surface-audit/operator.json +22 -0
  162. package/operators/frontend-surface-audit/operator.md +121 -0
  163. package/operators/frontend-surface-audit/operator.vi.md +121 -0
  164. package/operators/frontend-surface-audit/self-test.mjs +185 -0
  165. package/operators/frontend-surface-audit/validate.mjs +127 -0
  166. package/operators/git-publish/errors.json +54 -0
  167. package/operators/git-publish/operator.json +20 -0
  168. package/operators/git-publish/operator.md +180 -0
  169. package/operators/git-publish/operator.vi.md +178 -0
  170. package/operators/git-publish/self-test.mjs +177 -0
  171. package/operators/git-publish/validate.mjs +106 -0
  172. package/operators/platform-operate/errors.json +90 -0
  173. package/operators/platform-operate/operator.json +22 -0
  174. package/operators/platform-operate/operator.md +162 -0
  175. package/operators/platform-operate/operator.vi.md +160 -0
  176. package/operators/platform-operate/self-test.mjs +202 -0
  177. package/operators/platform-operate/validate.mjs +193 -0
  178. package/operators/quality-verify/errors.json +54 -0
  179. package/operators/quality-verify/operator.json +20 -0
  180. package/operators/quality-verify/operator.md +185 -0
  181. package/operators/quality-verify/operator.vi.md +177 -0
  182. package/operators/quality-verify/self-test.mjs +210 -0
  183. package/operators/quality-verify/validate.mjs +197 -0
  184. package/operators/release-deploy/errors.json +158 -0
  185. package/operators/release-deploy/operator.json +23 -0
  186. package/operators/release-deploy/operator.md +183 -0
  187. package/operators/release-deploy/operator.vi.md +181 -0
  188. package/operators/release-deploy/self-test.mjs +235 -0
  189. package/operators/release-deploy/validate.mjs +129 -0
  190. package/operators/uat-verify/errors.json +78 -0
  191. package/operators/uat-verify/operator.json +25 -0
  192. package/operators/uat-verify/operator.md +162 -0
  193. package/operators/uat-verify/operator.vi.md +161 -0
  194. package/operators/uat-verify/self-test.mjs +270 -0
  195. package/operators/uat-verify/validate.mjs +202 -0
  196. package/operators/workspace-bind/errors.json +90 -0
  197. package/operators/workspace-bind/operator.json +21 -0
  198. package/operators/workspace-bind/operator.md +148 -0
  199. package/operators/workspace-bind/operator.vi.md +148 -0
  200. package/operators/workspace-bind/self-test.mjs +205 -0
  201. package/operators/workspace-bind/validate.mjs +152 -0
  202. package/package.json +50 -0
  203. package/readiness/initialization/workspaces/commit-policy.json +91 -0
  204. package/readiness/initialization/workspaces/config.schema.json +35 -0
  205. package/readiness/initialization/workspaces/device-state.schema.json +54 -0
  206. package/readiness/initialization/workspaces/local-route.schema.json +206 -0
  207. package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
  208. package/resources/INDEX.md +96 -0
  209. package/resources/INDEX.vi.md +99 -0
  210. package/resources/agents/profiles/claude.json +131 -0
  211. package/resources/agents/profiles/openai.json +131 -0
  212. package/resources/orchestrator.json +71 -0
  213. package/resources/tools.json +85 -0
  214. package/routing.json +272 -0
  215. package/scripts/alias-registry.mjs +31 -0
  216. package/scripts/device-state.mjs +497 -0
  217. package/scripts/device-state.spec.mjs +18 -0
  218. package/scripts/errors-registry.mjs +68 -0
  219. package/scripts/generate-alias-doc.mjs +66 -0
  220. package/scripts/generate-grammar-dna.mjs +387 -0
  221. package/scripts/generate-operators-index.mjs +95 -0
  222. package/scripts/generate-presentation-owned.mjs +681 -0
  223. package/scripts/install-cli.spec.mjs +74 -0
  224. package/scripts/json-schema.mjs +94 -0
  225. package/scripts/operator-md.mjs +96 -0
  226. package/scripts/run-operator-self-tests.mjs +36 -0
  227. package/scripts/validate-alias.mjs +165 -0
  228. package/scripts/validate-defaults.mjs +72 -0
  229. package/scripts/validate-knowledge-citations.mjs +90 -0
  230. package/scripts/validate-operator.mjs +125 -0
  231. package/scripts/validate-request.mjs +80 -0
  232. package/scripts/validate-resources.mjs +117 -0
  233. package/scripts/validate-response.mjs +141 -0
  234. package/scripts/validate-routing.mjs +91 -0
  235. package/scripts/validate-step.mjs +50 -0
  236. package/scripts/validate-templates.mjs +226 -0
  237. package/scripts/validate-templates.spec.mjs +144 -0
  238. package/scripts/validate-workflows.mjs +106 -0
  239. package/scripts/workspace-portable.mjs +389 -0
  240. package/scripts/workspace-portable.spec.mjs +246 -0
  241. package/templates/README.md +47 -0
  242. package/templates/README.vi.md +45 -0
  243. package/templates/changes.example.md +27 -0
  244. package/templates/grammars.template.md +34 -0
  245. package/templates/kinds/architecture-decision.contract.json +14 -0
  246. package/templates/kinds/architecture-decision.skeleton.md +52 -0
  247. package/templates/kinds/backend-source-application.contract.json +10 -0
  248. package/templates/kinds/backend-source-application.skeleton.md +34 -0
  249. package/templates/kinds/business-promise-authority.contract.json +13 -0
  250. package/templates/kinds/business-promise-authority.skeleton.md +67 -0
  251. package/templates/kinds/capture.schema.json +33 -0
  252. package/templates/kinds/changes.contract.json +10 -0
  253. package/templates/kinds/changes.skeleton.md +26 -0
  254. package/templates/kinds/checks.schema.json +44 -0
  255. package/templates/kinds/claims.schema.json +165 -0
  256. package/templates/kinds/conformance.schema.json +15 -0
  257. package/templates/kinds/content-brief.contract.json +12 -0
  258. package/templates/kinds/content-brief.skeleton.md +45 -0
  259. package/templates/kinds/content-generation-receipt.contract.json +11 -0
  260. package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
  261. package/templates/kinds/content-review.contract.json +11 -0
  262. package/templates/kinds/content-review.skeleton.md +45 -0
  263. package/templates/kinds/contract.schema.json +28 -0
  264. package/templates/kinds/coverage-matrix.schema.json +86 -0
  265. package/templates/kinds/coverage.schema.json +62 -0
  266. package/templates/kinds/current-state.schema.json +40 -0
  267. package/templates/kinds/delta.schema.json +95 -0
  268. package/templates/kinds/e2e.schema.json +63 -0
  269. package/templates/kinds/frontend-direction-decision.contract.json +93 -0
  270. package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
  271. package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
  272. package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
  273. package/templates/kinds/frontend-source-application.contract.json +10 -0
  274. package/templates/kinds/frontend-source-application.skeleton.md +30 -0
  275. package/templates/kinds/frontend-surface-audit.contract.json +52 -0
  276. package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
  277. package/templates/kinds/gate-result.schema.json +64 -0
  278. package/templates/kinds/git-publication.contract.json +82 -0
  279. package/templates/kinds/git-publication.skeleton.md +61 -0
  280. package/templates/kinds/independent-critique.contract.json +9 -0
  281. package/templates/kinds/independent-critique.skeleton.md +28 -0
  282. package/templates/kinds/inventory.schema.json +35 -0
  283. package/templates/kinds/model.schema.json +99 -0
  284. package/templates/kinds/mutations.schema.json +65 -0
  285. package/templates/kinds/platform-operation-receipt.contract.json +76 -0
  286. package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
  287. package/templates/kinds/probes.schema.json +130 -0
  288. package/templates/kinds/proof.schema.json +17 -0
  289. package/templates/kinds/quality-verification.contract.json +87 -0
  290. package/templates/kinds/quality-verification.skeleton.md +58 -0
  291. package/templates/kinds/release-deployment.contract.json +85 -0
  292. package/templates/kinds/release-deployment.skeleton.md +67 -0
  293. package/templates/kinds/route.schema.json +293 -0
  294. package/templates/kinds/stack-model.schema.json +90 -0
  295. package/templates/kinds/uat-capture.schema.json +35 -0
  296. package/templates/kinds/uat-flow-verification.contract.json +11 -0
  297. package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
  298. package/templates/kinds/uat-snapshot.schema.json +96 -0
  299. package/templates/kinds/uat-verdicts.schema.json +41 -0
  300. package/templates/kinds/ui-coverage.schema.json +77 -0
  301. package/templates/kinds/verdicts.schema.json +39 -0
  302. package/templates/kinds/workspace-route-binding.contract.json +12 -0
  303. package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
  304. package/templates/kinds/writes.schema.json +37 -0
  305. package/templates/operator.template.md +73 -0
  306. package/templates/patterns.template.md +31 -0
  307. package/templates/step/request.schema.json +42 -0
  308. package/templates/step/response.schema.json +144 -0
  309. package/templates/ui-composition.template.md +36 -0
  310. package/templates/ui-presentation.template.md +57 -0
  311. package/templates/ui-proof.template.md +34 -0
  312. package/workflows/README.md +37 -0
  313. package/workflows/README.vi.md +37 -0
  314. package/workflows/backend-feature.json +59 -0
  315. package/workflows/content-unit.json +19 -0
  316. package/workflows/frontend-new-surface.json +81 -0
  317. package/workflows/frontend-reconstruct.json +67 -0
  318. package/workflows/frontend-refine.json +67 -0
  319. package/workflows/frontend-with-uat.json +78 -0
  320. package/workflows/full-feature.json +104 -0
  321. package/workflows/release.json +29 -0
@@ -0,0 +1,79 @@
1
+ # Import
2
+
3
+ Tệp này trả lời một câu hỏi: cho một tệp backend, nó được import gì, qua đường dẫn nào, theo kiểu
4
+ nào, và theo thứ tự nào?
5
+
6
+ Nguồn: `tsconfig.json` (`paths`), `jest.config.ts` (`moduleNameMapper`), `eslint.config.mjs`,
7
+ `features/api/core/graphql/mutations/courses/add-to-cart/*`,
8
+ `features/api/core/graphql/queries/courses/course/course.handler.ts`,
9
+ `modules/platform/exceptions/errors/ai/ai-quota-exhausted.ts`,
10
+ `modules/platform/exceptions/filters/abstract-exception-http.filter.ts`.
11
+
12
+ ## BE-IMPORTS-1 — Alias
13
+
14
+ | Case | Dùng khi | Viết |
15
+ | --- | --- | --- |
16
+ | Case 1 | Một năng lực | `@modules/<capability>/<đường dẫn sâu tới tệp>` — `import { ICQRSHandler } from "@modules/platform/cqrs/icqrs-handler"` |
17
+ | Case 2 | Một cánh cửa | `@features/<door>/…` (dùng từ `apps/` và kiểm thử) |
18
+ | Case 3 | Trợ giúp kiểm thử | `import { makeEntityManagerMock } from "@tests/mocks/entity-manager.mock"` |
19
+ | Case 4 | Trong một đơn vị | `./add-to-cart.command`, `./graphql-types/request`, `./add-to-cart.service` |
20
+ | Case 5 | Ngược lên trong cùng cánh cửa | `import { ExecuteParams } from "../../../../types/execute"` — 290 tệp dưới `features` dùng đường dẫn tương đối này; 0 tệp dùng `@features/api/core/types/execute` (lint `no-self-module-alias`) |
21
+ | Case 6 | Trong cây exception | `import { AbstractException } from "../abstract"` |
22
+
23
+ ## BE-IMPORTS-2 — Kiểu ngoặc
24
+
25
+ Mọi import đều nhiều dòng, một binding mỗi dòng, dấu phẩy cuối. Lint `object-curly-newline` với
26
+ `ImportDeclaration: "always"` ép điều này.
27
+
28
+ | Case | Dùng khi | Viết |
29
+ | --- | --- | --- |
30
+ | Case 1 | Một binding | `import {\n Injectable,\n} from "@nestjs/common"` |
31
+ | Case 2 | Nhiều binding | `import {\n CommandHandler,\n ICommandHandler,\n} from "@nestjs/cqrs"` |
32
+ | Case 3 | Chỉ kiểu | `import type {\n EntityManager,\n} from "typeorm"`; `import type {\n AbstractExceptionMetadata,\n} from "../abstract"` (1388 trên 4463 tệp dùng `import type`) |
33
+
34
+ ## BE-IMPORTS-3 — Thứ tự
35
+
36
+ Không bị lint ép. Import đầu tiên chiếm ưu thế trong tệp đơn vị GraphQL là framework Nest.
37
+
38
+ | Case | Dùng khi | Viết |
39
+ | --- | --- | --- |
40
+ | Case 1 | Framework trước | `@nestjs/graphql`, `@nestjs/common`, `@nestjs/cqrs` (import đầu tiên ở 303 trên 398 tệp GraphQL được lấy mẫu) |
41
+ | Case 2 | Rồi năng lực | `@modules/api/…`, `@modules/integrations/…`, `@modules/platform/…`, `@modules/databases/…` (46 tệp bắt đầu ở đây) |
42
+ | Case 3 | Rồi tương đối | `../../../../types/execute` (41 tệp bắt đầu ở đây), rồi `./…` sau cùng (7) |
43
+ | Case 4 | Lệch | `add-to-cart.handler.ts` mở bằng `@modules/platform/cqrs/icqrs-handler` và xen `@nestjs/common` sau nhiều import `@modules`; thứ tự trên là ưu thế, không phải toàn thể |
44
+
45
+ ## BE-IMPORTS-4 — Chiều phân tầng
46
+
47
+ | Case | Dùng khi | Viết |
48
+ | --- | --- | --- |
49
+ | Case 1 | `features` → `modules` | luôn được phép: handler import entity, exception, decorator từ `@modules/…` |
50
+ | Case 2 | `modules` → `features` | không được phép (lint `no-capability-imports-features`); 6 tệp không phải spec dưới `modules/bussiness/{daily-quest,flashcard,kpi-reward,streak,weekly-challenge}` vẫn làm vậy — ghi nhận là nợ, không phải mẫu |
51
+ | Case 3 | `modules/**/*.module.ts` → module khác trong repo | không import; module năng lực được đăng ký `isGlobal: true` ở gốc app (lint `no-non-global-module-import`, ở mức error cho cả `src/modules` lẫn `src/features`) |
52
+ | Case 4 | Barrel thư mục | không tồn tại để import; mọi import gọi thẳng tên tệp (lint `must-deep-module-import`, `no-folder-reexport`) |
53
+ | Case 5 | Thoát tương đối qua năng lực khác | `from "../../modules/…"` xuất hiện ở 0 tệp (lint `no-relative-capability-escape`) |
54
+
55
+ ## BE-IMPORTS-5 — Một đơn vị GraphQL import gì
56
+
57
+ | Case | Dùng khi | Viết |
58
+ | --- | --- | --- |
59
+ | Case 1 | Handler | `@modules/platform/cqrs/icqrs-handler`, entity từ `@modules/databases/postgresql/primary/entities/*.entity`, exception từ `@modules/platform/exceptions/errors/<domain>/<name>`, `@nestjs/common`, `@nestjs/cqrs`, `typeorm` (kiểu), `./<name>.command` |
60
+ | Case 2 | Resolver | `@nestjs/graphql`, `@nestjs/common`, `@modules/api/apollo/server/decorators/locale.decorators`, `@modules/api/apollo/server/interceptors/graphql-transform.interceptor`, `@modules/integrations/keycloak/guards/keycloak-auth-graphql.guard`, `@modules/integrations/keycloak/keycloak.decorators`, `@modules/platform/throttler/*`, `./graphql-types/request`, `./graphql-types/response`, `./<name>.service` |
61
+ | Case 3 | Response | `@nestjs/graphql`, `@modules/api/apollo/server/graphql-types/object-types/graphql-response`, `@modules/api/apollo/server/types/graphql-response`, entity |
62
+ | Case 4 | Module | `@nestjs/common` và bốn tệp anh em |
63
+
64
+ ## BE-IMPORTS-6 — Bị cấm
65
+
66
+ | Case | Dùng khi | Viết |
67
+ | --- | --- | --- |
68
+ | Case 1 | Default export | không có (lint `no-default-export`; các điểm vào vòng đời Jest được miễn) |
69
+ | Case 2 | `process.env` | chỉ `src/modules/platform/env/utils/parse-env.ts`; mọi nơi khác gọi `envConfig()` |
70
+ | Case 3 | `console.*` | lint error trong `src/**`; 188 tệp ghi log qua `WinstonService`, còn sót 12 chỗ `console.` |
71
+ | Case 4 | `Logger` của Nest | không dùng (lint `no-nest-logger`, `no-framework-logger`) |
72
+ | Case 5 | Token cache thô ngoài module cache | lint `must-use-cache-service` |
73
+
74
+ ## BE-IMPORTS-7 — Jest nhìn thấy cùng alias
75
+
76
+ | Case | Dùng khi | Viết |
77
+ | --- | --- | --- |
78
+ | Case 1 | `jest.config.ts` | `moduleNameMapper: { "^@modules/(.*)$": "<rootDir>/src/modules/$1", "^@features/(.*)$": "<rootDir>/src/features/$1", "^@tests/(.*)$": "<rootDir>/src/tests/$1" }` |
79
+ | Case 2 | Spec giả lập một đường dẫn module | `jest.mock("@modules/platform/env/config", () => ({ envConfig: () => ({ … }) }))` |
@@ -0,0 +1,87 @@
1
+ # Naming
2
+
3
+ This file answers one question: given a backend file, class, type, constant or method, what is it
4
+ called?
5
+
6
+ Sources: `features/api/core/graphql/mutations/courses/add-to-cart/*`,
7
+ `features/api/core/graphql/queries/courses/course/*`, `modules/platform/exceptions/errors/**`,
8
+ `modules/databases/postgresql/primary/enums/locale.ts`, `modules/ai/ai-entitlement.service.ts`,
9
+ `modules/ai/constants/`, `modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`.
10
+
11
+ ## BE-NAMING-1 — Files: kebab-case plus a role suffix
12
+
13
+ | Case | When | Write |
14
+ | --- | --- | --- |
15
+ | Case 1 | GraphQL unit | `add-to-cart.handler.ts`, `add-to-cart.service.ts`, `add-to-cart.resolver.ts`, `add-to-cart.command.ts`, `course.query.ts`, `add-to-cart.module.ts`, `add-to-cart.module-definition.ts` |
16
+ | Case 2 | Capability | `winston.service.ts`, `winston.providers.ts`, `winston.decorators.ts`, `keycloak-auth-graphql.guard.ts`, `graphql-transform.interceptor.ts`, `abstract-exception-http.filter.ts` |
17
+ | Case 3 | Data | `cart-item.entity.ts`, `enums/locale.ts`, `kpi-current.util.ts` |
18
+ | Case 4 | Exception | `errors/courses/challenge-not-found.ts` — no suffix (289 of 294) |
19
+ | Case 5 | Spec | same basename plus `.spec` / `.int-spec`: `add-to-cart.handler.spec.ts`, `schema-builds.int-spec.ts` |
20
+ | Case 6 | Migration | `1719200000000-AddIsEnrolledToEnrollments.ts` |
21
+
22
+ ## BE-NAMING-2 — Classes: PascalCase subject plus role
23
+
24
+ | Case | When | Write |
25
+ | --- | --- | --- |
26
+ | Case 1 | Handler / Service / Resolver | `AddToCartHandler`, `AddToCartService`, `AddToCartResolver`, `CourseHandler` (154 `…Handler`, 701 `…Service`, 309 `…Resolver`) |
27
+ | Case 2 | Message | `AddToCartCommand` (83), `CourseQuery` (67) |
28
+ | Case 3 | GraphQL types | `AddToCartRequest` (209 `…Request`), `AddToCartResponse` (303 `…Response`) |
29
+ | Case 4 | Unit module | `AddToCartSingleMutationModule extends ConfigurableModuleClass`; query units `…SingleQueryModule` |
30
+ | Case 5 | Capability module | `AiModule`, `AiBalancerModule`, `WinstonModule` (552 `…Module`) |
31
+ | Case 6 | Entity | `CartItemEntity`, `CourseEntity`, `EnrollmentEntity`, `UserEntity` (200) |
32
+ | Case 7 | Others | `KeycloakAuthGraphQLGuard`, `GraphQLTransformInterceptor`, `AbstractExceptionHttpFilter`, `ICQRSHandler` (abstract base, `I` prefix kept) |
33
+
34
+ ## BE-NAMING-3 — Exception identity
35
+
36
+ | Case | When | Write |
37
+ | --- | --- | --- |
38
+ | Case 1 | Class | `ChallengeNotFoundException`, `AiQuotaExhaustedException` — ends in `Exception` (337; lint `exception-name-ends-in-exception`) |
39
+ | Case 2 | Code | `"CHALLENGE_NOT_FOUND_EXCEPTION"`, `"AI_QUOTA_EXHAUSTED_EXCEPTION"` — the class name in UPPER_SNAKE (lint `exception-code-matches-class-name`) |
40
+ | Case 3 | Metadata | `interface ChallengeNotFoundExceptionMetadata extends AbstractExceptionMetadata` (285 of 294 files; lint `exception-metadata-type-named-for-class`) |
41
+ | Case 4 | Message | a short sentence: `"Challenge not found"`, `` `AI quota exhausted (${window})` `` |
42
+
43
+ ## BE-NAMING-4 — Enums
44
+
45
+ | Case | When | Write |
46
+ | --- | --- | --- |
47
+ | Case 1 | Declaration | `export enum Locale { Vi = "vi", En = "en" }`, `export enum ActionType { … }` — PascalCase name and members, string values (137 enums, 0 `const enum`) |
48
+ | Case 2 | GraphQL registration | `export const GraphQLTypeLocale = createEnumType(Locale)` then `registerEnumType(GraphQLTypeLocale, { name: "Locale", … })` |
49
+ | Case 3 | Log identity | `WinstonLog.HttpExceptionLogged` — an enum member, never a string message |
50
+ | Case 4 | Throttle level | `ThrottlerConfig.Medium` |
51
+
52
+ ## BE-NAMING-5 — Constants
53
+
54
+ | Case | When | Write |
55
+ | --- | --- | --- |
56
+ | Case 1 | Module-level value | `const SUCCESS_MESSAGE_METADATA = "graphqlSuccessMessage"`, `TIER_ALLOWED_CATEGORIES`, `const POSTGRESQL_PRIMARY = "primary"` (194 exported UPPER_SNAKE consts) |
57
+ | Case 2 | Constants file | `modules/ai/constants/ai-entitlement.constants.ts`, `constants/credit-cost.ts` |
58
+ | Case 3 | Exported function value | camelCase arrow: `export const getKpiCurrentValues = (stats: UserStatsResult): Record<KpiKey, number> => …`, `export const GraphQLSuccessMessage = (message) => SetMetadata(…)` (decorator factories keep PascalCase) |
59
+ | Case 4 | Spec fixtures | `const BASE_CREDITS_5H = 30`, `const futureDate = (): Date => …` |
60
+
61
+ ## BE-NAMING-6 — Methods
62
+
63
+ | Case | When | Write |
64
+ | --- | --- | --- |
65
+ | Case 1 | Handler body | `protected override async process(command: AddToCartCommand)` — always `process` (140/140) |
66
+ | Case 2 | Service and resolver door | `async execute(params: ExecuteParams<AddToCartRequest>)` |
67
+ | Case 3 | Capability service | verbs: `resolve`, `consume`, `history`, `snapshot`, `assertNotOverQuota`, `getSettings`, `grantTier` (`AiEntitlementService`) |
68
+ | Case 4 | Not a bare verb export | lint `no-bare-verb-export`; `getKpiCurrentValues`, not `get` |
69
+ | Case 5 | No version in a name | lint `no-version-in-name` |
70
+
71
+ ## BE-NAMING-7 — Type names
72
+
73
+ | Case | When | Write |
74
+ | --- | --- | --- |
75
+ | Case 1 | Input object | `…Params` (614 interfaces): `ExecuteParams<T>`, `UseQueryCourseSwrParams` |
76
+ | Case 2 | Output object | `…Result` (216): `UserStatsResult` |
77
+ | Case 3 | Options | `…Options` (35) |
78
+ | Case 4 | Exception payload | `…ExceptionMetadata` (290) |
79
+ | Case 5 | Plain interface for a decorated class | `IAbstractGraphQLResponse<T>` beside `AbstractGraphQLResponse` |
80
+
81
+ ## BE-NAMING-8 — GraphQL surface
82
+
83
+ | Case | When | Write |
84
+ | --- | --- | --- |
85
+ | Case 1 | Field name | camelCase in the decorator: `@Mutation(() => AddToCartResponse, { name: "addToCart", … })` |
86
+ | Case 2 | Argument | always `"request"`: `@Args("request", { description: "Course id to add to the cart." }) request: AddToCartRequest` |
87
+ | Case 3 | Own decorators | `@InjectPrimaryPostgreSQLEntityManager()`, `@KeycloakGraphQLUser()`, `@GraphQLLocale()`, `@UseThrottler(…)`, `@GraphQLSuccessMessage({…})` |
@@ -0,0 +1,87 @@
1
+ # Đặt tên
2
+
3
+ Tệp này trả lời một câu hỏi: cho một tệp, lớp, kiểu, hằng hay phương thức backend, nó được gọi là
4
+ gì?
5
+
6
+ Nguồn: `features/api/core/graphql/mutations/courses/add-to-cart/*`,
7
+ `features/api/core/graphql/queries/courses/course/*`, `modules/platform/exceptions/errors/**`,
8
+ `modules/databases/postgresql/primary/enums/locale.ts`, `modules/ai/ai-entitlement.service.ts`,
9
+ `modules/ai/constants/`, `modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`.
10
+
11
+ ## BE-NAMING-1 — Tệp: kebab-case cộng hậu tố vai trò
12
+
13
+ | Case | Dùng khi | Viết |
14
+ | --- | --- | --- |
15
+ | Case 1 | Đơn vị GraphQL | `add-to-cart.handler.ts`, `add-to-cart.service.ts`, `add-to-cart.resolver.ts`, `add-to-cart.command.ts`, `course.query.ts`, `add-to-cart.module.ts`, `add-to-cart.module-definition.ts` |
16
+ | Case 2 | Năng lực | `winston.service.ts`, `winston.providers.ts`, `winston.decorators.ts`, `keycloak-auth-graphql.guard.ts`, `graphql-transform.interceptor.ts`, `abstract-exception-http.filter.ts` |
17
+ | Case 3 | Dữ liệu | `cart-item.entity.ts`, `enums/locale.ts`, `kpi-current.util.ts` |
18
+ | Case 4 | Exception | `errors/courses/challenge-not-found.ts` — không hậu tố (289 trên 294) |
19
+ | Case 5 | Spec | cùng tên gốc cộng `.spec` / `.int-spec`: `add-to-cart.handler.spec.ts`, `schema-builds.int-spec.ts` |
20
+ | Case 6 | Migration | `1719200000000-AddIsEnrolledToEnrollments.ts` |
21
+
22
+ ## BE-NAMING-2 — Lớp: chủ thể PascalCase cộng vai trò
23
+
24
+ | Case | Dùng khi | Viết |
25
+ | --- | --- | --- |
26
+ | Case 1 | Handler / Service / Resolver | `AddToCartHandler`, `AddToCartService`, `AddToCartResolver`, `CourseHandler` (154 `…Handler`, 701 `…Service`, 309 `…Resolver`) |
27
+ | Case 2 | Thông điệp | `AddToCartCommand` (83), `CourseQuery` (67) |
28
+ | Case 3 | Kiểu GraphQL | `AddToCartRequest` (209 `…Request`), `AddToCartResponse` (303 `…Response`) |
29
+ | Case 4 | Module đơn vị | `AddToCartSingleMutationModule extends ConfigurableModuleClass`; đơn vị query là `…SingleQueryModule` |
30
+ | Case 5 | Module năng lực | `AiModule`, `AiBalancerModule`, `WinstonModule` (552 `…Module`) |
31
+ | Case 6 | Entity | `CartItemEntity`, `CourseEntity`, `EnrollmentEntity`, `UserEntity` (200) |
32
+ | Case 7 | Khác | `KeycloakAuthGraphQLGuard`, `GraphQLTransformInterceptor`, `AbstractExceptionHttpFilter`, `ICQRSHandler` (lớp gốc trừu tượng, giữ tiền tố `I`) |
33
+
34
+ ## BE-NAMING-3 — Danh tính exception
35
+
36
+ | Case | Dùng khi | Viết |
37
+ | --- | --- | --- |
38
+ | Case 1 | Lớp | `ChallengeNotFoundException`, `AiQuotaExhaustedException` — kết thúc bằng `Exception` (337; lint `exception-name-ends-in-exception`) |
39
+ | Case 2 | Mã | `"CHALLENGE_NOT_FOUND_EXCEPTION"`, `"AI_QUOTA_EXHAUSTED_EXCEPTION"` — tên lớp viết UPPER_SNAKE (lint `exception-code-matches-class-name`) |
40
+ | Case 3 | Metadata | `interface ChallengeNotFoundExceptionMetadata extends AbstractExceptionMetadata` (285 trên 294 tệp; lint `exception-metadata-type-named-for-class`) |
41
+ | Case 4 | Thông điệp | một câu ngắn: `"Challenge not found"`, `` `AI quota exhausted (${window})` `` |
42
+
43
+ ## BE-NAMING-4 — Enum
44
+
45
+ | Case | Dùng khi | Viết |
46
+ | --- | --- | --- |
47
+ | Case 1 | Khai báo | `export enum Locale { Vi = "vi", En = "en" }`, `export enum ActionType { … }` — tên và thành viên PascalCase, giá trị chuỗi (137 enum, 0 `const enum`) |
48
+ | Case 2 | Đăng ký GraphQL | `export const GraphQLTypeLocale = createEnumType(Locale)` rồi `registerEnumType(GraphQLTypeLocale, { name: "Locale", … })` |
49
+ | Case 3 | Danh tính log | `WinstonLog.HttpExceptionLogged` — một thành viên enum, không bao giờ là chuỗi thông điệp |
50
+ | Case 4 | Mức throttle | `ThrottlerConfig.Medium` |
51
+
52
+ ## BE-NAMING-5 — Hằng
53
+
54
+ | Case | Dùng khi | Viết |
55
+ | --- | --- | --- |
56
+ | Case 1 | Giá trị cấp module | `const SUCCESS_MESSAGE_METADATA = "graphqlSuccessMessage"`, `TIER_ALLOWED_CATEGORIES`, `const POSTGRESQL_PRIMARY = "primary"` (194 hằng UPPER_SNAKE được export) |
57
+ | Case 2 | Tệp hằng | `modules/ai/constants/ai-entitlement.constants.ts`, `constants/credit-cost.ts` |
58
+ | Case 3 | Giá trị hàm được export | arrow camelCase: `export const getKpiCurrentValues = (stats: UserStatsResult): Record<KpiKey, number> => …`, `export const GraphQLSuccessMessage = (message) => SetMetadata(…)` (nhà máy decorator giữ PascalCase) |
59
+ | Case 4 | Fixture trong spec | `const BASE_CREDITS_5H = 30`, `const futureDate = (): Date => …` |
60
+
61
+ ## BE-NAMING-6 — Phương thức
62
+
63
+ | Case | Dùng khi | Viết |
64
+ | --- | --- | --- |
65
+ | Case 1 | Thân handler | `protected override async process(command: AddToCartCommand)` — luôn là `process` (140/140) |
66
+ | Case 2 | Cửa của service và resolver | `async execute(params: ExecuteParams<AddToCartRequest>)` |
67
+ | Case 3 | Service năng lực | động từ: `resolve`, `consume`, `history`, `snapshot`, `assertNotOverQuota`, `getSettings`, `grantTier` (`AiEntitlementService`) |
68
+ | Case 4 | Không export động từ trần | lint `no-bare-verb-export`; `getKpiCurrentValues`, không phải `get` |
69
+ | Case 5 | Không phiên bản trong tên | lint `no-version-in-name` |
70
+
71
+ ## BE-NAMING-7 — Tên kiểu
72
+
73
+ | Case | Dùng khi | Viết |
74
+ | --- | --- | --- |
75
+ | Case 1 | Object đầu vào | `…Params` (614 interface): `ExecuteParams<T>`, `UseQueryCourseSwrParams` |
76
+ | Case 2 | Object đầu ra | `…Result` (216): `UserStatsResult` |
77
+ | Case 3 | Tùy chọn | `…Options` (35) |
78
+ | Case 4 | Payload exception | `…ExceptionMetadata` (290) |
79
+ | Case 5 | Interface thuần cho một lớp có decorator | `IAbstractGraphQLResponse<T>` bên cạnh `AbstractGraphQLResponse` |
80
+
81
+ ## BE-NAMING-8 — Bề mặt GraphQL
82
+
83
+ | Case | Dùng khi | Viết |
84
+ | --- | --- | --- |
85
+ | Case 1 | Tên trường | camelCase trong decorator: `@Mutation(() => AddToCartResponse, { name: "addToCart", … })` |
86
+ | Case 2 | Đối số | luôn là `"request"`: `@Args("request", { description: "Course id to add to the cart." }) request: AddToCartRequest` |
87
+ | Case 3 | Decorator của mình | `@InjectPrimaryPostgreSQLEntityManager()`, `@KeycloakGraphQLUser()`, `@GraphQLLocale()`, `@UseThrottler(…)`, `@GraphQLSuccessMessage({…})` |
@@ -0,0 +1,79 @@
1
+ # Test
2
+
3
+ This file answers one question: given a backend unit, where does its spec live, how is the subject
4
+ built, what does it assert, and what does it leave alone?
5
+
6
+ Sources: `jest.config.ts`, `features/api/core/graphql/mutations/courses/add-to-cart/add-to-cart.handler.spec.ts`,
7
+ `modules/ai/ai-entitlement.service.spec.ts`,
8
+ `modules/ai/ping/classes/abstract-provider-ping.service.spec.ts`, `src/tests/**`, `eslint.config.mjs`.
9
+
10
+ ## BE-TEST-1 — Placement and lanes
11
+
12
+ | Case | When | Write |
13
+ | --- | --- | --- |
14
+ | Case 1 | Unit | `<name>.spec.ts` beside `<name>.ts` (875; lint `unit-test-colocated`) |
15
+ | Case 2 | Handler twin | `add-to-cart.handler.spec.ts` beside the handler (118 of 149 handlers; lint `handler-has-twin-spec`) |
16
+ | Case 3 | Service twin | 271 of 396 services under `src/modules` |
17
+ | Case 4 | Integration | `*.int-spec.ts` (7), Testcontainers, `globalSetup: src/tests/helpers/e2e-setup.ts`, `testTimeout: 120_000` |
18
+ | Case 5 | E2E and harness | `*.e2e-spec.ts`, `*.harness-spec.ts` under `src/tests/<lane>`, excluded from `unit` by suffix |
19
+ | Case 6 | Projects | `projects: [{ displayName: "unit", testMatch: ["**/*.spec.ts"], testPathIgnorePatterns: ["\\.int-spec\\.ts$", "\\.e2e-spec\\.ts$", "\\.harness-spec\\.ts$"] }, { displayName: "integration", testMatch: ["**/*.int-spec.ts"], … }]` |
20
+ | Case 7 | Runner | `jest` with `ts-jest` (`diagnostics: false`), `testEnvironment: "node"` |
21
+
22
+ ## BE-TEST-2 — Handler spec: construct directly, cast the mocks
23
+
24
+ | Case | When | Write |
25
+ | --- | --- | --- |
26
+ | Case 1 | Subject | `const handler = new AddToCartHandler({ exists } as never)` — the entity manager is a bare object of `jest.fn()`; `as never` appears in 496 of 875 specs, `as unknown as` in 323 |
27
+ | Case 2 | Message factory | `const command = (courseId: string) => new AddToCartCommand({ request: { courseId }, user: { id: "u1" } } as never)` |
28
+ | Case 3 | Ordered mock answers | `const exists = jest.fn().mockResolvedValueOnce(false)`; later `exists.mockResolvedValueOnce(true).mockResolvedValueOnce(true)` |
29
+ | Case 4 | Call through the public door | `handler.execute(command("missing"))`, never `process` |
30
+
31
+ ## BE-TEST-3 — Service spec: a testing module with shared mocks
32
+
33
+ | Case | When | Write |
34
+ | --- | --- | --- |
35
+ | Case 1 | Module | `Test.createTestingModule({ … })` (274 specs) with `getEntityManagerToken(POSTGRESQL_PRIMARY)` |
36
+ | Case 2 | Entity manager | `import { makeEntityManagerMock } from "@tests/mocks/entity-manager.mock"` with `EntityManagerMock`, `QueryBuilderMock` types |
37
+ | Case 3 | Config | `jest.mock("@modules/platform/env/config", () => ({ envConfig: () => ({ ai: { ping: mockPingConfig } }) }))` |
38
+ | Case 4 | Abstract class | a local subclass: `class TestPingService extends AbstractProviderPingService { protected readonly provider = ModelProvider.OpenAI; execute = jest.fn()…; protected executePing(key: string) { return this.execute(key) } }` |
39
+ | Case 5 | Fixture constants | documented UPPER_SNAKE: `/** Free base credit caps the mocked quota config hands back (credits per window). */ const BASE_CREDITS_5H = 30` |
40
+
41
+ ## BE-TEST-4 — What is asserted
42
+
43
+ | Case | When | Write |
44
+ | --- | --- | --- |
45
+ | Case 1 | The exception type | `await expect(handler.execute(command("missing"))).rejects.toBeInstanceOf(CourseNotFoundException)` (262 specs use `rejects.toBeInstanceOf` / `toThrow(`; 49 match a message string) |
46
+ | Case 2 | Ordering of side effects | `await expect(handler.execute(…)).rejects.toBeInstanceOf(UserNotFoundException); expect(exists).not.toHaveBeenCalled()` |
47
+ | Case 3 | The returned row | `await expect(handler.execute(command("one"))).resolves.toBe(existing)`; `.resolves.toEqual({ id: "cart-2" })` |
48
+ | Case 4 | Persisted argument | `expect(save).toHaveBeenCalledWith({ id: "draft" })` |
49
+ | Case 5 | `it` phrasing | verb first: `throws` (83), `rejects` (65), `returns` (50), `refuses` (25), `maps`, `creates` — e.g. `it("rejects anonymous callers before querying course state", …)` |
50
+
51
+ ## BE-TEST-5 — What is not asserted
52
+
53
+ | Case | When | Write |
54
+ | --- | --- | --- |
55
+ | Case 1 | A call alone | a spec that only proves a mock was invoked (lint `no-call-only-spec`) |
56
+ | Case 2 | Envelope instead of state, in e2e | an e2e step asserts the persisted row, not `success: true` (lint `e2e-asserts-persisted-state`) |
57
+ | Case 3 | Elapsed time | no `sleep` in a flow; poll for state (lint `no-sleep-in-flow`) |
58
+ | Case 4 | Live model output, in e2e | no provider call in e2e (`no-model-call-in-e2e`); the harness lane owns it (`harness-calls-provider-directly`) |
59
+ | Case 5 | A branch inside one step | one step proves one outcome (lint `no-branch-in-flow-step`) |
60
+
61
+ ## BE-TEST-6 — Names and layout
62
+
63
+ | Case | When | Write |
64
+ | --- | --- | --- |
65
+ | Case 1 | `describe` | the class: `describe("AddToCartHandler", () => { … })`, `describe("AbstractProviderPingService", …)` (367 `…Service`, 121 `…Handler`, 58 `…Resolver`) |
66
+ | Case 2 | Formatting | callbacks on a new line: `describe("AddToCartHandler",\n () => {` and `it("…",\n async () => {` — the repo's `function-call-argument-newline: always` rule |
67
+ | Case 3 | Imports | same multi-line brace style as source; subject first, then exceptions, then mocks |
68
+
69
+ ## BE-TEST-7 — Coverage and lint carve-outs
70
+
71
+ | Case | When | Write |
72
+ | --- | --- | --- |
73
+ | Case 1 | Coverage denominator | `collectCoverageFrom: ["src/**/*.ts", "apps/**/*.ts", "!src/tests/**", "!**/*.spec.ts", "!**/*.int-spec.ts", "!**/*.e2e-spec.ts", "!**/*.harness-spec.ts", "!**/*.d.ts", "!**/main.ts"]` |
74
+ | Case 2 | Allowed only in specs | `as unknown as X`, `process.env` (under `src/tests/**`), `new Error(…)` in `src/tests/**` and `apps/*/test/**` |
75
+
76
+ ## Open question
77
+
78
+ 31 of 149 handlers and 125 of 396 services have no colocated spec today. The twin is the pattern
79
+ (lint rule exists); the gap is recorded, not excused.
@@ -0,0 +1,79 @@
1
+ # Kiểm thử
2
+
3
+ Tệp này trả lời một câu hỏi: cho một đơn vị backend, spec của nó nằm đâu, đối tượng được dựng
4
+ thế nào, khẳng định gì, và để yên điều gì?
5
+
6
+ Nguồn: `jest.config.ts`, `features/api/core/graphql/mutations/courses/add-to-cart/add-to-cart.handler.spec.ts`,
7
+ `modules/ai/ai-entitlement.service.spec.ts`,
8
+ `modules/ai/ping/classes/abstract-provider-ping.service.spec.ts`, `src/tests/**`, `eslint.config.mjs`.
9
+
10
+ ## BE-TEST-1 — Vị trí và làn
11
+
12
+ | Case | Dùng khi | Viết |
13
+ | --- | --- | --- |
14
+ | Case 1 | Đơn vị | `<name>.spec.ts` cạnh `<name>.ts` (875; lint `unit-test-colocated`) |
15
+ | Case 2 | Bản sao của handler | `add-to-cart.handler.spec.ts` cạnh handler (118 trên 149 handler; lint `handler-has-twin-spec`) |
16
+ | Case 3 | Bản sao của service | 271 trên 396 service dưới `src/modules` |
17
+ | Case 4 | Tích hợp | `*.int-spec.ts` (7), Testcontainers, `globalSetup: src/tests/helpers/e2e-setup.ts`, `testTimeout: 120_000` |
18
+ | Case 5 | Đầu cuối và harness | `*.e2e-spec.ts`, `*.harness-spec.ts` dưới `src/tests/<lane>`, bị loại khỏi `unit` theo hậu tố |
19
+ | Case 6 | Projects | `projects: [{ displayName: "unit", testMatch: ["**/*.spec.ts"], testPathIgnorePatterns: ["\\.int-spec\\.ts$", "\\.e2e-spec\\.ts$", "\\.harness-spec\\.ts$"] }, { displayName: "integration", testMatch: ["**/*.int-spec.ts"], … }]` |
20
+ | Case 7 | Bộ chạy | `jest` với `ts-jest` (`diagnostics: false`), `testEnvironment: "node"` |
21
+
22
+ ## BE-TEST-2 — Spec handler: dựng trực tiếp, ép kiểu các mock
23
+
24
+ | Case | Dùng khi | Viết |
25
+ | --- | --- | --- |
26
+ | Case 1 | Đối tượng | `const handler = new AddToCartHandler({ exists } as never)` — entity manager là một object trần gồm `jest.fn()`; `as never` xuất hiện ở 496 trên 875 spec, `as unknown as` ở 323 |
27
+ | Case 2 | Nhà máy thông điệp | `const command = (courseId: string) => new AddToCartCommand({ request: { courseId }, user: { id: "u1" } } as never)` |
28
+ | Case 3 | Câu trả lời mock theo thứ tự | `const exists = jest.fn().mockResolvedValueOnce(false)`; sau đó `exists.mockResolvedValueOnce(true).mockResolvedValueOnce(true)` |
29
+ | Case 4 | Gọi qua cửa công khai | `handler.execute(command("missing"))`, không bao giờ gọi `process` |
30
+
31
+ ## BE-TEST-3 — Spec service: một testing module với mock dùng chung
32
+
33
+ | Case | Dùng khi | Viết |
34
+ | --- | --- | --- |
35
+ | Case 1 | Module | `Test.createTestingModule({ … })` (274 spec) với `getEntityManagerToken(POSTGRESQL_PRIMARY)` |
36
+ | Case 2 | Entity manager | `import { makeEntityManagerMock } from "@tests/mocks/entity-manager.mock"` cùng các kiểu `EntityManagerMock`, `QueryBuilderMock` |
37
+ | Case 3 | Cấu hình | `jest.mock("@modules/platform/env/config", () => ({ envConfig: () => ({ ai: { ping: mockPingConfig } }) }))` |
38
+ | Case 4 | Lớp trừu tượng | một lớp con cục bộ: `class TestPingService extends AbstractProviderPingService { protected readonly provider = ModelProvider.OpenAI; execute = jest.fn()…; protected executePing(key: string) { return this.execute(key) } }` |
39
+ | Case 5 | Hằng fixture | UPPER_SNAKE có chú thích: `/** Free base credit caps the mocked quota config hands back (credits per window). */ const BASE_CREDITS_5H = 30` |
40
+
41
+ ## BE-TEST-4 — Điều được khẳng định
42
+
43
+ | Case | Dùng khi | Viết |
44
+ | --- | --- | --- |
45
+ | Case 1 | Kiểu exception | `await expect(handler.execute(command("missing"))).rejects.toBeInstanceOf(CourseNotFoundException)` (262 spec dùng `rejects.toBeInstanceOf` / `toThrow(`; 49 so khớp chuỗi thông điệp) |
46
+ | Case 2 | Thứ tự tác dụng phụ | `await expect(handler.execute(…)).rejects.toBeInstanceOf(UserNotFoundException); expect(exists).not.toHaveBeenCalled()` |
47
+ | Case 3 | Hàng được trả về | `await expect(handler.execute(command("one"))).resolves.toBe(existing)`; `.resolves.toEqual({ id: "cart-2" })` |
48
+ | Case 4 | Đối số được lưu | `expect(save).toHaveBeenCalledWith({ id: "draft" })` |
49
+ | Case 5 | Cách viết `it` | động từ đứng đầu: `throws` (83), `rejects` (65), `returns` (50), `refuses` (25), `maps`, `creates` — ví dụ `it("rejects anonymous callers before querying course state", …)` |
50
+
51
+ ## BE-TEST-5 — Điều không được khẳng định
52
+
53
+ | Case | Dùng khi | Viết |
54
+ | --- | --- | --- |
55
+ | Case 1 | Chỉ một lời gọi | một spec chỉ chứng minh mock đã được gọi (lint `no-call-only-spec`) |
56
+ | Case 2 | Phong bì thay vì trạng thái, trong e2e | một bước e2e khẳng định hàng đã lưu, không phải `success: true` (lint `e2e-asserts-persisted-state`) |
57
+ | Case 3 | Thời gian trôi | không `sleep` trong luồng; thăm dò trạng thái (lint `no-sleep-in-flow`) |
58
+ | Case 4 | Đầu ra mô hình thật, trong e2e | không gọi nhà cung cấp trong e2e (`no-model-call-in-e2e`); làn harness sở hữu việc đó (`harness-calls-provider-directly`) |
59
+ | Case 5 | Một nhánh trong một bước | một bước chứng minh một kết quả (lint `no-branch-in-flow-step`) |
60
+
61
+ ## BE-TEST-6 — Tên và bố cục
62
+
63
+ | Case | Dùng khi | Viết |
64
+ | --- | --- | --- |
65
+ | Case 1 | `describe` | tên lớp: `describe("AddToCartHandler", () => { … })`, `describe("AbstractProviderPingService", …)` (367 `…Service`, 121 `…Handler`, 58 `…Resolver`) |
66
+ | Case 2 | Định dạng | callback xuống dòng mới: `describe("AddToCartHandler",\n () => {` và `it("…",\n async () => {` — luật `function-call-argument-newline: always` của repo |
67
+ | Case 3 | Import | cùng kiểu ngoặc nhiều dòng như mã nguồn; đối tượng trước, rồi exception, rồi mock |
68
+
69
+ ## BE-TEST-7 — Độ phủ và miễn trừ lint
70
+
71
+ | Case | Dùng khi | Viết |
72
+ | --- | --- | --- |
73
+ | Case 1 | Mẫu số độ phủ | `collectCoverageFrom: ["src/**/*.ts", "apps/**/*.ts", "!src/tests/**", "!**/*.spec.ts", "!**/*.int-spec.ts", "!**/*.e2e-spec.ts", "!**/*.harness-spec.ts", "!**/*.d.ts", "!**/main.ts"]` |
74
+ | Case 2 | Chỉ được phép trong spec | `as unknown as X`, `process.env` (dưới `src/tests/**`), `new Error(…)` trong `src/tests/**` và `apps/*/test/**` |
75
+
76
+ ## Câu hỏi để ngỏ
77
+
78
+ 31 trên 149 handler và 125 trên 396 service hôm nay chưa có spec kề bên. Bản sao là mẫu (luật lint
79
+ đã có); khoảng trống được ghi nhận, không được bào chữa.
@@ -0,0 +1,73 @@
1
+ # Typing
2
+
3
+ This file answers one question: given a backend value, how is its type declared?
4
+
5
+ Sources: `tsconfig.json` (`strictNullChecks: true`, `noImplicitAny: false`), `eslint.config.mjs`,
6
+ `features/api/core/types/execute.ts`, `features/api/core/graphql/mutations/courses/add-to-cart/*`,
7
+ `modules/platform/exceptions/errors/abstract.ts`, `errors/ai/ai-quota-exhausted.ts`,
8
+ `modules/api/apollo/server/types/graphql-response.ts`,
9
+ `modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`,
10
+ `modules/databases/postgresql/primary/enums/locale.ts`, `modules/platform/cqrs/icqrs-handler.ts`.
11
+
12
+ ## BE-TYPING-1 — `interface` for object shapes, `type` for aliases
13
+
14
+ | Case | When | Write |
15
+ | --- | --- | --- |
16
+ | Case 1 | Object shape | `export interface ExecuteParams<T> { request: T; locale?: Locale; user?: UserEntity; … }` (1991 `export interface` against 197 `export type`, of which only 12 are object literals) |
17
+ | Case 2 | Exception metadata | `export interface AiQuotaExhaustedExceptionMetadata extends AbstractExceptionMetadata { window: string }` |
18
+ | Case 3 | Response contract | `export interface IAbstractGraphQLResponse<T = undefined> { success: boolean; message: string; data?: T; error?: string }` |
19
+ | Case 4 | Alias | `export type GraphQLSuccessMessage = Record<Locale, string>` — shares its name with the decorator const |
20
+
21
+ ## BE-TYPING-2 — Named parameter types, no inline objects
22
+
23
+ | Case | When | Write |
24
+ | --- | --- | --- |
25
+ | Case 1 | Function taking an object | `constructor({ window, originalError }: AiQuotaExhaustedExceptionMetadata)` — destructured from a named interface (lint `no-inline-param-type`, `no-inline-object-type`) |
26
+ | Case 2 | Handler input | `process(command: AddToCartCommand)` — the class is the type |
27
+ | Case 3 | Return | always declared on public methods: `Promise<CartItemEntity>`, `Record<KpiKey, number>`, `Observable<GraphQLResponse<T>>` |
28
+ | Case 4 | Primitive param | positional, annotated: `execute(key: string)`, `getOriginalError(): Error` |
29
+
30
+ ## BE-TYPING-3 — `readonly`
31
+
32
+ | Case | When | Write |
33
+ | --- | --- | --- |
34
+ | Case 1 | Injected dependency | `private readonly entityManager: EntityManager` (2489 against 26 without `readonly`) |
35
+ | Case 2 | Message payload | `constructor(readonly params: ExecuteParams<AddToCartRequest>) {}` |
36
+ | Case 3 | Exception fields | `readonly code: string`, `readonly metadata?: Record<string, unknown>`, `readonly httpStatus?: number` |
37
+ | Case 4 | Decorated GraphQL field | not `readonly`: `@Field(() => ID, { description: "Course id." }) courseId: string` — class-validator/GraphQL classes are mutable DTOs |
38
+
39
+ ## BE-TYPING-4 — Enums
40
+
41
+ | Case | When | Write |
42
+ | --- | --- | --- |
43
+ | Case 1 | Declaration | `export enum Locale { Vi = "vi", En = "en" }` — string-valued, PascalCase members (137 enums; `const enum` 0, lint `no-const-enum`) |
44
+ | Case 2 | Home | `modules/databases/postgresql/primary/enums/` (76 files), plus per-module `enums/` folders (`platform/exceptions/enums`, `integrations/cache/enums`, `ai/balancer/enums`) |
45
+ | Case 3 | GraphQL exposure | `export const GraphQLTypeLocale = createEnumType(Locale)` + `registerEnumType(GraphQLTypeLocale, { name: "Locale", description: "…", valuesMap: { [Locale.Vi]: { … } } })` |
46
+ | Case 4 | Boolean on an entity | `isEnrolled: true` in `where` — `is` prefix |
47
+
48
+ ## BE-TYPING-5 — `unknown`, never `any`
49
+
50
+ | Case | When | Write |
51
+ | --- | --- | --- |
52
+ | Case 1 | Unknown payload | `readonly metadata?: Record<string, unknown>` |
53
+ | Case 2 | Narrowing an error | `originalError: error instanceof Error ? error : undefined`; `error instanceof Error ? error : new Error(String(error))` |
54
+ | Case 3 | Apollo's untyped error | `const original = (error as { originalError?: unknown })?.originalError ?? error; if (original instanceof AbstractException) { … }` |
55
+ | Case 4 | Residual | 13 `any` sites in 4463 files; lint `@typescript-eslint/no-explicit-any` is at error |
56
+ | Case 5 | Double cast | `as unknown as X` only inside `*.spec.ts` and `src/tests/**` (323 specs); banned elsewhere by `no-restricted-syntax` |
57
+
58
+ ## BE-TYPING-6 — Generics
59
+
60
+ | Case | When | Write |
61
+ | --- | --- | --- |
62
+ | Case 1 | Template base | `export abstract class ICQRSHandler<TParams, TResponse = unknown>` — `T` prefix on descriptive names |
63
+ | Case 2 | Single | `IAbstractGraphQLResponse<T = undefined>`, `GraphQLTransformInterceptor<T = unknown>` |
64
+ | Case 3 | Static factory | `static fromJSON<T extends AbstractException>(this: new (message: string, code: string, metadata?: Record<string, unknown>) => T, json: string): T` |
65
+
66
+ ## BE-TYPING-7 — GraphQL classes
67
+
68
+ | Case | When | Write |
69
+ | --- | --- | --- |
70
+ | Case 1 | Input | `@InputType({ description: "…" }) export class AddToCartRequest { @Field(() => ID, { description: "Course id." }) courseId: string }` |
71
+ | Case 2 | Output | `@ObjectType({ description: "…" }) export class AddToCartResponse extends AbstractGraphQLResponse implements IAbstractGraphQLResponse<CartItemEntity> { @Field(() => CartItemEntity, { nullable: true, description: "…" }) data: CartItemEntity }` |
72
+ | Case 3 | `data` nullability | always `nullable: true` — the interceptor writes `data = null` on failure |
73
+ | Case 4 | Entity table | every `@Entity` names its table (lint `require-entity-table-name`, 181/181); no `eager: true` relations |
@@ -0,0 +1,73 @@
1
+ # Kiểu
2
+
3
+ Tệp này trả lời một câu hỏi: cho một giá trị backend, kiểu của nó được khai báo thế nào?
4
+
5
+ Nguồn: `tsconfig.json` (`strictNullChecks: true`, `noImplicitAny: false`), `eslint.config.mjs`,
6
+ `features/api/core/types/execute.ts`, `features/api/core/graphql/mutations/courses/add-to-cart/*`,
7
+ `modules/platform/exceptions/errors/abstract.ts`, `errors/ai/ai-quota-exhausted.ts`,
8
+ `modules/api/apollo/server/types/graphql-response.ts`,
9
+ `modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`,
10
+ `modules/databases/postgresql/primary/enums/locale.ts`, `modules/platform/cqrs/icqrs-handler.ts`.
11
+
12
+ ## BE-TYPING-1 — `interface` cho hình dạng object, `type` cho bí danh
13
+
14
+ | Case | Dùng khi | Viết |
15
+ | --- | --- | --- |
16
+ | Case 1 | Hình dạng object | `export interface ExecuteParams<T> { request: T; locale?: Locale; user?: UserEntity; … }` (1991 `export interface` so với 197 `export type`, trong đó chỉ 12 là object literal) |
17
+ | Case 2 | Metadata exception | `export interface AiQuotaExhaustedExceptionMetadata extends AbstractExceptionMetadata { window: string }` |
18
+ | Case 3 | Hợp đồng response | `export interface IAbstractGraphQLResponse<T = undefined> { success: boolean; message: string; data?: T; error?: string }` |
19
+ | Case 4 | Bí danh | `export type GraphQLSuccessMessage = Record<Locale, string>` — trùng tên với const decorator |
20
+
21
+ ## BE-TYPING-2 — Kiểu tham số có tên, không object nội tuyến
22
+
23
+ | Case | Dùng khi | Viết |
24
+ | --- | --- | --- |
25
+ | Case 1 | Hàm nhận object | `constructor({ window, originalError }: AiQuotaExhaustedExceptionMetadata)` — phá cấu trúc từ một interface có tên (lint `no-inline-param-type`, `no-inline-object-type`) |
26
+ | Case 2 | Đầu vào handler | `process(command: AddToCartCommand)` — lớp chính là kiểu |
27
+ | Case 3 | Trả về | luôn khai báo trên phương thức công khai: `Promise<CartItemEntity>`, `Record<KpiKey, number>`, `Observable<GraphQLResponse<T>>` |
28
+ | Case 4 | Tham số nguyên thủy | theo vị trí, có chú kiểu: `execute(key: string)`, `getOriginalError(): Error` |
29
+
30
+ ## BE-TYPING-3 — `readonly`
31
+
32
+ | Case | Dùng khi | Viết |
33
+ | --- | --- | --- |
34
+ | Case 1 | Phụ thuộc được tiêm | `private readonly entityManager: EntityManager` (2489 so với 26 không `readonly`) |
35
+ | Case 2 | Payload thông điệp | `constructor(readonly params: ExecuteParams<AddToCartRequest>) {}` |
36
+ | Case 3 | Trường exception | `readonly code: string`, `readonly metadata?: Record<string, unknown>`, `readonly httpStatus?: number` |
37
+ | Case 4 | Trường GraphQL có decorator | không `readonly`: `@Field(() => ID, { description: "Course id." }) courseId: string` — lớp class-validator/GraphQL là DTO khả biến |
38
+
39
+ ## BE-TYPING-4 — Enum
40
+
41
+ | Case | Dùng khi | Viết |
42
+ | --- | --- | --- |
43
+ | Case 1 | Khai báo | `export enum Locale { Vi = "vi", En = "en" }` — giá trị chuỗi, thành viên PascalCase (137 enum; `const enum` 0, lint `no-const-enum`) |
44
+ | Case 2 | Nhà | `modules/databases/postgresql/primary/enums/` (76 tệp), cộng thư mục `enums/` theo module (`platform/exceptions/enums`, `integrations/cache/enums`, `ai/balancer/enums`) |
45
+ | Case 3 | Phơi ra GraphQL | `export const GraphQLTypeLocale = createEnumType(Locale)` + `registerEnumType(GraphQLTypeLocale, { name: "Locale", description: "…", valuesMap: { [Locale.Vi]: { … } } })` |
46
+ | Case 4 | Boolean trên entity | `isEnrolled: true` trong `where` — tiền tố `is` |
47
+
48
+ ## BE-TYPING-5 — `unknown`, không bao giờ `any`
49
+
50
+ | Case | Dùng khi | Viết |
51
+ | --- | --- | --- |
52
+ | Case 1 | Payload chưa biết | `readonly metadata?: Record<string, unknown>` |
53
+ | Case 2 | Thu hẹp một lỗi | `originalError: error instanceof Error ? error : undefined`; `error instanceof Error ? error : new Error(String(error))` |
54
+ | Case 3 | Lỗi không kiểu của Apollo | `const original = (error as { originalError?: unknown })?.originalError ?? error; if (original instanceof AbstractException) { … }` |
55
+ | Case 4 | Còn sót | 13 chỗ `any` trong 4463 tệp; lint `@typescript-eslint/no-explicit-any` ở mức error |
56
+ | Case 5 | Ép kép | `as unknown as X` chỉ bên trong `*.spec.ts` và `src/tests/**` (323 spec); bị cấm nơi khác bởi `no-restricted-syntax` |
57
+
58
+ ## BE-TYPING-6 — Generic
59
+
60
+ | Case | Dùng khi | Viết |
61
+ | --- | --- | --- |
62
+ | Case 1 | Lớp gốc khuôn | `export abstract class ICQRSHandler<TParams, TResponse = unknown>` — tiền tố `T` trên tên mô tả |
63
+ | Case 2 | Đơn | `IAbstractGraphQLResponse<T = undefined>`, `GraphQLTransformInterceptor<T = unknown>` |
64
+ | Case 3 | Nhà máy tĩnh | `static fromJSON<T extends AbstractException>(this: new (message: string, code: string, metadata?: Record<string, unknown>) => T, json: string): T` |
65
+
66
+ ## BE-TYPING-7 — Lớp GraphQL
67
+
68
+ | Case | Dùng khi | Viết |
69
+ | --- | --- | --- |
70
+ | Case 1 | Đầu vào | `@InputType({ description: "…" }) export class AddToCartRequest { @Field(() => ID, { description: "Course id." }) courseId: string }` |
71
+ | Case 2 | Đầu ra | `@ObjectType({ description: "…" }) export class AddToCartResponse extends AbstractGraphQLResponse implements IAbstractGraphQLResponse<CartItemEntity> { @Field(() => CartItemEntity, { nullable: true, description: "…" }) data: CartItemEntity }` |
72
+ | Case 3 | Tính nullable của `data` | luôn `nullable: true` — interceptor ghi `data = null` khi thất bại |
73
+ | Case 4 | Bảng của entity | mọi `@Entity` đều đặt tên bảng (lint `require-entity-table-name`, 181/181); không quan hệ `eager: true` |
@@ -0,0 +1,29 @@
1
+ # Frontend code patterns
2
+
3
+ `knowledge/ui/` decides what the interface must be: which Grammar object renders, which gap, which
4
+ tone. `knowledge/patterns/fe/` decides how the code that produces that interface is written: where
5
+ a unit lives, what its files are called, how a component function is shaped, where a class string
6
+ sits, how a failure is represented, and where the spec goes. A pattern rule never chooses a visual;
7
+ a `ui/` rule never chooses a file name. Every rule below was extracted from
8
+ `starci-academy-fe/src/` and `starci-academy-fe/packages/grammar/src/` by opening files and counting,
9
+ and each table cites the files it was read from. Where the code is split, the file records the
10
+ dominant variant with its count instead of legislating.
11
+
12
+ ## Catalog
13
+
14
+ | Knowledge | What it decides | Rules |
15
+ | --- | --- | --- |
16
+ | [Folder](folder.md) | Tier directories, the file set of one unit, what a unit folder may not hold | FE-FOLDER-1 … FE-FOLDER-6 |
17
+ | [Naming](naming.md) | Folder, export, props type, class-name export, hook, constant and spec names | FE-NAMING-1 … FE-NAMING-7 |
18
+ | [Function](function.md) | Component shape, the `props` parameter, the three-part contract, helpers, route files | FE-FUNCTION-1 … FE-FUNCTION-7 |
19
+ | [Imports](imports.md) | The `@/` alias, Grammar entry, import order, tier direction, the hooks barrel | FE-IMPORTS-1 … FE-IMPORTS-7 |
20
+ | [Comment](comment.md) | Export docblocks, field docs, decision prose, `//` sentences, banned content | FE-COMMENT-1 … FE-COMMENT-5 |
21
+ | [Typing](typing.md) | `type` over `interface`, `readonly`, literal unions, `Array<T>`, inferred returns | FE-TYPING-1 … FE-TYPING-7 |
22
+ | [Error](error.md) | Failure as a state, the GraphQL envelope, `throw new Error`, toasts | FE-ERROR-1 … FE-ERROR-5 |
23
+ | [Test](test.md) | Spec placement, connected versus pure spec, what is asserted, what is not | FE-TEST-1 … FE-TEST-6 |
24
+
25
+ ## Sources
26
+
27
+ Application: `D:\Repositories\starci-academy-fe\src\` (976 non-spec TypeScript files, 497 specs).
28
+ Grammar package: `D:\Repositories\starci-academy-fe\packages\grammar\src\`. Lint canon consulted for
29
+ rule names only: `@starci/eslint-canon-fe` as installed under `node_modules`.
@@ -0,0 +1,29 @@
1
+ # Mẫu mã nguồn frontend
2
+
3
+ `knowledge/ui/` quyết định giao diện phải là gì: đối tượng Grammar nào được vẽ, khoảng cách nào,
4
+ sắc thái nào. `knowledge/patterns/fe/` quyết định mã nguồn tạo ra giao diện ấy được viết ra sao:
5
+ một đơn vị mã nằm ở đâu, các tệp của nó tên gì, hàm component có hình dạng nào, chuỗi class đặt ở
6
+ đâu, thất bại được biểu diễn thế nào, và spec đi kèm nằm chỗ nào. Một luật mẫu không bao giờ chọn
7
+ hình ảnh; một luật `ui/` không bao giờ chọn tên tệp. Mọi luật dưới đây được rút ra từ
8
+ `starci-academy-fe/src/` và `starci-academy-fe/packages/grammar/src/` bằng cách mở tệp và đếm, và
9
+ mỗi bảng đều dẫn nguồn tệp đã đọc. Nơi nào mã nguồn chia hai ngả, tệp ghi lại biến thể chiếm ưu thế
10
+ cùng con số thay vì áp đặt.
11
+
12
+ ## Danh mục
13
+
14
+ | Tri thức | Quyết định điều gì | Luật |
15
+ | --- | --- | --- |
16
+ | [Thư mục](folder.vi.md) | Thư mục theo tầng, bộ tệp của một đơn vị, thứ một thư mục đơn vị không được chứa | FE-FOLDER-1 … FE-FOLDER-6 |
17
+ | [Đặt tên](naming.vi.md) | Tên thư mục, export, kiểu props, export class-name, hook, hằng và spec | FE-NAMING-1 … FE-NAMING-7 |
18
+ | [Hàm](function.vi.md) | Hình dạng component, tham số `props`, hợp đồng ba phần, helper, tệp route | FE-FUNCTION-1 … FE-FUNCTION-7 |
19
+ | [Import](imports.vi.md) | Alias `@/`, cửa vào Grammar, thứ tự import, chiều giữa các tầng, barrel hooks | FE-IMPORTS-1 … FE-IMPORTS-7 |
20
+ | [Chú thích](comment.vi.md) | Docblock cho export, chú thích trường, văn xuôi lý giải quyết định, câu `//`, nội dung bị cấm | FE-COMMENT-1 … FE-COMMENT-5 |
21
+ | [Kiểu](typing.vi.md) | `type` thay cho `interface`, `readonly`, union literal, `Array<T>`, kiểu trả về suy luận | FE-TYPING-1 … FE-TYPING-7 |
22
+ | [Lỗi](error.vi.md) | Thất bại là một trạng thái, phong bì GraphQL, `throw new Error`, toast | FE-ERROR-1 … FE-ERROR-5 |
23
+ | [Kiểm thử](test.vi.md) | Vị trí spec, spec nửa nối và nửa thuần, điều được khẳng định và điều không | FE-TEST-1 … FE-TEST-6 |
24
+
25
+ ## Nguồn
26
+
27
+ Ứng dụng: `D:\Repositories\starci-academy-fe\src\` (976 tệp TypeScript không phải spec, 497 spec).
28
+ Gói Grammar: `D:\Repositories\starci-academy-fe\packages\grammar\src\`. Bộ luật lint chỉ được tra để
29
+ lấy tên luật: `@starci/eslint-canon-fe` như đã cài trong `node_modules`.