specline 1.4.0 → 2.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.
Files changed (75) hide show
  1. package/README.md +132 -125
  2. package/adapters/claude/deploy.json +12 -0
  3. package/adapters/claude/hooks/hooks.json +12 -0
  4. package/adapters/claude/hooks.json +12 -0
  5. package/adapters/claude/orchestration.md +17 -0
  6. package/adapters/codex/agent.toml.hbs +7 -0
  7. package/adapters/codex/deploy.json +12 -0
  8. package/adapters/codex/hooks.json +12 -0
  9. package/adapters/codex/orchestration.md +18 -0
  10. package/adapters/cursor/deploy.json +12 -0
  11. package/adapters/cursor/hooks.json +9 -0
  12. package/adapters/cursor/orchestration.md +17 -0
  13. package/adapters/opencode/deploy.json +12 -0
  14. package/adapters/opencode/orchestration.md +18 -0
  15. package/adapters/opencode/plugin.js +10 -0
  16. package/cli.mjs +161 -558
  17. package/core/agents/specline-backend-dev.yaml +45 -0
  18. package/core/agents/specline-code-reviewer.yaml +67 -0
  19. package/core/agents/specline-config-dev.yaml +50 -0
  20. package/core/agents/specline-config-reviewer.yaml +70 -0
  21. package/core/agents/specline-explore-assistant.yaml +79 -0
  22. package/core/agents/specline-frontend-dev.yaml +45 -0
  23. package/core/agents/specline-spec-creator.yaml +58 -0
  24. package/core/agents/specline-spec-reviewer.yaml +58 -0
  25. package/core/agents/specline-test-runner.yaml +62 -0
  26. package/core/agents/specline-test-writer.yaml +67 -0
  27. package/core/bootstrap/using-specline.md +14 -0
  28. package/core/gates/pipeline-gate-checks/a1-covers-ref.sh +125 -0
  29. package/core/gates/pipeline-gate-checks/a2-a3-reverse.sh +171 -0
  30. package/core/gates/pipeline-gate-checks/c1-exception.sh +71 -0
  31. package/core/gates/pipeline-gate-checks/c2-vague.sh +60 -0
  32. package/core/gates/pipeline-gate-checks/common.sh +68 -0
  33. package/core/gates/pipeline-gate-checks/d1-cycle.sh +149 -0
  34. package/core/gates/pipeline-gate-checks/d3-type-file.sh +260 -0
  35. package/core/gates/pipeline-gate.sh +1456 -0
  36. package/core/hooks/session-start.sh +259 -0
  37. package/core/skills/specline-apply-change/SKILL.md +197 -0
  38. package/core/skills/specline-archive-change/SKILL.md +173 -0
  39. package/core/skills/specline-explore/SKILL.md +504 -0
  40. package/core/skills/specline-knowledge/SKILL.md +539 -0
  41. package/core/skills/specline-pipeline/SKILL.md +604 -0
  42. package/core/skills/specline-pipeline/references/error-recovery-details.md +49 -0
  43. package/core/skills/specline-pipeline/references/event-log-spec.md +59 -0
  44. package/core/skills/specline-pipeline/references/pipeline-state-schema.md +87 -0
  45. package/core/skills/specline-pipeline/templates/subagent-prompts.md +397 -0
  46. package/core/skills/specline-propose/SKILL.md +186 -0
  47. package/core/skills/specline-quickfix/SKILL.md +289 -0
  48. package/core/templates/AGENTS.md.hbs +5 -0
  49. package/core/templates/specline/config.yaml +15 -0
  50. package/lib/deploy-claude.mjs +80 -0
  51. package/lib/deploy-codex.mjs +77 -0
  52. package/lib/deploy-opencode.mjs +93 -0
  53. package/lib/deploy.mjs +668 -0
  54. package/lib/gate.mjs +103 -0
  55. package/lib/hash.mjs +13 -0
  56. package/lib/hook.mjs +105 -0
  57. package/lib/init.mjs +122 -0
  58. package/lib/lock.mjs +99 -0
  59. package/lib/merge.mjs +184 -0
  60. package/lib/paths.mjs +40 -0
  61. package/lib/platforms.mjs +74 -0
  62. package/lib/render-agents.mjs +88 -0
  63. package/lib/render.mjs +126 -0
  64. package/lib/sync.mjs +253 -0
  65. package/lib/tty-select.mjs +89 -0
  66. package/package.json +4 -1
  67. package/templates/.cursor/README.md +18 -0
  68. package/templates/.cursor/agents/specline-code-reviewer.md +18 -2
  69. package/templates/.cursor/agents/specline-spec-creator.md +51 -2
  70. package/templates/.cursor/agents/specline-test-runner.md +10 -1
  71. package/templates/.cursor/agents/specline-test-writer.md +58 -7
  72. package/templates/.cursor/hooks/specline-pipeline-gate-checks/a2-a3-reverse.sh +1 -1
  73. package/templates/.cursor/hooks/specline-pipeline-gate.sh +118 -0
  74. package/templates/.cursor/skills/specline-pipeline/SKILL.md +10 -4
  75. package/templates/.cursor/skills/specline-propose/SKILL.md +3 -3
@@ -14,6 +14,11 @@ description: 审查代码变更的质量、安全性和最佳实践。产出结
14
14
  5. **错误处理**:异常是否被妥善捕获和处理
15
15
  6. **测试友好**:代码是否易于测试
16
16
  7. **合同一致性**:实现是否与 Spec 中本任务覆盖的 Scenario 一致?任务声称覆盖的 Requirement 是否真的被满足?代码行为是否与 Spec 描述的 WHEN/THEN 语义吻合?
17
+ 8. **契约一致性**(新增):若 `design.md` 包含「对外接口契约」章节,检查实现代码是否与契约一致:
18
+ - CLI 命令:命令名是否注册?参数签名是否匹配?
19
+ - HTTP 端点:路径是否被注册?请求/响应格式是否匹配?
20
+ - 模块导出:导出符号是否存在?函数签名是否匹配?
21
+ - 契约偏离标记为 `contract_mismatch`,severity 为 `error`(阻断)
17
22
  8. **架构合规性**:实现代码是否符合 design.md 的 Architecture Impact Analysis 章节?
18
23
  - 新增代码所在的模块/层级是否与 Impact Analysis 中声明的模块边界一致?
19
24
  - 依赖方向是否遵守 Impact Analysis 中分析的依赖方向约束(违规 → error)?
@@ -41,10 +46,12 @@ description: 审查代码变更的质量、安全性和最佳实践。产出结
41
46
 
42
47
  1. 查看 git diff 获取变更文件列表
43
48
  2. 对照 `specline/changes/<change-name>/tasks.md` 中的 `Covers` 追溯链,知道每个文件属于哪个任务、覆盖哪个 Requirement
44
- 3. 读取 `specline/changes/<change-name>/design.md` 的 Architecture Impact Analysis 章节,作为架构合规性审查的基准
49
+ 3. 读取 `specline/changes/<change-name>/design.md`:
50
+ - Architecture Impact Analysis 章节,作为架构合规性审查的基准
51
+ - 如果存在「对外接口契约」章节,作为契约一致性审查的基准
45
52
  4. 逐一审查变更代码
46
53
  5. 每个发现标记 severity:`error`(必须修复)或 `warning`(建议改进)
47
- 6. 每个发现标注 `type`:`architecture`(架构违规)/ `security`(安全)/ `logic`(逻辑错误)/ `style`(风格)/ `unit_test_quality`(测试质量)/ `other`
54
+ 6. 每个发现标注 `type`:`contract_mismatch`(契约不一致)/ `architecture`(架构违规)/ `security`(安全)/ `logic`(逻辑错误)/ `style`(风格)/ `unit_test_quality`(测试质量)/ `other`
48
55
  7. 每个发现标注 `covers`:对应的 Requirement 名称(从 tasks.md 的 Covers 中获取)
49
56
  8. 每个发现标注 `task_id`:对应的任务编号
50
57
 
@@ -55,6 +62,15 @@ description: 审查代码变更的质量、安全性和最佳实践。产出结
55
62
  ```json
56
63
  {
57
64
  "findings": [
65
+ {
66
+ "severity": "error",
67
+ "type": "contract_mismatch",
68
+ "file": "src/routes/users.ts",
69
+ "line": 15,
70
+ "task_id": "2",
71
+ "covers": "Requirement: 用户注册",
72
+ "message": "HTTP 端点路径与契约不一致:契约定义为 POST /api/users,实际实现为 POST /api/accounts/register。请修正为契约定义的路径"
73
+ },
58
74
  {
59
75
  "severity": "error",
60
76
  "type": "architecture",
@@ -198,7 +198,52 @@ specline-pipeline-gate.sh new --change "<change-name>"
198
198
  - 置信度:✅/⚠️
199
199
  ```
200
200
 
201
- > **无架构文档时的尾部补充**:若 Step 1.5 未发现任何显式架构文档,在 Architecture Impact Analysis 章节末尾追加:
201
+ #### Step 5.5: 判断是否需要对外接口契约
202
+
203
+ 在生成完整的 design.md 之后,检查 tasks.md 是否需要对外接口契约:
204
+
205
+ **判断逻辑**(以 tasks.md 为决策源头):
206
+ - 扫描 tasks.md 中所有任务的 Type 标注
207
+ - 如果存在 `Type: frontend`、`Type: backend`、`Type: infra`、`Type: db` 且该任务 `Testable: true`(或未标注 Testable 但 tasks.md 末尾「测试文件归属」表格中标注了 specline-test-writer 负责的集成/E2E 测试)→ **需要生成契约章节**
208
+ - 如果所有任务均为 `Type: config` 或 `Type: docs`,或虽有 code 任务但均无 test-writer 负责的测试 → **跳过契约章节**
209
+
210
+ 若需要契约,在 design.md 的 Architecture Impact Analysis 章节之后追加「对外接口契约」章节:
211
+
212
+ ```markdown
213
+ ## 对外接口契约 (External Interface Contract)
214
+
215
+ > 此章节为黑盒测试(specline-test-writer)提供对外接口定义。
216
+ > Test-Writer 据此编写集成/E2E 测试,不读取任何实现源码。
217
+ > Coding Agent 必须按此契约实现对外接口。
218
+
219
+ ### CLI 命令
220
+
221
+ | 命令 | 格式 | 参数 | 输出 | 退出码 |
222
+ |------|------|------|------|--------|
223
+ | <命令名> | `<CLI调用格式>` | <参数名>: <类型> (描述) | stdout: <输出描述> | 0=成功, 1=失败 |
224
+
225
+ ### HTTP 端点
226
+
227
+ | 方法 | 路径 | 请求体/参数 | 成功响应 | 错误响应 |
228
+ |------|------|------------|----------|----------|
229
+ | <方法> | <路径> | `<请求格式描述>` | <状态码> + `<响应体>` | <状态码> `{ "error": "<消息>" }` |
230
+
231
+ ### 模块导出
232
+
233
+ | 模块文件 | 导出符号 | 签名 | 说明 |
234
+ |----------|----------|------|------|
235
+ | <文件路径> | <函数/类名> | `<签名>` | <一句话说明> |
236
+ ```
237
+
238
+ **契约生成规则**:
239
+
240
+ - **CLI 命令**:从 Spec 的 WHEN/THEN 场景反推 CLI 命令格式。如果 Spec 描述的是命令行工具行为(如 "WHEN 用户运行 `specline quickfix`"),则提取命令名和参数
241
+ - **HTTP 端点**:从 Spec 的 WHEN/THEN 场景反推 HTTP API 格式。根据行为语义推测 HTTP 方法和路径(RESTful 约定),根据 THEN 推测响应状态码和格式
242
+ - **模块导出**:从 tasks.md 的 Files 字段推导模块文件路径,从 Spec 的 WHEN/THEN 反推需要导出的函数名和签名。**只列出被外部调用的导出**(如被 CLI 入口调用的函数、被其他模块 import 的函数),不列出内部 helper
243
+ - **粒度控制**:只定义「外部可调用的接口」——CLI 命令、HTTP 端点、模块间主要导出。不定义内部私有函数/helper
244
+ - 如果某类接口不存在(如纯 CLI 工具无 HTTP 端点),对应的子章节写「(无)」而非省略
245
+
246
+ > **注意**:接口契约是技术设计决策(API 叫什么名字、参数是什么类型),应放在 design.md 而非 spec.md。Spec 负责「用户要什么」,契约负责「怎么对外暴露」。
202
247
  > ```markdown
203
248
  > > ⚠️ 未发现项目显式架构文档(AGENTS.md / CLAUDE.md / .cursor/rules/)。以上分析基于代码库目录结构推断,建议补充架构文档以提高后续变更的分析精度。
204
249
  > ```
@@ -321,7 +366,11 @@ specline-pipeline-gate.sh new --change "<change-name>"
321
366
  - 如果仍 < 60%,记录警告但不阻塞,留给人工 Gate 1 决策
322
367
  3. **文件冲突自检**:检查第一批次中各任务的 Files 是否有交集
323
368
  - 如果有交集,修整 tasks.md(合并冲突任务或调整文件范围)
324
- 4. 完成后输出摘要:
369
+ 4. **契约一致性自检**:如果 design.md 包含「对外接口契约」章节,检查:
370
+ - tasks.md 的「测试文件归属」表格中是否有 specline-test-writer 负责的集成/E2E 测试任务(必须一致:有契约 → 有测试任务,无测试任务 → 无契约)
371
+ - 契约章节中定义的 CLI 命令/HTTP 端点/模块导出是否与 tasks.md 中对应任务的 Covers 中引用的 Scenario 相关
372
+ 5. 完成后输出摘要:
325
373
  - 生成了哪些文件
326
374
  - 共有 N 个任务,独立任务 M 个(并行度 = M/N)
327
375
  - 第 1 批次几个任务
376
+ - 是否生成了接口契约
@@ -47,6 +47,7 @@ description: 执行测试并分析失败原因。语言无关,自动检测项
47
47
  |---------|---------|---------|-----------|
48
48
  | `test_bug` | 测试逻辑/断言写错了 | test-writer 修改测试代码 | 自动循环(最多 2 次) |
49
49
  | `impl_bug` | 实现代码行为不符合 Spec | coding agent 修改实现代码 | 用 Covers 追溯链定位任务后自动修复 |
50
+ | `contract_mismatch` | 实现代码的对外接口与 design.md 契约不一致(如 HTTP 路径/方法不匹配、CLI 参数签名不匹配、模块导出命名不一致) | coding agent 按契约修正代码;若契约本身有问题则回 spec-creator 修正 design.md | 优先回 coding agent 修复(最多 2 次);若 2 次后仍不一致,暂停并报告用户确认以 design.md 为准还是以代码为准 |
50
51
  | `env_issue` | 测试环境/依赖问题(如测试框架未安装) | 检查环境配置 | 暂停,告知用户 |
51
52
  | `spec_ambiguity` | Spec 描述模糊导致理解偏差 | 需要人工澄清 | **暂停流水线,等待用户确认** |
52
53
 
@@ -83,6 +84,14 @@ description: 执行测试并分析失败原因。语言无关,自动检测项
83
84
  "covers": "Requirement: CLI 错误处理, Scenario: 无效文件",
84
85
  "reason": "CLI 未对无效 task 文件进行校验,应返回 exit code 1"
85
86
  },
87
+ {
88
+ "test": "tests/integration/test_users_api.py > test_create_user_returns_201",
89
+ "error": "HTTPError: 404 Client Error for url: /api/users",
90
+ "classification": "contract_mismatch",
91
+ "task_id": "1",
92
+ "covers": "Requirement: 用户注册, Scenario: 成功注册",
93
+ "reason": "接口契约定义 POST /api/users,但实现代码注册在 POST /api/accounts/register。测试按契约调用 /api/users 收到 404"
94
+ },
86
95
  {
87
96
  "test": "tests/api.test.ts > Session persistence after restart",
88
97
  "error": "Expected session stored in Redis but stored in memory",
@@ -91,7 +100,7 @@ description: 执行测试并分析失败原因。语言无关,自动检测项
91
100
  }
92
101
  ],
93
102
  "recommendation": "fix_impl",
94
- "detail": "3 个失败中 2 个为实现代码问题(任务 2),1 个为 spec 模糊需人工澄清"
103
+ "detail": "4 个失败中 2 个为实现代码问题(任务 2),1 个为契约不一致(任务 1),1 个为 spec 模糊需人工澄清"
95
104
  }
96
105
  ```
97
106
 
@@ -44,8 +44,8 @@ description: 黑盒测试工程师——只能基于 Spec 文档编写测试,
44
44
 
45
45
  1. **不能读取实现源代码**:禁止读取任何业务逻辑、组件实现、API handler 等源码文件
46
46
  2. **只能基于以下输入**:
47
- - Spec 文档(需求规格)
48
- - `design.md`(技术设计中公开的接口定义)
47
+ - Spec 文档(需求规格,获取行为验收标准 WHEN/THEN)
48
+ - `design.md` 的「对外接口契约」章节(获取 CLI 命令、HTTP 端点、模块导出的技术签名——如果章节不存在则跳过)
49
49
  - `tasks.md`(Covers 追溯链)
50
50
  - 项目的 `package.json`/`pyproject.toml` 等**配置文件**(用于确定框架,不是实现代码)
51
51
  3. **只能通过 CLI 执行或 HTTP 调用来验证行为**,不可直接 import 内部模块或组件
@@ -53,11 +53,62 @@ description: 黑盒测试工程师——只能基于 Spec 文档编写测试,
53
53
  ## 工作方式
54
54
 
55
55
  1. 检测项目技术栈,确定测试框架
56
- 2. 仔细阅读 Spec 中的每个 Scenario
57
- 3. 对照 `tasks.md` 中的 `Covers` 追溯链,确保每个 Scenario 都有测试覆盖
58
- 4. 每个 Scenario 至少生成 1 个对应的测试函数
59
- 5. 测试函数命名遵循对应框架的约定
60
- 6. 测试函数必须包含描述性注释/名称(对应 Spec 中的 Scenario 名称)
56
+ 2. 仔细阅读 Spec 中的每个 Scenario,理解验收标准(WHEN/THEN)
57
+ 3. **读取 `design.md` 的「对外接口契约」章节**,获取 CLI 命令格式、HTTP 端点、模块导出签名
58
+ - 如果 design.md 无此章节 change 无黑盒测试任务需求,技能级自检:确认 tasks.md 末尾「测试文件归属」表格中是否确实无 specline-test-writer 负责的测试任务
59
+ 4. 对照 `tasks.md` 中的 `Covers` 追溯链,确保每个 Scenario 都有测试覆盖
60
+ 5. 每个 Scenario 至少生成 1 个对应的测试函数
61
+ 6. 测试函数命名遵循对应框架的约定
62
+ 7. 测试函数必须包含描述性注释/名称(对应 Spec 中的 Scenario 名称)
63
+
64
+ ## 合同驱动测试 (Contract-Driven Testing)
65
+
66
+ 当 design.md 包含「对外接口契约」章节时,按以下规则编写测试:
67
+
68
+ ### CLI 命令测试
69
+
70
+ 从契约的 CLI 命令表格获取命令格式和参数:
71
+ ```python
72
+ # 契约: | quickfix | `specline quickfix <description>` | description: string | stdout: summary, exit 0/1 |
73
+ def test_quickfix_success():
74
+ result = subprocess.run(["specline", "quickfix", "修复按钮"], capture_output=True, text=True)
75
+ assert result.returncode == 0
76
+ assert "summary" in result.stdout.lower()
77
+ ```
78
+
79
+ ### HTTP 端点测试
80
+
81
+ 从契约的 HTTP 端点表格获取路径和方法:
82
+ ```python
83
+ # 契约: | POST | /api/users | `{ name: string, email: string }` | 201 + `{ id: string }` | 409 `{ "error": "duplicate" }` |
84
+ def test_create_user_success():
85
+ resp = requests.post(f"{BASE_URL}/api/users", json={"name": "张三", "email": "test@example.com"})
86
+ assert resp.status_code == 201
87
+ assert "id" in resp.json()
88
+
89
+ def test_create_user_duplicate():
90
+ # 先创建一个用户
91
+ requests.post(f"{BASE_URL}/api/users", json={"name": "张三", "email": "dup@example.com"})
92
+ # 重复创建
93
+ resp = requests.post(f"{BASE_URL}/api/users", json={"name": "李四", "email": "dup@example.com"})
94
+ assert resp.status_code == 409
95
+ assert resp.json()["error"] == "duplicate"
96
+ ```
97
+
98
+ ### 模块导出测试
99
+
100
+ 从契约的模块导出表格获取函数签名,但**只通过 CLI/HTTP 间接调用**,不直接 import:
101
+ ```python
102
+ # 契约: | src/services/auth.py | createSession | `(userId: string) => Promise<Session>` |
103
+ # ❌ 不能: from src.services.auth import createSession
104
+ # ✅ 改为: 通过 HTTP API 间接测试
105
+ def test_create_session_via_api():
106
+ resp = requests.post(f"{BASE_URL}/api/sessions", json={"userId": "user-123"})
107
+ assert resp.status_code == 201
108
+ assert "sessionToken" in resp.json()
109
+ ```
110
+
111
+ **重要**:模块导出契约主要用于 Code Review 阶段校验实现一致性。test-writer 仍然只通过外部接口(CLI/HTTP)测试行为。
61
112
 
62
113
  ## 测试映射规则(语言无关)
63
114
 
@@ -44,7 +44,7 @@ run_a2_a3_reverse() {
44
44
  tmp_tasks_scens=$(mktemp) || return 1
45
45
 
46
46
  _a2a3_cleanup() {
47
- rm -f "$tmp_spec_reqs" "$tmp_spec_scens" "$tmp_tasks_reqs" "$tmp_tasks_scens"
47
+ rm -f "${tmp_spec_reqs:-}" "${tmp_spec_scens:-}" "${tmp_tasks_reqs:-}" "${tmp_tasks_scens:-}"
48
48
  }
49
49
  trap _a2a3_cleanup RETURN
50
50
 
@@ -781,6 +781,119 @@ gate_spec() {
781
781
  pass "Spec Gate 全部通过"
782
782
  }
783
783
 
784
+ # ===== 对外接口契约签名检查 =====
785
+ # 以 tasks.md 为决策源头:有 test-writer 任务才需要契约,有契约才检查签名存在性
786
+ check_contract_signatures() {
787
+ local tasks_file="$1"
788
+ local design_file="$PROJECT_ROOT/specline/changes/$CHANGE/design.md"
789
+
790
+ # 1. 判断是否需要契约:检查 tasks.md 末尾「测试文件归属」表格中是否有 specline-test-writer 负责的集成/E2E 测试
791
+ local has_test_writer=false
792
+ if grep -q 'specline-test-writer' "$tasks_file" 2>/dev/null; then
793
+ has_test_writer=true
794
+ fi
795
+
796
+ if [ "$has_test_writer" = false ]; then
797
+ echo "ℹ️ tasks.md 中无 specline-test-writer 负责的测试任务,跳过契约检查"
798
+ return 0
799
+ fi
800
+
801
+ # 2. 有 test-writer 任务 → 设计文档必须有契约章节
802
+ if ! grep -q '^## 对外接口契约' "$design_file" 2>/dev/null; then
803
+ fail "tasks.md 中存在 specline-test-writer 负责的集成/E2E 测试任务,但 design.md 缺少「对外接口契约」章节。请运行 /specline-pipeline propose 重新生成,或手动补充"
804
+ fi
805
+
806
+ echo "正在检查对外接口契约签名..."
807
+
808
+ local contract_errors=""
809
+
810
+ # 3. 解析契约章节中的 CLI 命令,检查命令注册代码是否存在
811
+ local in_cli_section=false
812
+ while IFS= read -r line; do
813
+ # 检测章节边界
814
+ if echo "$line" | grep -q '^### CLI'; then
815
+ in_cli_section=true
816
+ continue
817
+ elif echo "$line" | grep -q '^### HTTP'; then
818
+ in_cli_section=false
819
+ continue
820
+ fi
821
+
822
+ if [ "$in_cli_section" = true ] && echo "$line" | grep -q '^|.*|.*|.*|.*|.*|$'; then
823
+ local cli_cmd
824
+ cli_cmd=$(echo "$line" | awk -F'|' '{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2}')
825
+ # 跳过表头行
826
+ if [ "$cli_cmd" != "命令" ] && [ -n "$cli_cmd" ]; then
827
+ # grep 搜索命令注册(CLI 命令通常在 specline-pipeline-gate.sh 或其他 .sh 文件中有 case 分支)
828
+ if ! grep -rq "$cli_cmd" "$PROJECT_ROOT" --include="*.sh" --include="*.py" --include="*.ts" --include="*.go" 2>/dev/null; then
829
+ contract_errors="${contract_errors}
830
+ CLI 命令 '$cli_cmd' 在契约中定义,但未在代码中找到注册(搜索了 .sh/.py/.ts/.go 文件)"
831
+ fi
832
+ fi
833
+ fi
834
+ done < <(awk '/^## 对外接口契约/,/^## [^对]/' "$design_file" 2>/dev/null)
835
+
836
+ # 4. 解析契约章节中的 HTTP 端点,检查路由注册代码是否存在
837
+ local in_http_section=false
838
+ while IFS= read -r line; do
839
+ if echo "$line" | grep -q '^### HTTP'; then
840
+ in_http_section=true
841
+ continue
842
+ elif echo "$line" | grep -qE '^### (模块导出|CLI)'; then
843
+ in_http_section=false
844
+ continue
845
+ fi
846
+
847
+ if [ "$in_http_section" = true ] && echo "$line" | grep -q '^|.*|.*|.*|.*|.*|$'; then
848
+ local http_path
849
+ http_path=$(echo "$line" | awk -F'|' '{gsub(/^[ \t]+|[ \t]+$/, "", $3); print $3}')
850
+ # 跳过表头行
851
+ if [ "$http_path" != "路径" ] && [ -n "$http_path" ]; then
852
+ # 去掉路径参数中的动态段(如 /api/users/:id → /api/users/ 前缀匹配)
853
+ local static_prefix
854
+ static_prefix=$(echo "$http_path" | sed 's/:[a-zA-Z_][a-zA-Z0-9_]*/FINDANY/' | sed 's/\/FINDANY//g')
855
+ if ! grep -rq "$static_prefix" "$PROJECT_ROOT" --include="*.py" --include="*.ts" --include="*.tsx" --include="*.go" --include="*.rs" 2>/dev/null; then
856
+ contract_errors="${contract_errors}
857
+ HTTP 路径 '$http_path' 在契约中定义,但未在代码中找到路由注册(搜索了 .py/.ts/.tsx/.go/.rs 文件)"
858
+ fi
859
+ fi
860
+ fi
861
+ done < <(awk '/^## 对外接口契约/,/^## [^对]/' "$design_file" 2>/dev/null)
862
+
863
+ # 5. 解析契约章节中的模块导出
864
+ local in_exports_section=false
865
+ while IFS= read -r line; do
866
+ if echo "$line" | grep -q '^### 模块导出'; then
867
+ in_exports_section=true
868
+ continue
869
+ elif echo "$line" | grep -qE '^## [^对]' && [ "$in_exports_section" = true ]; then
870
+ break
871
+ fi
872
+
873
+ if [ "$in_exports_section" = true ] && echo "$line" | grep -q '^|.*|.*|.*|.*|$'; then
874
+ local export_file export_name
875
+ export_file=$(echo "$line" | awk -F'|' '{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2}')
876
+ export_name=$(echo "$line" | awk -F'|' '{gsub(/^[ \t]+|[ \t]+$/, "", $3); print $3}')
877
+ # 跳过表头行
878
+ if [ "$export_file" != "模块文件" ] && [ -n "$export_file" ] && [ -n "$export_name" ]; then
879
+ if [ ! -f "$PROJECT_ROOT/$export_file" ]; then
880
+ contract_errors="${contract_errors}
881
+ 模块文件 '$export_file' 在契约中定义,但文件不存在"
882
+ elif ! grep -q "export.*$export_name\b\|func $export_name\b\|def $export_name\b\|pub fn $export_name\b" "$PROJECT_ROOT/$export_file" 2>/dev/null; then
883
+ contract_errors="${contract_errors}
884
+ 导出符号 '$export_name' 在契约中定义,但未在 '$export_file' 中找到对应声明"
885
+ fi
886
+ fi
887
+ fi
888
+ done < <(awk '/^## 对外接口契约/,/^## [^对]/' "$design_file" 2>/dev/null)
889
+
890
+ if [ -n "$contract_errors" ]; then
891
+ fail "对外接口契约签名不一致:${contract_errors}"
892
+ fi
893
+
894
+ pass "对外接口契约签名检查通过"
895
+ }
896
+
784
897
  gate_build() {
785
898
  load_project_config
786
899
 
@@ -905,6 +1018,11 @@ gate_build() {
905
1018
  fi
906
1019
  fi
907
1020
 
1021
+ # 对外接口契约检查(以 tasks.md 为决策源头)
1022
+ if [ -f "$tasks_file" ]; then
1023
+ check_contract_signatures "$tasks_file"
1024
+ fi
1025
+
908
1026
  write_gate_passed "phases.coding.gates.build_gate"
909
1027
  pass "Build Gate 全部通过"
910
1028
  }
@@ -216,7 +216,7 @@ Human Gate 1 具体交互:使用 `AskUserQuestion`,title="确认 Spec 和任
216
216
 
217
217
  ### Phase 2: CODING
218
218
 
219
- > **并行加速**:Human Gate 1 通过后,**同时**启动 coding 和 specline-test-writer。specline-test-writer 是黑盒的——只需要 Spec 文档,不需要实现代码。两者并行可节省 specline-test-writer 的编写时间。
219
+ > **并行加速**:Human Gate 1 通过后,**同时**启动 coding 和 specline-test-writer。specline-test-writer 是黑盒的——读取 Spec(验收标准)和 design.md(对外接口契约),不需要实现代码。两者并行可节省 specline-test-writer 的编写时间。
220
220
 
221
221
  #### Step 6: 并行启动(test-writer + DAG 构建)
222
222
 
@@ -238,7 +238,7 @@ Track A 和 Track B 同时启动,互不阻塞。test-writer 在 Coding 全部
238
238
 
239
239
  **6a. 启动 specline-test-writer(与 Coding 阶段 Agent 同时启动)**:
240
240
 
241
- 使用 Task 工具,subagent_type="specline-test-writer",prompt 中包含 changeName/Spec/Tasks 路径和黑盒约束。specline-test-writer 只编写 tests/integration/** 和 tests/e2e/** 下的测试,产出 test-code-result.json。
241
+ 使用 Task 工具,subagent_type="specline-test-writer",prompt 中包含 changeName/Spec/Design/Tasks 路径和黑盒约束。test-writer 从 `design.md` 的「对外接口契约」章节获取 CLI 命令/HTTP 端点/模块导出签名(若章节不存在则在 prompt 中注明无需契约),从 spec.md 获取行为验收标准(WHEN/THEN)。specline-test-writer 只编写 tests/integration/** 和 tests/e2e/** 下的测试,产出 test-code-result.json。
242
242
 
243
243
  **6b. 解析 tasks.md,构建任务 DAG**:
244
244
 
@@ -313,6 +313,10 @@ sed -i '' "s/^## ${task_id}\. \[ \]/## ${task_id}. [x]/" specline/changes/<name>
313
313
  Build Gate 校验内容:
314
314
  - 编译/语法检查(原有逻辑)
315
315
  - **单元测试文件存在性检查**(新增):对 Testable=true 的任务,检查其 `tests/unit/` 和 `tests/models/` 下的单元测试文件是否存在且语法正确。如果 Testable=true 的任务未产出对应测试文件,Build Gate 失败
316
+ - **对外接口契约签名检查**(新增):以 tasks.md 为决策源头——仅当 tasks.md 末尾「测试文件归属」表格中存在 specline-test-writer 负责的测试时,才检查 design.md 的「对外接口契约」章节:
317
+ - 有 test-writer 任务但缺契约章节 → 阻断(报错:缺契约)
318
+ - 有契约 → 逐项检查 CLI 命令注册、HTTP 路径注册、模块导出声明是否存在(只检查签名存在性,不检查语义正确性)
319
+ - 无 test-writer 任务 → 跳过
316
320
 
317
321
  exit code 0 = 通过,进入 Phase 3。失败处理见 [Layer 3: Build Gate 失败处理](#build-gate-失败处理)。
318
322
 
@@ -376,7 +380,7 @@ specline-test-writer 已在 Phase 2(Step 6a)与 Coding 并行启动。进入
376
380
 
377
381
  > `test_framework` 写入状态文件后,后续 `specline-pipeline-gate.sh` 的 test gate 会自动读取并选择正确的测试命令(Jest/pytest/go test 等)。
378
382
 
379
- > **黑盒约束回顾**:specline-test-writer 只能基于 Spec 文档编写测试,不能读取任何实现源代码。specline-test-writer 会自动检测项目测试框架(Jest/pytest/go test 等),按项目实际语言编写测试。
383
+ > **黑盒约束回顾**:specline-test-writer 只能基于 Spec 文档(验收标准)和 design.md 的「对外接口契约」章节(接口签名)编写测试,不能读取任何实现源代码。specline-test-writer 会自动检测项目测试框架(Jest/pytest/go test 等),按项目实际语言编写测试。
380
384
 
381
385
  #### Step 13: 测试门禁链(串行)
382
386
 
@@ -420,6 +424,7 @@ specline-pipeline-gate.sh archive --execute --change "<name>"
420
424
  4. **Gate 重置**:`jq '...gate.passed = null' "$STATE_FILE"`
421
425
  5. **特殊规则**:
422
426
  - `spec_ambiguity` → 暂停流水线展示模糊点(minimal/none 策略下降级为 WARNING)
427
+ - `contract_mismatch` → 优先回 coding agent 按契约修正(最多 2 次);2 次后仍不一致 → 暂停并报告用户确认
423
428
  - 接口不兼容 → 只重置受影响的下游任务(保留未受影响任务的状态)
424
429
  - Hook 阻断 → 先诊断原因 → 与用户沟通(AskUserQuestion)→ 修复后重试 → 绝不静默降级
425
430
  - 测试失败优先级:单元测试优先于集成/E2E
@@ -442,6 +447,7 @@ specline-pipeline-gate.sh archive --execute --change "<name>"
442
447
  - **集成/E2E 测试失败**(`tests/integration/` / `tests/e2e/`):先判断是测试代码还是实现代码问题
443
448
  - 测试代码问题 → specline-test-writer 自修(最多 2 次)
444
449
  - 实现代码问题 → Covers 追溯定位 → 回 coding agent 修复 → 用影响范围算法重置受影响 Gate
450
+ - **契约不一致** (`contract_mismatch`):实现代码的对外接口与 design.md 契约不一致 → 优先回 coding agent 按契约修正代码(最多 2 次);若 2 次后仍不一致,暂停并报告用户确认以 design.md 契约为准还是以代码为准
445
451
  - Gate 重置:`.phases.test.sub_phases.integration.gates.test_integration_gate.passed = null | .phases.test.sub_phases.e2e.gates.test_e2e_gate.passed = null`
446
452
  - **优先级**:单元测试失败优先修复;代码修复后所有测试 Gate 全部重置
447
453
 
@@ -604,7 +610,7 @@ AskUserQuestion({
604
610
  每阶段完成后,编排者自查:
605
611
 
606
612
  - [ ] **SPEC 阶段**:4 Artifact 齐全(proposal/design/tasks/specs);Spec Gate 通过;HG1 已确认;spec-review.json status=approved
607
- - [ ] **CODING 阶段**:全部批次完成;每个 task 产出报告存在;tasks.md checkbox 全部 [x];Build Gate 通过;Testable=true 任务的 test_files 非空
613
+ - [ ] **CODING 阶段**:全部批次完成;每个 task 产出报告存在;tasks.md checkbox 全部 [x];Build Gate 通过(含契约签名检查);Testable=true 任务的 test_files 非空
608
614
  - [ ] **CODE REVIEW 阶段**:code-review.json 存在;error 计数 = 0;Lint Gate 通过;HG2 已处理
609
615
  - [ ] **TEST 阶段**:test_framework 已写入状态文件;test-unit/integration/e2e Gate 全绿
610
616
  - [ ] **ARCHIVE 阶段**:HG3 已确认;归档目录已创建;session 绑定已解除;Delta spec sync 决策已完成
@@ -50,7 +50,7 @@ specline-pipeline-gate.sh new --change "<name>"
50
50
  |------|----------|------|---------|
51
51
  | 1 | proposal.md | `specline/changes/<name>/proposal.md` | What/Why/Scope/Non-goals |
52
52
  | 2 | spec.md | `specline/changes/<name>/specs/<capability>/spec.md` | Purpose/Requirements/Scenarios(WHEN/THEN) |
53
- | 3 | design.md | `specline/changes/<name>/design.md` | Architecture/Decisions/DataFlow |
53
+ | 3 | design.md | `specline/changes/<name>/design.md` | Architecture/Decisions/DataFlow/Contract |
54
54
  | 4 | tasks.md | `specline/changes/<name>/tasks.md` | Type/Depends/Covers/Files 标注 |
55
55
 
56
56
  **每个 Artifact 的创建规则**:
@@ -65,7 +65,7 @@ specline-pipeline-gate.sh new --change "<name>"
65
65
 
66
66
  - **spec.md**:H1 标题含 "Specification",包含 `## Purpose` 和 `## Requirements`,每个 Requirement 至少 1 个 Scenario,每个 Scenario 含 `**WHEN**`/`**THEN**` 配对,至少覆盖 Happy Path 和 1 个异常场景
67
67
 
68
- - **design.md**:包含 Architecture Overview、Key Design Decisions(每项说明选择理由和替代方案)、Data Flow、Component Interaction、**Architecture Impact Analysis**(侵入点/模块边界/依赖方向/数据影响/接口兼容性分析,每项标注置信度 ✅/⚠️)
68
+ - **design.md**:包含 Architecture Overview、Key Design Decisions(每项说明选择理由和替代方案)、Data Flow、Component Interaction、**Architecture Impact Analysis**(侵入点/模块边界/依赖方向/数据影响/接口兼容性分析,每项标注置信度 ✅/⚠️)、**对外接口契约**(如有 specline-test-writer 负责的集成/E2E 测试;CLI 命令/HTTP 端点/模块导出签名)
69
69
 
70
70
  - **tasks.md**:每个任务必须标注:
71
71
  - **Type**: frontend | backend | infra | db | config | docs
@@ -180,7 +180,7 @@ specline-spec-creator 生成的 tasks.md 末尾会包含「测试文件归属」
180
180
 
181
181
  - [ ] proposal.md 包含:What / Why / In Scope / Out of Scope(两段显式分开)/ Impact
182
182
  - [ ] spec.md 包含:Purpose + Requirements,每个 Requirement ≥1 Scenario(含 WHEN/THEN),Happy Path + 至少 1 个异常场景
183
- - [ ] design.md 包含:Architecture Overview、Key Design Decisions(理由+替代方案)、Data Flow、Component Interaction、**Architecture Impact Analysis**(侵入点/模块边界/依赖方向/数据影响/接口兼容性,每项带置信度 ✅/⚠️)
183
+ - [ ] design.md 包含:Architecture Overview、Key Design Decisions(理由+替代方案)、Data Flow、Component Interaction、**Architecture Impact Analysis**(侵入点/模块边界/依赖方向/数据影响/接口兼容性,每项带置信度 ✅/⚠️)、**对外接口契约**(如有 test-writer 测试任务;CLI/HTTP/模块导出表格)
184
184
  - [ ] tasks.md 每个任务标注完整(Type/Depends/Covers/Testable/Files),Depends: (none) 占比 ≥ 60%,第 1 批次 Files 无重叠
185
185
  - [ ] 测试文件归属表格存在:单元测试归属 coding agent,集成/E2E 归属 test-writer
186
186
  - [ ] `specline-pipeline-gate.sh artifacts --json` 确认 4 个文件齐全