Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .specify/feature.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"feature_directory": "specs/005-related-entities-tab"
"feature_directory": "specs/006-skill-template-polish"
}
264 changes: 40 additions & 224 deletions skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
---
name: nexusx-4phase
description: 基于 nexusx 的四阶段开发模式,从 Schema 建模到 API 响应组装再到 TS SDK 的完整项目构建流程。
argument-hint: "[项目路径] 创建四阶段项目的目标目录"
---

# nexusx 四阶段开发模式

## 适用版本

本 skill 假设 **Python >= 3.12** 且 **nexusx >= 3.2**。特性-版本对照:

| 特性 | 起始版本 |
|---|---|
| UseCase GraphQL MCP(`create_use_case_graphql_mcp_server`) | 3.0+ |
| 虚拟实体(`ErManager.add_virtual_entities`) | 3.2+ |
| 跨层数据流(`ExposeAs` / `SendTo` / `Collector`) | 3.0+ |

正文中如提到具体 API 的版本门槛,均以此表为准;不再在各 phase 文档中散落声明。

> [!IMPORTANT]
> **Python 版本要求:`>= 3.12`。**
> 该 skill 依赖的 `SQLModel + SQLAlchemy + nexusx ErManager` 关系解析,在较低 Python 版本(尤其是 3.10)下,容易因为类型注解延迟求值与 ORM relationship 解析组合而出现兼容问题。
> 如使用 `Relationship(...)`、自引用关系、虚拟实体(`ErManager.add_virtual_entities`)或 Alembic 自动迁移,请统一使用 Python 3.12+。

## 调用约定

```
/nexusx-4phase [项目目录路径]
```

参数为目标目录路径(可选)。未提供时,skill 引导用户在当前位置或指定路径下创建项目。

基于 nexusx 的渐进式开发方法论。项目在一个 `src/` 目录下逐步演进,每个阶段在上一阶段基础上新增代码。

| Phase | 职责 | 产出 |
Expand All @@ -32,7 +56,7 @@ argument-hint: "[项目路径] 创建四阶段项目的目标目录"
┌──────────────────────────────────────────────┐
│ V 降:定义验收标准 │
│ "在当前 Phase 开始之前,先定义什么算做完。" │
│ 写入 spec/<phase>.md 的"验收标准"部分 │
│ 写入 phaseN.md 的"验收标准"部分
└──────────────────────────────────────────────┘
┌───────────────┐
Expand All @@ -42,236 +66,27 @@ argument-hint: "[项目路径] 创建四阶段项目的目标目录"
┌──────────────────────────────────────────────┐
│ V 升:逐条回查验收 │
│ "一条一条对照验收标准,通过才可继续。" │
│ 用户逐条确认 → 写入 spec/<phase>.md │
│ 用户逐条确认 → 写入 phaseN.md
└──────────────────────────────────────────────┘
```

> `phaseN.md` 完整路径为 `specs/<编号>-<需求简述>/phaseN.md`,详见 `spec-management.md` 的「目录命名」。

验收标准必须是**可观察、可操作的**——不写"代码健壮",写"GraphiQL 中执行 X query 返回 Y"。

## 阶段实现

Phase 0(需求确认)完整包含在本文件中。
每个阶段(含 Phase 0)有独立的详细指令文件:

Phase 0 完成并确认后,读取当前阶段的详细指令:
- **Phase 1**: 读取 `phases/phase1.md`
- **Phase 2**: 读取 `phases/phase2.md`
- **Phase 3**: 读取 `phases/phase3.md`
- **Phase 4**: 读取 `phases/phase4.md`
- **Phase 0**(需求确认): 读取 `phases/phase0.md` — 业务实体 / 关系 / 聚合根 / 用例方法 / 第三方库 / DB 选型的逐项确认
- **Phase 1**(Schema + ER Diagram): 读取 `phases/phase1.md`
- **Phase 2**(方法实现 + Entity 挂载): 读取 `phases/phase2.md`
- **Phase 3**(UseCase 响应组装 + MCP): 读取 `phases/phase3.md`
- **Phase 4**(OpenAPI → TS SDK): 读取 `phases/phase4.md`

每个阶段完成后,继续进行下一阶段之前暂停并等待用户确认。

对于 Spec 管理工作流(目录命名、文件格式、迭代规则、交付验证),读取 `spec-management.md`。

## Phase 0: 需求确认(必做)

在写任何代码之前,必须与用户逐项确认以下内容。每一项都需要用户明确认可后才算完成。

### Step 0-1: 术语与实体定义

逐一列出所有业务实体,每个实体说明:

- **业务含义**(一句话,团队无歧义)
- **核心字段**(名称 + 类型 + 语义说明,不需要穷举,但关键属性不能遗漏)
- **字段约束**(唯一、非空、枚举值、联合唯一等)

用表格形式呈现,方便用户逐行确认。

### Step 0-2: 实体关系

用文本 ER 图展示实体间关系,每条关系标明:

- 方向(1:N / N:1 / M:N)
- 业务含义(如「会话包含多条消息」)
- 是否需要中间实体

```
User ──1:N──→ Participant
Conversation ──1:N──→ Message
...
```

**必须与用户确认关系方向和基数是否正确。**

### Step 0-3: 聚合根

明确哪个(或哪些)实体是聚合根。聚合根决定:

- 主要的业务入口(从哪个实体开始查询)
- @query / @mutation 挂在哪些实体上
- Phase 3 的 service 划分依据

#### 根类型选择(3.2+)

每个聚合根必须明确是 **SQLModel 实体**还是 **虚拟实体(普通 `pydantic.BaseModel`)**:

| 类型 | 何时选 | 数据持久化 | 例子 |
|------|--------|-----------|------|
| **SQLModel 实体** | 数据需要落库、参与 alembic 迁移、是 GraphQL 实体入口 | ✅ 表 | `User` / `Sprint` / `Conversation` |
| **虚拟实体(BaseModel)** | 响应根不对应ORM表:组装自外部 claims、聚合视图、第三方 SDK DTO | ❌ 不建表 | `CurrentUser`(OIDC claims)、`Page[T]`(分页 wrapper)、第三方 SDK 镜像 |

虚拟实体通过 `ErManager.add_virtual_entities([...])` 在 Phase 1 注册,与 SQLModel 实体在 Resolver / ER / Voyager 中同等对待(详见 `docs/guide/virtual_entities.md`)。

**判断依据**:如果根字段全部来自数据库表 → SQLModel;如果字段来自请求上下文(JWT、headers)或聚合多个源 → 虚拟实体。混合场景(如 `Page[TaskDTO]` 包含真实 task 数据)由 Phase 3 DTO 组合,根本身仍是虚拟实体。

### Step 0-4: 业务域划分 + 用例方法

**⚠️ 禁止自行决定 Service 切分方案。必须提出候选方案与用户讨论,由用户最终确认。**

#### Step 0-4a: 提出 Service 切分候选方案

业务域(Service)按功能边界划分,不按实体划分。Service 切分直接影响:
- 目录结构(`service/<domain>/`)
- Phase 2 的 methods.py 粒度
- Phase 3 的 UseCaseService 类划分
- MCP 和 REST 的入口组织

**必须向用户提出至少一种候选方案**,说明每种方案的切分依据和优劣,由用户选择或修正。

常见的切分策略参考:

| 策略 | 示例 | 适用场景 |
|------|------|----------|
| 按业务功能域 | `auth` / `chat` / `order` | 业务边界清晰,领域间耦合低 |
| 按聚合根 | `user` / `conversation` / `message` | 实体独立性强,CRUD 为主 |
| 混合(功能域 + 独立聚合) | `auth` / `chat`(含 conversation+message) | 部分域跨实体协作 |

**向用户展示的格式:**

```
方案 A:按功能域
auth/ → register, login
chat/ → create_conversation, list_messages, send_message
优势:业务内聚,方法自然归组
劣势:chat 域可能过大

方案 B:按聚合根
user/ → register, login
conversation/ → create_conversation, list_messages
message/ → send_message
优势:每个 service 粒度均匀
劣势:conversation 和 message 强耦合却拆开了
```

**必须等用户明确选择后才能继续。** 如果用户提出自己的分法,按用户的来。

#### Step 0-4b: 按确认的 Service 划分列出用例方法

用户确认 Service 切分后,按每个业务域列出用例方法。每个方法说明:

- **方法名**(动词开头,如 `create_conversation`、`list_messages`)
- **业务意图**(一句话,如「创建群聊并自动将创建者加入为 owner」)
- **挂载实体**(挂在哪个 Entity 的 @query / @mutation 上,供 GraphQL 使用)
- **关键参数**(列出参数名和含义,不需要完整签名)

示例格式:

| 业务域 | 方法名 | 业务意图 | 挂载实体 | 关键参数 |
|--------|--------|----------|----------|----------|
| auth | register | 注册新用户 | User | username, nickname, password |
| auth | login | 登录返回 JWT | User | username, password |
| chat | create_conversation | 创建会话 | Conversation | type, creator_id, name |
| chat | list_messages | 查询会话消息(分页) | Conversation | conversation_id, before_id, limit |

**用例方法不需要实现细节,但必须逻辑自洽**:
- mutation 的参数是否足以完成操作
- 创建类 mutation 是否有遗漏的副作用(如自动创建关联记录)
- 查询类方法是否覆盖了核心场景

### Step 0-5: GraphQL 定位

GraphQL 是辅助开发测试和 AI 测试的接口,不是正式 API。

业务方法的定义和挂载关系:

```
service/<domain>/methods.py ← 独立定义业务逻辑(核心)
↓ 挂载 ↓ 挂载
Entity @query/@mutation UseCaseService @query/@mutation
(GraphQL 辅助测试) (REST + MCP 正式接口)
```

- Phase 2:方法体在 `service/<domain>/methods.py` 中实现,`models.py` 的 `mount_method()` 函数挂载到 Entity,`main.py` 显式调用
- Phase 3:同一个方法挂载到 UseCaseService(REST/MCP 使用),DTO 转换在 Service 层完成

### Step 0-6: 第三方库确认

列出项目中涉及的非业务功能领域(认证、实时推送、文件存储、数据迁移等),对每个领域:

- **说明候选方案**(推荐成熟第三方库 vs 手写实现)
- **给出推荐理由**(社区活跃度、维护状态、与 FastAPI/SQLModel 的兼容性)
- **必须调查用户提到的第三方库的当前维护状态**(避免选用已停止维护的库)

用表格形式呈现:

| 功能领域 | 推荐方案 | 理由 | 备注 |
|----------|----------|------|------|
| 认证 | ... | ... | ... |
| ... | ... | ... | ... |

**注意事项**:
- 优先使用 FastAPI 生态内的主流方案,减少集成风险
- 如果用户指定了某个库,必须先调查其维护状态和兼容性,发现问题要及时告知用户并提供替代方案
- 对于 nexusx 已覆盖的领域(ORM、GraphQL、MCP),不再重复讨论

**必须与用户确认每个领域的选型后才能继续。**

### Step 0-7: 数据持久化与迁移策略

**⚠️ 必须由用户明确选定 DB 类型与迁移策略,决定 Phase 1 的 `db.py` / `database.py` 实现方式以及是否引入 alembic。**

#### 选型决策表

| 选项 | async DB URL | 持久化 | Alembic | 额外依赖 | 适用场景 |
|------|-------------|--------|---------|---------|---------|
| **In-memory SQLite** | `sqlite+aiosqlite://` | ❌ 进程退出即丢 | ❌ 不需要 | `aiosqlite` | 纯原型/Demo/团队讨论数据样本,不关心数据保留 |
| **File-backed SQLite** | `sqlite+aiosqlite:///./var/<name>.db` | ✅ 文件 | ✅ 必须 | `aiosqlite` | 本地开发、单人项目、轻量持久化 |
| **Docker PostgreSQL** | `postgresql+asyncpg://user:pwd@localhost:5432/db` | ✅ 容器卷 | ✅ 必须 | `asyncpg` + docker-compose | 团队开发、生产前演练 |
| **Docker MySQL** | `mysql+aiomysql://user:pwd@localhost:3306/db` | ✅ 容器卷 | ✅ 必须 | `aiomysql` + docker-compose | 同上,团队偏好 MySQL |
| **External DB** | 各种 | ✅ | ✅ 必须 | 视驱动 | 已有 DB 基础设施 |

#### 决策影响(下游 Phase 必须遵守)

- **Phase 1 `db.py`**:engine URL 取决于此决策
- **Phase 1 `database.py`**:
- **in-memory**:`init_db()` 做 `create_all` + mock seed(每次重启自动恢复,讨论用样本数据)
- **持久化(file / docker / external)**:`init_db()` 改为 no-op,schema 由 alembic 管,seed 改为一次性 `scripts/load_seed.py`(保留 ID)
- **Phase 1 引入 alembic**(持久化场景必须):
- `alembic init alembic`
- `env.py`:`import src.models` 注册表 + `target_metadata = SQLModel.metadata` + 同步 URL(app 用 async,alembic 用 sync)
- SQLite 必须 `render_as_batch=True`;PostgreSQL / MySQL 不需要
- `script.py.mako` 模板加 `import sqlmodel`(SQLModel 的 `AutoString` 类型需要)
- `pyproject.toml` 加 `alembic>=1.13`
- 生成 baseline:`alembic revision --autogenerate -m "init schema"` → 检查 → `alembic upgrade head`
- `.gitignore` 加 `var/`(file sqlite 场景)

#### 用户必须输出的明确结论(写入 `spec/phase0.md`)

```
DB 选型:[in-memory sqlite / file sqlite / docker pg / docker mysql / external ___]
async DATABASE_URL:________________
sync DATABASE_URL_SYNC(alembic + load_seed 用):________________
是否引入 alembic:[是 / 否]
是否需要 docker-compose:[是 / 否]
init_db() 策略:[create_all+seed / no-op+alembic / 其他]
```

**用户未明确选定前,禁止进入 Phase 1。**

### Step 0-8: 检查清单

全部确认后,向用户展示汇总,确保以下问题已回答:

- [ ] 所有实体和字段是否完整,约束是否清晰?
- [ ] 实体关系方向和基数是否正确?
- [ ] 聚合根是否明确?
- [ ] **每个聚合根的类型是否确认:SQLModel(落表)还是虚拟实体(BaseModel,不落表)?**
- [ ] **Service 切分方案是否由用户确认(不是模型自行决定)?**
- [ ] 核心用例是否覆盖主要业务场景,逻辑是否自洽?
- [ ] 第三方库选型是否确认,维护状态是否已调查?
- [ ] **DB 选型 + 迁移策略是否由用户明确确认(Step 0-7)?**
- [ ] 是否有明显的遗漏或边界情况需要讨论?

**全部确认后才能进入 Phase 1。**
对于 Spec 管理工作流(目录命名、文件格式、迭代规则、交付验证、迁移指引),读取 `spec-management.md`。

## 参考实现

Expand All @@ -291,15 +106,16 @@ src/
│ │ ├── methods.py # Phase 2: 独立业务方法
│ │ ├── dtos.py # Phase 3: DTO
│ │ ├── service.py # Phase 3: UseCaseService
│ │ ├── test.py # Phase 3: unittest, file or folder, depends on complexity
│ │ └── spec.md # Phase 3: 服务说明
│ │ └── spec.md # Phase 3: 服务说明(测试不放此处,见下方 tests/)
│ └── chat/
│ ├── methods.py
│ ├── dtos.py
│ ├── service.py
│ ├── test.py
│ └── spec.md
├── main.py # 逐步扩展(voyager → graphql → create_use_case_router → mcp)
tests/ # 项目级测试目录(不在 service/<domain>/ 下,规避循环导入)
├── conftest.py # 共享 fixture(in-memory sqlite + monkey-patch session)
└── test_<domain>_methods.py # 每个业务域一个测试文件
alembic/ # Phase 1 持久化场景才引入(file sqlite / docker / external)
├── env.py # 接 SQLModel.metadata + sync URL + render_as_batch(sqlite)
├── script.py.mako # 模板加 import sqlmodel
Expand Down
Loading