@wneng/create-keel 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +497 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/feature/templates/arch/adr.md.eta +31 -0
- package/src/feature/templates/backend/design.md.eta +35 -0
- package/src/feature/templates/frontend/design.md.eta +34 -0
- package/src/feature/templates/pm/prd.md.eta +37 -0
- package/src/feature/templates/sample/user-signup/arch/adr.md.eta +48 -0
- package/src/feature/templates/sample/user-signup/backend/design.md.eta +77 -0
- package/src/feature/templates/sample/user-signup/frontend/design.md.eta +54 -0
- package/src/feature/templates/sample/user-signup/pm/prd.md.eta +46 -0
- package/src/feature/templates/sample/user-signup/test/test-plan.md.eta +40 -0
- package/src/feature/templates/test/test-plan.md.eta +23 -0
- package/src/standards/templates/coding-style-dart.md.eta +49 -0
- package/src/standards/templates/coding-style-go.md.eta +52 -0
- package/src/standards/templates/coding-style-java.md.eta +50 -0
- package/src/standards/templates/coding-style-python.md.eta +51 -0
- package/src/standards/templates/coding-style-rust.md.eta +52 -0
- package/src/standards/templates/coding-style-typescript.md.eta +50 -0
- package/src/standards/templates/dependency-cruiser.config.cjs.eta +57 -0
- package/src/standards/templates/design-tokens.ts.eta +37 -0
- package/src/standards/templates/module-boundaries.md.eta +82 -0
- package/src/standards/templates/tech-stack-agent.md.eta +35 -0
- package/src/standards/templates/tech-stack-miniapp.md.eta +34 -0
- package/src/standards/templates/tech-stack-mobile.md.eta +36 -0
- package/src/standards/templates/tech-stack-server.md.eta +50 -0
- package/src/standards/templates/tech-stack-web.md.eta +36 -0
- package/src/standards/templates/ui-design-system.md.eta +70 -0
- package/src/templates/docs-skeleton/files/usage-quickstart.md +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wneng/create-keel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Scaffolder for Contract First + Vibe Coding projects (keel conventions)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scaffold",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"src/templates",
|
|
19
|
+
"src/feature/templates",
|
|
20
|
+
"src/standards/templates",
|
|
19
21
|
"README.md",
|
|
20
22
|
"LICENSE"
|
|
21
23
|
],
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# ADR-<%= it.feature.nextAdrNumber %>:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 状态:**Proposed**(<%= it.year %>)
|
|
4
|
+
> 决策范围:<%= it.feature.title %>
|
|
5
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
6
|
+
> 来源 PRD:[docs/01-背景与需求/prd-<%= it.feature.slug %>.md](../01-背景与需求/prd-<%= it.feature.slug %>.md)
|
|
7
|
+
|
|
8
|
+
## 背景
|
|
9
|
+
|
|
10
|
+
<!-- 为什么现在做出决定?什么约束或信号触发了这份 ADR? -->
|
|
11
|
+
|
|
12
|
+
## 决策
|
|
13
|
+
|
|
14
|
+
<!-- 一段话说清决策结论,不含歧义。 -->
|
|
15
|
+
|
|
16
|
+
## 备选方案
|
|
17
|
+
|
|
18
|
+
- **方案 A** —— 优点 / 缺点 / 为何不采用
|
|
19
|
+
- **方案 B** —— 优点 / 缺点 / 为何不采用
|
|
20
|
+
|
|
21
|
+
## 影响
|
|
22
|
+
|
|
23
|
+
### 正面
|
|
24
|
+
- 列出正面影响
|
|
25
|
+
|
|
26
|
+
### 负面 / 风险
|
|
27
|
+
- 列出风险与缓解措施
|
|
28
|
+
|
|
29
|
+
## 参考
|
|
30
|
+
|
|
31
|
+
- 契约:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 后端设计:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> PRD:[docs/01-背景与需求/prd-<%= it.feature.slug %>.md](../01-背景与需求/prd-<%= it.feature.slug %>.md)
|
|
5
|
+
|
|
6
|
+
## 契约
|
|
7
|
+
|
|
8
|
+
请求 / 响应形态的唯一真值源是 OpenAPI 文档。本设计文档与契约保持同步;
|
|
9
|
+
两者不一致时以契约为准。
|
|
10
|
+
|
|
11
|
+
- 路径:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
12
|
+
|
|
13
|
+
## 模块边界
|
|
14
|
+
|
|
15
|
+
<!-- 本特性涉及哪些模块?列出会变更的 controller / service / repository。 -->
|
|
16
|
+
|
|
17
|
+
## 数据模型
|
|
18
|
+
|
|
19
|
+
<!-- 新增表、字段、索引。引用 contracts/dictionaries/ 中的字典定义。 -->
|
|
20
|
+
|
|
21
|
+
## 错误处理
|
|
22
|
+
|
|
23
|
+
<!-- 错误码(DOMAIN.SUBDOMAIN.REASON)、HTTP 状态码映射、重试策略。 -->
|
|
24
|
+
|
|
25
|
+
## 时序
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
client -> controller -> service -> repository -> db
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 测试计划
|
|
32
|
+
|
|
33
|
+
- service 层单元测试
|
|
34
|
+
- 与 `contracts/openapi/api.yaml` 的契约测试
|
|
35
|
+
- 见:[docs/07-质量与测试/test-plans/<%= it.feature.slug %>.md](../07-质量与测试/test-plans/<%= it.feature.slug %>.md)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 前端设计:<%= it.feature.title %>(web)
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> 后端设计:[docs/04-后端详细设计/<%= it.feature.slug %>.md](../04-后端详细设计/<%= it.feature.slug %>.md)
|
|
5
|
+
|
|
6
|
+
## 用户流程
|
|
7
|
+
|
|
8
|
+
<!-- 本特性在 web 端的逐步用户旅程。 -->
|
|
9
|
+
|
|
10
|
+
## 页面 / 组件拆分
|
|
11
|
+
|
|
12
|
+
| 组件 | 职责 |
|
|
13
|
+
|------|------|
|
|
14
|
+
| `<%= it.feature.slug %>Page` | 顶层路由组件 |
|
|
15
|
+
| | |
|
|
16
|
+
|
|
17
|
+
## 状态管理
|
|
18
|
+
|
|
19
|
+
<!-- 本地状态、全局 store 切片、缓存失效规则。 -->
|
|
20
|
+
|
|
21
|
+
## API 调用
|
|
22
|
+
|
|
23
|
+
本特性通过 OpenAPI 契约调用后端。请由 `contracts/openapi/api.yaml` 生成
|
|
24
|
+
客户端,避免手写 fetch 代码。
|
|
25
|
+
|
|
26
|
+
- 契约:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
27
|
+
|
|
28
|
+
## 校验
|
|
29
|
+
|
|
30
|
+
<!-- 客户端校验规则;服务端是权威,但客户端校验能让错误 UX 更快。 -->
|
|
31
|
+
|
|
32
|
+
## 无障碍
|
|
33
|
+
|
|
34
|
+
<!-- 键盘导航、屏幕阅读器标签、对比度说明。 -->
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# PRD:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> 由 `create-keel feature add` 在 <%= it.generatedAt %> 创建
|
|
5
|
+
|
|
6
|
+
## 背景
|
|
7
|
+
|
|
8
|
+
<!-- 描述用户问题与业务背景。替换本段。 -->
|
|
9
|
+
|
|
10
|
+
## 目标
|
|
11
|
+
|
|
12
|
+
- [ ] 主要目标
|
|
13
|
+
- [ ] 次要目标
|
|
14
|
+
|
|
15
|
+
## 非目标
|
|
16
|
+
|
|
17
|
+
- 不在本次范围
|
|
18
|
+
|
|
19
|
+
## 用户故事
|
|
20
|
+
|
|
21
|
+
**作为** [角色],**我希望** [能力],**以便** [收益]。
|
|
22
|
+
|
|
23
|
+
## 验收标准(EARS)
|
|
24
|
+
|
|
25
|
+
下列标准使用 EARS 语法(WHEN / IF / WHILE / WHERE + THE \<system\> SHALL
|
|
26
|
+
\<response\>)。请把占位描述替换为真实行为;保留 EARS 句型,让
|
|
27
|
+
`governance-lint` 把这些条目识别为可测试规则。
|
|
28
|
+
|
|
29
|
+
1. WHEN [触发事件发生时],THE 系统 SHALL [可观察响应]。
|
|
30
|
+
2. IF [出现意外情况],THEN THE 系统 SHALL [错误处理 / 降级]。
|
|
31
|
+
3. WHILE [处于某状态时],THE 系统 SHALL [持续行为]。
|
|
32
|
+
|
|
33
|
+
## 关联引用
|
|
34
|
+
|
|
35
|
+
- 后端设计:[docs/04-后端详细设计/<%= it.feature.slug %>.md](../04-后端详细设计/<%= it.feature.slug %>.md)
|
|
36
|
+
- 前端设计:[docs/05-前端客户端详细设计/<%= it.feature.slug %>-web.md](../05-前端客户端详细设计/<%= it.feature.slug %>-web.md)
|
|
37
|
+
- 契约:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ADR-<%= it.feature.nextAdrNumber %>:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 状态:**Proposed**(<%= it.year %>)
|
|
4
|
+
> 决策范围:账户注册流程 + 验证模型
|
|
5
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
6
|
+
> 来源 PRD:[docs/01-背景与需求/prd-<%= it.feature.slug %>.md](../01-背景与需求/prd-<%= it.feature.slug %>.md)
|
|
7
|
+
|
|
8
|
+
## 背景
|
|
9
|
+
|
|
10
|
+
PRD 要求自助注册账户并强制邮箱验证。本领域常见的实现模式有三种:应用内
|
|
11
|
+
验证 token、邮件 magic link、邮件数字验证码。三者在安全与 UX 上各有权衡,
|
|
12
|
+
选型会影响 `pending` 用户的存活时间以及鉴权模型的形态。
|
|
13
|
+
|
|
14
|
+
## 决策
|
|
15
|
+
|
|
16
|
+
采用 **基于服务端存储 token 的邮件 magic link**:
|
|
17
|
+
|
|
18
|
+
- 注册时在 `users` 表创建 `pending` 行,并在 `verification_tokens` 表创建
|
|
19
|
+
一条 UUIDv4 token,TTL 24 小时
|
|
20
|
+
- 验证邮件包含一条指向 `GET /auth/verify?token=...` 的链接
|
|
21
|
+
- 成功时把用户切到 `active`,token 标记为已消费(一次性),并下发会话 cookie
|
|
22
|
+
- 失败 / 过期时展示"重新申请链接"的 CTA
|
|
23
|
+
|
|
24
|
+
## 备选方案
|
|
25
|
+
|
|
26
|
+
- **数字验证码(6 位 OTP)**—— 在移动端输入更顺手,但桌面浏览器 UX 反而
|
|
27
|
+
更差,多了一步冗余输入。否决。
|
|
28
|
+
- **不验证,只发邮件提示**—— 上线最快,但会让用户表充斥拼写错误与垃圾。
|
|
29
|
+
与 PRD 目标 #2 冲突。否决。
|
|
30
|
+
- **仅联邦身份(Google / GitHub)**—— 适用场景下 UX 最佳,但会把没有这些
|
|
31
|
+
账户的用户挡在外面,且 PRD 把第三方登录明确列为 v1 非目标。否决。
|
|
32
|
+
|
|
33
|
+
## 影响
|
|
34
|
+
|
|
35
|
+
### 正面
|
|
36
|
+
- 标准成熟方案,每种后端栈都有现成库支持
|
|
37
|
+
- token 轮换策略简单(删除 + 新建一行)
|
|
38
|
+
- `pending` → `active` 状态机清晰可审计
|
|
39
|
+
|
|
40
|
+
### 负面 / 风险
|
|
41
|
+
- 邮件送达率成为硬依赖,必须监控退信率
|
|
42
|
+
- 24 小时 TTL 可能对一周才看一次邮箱的用户偏短;上线后根据转化遥测
|
|
43
|
+
数据再决定是否拉长
|
|
44
|
+
|
|
45
|
+
## 参考
|
|
46
|
+
|
|
47
|
+
- 契约:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
48
|
+
- 关联:未来的 `password-reset` ADR 会复用同一张 token 表的 schema
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 后端设计:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> PRD:[docs/01-背景与需求/prd-<%= it.feature.slug %>.md](../01-背景与需求/prd-<%= it.feature.slug %>.md)
|
|
5
|
+
|
|
6
|
+
## 契约
|
|
7
|
+
|
|
8
|
+
请求 / 响应形态的唯一真值源是 OpenAPI 文档。本设计文档描述**实现**;两者
|
|
9
|
+
不一致时以契约为准。
|
|
10
|
+
|
|
11
|
+
- 路径:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
12
|
+
|
|
13
|
+
## 模块边界
|
|
14
|
+
|
|
15
|
+
本特性涉及三个模块:
|
|
16
|
+
|
|
17
|
+
- `auth/signup` —— 拥有 `POST /users/signup` 与 `GET /auth/verify`
|
|
18
|
+
- `email` —— 拥有验证邮件的发送(模板 + 传输)
|
|
19
|
+
- `users` —— 拥有 `users` 表与 `pending`/`active` 状态机
|
|
20
|
+
|
|
21
|
+
## 数据模型
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
users
|
|
25
|
+
id uuid PK
|
|
26
|
+
email text UNIQUE NOT NULL
|
|
27
|
+
password_hash text NOT NULL -- bcrypt cost 12
|
|
28
|
+
state text NOT NULL -- enum('pending','active','suspended')
|
|
29
|
+
created_at timestamptz DEFAULT now()
|
|
30
|
+
activated_at timestamptz NULL
|
|
31
|
+
|
|
32
|
+
verification_tokens
|
|
33
|
+
token uuid PK
|
|
34
|
+
user_id uuid FK -> users.id NOT NULL
|
|
35
|
+
expires_at timestamptz NOT NULL -- created_at + 24h
|
|
36
|
+
consumed_at timestamptz NULL
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`users` 表上加索引 `users_email_lower_unique`(基于 `lower(email)`),
|
|
40
|
+
保证大小写不敏感的唯一性。
|
|
41
|
+
|
|
42
|
+
## 错误处理
|
|
43
|
+
|
|
44
|
+
| 错误码 | HTTP | 触发场景 |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `USER.AUTH.EMAIL_INVALID` | 400 | 邮箱不符合 RFC 5322 |
|
|
47
|
+
| `USER.AUTH.PASSWORD_TOO_SHORT` | 400 | 密码不足 8 字符 |
|
|
48
|
+
| `USER.AUTH.EMAIL_TAKEN` | 409 | 通用响应,不暴露邮箱是否存在 |
|
|
49
|
+
| `USER.AUTH.RATE_LIMITED` | 429 | 对应 PRD 验收标准 4 |
|
|
50
|
+
| `USER.AUTH.TOKEN_EXPIRED` | 410 | 验证链接超出 24 小时后被点击 |
|
|
51
|
+
| `USER.AUTH.TOKEN_INVALID` | 400 | token 不存在或已被消费 |
|
|
52
|
+
|
|
53
|
+
## 时序
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
POST /users/signup
|
|
57
|
+
controller 校验请求体
|
|
58
|
+
-> service.signup(email, password)
|
|
59
|
+
-> users.create(state='pending')
|
|
60
|
+
-> tokens.create(user_id, ttl=24h)
|
|
61
|
+
-> email.send_verification(user, token.token)
|
|
62
|
+
-> 201 Created { id, email, state }
|
|
63
|
+
|
|
64
|
+
GET /auth/verify?token=...
|
|
65
|
+
controller -> service.verify(token)
|
|
66
|
+
-> tokens.consume(token) # 拒绝过期 / 已用 token
|
|
67
|
+
-> users.activate(user_id)
|
|
68
|
+
-> session.create(user_id)
|
|
69
|
+
-> 302 重定向到 /welcome
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 测试计划
|
|
73
|
+
|
|
74
|
+
- `service.signup` 的单元测试覆盖全部 6 种错误码
|
|
75
|
+
- 针对 `contracts/openapi/api.yaml` 的契约测试
|
|
76
|
+
- 属性测试:用同一 token 重复 verify 应当幂等
|
|
77
|
+
- 见:[docs/07-质量与测试/test-plans/<%= it.feature.slug %>.md](../07-质量与测试/test-plans/<%= it.feature.slug %>.md)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 前端设计:<%= it.feature.title %>(web)
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> 后端设计:[docs/04-后端详细设计/<%= it.feature.slug %>.md](../04-后端详细设计/<%= it.feature.slug %>.md)
|
|
5
|
+
|
|
6
|
+
## 用户流程
|
|
7
|
+
|
|
8
|
+
1. 访客从市场站点跳转到 `/signup`
|
|
9
|
+
2. 填写邮箱 + 密码;客户端实时校验长度与格式
|
|
10
|
+
3. 提交 → 按钮进入 loading → 成功或失败 toast
|
|
11
|
+
4. 成功后页面切到"请查收邮件",提供"重新发送"按钮
|
|
12
|
+
5. 用户点击邮件中的链接 → 后端验证 → 302 跳到 `/welcome`
|
|
13
|
+
|
|
14
|
+
## 页面 / 组件拆分
|
|
15
|
+
|
|
16
|
+
| 组件 | 职责 |
|
|
17
|
+
|------|------|
|
|
18
|
+
| `SignupPage` | 顶层路由,持有表单状态与提交逻辑 |
|
|
19
|
+
| `EmailField` | 提交前内联校验 RFC-5322 邮箱语法 |
|
|
20
|
+
| `PasswordField` | 长度计 + 强度提示 |
|
|
21
|
+
| `SubmitButton` | 两个字段都合法前禁用;请求中显示 loading 旋转图 |
|
|
22
|
+
| `CheckEmailScreen` | 注册后确认页;带 60 秒冷却的"重新发送"按钮 |
|
|
23
|
+
| `WelcomePage` | 验证后落地页 |
|
|
24
|
+
|
|
25
|
+
## 状态管理
|
|
26
|
+
|
|
27
|
+
- 表单字段使用组件本地 state,表单层无需全局 store
|
|
28
|
+
- `useSignupMutation` 包装 API 调用,由 tanstack-query 处理重试策略
|
|
29
|
+
- 鉴权会话在 `/welcome` 加载**之后**填充,使用 verify 重定向时下发的
|
|
30
|
+
cookie,永远不做乐观更新
|
|
31
|
+
|
|
32
|
+
## API 调用
|
|
33
|
+
|
|
34
|
+
本特性使用 OpenAPI 生成的客户端,不要手写 fetch 调用。
|
|
35
|
+
|
|
36
|
+
- 契约:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
37
|
+
- 操作:`POST /users/signup`(提交表单)、`GET /auth/verify`(验证落地)
|
|
38
|
+
|
|
39
|
+
## 校验
|
|
40
|
+
|
|
41
|
+
客户端校验与后端接受的形态保持一致;服务端仍然是 `EMAIL_TAKEN` 与限流
|
|
42
|
+
响应的权威。
|
|
43
|
+
|
|
44
|
+
| 字段 | 规则 | UI 反馈 |
|
|
45
|
+
|------|------|---------|
|
|
46
|
+
| email | RFC 5322 语法 | 内联提示"请输入合法邮箱" |
|
|
47
|
+
| password | ≥ 8 字符 | 输入框下方显示强度计 + 字符计数 |
|
|
48
|
+
|
|
49
|
+
## 无障碍
|
|
50
|
+
|
|
51
|
+
- 表单可纯键盘操作;回车提交
|
|
52
|
+
- 错误信息使用 `role="alert"`,让屏幕阅读器即时朗读
|
|
53
|
+
- 校验状态绝不只用颜色表达,必须配图标 + 文字
|
|
54
|
+
- 密码字段的"显示 / 隐藏"切换可被 Tab 触达并被屏幕阅读器朗读
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# PRD:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> 这是 `create-keel --sample-feature` 附带的样例特性。把它当作可读的范例:
|
|
5
|
+
> 看一遍后用你产品自己的 PRD 替换它。
|
|
6
|
+
|
|
7
|
+
## 背景
|
|
8
|
+
|
|
9
|
+
终端用户需要能在平台上自助创建账户。当前由销售团队手动开通账户,这种方式
|
|
10
|
+
撑不过最初 50 个客户。自助注册表单去掉手动环节,把首次价值时间从数天
|
|
11
|
+
缩短到数分钟。
|
|
12
|
+
|
|
13
|
+
## 目标
|
|
14
|
+
|
|
15
|
+
- [ ] 新用户能用邮箱 + 密码在 60 秒内完成账户创建
|
|
16
|
+
- [ ] 验证邮箱前账户除了浏览公开文档外不能做任何事
|
|
17
|
+
- [ ] 销售团队不再为自助层用户手动开账户
|
|
18
|
+
|
|
19
|
+
## 非目标
|
|
20
|
+
|
|
21
|
+
- 第三方登录(Google / GitHub)—— v1 不做,单独排期
|
|
22
|
+
- SAML / SSO —— 企业层路线图
|
|
23
|
+
- 密码重置流程 —— 由姊妹特性 `password-reset` 覆盖
|
|
24
|
+
|
|
25
|
+
## 用户故事
|
|
26
|
+
|
|
27
|
+
**作为** 潜在用户,**我希望** 能用邮箱与密码自助注册账户,**以便** 不必
|
|
28
|
+
联系销售就能开始试用产品。
|
|
29
|
+
|
|
30
|
+
**作为** 产品负责人,**我希望** 新账户必须验证邮箱,**以便** 垃圾邮件与
|
|
31
|
+
拼写错误不会污染活跃用户指标。
|
|
32
|
+
|
|
33
|
+
## 验收标准(EARS)
|
|
34
|
+
|
|
35
|
+
1. WHEN 访客在注册表单中提交语法合法的邮箱与不少于 8 字符的密码,THE 系统 SHALL 在 30 秒内创建一条 `pending` 状态的用户记录并发送验证邮件。
|
|
36
|
+
2. IF 访客提交的邮箱已被注册,THEN THE 系统 SHALL 返回 HTTP 409 与不暴露邮箱存在性的通用错误信息。
|
|
37
|
+
3. WHILE 用户处于 `pending` 状态,THE 系统 SHALL 拒绝该用户发起的每一次已认证 API 调用,返回 HTTP 403 与错误码 `USER.AUTH.EMAIL_NOT_VERIFIED`。
|
|
38
|
+
4. WHERE 启用了限流,THE 系统 SHALL 允许同一 IP 每 15 分钟最多 5 次注册尝试,超出后返回 HTTP 429。
|
|
39
|
+
5. WHEN 用户在收到链接的 24 小时内点击验证链接,THE 系统 SHALL 把用户记录切换到 `active` 状态并重定向到注册后欢迎页。
|
|
40
|
+
|
|
41
|
+
## 关联引用
|
|
42
|
+
|
|
43
|
+
- 后端设计:[docs/04-后端详细设计/<%= it.feature.slug %>.md](../04-后端详细设计/<%= it.feature.slug %>.md)
|
|
44
|
+
- 前端设计:[docs/05-前端客户端详细设计/<%= it.feature.slug %>-web.md](../05-前端客户端详细设计/<%= it.feature.slug %>-web.md)
|
|
45
|
+
- 契约:[<%= it.feature.contractAnchor %>](../../<%= it.feature.contractAnchor %>)
|
|
46
|
+
- 测试计划:[docs/07-质量与测试/test-plans/<%= it.feature.slug %>.md](../07-质量与测试/test-plans/<%= it.feature.slug %>.md)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 测试计划:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> PRD:[docs/01-背景与需求/prd-<%= it.feature.slug %>.md](../../01-背景与需求/prd-<%= it.feature.slug %>.md)
|
|
5
|
+
|
|
6
|
+
## 覆盖矩阵
|
|
7
|
+
|
|
8
|
+
下表把每条 PRD 验收标准映射到一个或多个测试产物。
|
|
9
|
+
|
|
10
|
+
| PRD 标准 | 测试类型 | 位置 |
|
|
11
|
+
|---------|---------|------|
|
|
12
|
+
| 1. WHEN 合法注册 → 201 + 邮件已发送 | 单元 + 端到端 | `server/test/signup.test.ts` + `web/e2e/signup-happy.spec.ts` |
|
|
13
|
+
| 2. IF 邮箱已被占 → 409 通用 | 单元 | `server/test/signup.duplicate.test.ts` |
|
|
14
|
+
| 3. WHILE pending → 403 EMAIL_NOT_VERIFIED | 集成 | `contracts/tests/acceptance/<%= it.feature.slug %>.feature` |
|
|
15
|
+
| 4. WHERE 启用限流 → 5 次 / 15 分钟 | 集成 | `server/test/signup.rate-limit.test.ts` |
|
|
16
|
+
| 5. WHEN 24 小时内 verify → active + 重定向 | 单元 + 端到端 | `server/test/verify.test.ts` + `web/e2e/signup-verify.spec.ts` |
|
|
17
|
+
|
|
18
|
+
## 边界用例
|
|
19
|
+
|
|
20
|
+
- [ ] 邮箱 / 密码为空 → 400,两条错误同时返回
|
|
21
|
+
- [ ] 邮箱含首尾空格 → 查询前裁剪
|
|
22
|
+
- [ ] 密码恰好 8 字符 → 接受(边界)
|
|
23
|
+
- [ ] 验证链接在第 24 小时 - 1 秒被点击 → 接受(边界)
|
|
24
|
+
- [ ] 验证链接被点击两次 → 第二次返回 `TOKEN_INVALID`,不是 500
|
|
25
|
+
- [ ] 同一邮箱并发注册 → 仅一笔成功,其余 409
|
|
26
|
+
|
|
27
|
+
## 属性测试
|
|
28
|
+
|
|
29
|
+
`signup → verify` 流程具有天然的 round-trip 性质:
|
|
30
|
+
|
|
31
|
+
> 对任意合法的 (email, password),注册之后用收到的 token 完成 verify,
|
|
32
|
+
> 结果应当是一个状态为 `active` 且邮箱与输入相同的用户记录。
|
|
33
|
+
|
|
34
|
+
用 fast-check 实现,放在 `server/test/signup.properties.test.ts`。
|
|
35
|
+
|
|
36
|
+
## 缺陷追踪
|
|
37
|
+
|
|
38
|
+
每次缺陷在 `docs/07-质量与测试/defects/<%= it.feature.slug %>-bug-NNN.md`
|
|
39
|
+
立一份记录,关联回本测试计划。本特性提交后的第一个缺陷文件命名为
|
|
40
|
+
`<%= it.feature.slug %>-bug-001.md`。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 测试计划:<%= it.feature.title %>
|
|
2
|
+
|
|
3
|
+
> 特性 slug:`<%= it.feature.slug %>`
|
|
4
|
+
> PRD:[docs/01-背景与需求/prd-<%= it.feature.slug %>.md](../../01-背景与需求/prd-<%= it.feature.slug %>.md)
|
|
5
|
+
|
|
6
|
+
## 覆盖矩阵
|
|
7
|
+
|
|
8
|
+
| 验收标准(PRD) | 测试类型 | 位置 |
|
|
9
|
+
|----------------|---------|------|
|
|
10
|
+
| 1. WHEN ... SHALL ... | 单元 | `server/test/<%= it.feature.slug %>.test.ts` |
|
|
11
|
+
| 2. IF ... THEN SHALL ... | 契约 | `contracts/tests/acceptance/<%= it.feature.slug %>.feature` |
|
|
12
|
+
| 3. WHILE ... SHALL ... | 端到端 | `web/e2e/<%= it.feature.slug %>.spec.ts` |
|
|
13
|
+
|
|
14
|
+
## 边界用例
|
|
15
|
+
|
|
16
|
+
- [ ] 空 / null 输入
|
|
17
|
+
- [ ] 边界值
|
|
18
|
+
- [ ] 并发 / 竞态(如适用)
|
|
19
|
+
|
|
20
|
+
## 缺陷追踪
|
|
21
|
+
|
|
22
|
+
每次缺陷在 `docs/07-质量与测试/defects/<%= it.feature.slug %>-bug-NNN.md`
|
|
23
|
+
立一份记录,关联回本测试计划。
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Dart 编码规范
|
|
2
|
+
|
|
3
|
+
> 真值是 `dart format` + `dart analyze`(包含 `analysis_options.yaml`);本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `analyze` job 阻断违规。
|
|
4
|
+
|
|
5
|
+
config: mobile/analysis_options.yaml
|
|
6
|
+
|
|
7
|
+
## 命名规范
|
|
8
|
+
|
|
9
|
+
- 类 / 枚举 / typedef:`PascalCase`
|
|
10
|
+
- 方法 / 变量 / 字段:`camelCase`;常量 `lowerCamelCase` 或 `kPascalCase`(与项目内一种二选一锁死)
|
|
11
|
+
- 库 / 文件名:`snake_case.dart`
|
|
12
|
+
- 私有:下划线前缀 `_internal`
|
|
13
|
+
|
|
14
|
+
## 格式化
|
|
15
|
+
|
|
16
|
+
- `dart format` 单一权威;评审禁讨论缩进 / 行宽
|
|
17
|
+
- 行宽 100;2 空格缩进;尾随逗号在多参 / 多元素结构上必加(dart format 行为依赖它)
|
|
18
|
+
- import 顺序:`dart:*` → `package:*` → 相对路径,每段空行
|
|
19
|
+
|
|
20
|
+
## 错误处理
|
|
21
|
+
|
|
22
|
+
- `Exception` 子类描述业务错误;`Error` 仅用于不可恢复的 bug
|
|
23
|
+
- 禁止 `catch` 不绑定变量;至少 `catch (e, st)` 并 log
|
|
24
|
+
- 异步:`async/await` 是 happy path;`Future.wait` 时显式选择 `eagerError` 行为
|
|
25
|
+
- 空值:用 sound null safety;禁止 `!` 解包除非有断言注释
|
|
26
|
+
|
|
27
|
+
## Widget / 状态管理
|
|
28
|
+
|
|
29
|
+
- StatelessWidget 优先;StatefulWidget 仅在必要时
|
|
30
|
+
- 状态管理库(Provider / Riverpod / Bloc):项目内统一一种,写在 ADR 中
|
|
31
|
+
- build 方法保持 < 50 行;超过就拆子组件
|
|
32
|
+
|
|
33
|
+
## 依赖与模块
|
|
34
|
+
|
|
35
|
+
- `pubspec.yaml` 钉版本,与 `tech-stack-mobile.md` 对齐
|
|
36
|
+
- 跨 `apps/*` 走 `packages/<shared>/` 或独立 pub package;禁止直接 path import 兄弟 app
|
|
37
|
+
|
|
38
|
+
## 注释与文档
|
|
39
|
+
|
|
40
|
+
- 公共 API 必须有 dartdoc(`///` 三斜杠)
|
|
41
|
+
- 复杂逻辑加 `// Why:` 段
|
|
42
|
+
- `// TODO` 必须带 issue 链接
|
|
43
|
+
|
|
44
|
+
## lint 配置真值
|
|
45
|
+
|
|
46
|
+
| 工具 | 配置文件 | CI job |
|
|
47
|
+
|------|---------|--------|
|
|
48
|
+
| dart format | (内置,无配置) | `format-check` |
|
|
49
|
+
| dart analyze | `mobile/analysis_options.yaml` | `analyze` |
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Go 编码规范
|
|
2
|
+
|
|
3
|
+
> 真值是 `gofmt` + `golangci-lint` 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `lint` job 阻断违规。
|
|
4
|
+
|
|
5
|
+
config: server/.golangci.yml
|
|
6
|
+
|
|
7
|
+
## 命名规范
|
|
8
|
+
|
|
9
|
+
- 包名:全小写单词,**无下划线 / 驼峰**;表达内容而非组织(`http` 而非 `httputil`)
|
|
10
|
+
- 导出符号:`PascalCase`;非导出:`camelCase`
|
|
11
|
+
- 错误变量:`Err<Reason>`(`ErrNotFound`);接口:以 `-er` 结尾(`Reader`)
|
|
12
|
+
- 测试:`Test<Func>`;fuzz:`Fuzz<Func>`;benchmark:`Benchmark<Func>`
|
|
13
|
+
|
|
14
|
+
## 格式化
|
|
15
|
+
|
|
16
|
+
- `gofmt -s` 单一权威;评审禁讨论缩进 / 行宽
|
|
17
|
+
- 行宽建议 120 但不强制(gofmt 不限);import 三段(标准库 → 第三方 → 本仓库),用空行隔开
|
|
18
|
+
- `goimports` 自动处理;CI 中 `gofmt -l` 列出未格式化文件即阻断
|
|
19
|
+
|
|
20
|
+
## 错误处理
|
|
21
|
+
|
|
22
|
+
- 错误是返回值,不是异常;`if err != nil { return ... }` 是常态
|
|
23
|
+
- 包裹错误用 `fmt.Errorf("...: %w", err)`;上层用 `errors.Is` / `errors.As` 解包
|
|
24
|
+
- 禁止 `_ = err` 吞错;明确处理或加注释 `// best-effort, see issue #xxx`
|
|
25
|
+
- panic 仅在不可恢复(程序 bug)时用;HTTP handler 不允许 panic 逃逸
|
|
26
|
+
- sentinel error 在包级声明:`var ErrNotFound = errors.New("not found")`
|
|
27
|
+
|
|
28
|
+
## 并发约束
|
|
29
|
+
|
|
30
|
+
- 优先 channel;mutex 仅在 channel 不合适时用
|
|
31
|
+
- 禁止 goroutine 泄露;启动 goroutine 必须有明确 cancel / done 机制
|
|
32
|
+
- `context.Context` 是函数第一个参数;禁止存储在 struct field
|
|
33
|
+
|
|
34
|
+
## 依赖与模块
|
|
35
|
+
|
|
36
|
+
- 第三方依赖在 `go.mod` 钉版本,与 `tech-stack-server.md` 对齐
|
|
37
|
+
- 主模块 `go.mod` 跟随仓库根;跨 `apps/*` 在 multi-app 模式下走 `internal/<shared>` 或独立 module
|
|
38
|
+
|
|
39
|
+
## 注释与文档
|
|
40
|
+
|
|
41
|
+
- 包级 doc comment:`// Package <name> ...`,必须存在
|
|
42
|
+
- 导出符号 doc comment:`// <Name> ...` 开头(godoc 约定)
|
|
43
|
+
- 不写 `// TODO` 而不带 issue 链接
|
|
44
|
+
|
|
45
|
+
## lint 配置真值
|
|
46
|
+
|
|
47
|
+
| 工具 | 配置文件 | CI job |
|
|
48
|
+
|------|---------|--------|
|
|
49
|
+
| gofmt | (内置,无配置) | `format-check` |
|
|
50
|
+
| golangci-lint | `server/.golangci.yml` | `lint` |
|
|
51
|
+
| go vet | (内置) | `vet` |
|
|
52
|
+
| govulncheck | (命令行运行) | `security` |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Java 编码规范
|
|
2
|
+
|
|
3
|
+
> 真值是 Checkstyle / Spotless 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `checkstyle` 与 `spotless:check` 阻断违规。
|
|
4
|
+
|
|
5
|
+
config: server/checkstyle.xml
|
|
6
|
+
|
|
7
|
+
## 命名规范
|
|
8
|
+
|
|
9
|
+
- 类 / 接口 / 枚举:`PascalCase`;接口禁止 `I` 前缀
|
|
10
|
+
- 方法 / 字段:`camelCase`;常量:`UPPER_SNAKE_CASE`
|
|
11
|
+
- 包名:全小写 `com.<org>.<product>.<module>`,禁止下划线
|
|
12
|
+
- 测试类:`<被测类>Test`;集成测试 `<场景>IT`
|
|
13
|
+
|
|
14
|
+
## 格式化
|
|
15
|
+
|
|
16
|
+
- Spotless(Google Java Format AOSP 风格)单一权威;评审禁讨论分号 / 大括号位置
|
|
17
|
+
- 行宽 120;4 空格缩进;import 顺序:`java.*` → `javax.*` → 第三方 → `com.<org>.*`,每段空行
|
|
18
|
+
- 删除未使用 import(Spotless 自动)
|
|
19
|
+
|
|
20
|
+
## 错误处理
|
|
21
|
+
|
|
22
|
+
- 检查异常仅在确实可恢复时使用;其余抛 `RuntimeException` 子类
|
|
23
|
+
- 禁止 `catch (Exception e)` 吞错;至少 log + rethrow 或用 sentinel 返回值
|
|
24
|
+
- 资源管理走 try-with-resources;禁止手写 `finally { close() }`
|
|
25
|
+
- API 边界统一异常→`ProblemDetails`(RFC 7807)
|
|
26
|
+
|
|
27
|
+
## 设计约束
|
|
28
|
+
|
|
29
|
+
- POJO 走 record(Java 17+)或 Lombok `@Value`;二选一在 ADR 中记录
|
|
30
|
+
- 禁止字段级公共可变状态;若必须用 `volatile` / `AtomicReference`
|
|
31
|
+
- 依赖注入由 Spring 管理;构造函数注入,禁止字段注入
|
|
32
|
+
|
|
33
|
+
## 依赖与模块
|
|
34
|
+
|
|
35
|
+
- 第三方依赖在 `pom.xml` 钉版本,与 `tech-stack-server.md` frontmatter 对齐
|
|
36
|
+
- 跨模块引用:`apps/*` 之间不允许直接 import(multi-app 模式下走 contract / 共享 packages)
|
|
37
|
+
|
|
38
|
+
## 注释与文档
|
|
39
|
+
|
|
40
|
+
- 公共类 / 公共方法必须有 JavaDoc,含 `@param` `@return` `@throws`
|
|
41
|
+
- 复杂算法或决策点加 `// Why:` 段
|
|
42
|
+
- 禁止 `// TODO` 不带 issue 链接
|
|
43
|
+
|
|
44
|
+
## lint 配置真值
|
|
45
|
+
|
|
46
|
+
| 工具 | 配置文件 | CI job |
|
|
47
|
+
|------|---------|--------|
|
|
48
|
+
| Checkstyle | `server/checkstyle.xml` | `checkstyle` |
|
|
49
|
+
| Spotless | `server/pom.xml` 中 `<plugin>spotless</plugin>` 段 | `spotless-check` |
|
|
50
|
+
| 编译警告 | `pom.xml` `-Werror` | `compile` |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Python 编码规范
|
|
2
|
+
|
|
3
|
+
> 真值是 `ruff` + `mypy` 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `lint` 与 `typecheck` job 阻断违规。
|
|
4
|
+
|
|
5
|
+
config: server/pyproject.toml
|
|
6
|
+
|
|
7
|
+
## 命名规范
|
|
8
|
+
|
|
9
|
+
- 模块 / 包:`snake_case`,全小写
|
|
10
|
+
- 类:`PascalCase`;异常类:以 `Error` 结尾(`UserNotFoundError`)
|
|
11
|
+
- 函数 / 变量:`snake_case`;常量:`UPPER_SNAKE_CASE`
|
|
12
|
+
- 私有:单下划线前缀 `_helper`;name mangling 双下划线仅在确需时
|
|
13
|
+
|
|
14
|
+
## 格式化
|
|
15
|
+
|
|
16
|
+
- `ruff format` 单一权威(替代 black + isort);评审禁讨论引号 / 行宽
|
|
17
|
+
- 行宽 100;4 空格缩进;import 顺序:标准库 → 第三方 → 本地
|
|
18
|
+
- 删除未使用 import(ruff 自动)
|
|
19
|
+
|
|
20
|
+
## 类型注解
|
|
21
|
+
|
|
22
|
+
- 全文件类型注解;函数签名禁止省略
|
|
23
|
+
- 严格 mypy:`disallow_untyped_defs = true`,`strict_optional = true`
|
|
24
|
+
- 优先 `pydantic.BaseModel` 描述 API 边界数据;`@dataclass(slots=True)` 描述内部值对象
|
|
25
|
+
|
|
26
|
+
## 错误处理
|
|
27
|
+
|
|
28
|
+
- 业务异常自定义 `XxxError(BaseAppError)`;禁止 `raise Exception(...)`
|
|
29
|
+
- 禁止裸 `except:`;至少 `except Exception:` 并 log
|
|
30
|
+
- 资源管理走 `with`;禁止手写 `try / finally: f.close()`
|
|
31
|
+
- 异步:`asyncio.gather` 时显式选择 `return_exceptions` 行为
|
|
32
|
+
|
|
33
|
+
## 依赖与导入
|
|
34
|
+
|
|
35
|
+
- 第三方依赖在 `pyproject.toml` 钉版本,与 `tech-stack-server.md` 对齐
|
|
36
|
+
- 跨 `apps/*` 引用走 `apps/_shared/` 或共享 distribution;禁止直接 `from apps.other import ...`
|
|
37
|
+
- 循环 import:拆模块或用 TYPE_CHECKING 延迟
|
|
38
|
+
|
|
39
|
+
## 注释与文档
|
|
40
|
+
|
|
41
|
+
- 公共函数 / 类必须 docstring(Google 或 PEP 257 风格,仓库二选一锁死)
|
|
42
|
+
- 复杂决策点加 `# Why:` 段
|
|
43
|
+
- `# TODO` 必须带 issue 链接
|
|
44
|
+
|
|
45
|
+
## lint 配置真值
|
|
46
|
+
|
|
47
|
+
| 工具 | 配置文件 | CI job |
|
|
48
|
+
|------|---------|--------|
|
|
49
|
+
| ruff | `server/pyproject.toml` `[tool.ruff]` 段 | `lint` |
|
|
50
|
+
| mypy | `server/pyproject.toml` `[tool.mypy]` 段 | `typecheck` |
|
|
51
|
+
| pip-audit | (命令行) | `security` |
|