@namewta/speculo 0.2.3 → 0.2.7
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 +11 -15
- package/dist/src/index.js +72 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/migrate.js +8 -8
- package/dist/src/migrate.js.map +1 -1
- package/dist/src/workflows.js +2 -2
- package/dist/src/workflows.js.map +1 -1
- package/package.json +1 -1
- package/template/.speculo/README.md +3 -3
- package/template/AGENTS.md +4 -0
- package/template/CLAUDE.md +3 -0
- package/template/canonical/README.md +114 -0
- package/template/canonical/canonical-domain-modeling.md +289 -0
- package/template/canonical/canonical-skill-example.md +608 -0
- package/template/canonical/canonical-teach.md +296 -0
- package/template/commands/archive-and-consolidate.md +49 -0
- package/template/commands/docs-sync.md +2 -2
- package/template/commands/retro.md +1 -1
- package/template/commands/status.md +2 -2
- package/template/skills/archive-and-consolidate/SKILL.md +179 -0
- package/template/skills/archive-and-consolidate/assets/archive-plan-template.md +34 -0
- package/template/skills/archive-and-consolidate/assets/cleanup-candidate-template.md +69 -0
- package/template/skills/archive-and-consolidate/assets/consolidation-plan-template.md +67 -0
- package/template/skills/archive-and-consolidate/references/archive-rules.md +48 -0
- package/template/skills/archive-and-consolidate/references/cleanup-rules.md +73 -0
- package/template/skills/archive-and-consolidate/references/consolidation-rules.md +70 -0
- package/template/skills/archive-and-consolidate/references/knowledge-graduation.md +50 -0
- package/template/skills/docs-sync/references/workflow-scope-contract.md +3 -3
- package/template/skills/speculo-retro/SKILL.md +1 -1
- package/template/skills/speculo-retro/references/issue-drafting-sop.md +1 -1
- package/template/skills/worktree-isolation/references/merge-and-cleanup.md +2 -2
- package/template/vendor/README.md +3 -3
- package/template/vendor/khazix-skills/neat-freak/SKILL.md +210 -0
- package/template/vendor/khazix-skills/neat-freak/references/agent-paths.md +72 -0
- package/template/vendor/khazix-skills/neat-freak/references/governance.md +88 -0
- package/template/vendor/khazix-skills/neat-freak/references/sync-matrix.md +77 -0
- package/template/vendor/khazix-skills/neat-freak/references/verification.md +92 -0
- package/template/vendor/khazix-skills/neat-freak/scripts/audit-inventory.sh +106 -0
- package/template/workflows/person/INDEX.md +12 -0
- package/template/workflows/person/M-mao-zedong-cognitive-os/M-mao-zedong-cognitive-os.md +73 -74
- package/template/workflows/specdev/D-diagnose-bugs/D-diagnose-bugs.md +85 -0
- package/template/workflows/specdev/D-diagnose-bugs/cleanup-postmortem.md +37 -0
- package/template/workflows/specdev/D-diagnose-bugs/feedback-loop-techniques.md +84 -0
- package/template/workflows/specdev/D-diagnose-bugs/hypothesis-format.md +46 -0
- package/template/workflows/specdev/D-diagnose-bugs/instrumentation-rules.md +51 -0
- package/template/workflows/specdev/G-grill-with-docs/G-grill-with-docs.md +54 -0
- package/template/workflows/specdev/G-grill-with-docs/adr-format.md +77 -0
- package/template/workflows/specdev/G-grill-with-docs/context-format.md +63 -0
- package/template/workflows/specdev/G-grill-with-docs/domain-modeling-rules.md +93 -0
- package/template/workflows/specdev/G-grill-with-docs/grilling-protocol.md +54 -0
- package/template/workflows/specdev/G-grill-with-docs/log-format.md +99 -0
- package/template/workflows/specdev/I-implement/I-implement.md +85 -0
- package/template/workflows/specdev/I-implement/code-review-process.md +83 -0
- package/template/workflows/specdev/I-implement/codebase-design-glossary.md +109 -0
- package/template/workflows/specdev/I-implement/deepening.md +37 -0
- package/template/workflows/specdev/I-implement/design-it-twice.md +44 -0
- package/template/workflows/specdev/I-implement/tdd-examples.md +139 -0
- package/template/workflows/specdev/I-implement/tdd-rules.md +31 -0
- package/template/workflows/specdev/I-init-setup/I-init-setup.md +132 -0
- package/template/workflows/specdev/I-init-setup/domain-layout.md +90 -0
- package/template/workflows/specdev/I-init-setup/status-labels.md +54 -0
- package/template/workflows/specdev/I-init-setup/tracking-convention.md +58 -0
- package/template/workflows/specdev/INDEX.md +88 -0
- package/template/workflows/specdev/S-spec/S-spec.md +91 -0
- package/template/workflows/specdev/T-tickets/T-tickets.md +241 -0
- package/template/workflows/specdev/W-wayfinder/W-wayfinder.md +209 -0
- package/template/workflows/specdev/_state/adr/.gitkeep +0 -0
- package/template/workflows/specdev/_state/archive/.gitkeep +0 -0
- package/template/workflows/specdev/_state/changes/.gitkeep +0 -0
- package/template/workflows/specdev/_state/context/.gitkeep +0 -0
- package/template/workflows/{matt-pocock → specdev}/_state/status.json +1 -1
- package/template/commands/finalize.md +0 -37
- package/template/commands/knowledge-prune.md +0 -20
- package/template/skills/change-lifecycle/SKILL.md +0 -25
- package/template/skills/change-lifecycle/assets/completion-summary-template.md +0 -25
- package/template/skills/change-lifecycle/assets/completion-verification-template.md +0 -29
- package/template/skills/change-lifecycle/references/completion-gate.md +0 -19
- package/template/skills/change-lifecycle/references/finalize-archive.md +0 -32
- package/template/skills/knowledge-prune/SKILL.md +0 -29
- package/template/skills/knowledge-prune/references/audit-rules.md +0 -24
- package/template/skills/runtime-context/SKILL.md +0 -54
- package/template/skills/runtime-context/references/path-resolution.md +0 -41
- package/template/workflows/matt-pocock/PERSISTENCE.md +0 -80
- package/template/workflows/matt-pocock/WORKFLOW.md +0 -103
- package/template/workflows/matt-pocock/_state/archive/.gitkeep +0 -1
- package/template/workflows/matt-pocock/_state/changes/.gitkeep +0 -1
- package/template/workflows/matt-pocock/atomic-skills/ask-matt.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/claude-handoff.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/code-review.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/codebase-design.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/diagnosing-bugs.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/domain-modeling.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/grill-me.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/grill-with-docs.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/grilling.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/handoff.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/implement.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/improve-codebase-architecture.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/loop-me.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/prototype.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/research.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/resolving-merge-conflicts.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/setup-matt-pocock-skills.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/tdd.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/teach.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/to-spec.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/to-tickets.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/triage.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/wayfinder.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/wizard.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/writing-beats.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/writing-fragments.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/writing-great-skills.md +0 -21
- package/template/workflows/matt-pocock/atomic-skills/writing-shape.md +0 -20
- package/template/workflows/matt-pocock/routes/architecture.md +0 -24
- package/template/workflows/matt-pocock/routes/diagnose.md +0 -22
- package/template/workflows/matt-pocock/routes/experimental.md +0 -18
- package/template/workflows/matt-pocock/routes/idea-to-delivery.md +0 -63
- package/template/workflows/matt-pocock/routes/merge-conflicts.md +0 -19
- package/template/workflows/matt-pocock/routes/productivity.md +0 -25
- package/template/workflows/matt-pocock/routes/research-prototype.md +0 -20
- package/template/workflows/matt-pocock/routes/review.md +0 -19
- package/template/workflows/matt-pocock/routes/setup.md +0 -42
- package/template/workflows/matt-pocock/routes/triage.md +0 -25
- package/template/workflows/matt-pocock/routes/wayfinder.md +0 -27
- package/template/workflows/person/PERSISTENCE.md +0 -56
- package/template/workflows/person/WORKFLOW.md +0 -50
- package/template/workflows/person/_state/.config/LESSONS.md +0 -3
- package/template/workflows/person/_state/.config/RULES.md +0 -3
- package/template/workflows/person/_state/.config/context/.gitkeep +0 -1
- package/template/workflows/person/_templates/mao-consultation-output-template.md +0 -55
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CONTEXT.md 格式
|
|
2
|
+
|
|
3
|
+
领域词汇表存放在变更目录的 `<Path>{roots.state}/specdev/changes/{change}/CONTEXT.md</Path>` 中。它只包含精炼的规范术语定义,不包含实现细节、设计决策或草稿内容。
|
|
4
|
+
|
|
5
|
+
## 模板
|
|
6
|
+
|
|
7
|
+
```md
|
|
8
|
+
# {项目名称} 领域词汇表
|
|
9
|
+
|
|
10
|
+
{对该上下文是什么以及为什么存在的一两句话描述。}
|
|
11
|
+
|
|
12
|
+
## {术语分组}
|
|
13
|
+
|
|
14
|
+
**Order**:
|
|
15
|
+
{对该术语的一两句话描述}
|
|
16
|
+
_Avoid_: Purchase, transaction
|
|
17
|
+
|
|
18
|
+
**Invoice**:
|
|
19
|
+
发货后发送给客户的付款请求。
|
|
20
|
+
_Avoid_: Bill, payment request
|
|
21
|
+
|
|
22
|
+
**Customer**:
|
|
23
|
+
下订单的个人或组织。
|
|
24
|
+
_Avoid_: Client, buyer, account
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 规则
|
|
28
|
+
|
|
29
|
+
- **要有主见。** 当同一概念存在多个词时,选择最好的那个,并将其他的列在 `_Avoid_` 下。不要犹豫——明确选定一个规范术语。
|
|
30
|
+
- **定义保持精炼。** 最多一两句话。定义它是什么,而不是它做什么。术语的定义描述概念的本质,而非其行为或实现。
|
|
31
|
+
- **仅包含特定于该项目上下文的术语。** 通用的编程概念(超时、错误类型、工具模式)即使项目广泛使用也不属于这里。添加术语前自问:这是该上下文独有的概念,还是一个通用编程概念?只有前者才属于这里。
|
|
32
|
+
- **当自然形成聚类时,用子标题分组术语。** 如果所有术语属于一个单一的凝聚领域,扁平列表也可以。当术语数量超过 10 个时,几乎总能找到自然分组。
|
|
33
|
+
- **随时增删改。** 模型演进时,直接修改文件:添加新术语、删除废弃术语、修正定义、术语更名。不要堆积——保持词汇表精炼且反映当前模型。
|
|
34
|
+
|
|
35
|
+
## 增删改操作
|
|
36
|
+
|
|
37
|
+
### 新增术语
|
|
38
|
+
|
|
39
|
+
在合适的子标题下追加条目。如果现有子标题都不匹配,新建一个子标题。格式:
|
|
40
|
+
|
|
41
|
+
```md
|
|
42
|
+
**{术语名}**:
|
|
43
|
+
{定义}
|
|
44
|
+
_Avoid_: {避免使用的同义词,用逗号分隔}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 修改术语
|
|
48
|
+
|
|
49
|
+
直接更新定义文本和 `_Avoid_` 列表。如果术语的含义已经变化,更新定义以反映当前理解。在 `_Avoid_` 中添加新出现的同义词,移除不再使用的同义词。
|
|
50
|
+
|
|
51
|
+
### 删除术语
|
|
52
|
+
|
|
53
|
+
移除整个条目(术语名、定义、`_Avoid_` 行)。如果术语已不再使用或已被其他术语替代,直接删除,不要保留废弃标记。词汇表只反映当前模型。
|
|
54
|
+
|
|
55
|
+
### 术语更名
|
|
56
|
+
|
|
57
|
+
删除旧条目,新增新条目。在 `_Avoid_` 中保留旧名称作为新术语的避免项——这样未来的读者能理解新旧术语的对应关系。例如,将 "Client" 更名为 "Customer":
|
|
58
|
+
|
|
59
|
+
```md
|
|
60
|
+
**Customer**:
|
|
61
|
+
下订单的个人或组织。
|
|
62
|
+
_Avoid_: Client, buyer, account
|
|
63
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 领域建模规程
|
|
2
|
+
|
|
3
|
+
在设计过程中积极构建和精炼项目的领域模型。这是*主动*规程——挑战术语、发明边界场景、并在决策结晶的那一刻立即写入词汇表和决策。仅仅*阅读*文档获取词汇不是本规程——本规程用于当你正在*改变*模型,而不仅仅是消费它时。
|
|
4
|
+
|
|
5
|
+
## 三文件分工
|
|
6
|
+
|
|
7
|
+
三个文件各司其职,三者关系为:LOG.md 是最完整的记录 → CONTEXT.md 从中提取术语定义 → ADR.md 从中筛选同时满足三个条件的架构决策。
|
|
8
|
+
|
|
9
|
+
| 文件 | 职责 | 维护方式 |
|
|
10
|
+
|------|------|----------|
|
|
11
|
+
| `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>` | 完整设计轨迹——所有确认、延后、被替代的结论 | 持续增删改,条目可被后续决定修订 |
|
|
12
|
+
| `<Path>{roots.state}/specdev/changes/{change}/CONTEXT.md</Path>` | 精炼的规范词汇表——只保留当前有效的术语 | 增删改,保持精炼 |
|
|
13
|
+
| `<Path>{roots.state}/specdev/changes/{change}/ADR.md</Path>` | 难以逆转、令人意外、存在真实权衡的架构决策 | 只追加不删除,可标记废弃 |
|
|
14
|
+
|
|
15
|
+
## 对照词汇表挑战
|
|
16
|
+
|
|
17
|
+
当用户使用的术语与 `CONTEXT.md` 中的现有语言冲突时,立即指出。"你的词汇表将 'cancellation' 定义为 X,但你似乎指的是 Y——到底是哪个?"如果用户确认应修改词汇表,更新 `CONTEXT.md` 中的定义并追加日志。
|
|
18
|
+
|
|
19
|
+
## 精炼模糊语言
|
|
20
|
+
|
|
21
|
+
当用户使用含糊或重载的术语时,提出一个精确的规范术语。"你在说 'account'——你指的是 Customer 还是 User?它们是不同的东西。"在 `CONTEXT.md` 中为新术语添加条目,将模糊的同义词列入 `_Avoid_`。
|
|
22
|
+
|
|
23
|
+
## 讨论具体场景
|
|
24
|
+
|
|
25
|
+
当讨论领域关系时,用具体场景进行压力测试。发明探索边界情况的场景,迫使用户精确界定概念之间的边界。"你说订单可以部分取消——未取消的商品怎么办?它们还能发货吗?"
|
|
26
|
+
|
|
27
|
+
## 与代码交叉引用
|
|
28
|
+
|
|
29
|
+
当用户陈述某事如何工作时,检查代码是否一致。如果发现矛盾,指出来:"你的代码取消的是整个 Order,但你刚才说部分取消是可能的——哪个是正确的?"在日志中记录这种矛盾的发现和解决过程。
|
|
30
|
+
|
|
31
|
+
## 及时更新 LOG.md
|
|
32
|
+
|
|
33
|
+
当设计问答中形成任何结论时,当场更新 `LOG.md`。它是完整的设计轨迹——记录"当时讨论了什么、最终固定了什么行为以及对应哪个 ADR"。不要批量处理——发生时立即捕获。具体格式参见 `<Path>{roots.workflows}/specdev/G-grill-with-docs/log-format.md</Path>`。
|
|
34
|
+
|
|
35
|
+
### 日志条目格式
|
|
36
|
+
|
|
37
|
+
每条日志使用 `## LOG-XXXX: {标题}` 二级标题,编号从 `0001` 开始顺序递增。每个条目包含:
|
|
38
|
+
|
|
39
|
+
- **Status** — `accepted`(已确认)、`deferred`(暂不决定)、`superseded`(被后续决定替代)
|
|
40
|
+
- **Superseded by** — 如状态为 `superseded`,标注替代它的日志编号
|
|
41
|
+
- **Related** — 如该结论对应某个 ADR,标注 `Related: ADR-XXXX`
|
|
42
|
+
- **正文** — 背景、讨论的问题、做出的决定及原因,可以记录具体场景和交互细节
|
|
43
|
+
|
|
44
|
+
### 维护规则
|
|
45
|
+
|
|
46
|
+
- 每次完成设计问答,同步更新 `LOG.md`、`CONTEXT.md` 与 `ADR.md`。
|
|
47
|
+
- 后续确认改变既有结论时,直接修订原日志条目,并记录替代关系,不保留互相矛盾的"现行规则"。
|
|
48
|
+
- 日志可以记录具体交互和边界场景;词汇表保持精炼;ADR 只记录难以逆转、令人意外且存在真实权衡的决定。
|
|
49
|
+
- 每个日志条目应关联对应的 ADR(如有):`Related: ADR-XXXX`。
|
|
50
|
+
- 状态为 `deferred` 的条目保留,以便后续恢复讨论时知道从什么问题开始。
|
|
51
|
+
|
|
52
|
+
## 及时更新 CONTEXT.md
|
|
53
|
+
|
|
54
|
+
当术语确定时,当场更新 `CONTEXT.md`。不要批量处理——发生时立即捕获。具体格式参见 `<Path>{roots.workflows}/specdev/G-grill-with-docs/context-format.md</Path>`。
|
|
55
|
+
|
|
56
|
+
`CONTEXT.md` 应该完全不包含实现细节。不要将 `CONTEXT.md` 当作规范、草稿纸或实现决策的仓库。它只是词汇表,别无其他。
|
|
57
|
+
|
|
58
|
+
- **新增术语**:在合适的子标题下追加条目。
|
|
59
|
+
- **修改术语**:直接更新定义文本和 `_Avoid_` 列表。
|
|
60
|
+
- **删除术语**:移除整个条目。
|
|
61
|
+
- **术语更名**:删除旧条目,新增新条目。
|
|
62
|
+
|
|
63
|
+
## 谨慎更新 ADR.md
|
|
64
|
+
|
|
65
|
+
仅在以下三个条件全部满足时才向 `ADR.md` 追加一条决策记录:
|
|
66
|
+
|
|
67
|
+
1. **难以逆转**——以后改变主意的成本是有意义的
|
|
68
|
+
2. **没有上下文会令人惊讶**——未来的读者会疑惑"他们为什么这样做?"
|
|
69
|
+
3. **真实权衡的结果**——存在真正的替代方案,你出于特定原因选择了一个
|
|
70
|
+
|
|
71
|
+
如果缺少任何一个条件,跳过 ADR。具体格式参见 `<Path>{roots.workflows}/specdev/G-grill-with-docs/adr-format.md</Path>`。
|
|
72
|
+
|
|
73
|
+
`ADR.md` 中每个决策是一个 `## NNNN: {标题}` 二级标题,编号顺序递增,条目之间用 `---` 分隔。可以修改已有条目、标记废弃——但不要删除。
|
|
74
|
+
|
|
75
|
+
## 什么算 ADR
|
|
76
|
+
|
|
77
|
+
- **架构形态。** "我们使用 monorepo。" "写模型采用事件溯源,读模型投影到 Postgres。"
|
|
78
|
+
- **上下文间的集成模式。** "Ordering 和 Billing 通过领域事件通信,而非同步 HTTP。"
|
|
79
|
+
- **带来锁定效应的技术选择。** 数据库、消息总线、认证提供商、部署目标。不是每个库——只是那些需要花一个季度才能替换的。
|
|
80
|
+
- **边界和范围决策。** "客户数据由 Customer 上下文拥有;其他上下文仅通过 ID 引用它。"明确的"不做"和"要做"同样有价值。
|
|
81
|
+
- **有意偏离显而易见路径的决策。** "我们使用手动 SQL 而不是 ORM,因为 X。"任何合理读者会假设相反的情况。
|
|
82
|
+
- **代码中不可见的约束。** "由于合规要求,我们不能使用 AWS。" "由于合作伙伴 API 合同,响应时间必须低于 200ms。"
|
|
83
|
+
- **拒绝的原因不明显的被拒绝替代方案。** 如果你考虑了 GraphQL 而因微妙原因选择了 REST,记录下来——否则 6 个月后有人会再次建议 GraphQL。
|
|
84
|
+
|
|
85
|
+
## 三文件同步规则
|
|
86
|
+
|
|
87
|
+
访谈中每完成一轮设计问答,按以下顺序同步三个文件:
|
|
88
|
+
|
|
89
|
+
1. **LOG.md** 先更新——立即追加日志条目,记录本次讨论的结论
|
|
90
|
+
2. **CONTEXT.md** 随后更新——从日志中提取新术语或修正的术语定义
|
|
91
|
+
3. **ADR.md** 最后更新——检查是否需要满足三条件追加 ADC(架构决策记录)
|
|
92
|
+
|
|
93
|
+
如果 CONTEXT 或 ADR 的更新来自日志条目,在日志中补充 `Related: ADR-XXXX` 的关联标注。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 访谈协议
|
|
2
|
+
|
|
3
|
+
请对我进行无情的面试,深入探讨该方案的每一个方面,直到我们达成共识。沿设计树的每个分支逐步推进,逐个解决决策之间的依赖关系。每个问题都给出你的推荐答案。
|
|
4
|
+
|
|
5
|
+
## 核心规则
|
|
6
|
+
|
|
7
|
+
### 一次只问一个问题
|
|
8
|
+
|
|
9
|
+
等待我对每个问题给出反馈后再继续。一次问多个问题会让人困惑,也会让讨论失去焦点。每个问题的回答会自然引出下一个分支的追问,不要跳跃。
|
|
10
|
+
|
|
11
|
+
### 每个问题给出推荐答案
|
|
12
|
+
|
|
13
|
+
不要只抛出问题。基于你的分析,给出你认为最好的答案,并解释为什么。然后让我确认、反驳或修正。推荐答案让讨论有锚点——我可以直接同意、微调、或推翻重来,而不是从白纸开始。
|
|
14
|
+
|
|
15
|
+
### 事实自己查,决策问用户
|
|
16
|
+
|
|
17
|
+
如果某个*事实*可以通过探索代码库找到,请自行查找,不要来问我。包括:现有实现方式、类型定义、数据流、命名约定、文件结构。但*决策*由我来做——将每个决策提交给我并等待我的回答。事实和决策的边界:事实是关于"现在是什么",决策是关于"应该是什么"。
|
|
18
|
+
|
|
19
|
+
### 沿设计树逐分支推进
|
|
20
|
+
|
|
21
|
+
不要跳跃。如果一个决策依赖另一个决策,先解决被依赖的那个。识别出依赖关系并告诉我:"我们需要先决定 X,因为 Y 的选择取决于 X 的结论。"设计树的典型分支顺序:
|
|
22
|
+
|
|
23
|
+
1. **核心概念** — 领域的实体、值对象、聚合根是什么?
|
|
24
|
+
2. **边界与关系** — 概念之间的边界在哪里?它们如何关联?
|
|
25
|
+
3. **行为与规则** — 每个概念能做什么?有什么约束?
|
|
26
|
+
4. **实现映射** — 概念如何映射到代码结构、数据模型、接口?
|
|
27
|
+
5. **边界场景** — 极端情况和异常如何处理?
|
|
28
|
+
|
|
29
|
+
### 共识之前不执行
|
|
30
|
+
|
|
31
|
+
在我确认我们已达成共识之前,不要执行该方案。即使讨论看起来已经穷尽,也要明确询问:"我们是否已就该设计达成共识?"只有在得到肯定回答后才进入下一阶段。
|
|
32
|
+
|
|
33
|
+
## 访谈中维护 LOG.md
|
|
34
|
+
|
|
35
|
+
在访谈过程中,每当一个结论被确认、延后或被替代时,当场更新 `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>`。不要等访谈结束再批量写入——发生时立即捕获。具体格式参见 `<Path>{roots.workflows}/specdev/G-grill-with-docs/log-format.md</Path>`。
|
|
36
|
+
|
|
37
|
+
写入 LOG.md 的时机:
|
|
38
|
+
|
|
39
|
+
- **结论被确认** — 用户明确同意某个设计决定时,立即追加一条 `accepted` 日志
|
|
40
|
+
- **决定被延后** — 用户说"先不定"或"后面再讨论"时,追加一条 `deferred` 日志,记录为什么暂不决定以及从什么角度恢复讨论
|
|
41
|
+
- **结论被替代** — 后续讨论推翻了之前的决定时,将原条目状态改为 `superseded`,标注 `Superseded by: LOG-XXXX`,再新建替代条目
|
|
42
|
+
|
|
43
|
+
## 访谈节奏
|
|
44
|
+
|
|
45
|
+
- **开场**:先理解用户想打磨的方案是什么。问清楚范围和目标,然后开始沿设计树推进。
|
|
46
|
+
- **推进**:每个回答后,识别下一个最关键的未解决问题。优先解决会阻塞其他决策的问题。
|
|
47
|
+
- **收束**:当设计树的主要分支都已遍历、且用户确认共识时,访谈结束。不要无限追问无关细节。
|
|
48
|
+
|
|
49
|
+
## 好的访谈问题范例
|
|
50
|
+
|
|
51
|
+
- "你把 X 称为 'account'——你指的是 Customer 还是 User?它们在代码中是不同的概念。我建议用 Customer,因为它更精确地描述了购买关系。"
|
|
52
|
+
- "你提到订单可以部分取消。未取消的商品怎么办——它们还能发货吗?我建议将它们标记为可发货状态,因为取消是针对商品行而非整个订单。"
|
|
53
|
+
- "你打算用事件溯源还是 CRUD?考虑到审计需求,我推荐事件溯源——虽然写路径更复杂,但天然支持完整审计日志。"
|
|
54
|
+
- "Ordering 和 Billing 之间你选择了同步 HTTP 调用。这意味着 Billing 挂了订单也创建不了。你确定要这种耦合?我建议用异步领域事件——订单创建后发出事件,Billing 异步消费。"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# LOG.md 格式
|
|
2
|
+
|
|
3
|
+
设计决策日志存放在变更目录的 `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>` 中。它记录设计访谈中已经确认、延后或被替代的具体结论——"当时讨论了什么、最终固定了什么行为以及对应哪个 ADR"。与 CONTEXT.md 的精炼不同,LOG.md 可以记录讨论中的具体场景、边界条件和交互细节。
|
|
4
|
+
|
|
5
|
+
## 规则
|
|
6
|
+
|
|
7
|
+
- **记录每一次设计结论。** 无论大小,只要在访谈中确认、延后或被替代,都写入 LOG.md。宁可多记,不要遗漏。
|
|
8
|
+
- **状态驱动。** 每个条目明确标记 `accepted`、`deferred` 或 `superseded`,让读者一眼知道当前有效性。
|
|
9
|
+
- **关联 ADR。** 如果该结论同时满足 ADR 的三个条件,在 LOG 中标注 `Related: ADR-XXXX`,并在对应 ADR 条目中也关联回 LOG。
|
|
10
|
+
- **保持可修订。** 后续决定改变既有结论时,直接更新原条目状态和正文,不要新建一条矛盾的条目。标注 `Superseded by: LOG-XXXX`。
|
|
11
|
+
- **不堆积废弃条目。** 被替代的条目保留但标记清楚;延后(deferred)的条目保留以便后续恢复讨论。
|
|
12
|
+
- **记录具体交互和边界。** 与 CONTEXT.md 的精炼不同,LOG.md 可以记录讨论中的具体场景、边界条件和交互细节。
|
|
13
|
+
|
|
14
|
+
## 模板
|
|
15
|
+
|
|
16
|
+
```md
|
|
17
|
+
# 设计决策日志
|
|
18
|
+
|
|
19
|
+
本文件记录设计访谈中已经确认、延后或被替代的具体结论。它保存"当时讨论了什么、最终固定了什么行为以及对应哪个 ADR";CONTEXT.md 是规范词汇表,ADR.md 是难以逆转的架构决策,本文件则是可持续增删改的完整设计轨迹。
|
|
20
|
+
|
|
21
|
+
## 维护规则
|
|
22
|
+
|
|
23
|
+
- 每次完成一个设计问答,同步更新 LOG.md、CONTEXT.md 与 ADR.md。
|
|
24
|
+
- 已确认结论使用 `accepted`;暂不决定使用 `deferred`;被后续决定替代使用 `superseded`。
|
|
25
|
+
- 后续确认改变既有结论时,直接修订原日志条目,并记录替代关系,不保留互相矛盾的"现行规则"。
|
|
26
|
+
- 日志可以记录具体交互和边界;词汇表保持精炼;ADR 只记录难以逆转、令人意外且存在真实权衡的决定。
|
|
27
|
+
|
|
28
|
+
## LOG-0001: {决策的简短标题}
|
|
29
|
+
|
|
30
|
+
Status: accepted
|
|
31
|
+
Related: ADR-0001
|
|
32
|
+
|
|
33
|
+
{背景:讨论了什么问题,做出了什么决定,以及为什么。可以记录具体的交互过程、边界场景和固定行为。}
|
|
34
|
+
|
|
35
|
+
## LOG-0002: {另一决策标题}
|
|
36
|
+
|
|
37
|
+
Status: deferred
|
|
38
|
+
|
|
39
|
+
{为什么暂不决定,以及后续恢复讨论时应从什么问题开始。}
|
|
40
|
+
|
|
41
|
+
## LOG-0003: {被替代的决策标题}
|
|
42
|
+
|
|
43
|
+
Status: superseded
|
|
44
|
+
Superseded by: LOG-0004
|
|
45
|
+
|
|
46
|
+
{原决策内容,以及为什么被替代。保留作为设计演进的历史上下文。}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 状态说明
|
|
50
|
+
|
|
51
|
+
- **Status: accepted**——已确认的现行结论。这是讨论后用户明确同意的决定,当前仍然有效。
|
|
52
|
+
- **Status: deferred**——暂不决定,留待后续讨论。记录为什么暂不决定以及从什么角度恢复讨论,以便后续接续上下文。
|
|
53
|
+
- **Status: superseded**——被后续决定替代。标注 `Superseded by: LOG-XXXX` 指向替代条目。保留原条目作为设计演进的历史上下文。
|
|
54
|
+
|
|
55
|
+
## 追加新日志
|
|
56
|
+
|
|
57
|
+
1. 读取 `<Path>{roots.state}/specdev/changes/{change}/LOG.md</Path>`,找到最高现有编号
|
|
58
|
+
2. 编号加 1(从 `0001` 开始,不足四位补零)
|
|
59
|
+
3. 在文件末尾追加新条目
|
|
60
|
+
|
|
61
|
+
## 修改已有日志
|
|
62
|
+
|
|
63
|
+
- **改变结论**——将原条目状态改为 `superseded`,标注 `Superseded by: LOG-XXXX`,在新条目中说明替代原因。不要直接修改原条目的结论内容——保留它让读者能看到设计是如何演进的。
|
|
64
|
+
- **延后决定被重新讨论**——将状态从 `deferred` 改为 `accepted`,或新建条目替代原条目(原条目改为 `superseded`)。如果内容没有变化只是状态升级,可以直接改状态;如果结论发生了变化,使用替代模式。
|
|
65
|
+
- **补充细节**——直接编辑条目正文,不改变状态。可以追加更多场景、边界条件或交互细节。
|
|
66
|
+
- **关联 ADR**——如果后来为该日志创建了 ADR,补充 `Related: ADR-XXXX` 标注。
|
|
67
|
+
- **不要删除**——即使结论被替代,保留条目作为设计演进的历史上下文。
|
|
68
|
+
|
|
69
|
+
## 示例:被替代的日志
|
|
70
|
+
|
|
71
|
+
以下示例展示一条日志从 accepted 变为 superseded 的全过程:
|
|
72
|
+
|
|
73
|
+
原始条目:
|
|
74
|
+
|
|
75
|
+
```md
|
|
76
|
+
## LOG-0005: 订单状态机使用三态模型
|
|
77
|
+
|
|
78
|
+
Status: accepted
|
|
79
|
+
|
|
80
|
+
订单状态为 pending → confirmed → completed 的三态模型。
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
讨论后发现需要更细粒度,追加替代条目:
|
|
84
|
+
|
|
85
|
+
```md
|
|
86
|
+
## LOG-0005: 订单状态机使用三态模型
|
|
87
|
+
|
|
88
|
+
Status: superseded
|
|
89
|
+
Superseded by: LOG-0007
|
|
90
|
+
|
|
91
|
+
订单状态为 pending → confirmed → completed 的三态模型。后续讨论发现 confirmed 状态无法区分"已付款待发货"和"已发货待签收",因此改为五态模型。
|
|
92
|
+
|
|
93
|
+
## LOG-0007: 订单状态机使用五态模型
|
|
94
|
+
|
|
95
|
+
Status: accepted
|
|
96
|
+
|
|
97
|
+
订单状态为 pending → paid → shipped → delivered → completed 的五态模型。
|
|
98
|
+
详见 LOG-0005 的讨论背景。
|
|
99
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: specdev/implement
|
|
3
|
+
type: workflow-entry
|
|
4
|
+
workflow: specdev
|
|
5
|
+
name: 实现
|
|
6
|
+
description: 基于 spec 或 tickets 实现工作——以深层模块设计原则指导架构、以 TDD 红绿循环驱动编码、以双轴审查把关质量。
|
|
7
|
+
keywords: [实现, TDD, 代码审查, 模块设计, 重构]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 实现
|
|
11
|
+
|
|
12
|
+
基于 spec 或 tickets 实现工作——融合设计检查、TDD、审查、提交的完整实现流程。每一步引用内部子文件,不依赖外部 skill。
|
|
13
|
+
|
|
14
|
+
在开始实现之前,读取当前变更的上下文与架构决策:
|
|
15
|
+
|
|
16
|
+
- **CONTEXT.md** —— 项目领域术语与概念:`<Path>{roots.state}/specdev/changes/{change}/CONTEXT.md</Path>`
|
|
17
|
+
- **ADR.md** —— 架构决策记录:`<Path>{roots.state}/specdev/changes/{change}/ADR.md</Path>`
|
|
18
|
+
|
|
19
|
+
如果这些文件不存在,先运行 `<Path>{roots.workflows}/specdev/W-wayfinder/W-wayfinder.md</Path>` 或询问用户以建立上下文。
|
|
20
|
+
|
|
21
|
+
## 流程
|
|
22
|
+
|
|
23
|
+
### 1. 设计检查
|
|
24
|
+
|
|
25
|
+
在编写任何代码之前,检查当前变更涉及的模块接口设计。使用 `<Path>{roots.workflows}/specdev/I-implement/codebase-design-glossary.md</Path>` 中的术语表评估:
|
|
26
|
+
|
|
27
|
+
- 要实现的代码属于哪些**模块**?
|
|
28
|
+
- 每个模块的**接口**是什么?(类型签名、不变量、顺序约束、错误模式)
|
|
29
|
+
- 接口的**深度**如何?调用者是否获得了足够的**杠杆效应**?
|
|
30
|
+
- **接缝**放在哪里?是否有至少两个适配器来证明接缝是真实的?
|
|
31
|
+
- 每个接缝处的依赖属于哪个类别?(进程内、本地可替换、远程但自有、真正的外部依赖——参见 `<Path>{roots.workflows}/specdev/I-implement/deepening.md</Path>`)
|
|
32
|
+
|
|
33
|
+
如果需要探索替代接口设计,启动 `<Path>{roots.workflows}/specdev/I-implement/design-it-twice.md</Path>` 流程。
|
|
34
|
+
|
|
35
|
+
**完成标准**:模块接口设计已检查——深度、接缝位置、适配器策略合理。每个接缝的依赖类别已分类。
|
|
36
|
+
|
|
37
|
+
### 2. TDD 循环
|
|
38
|
+
|
|
39
|
+
按照 `<Path>{roots.workflows}/specdev/I-implement/tdd-rules.md</Path>` 执行红→绿循环:
|
|
40
|
+
|
|
41
|
+
1. 确认每个接缝的测试策略(参见 `<Path>{roots.workflows}/specdev/I-implement/tdd-examples.md</Path>` 中的示例)
|
|
42
|
+
2. 在接缝处编写失败的测试——通过公共接口验证行为
|
|
43
|
+
3. 只写足以通过测试的代码
|
|
44
|
+
4. 每个循环一张垂直切片,响应上一个循环的反馈
|
|
45
|
+
|
|
46
|
+
循环重复直到所有 tickets 实现完成。如果审查后发现阻塞性问题(步骤 3),返回此步骤继续循环。
|
|
47
|
+
|
|
48
|
+
**完成标准**:红→绿循环完成——每个接缝一张垂直切片,测试通过公共接口验证行为。所有 tickets 实现完成。
|
|
49
|
+
|
|
50
|
+
### 3. 审查
|
|
51
|
+
|
|
52
|
+
按照 `<Path>{roots.workflows}/specdev/I-implement/code-review-process.md</Path>` 执行双轴审查:
|
|
53
|
+
|
|
54
|
+
- **标准轴** — 代码是否符合编码规范?是否出现 Fowler 代码异味?
|
|
55
|
+
- **规范轴** — 代码是否忠实地实现了 `<Path>{roots.state}/specdev/changes/{change}/spec.md</Path>` 中的要求?
|
|
56
|
+
|
|
57
|
+
如果审查发现阻塞性问题,返回步骤 2「TDD 循环」修复;否则进入提交。
|
|
58
|
+
|
|
59
|
+
**完成标准**:双轴审查完成——标准轴和规范轴均已通过,无阻塞性问题。
|
|
60
|
+
|
|
61
|
+
### 4. 提交
|
|
62
|
+
|
|
63
|
+
1. 运行类型检查:`npx tsc --noEmit`
|
|
64
|
+
2. 运行完整测试套件:`npx vitest run`
|
|
65
|
+
3. 将更改提交到当前分支:`git add -A && git commit -m "<描述性提交信息>"`
|
|
66
|
+
4. 更新 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 下的状态文件
|
|
67
|
+
|
|
68
|
+
**完成标准**:代码已提交到当前分支,类型检查和测试通过,状态文件已更新。
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 子文件引用
|
|
73
|
+
|
|
74
|
+
以下子文件包含各步骤的详细规则、示例和参考材料,仅在对应步骤进入时加载:
|
|
75
|
+
|
|
76
|
+
| 文件 | 内容 | 触发条件 |
|
|
77
|
+
|------|------|---------|
|
|
78
|
+
| `<Path>{roots.workflows}/specdev/I-implement/codebase-design-glossary.md</Path>` | 深层模块设计的 8 个术语定义、原则、为可测试性设计 | 步骤 1「设计检查」进入时 |
|
|
79
|
+
| `<Path>{roots.workflows}/specdev/I-implement/deepening.md</Path>` | 依赖类别、接缝纪律、测试策略 | 设计检查中需要分析依赖时 |
|
|
80
|
+
| `<Path>{roots.workflows}/specdev/I-implement/design-it-twice.md</Path>` | 并行子 Agent 探索替代接口的三步流程 | 设计检查中需要探索替代方案时 |
|
|
81
|
+
| `<Path>{roots.workflows}/specdev/I-implement/tdd-rules.md</Path>` | 红→绿循环的完整规则、反模式、接缝测试策略 | 步骤 2「TDD 循环」进入时 |
|
|
82
|
+
| `<Path>{roots.workflows}/specdev/I-implement/tdd-examples.md</Path>` | 好的测试 vs 坏的测试、同义反复、Mock 指南、为可 Mock 性设计 | 编写具体测试时参考 |
|
|
83
|
+
| `<Path>{roots.workflows}/specdev/I-implement/code-review-process.md</Path>` | 双轴审查流程、12 个 Fowler 异味基线、子 Agent 提示词模板 | 步骤 3「审查」进入时 |
|
|
84
|
+
|
|
85
|
+
实现的产物(代码变更)直接写入仓库的源代码目录。变更追踪、spec 和 ADR 仍存放在 `<Path>{roots.state}/specdev/changes/{change}/</Path>` 下。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# 代码审查
|
|
2
|
+
|
|
3
|
+
对 `HEAD` 与实现开始前的某个固定点之间的 diff 进行双轴审查:
|
|
4
|
+
|
|
5
|
+
- **标准** — 代码是否符合此仓库已记录的编码规范?
|
|
6
|
+
- **规范** — 代码是否忠实地实现了原始 spec?
|
|
7
|
+
|
|
8
|
+
两个轴以**并行子 Agent** 方式运行,以免互相污染上下文,然后本流程汇总它们的发现。
|
|
9
|
+
|
|
10
|
+
## 流程
|
|
11
|
+
|
|
12
|
+
### 1. 确定固定点
|
|
13
|
+
|
|
14
|
+
实现开始前的 commit SHA、分支名、tag、`main` 或合并基准。通常为开始实现前的 HEAD。如果用户未指定,请询问。
|
|
15
|
+
|
|
16
|
+
一次性捕获 diff 命令:`git diff <固定点>...HEAD`(三个点,以便与合并基准比较)。同时通过 `git log <固定点>..HEAD --oneline` 记录 commit 列表。
|
|
17
|
+
|
|
18
|
+
继续之前,确认固定点可解析(`git rev-parse <固定点>`)且 diff 非空。无效引用或空 diff 应在此处失败 — 而非在两个并行子 Agent 内部。
|
|
19
|
+
|
|
20
|
+
### 2. 识别规范来源
|
|
21
|
+
|
|
22
|
+
规范来源为当前变更的 spec 文件:
|
|
23
|
+
|
|
24
|
+
`<Path>{roots.state}/specdev/changes/{change}/spec.md</Path>`
|
|
25
|
+
|
|
26
|
+
如果该文件不存在,**规范**子 Agent 将跳过并报告"无可用的规范"。
|
|
27
|
+
|
|
28
|
+
### 3. 识别标准来源
|
|
29
|
+
|
|
30
|
+
仓库中所有记录代码应如何编写的文件,如 `CODING_STANDARDS.md` 或 `CONTRIBUTING.md`。
|
|
31
|
+
|
|
32
|
+
在仓库记录的任何标准之上,标准轴始终携带以下**异味基线** — 一组固定的 Fowler 代码异味(《重构》第 3 章),即使仓库没有任何记录也适用。两条规则约束它:
|
|
33
|
+
|
|
34
|
+
- **仓库优先。** 已记录的仓库标准始终优先;当它认可基线可能标记的内容时,抑制该异味。
|
|
35
|
+
- **始终是判断。** 每个异味是一个带标签的启发式("可能的 Feature Envy"),从来不是硬性违规 — 并且,与这里的任何标准一样,跳过工具链已在强制执行的内容。
|
|
36
|
+
|
|
37
|
+
每个异味读作*它是什么* → *如何修复*;将其与 diff 进行匹配:
|
|
38
|
+
|
|
39
|
+
- **Mysterious Name** — 函数、变量或类型,其名称不能揭示它做什么或持有什么。→ 重命名;如果没有诚实的名称可用,说明设计不清晰。
|
|
40
|
+
- **Duplicated Code** — 相同的逻辑形态出现在变更中的一个以上代码块或文件中。→ 提取共享形态,从两处调用。
|
|
41
|
+
- **Feature Envy** — 一个方法过多地访问另一个对象的数据,而非自身数据。→ 将该方法移动到它所羡慕的数据上。
|
|
42
|
+
- **Data Clumps** — 相同的几个字段或参数总是一起出现(一个等待诞生的类型)。→ 将它们捆绑成一个类型,传递该类型。
|
|
43
|
+
- **Primitive Obsession** — 一个基本类型或字符串替代了应拥有自己类型的领域概念。→ 为该概念创建自己的小型类型。
|
|
44
|
+
- **Repeated Switches** — 对同一类型的相同 `switch`/`if` 级联在变更中反复出现。→ 用多态替代,或使用两个位置共享的一个映射。
|
|
45
|
+
- **Shotgun Surgery** — 一个逻辑变更迫使在 diff 中跨多个文件进行分散编辑。→ 将一起变更的内容汇聚到一个模块中。
|
|
46
|
+
- **Divergent Change** — 一个文件或模块因多个不相关的原因被编辑。→ 拆分,使每个模块因一个原因变更。
|
|
47
|
+
- **Speculative Generality** — 为 spec 中没有的需求添加的抽象、参数或钩子。→ 删除它;回退内联,直到真实需求出现。
|
|
48
|
+
- **Message Chains** — 调用者不应依赖的长链式 `a.b().c().d()` 导航。→ 在第一个对象上用一个方法隐藏整个链路。
|
|
49
|
+
- **Middle Man** — 一个类或函数大部分只是委托转发。→ 删除它,直接调用真正的目标。
|
|
50
|
+
- **Refused Bequest** — 一个子类或实现者忽略或覆盖了其继承的大部分内容。→ 放弃继承,使用组合。
|
|
51
|
+
|
|
52
|
+
### 4. 并行启动两个子 Agent
|
|
53
|
+
|
|
54
|
+
发送一条消息,包含两个 `Agent` 工具调用。两者均使用 `general-purpose` 子 Agent。
|
|
55
|
+
|
|
56
|
+
**标准子 Agent 提示词** — 包含:
|
|
57
|
+
|
|
58
|
+
- 完整的 diff 命令和 commit 列表。
|
|
59
|
+
- 在第 3 步中找到的标准来源文件列表,**加上第 3 步中的异味基线全文粘贴** — 子 Agent 没有其他途径获取它。
|
|
60
|
+
- 任务简述:"报告 — 在相关时按文件/代码块 — (a) diff 违反已记录标准的每个地方:引用标准(文件 + 规则);以及 (b) 你发现的任何基线异味:命名并引用代码块。区分硬性违规和判断性调用 — 已记录标准的违规可以是硬性的,但基线异味始终是判断性调用,且已记录的仓库标准覆盖基线。跳过工具链已强制执行的内容。400 词以内。"
|
|
61
|
+
|
|
62
|
+
**规范子 Agent 提示词** — 包含:
|
|
63
|
+
|
|
64
|
+
- diff 命令和 commit 列表。
|
|
65
|
+
- spec 的路径或已获取的内容:`<Path>{roots.state}/specdev/changes/{change}/spec.md</Path>`。
|
|
66
|
+
- 任务简述:"报告:(a) spec 要求但缺失或不完整的需求;(b) diff 中存在但未被要求的、超出范围的行为;(c) 看起来已实现但实现方式错误的需求。每个发现引用 spec 原文。400 词以内。"
|
|
67
|
+
|
|
68
|
+
如果 spec 缺失,跳过规范子 Agent 并在最终报告中注明。
|
|
69
|
+
|
|
70
|
+
### 5. 汇总
|
|
71
|
+
|
|
72
|
+
在 `## 标准` 和 `## 规范` 标题下呈现两份报告,原文或略微整理。**不要**合并或重新排名发现 — 两个轴故意分离(见*为什么是两个轴*)。
|
|
73
|
+
|
|
74
|
+
以一行摘要结束:每个轴的发现总数,以及每个轴内_最严重的问题_(如有)。不要在轴之间选一个"赢家" — 那正是分离设计要防止的重新排名。
|
|
75
|
+
|
|
76
|
+
## 为什么是两个轴
|
|
77
|
+
|
|
78
|
+
一个变更可能通过一个轴而未通过另一个:
|
|
79
|
+
|
|
80
|
+
- 代码遵循所有标准但实现了错误的东西 → **标准通过,规范未通过。**
|
|
81
|
+
- 代码完全按 spec 要求做了,但违反了项目的约定 → **规范通过,标准未通过。**
|
|
82
|
+
|
|
83
|
+
分开报告可以防止一个轴掩盖另一个轴。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 代码仓设计
|
|
2
|
+
|
|
3
|
+
设计**深层模块**:通过一个小接口承载大量行为,放置在干净的缝合点处,可通过该接口进行测试。在任何设计或重构代码的地方使用这些语言和原则。目标是为调用者提供杠杆效应,为维护者提供局部性,为所有人提供可测试性。
|
|
4
|
+
|
|
5
|
+
## 术语表
|
|
6
|
+
|
|
7
|
+
严格使用以下术语 — 不要用 "component"、"service"、"API" 或 "boundary" 替代。一致的语言才是重点。
|
|
8
|
+
|
|
9
|
+
**Module(模块)** — 任何具有接口和实现的东西。有意识地与规模无关:函数、类、包或跨层切片。_避免使用_:unit、component、service。
|
|
10
|
+
|
|
11
|
+
**Interface(接口)** — 调用者正确使用模块所需了解的一切:类型签名,还包括不变量、顺序约束、错误模式、必需配置和性能特征。_避免使用_:API、signature(太窄 — 它们仅指类型层面的表面)。
|
|
12
|
+
|
|
13
|
+
**Implementation(实现)** — 模块内部的内容,它的代码体。区别于 **Adapter(适配器)**:一个东西可以是一个小适配器加一个大实现(Postgres 仓库),也可以是一个大适配器加一个小实现(内存假实现)。当讨论缝合点时用 "adapter";否则用 "implementation"。
|
|
14
|
+
|
|
15
|
+
**Depth(深度)** — 接口处的杠杆效应:调用者(或测试)每学习一个单位的接口可以驱动的行为量。当大量行为隐藏在小接口后面时,模块是**深层的**;当接口几乎和实现一样复杂时,模块是**浅层的**。
|
|
16
|
+
|
|
17
|
+
**Seam(缝合点)** _(Michael Feathers)_ — 一个可以在不编辑该位置的情况下改变行为的地方;模块接口所在的*位置*。缝合点放在哪里本身就是一个设计决策,与缝合点后面放什么不同。_避免使用_:boundary(与 DDD 的有界上下文重载)。
|
|
18
|
+
|
|
19
|
+
**Adapter(适配器)** — 在缝合点处满足接口的具体事物。描述的是*角色*(它填充哪个槽位),而非实质(内部是什么)。
|
|
20
|
+
|
|
21
|
+
**Leverage(杠杆效应)** — 调用者从深度中获得的好处:每学习一个单位的接口获得更多的能力。一个实现为 N 个调用点和 M 个测试带来回报。
|
|
22
|
+
|
|
23
|
+
**Locality(局部性)** — 维护者从深度中获得的好处:变更、bug、知识和验证集中在一个地方,而非分散在调用者之间。一次修复,处处生效。
|
|
24
|
+
|
|
25
|
+
## 深层 vs 浅层
|
|
26
|
+
|
|
27
|
+
**深层模块** = 小接口 + 大量实现:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌─────────────────────┐
|
|
31
|
+
│ 小接口 │ ← 少量方法,简单参数
|
|
32
|
+
├─────────────────────┤
|
|
33
|
+
│ │
|
|
34
|
+
│ 深层实现 │ ← 隐藏的复杂逻辑
|
|
35
|
+
│ │
|
|
36
|
+
└─────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**浅层模块** = 大接口 + 少量实现(应避免):
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
┌─────────────────────────────────┐
|
|
43
|
+
│ 大接口 │ ← 大量方法,复杂参数
|
|
44
|
+
├─────────────────────────────────┤
|
|
45
|
+
│ 薄实现 │ ← 仅仅是透传
|
|
46
|
+
└─────────────────────────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
设计接口时,问自己:
|
|
50
|
+
|
|
51
|
+
- 我能减少方法数量吗?
|
|
52
|
+
- 我能简化参数吗?
|
|
53
|
+
- 我能隐藏更多内部的复杂性吗?
|
|
54
|
+
|
|
55
|
+
## 原则
|
|
56
|
+
|
|
57
|
+
- **深度是接口的属性,而非实现的属性。** 一个深层模块内部可以由小型、可模拟、可替换的部分组成 — 只是它们不属于接口的一部分。一个模块可以拥有**内部缝合点**(对其实现私有,用于其自身测试)以及位于其接口处的**外部缝合点**。
|
|
58
|
+
- **删除测试。** 想象删除这个模块。如果复杂性消失,它就是个透传层。如果复杂性在 N 个调用者中重新出现,它就在发挥价值。
|
|
59
|
+
- **接口就是测试表面。** 调用者和测试穿过同一个缝合点。如果你想测试接口_之外_的内容,模块可能形状不对。
|
|
60
|
+
- **一个适配器意味着假设的缝合点。两个适配器意味着真实的缝合点。** 除非有东西确实在缝合点两侧变化,否则不要引入缝合点。
|
|
61
|
+
|
|
62
|
+
## 为可测试性而设计
|
|
63
|
+
|
|
64
|
+
良好的接口使测试变得自然:
|
|
65
|
+
|
|
66
|
+
1. **接收依赖,不要创建依赖。**
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// 可测试
|
|
70
|
+
function processOrder(order, paymentGateway) {}
|
|
71
|
+
|
|
72
|
+
// 难以测试
|
|
73
|
+
function processOrder(order) {
|
|
74
|
+
const gateway = new StripeGateway();
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
2. **返回结果,不要产生副作用。**
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// 可测试
|
|
82
|
+
function calculateDiscount(cart): Discount {}
|
|
83
|
+
|
|
84
|
+
// 难以测试
|
|
85
|
+
function applyDiscount(cart): void {
|
|
86
|
+
cart.total -= discount;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
3. **小表面积。** 更少的方法 = 更少的测试需求。更少的参数 = 更简单的测试设置。
|
|
91
|
+
|
|
92
|
+
## 关系
|
|
93
|
+
|
|
94
|
+
- 一个 **Module** 恰好有一个 **Interface**(它向调用者和测试呈现的表面)。
|
|
95
|
+
- **Depth** 是一个 **Module** 的属性,对照其 **Interface** 来度量。
|
|
96
|
+
- 一个 **Seam** 是一个 **Module** 的 **Interface** 所在的位置。
|
|
97
|
+
- 一个 **Adapter** 位于 **Seam** 处,满足 **Interface**。
|
|
98
|
+
- **Depth** 为调用者产生 **Leverage**,为维护者产生 **Locality**。
|
|
99
|
+
|
|
100
|
+
## 已拒绝的框架
|
|
101
|
+
|
|
102
|
+
- **深度作为实现行数与接口行数之比** (Ousterhout):奖励填充实现。我们使用深度即杠杆效应来替代。
|
|
103
|
+
- **"Interface" 作为 TypeScript 的 `interface` 关键字或类的公开方法**:太窄 — 此处的接口包括调用者必须了解的每个事实。
|
|
104
|
+
- **"Boundary"**:与 DDD 的有界上下文重载。说 **seam** 或 **interface**。
|
|
105
|
+
|
|
106
|
+
## 深入阅读
|
|
107
|
+
|
|
108
|
+
- **给定依赖的情况下深化一个集群** — 见 `<Path>{roots.workflows}/specdev/I-implement/deepening.md</Path>`:依赖类别、缝合点规程、以及替换而非分层的测试。
|
|
109
|
+
- **探索替代接口** — 见 `<Path>{roots.workflows}/specdev/I-implement/design-it-twice.md</Path>`:启动并行子 agent,以几种截然不同的方式设计接口,然后在深度、局部性和缝合点位置上进行比较。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 深化
|
|
2
|
+
|
|
3
|
+
如何在给定依赖关系的情况下,安全地深化一组浅模块。假定你已掌握 `<Path>{roots.workflows}/specdev/I-implement/codebase-design-glossary.md</Path>` 中的词汇 — **module**(模块)、**interface**(接口)、**seam**(接缝)、**adapter**(适配器)。
|
|
4
|
+
|
|
5
|
+
## 依赖类别
|
|
6
|
+
|
|
7
|
+
在评估一个深化候选时,对其依赖进行分类。类别决定了深化后的模块如何通过其接缝进行测试。
|
|
8
|
+
|
|
9
|
+
### 1. 进程内
|
|
10
|
+
|
|
11
|
+
纯计算、内存状态、无 I/O。始终可深化 — 合并模块并通过新接口直接测试。不需要适配器。
|
|
12
|
+
|
|
13
|
+
### 2. 本地可替换
|
|
14
|
+
|
|
15
|
+
具有本地测试替代品的依赖(PGLite 替代 Postgres、内存文件系统)。如果存在替代品则可深化。深化后的模块在测试套件中使用运行的替代品进行测试。接缝是内部的;在模块的外部接口处不需要端口。
|
|
16
|
+
|
|
17
|
+
### 3. 远程但自有(端口与适配器)
|
|
18
|
+
|
|
19
|
+
跨网络边界的自有服务(微服务、内部 API)。在接缝处定义一个 **port**(端口,即接口)。深模块拥有逻辑;传输层作为 **adapter**(适配器)注入。测试使用内存适配器。生产环境使用 HTTP/gRPC/队列适配器。
|
|
20
|
+
|
|
21
|
+
建议形式:*"在接缝处定义一个端口,为生产环境实现 HTTP 适配器,为测试实现内存适配器,这样逻辑就驻留在一个深模块中,即使它跨网络部署。"*
|
|
22
|
+
|
|
23
|
+
### 4. 真正的外部依赖(Mock)
|
|
24
|
+
|
|
25
|
+
你无法控制的第三方服务(Stripe、Twilio 等)。深化后的模块将外部依赖作为注入端口;测试提供一个 mock 适配器。
|
|
26
|
+
|
|
27
|
+
## 接缝纪律
|
|
28
|
+
|
|
29
|
+
- **一个适配器意味着假设性接缝。两个适配器意味着真正的接缝。** 除非至少有两个适配器是合理的(通常是生产 + 测试),否则不要引入端口。单一适配器的接缝只是间接层。
|
|
30
|
+
- **内部接缝 vs 外部接缝。** 一个深模块可以既有内部接缝(对其实现私有,供其自身的测试使用),也有其接口处的外部接缝。不要仅仅因为测试使用了内部接缝就通过接口暴露它们。
|
|
31
|
+
|
|
32
|
+
## 测试策略:替换,而非叠加
|
|
33
|
+
|
|
34
|
+
- 一旦深化后模块接口的测试存在,旧有浅模块上的单元测试就变成了废料 — 删除它们。
|
|
35
|
+
- 在深化后模块的接口处编写新测试。**接口就是测试表面**。
|
|
36
|
+
- 测试通过接口断言可观察的结果,而非内部状态。
|
|
37
|
+
- 测试应经受住内部重构 — 它们描述的是行为,而非实现。如果测试在实现改变时必须更改,那它就是在测试接口之后的东西。
|