super-engineer-workflow 0.1.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/CHANGELOG.md +9 -0
- package/CONTRIBUTING.md +34 -0
- package/LICENSE +21 -0
- package/README.md +300 -0
- package/SECURITY.md +21 -0
- package/bin/super-engineer.js +19 -0
- package/docs/se/345/221/275/344/273/244/345/215/217/350/256/256.md +335 -0
- package/docs//344/270/255/346/226/207/344/275/277/347/224/250/346/211/213/345/206/214.md +707 -0
- package/docs//345/205/254/345/274/200/345/217/221/345/270/203/346/243/200/346/237/245/346/270/205/345/215/225.md +43 -0
- package/docs//345/277/253/351/200/237/345/210/235/345/247/213/345/214/226/345/267/245/344/275/234/345/214/272.md +419 -0
- package/docs//351/241/271/347/233/256/346/236/266/346/236/204/344/270/216/350/256/276/350/256/241/350/257/264/346/230/216.md +657 -0
- package/package.json +55 -0
- package/scripts/se-cli.py +301 -0
- package/scripts/se-setup.py +331 -0
- package/scripts/se-smoke-test.py +86 -0
- package/super-engineer-workflow/SKILL.md +439 -0
- package/super-engineer-workflow/adapters/go.yml +8 -0
- package/super-engineer-workflow/adapters/java-gradle.yml +8 -0
- package/super-engineer-workflow/adapters/java-maven.yml +8 -0
- package/super-engineer-workflow/adapters/node-react.yml +8 -0
- package/super-engineer-workflow/adapters/node-vue.yml +8 -0
- package/super-engineer-workflow/adapters/python.yml +8 -0
- package/super-engineer-workflow/agents/openai.yaml +4 -0
- package/super-engineer-workflow/assets/config-schema.json +100 -0
- package/super-engineer-workflow/assets/config.example.yml +12 -0
- package/super-engineer-workflow/assets/plan-schema.json +362 -0
- package/super-engineer-workflow/assets/status-schema.json +83 -0
- package/super-engineer-workflow/assets/workspace.example.yml +25 -0
- package/super-engineer-workflow/config.example.yml +12 -0
- package/super-engineer-workflow/references/contracts.md +39 -0
- package/super-engineer-workflow/references/execution-modes.md +38 -0
- package/super-engineer-workflow/references/java.md +21 -0
- package/super-engineer-workflow/references/planning.md +45 -0
- package/super-engineer-workflow/references/platform-openclaw.md +10 -0
- package/super-engineer-workflow/references/project-docs.md +7 -0
- package/super-engineer-workflow/references/review-checklist.md +26 -0
- package/super-engineer-workflow/references/se-commands.md +582 -0
- package/super-engineer-workflow/references/verify-checklist.md +45 -0
- package/super-engineer-workflow/references/workflow.md +208 -0
- package/super-engineer-workflow/scripts/archive-openspec.py +110 -0
- package/super-engineer-workflow/scripts/bootstrap-openspec.py +42 -0
- package/super-engineer-workflow/scripts/common.py +3285 -0
- package/super-engineer-workflow/scripts/generate-discovery.py +185 -0
- package/super-engineer-workflow/scripts/generate-review-report.py +296 -0
- package/super-engineer-workflow/scripts/generate-self-check.py +185 -0
- package/super-engineer-workflow/scripts/generate-smart-plan.py +429 -0
- package/super-engineer-workflow/scripts/init-workspace.py +68 -0
- package/super-engineer-workflow/scripts/prepare-archive-openspec.py +186 -0
- package/super-engineer-workflow/scripts/propose-openspec.py +170 -0
- package/super-engineer-workflow/scripts/run-verify-and-report.py +399 -0
- package/super-engineer-workflow/scripts/run-workflow.py +506 -0
- package/super-engineer-workflow/scripts/update-status.py +43 -0
- package/super-engineer-workflow/scripts/writeback-openspec.py +311 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 验证清单
|
|
2
|
+
|
|
3
|
+
验证至少要回答这些问题:
|
|
4
|
+
|
|
5
|
+
- 选中的构建或测试命令是否通过
|
|
6
|
+
- 实际执行的命令是什么
|
|
7
|
+
- 命令在哪个仓库、哪个目录执行
|
|
8
|
+
- 命令耗时、退出码、失败摘要是什么
|
|
9
|
+
- 是否确实需要启动服务
|
|
10
|
+
- 是否还有需要用户手工执行的检查项
|
|
11
|
+
|
|
12
|
+
建议优先级:
|
|
13
|
+
|
|
14
|
+
- 优先执行最小相关测试命令
|
|
15
|
+
- 其次执行构建或全量测试命令
|
|
16
|
+
- 只有确实需要时才执行启动命令
|
|
17
|
+
- 输出简洁摘要,不要直接堆原始日志
|
|
18
|
+
|
|
19
|
+
验证矩阵建议包含:
|
|
20
|
+
|
|
21
|
+
- static:lint、format、typecheck
|
|
22
|
+
- unit:单元测试
|
|
23
|
+
- integration:模块或集成测试
|
|
24
|
+
- build:编译或打包
|
|
25
|
+
- smoke:人工接口或页面验证
|
|
26
|
+
|
|
27
|
+
当前无法自动识别命令时,应写明缺口,并把工作流置为 blocked。
|
|
28
|
+
|
|
29
|
+
## 主流项目自动识别
|
|
30
|
+
|
|
31
|
+
工作流会优先根据项目根目录文件推断验证命令:
|
|
32
|
+
|
|
33
|
+
- Java Maven:`./mvnw test` 或 `mvn test`
|
|
34
|
+
- Java Gradle:`./gradlew test` 或 `gradle test`
|
|
35
|
+
- Node.js / Vue / React / Next / Nuxt:根据 `package.json` scripts 和锁文件推断 `npm`、`pnpm`、`yarn` 或 `bun` 命令
|
|
36
|
+
- Go:`go test ./...`
|
|
37
|
+
- Python:优先 `python -m pytest`,否则 `python -m unittest discover`;uv / Poetry 项目会加对应前缀
|
|
38
|
+
- Rust:`cargo test`
|
|
39
|
+
- .NET:`dotnet test`
|
|
40
|
+
- PHP:`composer test` 或 `vendor/bin/phpunit`
|
|
41
|
+
- Ruby:`bundle exec rspec` 或 `bundle exec rake test`
|
|
42
|
+
- Make:优先 `make test`,否则 `make`
|
|
43
|
+
- CMake:已有 `build` 目录时使用 `ctest --test-dir build`
|
|
44
|
+
|
|
45
|
+
如果自动推断不适合当前团队,应在 `workspace.yml.verify_commands` 中覆盖。覆盖命令优先于自动识别结果。
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# 工作流契约
|
|
2
|
+
|
|
3
|
+
## 配置文件
|
|
4
|
+
|
|
5
|
+
工作流会读取两份配置:
|
|
6
|
+
|
|
7
|
+
- `<workspace>/workspace.yml`
|
|
8
|
+
- `~/.super-engineer/skill-config.yml`
|
|
9
|
+
|
|
10
|
+
`<workspace>` 就是当前使用这个 skill 的目录。
|
|
11
|
+
|
|
12
|
+
`workspace.yml` 必须包含:
|
|
13
|
+
|
|
14
|
+
- `version`
|
|
15
|
+
- `mode`
|
|
16
|
+
- `workflow_source`
|
|
17
|
+
- `todo_file`
|
|
18
|
+
- `demand_file`
|
|
19
|
+
- `reference_files`
|
|
20
|
+
- `code_path`
|
|
21
|
+
- `output_dir`
|
|
22
|
+
|
|
23
|
+
其中 `demand_file` 可选,主要作为 `/se:propose` 的原始需求输入。以上路径可以使用相对路径或绝对路径。相对路径按当前工作空间根目录解析。
|
|
24
|
+
|
|
25
|
+
`workspace.yml` 是用户维护的工作空间契约。AI 只能读取和校验该文件,禁止自动编辑、重写或格式化它。如果配置需要调整,AI 必须停止并说明需要用户修改的字段。
|
|
26
|
+
|
|
27
|
+
`workspace.yml` 支持可选 `verify_commands`。当项目自动识别出的验证命令不准确,或团队希望固定验证命令时,可以配置:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
verify_commands:
|
|
31
|
+
default: pnpm test && pnpm build
|
|
32
|
+
frontend-app: pnpm test && pnpm build
|
|
33
|
+
user-service: go test ./...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
key 可以是 `default`、目标仓库目录名或目标仓库绝对路径。匹配优先级是绝对路径、目录名、`default`。
|
|
37
|
+
|
|
38
|
+
`workspace.yml` 支持可选 `vars`:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
vars:
|
|
42
|
+
demand_name: 7-deamnd-addition-rate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
路径字段可以通过 `${demand_name}` 或 `${vars.demand_name}` 引用变量。
|
|
46
|
+
|
|
47
|
+
`~/.super-engineer/skill-config.yml` 可以包含可选通知:
|
|
48
|
+
|
|
49
|
+
- `notification.pushplus.token`
|
|
50
|
+
- `notification.pushplus.ordinary.enabled`
|
|
51
|
+
- `notification.pushplus.ordinary.channel`
|
|
52
|
+
- `notification.pushplus.ordinary.template`
|
|
53
|
+
- `notification.feishu.enabled`
|
|
54
|
+
- `notification.feishu.webhook_url`
|
|
55
|
+
- `notification.feishu.secret`
|
|
56
|
+
|
|
57
|
+
如果 `~/.super-engineer/skill-config.yml` 不存在:
|
|
58
|
+
|
|
59
|
+
- 首次运行时自动创建
|
|
60
|
+
- 创建后当前工作流立即停止
|
|
61
|
+
- 用户完善配置后再重新执行
|
|
62
|
+
|
|
63
|
+
通知规则:
|
|
64
|
+
|
|
65
|
+
- `ordinary.enabled=true` 时发送普通 PushPlus 消息,默认发给自己
|
|
66
|
+
- `feishu.enabled=true` 时通过飞书原生自定义机器人 webhook 发送消息
|
|
67
|
+
- `feishu.secret` 可选,仅在机器人开启签名校验时填写
|
|
68
|
+
- 两条路由可以同时开启,工作流结束后会分别发送
|
|
69
|
+
|
|
70
|
+
`workflow_source` 支持:
|
|
71
|
+
|
|
72
|
+
- `todo`
|
|
73
|
+
- `openspec`
|
|
74
|
+
|
|
75
|
+
OpenSpec 模式下还需要:
|
|
76
|
+
|
|
77
|
+
- `openspec.changes_dir`
|
|
78
|
+
- 可选 `openspec.tasks_file`
|
|
79
|
+
- 可选 `openspec.proposal_file`
|
|
80
|
+
- 可选 `openspec.design_file`
|
|
81
|
+
- 可选 `openspec.specs_dir`
|
|
82
|
+
- 可选 `openspec.writeback_dir`
|
|
83
|
+
|
|
84
|
+
OpenSpec change 名称必须通过 `/se:propose <change-name>` 显式指定。工作流不会从 `vars.demand_name`、需求文件名或需求标题推导 change 名称。`openspec.change_dir` / `openspec.change_name` 只作为兼容旧配置的字段,不建议新增配置。
|
|
85
|
+
|
|
86
|
+
`todo_file` 在两种模式下含义不同:
|
|
87
|
+
|
|
88
|
+
- `todo`:用户直接维护
|
|
89
|
+
- `openspec`:由 OpenSpec `tasks.md` 桥接生成,作为执行入口
|
|
90
|
+
|
|
91
|
+
`demand_file` 是原始需求文件:
|
|
92
|
+
|
|
93
|
+
- `openspec` 模式下,`/se:propose` 优先读取它生成或完善 change
|
|
94
|
+
- 可以配置为本地 Markdown 路径,也可以配置为飞书/Lark 云文档 URL
|
|
95
|
+
- 飞书/Lark 云文档 URL 通过官方 `lark-cli docs +fetch` 读取;未安装时按提示执行 `npx @larksuite/cli@latest install`、`lark-cli config init --new`、`lark-cli auth login --recommend`
|
|
96
|
+
- `reference_files` 是技术参考资料,不应该用来猜测哪个文件是原始需求
|
|
97
|
+
- `openspec` 模式下,`/se:propose` 必须读取真实存在的 `reference_files`,并把内容写入 `propose-input.json` / `propose-input.md`,作为生成 `proposal.md`、`design.md`、`tasks.md` 的上下文
|
|
98
|
+
|
|
99
|
+
`code_path` 可以是:
|
|
100
|
+
|
|
101
|
+
- 单个项目仓库根目录
|
|
102
|
+
- 包含多个服务仓库的聚合目录
|
|
103
|
+
|
|
104
|
+
如果是聚合目录,工作流应优先根据 todo 中的服务名约束自动定位目标仓库。
|
|
105
|
+
|
|
106
|
+
如果 todo 中明确指定了多个服务,工作流应解析出多个目标仓库,并在后续阶段逐仓执行。
|
|
107
|
+
|
|
108
|
+
实施阶段会尝试识别主流工程类型并推断验证命令:
|
|
109
|
+
|
|
110
|
+
- Java:Maven / Gradle
|
|
111
|
+
- Node.js / 前端:npm / pnpm / yarn / bun,包含 Vue、React、Next、Nuxt、Svelte、Angular 等常见框架
|
|
112
|
+
- Go:`go test ./...`
|
|
113
|
+
- Python:pytest / unittest,支持 uv / Poetry 前缀
|
|
114
|
+
- Rust:Cargo
|
|
115
|
+
- .NET:dotnet
|
|
116
|
+
- PHP:Composer / PHPUnit
|
|
117
|
+
- Ruby:Bundler / RSpec / Rake
|
|
118
|
+
- Make / CMake:Makefile 或已有 build 目录下的 CTest
|
|
119
|
+
|
|
120
|
+
如果无法识别可靠验证命令,verify 阶段必须进入 blocked,而不是伪造通过结果。
|
|
121
|
+
|
|
122
|
+
## 运行时目录布局
|
|
123
|
+
|
|
124
|
+
工作空间内部只保存给 AI 使用的数据:
|
|
125
|
+
|
|
126
|
+
- `<workspace>/.super-engineer/current-session.json`
|
|
127
|
+
- `<workspace>/.super-engineer/se-state.json`
|
|
128
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/discovery.json`
|
|
129
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/plan.json`
|
|
130
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/self-check.json`
|
|
131
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/review.json`
|
|
132
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/verify.json`
|
|
133
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/status.json`
|
|
134
|
+
|
|
135
|
+
给人查看的 Markdown 产物统一写到输出目录:
|
|
136
|
+
|
|
137
|
+
- `<output_dir>/<session_id>/discovery.md`
|
|
138
|
+
- `<output_dir>/<session_id>/plan.md`
|
|
139
|
+
- `<output_dir>/<session_id>/self-check.md`
|
|
140
|
+
- `<output_dir>/<session_id>/review.md`
|
|
141
|
+
- `<output_dir>/<session_id>/verify.md`
|
|
142
|
+
|
|
143
|
+
会话附加产物:
|
|
144
|
+
|
|
145
|
+
- `<workspace>/.super-engineer/sessions/<session_id>/notification.json`
|
|
146
|
+
|
|
147
|
+
通知验收规则:
|
|
148
|
+
|
|
149
|
+
- `notification.json` 是唯一通知证据
|
|
150
|
+
- `status.json.notification_status` 只是摘要,不能单独作为通知成功依据
|
|
151
|
+
- 通知只能由 `run-workflow.py verify` 调用 `run-verify-and-report.py` 和 `common.notify_workflow_result()` 发送
|
|
152
|
+
- AI 禁止直接调用飞书 webhook,禁止手工拼接飞书卡片 JSON
|
|
153
|
+
- 启用飞书时,`notification.json` 必须包含 `source=run-workflow.py verify`、fingerprint 匹配、`route=feishu`、`template=interactive`、`status=sent` 的结果,才算飞书通知成功
|
|
154
|
+
|
|
155
|
+
## 会话规则
|
|
156
|
+
|
|
157
|
+
- OpenSpec 模式下,`se-state.json` 是脚本状态机,记录 `phase`、`allowed_next`、`current_change`、`last_command` 和关键产物路径
|
|
158
|
+
- `/se:propose` 后状态为 `proposed`,只允许 `/se:bridge`
|
|
159
|
+
- `/se:bridge` 后状态为 `bridged`,允许 `/se:apply` 或 `/se:plan`
|
|
160
|
+
- `/se:plan` 后状态为 `planned`,只允许 `/se:apply`
|
|
161
|
+
- `/se:verify` 通过后状态为 `verified`,只允许 `/se:archive-check`
|
|
162
|
+
- `/se:archive-check` 通过后状态为 `archive_ready`,只允许 `/se:archive`
|
|
163
|
+
- 所有阶段推进必须先通过 `run-workflow.py validate-state <command>` 等价校验,不能只依赖 AI 回复
|
|
164
|
+
- 每次执行 `plan` 都必须创建新的 `session_id`
|
|
165
|
+
- 新会话不能覆盖历史会话目录
|
|
166
|
+
- `current-session.json` 只指向当前正在推进的会话
|
|
167
|
+
- 后续 `start-implement`、`finish-implement`、`review`、`verify`、`status` 都基于当前会话执行
|
|
168
|
+
- `plan` 会自动执行 `discover`,`finish-implement` 会自动执行 `self-check`
|
|
169
|
+
- `workflow_source=openspec` 时,只有用户显式执行 `/se:bridge` 才会桥接 OpenSpec `tasks.md` 到 `todo_file`
|
|
170
|
+
- `workflow_source=openspec` 时,`init` / `plan` 只校验已有桥接 todo,不能自动创建或重写 `todo_file`
|
|
171
|
+
- `workflow_source=openspec` 时,`propose-openspec` 优先调用 OpenSpec CLI 创建 change、读取 status 和 artifact instructions
|
|
172
|
+
- `workflow_source=openspec` 时,`review` / `verify` 会自动把执行摘要写回 `openspec.writeback_dir`
|
|
173
|
+
- OpenSpec 长期规格归档建议显式执行 `prepare-archive-openspec` 与 `archive-openspec`;归档检查会结合 OpenSpec CLI status 与 super-engineer 的 spec baseline 冲突检测
|
|
174
|
+
- `prepare-archive-openspec` 会检测 spec baseline 是否发生变化;只有 `merge_mode=safe_merge` 才允许后续归档
|
|
175
|
+
- `auto` 模式下,除非进入硬阻塞,否则不能在对话里要求用户批准继续
|
|
176
|
+
- 工作流总耗时按当前会话开始到 verify 收口结束的真实墙钟时间计算
|
|
177
|
+
- AI 禁止编辑 `<workspace>/workspace.yml`
|
|
178
|
+
- AI 禁止直接写 `.super-engineer/current-session.json`、`.super-engineer/sessions/**/status.json`、`plan.json`、`review.json`、`verify.json`、`notification.json` 和 output 下的标准 Markdown 报告;这些标准产物只能由脚本生成
|
|
179
|
+
|
|
180
|
+
## 硬阻塞定义
|
|
181
|
+
|
|
182
|
+
只有出现以下情况,才允许停止并等待用户:
|
|
183
|
+
|
|
184
|
+
- 工作空间配置缺失或不合法
|
|
185
|
+
- todo 文件缺失,且无法自动创建或内容为空到无法判断需求
|
|
186
|
+
- 无法定位目标仓库
|
|
187
|
+
- 多仓场景下目标服务不明确
|
|
188
|
+
- 必要命令无法执行,且无法自动兜底
|
|
189
|
+
- 验证失败到必须人工介入
|
|
190
|
+
- 实现自查或 review 发现阻塞级问题
|
|
191
|
+
- 发现需求与代码现实严重冲突,继续修改会明显越界
|
|
192
|
+
|
|
193
|
+
以下情况不属于硬阻塞,必须继续推进:
|
|
194
|
+
|
|
195
|
+
- 计划还不够精确
|
|
196
|
+
- 还需要先去代码里定位具体实现位置
|
|
197
|
+
- 想先让用户确认某一步是否继续
|
|
198
|
+
- review 过程中发现计划要补充
|
|
199
|
+
|
|
200
|
+
## 产物规则
|
|
201
|
+
|
|
202
|
+
- JSON 产物保持稳定、结构化、便于机器读取
|
|
203
|
+
- Markdown 产物保持简洁、便于人阅读
|
|
204
|
+
- 只要阶段、阻塞、下一步动作发生变化,就更新当前会话的 `status.json`
|
|
205
|
+
- 必须通过 `scripts/run-workflow.py` 推进阶段,避免手工拼接状态
|
|
206
|
+
- verify 收口后,如果配置了通知,自动发送工作流完成通知,但通知失败不能覆盖真实验证结论
|
|
207
|
+
- 工作流完成通知只能通过 `run-workflow.py verify` 发送,禁止 AI 直接调用飞书 webhook,禁止 AI 手工拼接飞书卡片 JSON
|
|
208
|
+
- 如果 session 已经被标记为 `done`,但缺少 `verify.json`、`notification.json` 或输出目录下的 Markdown 报告,说明前一次没有走标准收口,应通过 `/se:verify` 重新执行标准验证收口
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import shutil
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from common import (
|
|
10
|
+
openspec_archive_root,
|
|
11
|
+
openspec_change_dir,
|
|
12
|
+
openspec_writeback_dir,
|
|
13
|
+
read_json,
|
|
14
|
+
update_se_state,
|
|
15
|
+
workflow_source,
|
|
16
|
+
workspace_root,
|
|
17
|
+
load_workspace_config,
|
|
18
|
+
write_managed_json,
|
|
19
|
+
write_managed_text,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def sync_specs(change_dir: Path) -> list[dict[str, str]]:
|
|
24
|
+
delta_root = change_dir / "specs"
|
|
25
|
+
if not delta_root.exists():
|
|
26
|
+
return []
|
|
27
|
+
repo_openspec_root = change_dir.parent.parent
|
|
28
|
+
target_specs_root = repo_openspec_root / "specs"
|
|
29
|
+
synced: list[dict[str, str]] = []
|
|
30
|
+
for source in sorted(delta_root.rglob("*.md")):
|
|
31
|
+
relative = source.relative_to(delta_root)
|
|
32
|
+
target = target_specs_root / relative
|
|
33
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
shutil.copy2(source, target)
|
|
35
|
+
synced.append({"source": str(source), "target": str(target)})
|
|
36
|
+
return synced
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_markdown(payload: dict) -> str:
|
|
40
|
+
lines = [
|
|
41
|
+
"# Archive Result",
|
|
42
|
+
"",
|
|
43
|
+
"## Summary",
|
|
44
|
+
f"- change: {payload.get('change_name', '')}",
|
|
45
|
+
f"- archived_to: {payload.get('archived_to', '')}",
|
|
46
|
+
"",
|
|
47
|
+
"## Synced Specs",
|
|
48
|
+
]
|
|
49
|
+
synced = payload.get("synced_specs", [])
|
|
50
|
+
if synced:
|
|
51
|
+
lines.extend(f"- {item.get('source', '')} -> {item.get('target', '')}" for item in synced)
|
|
52
|
+
else:
|
|
53
|
+
lines.append("- 暂无")
|
|
54
|
+
lines.append("")
|
|
55
|
+
return "\n".join(lines)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> None:
|
|
59
|
+
parser = argparse.ArgumentParser(description="归档 OpenSpec change,并把 delta specs 合并回主 specs。")
|
|
60
|
+
parser.add_argument("--workspace", help="工作空间路径,默认读取当前目录")
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
|
|
63
|
+
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
64
|
+
config = load_workspace_config(workspace)
|
|
65
|
+
if workflow_source(config) != "openspec":
|
|
66
|
+
raise SystemExit("当前 workspace.yml 未启用 OpenSpec 模式,无需执行 archive-openspec。")
|
|
67
|
+
|
|
68
|
+
change_dir = openspec_change_dir(config)
|
|
69
|
+
writeback_dir = openspec_writeback_dir(config)
|
|
70
|
+
archive_input = read_json(writeback_dir / "archive-input.json", {})
|
|
71
|
+
if not archive_input:
|
|
72
|
+
raise SystemExit(f"未找到 archive-input.json:{writeback_dir / 'archive-input.json'}")
|
|
73
|
+
if not archive_input.get("archive_ready", False):
|
|
74
|
+
blockers = ", ".join(archive_input.get("blockers", []))
|
|
75
|
+
raise SystemExit(f"当前 change 尚未满足归档条件:{blockers}")
|
|
76
|
+
if str(archive_input.get("merge_mode", "")).strip() != "safe_merge":
|
|
77
|
+
raise SystemExit("当前 change 不是 safe_merge,需先人工处理 spec 冲突。")
|
|
78
|
+
if archive_input.get("spec_conflicts"):
|
|
79
|
+
raise SystemExit("检测到 spec 冲突,归档已中止。")
|
|
80
|
+
|
|
81
|
+
synced_specs = sync_specs(change_dir)
|
|
82
|
+
archive_root = openspec_archive_root(config)
|
|
83
|
+
archive_root.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
archive_name = f"{datetime.now(timezone.utc).strftime('%Y-%m-%d')}-{change_dir.name}"
|
|
85
|
+
archive_target = archive_root / archive_name
|
|
86
|
+
if archive_target.exists():
|
|
87
|
+
raise SystemExit(f"归档目标已存在:{archive_target}")
|
|
88
|
+
shutil.move(str(change_dir), str(archive_target))
|
|
89
|
+
|
|
90
|
+
payload = {
|
|
91
|
+
"change_name": change_dir.name,
|
|
92
|
+
"archived_to": str(archive_target),
|
|
93
|
+
"synced_specs": synced_specs,
|
|
94
|
+
}
|
|
95
|
+
write_managed_json(config, archive_target / "super-engineer" / "archive-result.json", payload)
|
|
96
|
+
write_managed_text(config, archive_target / "super-engineer" / "archive-result.md", build_markdown(payload))
|
|
97
|
+
update_se_state(
|
|
98
|
+
config,
|
|
99
|
+
phase="archived",
|
|
100
|
+
last_command="/se:archive",
|
|
101
|
+
artifacts={
|
|
102
|
+
"archive_result": str(archive_target / "super-engineer" / "archive-result.json"),
|
|
103
|
+
"archived_to": str(archive_target),
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
print(f"archived_to={archive_target}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from common import ensure_workflow_inputs, load_workspace_config, todo_path, update_se_state, workflow_source, workspace_root
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
parser = argparse.ArgumentParser(description="根据 OpenSpec change/tasks 生成桥接 todo 文件。")
|
|
12
|
+
parser.add_argument("--workspace", help="工作空间路径,默认读取当前目录")
|
|
13
|
+
parser.add_argument("--explicit-se-bridge", action="store_true", help="确认本次调用来自用户显式 /se:bridge 命令。")
|
|
14
|
+
args = parser.parse_args()
|
|
15
|
+
|
|
16
|
+
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
17
|
+
config = load_workspace_config(workspace)
|
|
18
|
+
if workflow_source(config) != "openspec":
|
|
19
|
+
raise SystemExit("当前 workspace.yml 未启用 OpenSpec 模式,无需执行 bootstrap-openspec。")
|
|
20
|
+
if not args.explicit_se_bridge:
|
|
21
|
+
raise SystemExit(
|
|
22
|
+
"拒绝执行桥接:bootstrap-openspec 只能由用户显式 /se:bridge 触发。"
|
|
23
|
+
"如果当前命令是 /se:propose、/se:init、/se:plan 或 /se:apply,必须停止,不能生成 todo.md。"
|
|
24
|
+
)
|
|
25
|
+
result = ensure_workflow_inputs(config, allow_bridge_write=True)
|
|
26
|
+
update_se_state(
|
|
27
|
+
config,
|
|
28
|
+
phase="bridged",
|
|
29
|
+
last_command="/se:bridge",
|
|
30
|
+
artifacts={
|
|
31
|
+
"todo": str(todo_path(config)),
|
|
32
|
+
"bridge_source": str(result.get("bridge_source", "")),
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
print(f"workflow_source={result.get('workflow_source', '')}")
|
|
36
|
+
print(f"todo={todo_path(config)}")
|
|
37
|
+
print(f"bridge_generated={'true' if result.get('bridge_generated') else 'false'}")
|
|
38
|
+
print(f"bridge_source={result.get('bridge_source', '')}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|