@flyin-ai/alloy 0.2.0-beta.0 → 0.2.0-beta.2

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 (41) hide show
  1. package/commands/alloy/apply.md +222 -430
  2. package/commands/alloy/archive.md +156 -132
  3. package/commands/alloy/discard.md +26 -11
  4. package/commands/alloy/finish.md +212 -116
  5. package/commands/alloy/fix.md +103 -258
  6. package/commands/alloy/plan.md +204 -301
  7. package/commands/alloy/references/apply-precheck.md +21 -0
  8. package/commands/alloy/references/apply-rationalizations.md +18 -0
  9. package/commands/alloy/references/apply-subagent-commit.md +49 -0
  10. package/commands/alloy/references/apply-worktree.md +130 -0
  11. package/commands/alloy/references/archive-rationalizations.md +16 -0
  12. package/commands/alloy/references/archive-worktree-cleanup.md +94 -0
  13. package/commands/alloy/references/artifact-hash-commit.md +49 -0
  14. package/commands/alloy/references/branch-naming.md +65 -0
  15. package/commands/alloy/references/branch-validation.md +36 -0
  16. package/commands/alloy/references/fix-precommit-check.md +58 -0
  17. package/commands/alloy/references/interaction-style.md +34 -1
  18. package/commands/alloy/references/main-branch-detection.md +3 -6
  19. package/commands/alloy/references/phase-downgrade-path.md +27 -0
  20. package/commands/alloy/references/plan-rollback.md +84 -0
  21. package/commands/alloy/references/spec-sync.md +62 -0
  22. package/commands/alloy/references/start-rationalizations.md +18 -0
  23. package/commands/alloy/start.md +189 -227
  24. package/dist/cli/commands/completion.js +13 -2
  25. package/dist/cli/commands/completion.js.map +1 -1
  26. package/dist/cli/commands/internal/artifact.d.ts +7 -0
  27. package/dist/cli/commands/internal/artifact.js +70 -0
  28. package/dist/cli/commands/internal/artifact.js.map +1 -0
  29. package/dist/cli/commands/internal/guard.js +204 -2
  30. package/dist/cli/commands/internal/guard.js.map +1 -1
  31. package/dist/cli/commands/internal/progress.d.ts +10 -0
  32. package/dist/cli/commands/internal/progress.js +70 -0
  33. package/dist/cli/commands/internal/progress.js.map +1 -0
  34. package/dist/cli/commands/internal/spec-audit.d.ts +42 -0
  35. package/dist/cli/commands/internal/spec-audit.js +363 -0
  36. package/dist/cli/commands/internal/spec-audit.js.map +1 -0
  37. package/dist/cli/commands/internal/state.js +4 -0
  38. package/dist/cli/commands/internal/state.js.map +1 -1
  39. package/dist/cli/index.js +30 -0
  40. package/dist/cli/index.js.map +1 -1
  41. package/package.json +2 -1
@@ -3,45 +3,53 @@ name: "Alloy: Archive"
3
3
  description: Alloy 归档阶段 - apply 完成后进入
4
4
  category: Workflow
5
5
  tags: [alloy, workflow]
6
+ spec: 01-product-spec/04-archive-spec.md
7
+ behaviors:
8
+ preconditions: 5
9
+ hard_stops: 7
10
+ user_gates: 3
11
+ warns: 1
12
+ artifacts: [delta-spec, archive]
13
+ transitions_to: archived
14
+ external_calls: [opsx:archive]
6
15
  ---
7
16
 
8
17
  # alloy-archive
9
18
 
10
- 你是 Alloy 的归档阶段编排器。你的职责是:验证 change 已完成执行,执行 Delta Spec 合并和归档,将 phase 推进到 `archived`。
19
+ 你是 Alloy 的归档阶段编排器。验证 change 已完成执行,执行 Delta Spec 合并和归档,推进 phase `archived`。
11
20
 
12
- **核心原则:先锁定文档证据链,再合入代码。** archive 只负责 spec 归档,代码合入由后续的 `/alloy:finish` 完成。
21
+ ```
22
+ [HARD_STOP] NO ARCHIVE WITH FAIL
23
+ verify.md FAIL / merge 冲突 / memory 批量 / git status dirty 任一存在 = 拒绝归档
24
+ 违反字面 = 违反精神:哪怕看似"小问题"、"先归档再补"、或用户主动说"一次过吧"要求批量打包 memory,也算违反 Iron Law。逐条 = 逐条——用户要求合并不算授权。
25
+ ```
13
26
 
14
- **调用外部命令或技能前,先输出标题和状态描述,再执行操作。不要只出标题然后沉默。**
27
+ **核心原则:先锁定文档证据链,再合入代码。** archive 只负责 spec 归档,代码合入由 `/alloy:finish` 完成。
15
28
 
16
- **捕获阶段启动时间**(命令调用后第一时间,前置检查之前,幂等——重入时返回已有值):
29
+ **调用外部命令或技能前,先输出标题和状态描述,再执行操作。**
30
+
31
+ **捕获阶段启动时间**(幂等,重入时返回已有值):
17
32
  ```bash
18
33
  PHASE_START=$(alloy _state timestamp ensure openspec/changes/<name> archive)
19
34
  ```
20
35
 
21
36
  ---
22
37
 
23
- **什么算"archive 操作不当"(反例):**
24
- - verify.md 的 Overall Decision 是 FAIL 但仍然继续归档——阻塞问题被无视
25
- - 跳过 archive 直接手动 merge——Delta Spec 没有被同步,主 spec 落后于代码
26
- - openspec archive 返回错误但忽视警告继续——"反正代码对的,spec 后面再说"
27
-
28
- ### Red Flags——STOP,不要继续
38
+ ### Red Flags(第三层防御——任一借口出现即 STOP)
29
39
 
30
- 以下任何一个念头出现,都意味着闸门正在被绕过:
40
+ 主文件保留 5 条核心借口,完整 11 条见 `commands/alloy/references/archive-rationalizations.md`。
31
41
 
32
42
  | 借口 | 现实 |
33
43
  |------|------|
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 的归档流程完全一样。 |
44
+ | "verify.md FAIL 是小问题,先归档再说" | FAIL = 阻塞问题。归档不可逆——带着 FAIL 归档意味着 spec 与代码偏差被永久封存。 |
45
+ | "spec 合并看起来没问题,直接继续" | 没看过的 spec 变更 = 代码与规格可能已分叉。审查只需 1 分钟,修复分叉需要 1 小时。 |
46
+ | "merge 冲突了,git merge --abort 一下让流程继续" | 冲突 = 代码状态未达预期,自动 abort = 隐藏真问题。退出 skill 让用户处理是唯一合法路径(§3.5.1)。 |
47
+ | "memory 候选都对,全部写入吧" | 单次确认承担不了全局污染风险。每条独立 USER_GATE,无例外(§5.2.2)。即使用户说"一次过吧",逐条规则不可合并——用户要求打包不算授权。 |
48
+ | "另一个 change 也在 archive,等一下吧" | change 并行 archive = Delta Spec 合并顺序敏感。先归档晚开始的 = 主 spec 状态错乱。必须串行。 |
39
49
 
40
50
  ---
41
51
 
42
- ## 前置检查(HARD STOP)
43
-
44
- ### [Step 1/3] 前置检查
52
+ ## 前置检查
45
53
 
46
54
  ```
47
55
  ┌──────────────────────────────────────┐
@@ -50,177 +58,193 @@ PHASE_START=$(alloy _state timestamp ensure openspec/changes/<name> archive)
50
58
  └──────────────────────────────────────┘
51
59
  ```
52
60
 
53
- **0. Skill 预检:** 确认以下依赖可用:
54
- cmd: opsx/archive
61
+ ### [Step 1/3] 前置检查
55
62
 
56
- 读取 `commands/alloy/references/skill-precheck.md` 了解检测方法。任一不可用 引导 `alloy init` → STOP。
63
+ **0. Skill 预检:** cmd: opsx/archive
57
64
 
58
- **1. phase 检查:**
65
+ 读取 `commands/alloy/references/skill-precheck.md` 检测。不可用 → 引导 `alloy init` → STOP。
66
+
67
+ **1. Worktree 清洁度(PRECONDITION_FAIL):** archive 会 commit 归档变更并合并 worktree——未 commit 的非 spec/changes 路径变更会污染结果。
59
68
 
60
- 先通过 `alloy _guard` 做硬校验:
61
69
  ```bash
62
- alloy _guard openspec/changes/<name> archived
70
+ DIRTY=$(git status --porcelain -uno)
71
+ if [ -n "$DIRTY" ]; then
72
+ echo "⛔ [PRECONDITION_FAIL] worktree 有未提交变更,archive 拒绝执行:"
73
+ git status --short
74
+ echo ""
75
+ echo " 请先 commit 或 stash 保留变更。"
76
+ echo " 禁止:git stash drop / git reset --hard / git checkout . / git restore . 直接丢弃工作(§3.5.1)。"
77
+ exit 1
78
+ fi
63
79
  ```
64
80
 
65
- guard 报错(phase 不匹配),读取 `commands/alloy/references/phase-routing.md` 按路由表自动跳转。当前 phase=applied 时 precheck 通过。
81
+ 跳过 untracked(`-uno`)——untracked 不会被 commit/merge 影响 archive。
66
82
 
67
- **HARD STOP 保留场景:** change 目录不存在(前序阶段完全没做)→ 引导用户先运行 `/alloy:start`。
83
+ **2. phase 检查(PRECONDITION_FAIL):**
68
84
 
69
- **2. verify.md 存在且 Overall Decision 不是 FAIL:**
70
85
  ```bash
71
- test -f openspec/changes/<name>/verify.md && ! grep -q '^- \[x\] ❌ FAIL' openspec/changes/<name>/verify.md
86
+ alloy _guard precheck openspec/changes/<name> applied
72
87
  ```
73
- 不满足 → "verify.md 不存在或 Overall Decision 为 FAIL。请先修复阻塞问题。"
88
+
89
+ 不匹配时读取 `commands/alloy/references/phase-routing.md` 自动跳转。change 目录不存在 → 引导 `/alloy:start`。
90
+
91
+ **3. verify.md 检查(PRECONDITION_FAIL):**
92
+
93
+ ```bash
94
+ alloy _guard verify-passed openspec/changes/<name>
95
+ ```
96
+
97
+ FAIL → "verify.md 有阻塞问题。请先修复。" PASS/WARNING → 继续。
98
+
99
+ **4. 多 change 并行 archive 检测(WARN,task #14):** Delta Spec 合并顺序敏感——同期多个 change 在 archive 状态时,先归档晚开始的可能导致主 spec 状态错乱。
100
+
101
+ ```bash
102
+ PARALLEL=$(find openspec/changes -maxdepth 2 -name .alloy.yaml \
103
+ -exec grep -l "phase: applied\|phase: archive" {} \; 2>/dev/null \
104
+ | grep -v "/<name>/" | wc -l)
105
+ if [ "$PARALLEL" -gt 0 ]; then
106
+ echo "⚠️ [WARN] 检测到 $PARALLEL 个其他 change 处于 applied/archive 状态:"
107
+ find openspec/changes -maxdepth 2 -name .alloy.yaml \
108
+ -exec grep -l "phase: applied\|phase: archive" {} \; 2>/dev/null | grep -v "/<name>/"
109
+ echo ""
110
+ echo " Delta Spec 合并顺序敏感,建议按 archive 启动时间串行处理。"
111
+ echo " 继续当前 archive 前请确认其他 change 不会同时归档。"
112
+ fi
113
+ ```
114
+
115
+ 不阻断——仅提示。
74
116
 
75
117
  ---
76
- ### [Step 2/3] /opsx:archive
77
118
 
78
- > [Step 2/3] /opsx:archive
79
- > 正在归档——Delta Spec 合并到主 spec → 移入 archive/...
119
+ ### [Step 2/3] /opsx:archive
80
120
 
81
- 使用 Slash 命令 `/opsx:archive` 执行归档。这是 OpenSpec 的标准归档命令,Alloy 不重复建造。
121
+ ```
122
+ [Step 2/3] /opsx:archive
123
+ 正在归档——Delta Spec 合并到主 spec → 移入 archive/...
124
+ ```
82
125
 
83
- **Agent 执行:** 调用 `/opsx:archive`,传入 change name。该命令自动完成:
84
- - Delta Spec 合并到主 spec(`openspec/specs/`)
85
- - Change 目录移至 `openspec/changes/archive/YYYY-MM-DD-<name>/`
86
- - 自有幂等检查——已归档则 Skip
126
+ 调用 `/opsx:archive`,传入 change name。该命令自动完成 Delta Spec 合并 + 目录移动。自有幂等检查——已归档则 Skip。
87
127
 
88
- **错误处理:**
89
- - `/opsx:archive` 返回错误(权限、冲突等)→ [HARD STOP],不推进 phase
90
- - `/opsx:archive` 不可用(OpenSpec 未安装)→ 引导用户运行 `alloy init` 安装 OpenSpec
128
+ **错误处理(HARD_STOP):** 返回错误 → ⛔ `[HARD_STOP] /opsx:archive 失败,归档中止`。不可用 → 引导 `alloy init`。**禁止:忽略错误继续后续步骤——Delta Spec 未合并时主 spec 与代码已分叉,强行推进 phase 会永久封存分叉。**
91
129
 
92
- **命令执行后立即记录:**
93
130
  ```bash
94
131
  alloy _skill log openspec/changes/<name> archive opsx:archive
95
132
  ```
96
133
 
97
- **归档变更提交(必须在 worktree 清理之前):**
134
+ **Delta Spec 合并审查(USER_GATE,task #22 强制 diff 注入):**
98
135
 
99
- `/opsx:archive` 执行 `mv` 移动目录,但不负责 git commit。如果当前在 worktree 中,变更必须先 commit 到 worktree 分支,否则 worktree 清理时 merge 会丢失归档操作。
136
+ 合并完成后,**必须先采集 diff 写入 AskUserQuestion 上下文**,沉默不算授权——agent 不可基于"看起来没问题"自动通过。
100
137
 
101
138
  ```bash
102
- git add -A openspec/specs/ openspec/changes/
103
- git diff --cached --quiet || git commit -m "chore(<name>): 归档目录移动"
139
+ SPEC_DIFF=$(git diff --stat openspec/specs/)
140
+ SPEC_DIFF_FULL=$(git diff openspec/specs/ | head -200) # 200 行防爆量
104
141
  ```
105
142
 
106
- `git diff --cached --quiet` 在无变更时跳过(`opsx:archive` 已自行 commit 的场景)。
143
+ 🔴 USER_GATE(必须 AskUserQuestion,问题模板):
144
+
145
+ > Delta Spec 合并结果:
146
+ > ```
147
+ > [SPEC_DIFF stat 摘要]
148
+ > ```
149
+ > 前 200 行 diff:
150
+ > ```
151
+ > [SPEC_DIFF_FULL]
152
+ > ```
153
+ > 选项:
154
+ > (a) 确认并继续提交归档变更
155
+ > (b) 调整 spec 合并内容——退出 skill,回到 `/opsx:archive` 参数调整或手动修正 spec 后重新运行
156
+
157
+ **违反字面 = 违反精神:** 哪怕 diff 看似"明显合理",没经过用户明确选择 (a) = 不算授权。禁止 agent 基于"diff 短"或"无 conflict"自动跳过此 USER_GATE。
107
158
 
108
- 归档成功后,确定归档路径并处理后续步骤:
159
+ **归档变更提交(HARD_STOP §5.2.1 git add 限路径):** 必须在 worktree 清理之前 commit,否则清理时 merge 会丢失归档操作。**禁止 `git add -A` 无路径——只 add `openspec/specs/ openspec/changes/` 两个明确路径,避免把无关 working tree 变更卷入归档 commit(§5.2.1)。**
109
160
 
110
161
  ```bash
111
- # /opsx:archive 已将 change 目录移至 archive/,后续操作使用归档路径
112
- ARCHIVE_DIR="openspec/changes/archive/$(date +%Y-%m-%d)-<name>"
162
+ git add openspec/specs/ openspec/changes/
163
+ git diff --cached --quiet || git commit -m "chore(<name>): 归档目录移动"
113
164
  ```
114
165
 
115
- **读取 retrospective.md §6 Promote Candidates:** 检查是否有标记 `→ Promote to: memory` 的条目。若有,将 Why/How to apply 写入 `~/.claude/memory/` 对应的 memory 文件(feedback 类型),使其在后续 session 中自动加载。
166
+ `git commit` 失败 `[HARD_STOP] 归档 commit 失败,archive 中止。检查 git 状态后重试。`
167
+
168
+ 归档路径:`ARCHIVE_DIR="openspec/changes/archive/$(date +%Y-%m-%d)-<name>"`
169
+
170
+ **读取 retrospective.md §6 Promote Candidates:** 标记 `→ Promote to: memory` 的条目,将 Why/How to apply 写入 `~/.claude/memory/` 对应文件。这是 retrospective 从"死文档"变"活反馈"的关键。
171
+
172
+ **memory 写入逐条确认(USER_GATE + HARD_STOP §5.2.2):**
173
+
174
+ 详细禁令见 `commands/alloy/references/interaction-style.md` "沉默 ≠ 授权"章节——批量打包是首条反模式。本阶段适用:retrospective Promote Candidates 必须**每条独立** AskUserQuestion,无论候选数量、相似度或"看起来都对"。**[HARD_STOP] 即使用户主动说"一次过吧"或"都挺合理的",也不可合并——用户要求打包不算授权,agent 必须拒绝并逐条展示。**
175
+
176
+ 逐条流程:
177
+
178
+ 1. 解析 retrospective.md §6,提取每条 `→ Promote to: memory` 候选
179
+ 2. 对每条候选**单独** AskUserQuestion:
180
+
181
+ > 候选 [N/M]:写入 ~/.claude/memory/?
182
+ > 内容:[Why + How to apply 摘要]
183
+ > 选项:(a) 写入 (b) 跳过 (c) 修改后写入
184
+
185
+ 3. (a) → 立即写入对应 memory 文件
186
+ 4. (b) → 跳过,记录到 retrospective.md 末尾"Skipped from memory promotion"章节
187
+ 5. (c) → 用户提供调整后的 Why/How 文本,写入修改版
188
+ 6. 全部条目处理后输出汇总:N 条写入、M 条跳过、K 条修改后写入
116
189
 
117
- > 这是 retrospective 从"死文档"变成"活反馈"的关键步骤——教训不跨 cycle 就不是教训。
190
+ Promote Candidates 跳过本步骤。
118
191
 
119
- **Worktree 清理(如果 apply 期间使用了 worktree):**
192
+ **Worktree 清理(如果 apply 期间使用了 worktree):** 读取 `commands/alloy/references/archive-worktree-cleanup.md` 执行完整流程。要点:
193
+
194
+ - task #21 silent fallback 检测:state 字段未写但 worktree 存在 → ⛔ PRECONDITION_FAIL,禁 silent fallback
195
+ - 遗留 change 兼容:FEATURE_BRANCH / WORKTREE_BRANCH 缺失时回退/检测,仍缺失 → ⛔ PRECONDITION_FAIL
196
+ - merge 成功 → 🔴 USER_GATE(确认并清理 / 退出审查),(a) 选清理 → `git worktree remove` + `git branch -d`
197
+ - merge 冲突 → 报告冲突现场后退出 skill。**[HARD_STOP §3.5.1] 禁 agent 自动 `git merge --abort` / `git reset --hard` / `git checkout .` / `git restore .` / `git stash` / `git clean -fd` / `git push --force` 任何一个。** 违反字面 = 违反精神。
198
+
199
+ 未使用 worktree(`worktree=skipped` 或字段缺失且 git 无残留)时跳过本段。
200
+
201
+ **Worktree 合并时间记录:** worktree merge + remove + branch -d 全部成功后,写入 `worktree_merged_at`:
120
202
 
121
203
  ```bash
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
204
+ WORKTREE_MERGED_AT=$(date '+%Y-%m-%d %H:%M:%S')
205
+ alloy _state write "$ARCHIVE_DIR" worktree_merged_at "$WORKTREE_MERGED_AT"
168
206
  ```
169
207
 
170
- 如果 apply 期间未使用 worktree(worktree 为 null 或 skipped),则跳过此步骤。
208
+ 未使用 worktree 时跳过本步。
171
209
 
172
- **记录完成时间并提交(所有 .alloy.yaml 变更在 commit 之前完成):**
210
+ **记录完成时间并提交(HARD_STOP §5.2.1 git add 限路径):**
173
211
 
174
212
  ```bash
175
- # 写入完成时间(:="${...:-...}" 兜底:防 Agent 漏 capture)
176
213
  COMPLETED_AT="${COMPLETED_AT:-$(date '+%Y-%m-%d %H:%M:%S')}"
177
214
  COMPLETED_AT_JSON=$(python3 -c "import json; print(json.dumps({'archive':{'completed_at': '$COMPLETED_AT'}}))")
178
215
  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/
216
+ # §5.2.1: git add 限路径,禁 -A 无路径
217
+ git add openspec/specs/ openspec/changes/
183
218
  git commit -m "chore(<name>): 归档阶段完成"
184
219
  ```
185
- commit 失败必须阻断——.alloy.yaml 变更未提交时,后续 finish 阶段状态不一致。
186
220
 
187
- > Delta Spec 已合并到主 spec
188
- > ✓ Change 已归档到 $ARCHIVE_DIR
189
- > 归档变更已提交(目录移动 + 阶段完成时间)
221
+ `git commit` 失败 `[HARD_STOP] 归档 commit 失败,archive 中止。.alloy.yaml 变更未提交时 finish 状态不一致。检查 git 状态后重试,禁止在 commit 失败时继续执行后续步骤。`
222
+
223
+ ### [Step 3/3] 推进 phase
190
224
 
191
- ### [Step 3/3] 完成
225
+ **通过 `alloy _guard` 校验并推进 phase(HARD_STOP §5.2.3 路径 B 降级):**
226
+
227
+ 降级路径详见 `commands/alloy/references/phase-downgrade-path.md`(archive 阶段降级 → `applied`)。**禁止 agent 自动 `git reset --hard` / `git checkout .` 清场(§3.5.1)。**
192
228
 
193
- **通过 `alloy _guard` 校验并推进 phase:**
194
229
  ```bash
195
230
  alloy _guard "$ARCHIVE_DIR" archived --apply
196
- git add -A openspec/specs/ openspec/changes/
231
+ git add openspec/specs/ openspec/changes/
197
232
  git commit -m "chore(<name>): phase → archived"
198
233
  ```
199
234
 
200
235
  ```
201
236
  ┌──────────────────────────────────────┐
202
237
  │ Alloy [4/5] · Phase: Archive — DONE │
203
- │ 启动时间: phase_timings.archive.started_at 读取
204
- │ 完成时间: phase_timings.archive.completed_at 读取
205
- │ 耗时: completed_at - started_at
238
+ │ 启动时间: phase_timings.archive.started_at
239
+ │ 完成时间: phase_timings.archive.completed_at
240
+ │ 耗时: completed_at - started_at
206
241
  └──────────────────────────────────────┘
207
242
 
208
- → Change: <name>
209
- → Phase: archived
243
+ → Change: <name> Phase: archived
210
244
  → 归档位置: archive/YYYY-MM-DD-<name>/
245
+ → ✓ Delta Spec 已合并 ✓ Change 已归档
246
+ → 代码合入由 /alloy:finish 处理
247
+ ```
211
248
 
212
- Delta Spec 已合并到主 spec
213
- ✓ Change 已归档
214
-
215
- > → 代码合入由 `/alloy:finish` 处理
216
-
217
- ---
218
-
219
- ## 闸门规则
249
+ archive 不做代码合并——代码合入由 `/alloy:finish` 处理。准备好后运行 `/alloy:finish` 进入收尾阶段。
220
250
 
221
- - **git add 只用精确路径** — 永远不用 `-a`、`.`。
222
- archive 用 `git add -A openspec/specs/ openspec/changes/`(`-A` 限路径,只追踪这两个目录的新增/修改/删除),反例:`git add -A`(无路径限定,扫全仓)
223
- - **phase 必须为 applied** —— 只有 apply 完成的 change 才能归档
224
- - **verify.md 必须存在且非 FAIL** —— 阻塞问题必须先修复
225
- - **先归档后合入** —— spec 文档先锁定,代码后通过 `/alloy:finish` 合入,避免"代码合入了 spec 还没跟上"
226
- - **archive 不做代码合并** —— 代码合入是 `/alloy:finish` 的职责
@@ -3,13 +3,21 @@ name: "Alloy: Discard"
3
3
  description: Alloy 放弃 change - 需要放弃时调用
4
4
  category: Workflow
5
5
  tags: [alloy, workflow]
6
+ spec: 01-product-spec/07-discard-spec.md
7
+ behaviors:
8
+ stops: 0
9
+ hard_stops: 1
10
+ artifacts: [discard-archive]
11
+ artifacts: []
12
+ transitions_to: ""
13
+ external_calls: []
6
14
  ---
7
15
 
8
16
  # alloy-discard
9
17
 
10
18
  你是 Alloy 的放弃清理器。你的职责是:根据 change 的当前 phase 执行分级清理,确保用户明确确认后再删除。
11
19
 
12
- **核心原则:精确确认才能删除,残留必须清理。**
20
+ **核心原则:软删除(移到 archive/)而非物理删除,保留完整审计链,允许误删后恢复。**
13
21
 
14
22
  每个 change 必须有独立的 feature 分支(start step 6 保证),discard 时可安全删除整个分支。
15
23
 
@@ -36,8 +44,8 @@ alloy _config read . main_branch
36
44
 
37
45
  | phase | 清理动作 |
38
46
  |-------|---------|
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 目录` |
47
+ | started / planned | `git checkout <main_branch>` + `git branch -D <feature_branch>` + 软删除 `archive/` |
48
+ | applied / archived | `git worktree remove` + `git checkout <main_branch>` + `git branch -D <feature_branch>` + 软删除 `archive/` |
41
49
  | finished | **[HARD STOP] 已完成的 change 不可 discard。** finished 是终态 |
42
50
 
43
51
  ---
@@ -55,13 +63,13 @@ alloy _config read . main_branch
55
63
  清理前必须展示将要删除的内容并等待用户精确确认:
56
64
 
57
65
  ```
58
- 将删除以下内容,不可恢复:
66
+ 将软删除以下内容(移到 archive/ 保留审计链,可手动恢复):
59
67
 
60
68
  Change: <name>
61
69
  Phase: <phase>
62
70
  Feature 分支: <feature_branch>(如有)
63
71
  Worktree: <path>(如有)
64
- 目录: openspec/changes/<name>/
72
+ 目录: openspec/changes/<name>/ → archive/YYYY-MM-DD-discard-<name>/
65
73
  切回分支: <main_branch>(如有)
66
74
 
67
75
  输入 'discard <name>' 确认,或输入其他任意内容取消。
@@ -80,10 +88,10 @@ alloy _config read . main_branch
80
88
 
81
89
  | 借口 | 现实 |
82
90
  |------|------|
83
- | "就删个目录而已,y 就行了" | 删除是不可逆操作。`discard <name>` 的精确匹配是防手滑的最后一道防线。"y"、"好"、"删了吧"都不算。 |
91
+ | "就删个目录而已,y 就行了" | 即使软删除可恢复,确认步骤不可跳过。`discard <name>` 的精确匹配是防手滑的最后一道防线。"y"、"好"、"删了吧"都不算。 |
84
92
  | "这个 change 已完成,直接删了吧" | finished 是终态——不可 discard。已完成的 change 有完整审计链,删除会破坏追溯性。 |
85
93
  | "不用列出清单了,我知道有什么" | 必须先展示六行删除清单(Change/Phase/分支/Worktree/目录/切回分支),让用户确认每个待删除项的完整性和正确性。 |
86
- | "不用按顺序清理,直接 rm -rf 就行了" | 清理必须按序:worktree remove → checkout main → branch -D → rm 目录。不按序可能留下孤儿 worktree 或残留分支。
94
+ | "不用按顺序清理,直接 rm -rf 就行了" | 清理必须按序:worktree remove → checkout main → branch -D → mv 到 archive。rm -rf 不可恢复,软删除保留审计链。 |
87
95
 
88
96
  ---
89
97
 
@@ -101,8 +109,13 @@ git checkout <main_branch>
101
109
  # 3. git branch -D <feature_branch>
102
110
  git branch -D <feature_branch>
103
111
 
104
- # 4. rm -rf openspec/changes/<name>/
105
- rm -rf openspec/changes/<name>/
112
+ # 4. 软删除——移动到 archive/ 保留审计链
113
+ DISCARD_DIR="openspec/changes/archive/$(date +%Y-%m-%d)-discard-<name>"
114
+ mkdir -p openspec/changes/archive/
115
+ mv openspec/changes/<name>/ "$DISCARD_DIR"
116
+
117
+ # 5. 记录 discarded_at 时间戳
118
+ alloy _state merge "$DISCARD_DIR" phase_timings "{\"discarded_at\":\"$(date '+%Y-%m-%d %H:%M:%S')\"}"
106
119
  ```
107
120
 
108
121
  若 `main_branch` 未记录,跳过步骤 2,提示用户手动切回主分支。
@@ -116,7 +129,9 @@ rm -rf openspec/changes/<name>/
116
129
  Alloy · 放弃 Change — DONE
117
130
  ──────────────────────────────────────
118
131
 
119
- ✓ <name> 已清理
120
- 已删除:<列出实际删除的内容(分支/worktree/目录)>
132
+ ✓ <name> 已软删除
133
+ 分支/worktree:已清理
134
+ 归档位置:archive/YYYY-MM-DD-discard-<name>/
135
+ 恢复方式:mv 回 openspec/changes/<name>/ 即可恢复
121
136
  当前分支:<main_branch>(或提示用户手动切换)
122
137
  ```