@house365/comate 0.9.10 → 0.9.12

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 CHANGED
@@ -93,13 +93,17 @@ skills/
93
93
  comate-fix/ 预设路径 skill(bug + 小调整)
94
94
  comate-grill/ 决策点穷追式澄清工具
95
95
  comate-kb-integration/ 第三方对接档案生成工具(知识库配套)
96
- docs/
97
- COMATE_MANUAL.md 完整操作手册
98
- ```
96
+ docs/
97
+ COMATE_BEGINNER_GUIDE.md 面向新手的安装与使用手册
98
+ COMATE_MANUAL.md 完整操作手册
99
+ ```
99
100
 
100
101
  ## 文档
101
102
 
102
- 完整说明见 [docs/COMATE_MANUAL.md](docs/COMATE_MANUAL.md):安装前置、命令参考、阶段详解、状态文件字段、决策阻塞点、故障排除。
103
+ 文档:
104
+
105
+ - [入门操作手册](docs/COMATE_BEGINNER_GUIDE.md):面向第一次使用 comate 的同学,覆盖安装、`init`、目录、PRD、Skill 流程和常见问题。
106
+ - [完整技术手册](docs/COMATE_MANUAL.md):安装前置、命令参考、阶段详解、状态文件字段、决策阻塞点、故障排除。
103
107
 
104
108
  ## License
105
109
 
package/bin/init-run.mjs CHANGED
@@ -68,15 +68,13 @@ export async function init(pkgRoot) {
68
68
  }
69
69
  }
70
70
 
71
- // comate's skills are bash scripts needing associative arrays (bash 4+), and
72
- // macOS ships 3.2. Fail here rather than installing everything and letting the
73
- // first /comate call die inside a sourced library.
71
+ // comate's scripts intentionally support macOS's system Bash 3.2. Fail only
72
+ // when Bash is older than the syntax the state machine supports.
74
73
  const bash = bashVersion();
75
- if (bash !== null && bash < 4) {
74
+ if (bash !== null && bash < 3) {
76
75
  console.error(
77
- `\ncomate 的脚本需要 bash >= 4,当前 ${bash === 0 ? '未知版本' : `bash ${bash}.x`}。\n` +
78
- 'macOS 自带 bash 3.2(授权原因),装一个新的:\n\n brew install bash\n\n' +
79
- '装好后重跑 comate init。',
76
+ `\ncomate 的脚本需要 bash >= 3.2,当前 ${bash === 0 ? '未知版本' : `bash ${bash}.x`}。\n` +
77
+ '请安装 Bash 3.2 或更高版本后重跑 comate init。',
80
78
  );
81
79
  return 3;
82
80
  }
@@ -0,0 +1,429 @@
1
+ # comate 入门操作手册
2
+
3
+ 这是一份给第一次使用 comate 的同学看的操作手册。按“安装 → 初始化 → 输入 /comate”的顺序,就可以开始使用。
4
+
5
+ ## 1. comate 是什么
6
+
7
+ comate 是一个 AI 编程流程编排器。它把一次需求变更拆成几个可以检查的阶段,让 AI 先理解需求和设计,再写代码,最后验证并归档。
8
+
9
+ 完整流程:
10
+
11
+ ```text
12
+ 原始 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 归档规格
25
+ ```
26
+
27
+ - **OpenSpec** 负责“要做什么”:proposal、spec、tasks 和归档。
28
+ - **Superpowers** 负责“怎么做好”:头脑风暴、技术设计、计划、执行和验证。
29
+ - **comate** 负责识别当前阶段,并把这些能力串起来。
30
+
31
+ ## 2. 安装前准备
32
+
33
+ ### 2.1 必需软件
34
+
35
+ | 软件 | 建议版本 | 用途 |
36
+ | --- | --- | --- |
37
+ | Node.js | 20 或更高 | 运行 comate 和 npm |
38
+ | npm | 随 Node.js 安装 | 安装 comate、OpenSpec |
39
+ | Git | 任意现代版本 | 管理代码和分支 |
40
+ | Bash | 3.2 或更高 | 执行 comate 的状态机脚本 |
41
+
42
+ Windows 用户建议安装 [Git for Windows](https://git-scm.com/download/win),它会提供 Git Bash。PowerShell 可以运行 `comate`,但流程脚本仍需要 Bash。
43
+
44
+ 检查环境:
45
+
46
+ ```bash
47
+ node --version
48
+ npm --version
49
+ git --version
50
+ bash --version
51
+ ```
52
+
53
+ ### 2.2 哪些步骤需要网络
54
+
55
+ 首次初始化时,`comate init` 可能需要访问 npm registry 安装 OpenSpec CLI,并访问 GitHub 安装 Superpowers。
56
+
57
+ comate 自己的 npm 包会随包携带 comate skill。npm 安装钩子只会尽力同步 comate 自身 skill,不会安装 Superpowers,也不会创建项目的 `openspec/` 和 `docs/`;这些由项目目录中的 `comate init` 完成。
58
+
59
+ ## 3. 安装 comate
60
+
61
+ 全局安装:
62
+
63
+ ```bash
64
+ npm install -g @house365/comate
65
+ ```
66
+
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
+ 进入项目根目录再运行:
84
+
85
+ ```bash
86
+ cd path/to/your-project
87
+ comate init
88
+ ```
89
+
90
+ Windows PowerShell 示例:
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
+ ```
189
+
190
+ 需要从官方文档生成对接档案时,使用 `/comate-kb-integration`。
191
+
192
+ ## 7. PRD 存放位置和命名规范
193
+
194
+ 推荐格式:
195
+
196
+ ```text
197
+ docs/knowledge-base/requirements/<YYYYMMDD-功能名>/PRD-<三位序号>-<功能名>.md
198
+ ```
199
+
200
+ 示例:
201
+
202
+ ```text
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 同级自动生成:
215
+
216
+ ```text
217
+ PRD-001.gate.yaml # 机器读取的门禁状态
218
+ PRD-001-REVIEW.md # 人类可读的评审报告
219
+ ```
220
+
221
+ PRD 通过门禁后如果又被修改,哈希会变化,门禁会变成过期状态,需要重新运行 `/comate-gate`。
222
+
223
+ ## 8. 从需求到代码的 Skill 流程
224
+
225
+ ### 8.1 需求来自一份 PRD
226
+
227
+ 先把 PRD 放到 `docs/knowledge-base/requirements/`,然后直接指定 PRD 路径:
228
+
229
+ ```text
230
+ /comate docs/knowledge-base/requirements/20260820-用户登录/PRD-001-用户登录.md
231
+ ```
232
+
233
+ `/comate` 会识别这是原始 PRD,自动进入 PRD 门禁和拆分流程;不需要先手动调用 `/comate-gate`。门禁通过后,它会继续处理拆分出来的 change。
234
+
235
+ 如果只想单独审阅或重新运行门禁,也可以直接输入:
236
+
237
+ ```text
238
+ /comate-gate docs/knowledge-base/requirements/20260820-用户登录/PRD-001-用户登录.md
239
+ ```
240
+
241
+ 对已有 change 继续工作时,输入:
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 或很小的调整
260
+
261
+ ```text
262
+ /comate-fix 修复登录失败时页面没有提示的问题
263
+ ```
264
+
265
+ 它走简化流程:`open → build → verify → archive`,跳过完整 brainstorming。涉及新能力、架构变化、数据库 schema 或多个模块时,应升级回完整 `/comate` 流程。
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 底层依赖分工
283
+
284
+ - **OpenSpec**:负责 `openspec/`、change 规格和归档。
285
+ - **Superpowers**:由 `comate init` 通过 `skills` CLI 从 `obra/superpowers` 安装,负责设计、规划、执行和收尾。
286
+ - **comate skill**:本项目提供的编排规则和状态机。
287
+
288
+ 首次使用时,如果 Superpowers 安装失败,design/build/verify 相关流程不能完整运行。先修复网络或手工安装,再重跑 `comate init`。
289
+
290
+ ## 10. 常用命令
291
+
292
+ ### 10.1 安装和检查
293
+
294
+ ```bash
295
+ comate --help
296
+ comate --version
297
+ comate init
298
+ comate doctor
299
+ ```
300
+
301
+ ### 10.2 查看 token 用量
302
+
303
+ ```bash
304
+ comate usage
305
+ comate usage daily
306
+ comate usage --global
307
+ comate usage --json
308
+ ```
309
+
310
+ ## 11. 配套 UI 查看器
311
+
312
+ `comate-ui` 是 comate 的本地只读浏览器工作区,用来可视化查看项目中的变更状态、OpenSpec 规格、PRD、Superpowers 设计文档、计划和验证报告。它不会修改项目文件,也不需要数据库。
313
+
314
+ 全局安装:
315
+
316
+ ```bash
317
+ npm install -g @house365/comate-ui@latest
318
+ ```
319
+
320
+ 安装后,在包含 `openspec/` 的项目根目录运行:
321
+
322
+ ```bash
323
+ comate-ui
324
+ ```
325
+
326
+ 也可以指定项目路径、端口或禁止自动打开浏览器:
327
+
328
+ ```bash
329
+ comate-ui D:\work\my-project
330
+ comate-ui D:\work\my-project --port 4300
331
+ comate-ui --no-open
332
+ ```
333
+
334
+ 查看内置示例:
335
+
336
+ ```bash
337
+ comate-ui --demo
338
+ ```
339
+
340
+ 查看器只绑定本机 `127.0.0.1`。默认会自动打开浏览器;端口被占用时会自动选择下一个可用端口,按 `Ctrl+C` 停止。
341
+
342
+ ## 12. 更新 comate
343
+
344
+ ```bash
345
+ npm install -g @house365/comate@latest
346
+ cd path/to/your-project
347
+ comate init
348
+ ```
349
+
350
+ npm 更新 CLI 和包内文件;`comate init` 负责把新版 comate skill 同步到用户级目录,并补齐当前项目缺少的 `openspec/`、`docs/` 文件。
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 安装失败
364
+
365
+ ```bash
366
+ skills add obra/superpowers -g -y
367
+ ```
368
+
369
+ GitHub 无法访问时,可以配置 npm/Git 代理、使用公司网络或 Git Bash/WSL,安装成功后再运行:
370
+
371
+ ```bash
372
+ comate init
373
+ comate doctor
374
+ ```
375
+
376
+ 当前 npm 包没有内置 Superpowers 离线快照;不要把“npm 安装成功”理解成 Superpowers 已安装完成。
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. 新手最短上手路径
409
+
410
+ ```bash
411
+ npm install -g @house365/comate
412
+ cd your-project
413
+ comate init
414
+ comate doctor
415
+ ```
416
+
417
+ 然后在 Claude Code、Codex、Cursor 等支持 Agent Skills 的工具中输入:
418
+
419
+ ```text
420
+ /comate 我想实现一个用户登录功能
421
+ ```
422
+
423
+ 等待 AI 在需要你决定的地方提问,确认设计和构建选项后,让流程走到验证和归档。
424
+
425
+ ## 16. 进一步阅读
426
+
427
+ - [完整技术手册](COMATE_MANUAL.md):状态字段、门禁规则、脚本接口和故障排查。
428
+ - [项目 README](../README.md):项目概览和快速安装。
429
+ - 初始化后生成的 `docs/knowledge-base/README.md`:PRD 溯源和命名规则。
@@ -89,7 +89,7 @@ cd your-project && comate init
89
89
 
90
90
  | 依赖 | 要求 | 检查方式 |
91
91
  |------------|-------------|-------|
92
- | bash | >= 4(需要关联数组) | `bash --version` |
92
+ | bash | >= 3.2 | `bash --version` |
93
93
  | git | 任意现代版本 | `git --version` |
94
94
  | sha256 | `sha256sum` 或 `shasum`(至少一个) | `command -v sha256sum shasum` |
95
95
  | LF 换行符 | 所有 `.sh` 文件必须使用 LF | `bash comate/scripts/comate.sh doctor` |
@@ -137,14 +137,14 @@ docs/
137
137
  ├── architecture/README.md # 架构规范占位(契约事实源,需项目自建)
138
138
  └── knowledge-base/
139
139
  ├── README.md # PRD → comate 流程说明 + 命名约定
140
- ├── requirements/_templates/PRD.md # PRD 模板(comate 版,无 MRD/FRD 层)
140
+ ├── requirements/_templates/PRD.md # PRD 模板
141
141
  └── integrations/_TEMPLATE/ # 第三方对接档案模板(README + api-snapshot)
142
142
  ```
143
143
 
144
144
  - 路径与 `comate-lib.sh` 硬编码的 `comate_kb_root=docs/knowledge-base` 对齐,PRD 放 `requirements/<YYYYMMDD-功能名>/` 下。
145
145
  - **幂等且不覆盖**:逐文件跳过已存在的,重跑 `comate init` 不动你已改过的 docs。
146
146
  - `architecture/` 只给占位 README(架构内容项目专属),是 PRD 门禁「契约对齐」的事实源,需按项目实际补齐。
147
- - 不含 MRD/FRD 模板——comate 只保留 PRD 层,FRD 内容消解进 change 的 proposal/design/tasks。
147
+ - PRD 是需求入口,具体实现约束由 change 的 proposal/design/tasks 承接。
148
148
 
149
149
  ### 2.6 comate 本身的技能布局
150
150
 
@@ -235,7 +235,7 @@ comate usage --json -s 2026-08-01 # 其余参数原样透传给 ccusage
235
235
  1. `archived: true` 或变更已在归档中 -> 流程完成
236
236
  2. `verify_result: pass` 且 `archived` 不为 `true` -> 调用 `/comate-archive`
237
237
  3. `verify_result: fail` -> 验证失败决策阻塞点(决策点 4)
238
- 4. `phase: verify` 或 tasks.md 全部勾选 -> 调用 `/comate-verify`
238
+ 4. `phase: verify` -> 调用 `/comate-verify`。`tasks.md` 全部勾选不能单独触发 verify;从 verify-fail 回到 build 后必须按 build 路由继续修复。
239
239
  5. `phase: build` 或有设计文档但计划/执行不完整 -> 按工作流路由
240
240
  6. `phase: design` 或有变更但无设计文档 -> 调用 `/comate-design`
241
241
  7. `phase: open` 或有活动变更但 `.comate.yaml` 缺失 -> 调用 `/comate-open`
@@ -645,7 +645,7 @@ bash comate/scripts/comate.sh status # 所有活动变更状态
645
645
  | 变更目录结构不完整 | 流程中断 | 按 comate-open 产出物要求补全 |
646
646
  | `openspec list --json` 失败 | openspec 未安装或未初始化 | 运行 `comate init`,或 `openspec init . --tools none` |
647
647
  | 子技能不可用 | Superpowers 未安装 | 运行 `comate init` 补装 |
648
- | bash 版本过低 | bash < 4(无关联数组) | Windows:Git Bash/WSL;macOS:`brew install bash` |
648
+ | bash 版本过低 | bash < 3.2 | Windows:Git Bash/WSL;macOS 自带 Bash 3.2 可直接使用 |
649
649
  | .sh 文件包含 CRLF | git 配置问题 | 仓库有 `.gitattributes` 强制 `eol=lf`;如已损坏,使用 `dos2unix` |
650
650
  | 脏工作树无法推进 | 未遵循 dirty-worktree 协议 | 按协议归属,暂停并询问用户 |
651
651
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@house365/comate",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "comate —— SDD (规约驱动开发) 的 AI 开发编排器(Agent Skills)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -185,8 +185,8 @@ agent 做决策只需读本节,其余参见末尾「参考附录」。决策
185
185
  1. `archived: true` 或 change 已移入 archive → 流程完成
186
186
  2. `verify_result: pass` 且 `archived` 非 `true` → 调用 `/comate-archive`
187
187
  3. `verify_result: fail` → 验证失败决策阻塞点(决策点 4)
188
- 4. `phase: verify` 或 tasks.md 全勾 → 调用 `/comate-verify`
189
- 5. `phase: build` 或有 Design Doc 但 plan/执行未完成 → 按 workflow 路由:`fix`→`/comate-fix`,`full`→`/comate-build`
188
+ 4. `phase: verify` → 调用 `/comate-verify`。`tasks.md` 全勾不能单独触发 verify;验证失败回退到 build 后,必须继续按 build 路由修复。
189
+ 5. `phase: build` 或有 Design Doc 但 plan/执行未完成 → 按 workflow 路由:`fix`→`/comate-fix`,`full`→`/comate-build`
190
190
  6. `phase: design` 或有 change 但无 Design Doc → 调用 `/comate-design`
191
191
  7. `phase: open` 或有活跃 change 但 `.comate.yaml` 缺失 → 调用 `/comate-open`
192
192
  8. 无活跃 change → 调用 `/comate-open`
@@ -10,7 +10,7 @@
10
10
 
11
11
  | 依赖 | 要求 | 检查 | 缺失处理 |
12
12
  |------|------|------|----------|
13
- | bash | ≥ 4(需要关联数组) | `bash --version` | Windows 用 Git Bash / WSL;macOS 自带 3.2,需 `brew install bash` |
13
+ | bash | ≥ 3.2 | `bash --version` | Windows 用 Git Bash / WSL;macOS 自带 3.2 可直接使用 |
14
14
  | git | 任意近代版本 | `git --version` | 安装 git |
15
15
  | sha256 工具 | `sha256sum` 或 `shasum` 至少其一 | `command -v sha256sum shasum` | Linux 装 coreutils;macOS 自带 `shasum`(perl) |
16
16
  | LF 换行 | 所有 `.sh` 必须是 LF | `doctor` 会检测 CRLF | 仓库已带 `.gitattributes` 强制 `eol=lf`;若被改坏,用 `dos2unix` 或重新 checkout |
@@ -80,42 +80,44 @@ FIELD_ORDER=(
80
80
  # Current schema version written by `state init`.
81
81
  readonly COMATE_SCHEMA_VERSION=1
82
82
 
83
- # --- Enum table: key -> space-separated legal values ---
84
- declare -A ENUMS=(
85
- [schema_version]="1"
86
- [workflow]="full fix"
87
- [phase]="open design build verify archive"
88
- [build_mode]="subagent-driven-development executing-plans direct"
89
- [tdd_mode]="tdd direct"
90
- [isolation]="branch worktree"
91
- [verify_mode]="light full"
92
- [verify_result]="pending pass fail"
93
- [branch_status]="pending handled discarded"
94
- [direct_override]="true false"
95
- [archived]="true false"
96
- [source_kind]="conversation file"
97
- )
83
+ # --- Enum tables ---
84
+ # Keep these as case-based lookups instead of associative arrays. macOS ships
85
+ # Bash 3.2, which has normal arrays but not `declare -A`.
86
+ enum_keys() {
87
+ printf '%s\n' \
88
+ schema_version workflow phase build_mode tdd_mode isolation verify_mode \
89
+ verify_result branch_status direct_override archived source_kind
90
+ }
91
+
92
+ enum_values() {
93
+ case "$1" in
94
+ schema_version) printf '%s\n' '1' ;;
95
+ workflow) printf '%s\n' 'full fix' ;;
96
+ phase) printf '%s\n' 'open design build verify archive' ;;
97
+ build_mode) printf '%s\n' 'subagent-driven-development executing-plans direct' ;;
98
+ tdd_mode) printf '%s\n' 'tdd direct' ;;
99
+ isolation) printf '%s\n' 'branch worktree' ;;
100
+ verify_mode) printf '%s\n' 'light full' ;;
101
+ verify_result) printf '%s\n' 'pending pass fail' ;;
102
+ branch_status) printf '%s\n' 'pending handled discarded' ;;
103
+ direct_override) printf '%s\n' 'true false' ;;
104
+ archived) printf '%s\n' 'true false' ;;
105
+ source_kind) printf '%s\n' 'conversation file' ;;
106
+ *) return 1 ;;
107
+ esac
108
+ }
98
109
 
99
110
  # Keys whose values are free-form (paths, dates, commands, hashes, refs).
100
- # Listed so validate_change/validate can flag genuinely unknown keys.
101
- declare -A FREEFORM_KEYS=(
102
- [design_doc]=1
103
- [plan]=1
104
- [verification_report]=1
105
- [handoff_context]=1
106
- [handoff_hash]=1
107
- [impl_context]=1
108
- [impl_hash]=1
109
- [base_ref]=1
110
- [base_branch]=1
111
- [source_manifest]=1
112
- [build_command]=1
113
- [verify_command]=1
114
- [created_at]=1
115
- [verified_at]=1
116
- [phase_step]=1
117
- [phase_step_data]=1
118
- )
111
+ # Listed as a case lookup so unknown fields can still be rejected on Bash 3.2.
112
+ is_freeform_key() {
113
+ case "$1" in
114
+ design_doc|plan|verification_report|handoff_context|handoff_hash|\
115
+ impl_context|impl_hash|base_ref|base_branch|source_manifest|\
116
+ build_command|verify_command|created_at|verified_at|phase_step|phase_step_data)
117
+ return 0 ;;
118
+ *) return 1 ;;
119
+ esac
120
+ }
119
121
 
120
122
  # --- Path location ---
121
123
 
@@ -157,7 +159,8 @@ validate_change_name() {
157
159
  # Free-form keys and unlisted keys pass through. null/empty always allowed.
158
160
  validate_enum() {
159
161
  local key="$1" value="$2"
160
- local legal="${ENUMS[$key]:-}"
162
+ local legal
163
+ legal=$(enum_values "$key" 2>/dev/null) || legal=""
161
164
  [ -z "$legal" ] && return 0
162
165
  [ -z "$value" ] && return 0
163
166
  [ "$value" = "null" ] && return 0
@@ -216,18 +219,6 @@ yaml_keys() {
216
219
  grep -E '^[A-Za-z0-9_-]+:' "$file" 2>/dev/null | sed 's/:.*//' || true
217
220
  }
218
221
 
219
- # _yaml_read_all <file> <assoc-array-name> — load every known field into the
220
- # named associative array, defaulting missing fields to "null".
221
- _yaml_read_all() {
222
- local file="$1" arrname="$2" k
223
- for k in "${FIELD_ORDER[@]}"; do
224
- local v
225
- v=$(yaml_get "$file" "$k")
226
- [ -z "$v" ] && v="null"
227
- printf -v "${arrname}[$k]" '%s' "$v"
228
- done
229
- }
230
-
231
222
  # validate_flat_yaml <file> — enforce that .comate.yaml is the
232
223
  # declared subset: top-level `key: value` scalars plus `#` comments only.
233
224
  # Reject indented lines, list items, block/flow collections. die(CORRUPT) on violation.
@@ -278,22 +269,25 @@ yaml_set() {
278
269
  [ -f "$file" ] || die "$COMATE_CORRUPT" "ERROR: state file not found: $file"
279
270
 
280
271
  # Reject keys we do not know about (prevents silent typo fields).
281
- if [ -z "${ENUMS[$key]:-}" ] && [ -z "${FREEFORM_KEYS[$key]:-}" ] && [ "$key" != "schema_version" ]; then
272
+ if ! enum_values "$key" >/dev/null 2>&1 && ! is_freeform_key "$key" && [ "$key" != "schema_version" ]; then
282
273
  die "$COMATE_USAGE" "ERROR: unknown field '$key'"
283
274
  fi
284
275
  validate_enum "$key" "$value"
285
276
 
286
- declare -A _cur
287
- _yaml_read_all "$file" _cur
288
- _cur[$key]="$value"
289
-
290
277
  local tmp
291
278
  tmp=$(mktemp "${TMPDIR:-/tmp}/comate.XXXXXX") || die "$COMATE_ENV" "ERROR: mktemp failed"
292
279
  {
293
280
  printf '# comate state — flat key:value only, no nesting\n'
294
281
  local k
295
282
  for k in "${FIELD_ORDER[@]}"; do
296
- printf '%s: %s\n' "$k" "${_cur[$k]:-null}"
283
+ local current
284
+ if [ "$k" = "$key" ]; then
285
+ current="$value"
286
+ else
287
+ current=$(yaml_get "$file" "$k")
288
+ fi
289
+ [ -z "$current" ] && current="null"
290
+ printf '%s: %s\n' "$k" "$current"
297
291
  done
298
292
  } > "$tmp"
299
293
 
@@ -460,10 +454,13 @@ GATE_FIELD_ORDER=(
460
454
 
461
455
  # gate_status is the only enum. `stale` is computed (hash mismatch) but also
462
456
  # storable so a detected-stale gate persists across runs.
463
- declare -A GATE_ENUMS=(
464
- [schema_version]="1"
465
- [gate_status]="reviewing blocked passed passed-with-tbd stale"
466
- )
457
+ gate_enum_values() {
458
+ case "$1" in
459
+ schema_version) printf '%s\n' '1' ;;
460
+ gate_status) printf '%s\n' 'reviewing blocked passed passed-with-tbd stale' ;;
461
+ *) return 1 ;;
462
+ esac
463
+ }
467
464
 
468
465
  # gate_stem_for <prd> — derive the short stem used to name a PRD's gate.yaml
469
466
  # and review report. A conforming PRD file is `PRD-<NNN>-<title>.md`, whose
@@ -472,12 +469,15 @@ declare -A GATE_ENUMS=(
472
469
  # names (no PRD-NNN prefix) fall back to the full basename so callers still get
473
470
  # a stable, collision-free stem instead of an error.
474
471
  gate_stem_for() {
475
- local base
472
+ local base stem
476
473
  base=$(basename "$1")
477
474
  base="${base%.markdown}"
478
475
  base="${base%.md}"
479
- if [[ "$base" =~ ^([Pp][Rr][Dd]-[0-9]+) ]]; then
480
- echo "${BASH_REMATCH[1]}"
476
+ # Avoid a capture group in `[[ =~ ]]`: the old parser in macOS Bash 3.2
477
+ # treats an unquoted `(` as syntax before the conditional can run.
478
+ stem=$(printf '%s\n' "$base" | sed -n 's/^\([Pp][Rr][Dd]-[0-9][0-9]*\).*/\1/p')
479
+ if [ -n "$stem" ]; then
480
+ echo "$stem"
481
481
  else
482
482
  echo "$base"
483
483
  fi
@@ -514,27 +514,27 @@ gate_yaml_set() {
514
514
  done
515
515
  [ "$known" -eq 1 ] || die "$COMATE_USAGE" "ERROR: unknown gate field '$key'"
516
516
 
517
- local legal="${GATE_ENUMS[$key]:-}"
517
+ local legal
518
+ legal=$(gate_enum_values "$key" 2>/dev/null) || legal=""
518
519
  if [ -n "$legal" ] && [ -n "$value" ] && [ "$value" != "null" ]; then
519
520
  local ok=0 v
520
521
  for v in $legal; do [ "$value" = "$v" ] && { ok=1; break; }; done
521
522
  [ "$ok" -eq 1 ] || die "$COMATE_USAGE" "ERROR: invalid value '$value' for '$key' (expected: $legal)"
522
523
  fi
523
524
 
524
- declare -A _g
525
- for k in "${GATE_FIELD_ORDER[@]}"; do
526
- local cv; cv=$(yaml_get "$file" "$k")
527
- [ -z "$cv" ] && cv="null"
528
- _g[$k]="$cv"
529
- done
530
- _g[$key]="$value"
531
-
532
525
  local tmp
533
526
  tmp=$(mktemp "${TMPDIR:-/tmp}/comate-gate.XXXXXX") || die "$COMATE_ENV" "ERROR: mktemp failed"
534
527
  {
535
528
  printf '# comate PRD gate — flat key:value only, no nesting\n'
536
529
  for k in "${GATE_FIELD_ORDER[@]}"; do
537
- printf '%s: %s\n' "$k" "${_g[$k]:-null}"
530
+ local current
531
+ if [ "$k" = "$key" ]; then
532
+ current="$value"
533
+ else
534
+ current=$(yaml_get "$file" "$k")
535
+ fi
536
+ [ -z "$current" ] && current="null"
537
+ printf '%s: %s\n' "$k" "$current"
538
538
  done
539
539
  } > "$tmp"
540
540
  mv "$tmp" "$file" || { rm -f "$tmp"; die "$COMATE_ENV" "ERROR: failed to replace $file"; }
@@ -31,23 +31,8 @@
31
31
 
32
32
  set -euo pipefail
33
33
 
34
- # Must come before sourcing the lib. comate-lib.sh declares associative arrays
35
- # at load time, which bash 3.2 (what macOS ships as /bin/bash) parses as an
36
- # ordinary array assignment — the subscript is then evaluated as arithmetic and
37
- # `set -u` aborts with "schema_version: unbound variable". doctor has its own
38
- # bash>=4 check, but it never got to run: the source above it already died.
39
- if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then
40
- cat >&2 <<EOF
41
- comate needs bash >= 4 (associative arrays), found ${BASH_VERSION:-unknown}.
42
-
43
- macOS ships bash 3.2 for licensing reasons. Install a current one:
44
- brew install bash
45
-
46
- Then re-run through it, e.g.:
47
- $(command -v brew >/dev/null 2>&1 && echo "\$(brew --prefix)/bin/bash" || echo /opt/homebrew/bin/bash) $0 $*
48
- EOF
49
- exit 3
50
- fi
34
+ # The scripts intentionally stay compatible with macOS's system Bash 3.2.
35
+ # Do not add Bash 4-only features (notably associative arrays) here.
51
36
 
52
37
  SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")" && pwd)"
53
38
  # shellcheck source=comate-lib.sh
@@ -356,11 +341,11 @@ state_validate() {
356
341
 
357
342
  # Enum validity (null/empty allowed by validate_enum, but it dies on bad — so check inline).
358
343
  local key legal v ok w
359
- for key in "${!ENUMS[@]}"; do
344
+ for key in $(enum_keys); do
360
345
  v=$(yaml_get "$yaml" "$key")
361
346
  [ -z "$v" ] && continue
362
347
  [ "$v" = "null" ] && continue
363
- legal="${ENUMS[$key]}"
348
+ legal=$(enum_values "$key")
364
349
  ok=0
365
350
  for w in $legal; do
366
351
  if [ "$v" = "$w" ]; then ok=1; break; fi
@@ -1413,13 +1398,23 @@ dependency_archived() {
1413
1398
  # gate — phase 0: PRD quality gate + split precondition
1414
1399
  # =====================================================================
1415
1400
 
1416
- # require_prd <prd> — ensure the PRD path is a real, non-empty file; echo it.
1401
+ # require_prd <prd> — ensure the PRD path is real, non-empty, and conventionally named.
1402
+ validate_prd_filename() {
1403
+ local prd="$1" base
1404
+ base=$(basename "$prd")
1405
+ case "$base" in
1406
+ PRD-[0-9][0-9][0-9]-?*.md) return 0 ;;
1407
+ *) die "$COMATE_USAGE" "ERROR: PRD filename must match PRD-NNN-title.md: $base" ;;
1408
+ esac
1409
+ }
1410
+
1417
1411
  require_prd() {
1418
1412
  local prd="$1"
1419
1413
  [ -n "$prd" ] || die "$COMATE_USAGE" "ERROR: PRD path required"
1420
1414
  case "$prd" in *..*) die "$COMATE_USAGE" "ERROR: PRD path cannot contain '..': $prd" ;; esac
1421
1415
  [ -f "$prd" ] || die "$COMATE_CORRUPT" "ERROR: PRD not found: $prd"
1422
1416
  file_nonempty "$prd" || die "$COMATE_CORRUPT" "ERROR: PRD is empty: $prd"
1417
+ validate_prd_filename "$prd"
1423
1418
  echo "$prd"
1424
1419
  }
1425
1420
 
@@ -1825,11 +1820,11 @@ doctor() {
1825
1820
 
1826
1821
  log_info "comate doctor"
1827
1822
 
1828
- # bash version >= 4 (need associative arrays).
1829
- if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
1830
- _d_pass "bash ${BASH_VERSION} (>= 4)"
1823
+ # The state machine is written for Bash 3.2-compatible syntax.
1824
+ if [ "${BASH_VERSINFO[0]:-0}" -ge 3 ]; then
1825
+ _d_pass "bash ${BASH_VERSION} (>= 3)"
1831
1826
  else
1832
- _d_fail "bash ${BASH_VERSION:-unknown} — comate needs bash >= 4 (associative arrays)"
1827
+ _d_fail "bash ${BASH_VERSION:-unknown} — comate needs bash >= 3"
1833
1828
  fi
1834
1829
 
1835
1830
  # git.
@@ -237,6 +237,18 @@ seed_open() {
237
237
  [ "$(comate state demo get verification_report)" = "null" ]
238
238
  }
239
239
 
240
+ @test "verify-fail keeps build authoritative when tasks remain checked" {
241
+ comate state demo init full
242
+ printf '%s\n' '# Tasks' '- [x] already implemented' > openspec/changes/demo/tasks.md
243
+ comate state demo set phase verify >/dev/null
244
+ comate state demo set verify_result fail >/dev/null
245
+ comate state demo transition verify-fail
246
+ [ "$(comate state demo get phase)" = "build" ]
247
+ [ "$(comate state demo get verify_result)" = "pending" ]
248
+ # The router must use the persisted phase; task completion is not a phase.
249
+ [ "$(comate state demo get phase)" != "verify" ]
250
+ }
251
+
240
252
  @test "recover reports phase-local progress" {
241
253
  comate state demo init full
242
254
  comate state demo set phase_step open.review-awaiting >/dev/null
@@ -346,8 +358,8 @@ SH
346
358
  local d="openspec/changes/demo"
347
359
  printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
348
360
  mkdir -p docs/knowledge-base/cb bin
349
- printf '# frd\n' > docs/knowledge-base/cb/frd.md
350
- comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
361
+ printf '# source\n' > docs/knowledge-base/cb/source.md
362
+ comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
351
363
  cat > bin/openspec <<'SH'
352
364
  #!/bin/bash
353
365
  set -e
@@ -427,13 +439,13 @@ SH
427
439
  @test "sources track records kb file and flips source_kind to file" {
428
440
  comate state demo init full
429
441
  mkdir -p docs/knowledge-base/cb
430
- printf '# frd\n' > docs/knowledge-base/cb/frd.md
431
- run comate sources demo track docs/knowledge-base/cb/frd.md --note "main entry"
442
+ printf '# source\n' > docs/knowledge-base/cb/source.md
443
+ run comate sources demo track docs/knowledge-base/cb/source.md --note "main entry"
432
444
  [ "$status" -eq 0 ]
433
445
  [ "$(comate state demo get source_kind)" = "file" ]
434
446
  [ "$(comate state demo get source_manifest)" = "openspec/changes/demo/.comate/sources.manifest" ]
435
447
  run comate sources demo list
436
- [[ "$output" == *"path:docs/knowledge-base/cb/frd.md"* ]]
448
+ [[ "$output" == *"path:docs/knowledge-base/cb/source.md"* ]]
437
449
  [[ "$output" == *"note:main entry"* ]]
438
450
  }
439
451
 
@@ -448,9 +460,9 @@ SH
448
460
  @test "sources check detects drift after file edited" {
449
461
  comate state demo init full
450
462
  mkdir -p docs/knowledge-base/cb
451
- printf '# frd\n' > docs/knowledge-base/cb/frd.md
452
- comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
453
- printf 'edited\n' >> docs/knowledge-base/cb/frd.md
463
+ printf '# source\n' > docs/knowledge-base/cb/source.md
464
+ comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
465
+ printf 'edited\n' >> docs/knowledge-base/cb/source.md
454
466
  run comate sources demo check
455
467
  [ "$status" -eq 1 ]
456
468
  [[ "$output" == *"drifted"* ]]
@@ -459,9 +471,9 @@ SH
459
471
  @test "sources find reverse-looks-up a tracked file" {
460
472
  comate state demo init full
461
473
  mkdir -p docs/knowledge-base/cb
462
- printf '# frd\n' > docs/knowledge-base/cb/frd.md
463
- comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
464
- run comate sources find docs/knowledge-base/cb/frd.md
474
+ printf '# source\n' > docs/knowledge-base/cb/source.md
475
+ comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
476
+ run comate sources find docs/knowledge-base/cb/source.md
465
477
  [ "$status" -eq 0 ]
466
478
  [[ "$output" == *"demo"* ]]
467
479
  }
@@ -471,11 +483,11 @@ SH
471
483
  local d="openspec/changes/demo"
472
484
  printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
473
485
  mkdir -p docs/knowledge-base/cb
474
- printf '# frd\n' > docs/knowledge-base/cb/frd.md
475
- comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
486
+ printf '# source\n' > docs/knowledge-base/cb/source.md
487
+ comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
476
488
  comate guard demo open --apply >/dev/null 2>&1
477
489
  comate handoff demo build --write >/dev/null 2>&1
478
- printf 'edited\n' >> docs/knowledge-base/cb/frd.md
490
+ printf 'edited\n' >> docs/knowledge-base/cb/source.md
479
491
  run comate guard demo build --json
480
492
  [ "$status" -eq 1 ]
481
493
  [[ "$output" == *'"block":true'* ]]
@@ -521,6 +533,14 @@ seed_prd() {
521
533
  [ -f "docs/knowledge-base/requirements/test/PRD-001.gate.yaml" ]
522
534
  }
523
535
 
536
+ @test "gate rejects PRD filenames outside the naming convention" {
537
+ mkdir -p docs/knowledge-base/requirements/test
538
+ printf '# PRD\n' > docs/knowledge-base/requirements/test/PRD-1.md
539
+ run comate gate docs/knowledge-base/requirements/test/PRD-1.md review
540
+ [ "$status" -eq 2 ]
541
+ [[ "$output" == *"PRD-NNN-title.md"* ]]
542
+ }
543
+
524
544
  @test "gate names key off PRD-NNN stem, dropping title" {
525
545
  local prd; prd=$(seed_prd)
526
546
  run comate gate "$prd" review
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: comate-gate
3
- description: "comate 阶段 0:PRD 门禁与拆分。用 /comate-gate 调用。对原始 PRD 做质量审阅+评分+阻断闸门,通过后按依赖/独立性判定拆成 1 或 N 个 change,逐个交给 comate-open。门禁状态钉在 PRD 同级 <PRD>.gate.yaml,门禁未通过禁止建 change。"
3
+ description: "comate 阶段 0:PRD 门禁与拆分。用 /comate-gate 调用。对原始 PRD 做质量审阅+评分+阻断闸门,通过后按依赖/独立性判定拆成 1 或 N 个 change,逐个交给 comate-open。门禁状态钉在 PRD 同级 gate 文件,门禁未通过禁止建 change。"
4
4
  ---
5
5
 
6
6
  # comate 阶段 0:PRD 门禁与拆分(Gate)
@@ -11,13 +11,11 @@ description: "comate 阶段 0:PRD 门禁与拆分。用 /comate-gate 调用。
11
11
  1. **门禁**:审阅 PRD 质量、百分制评分、建阻断台账。阻断问题未清零,**禁止建任何 change**。
12
12
  2. **拆分**:按依赖/整体性/独立性/规模判定该 PRD 变成几个 change,粒度由用户拍板(AI 只给建议)。
13
13
 
14
- **FRD 层已消解**:不再产独立 FRD 文档。PRD 直接作为 change 的源文档被 `sources track` 钉住,其内容顺着 open→design 流进 comate 原生产物(proposal / design / tasks)。
15
-
16
- ---
14
+ ---
17
15
 
18
16
  ## 核心铁律
19
17
 
20
- - **门禁状态是可验证的文件真相源**:钉在 PRD 同级 `<PRD-NNN>.gate.yaml`(如 `PRD-001.gate.yaml`)。评审报告 `<PRD-NNN>-REVIEW.md`(如 `PRD-001-REVIEW.md`)是给人看的派生物,**不是**状态源。
18
+ - **门禁状态是可验证的文件真相源**:钉在 PRD 同级的 gate 文件。评审报告是给人看的派生物,**不是**状态源。PRD 命名按本 skill 的前置规范执行,派生文件名由脚本确定性生成。
21
19
  - **AskUserQuestion 是真阻塞点**:文字提示、推荐方案、默认值、历史偏好、用户未反对,都**不能**替代明确回答。
22
20
  - **只审不改**:门禁只暴露 PRD 问题,不替 PRD 补内容、不替产品拍板。
23
21
  - **门禁未通过禁止建 change**:`gate_status ∈ {passed, passed-with-tbd}` 且 PRD 哈希未漂移,才允许 comate-open 建 change(由 `gate check` 硬强制)。
@@ -55,34 +53,25 @@ bash "$COMATE" gate <prd> check # comate-open 建 change 前的
55
53
  ### 1. 定位 PRD
56
54
 
57
55
  - 用户给了路径/编号 → 直接读该文件。
58
- - 未给 → 用 Glob 列出候选(默认约定 `docs/knowledge-base/requirements/**/PRD-*.md`,若项目无此结构,探测 `docs/**/*.md` 中文件名含 `PRD`/`需求` 的),按命中数分类(对齐 `comate/reference/decision-points.md` 首节三类框架):
56
+ - 未给 → 用 Glob 列出候选(默认搜索 `docs/knowledge-base/requirements/**/*.md`,若项目无此结构再探测 `docs/**/*.md`),按命中数分类(对齐 `comate/reference/decision-points.md` 首节三类框架):
59
57
  - **恰好 1 个候选** → 无 ≥2 有效选项,属"自动处理":直接选中该 PRD 并**报告选了哪份**,不 AskUserQuestion。
60
58
  - **多个候选** → 属"用户决策":用 **AskUserQuestion** 让用户选一个。
61
59
  - **0 个候选** → 属"停止条件":报告在约定路径下未找到 PRD,请用户提供路径或确认 PRD 位置,不发明选项。
62
- - 读取后重点提取:**功能清单**(各功能项 + 优先级 P0/P1/P2)、**范围(In/Out)**、**第三方依赖**、**验收标准**、文档状态。
63
- - 若 PRD 明显未定稿(状态字段非「已定稿」等),用 **AskUserQuestion** 提示可能未冻结并暂停确认;确认前只审阅,不建 gate。
64
-
65
- ### 1.5 命名规范化(前置,定位到 PRD 即检测)
66
-
67
- 用户一旦指到 `docs/knowledge-base/requirements/` 下的文件,**先检测命名是否规范,再进门禁**——因为 gate.yaml 与评审报告的名字都从 PRD 的 `PRD-<NNN>` 前缀派生,名字不规范会连带派生错。comate 只有 PRD 一层(无 MRD/FRD、无固定负责人),只查这三样:
68
-
69
- | 对象 | 规范 | 反例 → 规范名 |
70
- |------|------|--------------|
71
- | 功能目录 | `<YYYYMMDD-功能名>/` | `图片素材管理/` → `20260614-图片素材管理/` |
72
- | PRD 文件 | 目录内 `PRD-<三位序号>-<功能名>.md`(序号紧跟类型在前) | `图片素材管理_PRD_V1.md` → `PRD-001-图片素材管理.md`;`PRD-1.md` → `PRD-001-图片素材管理.md` |
73
- | 派生文件 | `PRD-<NNN>.gate.yaml`、`PRD-<NNN>-REVIEW.md`(脚本自动按 PRD-NNN 前缀生成,无需手动命名) | 旧式 `PRD-001-图片素材管理.gate.yaml`、`PRD-REVIEW-PRD-001-图片素材管理.md` → 迁移到新名 |
74
-
75
- **检测到不规范时**(顺序很重要,避免断链):
76
-
77
- 1. 先 **Grep** 全仓搜旧文件名/旧路径的所有引用(正文、其它文档、`.comate.yaml` 的 `source_manifest`、gate.yaml 的 `prd_path`/`review_report`)。
78
- 2. 用 **AskUserQuestion** 给出「旧名 → 建议新名」让用户确认(改名影响引用,不自作主张;功能名/日期推断不出时一并问)。
79
- 3. 确认后 **`git mv`** 改名(目录、PRD 文件;已存在的旧 gate.yaml/报告一并 `git mv` 到新名),保留 git 历史,**不要** Write 新文件+删旧。
80
- 4. **Edit** 把第 1 步搜到的引用全部改成新名/新路径。
81
- 5. 再 **Grep** 一次旧名确认零残留。
82
-
83
- 已规范或用户确认改名后,才进第 2 步门禁——此时 `gate review` 生成的 gate.yaml/报告自然是新名。若 PRD 名已规范,本步无操作,直接进门禁。
84
-
85
- ### 2. 门禁:质量审阅 + 评分(不可跳过)
60
+ - 读取后重点提取:**功能清单**(各功能项 + 优先级 P0/P1/P2)、**范围(In/Out)**、**第三方依赖**、**验收标准**、文档状态。检查内容是否可审阅即可,不要求 Markdown 标题、表格或模板字段完全一致。
61
+ - 若 PRD 明显未定稿(状态字段非「已定稿」等),用 **AskUserQuestion** 提示可能未冻结并暂停确认;确认前只审阅,不建 gate。
62
+
63
+ ### 1.5 命名规范化(必要前置检查)
64
+
65
+ PRD 的路径和文件名需要稳定、可追踪,以便 gate 状态和评审报告确定性派生。进入门禁前检查:
66
+
67
+ | 对象 | 规范 |
68
+ |------|------|
69
+ | 功能目录 | `<YYYYMMDD-功能名>/` |
70
+ | PRD 文件 | 目录内 `PRD-<三位序号>-<功能名>.md` |
71
+
72
+ 发现不规范时,先列出旧名和建议新名,等待用户确认后再用 `git mv` 改名,并同步修正引用。命名规范是流程前置条件;PRD 的正文格式不要求完全套用模板,只需能被审阅并包含可判断的需求内容。
73
+
74
+ ### 2. 门禁:质量审阅 + 评分(不可跳过)
86
75
 
87
76
  先初始化 gate(钉当前 PRD 哈希、拿到报告路径):
88
77
 
@@ -111,7 +100,7 @@ bash "$COMATE" gate <prd> review
111
100
  - **仓库事实**(已有架构约定、字段、接口、对接档案):先用 Read/Glob/rg 核实并引用来源,不丢给用户决定。
112
101
  - **产品决策**(范围、优先级、用户可见行为、状态流转、权限、数据口径、失败处理、验收规则):必须用 **AskUserQuestion** 问用户,不得用推荐方案/历史偏好/「待确认」代替确认。
113
102
 
114
- **写评审报告(必生成)** 到 `gate review` 回显的 `review_report` 路径(PRD 同级 `<PRD-NNN>-REVIEW.md`,如 `PRD-001-REVIEW.md`)。内容含:评分 + 评级 + 一句话总评、问题台账表(`ID` / 维度 / 严重程度 / 原文依据 / 影响章节 / 是否阻断 / 建议 / 产品回填列)、逐条问题。若无问题,明确写「审阅通过,未发现阻断项」。
103
+ **写评审报告(必生成)** 到 `gate review` 回显的 `review_report` 路径(与 PRD 同级,由脚本确定性命名)。内容含:评分 + 评级 + 一句话总评、问题台账表(`ID` / 维度 / 严重程度 / 原文依据 / 影响章节 / 是否阻断 / 建议 / 产品回填列)、逐条问题。若无问题,明确写「审阅通过,未发现阻断项」。
115
104
 
116
105
  **阻断规则**:影响范围、用户行为、状态流转、数据契约、权限、异常处理或验收标准的问题标「阻断」。把当前未关闭的阻断问题数写入 gate:
117
106
 
@@ -26,14 +26,13 @@ comate 采用**简化需求流**:只保留 **PRD**(产品需求文档),通过 `/
26
26
 
27
27
  PRD 门禁状态钉在同级 `PRD-<三位序号>.gate.yaml`(机器真相源,含 `gate_status` / `score` / `blocking_open` / `prd_hash` / `spawned_changes`)。评审报告为 `PRD-<三位序号>-REVIEW.md`(人类可读,含问题台账)。PRD 通过门禁后被改会因哈希漂移自动失效(`stale`),需重跑 gate。
28
28
 
29
- 扇出的 comate change 内部用 proposal / design / tasks 承接 PRD 的功能项内容——**不再产独立 FRD 文档**,PRD 直接作为 change 的源文档被 `sources track` 钉住。
29
+ 扇出的 comate change 内部用 proposal / design / tasks 承接 PRD 的功能项内容,PRD 直接作为 change 的源文档被 `sources track` 钉住。
30
30
 
31
- ## 命名约定
32
-
33
- - 功能目录: `<YYYYMMDD-功能名>/`,日期为该功能需求起始日,功能名用中文短名,如 `20260805-积分商城/`
34
- - PRD: 目录内 `PRD-<三位序号>-<功能名>.md`,功能名用目录功能名,如 `PRD-001-积分商城.md`
35
- - gate: 自动生成,`PRD-<三位序号>.gate.yaml`(去 title,只用 PRD-NNN),与 PRD 同级,如 `PRD-001.gate.yaml`
36
- - 评审报告: 自动生成,`PRD-<三位序号>-REVIEW.md`,与 PRD 同级,如 `PRD-001-REVIEW.md`
31
+ ## 命名约定
32
+
33
+ - 功能目录:`<YYYYMMDD-功能名>/`
34
+ - PRD:目录内 `PRD-<三位序号>-<功能名>.md`
35
+ - gate 和评审报告:由脚本根据 PRD 路径自动生成,不手工命名。
37
36
 
38
37
  ## 横向 (integrations/)
39
38
 
@@ -1,7 +1,7 @@
1
1
  # PRD-<序号>-<功能名>: <产品标题>
2
2
 
3
- > 复制本文件到对应功能目录 `requirements/<YYYYMMDD-功能名>/`,按命名约定重命名(如 `PRD-001-积分商城.md`,功能名用目录功能名),删除本行与所有 `<...>` 占位说明。
4
- > PRD = 产品需求文档,回答「做成什么样」。**本 PRD 由 `/comate-gate` 直接读取**:先过质量门禁(审阅+评分+阻断),通过后按依赖/独立性拆成 1 或 N 个 comate change,由 comate 实现——无需再写 FRD。
3
+ > 复制本文件到需求目录,按项目约定命名,删除本行与所有 `<...>` 占位说明。
4
+ > PRD = 产品需求文档,回答「做成什么样」。本 PRD 由 `/comate-gate` 直接读取:先过质量门禁(审阅、评分、阻断检查),通过后按依赖和独立性拆成 1 或 N 个 comate change。
5
5
 
6
6
  | 字段 | 内容 |
7
7
  |------|------|