@house365/comate 0.9.18 → 0.9.19
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/docs/COMATE_BEGINNER_GUIDE.md +76 -317
- package/docs/COMATE_INIT_DIRECTORY_GUIDE.md +201 -0
- package/docs/COMATE_MANUAL.md +2 -1
- package/package.json +1 -1
- package/skills/comate/scripts/comate.sh +163 -0
- package/skills/comate/tests/comate.bats +204 -0
- package/skills/comate-gate/SKILL.md +27 -12
- package/skills/comate-open/SKILL.md +31 -11
|
@@ -1,45 +1,33 @@
|
|
|
1
|
-
# comate
|
|
1
|
+
# comate 入门指南
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
这份指南覆盖 comate 的安装、初始化和日常使用。
|
|
4
4
|
|
|
5
5
|
## 1. comate 是什么
|
|
6
6
|
|
|
7
|
-
comate
|
|
8
|
-
|
|
9
|
-
完整流程:
|
|
7
|
+
comate 是一个面向 AI 编程工具的 SDD(规约驱动开发)编排器。它把一次需求变更按阶段推进:
|
|
10
8
|
|
|
11
9
|
```text
|
|
12
10
|
原始 PRD(可选)
|
|
13
|
-
|
|
14
|
-
/comate-gate PRD 门禁和拆分
|
|
15
|
-
↓
|
|
16
|
-
/comate-open 开题,创建 change
|
|
17
|
-
↓
|
|
18
|
-
/comate-design 深度设计
|
|
19
|
-
↓
|
|
20
|
-
/comate-build 计划和编码
|
|
21
|
-
↓
|
|
22
|
-
/comate-verify 验证和收尾
|
|
23
|
-
↓
|
|
24
|
-
/comate-archive 归档规格
|
|
11
|
+
-> gate -> open -> design -> build -> verify -> archive
|
|
25
12
|
```
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
- **Superpowers** 负责“怎么做好”:头脑风暴、技术设计、计划、执行和验证。
|
|
29
|
-
- **comate** 负责识别当前阶段,并把这些能力串起来。
|
|
14
|
+
通常只需要使用 `/comate`,它会根据当前项目状态自动进入下一阶段。
|
|
30
15
|
|
|
31
|
-
|
|
16
|
+
- `/comate-gate`:审阅 PRD,并在需要时拆分变更。
|
|
17
|
+
- `/comate-fix`:处理 Bug 或很小的文案、配置调整。
|
|
18
|
+
- `/comate-grill`:逐个澄清 PRD、设计或计划中的关键决策。
|
|
19
|
+
- `/comate-kb-integration`:根据第三方官方文档生成对接档案。
|
|
32
20
|
|
|
33
|
-
|
|
21
|
+
## 2. 安装前准备
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
| --- | --- | --- |
|
|
37
|
-
| Node.js | 20 或更高 | 运行 comate 和 npm |
|
|
38
|
-
| npm | 随 Node.js 安装 | 安装 comate、OpenSpec |
|
|
39
|
-
| Git | 任意现代版本 | 管理代码和分支 |
|
|
40
|
-
| Bash | 3.2 或更高 | 执行 comate 的状态机脚本 |
|
|
23
|
+
需要以下环境:
|
|
41
24
|
|
|
42
|
-
|
|
25
|
+
| 软件 | 要求 |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| Node.js | 20 或更高版本 |
|
|
28
|
+
| npm | 随 Node.js 安装 |
|
|
29
|
+
| Git | 任意现代版本 |
|
|
30
|
+
| Bash | 3.2 或更高版本 |
|
|
43
31
|
|
|
44
32
|
检查环境:
|
|
45
33
|
|
|
@@ -50,380 +38,151 @@ git --version
|
|
|
50
38
|
bash --version
|
|
51
39
|
```
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
Windows 用户请安装 [Git for Windows](https://git-scm.com/download/win),以提供 Git Bash。PowerShell 可以运行 `comate`,但 comate 的流程脚本仍需要 Bash。
|
|
54
42
|
|
|
55
|
-
|
|
43
|
+
首次初始化会访问 npm registry 和 GitHub,用于安装 OpenSpec CLI 与 Superpowers。
|
|
56
44
|
|
|
57
|
-
|
|
45
|
+
## 3. 安装并初始化项目
|
|
58
46
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
全局安装:
|
|
47
|
+
先安装 comate:
|
|
62
48
|
|
|
63
49
|
```bash
|
|
64
50
|
npm install -g @house365/comate
|
|
65
51
|
```
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
comate --version
|
|
71
|
-
comate --help
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
安装后,skill 通常会同步到用户级目录:
|
|
75
|
-
|
|
76
|
-
- macOS/Linux:`~/.agents/skills/`
|
|
77
|
-
- Windows:`%USERPROFILE%\.agents\skills\`
|
|
78
|
-
|
|
79
|
-
用户级 skill 可以被多个项目共用。项目自己的规格文件放在项目目录下的 `openspec/`。
|
|
80
|
-
|
|
81
|
-
## 4. 初始化项目:`comate init`
|
|
82
|
-
|
|
83
|
-
进入项目根目录再运行:
|
|
53
|
+
然后进入业务项目根目录初始化:
|
|
84
54
|
|
|
85
55
|
```bash
|
|
86
56
|
cd path/to/your-project
|
|
87
57
|
comate init
|
|
58
|
+
comate doctor
|
|
88
59
|
```
|
|
89
60
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```powershell
|
|
93
|
-
Set-Location D:\work\my-project
|
|
94
|
-
comate init
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
请不要在用户主目录直接运行。comate 会检查当前目录是否有 `.git`、`package.json`、`pyproject.toml`、`Cargo.toml` 等项目标记;空目录会先询问是否确认初始化。
|
|
98
|
-
|
|
99
|
-
### 4.1 `init` 会做什么
|
|
100
|
-
|
|
101
|
-
1. 检查 Bash 版本。
|
|
102
|
-
2. 检查 OpenSpec,不存在或版本过旧时执行 `npm install -g @fission-ai/openspec@latest`。
|
|
103
|
-
3. 检查 Superpowers,不存在时执行 `skills add obra/superpowers -g -y`。
|
|
104
|
-
4. 把 comate 自身 skill 同步到用户级 skill 目录。
|
|
105
|
-
5. 创建项目级 `openspec/`(已经存在则保留)。
|
|
106
|
-
6. 创建 `docs/` 知识库骨架(只补缺少的文件,不覆盖已有文件)。
|
|
107
|
-
|
|
108
|
-
某一步失败时,修好网络或环境后重新运行 `comate init` 即可。它是幂等的,已完成的步骤会跳过。
|
|
109
|
-
|
|
110
|
-
## 5. 初始化后的目录结构
|
|
111
|
-
|
|
112
|
-
```text
|
|
113
|
-
your-project/
|
|
114
|
-
├─ openspec/
|
|
115
|
-
│ ├─ changes/ # 每次需求变更的工作区
|
|
116
|
-
│ └─ specs/ # 已沉淀的主规格
|
|
117
|
-
├─ docs/
|
|
118
|
-
│ ├─ README.md
|
|
119
|
-
│ ├─ architecture/
|
|
120
|
-
│ │ └─ README.md
|
|
121
|
-
│ └─ knowledge-base/
|
|
122
|
-
│ ├─ README.md
|
|
123
|
-
│ ├─ requirements/
|
|
124
|
-
│ │ └─ _templates/PRD.md
|
|
125
|
-
│ └─ integrations/
|
|
126
|
-
│ ├─ README.md
|
|
127
|
-
│ └─ _TEMPLATE/
|
|
128
|
-
└─ 你的业务代码...
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### 5.1 `openspec/`:项目规格
|
|
132
|
-
|
|
133
|
-
- `changes/<change-name>/`:正在进行的变更,包含 `proposal.md`、`design.md`、`tasks.md`、delta spec 和 `.comate.yaml`。
|
|
134
|
-
- `specs/<capability>/spec.md`:归档后长期维护的能力规格。
|
|
135
|
-
- `changes/archive/`:已经完成并归档的 change。
|
|
136
|
-
|
|
137
|
-
`.comate.yaml` 是 comate 的状态真相源,记录当前阶段、构建模式、验证结果等。
|
|
138
|
-
|
|
139
|
-
### 5.2 `docs/`:长期知识库
|
|
140
|
-
|
|
141
|
-
- `docs/`:项目背景、架构约束、原始 PRD、第三方对接资料。
|
|
142
|
-
- `openspec/`:某次变更的提案、设计、任务、能力规格和归档记录。
|
|
143
|
-
|
|
144
|
-
### 5.3 用户级 skill 目录
|
|
145
|
-
|
|
146
|
-
```text
|
|
147
|
-
~/.agents/skills/ # macOS/Linux
|
|
148
|
-
%USERPROFILE%\.agents\skills\ # Windows
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
已有的 Superpowers 或其他 skill 不会因为 `comate init` 被覆盖。
|
|
152
|
-
|
|
153
|
-
## 6. `docs/` 目录详解
|
|
154
|
-
|
|
155
|
-
### 6.1 `docs/README.md`
|
|
156
|
-
|
|
157
|
-
说明知识库整体规则、`architecture/` 与 `knowledge-base/` 的边界,以及它们和 `openspec/` 的关系。
|
|
158
|
-
|
|
159
|
-
### 6.2 `docs/architecture/`
|
|
160
|
-
|
|
161
|
-
放项目架构约束,例如前后端分层、API/数据库/错误码约定、命名规范和部署约束。`comate init` 只创建占位 README,真正的规则需要项目自己补充。这里是 `/comate-gate` 检查契约时的事实来源。
|
|
162
|
-
|
|
163
|
-
### 6.3 `docs/knowledge-base/`
|
|
164
|
-
|
|
165
|
-
放长期维护的需求和外部服务资料:
|
|
166
|
-
|
|
167
|
-
- `requirements/`:原始 PRD;
|
|
168
|
-
- `integrations/`:第三方服务对接档案;
|
|
169
|
-
- `README.md`:命名、溯源和 PRD 到 change 的流程说明。
|
|
170
|
-
|
|
171
|
-
### 6.4 `docs/knowledge-base/requirements/`
|
|
172
|
-
|
|
173
|
-
这里是 PRD 的固定位置。模板位于:
|
|
174
|
-
|
|
175
|
-
```text
|
|
176
|
-
docs/knowledge-base/requirements/_templates/PRD.md
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
复制模板到功能目录,填写后运行 `/comate-gate`。
|
|
180
|
-
|
|
181
|
-
### 6.5 `docs/knowledge-base/integrations/`
|
|
182
|
-
|
|
183
|
-
每个第三方平台建立一个子目录,例如:
|
|
184
|
-
|
|
185
|
-
```text
|
|
186
|
-
docs/knowledge-base/integrations/alipay/README.md
|
|
187
|
-
docs/knowledge-base/integrations/alipay/api-snapshot.md
|
|
188
|
-
```
|
|
61
|
+
`comate init` 会:
|
|
189
62
|
|
|
190
|
-
|
|
63
|
+
1. 安装或检查 OpenSpec CLI、Superpowers 和 comate skills。
|
|
64
|
+
2. 在用户级目录安装 skills(通常是 `~/.agents/skills/`;Windows 为 `%USERPROFILE%\.agents\skills\`)。
|
|
65
|
+
3. 在当前项目创建 `openspec/` 和 `docs/` 知识库骨架。
|
|
191
66
|
|
|
192
|
-
|
|
67
|
+
已有文件不会被覆盖,初始化可以重复执行。请在项目目录运行,不要直接在用户主目录运行。
|
|
193
68
|
|
|
194
|
-
|
|
69
|
+
初始化后,项目通常会多出:
|
|
195
70
|
|
|
196
71
|
```text
|
|
197
|
-
|
|
72
|
+
openspec/ # 变更和长期规格
|
|
73
|
+
docs/knowledge-base/requirements/ # PRD
|
|
74
|
+
docs/architecture/ # 架构约束
|
|
198
75
|
```
|
|
199
76
|
|
|
200
|
-
|
|
77
|
+
## 4. 第一次使用
|
|
201
78
|
|
|
202
|
-
|
|
203
|
-
docs/knowledge-base/requirements/20260820-用户登录/PRD-001-用户登录.md
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
规则:
|
|
207
|
-
|
|
208
|
-
1. 功能目录使用“日期 + 功能名”,日期格式为 `YYYYMMDD`。
|
|
209
|
-
2. PRD 文件以 `PRD-001`、`PRD-002` 这样的三位序号开头。
|
|
210
|
-
3. 文件名中的功能名应和目录功能名保持一致。
|
|
211
|
-
4. 同一个功能目录中的序号不要重复。
|
|
212
|
-
5. 不建议使用 `PRD-1.md` 或把 PRD 放到 `openspec/changes/`。
|
|
213
|
-
|
|
214
|
-
`/comate-gate` 会在 PRD 同级自动生成:
|
|
79
|
+
在 Claude Code、Codex、Cursor 等支持 Agent Skills 的工具中输入:
|
|
215
80
|
|
|
216
81
|
```text
|
|
217
|
-
|
|
218
|
-
PRD-001-REVIEW.md # 人类可读的评审报告
|
|
82
|
+
/comate 我想增加用户登录功能,支持邮箱和密码登录
|
|
219
83
|
```
|
|
220
84
|
|
|
221
|
-
|
|
85
|
+
comate 会创建并推进一个 change。过程中只在需要你决定时提问;确认设计、实现和验证结果后,流程会完成归档。
|
|
222
86
|
|
|
223
|
-
|
|
87
|
+
### 需求来自 PRD
|
|
224
88
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
先把 PRD 放到 `docs/knowledge-base/requirements/`,然后直接指定 PRD 路径:
|
|
89
|
+
复杂需求可以先把 PRD 放在 `docs/knowledge-base/requirements/`,然后直接指定文件:
|
|
228
90
|
|
|
229
91
|
```text
|
|
230
92
|
/comate docs/knowledge-base/requirements/20260820-用户登录/PRD-001-用户登录.md
|
|
231
93
|
```
|
|
232
94
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
如果只想单独审阅或重新运行门禁,也可以直接输入:
|
|
95
|
+
这会自动进入 PRD 门禁和拆分流程。只想单独审阅 PRD 时使用:
|
|
236
96
|
|
|
237
97
|
```text
|
|
238
|
-
/comate-gate
|
|
98
|
+
/comate-gate path/to/PRD.md
|
|
239
99
|
```
|
|
240
100
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
```text
|
|
244
|
-
/comate
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
comate 会自动从当前阶段继续:`open → design → build → verify → archive`。
|
|
248
|
-
|
|
249
|
-
### 8.2 需求来自普通对话
|
|
250
|
-
|
|
251
|
-
不需要先写 PRD,直接输入:
|
|
252
|
-
|
|
253
|
-
```text
|
|
254
|
-
/comate 我想增加用户登录功能,支持邮箱和密码登录
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
复杂需求仍建议先整理成 PRD,便于评审和追踪。
|
|
258
|
-
|
|
259
|
-
### 8.3 Bug 或很小的调整
|
|
101
|
+
### Bug 或小调整
|
|
260
102
|
|
|
261
103
|
```text
|
|
262
104
|
/comate-fix 修复登录失败时页面没有提示的问题
|
|
263
105
|
```
|
|
264
106
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
## 9. Skill 介绍
|
|
268
|
-
|
|
269
|
-
| Skill | 作用 |
|
|
270
|
-
| --- | --- |
|
|
271
|
-
| `/comate` | 总入口,自动检测当前 change 阶段并分派 |
|
|
272
|
-
| `/comate-gate` | 审阅 PRD、评分、记录阻断问题并拆分 change |
|
|
273
|
-
| `/comate-open` | 探索需求,创建 change 和 proposal/design/tasks 骨架 |
|
|
274
|
-
| `/comate-design` | 技术设计和方案头脑风暴,形成 Design Doc 和 delta spec |
|
|
275
|
-
| `/comate-build` | 制定实施计划、按任务编码并更新 tasks |
|
|
276
|
-
| `/comate-verify` | 运行测试和检查,生成验证报告并处理分支收尾 |
|
|
277
|
-
| `/comate-archive` | 同步 delta spec、标记设计文档并归档 change |
|
|
278
|
-
| `/comate-fix` | Bug 或小调整的快速路径 |
|
|
279
|
-
| `/comate-grill` | 对 PRD、计划或设计中的模糊决策逐个追问 |
|
|
280
|
-
| `/comate-kb-integration` | 根据第三方官方文档生成 integrations 对接档案 |
|
|
281
|
-
|
|
282
|
-
### 9.1 底层依赖分工
|
|
107
|
+
如果改动涉及新能力、架构、数据库结构或多个模块,请改用完整的 `/comate` 流程。
|
|
283
108
|
|
|
284
|
-
|
|
285
|
-
- **Superpowers**:由 `comate init` 通过 `skills` CLI 从 `obra/superpowers` 安装,负责设计、规划、执行和收尾。
|
|
286
|
-
- **comate skill**:本项目提供的编排规则和状态机。
|
|
109
|
+
### 中断后继续
|
|
287
110
|
|
|
288
|
-
|
|
111
|
+
回到同一个项目目录,再输入:
|
|
289
112
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
### 10.1 安装和检查
|
|
293
|
-
|
|
294
|
-
```bash
|
|
295
|
-
comate --help
|
|
296
|
-
comate --version
|
|
297
|
-
comate init
|
|
298
|
-
comate doctor
|
|
113
|
+
```text
|
|
114
|
+
/comate
|
|
299
115
|
```
|
|
300
116
|
|
|
301
|
-
|
|
117
|
+
comate 会读取 `openspec/changes/<change-name>/.comate.yaml`,从当前阶段继续。
|
|
302
118
|
|
|
303
|
-
|
|
304
|
-
comate usage
|
|
305
|
-
comate usage daily
|
|
306
|
-
comate usage --global
|
|
307
|
-
comate usage --json
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## 11. 配套 UI 查看器
|
|
119
|
+
## 5. 配套 UI(可选)
|
|
311
120
|
|
|
312
|
-
|
|
121
|
+
Comate UI 是本地只读的可视化工作台,用于查看 change 状态、规格、PRD、实施计划和验证结果,不会修改项目文件。
|
|
313
122
|
|
|
314
|
-
|
|
123
|
+
安装:
|
|
315
124
|
|
|
316
125
|
```bash
|
|
317
126
|
npm install -g @house365/comate-ui@latest
|
|
318
127
|
```
|
|
319
128
|
|
|
320
|
-
|
|
129
|
+
在已经执行过 `comate init` 的项目根目录启动:
|
|
321
130
|
|
|
322
131
|
```bash
|
|
323
132
|
comate-ui
|
|
324
133
|
```
|
|
325
134
|
|
|
326
|
-
|
|
135
|
+
启动后会自动打开浏览器。服务只监听本机 `127.0.0.1`,按 `Ctrl+C` 停止。也可以不安装直接运行,或指定项目和端口:
|
|
327
136
|
|
|
328
137
|
```bash
|
|
329
|
-
comate-ui
|
|
330
|
-
comate-ui
|
|
331
|
-
comate-ui --no-open
|
|
138
|
+
npx @house365/comate-ui@latest
|
|
139
|
+
comate-ui path/to/your-project --port 4300
|
|
140
|
+
comate-ui --no-open # 不自动打开浏览器
|
|
141
|
+
comate-ui --demo # 查看内置示例
|
|
332
142
|
```
|
|
333
143
|
|
|
334
|
-
|
|
144
|
+
## 6. 常用命令
|
|
335
145
|
|
|
336
146
|
```bash
|
|
337
|
-
comate
|
|
147
|
+
comate --help # 查看命令帮助
|
|
148
|
+
comate --version # 查看版本
|
|
149
|
+
comate doctor # 检查 Bash、OpenSpec 和 skills
|
|
150
|
+
comate update # 更新到 npm 最新版本
|
|
151
|
+
comate usage # 查看当前项目 token 用量
|
|
152
|
+
comate usage daily # 按天查看
|
|
153
|
+
comate usage --global # 查看全部项目
|
|
338
154
|
```
|
|
339
155
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
## 12. 更新 comate
|
|
156
|
+
更新后建议在各项目根目录重新执行一次 `comate init`,把新版 skills 同步到用户级目录:
|
|
343
157
|
|
|
344
158
|
```bash
|
|
345
|
-
|
|
159
|
+
comate update
|
|
346
160
|
cd path/to/your-project
|
|
347
161
|
comate init
|
|
348
162
|
```
|
|
349
163
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
更新是幂等的:已有 Superpowers 不覆盖,已有项目规格和 docs 文件不覆盖。若使用 `npm install --ignore-scripts`,请手动执行 `comate init`。
|
|
353
|
-
|
|
354
|
-
## 13. 网络问题和手工补装
|
|
355
|
-
|
|
356
|
-
### OpenSpec 安装失败
|
|
357
|
-
|
|
358
|
-
```bash
|
|
359
|
-
npm install -g @fission-ai/openspec@latest
|
|
360
|
-
openspec --version
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Superpowers 安装失败
|
|
164
|
+
## 7. 常见问题
|
|
364
165
|
|
|
365
|
-
|
|
366
|
-
skills add obra/superpowers -g -y
|
|
367
|
-
```
|
|
166
|
+
### `comate init` 失败
|
|
368
167
|
|
|
369
|
-
|
|
168
|
+
先运行:
|
|
370
169
|
|
|
371
170
|
```bash
|
|
372
|
-
comate init
|
|
373
171
|
comate doctor
|
|
374
172
|
```
|
|
375
173
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
## 14. 常见问题
|
|
379
|
-
|
|
380
|
-
### `comate init` 应该在哪运行?
|
|
381
|
-
|
|
382
|
-
在业务项目根目录运行,也就是能看到 `.git` 或 `package.json` 的目录。不要在 `C:\Users\你的用户名` 或 `~` 直接运行。
|
|
383
|
-
|
|
384
|
-
### npm 安装后为什么还要 `comate init`?
|
|
385
|
-
|
|
386
|
-
npm 只安装 CLI 和包本身。`init` 才会准备 OpenSpec、Superpowers 与 comate skill,并创建当前项目的 `openspec/`、`docs/`。
|
|
387
|
-
|
|
388
|
-
### 重跑 init 会不会覆盖我的文件?
|
|
389
|
-
|
|
390
|
-
不会。已有 skill、`openspec/`、docs 文件会保留,只补缺少的内容。
|
|
391
|
-
|
|
392
|
-
### 找不到 `openspec` 怎么办?
|
|
393
|
-
|
|
394
|
-
重开终端让全局 npm PATH 生效,然后运行 `openspec --version`。仍找不到就执行 `npm install -g @fission-ai/openspec@latest`,再运行 `comate init`。
|
|
395
|
-
|
|
396
|
-
### 找不到 Bash 怎么办?
|
|
397
|
-
|
|
398
|
-
Windows 安装 Git for Windows;macOS 自带的 Bash 3.2 可以直接使用,不需要另外安装 Bash 4+。
|
|
399
|
-
|
|
400
|
-
### 流程中断了怎么办?
|
|
401
|
-
|
|
402
|
-
回到同一个项目目录,再输入 `/comate`。comate 会读取 `openspec/changes/<name>/.comate.yaml` 和已有产物,从当前阶段继续。先运行 `comate doctor` 可以排除环境问题。
|
|
403
|
-
|
|
404
|
-
### 有多个 change 怎么办?
|
|
405
|
-
|
|
406
|
-
在对话中明确 change 名称。不要同时修改多个 change 的 `tasks.md`。
|
|
407
|
-
|
|
408
|
-
## 15. 新手最短上手路径
|
|
174
|
+
如果是网络问题,确认 npm registry 和 GitHub 可访问,修复代理或网络后重新运行 `comate init`。也可以手动补装:
|
|
409
175
|
|
|
410
176
|
```bash
|
|
411
|
-
npm install -g @
|
|
412
|
-
|
|
177
|
+
npm install -g @fission-ai/openspec@latest
|
|
178
|
+
skills add obra/superpowers -g -y
|
|
413
179
|
comate init
|
|
414
|
-
comate doctor
|
|
415
180
|
```
|
|
416
181
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
```text
|
|
420
|
-
/comate 我想实现一个用户登录功能
|
|
421
|
-
```
|
|
182
|
+
### 找不到 `openspec` 或 Bash
|
|
422
183
|
|
|
423
|
-
|
|
184
|
+
重开终端让全局 npm PATH 生效。Windows 请安装 Git for Windows,并确认 `bash --version` 不低于 3.2。
|
|
424
185
|
|
|
425
|
-
|
|
186
|
+
### 重跑 `init` 会覆盖项目文件吗?
|
|
426
187
|
|
|
427
|
-
|
|
428
|
-
- [项目 README](../README.md):项目概览和快速安装。
|
|
429
|
-
- 初始化后生成的 `docs/knowledge-base/README.md`:PRD 溯源和命名规则。
|
|
188
|
+
不会。`init` 只补充缺少的 skills、`openspec/` 和 `docs/` 文件,已有内容会保留。
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# comate init 目录说明
|
|
2
|
+
|
|
3
|
+
`comate init` 会同时准备两类内容:用户级 skills,以及当前项目的 OpenSpec 和知识库目录。
|
|
4
|
+
|
|
5
|
+
## 1. 初始化时会发生什么
|
|
6
|
+
|
|
7
|
+
在项目根目录运行:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
comate init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
它会:
|
|
14
|
+
|
|
15
|
+
- 将 comate、OpenSpec 和 Superpowers 相关 skills 安装到用户级 skills 目录。
|
|
16
|
+
- 在当前项目创建 `openspec/`。
|
|
17
|
+
- 在当前项目补齐 `docs/` 知识库骨架。
|
|
18
|
+
- 保留已经存在的文件,不覆盖项目已有内容。
|
|
19
|
+
|
|
20
|
+
项目目录通常会得到以下结构:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
your-project/
|
|
24
|
+
├── openspec/
|
|
25
|
+
│ ├── config.yaml
|
|
26
|
+
│ ├── changes/
|
|
27
|
+
│ │ └── archive/
|
|
28
|
+
│ └── specs/
|
|
29
|
+
└── docs/
|
|
30
|
+
├── README.md
|
|
31
|
+
├── architecture/
|
|
32
|
+
│ └── README.md
|
|
33
|
+
└── knowledge-base/
|
|
34
|
+
├── README.md
|
|
35
|
+
├── requirements/
|
|
36
|
+
│ └── _templates/
|
|
37
|
+
│ └── PRD.md
|
|
38
|
+
└── integrations/
|
|
39
|
+
├── README.md
|
|
40
|
+
└── _TEMPLATE/
|
|
41
|
+
├── README.md
|
|
42
|
+
└── api-snapshot.md
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 2. 用户级 skills
|
|
46
|
+
|
|
47
|
+
默认目录:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
macOS/Linux: ~/.agents/skills/
|
|
51
|
+
Windows: %USERPROFILE%\.agents\skills\
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
这里保存可被多个项目共用的 skills。常见目录包括:
|
|
55
|
+
|
|
56
|
+
| 目录 | 用途 |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `comate/` | comate 总入口、状态脚本和共享规则 |
|
|
59
|
+
| `comate-open/` | 创建 change,生成初始产物 |
|
|
60
|
+
| `comate-design/` | 技术设计和方案确认 |
|
|
61
|
+
| `comate-build/` | 计划和代码实现 |
|
|
62
|
+
| `comate-verify/` | 测试、验证和分支收尾 |
|
|
63
|
+
| `comate-archive/` | 归档 change 并同步规格 |
|
|
64
|
+
| `comate-fix/` | Bug 或小调整的快速流程 |
|
|
65
|
+
| `comate-gate/` | PRD 门禁和拆分 |
|
|
66
|
+
| `comate-grill/` | 澄清关键决策 |
|
|
67
|
+
| `comate-kb-integration/` | 生成第三方对接档案 |
|
|
68
|
+
| `openspec-*/` | OpenSpec 提供的配套 skills |
|
|
69
|
+
|
|
70
|
+
通常不需要手动修改这些目录。升级 comate 后,在项目根目录重新运行 `comate init`,即可同步新版 comate skills。
|
|
71
|
+
|
|
72
|
+
## 3. 项目级 `openspec/`
|
|
73
|
+
|
|
74
|
+
`openspec/` 保存当前项目的变更规格,是项目自己的文件,应纳入版本控制。
|
|
75
|
+
|
|
76
|
+
### `openspec/config.yaml`
|
|
77
|
+
|
|
78
|
+
OpenSpec 的项目配置文件,记录当前项目使用的规格 schema。通常由初始化命令创建;已有配置会保留,不要随意删除。
|
|
79
|
+
|
|
80
|
+
### `openspec/changes/`
|
|
81
|
+
|
|
82
|
+
每个进行中的需求或修复对应一个 change:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
openspec/changes/<change-name>/
|
|
86
|
+
├── .comate.yaml
|
|
87
|
+
├── proposal.md
|
|
88
|
+
├── design.md
|
|
89
|
+
├── tasks.md
|
|
90
|
+
├── specs/<capability>/spec.md
|
|
91
|
+
└── .comate/
|
|
92
|
+
├── handoff/
|
|
93
|
+
└── sources.manifest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
这些文件由 `/comate`、`/comate-fix` 及各阶段 skill 逐步生成:
|
|
97
|
+
|
|
98
|
+
- `.comate.yaml`:comate 当前阶段和验证状态的事实来源。
|
|
99
|
+
- `proposal.md`:为什么做、做什么、范围和非目标。
|
|
100
|
+
- `design.md`:高层设计、方案选择和数据流。
|
|
101
|
+
- `tasks.md`:实施任务清单,完成一项勾选一项。
|
|
102
|
+
- `specs/<capability>/spec.md`:本次变更的增量能力规格。
|
|
103
|
+
- `.comate/handoff/`:阶段之间自动生成的交接上下文。
|
|
104
|
+
- `.comate/sources.manifest`:需求源文件的追踪清单,仅在需求来自文件时出现。
|
|
105
|
+
|
|
106
|
+
`openspec/changes/archive/` 在初始化时就会创建。`<change-name>` 完成并归档后,会移动到:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
openspec/changes/archive/YYYY-MM-DD-<change-name>/
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `openspec/specs/`
|
|
113
|
+
|
|
114
|
+
这里保存已经沉淀的长期能力规格:
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
openspec/specs/<capability>/spec.md
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
归档 change 时,增量规格会合并到对应的主规格中。
|
|
121
|
+
|
|
122
|
+
## 4. 项目级 `docs/`
|
|
123
|
+
|
|
124
|
+
`docs/` 保存跨多个 change 长期有效的项目文档。初始化只创建骨架,具体内容需要项目团队补充。
|
|
125
|
+
|
|
126
|
+
### `docs/README.md`
|
|
127
|
+
|
|
128
|
+
说明 `docs/` 的整体边界,以及架构文档、知识库和 `openspec/` 的分工。一般不需要频繁修改。
|
|
129
|
+
|
|
130
|
+
### `docs/architecture/`
|
|
131
|
+
|
|
132
|
+
保存项目架构约束,例如:
|
|
133
|
+
|
|
134
|
+
- 前后端分层和模块依赖;
|
|
135
|
+
- API、DTO、数据库和错误码约定;
|
|
136
|
+
- 命名、日志、测试和部署规范。
|
|
137
|
+
|
|
138
|
+
初始化时只有占位说明。项目应根据实际技术栈补充正式规则。涉及架构约束的 PRD,门禁会以这里的内容作为对齐依据。
|
|
139
|
+
|
|
140
|
+
### `docs/knowledge-base/`
|
|
141
|
+
|
|
142
|
+
保存需求和第三方服务资料。
|
|
143
|
+
|
|
144
|
+
#### `requirements/`
|
|
145
|
+
|
|
146
|
+
存放原始 PRD。推荐按功能和日期组织:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
docs/knowledge-base/requirements/<YYYYMMDD-功能名>/
|
|
150
|
+
├── PRD-001-功能名.md
|
|
151
|
+
├── PRD-001.gate.yaml
|
|
152
|
+
└── PRD-001-REVIEW.md
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`_templates/PRD.md` 是新建 PRD 的模板。`.gate.yaml` 和 `-REVIEW.md` 由 `/comate-gate` 生成,不需要手动创建。
|
|
156
|
+
|
|
157
|
+
#### `integrations/`
|
|
158
|
+
|
|
159
|
+
保存第三方服务的长期对接档案:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
docs/knowledge-base/integrations/<service-id>/
|
|
163
|
+
├── README.md
|
|
164
|
+
└── api-snapshot.md
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`_TEMPLATE/` 只存模板,不代表真实服务。接入新服务时,复制模板或使用 `/comate-kb-integration` 生成档案,并维护 `integrations/README.md` 中的服务索引。
|
|
168
|
+
|
|
169
|
+
## 5. 后续流程会新增的目录
|
|
170
|
+
|
|
171
|
+
以下目录不是 `comate init` 必然立即创建的,而是在对应阶段产生:
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
docs/superpowers/
|
|
175
|
+
├── specs/ # design 阶段的设计文档
|
|
176
|
+
├── plans/ # build 阶段的实施计划
|
|
177
|
+
└── reports/ # verify 阶段的验证报告
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
如果项目暂时没有进行中的 change,这些目录不存在是正常的。
|
|
181
|
+
|
|
182
|
+
## 6. 哪些内容应提交到 Git
|
|
183
|
+
|
|
184
|
+
建议提交项目级内容:
|
|
185
|
+
|
|
186
|
+
- `openspec/`
|
|
187
|
+
- `docs/architecture/`
|
|
188
|
+
- `docs/knowledge-base/`
|
|
189
|
+
- 已生成的 `docs/superpowers/`
|
|
190
|
+
|
|
191
|
+
用户级 `~/.agents/skills/` 或 `%USERPROFILE%\.agents\skills\` 属于本机环境,不放进项目仓库。
|
|
192
|
+
|
|
193
|
+
## 7. 重跑 init
|
|
194
|
+
|
|
195
|
+
`comate init` 是幂等的:
|
|
196
|
+
|
|
197
|
+
- 已安装的依赖和 skills 会跳过或复用;
|
|
198
|
+
- 已存在的 `openspec/` 和 `docs/` 文件会保留;
|
|
199
|
+
- 缺少的目录或模板文件会补齐。
|
|
200
|
+
|
|
201
|
+
因此升级 comate 后,可以安全地在每个项目根目录重新运行一次 `comate init`。
|
package/docs/COMATE_MANUAL.md
CHANGED
|
@@ -595,7 +595,8 @@ bash "$COMATE" doctor
|
|
|
595
595
|
| `gate <prd> review` | 建/刷新 gate.yaml,钉当前 PRD sha256,回显评审报告路径。PRD 变了则状态重置为 `reviewing` |
|
|
596
596
|
| `gate <prd> set <field> <value>` | 写字段:`score` / `blocking_open` / `gate_status` 等 |
|
|
597
597
|
| `gate <prd> pass [--tbd]` | 校验评分、拆分映射和报告。普通 pass 要求 `blocking_open=0`;`--tbd` 要求至少一个经用户接受的未解决阻断项 |
|
|
598
|
-
| `gate <prd> spawned <change>` |
|
|
598
|
+
| `gate <prd> spawned <change>` | 校验 change 属于确认映射、gate 未 stale、open 骨架与 PRD 源追踪完整后,记录据此 PRD 扇出的 change(逗号拼接,幂等) |
|
|
599
|
+
| `gate <prd> fanout-status [--json]` | 对比 `changes_map` 与 `spawned_changes`,输出 expected/spawned/complete/missing/invalid,用于多 change 中断恢复与完整性校验 |
|
|
599
600
|
| `gate <prd> status [--json]` | 查状态;哈希漂移时自动判 `stale`(不改文件) |
|
|
600
601
|
| `gate <prd> check` | comate-open 建 change 前的前置守卫。PASS 要求 gate 存在、状态 ∈ {passed, passed-with-tbd}、哈希未漂移;否则退出码 1 阻塞 |
|
|
601
602
|
|
package/package.json
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
# gate <prd> set <field> <value> — write score | blocking_open | gate_status | …
|
|
22
22
|
# gate <prd> pass [--tbd] — pass clean gate or explicitly accepted TBD blockers
|
|
23
23
|
# gate <prd> spawned <change> — record a change fanned out from this PRD
|
|
24
|
+
# gate <prd> fanout-status [--json] — report mapped/spawned fanout progress
|
|
24
25
|
# gate <prd> status [--json] — print gate status (computes stale on hash drift)
|
|
25
26
|
# gate <prd> check — precondition guard before spawning a change
|
|
26
27
|
# archive <name> [--dry-run] — sync delta specs + annotate + move + mark archived
|
|
@@ -62,6 +63,7 @@ Usage: comate.sh <command> ...
|
|
|
62
63
|
gate <prd> set <field> <value> score | blocking_open | gate_status | …
|
|
63
64
|
gate <prd> pass [--tbd] pass: zero blockers; --tbd: accepted blockers
|
|
64
65
|
gate <prd> spawned <change> record a change fanned out from this PRD
|
|
66
|
+
gate <prd> fanout-status [--json] report mapped/spawned fanout progress
|
|
65
67
|
gate <prd> status [--json]
|
|
66
68
|
gate <prd> check precondition guard before spawning a change
|
|
67
69
|
archive <name> [--dry-run]
|
|
@@ -1471,10 +1473,52 @@ gate_set() {
|
|
|
1471
1473
|
[ -n "$field" ] || die "$COMATE_USAGE" "Usage: comate.sh gate <prd> set <field> <value>"
|
|
1472
1474
|
local gate; gate=$(gate_path_for "$prd")
|
|
1473
1475
|
[ -f "$gate" ] || die "$COMATE_CORRUPT" "ERROR: gate not found (run 'gate review' first): $gate"
|
|
1476
|
+
if [ "$field" = "changes_map" ]; then
|
|
1477
|
+
gate_validate_changes_map "$value"
|
|
1478
|
+
local current_map current_status
|
|
1479
|
+
current_map=$(yaml_get "$gate" changes_map)
|
|
1480
|
+
current_status=$(yaml_get "$gate" gate_status)
|
|
1481
|
+
case "$current_status" in
|
|
1482
|
+
passed|passed-with-tbd)
|
|
1483
|
+
[ "$value" = "$current_map" ] || \
|
|
1484
|
+
die "$COMATE_BLOCK" "BLOCK: changes_map is immutable after gate pass; re-run gate review before remapping"
|
|
1485
|
+
;;
|
|
1486
|
+
esac
|
|
1487
|
+
fi
|
|
1474
1488
|
gate_yaml_set "$gate" "$field" "$value"
|
|
1475
1489
|
log_pass "[GATE] set $field=$value"
|
|
1476
1490
|
}
|
|
1477
1491
|
|
|
1492
|
+
# gate_validate_changes_map <changes_map> — reject malformed entries and
|
|
1493
|
+
# duplicate names when the user-confirmed mapping is first persisted.
|
|
1494
|
+
gate_validate_changes_map() {
|
|
1495
|
+
local map="$1" rest entry name items seen=""
|
|
1496
|
+
[ -n "$map" ] && [ "$map" != "null" ] || \
|
|
1497
|
+
die "$COMATE_USAGE" "ERROR: changes_map cannot be empty"
|
|
1498
|
+
case "$map" in
|
|
1499
|
+
\;*|*\;\;*|*\;) die "$COMATE_USAGE" "ERROR: changes_map contains an empty entry" ;;
|
|
1500
|
+
esac
|
|
1501
|
+
rest="$map"
|
|
1502
|
+
while [ -n "$rest" ]; do
|
|
1503
|
+
case "$rest" in
|
|
1504
|
+
*\;*) entry="${rest%%;*}"; rest="${rest#*;}" ;;
|
|
1505
|
+
*) entry="$rest"; rest="" ;;
|
|
1506
|
+
esac
|
|
1507
|
+
name="${entry%%:*}"
|
|
1508
|
+
items="${entry#*:}"
|
|
1509
|
+
[ -n "$name" ] && [ "$name" != "$entry" ] || \
|
|
1510
|
+
die "$COMATE_USAGE" "ERROR: malformed changes_map entry '$entry'"
|
|
1511
|
+
[ -n "$(printf '%s' "$items" | tr -d '[:space:]')" ] || \
|
|
1512
|
+
die "$COMATE_USAGE" "ERROR: empty function mapping for change '$name'"
|
|
1513
|
+
validate_change_name "$name"
|
|
1514
|
+
case ",$seen," in
|
|
1515
|
+
*",$name,"*) die "$COMATE_USAGE" "ERROR: duplicate change '$name' in changes_map" ;;
|
|
1516
|
+
esac
|
|
1517
|
+
[ -n "$seen" ] && seen="$seen,"
|
|
1518
|
+
seen="$seen$name"
|
|
1519
|
+
done
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1478
1522
|
# gate <prd> pass [--tbd] — flip to passed/passed-with-tbd. Normal pass requires
|
|
1479
1523
|
# zero blockers; --tbd requires at least one explicitly accepted blocker.
|
|
1480
1524
|
gate_pass() {
|
|
@@ -1501,6 +1545,7 @@ gate_pass() {
|
|
|
1501
1545
|
if [ -z "$changes_map" ] || [ "$changes_map" = "null" ]; then
|
|
1502
1546
|
die "$COMATE_BLOCK" "BLOCK: changes_map is required before passing the gate"
|
|
1503
1547
|
fi
|
|
1548
|
+
gate_validate_changes_map "$changes_map"
|
|
1504
1549
|
local report; report=$(yaml_get "$gate" review_report)
|
|
1505
1550
|
if [ -z "$report" ] || [ "$report" = "null" ] || ! file_nonempty "$report"; then
|
|
1506
1551
|
die "$COMATE_BLOCK" "BLOCK: review report missing or empty ($report) — the gate report is mandatory"
|
|
@@ -1524,10 +1569,15 @@ gate_spawned() {
|
|
|
1524
1569
|
validate_change_name "$change"
|
|
1525
1570
|
local gate; gate=$(gate_path_for "$prd")
|
|
1526
1571
|
[ -f "$gate" ] || die "$COMATE_CORRUPT" "ERROR: gate not found: $gate"
|
|
1572
|
+
local changes_map; changes_map=$(yaml_get "$gate" changes_map)
|
|
1573
|
+
gate_change_is_mapped "$changes_map" "$change" || \
|
|
1574
|
+
die "$COMATE_BLOCK" "BLOCK: change '$change' is not present in changes_map"
|
|
1527
1575
|
local cur; cur=$(yaml_get "$gate" spawned_changes)
|
|
1528
1576
|
case ",$cur," in
|
|
1529
1577
|
*",$change,"*) log_info "[GATE] $change already recorded" ; return 0 ;;
|
|
1530
1578
|
esac
|
|
1579
|
+
gate_check "$prd" >/dev/null
|
|
1580
|
+
gate_require_skeleton "$prd" "$change"
|
|
1531
1581
|
if [ -z "$cur" ] || [ "$cur" = "null" ]; then
|
|
1532
1582
|
gate_yaml_set "$gate" spawned_changes "$change"
|
|
1533
1583
|
else
|
|
@@ -1536,6 +1586,118 @@ gate_spawned() {
|
|
|
1536
1586
|
log_pass "[GATE] recorded spawned change: $change"
|
|
1537
1587
|
}
|
|
1538
1588
|
|
|
1589
|
+
# gate_require_skeleton <prd> <change> — prove that comate-open finished the
|
|
1590
|
+
# gate skeleton before it is counted as spawned.
|
|
1591
|
+
gate_require_skeleton() {
|
|
1592
|
+
local prd="$1" change="$2" dir yaml manifest tracked_prd f
|
|
1593
|
+
dir=$(comate_change_dir "$change")
|
|
1594
|
+
for f in .openspec.yaml proposal.md design.md tasks.md .comate.yaml; do
|
|
1595
|
+
file_nonempty "$dir/$f" || \
|
|
1596
|
+
die "$COMATE_BLOCK" "BLOCK: change '$change' skeleton incomplete: missing or empty $dir/$f"
|
|
1597
|
+
done
|
|
1598
|
+
yaml="$dir/.comate.yaml"
|
|
1599
|
+
validate_flat_yaml "$yaml"
|
|
1600
|
+
[ "$(yaml_get "$yaml" phase)" = "open" ] || \
|
|
1601
|
+
die "$COMATE_BLOCK" "BLOCK: change '$change' skeleton must remain in phase=open"
|
|
1602
|
+
[ "$(yaml_get "$yaml" source_kind)" = "file" ] || \
|
|
1603
|
+
die "$COMATE_BLOCK" "BLOCK: change '$change' skeleton has not tracked its PRD source"
|
|
1604
|
+
manifest=$(yaml_get "$yaml" source_manifest)
|
|
1605
|
+
[ -n "$manifest" ] && [ "$manifest" != "null" ] && [ -f "$manifest" ] || \
|
|
1606
|
+
die "$COMATE_BLOCK" "BLOCK: change '$change' source manifest is missing"
|
|
1607
|
+
tracked_prd=$(validate_kb_path "$prd") || \
|
|
1608
|
+
die "$COMATE_BLOCK" "BLOCK: PRD source path is outside the knowledge base: $prd"
|
|
1609
|
+
local record_ok=0 line record_path="" record_hash=""
|
|
1610
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
1611
|
+
case "$line" in
|
|
1612
|
+
path:*) record_path="${line#path:}"; record_hash="" ;;
|
|
1613
|
+
sha256:*)
|
|
1614
|
+
record_hash="${line#sha256:}"
|
|
1615
|
+
if [ "$record_path" = "$tracked_prd" ] && [ -n "$record_hash" ]; then record_ok=1; fi
|
|
1616
|
+
;;
|
|
1617
|
+
---|"") record_path=""; record_hash="" ;;
|
|
1618
|
+
esac
|
|
1619
|
+
done < "$manifest"
|
|
1620
|
+
[ "$record_ok" -eq 1 ] || \
|
|
1621
|
+
die "$COMATE_BLOCK" "BLOCK: change '$change' source manifest lacks a complete path/sha256 record for $tracked_prd"
|
|
1622
|
+
local drift rc=0
|
|
1623
|
+
drift=$(sources_drift "$manifest") || rc=$?
|
|
1624
|
+
[ "$rc" -eq 0 ] || die "$COMATE_BLOCK" "BLOCK: change '$change' source evidence is stale: $drift"
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
# gate_change_names <changes_map> — emit mapped change names, one per line.
|
|
1628
|
+
# The persisted format is `name:function item,...;name:function item,...`.
|
|
1629
|
+
gate_change_names() {
|
|
1630
|
+
local map="$1" entry name
|
|
1631
|
+
[ -n "$map" ] && [ "$map" != "null" ] || return 0
|
|
1632
|
+
gate_validate_changes_map "$map"
|
|
1633
|
+
while [ -n "$map" ]; do
|
|
1634
|
+
case "$map" in
|
|
1635
|
+
*\;*) entry="${map%%;*}"; map="${map#*;}" ;;
|
|
1636
|
+
*) entry="$map"; map="" ;;
|
|
1637
|
+
esac
|
|
1638
|
+
name="${entry%%:*}"
|
|
1639
|
+
[ -n "$name" ] || die "$COMATE_USAGE" "ERROR: malformed changes_map entry '$entry'"
|
|
1640
|
+
[ "$name" != "$entry" ] || die "$COMATE_USAGE" "ERROR: malformed changes_map entry '$entry'"
|
|
1641
|
+
validate_change_name "$name"
|
|
1642
|
+
printf '%s\n' "$name"
|
|
1643
|
+
done
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
# gate_change_is_mapped <changes_map> <change> — membership test for a mapped name.
|
|
1647
|
+
gate_change_is_mapped() {
|
|
1648
|
+
local map="$1" wanted="$2" name
|
|
1649
|
+
gate_validate_changes_map "$map"
|
|
1650
|
+
while IFS= read -r name; do
|
|
1651
|
+
[ "$name" = "$wanted" ] && return 0
|
|
1652
|
+
done <<EOF
|
|
1653
|
+
$(gate_change_names "$map")
|
|
1654
|
+
EOF
|
|
1655
|
+
return 1
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
# gate_fanout_status <prd> [--json] — calculate persisted fanout progress.
|
|
1659
|
+
# This is deliberately derived from changes_map and spawned_changes so an
|
|
1660
|
+
# interrupted run can resume from the missing names instead of relying on
|
|
1661
|
+
# conversation/task state.
|
|
1662
|
+
gate_fanout_status() {
|
|
1663
|
+
local prd; prd=$(require_prd "$1")
|
|
1664
|
+
local json="${2:-}" gate; gate=$(gate_path_for "$prd")
|
|
1665
|
+
[ -f "$gate" ] || die "$COMATE_CORRUPT" "ERROR: gate not found: $gate"
|
|
1666
|
+
local map spawned name expected=0 count=0 missing="" invalid=""
|
|
1667
|
+
map=$(yaml_get "$gate" changes_map); spawned=$(yaml_get "$gate" spawned_changes)
|
|
1668
|
+
gate_validate_changes_map "$map"
|
|
1669
|
+
while IFS= read -r name; do
|
|
1670
|
+
[ -n "$name" ] || continue
|
|
1671
|
+
expected=$((expected + 1))
|
|
1672
|
+
case ",$spawned," in
|
|
1673
|
+
*",$name,"*) count=$((count + 1)) ;;
|
|
1674
|
+
*) [ -n "$missing" ] && missing="$missing,"; missing="$missing$name" ;;
|
|
1675
|
+
esac
|
|
1676
|
+
done <<EOF
|
|
1677
|
+
$(gate_change_names "$map")
|
|
1678
|
+
EOF
|
|
1679
|
+
if [ -n "$spawned" ] && [ "$spawned" != "null" ]; then
|
|
1680
|
+
local rest="$spawned" item
|
|
1681
|
+
while [ -n "$rest" ]; do
|
|
1682
|
+
case "$rest" in
|
|
1683
|
+
*,*) item="${rest%%,*}"; rest="${rest#*,}" ;;
|
|
1684
|
+
*) item="$rest"; rest="" ;;
|
|
1685
|
+
esac
|
|
1686
|
+
gate_change_is_mapped "$map" "$item" || { [ -n "$invalid" ] && invalid="$invalid,"; invalid="$invalid$item"; }
|
|
1687
|
+
done
|
|
1688
|
+
fi
|
|
1689
|
+
local complete=false
|
|
1690
|
+
[ "$expected" -gt 0 ] && [ "$count" -eq "$expected" ] && [ -z "$invalid" ] && complete=true
|
|
1691
|
+
if [ "$json" = "--json" ]; then
|
|
1692
|
+
printf '{"expected":%s,"spawned":%s,"complete":%s,"missing":"%s","invalid":"%s"}\n' \
|
|
1693
|
+
"$expected" "$count" "$complete" "$(json_escape "$missing")" "$(json_escape "$invalid")"
|
|
1694
|
+
else
|
|
1695
|
+
[ -z "$missing" ] && missing=none
|
|
1696
|
+
[ -z "$invalid" ] && invalid=none
|
|
1697
|
+
echo "fanout: expected=$expected spawned=$count complete=$complete missing=$missing invalid=$invalid"
|
|
1698
|
+
fi
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1539
1701
|
# _gate_is_stale <gate> <prd> — true (0) if the recorded hash no longer matches
|
|
1540
1702
|
# the PRD on disk.
|
|
1541
1703
|
_gate_is_stale() {
|
|
@@ -1956,6 +2118,7 @@ case "$CMD" in
|
|
|
1956
2118
|
set) gate_set "$PRD" "${1:-}" "${2:-}" ;;
|
|
1957
2119
|
pass) gate_pass "$PRD" "${1:-}" ;;
|
|
1958
2120
|
spawned) gate_spawned "$PRD" "${1:-}" ;;
|
|
2121
|
+
fanout-status) gate_fanout_status "$PRD" "${1:-}" ;;
|
|
1959
2122
|
status) gate_status "$PRD" "${1:-}" ;;
|
|
1960
2123
|
check) gate_check "$PRD" ;;
|
|
1961
2124
|
*) die "$COMATE_USAGE" "Unknown gate subcommand: $SUB" ;;
|
|
@@ -565,6 +565,17 @@ seed_prd() {
|
|
|
565
565
|
echo "docs/knowledge-base/requirements/test/PRD-001-demo.md"
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
+
seed_gate_skeleton() {
|
|
569
|
+
local prd="$1" change="$2" d="openspec/changes/$2"
|
|
570
|
+
mkdir -p "$d"
|
|
571
|
+
printf 'schema: specs-driven\n' > "$d/.openspec.yaml"
|
|
572
|
+
printf '# proposal\n' > "$d/proposal.md"
|
|
573
|
+
printf '# design\n' > "$d/design.md"
|
|
574
|
+
printf -- '- [ ] task\n' > "$d/tasks.md"
|
|
575
|
+
comate state "$change" init full >/dev/null
|
|
576
|
+
comate sources "$change" track "$prd" >/dev/null
|
|
577
|
+
}
|
|
578
|
+
|
|
568
579
|
@test "gate review creates gate.yaml next to PRD" {
|
|
569
580
|
local prd; prd=$(seed_prd)
|
|
570
581
|
run comate gate "$prd" review
|
|
@@ -643,6 +654,71 @@ seed_prd() {
|
|
|
643
654
|
[[ "$output" == *"changes_map"* ]]
|
|
644
655
|
}
|
|
645
656
|
|
|
657
|
+
@test "gate rejects changes_map entries without a function mapping" {
|
|
658
|
+
local prd; prd=$(seed_prd)
|
|
659
|
+
comate gate "$prd" review >/dev/null
|
|
660
|
+
run comate gate "$prd" set changes_map "login-flow"
|
|
661
|
+
[ "$status" -eq 2 ]
|
|
662
|
+
[[ "$output" == *"malformed changes_map"* ]]
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
@test "gate rejects duplicate change names in changes_map" {
|
|
666
|
+
local prd; prd=$(seed_prd)
|
|
667
|
+
comate gate "$prd" review >/dev/null
|
|
668
|
+
run comate gate "$prd" set changes_map "login-flow:功能项1;login-flow:功能项2"
|
|
669
|
+
[ "$status" -eq 2 ]
|
|
670
|
+
[[ "$output" == *"duplicate change"* ]]
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
@test "gate rejects an empty middle entry in changes_map" {
|
|
674
|
+
local prd; prd=$(seed_prd)
|
|
675
|
+
comate gate "$prd" review >/dev/null
|
|
676
|
+
run comate gate "$prd" set changes_map "login-flow:功能项1;;logout-flow:功能项2"
|
|
677
|
+
[ "$status" -eq 2 ]
|
|
678
|
+
[[ "$output" == *"empty entry"* ]]
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
@test "gate rejects a trailing empty entry in changes_map" {
|
|
682
|
+
local prd; prd=$(seed_prd)
|
|
683
|
+
comate gate "$prd" review >/dev/null
|
|
684
|
+
run comate gate "$prd" set changes_map "login-flow:功能项1;"
|
|
685
|
+
[ "$status" -eq 2 ]
|
|
686
|
+
[[ "$output" == *"empty entry"* ]]
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
@test "gate rejects an empty function mapping" {
|
|
690
|
+
local prd; prd=$(seed_prd)
|
|
691
|
+
comate gate "$prd" review >/dev/null
|
|
692
|
+
run comate gate "$prd" set changes_map "login-flow:"
|
|
693
|
+
[ "$status" -eq 2 ]
|
|
694
|
+
[[ "$output" == *"empty function mapping"* ]]
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
@test "gate rejects an all-whitespace function mapping" {
|
|
698
|
+
local prd; prd=$(seed_prd)
|
|
699
|
+
comate gate "$prd" review >/dev/null
|
|
700
|
+
run comate gate "$prd" set changes_map "login-flow: "
|
|
701
|
+
[ "$status" -eq 2 ]
|
|
702
|
+
[[ "$output" == *"empty function mapping"* ]]
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
@test "gate keeps a passed changes_map immutable except for idempotent writes" {
|
|
706
|
+
local prd; prd=$(seed_prd)
|
|
707
|
+
comate gate "$prd" review >/dev/null
|
|
708
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
709
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
710
|
+
comate gate "$prd" set changes_map "a:功能项1;b:功能项2;c:功能项3;d:功能项4" >/dev/null
|
|
711
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
712
|
+
comate gate "$prd" pass >/dev/null
|
|
713
|
+
run comate gate "$prd" set changes_map "a:功能项1;b:功能项2;c:功能项3;d:功能项4"
|
|
714
|
+
[ "$status" -eq 0 ]
|
|
715
|
+
run comate gate "$prd" set changes_map "a:功能项1"
|
|
716
|
+
[ "$status" -eq 1 ]
|
|
717
|
+
[[ "$output" == *"immutable after gate pass"* ]]
|
|
718
|
+
run comate gate "$prd" fanout-status --json
|
|
719
|
+
[[ "$output" == *'"expected":4'* ]]
|
|
720
|
+
}
|
|
721
|
+
|
|
646
722
|
@test "gate pass --tbd requires unresolved blockers" {
|
|
647
723
|
local prd; prd=$(seed_prd)
|
|
648
724
|
comate gate "$prd" review >/dev/null
|
|
@@ -727,6 +803,7 @@ seed_prd() {
|
|
|
727
803
|
printf '# 报告\n' > \
|
|
728
804
|
"docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
729
805
|
comate gate "$prd" pass >/dev/null
|
|
806
|
+
seed_gate_skeleton "$prd" login-flow
|
|
730
807
|
run comate gate "$prd" spawned login-flow
|
|
731
808
|
[ "$status" -eq 0 ]
|
|
732
809
|
local gate="docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
|
|
@@ -743,6 +820,8 @@ seed_prd() {
|
|
|
743
820
|
printf '# 报告\n' > \
|
|
744
821
|
"docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
745
822
|
comate gate "$prd" pass >/dev/null
|
|
823
|
+
seed_gate_skeleton "$prd" login-flow
|
|
824
|
+
seed_gate_skeleton "$prd" logout-flow
|
|
746
825
|
comate gate "$prd" spawned login-flow >/dev/null
|
|
747
826
|
comate gate "$prd" spawned logout-flow >/dev/null
|
|
748
827
|
local gate="docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
|
|
@@ -751,6 +830,131 @@ seed_prd() {
|
|
|
751
830
|
[[ "$sc" == *"logout-flow"* ]]
|
|
752
831
|
}
|
|
753
832
|
|
|
833
|
+
@test "gate spawned remains idempotent after an already recorded change leaves the active directory" {
|
|
834
|
+
local prd; prd=$(seed_prd)
|
|
835
|
+
comate gate "$prd" review >/dev/null
|
|
836
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
837
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
838
|
+
comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
|
|
839
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
840
|
+
comate gate "$prd" pass >/dev/null
|
|
841
|
+
seed_gate_skeleton "$prd" login-flow
|
|
842
|
+
comate gate "$prd" spawned login-flow >/dev/null
|
|
843
|
+
mkdir -p openspec/changes/archive
|
|
844
|
+
mv openspec/changes/login-flow openspec/changes/archive/2026-08-28-login-flow
|
|
845
|
+
run comate gate "$prd" spawned login-flow
|
|
846
|
+
[ "$status" -eq 0 ]
|
|
847
|
+
[[ "$output" == *"already recorded"* ]]
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
@test "gate fanout status reports missing changes instead of treating one of four as complete" {
|
|
851
|
+
local prd; prd=$(seed_prd)
|
|
852
|
+
comate gate "$prd" review >/dev/null
|
|
853
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
854
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
855
|
+
comate gate "$prd" set changes_map "login-flow:功能项1;logout-flow:功能项2;ops:功能项3;report:功能项4" >/dev/null
|
|
856
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
857
|
+
comate gate "$prd" pass >/dev/null
|
|
858
|
+
seed_gate_skeleton "$prd" login-flow
|
|
859
|
+
comate gate "$prd" spawned login-flow >/dev/null
|
|
860
|
+
run comate gate "$prd" fanout-status --json
|
|
861
|
+
[ "$status" -eq 0 ]
|
|
862
|
+
[[ "$output" == *'"expected":4'* ]]
|
|
863
|
+
[[ "$output" == *'"spawned":1'* ]]
|
|
864
|
+
[[ "$output" == *'"complete":false'* ]]
|
|
865
|
+
[[ "$output" == *'logout-flow'* ]]
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
@test "gate spawned rejects a change outside the confirmed map" {
|
|
869
|
+
local prd; prd=$(seed_prd)
|
|
870
|
+
comate gate "$prd" review >/dev/null
|
|
871
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
872
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
873
|
+
comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
|
|
874
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
875
|
+
comate gate "$prd" pass >/dev/null
|
|
876
|
+
seed_gate_skeleton "$prd" other-flow
|
|
877
|
+
run comate gate "$prd" spawned other-flow
|
|
878
|
+
[ "$status" -eq 1 ]
|
|
879
|
+
[[ "$output" == *"not present in changes_map"* ]]
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
@test "gate fanout status becomes complete only after every mapped change is spawned" {
|
|
883
|
+
local prd; prd=$(seed_prd)
|
|
884
|
+
comate gate "$prd" review >/dev/null
|
|
885
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
886
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
887
|
+
comate gate "$prd" set changes_map "login-flow:功能项1;logout-flow:功能项2" >/dev/null
|
|
888
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
889
|
+
comate gate "$prd" pass >/dev/null
|
|
890
|
+
seed_gate_skeleton "$prd" login-flow
|
|
891
|
+
seed_gate_skeleton "$prd" logout-flow
|
|
892
|
+
comate gate "$prd" spawned login-flow >/dev/null
|
|
893
|
+
comate gate "$prd" spawned logout-flow >/dev/null
|
|
894
|
+
run comate gate "$prd" fanout-status
|
|
895
|
+
[ "$status" -eq 0 ]
|
|
896
|
+
[[ "$output" == *"complete=true"* ]]
|
|
897
|
+
[[ "$output" == *"missing=none"* ]]
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
@test "gate spawned rejects an incomplete change skeleton" {
|
|
901
|
+
local prd; prd=$(seed_prd)
|
|
902
|
+
comate gate "$prd" review >/dev/null
|
|
903
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
904
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
905
|
+
comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
|
|
906
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
907
|
+
comate gate "$prd" pass >/dev/null
|
|
908
|
+
mkdir -p openspec/changes/login-flow
|
|
909
|
+
run comate gate "$prd" spawned login-flow
|
|
910
|
+
[ "$status" -eq 1 ]
|
|
911
|
+
[[ "$output" == *"skeleton incomplete"* ]]
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
@test "gate spawned blocks a new registration after the PRD becomes stale" {
|
|
915
|
+
local prd; prd=$(seed_prd)
|
|
916
|
+
comate gate "$prd" review >/dev/null
|
|
917
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
918
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
919
|
+
comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
|
|
920
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
921
|
+
comate gate "$prd" pass >/dev/null
|
|
922
|
+
seed_gate_skeleton "$prd" login-flow
|
|
923
|
+
printf '\nchanged\n' >> "$prd"
|
|
924
|
+
run comate gate "$prd" spawned login-flow
|
|
925
|
+
[ "$status" -eq 1 ]
|
|
926
|
+
[[ "$output" == *"hash mismatch"* ]]
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
@test "gate spawned accepts a normalized PRD path recorded in the source manifest" {
|
|
930
|
+
local prd; prd=$(seed_prd)
|
|
931
|
+
prd="./$prd"
|
|
932
|
+
comate gate "$prd" review >/dev/null
|
|
933
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
934
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
935
|
+
comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
|
|
936
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
937
|
+
comate gate "$prd" pass >/dev/null
|
|
938
|
+
seed_gate_skeleton "$prd" login-flow
|
|
939
|
+
run comate gate "$prd" spawned login-flow
|
|
940
|
+
[ "$status" -eq 0 ]
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
@test "gate spawned rejects a truncated PRD source record without sha256" {
|
|
944
|
+
local prd; prd=$(seed_prd)
|
|
945
|
+
comate gate "$prd" review >/dev/null
|
|
946
|
+
comate gate "$prd" set score 90 >/dev/null
|
|
947
|
+
comate gate "$prd" set blocking_open 0 >/dev/null
|
|
948
|
+
comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
|
|
949
|
+
printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
|
|
950
|
+
comate gate "$prd" pass >/dev/null
|
|
951
|
+
seed_gate_skeleton "$prd" login-flow
|
|
952
|
+
printf 'path:%s\n' "$prd" > openspec/changes/login-flow/.comate/sources.manifest
|
|
953
|
+
run comate gate "$prd" spawned login-flow
|
|
954
|
+
[ "$status" -eq 1 ]
|
|
955
|
+
[[ "$output" == *"complete path/sha256 record"* ]]
|
|
956
|
+
}
|
|
957
|
+
|
|
754
958
|
@test "gate status returns none when gate does not exist" {
|
|
755
959
|
mkdir -p docs/knowledge-base/requirements/test
|
|
756
960
|
run comate gate "docs/knowledge-base/requirements/test/PRD-001-demo.md" status
|
|
@@ -41,8 +41,9 @@ gate 命令族(退出码:`0` 通过 / `1` 阻塞 / `2` 用法 / `3` 环境 /
|
|
|
41
41
|
bash "$COMATE" gate <prd> review # 建/刷新 gate.yaml,钉 PRD 哈希,回报告路径
|
|
42
42
|
bash "$COMATE" gate <prd> set <field> <value> # 写 score | blocking_open | gate_status
|
|
43
43
|
bash "$COMATE" gate <prd> pass [--tbd] # 普通通过要求 0 阻断;--tbd 要求有已授权的未解决阻断
|
|
44
|
-
bash "$COMATE" gate <prd> spawned <change> # 记录据此 PRD 扇出的 change
|
|
45
|
-
bash "$COMATE" gate <prd> status [--json]
|
|
44
|
+
bash "$COMATE" gate <prd> spawned <change> # 记录据此 PRD 扇出的 change
|
|
45
|
+
bash "$COMATE" gate <prd> fanout-status [--json] # 查看映射/已创建/待创建进度
|
|
46
|
+
bash "$COMATE" gate <prd> status [--json] # 查状态(哈希漂移自动判 stale)
|
|
46
47
|
bash "$COMATE" gate <prd> check # comate-open 建 change 前的前置守卫
|
|
47
48
|
```
|
|
48
49
|
|
|
@@ -205,21 +206,33 @@ bash "$COMATE" gate <prd> pass --tbd
|
|
|
205
206
|
|
|
206
207
|
`pass` 会硬校验评分为 0–100、`changes_map` 非空、报告非空;普通 pass 要求 `blocking_open=0`,`--tbd` 则要求存在至少 1 个已记录且经用户明确接受的未解决阻断项。之后 PRD 再被改,哈希漂移会使门禁 `stale`,须重跑 `/comate-gate`。
|
|
207
208
|
|
|
208
|
-
### 6. 逐 Change 初始化(调 comate-open)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
### 6. 逐 Change 初始化(调 comate-open)
|
|
210
|
+
|
|
211
|
+
这是一个可恢复的扇出循环,不是一次性只执行首项的提示。每次进入本步骤先运行:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
bash "$COMATE" gate <prd路径> fanout-status --json
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
只处理返回 JSON 中 `missing` 列出的 change;不要重新处理 `spawned` 中已有的 change。每创建一个 change 后立即运行 `gate spawned`,再重新运行 `fanout-status`,直到 `complete: true`。如果本轮因上下文、用户决策或子 skill 中断,下一轮必须从 `fanout-status` 恢复,不能根据对话记忆猜测剩余项。
|
|
218
|
+
|
|
219
|
+
对 `missing` 里的每个 change,逐个调用 comate-open。**每次都在 change 描述里点明**:本 change 承接 PRD 的哪些功能项、范围边界、以及(多 change 时)相邻职责归哪个 change(In/Out 显式互引,防重复防遗漏)。若同名目录或 `.comate.yaml` 已存在,视为中断恢复:让 comate-open 复用现有状态、补齐缺失产物和源追踪后登记,不得重复执行 `state init`。
|
|
220
|
+
|
|
221
|
+
**立即执行**:用 Skill 工具加载 `comate-open`,传入 change 名与来源 PRD,并明确使用“gate 扇出骨架模式”。该模式创建/补齐并校验当前 change 的 `.openspec.yaml`、open 三件套、`.comate.yaml` 和 PRD 源追踪,最后登记 `gate spawned` 并保持 `phase: open` 返回本 gate 循环;不得在同一次 gate 调用中自动进入该 change 的 design/build。
|
|
222
|
+
|
|
223
|
+
comate-open 内部会:
|
|
213
224
|
- 先跑 `gate check <prd>` 前置守卫(未通过则阻断);
|
|
214
225
|
- 生成 proposal.md(范围/目标/非目标承接该 change 的功能项,不脱离 PRD 另起炉灶);
|
|
215
226
|
- `sources track <prd>` 钉住 PRD 为源(`source_kind=file`);
|
|
216
227
|
- design.md / tasks.md 由后续 comate-design 细化。
|
|
217
228
|
|
|
218
|
-
|
|
229
|
+
每建成一个,记录到门禁(目录已存在后才能登记):
|
|
219
230
|
|
|
220
231
|
```bash
|
|
221
|
-
bash "$COMATE" gate <prd> spawned <change-name>
|
|
222
|
-
```
|
|
232
|
+
bash "$COMATE" gate <prd> spawned <change-name>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
登记后继续处理 `fanout-status` 的 `missing` 列表。只有 `complete: true` 才能进入步骤 7,并且收尾报告中的“已创建 N 个 change”必须等于映射项数量;部分完成必须明确报告剩余项,不能宣称扇出完成。
|
|
223
236
|
|
|
224
237
|
若门禁是 `passed-with-tbd`,在该 change 的 proposal/design 受影响处标 **「⚠ 待确认」** 并引用评审报告。
|
|
225
238
|
|
|
@@ -229,7 +242,8 @@ bash "$COMATE" gate <prd> spawned <change-name>
|
|
|
229
242
|
- PRD 审阅评分 + 评级、阻断问题关闭情况;
|
|
230
243
|
- 评审报告路径、gate.yaml 路径与最终 `gate_status`;
|
|
231
244
|
- 拆分映射(功能项 → N 个 change);
|
|
232
|
-
- 已建的 change 列表(名称 + 承接功能项 + 是否带 ⚠ TBD);
|
|
245
|
+
- 已建的 change 列表(名称 + 承接功能项 + 是否带 ⚠ TBD);
|
|
246
|
+
- 扇出完整性(`fanout-status` 的 expected/spawned/missing/invalid);
|
|
233
247
|
- 待用户补的字段(如技术负责人)。
|
|
234
248
|
|
|
235
249
|
提示:「已过门禁并创建 N 个 change。对每个 change 调用 `/comate` 继续 design→build→verify→archive。」
|
|
@@ -245,7 +259,8 @@ bash "$COMATE" gate <prd> spawned <change-name>
|
|
|
245
259
|
- AskUserQuestion 是真实阻塞点:文字输出/推荐/默认值/历史选择/用户未反对都不能替代明确回答。
|
|
246
260
|
- 拆分粒度(1 PRD → 几个 change)由用户定,AI 只给建议不拍板。
|
|
247
261
|
- 每个 change 承接 PRD 对应功能项,不另起炉灶;范围/依赖/验收与 PRD 对齐。
|
|
248
|
-
- 多 change 时在各 change 的 In/Out Scope 显式互引边界职责归属。
|
|
262
|
+
- 多 change 时在各 change 的 In/Out Scope 显式互引边界职责归属。
|
|
263
|
+
- 扇出必须以 `gate fanout-status` 为持久化进度真相;`TaskCreate` 仅作展示,不得替代 gate.yaml 状态。
|
|
249
264
|
- 门禁通过后 PRD 若被改(哈希漂移),gate 转 stale,禁止再扇出 change,须重跑本 skill。
|
|
250
265
|
- 契约以项目架构文档(如 `docs/architecture/`,若存在)为事实源,change 里点名遵循了哪条,不硬编码具体值、不自造。
|
|
251
266
|
- 不写应用代码——本 skill 止于「过门禁 + 拆分 + 建 change 骨架」,实现由后续 comate 阶段驱动。
|
|
@@ -36,7 +36,23 @@ bash "$COMATE" gate <prd路径> check
|
|
|
36
36
|
|
|
37
37
|
需求**来自对话**(非 PRD)时跳过本步——门禁只约束「PRD → change」这条路径。
|
|
38
38
|
|
|
39
|
-
### 3. 创建 Change 结构 + 初始化状态
|
|
39
|
+
### 3. 创建 Change 结构 + 初始化状态
|
|
40
|
+
|
|
41
|
+
#### Gate 扇出骨架模式
|
|
42
|
+
|
|
43
|
+
当调用方明确说明本次来自 `comate-gate` 的批量扇出时,使用独立的骨架模式:
|
|
44
|
+
|
|
45
|
+
1. 运行 gate check;
|
|
46
|
+
2. 创建或补齐 `.openspec.yaml`、proposal.md、design.md、tasks.md;
|
|
47
|
+
3. `.comate.yaml` 不存在时才执行 `state init full`,已存在时读取并复用,不能重复 init;
|
|
48
|
+
4. 追踪源 PRD;
|
|
49
|
+
5. 运行 `state check open` 并检查所有骨架文件非空、proposal 范围忠实承接映射;
|
|
50
|
+
6. 最后执行 `gate spawned`。脚本会再次校验 gate 未 stale、骨架完整且源 manifest 确实追踪该 PRD;
|
|
51
|
+
7. 登记成功后保持 `phase: open`,立即返回 comate-gate 的扇出循环。
|
|
52
|
+
|
|
53
|
+
骨架模式不执行 Step 4 后的 `phase_step` 记录、Step 5 产物确认、open guard 或本文“自动流转”;**不得**在同一次 gate 调用中加载 `comate-design`。下一项由 gate 根据 `fanout-status` 的 `missing` 列表继续处理。
|
|
54
|
+
|
|
55
|
+
若不是 gate 扇出调用,保持普通 open 流程并按本文退出条件进入 design。
|
|
40
56
|
|
|
41
57
|
**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。若用户意图未明确、需先形成建议,改为加载 `openspec-propose`。禁止跳过。
|
|
42
58
|
|
|
@@ -63,10 +79,12 @@ if [ -z "$COMATE" ]; then
|
|
|
63
79
|
return 1 2>/dev/null || exit 1
|
|
64
80
|
fi
|
|
65
81
|
|
|
66
|
-
bash "$COMATE" state <name> init full
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
这会创建 `openspec/changes/<name>/.comate.yaml`,初始状态:`workflow: full`, `phase: open`, `phase_step: null`。
|
|
82
|
+
bash "$COMATE" state <name> init full
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
这会创建 `openspec/changes/<name>/.comate.yaml`,初始状态:`workflow: full`, `phase: open`, `phase_step: null`。
|
|
86
|
+
|
|
87
|
+
**恢复规则**:若 change 目录或 `.comate.yaml` 已存在,逐项检查并补齐缺失产物;`.comate.yaml` 存在时跳过 `state init`。这覆盖“骨架已创建但尚未 gate spawned”中断点,完成校验后直接补登记。
|
|
70
88
|
|
|
71
89
|
### 3b. 判定需求来源(源文件追踪)
|
|
72
90
|
|
|
@@ -84,13 +102,13 @@ bash "$COMATE" sources <name> track docs/knowledge-base/<path>.md --note "<可
|
|
|
84
102
|
|
|
85
103
|
### 3c. PRD 扇出记录(从 PRD 建 change 时必须登记)
|
|
86
104
|
|
|
87
|
-
若本 change 来自 PRD
|
|
105
|
+
若本 change 来自 PRD,在创建、源追踪和完整性校验全部完成后,把该 change 记回门禁:
|
|
88
106
|
|
|
89
107
|
```bash
|
|
90
108
|
bash "$COMATE" gate <prd路径> spawned <name>
|
|
91
109
|
```
|
|
92
110
|
|
|
93
|
-
此步将 change 名称追加到 `<PRD>.gate.yaml` 的 `spawned_changes` 列表,用于追溯 PRD → change
|
|
111
|
+
此步将 change 名称追加到 `<PRD>.gate.yaml` 的 `spawned_changes` 列表,用于追溯 PRD → change 的完整扇出关系。不得在文件不完整或源追踪前提前登记。
|
|
94
112
|
|
|
95
113
|
需求**来自对话**时跳过本步。
|
|
96
114
|
|
|
@@ -187,7 +205,9 @@ bash "$COMATE" state <name> set phase_step open.review-confirmed
|
|
|
187
205
|
|
|
188
206
|
然后继续执行退出条件(guard)。
|
|
189
207
|
|
|
190
|
-
## 退出条件
|
|
208
|
+
## 退出条件
|
|
209
|
+
|
|
210
|
+
以下退出条件仅适用于普通 open;gate 扇出骨架模式使用 Step 3 的专用返回条件。
|
|
191
211
|
|
|
192
212
|
- 三件套已创建且内容完整
|
|
193
213
|
- **用户已确认**
|
|
@@ -201,9 +221,9 @@ bash "$COMATE" guard <name> open --apply
|
|
|
201
221
|
|
|
202
222
|
full 流程自动更新为 `phase: design`;fix 预设自动更新为 `phase: build`。
|
|
203
223
|
|
|
204
|
-
## 自动流转
|
|
205
|
-
|
|
206
|
-
|
|
224
|
+
## 自动流转
|
|
225
|
+
|
|
226
|
+
仅普通 open 在用户确认后自动流转:
|
|
207
227
|
|
|
208
228
|
> **REQUIRED NEXT SKILL(完整流程):** 调用 `comate-design` skill 进入深度设计。
|
|
209
229
|
>
|