diff --git a/docs/skills/coze-backend-go/README.md b/docs/skills/coze-backend-go/README.md new file mode 100644 index 0000000000..8e19924827 --- /dev/null +++ b/docs/skills/coze-backend-go/README.md @@ -0,0 +1,75 @@ +## Coze Backend Go + DDD - Skill + +This is the Coze Studio backend engineering conventions + +### What this skill covers + +- **IDL-first API design** (Thrift → Hertz codegen) +- **DDD layering boundaries** (Handler / Application / Domain / Repository / DAL) +- **DB DDL → `gorm/gen` reverse generation** +- **Domain-scoped error codes (errno)** and layered error-handling rules +- **crossdomain** interface-based calls +- **Middleware and session** conventions +- **Transactions**, **mocks/tests**, and **ID generation** + +### Files + +- Entry: `SKILL.md` +- SOP: `checklist.md` +- Notes: `notes.md` +- Code pointers: `citations.md` +- Detailed topics: `skill-01-*.md` … `skill-13-*.md` + +### How to use + +This is a **tool-agnostic** prompt+documentation bundle. You can install it in either: + +- **Project-level**: put this directory anywhere inside the target repo, then point your AI coding tool to load it as repository knowledge/instructions. +- **Global-level**: store it in your personal prompt library, and attach/reference it when working on `coze-studio/backend`. + +### Common tool setups (optional) + +Assume you vendor these skills under `docs/skills/` in your repository (for example: `docs/skills/coze-backend-go/`). + +#### Cursor + +Project root: + +```bash +mkdir -p .cursor && ln -snf ../docs/skills .cursor/skills +``` + +Windows: copy to `.cursor/skills/` instead of symlink. + +#### Claude Code + +Project root: + +```bash +mkdir -p .claude && ln -snf ../docs/skills .claude/skills +``` + +Windows: copy to `.claude/skills/` instead of symlink. + +#### Codex + +Copy the skill directory into your global skills home, then restart your tool: + +```bash +cp -r docs/skills/coze-backend-go "$CODEX_HOME/skills/" +``` + +If you have multiple skills, copy multiple subdirectories. + +Suggested trigger keywords (when asking an AI coding tool for help): +"backend", "DDD", "IDL", "Thrift", "errno", "gorm/gen", "DAL", "crossdomain", "middleware", "session", "transaction", etc. + +### Alignment with official standards + +This skill is derived from: + +- The official Coze Studio [Development Standards](https://github.com/coze-dev/coze-studio/wiki/7.-Development-Standards) +- The actual structure and code of this repository + +Where they differ, this skill follows **the real repository** (for example, using `mockgen` for Go mocks rather than introducing other mock libraries). + diff --git a/docs/skills/coze-backend-go/SKILL.md b/docs/skills/coze-backend-go/SKILL.md new file mode 100644 index 0000000000..0203e724e2 --- /dev/null +++ b/docs/skills/coze-backend-go/SKILL.md @@ -0,0 +1,84 @@ +--- +name: coze-backend-go +description: "Documentation of Coze Studio backend Go + DDD conventions. Use when editing `backend/`, implementing APIs (IDL-first), working with Domain/Application/Repository/DAL boundaries, generating DAL via gorm/gen, handling errno and layered errors, crossdomain calls, sessions/middleware, transactions, mocks/tests, and ID generation." +--- + +# Coze Studio Backend Go + DDD + +This skill documents the Coze Studio backend conventions as **actionable AI-coding guidelines**: **Go practices**, **DDD layering**, **IDL-driven development**, DB reverse generation via `gorm/gen`, and Coze-specific patterns such as **domain-scoped error codes**, **space/session isolation**, and **workflow-domain conventions**. + +> This skill is aligned with `coze-studio` backend **v0.5.1**. If the repository evolves (structure/codegen commands), treat the repo as the source of truth and refresh this skill accordingly. + +## Emphasis + +- **Go**: interface vs implementation separation, Functional Options, error-as-value, `internal` packages, `mockgen`, context propagation, and chunked batch queries. +- **DDD**: business invariants live in the Domain layer; API/Application layers orchestrate and map models only. +- **Coze conventions**: IDL-first, unified `{code,msg,data}` responses, errno ranges per domain, crossdomain interfaces, and workflow-specific error handling. +- **Multi-space (tenant-like)**: most resources are scoped by `SpaceID` and must enforce access control (user ↔ space membership) at the Application boundary; never query/update cross-space data without explicit checks. + +--- + +## Skill 0: Architecture and layering overview + +``` +idl/ ← (1) IDL definitions (Thrift) +backend/ + api/ + handler/coze/ ← (2) HTTP handlers (generated stubs + manual bodies) + router/coze/ ← (3) Route registration (generated) + model/ ← (4) API request/response structs (generated) + middleware/ ← (5) Middleware (session/auth/log/etc.) + application/{domain}/ ← (6) Application services (orchestration only) + domain/{domain}/ + entity/ ← (7) Domain entities + service/ ← (8) Domain service interfaces + implementations + repository/ ← (9) Repository interfaces + implementations + internal/dal/ + model/ ← (10) gorm/gen models (*.gen.go) + query/ ← (11) gorm/gen query APIs (*.gen.go) + dto/ ← (12) Domain DTOs + crossdomain/ ← (13) Cross-domain per-domain packages (ACL) + {domain}/ + contract.go ← cross-domain interface definitions (anti-corruption layer) + impl/ ← concrete implementations + model/ ← cross-domain models (stable, avoid domain entities) + *mock/ ← mocks (generated) + infra/ ← (14) Infrastructure capabilities (interface + impl/) + {capability}/ + *.go ← capability interfaces/types (acts as "contract") + entity/ ← (optional) infra-level entities/VOs + impl/ ← implementations (mysql/redis/nsq/...) + types/ + ddl/ ← (15) DB reverse-generation scripts (gen_orm_query.go) + errno/ ← (16) Global/domain error codes + consts/ ← (17) Global constants +``` + +Boot entry: `backend/main.go` → `application.Init(ctx)` → `router.GeneratedRegister(s)` + +Cross-domain calls must go through the `crossdomain/{domain}/contract.go` interfaces instead of directly depending on other domains’ `service` packages. + +--- + +## Documents index + +| Topic | File | +|------|------| +| End-to-end “add a module” SOP | [checklist.md](checklist.md) | +| Notes and caveats | [notes.md](notes.md) | +| Code pointers (paths + line ranges) | [citations.md](citations.md) | +| Skill 1: IDL-first API design | [skill-01-idl.md](skill-01-idl.md) | +| Skill 2: DB DDL → gorm/gen DAL generation | [skill-02-dal.md](skill-02-dal.md) | +| Skill 3: Domain module structure (DDD) | [skill-03-domain.md](skill-03-domain.md) | +| Skill 4: Application layer conventions | [skill-04-application.md](skill-04-application.md) | +| Skill 5: Transaction patterns | [skill-05-transaction.md](skill-05-transaction.md) | +| Skill 6: Errno + layered validation/error handling | [skill-06-errno.md](skill-06-errno.md) | +| Skill 7: Handler conventions | [skill-07-handler.md](skill-07-handler.md) | +| Skill 8: Middleware & session conventions | [skill-08-middleware.md](skill-08-middleware.md) | +| Skill 9: crossdomain conventions | [skill-09-crossdomain.md](skill-09-crossdomain.md) | +| Skill 10: DTO conventions | [skill-10-dto.md](skill-10-dto.md) | +| Skill 11: Mocks & tests | [skill-11-mock.md](skill-11-mock.md) | +| Skill 12: ID generation rules | [skill-12-id.md](skill-12-id.md) | +| Skill 13: Mermaid flowcharts & architecture diagrams | [skill-13-flowchart-generator.md](skill-13-flowchart-generator.md) | +| Skill 14: Eino conventions | [skill-14-eino.md](skill-14-eino.md) | + diff --git a/docs/skills/coze-backend-go/checklist.md b/docs/skills/coze-backend-go/checklist.md new file mode 100644 index 0000000000..c9d73ae4c3 --- /dev/null +++ b/docs/skills/coze-backend-go/checklist.md @@ -0,0 +1,53 @@ +# End-to-end Development Checklist (AI Coding SOP) + +``` +Full steps to add a new feature/module: + +Step 1: Database DDL + - Create/upgrade tables in the test database (DDL/DDL_UPGRADE) + - Add mapping in types/ddl/gen_orm_query.go: path2Table2Columns2Model + - Run: go run types/ddl/gen_orm_query.go (requires MYSQL_DSN env var) + - Generates: + domain/{X}/internal/dal/model/*.gen.go + domain/{X}/internal/dal/query/*.gen.go + +Step 2: IDL definition + - Define Service/Request/Response in idl/{module}/*.thrift + - Run Hertz codegen (repo uses `hz`, see `backend/.hz`): + - `hz update -idl idl/api.thrift -enable_extends` + - Note: `-enable_extends` is critical for the master entry `idl/api.thrift` (otherwise route generation may be empty). See coze-studio issue #372. + - Generates: + api/router/coze/api.go (routes) + api/model/{gen_path}/*.go (API structs) + api/handler/coze/{service}_service.go (handler stubs) + +Step 3: Domain layer implementation + - entity/ domain entities (embed crossdomain model + domain methods) + - dto/ service input/output DTOs (domain-scoped) + - service/ + - service.go Service interface (with //go:generate mockgen) + - service_impl.go impl struct + NewService (dependency injection) + - {feature}.go split by responsibility (multiple files) + - repository/ + - {X}_repository.go Repository interface + - {X}_impl.go implementation (holds DAL/DAO) + - option.go Functional Options (field projection) + - internal/dal/ + - {table}.go DAO implementation (CRUD + WithTX variants) + +Step 4: Register errno + - Add error-code constants in types/errno/{domain}.go (allocate within range) + - Register in init(): code.Register(...) + +Step 5: Application layer + - application/{X}/init.go InitService + dependency injection + - application/{X}/*.go orchestrate, call Domain services + +Step 6: Implement handler bodies + - api/handler/coze/{service}_service.go fill handler function bodies + +Step 7: Register crossdomain (if needed) + - crossdomain/{X}/ define interface contracts + - application/application.go SetDefaultSVC(...) +``` + diff --git a/docs/skills/coze-backend-go/citations.md b/docs/skills/coze-backend-go/citations.md new file mode 100644 index 0000000000..5722f3bf84 --- /dev/null +++ b/docs/skills/coze-backend-go/citations.md @@ -0,0 +1,87 @@ +# Citations (code pointer index) + +This repository’s code changes over time. To avoid copying stale code into the docs, this file provides **file paths + line ranges** you can open on-demand. + +All paths are relative to the repo root; `backend/` is the backend directory. + +--- + +## Skill 0 / Boot and layering + +| What | Path and lines | +|------|---------------| +| main entry, `application.Init`, `startHttpServer`, middleware ordering | `backend/main.go` (L43-104) | +| Route registration (generated) | `backend/api/router/coze/api.go` (L17-80) | +| `GeneratedRegister` | `backend/api/router/register.go` (L34-39) | + +--- + +## Skill 1: IDL + +| What | Path and lines | +|------|---------------| +| Base / `BaseResp`, `EmptyResp` (code+msg+data) | `idl/base.thrift` (L1-42) | +| Service methods and `api.post` / `api.gen_path` annotations | `idl/plugin/plugin_develop.thrift` (L6-50) | + +--- + +## Skill 2: DAL generation + +| What | Path and lines | +|------|---------------| +| `path2Table2Columns2Model` config | `backend/types/ddl/gen_orm_query.go` (L44-225) | +| generator main, DSN, FieldModify | `backend/types/ddl/gen_orm_query.go` (L231-326) | +| PO models (`*.gen.go`) | `backend/domain/plugin/internal/dal/model/plugin_draft.gen.go` (L1-33) | +| Query APIs (`*.gen.go`) | `backend/domain/plugin/internal/dal/query/plugin_draft.gen.go` (L1-46) | + +--- + +## Skill 3: DDD (4 layers) + +| What | Path and lines | +|------|---------------| +| Entity embeds crossdomain model | `backend/domain/plugin/entity/plugin.go` (L24-54) | +| Service interface, `//go:generate mockgen` | `backend/domain/plugin/service/service.go` (L27-92) | +| `service_impl`, `NewService` DI | `backend/domain/plugin/service/service_impl.go` (L30-65) | +| Repository interface | `backend/domain/plugin/repository/plugin_repository.go` (L27-53) | +| Functional Options (`option.go`) | `backend/domain/plugin/repository/option.go` (L23-86) | +| DAO Create, `ToDO`, `genPluginID` | `backend/domain/plugin/internal/dal/plugin_draft.go` (L39-138) | +| Keyset-pagination list | `backend/domain/plugin/internal/dal/plugin_draft.go` (L158-190) | +| MGet, `slices.Chunks` | `backend/domain/plugin/internal/dal/plugin_draft.go` (L192-212) | +| `CreateWithTX` | `backend/domain/plugin/internal/dal/plugin_draft.go` (L287-315) | +| Repository impl (holds DAO) | `backend/domain/plugin/repository/plugin_impl.go` (L45-72) | +| Transaction template (Begin/defer Rollback/Commit) | `backend/domain/plugin/repository/plugin_impl.go` (L133-170) | +| Multi-table transaction: PublishPlugin | `backend/domain/plugin/repository/plugin_impl.go` (L283-351) | +| DTO definitions | `backend/domain/plugin/dto/plugin.go` (L27-96) | +| Repository mocks | `backend/domain/plugin/repository/mock/` | + +--- + +## Skill 4: Application + +| What | Path and lines | +|------|---------------| +| Application struct, DomainSVC | `backend/application/plugin/plugin.go` (L49-59) | +| InitService and DI | `backend/application/plugin/init.go` (L38-89) | +| `basicServices` → `primaryServices` → `complexServices`, `SetDefaultSVC` | `backend/application/application.go` (L84-171) | + +--- + +## Skill 6: Errors and handlers + +| What | Path and lines | +|------|---------------| +| errno constants and range | `backend/types/errno/plugin.go` (L25-54) | +| `init`, `code.Register`, `WithAffectStability` | `backend/types/errno/plugin.go` (L56-103) | +| `StatusError` interface | `backend/pkg/errorx/error.go` (L26-36) | +| `errorx.New`, `WrapByCode`, `Wrapf` | `backend/pkg/errorx/error.go` (L54-78) | +| HTTP error response mapping | `backend/api/internal/httputil/error_resp.go` (L35-54) | +| `invalidParamRequestResponse`, `internalServerErrorResponse` | `backend/api/handler/coze/base.go` (L27-33) | +| Handler example: BindAndValidate, validation, calling Application | `backend/api/handler/coze/plugin_develop_service.go` (L34-77) | +| `GetUIDFromCtx`, `MustGetUIDFromCtx` | `backend/application/base/ctxutil/session.go` (L36-52) | +| Session auth middleware | `backend/api/middleware/session.go` (L41-75) | + +--- + +Open the files above at the referenced line ranges. If line numbers drift due to edits, search nearby for the mentioned symbols. + diff --git a/docs/skills/coze-backend-go/notes.md b/docs/skills/coze-backend-go/notes.md new file mode 100644 index 0000000000..258980573d --- /dev/null +++ b/docs/skills/coze-backend-go/notes.md @@ -0,0 +1,22 @@ +# Notes + +1. **Generated code vs handwritten code**: files under `api/router/coze/api.go`, `api/model/`, `dal/model/*.gen.go`, and `dal/query/*.gen.go` are **generated** and must **not** be edited manually. Handler function bodies, middleware bodies, the `application/` layer, and the `domain/` layer are handwritten. + +2. **Framework choices**: HTTP uses [Hertz](https://github.com/cloudwego/hertz) (CloudWeGo), ORM uses gorm + `gorm/gen`, and IDL uses Apache Thrift. + +3. **The Application layer is not a mandatory “classic DDD” layer**, but in this codebase it acts as the DDD Application Service: API model mapping, cross-domain orchestration, and event publishing. + +4. **`internal/` packages** enforce Go access boundaries: `domain/{X}/internal/dal/` can only be used by code within the same domain module. The Application layer must not call DAL directly; it must go through repository interfaces. + +5. **Batch MGet**: for bulk reads, use `slices.Chunks(ids, 20)` to chunk requests and avoid overly large `IN (...)` clauses. + +6. **Go error handling**: business errors should be returned as `error` (via `errorx` + `errno`). Avoid `panic` on business paths; reserve `panic` for unrecoverable programming errors. The Application layer may use `defer` + `recover` as a final safety net and convert panics to standard error codes. + +7. **Errors must carry a business code**: the Application and Domain layers must not return bare `errors.New(...)` or `fmt.Errorf(...)` for expected business errors. Use `errorx.New(errno.ErrXxx, ...)` for business errors; otherwise the Handler layer will treat it as a system error and respond with HTTP 500, which is both non-actionable and hard to distinguish from real incidents. When propagating errors, use `errorx.Wrapf` / `errorx.WrapByCode` (or `github.com/pkg/errors` Wrap/Wrapf) to preserve call context; before returning to HTTP, ensure it is converted to a status error with a proper errno code. + +8. **File header comments**: new files do not need to add legacy copyright headers (this is an internal project). Keep existing headers in old files as-is. + +9. **When to use `pkg/`**: small generic utilities (e.g., time/json/file/lang helpers) without business semantics and without direct external system access. Do not put DB/Redis/HTTP SDK integrations into `pkg/`; those belong to `infra/` or domain-specific adapters. + +10. **Avoid reinventing wheels**: before adding “utility” code, check whether `pkg/` already provides a similar helper. + diff --git a/docs/skills/coze-backend-go/skill-01-idl.md b/docs/skills/coze-backend-go/skill-01-idl.md new file mode 100644 index 0000000000..e61793c663 --- /dev/null +++ b/docs/skills/coze-backend-go/skill-01-idl.md @@ -0,0 +1,63 @@ +# Skill 1: Design APIs starting from IDL (IDL-first) + +## Rules + +1. **All APIs start from `.thrift` files under `idl/`.** +2. Each service method’s annotations define the HTTP route, category, and generated code path. +3. `base.thrift` defines the shared `Base` (request) and `BaseResp` (response) structures. +4. Responses follow a unified `{code, msg, data}` shape and include `base.BaseResp` as the tail field. +5. **One method parameter and one return value only**, both must be custom `struct` types. +6. Request/response naming follows `{Method}Request` / `{Method}Response`. +7. Each `Request` struct must include a `Base` field (`base.Base`, field id `255`, `optional`). +8. Each `Response` struct must include a `BaseResp` field (`base.BaseResp`, field id `255`, `optional`). +9. New fields should be declared as `optional`; avoid `required` for forward compatibility. + +## Codegen (Hertz `hz`) + +`coze-studio` backend v0.5.1 uses `hz` (Hertz toolkit) to generate routes, handler stubs, and `api/model` structs from Thrift IDL. See `backend/.hz` for the configured output directories (handler/model/router). + +Common command (run from repo root): + +```bash +# Update the master IDL (aggregation/extends) generated routes + handler/model +hz update -idl idl/api.thrift -enable_extends +``` + +Notes: +- `idl/api.thrift` is usually the master IDL aggregating multiple services (incl. `extends`); in practice `-enable_extends` is required, otherwise route generation may be empty. +- `hz update` only needs the IDL file that defines the `service`; dependent IDLs will be generated automatically. + +**IDL template** (see `plugin_develop.thrift` as reference): + +```thrift +service XxxService { + XxxResponse XxxMethod(1: XxxRequest request) + (api.post='/api/xxx/yyy', api.category="xxx", api.gen_path="xxx") +} +struct XxxRequest { + 1: optional i64 id (api.js_conv = "str"), + 255: optional base.Base Base +} +struct XxxResponse { + 1: i64 code, + 2: string msg, + 3: XxxData data, + 255: optional base.BaseResp BaseResp +} +``` + +## Conventions + +- Service names and method names use **camelCase**. +- Typically, one service per `.thrift` file (except for explicit aggregation via `extends`). +- APIs follow a **RESTful** style for paths; when adding new endpoints, use existing modules as a style reference instead of inventing new patterns. + +## Generated code artifacts + +- `backend/api/router/coze/api.go` — route registration (**generated**; do not edit) +- `backend/api/router/coze/middleware.go` — middleware stubs (can be manually filled) +- `backend/api/model/{gen_path}/` — request/response structs (**generated**) +- `backend/api/handler/coze/` — handler stubs (implement handler bodies manually) + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-02-dal.md b/docs/skills/coze-backend-go/skill-02-dal.md new file mode 100644 index 0000000000..48174207bb --- /dev/null +++ b/docs/skills/coze-backend-go/skill-02-dal.md @@ -0,0 +1,27 @@ +# Skill 2: Database DDL → reverse-generate DAL via `gorm/gen` + +## Flow + +``` +1) Create/upgrade tables in the test database (DDL/DDL_UPGRADE) +2) Run backend/types/ddl/gen_orm_query.go +3) Auto-generates: + domain/{X}/internal/dal/model/*.gen.go ← PO structs + domain/{X}/internal/dal/query/*.gen.go ← type-safe query API +``` + +## Configuration + +In `gen_orm_query.go`, the `path2Table2Columns2Model` mapping controls generation: +- **key**: destination path (e.g., `domain/xxx/internal/dal/query`) +- **value**: table → JSON column → Go type mapping (used to deserialize JSON columns to strong types) + +## Key details + +- `deleted_at` uses `gorm.DeletedAt` (soft delete) +- `created_at` / `updated_at` use millisecond timestamps (`autoCreateTime:milli` / `autoUpdateTime:milli`) +- JSON columns use `gen.FieldModify` to specify the serialization type and the `serializer:json` tag +- DSN is read from `MYSQL_DSN` (default `root:root@tcp(localhost:3306)/opencoze`) + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-03-domain.md b/docs/skills/coze-backend-go/skill-03-domain.md new file mode 100644 index 0000000000..b89cfa8ccb --- /dev/null +++ b/docs/skills/coze-backend-go/skill-03-domain.md @@ -0,0 +1,45 @@ +# Skill 3: Domain module structure (DDD) + +**Principle**: business invariants and domain logic live in the Domain layer (Entity/Service/Repository). API and Application layers only do binding/validation, orchestration, and model mapping — they must not own business rules. + +--- + +## Entity (domain entity) + +- **Path**: `domain/{X}/entity/` +- Encapsulates domain objects, **not DB schemas**; contains domain behavior and invariants +- Common pattern: embed a crossdomain model, then add domain-specific methods; if legacy DB fields exist, gradually converge toward proper boundaries + +## Service (domain service) + +- **Path**: `domain/{X}/service/` +- `service.go` defines the **interface** (with `//go:generate mockgen`) +- Implementation can be a private `impl` struct + `NewXxx(...)` constructor (DI), or returned from repository wiring depending on the domain’s conventions +- Split business logic across multiple files by responsibility (e.g., `aggregation.go`, `workflow.go`, `message_extraction.go`) + +## Repository + +- **Path**: `domain/{X}/repository/` +- Defines repository **interfaces** +- **Implementation options** (follow each domain’s reality): + - Provide `impl` under `repository/` holding DAO(s), or + - `NewXxxRepository(...)` returns a DAO from `internal/dal/` directly +- Optional: + - `option.go` uses **Functional Options** to control field projection + - `mock/` stores `mockgen` outputs + +## DAL (data access layer) + +- **Path**: `domain/{X}/internal/dal/` +- Used by Repository; should not be accessed directly by higher layers +- Each DAL struct focuses on a single table or a single storage category +- Internally uses `gorm/gen` model/query; perform PO → domain conversions where needed +- Large listings should use **keyset/cursor-based pagination** (avoid deep `offset`) + +The Domain layer talks to: + +- **Cross-domain contracts** via `backend/crossdomain/{Y}/contract.go` (per-domain packages; never depend on other domains’ `service` packages directly). +- **Infrastructure capabilities** via interfaces under `backend/infra/{capability}/*.go` (implementations usually live in `backend/infra/{capability}/impl/`; business code should not depend on concrete impl packages). + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-04-application.md b/docs/skills/coze-backend-go/skill-04-application.md new file mode 100644 index 0000000000..aa9933b78e --- /dev/null +++ b/docs/skills/coze-backend-go/skill-04-application.md @@ -0,0 +1,22 @@ +# Skill 4: Application layer conventions + +## Responsibilities + +- **Orchestrate** multiple Domain services; no core business logic +- Handle: API model ↔ Domain DTO mapping, crossdomain calls, event publishing +- Each Application package has an `init.go` for dependency injection and service initialization + +Application services: + +- Call **domain services** under `backend/domain/*/service`. +- Interact with **crossdomain contracts** under `backend/crossdomain/*/contract.go` (per-domain packages). +- Depend on **infrastructure capability interfaces** under `backend/infra/*/*.go` (never depend on `impl` directly in business code). + +## Initialization chain (3 tiers) + +The system is initialized in dependency order: + +`basicServices` → `primaryServices` → `complexServices` + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-05-transaction.md b/docs/skills/coze-backend-go/skill-05-transaction.md new file mode 100644 index 0000000000..0dfd09ad4b --- /dev/null +++ b/docs/skills/coze-backend-go/skill-05-transaction.md @@ -0,0 +1,24 @@ +# Skill 5: Transaction conventions + +## Standard transaction template + +```go +tx := p.query.Begin() +if tx.Error != nil { return tx.Error } +defer func() { + if r := recover(); r != nil { + tx.Rollback(); err = fmt.Errorf("catch panic: %v\nstack=%s", r, debug.Stack()); return + } + if err != nil { tx.Rollback() } +}() +// ... business operations via WithTX(ctx, tx, ...) ... +return tx.Commit() +``` + +## DAL must provide both variants + +- `Create(ctx, entity)` — standalone transaction +- `CreateWithTX(ctx, tx, entity)` — participate in an external transaction + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-06-errno.md b/docs/skills/coze-backend-go/skill-06-errno.md new file mode 100644 index 0000000000..e8e142ddaa --- /dev/null +++ b/docs/skills/coze-backend-go/skill-06-errno.md @@ -0,0 +1,186 @@ +# Skill 6: Error codes (errno) and validation conventions + +This document consolidates **error-code definition & registration**, **error infrastructure**, and **layered responsibilities** for validation and error propagation. + +--- + +## 1) Error-code definition and registration + +### Where to define + +`backend/types/errno/{domain}.go` — one file per domain. + +### Ranges and naming + +Naming follows `Err{Domain}{Scenario}Code`. Example (Plugin domain): + +```go +// Plugin: 109 000 000 ~ 109 999 999 +const ( + ErrPluginInvalidParamCode = 109000000 + ErrPluginPermissionCode = 109000001 + ... +) +``` + +**Domain ranges** + +| Domain | Range | File | +|---|---|---| +| Agent | 100 000 000 ~ 100 999 999 | `backend/types/errno/agent.go` | +| App | 101 000 000 ~ 101 999 999 | `backend/types/errno/app.go` | +| Connector | 102 000 000 ~ 102 999 999 | `backend/types/errno/connector.go` | +| Conversation | 103 000 000 ~ 103 999 999 | `backend/types/errno/conversation.go` | +| Knowledge | 105 000 000 ~ 105 999 999 | `backend/types/errno/knowledge.go` | +| Permission | 108 000 000 ~ 108 999 999 | `backend/types/errno/permission.go` | +| Plugin | 109 000 000 ~ 109 999 999 | `backend/types/errno/plugin.go` | +| User | 700 000 000 ~ 700 999 999 | `backend/types/errno/user.go` | +| Workflow | mixed/special ranges | `backend/types/errno/workflow.go` | + +Each domain should have dedicated “invalid param” and “permission denied” codes (e.g., `ErrXxxInvalidParamCode`, `ErrXxxPermissionCode`). + +### Registration and usage + +- Register in `init()` via `code.Register(errCode, "message template {key}", opts...)` (`{key}` is a placeholder). +- `code.WithAffectStability(false)` means it should not impact stability metrics (expected business errors). +- Create: `errorx.New(errno.ErrXxx, errorx.KV("key", "val"))` or `errorx.KVf("key", format, args...)`. +- Wrap: `errorx.WrapByCode(err, code, options...)` (with a business code) or `errorx.Wrapf(err, format, args...)` (context only). + +### Application/Domain: no bare errors — business errors must carry codes + +- **Do not** `return errors.New(...)` or `return fmt.Errorf(...)` in the Application/Domain layers for expected business failures. Bare errors are treated as system errors at the Handler boundary and will become **HTTP 500 + "internal server error"**. +- **Must** wrap expected errors (invalid param, permission, not found, DB constraint conflicts, etc.) with `errorx.New(errno.ErrXxx, ...)` or `errorx.WrapByCode(err, errno.ErrXxx, ...)`, so the API responds as HTTP 200 with clear `{code,msg}`. + +### Recommendation: preserve context with Wrap + +- Prefer `errorx.Wrapf(err, "context: %s", arg)` or `errorx.WrapByCode(err, code, ...)` when passing errors upward. +- If `github.com/pkg/errors` is used, `errors.Wrap/Wrapf` can also preserve stack/cause chains; before returning to HTTP, ensure the error becomes a status error with a proper errno code to avoid falling back to 500. + +--- + +## 2) Error infrastructure + +### Core error type: `StatusError` + +Unified interface: `Code()`, `Msg()`, `IsAffectStability()`, `Extra()`. + +### HTTP error handling behavior + +- Business errors (`StatusError`) → **HTTP 200** + `{code: bizCode, msg: bizMsg}` +- System errors (non-`StatusError`) → **HTTP 500** + `{code: 500, msg: "internal server error"}` +- Bind/validate failures → **HTTP 400** via `invalidParamRequestResponse(c, errMsg)` + +### `WithAffectStability` convention + +| Error type | WithAffectStability | Meaning | +|---|---:|---| +| Invalid param | false | user input issue | +| Permission denied | false | expected access control | +| Not found | false | expected business state | +| DB/Redis/IDgen failures | true | infrastructure incident | + +Errors with business codes are logged via warn; bare errors are logged as errors. + +--- + +## 3) Request flow and layer responsibilities (architecture) + +```mermaid +graph TD + "HTTP request" --> "Middleware" + "Middleware" --> "Handler/API (backend/api/handler)" + "Handler/API (backend/api/handler)" --> "Application (backend/application)" + "Application (backend/application)" --> "Domain Service (backend/domain/*/service)" + "Domain Service (backend/domain/*/service)" --> "Repository/DAL (backend/domain/*/internal/dal)" + "Domain Service (backend/domain/*/service)" --> "Repository Interface (backend/domain/*/repository)" +``` + +--- + +## 4) Validation and error responsibilities by layer + +### 4.1 Middleware + +**Purpose**: authn/authz prerequisites for all business logic. + +- **Session auth**: validate `session_key` cookie; failure → `401 Unauthorized` +- **OpenAPI auth**: validate `Authorization: Bearer `; failure → `ErrUserAuthenticationFailed` + +### 4.2 Handler/API + +**Purpose**: bind + format validation and simple prechecks (non-zero, enum, length, etc.). + +- Unified responses: + - `invalidParamRequestResponse(c, errMsg)` → HTTP 400 + - `internalServerErrorResponse(ctx, c, err)` → HTTP 200 (biz code) or HTTP 500 (system) +- Patterns: + - `BindAndValidate` tag validation + - plus manual checks (enums/ranges/combination constraints) + - OpenAPI routes also use `invalidParamRequestResponse` + +### 4.3 Application + +**Purpose**: semantic checks (login state, space permission, business combinations), orchestrate domain services, and convert panics to standard errors. + +- Read UID from context; missing UID → permission error +- `checkUserSpace` / cross-user space checks +- Workflow: use `defer` + `vo.WrapIfNeeded` safety net; non-WorkflowError mapped to `ErrWorkflowOperationFail`, etc. + +### 4.4 Domain Service + +**Purpose**: core business invariants (required fields, consistency, state transitions). This layer is the primary source of business errno codes. + +- Required/combined params: `errorx.New(errno.ErrXxxInvalidParamCode, errorx.KV("msg", "..."))` +- DB failures: wrap as `ErrXxxDBCode`; illegal states: `ErrXxxNonRetryableCode` / `ErrXxxNotExistCode`, etc. +- You may keep a private `checkRequest` helper to normalize errors. +- **Workflow domain**: use `vo.WorkflowError` / `vo.NewError` / `vo.WrapError` / `vo.WrapIfNeeded` instead of `errorx.New`. + +### 4.5 Repository/DAL + +**Purpose**: persistence only; no business validation. Convert storage failures to infra errors or “not found”. + +- **Knowledge style**: DAL returns raw gorm errors; Domain wraps them; empty-list reads short-circuit to nil +- **Workflow style**: DAL uses `vo.WrapError` / `vo.WrapIfNeeded`; `gorm.ErrRecordNotFound` → `ErrWorkflowNotFound`; read operations protected by `defer` + WrapIfNeeded + +--- + +## 5) Quick reference: what each layer does + +```mermaid +graph LR + A["Middleware"] --> B["Auth/session checks
401 / ErrUserAuthenticationFailed"] + C["Handler/API"] --> D["BindAndValidate + basic checks
HTTP 400 / invalidParamRequestResponse"] + E["Application"] --> F["Login/space permission
panic safety net; vo.WrapIfNeeded"] + G["Domain Service"] --> H["Invariants/state transitions
errorx.New(ErrXxx...) / ErrXxxDBCode"] + I["Repository/DAL"] --> J["Persistence only
Knowledge: pass-through gorm
Workflow: WrapError/WrapIfNeeded"] +``` + +## 6) Validation order & principles + +- Validation should be **progressive by layer** (strict-to-semantic), without repeating the exact same checks everywhere. +- Do not overuse `strings.TrimSpace()` just to “detect empty”; spaces may be valid input (e.g., passwords). Incorrect input should fail naturally. +- Avoid defensive `req == nil` checks in most paths; nil requests or nil injected dependencies should be caught during development, not handled repeatedly at runtime. +- Avoid redundant `v == nil { continue }` checks when iterating over `[]*Object` unless it is realistically possible. + +--- + +## 7) OpenAPI error-code mapping + +The Workflow domain maintains an internal errno → OpenAPI errno mapping table; external APIs should not expose internal workflow error codes directly. + +--- + +## Notes + +1. Two DAL styles are acceptable: Knowledge (pass-through) vs Workflow (wrap inside DAL). +2. `errorx.KV` vs `errorx.KVf`: KV replaces `{key}` with a value; KVf supports formatted values. +3. Workflow errors (`WorkflowError`) provide extra methods like `Level()`, `OpenAPICode()`, `AppendDebug()`, and are workflow-chain specific. +4. No bare errors in Application/Domain for expected business paths — always attach errno. +5. Prefer Wrap to preserve context for troubleshooting. + +--- + +## Citations + +See `backend/pkg/errorx/error.go`, `backend/pkg/errorx/code/register.go`, `backend/api/internal/httputil/error_resp.go`, `backend/api/handler/coze/base.go`, and [citations.md](citations.md). + diff --git a/docs/skills/coze-backend-go/skill-07-handler.md b/docs/skills/coze-backend-go/skill-07-handler.md new file mode 100644 index 0000000000..a95e5fdc0f --- /dev/null +++ b/docs/skills/coze-backend-go/skill-07-handler.md @@ -0,0 +1,30 @@ +# Skill 7: Handler conventions + +## Handler file naming + +`backend/api/handler/coze/{service_name}_service.go` (generated by IDL; the header contains `// Code generated by hertz generator.`) + +## Standard handler pattern + +```go +// @router /api/xxx/yyy [POST] +func XxxMethod(ctx context.Context, c *app.RequestContext) { + var req XxxRequest + if err := c.BindAndValidate(&req); err != nil { + invalidParamRequestResponse(c, err.Error()); return + } + // Parameter prechecks + if req.GetXxx() <= 0 { + invalidParamRequestResponse(c, "xxx is invalid"); return + } + // Call Application service + resp, err := xxx.ApplicationSVC.Method(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err); return + } + c.JSON(consts.StatusOK, resp) +} +``` + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-08-middleware.md b/docs/skills/coze-backend-go/skill-08-middleware.md new file mode 100644 index 0000000000..7963cca26e --- /dev/null +++ b/docs/skills/coze-backend-go/skill-08-middleware.md @@ -0,0 +1,29 @@ +# Skill 8: Middleware and session conventions + +## Global middleware order (must not be changed casually) + +```go +s.Use(middleware.ContextCacheMW()) // must be first +s.Use(middleware.RequestInspectorMW()) // must be second +s.Use(middleware.SetHostMW()) +s.Use(middleware.SetLogIDMW()) +s.Use(corsHandler) +s.Use(middleware.AccessLogMW()) +s.Use(middleware.OpenapiAuthMW()) +s.Use(middleware.SessionAuthMW()) +s.Use(middleware.I18nMW()) // must be after SessionAuthMW +``` + +## Session access conventions + +- Session data is stored in the context cache (key = `consts.SessionDataKeyInCtx`) +- Use `ctxutil.GetUIDFromCtx(ctx)` to safely get the UID (returns `*int64`) +- Use `ctxutil.MustGetUIDFromCtx(ctx)` to require the UID (panics if nil) + +## Space (multi-space) authorization + +- Middleware authenticates the user and injects session into context, but **space authorization is generally enforced in the Application layer** using `req.SpaceID` + user-space membership checks (e.g., `checkUserSpace` pattern in workflow application). +- Do not assume “logged-in” implies “can access any space”. For space-scoped resources, validate `uid ∈ space` before calling domain services / repositories. + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-09-crossdomain.md b/docs/skills/coze-backend-go/skill-09-crossdomain.md new file mode 100644 index 0000000000..0144cdd3cc --- /dev/null +++ b/docs/skills/coze-backend-go/skill-09-crossdomain.md @@ -0,0 +1,30 @@ +# Skill 9: crossdomain conventions + +## Purpose + +When Domain A needs to call Domain B, **do not directly depend** on B’s `service` package. Instead, call through the interfaces defined under: + +- `backend/crossdomain/{B}/contract.go` — cross-domain interface definitions (anti-corruption layer) +- `backend/crossdomain/{B}/impl/` — concrete implementations +- (common) `backend/crossdomain/{B}/model/` — cross-domain models (prefer stable shapes; avoid exposing Domain entities directly) +- (common) `backend/crossdomain/{B}/*mock/` — mocks (generated or handwritten, depending on the package) + +## Registration + +In `application.Init()`, register global singletons via `SetDefaultSVC(...)` (exact function names depend on each crossdomain package), wiring Domain services behind crossdomain contracts. + +## Shape (current repo) + +In `coze-studio` v0.5.1, crossdomain uses a **per-domain package** structure instead of a centralized `contract/` directory. A typical layout: + +``` +backend/crossdomain/{domain}/ + contract.go + impl/ + model/ + convert/ (optional) + {domain}mock/ (optional) +``` + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-10-dto.md b/docs/skills/coze-backend-go/skill-10-dto.md new file mode 100644 index 0000000000..555ebe3c5f --- /dev/null +++ b/docs/skills/coze-backend-go/skill-10-dto.md @@ -0,0 +1,18 @@ +# Skill 10: DTO conventions + +## Location + +`domain/{X}/dto/` — domain-scoped data transfer objects used within the Domain/Service layer. **Do not** reuse API-layer Request/Response structs here. + +## Layering quick map + +| Type | Location | Notes | +|------|----------|------| +| API Request/Response | `backend/api/model/` | Generated from IDL; HTTP layer only | +| Domain DTO | `domain/{X}/dto/` | Service input/output | +| Domain Entity | `domain/{X}/entity/` | Rich domain object with methods | +| DAL PO | `domain/{X}/internal/dal/model/*.gen.go` | Pure DB mapping; generated | +| crossdomain Model | `crossdomain/{X}/model/` | Shared structures across domains | + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-11-mock.md b/docs/skills/coze-backend-go/skill-11-mock.md new file mode 100644 index 0000000000..4413bbe63f --- /dev/null +++ b/docs/skills/coze-backend-go/skill-11-mock.md @@ -0,0 +1,30 @@ +# Skill 11: Mocks and tests + +## Generating mocks (project default: mockgen) + +This repo uses **`mockgen`** to generate mocks for Go interfaces. + +At the top of a Service interface file, add: + +```go +//go:generate mockgen -destination ../../../internal/mock/domain/plugin/interface.go --package mockPlugin -source service.go +``` + +Then run `go generate ./...` (or the project’s recommended generate command) to produce mock implementations. + +## Repository mocks + +Store repository-interface mocks under `repository/mock/` (generated by `mockgen`). + +## Test structure (AAA pattern) + +When writing unit tests, follow the AAA (Arrange–Act–Assert) pattern: + +- **Arrange**: set up mocks and inputs. +- **Act**: call the function or method under test. +- **Assert**: verify expectations via assertions. + +Use `_test.go` files colocated with the code under test, and keep test file names consistent with their corresponding source files. Prefer **table-driven tests** to cover multiple input/output scenarios in one place. + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-12-id.md b/docs/skills/coze-backend-go/skill-12-id.md new file mode 100644 index 0000000000..b88458a690 --- /dev/null +++ b/docs/skills/coze-backend-go/skill-12-id.md @@ -0,0 +1,9 @@ +# Skill 12: ID generation conventions + +- All business IDs are generated via `infra/idgen.IDGenerator` (Snowflake; distributed unique) +- ID type is unified as `int64` +- In IDL, any `i64` ID field must add `api.js_conv = "str"` (JavaScript numeric precision) +- ID generation must avoid reserved product IDs (see the retry logic of `genPluginID` in `dal/plugin_draft.go`) + +See [citations.md](citations.md) for code pointers. + diff --git a/docs/skills/coze-backend-go/skill-13-flowchart-generator.md b/docs/skills/coze-backend-go/skill-13-flowchart-generator.md new file mode 100644 index 0000000000..e58084557f --- /dev/null +++ b/docs/skills/coze-backend-go/skill-13-flowchart-generator.md @@ -0,0 +1,151 @@ +--- +name: flowchart-generator +description: > + Generate Mermaid 9.2.2-compatible flowcharts, sequence diagrams, and system architecture diagrams. + Use when the user asks to draw a flowchart/sequence diagram/architecture diagram, mentions Mermaid, + or needs a diagram to communicate a business or code flow. +--- + +# Flowchart Generator Skill + +This skill generates **Mermaid diagrams** compatible with **Mermaid 9.2.2**, focusing on flowcharts, sequence diagrams, and high-level architecture diagrams. + +Goals: +- Abstract a clear process/architecture from **requirements** or **existing code structure**. +- Output Mermaid syntax that renders in Markdown/Confluence. +- Keep diagrams **simple, layered, and easy to modify**. + +--- + +## When to use (triggers) + +Use this skill when the user asks for: +- “draw a flowchart / sequence diagram / architecture diagram / system design diagram” +- “express this workflow/system in Mermaid” +- “generate Mermaid I can paste into Confluence” +- “derive a call graph/module diagram from code structure” + +Keywords: `mermaid`, `flowchart`, `sequenceDiagram`, “system overview”, “architecture diagram”, etc. + +--- + +## Core principles + +1. **Abstract first, draw second** + - Identify actors/modules, key steps, inputs/outputs, then map into Mermaid. + - If using code as input, focus on boundaries and the “main trunk” call chain; don’t diagram every helper function. + +2. **Mermaid 9.2.2 compatibility** + - Prefer classic syntax: `graph LR` / `graph TD`, `sequenceDiagram`. + - **Do not use `\n` inside node labels**; keep a single line. If you need more info, shorten the label. + - One line per edge or node definition; avoid compact multi-target syntax like `A --> B C`. + - Keep edge labels short (`|Text|`), or omit them if the environment is strict. + - Avoid very new syntax features; stick to basic shapes. + +3. **Semantics > details** + - Target **10–20 nodes** for a system diagram; if larger, split into multiple diagrams. + - Name nodes as “role + key responsibility” (e.g., `TriggerRepository`, `WeworkMessageAggregator`). + - Avoid enumerating all fields/parameters; show the main interactions and directions. + +4. **Language handling** + - Mermaid node IDs should be ASCII-friendly (English or pinyin without spaces); labels can be localized. + +--- + +## Diagram types and templates + +### 1) System overview (component/module diagram) + +Use to show module boundaries and dependencies. + +```mermaid +graph LR +subgraph Admin[Admin module] + AdminUI[Trigger config UI] + AdminApp[Trigger Application] + TriggerRepo[TriggerRepository] + TriggerDB[(Trigger and Binding tables)] + TriggerCache[Trigger caches] + AdminUI --> AdminApp + AdminApp --> TriggerRepo + TriggerRepo --> TriggerDB + TriggerRepo --> TriggerCache +end +subgraph MsgBus[Message bus and callbacks] + WecomMQ[(MQ: WeCom messages)] + QybHandler[callbackHandler QYB] + JzhdHandler[callbackHandler JZHD] + WecomMQ --> QybHandler + WecomMQ --> JzhdHandler +end +``` + +Guidelines: +- Each `subgraph` is one domain/module. +- Declare nodes first, then connect them with simple arrows. +- Split the diagram if it becomes too large. + +### 2) Sequence diagram (`sequenceDiagram`) + +Use to show time-ordered interactions. + +```mermaid +sequenceDiagram + participant Client as Client + participant API as API Gateway + participant Service as Domain Service + participant Repo as Repository + participant MQ as Message Queue + Client ->> API: Request + API ->> Service: Validate and call + Service ->> Repo: Read or write + Repo -->> Service: Result + Service ->> MQ: Publish async message + Service -->> API: Sync response +``` + +Guidelines: +- Keep `participant` lines simple. +- Use `alt/else/end` only when branching is essential, and keep branch names short. +- Keep messages short (e.g., “Check cache”, “AppendMessage”, “AsyncExecuteWorkflow”). + +--- + +## Generating diagrams from code + +When the user asks to “generate a diagram from code”, follow: + +1. Identify domains/boundaries (directories/dependencies) and map them to `subgraph`. +2. Extract key roles: + - Core structs, key interfaces, and external dependencies (MQ topics, crossdomain services). +3. Build the main call chain (A → B → C → D) and only diagram that trunk. +4. Output style: + - One short sentence describing the diagram’s purpose. + - Then a Mermaid code block containing **only** the diagram (no extra narration inside). + +--- + +## Mermaid 9.2.2 compatibility checklist + +1. No `\n` in labels: + - Bad: `Node[Application\nTrigger Application]` + - Good: `Node[Application Trigger Application]` +2. One edge per line: + - Bad: `A --> B C` + - Good: + - `A --> B` + - `A --> C` +3. `subgraph`: + - Use `subgraph Name[Display Name]` ... `end` + - Stick to basic shapes: `[text]`, `(text)`, `((text))`, `[(text)]` +4. Edge labels optional: + - If strict renderers fail, remove labels and keep `A --> B`. + +--- + +## Suggested answer structure + +1. One short sentence describing what the diagram represents. +2. A Mermaid code block containing the diagram. +3. If the user’s renderer fails repeatedly, simplify progressively (remove labels → remove subgraphs → keep only basic nodes/edges). + diff --git a/docs/skills/coze-backend-go/skill-14-eino.md b/docs/skills/coze-backend-go/skill-14-eino.md new file mode 100644 index 0000000000..228e7a2924 --- /dev/null +++ b/docs/skills/coze-backend-go/skill-14-eino.md @@ -0,0 +1,25 @@ +# Skill 14: Eino (CloudWeGo) conventions (v0.5.1) + +## When you will touch Eino + +In `coze-studio/backend`, Eino is mainly used for: + +- **Streaming production/consumption**: `schema.StreamReader`, `schema.Message`, etc. (e.g., agent streaming events, conversation messages). +- **LLM/Embedding/RAG component abstractions**: `components/model`, `components/embedding`, `components/retriever/indexer`, etc. +- **Composition / workflow execution**: workflow domain uses `compose`, `schema`, etc. for node execution and callbacks. + +## Code distribution (practical buckets) + +- `backend/domain/workflow/**`: the heaviest usage (node execution, stream, compose, callbacks, schema). +- `backend/domain/agent/**`, `backend/domain/conversation/**`, `backend/domain/knowledge/**`: message/execution/retrieval-related code often references `schema`. +- `backend/infra/embedding/**`, `backend/infra/document/**`: Eino-ext component wrappers, RAG retrieval/parsing pipelines, etc. +- `backend/internal/mock/**`, `backend/domain/**/varmock/**`: mocks/tests around Eino-related interfaces/objects. + +## Conventions & boundaries + +- **Treat Eino as a component/protocol layer**: when Domain/Application uses Eino `schema`/interfaces, prefer existing project wrappers (e.g., `infra/embedding`, `bizpkg/llm`, or domain-level adapters) rather than assembling complex chains in handlers. +- **Avoid leaking complex Eino compositions through crossdomain**: crossdomain `contract.go` should expose stable business interfaces; inputs/outputs may carry necessary `schema.Message`/`StreamReader`, but do not leak Domain-internal node/compose structures. +- **Testability first**: depend on interfaces for external dependencies (models/embedding/retrieval), generate mocks under `internal/mock` (or domain mock dirs), and avoid `new`-ing concrete providers in business code. + +> Note: this skill is about *where to put code* and *how to keep it testable* in this repo, not a full Eino API tutorial. For `hz`/Eino details, defer to CloudWeGo official docs. +