@namewta/speculo 0.2.9 → 0.2.11
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/README.md +5 -2
- package/package.json +1 -1
- package/template/canonical/canonical-specdev-tickets.md +19 -19
- package/template/workflows/specdev/P-goal-plan/execution-sections.md +35 -12
- package/template/workflows/specdev/P-goal-plan/governance-sections.md +3 -3
- package/template/workflows/specdev/P-goal-plan/lead-orchestration-protocol.md +5 -3
- package/template/workflows/specdev/T-tickets/T-tickets.md +10 -10
- package/template/workflows/specdev/T-tickets/tickets-map-template.md +14 -14
- package/template/workflows/specdev/common/prototype/LOGIC.md +89 -0
- package/template/workflows/specdev/common/prototype/SKILL.md +78 -0
- package/template/workflows/specdev/common/prototype/UI.md +120 -0
package/README.md
CHANGED
|
@@ -79,12 +79,15 @@ Every workflow ships an `INDEX.md` as its auto-generated work catalog. Work entr
|
|
|
79
79
|
|
|
80
80
|
## Acknowledgments — Honoring Open Source Heritage
|
|
81
81
|
|
|
82
|
-
Speculo stands on the shoulders of pioneers. With deep gratitude, we honor:
|
|
82
|
+
Speculo stands on the shoulders of pioneers — including our own failures. With deep gratitude, we honor:
|
|
83
83
|
|
|
84
|
+
- **[SpecForge](https://github.com/NAMEWTA/specforge)** — the author's own previous project. A CLI-driven SDD tool whose failure taught us the most important lesson: in the AI era, documents are the interface, not CLI commands. Making humans learn commands to manage AI documents gets the relationship backwards.
|
|
84
85
|
- **[Matt Pocock Skills](https://github.com/mattpocock/skills)** — the groundbreaking work that defined AI-assisted development workflows and inspired the very concept of packageable agent skills.
|
|
85
86
|
- **[Khazix Skills](https://github.com/KKKKhazix/khazix-skills)** — a rich ecosystem of practical agent skills that demonstrated the power of community-driven workflow sharing.
|
|
87
|
+
- **[OpenSpec](https://github.com/Fission-AI/OpenSpec)** — a lightweight spec-driven development framework whose changes/ directory structure and archive mechanism deeply influenced Speculo's persistence contract design.
|
|
88
|
+
- **[Superpowers](https://github.com/obra/superpowers)** — a complete agentic development methodology whose skill orchestration and subagent dispatch provided key reference for workflow package design.
|
|
86
89
|
|
|
87
|
-
Speculo
|
|
90
|
+
Speculo synthesizes lessons from all: from failure we learned "documents are the interface"; from Matt we inherited skill methodology; from OpenSpec we adopted engineering management; from Superpowers we studied orchestration. Together they form package-based workflow management, persistence contracts, and a unified install/migrate lifecycle. We carry their spirit forward.
|
|
88
91
|
|
|
89
92
|
## License
|
|
90
93
|
|
package/package.json
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
|
|
70
70
|
迭代直到用户批准拆分方案。每次修改后重新展示完整列表。
|
|
71
71
|
|
|
72
|
-
**完成标准**:用户已确认粒度、阻塞边与合并/拆分方案。批准后,tickets 将写入 `ticket/` 目录(一个 ticket 一个独立文件,命名为
|
|
72
|
+
**完成标准**:用户已确认粒度、阻塞边与合并/拆分方案。批准后,tickets 将写入 `ticket/` 目录(一个 ticket 一个独立文件,命名为 `NN-<name>.md`),并生成 `tickets-map.md` 作为总体地图和执行清单。
|
|
73
73
|
|
|
74
74
|
### 5. 发布
|
|
75
75
|
|
|
@@ -81,14 +81,14 @@
|
|
|
81
81
|
|
|
82
82
|
**5b. 写入单个 ticket 文件**
|
|
83
83
|
|
|
84
|
-
按依赖顺序(无阻塞者在前,被阻塞者在后),为每个 ticket 创建独立文件 `ticket
|
|
84
|
+
按依赖顺序(无阻塞者在前,被阻塞者在后),为每个 ticket 创建独立文件 `ticket/NN-<ticket-name>.md`。`NN` 为 ticket 编号(两位零填充阿拉伯数字:`01`, `02`, ..., `10`, ...),代表执行顺序。文件名与编号均不含 `#` 字符,避免 Markdown 链接被编码为 `%23`。
|
|
85
85
|
|
|
86
86
|
每个 ticket 文件按以下模板填写:
|
|
87
87
|
|
|
88
88
|
```markdown
|
|
89
|
-
# Ticket
|
|
89
|
+
# Ticket NN: <标题>
|
|
90
90
|
|
|
91
|
-
- **被阻塞于:** `./ticket
|
|
91
|
+
- **被阻塞于:** `./ticket/NN-<name>.md`, `./ticket/NN-<name>.md`(相对路径,或多个用逗号分隔。无阻塞则写"无 —— 可立即开始"。查看被引用 ticket 文件中的状态字段自行判断是否已就绪)
|
|
92
92
|
- **状态:** 未开始
|
|
93
93
|
|
|
94
94
|
<!-- 如需了解整体上下文、所有 ticket 的依赖关系全景或横切关注点,请查看 `../tickets-map.md`。 -->
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
|
|
147
147
|
**模板填写说明:**
|
|
148
148
|
|
|
149
|
-
- **被阻塞于**使用指向 `./ticket/` 目录的相对路径(如 `./ticket
|
|
149
|
+
- **被阻塞于**使用指向 `./ticket/` 目录的相对路径(如 `./ticket/01-auth.md`),多个用逗号分隔。执行者应自行打开被引用的 ticket 文件查看其状态字段,判断阻塞是否已解除
|
|
150
150
|
- **状态**初始固定为"未开始";实现者开始工作时改为"进行中",完成后改为"已完成"
|
|
151
151
|
- **战略与背景**是必填段——为执行者提供该 ticket 的决策锚点和当前现状。从 spec、ADR、对话中提取,不确定的标记 `[待确认]`
|
|
152
152
|
- **范围边界**是必填段——明确本 ticket 的 IN/REUSE/OUT 三列,防止范围蔓延。OUT 列吸收"明确不做"的内容
|
|
@@ -172,22 +172,22 @@
|
|
|
172
172
|
|
|
173
173
|
| 编号 | Ticket | 被阻塞于 | 状态 |
|
|
174
174
|
|------|--------|----------|------|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
175
|
+
| 01 | [ticket-name](./ticket/01-<kebab-title>.md) | 无 | 未开始 |
|
|
176
|
+
| 02 | [ticket-name](./ticket/02-<kebab-title>.md) | 01 | 未开始 |
|
|
177
|
+
| 10 | [ticket-name](./ticket/10-<kebab-title>.md) | 02, 05 | 未开始 |
|
|
178
178
|
|
|
179
179
|
> 状态枚举:未开始 / 进行中 / 已完成。所有 ticket 发布时初始状态为"未开始",随实现进度手动更新。
|
|
180
|
-
> **被阻塞于**列填写阻塞本 ticket 的 ticket 编号(如
|
|
180
|
+
> **被阻塞于**列填写阻塞本 ticket 的 ticket 编号(如 `01`、`02, 05`),执行者需自行打开对应 ticket 文件查看其状态。不可仅凭此表判断——始终以对应 ticket 文件中的状态字段为准。
|
|
181
181
|
|
|
182
182
|
## 依赖关系
|
|
183
183
|
|
|
184
184
|
<!-- 用 ASCII 树形图展示 ticket 之间的阻塞关系。 -->
|
|
185
185
|
|
|
186
186
|
```
|
|
187
|
-
|
|
188
|
-
├──
|
|
189
|
-
└──
|
|
190
|
-
└──
|
|
187
|
+
01-<name> ← 无阻塞,可立即开始
|
|
188
|
+
├── 02-<name> ← 阻塞于 01
|
|
189
|
+
└── 03-<name> ← 阻塞于 01
|
|
190
|
+
└── 04-<name> ← 阻塞于 03
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
## 横切关注点
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
|
|
203
203
|
<!-- 依赖图的文字说明。简单线性链可省略整个小节。对于扩展-收缩模式,解释三阶段。 -->
|
|
204
204
|
|
|
205
|
-
<描述为何 ticket
|
|
205
|
+
<描述为何 ticket B 被 ticket A 阻塞。扩展-收缩模式:扩展阶段创建新形式 → 迁移批次逐步切换调用点 → 收缩阶段删除旧形式。>
|
|
206
206
|
|
|
207
207
|
## 风险与注意事项
|
|
208
208
|
|
|
@@ -211,22 +211,22 @@
|
|
|
211
211
|
|
|
212
212
|
**tickets-map.md 填写说明:**
|
|
213
213
|
|
|
214
|
-
- **执行清单**的 Ticket 列使用指向 `./ticket/`
|
|
215
|
-
- **编号**列使用
|
|
216
|
-
- **被阻塞于**列填写阻塞者的编号(如
|
|
214
|
+
- **执行清单**的 Ticket 列使用指向 `./ticket/` 目录的相对链接(纯数字编号前缀,如 `./ticket/01-auth.md`),可在 markdown 渲染器中直接点击跳转
|
|
215
|
+
- **编号**列使用 `01`、`02`、`10` 格式(两位零填充阿拉伯数字,不含 `#`),代表依赖顺序
|
|
216
|
+
- **被阻塞于**列填写阻塞者的编号(如 `01`、`02, 05`),执行者需自行查看对应 ticket 文件的状态字段确认是否已就绪
|
|
217
217
|
- **状态**列由 T-tickets 初始化为"未开始",后续由实现者手动更新——始终以对应 ticket 文件中的状态字段为权威来源
|
|
218
218
|
- **依赖关系**用 ASCII 树形图直观展示阻塞链;纯线性链用缩进列表即可
|
|
219
219
|
- **横切关注点**只放跨 ticket 的规则——单 ticket 的规则留在该 ticket 文件内
|
|
220
220
|
- **阻塞关系说明**在依赖图非平凡时补充文字解释,特别是扩展-收缩排序的三阶段
|
|
221
221
|
|
|
222
|
-
**完成标准**:`ticket/` 目录已创建,所有 ticket 独立文件已按依赖顺序写入(命名为
|
|
222
|
+
**完成标准**:`ticket/` 目录已创建,所有 ticket 独立文件已按依赖顺序写入(命名为 `NN-<ticket-name>.md`);`tickets-map.md` 已写入——包含总体摘要、执行清单(编号、ticket 链接、被阻塞于、状态四列,初始均为"未开始")、依赖关系图和横切关注点;每个 ticket 声明阻塞边(使用相对路径)、战略与背景、范围边界、交付物、保留/不动和验收标准。
|
|
223
223
|
|
|
224
224
|
## 子文件引用
|
|
225
225
|
|
|
226
226
|
本入口为单文件 work,所有内容均已内联。以下引用供其他 work 读取产物:
|
|
227
227
|
|
|
228
228
|
- ``tickets-map`` —— 总体地图与执行清单(编号 | Ticket | 被阻塞于 | 状态)
|
|
229
|
-
- `specdev/changes/{change}/ticket/` —— 独立 ticket 文件目录,每个文件命名为
|
|
229
|
+
- `specdev/changes/{change}/ticket/` —— 独立 ticket 文件目录,每个文件命名为 `NN-<ticket-name>.md`(`NN` = `01`, `02`, ..., `10`, ...)
|
|
230
230
|
- ``spec`` —— 上游 spec(拆分依据)
|
|
231
231
|
- `specdev/adr/` —— 永久架构决策目录(已确认并提升的 ADR)
|
|
232
232
|
- `specdev/context/` —— 永久领域词汇表目录(已确认并提升的 CONTEXT)
|
|
@@ -32,15 +32,16 @@ P0 门禁先开,阻塞所有 P1/P2 关闭。ticket 可以在其依赖就绪后
|
|
|
32
32
|
- 用注释标注门禁边界:`--- P0 gate ---`
|
|
33
33
|
- 可立即开始的 ticket 标注 `[READY]`
|
|
34
34
|
- 扇出点标注 `[FAN-OUT: N路并行]`
|
|
35
|
+
- ticket 编号使用两位零填充纯数字(`01`, `02`, …),**不含** `#`
|
|
35
36
|
|
|
36
37
|
示例格式:
|
|
37
38
|
```
|
|
38
|
-
|
|
39
|
-
├→
|
|
40
|
-
├→
|
|
41
|
-
└→
|
|
39
|
+
04 [READY] → 05 [FAN-OUT: 3路并行]
|
|
40
|
+
├→ 06 [P0]
|
|
41
|
+
├→ 07 [P1]
|
|
42
|
+
└→ 08 [P1]
|
|
42
43
|
--- P0 gate ---
|
|
43
|
-
|
|
44
|
+
06 → 09 [P1] → 10 [P2]
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
将丰富后的 DAG 回写到 tickets-map.md 的「依赖关系」节,替换 T-tickets 写入的基础版本。
|
|
@@ -55,15 +56,27 @@ tickets-map.md 的「并行规则」节已由模板预设默认值(最大 3
|
|
|
55
56
|
|
|
56
57
|
## §5 — Per-Ticket Execution Protocol
|
|
57
58
|
|
|
59
|
+
本协议是对 I-implement 的实例化执行;实现者必须同时遵循 I-implement 四步协议(设计检查→TDD→双轴审查→提交)。权威入口:`<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>`。
|
|
60
|
+
|
|
58
61
|
### 协议定制
|
|
59
62
|
|
|
60
63
|
根据 input-validation.md 检测到的执行模型选择协议骨架:
|
|
61
64
|
|
|
62
65
|
#### Lead+Subagent 模型(完整八步)
|
|
63
66
|
|
|
64
|
-
1. **读取** —— Lead
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
1. **读取** —— 实现者(Lead 与子代理)在开始前**必须按顺序读取以下文件**建立完整上下文:
|
|
68
|
+
|
|
69
|
+
| # | 文件 | 用途 |
|
|
70
|
+
|---|------|------|
|
|
71
|
+
| 1 | `<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>` | specdev 实现流程入口——设计检查、TDD 循环、双轴审查、提交 |
|
|
72
|
+
| 2 | 本 ticket 全文 | 验收标准、范围边界、交付物、保留/不动 |
|
|
73
|
+
| 3 | 合同/参考权威对应行(如激活) | 编号验收条目或对照路径 |
|
|
74
|
+
| 4 | 项目 skills(如有) | 前后端编排、构建规范、路由/菜单、数据库标准等项目级约定——按实际检出的 skill 路径追加,可变 |
|
|
75
|
+
|
|
76
|
+
Lead 读取 issue 全文、合同/参考权威对应行、ticket 的验收标准。如果激活参考权威模式,对照参考快照中的对应交互路径。
|
|
77
|
+
|
|
78
|
+
2. **派单** —— Lead 输出结构化派单行 `IMPLEMENTER_DISPATCH <n> issue=<url> gate=<P0|P1|P2> allowlist=<files> contract_ids=<...>`(`<n>` 为两位零填充纯数字编号,如 `01`,不含 `#`),然后生成实现子代理(model: fable, 唯一 name)。Lead+Subagent 模型下,加载 `<Path>{roots.workflows}/specdev/P-goal-plan/lead-orchestration-protocol.md</Path>` 获取完整的编排协议——包括子代理上下文载荷结构、handoff 交接、合并冲突解决、Worktree 隔离和收尾审查的详细步骤。
|
|
79
|
+
3. **实现** —— 子代理在 file allowlist 内实现变更,按 ticket 指定的测试矩阵运行测试;实现过程遵循 I-implement 的设计检查与 TDD 红绿循环。
|
|
67
80
|
4. **双轴审查** —— 实现完成后,立即启动两个审查子代理并行运行:
|
|
68
81
|
- `reviewer-standards-<n>`:代码质量、架构、测试覆盖
|
|
69
82
|
- `reviewer-spec-<n>`:spec 合规、验收标准
|
|
@@ -75,9 +88,17 @@ tickets-map.md 的「并行规则」节已由模板预设默认值(最大 3
|
|
|
75
88
|
|
|
76
89
|
#### 简化模型(精简协议)
|
|
77
90
|
|
|
78
|
-
1. **读取**
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
1. **读取** —— 实现者在开始前**必须按顺序读取以下文件**建立完整上下文:
|
|
92
|
+
|
|
93
|
+
| # | 文件 | 用途 |
|
|
94
|
+
|---|------|------|
|
|
95
|
+
| 1 | `<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>` | specdev 实现流程入口——设计检查、TDD 循环、双轴审查、提交 |
|
|
96
|
+
| 2 | 本 ticket 文件 | 验收标准、范围边界、交付物 |
|
|
97
|
+
| 3 | spec 对应 User Story / 验收标准 | 行为与验收锚点 |
|
|
98
|
+
| 4 | 项目 skills(如有) | 项目级约定——按实际检出路径追加,可变 |
|
|
99
|
+
|
|
100
|
+
2. **实现** — 在 ticket 声明的范围内实现变更;遵循 I-implement 的设计检查与 TDD 红绿循环。
|
|
101
|
+
3. **审查** — 单审查者检查代码质量和 spec 合规(对齐 I-implement 双轴审查意图)。
|
|
81
102
|
4. **门禁** — 类型检查、测试通过。
|
|
82
103
|
5. **关闭** — 提交并关闭 issue。
|
|
83
104
|
|
|
@@ -87,12 +108,14 @@ tickets-map.md 的「并行规则」节已由模板预设默认值(最大 3
|
|
|
87
108
|
- 测试矩阵从 tickets 或 spec 的 Test Decisions 提取
|
|
88
109
|
- 双轴审查的派单模板写为可复制的文本块
|
|
89
110
|
- Lead 纪律写为不可协商的约束
|
|
111
|
+
- **§5 步骤 1 表格必须以 I-implement 为第一行**;项目 skills 仅作为后续可变项追加,不得因 skills 列表变化而挤掉或省略 I-implement
|
|
112
|
+
- ticket 编号、派单行、进度行一律使用纯数字(`01`),不使用 `#01`
|
|
90
113
|
|
|
91
114
|
### 草拟与确认
|
|
92
115
|
|
|
93
116
|
输出 §5 完整协议文本。等待用户确认后进入治理章节。
|
|
94
117
|
|
|
95
|
-
**完成标准**:§4 DAG 图无循环、门禁标注正确、对照表完整且经用户确认;§5
|
|
118
|
+
**完成标准**:§4 DAG 图无循环、门禁标注正确、对照表完整且经用户确认;§5 执行协议八步/精简流程已定制填入具体路径、测试矩阵和双轴审查模板,步骤 1 清单以 I-implement 为首项,经用户确认。
|
|
96
119
|
|
|
97
120
|
## 子文件引用
|
|
98
121
|
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
#### TICKET_DONE 格式(Lead+Subagent 模型)
|
|
71
71
|
|
|
72
72
|
```
|
|
73
|
-
TICKET_DONE
|
|
73
|
+
TICKET_DONE <n> (<k>/<N>) gate=<P0|P1|P2> contract_ids=<P0-01,P1-03> verify=<cmd:result> commit=<sha>
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
字段说明:
|
|
77
|
-
-
|
|
77
|
+
- `<n>` —— ticket 编号(两位零填充纯数字,如 `01`,不含 `#`)
|
|
78
78
|
- `(<k>/<N>)` —— 进度计数(当前第几个 / 总数)
|
|
79
79
|
- `gate` —— 门禁层级
|
|
80
80
|
- `contract_ids` —— 如激活合同模式,列出本 ticket 覆盖的合同条目 ID,逗号分隔;如无合同则使用 `adr_ref=<ADR-NNNN>`
|
|
@@ -94,7 +94,7 @@ MILESTONE_DONE issues_closed=<N>/<N> contract_todo=0 verify=GREEN
|
|
|
94
94
|
#### 格式变体
|
|
95
95
|
|
|
96
96
|
- **切面跟踪**(ticket 数 > 15 时推荐):在 TICKET_DONE 中增加 `slice=<ADR|DATA|SHELL|WORK|...>` 字段,按切面分组追踪进度
|
|
97
|
-
- **简化模型**:使用简化格式 `TICKET_DONE
|
|
97
|
+
- **简化模型**:使用简化格式 `TICKET_DONE <n> verify=<cmd:result>`
|
|
98
98
|
|
|
99
99
|
### 草拟与确认
|
|
100
100
|
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
| 目标规划文档 | `<Path>{roots.state}/specdev/changes/{change}/goal-plan.md</Path>` | 里程碑级约束、门禁次序、Definition of Done——子代理理解全局上下文和自身 ticket 在整体中的位置 |
|
|
18
18
|
| ticket 文件 | `<Path>{roots.state}/specdev/changes/{change}/</Path>` 下对应 ticket | 验收标准、file allowlist、合同引用、阻塞关系——子代理的直接任务定义,包含「要构建什么」「范围边界」「保留/不动」 |
|
|
19
19
|
| 合同验收条目 | 合同文档中本 ticket 覆盖的条目(如激活合同模式) | 编号验收条目及当前状态——子代理必须逐个满足的可检查条件 |
|
|
20
|
-
| 实现方法参考 | `<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>` | 深层模块设计原则、TDD
|
|
20
|
+
| 实现方法参考 | `<Path>{roots.workflows}/specdev/I-implement/I-implement.md</Path>` | 深层模块设计原则、TDD 红绿循环、双轴审查流程——子代理的实现方法论;子代理开始实现前**必须先读**,与 goal-plan §5 步骤 1 第一项一致 |
|
|
21
21
|
|
|
22
22
|
### 1.2 派单模板
|
|
23
23
|
|
|
24
24
|
Lead 向子代理发送的内容应包含以下结构化信息块,而非仅一行元数据:
|
|
25
25
|
|
|
26
26
|
```
|
|
27
|
-
IMPLEMENTER_DISPATCH
|
|
27
|
+
IMPLEMENTER_DISPATCH <n>
|
|
28
28
|
issue: <url>
|
|
29
29
|
gate: <P0|P1|P2>
|
|
30
30
|
allowlist: <files>
|
|
@@ -36,10 +36,12 @@ IMPLEMENTER_DISPATCH #<n>
|
|
|
36
36
|
context: <{roots.state}/specdev/changes/{change}/CONTEXT.md>
|
|
37
37
|
permanent_context: <{roots.state}/specdev/context/>
|
|
38
38
|
goal_plan: <{roots.state}/specdev/changes/{change}/goal-plan.md>
|
|
39
|
-
ticket_file: <{roots.state}/specdev/changes/{change}/<
|
|
39
|
+
ticket_file: <{roots.state}/specdev/changes/{change}/ticket/<nn>-<slug>.md>
|
|
40
40
|
implement_ref: <{roots.workflows}/specdev/I-implement/I-implement.md>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
其中 `<n>` / `<nn>` 为两位零填充纯数字 ticket 编号(如 `01`),不含 `#`。
|
|
44
|
+
|
|
43
45
|
Lead 在生成子代理时将以上文件作为上下文传入,确保子代理在开始实现前已读取全部载荷。永久 ADR 和永久 CONTEXT 目录可能为空——静默继续。
|
|
44
46
|
|
|
45
47
|
**完成标准**:每个子代理在启动时收到完整的上下文载荷(ADR、CONTEXT、goal-plan、ticket 文件、合同条目、I-implement 参考),所有路径指向真实存在的文件;IMPLEMENTER_DISPATCH 行和附加上下文已一并传递给子代理。
|
|
@@ -80,7 +80,7 @@ keywords: [tickets, 拆分, 任务, 垂直切片, 阻塞, 曳光弹]
|
|
|
80
80
|
|
|
81
81
|
迭代直到用户批准拆分方案。每次修改后重新展示完整列表。
|
|
82
82
|
|
|
83
|
-
**完成标准**:用户已确认粒度、阻塞边与合并/拆分方案。批准后,tickets 将写入 `ticket/` 目录(一个 ticket 一个独立文件,命名为
|
|
83
|
+
**完成标准**:用户已确认粒度、阻塞边与合并/拆分方案。批准后,tickets 将写入 `ticket/` 目录(一个 ticket 一个独立文件,命名为 `NN-<name>.md`),并生成 `tickets-map.md` 作为总体地图和执行清单。
|
|
84
84
|
|
|
85
85
|
### 5. 发布
|
|
86
86
|
|
|
@@ -92,14 +92,14 @@ keywords: [tickets, 拆分, 任务, 垂直切片, 阻塞, 曳光弹]
|
|
|
92
92
|
|
|
93
93
|
**5b. 写入单个 ticket 文件**
|
|
94
94
|
|
|
95
|
-
按依赖顺序(无阻塞者在前,被阻塞者在后),为每个 ticket 创建独立文件 `<Path>{roots.state}/specdev/changes/{change}/ticket
|
|
95
|
+
按依赖顺序(无阻塞者在前,被阻塞者在后),为每个 ticket 创建独立文件 `<Path>{roots.state}/specdev/changes/{change}/ticket/NN-<ticket-name>.md</Path>`。`NN` 为 ticket 编号(两位零填充阿拉伯数字:`01`, `02`, ..., `10`, ...),代表执行顺序。文件名与编号均不含 `#` 字符,避免 Markdown 链接被编码为 `%23`。
|
|
96
96
|
|
|
97
97
|
每个 ticket 文件按以下模板填写:
|
|
98
98
|
|
|
99
99
|
```markdown
|
|
100
|
-
# Ticket
|
|
100
|
+
# Ticket NN: <标题>
|
|
101
101
|
|
|
102
|
-
- **被阻塞于:** `./ticket
|
|
102
|
+
- **被阻塞于:** `./ticket/NN-<name>.md`, `./ticket/NN-<name>.md`(相对路径,或多个用逗号分隔。无阻塞则写"无 —— 可立即开始"。查看被引用 ticket 文件中的状态字段自行判断是否已就绪)
|
|
103
103
|
- **状态:** 未开始
|
|
104
104
|
|
|
105
105
|
<!-- 如需了解整体上下文、所有 ticket 的依赖关系全景或横切关注点,请查看 `../tickets-map.md`。 -->
|
|
@@ -157,7 +157,7 @@ keywords: [tickets, 拆分, 任务, 垂直切片, 阻塞, 曳光弹]
|
|
|
157
157
|
|
|
158
158
|
**模板填写说明:**
|
|
159
159
|
|
|
160
|
-
- **被阻塞于**使用指向 `./ticket/` 目录的相对路径(如 `./ticket
|
|
160
|
+
- **被阻塞于**使用指向 `./ticket/` 目录的相对路径(如 `./ticket/01-auth.md`),多个用逗号分隔。执行者应自行打开被引用的 ticket 文件查看其状态字段,判断阻塞是否已解除
|
|
161
161
|
- **状态**初始固定为"未开始";实现者开始工作时改为"进行中",完成后改为"已完成"
|
|
162
162
|
- **战略与背景**是必填段——为执行者提供该 ticket 的决策锚点和当前现状。从 spec、ADR、对话中提取,不确定的标记 `[待确认]`
|
|
163
163
|
- **范围边界**是必填段——明确本 ticket 的 IN/REUSE/OUT 三列,防止范围蔓延。OUT 列吸收"明确不做"的内容
|
|
@@ -178,16 +178,16 @@ T-tickets 阶段填写的列:**编号**、**Ticket**、**被阻塞于**、**
|
|
|
178
178
|
|
|
179
179
|
**tickets-map.md 填写说明:**
|
|
180
180
|
|
|
181
|
-
- **执行清单**的 Ticket 列使用指向 `./ticket/`
|
|
182
|
-
- **编号**列使用
|
|
183
|
-
- **被阻塞于**列填写阻塞者的编号(如
|
|
181
|
+
- **执行清单**的 Ticket 列使用指向 `./ticket/` 目录的相对链接(纯数字编号前缀,如 `./ticket/01-auth.md`)
|
|
182
|
+
- **编号**列使用 `01`、`02`、`10` 格式(两位零填充阿拉伯数字,不含 `#`),代表依赖顺序
|
|
183
|
+
- **被阻塞于**列填写阻塞者的编号(如 `01`、`02, 05`)
|
|
184
184
|
- **状态**列由 T-tickets 初始化为"未开始",后续由实现者手动更新——始终以对应 ticket 文件中的状态字段为权威来源
|
|
185
185
|
- **Gate** 列(P0/P1/P2)和 **Contract ID** 列由 P-goal-plan 填充;T-tickets 阶段留空或标 `[待标注]`
|
|
186
186
|
- **依赖关系**用 ASCII 树形图展示阻塞链——T-tickets 写入基础结构,P-goal-plan 叠加门禁标注
|
|
187
187
|
- **横切关注点**只放跨 ticket 的规则——单 ticket 的规则留在该 ticket 文件内
|
|
188
188
|
- **阻塞关系说明**在依赖图非平凡时补充文字解释
|
|
189
189
|
|
|
190
|
-
**完成标准**:`ticket/` 目录已创建,所有 ticket 独立文件已按依赖顺序写入(命名为
|
|
190
|
+
**完成标准**:`ticket/` 目录已创建,所有 ticket 独立文件已按依赖顺序写入(命名为 `NN-<ticket-name>.md`);`tickets-map.md` 已按 `<Path>{roots.workflows}/specdev/T-tickets/tickets-map-template.md</Path>` 格式写入——包含总体摘要、六列执行清单(Gate 和 Contract ID 列为 `[待标注]`)、基础依赖关系 ASCII 树形图、并行规则和横切关注点;每个 ticket 声明阻塞边、战略与背景、范围边界、交付物、保留/不动和验收标准。
|
|
191
191
|
|
|
192
192
|
## 子文件引用
|
|
193
193
|
|
|
@@ -198,7 +198,7 @@ T-tickets 阶段填写的列:**编号**、**Ticket**、**被阻塞于**、**
|
|
|
198
198
|
本入口为单文件 work,所有 ticket 拆分流程内容均已内联。以下引用供其他 work 读取产物:
|
|
199
199
|
|
|
200
200
|
- `<Path>{roots.state}/specdev/changes/{change}/tickets-map.md</Path>` —— 总体地图与执行清单(编号 | Ticket | 被阻塞于 | Gate | Contract ID | 状态),格式遵循 `<Path>{roots.workflows}/specdev/T-tickets/tickets-map-template.md</Path>`
|
|
201
|
-
- `<Path>{roots.state}/specdev/changes/{change}/ticket/</Path>` —— 独立 ticket 文件目录,每个文件命名为
|
|
201
|
+
- `<Path>{roots.state}/specdev/changes/{change}/ticket/</Path>` —— 独立 ticket 文件目录,每个文件命名为 `NN-<ticket-name>.md`(`NN` = `01`, `02`, ..., `10`, ...)
|
|
202
202
|
- `<Path>{roots.state}/specdev/changes/{change}/spec.md</Path>` —— 上游 spec(拆分依据)
|
|
203
203
|
- `<Path>{roots.state}/specdev/adr/</Path>` —— 永久架构决策目录(已确认并提升的 ADR)
|
|
204
204
|
- `<Path>{roots.state}/specdev/context/</Path>` —— 永久领域词汇表目录(已确认并提升的 CONTEXT)
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
| 编号 | Ticket | 被阻塞于 | Gate | Contract ID | 状态 |
|
|
8
8
|
|------|--------|----------|------|-------------|------|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
9
|
+
| 01 | [ticket-name](./ticket/01-<kebab-title>.md) | 无 | P0 | — | 未开始 |
|
|
10
|
+
| 02 | [ticket-name](./ticket/02-<kebab-title>.md) | 01 | P1 | P1-03 | 未开始 |
|
|
11
|
+
| 10 | [ticket-name](./ticket/10-<kebab-title>.md) | 02, 05 | P2 | — | 未开始 |
|
|
12
12
|
|
|
13
13
|
> **状态枚举**:未开始 / 进行中 / 已完成。所有 ticket 初始均为"未开始"。
|
|
14
|
-
> **被阻塞于**列填写阻塞本 ticket 的 ticket 编号(如
|
|
14
|
+
> **被阻塞于**列填写阻塞本 ticket 的 ticket 编号(如 `01`、`02, 05`),执行者需自行打开对应 ticket 文件查看其状态。不可仅凭此表判断——始终以对应 ticket 文件中的状态字段为准。
|
|
15
15
|
> **Gate 列**:P0 = 核心基础设施(阻塞所有后续工作)/ P1 = 主要功能切片 / P2 = 增强和边界情况。由 P-goal-plan 填充,T-tickets 阶段留空或标注 `[待标注]`。
|
|
16
16
|
> **Contract ID 列**:如有冻结合同/验收文档,填写本 ticket 覆盖的验收条目 ID(如 `P0-01, P1-03`);无合同则填 `—`。由 P-goal-plan 填充。
|
|
17
17
|
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
- 扇出点标注 [FAN-OUT: N路并行]
|
|
30
30
|
|
|
31
31
|
示例格式:
|
|
32
|
-
|
|
33
|
-
├→
|
|
34
|
-
├→
|
|
35
|
-
└→
|
|
32
|
+
01 [READY] → 02 [FAN-OUT: 3路并行]
|
|
33
|
+
├→ 03 [P0]
|
|
34
|
+
├→ 04 [P1]
|
|
35
|
+
└→ 05 [P1]
|
|
36
36
|
--- P0 gate ---
|
|
37
|
-
|
|
37
|
+
03 → 06 [P1] → 07 [P2]
|
|
38
38
|
-->
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
|
|
42
|
-
├──
|
|
43
|
-
└──
|
|
44
|
-
└──
|
|
41
|
+
01-<name> ← 无阻塞,可立即开始
|
|
42
|
+
├── 02-<name> ← 阻塞于 01
|
|
43
|
+
└── 03-<name> ← 阻塞于 01
|
|
44
|
+
└── 04-<name> ← 阻塞于 03
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## 并行规则
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
<!-- 依赖图的文字说明。简单线性链可省略整个小节。对于扩展-收缩模式,解释三阶段。 -->
|
|
65
65
|
|
|
66
|
-
<描述为何 ticket
|
|
66
|
+
<描述为何 ticket B 被 ticket A 阻塞。扩展-收缩模式:扩展阶段创建新形式 → 迁移批次逐步切换调用点 → 收缩阶段删除旧形式。>
|
|
67
67
|
|
|
68
68
|
## 风险与注意事项
|
|
69
69
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 逻辑原型
|
|
2
|
+
|
|
3
|
+
构建一个微小的交互式终端应用,让用户手动驱动状态模型。当问题涉及**业务逻辑、状态转换或数据形态**时使用——这类问题在纸面上看起来合理,但只有推进真实用例后才会暴露出不对劲的地方。
|
|
4
|
+
|
|
5
|
+
## 适用场景
|
|
6
|
+
|
|
7
|
+
- "我不确定这个状态机能否处理先 X 后 Y 的边界情况。"
|
|
8
|
+
- "这个数据模型真的能表示那种情况吗……"
|
|
9
|
+
- "我想在写之前先感受一下 API 应该长什么样。"
|
|
10
|
+
- 任何用户想要**按按钮、观察状态变化**的场景。
|
|
11
|
+
|
|
12
|
+
如果问题是"这个应该长什么样"——选错了分支。用 [UI.md](UI.md)。
|
|
13
|
+
|
|
14
|
+
## 流程
|
|
15
|
+
|
|
16
|
+
### 1. 陈述问题
|
|
17
|
+
|
|
18
|
+
在写代码之前,写下你正在为哪个状态模型和哪个问题做原型。一段话即可,放在原型的 README 或文件顶部的注释中。回答了错误问题的逻辑原型是纯粹浪费——让问题显式化,这样之后可以核查,无论用户是现在看着还是稍后 AFK 回来再看。
|
|
19
|
+
|
|
20
|
+
### 2. 选择语言
|
|
21
|
+
|
|
22
|
+
使用宿主项目所用的语言。如果项目没有明显的运行时(如文档仓库),则询问。
|
|
23
|
+
|
|
24
|
+
遵循项目已有的工具链约定——不要仅为原型引入新的包管理器或运行时。
|
|
25
|
+
|
|
26
|
+
### 3. 将逻辑隔离到一个可移植模块中
|
|
27
|
+
|
|
28
|
+
将实际逻辑——回答问题的部分——放在一个小巧、纯净的接口后面,使其之后可以被提取并放入正式代码库。围绕它的 TUI 是一次性的;逻辑模块不应该是一次性的。
|
|
29
|
+
|
|
30
|
+
正确的形态取决于问题:
|
|
31
|
+
|
|
32
|
+
- **纯 reducer**——`(state, action) => state`。适用于动作为离散事件且状态为单一值的场景。
|
|
33
|
+
- **状态机**——显式的状态和转换。适用于"当前哪些操作是合法的"本身就是问题的一部分。
|
|
34
|
+
- **一组纯函数**操作一个纯数据类型。适用于没有隐式当前状态、只有转换的场景。
|
|
35
|
+
- **类或模块**——具有清晰方法接口,当逻辑确实拥有持续性内部状态时使用。
|
|
36
|
+
|
|
37
|
+
选择最适合所问问题的形态,而*不是*最容易接入 TUI 的形态。保持纯净:无 I/O、无终端代码、无用于控制流的 `console.log`。TUI 导入它并调用它;反向不传递任何内容。
|
|
38
|
+
|
|
39
|
+
这就是让原型在自身生命周期之后仍有价值的关键:当问题得到回答后,验证通过的 reducer / 状态机 / 函数集可以被单独提升到正式模块中。
|
|
40
|
+
|
|
41
|
+
### 4. 构建最小的 TUI 来暴露状态
|
|
42
|
+
|
|
43
|
+
将其构建为**轻量 TUI**——每次 tick 清屏(`console.clear()` / `print("\033[2J\033[H")` / 等价方式)并重新渲染整个帧。用户应始终看到一个稳定视图,而非不断增长的滚动回溯。
|
|
44
|
+
|
|
45
|
+
每帧包含两部分,顺序如下:
|
|
46
|
+
|
|
47
|
+
1. **当前状态**,pretty-print 且 diff 友好(每行一个字段,或格式化 JSON)。使用**粗体**标注字段名或节标题,**暗色**标注次要上下文(时间戳、ID、派生值)。原生 ANSI 转义码即可——`\x1b[1m` 粗体、`\x1b[2m` 暗色、`\x1b[0m` 重置。无需引入样式库,除非项目中已经存在。
|
|
48
|
+
2. **键盘快捷键**,列在底部:`[a] 添加用户 [d] 删除用户 [t] 推动时钟 [q] 退出`。粗体标键、暗色标描述,或反过来——怎么读起来清晰怎么来。
|
|
49
|
+
|
|
50
|
+
行为:
|
|
51
|
+
|
|
52
|
+
1. **初始化状态**——单个内存中的对象/结构体。启动时渲染第一帧。
|
|
53
|
+
2. **每次读取一次按键(或一行)**,分发到修改状态的处理器。
|
|
54
|
+
3. **每次操作后重新渲染**完整帧——不追加,而是替换。
|
|
55
|
+
4. **循环直到退出。**
|
|
56
|
+
|
|
57
|
+
整个帧应适配一屏。
|
|
58
|
+
|
|
59
|
+
### 5. 一条命令即可运行
|
|
60
|
+
|
|
61
|
+
向项目已有任务运行器添加一条脚本(`package.json` scripts、`Makefile`、`justfile`、`pyproject.toml`)。用户应运行 `pnpm run <原型名称>` 或等价命令——永远不需要记住路径。
|
|
62
|
+
|
|
63
|
+
如果宿主项目没有任务运行器,直接把命令写在原型 README 的顶部。
|
|
64
|
+
|
|
65
|
+
### 6. 交付
|
|
66
|
+
|
|
67
|
+
给用户运行命令。他们会自己驱动它;有趣的时刻是他们说"等等,那不应该可能"或"嗯,我以为 X 会不一样"——那些是_想法_中的 bug,这正是整个原型的目的。如果他们想添加新操作,就添加。原型会演化。
|
|
68
|
+
|
|
69
|
+
### 7. 捕获答案并持久化
|
|
70
|
+
|
|
71
|
+
原型回答问题后,按 [SKILL](SKILL.md) 中持久化约定的方式捕获答案:
|
|
72
|
+
|
|
73
|
+
1. **提升验证过的逻辑**:将验证通过的 reducer / 状态机 / 函数集提升到正式模块中(决策已被吸收)。
|
|
74
|
+
2. **持久化答案记录**:在 `<Path>{roots.state}/<workflow>/changes/{change}/prototype/logic-<topic>.md</Path>` 创建答案文件,记录:
|
|
75
|
+
- 所回答的问题
|
|
76
|
+
- 结论——什么可行、什么不可行
|
|
77
|
+
- 被验证的逻辑模块的描述
|
|
78
|
+
- throwaway 分支指针(TUI 外壳代码所在位置)
|
|
79
|
+
3. **更新索引**:将新答案追加到 `prototype/index.md` 表格中。
|
|
80
|
+
|
|
81
|
+
TUI 外壳代码仍提交到 throwaway 分支——它是一次性的交互壳,真正有价值的部分(逻辑模块)已经提升到正式代码中。
|
|
82
|
+
|
|
83
|
+
## 反模式
|
|
84
|
+
|
|
85
|
+
- **不要加测试。** 需要测试的原型不再是原型。
|
|
86
|
+
- **不要接入真实数据库。** 使用内存存储,除非问题本身就是关于持久化的。
|
|
87
|
+
- **不要泛化。** 不要"如果我们以后想支持 X 呢"。原型只回答一个问题。
|
|
88
|
+
- **不要把逻辑和 TUI 混在一起。** 如果 reducer / 状态机引用了 `console.log`、提示符或终端转义码,它就不可移植了。让 TUI 成为纯模块外面的薄壳。
|
|
89
|
+
- **不要把 TUI 外壳发布到生产环境。** 外壳是为在终端中手动驱动而优化的。背后的逻辑模块才是值得保留的部分。
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prototype
|
|
3
|
+
description: 构建一个一次性原型来回答设计问题。当用户想要快速验证某个状态模型或逻辑是否正确,或探索 UI 应该长什么样时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 原型
|
|
7
|
+
|
|
8
|
+
原型是**回答问题的 disposable 代码**。问题决定形态。
|
|
9
|
+
|
|
10
|
+
## 选择分支
|
|
11
|
+
|
|
12
|
+
确定正在回答哪个问题——从用户的提示、周围代码中推断,或用户在场时直接询问:
|
|
13
|
+
|
|
14
|
+
- **"这个逻辑 / 状态模型对吗?"** → [LOGIC.md](LOGIC.md)。构建一个微小的交互式终端应用,推动状态机经过那些在纸面上难以推理的用例。
|
|
15
|
+
- **"这个应该长什么样?"** → [UI.md](UI.md)。在单个路由上生成几个截然不同的 UI 变体,通过 URL 查询参数和底部浮动栏切换。
|
|
16
|
+
|
|
17
|
+
两条分支产生的产物截然不同——选错会浪费整个原型。如果问题确实模糊且无法联系用户,默认选择与周围代码更匹配的分支(后端模块 → logic;页面或组件 → UI),并在原型顶部声明假设。
|
|
18
|
+
|
|
19
|
+
## 通用规则
|
|
20
|
+
|
|
21
|
+
1. **从第一天起就是 disposable,并明确标注。** 将原型代码放在离实际使用位置近的地方(紧邻它正在为哪个模块或页面做原型),这样上下文一目了然——但命名要让随便一个读者都能看出这是原型而非生产代码。对于 disposable UI 路由,遵循项目已有的路由约定,不要发明新的顶层结构。
|
|
22
|
+
2. **一条命令即可运行。** 使用项目已有任务运行器支持的方式——`pnpm <名称>`、`python <路径>`、`bun <路径>` 等。用户必须能不加思考就启动它。
|
|
23
|
+
3. **默认无持久化。** 状态存在于内存中。持久化是原型正在_检查_的东西,而非原型应该依赖的东西。如果问题明确涉及数据库,用一个临时库或本地文件,名称要清楚标注"PROTOTYPE — 可随时清除"。
|
|
24
|
+
4. **跳过打磨。** 不写测试,不做超出让原型_可运行_范围的错误处理,不建抽象。目的是快速学习。
|
|
25
|
+
5. **展示状态。** 每次操作后(logic)或每次变体切换时(UI),打印或渲染完整的相关状态,让用户能看到什么发生了变化。
|
|
26
|
+
6. **完成后捕获结论。** 将验证通过的决策融入正式代码。然后将答案和结论持久化到变更目录:
|
|
27
|
+
- 在 `<Path>{roots.state}/<workflow>/changes/{change}/prototype/</Path>` 下创建答案文件
|
|
28
|
+
- 维护 `prototype/index.md` 索引表
|
|
29
|
+
- 原型代码本身仍为一次性代码:提交到 throwaway 分支,保持脱离主分支。答案文件中记录该分支的引用指针
|
|
30
|
+
- 具体持久化规范见下方「持久化约定」章节
|
|
31
|
+
|
|
32
|
+
## 持久化约定
|
|
33
|
+
|
|
34
|
+
### 产物位置
|
|
35
|
+
|
|
36
|
+
原型答案写入当前 change 目录下的 `prototype/` 子目录:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
<Path>{roots.state}/<workflow>/changes/{change}/prototype/<type>-<topic>.md</Path>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `<type>` 为 `logic` 或 `ui`,对应原型分支类型
|
|
43
|
+
- `<topic>` 为 kebab-case 主题名,概括原型所回答的问题,如 `auth-state-machine.md`、`settings-page-layout.md`
|
|
44
|
+
- `<workflow>` 为当前 workflow 目录名(如 `specdev`)
|
|
45
|
+
- `<change>` 为当前活跃变更目录名(格式 `<YYYY-MM-DD>-<topic>`,从 `<Path>{roots.state}/<workflow>/status.json</Path>` 的 `active` 数组中获取)
|
|
46
|
+
|
|
47
|
+
### 答案文件内容
|
|
48
|
+
|
|
49
|
+
每个答案文件包含以下信息:
|
|
50
|
+
|
|
51
|
+
- **问题**:原型所回答的具体问题
|
|
52
|
+
- **结论**:验证后的结论——什么可行、什么不可行、为什么
|
|
53
|
+
- **验证内容**(仅 logic 原型):被验证的 reducer / 状态机 / 函数集的描述
|
|
54
|
+
- **UI 评估记录**(仅 UI 原型):哪个变体胜出及原因、各变体的结构差异分析、从落选变体中提取的有价值元素
|
|
55
|
+
- **原型代码引用**:throwaway 分支名称,指向原型代码所在的 git 分支
|
|
56
|
+
|
|
57
|
+
### 维护 prototype/index.md
|
|
58
|
+
|
|
59
|
+
在 `prototype/` 目录下维护一个索引文件 `<Path>{roots.state}/<workflow>/changes/{change}/prototype/index.md</Path>`,仅包含一张表格:
|
|
60
|
+
|
|
61
|
+
| 类型 | 文件 | 问题概述 | 结论摘要 |
|
|
62
|
+
|------|------|---------|---------|
|
|
63
|
+
| logic | `auth-state-machine.md` | 认证状态机能否正确处理 token 过期 + 并发刷新 | 可行;需增加 TOKEN_EXPIRED 中间态 |
|
|
64
|
+
| ui | `settings-layout.md` | 设置页三种布局方案对比 | B 方案(侧边栏布局)胜出;吸收 C 的面包屑导航 |
|
|
65
|
+
|
|
66
|
+
- 表格四列:类型(`logic` / `ui`)、文件(`prototype/` 下的相对路径)、问题概述(一句话概括)、结论摘要(一句话概括结论)
|
|
67
|
+
- 每次新增答案文件后,向表格追加一行
|
|
68
|
+
- `index.md` 除表格外无需其它内容
|
|
69
|
+
|
|
70
|
+
### 去重与增量更新
|
|
71
|
+
|
|
72
|
+
在开始新原型之前:
|
|
73
|
+
|
|
74
|
+
1. 先读取 `<Path>{roots.state}/<workflow>/changes/{change}/prototype/index.md</Path>`,检查是否已有同名或高度相关的原型记录
|
|
75
|
+
2. 如已存在对应 `.md` 文件,先读取其完整内容
|
|
76
|
+
3. 如现有结论已覆盖当前问题,直接引用,无需重复原型
|
|
77
|
+
4. 如需更新(新发现补充、结论修正),在原文件基础上增删改,并同步更新 `index.md` 中对应行的概述
|
|
78
|
+
5. 如需回答全新问题,创建新文件并追加到 `index.md` 表格
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# UI 原型
|
|
2
|
+
|
|
3
|
+
在单个路由上生成**几个截然不同的 UI 变体**,通过底部浮动栏切换。用户在浏览器中翻看变体,选一个(或从每个中偷一些元素),然后丢弃其余。
|
|
4
|
+
|
|
5
|
+
如果问题是关于逻辑/状态而非界面外观——选错了分支。用 [LOGIC.md](LOGIC.md)。
|
|
6
|
+
|
|
7
|
+
## 适用场景
|
|
8
|
+
|
|
9
|
+
- "这个页面应该长什么样?"
|
|
10
|
+
- "我想在提交之前看几个仪表盘方案。"
|
|
11
|
+
- "给设置页试一种不同的布局。"
|
|
12
|
+
- 任何用户本来会在脑子里花一天时间在三个模糊线框图之间犹豫不决的场景。
|
|
13
|
+
|
|
14
|
+
## 两种子形态 —— 强烈偏好子形态 A
|
|
15
|
+
|
|
16
|
+
UI 原型在**与应用的其余部分产生摩擦**时才最容易评判——真实的 header、真实的 sidebar、真实的数据、真实的信息密度。单独的一次性路由是真空:每个变体在隔离状态下看起来都不错。只要有合理的现有页面可以承载变体,就默认使用子形态 A。只有当原型确实没有邻近的宿主时才使用子形态 B。
|
|
17
|
+
|
|
18
|
+
### 子形态 A — 调整现有页面(首选)
|
|
19
|
+
|
|
20
|
+
路由已存在。变体在**同一路由**上渲染,通过 `?variant=` URL 查询参数控制。现有的数据获取、参数和认证全部保留——只替换渲染部分。这是默认选项;除非有明确的理由不这样做,否则选它。
|
|
21
|
+
|
|
22
|
+
如果原型针对的东西还没有页面,但*自然地应该存在于某个页面内部*(仪表盘的新区域、设置页的新卡片、现有流程中的新步骤)——这仍然是子形态 A。将变体挂载在宿主页面内部。
|
|
23
|
+
|
|
24
|
+
### 子形态 B — 新建页面(最后手段)
|
|
25
|
+
|
|
26
|
+
仅当被原型化的事物确实没有现成页面可以嵌入时使用——例如一个全新的顶层界面,或一个无法合理嵌入任何地方的流程。
|
|
27
|
+
|
|
28
|
+
按照项目已有的路由约定创建一个**一次性路由**——不要发明新的顶层结构。命名要让人一眼看出是原型(例如在路径或文件名中包含 `prototype` 字样)。同样使用 `?variant=` 模式。
|
|
29
|
+
|
|
30
|
+
在提交子形态 B 之前,做一个合理性检查:真的没有现成页面可以嵌入吗?空路由会隐藏有内容的页面能够暴露的设计问题。
|
|
31
|
+
|
|
32
|
+
两种子形态下,底部浮动栏完全相同。
|
|
33
|
+
|
|
34
|
+
## 流程
|
|
35
|
+
|
|
36
|
+
### 1. 陈述问题并确定变体数量 N
|
|
37
|
+
|
|
38
|
+
默认 **3 个变体**。超过 5 个就不再是截然不同,而是噪音——以此为上限。
|
|
39
|
+
|
|
40
|
+
将计划写在一行内,放在原型所在位置或文件顶部注释中:
|
|
41
|
+
|
|
42
|
+
> "设置页的三个变体,通过 `?variant=` 切换,在现有 `/settings` 路由上。"
|
|
43
|
+
|
|
44
|
+
无论用户是否在场反对,这都能成立。
|
|
45
|
+
|
|
46
|
+
### 2. 生成截然不同的变体
|
|
47
|
+
|
|
48
|
+
起草每个变体。每个变体必须满足:
|
|
49
|
+
|
|
50
|
+
- 页面的目的和它能访问的数据。
|
|
51
|
+
- 项目的组件库 / 样式系统(TailwindCSS、shadcn、MUI、纯 CSS,等等)。
|
|
52
|
+
- 清晰的导出组件名,例如 `VariantA`、`VariantB`、`VariantC`。
|
|
53
|
+
|
|
54
|
+
变体必须在**结构上不同**——不同的布局、不同的信息层次、不同的主要操作入口,而不仅仅是不同的颜色。三个微调过的卡片网格不是 UI 原型,是壁纸。如果两份草稿太相似,用明确的"不要用卡片网格"指引重做其中一个。
|
|
55
|
+
|
|
56
|
+
### 3. 将它们串接起来
|
|
57
|
+
|
|
58
|
+
在路由上创建一个单一的切换器组件:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// 伪代码 —— 根据项目框架调整
|
|
62
|
+
const variant = searchParams.get('variant') ?? 'A';
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{variant === 'A' && <VariantA {...data} />}
|
|
66
|
+
{variant === 'B' && <VariantB {...data} />}
|
|
67
|
+
{variant === 'C' && <VariantC {...data} />}
|
|
68
|
+
<PrototypeSwitcher variants={['A','B','C']} current={variant} />
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
对于子形态 A(现有页面):将现有数据获取保持在切换器上方;每个变体只替换渲染的子树。
|
|
74
|
+
|
|
75
|
+
对于子形态 B(新建页面):`/prototype/<名称>` 下的一次性路由挂载同一个切换器。
|
|
76
|
+
|
|
77
|
+
### 4. 构建浮动切换器
|
|
78
|
+
|
|
79
|
+
一个位于屏幕底部中央的固定定位小栏,包含三个元素:
|
|
80
|
+
|
|
81
|
+
- **左箭头**——切换到上一个变体(循环)。
|
|
82
|
+
- **变体标签**——显示当前变体标识,如果变体导出了名称,也显示名称。例如 `B — 侧边栏布局`。
|
|
83
|
+
- **右箭头**——切换到下一个(循环)。
|
|
84
|
+
|
|
85
|
+
行为:
|
|
86
|
+
|
|
87
|
+
- 点击箭头更新 URL 查询参数(使用框架的路由器——Next 上用 `router.replace`、React Router 上用 `navigate`,等等),使变体可分享且在刷新后保持。
|
|
88
|
+
- 键盘:`←` 和 `→` 方向键也可切换。当 `<input>`、`<textarea>` 或 `[contenteditable]` 元素聚焦时不要拦截方向键。
|
|
89
|
+
- 在视觉上与页面区分(如高对比度胶囊形、微妙阴影),使其明显不是被评估的设计的一部分。
|
|
90
|
+
- 在生产构建中隐藏——通过 `process.env.NODE_ENV !== 'production'` 或等价检查进行门控,这样即使原型不小心合入也不会把切换器发布给用户。
|
|
91
|
+
|
|
92
|
+
将切换器放在一个共享组件中,供两种子形态复用。放置在项目中共享 UI 组件的通常位置。
|
|
93
|
+
|
|
94
|
+
### 5. 交付
|
|
95
|
+
|
|
96
|
+
给出 URL(以及 `?variant=` 的各个键值)。用户会在有空时翻看。最有趣的反馈通常是**"我想要 B 方案的头和 C 方案的侧边栏"**——那才是他们真正想要的设计。
|
|
97
|
+
|
|
98
|
+
### 6. 捕获答案并清理
|
|
99
|
+
|
|
100
|
+
一旦某个变体胜出,按 [SKILL](SKILL.md) 中持久化约定的方式捕获答案:
|
|
101
|
+
|
|
102
|
+
1. **融入正式代码**:
|
|
103
|
+
- **子形态 A** — 将胜出变体融入现有页面;从主分支移除落选变体和切换器。
|
|
104
|
+
- **子形态 B** — 将胜出变体提升为正式路由;从主分支移除一次性路由和切换器。
|
|
105
|
+
2. **持久化评估记录**:在 `<Path>{roots.state}/<workflow>/changes/{change}/prototype/ui-<topic>.md</Path>` 创建答案文件,记录:
|
|
106
|
+
- 所回答的 UI 问题
|
|
107
|
+
- 哪个变体胜出及原因——完整的评估推理
|
|
108
|
+
- 各变体的结构差异分析
|
|
109
|
+
- 从落选变体中提取的有价值元素(如果适用)
|
|
110
|
+
- throwaway 分支指针
|
|
111
|
+
3. **UI 规范沉淀**:将评估过程中产生的 UI 规范洞察(如布局原则、信息层次、交互模式选择理由)写入答案文件,供后续 spec 编写引用。
|
|
112
|
+
4. **清理原型代码**:将完整变体集(包括落选变体和切换器)提交到 throwaway 分支,不进入主分支。变体组件和切换器留在主分支会快速腐烂并误导后续读者。
|
|
113
|
+
5. **更新索引**:将新答案追加到 `prototype/index.md` 表格中。
|
|
114
|
+
|
|
115
|
+
## 反模式
|
|
116
|
+
|
|
117
|
+
- **变体仅颜色或文案不同。** 那是微调,不是原型。真正的变体在结构上存在分歧。
|
|
118
|
+
- **变体之间共享过多代码。** 共享一个 `<Header>` 没问题;共享一个 `<Layout>` 就失去了意义。每个变体应该能够自由地抛弃布局。
|
|
119
|
+
- **将变体接入真实的数据变更。** 只读原型完全没问题。如果变体需要变更数据,将其指向一个桩——问题是"这个应该长什么样",不是"后端是否正常工作"。
|
|
120
|
+
- **将原型直接提升到生产环境。** 变体代码是在原型约束下编写的(无测试、最小错误处理)。融入时要正确重写。
|