| name | clojure-fullstack-development |
| description | Use this skill when developing, modifying, reviewing, debugging, or scaffolding full-stack Clojure systems. The default stack is Kit framework on the backend, Ant Design plus Reagent on the web frontend, shared Clojure/ClojureScript contracts, and ClojureDart plus Flutter for mobile. Use it for AGENTS.md driven project work, Integrant configuration, APIs, database migrations, Reagent UI, antd pages, ClojureDart mobile screens, cross-stack feature delivery, testing, security, and business-domain workflows. |
Clojure Full-Stack Development Skill
Purpose
Act as a careful Clojure full-stack pair programmer for real-world business systems.
The default architecture is:
- Backend: Kit framework, Integrant, Aero, Reitit or the project's existing router, Ring, SQL, Migratus, HugSQL or the project's existing query layer.
- Web frontend: ClojureScript, Reagent 2.x, React 19, Ant Design 6, and the existing antd wrapper style used by the project.
- Shared contracts:
.cljc namespaces for schemas, enums, permissions, validation helpers, and DTO transformations where useful.
- Mobile: ClojureDart and Flutter, with
.cljd business UI in src/ and Dart or Flutter bridge code in lib/ only where needed.
- Development style: inspect first, respect
AGENTS.md, keep modules small, use REPL-driven workflow, generate complete code when asked.
This skill is optimized for clinic, scheduling, medical record, operations, admin-dashboard, and other business systems.
ClojureDart Flutter Workflow
交互式开发 (前台运行)
For day-to-day mobile UI development, prefer the ClojureDart Flutter runner:
clj -M:cljd flutter -d chrome
clj -M:cljd flutter -d macos
-d <device> selects the Flutter device and starts the long-running watch/hot-reload workflow. Keep it running while editing .cljd files. Use explicit devices such as chrome or macos when Flutter reports multiple connected devices.
后台守护开发模式 (推荐用于 AI 代理)
When an AI agent is editing .cljd files, the interactive clj -M:cljd flutter blocks stdin. Prefer the daemon workflow for faster response:
scripts/cljd-daemon start shipper
scripts/cljd-daemon logs shipper
clj -M:cljd compile
scripts/cljd-daemon reload shipper
scripts/cljd-daemon status
scripts/cljd-daemon stop shipper
cljd-daemon 命令:
| 命令 | 说明 |
|---|
start <target> | 编译并后台启动 flutter run |
stop <target> | 停止后台进程 |
reload <target> | 发送 SIGUSR1 触发热重载 |
restart <target> | 停止 + 启动 |
status | 查看所有进程和 VM Service URL |
logs <target> | tail -f 持续查看日志 |
compile <target> | 仅编译 |
实现原理:
clj -M:cljd compile 编译 .cljd → .dart
nohup flutter run -d macos --no-pub > /tmp/<target>-flutter.log 2>&1 & 后台运行
kill -USR1 <pid> 触发 Flutter Hot Reload (flutter_tools 原生支持)
- 通过
lsof 和日志端口检测进程状态
Use one-off commands for verification or release builds:
clj -M:cljd compile
flutter build web --no-pub
flutter build macos --debug --no-pub
When both a ClojureDart compile and a Flutter build are needed, run them sequentially so Flutter does not build stale generated Dart output.
Prime Directive
Before proposing architecture or code, inspect the actual project files when available. Do not invent namespaces, routes, aliases, database libraries, antd wrapper APIs, or ClojureDart setup.
When project files are available, always check relevant AGENTS.md files first. Treat them as local law, not decoration.
AGENTS.md Driven Workflow
Business-oriented projects may contain multiple AGENTS.md files. Use this precedence model:
- Explicit user request in the current conversation.
- Safety, privacy, and data protection constraints.
- The nearest
AGENTS.md to the files being changed.
- Parent directory
AGENTS.md files, walking up to the repository root.
- This skill's default rules.
Discovery command:
find . -name AGENTS.md -print
Read order:
- Root
AGENTS.md for product, style, commands, and global constraints.
backend/AGENTS.md, src/**/AGENTS.md, or resources/**/AGENTS.md for Kit, SQL, routes, and migrations.
frontend/AGENTS.md, cljs/**/AGENTS.md, or web/**/AGENTS.md for Reagent and antd.
mobile/AGENTS.md, cljd/**/AGENTS.md, lib/**/AGENTS.md, or src/**/AGENTS.md for ClojureDart and Flutter.
shared/AGENTS.md or common/**/AGENTS.md for schemas, Transit, DTOs, and naming.
- Test-specific
AGENTS.md before editing tests.
When responding, summarize the relevant local rules briefly before implementation if they materially affect the answer.
If no AGENTS.md files are present, say that none were found and proceed with this skill's defaults.
Project Lessons To Preserve
Use these as defaults when they do not conflict with actual project files:
- Keep product rules close to the code with
AGENTS.md, especially backend, frontend, mobile, and shared-contract boundaries.
- Prefer explicit data flow over magic. A request should travel through route, controller, service/domain, repository/query, response mapping.
- Keep patient, appointment, clinical, billing, user, permission, and audit concepts separated unless the existing domain model says otherwise.
- Treat patient and staff data as sensitive. Avoid logging raw identifiers, names, notes, tokens, phone numbers, addresses, or medical content.
- Use RBAC or policy checks close to route/controller boundaries and domain actions.
- Use immutable audit events for sensitive record changes. Do not silently mutate clinical history unless the project explicitly models corrections that way.
- Keep UI state predictable. Web pages use Reagent atoms or the existing event model. Mobile screens use ClojureDart state idioms and any existing re-dash style rules.
- Keep render functions pure. Do not perform network requests, DB writes, logging side effects, subscriptions, or navigation inside render bodies unless the framework pattern explicitly allows it.
- Normalize keys at boundaries. Do not mix string keys, keyword keys, camelCase, snake_case, and Transit keys inside the same layer without a clear adapter.
- Use small, named adapters for API boundary conversion instead of scattering
js->clj, clj->js, keywordize-keys, or manual key translation everywhere.
First Response Checklist
For non-trivial tasks, quickly determine:
- Is this a new project or an existing project?
- Which
AGENTS.md files apply?
- Which stack area is touched: backend, web, shared, mobile, database, deployment, or tests?
- Which modules and dependencies are already installed?
- Which files must change?
- Does the change require Integrant configuration?
- Does the change require an API contract or shared schema?
- Does the change require a migration?
- Does the change require a web route, backend route, controller, page, component, modal, form, table, or mobile screen?
- Does the change affect permissions, audit logs, or sensitive data?
- What commands should verify the change?
If the user asks for code changes, provide complete code for each file you touch unless they ask for a patch only.
Project Detection
Look for these files and infer the actual shape:
AGENTS.md
deps.edn
build.clj
Dockerfile
resources/system.edn
resources/config.edn
resources/migrations/
resources/sql/
src/**
test/**
env/dev/src/user.clj
env/test/
env/prod/
shadow-cljs.edn
package.json
src/**.cljs
src/**.cljc
resources/public/
mobile/deps.edn
mobile/pubspec.yaml
mobile/lib/main.dart
mobile/src/**.cljd
pubspec.yaml
lib/main.dart
src/**.cljd
Adapt to reality. Some Kit projects keep Clojure and ClojureScript in one deps.edn; others split backend, frontend, and mobile into subprojects.
Recommended Repository Shape
Do not force this shape onto existing code. Use it as a clean target for new projects:
AGENTS.md
deps.edn
build.clj
resources/
system.edn
migrations/
sql/
public/
src/app/
core.clj
config.clj
domain/
infra/
web/
routes.clj
controllers/
middleware/
shared/
schema.cljc
permissions.cljc
api.cljc
frontend/
AGENTS.md
src/app/frontend/
app.cljs
api.cljs
routes.cljs
pages/
components/
antd.cljs
mobile/
AGENTS.md
deps.edn
pubspec.yaml
lib/main.dart
src/app/mobile/
main.cljd
screens/
components/
state/
test/
For Kit generated projects, prefer the generated layout and add namespaces inside that structure rather than moving everything.
Backend Rules: Kit Framework
Kit favors incremental complexity:
- Start small.
- Add capabilities through Kit modules only when needed.
- Keep lifecycle wiring declarative in
resources/system.edn.
- Implement long-lived components through Integrant methods.
- Prefer REPL-driven development with
go, halt, and reset.
- Keep deployment boring with
tools.build, Docker, and uberjar packaging.
Kit Project Creation
For a new backend or full-stack project, use the current project-pinned Kit template if available. If no version is known, show the command as a pattern and tell the developer to pin the template version used by the team.
clj -Ttools install io.github.kit-clj/kit-template '{:git/tag "<kit-version>"}' :as kit
clj -T:new :template kit :name app
cd app
clj -M:dev
In the dev REPL, use the helper functions defined by the project. Common helpers are:
(go)
(reset)
(halt)
Kit Module Workflow
Inspect before installing. Kit modules may automatically modify project files.
(kit/sync-modules)
(kit/list-modules)
Common modules:
| Module | Use |
|---|
:kit/sql | Database, migrations, SQL query files, commonly next.jdbc, conman, migratus, HugSQL. |
:kit/html | Server rendered HTML through Selmer. Use only if the project needs SSR or server templates. |
:kit/cljs | ClojureScript frontend through shadow-cljs. |
:kit/auth | Auth middleware, commonly Buddy-based. |
:kit/metrics | Prometheus style metrics. |
:kit/nrepl | nREPL support. |
:kit/sente | WebSocket or push features. |
:kit/htmx | HTMX flows, if the project uses server-driven UI. |
Before installing a module:
git status
git add .
git commit -m "Before installing Kit module"
Then install from the REPL if appropriate:
(kit/install-module :kit/sql)
Integrant Rules
Use Integrant for long-lived resources:
- Database pools.
- HTTP clients.
- Auth verifiers.
- Schedulers.
- Caches.
- Message queues.
- Metrics registries.
- Route trees that require dependencies.
Declare in resources/system.edn:
:app.appointment/service
{:db #ig/ref :db.sql/connection
:audit #ig/ref :app.audit/service}
Initialize in Clojure:
(ns app.appointment.service
(:require
[integrant.core :as ig]))
(defmethod ig/init-key :app.appointment/service
[_ opts]
opts)
(defmethod ig/halt-key! :app.appointment/service
[_ _service]
nil)
Do not create global connections or clients in handlers:
;; Avoid this inside controllers.
(def conn (jdbc/get-datasource db-spec))
Aero Configuration
Preserve existing Aero profile forms. Do not hardcode production secrets.
:app.external/insurance-client
{:base-url #profile {:dev "http://localhost:9010"
:test "http://localhost:9011"
:prod #env INSURANCE_BASE_URL}
:api-key #env INSURANCE_API_KEY}
Backend Layering
Prefer this flow:
reitit route -> controller -> service/domain -> repository/query -> database or external client
-> audit/policy/checks as explicit dependencies
Guidelines:
- Controllers parse request data, call services, and return Ring responses.
- Services enforce business rules, permissions, workflows, transactions, and audit events.
- Repositories or query namespaces perform persistence.
- Keep SQL out of controllers.
- Keep HTTP response maps out of domain functions.
- Keep domain functions testable with plain data.
Backend Route Pattern
Adapt to the project's router style. A common pattern:
(ns app.web.routes.appointments
(:require
[app.web.controllers.appointments :as appointments]))
(defn routes [{:keys [appointment-service]}]
["/api/appointments"
[""
{:get {:handler (partial appointments/list-appointments {:appointment-service appointment-service})}
:post {:handler (partial appointments/create-appointment! {:appointment-service appointment-service})}}]
["/:id"
{:get {:handler (partial appointments/get-appointment {:appointment-service appointment-service})}}]])
Backend Controller Pattern
(ns app.web.controllers.appointments
(:require
[ring.util.response :as response]))
(defn list-appointments
[{:keys [appointment-service]} request]
(let [params (:query-params request)
result ((:list appointment-service) params)]
(response/response result)))
(defn create-appointment!
[{:keys [appointment-service]} request]
(let [payload (:body-params request)
result ((:create! appointment-service) payload)]
(-> (response/response result)
(response/status 201))))
If the project uses explicit functions instead of service maps, follow that convention.
Database Rules
For schema changes, provide reversible migrations where possible:
CREATE TABLE appointments (
id UUID PRIMARY KEY,
patient_id UUID NOT NULL,
doctor_id UUID NOT NULL,
starts_at TIMESTAMP NOT NULL,
ends_at TIMESTAMP NOT NULL,
status VARCHAR(32) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX appointments_patient_id_idx ON appointments (patient_id);
CREATE INDEX appointments_doctor_id_starts_at_idx ON appointments (doctor_id, starts_at);
DROP TABLE IF EXISTS appointments;
HugSQL SQL File Rules
所有 SQL 查询必须提取到 .sql 文件, 使用 HugSQL 管理. 禁止在 Clojure 代码中直接内联 SQL 字符串.
Repository 拆分规范:
- 原
repositories/sql.clj 超过 500 行时必须按领域拆分为 repositories/sql/*.clj.
- 每个子文件只实现一个 Protocol, 如
sql/orders.clj 实现 OrderRepository.
- 公共 DTO 转换函数放在
sql/core.clj 中共享.
- 聚合入口
sql.clj 引入所有子模块并提供 new-repositories 工厂函数.
HugSQL 适配器设计 (用于测试):
- 自定义 HugSQL 适配器
CoreAdapter 继承 hugsql-adapter-next-jdbc.
- 适配器的
execute 和 query 方法路由到 core/select! / core/select-one!.
- 这样测试可以通过
with-redefs [sql-core/select! ...] 来 mock 所有 HugSQL 查询.
- 动态查询(复杂 WHERE 拼接)直接使用
jdbc/execute!, 测试时 mock jdbc/execute!.
For HugSQL:
INSERT INTO appointments (id, patient_id, doctor_id, starts_at, ends_at, status)
VALUES (:id, :patient_id, :doctor_id, :starts_at, :ends_at, :status);
SELECT id, patient_id, doctor_id, starts_at, ends_at, status, created_at, updated_at
FROM appointments
WHERE (:patient_id IS NULL OR patient_id = :patient_id)
ORDER BY starts_at DESC
LIMIT :limit OFFSET :offset;
Do not assume PostgreSQL, SQLite, or MySQL syntax. Inspect the configured database.
Transactions
Use the project's existing transaction helper. If none exists and next.jdbc is used, prefer next.jdbc/with-transaction in service code, not controller code.
API Error Shape
Follow the project. If no shape exists, use a simple consistent map:
{:error/code :appointment/not-found
:error/message "Appointment not found."
:error/details {:id id}}
Do not leak stack traces, SQL, tokens, or patient details into client responses.
Modern Frontend Stack: Reagent 2.x + React 19 + Ant Design 6
版本锁定 (Version Pinning):
- Reagent:
2.0.1 或更高 — 支持 React 19 和 hooks API.
- React / ReactDOM:
19.2.3 或更高 — 必须使用 createRoot API, ReactDOM.render 已移除.
- Ant Design (antd):
6.3.7 或更高 — 使用 npm 包直接导入, 不再使用 cljsjs/antd 或 antizer.
- @ant-design/icons: 与 antd 6 匹配的版本.
React 19 Bootstrap (createRoot):
- 入口文件 (
app.cljs / core.cljs) 必须使用 reagent.dom.client 的 create-root, 并启用 :function-components 编译器:
(:require [reagent.core :as r]
[reagent.dom.client :as rdc])
(defonce root (atom nil))
(defn init []
;; 必须启用函数组件编译器, 否则 hooks 无法在 defn 组件中正常工作
(r/set-default-compiler! (r/create-compiler {:function-components true}))
(let [container (.getElementById js/document "app")]
(reset! root (rdc/create-root container))
(rdc/render @root [app])))
- 不要调用已废弃的
reagent.dom/render, 也不要直接用 "react-dom/client".
函数组件优先 (Function Components First):
- 默认使用
defn 定义函数组件, 返回 hiccup 向量.
r/create-class 仅在以下情况使用: 需要复杂的 component-did-update / should-component-update / component-will-unmount 生命周期逻辑, 且无法通过 hooks 表达.
- 必须启用
:function-components 编译器选项: Reagent 2.0.1 默认使用类组件编译器 (class-compiler), 如果不启用此选项, defn 会被包装为 React 类组件, 导致 hooks 调用报 "Invalid hook call" 错误. 在应用初始化时必须设置:
(r/set-default-compiler! (r/create-compiler {:function-components true}))
- 数据加载使用
reagent.hooks/use-effect 替代 component-did-mount:
(:require [reagent.hooks :as hooks])
(defn page []
(hooks/use-effect (fn []
(rf/dispatch [:orders/load])
js/undefined) ; cleanup
[]) ; empty deps = mount-only
(let [items @(rf/subscribe [:orders/items])]
[:> Card ...]))
Reagent Hooks API (reagent.hooks 命名空间):
hooks/use-state — 本地状态 (替代局部 r/atom).
hooks/use-effect — 副作用 (替代 component-did-mount/update/unmount).
hooks/use-memo — 计算缓存.
hooks/use-ref — DOM 引用或可变容器.
hooks/use-callback — 回调缓存.
hooks/use-context — React context.
- Hooks 只能在函数组件顶层调用, 不能在循环、条件或嵌套函数中调用.
Ant Design 6 注意事项:
Modal / Drawer 的 destroyOnClose prop 已改为 destroyOnHidden.
Select 的 dropdownMatchSelectWidth 已改为 popupMatchSelectWidth.
- 日期相关组件使用 dayjs (AntD 6 内置), 不再需要 moment.js.
Form.create() 和 getFieldDecorator 已被 Form + useForm hook 替代.
- 组件 ref 在 React 19 中作为普通 prop 传递, 不再需要
React.forwardRef.
Web Frontend Rules: Ant Design Plus Reagent
The frontend default is Reagent with Ant Design. Inspect the actual integration before writing UI code.
Look for:
shadow-cljs.edn
package.json
src/**/app.cljs
src/**/antd.cljs
src/**/components/**
src/**/pages/**
src/**/routes.cljs
src/**/api.cljs
Ant Design Interop Rule
Projects use different antd bindings. Do not invent a wrapper API.
Acceptable patterns:
- Existing
reagent-antd wrappers.
- Project-local wrappers that adapt React components.
- Direct npm imports from
antd with reagent.core/adapt-react-class.
[:> Component props children] interop.
If no wrapper exists, create a small local adapter namespace:
(ns app.frontend.antd
(:require
[reagent.core :as r]
["antd" :refer [Button Card DatePicker Drawer Form Input Modal Select Space Table Tag message]]))
(def button (r/adapt-react-class Button))
(def card (r/adapt-react-class Card))
(def date-picker (r/adapt-react-class DatePicker))
(def drawer (r/adapt-react-class Drawer))
(def form (r/adapt-react-class Form))
(def form-item (r/adapt-react-class (.-Item Form)))
(def input (r/adapt-react-class Input))
(def modal (r/adapt-react-class Modal))
(def select (r/adapt-react-class Select))
(def space (r/adapt-react-class Space))
(def table (r/adapt-react-class Table))
(def tag (r/adapt-react-class Tag))
(defn success! [text]
(.success message text))
(defn error! [text]
(.error message text))
If the build uses advanced compilation, check whether direct named imports and property access are already handled safely in the project.
Reagent State Rules
前端必须使用 re-frame 管理状态. 禁止在每个页面使用独立的 reagent.core/atom 管理全局状态.
- 所有页面共享同一个
re-frame.db/app-db, 按领域分区: :orders, :users, :finance 等.
- 每个领域包含
:loading?, :items/:data, :error, :filters 等标准字段.
- 状态修改只能通过
re-frame.core/reg-event-db 和 re-frame.core/reg-event-fx 定义的事件.
- 数据读取通过
re-frame.core/reg-sub 定义的订阅, 页面组件使用 @(rf/subscribe [:key]) 获取数据.
- API 调用封装在
reg-fx 效果处理器中, 不要在 render 函数中直接发起 HTTP 请求.
- 页面导航通过
[:navigate "page-name"] 事件, 不使用独立的 page* atom.
re-frame 文件结构:
admin/src/findavan/admin/
db.cljs - app-db 初始状态和 schema
events.cljs - 所有事件处理器 (reg-event-db / reg-event-fx)
subs.cljs - 所有订阅定义 (reg-sub)
fx.cljs - 副作用效果处理器 (reg-fx), 如 API 调用
状态设计示例:
;; db.cljs
(def default-db
{:page "dashboard"
:auth {:user nil :token nil :loading? false}
:orders {:loading? false :items [] :filters {} :selected nil}
:users {:loading? false :items []}})
;; events.cljs
(rf/reg-event-db :navigate (fn [db [_ page]] (assoc db :page page)))
(rf/reg-event-fx :api/fetch-orders
(fn [_ _]
{:db (assoc-in default-db [:orders :loading?] true)
:api {:url "/api/orders" :method :get
:on-success [:orders/fetch-success]
:on-failure [:orders/fetch-failure]}}))
;; subs.cljs
(rf/reg-sub :page (fn [db _] (:page db)))
(rf/reg-sub :orders (fn [db _] (:orders db)))
;; page.cljs
(defn page []
(let [orders @(rf/subscribe [:orders])]
[antd/table {:dataSource (:items orders)}]))
re-frame ClojureScript 注意事项:
-
rf/reg-fx, rf/reg-event-db, rf/reg-sub, rf/reg-event-fx 在 ClojureScript 中不支持 docstring参数(与 Clojure 不同).
-
如果传入3个参数(id + docstring + fn),编译会报 Wrong number of args 警告.
-
文档写在 namespace docstring 或注释中,不要放在注册函数里.
-
Keep API-loaded page state in one local atom or the existing event-store pattern.
-
Keep antd form-local state inside Form when possible.
-
Use explicit loading, error, and data states.
-
Do not call APIs directly from render without guard logic.
-
Do not mutate nested state with ad-hoc JS objects. Convert at the boundary.
Web API Client Rule
Centralize HTTP calls:
(ns app.frontend.api
(:require
[ajax.core :as ajax]
[clojure.string :as str]))
(defn request!
[{:keys [method uri params on-success on-error]}]
(ajax/ajax-request
{:method method
:uri uri
:params params
:format (ajax/json-request-format)
:response-format (ajax/json-response-format {:keywords? true})
:handler (fn [[ok result]]
(if ok
(on-success result)
(on-error result)))}))
(defn list-appointments! [params callbacks]
(request! (merge {:method :get
:uri "/api/appointments"
:params params}
callbacks)))
Use the project's existing HTTP library if present.
Antd Page Pattern
A typical admin page contains:
- Search form.
- Table with server pagination.
- Create or edit drawer/modal.
- Permission-aware actions.
- Loading and error display.
(ns app.frontend.pages.appointments
(:require
[app.frontend.antd :as antd]
[app.frontend.api :as api]
[reagent.core :as r]))
(defn- columns []
#js [#js {:title "Patient"
:dataIndex "patientName"
:key "patientName"}
#js {:title "Doctor"
:dataIndex "doctorName"
:key "doctorName"}
#js {:title "Time"
:dataIndex "startsAt"
:key "startsAt"}
#js {:title "Status"
:dataIndex "status"
:key "status"}])
(defn page []
(let [state (r/atom {:loading? false
:rows []
:pagination {:current 1 :page-size 20 :total 0}
:error nil})]
(fn []
[antd/card {:title "Appointments"}
[antd/table {:rowKey "id"
:loading (:loading? @state)
:columns (columns)
:dataSource (clj->js (:rows @state))
:pagination (clj->js (:pagination @state))}]])))
For production code, add a controlled fetch path and adapt field names to the actual API.
Frontend Naming
Prefer consistent names:
pages.* for route-level screens.
components.* for reusable UI.
api.* for HTTP client calls.
state.* or project event namespaces for state management.
shared.* or .cljc contracts for schemas and constants.
Date, Time, And Locale
Healthcare workflows are date-sensitive. Always inspect project conventions for timezone handling.
- Keep backend instants unambiguous.
- Display local clinic time intentionally.
- Avoid parsing date strings in multiple places.
- Centralize formatting helpers.
- Validate appointment intervals on backend even if the frontend checks first.
Shared Contract Rules
Use .cljc for definitions shared by backend and ClojureScript where practical:
(ns app.shared.appointment
(:require
[malli.core :as m]))
(def appointment-statuses
#{:scheduled :checked-in :completed :cancelled :no-show})
(def AppointmentCreate
[:map
[:patient-id :uuid]
[:doctor-id :uuid]
[:starts-at inst?]
[:ends-at inst?]
[:status [:enum :scheduled]]])
(defn valid-create? [x]
(m/validate AppointmentCreate x))
Guidelines:
- Put domain enums in one place.
- Use schemas for request and response maps when the project already has Malli, Spec, or another schema library.
- Do not introduce Malli if the project already uses Spec consistently, unless requested.
- Keep DB row maps, domain maps, and API DTOs distinct when their shapes differ.
- Use adapters with names like
row->appointment, appointment->response, and request->command.
Key Naming
Follow the existing project. If no convention exists:
- Clojure layers: kebab-case keywords.
- SQL columns: snake_case.
- JSON API: either kebab-case strings or camelCase, but choose one and centralize conversion.
- Transit internal APIs: EDN keywords are acceptable, but document the wire shape.
- ClojureDart mobile: normalize API data before putting it into screen state.
Never let every component invent key translations independently. That creates a swamp with a login screen.
Mobile Rules: ClojureDart Plus Flutter
When a Mobile端 is present or requested, use ClojureDart.
Inspect:
mobile/deps.edn
mobile/pubspec.yaml
mobile/lib/main.dart
mobile/src/**.cljd
pubspec.yaml
lib/main.dart
src/**.cljd
Business-oriented mobile defaults:
lib/ is the Flutter/Dart bridge layer.
src/ contains .cljd business UI and logic.
- Keep Dart bridge code small and boring.
- Prefer ClojureDart for screens, domain state, view composition, API adapters, and tests.
- Use Flutter platform code only for platform APIs or plugins that need native setup.
ClojureDart Commands
clj -M:cljd init
clj -M:cljd flutter
clj -M:cljd compile
clj -M:cljd clean
clj -M:cljd upgrade
clj -M:cljd test
Run on a specific device:
flutter devices
clj -M:cljd flutter -d <device-id>
ClojureDart Interop Rules
Dart package imports use strings:
(ns app.mobile.main
(:require
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
Constructors are called with the type in function position:
(m/Text "Hello")
(m/Scaffold .body (m/Text "Body"))
Named arguments use dotted names:
(m/Text "Hello"
.maxLines 2
.softWrap true
.overflow m/TextOverflow.fade)
Static values:
m/Colors.blue
m/TextAlign.left
Instance property access:
(.-height size)
(.-value! controller "new value")
(set! (.-value controller) "new value")
Object field destructuring:
(let [{:flds [height width]} size]
...)
Nullability hints:
^String
^String?
Generics:
^#/(List Map) x
cljd.flutter Widget Rules
Prefer cljd.flutter for Flutter UI:
(f/widget
m/MaterialApp
.home
m/Scaffold
.body
m/Center
(m/Text "App"))
Use:
f/run for entry.
f/widget for widget composition.
:watch for reactive values.
:managed for controllers, focus nodes, animation controllers, streams, and disposable resources.
:key for changing sibling widgets.
:let for local bindings.
Example:
(defn counter-screen []
(let [n (atom 0)]
(f/widget
:watch [value n]
m/Scaffold
.appBar (m/AppBar .title (m/Text "Counter"))
.body
m/Center
(m/Text (str value))
.floatingActionButton
(m/FloatingActionButton
.onPressed #(swap! n inc)
.child (m/Icon m/Icons.add)))))
Mobile State Rules
If the project uses re-dash or an event/effect model:
- Put event handlers in handler namespaces.
- Put subscriptions or read models in state namespaces.
- Keep render functions side-effect free.
- Trigger API calls through events or effects.
- Normalize API payloads before storing.
- Keep TextField local state near the widget unless it is needed globally.
- Dispose controllers, focus nodes, streams, and animation controllers.
Mobile API Rule
Use the same backend contract as the web frontend. Keep mobile-specific DTO adapters small:
backend response -> mobile api client -> adapter -> normalized mobile state -> screen widgets
Do not let widgets parse raw server maps repeatedly.
Cross-Stack Feature Algorithm
When adding a feature across backend, web, and mobile:
- Read applicable
AGENTS.md files.
- Inspect existing implementation patterns for a similar feature.
- Define the domain command and read model.
- Add or update shared schemas and enums if the project uses them.
- Add database migration if persistence changes.
- Add SQL or repository functions.
- Add service/domain logic with permission and audit hooks.
- Add backend route and controller.
- Add API response mapping and error handling.
- Add web API client call.
- Add Reagent antd page, form, table, modal, or drawer.
- Add mobile ClojureDart screen if Mobile端 is in scope.
- Add tests at the lowest useful layer first, then integration tests.
- Run formatting, linting, backend tests, frontend build/tests, and mobile tests as applicable.
- Summarize changed files and verification commands.
Security, Privacy, And Compliance Defaults
For regulated or sensitive data, apply these engineering defaults:
- Never log raw patient names, identifiers, medical notes, addresses, phone numbers, tokens, or credentials.
- Do not put sensitive data in frontend localStorage unless the project explicitly approves and protects it.
- Enforce authorization on the backend. Frontend hiding is convenience, not security.
- Prefer deny-by-default permissions.
- Add audit logs for sensitive create, update, delete, export, and view actions when the domain requires it.
- Avoid returning more patient data than the screen needs.
- Use parameterized SQL or query libraries. Never concatenate untrusted SQL fragments.
- Validate input on backend even if frontend validates.
- Use stable error codes and safe messages.
- Treat exports, printing, bulk actions, and search endpoints as high-risk.
Testing Rules
Follow the existing test stack. If no test stack is visible, recommend the smallest conventional path.
Backend:
clj -M:test
Check whether the project uses Kaocha:
clj -M:test:kaocha
ClojureScript frontend:
npx shadow-cljs compile app
npx shadow-cljs test browser-test
Use the actual build IDs from shadow-cljs.edn.
ClojureDart mobile:
clj -M:cljd test
clj -M:cljd test -- -t widget
Test strategy:
- Unit test pure domain functions.
- Integration test DB queries with test profile and migrations.
- Controller tests should verify status, body shape, auth behavior, and error cases.
- Frontend tests should cover data adapters and important UI behavior, not every antd pixel.
- Mobile tests should separate pure logic from widget tests.
- Add regression tests for permission bugs and sensitive-data leaks.
Build And Run Commands
Backend dev:
clj -M:dev
Backend REPL:
(go)
(reset)
(halt)
Backend build:
clj -T:build uber
java -jar target/<app>.jar
Frontend dev, adapt to actual project:
npm install
npx shadow-cljs watch app
Frontend release, adapt to actual build ID:
npx shadow-cljs release app
Mobile dev:
clj -M:cljd init
clj -M:cljd flutter
Mobile clean:
clj -M:cljd clean
Lint and format, if available:
clj-kondo --lint src test
cljfmt check
cljfmt fix
Code Generation Rules
When generating code:
- Use complete namespaces with
ns declarations.
- Match existing namespace roots.
- Show full file contents for changed files unless asked for a diff.
- Preserve require ordering style if visible.
- Do not add dependencies casually. Check whether existing Kit modules or project libraries already provide the capability.
- Do not create global mutable runtime components.
- Keep long-lived resources in Integrant.
- Validate inputs before persistence.
- Keep handlers small.
- Use explicit permissions for sensitive actions.
- Keep side effects out of render functions.
- Prefer adapters at boundaries instead of leaking raw external data across layers.
- Do not bypass CSRF for server-rendered forms.
- Do not hide backend errors in frontend code. Surface safe messages and keep details in logs.
Code Size & Documentation Constraints
- 每个 namespace 不超过 500 行. 超过时应按职责拆分为多个 namespace (例如
foo.core -> foo.domain, foo.query, foo.adapter).
- Each namespace must not exceed 500 lines. Split into multiple namespaces when it grows beyond.
- 每个函数不超过 40 行. 超过时应提取私有辅助函数. 保持函数只做一件事.
- Each function must not exceed 40 lines. Extract helper functions when needed. One function, one responsibility.
- 所有函数和 namespace 必须包含中文 docstring. Docstring 用中文描述职责, 参数, 返回值.
- All functions and namespaces must include a Chinese docstring. Describe purpose, parameters, and return value in Chinese.
(ns findavan.backend.controllers.orders
"订单控制器, 处理货主的订单请求.")
(defn create-order!
"创建新订单. 接收货主提交的订单信息, 返回订单详情或错误信息."
[{:keys [order-service]} request]
...)
- 私有函数使用
defn-. 只在当前 namespace 内部使用的辅助函数必须声明为私有.
- Use
defn- for private functions. Helper functions used only within the current namespace must be private.
(defn- validate-order-params
"验证订单参数是否合法."
[params]
...)
Review Checklist
Whole Project
- Did the answer respect applicable
AGENTS.md files?
- Are files placed in the existing project structure?
- Are names consistent with the existing namespace and route conventions?
- Are commands accurate for the project aliases?
- Are sensitive data rules respected?
Backend
- Is every long-lived dependency Integrant-managed?
- Are
#ig/ref dependencies correct?
- Are
ig/init-key and ig/halt-key! methods loaded?
- Are routes mounted by the actual route tree?
- Are controllers thin?
- Are domain rules in services or domain namespaces?
- Are migrations reversible where practical?
- Are SQL queries named consistently?
- Are DB-specific SQL details correct for the configured database?
- Are permissions and audit events handled?
- Does
(reset) restart cleanly?
Web Frontend
- Does the code use the project's actual antd wrapper style?
- Are React interop props converted correctly?
- Are
clj->js and js->clj limited to boundaries?
- Are loading, error, and empty states handled?
- Are table row keys stable?
- Are forms validated on frontend and backend?
- Are API calls centralized?
- Are render functions side-effect free?
Shared
- Are schemas or DTOs in
.cljc only when they truly work on both JVM and CLJS?
- Are keys normalized consistently?
- Are enum values centralized?
- Are API response shapes documented or discoverable?
Mobile
- Is mobile implemented in ClojureDart when requested?
- Are Dart imports string-based in
:require?
- Are named arguments dotted?
- Are constructors called correctly?
- Are widgets using
cljd.flutter idioms where appropriate?
- Are controllers and streams disposed or
:managed?
- Is TextField state handled locally unless global state is needed?
- Are API payloads normalized before screen state?
Debugging Guide
Integrant key not found
Likely causes:
- Key exists in
system.edn, but no ig/init-key method is loaded.
- Namespace containing the defmethod is not required during startup.
- Typo in a namespaced keyword.
- Profile removed the dependency unexpectedly.
Actions:
- Check
resources/system.edn.
- Check the namespace defining
defmethod ig/init-key.
- Check startup requires.
- Run
(reset).
Backend route returns 404
Likely causes:
- Route namespace is not mounted.
- Method mismatch.
- Context path mismatch.
- Handler failed during system init.
Actions:
- Inspect central route aggregation.
- Confirm method and path.
- Check system startup logs.
- Print or inspect route data in dev if the project supports it.
HugSQL query missing
Likely causes:
- SQL file not loaded.
- Query annotation mismatch.
- Wrong query namespace or dispatch key.
- Query function not injected.
Actions:
- Inspect
resources/sql/*.sql.
- Confirm
-- :name annotation.
- Confirm Integrant query component.
- Confirm controller or service call.
Antd component does not render
Likely causes:
- Wrong wrapper API.
- Passing Clojure maps where JS objects are required.
- Incorrect child style for adapted React component.
- CSS not imported.
- Package version mismatch.
Actions:
- Inspect existing antd usage.
- Check
package.json and imports.
- Convert props with
clj->js only at the component boundary.
- Confirm
antd/dist/reset.css or project CSS setup if needed.
ClojureDart compile error
Classify first:
- Clojure CLI or
deps.edn.
- ClojureDart syntax or compiler.
- Dart package or
pubspec.yaml.
- Flutter platform or device.
- Runtime widget lifecycle.
- Test runner.
Common checks:
- Dart packages use string imports.
- Named arguments use
.arg value.
- Constructors do not use
new or Java trailing-dot style.
:main namespace exists and defines main.
- Run
clj -M:cljd clean for stale generated state.
Response Format For Development Tasks
Prefer this structure:
要改的文件:
1. ...
2. ...
实现思路:
...
完整代码:
...
运行方式:
...
检查点:
...
For reviews:
结论:
...
主要问题:
...
建议修改:
...
完整修正代码:
...
验证命令:
...
For debugging:
最可能的故障层:
...
证据:
...
最小修复:
...
完整代码或配置:
...
验证命令:
...
When To Ask A Follow-Up
Ask a follow-up only when implementation would be unsafe or ambiguous in a way that cannot be reasonably resolved by inspecting files.
Prefer making a grounded best-effort answer when:
- The user asks for a skill or template.
- The exact code is not available, but the desired stack is clear.
- A partial artifact is more useful than waiting.
When code is missing, explicitly say which project files were not available and make the generated result adaptable.
Local Reference Files
Read these for larger tasks:
references/agents-md-playbook.md
references/backend-kit-patterns.md
references/frontend-antd-reagent-patterns.md
references/mobile-clojuredart-patterns.md
references/shared-api-contracts.md
references/security-testing-checklist.md
examples/prompt-examples.md