specline 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/cli.mjs +169 -0
- package/package.json +30 -0
- package/templates/.cursor/agents/specline-backend-dev.md +47 -0
- package/templates/.cursor/agents/specline-code-reviewer.md +51 -0
- package/templates/.cursor/agents/specline-frontend-dev.md +47 -0
- package/templates/.cursor/agents/specline-spec-creator.md +216 -0
- package/templates/.cursor/agents/specline-spec-reviewer.md +115 -0
- package/templates/.cursor/agents/specline-test-runner.md +98 -0
- package/templates/.cursor/agents/specline-test-writer.md +119 -0
- package/templates/.cursor/commands/specline-explore.md +173 -0
- package/templates/.cursor/commands/specline-pipeline.md +22 -0
- package/templates/.cursor/hooks/specline-agent-guard.sh +15 -0
- package/templates/.cursor/hooks/specline-auto-format.sh +12 -0
- package/templates/.cursor/hooks/specline-pipeline-gate.sh +682 -0
- package/templates/.cursor/hooks/specline-shell-guard.sh +18 -0
- package/templates/.cursor/hooks.json +25 -0
- package/templates/.cursor/skills/specline-apply-change/SKILL.md +140 -0
- package/templates/.cursor/skills/specline-archive-change/SKILL.md +114 -0
- package/templates/.cursor/skills/specline-explore/SKILL.md +288 -0
- package/templates/.cursor/skills/specline-pipeline/SKILL.md +674 -0
- package/templates/.cursor/skills/specline-propose/SKILL.md +79 -0
- package/templates/.specline-config.yaml +1 -0
- package/templates/specline/config.yaml +20 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specline-pipeline
|
|
3
|
+
description: >-
|
|
4
|
+
开发流水线元 Skill。编排 Spec → Coding → Review → Test → Archive 全流程,
|
|
5
|
+
调度子 Agent 和 Gate 脚本,支持断点续跑。输入自然语言需求自动推进到归档。
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /specline-pipeline 开发流水线编排 Skill
|
|
9
|
+
|
|
10
|
+
## 角色定位
|
|
11
|
+
|
|
12
|
+
你是**流水线编排者**,不是执行者。
|
|
13
|
+
|
|
14
|
+
**你做:**
|
|
15
|
+
- 读取 `.pipeline-state.json` 确定当前阶段和恢复点
|
|
16
|
+
- 启动子 Agent(specline-spec-creator, specline-spec-reviewer, specline-frontend-dev, specline-backend-dev, specline-code-reviewer, specline-test-writer, specline-test-runner)
|
|
17
|
+
- 解析 `tasks.md` 构建任务依赖 DAG,按批次并发派发 coding Agent
|
|
18
|
+
- 显式调用 `specline-pipeline-gate.sh` 进行门禁校验
|
|
19
|
+
- 根据 exit code 决策:前进 / 回退修复 / 暂停等人工确认
|
|
20
|
+
- 写入状态文件(子 Agent 完成后写入 `completed_at`)
|
|
21
|
+
|
|
22
|
+
**你不做:**
|
|
23
|
+
- 需求判断和 Spec 编写(由 specline-spec-creator 直接生成 4 个规划文件)
|
|
24
|
+
- 代码审查(由 specline-code-reviewer 负责)
|
|
25
|
+
- 门禁判断(由 specline-pipeline-gate.sh 负责,零 LLM 参与)
|
|
26
|
+
- 代码编写(由 coding agents 负责)
|
|
27
|
+
- 测试编写(由 specline-test-writer 负责)
|
|
28
|
+
|
|
29
|
+
## 入口模式
|
|
30
|
+
|
|
31
|
+
1. **新建流水线**: `/specline-pipeline <自然语言需求>`
|
|
32
|
+
2. **恢复流水线**: `/specline-pipeline --change <change-name>`
|
|
33
|
+
3. **自动发现**: `/specline-pipeline`(无参数,列出所有未完成流水线)
|
|
34
|
+
|
|
35
|
+
## 用户交互规范(省 request)
|
|
36
|
+
|
|
37
|
+
**所有需要用户做选择的交互,必须使用 `AskUserQuestion` 工具而非自由文本询问。** 结构化问题能让 Cursor 在单次请求中完成交互,避免额外轮次。
|
|
38
|
+
|
|
39
|
+
使用模式:
|
|
40
|
+
```javascript
|
|
41
|
+
AskUserQuestion({
|
|
42
|
+
title: "简洁标题",
|
|
43
|
+
questions: [{
|
|
44
|
+
id: "unique_id",
|
|
45
|
+
prompt: "问题描述",
|
|
46
|
+
options: [
|
|
47
|
+
{ id: "option_a", label: "选项 A 的描述" },
|
|
48
|
+
{ id: "option_b", label: "选项 B 的描述" }
|
|
49
|
+
],
|
|
50
|
+
allow_multiple: false // 单选;需要多选时设为 true
|
|
51
|
+
}]
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
各场景示例见下方。
|
|
56
|
+
|
|
57
|
+
## 流水线阶段定义
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
PHASE 1: SPEC
|
|
61
|
+
specline-spec-creator(直接生成 4 个 Artifact)→ specline-spec-reviewer
|
|
62
|
+
→ GATE: specline-pipeline-gate.sh spec
|
|
63
|
+
→ 🟡 HUMAN GATE: 人工确认 Spec & 任务规划
|
|
64
|
+
|
|
65
|
+
PHASE 2: CODING
|
|
66
|
+
解析 tasks.md → 按依赖层级分批并发派发 coding agents
|
|
67
|
+
→ GATE: specline-pipeline-gate.sh build
|
|
68
|
+
|
|
69
|
+
PHASE 3: CODE REVIEW
|
|
70
|
+
specline-code-reviewer → code-review.json
|
|
71
|
+
→ GATE: specline-pipeline-gate.sh lint
|
|
72
|
+
→ 🟡 HUMAN GATE: warnings 可选复核
|
|
73
|
+
|
|
74
|
+
PHASE 4: TEST
|
|
75
|
+
单元测试 → GATE → 集成测试 → GATE → E2E → GATE
|
|
76
|
+
(specline-test-writer 为黑盒,不能看实现代码)
|
|
77
|
+
|
|
78
|
+
PHASE 5: ARCHIVE
|
|
79
|
+
→ 🟡 HUMAN GATE: 归档确认
|
|
80
|
+
→ specline-pipeline-gate.sh archive --execute --change <change>
|
|
81
|
+
→ GATE: specline-pipeline-gate.sh archive
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 新建流水线执行流程 (Phase 1: SPEC)
|
|
85
|
+
|
|
86
|
+
### Step 1: 创建 Change
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
specline-pipeline-gate.sh new --change "<kebab-case-name>"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
初始化 `.pipeline-state.json`:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"version": 1,
|
|
97
|
+
"change_name": "<name>",
|
|
98
|
+
"created_at": "<ISO8601>",
|
|
99
|
+
"updated_at": "<ISO8601>",
|
|
100
|
+
"current_phase": "spec",
|
|
101
|
+
"current_step": "specline-spec-creator",
|
|
102
|
+
"phases": {
|
|
103
|
+
"spec": { "status": "in_progress", "retry_count": 0, "sub_phases": {}, "gates": { "spec_gate": { "passed": null }, "human_gate_1": { "passed": null } } },
|
|
104
|
+
"coding": { "status": "pending", "tasks": [], "sub_phases": {}, "gates": { "build_gate": { "passed": null } } },
|
|
105
|
+
"code_review": { "status": "pending", "retry_count": 0, "gates": { "lint_gate": { "passed": null }, "human_gate_2": { "passed": null } } },
|
|
106
|
+
"test": { "status": "pending", "framework": null, "sub_phases": { "unit": { "status": "pending", "gates": { "test_unit_gate": { "passed": null } } }, "integration": { "status": "pending", "gates": { "test_integration_gate": { "passed": null } } }, "e2e": { "status": "pending", "gates": { "test_e2e_gate": { "passed": null } } } } },
|
|
107
|
+
"archive": { "status": "pending", "gates": { "human_gate_3": { "passed": null }, "archive_gate": { "passed": null } } }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Step 2: 启动 specline-spec-creator
|
|
113
|
+
|
|
114
|
+
specline-spec-creator 子 Agent 的职责是根据内联模板直接生成全部规划文件:
|
|
115
|
+
- `proposal.md` — 需求提案(What/Why/Scope)
|
|
116
|
+
- `design.md` — 技术设计(架构/决策/数据流)
|
|
117
|
+
- `tasks.md` — 任务拆解清单(含 Type/Depends/Covers/Files 标注)
|
|
118
|
+
- `specs/<capability>/spec.md` — 功能规格(Requirements/Scenarios)
|
|
119
|
+
|
|
120
|
+
使用 Task 工具,subagent_type="specline-spec-creator",描述中传入 change name 和自然语言需求,让 specline-spec-creator 根据内联模板直接生成。
|
|
121
|
+
|
|
122
|
+
> **任务标注规范**:tasks.md 每个任务必须包含:
|
|
123
|
+
> - `Type`: frontend | backend | infra | db | config | docs
|
|
124
|
+
> - `Depends`: (none) | 任务编号
|
|
125
|
+
> - `Covers`: Requirement: xxx, Scenario: xxx(链接到 Spec,必填)
|
|
126
|
+
> - `Files`: 相对路径,列出本任务将修改/创建的所有文件(必填,用于冲突检测)
|
|
127
|
+
|
|
128
|
+
> **注意**:specline-spec-creator 直接按内联模板生成 4 个 Artifact 并自检输出完整性(含并行度 ≥ 60% 和 Files 无冲突自检)。
|
|
129
|
+
|
|
130
|
+
完成后写入状态:
|
|
131
|
+
```bash
|
|
132
|
+
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
133
|
+
jq --arg time "$NOW" '.updated_at = $time | .phases.spec.sub_phases["specline-spec-creator"] = {"status": "completed", "completed_at": $time}' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Step 3: 审核全部规划文件(specline-spec-reviewer)
|
|
137
|
+
|
|
138
|
+
specline-spec-reviewer 审核三份文件:
|
|
139
|
+
1. `specs/` 下所有 spec.md 的完整性和一致性
|
|
140
|
+
2. `design.md` 的技术决策合理性和覆盖完整性(新增)
|
|
141
|
+
3. `tasks.md` 的格式、独立性、覆盖度、文件冲突(新增)
|
|
142
|
+
|
|
143
|
+
产出 spec-review.json (`{ "status": "approved"|"rejected", "feedback": [...], "coverage": {...}, "task_stats": {...} }`)。
|
|
144
|
+
|
|
145
|
+
若 rejected:将 feedback 反馈给用户修改,或手动编辑相应文件后重新审核(最多 3 次循环)。
|
|
146
|
+
|
|
147
|
+
### Step 4: Spec Gate
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
.cursor/hooks/specline-pipeline-gate.sh spec --change "<name>"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
校验内容(在原有 spec.md 校验基础上增加):
|
|
154
|
+
- ✓ `proposal.md` 存在
|
|
155
|
+
- ✓ `design.md` 存在
|
|
156
|
+
- ✓ `tasks.md` 存在,且每个任务含 `Type:`、`Depends:`、`Covers:`、`Files:` 标注
|
|
157
|
+
- ✓ 每个 Requirement 至少被 1 个 task 的 `Covers:` 引用(通过 spec-review.json 的 coverage 字段)
|
|
158
|
+
- ✓ 第 1 批次任务的 `Files` 集合互不重叠
|
|
159
|
+
- ✓ 至少 1 个任务 `Depends: (none)`
|
|
160
|
+
|
|
161
|
+
exit code 0 = 通过,写入 passed。exit code != 0 = 失败,读取 stderr 展示给用户。
|
|
162
|
+
|
|
163
|
+
### Step 5: 人工确认 (Human Gate 1)
|
|
164
|
+
|
|
165
|
+
Spec Gate 通过后,使用 `AskUserQuestion` 工具请求确认。展示内容包括:需求提案摘要、功能需求列表、任务拆解概览(含并行组)。
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
AskUserQuestion({
|
|
169
|
+
title: "确认 Spec 和任务规划",
|
|
170
|
+
questions: [{
|
|
171
|
+
id: "spec_confirm",
|
|
172
|
+
prompt: "specline-spec-creator 已生成 spec / design / tasks 文件并通过格式校验。请确认:\n" +
|
|
173
|
+
"1. 需求描述是否准确?\n" +
|
|
174
|
+
"2. 任务拆解是否合理?独立任务是否足够?\n" +
|
|
175
|
+
"3. 验收场景是否覆盖核心路径和异常路径?",
|
|
176
|
+
options: [
|
|
177
|
+
{ id: "approve", label: "确认通过,继续编码阶段" },
|
|
178
|
+
{ id: "reject", label: "不通过,手动修改后重新审核" }
|
|
179
|
+
]
|
|
180
|
+
}]
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- `approve` → 写入 `human_gate_1.passed = true`,进入 Phase 2
|
|
185
|
+
- `reject` → 等待用户修改 spec/tasks 文件后,回到 Step 3 重新审核
|
|
186
|
+
|
|
187
|
+
## Phase 2: CODING 阶段
|
|
188
|
+
|
|
189
|
+
> **并行加速**:Human Gate 1 通过后,**同时**启动 coding 和 specline-test-writer。specline-test-writer 是黑盒的——只需要 Spec 文档,不需要实现代码。两者并行可节省 specline-test-writer 的编写时间。
|
|
190
|
+
|
|
191
|
+
### Step 6: 并行启动 specline-test-writer(与 Coding 并发)
|
|
192
|
+
|
|
193
|
+
**6a. 启动 specline-test-writer(与 Coding 阶段 Agent 同时启动)**:
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// 在派发 coding agent 的同时,启动 specline-test-writer
|
|
197
|
+
Task({
|
|
198
|
+
subagent_type: "specline-test-writer",
|
|
199
|
+
description: `编写测试: ${changeName}`,
|
|
200
|
+
prompt: `
|
|
201
|
+
你收到一个测试编写任务。请基于 Spec 编写所有测试用例。
|
|
202
|
+
|
|
203
|
+
## 上下文文件(只读参考)
|
|
204
|
+
- Spec: specline/changes/${changeName}/specs/*/spec.md
|
|
205
|
+
- Tasks: specline/changes/${changeName}/tasks.md
|
|
206
|
+
|
|
207
|
+
## 关键约束
|
|
208
|
+
1. 你是黑盒测试工程师,不能读取实现源代码
|
|
209
|
+
2. 先检测项目的测试框架(读配置文件),按项目实际语言和框架编写测试
|
|
210
|
+
3. 每个 Scenario 至少生成 1 个对应的测试函数(命名遵循框架约定)
|
|
211
|
+
4. 基于 Covers 追溯链,确保每个 Scenario 都有测试
|
|
212
|
+
|
|
213
|
+
## 产出报告
|
|
214
|
+
完成后在 .cursor/tmp/test-code-result.json 写入状态(含 test_framework / language / test_dir / scenarios_covered 等字段)
|
|
215
|
+
`
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
specline-test-writer 在 Coding 阶段并行执行,不阻塞 coding agent 的派发。只有当 Coding 全部完成进入 TEST 阶段时,才需要等待 specline-test-writer 完成。
|
|
220
|
+
|
|
221
|
+
**6b. 解析 tasks.md,构建任务 DAG**:
|
|
222
|
+
|
|
223
|
+
读取 `specline/changes/<name>/tasks.md`,解析每个任务的 `Type`、`Depends`、`Covers`、`Files` 标注,构建依赖关系图,划分为多个**并行批次**。
|
|
224
|
+
|
|
225
|
+
> **断点续跑时的任务状态恢复**:解析 tasks.md 时同时读取每个任务的 checkbox 状态(`[x]` 表示已完成,`[ ]` 表示未完成)。已完成的任务在 DAG 中标记为 `status: "completed"`,后续批次派发时自动跳过。
|
|
226
|
+
|
|
227
|
+
解析算法(jq + grep):
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
TASKS_FILE="specline/changes/<name>/tasks.md"
|
|
231
|
+
|
|
232
|
+
# 提取每个任务的核心元数据
|
|
233
|
+
# 期望输出格式:TASK_NUM|TYPE|DEPS|COVERS|FILES
|
|
234
|
+
rg -N --no-line-number '^## \d+\.|^- \*\*Type\*\*:|^- \*\*Depends\*\*:|^- \*\*Covers\*\*:|^- \*\*Files\*\*:' "$TASKS_FILE" | while read -r line; do
|
|
235
|
+
# 解析并按批次分组
|
|
236
|
+
...
|
|
237
|
+
done
|
|
238
|
+
|
|
239
|
+
# 构建任务列表写入状态文件(含 Files 用于冲突检测,Covers 用于追溯)
|
|
240
|
+
jq --argjson tasks '[
|
|
241
|
+
{"id":"1","type":"backend","deps":[],"batch":1,"status":"pending","covers":"Requirement: 数据模型","files":["server/models.py"]},
|
|
242
|
+
{"id":"2","type":"frontend","deps":[],"batch":1,"status":"pending","covers":"Requirement: 登录页面","files":["src/components/Login.tsx"]}
|
|
243
|
+
]' '.phases.coding.tasks = $tasks' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
批次划分规则:
|
|
247
|
+
- 批次 1:所有 `Depends: (none)` 的任务
|
|
248
|
+
- 批次 N:所有依赖仅限于 1..N-1 批次内已完成任务的任务
|
|
249
|
+
|
|
250
|
+
### Step 6c: 文件冲突检测(每批次派发前)
|
|
251
|
+
|
|
252
|
+
在派发每批任务之前,检查该批次中所有任务的 `Files` 集合是否有交集:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# 伪代码:检测当前批次任务的文件冲突
|
|
256
|
+
# 读取当前批次所有任务的 files 数组
|
|
257
|
+
# 如果有任意两个任务的 files 有交集 → 标记冲突任务对,暂停并报告用户
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
如果检测到文件冲突:
|
|
261
|
+
1. 标记冲突的任务为 `status: "conflict"`
|
|
262
|
+
2. 暂停流水线,告知用户哪两个任务的文件范围重叠
|
|
263
|
+
3. 用户可以手动调整 tasks.md 后恢复
|
|
264
|
+
|
|
265
|
+
### Step 7: 按批次并发派发 Coding Agent
|
|
266
|
+
|
|
267
|
+
对每个批次依次处理:
|
|
268
|
+
|
|
269
|
+
**7a. 同一批次内所有任务并发派发**,根据 Type 选择对应的 agent:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
Type: frontend → subagent_type: "specline-frontend-dev"
|
|
273
|
+
Type: backend → subagent_type: "specline-backend-dev"
|
|
274
|
+
Type: infra → subagent_type: "specline-backend-dev"(基础设施类,用后端 agent 处理)
|
|
275
|
+
Type: db → subagent_type: "specline-backend-dev"(数据库迁移,用后端 agent 处理)
|
|
276
|
+
Type: config → 编排者直接处理(简单的文件创建/修改,不启动 agent)
|
|
277
|
+
Type: docs → 编排者直接处理(文档编写,不启动 agent)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
每个任务启动一个独立的子 Agent:
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
for (const task of currentBatchTasks) {
|
|
284
|
+
if (task.type === "config" || task.type === "docs") {
|
|
285
|
+
// 简单文件操作,编排者直接写入
|
|
286
|
+
// 不启动 agent,节省资源
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
Task({
|
|
291
|
+
subagent_type: task.type === "frontend" ? "specline-frontend-dev" : "specline-backend-dev",
|
|
292
|
+
description: `实现任务 ${task.id}: ${task.title} [${task.type}]`,
|
|
293
|
+
prompt: `
|
|
294
|
+
你收到一个编码任务(Type: ${task.type}),请只实现本任务范围内的代码。
|
|
295
|
+
|
|
296
|
+
## 上下文文件(只读参考)
|
|
297
|
+
- Spec: specline/changes/${changeName}/specs/${capability}/spec.md
|
|
298
|
+
- Design: specline/changes/${changeName}/design.md
|
|
299
|
+
- Tasks: specline/changes/${changeName}/tasks.md
|
|
300
|
+
|
|
301
|
+
## 当前任务(只实现这个)
|
|
302
|
+
任务 ID: ${task.id}
|
|
303
|
+
覆盖需求: ${task.covers}
|
|
304
|
+
预期文件: ${task.files}
|
|
305
|
+
|
|
306
|
+
从 tasks.md 中提取的任务 ${task.id} 的完整描述:
|
|
307
|
+
---
|
|
308
|
+
${task.content}
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 约束
|
|
312
|
+
1. 只修改本任务 Files 范围内的文件
|
|
313
|
+
2. 不修改其他任务负责的文件
|
|
314
|
+
3. 与已完成任务的接口约定必须遵守(参考已生成的接口/类型定义文件)
|
|
315
|
+
4. 确认过 design.md 中的技术决策后再动手
|
|
316
|
+
5. **完成后必须将 tasks.md 中本任务的 `[ ]` 改为 `[x]`**(方便断点续跑识别进度)
|
|
317
|
+
|
|
318
|
+
## 产出报告
|
|
319
|
+
完成后在 .cursor/tmp/task-${task.id}-result.json 写入:
|
|
320
|
+
{
|
|
321
|
+
"task_id": "${task.id}",
|
|
322
|
+
"type": "${task.type}",
|
|
323
|
+
"covers": "${task.covers}",
|
|
324
|
+
"status": "completed",
|
|
325
|
+
"files_changed": [...],
|
|
326
|
+
"summary": "..."
|
|
327
|
+
}
|
|
328
|
+
`
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**7b. 等待当前批次所有 Agent 完成后**:
|
|
334
|
+
1. 验证每个 Agent 的产出报告(`.cursor/tmp/task-<id>-result.json`)
|
|
335
|
+
2. 更新状态文件中对应 task 的 `status` 和 `completed_at`
|
|
336
|
+
3. **验证 tasks.md 中对应任务的 checkbox 已从 `[ ]` 变为 `[x]`**(如果未标记,自动补标)
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# 更新状态文件
|
|
340
|
+
jq --arg task_id "1" --arg time "$NOW" '
|
|
341
|
+
.phases.coding.tasks |= map(
|
|
342
|
+
if .id == $task_id then .status = "completed" | .completed_at = $time else . end
|
|
343
|
+
)
|
|
344
|
+
' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
345
|
+
|
|
346
|
+
# 如果 Agent 忘记标记 tasks.md,自动补标
|
|
347
|
+
# sed 将 "## <task_id>. [ ]" 替换为 "## <task_id>. [x]"
|
|
348
|
+
sed -i '' "s/^## ${task_id}\. \[ \]/## ${task_id}. [x]/" specline/changes/<name>/tasks.md
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**7c. 检查是否有下一批次**。如有,回到 6c(冲突检测)→ 7a 继续派发。
|
|
352
|
+
|
|
353
|
+
### Step 8: Build Gate
|
|
354
|
+
|
|
355
|
+
全部批次完成后,运行 Build Gate:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
.cursor/hooks/specline-pipeline-gate.sh build --change "<name>"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
失败 → 分析失败原因,定位到具体任务:
|
|
362
|
+
|
|
363
|
+
**8a. 单个任务构建失败** → 回对应 coding agent 修复(最多 2 次循环)
|
|
364
|
+
|
|
365
|
+
**8b. 接口不兼容** → 计算影响范围,只重置受影响的下游任务:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
# 影响范围分析:基于 tasks.md 的 Depends 关系,计算受影响的下游任务
|
|
369
|
+
# 例如:Task 1 的 API 签名改了 → Task 3 (Depends: 1)、Task 5 (Depends: 1,3) 需要重跑
|
|
370
|
+
# Task 2 无依赖关系 → 不受影响,保持 completed
|
|
371
|
+
|
|
372
|
+
AFFECTED_TASK_IDS=("3" "5") # 从 DAG 计算得出
|
|
373
|
+
|
|
374
|
+
for tid in "${AFFECTED_TASK_IDS[@]}"; do
|
|
375
|
+
jq --arg tid "$tid" '
|
|
376
|
+
.phases.coding.tasks |= map(
|
|
377
|
+
if .id == $tid then .status = "pending" | .completed_at = null else . end
|
|
378
|
+
)
|
|
379
|
+
' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
380
|
+
done
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
影响范围算法:
|
|
384
|
+
1. 找到被修改的任务 ID 集合 M
|
|
385
|
+
2. 遍历所有任务,如果某任务的 Depends 列表中包含 M 中任一 ID,则加入受影响集合
|
|
386
|
+
3. 递归执行第 2 步直到不再扩展
|
|
387
|
+
|
|
388
|
+
**8c. Build Gate 重置**:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
jq '.phases.coding.gates.build_gate.passed = null' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
修复后**只重跑受影响的任务**(按原批次顺序),未受影响的任务保持 completed 状态。
|
|
395
|
+
|
|
396
|
+
## Phase 3: CODE REVIEW 阶段
|
|
397
|
+
|
|
398
|
+
### Step 9: 启动 specline-code-reviewer
|
|
399
|
+
|
|
400
|
+
specline-code-reviewer 审查代码变更。审查时利用 tasks.md 的 `Covers` 追溯链:每个 finding 应标注涉及的文件和对应的 Requirement/Scenario。
|
|
401
|
+
|
|
402
|
+
产出 code-review.json (`{ "findings": [{ "severity": "error"|"warning", "file": "...", "covers": "Requirement: xxx", "message": "..." }] }`)。
|
|
403
|
+
|
|
404
|
+
### Step 10: Lint Gate
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
.cursor/hooks/specline-pipeline-gate.sh lint --change "<name>"
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
检查 eslint/ruff 退出码 + code-review.json 中 error 计数。
|
|
411
|
+
|
|
412
|
+
失败 → 根据 findings 的 `file` 和 `covers` 字段定位到具体任务,只回对应 coding agent 修复(最多 2 次)。
|
|
413
|
+
|
|
414
|
+
### Step 11: 可选人工复核 (Human Gate 2)
|
|
415
|
+
|
|
416
|
+
仅当 code-review.json 中 warnings > 0 且 errors = 0 时,使用 `AskUserQuestion`:
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
AskUserQuestion({
|
|
420
|
+
title: "代码审查复核",
|
|
421
|
+
questions: [{
|
|
422
|
+
id: "review_check",
|
|
423
|
+
prompt: "代码审查发现 " + warning_count + " 个警告,0 个错误。是否需要人工复核?",
|
|
424
|
+
options: [
|
|
425
|
+
{ id: "skip", label: "无需复核,自动继续测试阶段" },
|
|
426
|
+
{ id: "review", label: "需要人工复核" }
|
|
427
|
+
]
|
|
428
|
+
}]
|
|
429
|
+
})
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
- `skip`(默认)→ 自动继续
|
|
433
|
+
- `review` → 展示警告详情,等待人工处理
|
|
434
|
+
|
|
435
|
+
## Phase 4: TEST 阶段
|
|
436
|
+
|
|
437
|
+
### Step 12: 确认 specline-test-writer 完成
|
|
438
|
+
|
|
439
|
+
specline-test-writer 已在 Phase 2(Step 6a)与 Coding 并行启动。进入 TEST 阶段时,检查 specline-test-writer 是否已完成:
|
|
440
|
+
|
|
441
|
+
- 已完成 → 读取 `.cursor/tmp/test-code-result.json` 获取 `test_framework`,写入 `.pipeline-state.json`:
|
|
442
|
+
```bash
|
|
443
|
+
FRAMEWORK=$(jq -r '.test_framework' .cursor/tmp/test-code-result.json)
|
|
444
|
+
jq --arg fw "$FRAMEWORK" '.phases.test.framework = $fw' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
445
|
+
```
|
|
446
|
+
然后直接进入测试执行
|
|
447
|
+
- 未完成 → 等待 specline-test-writer 完成(展示等待状态),完成后同上写入框架信息
|
|
448
|
+
|
|
449
|
+
> `test_framework` 写入状态文件后,后续 `specline-pipeline-gate.sh` 的 test gate 会自动读取并选择正确的测试命令(Jest/pytest/go test 等)。
|
|
450
|
+
|
|
451
|
+
> **黑盒约束回顾**:specline-test-writer 只能基于 Spec 文档编写测试,不能读取任何实现源代码。specline-test-writer 会自动检测项目测试框架(Jest/pytest/go test 等),按项目实际语言编写测试。
|
|
452
|
+
|
|
453
|
+
### Step 13: 测试门禁链(串行)
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
# 单元测试
|
|
457
|
+
.cursor/hooks/specline-pipeline-gate.sh test-unit --change "<name>"
|
|
458
|
+
# 集成测试
|
|
459
|
+
.cursor/hooks/specline-pipeline-gate.sh test-integration --change "<name>"
|
|
460
|
+
# E2E 测试
|
|
461
|
+
.cursor/hooks/specline-pipeline-gate.sh test-e2e --change "<name>"
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
任何测试失败 → specline-test-runner 分析原因:
|
|
465
|
+
- 测试代码问题 → specline-test-writer 自修(最多 2 次)
|
|
466
|
+
- 实现代码问题 → 利用 `Covers` 追溯链定位到具体任务,只回对应 coding agent 修复 → **使用影响范围算法精确重置受影响任务的 Gate**
|
|
467
|
+
- `spec_ambiguity`(Spec 模糊)→ **不自动循环修复**,暂停流水线并展示模糊点给用户,等待用户澄清 Spec 后继续
|
|
468
|
+
- 循环最多 2 次
|
|
469
|
+
|
|
470
|
+
代码修复后 Gate 重置:
|
|
471
|
+
```bash
|
|
472
|
+
jq '
|
|
473
|
+
.phases.test.sub_phases.unit.gates.test_unit_gate.passed = null |
|
|
474
|
+
.phases.test.sub_phases.integration.gates.test_integration_gate.passed = null |
|
|
475
|
+
.phases.test.sub_phases.e2e.gates.test_e2e_gate.passed = null
|
|
476
|
+
' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Phase 5: ARCHIVE 阶段
|
|
480
|
+
|
|
481
|
+
### Step 14: 归档确认 (Human Gate 3)
|
|
482
|
+
|
|
483
|
+
全部测试通过后,使用 `AskUserQuestion` 请求归档确认:
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
AskUserQuestion({
|
|
487
|
+
title: "归档确认",
|
|
488
|
+
questions: [{
|
|
489
|
+
id: "archive_confirm",
|
|
490
|
+
prompt: "全部测试通过。变更摘要:新增 " + new_files + " 个文件,修改 " + modified_files + " 个文件。是否归档此变更?",
|
|
491
|
+
options: [
|
|
492
|
+
{ id: "archive", label: "确认归档" },
|
|
493
|
+
{ id: "cancel", label: "暂不归档" }
|
|
494
|
+
]
|
|
495
|
+
}]
|
|
496
|
+
})
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
- `archive` → 执行 specline-pipeline-gate.sh archive --execute --change
|
|
500
|
+
- `cancel` → 暂停流水线,保留状态文件待后续继续
|
|
501
|
+
|
|
502
|
+
### Step 15: 归档
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
specline-pipeline-gate.sh archive --execute --change "<name>"
|
|
506
|
+
.cursor/hooks/specline-pipeline-gate.sh archive --change "<name>"
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## 断点续跑流程
|
|
512
|
+
|
|
513
|
+
### 发现未完成流水线
|
|
514
|
+
|
|
515
|
+
扫描 `specline/changes/*/.pipeline-state.json`:
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
for f in specline/changes/*/.pipeline-state.json; do
|
|
519
|
+
PHASE=$(jq -r '.current_phase' "$f")
|
|
520
|
+
STATUS=$(jq -r '.phases."'"$PHASE"'".status' "$f")
|
|
521
|
+
if [ "$STATUS" != "completed" ] && [ "$PHASE" != "archive" ]; then
|
|
522
|
+
echo "$(basename $(dirname $f)): phase=$PHASE"
|
|
523
|
+
fi
|
|
524
|
+
done
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**使用 AskUserQuestion 让用户选择:**
|
|
528
|
+
|
|
529
|
+
- 只有 1 个未完成 → 直接展示进度摘要确认
|
|
530
|
+
- 有多个未完成 → 动态构建 options 列表:
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
AskUserQuestion({
|
|
534
|
+
title: "发现未完成流水线",
|
|
535
|
+
questions: [{
|
|
536
|
+
id: "pipeline_select",
|
|
537
|
+
prompt: "发现以下未完成的流水线,请选择要继续的:",
|
|
538
|
+
options: [
|
|
539
|
+
{ id: "change-a", label: "change-a (SPEC 阶段已完成)" },
|
|
540
|
+
{ id: "change-b", label: "change-b (CODING 阶段进行中)" }
|
|
541
|
+
// ... 动态生成
|
|
542
|
+
],
|
|
543
|
+
allow_multiple: false
|
|
544
|
+
}]
|
|
545
|
+
})
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
用户选择后:
|
|
549
|
+
- 指定 change → 加载状态文件,计算恢复点
|
|
550
|
+
- 无需再询问"是否继续",直接展示恢复摘要并自动开始恢复
|
|
551
|
+
|
|
552
|
+
### 恢复算法
|
|
553
|
+
|
|
554
|
+
从后往前扫描 `phases` 中每个阶段的 `gates`,找到最后一个 `passed: true` 且 `run_at` 有值的门禁:
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
RESTORE_POINT="spec" # 默认从 spec 开始
|
|
558
|
+
|
|
559
|
+
for phase in archive test code_review coding spec; do
|
|
560
|
+
GATES=$(jq -r ".phases.${phase}.gates | keys[]" "$STATE_FILE" 2>/dev/null)
|
|
561
|
+
for gate in $GATES; do
|
|
562
|
+
PASSED=$(jq -r ".phases.${phase}.gates.${gate}.passed" "$STATE_FILE")
|
|
563
|
+
if [ "$PASSED" = "true" ]; then
|
|
564
|
+
# 找到最后通过的 gate,下一阶段为恢复点
|
|
565
|
+
case "$phase" in
|
|
566
|
+
spec) RESTORE_POINT="coding";;
|
|
567
|
+
coding) RESTORE_POINT="code_review";;
|
|
568
|
+
code_review) RESTORE_POINT="test";;
|
|
569
|
+
test) RESTORE_POINT="archive";;
|
|
570
|
+
esac
|
|
571
|
+
fi
|
|
572
|
+
done
|
|
573
|
+
done
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### 重置不可信子阶段
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
# 将恢复阶段中 completed_at 为空的子阶段重置为 pending
|
|
580
|
+
jq --arg phase "$RESTORE_POINT" '
|
|
581
|
+
if .phases[$phase].sub_phases then
|
|
582
|
+
.phases[$phase].sub_phases |= with_entries(
|
|
583
|
+
if .value.completed_at == null then
|
|
584
|
+
.value.status = "pending"
|
|
585
|
+
else . end
|
|
586
|
+
)
|
|
587
|
+
else . end
|
|
588
|
+
' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### 从 tasks.md 恢复已完成任务状态
|
|
592
|
+
|
|
593
|
+
恢复到 CODING 阶段时,必须先读取 tasks.md 的 checkbox 状态,与 `.pipeline-state.json` 交叉校验:
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# 从 tasks.md 提取 checkbox 状态
|
|
597
|
+
# "## 1. [x]" → task 1 已完成, "## 2. [ ]" → task 2 未完成
|
|
598
|
+
grep -n '^## \d\+\.' specline/changes/<name>/tasks.md | while read line; do
|
|
599
|
+
task_id=$(echo "$line" | sed 's/.*## \([0-9]*\)\. .*/\1/')
|
|
600
|
+
if echo "$line" | grep -q '\[x\]'; then
|
|
601
|
+
# 同步到状态文件
|
|
602
|
+
jq --arg tid "$task_id" '
|
|
603
|
+
.phases.coding.tasks |= map(
|
|
604
|
+
if .id == $tid then .status = "completed" else . end
|
|
605
|
+
)' "$STATE_FILE" > tmp && mv tmp "$STATE_FILE"
|
|
606
|
+
fi
|
|
607
|
+
done
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
这个交叉校验确保:即使 `.pipeline-state.json` 丢失或损坏,tasks.md 的 `[x]`/`[ ]` 标记仍可作为任务进度的可靠来源。
|
|
611
|
+
|
|
612
|
+
### 展示恢复摘要
|
|
613
|
+
|
|
614
|
+
计算恢复点后,**直接开始恢复,不需要再次人工确认**(用户选择 pipeline 时已确认意图)。
|
|
615
|
+
|
|
616
|
+
如果只有一个未完成流水线,使用 `AskUserQuestion` 做一次快速确认:
|
|
617
|
+
|
|
618
|
+
```javascript
|
|
619
|
+
AskUserQuestion({
|
|
620
|
+
title: "恢复流水线",
|
|
621
|
+
questions: [{
|
|
622
|
+
id: "resume_confirm",
|
|
623
|
+
prompt: "变更: " + change_name + "\n已完成: SPEC 阶段\n未完成: CODING 阶段\n将从 CODING 阶段继续。",
|
|
624
|
+
options: [
|
|
625
|
+
{ id: "continue", label: "继续执行" },
|
|
626
|
+
{ id: "cancel", label: "取消" }
|
|
627
|
+
]
|
|
628
|
+
}]
|
|
629
|
+
})
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## 状态写入规则
|
|
633
|
+
|
|
634
|
+
所有状态写入由 Gate 脚本或 Skill 编排逻辑完成,**不使用 LLM 写入状态**:
|
|
635
|
+
|
|
636
|
+
- Gate 脚本通过后自动写入 `gate.passed = true`
|
|
637
|
+
- 子 Agent 完成后 Skill 写入 `completed_at`
|
|
638
|
+
- 代码修复后 Skill 重置相关 gates 为 null
|
|
639
|
+
|
|
640
|
+
## 结构化事件日志(可观测性)
|
|
641
|
+
|
|
642
|
+
每个关键事件追加写入 `specline/changes/<name>/pipeline-events.jsonl`(JSON Lines 格式,每行一个事件):
|
|
643
|
+
|
|
644
|
+
日志事件类型:
|
|
645
|
+
|
|
646
|
+
```json
|
|
647
|
+
{"ts":"...","event":"pipeline_start","change":"<name>"}
|
|
648
|
+
{"ts":"...","event":"phase_transition","from":"spec","to":"coding"}
|
|
649
|
+
{"ts":"...","event":"agent_start","agent":"specline-spec-creator","task":null}
|
|
650
|
+
{"ts":"...","event":"agent_done","agent":"specline-spec-creator","result":"completed"}
|
|
651
|
+
{"ts":"...","event":"agent_start","agent":"specline-frontend-dev","task":"1","type":"frontend"}
|
|
652
|
+
{"ts":"...","event":"agent_done","agent":"specline-frontend-dev","task":"1","result":"completed","files_changed":["..."],"duration_ms":45200}
|
|
653
|
+
{"ts":"...","event":"gate_run","phase":"build","exit_code":0,"passed":true}
|
|
654
|
+
{"ts":"...","event":"gate_run","phase":"lint","exit_code":1,"passed":false,"stderr":"..."}
|
|
655
|
+
{"ts":"...","event":"conflict_detected","tasks":["1","2"],"overlap_files":["src/utils/api.ts"]}
|
|
656
|
+
{"ts":"...","event":"retry","phase":"coding","task":"3","attempt":2,"reason":"build_failure"}
|
|
657
|
+
{"ts":"...","event":"pipeline_pause","reason":"human_gate_1"}
|
|
658
|
+
{"ts":"...","event":"pipeline_resume","from_phase":"coding"}
|
|
659
|
+
{"ts":"...","event":"pipeline_complete","change":"<name>"}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**写入原则**:
|
|
663
|
+
- 每个事件一行,JSON 对象结尾无逗号
|
|
664
|
+
- 任何编排动作(启动 agent、运行 gate、状态转换)都写入事件日志
|
|
665
|
+
- Gate 脚本不写事件日志(Gate 是无状态的),仅编排层写入
|
|
666
|
+
- 事件日志用于人工排查问题和统计分析,不影响流水线决策
|
|
667
|
+
|
|
668
|
+
## 关键约束
|
|
669
|
+
|
|
670
|
+
1. **不做判断,只做编排**:不要评估代码质量、需求好坏、测试覆盖——这些由子 Agent 和 Gate 脚本负责
|
|
671
|
+
2. **所有门禁通过 `specline-pipeline-gate.sh` 执行**:不要自己写 grep/检查逻辑,调用脚本即可
|
|
672
|
+
3. **状态文件是唯一真相源**:所有决策基于 `.pipeline-state.json` 的当前值
|
|
673
|
+
4. **人工确认点必须暂停**:不要自动跳过 human_gate
|
|
674
|
+
5. **测试 Agent 必须黑盒**:不给 specline-test-writer 传递源代码文件路径
|