用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/VilnaCRM-Org/crm --skill architecture命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Use when improving frontend performance, Lighthouse, web-vitals, or a11y.
Keep the Bats make-target contract in sync when adding or renaming a Makefile target so the bats CI check stays green
Use when adding frontend telemetry, logs, Sentry, or web-vitals signals.
正在显示 SKILL.md
基于 SOC 职业分类
| name | architecture |
| description | Use when placing features, repositories, hooks, or fixing dependency-cruiser errors. |
VilnaCRM is a multi-frontend, microservice-backed CRM. This skill captures the
frontend architecture: what modules exist, how a feature is layered, and
which boundaries are enforced as dependency-cruiser errors.
Every feature follows the same Component → Hook → Repository → API flow, illustrated by the onboarding feature:
flowchart TD
api["API<br/>[Software System]"]
graphQLRepository["GraphQLRepository<br/>[Repository]"]
stepProvider["StepProvider<br/>[Data provider]"]
graphQLStepperInformer["GraphQLStepperInformer<br/>[Transformer]"]
onboarding["Onboarding<br/>[Component]"]
reactHook["React hook"]
mutation["Mutation<br/>[GraphQL]"]
stepper["Stepper<br/>[Component]"]
content["Content<br/>[Component]"]
state["State<br/>[Redux]"]
tariff["Tariff<br/>[Component]"]
payment["Payment<br/>[Component]"]
onboarding --> reactHook
reactHook --> mutation
mutation --> api
onboarding --> stepProvider
reactHook --> stepProvider
stepProvider --> graphQLRepository
graphQLRepository --> api
stepProvider <--> graphQLStepperInformer
onboarding --> stepper
onboarding --> content
stepper --> state
content --> state
content --> tariff
content --> payment
tariff --> state
payment --> state
Read the layers top to bottom:
features/<feature>/components/) — presentation only.
They consume hooks and render children; they never import repositories,
the module store, or src/services/ directly.features/<feature>/hooks/use-*.ts) — UI logic, Redux state
access, and the only place that calls repository APIs and dispatches
mutations.features/<feature>/repositories/) — the only layer
allowed to talk to src/services/https-client/. Exposed through their
index file; internal files are private.error)These come from .dependency-cruiser.js. They run on every PR via
make lint-deps. Internalize them before placing a file.
Module and feature isolation:
no-cross-module-imports — one module importing another module's
internals.no-cross-feature-imports — sibling features in the same module
importing each other. Use the module's shared hooks/, lib/,
store/, types/, or utils/.no-components-import-modules — src/components/* depending on any
src/modules/*.Public API contract (issue #107):
no-module-internal-imports — code outside a module reaching a
src/modules/<m>/* file other than the module index barrel. Enter
through @/modules/<m>. Exceptions: the DI composition root and the
app-shell router's code-split route/guard entries.no-feature-internal-imports — module-level store / types / lib /
hooks / utils / config reaching a feature's internals instead of its
index barrel (e.g. @auth). Sibling features go through
no-cross-feature-imports.Repository boundary:
no-repository-internal-imports — reaching past a repository's index
file.no-repositories-to-ui-hooks — repositories depending on feature
components/, hooks/, routes/, or module hooks/ / store/.no-feature-direct-http-client — anything outside repositories/
importing src/services/https-client/.no-store-direct-http-client — module store/ importing
src/services/https-client/.UI layering:
no-feature-ui-to-services — feature components/, hooks/,
routes/ importing src/services/.no-components-to-repositories — components importing repositories
directly (go through a hook).no-components-to-store — components importing the module store
directly. Exception: hook files (use-*.ts).no-store-to-feature-ui — module store importing feature
components/, hooks/, routes/.no-lib-to-features — module lib/ depending on its own features/.DI containment (per-module composition roots, issue #109):
src/config/dependency-injection-config.ts (zero registrations) plus each
per-module / per-infra registrar: src/services/<x>/di.ts,
src/utils/<x>/di.ts, src/modules/<m>/config/di.ts. Each registers only its
own bindings against a co-located tokens.ts.no-di-config-import-outside-composition-root — the aggregator and every
registrar di.ts may be imported only by the aggregator itself and the
store/hook bridges (src/index.tsx, src/app.tsx, src/stores/,
src/modules/*/store/*-slice.ts,
src/modules/*/features/*/stores/index.ts); importing one elsewhere pulls the
DI graph onto the auth paint path.no-composition-root-cross-module-imports — a module's config/ (root +
tokens) may wire only its own module plus shared infra, never a sibling module.config/di.ts) is exempt from
no-feature-internal-imports / no-repository-internal-imports so it can
register the feature's concrete internals.Folder shape:
module-allowed-folders — module root may only contain config,
features, hooks, lib, store, types, utils.feature-allowed-folders — feature root may only contain assets,
components, hooks, i18n, repositories, routes, types,
utils.feature-hooks-file-convention — files inside features/*/hooks/
must be index.* or use-<kebab>.*.Naming:
no-uppercase-paths, src-module-name-kebab-case,
src-feature-name-kebab-case — no uppercase letters in any source or
test path.When make lint-deps reports a violation, the fix is almost always to
introduce or use the missing layer (a hook, a repository public re-export),
never to silence the rule.
VilnaCRM ships three frontend products plus a shared library, all behind a backend-for-frontend (BFF):
flowchart TD
customer(["Customer<br/>[Person]"])
website["Website<br/>[Frontend]<br/>Public site with SSR"]
crm["CRM<br/>[Frontend]<br/>Protected workspace for customers"]
uiLibrary["UI library<br/>[Frontend]<br/>Shared UI primitives and 404 pages"]
bff["Backend for frontend<br/>[Backend]<br/>Load balancing, logging, tracing, rate limiting"]
customer -.-> website
customer -.-> crm
website -.-> uiLibrary
crm -.-> uiLibrary
website -.-> bff
crm -.-> bff
This repository is the CRM frontend. Shared UI lives in src/components/
under the ui- prefix (ui-button/index.tsx exporting UIButton).
Every box below is — or will be — a directory under src/modules/<kebab>/.
Names are lowercase kebab-case (enforced by src-module-name-kebab-case).
flowchart TD
uiLibrary["ui-library<br/>(src/components/ui-*)"]
uiLibrary -.-> user["user<br/>auth, registration, 2FA, profile"]
uiLibrary -.-> core["core<br/>sidebar, navbar, footer, user menu"]
uiLibrary -.-> notification["notification"]
uiLibrary -.-> orders["orders"]
uiLibrary -.-> integrations["integrations"]
uiLibrary -.-> ipTelephony["ip-telephony"]
uiLibrary -.-> userTask["user-task"]
uiLibrary -.-> search["search"]
uiLibrary -.-> crmImport["crm-import"]
uiLibrary -.-> project["project"]
uiLibrary -.-> payment["payment"]
uiLibrary -.-> analytics["analytics"]
uiLibrary -.-> webhook["webhook"]
uiLibrary -.-> configs["configs"]
uiLibrary -.-> employees["employees"]
uiLibrary -.-> dashboards["dashboards"]
uiLibrary -.-> tariff["tariff"]
uiLibrary -.-> userOnboarding["user-onboarding"]
uiLibrary -.-> employment["employment"]
uiLibrary -.-> product["product"]
uiLibrary -.-> featureToggle["feature-toggle"]
dashboards --> salesmanBoard["salesman-board"]
dashboards --> managementBoard["management-board"]
project --> crud["crud"]
product --> crud
crud --> order["order"]
Module composition (dashboards → salesman-board, product → crud,
crud → order) is the only place sub-module imports are allowed. Everything
else must go through src/modules/<m>/lib/, store/, types/, or utils/,
or through the public exports of another module — never a deep import.
The frontend never talks to a microservice directly. It calls the BFF / API gateway, and the repository layer hides which service answered.
Each backend has its own database (MariaDB / MongoDB / MemoryDB / Vertica), cache, and file storage. Those are implementation details the frontend must not encode — call the BFF and let the service decide.
/crm is composed top-down from core module primitives plus feature
modules mounted into content:
flowchart TD
crmPage["crm page<br/>[Component]"]
crmPage --> navbar["navbar"]
crmPage --> sidebar["sidebar"]
crmPage --> header["header"]
crmPage --> content["content"]
navbar --> notification["notification"]
notification --> userMenu["user-menu"]
userMenu --> userProfileModal["user-profile-modal"]
sidebar --> projectMod["project [module]"]
content --> modulesGroup["crm functional modules"]
modulesGroup --> tasks["tasks"]
modulesGroup --> validationDashboard["validation-service-dashboard"]
modulesGroup --> userTasks["user-tasks"]
modulesGroup --> integration["integration"]
modulesGroup --> customerProfile["customer-profile"]
modulesGroup --> ipTelephony["ip-telephony"]
modulesGroup --> billing["billing"]
modulesGroup --> smsMarketing["sms-marketing"]
modulesGroup --> payment["payment"]
modulesGroup --> product["product"]
modulesGroup --> userCommunication["user-communication"]
modulesGroup --> settings["settings"]
modulesGroup --> integrationApi["integration-with-api"]
The core module owns navbar, sidebar, header, footer, and user menu
chrome. Feature modules mount under content via the router.
flowchart TD
onboarding["onboarding"] --> stepper["stepper"]
onboarding --> content["content"]
content --> billing["billing / plan setup"]
billing --> tariff["tariff"]
billing --> payment["payment"]
content --> crmImport["crm-import"]
content --> dashboard["dashboard"]
content --> integrations["integrations"]
content --> ipTelephony["ip-telephony"]
content --> employees["employees"]
user-onboarding orchestrates the flow; each step is a feature module
already documented in the catalog. The layered-architecture diagram at the
top of this skill shows the internal data flow.
flowchart TD
twoFA["2fa [component]"] --> twoFADto["2fa-dto [type]"]
signIn["sign-in [component]"] --> signInDto["sign-in-dto [type]"]
signUp["sign-up [component]"] --> signUpDto["sign-up-dto [type]"]
userProfile["user-profile [component]"] --> userEntity["user-entity [type]"]
userProfile --> basicInfo["basic-info [component]"]
userProfile --> twoFASection["2fa-section [component]"]
userProfile --> userPasswordChange["user-password-change [component]"]
basicInfo --> userBasicInfo["user-basic-info-entity [type]"]
twoFASection --> twoFAChange["2fa-otp-change-entity [type]"]
userPasswordChange --> restorePassword["restore-password [component]"]
restorePassword --> restoreDto["restore-password-dto [type]"]
Each component pairs with a typed DTO in types/. The DTO is the shape the
repository returns; the component never sees the raw API response.
digraph placement {
"New work" [shape=doublecircle];
"Used by 2+ modules without module-specific copy?" [shape=diamond];
"Belongs to one feature?" [shape=diamond];
"Shared by 2+ features in the same module?" [shape=diamond];
"Calls an external API?" [shape=diamond];
"Holds Redux state?" [shape=diamond];
"src/components/ui-*" [shape=box];
"src/modules/<m>/features/<f>/components or hooks" [shape=box];
"src/modules/<m>/lib or hooks" [shape=box];
"features/<f>/repositories (new) or module-level lib" [shape=box];
"src/modules/<m>/store" [shape=box];
"New work" -> "Used by 2+ modules without module-specific copy?";
"Used by 2+ modules without module-specific copy?" -> "src/components/ui-*" [label="yes"];
"Used by 2+ modules without module-specific copy?" -> "Belongs to one feature?" [label="no"];
"Belongs to one feature?" -> "Calls an external API?" [label="yes"];
"Calls an external API?" -> "features/<f>/repositories (new) or module-level lib" [label="yes"];
"Calls an external API?" -> "Holds Redux state?" [label="no"];
"Holds Redux state?" -> "src/modules/<m>/store" [label="yes"];
"Holds Redux state?" -> "src/modules/<m>/features/<f>/components or hooks" [label="no"];
"Belongs to one feature?" -> "Shared by 2+ features in the same module?" [label="no"];
"Shared by 2+ features in the same module?" -> "src/modules/<m>/lib or hooks" [label="yes"];
"Shared by 2+ features in the same module?" -> "src/components/ui-*" [label="no"];
}
When the placement is non-obvious, stop and re-read the boundary table — the violation is usually telling you the layer you skipped.
make lint-deps
Runs dependency-cruiser. Treat any error as the architecture telling you
something is in the wrong layer. Fix the import path or move the file;
never weaken .dependency-cruiser.js.
For broader checks before commit:
make format
make lint
no-feature-direct-http-client fires — a component or hook is calling
https-client itself. Move the call into a repository and consume it
through a hook.no-components-to-store fires on a .tsx — a component is importing
the slice directly. Expose state through a use-*.ts selector hook and
import that.no-cross-feature-imports fires — one feature reaches into a sibling
feature's components/ or hooks/. Promote the shared code to
src/modules/<m>/lib/ or hooks/.feature-hooks-file-convention fires — a non-hook file (helper, types)
was placed inside hooks/. Move it to utils/, types/, or lib/.module-allowed-folders fires on helpers/ — old layout still in use.
Rename to lib/ (or utils/ at feature level).hooks/ or components/ — mapping logic
crept into the wrong layer. Move the mapping into the repository or a
dedicated transformer.src/stores/ or expose a hook from the owning
module.