@flyin-ai/alloy 0.1.1 → 0.2.0-beta.1
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 +28 -90
- package/commands/alloy/apply.md +274 -119
- package/commands/alloy/archive.md +116 -60
- package/commands/alloy/discard.md +57 -15
- package/commands/alloy/finish.md +92 -70
- package/commands/alloy/fix.md +272 -53
- package/commands/alloy/plan.md +125 -63
- package/commands/alloy/references/interaction-style.md +82 -0
- package/commands/alloy/references/main-branch-detection.md +32 -0
- package/commands/alloy/references/phase-routing.md +21 -0
- package/commands/alloy/references/skill-precheck.md +46 -0
- package/commands/alloy/start.md +187 -62
- package/commands/alloy/status.md +1 -1
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +28 -6
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js +40 -37
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/internal/config.d.ts +1 -0
- package/dist/cli/commands/internal/config.js +45 -0
- package/dist/cli/commands/internal/config.js.map +1 -0
- package/dist/cli/commands/internal/guard.js +2 -41
- package/dist/cli/commands/internal/guard.js.map +1 -1
- package/dist/cli/commands/internal/record.js +10 -47
- package/dist/cli/commands/internal/record.js.map +1 -1
- package/dist/cli/commands/internal/skill-usage.d.ts +1 -0
- package/dist/cli/commands/internal/skill-usage.js +78 -0
- package/dist/cli/commands/internal/skill-usage.js.map +1 -0
- package/dist/cli/commands/internal/state.js +105 -6
- package/dist/cli/commands/internal/state.js.map +1 -1
- package/dist/cli/commands/status.d.ts +1 -0
- package/dist/cli/commands/status.js +50 -11
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/update.js +20 -17
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +73 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/hash.d.ts +3 -0
- package/dist/cli/utils/hash.js +42 -0
- package/dist/cli/utils/hash.js.map +1 -0
- package/dist/cli/utils/state.d.ts +4 -2
- package/dist/cli/utils/state.js +34 -2
- package/dist/cli/utils/state.js.map +1 -1
- package/dist/core/artifacts.d.ts +3 -0
- package/dist/core/artifacts.js +45 -0
- package/dist/core/artifacts.js.map +1 -0
- package/dist/core/detect-installations.d.ts +19 -0
- package/dist/core/detect-installations.js +65 -0
- package/dist/core/detect-installations.js.map +1 -0
- package/dist/core/openspec.d.ts +2 -1
- package/dist/core/openspec.js +30 -12
- package/dist/core/openspec.js.map +1 -1
- package/dist/core/skills.js +16 -0
- package/dist/core/skills.js.map +1 -1
- package/dist/core/superpowers.d.ts +8 -1
- package/dist/core/superpowers.js +41 -20
- package/dist/core/superpowers.js.map +1 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/utils/format.d.ts +31 -0
- package/dist/utils/format.js +101 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/output.d.ts +16 -0
- package/dist/utils/output.js +38 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/prompt.d.ts +0 -1
- package/dist/utils/prompt.js +11 -83
- package/dist/utils/prompt.js.map +1 -1
- package/openspec/schemas/alloy/instructions/retrospective.md +17 -35
- package/openspec/schemas/alloy/templates/design.md +2 -0
- package/openspec/schemas/alloy/templates/draft.md +2 -0
- package/openspec/schemas/alloy/templates/plans.md +2 -0
- package/openspec/schemas/alloy/templates/proposal.md +2 -0
- package/openspec/schemas/alloy/templates/retrospective.md +39 -19
- package/openspec/schemas/alloy/templates/specs.md +2 -0
- package/openspec/schemas/alloy/templates/tasks.md +2 -0
- package/package.json +8 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "Alloy: Archive"
|
|
3
|
-
description: Alloy
|
|
3
|
+
description: Alloy 归档阶段 - apply 完成后进入
|
|
4
4
|
category: Workflow
|
|
5
5
|
tags: [alloy, workflow]
|
|
6
6
|
---
|
|
@@ -11,39 +11,50 @@ tags: [alloy, workflow]
|
|
|
11
11
|
|
|
12
12
|
**核心原则:先锁定文档证据链,再合入代码。** archive 只负责 spec 归档,代码合入由后续的 `/alloy:finish` 完成。
|
|
13
13
|
|
|
14
|
+
**调用外部命令或技能前,先输出标题和状态描述,再执行操作。不要只出标题然后沉默。**
|
|
15
|
+
|
|
16
|
+
**捕获阶段启动时间**(命令调用后第一时间,前置检查之前,幂等——重入时返回已有值):
|
|
17
|
+
```bash
|
|
18
|
+
PHASE_START=$(alloy _state timestamp ensure openspec/changes/<name> archive)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
14
23
|
**什么算"archive 操作不当"(反例):**
|
|
15
24
|
- verify.md 的 Overall Decision 是 FAIL 但仍然继续归档——阻塞问题被无视
|
|
16
25
|
- 跳过 archive 直接手动 merge——Delta Spec 没有被同步,主 spec 落后于代码
|
|
17
26
|
- openspec archive 返回错误但忽视警告继续——"反正代码对的,spec 后面再说"
|
|
18
27
|
|
|
28
|
+
### Red Flags——STOP,不要继续
|
|
29
|
+
|
|
30
|
+
以下任何一个念头出现,都意味着闸门正在被绕过:
|
|
31
|
+
|
|
32
|
+
| 借口 | 现实 |
|
|
33
|
+
|------|------|
|
|
34
|
+
| "verify.md FAIL 是小问题,先归档再说" | FAIL = 阻塞问题。归档不可逆——带着 FAIL 归档意味着 spec 与代码的偏差被永久封存。 |
|
|
35
|
+
| "跳过 archive 直接 merge,spec 后面补" | Delta Spec 不同步 = 主 spec 落后于代码。"后面补"的 spec 永远不会补。 |
|
|
36
|
+
| "openspec archive 报错了,但代码是对的,忽略吧" | 归档报错意味着 Delta Spec 合并失败。忽略 = 主 spec 停留在旧版本,下次 change 基于过期 spec 做设计。 |
|
|
37
|
+
| "worktree 合并冲突了,跳过清理吧" | 冲突不解决 = 代码丢失。worktree 中的变更没有合入 feature 分支就删除,等于白做。 |
|
|
38
|
+
| "反正是个小 change,不用归档了吧" | 小 change 也需要 spec 同步。不归档 = 主 spec 和代码永久分叉。大小 change 的归档流程完全一样。 |
|
|
39
|
+
|
|
19
40
|
---
|
|
20
41
|
|
|
21
42
|
## 前置检查(HARD STOP)
|
|
22
43
|
|
|
23
44
|
### [Step 1/3] 前置检查
|
|
24
45
|
|
|
25
|
-
**记录阶段开始时间:**
|
|
26
|
-
```bash
|
|
27
|
-
COMPLETED_AT=$(date "+%Y-%m-%d %H:%M:%S")
|
|
28
|
-
TIMINGS=$(alloy _state read openspec/changes/<name> phase_timings 2>/dev/null || echo "{}")
|
|
29
|
-
echo "$TIMINGS" | python3 -c "
|
|
30
|
-
import sys,json
|
|
31
|
-
content = sys.stdin.read()
|
|
32
|
-
d = json.loads(content) if content.strip() else {}
|
|
33
|
-
p = d.setdefault('archive',{})
|
|
34
|
-
if 'started_at' not in p:
|
|
35
|
-
p['started_at']='$COMPLETED_AT'
|
|
36
|
-
print(json.dumps(d))
|
|
37
|
-
" | while read -r val; do alloy _state write openspec/changes/<name> phase_timings "$val"; done
|
|
38
|
-
```
|
|
39
|
-
|
|
40
46
|
```
|
|
41
47
|
┌──────────────────────────────────────┐
|
|
42
48
|
│ Alloy [4/5] · Phase: Archive │
|
|
43
|
-
│ 启动时间:
|
|
49
|
+
│ 启动时间: $PHASE_START
|
|
44
50
|
└──────────────────────────────────────┘
|
|
45
51
|
```
|
|
46
52
|
|
|
53
|
+
**0. Skill 预检:** 确认以下依赖可用:
|
|
54
|
+
cmd: opsx/archive
|
|
55
|
+
|
|
56
|
+
读取 `commands/alloy/references/skill-precheck.md` 了解检测方法。任一不可用 → 引导 `alloy init` → STOP。
|
|
57
|
+
|
|
47
58
|
**1. phase 检查:**
|
|
48
59
|
|
|
49
60
|
先通过 `alloy _guard` 做硬校验:
|
|
@@ -51,17 +62,7 @@ print(json.dumps(d))
|
|
|
51
62
|
alloy _guard openspec/changes/<name> archived
|
|
52
63
|
```
|
|
53
64
|
|
|
54
|
-
若 guard 报错(phase
|
|
55
|
-
|
|
56
|
-
| 当前 phase | 行为 |
|
|
57
|
-
|-----------|------|
|
|
58
|
-
| started | "尚未 plan,自动进入 /alloy:plan" → 加载 alloy-plan 指令 |
|
|
59
|
-
| planned | "尚未 apply,自动进入 /alloy:apply" → 加载 alloy-apply 指令 |
|
|
60
|
-
| applied | precheck 通过,继续归档 |
|
|
61
|
-
| archived | "已归档,自动进入 /alloy:finish" → 加载 alloy-finish 指令 |
|
|
62
|
-
| finished | "工作流已完成" → STOP |
|
|
63
|
-
|
|
64
|
-
**实现方式:** 输出对应命令文件的完整指令,将 change name 和当前进度信息作为上下文传入。
|
|
65
|
+
若 guard 报错(phase 不匹配),读取 `commands/alloy/references/phase-routing.md` 按路由表自动跳转。当前 phase=applied 时 precheck 通过。
|
|
65
66
|
|
|
66
67
|
**HARD STOP 保留场景:** change 目录不存在(前序阶段完全没做)→ 引导用户先运行 `/alloy:start`。
|
|
67
68
|
|
|
@@ -88,6 +89,22 @@ test -f openspec/changes/<name>/verify.md && ! grep -q '^- \[x\] ❌ FAIL' opens
|
|
|
88
89
|
- `/opsx:archive` 返回错误(权限、冲突等)→ [HARD STOP],不推进 phase
|
|
89
90
|
- `/opsx:archive` 不可用(OpenSpec 未安装)→ 引导用户运行 `alloy init` 安装 OpenSpec
|
|
90
91
|
|
|
92
|
+
**命令执行后立即记录:**
|
|
93
|
+
```bash
|
|
94
|
+
alloy _skill log openspec/changes/<name> archive opsx:archive
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**归档变更提交(必须在 worktree 清理之前):**
|
|
98
|
+
|
|
99
|
+
`/opsx:archive` 执行 `mv` 移动目录,但不负责 git commit。如果当前在 worktree 中,变更必须先 commit 到 worktree 分支,否则 worktree 清理时 merge 会丢失归档操作。
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git add -A openspec/specs/ openspec/changes/
|
|
103
|
+
git diff --cached --quiet || git commit -m "chore(<name>): 归档目录移动"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`git diff --cached --quiet` 在无变更时跳过(`opsx:archive` 已自行 commit 的场景)。
|
|
107
|
+
|
|
91
108
|
归档成功后,确定归档路径并处理后续步骤:
|
|
92
109
|
|
|
93
110
|
```bash
|
|
@@ -99,39 +116,85 @@ ARCHIVE_DIR="openspec/changes/archive/$(date +%Y-%m-%d)-<name>"
|
|
|
99
116
|
|
|
100
117
|
> 这是 retrospective 从"死文档"变成"活反馈"的关键步骤——教训不跨 cycle 就不是教训。
|
|
101
118
|
|
|
102
|
-
|
|
119
|
+
**Worktree 清理(如果 apply 期间使用了 worktree):**
|
|
120
|
+
|
|
103
121
|
```bash
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
WORKTREE_PATH=$(alloy _state read "$ARCHIVE_DIR" worktree 2>/dev/null)
|
|
123
|
+
FEATURE_BRANCH=$(alloy _state read "$ARCHIVE_DIR" feature_branch 2>/dev/null)
|
|
124
|
+
WORKTREE_BRANCH=$(alloy _state read "$ARCHIVE_DIR" worktree_branch 2>/dev/null)
|
|
125
|
+
|
|
126
|
+
if [ "$WORKTREE_PATH" != "null" ] && [ -n "$WORKTREE_PATH" ] && [ "$WORKTREE_PATH" != "skipped" ]; then
|
|
127
|
+
echo " ℹ 检测到 worktree($WORKTREE_PATH),正在合并回 feature 分支..."
|
|
128
|
+
|
|
129
|
+
# 向下兼容:遗留 change 无 feature_branch → 退回到 feature/<name>
|
|
130
|
+
if [ -z "$FEATURE_BRANCH" ] || [ "$FEATURE_BRANCH" = "null" ]; then
|
|
131
|
+
FEATURE_BRANCH="feature/<name>"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# 向下兼容:遗留 change 无 worktree_branch → 从 worktree 实际状态检测
|
|
135
|
+
if [ -z "$WORKTREE_BRANCH" ] || [ "$WORKTREE_BRANCH" = "null" ]; then
|
|
136
|
+
WORKTREE_BRANCH=$(git worktree list --porcelain | awk -v path="$WORKTREE_PATH" '
|
|
137
|
+
/^worktree / { wt = substr($0, 10) }
|
|
138
|
+
/^branch / && wt == path { gsub(/^refs\/heads\//, "", $2); print $2; exit }
|
|
139
|
+
')
|
|
140
|
+
if [ -z "$WORKTREE_BRANCH" ]; then
|
|
141
|
+
echo " ⚠ 无法检测 worktree 分支名(worktree_branch 为空且 git worktree list 未匹配)"
|
|
142
|
+
echo " 请手动指定: git merge <worktree-branch> && git worktree remove $WORKTREE_PATH"
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# 切回主仓库目录
|
|
148
|
+
MAIN_ROOT=$(cd "$WORKTREE_PATH" && git rev-parse --show-toplevel 2>/dev/null)
|
|
149
|
+
|
|
150
|
+
# 从 worktree 分支合并代码到 feature 分支
|
|
151
|
+
cd "$MAIN_ROOT"
|
|
152
|
+
git merge "$WORKTREE_BRANCH" --no-edit
|
|
153
|
+
|
|
154
|
+
if [ $? -eq 0 ]; then
|
|
155
|
+
# 删除 worktree 目录和分支
|
|
156
|
+
git worktree remove "$WORKTREE_PATH"
|
|
157
|
+
git branch -d "$WORKTREE_BRANCH"
|
|
158
|
+
# 标记已清理(保留 worktree/worktree_branch 原值不删除)
|
|
159
|
+
WORKTREE_MERGED_AT=$(date '+%Y-%m-%d %H:%M:%S')
|
|
160
|
+
alloy _state write "$ARCHIVE_DIR" worktree_merged_at "$WORKTREE_MERGED_AT"
|
|
161
|
+
echo " ✓ worktree 已合并至 $FEATURE_BRANCH 分支并清理"
|
|
162
|
+
else
|
|
163
|
+
echo " ⚠ merge 冲突,请手动解决后再继续"
|
|
164
|
+
echo " 先: git checkout $FEATURE_BRANCH && git merge $WORKTREE_BRANCH"
|
|
165
|
+
exit 1
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
106
168
|
```
|
|
107
|
-
(git commit 失败不阻断——可能没有变更或不在 git 仓库中)
|
|
108
169
|
|
|
109
|
-
|
|
110
|
-
> ✓ Change 已归档到 $ARCHIVE_DIR
|
|
111
|
-
> ✓ 归档变更已提交
|
|
170
|
+
如果 apply 期间未使用 worktree(worktree 为 null 或 skipped),则跳过此步骤。
|
|
112
171
|
|
|
113
|
-
|
|
172
|
+
**记录完成时间并提交(所有 .alloy.yaml 变更在 commit 之前完成):**
|
|
114
173
|
|
|
115
|
-
**记录阶段完成时间:**
|
|
116
174
|
```bash
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
print(json.dumps(d))
|
|
127
|
-
" | while read -r val; do alloy _state write "$ARCHIVE_DIR" phase_timings "$val"; done
|
|
128
|
-
git add "$ARCHIVE_DIR/"
|
|
129
|
-
git commit -m "chore(<name>): 记录 archive 阶段完成时间"
|
|
175
|
+
# 写入完成时间(:="${...:-...}" 兜底:防 Agent 漏 capture)
|
|
176
|
+
COMPLETED_AT="${COMPLETED_AT:-$(date '+%Y-%m-%d %H:%M:%S')}"
|
|
177
|
+
COMPLETED_AT_JSON=$(python3 -c "import json; print(json.dumps({'archive':{'completed_at': '$COMPLETED_AT'}}))")
|
|
178
|
+
alloy _state merge "$ARCHIVE_DIR" phase_timings "$COMPLETED_AT_JSON"
|
|
179
|
+
|
|
180
|
+
# commit:.alloy.yaml 变更(phase_timings + worktree_merged_at)
|
|
181
|
+
# 归档目录移动已在 worktree 清理前提交(或无 worktree 时在上方提交)
|
|
182
|
+
git add -A openspec/specs/ openspec/changes/
|
|
183
|
+
git commit -m "chore(<name>): 归档阶段完成"
|
|
130
184
|
```
|
|
185
|
+
commit 失败必须阻断——.alloy.yaml 变更未提交时,后续 finish 阶段状态不一致。
|
|
186
|
+
|
|
187
|
+
> ✓ Delta Spec 已合并到主 spec
|
|
188
|
+
> ✓ Change 已归档到 $ARCHIVE_DIR
|
|
189
|
+
> ✓ 归档变更已提交(目录移动 + 阶段完成时间)
|
|
190
|
+
|
|
191
|
+
### [Step 3/3] 完成
|
|
131
192
|
|
|
132
193
|
**通过 `alloy _guard` 校验并推进 phase:**
|
|
133
194
|
```bash
|
|
134
195
|
alloy _guard "$ARCHIVE_DIR" archived --apply
|
|
196
|
+
git add -A openspec/specs/ openspec/changes/
|
|
197
|
+
git commit -m "chore(<name>): phase → archived"
|
|
135
198
|
```
|
|
136
199
|
|
|
137
200
|
```
|
|
@@ -149,21 +212,14 @@ alloy _guard "$ARCHIVE_DIR" archived --apply
|
|
|
149
212
|
✓ Delta Spec 已合并到主 spec
|
|
150
213
|
✓ Change 已归档
|
|
151
214
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
alloy _state read "$ARCHIVE_DIR" worktree
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
- worktree 有值 → 代码在独立 worktree 分支上,尚未合入。运行 `/alloy:finish` 完成代码合入与现场清理。
|
|
159
|
-
- worktree 为 `null` → 代码在当前分支上。运行 `/alloy:finish` 完成收尾。
|
|
215
|
+
> → 代码合入由 `/alloy:finish` 处理
|
|
160
216
|
|
|
161
217
|
---
|
|
162
218
|
|
|
163
219
|
## 闸门规则
|
|
164
220
|
|
|
165
|
-
- **git add 只用精确路径** — 永远不用 `-
|
|
166
|
-
archive
|
|
221
|
+
- **git add 只用精确路径** — 永远不用 `-a`、`.`。
|
|
222
|
+
archive 用 `git add -A openspec/specs/ openspec/changes/`(`-A` 限路径,只追踪这两个目录的新增/修改/删除),反例:`git add -A`(无路径限定,扫全仓)
|
|
167
223
|
- **phase 必须为 applied** —— 只有 apply 完成的 change 才能归档
|
|
168
224
|
- **verify.md 必须存在且非 FAIL** —— 阻塞问题必须先修复
|
|
169
225
|
- **先归档后合入** —— spec 文档先锁定,代码后通过 `/alloy:finish` 合入,避免"代码合入了 spec 还没跟上"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "Alloy: Discard"
|
|
3
|
-
description: Alloy 放弃 change -
|
|
3
|
+
description: Alloy 放弃 change - 需要放弃时调用
|
|
4
4
|
category: Workflow
|
|
5
5
|
tags: [alloy, workflow]
|
|
6
6
|
---
|
|
@@ -9,6 +9,10 @@ tags: [alloy, workflow]
|
|
|
9
9
|
|
|
10
10
|
你是 Alloy 的放弃清理器。你的职责是:根据 change 的当前 phase 执行分级清理,确保用户明确确认后再删除。
|
|
11
11
|
|
|
12
|
+
**核心原则:精确确认才能删除,残留必须清理。**
|
|
13
|
+
|
|
14
|
+
每个 change 必须有独立的 feature 分支(start step 6 保证),discard 时可安全删除整个分支。
|
|
15
|
+
|
|
12
16
|
---
|
|
13
17
|
|
|
14
18
|
## 读取当前状态
|
|
@@ -18,24 +22,34 @@ Alloy · 放弃 Change
|
|
|
18
22
|
──────────────────────────────────────
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
读取必要信息:
|
|
22
26
|
```bash
|
|
23
27
|
alloy _state read openspec/changes/<name> phase
|
|
24
28
|
alloy _state read openspec/changes/<name> worktree
|
|
29
|
+
alloy _state read openspec/changes/<name> feature_branch
|
|
30
|
+
alloy _config read . main_branch
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
---
|
|
28
34
|
|
|
29
35
|
## Phase 分级行为
|
|
30
36
|
|
|
31
|
-
| phase |
|
|
32
|
-
|
|
33
|
-
| started / planned |
|
|
34
|
-
| applied / archived |
|
|
37
|
+
| phase | 清理动作 |
|
|
38
|
+
|-------|---------|
|
|
39
|
+
| started / planned | `git checkout <main_branch>` + `git branch -D <feature_branch>` + `rm change 目录` |
|
|
40
|
+
| applied / archived | `git worktree remove` + `git checkout <main_branch>` + `git branch -D <feature_branch>` + `rm change 目录` |
|
|
35
41
|
| finished | **[HARD STOP] 已完成的 change 不可 discard。** finished 是终态 |
|
|
36
42
|
|
|
37
43
|
---
|
|
38
44
|
|
|
45
|
+
## 安全兜底
|
|
46
|
+
|
|
47
|
+
- `feature_branch` == `main_branch` → 不删分支(理论上不会发生,start step 6 已拦截)
|
|
48
|
+
- `main_branch` 未记录 → 提示用户手动切回主分支,不执行 `git checkout`
|
|
49
|
+
- `feature_branch` 未记录 → 仅删除 worktree 和 change 目录,不删分支
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
39
53
|
## 确认提示
|
|
40
54
|
|
|
41
55
|
清理前必须展示将要删除的内容并等待用户精确确认:
|
|
@@ -43,11 +57,12 @@ alloy _state read openspec/changes/<name> worktree
|
|
|
43
57
|
```
|
|
44
58
|
将删除以下内容,不可恢复:
|
|
45
59
|
|
|
46
|
-
Change:
|
|
47
|
-
Phase:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
目录:
|
|
60
|
+
Change: <name>
|
|
61
|
+
Phase: <phase>
|
|
62
|
+
Feature 分支: <feature_branch>(如有)
|
|
63
|
+
Worktree: <path>(如有)
|
|
64
|
+
目录: openspec/changes/<name>/
|
|
65
|
+
切回分支: <main_branch>(如有)
|
|
51
66
|
|
|
52
67
|
输入 'discard <name>' 确认,或输入其他任意内容取消。
|
|
53
68
|
```
|
|
@@ -56,16 +71,42 @@ alloy _state read openspec/changes/<name> worktree
|
|
|
56
71
|
- 用户说"好"——不算,需要精确输入 `discard <name>`
|
|
57
72
|
- 用户说"删吧"——不算,同上
|
|
58
73
|
- 用户说"y"——不算,需要完整匹配
|
|
74
|
+
- 用户说"嗯"——不算,中文里可以表示"知道了"而非"确认执行"
|
|
75
|
+
- 用户说"删了吧"——不算,虽然语义明确但不够精确,仍需要完整匹配
|
|
59
76
|
|
|
60
77
|
只有用户精确输入 `discard <name>` 后才执行清理。精确匹配是故意的——防止手滑删除。
|
|
61
78
|
|
|
79
|
+
### Red Flags——STOP,不可跳过闸门
|
|
80
|
+
|
|
81
|
+
| 借口 | 现实 |
|
|
82
|
+
|------|------|
|
|
83
|
+
| "就删个目录而已,y 就行了" | 删除是不可逆操作。`discard <name>` 的精确匹配是防手滑的最后一道防线。"y"、"好"、"删了吧"都不算。 |
|
|
84
|
+
| "这个 change 已完成,直接删了吧" | finished 是终态——不可 discard。已完成的 change 有完整审计链,删除会破坏追溯性。 |
|
|
85
|
+
| "不用列出清单了,我知道有什么" | 必须先展示六行删除清单(Change/Phase/分支/Worktree/目录/切回分支),让用户确认每个待删除项的完整性和正确性。 |
|
|
86
|
+
| "不用按顺序清理,直接 rm -rf 就行了" | 清理必须按序:worktree remove → checkout main → branch -D → rm 目录。不按序可能留下孤儿 worktree 或残留分支。
|
|
87
|
+
|
|
62
88
|
---
|
|
63
89
|
|
|
64
90
|
## 确认后清理
|
|
65
91
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
**执行顺序(必须按序):**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 1. git worktree remove(如存在且 phase ≥ applied)
|
|
96
|
+
git worktree remove <path> --force
|
|
97
|
+
|
|
98
|
+
# 2. git checkout <main_branch>(切离要删的分支)
|
|
99
|
+
git checkout <main_branch>
|
|
100
|
+
|
|
101
|
+
# 3. git branch -D <feature_branch>
|
|
102
|
+
git branch -D <feature_branch>
|
|
103
|
+
|
|
104
|
+
# 4. rm -rf openspec/changes/<name>/
|
|
105
|
+
rm -rf openspec/changes/<name>/
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
若 `main_branch` 未记录,跳过步骤 2,提示用户手动切回主分支。
|
|
109
|
+
若 `feature_branch` 未记录,跳过步骤 3。
|
|
69
110
|
|
|
70
111
|
---
|
|
71
112
|
|
|
@@ -76,5 +117,6 @@ Alloy · 放弃 Change — DONE
|
|
|
76
117
|
──────────────────────────────────────
|
|
77
118
|
|
|
78
119
|
✓ <name> 已清理
|
|
79
|
-
|
|
120
|
+
已删除:<列出实际删除的内容(分支/worktree/目录)>
|
|
121
|
+
当前分支:<main_branch>(或提示用户手动切换)
|
|
80
122
|
```
|
package/commands/alloy/finish.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "Alloy: Finish"
|
|
3
|
-
description: Alloy
|
|
3
|
+
description: Alloy 收尾阶段 - archive 完成后进入
|
|
4
4
|
category: Workflow
|
|
5
5
|
tags: [alloy, workflow]
|
|
6
6
|
---
|
|
@@ -9,42 +9,53 @@ tags: [alloy, workflow]
|
|
|
9
9
|
|
|
10
10
|
你是 Alloy 的收尾命令。你的职责是:在 spec 已归档(phase=archived)的前提下,完成代码合入与现场清理,将 phase 推进到 `finished`。
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**核心原则:只做代码合入,不碰 spec。** 如果合入过程中(如 PR 审查)发现需要修改 spec,那是另一个 change 的事——当前 change 的 spec 已归档封存。
|
|
13
|
+
|
|
14
|
+
**交互风格:** 主分支确认、合并策略选择使用 `AskUserQuestion` 工具。**合并确认仍用精确文本匹配**(安全机制)。详见 `commands/alloy/references/interaction-style.md`。
|
|
15
|
+
|
|
16
|
+
**调用外部命令或技能前,先输出标题和状态描述,再执行操作。不要只出标题然后沉默。**
|
|
17
|
+
|
|
18
|
+
**捕获阶段启动时间**(命令调用后第一时间,前置检查之前,幂等——重入时返回已有值):
|
|
19
|
+
```bash
|
|
20
|
+
PHASE_START=$(alloy _state timestamp ensure openspec/changes/<name> finish)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
13
24
|
|
|
14
25
|
**什么算"finish 使用不当"(反例):**
|
|
15
26
|
- phase 不是 archived 时调 finish——"反正就合个代码"——跳过了 archive,spec 没有同步
|
|
16
27
|
- 分支已 merge 或删除后重复调 finish——浪费操作,应该直接告知用户无需再次 finish
|
|
17
28
|
- finish 过程中试图修改 spec——spec 已归档,任何 spec 级变更应走新 change
|
|
18
29
|
|
|
30
|
+
### Red Flags——STOP,不要继续
|
|
31
|
+
|
|
32
|
+
以下任何一个念头出现,都意味着闸门正在被绕过:
|
|
33
|
+
|
|
34
|
+
| 借口 | 现实 |
|
|
35
|
+
|------|------|
|
|
36
|
+
| "phase 不是 archived,但代码都已经写好了,直接合吧" | archive 不可跳过——spec 归档和代码合入是两件事。先归档再合入,顺序不可颠倒。 |
|
|
37
|
+
| "分支已经删了,finish 白跑了" | finish 前置检查的第一步就是确认分支存在。分支不存在 = 无需再次 finish,直接告知用户。 |
|
|
38
|
+
| "PR 审查说要改 spec,我在 finish 里顺手改了吧" | spec 已归档封存。任何 spec 级变更 = 新 change。当前 change 的 finish 不涉及 spec 修改。 |
|
|
39
|
+
| "选'保持分支'就等于没做完,太麻烦了,直接 merge 吧" | 保持分支是合法选项——用户可能有后续计划。替用户选 merge 是越权。 |
|
|
40
|
+
| "merge 确认太啰嗦了,用户说'好'就是同意了" | merge 确认必须精确文本匹配。`merge <branch> into <branch>` 是安全机制——防止手滑合入。 |
|
|
41
|
+
|
|
19
42
|
---
|
|
20
43
|
|
|
21
44
|
## 前置检查
|
|
22
45
|
|
|
23
|
-
**记录阶段开始时间:**
|
|
24
|
-
```bash
|
|
25
|
-
COMPLETED_AT=$(date "+%Y-%m-%d %H:%M:%S")
|
|
26
|
-
TIMINGS=$(alloy _state read openspec/changes/<name> phase_timings 2>/dev/null || echo "{}")
|
|
27
|
-
echo "$TIMINGS" | python3 -c "
|
|
28
|
-
import sys,json
|
|
29
|
-
content = sys.stdin.read()
|
|
30
|
-
d = json.loads(content) if content.strip() else {}
|
|
31
|
-
p = d.setdefault('finish',{})
|
|
32
|
-
if 'started_at' not in p:
|
|
33
|
-
p['started_at']='$COMPLETED_AT'
|
|
34
|
-
print(json.dumps(d))
|
|
35
|
-
" | while read -r val; do alloy _state write openspec/changes/<name> phase_timings "$val"; done
|
|
36
|
-
```
|
|
37
|
-
|
|
38
46
|
```
|
|
39
47
|
┌──────────────────────────────────────┐
|
|
40
48
|
│ Alloy [5/5] · Phase: Finish │
|
|
41
|
-
│ 启动时间:
|
|
49
|
+
│ 启动时间: $PHASE_START
|
|
42
50
|
└──────────────────────────────────────┘
|
|
43
51
|
```
|
|
44
52
|
|
|
45
53
|
### [Step 1/3] 前置检查
|
|
46
54
|
|
|
47
|
-
**0. Skill 预检:**
|
|
55
|
+
**0. Skill 预检:** 确认以下依赖可用:
|
|
56
|
+
skill: finishing-a-development-branch
|
|
57
|
+
|
|
58
|
+
读取 `commands/alloy/references/skill-precheck.md` 了解检测方法。任一不可用 → 引导 `alloy init` → STOP。
|
|
48
59
|
|
|
49
60
|
> phase 是否为 archived? <检查结果>
|
|
50
61
|
|
|
@@ -55,25 +66,24 @@ print(json.dumps(d))
|
|
|
55
66
|
alloy _guard openspec/changes/<name> finished
|
|
56
67
|
```
|
|
57
68
|
|
|
58
|
-
若 guard 报错(phase
|
|
59
|
-
|
|
60
|
-
| 当前 phase | 行为 |
|
|
61
|
-
|-----------|------|
|
|
62
|
-
| started | "尚未 plan,自动进入 /alloy:plan" → 加载 alloy-plan 指令 |
|
|
63
|
-
| planned | "尚未 apply,自动进入 /alloy:apply" → 加载 alloy-apply 指令 |
|
|
64
|
-
| applied | "尚未归档,自动进入 /alloy:archive" → 加载 alloy-archive 指令 |
|
|
65
|
-
| archived | precheck 通过,继续收尾 |
|
|
66
|
-
| finished | "工作流已完成" → STOP |
|
|
67
|
-
|
|
68
|
-
**实现方式:** 输出对应命令文件的完整指令,将 change name 和当前进度信息作为上下文传入。
|
|
69
|
+
若 guard 报错(phase 不匹配),读取 `commands/alloy/references/phase-routing.md` 按路由表自动跳转。当前 phase=archived 时 precheck 通过。
|
|
69
70
|
|
|
70
71
|
**HARD STOP 保留场景:** 分支不存在(可能已 merge 或删除)→ 提示无需再次 finish。
|
|
71
72
|
|
|
72
73
|
确认当前有对应的 git 分支存在:
|
|
73
74
|
```bash
|
|
74
|
-
git branch --list <
|
|
75
|
+
git branch --list <feature_branch>
|
|
76
|
+
```
|
|
77
|
+
分支不存在 → "分支 <feature_branch> 不存在,可能已 merge 或删除。无需再次 finish。"
|
|
78
|
+
|
|
79
|
+
读取主分支作为默认合并目标:
|
|
80
|
+
```bash
|
|
81
|
+
alloy _config read . main_branch
|
|
82
|
+
```
|
|
83
|
+
若 `main_branch` 未记录(输出 `null`)→ 读取 `commands/alloy/references/main-branch-detection.md`,按 3 级优先级自动检测主分支。检测到后让用户确认(Y/n),确认后写入配置:
|
|
84
|
+
```bash
|
|
85
|
+
alloy _config write . main_branch <确认的主分支名>
|
|
75
86
|
```
|
|
76
|
-
分支不存在 → "分支 <change-name> 不存在,可能已 merge 或删除。无需再次 finish。"
|
|
77
87
|
|
|
78
88
|
---
|
|
79
89
|
|
|
@@ -94,15 +104,20 @@ git branch --list <change-name>
|
|
|
94
104
|
```
|
|
95
105
|
Change: <name>
|
|
96
106
|
状态:phase=archived(spec 已归档,代码待合入)
|
|
97
|
-
当前分支:<
|
|
98
|
-
基础分支:<
|
|
107
|
+
当前分支:<feature_branch>
|
|
108
|
+
基础分支:<main_branch>(从 openspec/config.yaml 读取)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**技能加载后立即记录:**
|
|
112
|
+
```bash
|
|
113
|
+
alloy _skill log openspec/changes/<name> finish superpowers:finishing-a-development-branch
|
|
99
114
|
```
|
|
100
115
|
|
|
101
116
|
技能加载后,按其指引提供 3 个选项。
|
|
102
117
|
|
|
103
118
|
### 各选项的后续行为
|
|
104
119
|
|
|
105
|
-
**选项 1
|
|
120
|
+
**选项 1:本地合并(squash)**
|
|
106
121
|
|
|
107
122
|
在执行 merge 之前,必须展示确认信息并等待用户确认:
|
|
108
123
|
|
|
@@ -111,42 +126,64 @@ Change: <name>
|
|
|
111
126
|
>
|
|
112
127
|
> 即将执行本地合并:
|
|
113
128
|
>
|
|
114
|
-
>
|
|
115
|
-
>
|
|
116
|
-
> | 源分支 | <change-name> |
|
|
117
|
-
> | 目标分支 | <base-branch> |
|
|
129
|
+
> 源分支:<feature_branch>
|
|
130
|
+
> 目标分支:<main_branch>
|
|
118
131
|
>
|
|
119
132
|
> 即将合入的提交:
|
|
120
133
|
> ```
|
|
121
|
-
> <git log
|
|
134
|
+
> <git log main_branch..feature_branch --oneline 的输出>
|
|
122
135
|
> ```
|
|
123
136
|
>
|
|
124
137
|
> 合并后 worktree 将被清理,分支将被删除。
|
|
125
138
|
>
|
|
126
|
-
> 输入 merge <
|
|
139
|
+
> 输入 merge <feature_branch> into <main_branch> 确认,或输入其他内容取消。
|
|
127
140
|
|
|
128
141
|
**必须等待用户精确输入确认语句。** "好"、"可以"、"y" 都不算确认。
|
|
129
142
|
|
|
130
|
-
|
|
143
|
+
用户确认后,记录完成时间、推进 phase,再 squash 合并:
|
|
131
144
|
```bash
|
|
132
|
-
|
|
145
|
+
# 确定归档路径(archive 阶段已将目录移至 archive/ 下)
|
|
146
|
+
ARCHIVE_DIR=$(ls -d openspec/changes/archive/*-<name> 2>/dev/null | sort -r | head -1)
|
|
147
|
+
CHANGE_DIR="${ARCHIVE_DIR:-openspec/changes/<name>}"
|
|
148
|
+
|
|
149
|
+
# 记录完成时间 + 推进 phase 到 finished(所有状态变更在 squash merge 之前完成)
|
|
150
|
+
COMPLETED_AT=$(date "+%Y-%m-%d %H:%M:%S")
|
|
151
|
+
alloy _state merge "$CHANGE_DIR" phase_timings "{\"finish\":{\"completed_at\":\"${COMPLETED_AT:-$(date '+%Y-%m-%d %H:%M:%S')}\"}}"
|
|
152
|
+
alloy _guard "$CHANGE_DIR" finished --apply
|
|
153
|
+
git add -A "$CHANGE_DIR" openspec/config.yaml
|
|
154
|
+
git commit -m "chore(<name>): 记录 finish 阶段完成时间"
|
|
155
|
+
|
|
156
|
+
git checkout <main_branch>
|
|
133
157
|
git pull || echo "⚠️ git pull 失败(网络问题或冲突),请手动处理后再继续"
|
|
134
|
-
git merge <
|
|
158
|
+
git merge --squash <feature_branch>
|
|
159
|
+
# 抓取被合入分支的完整 commit 列表,生成类似 GitHub squash merge 的 commit message
|
|
160
|
+
COMMIT_LOG=$(git log <main_branch>..<feature_branch> --format="* %s")
|
|
161
|
+
git commit -m "$(cat <<EOF
|
|
162
|
+
chore(<name>): 合入 main(squash merge)
|
|
163
|
+
|
|
164
|
+
${COMMIT_LOG}
|
|
165
|
+
EOF
|
|
166
|
+
)"
|
|
167
|
+
# squash merge 不产生 merge commit,git 无法识别分支已合入,使用 -D 强删
|
|
168
|
+
git branch -D <feature_branch>
|
|
135
169
|
```
|
|
136
170
|
|
|
137
|
-
若 `git pull` 失败(网络不可达、认证失败),输出警告并暂停,让用户决定是否跳过 pull 直接 merge。若 `git merge` 冲突,输出冲突文件列表,让用户手动解决后继续。
|
|
138
|
-
```bash
|
|
139
|
-
alloy _guard openspec/changes/<name> finished --apply
|
|
140
|
-
```
|
|
171
|
+
若 `git pull` 失败(网络不可达、认证失败),输出警告并暂停,让用户决定是否跳过 pull 直接 merge。若 `git merge --squash` 冲突,输出冲突文件列表,让用户手动解决后继续。
|
|
141
172
|
|
|
142
|
-
提示:"代码已合入 <
|
|
173
|
+
提示:"代码已合入 <main_branch>。Alloy 工作流完成。"
|
|
143
174
|
|
|
144
175
|
**选项 2:创建 PR**
|
|
145
|
-
- PR
|
|
176
|
+
- 先记录完成时间并推进 phase,作为分支上最后一个提交(PR squash merge 后主分支仅 1 个 commit):
|
|
146
177
|
```bash
|
|
147
|
-
|
|
178
|
+
ARCHIVE_DIR=$(ls -d openspec/changes/archive/*-<name> 2>/dev/null | sort -r | head -1)
|
|
179
|
+
CHANGE_DIR="${ARCHIVE_DIR:-openspec/changes/<name>}"
|
|
180
|
+
COMPLETED_AT=$(date "+%Y-%m-%d %H:%M:%S")
|
|
181
|
+
alloy _state merge "$CHANGE_DIR" phase_timings "{\"finish\":{\"completed_at\":\"${COMPLETED_AT:-$(date '+%Y-%m-%d %H:%M:%S')}\"}}"
|
|
182
|
+
alloy _guard "$CHANGE_DIR" finished --apply
|
|
183
|
+
git add -A "$CHANGE_DIR" openspec/config.yaml
|
|
184
|
+
git commit -m "chore(<name>): 记录 finish 阶段完成时间"
|
|
148
185
|
```
|
|
149
|
-
- 提示:"PR
|
|
186
|
+
- 提示:"PR 已创建。审查通过后 squash merge 即可完成。"
|
|
150
187
|
- 当用户收到 PR 审查反馈并在对话中讨论时,遵循以下行为规范(来自 superpowers:receiving-code-review):
|
|
151
188
|
- **验证优先** —— 不要盲从审查意见。先验证 reviewer 指出的问题是否真实存在,再决定是否修改
|
|
152
189
|
- **技术推理** —— 如果你的实现有技术理由,解释原因而不是被动接受。reviewer 可能缺少上下文
|
|
@@ -162,22 +199,7 @@ alloy _guard openspec/changes/<name> finished --apply
|
|
|
162
199
|
|
|
163
200
|
### [Step 3/3] 完成
|
|
164
201
|
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
COMPLETED_AT=$(date "+%Y-%m-%d %H:%M:%S")
|
|
168
|
-
TIMINGS=$(alloy _state read openspec/changes/<name> phase_timings 2>/dev/null || echo "{}")
|
|
169
|
-
echo "$TIMINGS" | python3 -c "
|
|
170
|
-
import sys,json
|
|
171
|
-
content = sys.stdin.read()
|
|
172
|
-
d = json.loads(content) if content.strip() else {}
|
|
173
|
-
p = d.setdefault('finish',{})
|
|
174
|
-
if 'completed_at' not in p:
|
|
175
|
-
p['completed_at']='$COMPLETED_AT'
|
|
176
|
-
print(json.dumps(d))
|
|
177
|
-
" | while read -r val; do alloy _state write openspec/changes/<name> phase_timings "$val"; done
|
|
178
|
-
git add openspec/changes/<name>/
|
|
179
|
-
git commit -m "chore(<name>): 记录 finish 阶段完成时间"
|
|
180
|
-
```
|
|
202
|
+
完成时间已在 Step 2 的记录 commit 中写入。finish 阶段不产生额外 commit——合入 commit(选项 1)或 PR(选项 2)本身就是终端动作。
|
|
181
203
|
|
|
182
204
|
```
|
|
183
205
|
┌──────────────────────────────────────┐
|
|
@@ -196,8 +218,8 @@ git commit -m "chore(<name>): 记录 finish 阶段完成时间"
|
|
|
196
218
|
|
|
197
219
|
## 闸门规则
|
|
198
220
|
|
|
199
|
-
- **git add 只用精确路径** — 永远不用 `-
|
|
200
|
-
finish
|
|
221
|
+
- **git add 只用精确路径** — 永远不用 `-a`、`.`。
|
|
222
|
+
finish 阶段用 `git add -A "$CHANGE_DIR" openspec/config.yaml`(`$CHANGE_DIR` 为当前 change 的归档路径,`-A` 限路径,只追踪 change 目录和 openspec 配置的新增/修改/删除),代码合入由 git merge 处理
|
|
201
223
|
- **phase 必须为 archived** —— spec 已归档的 change 才能 finish
|
|
202
224
|
- **分支必须存在** —— 分支已 merge 或删除时无需再次 finish
|
|
203
225
|
- **不涉及 spec 变更** —— spec 已归档封存,任何 spec 级修改应走新 change
|