Simplify system implementation

This commit is contained in:
yuxuanhui
2026-08-04 18:59:21 +08:00
parent bf8da90433
commit ee0064135c
19 changed files with 418 additions and 1169 deletions
@@ -0,0 +1,24 @@
# 配置与运行时
## 配置入口
进程配置集中在 `zhixing-server/src/zhixing_server/bootstrap/config.py:Settings`:
- 继承 `pydantic_settings.BaseSettings`,字段声明默认值和可接受类型。
- 通过 `SettingsConfigDict(env_file=".env", env_prefix="ZHIXING_", extra="ignore")` 读取环境变量;新增变量应遵循 `ZHIXING_<FIELD>` 命名。
- `app_env` 使用 `Literal["development", "test", "production"]`,不要在业务代码里重复字符串校验。
- `get_settings()` 使用 `@lru_cache`,在一个进程内复用同一配置对象。
端点通过 FastAPI 依赖注入拿配置,例如 `interfaces/http/system.py:system_status` 的 `Annotated[Settings, Depends(get_settings)]`。不要在路由或领域代码中直接读取 `os.environ`、`.env` 或 Docker 变量。
## 应用生命周期与部署
- `bootstrap/app.py:create_app` 创建 `FastAPI(title=settings.app_name)`,然后挂载 `operational_router` 和 `api_v1_router`。
- `main.py` 只导出 ASGI `app`,容器入口可以安全导入它。
- 健康检查固定为 `/healthz`;业务 API 固定挂在 `/api/v1` 下。
- 开发环境由 Vite 将 `/api` 和 `/healthz` 代理到 `VITE_DEV_API_TARGET`(默认 `http://localhost:8000`);生产环境由 `zhixing-web/nginx/default.conf.template` 以 `API_UPSTREAM` 代理。浏览器代码不要拼接后端绝对地址。
- Docker Compose 的环境变量和端口默认值见根目录 `.env.example`、`docker-compose.dev.yml` 和 `docker-compose.prod.yml`。
## 目前没有的能力
当前仓库没有数据库连接、迁移、任务队列或日志配置实现。不要只因为 `Settings.log_level` 字段存在就引入一套日志框架;如果新增持久化或结构化日志,应先形成可验证的实现和对应规格。
@@ -1,51 +0,0 @@
# Database Guidelines
> Database patterns and conventions for this project.
---
## Overview
<!--
Document your project's database conventions here.
Questions to answer:
- What ORM/query library do you use?
- How are migrations managed?
- What are the naming conventions for tables/columns?
- How do you handle transactions?
-->
(To be filled by the team)
---
## Query Patterns
<!-- How should queries be written? Batch operations? -->
(To be filled by the team)
---
## Migrations
<!-- How to create and run migrations -->
(To be filled by the team)
---
## Naming Conventions
<!-- Table names, column names, index names -->
(To be filled by the team)
---
## Common Mistakes
<!-- Database-related mistakes your team has made -->
(To be filled by the team)
+38 -42
View File
@@ -1,54 +1,50 @@
# Directory Structure
# 后端目录与模块边界
> How backend code is organized in this project.
## 当前布局
---
## Overview
<!--
Document your project's backend directory structure here.
Questions to answer:
- How are modules/packages organized?
- Where does business logic live?
- Where are API endpoints defined?
- How are utilities and helpers organized?
-->
(To be filled by the team)
---
## Directory Layout
```
<!-- Replace with your actual structure -->
src/
├── ...
└── ...
```text
zhixing-server/
├── src/zhixing_server/
│ ├── bootstrap/ # 应用工厂和进程配置
│ │ ├── app.py
│ │ └── config.py
│ ├── interfaces/http/ # 跨上下文 HTTP 路由目录
│ │ ├── router.py # /api/v1 路由目录
│ │ └── system.py # /healthz、/api/v1/system/status
│ ├── modules/ # 业务 bounded context(目前为空)
│ │ └── README.md
│ ├── shared/ # 没有特定业务所有权的小型基础能力
│ ├── main.py # ASGI 入口,只导出 app
│ └── __init__.py
└── tests/ # 按行为/入口组织的 pytest 测试
```
---
`zhixing-server/src/zhixing_server/bootstrap/app.py:create_app` 负责组合 FastAPI 应用,`main.py` 只执行 `app = create_app()`。不要把应用组合、环境读取或业务逻辑塞进 `main.py`。
## Module Organization
## 新增业务上下文
<!-- How should new features/modules be organized? -->
只有在领域语言和所有权边界明确后,才在 `modules/` 下创建上下文。推荐形状来自 `zhixing-server/src/zhixing_server/modules/README.md`:
(To be filled by the team)
```text
modules/<bounded_context>/
├── domain/ # 实体、值对象、领域服务、端口
├── application/ # 用例和编排
├── infrastructure/ # 存储及外部系统适配器
└── presentation/ # HTTP 或消息驱动的交付适配器
```
---
domain 层不能导入 FastAPI、持久化客户端或 infrastructure 适配器。上下文内的 presentation 负责把输入转换成用例需要的类型;顶层 `interfaces/http` 只承载跨上下文的运维入口和路由目录。
## Naming Conventions
## 命名与导入
<!-- File and folder naming rules -->
- Python 包、模块和函数使用 `snake_case`;测试文件使用 `test_*.py`,例如 `tests/test_system_http.py`。
- 对外公开的类、响应模型和函数应有类型标注与文档字符串,参照 `bootstrap/config.py` 和 `interfaces/http/system.py`。
- `shared/` 只能放跨上下文且无业务归属的原语;不要把某个上下文的领域规则放进去。
- 避免新建全局 `services/`、`repositories/` 或 `utils/` 目录来绕过上下文边界。
(To be filled by the team)
## 参考实现
---
## Examples
<!-- Link to well-organized modules as examples -->
(To be filled by the team)
- 应用组合:`zhixing-server/src/zhixing_server/bootstrap/app.py`
- 路由目录:`zhixing-server/src/zhixing_server/interfaces/http/router.py`
- 运维/系统端点:`zhixing-server/src/zhixing_server/interfaces/http/system.py`
- 当前 HTTP 契约测试:`zhixing-server/tests/test_system_http.py`
+15 -42
View File
@@ -1,51 +1,24 @@
# Error Handling
# 错误处理
> How errors are handled in this project.
## 当前行为
---
服务目前没有自定义异常层、全局异常处理器或统一错误 envelope。FastAPI/Pydantic 负责请求验证和标准 HTTP 错误响应;现有成功端点只返回声明过的响应模型。新增错误契约前,应先确认前端是否需要稳定的错误字段,并用 HTTP 契约测试锁定它。
## Overview
## 分层规则
<!--
Document your project's error handling conventions here.
- 在 HTTP 边界完成输入解析和验证,不要把 `Request`、`HTTPException` 或 FastAPI 依赖对象带入 domain。
- 领域或应用层的业务失败应使用可识别的领域异常(当该层实际出现时),由 presentation 层映射为 HTTP 响应;不要在 domain 层导入 FastAPI。
- 不能恢复的配置错误应在启动时暴露,而不是在每个端点里静默使用空值。`Settings` 的类型和默认值应让错误尽早发生。
- 不要捕获 `Exception` 后返回成功、空对象或吞掉 traceback。只有在能补充上下文、转换边界错误或保证资源清理时才捕获具体异常。
Questions to answer:
- What error types do you define?
- How are errors propagated?
- How are errors logged?
- How are errors returned to clients?
-->
## 前端边界
(To be filled by the team)
`zhixing-web/src/shared/api/request-json.ts:requestJson` 对非 2xx 响应抛出 `ApiError`,并保留 HTTP 状态码;React Query 通过 `isError` 将其传给 feature 页面。页面应显式渲染加载、错误和成功状态,参照 `features/system/pages/system-status-page.tsx`,不要把错误静默成“无数据”。
---
当前 `requestJson` 只对响应体做泛型断言,不做运行时 schema 校验。因此来自外部或不稳定来源的数据在进入页面前必须增加明确的运行时校验,不能把 TypeScript 类型当成网络验证。
## Error Types
## 测试要求
<!-- Custom error classes/types -->
(To be filled by the team)
---
## Error Handling Patterns
<!-- Try-catch patterns, error propagation -->
(To be filled by the team)
---
## API Error Responses
<!-- Standard error response format -->
(To be filled by the team)
---
## Common Mistakes
<!-- Error handling mistakes your team has made -->
(To be filled by the team)
- 对新增 HTTP 错误状态,优先在 `zhixing-server/tests/` 验证状态码和 JSON 契约。
- 对前端错误展示,在页面测试中 mock query hook 的 `isError`/`error` 状态并断言用户可见文本。
- 不要只测试异常类被抛出而不测试边界响应或用户行为。
@@ -0,0 +1,30 @@
# HTTP API 契约
## 路由组合
`zhixing-server/src/zhixing_server/interfaces/http/router.py` 是 `/api/v1` 的唯一目录入口:
```python
api_v1_router = APIRouter(prefix="/api/v1")
api_v1_router.include_router(system_router, prefix="/system", tags=["system"])
```
跨上下文的运维端点放在 `operational_router`;业务端点应由对应上下文的 presentation 适配器提供,再由路由目录挂载。不要在 `bootstrap/app.py` 里堆积路径字符串。
## 响应模型
每个稳定的 JSON 响应都应有 Pydantic `BaseModel`,并在装饰器中声明 `response_model`。现有 `HealthResponse` 和 `SystemStatusResponse` 位于 `interfaces/http/system.py`:
- `/healthz` 返回严格的 `{"status": "ok"}`,设置 `include_in_schema=False` 以供容器探针使用。
- `/api/v1/system/status` 返回 `status`、`service`、`environment`;只暴露安全的运行时元数据,不返回环境变量原文或秘密。
- 字段名是前后端契约的一部分。变更时同步更新 `zhixing-server/tests/test_system_http.py`、`zhixing-web/src/features/system/api/system.types.ts` 和页面测试。
## 输入、依赖和错误
- 通过 FastAPI 参数、`Annotated` 依赖和 Pydantic 模型完成边界转换;不要把原始请求对象传入 domain。
- 简单、无阻塞的当前端点使用同步 `def`,保持与 `system.py` 一致;只有实际需要异步 I/O 时才使用 `async def`。
- 当前服务没有自定义错误 envelope。验证错误和显式 HTTP 错误沿用 FastAPI 默认契约,除非先增加全局约定并补充测试。
## 验证
后端契约用 `TestClient(create_app())` 测试,参照 `tests/test_system_http.py`。前端使用同源 `/api/v1/...` 路径,经 `shared/api/request-json.ts` 调用;不要在 feature 页面内直接调用 `fetch`。
+24 -29
View File
@@ -1,38 +1,33 @@
# Backend Development Guidelines
# 后端开发规格
> Best practices for backend development in this project.
后端是 Python 3.12 + FastAPI 的单体服务。当前代码只包含启动组合、环境配置、运维端点和 `system` HTTP 切片;业务 bounded context 尚未创建。新增后端代码应先确认它属于启动边界、共享基础设施、HTTP 入口还是某个明确的 bounded context。
---
## 规格导航
## Overview
| 规格 | 用途 |
| --- | --- |
| [目录与模块边界](./directory-structure.md) | 包结构、bounded context 和导入边界 |
| [配置与运行时](./configuration-and-runtime.md) | `Settings`、应用工厂和部署环境 |
| [HTTP 契约](./http-api-contracts.md) | 路由组合、响应模型和同源 API 路径 |
| [错误处理](./error-handling.md) | 当前 FastAPI 错误行为及跨层错误传递 |
| [质量与测试](./quality-guidelines.md) | Ruff、Pyright、pytest 及禁止模式 |
This directory contains guidelines for backend development. Fill in each file with your project's specific conventions.
## 开发前检查
---
- 先阅读 `docs/adr/0001-bounded-context-first-modular-monolith.md`,确认新业务是否有清晰的语言和所有权边界。
- 先阅读目标上下文的 `modules/<bounded_context>/README.md`(如已存在),再决定 domain、application、infrastructure、presentation 的位置。
- 变更 HTTP 字段时同时检查 `zhixing-server/tests/`、前端 feature API 类型以及 `docs/adr/0002-use-a-same-origin-browser-api.md`。
- 不要为了“未来可能需要”创建空的数据库、服务或日志层;当前仓库没有这些实现。
## Guidelines Index
## 质量检查
| Guide | Description | Status |
|-------|-------------|--------|
| [Directory Structure](./directory-structure.md) | Module organization and file layout | To fill |
| [Database Guidelines](./database-guidelines.md) | ORM patterns, queries, migrations | To fill |
| [Error Handling](./error-handling.md) | Error types, handling strategies | To fill |
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill |
| [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels | To fill |
在 `zhixing-server/` 下运行:
---
```bash
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest
```
## How to Fill These Guidelines
For each guideline file:
1. Document your project's **actual conventions** (not ideals)
2. Include **code examples** from your codebase
3. List **forbidden patterns** and why
4. Add **common mistakes** your team has made
The goal is to help AI assistants and new team members understand how YOUR project works.
---
**Language**: All documentation should be written in **English**.
根目录 `./dev.sh check` 和 `./dev.sh test` 会执行完整前后端检查。
@@ -1,51 +0,0 @@
# Logging Guidelines
> How logging is done in this project.
---
## Overview
<!--
Document your project's logging conventions here.
Questions to answer:
- What logging library do you use?
- What are the log levels and when to use each?
- What should be logged?
- What should NOT be logged (PII, secrets)?
-->
(To be filled by the team)
---
## Log Levels
<!-- When to use each level: debug, info, warn, error -->
(To be filled by the team)
---
## Structured Logging
<!-- Log format, required fields -->
(To be filled by the team)
---
## What to Log
<!-- Important events to log -->
(To be filled by the team)
---
## What NOT to Log
<!-- Sensitive data, PII, secrets -->
(To be filled by the team)
+27 -40
View File
@@ -1,51 +1,38 @@
# Quality Guidelines
# 后端质量与测试
> Code quality standards for backend development.
## 工具链
---
`zhixing-server/pyproject.toml` 固定 Python 3.12,使用 `uv` 管理环境和锁文件。质量门禁由以下配置定义:
## Overview
- Ruff 格式化:100 列、双引号。
- Ruff lint:`B`, `E`, `F`, `I`, `SIM`, `UP`。
- Pyright:`strict`,检查 `src` 和 `tests`。
- Pytest:严格配置和严格 markers,测试目录为 `tests/`。
<!--
Document your project's quality standards here.
不要只在编辑器里运行局部检查;提交前至少执行:
Questions to answer:
- What patterns are forbidden?
- What linting rules do you enforce?
- What are your testing requirements?
- What code review standards apply?
-->
```bash
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest
```
(To be filled by the team)
## 代码规则
---
- 函数、方法和公开类使用完整类型标注;公共入口写清楚参数、返回值和边界原因,参照 `bootstrap/config.py`、`bootstrap/app.py`。
- HTTP 响应显式使用 Pydantic 模型和 `response_model`,不要返回随意拼接的 dict。
- 配置通过 `Settings` 注入,不直接读取进程环境;应用组合集中在 `create_app`。
- 领域代码不得依赖 FastAPI、持久化客户端或 infrastructure,符合 `modules/README.md` 的边界。
- 变更端点时同时更新行为测试和与之对应的前端契约。
## Forbidden Patterns
## 测试形状
<!-- Patterns that should never be used and why -->
`tests/test_system_http.py` 使用 `TestClient(create_app())` 从真实应用组合出发,断言状态码和完整 JSON。新增路由优先采用相同的黑盒 HTTP 测试;只有纯领域逻辑才单独测试函数或对象。
(To be filled by the team)
## 禁止模式
---
## Required Patterns
<!-- Patterns that must always be used -->
(To be filled by the team)
---
## Testing Requirements
<!-- What level of testing is expected -->
(To be filled by the team)
---
## Code Review Checklist
<!-- What reviewers should check -->
(To be filled by the team)
- 不要提交未使用的 import、宽泛 `except Exception`、无类型的公共参数或绕过 Pyright 的 `Any`。
- 不要把秘密、完整环境变量、请求凭据写进响应或日志;系统状态端点只返回安全元数据。
- 不要引入没有真实使用场景的空数据库/日志抽象,或在尚未理解 bounded context 前创建业务模块。
- 不要为了让测试通过复制 `create_app` 的路由逻辑;测试应调用实际应用工厂。
+37 -47
View File
@@ -1,59 +1,49 @@
# Component Guidelines
# 组件与样式
> How components are built in this project.
## 组件形状
---
React 模块使用具名函数组件,不使用无意义的 default export。`features/system/pages/system-status-page.tsx` 展示了页面组件如何组合 query、store 和 shared UI;`shared/ui/card.tsx` 展示了通过原生 HTML 属性扩展 primitive 的方式。
## Overview
```tsx
export function Button({
className,
size,
type = "button",
variant,
...props
}: ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>) {
return (
<button
className={cn(buttonVariants({ size, variant }), className)}
type={type}
{...props}
/>
)
}
```
<!--
Document your project's component conventions here.
可复用组件应保留原生属性和 `className`,通过 `...props` 支持组合;只在确实需要时添加受限的 variant。
Questions to answer:
- What component patterns do you use?
- How are props defined?
- How do you handle composition?
- What accessibility standards apply?
-->
## 样式与组合
(To be filled by the team)
- 使用 Tailwind utility class;主题 token 和全局基础规则放在 `src/styles/globals.css`。
- 使用 `cn`(`shared/ui/utils.ts`)合并可选 class,使用 `class-variance-authority` 管理 Button、Badge 等有限变体。
- 复杂页面用 `Card`、`CardHeader`、`CardContent` 等 composition primitive,而不是复制一套容器样式。
- 新建 shared primitive 前先搜索是否已有 `Button`、`Badge`、`Card` 或 `cn`。
---
## 页面状态
## Component Structure
页面应把加载、错误、成功状态转成用户可见的语义文本。`SystemStatusPage` 根据 query 状态显示“正在连接”“连接异常”“运行正常”,没有数据时使用安全的默认服务名。
<!-- Standard structure of a component file -->
## 可访问性
(To be filled by the team)
- 交互元素必须使用真实的 `<button>` 或其他语义元素;`Button` 默认 `type="button"` 以避免意外提交。
- 只有图形含义的 icon 使用 `aria-hidden="true"`;没有文字的主题切换按钮提供 `aria-label="切换主题"`,参照 `system-status-page.tsx`。
- 使用 `main`、标题、段落等语义结构,文本状态不能只靠颜色表达。
---
## 避免
## Props Conventions
<!-- How props should be defined and typed -->
(To be filled by the team)
---
## Styling Patterns
<!-- How styles are applied (CSS modules, styled-components, Tailwind, etc.) -->
(To be filled by the team)
---
## Accessibility
<!-- A11y requirements and patterns -->
(To be filled by the team)
---
## Common Mistakes
<!-- Component-related mistakes your team has made -->
(To be filled by the team)
- 不要在 shared UI 中请求数据、读取 feature hook 或写业务分支。
- 不要用 `dangerouslySetInnerHTML`、无理由的 `any` 或无语义的 `<div onClick>`。
- 不要把所有页面样式搬进新的全局 CSS;优先使用现有 token 和局部 utility class。
+32 -43
View File
@@ -1,54 +1,43 @@
# Directory Structure
# 前端目录与 feature 边界
> How frontend code is organized in this project.
## 当前布局
---
## Overview
<!--
Document your project's frontend directory structure here.
Questions to answer:
- Where do components live?
- How are features/modules organized?
- Where are shared utilities?
- How are assets organized?
-->
(To be filled by the team)
---
## Directory Layout
```
<!-- Replace with your actual structure -->
src/
├── ...
└── ...
```text
zhixing-web/src/
├── app/ # Provider、Router、QueryClient、应用壳
│ ├── app.tsx
│ ├── providers.tsx
│ ├── query-client.ts
│ └── router.tsx
├── routes/route-tree.tsx # TanStack Router 路由树
├── features/system/ # system 垂直切片
│ ├── api/ # request adapter、query、API 类型
│ └── pages/ # 页面组件和同目录测试
├── shared/
│ ├── api/request-json.ts # 同源 JSON transport 和 ApiError
│ ├── config/ui-store.ts # 持久化 UI 偏好
│ └── ui/ # 可复用的 Button、Card、Badge、cn
├── styles/globals.css # Tailwind 主题和全局基础样式
└── test/setup.ts # Vitest + Testing Library 初始化
```
---
`@/*` 映射到 `src/*`,在源码跨目录导入时使用该 alias;配置文件和同目录相对导入保持现有风格。
## Module Organization
## Feature 组织
<!-- How should new features be organized? -->
每个业务 feature 将 API 适配、类型和页面放在自己的目录,例如 `features/system/api/system.api.ts`、`system.query.ts`、`system.types.ts` 和 `pages/system-status-page.tsx`。页面可以依赖本 feature 的 API 和 `shared`,但 `shared` 不能反向依赖 feature。
(To be filled by the team)
路由只负责把路径映射到页面。当前 `routes/route-tree.tsx` 将 `/` 映射到 `SystemStatusPage`;不要把请求逻辑或全局状态初始化塞进路由声明。
---
## 命名
## Naming Conventions
- React 组件和页面使用 PascalCase 导出,文件使用 kebab-case,例如 `system-status-page.tsx`。
- feature API 文件按职责使用 `*.api.ts`、`*.query.ts`、`*.types.ts`。
- shared UI primitive 使用小写文件名并导出 PascalCase 组件,例如 `button.tsx` 导出 `Button`。
- 测试与被测模块同目录,使用 `.test.tsx` 或 `.test.ts`。
<!-- File and folder naming rules -->
## 反模式
(To be filled by the team)
---
## Examples
<!-- Link to well-organized modules as examples -->
(To be filled by the team)
- 不要创建一个全局 `components/`、`hooks/` 或 `services/` 目录来掩盖 feature 所有权。
- 不要让页面直接 import 远端 URL、调用 `fetch` 或保存 React Query 数据到 Zustand。
- 不要通过 `../../..` 穿透 feature 边界;优先使用 `@/features/...` 或 `@/shared/...`。
+29 -39
View File
@@ -1,51 +1,41 @@
# Hook Guidelines
# Hook 与数据请求
> How hooks are used in this project.
## API 适配三件套
---
服务端数据按 feature 放置为 API 函数、query hook 和类型文件:
## Overview
1. `features/system/api/system.types.ts` 定义 `SystemStatus`。
2. `features/system/api/system.api.ts:getSystemStatus` 调用 shared transport,并把 `AbortSignal` 传给 `fetch`。
3. `features/system/api/system.query.ts:useSystemStatus` 暴露 React Query hook,并使用 `systemStatusQueryKey`。
<!--
Document your project's hook conventions here.
页面只调用 `useSystemStatus`,不直接调用 `fetch`。新增 feature 应保持同样分层。
Questions to answer:
- What custom hooks do you have?
- How do you handle data fetching?
- What are the naming conventions?
- How do you share stateful logic?
-->
```ts
export const systemStatusQueryKey = ["system", "status"] as const
(To be filled by the team)
export function useSystemStatus() {
return useQuery({
queryFn: ({ signal }) => getSystemStatus(signal),
queryKey: systemStatusQueryKey,
})
}
```
---
## React Query
## Custom Hook Patterns
- 服务器状态由 TanStack Query 管理;公共默认值在 `app/query-client.ts`(不跟随窗口刷新、失败重试 1 次、`staleTime` 30 秒)。
- query key 使用 `as const` 常量,避免页面散落字符串。
- 使用 query function 提供的 `signal` 支持取消请求;不要忽略它或在页面手写生命周期 fetch。
- 页面显式处理 `isPending`、`isError` 和 `data`,参照 `SystemStatusPage`。
<!-- How to create and structure custom hooks -->
## Hook 命名与副作用
(To be filled by the team)
- 自定义 hook 以 `use` 开头并表达资源或行为,例如 `useSystemStatus`、`useUiStore`。
- 纯数据 hook 不执行额外副作用;需要同步 DOM 的副作用集中在 `app/providers.tsx:ThemeEffect`,由 Zustand 主题驱动 `document.documentElement` 的 class。
- 不要把一个 hook 同时用作服务器缓存和 UI 偏好存储;两者分别使用 React Query 与 Zustand。
---
## 反模式
## Data Fetching
<!-- How data fetching is handled (React Query, SWR, etc.) -->
(To be filled by the team)
---
## Naming Conventions
<!-- Hook naming rules (use*, etc.) -->
(To be filled by the team)
---
## Common Mistakes
<!-- Hook-related mistakes your team has made -->
(To be filled by the team)
- 不要在组件中直接 `fetch`、重复设置 query key 或把响应复制到本地 `useState`。
- 不要用 Zustand 保存 API 响应以“共享”数据,也不要用 React Query 保存主题等本地偏好。
- 不要为了复用一次性的 `useEffect` 创建泛化 hook;先确认是否已有跨页面重复模式。
+26 -30
View File
@@ -1,39 +1,35 @@
# Frontend Development Guidelines
# 前端开发规格
> Best practices for frontend development in this project.
前端是 `zhixing-web/` 下的 React 19 + TypeScript + Vite 应用,使用 TanStack Router、TanStack Query、Zustand、Tailwind CSS 4 和 Vitest。代码采用 feature 垂直切片;当前只有 `system` feature。
---
## 规格导航
## Overview
| 规格 | 用途 |
| --- | --- |
| [目录与 feature 边界](./directory-structure.md) | `app`、`routes`、`features`、`shared` 的职责 |
| [组件与样式](./component-guidelines.md) | 函数组件、组合、Tailwind、可访问性 |
| [Hook 与数据请求](./hook-guidelines.md) | React Query API 适配和 hook 结构 |
| [状态管理](./state-management.md) | Query、Zustand 与局部状态的边界 |
| [类型安全](./type-safety.md) | 严格 TypeScript、API 类型和类型断言 |
| [质量与测试](./quality-guidelines.md) | Prettier、ESLint、Vitest、构建门禁 |
This directory contains guidelines for frontend development. Fill in each file with your project's specific conventions.
## 开发前检查
---
- 先判断代码属于某个 feature 还是跨 feature 的 shared 能力;不要把业务代码放进 `shared/`。
- API 字段变更时,同时检查后端 Pydantic 响应模型、`features/<name>/api/*.types.ts`、query hook 和页面测试。
- 通过 `/api/v1` 同源路径请求后端。开发代理见 `vite.config.ts`,生产代理见 `nginx/default.conf.template`。
- 先搜索已有的 `cn`、`requestJson`、query key 和 UI primitive,再创建新 helper。
## Guidelines Index
## 质量检查
| Guide | Description | Status |
|-------|-------------|--------|
| [Directory Structure](./directory-structure.md) | Module organization and file layout | To fill |
| [Component Guidelines](./component-guidelines.md) | Component patterns, props, composition | To fill |
| [Hook Guidelines](./hook-guidelines.md) | Custom hooks, data fetching patterns | To fill |
| [State Management](./state-management.md) | Local state, global state, server state | To fill |
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill |
| [Type Safety](./type-safety.md) | Type patterns, validation | To fill |
在 `zhixing-web/` 下运行:
---
```bash
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```
## How to Fill These Guidelines
For each guideline file:
1. Document your project's **actual conventions** (not ideals)
2. Include **code examples** from your codebase
3. List **forbidden patterns** and why
4. Add **common mistakes** your team has made
The goal is to help AI assistants and new team members understand how YOUR project works.
---
**Language**: All documentation should be written in **English**.
根目录 `./dev.sh check` 和 `./dev.sh test` 会执行完整前后端检查。
+25 -41
View File
@@ -1,51 +1,35 @@
# Quality Guidelines
# 前端质量与测试
> Code quality standards for frontend development.
## 工具链与门禁
---
`zhixing-web/package.json` 定义以下命令:
## Overview
- `pnpm format:check`:Prettier 格式检查。
- `pnpm lint`:ESLint,`--max-warnings=0`;React Hooks 规则启用。
- `pnpm typecheck`:`tsc -b --pretty false`。
- `pnpm test`:Vitest 单次运行。
- `pnpm build`:TypeScript project build 后执行 Vite build。
- `pnpm check`:按 format、lint、typecheck、test 的顺序执行。
<!--
Document your project's quality standards here.
提交前运行 `pnpm check`,需要验证产物时再运行 `pnpm build`。根目录 `./dev.sh check` 会把前端检查与后端检查串起来。
Questions to answer:
- What patterns are forbidden?
- What linting rules do you enforce?
- What are your testing requirements?
- What code review standards apply?
-->
## 测试形状
(To be filled by the team)
- Vitest 使用 `jsdom`,公共初始化在 `src/test/setup.ts`,加载 `@testing-library/jest-dom/vitest`。
- React 页面使用 Testing Library 从用户可见行为断言,参照 `features/system/pages/system-status-page.test.tsx`。
- 页面测试通过 `vi.mock` 替换 feature query hook,并在 `beforeEach` 设置稳定的 query 返回值;测试渲染和文案,不测试 React Query 内部实现。
- 新增加载、错误或空数据分支时,至少为关键用户可见状态添加测试。
---
## 代码审查检查项
## Forbidden Patterns
- API 请求是否仍经过 `shared/api/request-json.ts`,并使用同源 `/api/v1` 路径?
- 服务器状态是否留在 React Query,UI 偏好是否只放入必要的 Zustand store?
- 是否保持 strict TypeScript、无 unused、无 lint warning?
- 交互元素是否有语义标签、键盘可用性和必要的 aria 文本?
- feature/shared 边界是否清楚,是否复用了现有 `cn` 和 UI primitive?
<!-- Patterns that should never be used and why -->
## 禁止模式
(To be filled by the team)
---
## Required Patterns
<!-- Patterns that must always be used -->
(To be filled by the team)
---
## Testing Requirements
<!-- What level of testing is expected -->
(To be filled by the team)
---
## Code Review Checklist
<!-- What reviewers should check -->
(To be filled by the team)
- 不要提交格式化、lint、类型检查或测试失败的代码,也不要用 `eslint-disable`/`@ts-ignore` 隐藏问题而不说明原因。
- 不要用实现细节选择器(例如依赖 class 名)替代 Testing Library 的角色、文本或可访问名称。
- 不要在测试中复制被测逻辑或只断言组件成功挂载;断言真实用户可见结果。
+23 -38
View File
@@ -1,51 +1,36 @@
# State Management
# 状态管理
> How state is managed in this project.
## 三类状态
---
| 状态 | 当前方案 | 示例 |
| --- | --- | --- |
| 服务器状态 | TanStack Query | `useSystemStatus` 和 `queryClient` |
| 跨页面 UI 偏好 | Zustand + `persist` | `useUiStore.theme` |
| 组件瞬时状态 | React 自带状态/事件 | 当前页面暂无复杂局部状态 |
## Overview
URL/路由状态由 TanStack Router 承担;当前路由树只有静态 `/`,不要为了静态页面额外引入全局 store。
<!--
Document your project's state management conventions here.
## Zustand 使用边界
Questions to answer:
- What state management solution do you use?
- How is local vs global state decided?
- How do you handle server state?
- What are the patterns for derived state?
-->
`shared/config/ui-store.ts` 只保存主题这一类跨页面 UI 偏好,并以 `zhixing-ui` 持久化到浏览器存储。组件读取最小 selector:
(To be filled by the team)
```tsx
const theme = useUiStore((state) => state.theme)
const toggleTheme = useUiStore((state) => state.toggleTheme)
```
---
新增全局字段前,确认它需要跨多个页面共享且不属于服务端缓存;否则优先放在组件局部或 URL。
## State Categories
## 服务器状态
<!-- Local state, global state, server state, URL state -->
所有 API 数据都通过 `queryClient` 和 feature query hook 管理,利用缓存、重试和失效机制。页面不应把 `data` 再写入 Zustand 或重复维护 `loading` 标志。
(To be filled by the team)
## 副作用
---
主题 class 的 DOM 同步集中在 `AppProviders` 内的 `ThemeEffect`,并依赖 store selector。不要在每个页面分别切换 `document.documentElement`,也不要在渲染阶段直接修改 DOM。
## When to Use Global State
## 常见错误
<!-- Criteria for promoting state to global -->
(To be filled by the team)
---
## Server State
<!-- How server data is cached and synchronized -->
(To be filled by the team)
---
## Common Mistakes
<!-- State management mistakes your team has made -->
(To be filled by the team)
- 把后端响应、错误对象或加载状态复制进 Zustand。
- 通过 `useUiStore((state) => state)` 订阅整个 store,造成无关更新。
- 为一个页面才能使用的开关添加持久化全局状态。
+16 -42
View File
@@ -1,51 +1,25 @@
# Type Safety
# TypeScript 类型安全
> Type safety patterns in this project.
## 编译约束
---
`tsconfig.app.json` 开启 `strict`、`noUnusedLocals`、`noUnusedParameters`、`noFallthroughCasesInSwitch`、`forceConsistentCasingInFileNames`,模块解析为 `Bundler`。新代码应在这些约束下编译,不要通过放宽配置来消除错误。
## Overview
## 类型放置
<!--
Document your project's type safety conventions here.
- 远端响应类型放在所属 feature 的 `api/*.types.ts`,例如 `SystemStatus`;不要在页面内重复声明同一 JSON 形状。
- shared transport 使用泛型 `requestJson<T>`,feature API 负责传入 feature 类型。
- 组件 props 优先复用 React 原生属性并组合库类型,参照 `ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<...>`。
- 只在需要类型本身时使用 `import type`,保持当前模块风格。
Questions to answer:
- What type system do you use?
- How are types organized?
- What validation library do you use?
- How do you handle type inference?
-->
## API 契约
(To be filled by the team)
`SystemStatus.status` 使用字面量类型 `"ok"`,表示后端契约中稳定的枚举值。新增枚举或可选字段时,先确认后端响应模型、错误语义和页面分支,再更新类型和测试。
---
`requestJson` 在 JSON 边界使用 `(await response.json()) as T`;这是当前唯一集中的网络断言。因为它没有运行时 schema 校验,不能把该泛型当成外部输入验证。若数据源不受同一仓库契约控制,应在 feature API 层加入显式解析/校验。
## Type Organization
## 禁止模式
<!-- Where types are defined, shared types vs local types -->
(To be filled by the team)
---
## Validation
<!-- Runtime validation patterns (Zod, Yup, io-ts, etc.) -->
(To be filled by the team)
---
## Common Patterns
<!-- Type utilities, generics, type guards -->
(To be filled by the team)
---
## Forbidden Patterns
<!-- any, type assertions, etc. -->
(To be filled by the team)
- 不要使用 `any`、`@ts-ignore` 或无解释的 `as` 来绕过严格检查。
- 不要把 `Record<string, unknown>` 当作所有 API 的默认类型;为稳定响应定义具名 interface/type。
- 不要在页面中用字符串索引访问未知字段,或把 API 响应重复 cast 成不同形状。
- 只有配置工具需要 default export;React feature/shared 模块沿用当前具名导出风格。
+20 -212
View File
@@ -1,223 +1,31 @@
# Code Reuse Thinking Guide
# 代码复用思考指南
> **Purpose**: Stop and think before creating new code - does it already exist?
## 什么时候先搜索
---
## The Problem
**Duplicated code is the #1 source of inconsistency bugs.**
When you copy-paste or rewrite existing logic:
- Bug fixes don't propagate
- Behavior diverges over time
- Codebase becomes harder to understand
---
## Before Writing New Code
### Step 1: Search First
在创建 helper、常量、转换函数或新的 UI primitive 前,先搜索这些本地模式:
```bash
# Search for similar function names
grep -r "functionName" .
# Search for similar logic
grep -r "keyword" .
rg "requestJson|ApiError" zhixing-web/src
rg "QueryKey|queryKey|useQuery" zhixing-web/src
rg "className|cva\(|cn\(" zhixing-web/src/shared zhixing-web/src/features
rg "create_app|response_model|Depends\(" zhixing-server/src zhixing-server/tests
```
### Step 2: Ask These Questions
当前仓库已经有明确的复用点:
| Question | If Yes... |
|----------|-----------|
| Does a similar function exist? | Use or extend it |
| Is this pattern used elsewhere? | Follow the existing pattern |
| Could this be a shared utility? | Create it in the right place |
| Am I copying code from another file? | **STOP** - extract to shared |
- 网络 JSON 请求统一经 `zhixing-web/src/shared/api/request-json.ts`,不要在页面重新实现 `fetch` 和错误判断。
- Tailwind class 合并统一经 `zhixing-web/src/shared/ui/utils.ts:cn`;有限变体用 `cva`,参照 `button.tsx` 和 `badge.tsx`。
- 服务端应用组合统一经 `zhixing-server/src/zhixing_server/bootstrap/app.py:create_app`,测试也调用真实工厂。
- React Query key 以 feature 内 `as const` 常量维护,参照 `system.query.ts`。
---
## 复用与边界
## Common Duplication Patterns
- 只有跨 feature、无业务所有权的能力才进入 `shared/`;一次性页面逻辑留在 feature。
- 看到两处相似代码时,先确认输入、错误语义和生命周期是否真的相同,再抽象;不要为了消除两行重复创建泛化框架。
- API 类型和领域概念不能因为“看起来相同”就自动合并。后端 Pydantic 模型、feature TypeScript 类型和 UI view model 各自承担边界责任。
### Pattern 1: Copy-Paste Functions
## 验证问题
**Bad**: Copying a validation function to another file
**Good**: Extract to shared utilities, import where needed
### Pattern 2: Similar Components
**Bad**: Creating a new component that's 80% similar to existing
**Good**: Extend existing component with props/variants
### Pattern 3: Repeated Constants
**Bad**: Defining the same constant in multiple files
**Good**: Single source of truth, import everywhere
### Pattern 4: Repeated Payload Field Extraction
**Bad**: Multiple consumers cast the same JSON/event fields locally:
```typescript
const description = (ev as { description?: string }).description;
const context = (ev as { context?: ContextEntry[] }).context;
```
This is duplicated contract logic even when the code is only two lines. Each
consumer now has its own definition of what a valid payload means.
**Good**: Put the decoder, type guard, or projection next to the data owner:
```typescript
if (isThreadEvent(ev)) {
renderThreadEvent(ev);
}
```
**Rule**: If the same untyped payload field is read in 2+ places, create a
shared type guard / normalizer / projection before adding a third reader.
---
## When to Abstract
**Abstract when**:
- Same code appears 3+ times
- Logic is complex enough to have bugs
- Multiple people might need this
**Don't abstract when**:
- Only used once
- Trivial one-liner
- Abstraction would be more complex than duplication
---
## After Batch Modifications
When you've made similar changes to multiple files:
1. **Review**: Did you catch all instances?
2. **Search**: Run grep to find any missed
3. **Consider**: Should this be abstracted?
### Reducers Should Use Exhaustive Structure
When state is derived from action-like values (`action`, `kind`, `status`,
`phase`), prefer a reducer with one `switch` over scattered `if/else` updates.
```typescript
// BAD - action-specific state transitions are hard to audit
if (action === "opened") { ... }
else if (action === "comment") { ... }
else if (action === "status") { ... }
// GOOD - one reducer owns the transition table
switch (event.action) {
case "opened":
...
return;
case "comment":
...
return;
}
```
This matters when the event log is the source of truth. A reducer is the
documented replay model; display code and commands should not duplicate pieces
of that replay model.
---
## Checklist Before Commit
- [ ] Searched for existing similar code
- [ ] No copy-pasted logic that should be shared
- [ ] No repeated untyped payload field extraction outside a shared decoder
- [ ] Constants defined in one place
- [ ] Similar patterns follow same structure
- [ ] Reducer/action transitions live in one reducer or command dispatcher
---
## Gotcha: Python if/elif/else Exhaustive Check
**Problem**: Python's if/elif/else chains have no compile-time exhaustive check. When you add a new value to a `Literal` type (e.g., `Platform`), existing if/elif/else chains silently fall through to `else` with wrong defaults.
**Symptom**: New platform works partially — some methods return Claude defaults instead of platform-specific values. No error is raised.
**Example** (`cli_adapter.py`):
```python
# BAD: "gemini" falls through to else, returns "claude"
@property
def cli_name(self) -> str:
if self.platform == "opencode":
return "opencode"
else:
return "claude" # gemini silently gets "claude"!
# GOOD: explicit branch for every platform
@property
def cli_name(self) -> str:
if self.platform == "opencode":
return "opencode"
elif self.platform == "gemini":
return "gemini"
else:
return "claude"
```
**Prevention**: When adding a new value to a Python `Literal` type, search for ALL if/elif/else chains that switch on that type and add explicit branches. Don't rely on `else` being correct for new values.
---
## Gotcha: Asymmetric Mechanisms Producing Same Output
**Problem**: When two different mechanisms must produce the same file set (e.g., recursive directory copy for init vs. manual `files.set()` for update), structural changes (renaming, moving, adding subdirectories) only propagate through the automatic mechanism. The manual one silently drifts.
**Symptom**: Init works perfectly, but update creates files at wrong paths or misses files entirely.
**Prevention**:
- **Best**: Eliminate the asymmetry — have the manual path call the automatic one (e.g., `collectTemplateFiles()` calls `getAllScripts()` instead of maintaining its own list)
- **If asymmetry is unavoidable**: Add a regression test that compares outputs from both mechanisms
- When migrating directory structures, search for ALL code paths that reference the old structure
**Real example**: `trellis update` had a manual `files.set()` list for 11 scripts that `getAllScripts()` already tracked. Fix: replaced the manual list with a `for..of getAllScripts()` loop. See `update.ts` refactor in v0.4.0-beta.3.
---
## Template File Registration (Trellis-specific)
When adding new files to `src/templates/trellis/scripts/`:
**Single registration point**: `src/templates/trellis/index.ts`
1. Add `export const xxxScript = readTemplate("scripts/path/file.py");`
2. Add to `getAllScripts()` Map
That's it. `commands/update.ts` uses `getAllScripts()` directly — no manual sync needed.
**Why this matters**: Without registration in `getAllScripts()`, `trellis update` won't sync the file to user projects. Bug fixes and features won't propagate.
**History**: Before v0.4.0-beta.3, `update.ts` had its own hand-maintained file list that frequently fell out of sync with `getAllScripts()`. This caused 11 Python files to be silently skipped during `trellis update`. The fix was to eliminate the duplicate list and use `getAllScripts()` as the single source of truth.
### Quick Checklist for New Scripts
```bash
# After adding a new .py file, verify it's in getAllScripts():
grep -l "newFileName" src/templates/trellis/index.ts # Should match
```
### Template Sync Convention
`.trellis/scripts/` (dogfooded) and `packages/cli/src/templates/trellis/scripts/` (template) must stay identical. After editing `.trellis/scripts/`, always sync:
```bash
rsync -av --delete --exclude='__pycache__' .trellis/scripts/ packages/cli/src/templates/trellis/scripts/
```
**Gotcha**: Running rsync with wrong source/destination paths can create nested garbage directories (e.g., `.trellis/scripts/packages/cli/...`). Always double-check paths before running.
- 新 helper 是否能被现有测试直接覆盖?
- 抽象后是否让导入方向更清楚,而不是引入 shared → feature 反向依赖?
- 是否保留了 `AbortSignal`、错误状态、可访问性和类型约束等原有行为?
@@ -1,327 +1,39 @@
# Cross-Layer Thinking Guide
# 跨层契约思考指南
> **Purpose**: Think through data flow across layers before implementing.
本仓库的最小链路是:
---
## The Problem
**Most bugs happen at layer boundaries**, not within layers.
Common cross-layer bugs:
- API returns format A, frontend expects format B
- Database stores X, service transforms to Y, but loses data
- Multiple layers implement the same logic differently
---
## Before Implementing Cross-Layer Features
### Step 1: Map the Data Flow
Draw out how data moves:
```
Source → Transform → Store → Retrieve → Transform → Display
```text
FastAPI 路由
→ Pydantic response model
→ 同源 /api/v1 代理
→ requestJson<T>
→ feature *.types.ts / *.api.ts
→ React Query hook
→ 页面加载、错误、成功状态
```
For each arrow, ask:
以系统状态为例,链路对应:
- What format is the data in?
- What could go wrong?
- Who is responsible for validation?
- 后端:`interfaces/http/router.py` 挂载 `/api/v1/system/status`,`interfaces/http/system.py` 返回 `SystemStatusResponse`。
- 后端测试:`tests/test_system_http.py` 断言状态码和完整 JSON。
- 前端:`features/system/api/system.api.ts` 使用 `/api/v1/system/status`,`system.types.ts` 描述字段,`system.query.ts` 管理缓存。
- 页面测试:`system-status-page.test.tsx` 断言“运行正常”和环境文本。
### Step 2: Identify Boundaries
## 修改 HTTP 字段前
| Boundary | Common Issues |
| --------------------- | --------------------------------- |
| API ↔ Service | Type mismatches, missing fields |
| Service ↔ Database | Format conversions, null handling |
| Backend ↔ Frontend | Serialization, date formats |
| Component ↔ Component | Props shape changes |
1. 找到后端响应模型、路由和现有契约测试。
2. 找到 feature API 函数、TypeScript 类型、query hook 和页面分支。
3. 确认开发 Vite 代理和生产 Nginx 仍覆盖该路径;浏览器代码保持同源路径。
4. 同步更新后端 HTTP 测试和前端行为测试,再运行两端质量命令。
### Step 3: Define Contracts
## 常见跨层遗漏
For each boundary:
- 只修改 Pydantic 字段,没有修改 `*.types.ts`,导致 UI 仍读取旧形状。
- 把 `/api/v1` 改成后端绝对 URL,绕过 `vite.config.ts` 和 Nginx 的同源代理。
- 在页面内直接 `fetch`,绕过 `requestJson` 的 `ApiError` 和 `AbortSignal`。
- 用 Zustand 缓存服务器响应,造成 React Query 缓存与全局 store 的双重事实来源。
- 只测试请求成功,没有测试 `isPending`、`isError` 或字段缺失时的安全展示。
- What is the exact input format?
- What is the exact output format?
- What errors can occur?
## 边界验证
---
## Common Cross-Layer Mistakes
### Mistake 1: Implicit Format Assumptions
**Bad**: Assuming date format without checking
**Good**: Explicit format conversion at boundaries
### Mistake 2: Scattered Validation
**Bad**: Validating the same thing in multiple layers
**Good**: Validate once at the entry point
### Mistake 3: Leaky Abstractions
**Bad**: Component knows about database schema
**Good**: Each layer only knows its neighbors
### Mistake 4: Every Consumer Parses The Same Payload
**Bad**: A command reads JSONL events and casts fields inline:
```typescript
const thread = (ev as { thread?: string }).thread;
const labels = (ev as { labels?: string[] }).labels;
```
This looks local, but it means every consumer owns a private version of the
event contract. The next field change will update one command and miss another.
**Good**: Decode once at the event boundary, then export typed projections:
```typescript
if (!isThreadEvent(ev)) return false;
return ev.thread === filter.thread;
```
**Rule**: For append-only logs, JSON streams, RPC payloads, or config files,
create one owner for:
- event / payload type definitions
- type guards and normalization from `unknown`
- metadata projections used by UI commands
- reducers that replay state from the source of truth
Rendering code may format fields, but it must not redefine the payload contract.
---
## Checklist for Cross-Layer Features
Before implementation:
- [ ] Mapped the complete data flow
- [ ] Identified all layer boundaries
- [ ] Defined format at each boundary
- [ ] Decided where validation happens
After implementation:
- [ ] Tested with edge cases (null, empty, invalid)
- [ ] Verified error handling at each boundary
- [ ] Checked data survives round-trip
- [ ] Checked that consumers import shared decoders / projections instead of
casting payload fields locally
- [ ] Checked that derived state points back to the source event identifier
(`seq`, `id`, `version`) instead of inventing a second cursor
---
## Cross-Platform Template Consistency
In Trellis, command templates (e.g., `record-session.md`) exist in **multiple platforms** with identical or near-identical content. This is a cross-layer boundary.
### Checklist: After Modifying Any Command Template
- [ ] Find all platforms with the same command: `find src/templates/*/commands/trellis/ -name "<command>.*"`
- [ ] Update all platform copies (Markdown `.md` and TOML `.toml`)
- [ ] For Gemini TOML: adapt line continuations (`\\` vs `\`) and triple-quoted strings
- [ ] Run `/trellis:check-cross-layer` to verify nothing was missed
**Real-world example**: Updated `record-session.md` in Claude to use `--mode record`, but forgot iFlow, Kilo, OpenCode, and Gemini — caught by cross-layer check.
---
## Generated Runtime Template Upgrade Consistency
Some generated files are both documentation and runtime input. In Trellis,
`.trellis/workflow.md` is parsed by `get_context.py`, `workflow_phase.py`,
SessionStart filters, and per-turn hooks. Template changes must be validated
against both fresh init and upgrade paths.
### Checklist: After Modifying A Runtime-Parsed Template
- [ ] Identify every runtime parser that reads the template, not just the file
writer that installs it
- [ ] Check whether relevant syntax lives outside obvious managed regions
such as tag blocks
- [ ] Verify fresh `init` output and a versioned `update` scenario that writes
the older `.trellis/.version`
- [ ] Add an upgrade regression using an older pristine template fixture, then
assert the installed file reaches the current packaged shape
- [ ] Update the backend spec that owns the runtime contract
---
## Versioned Documentation Boundary
Versioned documentation is a cross-layer boundary: source paths, `docs.json`
version routing, and the rendered version selector must all describe the same
release line.
### Checklist: Before Editing Versioned Docs
- [ ] Identify the target release line: stable, beta, or RC
- [ ] Verify the edited MDX path matches that line:
- stable: `docs-site/{start,advanced,...}` and `docs-site/zh/{start,advanced,...}`
- beta: `docs-site/beta/**` and `docs-site/zh/beta/**`
- RC: `docs-site/rc/**` and `docs-site/zh/rc/**`
- [ ] Verify `docs.json` navigation points the version label to the same paths
- [ ] Grep the opposite tree for release-line-specific terms before committing
- [ ] Treat beta content appearing under root release paths as a source-path bug,
not a rendering bug
**Real-world example**: A beta-only task workflow change documented
`prd.md` + `design.md` + `implement.md`, task-creation consent, and Codex
mode banners under root `start/` and `advanced/` paths. The docs site then
served 0.6 beta behavior under the Release selector. The fix was to restore root
release docs, move the 0.6 content to `beta/` and `zh/beta/`, and add a grep
audit for beta markers against the root release tree.
**Real-world example**: Codex inline mode changed workflow platform markers from
`[Codex]` / `[Kilo, Antigravity, Windsurf]` to `[codex-sub-agent]` /
`[codex-inline, Kilo, Antigravity, Windsurf]`. Fresh init was correct, but
`trellis update` only merged `[workflow-state:*]` blocks and preserved stale
markers outside those blocks. Result: upgraded projects got new hook scripts
but old workflow routing, so `get_context.py --mode phase --platform codex`
could return empty Phase 2.1 detail.
---
## Mode-Detection Probe Checklist
When a CLI auto-detects a mode by probing a remote resource (e.g., checking if `index.json` exists to decide marketplace vs direct download):
### Before implementing:
- [ ] Probe runs in **ALL** code paths that use the result (interactive, `-y`, `--flag` combos)
- [ ] 404 vs transient error are distinguished — don't treat both as "not found"
- [ ] Transient errors **abort or retry**, never silently switch modes
- [ ] Shared state (caches, prefetched data) is **reset** when context changes (e.g., user switches source)
- [ ] **Shortcut paths** (e.g., `--template` skipping picker) must have the same error-handling quality as the probed path — check that downstream functions don't call catch-all wrappers
### After implementing:
- [ ] Trace every path from probe result to the mode-decision branch — no fallthrough
- [ ] External format contracts (giget URI, raw URLs) are tested or at least documented as comments
- [ ] Metadata reads consume a complete response or use a streaming parser — never parse a fixed-size prefix as full JSON
- [ ] When reconstructing a composite identifier from parsed parts, verify **all** fields are included and in the **correct position** (e.g., `provider:repo/path#ref` not `provider:repo#ref/path`)
- [ ] Verify that **action functions** called after a shortcut don't internally use the old catch-all fetch — they must use the probe-quality variant when error distinction matters
**Real-world example**: Custom registry flow had 8 bugs across 3 review rounds: (1) probe only ran in interactive mode, (2) transient errors fell through to wrong mode, (3) giget URI had `#ref` in wrong position, (4) prefetched templates leaked across source switches, (5) `--template` shortcut bypassed probe but `downloadTemplateById` internally used catch-all `fetchTemplateIndex`, turning timeouts into "Template not found".
**Real-world example**: Agent-session update hints fetched npm `latest` metadata with `response.read(4096)` and then parsed it as complete JSON. The `@mindfoldhq/trellis` package metadata exceeded 4 KB, so the JSON was truncated, parse failed silently, and the first session injection showed no update hint. Fix: read the complete response before parsing, and add a regression where `version` is followed by an 8 KB metadata tail.
---
## Cross-Platform Template Consistency
In Trellis, command templates (e.g., `record-session.md`) exist in **multiple platforms** with identical or near-identical content. This is a cross-layer boundary.
### Checklist: After Modifying Any Command Template
- [ ] Find all platforms with the same command: `find src/templates/*/commands/trellis/ -name "<command>.*"`
- [ ] Update all platform copies (Markdown `.md` and TOML `.toml`)
- [ ] For Gemini TOML: adapt line continuations (`\\` vs `\`) and triple-quoted strings
- [ ] Run `/trellis:check-cross-layer` to verify nothing was missed
**Real-world example**: Updated `record-session.md` in Claude to use `--mode record`, but forgot iFlow, Kilo, OpenCode, and Gemini — caught by cross-layer check.
---
## Generated Runtime Template Upgrade Consistency
Some generated files are both documentation and runtime input. In Trellis,
`.trellis/workflow.md` is parsed by `get_context.py`, `workflow_phase.py`,
SessionStart filters, and per-turn hooks. Template changes must be validated
against both fresh init and upgrade paths.
### Checklist: After Modifying A Runtime-Parsed Template
- [ ] Identify every runtime parser that reads the template, not just the file
writer that installs it
- [ ] Check whether relevant syntax lives outside obvious managed regions
such as tag blocks
- [ ] Verify fresh `init` output and a versioned `update` scenario that writes
the older `.trellis/.version`
- [ ] Add an upgrade regression using an older pristine template fixture, then
assert the installed file reaches the current packaged shape
- [ ] Update the backend spec that owns the runtime contract
**Real-world example**: Codex inline mode changed workflow platform markers from
`[Codex]` / `[Kilo, Antigravity, Windsurf]` to `[codex-sub-agent]` /
`[codex-inline, Kilo, Antigravity, Windsurf]`. Fresh init was correct, but
`trellis update` only merged `[workflow-state:*]` blocks and preserved stale
markers outside those blocks. Result: upgraded projects got new hook scripts
but old workflow routing, so `get_context.py --mode phase --platform codex`
could return empty Phase 2.1 detail.
---
## Mode-Detection Probe Checklist
When a CLI auto-detects a mode by probing a remote resource (e.g., checking if `index.json` exists to decide marketplace vs direct download):
### Before implementing:
- [ ] Probe runs in **ALL** code paths that use the result (interactive, `-y`, `--flag` combos)
- [ ] 404 vs transient error are distinguished — don't treat both as "not found"
- [ ] Transient errors **abort or retry**, never silently switch modes
- [ ] Shared state (caches, prefetched data) is **reset** when context changes (e.g., user switches source)
- [ ] **Shortcut paths** (e.g., `--template` skipping picker) must have the same error-handling quality as the probed path — check that downstream functions don't call catch-all wrappers
### After implementing:
- [ ] Trace every path from probe result to the mode-decision branch — no fallthrough
- [ ] External format contracts (giget URI, raw URLs) are tested or at least documented as comments
- [ ] Metadata reads consume a complete response or use a streaming parser — never parse a fixed-size prefix as full JSON
- [ ] When reconstructing a composite identifier from parsed parts, verify **all** fields are included and in the **correct position** (e.g., `provider:repo/path#ref` not `provider:repo#ref/path`)
- [ ] Verify that **action functions** called after a shortcut don't internally use the old catch-all fetch — they must use the probe-quality variant when error distinction matters
**Real-world example**: Custom registry flow had 8 bugs across 3 review rounds: (1) probe only ran in interactive mode, (2) transient errors fell through to wrong mode, (3) giget URI had `#ref` in wrong position, (4) prefetched templates leaked across source switches, (5) `--template` shortcut bypassed probe but `downloadTemplateById` internally used catch-all `fetchTemplateIndex`, turning timeouts into "Template not found".
**Real-world example**: Agent-session update hints fetched npm `latest` metadata with `response.read(4096)` and then parsed it as complete JSON. The `@mindfoldhq/trellis` package metadata exceeded 4 KB, so the JSON was truncated, parse failed silently, and the first session injection showed no update hint. Fix: read the complete response before parsing, and add a regression where `version` is followed by an 8 KB metadata tail.
---
## When to Create Flow Documentation
Create detailed flow docs when:
- Feature spans 3+ layers
- Multiple teams are involved
- Data format is complex
- Feature has caused bugs before
---
## Event Log / Projection Boundary
Append-only logs are cross-layer contracts. A single event travels through:
```
CLI input → event writer → events.jsonl → reader → filter → reducer → display
```
### Checklist: After Adding A New Event Kind Or Field
- [ ] Add the event kind to the central event taxonomy
- [ ] Add a typed event variant or type guard at the event layer
- [ ] Add normalization helpers for array/object fields that come from
user input or JSON
- [ ] Keep `seq` / `id` assignment in the event writer only
- [ ] Make filters and reducers consume the typed event guard, not local casts
- [ ] Make display code consume reducer output or typed events, not raw JSON
- [ ] Add at least one regression that proves history replay and live filtering
use the same filter model
**Real-world example**: Thread channels added `kind: "thread"`, `description`,
`context`, labels, and `lastSeq`. The first implementation replayed thread
state correctly, but several commands still re-parsed event payload fields with
local casts. The fix was to make the core event layer own `ThreadChannelEvent`
and `isThreadEvent`, make `reduceChannelMetadata` the only channel metadata
projection, and make `reduceThreads` the only thread replay reducer.
提交前至少回答:输入在哪里解析?错误在哪里转换?状态由谁拥有?字段是否在后端、客户端类型和 UI 测试中一致?如果答案不清楚,先补充契约或拆分边界,再实现功能。
+7 -94
View File
@@ -1,97 +1,10 @@
# Thinking Guides
# 跨层思考指南
> **Purpose**: Expand your thinking to catch things you might not have considered.
这些指南用于本仓库中跨模块的设计检查,不替代 backend/frontend 的具体规格。
---
| 指南 | 触发场景 |
| --- | --- |
| [代码复用](./code-reuse-thinking-guide.md) | 要新增 helper、常量、UI primitive、query key 或重复转换时 |
| [跨层契约](./cross-layer-thinking-guide.md) | 变更后端 HTTP、前端 API 类型、query 或页面状态时 |
## Why Thinking Guides?
**Most bugs and tech debt come from "didn't think of that"**, not from lack of skill:
- Didn't think about what happens at layer boundaries → cross-layer bugs
- Didn't think about code patterns repeating → duplicated code everywhere
- Didn't think about edge cases → runtime errors
- Didn't think about future maintainers → unreadable code
These guides help you **ask the right questions before coding**.
---
## Available Guides
| Guide | Purpose | When to Use |
|-------|---------|-------------|
| [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md) | Identify patterns and reduce duplication | When you notice repeated patterns |
| [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md) | Think through data flow across layers | Features spanning multiple layers |
---
## Quick Reference: Thinking Triggers
### When to Think About Cross-Layer Issues
- [ ] Feature touches 3+ layers (API, Service, Component, Database)
- [ ] Data format changes between layers
- [ ] Multiple consumers need the same data
- [ ] You're not sure where to put some logic
- [ ] You are adding an event kind, JSONL record, RPC payload, or config field
- [ ] UI / command code starts casting raw payload fields directly
→ Read [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md)
### When to Think About Code Reuse
- [ ] You're writing similar code to something that exists
- [ ] You see the same pattern repeated 3+ times
- [ ] You're adding a new field to multiple places
- [ ] **You're modifying any constant or config**
- [ ] **You're creating a new utility/helper function** ← Search first!
- [ ] Two files read the same untyped payload field with local casts
- [ ] Multiple branches update the same derived state from `kind` / `action`
→ Read [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md)
### When Verifying AI Cross-Review Results
- [ ] Reviewer claims "user input can be malicious" → Check the actual data source (internal manifest? user config? external API?)
- [ ] Reviewer flags "missing validation" → Is the data from a trusted internal source?
- [ ] Reviewer says "behavior change" → Read the code comments — is it intentional design?
- [ ] Reviewer identifies a "bug" in test → Mentally delete the feature being tested — does the test still pass? If yes → tautological test
**Common AI reviewer false-positive patterns**:
1. **Trust boundary confusion**: Treating internal data (bundled JSON manifests) as untrusted external input
2. **Ignoring design comments**: Flagging intentional behavior documented in code comments as bugs
3. **Variable misreading**: Not tracing a variable to its actual definition (e.g., Map keyed by path vs name)
**Verification rule**: Every CRITICAL/WARNING finding must be verified against the actual code before prioritizing. Budget ~35% false-positive rate for AI reviews.
---
## Pre-Modification Rule (CRITICAL)
> **Before changing ANY value, ALWAYS search first!**
```bash
# Search for the value you're about to change
grep -r "value_to_change" .
```
This single habit prevents most "forgot to update X" bugs.
---
## How to Use This Directory
1. **Before coding**: Skim the relevant thinking guide
2. **During coding**: If something feels repetitive or complex, check the guides
3. **After bugs**: Add new insights to the relevant guide (learn from mistakes)
---
## Contributing
Found a new "didn't think of that" moment? Add it to the relevant guide.
---
**Core Principle**: 30 minutes of thinking saves 3 hours of debugging.
每次修改先用源码和测试验证假设,再更新对应规格;不要把通用框架偏好写成项目规则。
+17 -12
View File
@@ -21,24 +21,29 @@ the rest conversationally.
## Status (update the checkboxes as you complete each item)
- [ ] Fill backend guidelines
- [ ] Fill frontend guidelines
- [ ] Add code examples
- [x] Fill backend guidelines
- [x] Fill frontend guidelines
- [x] Add code examples
## Analysis Notes
- 后端当前没有数据库、迁移、持久化客户端或实际日志实现,因此移除了 `database-guidelines.md` 和 `logging-guidelines.md` 模板;相关能力出现时应先建立真实实现和对应规格。
- 规格以 `zhixing-server/src/`、`zhixing-server/tests/`、`zhixing-web/src/`、项目 README 和 ADR 为依据,GitNexus/ABCoder 索引未在本环境中可用。
---
## Spec files to populate
## Spec files populated
### Backend guidelines
| File | What to document |
|------|------------------|
| `.trellis/spec/backend/directory-structure.md` | Where different file types go (routes, services, utils) |
| `.trellis/spec/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions |
| `.trellis/spec/backend/error-handling.md` | How errors are caught, logged, and returned |
| `.trellis/spec/backend/logging-guidelines.md` | Log levels, format, what to log |
| `.trellis/spec/backend/quality-guidelines.md` | Code review standards, testing requirements |
| `.trellis/spec/backend/directory-structure.md` | 包结构、bounded context 和导入边界 |
| `.trellis/spec/backend/configuration-and-runtime.md` | Settings、应用工厂和部署环境 |
| `.trellis/spec/backend/http-api-contracts.md` | 路由组合、响应模型和同源 API 路径 |
| `.trellis/spec/backend/error-handling.md` | 当前 FastAPI 错误行为及跨层错误传递 |
| `.trellis/spec/backend/quality-guidelines.md` | Ruff、Pyright、pytest 及禁止模式 |
### Frontend guidelines
@@ -53,10 +58,10 @@ the rest conversationally.
| `.trellis/spec/frontend/quality-guidelines.md` | Linting, testing, accessibility |
### Thinking guides (already populated)
### Thinking guides
`.trellis/spec/guides/` contains general thinking guides pre-filled with
best practices. Customize only if something clearly doesn't fit this project.
`.trellis/spec/guides/` contains this project's code-reuse and cross-layer
contract checks, backed by the actual API/query/UI flow.
---