specline 1.3.4 → 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.
- package/README.md +132 -125
- package/adapters/claude/deploy.json +12 -0
- package/adapters/claude/hooks/hooks.json +12 -0
- package/adapters/claude/hooks.json +12 -0
- package/adapters/claude/orchestration.md +17 -0
- package/adapters/codex/agent.toml.hbs +7 -0
- package/adapters/codex/deploy.json +12 -0
- package/adapters/codex/hooks.json +12 -0
- package/adapters/codex/orchestration.md +18 -0
- package/adapters/cursor/deploy.json +12 -0
- package/adapters/cursor/hooks.json +9 -0
- package/adapters/cursor/orchestration.md +17 -0
- package/adapters/opencode/deploy.json +12 -0
- package/adapters/opencode/orchestration.md +18 -0
- package/adapters/opencode/plugin.js +10 -0
- package/cli.mjs +161 -558
- package/core/agents/specline-backend-dev.yaml +45 -0
- package/core/agents/specline-code-reviewer.yaml +67 -0
- package/core/agents/specline-config-dev.yaml +50 -0
- package/core/agents/specline-config-reviewer.yaml +70 -0
- package/core/agents/specline-explore-assistant.yaml +79 -0
- package/core/agents/specline-frontend-dev.yaml +45 -0
- package/core/agents/specline-spec-creator.yaml +58 -0
- package/core/agents/specline-spec-reviewer.yaml +58 -0
- package/core/agents/specline-test-runner.yaml +62 -0
- package/core/agents/specline-test-writer.yaml +67 -0
- package/core/bootstrap/using-specline.md +14 -0
- package/core/gates/pipeline-gate-checks/a1-covers-ref.sh +125 -0
- package/core/gates/pipeline-gate-checks/a2-a3-reverse.sh +171 -0
- package/core/gates/pipeline-gate-checks/c1-exception.sh +71 -0
- package/core/gates/pipeline-gate-checks/c2-vague.sh +60 -0
- package/core/gates/pipeline-gate-checks/common.sh +68 -0
- package/core/gates/pipeline-gate-checks/d1-cycle.sh +149 -0
- package/core/gates/pipeline-gate-checks/d3-type-file.sh +260 -0
- package/core/gates/pipeline-gate.sh +1456 -0
- package/core/hooks/session-start.sh +259 -0
- package/core/skills/specline-apply-change/SKILL.md +197 -0
- package/core/skills/specline-archive-change/SKILL.md +173 -0
- package/core/skills/specline-explore/SKILL.md +504 -0
- package/core/skills/specline-knowledge/SKILL.md +539 -0
- package/core/skills/specline-pipeline/SKILL.md +604 -0
- package/core/skills/specline-pipeline/references/error-recovery-details.md +49 -0
- package/core/skills/specline-pipeline/references/event-log-spec.md +59 -0
- package/core/skills/specline-pipeline/references/pipeline-state-schema.md +87 -0
- package/core/skills/specline-pipeline/templates/subagent-prompts.md +397 -0
- package/core/skills/specline-propose/SKILL.md +186 -0
- package/core/skills/specline-quickfix/SKILL.md +289 -0
- package/core/templates/AGENTS.md.hbs +5 -0
- package/core/templates/specline/config.yaml +15 -0
- package/lib/deploy-claude.mjs +80 -0
- package/lib/deploy-codex.mjs +77 -0
- package/lib/deploy-opencode.mjs +93 -0
- package/lib/deploy.mjs +668 -0
- package/lib/gate.mjs +103 -0
- package/lib/hash.mjs +13 -0
- package/lib/hook.mjs +105 -0
- package/lib/init.mjs +122 -0
- package/lib/lock.mjs +99 -0
- package/lib/merge.mjs +184 -0
- package/lib/paths.mjs +40 -0
- package/lib/platforms.mjs +74 -0
- package/lib/render-agents.mjs +88 -0
- package/lib/render.mjs +126 -0
- package/lib/sync.mjs +253 -0
- package/lib/tty-select.mjs +89 -0
- package/package.json +4 -1
- package/templates/.cursor/README.md +18 -0
- package/templates/.cursor/agents/specline-code-reviewer.md +63 -4
- package/templates/.cursor/agents/specline-spec-creator.md +120 -1
- package/templates/.cursor/agents/specline-spec-reviewer.md +21 -2
- package/templates/.cursor/agents/specline-test-runner.md +10 -1
- package/templates/.cursor/agents/specline-test-writer.md +58 -7
- package/templates/.cursor/hooks/specline-pipeline-gate-checks/a2-a3-reverse.sh +1 -1
- package/templates/.cursor/hooks/specline-pipeline-gate.sh +118 -0
- package/templates/.cursor/skills/specline-apply-change/SKILL.md +26 -0
- package/templates/.cursor/skills/specline-archive-change/SKILL.md +24 -0
- package/templates/.cursor/skills/specline-explore/SKILL.md +17 -0
- package/templates/.cursor/skills/specline-knowledge/SKILL.md +539 -0
- package/templates/.cursor/skills/specline-pipeline/SKILL.md +102 -3
- package/templates/.cursor/skills/specline-pipeline/templates/subagent-prompts.md +32 -0
- package/templates/.cursor/skills/specline-propose/SKILL.md +34 -3
- package/templates/.cursor/skills/specline-quickfix/SKILL.md +26 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specline-propose
|
|
3
|
+
description: >-
|
|
4
|
+
生成 Spec 规划文件(proposal/design/tasks/spec)。根据自然语言需求,
|
|
5
|
+
直接按内联模板创建全部 Artifact,不依赖外部 CLI。
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Layer 1: TL;DR
|
|
9
|
+
|
|
10
|
+
> **一句话**:根据自然语言需求生成完整的 Spec 规划文件。
|
|
11
|
+
> **入口**:`/specline-pipeline <需求>` 或由编排者通过 Task 工具派发
|
|
12
|
+
> **产出**:proposal.md / design.md / tasks.md / spec.md
|
|
13
|
+
> **耗时**:约 30 秒 - 2 分钟
|
|
14
|
+
|
|
15
|
+
**核心流程**:`需求 → 推导 change-name → 创建目录 → 生成 4 Artifact → 自检`
|
|
16
|
+
|
|
17
|
+
**最终生成的文件结构**:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
specline/changes/<change-name>/
|
|
21
|
+
├── proposal.md ← What & Why & Scope
|
|
22
|
+
├── design.md ← Architecture & Decisions
|
|
23
|
+
├── tasks.md ← 实现清单(含依赖 DAG)
|
|
24
|
+
└── specs/
|
|
25
|
+
└── <capability>/
|
|
26
|
+
└── spec.md ← Requirements & Scenarios
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Layer 2: Happy Path
|
|
32
|
+
|
|
33
|
+
**Input**: 用户需求描述(自然语言),由编排者传入 change-name。
|
|
34
|
+
|
|
35
|
+
### Step 1: 理解需求并推导 change name
|
|
36
|
+
|
|
37
|
+
如果编排者没有传入明确的 change name,从需求描述推导 kebab-case 名称(如 "添加用户登录功能" → `add-user-login`)。
|
|
38
|
+
|
|
39
|
+
### Step 2: 创建 Change 目录
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
specline gate new --change "<name>"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
创建 `specline/changes/<name>/` 目录及必要的元数据文件。
|
|
46
|
+
|
|
47
|
+
### Step 3: 按顺序生成 4 个 Artifact
|
|
48
|
+
|
|
49
|
+
| 顺序 | Artifact | 路径 | 内容要点 |
|
|
50
|
+
|------|----------|------|---------|
|
|
51
|
+
| 1 | proposal.md | `specline/changes/<name>/proposal.md` | What/Why/Scope/Non-goals |
|
|
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/Contract |
|
|
54
|
+
| 4 | tasks.md | `specline/changes/<name>/tasks.md` | Type/Depends/Covers/Files 标注 |
|
|
55
|
+
|
|
56
|
+
**每个 Artifact 的创建规则**:
|
|
57
|
+
|
|
58
|
+
- **proposal.md**:描述 What(做什么)/ Why(为什么做)以及以下两段(必须显式分开):
|
|
59
|
+
|
|
60
|
+
**## In Scope**(做什么):明确功能范围、涉及的系统/模块、目标用户
|
|
61
|
+
|
|
62
|
+
**## Out of Scope**(不做什么):明确排除哪些功能/场景以及排除理由。**这是提案中最有价值的部分之一**——一半的返工源于对"不做什么"的沉默分歧。明确 Out of Scope 防止需求蔓延,也保护了 In Scope 的交付承诺。
|
|
63
|
+
|
|
64
|
+
还包括:Impact(影响哪些系统)
|
|
65
|
+
|
|
66
|
+
- **spec.md**:H1 标题含 "Specification",包含 `## Purpose` 和 `## Requirements`,每个 Requirement 至少 1 个 Scenario,每个 Scenario 含 `**WHEN**`/`**THEN**` 配对,至少覆盖 Happy Path 和 1 个异常场景
|
|
67
|
+
|
|
68
|
+
- **design.md**:包含 Architecture Overview、Key Design Decisions(每项说明选择理由和替代方案)、Data Flow、Component Interaction、**Architecture Impact Analysis**(侵入点/模块边界/依赖方向/数据影响/接口兼容性分析,每项标注置信度 ✅/⚠️)、**对外接口契约**(如有 specline-test-writer 负责的集成/E2E 测试;CLI 命令/HTTP 端点/模块导出签名)
|
|
69
|
+
|
|
70
|
+
- **tasks.md**:每个任务必须标注:
|
|
71
|
+
- **Type**: frontend | backend | infra | db | config | docs
|
|
72
|
+
- **Depends**: (none) | 依赖的任务编号
|
|
73
|
+
- **Covers**: Requirement: xxx, Scenario: xxx
|
|
74
|
+
- **Testable**: true | false
|
|
75
|
+
- **Files**: 任务涉及的文件路径列表
|
|
76
|
+
|
|
77
|
+
任务拆分原则:
|
|
78
|
+
- 按功能领域垂直拆分(前/后端分开)
|
|
79
|
+
- `Depends: (none)` 占比 ≥ 60%
|
|
80
|
+
- 第 1 批次(无依赖任务)的 Files 集合互不重叠
|
|
81
|
+
|
|
82
|
+
> 💡 **并行度自检**:统计 tasks.md 中 `Depends: (none)` 的任务占比 — ≥ 60% 通过,< 60% 则重新拆解(最多 2 次),仍不达标则记录警告。
|
|
83
|
+
|
|
84
|
+
### Step 4: 验证完整性
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
specline gate artifacts --change "<name>" --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
确保 proposal/design/tasks/specs 四个文件都已存在。
|
|
91
|
+
|
|
92
|
+
### Step 5: 输出完成摘要
|
|
93
|
+
|
|
94
|
+
- Change 名称和位置
|
|
95
|
+
- 4 个文件生成确认
|
|
96
|
+
- 任务统计(总数 N、独立任务 M、并行度 M/N)
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Layer 3: 规范详解
|
|
101
|
+
|
|
102
|
+
### tasks.md 拆分规范
|
|
103
|
+
|
|
104
|
+
✅ **好的任务拆分**:
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
## 1. 数据模型 [x]
|
|
108
|
+
- Type: backend
|
|
109
|
+
- Depends: (none)
|
|
110
|
+
- Covers: Requirement: 用户数据模型
|
|
111
|
+
- Testable: true
|
|
112
|
+
- Files: server/models/user.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> 任务粒度适中,Files 范围明确,Covers 可追溯到具体 Requirement。
|
|
116
|
+
|
|
117
|
+
❌ **不好的任务拆分**:
|
|
118
|
+
|
|
119
|
+
```markdown
|
|
120
|
+
## 1. 实现后端 [ ]
|
|
121
|
+
- Type: backend
|
|
122
|
+
- Files: server/*.py
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> 太粗:Files 范围太大,没有 Covers 追溯链,Depends 缺失导致无法判断批次。
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 关键原则速查
|
|
130
|
+
|
|
131
|
+
| 原则 | 说明 |
|
|
132
|
+
|------|------|
|
|
133
|
+
| 垂直拆分 | 按功能领域分(前/后端分开),不按技术层分 |
|
|
134
|
+
| 独立可测 | 每个任务可独立验证完成状态 |
|
|
135
|
+
| 文件不交叠 | 第 1 批次(Depends: none)任务的文件集合无交集 |
|
|
136
|
+
| 可追溯 | 每个任务必须通过 Covers 追溯到具体 Requirement/Scenario |
|
|
137
|
+
| Testable 标注 | 无依赖 + 有可测代码 + 非 config/docs → Testable: true |
|
|
138
|
+
|
|
139
|
+
### 测试文件归属
|
|
140
|
+
|
|
141
|
+
specline-spec-creator 生成的 tasks.md 末尾会包含「测试文件归属」表格节,按 capability 分组列出:
|
|
142
|
+
|
|
143
|
+
| 测试文件(目录) | 测试类型 | 负责者 |
|
|
144
|
+
|-----------------|---------|-------|
|
|
145
|
+
| tests/unit/<module>/ | 单元测试 | Coding Agent (Task N) |
|
|
146
|
+
| tests/integration/test_<capability>.py | 集成测试 | specline-test-writer |
|
|
147
|
+
| tests/e2e/test_<capability>_flow.py | E2E 测试 | specline-test-writer |
|
|
148
|
+
|
|
149
|
+
> - 单元测试(`tests/unit/` 或 `tests/models/`)归属 coding agent
|
|
150
|
+
> - 集成测试(`tests/integration/`)和 E2E 测试(`tests/e2e/`)归属 specline-test-writer
|
|
151
|
+
> - coding agent 和 test-writer 应只在自己的边界内编写测试文件
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### Guardrails
|
|
156
|
+
|
|
157
|
+
- 所有文件直接写入 `specline/changes/<name>/`,不调用外部 CLI
|
|
158
|
+
- 先读已有 dependency 再生成后续文件
|
|
159
|
+
- 需求不明确时用结构化提问({{CONFIRM}})澄清
|
|
160
|
+
- 优先做出合理判断保持节奏,只在关键不清时询问
|
|
161
|
+
- **Hook 阻断不降级**:如果本 Skill 因 `specline-spec-creator` 子 Agent 被 hook 阻止而作为降级方案被调用,必须首先通知用户阻断原因,并尝试诊断修复(参考 specline-pipeline SKILL 中的 Hook 阻断处理规范)。不得在 hook 问题未解决时静默直接执行
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Anti-Rationalization 表格
|
|
166
|
+
|
|
167
|
+
生成 Spec 规划文件时,Agent 容易找借口跳过关键步骤:
|
|
168
|
+
|
|
169
|
+
| 借口 | 现实 |
|
|
170
|
+
|------|------|
|
|
171
|
+
| "需求很明确,不需要 proposal.md" | 明确的只是你脑子里的假设。Proposal 的作用是让这些假设显式化,供他人审视。 |
|
|
172
|
+
| "Scope 就是隐含的,不用写那么细" | 一半的返工源于对"不做什么"的沉默分歧。Out of Scope 是提案中最有价值的部分。 |
|
|
173
|
+
| "任务拆解是多余的,我能直接做" | 不拆解就无法并行、无法断点续跑、无法追溯。10 分钟的拆解省下 2 小时的重做。 |
|
|
174
|
+
| "并行度 50% 够了,不用追求 60%" | 60% 不是硬指标,但 <50% 意味着功能边界划分不合理——大概率任务之间耦合太紧。 |
|
|
175
|
+
| "测试文件归属表格我后面补" | 补的从来不会补。没有归属表格,coding agent 和 test-writer 会踩到对方的文件。 |
|
|
176
|
+
|
|
177
|
+
## Verification Checklist
|
|
178
|
+
|
|
179
|
+
生成 Spec 规划文件后,自查:
|
|
180
|
+
|
|
181
|
+
- [ ] proposal.md 包含:What / Why / In Scope / Out of Scope(两段显式分开)/ Impact
|
|
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**(侵入点/模块边界/依赖方向/数据影响/接口兼容性,每项带置信度 ✅/⚠️)、**对外接口契约**(如有 test-writer 测试任务;CLI/HTTP/模块导出表格)
|
|
184
|
+
- [ ] tasks.md 每个任务标注完整(Type/Depends/Covers/Testable/Files),Depends: (none) 占比 ≥ 60%,第 1 批次 Files 无重叠
|
|
185
|
+
- [ ] 测试文件归属表格存在:单元测试归属 coding agent,集成/E2E 归属 test-writer
|
|
186
|
+
- [ ] `specline gate artifacts --json` 确认 4 个文件齐全
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specline-quickfix
|
|
3
|
+
description: 轻量修改 Skill —— 小改动用 quickfix,大功能用 pipeline
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /specline-quickfix 轻量修改 Skill
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Layer 1: 速览与定位
|
|
11
|
+
|
|
12
|
+
**一句话定位**:小改动用 quickfix,大功能用 pipeline。
|
|
13
|
+
|
|
14
|
+
**入口**:`/specline-quickfix <描述>`
|
|
15
|
+
|
|
16
|
+
**你做:**
|
|
17
|
+
- 读取相关代码理解上下文
|
|
18
|
+
<!-- platform:cursor -->
|
|
19
|
+
- 直接 Write/StrReplace 编辑文件(不使用子 Agent)
|
|
20
|
+
- ReadLints 自动校验 + 修复(最多 2 次循环)
|
|
21
|
+
<!-- /platform:cursor -->
|
|
22
|
+
<!-- platform:claude,codex,opencode -->
|
|
23
|
+
- 直接编辑文件(使用平台原生编辑工具,不使用子 Agent)
|
|
24
|
+
- 运行项目 linter 命令自动校验 + 修复(最多 2 次循环)
|
|
25
|
+
<!-- /platform:claude,codex,opencode -->
|
|
26
|
+
- 运行项目已有单元测试(失败修复最多 2 次循环)
|
|
27
|
+
- 生成轻量归档(summary.md + files-changed.json)
|
|
28
|
+
|
|
29
|
+
**你不做:**
|
|
30
|
+
- 创建 proposal.md、design.md、tasks.md、specs/ 等规划文档
|
|
31
|
+
- 启动任何 specline-* 子 Agent
|
|
32
|
+
- 创建 `.pipeline-state.json` 或 `.pipeline-sessions.json`
|
|
33
|
+
- 写新测试、跑集成/E2E 测试
|
|
34
|
+
|
|
35
|
+
### 流程概览
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
UNDERSTAND ──→ IMPLEMENT ──→ REVIEW ──→ TEST ──→ ARCHIVE
|
|
39
|
+
(读代码) (直编辑) (Lint+自审) (现有单测) (轻量归档)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Hook 透明
|
|
43
|
+
|
|
44
|
+
Quickfix 不绑定 Pipeline session,所有 Hook(sessionStart、preToolUse、postToolUse、subagentStart、beforeShellExecution)自动透明放行,不产生任何拦截或提醒。
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Layer 2: Happy Path
|
|
49
|
+
|
|
50
|
+
### Phase 1: UNDERSTAND
|
|
51
|
+
|
|
52
|
+
**目标**:理解变更上下文,明确修改范围。
|
|
53
|
+
|
|
54
|
+
**Steps**:
|
|
55
|
+
|
|
56
|
+
1. 解析用户描述,提取关键词(文件名、函数名、错误信息等)
|
|
57
|
+
<!-- platform:cursor -->
|
|
58
|
+
2. 使用 Read 工具读取相关源文件,理解当前逻辑
|
|
59
|
+
<!-- /platform:cursor -->
|
|
60
|
+
<!-- platform:claude,codex,opencode -->
|
|
61
|
+
2. 读取相关源文件(使用平台原生读取工具),理解当前逻辑
|
|
62
|
+
<!-- /platform:claude,codex,opencode -->
|
|
63
|
+
3. 确认变更范围:
|
|
64
|
+
- 1-3 个文件 ✓
|
|
65
|
+
- 单一关注点 ✓
|
|
66
|
+
- 不涉及架构变更 ✓
|
|
67
|
+
- 不需要新测试 ✓
|
|
68
|
+
4. **意图模糊时**:使用 {{CONFIRM}} 向用户确认变更范围和目标,不要猜测
|
|
69
|
+
|
|
70
|
+
**准入条件**:变更范围已验证在 quickfix 适用范围内(参见 Layer 3 边界判断)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### Phase 2: IMPLEMENT
|
|
75
|
+
|
|
76
|
+
**目标**:直接编辑源文件,完成修改。
|
|
77
|
+
|
|
78
|
+
**Steps**:
|
|
79
|
+
|
|
80
|
+
<!-- platform:cursor -->
|
|
81
|
+
1. 使用 Write / StrReplace 工具直接编辑文件
|
|
82
|
+
2. **不使用子 Agent**,不调用 Task 工具
|
|
83
|
+
3. 编辑完成后,运行 ReadLints 检查新增的 lint 错误
|
|
84
|
+
<!-- /platform:cursor -->
|
|
85
|
+
<!-- platform:claude,codex,opencode -->
|
|
86
|
+
1. 使用平台原生编辑工具直接编辑文件
|
|
87
|
+
2. **不使用子 Agent**,不派发子任务
|
|
88
|
+
3. 编辑完成后,运行项目 linter 命令检查新增的语法和风格错误
|
|
89
|
+
<!-- /platform:claude,codex,opencode -->
|
|
90
|
+
|
|
91
|
+
**约束**:
|
|
92
|
+
- 只修改 UNDERSTAND 阶段确认的文件
|
|
93
|
+
- 如果发现需要修改第 4 个文件 → 暂停并建议转 `/specline-pipeline`
|
|
94
|
+
- 保持现有代码风格和命名约定
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### Phase 3: REVIEW
|
|
99
|
+
|
|
100
|
+
**目标**:通过 Lint 检查和 Agent 自审确保代码质量。
|
|
101
|
+
|
|
102
|
+
**Steps**:
|
|
103
|
+
|
|
104
|
+
<!-- platform:cursor -->
|
|
105
|
+
1. 运行 ReadLints 收集所有 lint 问题
|
|
106
|
+
2. **如有 lint 错误**:自动修复 → 再次 ReadLints → 最多循环 2 次
|
|
107
|
+
<!-- /platform:cursor -->
|
|
108
|
+
<!-- platform:claude,codex,opencode -->
|
|
109
|
+
1. 运行项目 linter 命令收集所有 lint 问题
|
|
110
|
+
2. **如有 lint 错误**:自动修复 → 再次运行 linter → 最多循环 2 次
|
|
111
|
+
<!-- /platform:claude,codex,opencode -->
|
|
112
|
+
- 第 1 次修复后仍有错误 → 分析原因,再次修复
|
|
113
|
+
- 第 2 次修复后仍有错误 → 报告用户,附错误列表和修复尝试记录,暂停
|
|
114
|
+
3. **Agent 自审**:
|
|
115
|
+
- 变更逻辑是否正确?
|
|
116
|
+
- 是否处理了边界条件?
|
|
117
|
+
- 是否引入了新问题(如未使用的导入、副作用)?
|
|
118
|
+
- 是否破坏现有功能?
|
|
119
|
+
4. 自审通过 → 进入 TEST 阶段
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Phase 4: TEST
|
|
124
|
+
|
|
125
|
+
**目标**:运行项目已有单元测试,确保不引入回归。
|
|
126
|
+
|
|
127
|
+
**Steps**:
|
|
128
|
+
|
|
129
|
+
1. **自动检测测试框架**:
|
|
130
|
+
- 检查 `package.json` scripts → Jest / Mocha / Vitest
|
|
131
|
+
- 检查 `pytest` / `go test` / `cargo test` 配置
|
|
132
|
+
2. **有测试配置**:运行现有单元测试
|
|
133
|
+
- 通过 → 进入 ARCHIVE 阶段
|
|
134
|
+
- 失败 → 分析失败原因,修复代码 → 重新运行 → 最多循环 2 次
|
|
135
|
+
- 第 2 次修复后仍失败 → 报告用户(附失败详情和修复尝试记录),暂停
|
|
136
|
+
3. **无测试配置**:跳过 TEST 阶段,进入 ARCHIVE 阶段(在 summary.md 中标注)
|
|
137
|
+
|
|
138
|
+
**不运行**:集成测试、E2E 测试、新编写的测试。
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Phase 5: ARCHIVE
|
|
143
|
+
|
|
144
|
+
**目标**:生成轻量归档,提供变更可追溯性。
|
|
145
|
+
|
|
146
|
+
**Steps**:
|
|
147
|
+
|
|
148
|
+
1. 在 `specline/changes/archive/` 下创建归档目录:
|
|
149
|
+
```
|
|
150
|
+
specline/changes/archive/YYYY-MM-DD-<description>/
|
|
151
|
+
├── summary.md
|
|
152
|
+
└── files-changed.json
|
|
153
|
+
```
|
|
154
|
+
2. **summary.md** 内容:
|
|
155
|
+
```markdown
|
|
156
|
+
# <变更标题>
|
|
157
|
+
|
|
158
|
+
## What
|
|
159
|
+
<一句话描述做了什么>
|
|
160
|
+
|
|
161
|
+
## Why
|
|
162
|
+
<为什么要做这个修改>
|
|
163
|
+
|
|
164
|
+
## Files Changed
|
|
165
|
+
- path/to/file1 — <修改简述>
|
|
166
|
+
- path/to/file2 — <修改简述>
|
|
167
|
+
|
|
168
|
+
## Test Result
|
|
169
|
+
- 通过 / 跳过(无现有单元测试)/ 失败(附详情)
|
|
170
|
+
```
|
|
171
|
+
3. **files-changed.json** 内容:
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"files": ["path/to/file1", "path/to/file2"],
|
|
175
|
+
"change_count": 2,
|
|
176
|
+
"description": "<变更描述>"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
4. 展示变更摘要,**询问用户**:是否需要 git commit?
|
|
180
|
+
|
|
181
|
+
**无人确认点**:整个 quickfix 流程不暂停等待人工确认(lint + test 是自动质量底线)。
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Layer 3: 异常与边界
|
|
186
|
+
|
|
187
|
+
### Quickfix vs Pipeline 边界判断
|
|
188
|
+
|
|
189
|
+
使用以下规则判断变更是否适合 quickfix:
|
|
190
|
+
|
|
191
|
+
| 维度 | Quickfix (`/specline-quickfix`) | Pipeline (`/specline-pipeline`) |
|
|
192
|
+
|------|-------------------------------|-------------------------------|
|
|
193
|
+
| 文件改动数 | 1-3 个 | 4+ 个 |
|
|
194
|
+
| 关注点 | 单一关注点 | 多关注点/跨模块 |
|
|
195
|
+
| 架构变更 | 无新架构/新组件 | 需要新组件/新 API |
|
|
196
|
+
| 测试 | 不需要新测试 | 需要写新测试 |
|
|
197
|
+
| 典型场景 | 修 bug、改配置、文档微调 | 新增功能、重构 |
|
|
198
|
+
| 产出 | summary.md + files-changed.json | proposal/design/tasks/specs + 全部测试 |
|
|
199
|
+
| 人工确认 | 0 个 | 3 个 |
|
|
200
|
+
| 耗时 | 1-3 分钟 | 10-30 分钟 |
|
|
201
|
+
|
|
202
|
+
**边界处理规则**:
|
|
203
|
+
|
|
204
|
+
| 异常情况 | 处理方式 |
|
|
205
|
+
|----------|----------|
|
|
206
|
+
| 变更范围 > 3 个文件 | 暂停,建议转 `/specline-pipeline` |
|
|
207
|
+
| 需要写新测试 | 暂停,建议转 `/specline-pipeline` |
|
|
208
|
+
| 涉及架构变更/新 API | 暂停,建议转 `/specline-pipeline` |
|
|
209
|
+
| Lint 修复 2 次后仍有错误 | 报告用户(附错误列表和修复记录),暂停 |
|
|
210
|
+
| 测试失败 2 次后仍失败 | 报告用户(附失败详情和修复记录),暂停 |
|
|
211
|
+
| 实现过程中发现需要额外文件 | 如果总数仍 ≤ 3 → 继续;如果 > 3 → 暂停并建议转 pipeline |
|
|
212
|
+
| 项目无测试配置 | 跳过 TEST 阶段,在 summary.md 中标注 |
|
|
213
|
+
|
|
214
|
+
### 不适合 Quickfix 的典型场景
|
|
215
|
+
|
|
216
|
+
- 新增功能模块(需要 spec/design 规划)
|
|
217
|
+
- 跨 3+ 模块的接口变更
|
|
218
|
+
- 数据库 schema 变更
|
|
219
|
+
- 需要新增测试覆盖的复杂修复
|
|
220
|
+
- 需要多人/多步骤协调的改动
|
|
221
|
+
|
|
222
|
+
**使用建议**:如果不确定,优先用 quickfix。如果需要更严格的流程保证,用 pipeline。
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Layer 4: 附录
|
|
227
|
+
|
|
228
|
+
### 与 Pipeline 的关系
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
specline-pipeline (完整流程) specline-quickfix (轻量流程)
|
|
232
|
+
SPEC UNDERSTAND
|
|
233
|
+
↓ ↓
|
|
234
|
+
CODING (子 Agent 并发) IMPLEMENT (单 Agent 直编)
|
|
235
|
+
↓ ↓
|
|
236
|
+
CODE REVIEW (review Agent) REVIEW (Lint 检查 + 自审)
|
|
237
|
+
↓ ↓
|
|
238
|
+
TEST (unit → integration → e2e) TEST (现有单测 only)
|
|
239
|
+
↓ ↓
|
|
240
|
+
ARCHIVE (Delta sync) ARCHIVE (summary + files-changed)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
两者完全独立,通过边界判断规则选择。不共享状态文件,不互相依赖。
|
|
244
|
+
|
|
245
|
+
### 归档目录结构兼容性
|
|
246
|
+
|
|
247
|
+
Quickfix 归档目录结构与 Pipeline 归档保持一致:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
specline/changes/archive/
|
|
251
|
+
├── YYYY-MM-DD-<pipeline-change>/ ← Pipeline 归档
|
|
252
|
+
│ ├── proposal.md
|
|
253
|
+
│ ├── design.md
|
|
254
|
+
│ ├── tasks.md
|
|
255
|
+
│ ├── specs/
|
|
256
|
+
│ └── ...
|
|
257
|
+
│
|
|
258
|
+
└── YYYY-MM-DD-<quickfix-description>/ ← Quickfix 归档
|
|
259
|
+
├── summary.md
|
|
260
|
+
└── files-changed.json
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
两种归档方式共存于同一目录,通过内容区分(Pipeline 归档有 proposal.md 等完整文档,Quickfix 归档只有 summary.md + files-changed.json)。
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Anti-Rationalization 表格
|
|
268
|
+
|
|
269
|
+
Quickfix 的极简流程容易让人产生"反正很快,随便点"的心态:
|
|
270
|
+
|
|
271
|
+
| 借口 | 现实 |
|
|
272
|
+
|------|------|
|
|
273
|
+
| "顺便多改一个文件也没事,就一行" | Quickfix 的 1-3 文件边界是防止范围蔓延的最后防线。第 4 个文件应该走 Pipeline。 |
|
|
274
|
+
| "不需要 lint 检查,我肉眼确认了" | 人类肉眼无法可靠检测拼写错误、未使用导入、类型不匹配。Lint 检查是自动化底线。 |
|
|
275
|
+
| "测试跳过没事,改动很小" | 改动越小越容易有隐性耦合。现有测试套件就是你的回归检测网。 |
|
|
276
|
+
| "不用归档了,就是个小修,没记录无所谓" | 不归档意味着不可追溯。三个月后没人记得这个修改是谁做的、为什么做的。 |
|
|
277
|
+
| "不用询问用户 git commit,我自己提交了" | Commit 是用户的决定,不是 Agent 的。擅自 commit 剥夺了用户的审查机会。 |
|
|
278
|
+
|
|
279
|
+
## Verification Checklist
|
|
280
|
+
|
|
281
|
+
Quickfix 完成后,自查:
|
|
282
|
+
|
|
283
|
+
- [ ] UNDERSTAND 阶段确认了变更范围(≤3 文件,单一关注点)
|
|
284
|
+
- [ ] IMPLEMENT 阶段只修改了确认的文件,未越界
|
|
285
|
+
- [ ] REVIEW 阶段 lint 检查通过(或 2 次修复后仍有错误已报告)
|
|
286
|
+
- [ ] Agent 自审完成:逻辑正确、边界已处理、未引入新问题、未破坏现有功能
|
|
287
|
+
- [ ] TEST 阶段现有单元测试全部通过(或已标注跳过原因)
|
|
288
|
+
- [ ] ARCHIVE 阶段 summary.md + files-changed.json 已写入归档目录
|
|
289
|
+
- [ ] 已询问用户是否需要 git commit
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Specline 项目配置
|
|
2
|
+
# 此文件由 specline init 生成,可按需修改
|
|
3
|
+
|
|
4
|
+
pipeline:
|
|
5
|
+
# 人机门禁策略: full / minimal / none
|
|
6
|
+
# full — HG1(SPEC确认) + HG2(Review复核) + HG3(归档确认) 均人工
|
|
7
|
+
# minimal — HG1 人工确认,HG2/HG3 自动通过
|
|
8
|
+
# none — 全部自动通过
|
|
9
|
+
human_gate_policy: full
|
|
10
|
+
|
|
11
|
+
gate:
|
|
12
|
+
# Gate 脚本调用方式
|
|
13
|
+
command: specline gate
|
|
14
|
+
# CLI 不可用时回退到本地脚本
|
|
15
|
+
fallback: specline/bin/gate.sh
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { PACKAGE_ROOT } from './paths.mjs';
|
|
4
|
+
import { getClaudeUpstreamManifest, writeManifestToProject } from './deploy.mjs';
|
|
5
|
+
import { mergeClaudeSettings, backupBeforeOverwrite } from './merge.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deploy Specline assets to a Claude Code project.
|
|
9
|
+
*
|
|
10
|
+
* Skills -> .claude/skills/ | Agents -> .claude/agents/*.md
|
|
11
|
+
* Hook -> merge .claude/settings.json (SessionStart)
|
|
12
|
+
*
|
|
13
|
+
* @param {string} projectDir Target project root
|
|
14
|
+
* @param {{ packageRoot?: string, dryRun?: boolean }} [options]
|
|
15
|
+
* @returns {{ filesWritten: string[], settingsAction: 'created'|'merged'|'unchanged' }}
|
|
16
|
+
*/
|
|
17
|
+
export function deployClaude(projectDir, options = {}) {
|
|
18
|
+
const { packageRoot = PACKAGE_ROOT, dryRun = false } = options;
|
|
19
|
+
|
|
20
|
+
const manifest = getClaudeUpstreamManifest(packageRoot);
|
|
21
|
+
|
|
22
|
+
const settingsRel = '.claude/settings.json';
|
|
23
|
+
manifest.delete(settingsRel);
|
|
24
|
+
|
|
25
|
+
const filesWritten = [];
|
|
26
|
+
|
|
27
|
+
if (!dryRun) {
|
|
28
|
+
for (const dir of ['.claude/skills', '.claude/agents']) {
|
|
29
|
+
const full = join(projectDir, dir);
|
|
30
|
+
if (!existsSync(full)) mkdirSync(full, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
writeManifestToProject(projectDir, manifest);
|
|
33
|
+
}
|
|
34
|
+
for (const rel of manifest.keys()) filesWritten.push(rel);
|
|
35
|
+
|
|
36
|
+
const settingsAction = mergeClaudeSettingsFile(projectDir, packageRoot, dryRun);
|
|
37
|
+
|
|
38
|
+
filesWritten.push(settingsRel);
|
|
39
|
+
return { filesWritten, settingsAction };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Merge .claude/settings.json with Specline hooks template.
|
|
44
|
+
*
|
|
45
|
+
* - Existing file: backup → merge hooks.SessionStart → preserve user config
|
|
46
|
+
* - No file: create with hooks section only
|
|
47
|
+
*
|
|
48
|
+
* @param {string} projectDir
|
|
49
|
+
* @param {string} packageRoot
|
|
50
|
+
* @param {boolean} dryRun
|
|
51
|
+
* @returns {'created'|'merged'|'unchanged'}
|
|
52
|
+
*/
|
|
53
|
+
function mergeClaudeSettingsFile(projectDir, packageRoot, dryRun) {
|
|
54
|
+
const settingsPath = join(projectDir, '.claude', 'settings.json');
|
|
55
|
+
const templatePath = join(packageRoot, 'adapters', 'claude', 'hooks', 'hooks.json');
|
|
56
|
+
if (!existsSync(templatePath)) return 'unchanged';
|
|
57
|
+
|
|
58
|
+
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
59
|
+
|
|
60
|
+
if (existsSync(settingsPath)) {
|
|
61
|
+
const existingContent = readFileSync(settingsPath, 'utf-8');
|
|
62
|
+
const merged = mergeClaudeSettings(existingContent, templateContent);
|
|
63
|
+
if (merged === existingContent) return 'unchanged';
|
|
64
|
+
|
|
65
|
+
if (!dryRun) {
|
|
66
|
+
backupBeforeOverwrite(settingsPath);
|
|
67
|
+
writeFileSync(settingsPath, merged, 'utf-8');
|
|
68
|
+
}
|
|
69
|
+
return 'merged';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!dryRun) {
|
|
73
|
+
const dir = dirname(settingsPath);
|
|
74
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
75
|
+
|
|
76
|
+
const freshSettings = mergeClaudeSettings('', templateContent);
|
|
77
|
+
writeFileSync(settingsPath, freshSettings, 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
return 'created';
|
|
80
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { PACKAGE_ROOT } from './paths.mjs';
|
|
4
|
+
import { getCodexUpstreamManifest, writeManifestToProject } from './deploy.mjs';
|
|
5
|
+
import { mergeHooksJson } from './merge.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deploy Specline assets to a Codex project.
|
|
9
|
+
*
|
|
10
|
+
* Skills -> .codex/skills/ | Agents -> .codex/agents/*.toml (YAML -> TOML)
|
|
11
|
+
* Hook -> .codex/hooks.json (copy or merge)
|
|
12
|
+
*
|
|
13
|
+
* @param {string} projectDir Target project root
|
|
14
|
+
* @param {{ packageRoot?: string, dryRun?: boolean }} [options]
|
|
15
|
+
* @returns {{ filesWritten: string[], hooksAction: 'created'|'merged'|'unchanged' }}
|
|
16
|
+
*/
|
|
17
|
+
export function deployCodex(projectDir, options = {}) {
|
|
18
|
+
const { packageRoot = PACKAGE_ROOT, dryRun = false } = options;
|
|
19
|
+
|
|
20
|
+
const manifest = getCodexUpstreamManifest(packageRoot);
|
|
21
|
+
|
|
22
|
+
const hooksRel = '.codex/hooks.json';
|
|
23
|
+
manifest.delete(hooksRel);
|
|
24
|
+
|
|
25
|
+
const filesWritten = [];
|
|
26
|
+
|
|
27
|
+
if (!dryRun) {
|
|
28
|
+
for (const dir of ['.codex/skills', '.codex/agents']) {
|
|
29
|
+
const full = join(projectDir, dir);
|
|
30
|
+
if (!existsSync(full)) mkdirSync(full, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
writeManifestToProject(projectDir, manifest);
|
|
33
|
+
}
|
|
34
|
+
for (const rel of manifest.keys()) filesWritten.push(rel);
|
|
35
|
+
|
|
36
|
+
const hooksAction = mergeCodexHooksFile(projectDir, packageRoot, dryRun);
|
|
37
|
+
|
|
38
|
+
filesWritten.push(hooksRel);
|
|
39
|
+
return { filesWritten, hooksAction };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Merge .codex/hooks.json with Specline hooks template.
|
|
44
|
+
*
|
|
45
|
+
* - Existing file: merge (preserve user-defined hooks, update specline hooks)
|
|
46
|
+
* - No file: copy from adapters/codex/hooks.json
|
|
47
|
+
*
|
|
48
|
+
* @param {string} projectDir
|
|
49
|
+
* @param {string} packageRoot
|
|
50
|
+
* @param {boolean} dryRun
|
|
51
|
+
* @returns {'created'|'merged'|'unchanged'}
|
|
52
|
+
*/
|
|
53
|
+
function mergeCodexHooksFile(projectDir, packageRoot, dryRun) {
|
|
54
|
+
const hooksPath = join(projectDir, '.codex', 'hooks.json');
|
|
55
|
+
const templatePath = join(packageRoot, 'adapters', 'codex', 'hooks.json');
|
|
56
|
+
if (!existsSync(templatePath)) return 'unchanged';
|
|
57
|
+
|
|
58
|
+
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
59
|
+
|
|
60
|
+
if (existsSync(hooksPath)) {
|
|
61
|
+
const existingContent = readFileSync(hooksPath, 'utf-8');
|
|
62
|
+
const merged = mergeHooksJson(existingContent, templateContent);
|
|
63
|
+
if (merged === existingContent) return 'unchanged';
|
|
64
|
+
|
|
65
|
+
if (!dryRun) {
|
|
66
|
+
writeFileSync(hooksPath, merged, 'utf-8');
|
|
67
|
+
}
|
|
68
|
+
return 'merged';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!dryRun) {
|
|
72
|
+
const dir = dirname(hooksPath);
|
|
73
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
74
|
+
copyFileSync(templatePath, hooksPath);
|
|
75
|
+
}
|
|
76
|
+
return 'created';
|
|
77
|
+
}
|