super-engineer-workflow 0.1.1 → 0.1.4
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 +24 -0
- package/README.md +3 -1
- 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 +22 -1
- package/package.json +1 -1
- package/scripts/se-setup.py +112 -6
- package/scripts/se-smoke-test.py +2 -0
- package/super-engineer-workflow/SKILL.md +50 -405
- package/super-engineer-workflow/scripts/common.py +50 -0
- package/super-engineer-workflow/scripts/generate-review-report.py +18 -2
- package/super-engineer-workflow/scripts/generate-self-check.py +1 -1
- package/super-engineer-workflow/scripts/generate-smart-plan.py +24 -0
- package/super-engineer-workflow/scripts/propose-openspec.py +20 -11
- package/super-engineer-workflow/scripts/run-verify-and-report.py +28 -5
- package/super-engineer-workflow/scripts/run-workflow.py +3 -3
- package/super-engineer-workflow/scripts/writeback-openspec.py +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
- Review 阶段优先读取 `plan-summary.json`,仅在缺失时回退到 `plan.json`。
|
|
6
|
+
- Self-check、verify、OpenSpec writeback 阶段优先读取轻量计划摘要,减少重复加载完整计划。
|
|
7
|
+
- Review 阶段默认压缩过长 diff 摘要,避免对话和报告带入大量无效上下文。
|
|
8
|
+
- Verify 阶段增加命令输出压缩工具,为长日志截断和摘要化提供统一入口。
|
|
9
|
+
|
|
10
|
+
## 0.1.3
|
|
11
|
+
|
|
12
|
+
- 压缩 `SKILL.md` 为轻量入口,详细规则改为按需读取 references,降低每次 `/se:*` 固定上下文消耗。
|
|
13
|
+
- 快捷命令模板改为极简形式,减少 Claude / Codex slash command 触发时的重复提示词。
|
|
14
|
+
- `/se:propose` 不再把 `reference_files` 全文复制进 `propose-input.json` / `propose-input.md`,改为路径、sha256、标题和摘要片段。
|
|
15
|
+
- 大型 Markdown 参考文档自动摘要,保留按需读取全文能力。
|
|
16
|
+
- OpenSpec bridge context 增加 proposal/design 摘要片段,避免后续阶段重复读取全文。
|
|
17
|
+
- `plan` 阶段新增 `plan-summary.json`,供后续阶段优先读取轻量计划摘要。
|
|
18
|
+
- 收紧脚本最终回复约束文案,默认输出更 compact。
|
|
19
|
+
|
|
20
|
+
## 0.1.2
|
|
21
|
+
|
|
22
|
+
- `se init` 在 `openspec` 模式下默认尝试执行 `openspec init . --tools codex,claude`。
|
|
23
|
+
- `se init` 自动生成工作区 `.claude/commands/se/*` 快捷命令。
|
|
24
|
+
- 当初始化时选择安装 Codex skill,同步生成 `~/.codex/prompts/se-*.md` 快捷提示。
|
|
25
|
+
- 增加 `--skip-openspec-init` 和 `--skip-commands` 以便高级用户跳过对应步骤。
|
|
26
|
+
|
|
3
27
|
## 0.1.1
|
|
4
28
|
|
|
5
29
|
- 调整 README 项目定位:适用于新系统开发和存量系统迭代,强调存量系统长期需求迭代优势更明显。
|
package/README.md
CHANGED
|
@@ -123,7 +123,9 @@ se version # 查看版本
|
|
|
123
123
|
python3 scripts/se-setup.py
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
脚本会依次完成环境检查、安装目标选择、工作区选择、代码目录配置、需求目录配置、工作流模式选择、OpenSpec
|
|
126
|
+
脚本会依次完成环境检查、安装目标选择、工作区选择、代码目录配置、需求目录配置、工作流模式选择、OpenSpec 初始化确认、快捷命令生成,并在执行前展示摘要。最终会创建 `workspace.yml`、`openspec/`、`superengineer/<demand_name>/需求.md`、`.claude/commands/se/*`,并可选安装 skill 到 Codex / Claude 本机目录。
|
|
127
|
+
|
|
128
|
+
当选择 `openspec` 模式时,`se init` 默认会尝试在工作区根目录执行 `openspec init . --tools codex,claude`。如果本机未安装 OpenSpec CLI,会跳过并给出提示;如果你希望跳过该步骤,可以使用 `--skip-openspec-init`。
|
|
127
129
|
|
|
128
130
|
如果需要非交互初始化:
|
|
129
131
|
|
|
@@ -112,7 +112,28 @@ python3 scripts/se-setup.py
|
|
|
112
112
|
4. 配置代码目录 `code_path` 和当前需求目录名。
|
|
113
113
|
5. 选择输入模式:`openspec` 或 `todo`。
|
|
114
114
|
6. 选择是否尝试执行 `openspec init`。
|
|
115
|
-
7.
|
|
115
|
+
7. 选择是否生成 Claude / Codex 快捷命令。
|
|
116
|
+
8. 展示初始化摘要,确认后再真正写入文件。
|
|
117
|
+
|
|
118
|
+
当输入模式为 `openspec` 时,默认会在工作区根目录尝试执行:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
openspec init . --tools codex,claude
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
如果本机没有安装 OpenSpec CLI,会跳过并提示。高级用户可以使用 `--skip-openspec-init` 跳过。
|
|
125
|
+
|
|
126
|
+
初始化会自动生成工作区 Claude Code 快捷命令:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
ai-workspace/.claude/commands/se/
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
如果初始化时选择安装到 Codex,也会同步生成:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
~/.codex/prompts/se-*.md
|
|
136
|
+
```
|
|
116
137
|
|
|
117
138
|
如果需要非交互初始化:
|
|
118
139
|
|
package/package.json
CHANGED
package/scripts/se-setup.py
CHANGED
|
@@ -28,7 +28,9 @@ def main() -> None:
|
|
|
28
28
|
help="安装 skill 到本机目录。默认:none",
|
|
29
29
|
)
|
|
30
30
|
parser.add_argument("--force-skill", action="store_true", help="安装 skill 时先删除已有目录。")
|
|
31
|
-
parser.add_argument("--openspec-init", action="store_true", help="如果安装了 openspec CLI,尝试执行 openspec init。")
|
|
31
|
+
parser.add_argument("--openspec-init", action="store_true", help="如果安装了 openspec CLI,尝试执行 openspec init。openspec 模式默认启用。")
|
|
32
|
+
parser.add_argument("--skip-openspec-init", action="store_true", help="openspec 模式下也跳过 openspec init。")
|
|
33
|
+
parser.add_argument("--skip-commands", action="store_true", help="跳过生成 Claude / Codex 快捷命令。")
|
|
32
34
|
parser.add_argument("--yes", action="store_true", help="非交互模式,全部使用参数或默认值。")
|
|
33
35
|
args = parser.parse_args()
|
|
34
36
|
|
|
@@ -55,7 +57,9 @@ def main() -> None:
|
|
|
55
57
|
demand_name=demand_name,
|
|
56
58
|
source=source,
|
|
57
59
|
mode=mode,
|
|
58
|
-
openspec_init=args.openspec_init,
|
|
60
|
+
openspec_init=should_init_openspec(source, args.openspec_init, args.skip_openspec_init),
|
|
61
|
+
install_target=install,
|
|
62
|
+
skip_commands=args.skip_commands,
|
|
59
63
|
)
|
|
60
64
|
print_summary(workspace, demand_name, source, mode)
|
|
61
65
|
|
|
@@ -73,6 +77,8 @@ def should_prompt(args: argparse.Namespace) -> bool:
|
|
|
73
77
|
args.install,
|
|
74
78
|
args.force_skill,
|
|
75
79
|
args.openspec_init,
|
|
80
|
+
args.skip_openspec_init,
|
|
81
|
+
args.skip_commands,
|
|
76
82
|
]
|
|
77
83
|
)
|
|
78
84
|
return not provided and sys.stdin.isatty()
|
|
@@ -129,8 +135,10 @@ def prompt_args(args: argparse.Namespace) -> argparse.Namespace:
|
|
|
129
135
|
},
|
|
130
136
|
)
|
|
131
137
|
print("")
|
|
132
|
-
print("Step 6/7 OpenSpec
|
|
133
|
-
args.openspec_init = prompt_bool("是否尝试执行 openspec init", default=
|
|
138
|
+
print("Step 6/7 OpenSpec 初始化与快捷命令")
|
|
139
|
+
args.openspec_init = prompt_bool("是否尝试执行 openspec init", default=True)
|
|
140
|
+
args.skip_openspec_init = not args.openspec_init
|
|
141
|
+
args.skip_commands = not prompt_bool("是否生成 Claude / Codex 快捷命令", default=True)
|
|
134
142
|
print("")
|
|
135
143
|
print("Step 7/7 执行前确认")
|
|
136
144
|
print(f" install: {args.install}")
|
|
@@ -141,6 +149,7 @@ def prompt_args(args: argparse.Namespace) -> argparse.Namespace:
|
|
|
141
149
|
print(f" source: {args.source}")
|
|
142
150
|
print(f" mode: {args.mode}")
|
|
143
151
|
print(f" openspec_init: {args.openspec_init}")
|
|
152
|
+
print(f" skip_commands: {args.skip_commands}")
|
|
144
153
|
if not prompt_bool("确认开始初始化", default=True):
|
|
145
154
|
raise SystemExit("已取消初始化。")
|
|
146
155
|
print("")
|
|
@@ -218,6 +227,8 @@ def init_workspace(
|
|
|
218
227
|
source: str,
|
|
219
228
|
mode: str,
|
|
220
229
|
openspec_init: bool,
|
|
230
|
+
install_target: str,
|
|
231
|
+
skip_commands: bool,
|
|
221
232
|
) -> None:
|
|
222
233
|
(workspace / "docs").mkdir(parents=True, exist_ok=True)
|
|
223
234
|
(workspace / "openspec" / "changes").mkdir(parents=True, exist_ok=True)
|
|
@@ -255,6 +266,16 @@ def init_workspace(
|
|
|
255
266
|
|
|
256
267
|
if openspec_init:
|
|
257
268
|
try_init_openspec(workspace)
|
|
269
|
+
if not skip_commands:
|
|
270
|
+
install_workspace_commands(workspace)
|
|
271
|
+
if install_target in ("codex", "both"):
|
|
272
|
+
install_codex_prompts()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def should_init_openspec(source: str, openspec_init: bool, skip_openspec_init: bool) -> bool:
|
|
276
|
+
if skip_openspec_init:
|
|
277
|
+
return False
|
|
278
|
+
return source == "openspec" or openspec_init
|
|
258
279
|
|
|
259
280
|
|
|
260
281
|
def build_workspace_yml(code_path: str, demand_name: str, source: str, mode: str) -> str:
|
|
@@ -292,8 +313,7 @@ def try_init_openspec(workspace: Path) -> None:
|
|
|
292
313
|
if not shutil.which("openspec"):
|
|
293
314
|
print("openspec_init=skipped:openspec CLI not found")
|
|
294
315
|
return
|
|
295
|
-
|
|
296
|
-
if config_file.exists():
|
|
316
|
+
if openspec_initialized(workspace):
|
|
297
317
|
print("openspec_init=skipped:already initialized")
|
|
298
318
|
return
|
|
299
319
|
result = subprocess.run(
|
|
@@ -308,6 +328,92 @@ def try_init_openspec(workspace: Path) -> None:
|
|
|
308
328
|
print(f"openspec_init={'ok' if result.returncode == 0 else 'failed'}")
|
|
309
329
|
|
|
310
330
|
|
|
331
|
+
def openspec_initialized(workspace: Path) -> bool:
|
|
332
|
+
markers = [
|
|
333
|
+
workspace / "openspec" / "config.yaml",
|
|
334
|
+
workspace / ".claude" / "commands" / "opsx" / "propose.md",
|
|
335
|
+
workspace / ".codex" / "skills" / "openspec-propose" / "SKILL.md",
|
|
336
|
+
]
|
|
337
|
+
return any(path.exists() for path in markers)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
SE_COMMANDS: dict[str, str] = {
|
|
341
|
+
"propose.md": """---
|
|
342
|
+
description: Super Engineer:生成或完善 OpenSpec change
|
|
343
|
+
argument-hint: <change-name>
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
请使用 super-engineer-workflow skill 执行:`/se:propose $ARGUMENTS`。
|
|
347
|
+
如果 `$ARGUMENTS` 为空,请先询问用户提供 OpenSpec change 名称。
|
|
348
|
+
""",
|
|
349
|
+
"propose-fix.md": """---
|
|
350
|
+
description: Super Engineer:需求补充后修正当前 OpenSpec change
|
|
351
|
+
argument-hint: <change-name>
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
请使用 super-engineer-workflow skill 执行:`/se:propose $ARGUMENTS`。
|
|
355
|
+
当前需求有补充,请修正当前 OpenSpec change;不要创建新的 change,不要改代码。
|
|
356
|
+
""",
|
|
357
|
+
"bridge.md": """---
|
|
358
|
+
description: Super Engineer:生成桥接 todo
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
请使用 super-engineer-workflow skill 执行:`/se:bridge`。
|
|
362
|
+
生成桥接 todo 并总结待审核项,不要改代码,不要进入实现。
|
|
363
|
+
""",
|
|
364
|
+
"plan.md": """---
|
|
365
|
+
description: Super Engineer:只生成实施计划
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
请使用 super-engineer-workflow skill 执行:`/se:plan`。
|
|
369
|
+
只生成计划,不要改代码。
|
|
370
|
+
""",
|
|
371
|
+
"apply.md": """---
|
|
372
|
+
description: Super Engineer:审核 todo 后进入交付阶段
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
请使用 super-engineer-workflow skill 执行:`/se:apply`。
|
|
376
|
+
我已审核当前桥接 todo,可以进入交付阶段。
|
|
377
|
+
""",
|
|
378
|
+
"archive-check.md": """---
|
|
379
|
+
description: Super Engineer:检查 OpenSpec 归档条件
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
请使用 super-engineer-workflow skill 执行:`/se:archive-check`。
|
|
383
|
+
""",
|
|
384
|
+
"archive.md": """---
|
|
385
|
+
description: Super Engineer:归档 OpenSpec change
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
请使用 super-engineer-workflow skill 执行:`/se:archive`。
|
|
389
|
+
""",
|
|
390
|
+
"status.md": """---
|
|
391
|
+
description: Super Engineer:查看当前工作流状态
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
请使用 super-engineer-workflow skill 执行:`/se:status`。
|
|
395
|
+
""",
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def install_workspace_commands(workspace: Path) -> None:
|
|
400
|
+
command_dir = workspace / ".claude" / "commands" / "se"
|
|
401
|
+
command_dir.mkdir(parents=True, exist_ok=True)
|
|
402
|
+
for name, content in SE_COMMANDS.items():
|
|
403
|
+
(command_dir / name).write_text(content, encoding="utf-8")
|
|
404
|
+
print(f"claude_commands=created:{command_dir}")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def install_codex_prompts() -> None:
|
|
408
|
+
prompt_dir = Path(os.environ.get("CODEX_HOME", "~/.codex")).expanduser() / "prompts"
|
|
409
|
+
prompt_dir.mkdir(parents=True, exist_ok=True)
|
|
410
|
+
for name, content in SE_COMMANDS.items():
|
|
411
|
+
codex_name = f"se-{name}"
|
|
412
|
+
codex_content = content.replace("~/.claude/skills/", "~/.codex/skills/")
|
|
413
|
+
(prompt_dir / codex_name).write_text(codex_content, encoding="utf-8")
|
|
414
|
+
print(f"codex_prompts=created:{prompt_dir}")
|
|
415
|
+
|
|
416
|
+
|
|
311
417
|
def print_summary(workspace: Path, demand_name: str, source: str, mode: str) -> None:
|
|
312
418
|
print("")
|
|
313
419
|
print("初始化完成。")
|
package/scripts/se-smoke-test.py
CHANGED
|
@@ -44,6 +44,8 @@ def main() -> None:
|
|
|
44
44
|
assert_file(workspace / "workspace.yml")
|
|
45
45
|
assert_file(workspace / "superengineer" / "1-demo" / "需求.md")
|
|
46
46
|
assert_dir(workspace / "openspec" / "changes")
|
|
47
|
+
assert_file(workspace / ".claude" / "commands" / "se" / "propose.md")
|
|
48
|
+
assert_file(workspace / ".claude" / "commands" / "se" / "bridge.md")
|
|
47
49
|
|
|
48
50
|
legacy = root / "legacy-workspace"
|
|
49
51
|
legacy.mkdir()
|
|
@@ -1,37 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: super-engineer-workflow
|
|
3
|
-
description: Mandatory for
|
|
3
|
+
description: Mandatory for `/se:*` super-engineer workflow commands. Treat `/se:*` as AI workflow commands, not shell text and not OpenSpec `/opsx:*`. Supports todo and OpenSpec-bridged delivery through workspace.yml, script-managed state, reports, verification, notifications, and archive.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Super Engineer Workflow
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Use this skill when the user sends `/se:*` or asks to run the super-engineer workflow.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Command Routing
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
`/se:*` is a workflow instruction for AI. It is not a shell command and must not be mapped to OpenSpec `/opsx:*`.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- `/se:*` 是 super-engineer 的 AI 工作流命令
|
|
17
|
-
- `/se:*` 不是普通聊天文本
|
|
18
|
-
- `/se:*` 不是 shell 命令
|
|
19
|
-
- `/se:*` 不是 OpenSpec 官方 `/opsx:*` 命令
|
|
20
|
-
- 不要回复“有什么可以帮你”这类泛化聊天
|
|
21
|
-
- 不要要求用户解释 `/se:*` 是什么
|
|
22
|
-
- 不要把 `/se:*` 翻译成其他命令前缀
|
|
23
|
-
- 必须按 [references/se-commands.md](references/se-commands.md) 解释并推进
|
|
24
|
-
|
|
25
|
-
`/se:*` 是用户发给 AI 的工作流指令,不是 shell 命令。AI 必须:
|
|
26
|
-
|
|
27
|
-
1. 识别命令名和用户补充说明
|
|
28
|
-
2. 读取 `<workspace>/workspace.yml`
|
|
29
|
-
3. 判断 `workflow_source`、`mode`、当前会话状态和 OpenSpec change 状态
|
|
30
|
-
4. 检查命令前置条件
|
|
31
|
-
5. 调用本 skill 的内部 workflow 推进阶段
|
|
32
|
-
6. 把结果、阻塞项和下一步建议汇报给用户
|
|
33
|
-
|
|
34
|
-
支持的命令:
|
|
14
|
+
Supported commands:
|
|
35
15
|
|
|
36
16
|
- `/se:init`
|
|
37
17
|
- `/se:propose <change-name>`
|
|
@@ -44,396 +24,61 @@ description: Mandatory for any user message that starts with `/se:` or asks to r
|
|
|
44
24
|
- `/se:archive`
|
|
45
25
|
- `/se:status`
|
|
46
26
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- 不要把 `/se:*` 映射为 OpenSpec 官方 `/opsx:*`
|
|
50
|
-
- 不要要求用户自己执行底层脚本
|
|
51
|
-
- 禁止 AI 编辑 `<workspace>/workspace.yml`;`workspace.yml` 是用户维护的工作空间契约,只能读取和校验,不能自动修改
|
|
52
|
-
- 禁止 AI 直接创建、修改或伪造 `.super-engineer/current-session.json`、`.super-engineer/sessions/**/status.json`、`plan.json`、`review.json`、`verify.json`、`notification.json` 等工作流状态产物;这些文件只能由本 skill 脚本写入
|
|
53
|
-
- 禁止 AI 直接在 `output_dir/<session_id>/` 下手工补写 `discovery.md`、`plan.md`、`self-check.md`、`review.md`、`verify.md` 等标准报告;这些标准报告只能由对应脚本生成
|
|
54
|
-
- 如果发现当前 session 缺少标准 JSON 或 Markdown 产物,不得手工补文件;必须通过 `/se:plan`、`/se:review`、`/se:verify` 或重新创建标准 session 来恢复
|
|
55
|
-
- 每次只能执行用户当前消息中明确请求的 `/se:*` 命令;“下一步建议”只能作为文字建议,绝不能自动执行下一步命令
|
|
56
|
-
- `openspec` 模式下,`/se:bridge` 生成的桥接 todo 必须先经过人工审核;用户审核后直接发送 `/se:apply` 进入交付阶段
|
|
57
|
-
- `openspec` 模式下,只有用户显式发送 `/se:bridge` 时才允许生成或重写 `todo_file`;`/se:propose`、`/se:init`、`/se:plan`、`/se:apply` 都严禁自动桥接
|
|
58
|
-
- 桥接 todo 的实际路径由 `workspace.yml.todo_file` 决定,不要假设固定文件名;如果用户没有特殊要求,推荐使用 `todo.md`
|
|
59
|
-
- `manual` 模式下,计划、实现、审查后按门禁停留
|
|
60
|
-
- `auto` 模式下,除非出现硬阻塞,否则连续推进
|
|
61
|
-
- `auto` 模式只在 `/se:apply` 命令内部生效;`/se:init`、`/se:propose`、`/se:bridge`、`/se:plan` 都必须在各自阶段完成后停止
|
|
62
|
-
- `/se:archive` 只能在 `archive_ready=true`、`merge_mode=safe_merge`、`spec_conflicts=[]` 时继续
|
|
63
|
-
- 当前置条件不满足时,停止该命令并明确说明缺少什么、应该先执行哪个 `/se:*` 命令
|
|
64
|
-
- `/se:propose` 必须显式携带 OpenSpec change 名称,例如 `/se:propose demand-addition-rate`;AI 不得根据需求标题或 `demand_name` 自行推导 change 名称
|
|
65
|
-
- `/se:propose <change-name>` 应先执行 `python3 scripts/run-workflow.py route-se --command-text "/se:propose <change-name>"`,优先使用 OpenSpec CLI 创建 change、读取 status 和 artifact instructions;随后 AI 根据 `propose-input.json`、`demand_file` 和 `reference_files` 生成或完善 OpenSpec artifacts
|
|
66
|
-
- `/se:propose` 执行全过程禁止调用 `bootstrap-openspec.py`、`run-workflow.py bootstrap-openspec`,禁止创建或修改 `workspace.yml.todo_file` 指向的文件;完成后 `se-state.phase` 必须是 `proposed`
|
|
67
|
-
- `/se:propose` 完成后只能提示下一步 `/se:bridge`,禁止提示 `/se:apply`
|
|
68
|
-
- `/se:propose` 的完成回复中禁止出现“确认无误后执行 `/se:apply`”“通过 `/se:apply` 进入实现阶段”等表达;如果需要提示后续,只能写“下一步:执行 `/se:bridge` 生成待审核 todo”
|
|
69
|
-
- `/se:bridge` 完成后只能提示人工审核 todo,审核通过后发送 `/se:apply`;禁止自动执行 `/se:apply`
|
|
70
|
-
- `/se:bridge` 必须通过 `python3 scripts/run-workflow.py route-se --command-text "/se:bridge"` 或带 `--explicit-se-bridge` 的受控入口执行;直接调用 `bootstrap-openspec` 且没有显式桥接标记时应视为错误流程
|
|
71
|
-
- 在进入 `/se:apply` 前,如果人工审核发现需求或 todo 有偏差,允许重新执行 `/se:propose <change-name>` 修正当前 change,然后再次执行 `/se:bridge` 重建待审核 todo;AI 禁止手工同步 `tasks.md` 到 `todo.md`
|
|
72
|
-
- `/se:bridge` 完成后禁止自动调用 plan、apply、start-implement、review、verify 或修改代码
|
|
73
|
-
- `/se:apply` 必须通过标准脚本序列推进:必要时先 `python3 scripts/run-workflow.py plan`,然后 `start-implement`,代码实现完成后 `finish-implement`,`auto` 模式下继续 `review` 和 `verify`;禁止只手工写状态或只手工发送通知后宣布完成
|
|
74
|
-
- `/se:verify` 通过前禁止提示 `/se:archive-check`
|
|
75
|
-
- `/se:archive-check` 未满足 `safe_merge` 前禁止提示 `/se:archive`
|
|
76
|
-
- 工作流完成通知必须由 `python3 scripts/run-workflow.py verify` 触发,禁止 AI 直接调用飞书 webhook,禁止 AI 手工拼接飞书卡片 JSON
|
|
77
|
-
- `notification.json` 是唯一通知证据;启用飞书时,只有 `notification.json` 中存在 `source=run-workflow.py verify`、fingerprint 匹配、`route=feishu`、`template=interactive`、`status=sent` 的结果,才算飞书通知成功
|
|
78
|
-
- `status.json` 中的 `notification_status=sent` 不能单独作为通知成功依据
|
|
79
|
-
- 如果 workflow 已完成但缺少 `notification.json`、`verify.json` 或输出目录下的 Markdown 报告,必须通过 `/se:verify` 重新走标准验证收口;不要新增独立收口命令,也不要手工拼接通知
|
|
80
|
-
- OpenSpec 模式必须使用 `<workspace>/.super-engineer/se-state.json` 作为阶段状态机;执行 `/se:bridge`、`/se:plan`、`/se:apply`、`/se:verify`、`/se:archive-check`、`/se:archive` 前必须通过脚本状态校验
|
|
81
|
-
- AI 不能自行决定下一步命令,只能依据 `se-state.json.allowed_next` 汇报下一步;如果用户请求的命令不在允许范围内,必须停止并说明当前 `phase` 和允许命令
|
|
82
|
-
- 当脚本输出 `final_reply_must` 或 `se_reply_constraint_begin` / `se_reply_constraint_end` 时,AI 最终回复必须遵守该约束;禁止在最终回复中追加与 `allowed_next` 冲突的 `/se:*` 命令
|
|
83
|
-
|
|
84
|
-
门禁命令停止规则:
|
|
85
|
-
|
|
86
|
-
- `/se:init`:完成初始化后停止
|
|
87
|
-
- `/se:propose <change-name>`:完成规格产物后停止
|
|
88
|
-
- `/se:bridge`:生成桥接 todo 后停止
|
|
89
|
-
- `/se:plan`:生成计划后停止,除非用户当前命令就是 `/se:apply`
|
|
90
|
-
- `/se:archive-check`:完成归档检查后停止
|
|
91
|
-
|
|
92
|
-
## 先读取这些输入
|
|
93
|
-
|
|
94
|
-
1. 工作空间配置:`<workspace>/workspace.yml`
|
|
95
|
-
2. Skill 配置:`~/.super-engineer/skill-config.yml`
|
|
96
|
-
3. `workspace.yml` 中配置的 `workflow_source`
|
|
97
|
-
4. `workspace.yml` 中配置的 `todo_file`
|
|
98
|
-
5. `workspace.yml` 中配置的 `demand_file`
|
|
99
|
-
6. `workspace.yml` 中配置的 `reference_files`
|
|
100
|
-
7. `workspace.yml` 中配置的 `code_path`
|
|
101
|
-
8. `workspace.yml` 中配置的 `output_dir`
|
|
102
|
-
9. 如果 `workflow_source=openspec`,继续读取 `workspace.yml` 中的 `openspec`
|
|
103
|
-
|
|
104
|
-
这里的 `<workspace>` 就是当前使用这个 skill 的目录。
|
|
105
|
-
|
|
106
|
-
## 强约束
|
|
107
|
-
|
|
108
|
-
- 工作空间根目录必须存在 `workspace.yml`
|
|
109
|
-
- AI 只能读取和校验 `workspace.yml`,禁止自动编辑、重写或格式化该文件;如果配置需要变更,必须停下并让用户处理
|
|
110
|
-
- `todo_file`、`reference_files`、`code_path`、`output_dir` 可以使用相对路径或绝对路径;相对路径按当前工作空间根目录解析
|
|
111
|
-
- `demand_file` 是可选原始需求输入,主要给 `/se:propose` 使用;如果配置了,`/se:propose` 必须优先读取它
|
|
112
|
-
- `demand_file` 支持本地 Markdown 路径或飞书/Lark 云文档 URL;云文档 URL 必须通过官方 `lark-cli` 读取,禁止 AI 手工复制云文档内容
|
|
113
|
-
- 当 `demand_file` 是飞书/Lark 云文档 URL 时,脚本会检查 `lark-cli` 是否可用;未安装时必须停止并提示用户执行 `npx @larksuite/cli@latest install`、`lark-cli config init --new`、`lark-cli auth login --recommend`
|
|
114
|
-
- `reference_files` 是 `/se:propose`、`/se:plan`、review 的强上下文;如果配置了,`/se:propose` 必须读取真实存在的参考文件并写入 `propose-input.json`
|
|
115
|
-
- `workspace.yml` 支持 `vars` 变量;路径字段可以使用 `${name}` 或 `${vars.name}` 引用变量,例如 `${demand_name}`
|
|
116
|
-
- `/se:bridge` 支持相对路径、绝对路径、`${demand_name}` 变量和 `openspec.changes_dir`;AI 禁止声称桥接脚本要求绝对路径或必须显式配置 `openspec.change_dir`
|
|
117
|
-
- 如果 `/se:bridge` 找不到当前 change,正确处理是提示先执行 `/se:propose <change-name>` 记录 active change,禁止要求用户改 `workspace.yml` 为绝对路径或新增 `openspec.change_dir`
|
|
118
|
-
- `workflow_source=todo` 时,`todo_file` 是用户维护的真实输入
|
|
119
|
-
- `workflow_source=openspec` 时,`todo_file` 是桥接后的执行入口,内容来自 OpenSpec `tasks.md`
|
|
120
|
-
- `workflow_source=openspec` 时,只有显式执行 `/se:bridge` 才允许重写 `todo_file`;`/se:init`、`/se:plan`、`/se:apply` 内部初始化只能校验已有 `todo_file`,不能覆盖人工审核后的 todo
|
|
121
|
-
- `workflow_source=openspec` 时,OpenSpec change 名称只能由 `/se:propose <change-name>` 显式指定;不得从 `vars.demand_name` 或需求标题推导
|
|
122
|
-
- `workspace.yml.verify_commands` 可覆盖自动识别出的验证命令;当存在覆盖命令时,verify 阶段必须优先使用覆盖命令
|
|
123
|
-
- 项目识别优先使用 `adapters/*.yml` 语言适配器,并保留内置 Maven、Gradle、Node、Go、Python、Rust、.NET、PHP、Ruby、Make、CMake 识别兜底
|
|
124
|
-
- 用户真实 Skill 配置位于 `~/.super-engineer/skill-config.yml`
|
|
125
|
-
- 如果启用了 `notification.pushplus.ordinary`,其中的 `token` 必须合法
|
|
126
|
-
- 如果启用了 `notification.feishu`,必须提供合法的飞书机器人 `webhook_url`
|
|
127
|
-
- 如果 `workspace.yml` 缺失,立即停止,并提示用户先补齐配置
|
|
128
|
-
- 不允许写回全局配置
|
|
129
|
-
- 如果 `code_path` 是多个服务的聚合目录,应优先从 todo 中识别“修改的服务是 xxx”或“修改的服务包括 xxx、yyy”这一类约束,并自动定位一个或多个目标仓库
|
|
130
|
-
- 如果 todo 中存在 `# 限制条件` 和 `# 待办` 章节,应把限制条件与真实需求分开解析
|
|
131
|
-
- 如果 todo 中存在 `##` 模块标题、`- [ ]` 未完成任务、`- [x]` 已完成任务和编号子要求,应按结构化任务模型解析
|
|
132
|
-
|
|
133
|
-
## 运行时目录约定
|
|
134
|
-
|
|
135
|
-
工作空间内只保存给 AI 持续推进流程所需的数据:
|
|
136
|
-
|
|
137
|
-
- `<workspace>/.super-engineer/current-session.json`
|
|
138
|
-
- `<workspace>/.super-engineer/se-state.json`
|
|
139
|
-
- `<workspace>/.super-engineer/todo-state.json`
|
|
140
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/discovery.json`
|
|
141
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/plan.json`
|
|
142
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/self-check.json`
|
|
143
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/review.json`
|
|
144
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/verify.json`
|
|
145
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/status.json`
|
|
146
|
-
|
|
147
|
-
给人看的 Markdown 产物写到 `output_dir` 下,并按会话归档:
|
|
148
|
-
|
|
149
|
-
- `<output_dir>/<session_id>/discovery.md`
|
|
150
|
-
- `<output_dir>/<session_id>/plan.md`
|
|
151
|
-
- `<output_dir>/<session_id>/self-check.md`
|
|
152
|
-
- `<output_dir>/<session_id>/review.md`
|
|
153
|
-
- `<output_dir>/<session_id>/verify.md`
|
|
154
|
-
|
|
155
|
-
每次新的 `plan` 都必须创建新的 `session_id`,不能覆盖历史会话。
|
|
156
|
-
|
|
157
|
-
OpenSpec 模式下,`se-state.json` 是命令状态机:
|
|
158
|
-
|
|
159
|
-
- `/se:propose` 后:`phase=proposed`,只允许 `/se:bridge`
|
|
160
|
-
- `/se:bridge` 后:`phase=bridged`,允许 `/se:apply` 或 `/se:plan`
|
|
161
|
-
- `/se:plan` 后:`phase=planned`,只允许 `/se:apply`
|
|
162
|
-
- `/se:verify` 通过后:`phase=verified`,只允许 `/se:archive-check`
|
|
163
|
-
- `/se:archive-check` 通过后:`phase=archive_ready`,只允许 `/se:archive`
|
|
164
|
-
|
|
165
|
-
状态机由脚本写入和校验,AI 禁止手工编辑。
|
|
166
|
-
|
|
167
|
-
todo 模式下,也必须以当前 session 的 `status.json`、`todo-state.json` 和标准产物校验作为状态依据;todo 状态不能写入 OpenSpec 专用的 `se-state.json`:
|
|
168
|
-
|
|
169
|
-
- 未创建 session:允许 `/se:init`、`/se:plan`、`/se:apply`
|
|
170
|
-
- `plan` / `wait_confirm_plan`:允许 `/se:apply`
|
|
171
|
-
- `implement`:只允许 AI 修改代码,完成后必须由脚本执行 `finish-implement`
|
|
172
|
-
- `self_check` / `wait_confirm_implement`:允许 `/se:review`
|
|
173
|
-
- `review` / `wait_confirm_review`:允许 `/se:verify`
|
|
174
|
-
- `done`:不再自动推进
|
|
175
|
-
- `blocked`:允许修复后重新 `/se:apply` 或 `/se:verify`
|
|
176
|
-
|
|
177
|
-
todo 模式下如果 `current-session.json` 指向旧 `output_dir`,或当前 session 缺少标准 `status.json` / `plan.json`,必须重新创建标准 session;禁止复用旧需求的会话状态。
|
|
178
|
-
|
|
179
|
-
工作流耗时和通知结果写回当前会话的 `status.json`,通知明细写入:
|
|
180
|
-
|
|
181
|
-
- `<workspace>/.super-engineer/sessions/<session_id>/notification.json`
|
|
182
|
-
|
|
183
|
-
`notification.json` 是通知是否成功的唯一证据。聊天记录、截图和 `status.json.notification_status` 都不能单独证明飞书通知合规。
|
|
184
|
-
|
|
185
|
-
标准 JSON 产物必须带有脚本来源标识:
|
|
186
|
-
|
|
187
|
-
- `plan.json`:`source=run-workflow.py plan`
|
|
188
|
-
- `self-check.json`:`source=run-workflow.py self-check`
|
|
189
|
-
- `review.json`:`source=run-workflow.py review`
|
|
190
|
-
- `verify.json`:`source=run-workflow.py verify`
|
|
191
|
-
- `notification.json`:`source=run-workflow.py verify`
|
|
192
|
-
|
|
193
|
-
缺少上述来源标识的产物视为非标准产物,不能作为工作流完成依据。
|
|
194
|
-
|
|
195
|
-
## 执行模式
|
|
196
|
-
|
|
197
|
-
先阅读 [references/execution-modes.md](references/execution-modes.md)。
|
|
198
|
-
|
|
199
|
-
- `manual`:在计划、实现、审查三个检查点等待用户确认
|
|
200
|
-
- `auto`:除非阻塞,否则沿工作流自动推进
|
|
201
|
-
|
|
202
|
-
始终保持 `status.json` 为当前会话的真实状态。`status.json` 是脚本管理文件,AI 不能直接编辑它,只能通过 `scripts/run-workflow.py` 或被该入口调用的脚本更新。
|
|
203
|
-
|
|
204
|
-
`auto` 模式下的执行纪律:
|
|
205
|
-
|
|
206
|
-
- 不要在正常推进阶段请求用户批准
|
|
207
|
-
- 不要说“批准 plan 后我再继续”
|
|
208
|
-
- 如果计划不够精确,应直接去代码里定位,再补充计划并继续
|
|
209
|
-
- 只有遇到 [references/workflow.md](references/workflow.md) 中定义的硬阻塞,才允许停下来询问用户
|
|
210
|
-
- 如果没有硬阻塞,就继续推进到实现、审查、验证,而不是把决策留在对话里
|
|
211
|
-
|
|
212
|
-
## 输入模式
|
|
213
|
-
|
|
214
|
-
`workspace.yml` 用 `workflow_source` 控制输入来源:
|
|
215
|
-
|
|
216
|
-
- `todo`:沿用当前模式,直接读取 `todo_file`
|
|
217
|
-
- `openspec`:从当前 active OpenSpec change 的 `tasks.md` 生成桥接 `todo_file`,并把 `proposal.md`、`design.md`、`specs/` 下的 markdown 自动并入参考上下文
|
|
218
|
-
|
|
219
|
-
OpenSpec 模式底层脚本只允许在对应 `/se:*` 阶段由受控入口执行:
|
|
220
|
-
|
|
221
|
-
- `python3 scripts/run-workflow.py propose-openspec`
|
|
222
|
-
- `python3 scripts/run-workflow.py bootstrap-openspec --explicit-se-bridge`
|
|
223
|
-
- `python3 scripts/run-workflow.py writeback-openspec`
|
|
224
|
-
- `python3 scripts/run-workflow.py prepare-archive-openspec`
|
|
225
|
-
- `python3 scripts/run-workflow.py archive-openspec`
|
|
226
|
-
|
|
227
|
-
`init` 和 `plan` 只校验已有桥接 todo,不能自动完成桥接。
|
|
228
|
-
`review` 和 `verify` 完成后会自动把执行摘要回写到 `openspec.writeback_dir`。
|
|
229
|
-
`archive-openspec` 只在 `prepare-archive-openspec` 产出的 `merge_mode=safe_merge` 时允许自动执行。
|
|
230
|
-
|
|
231
|
-
`openspec` 模式下,如果用户通过 `/se:*` 使用工作流,推荐阶段顺序是:
|
|
232
|
-
|
|
233
|
-
1. `/se:propose <change-name>`
|
|
234
|
-
2. `/se:bridge`
|
|
235
|
-
3. 人工审核 `todo.md`
|
|
236
|
-
4. `/se:apply`
|
|
237
|
-
5. `/se:archive-check`
|
|
238
|
-
6. `/se:archive`
|
|
239
|
-
|
|
240
|
-
`todo` 模式下,如果用户通过 `/se:*` 使用工作流,推荐阶段顺序是:
|
|
241
|
-
|
|
242
|
-
1. `/se:init`
|
|
243
|
-
2. `/se:plan`
|
|
244
|
-
3. `/se:apply`
|
|
245
|
-
4. `/se:review`
|
|
246
|
-
5. `/se:verify`
|
|
247
|
-
|
|
248
|
-
## 必走工作流
|
|
249
|
-
|
|
250
|
-
必须使用统一入口 [`scripts/run-workflow.py`](scripts/run-workflow.py) 推进工作流状态,不要在对话里手工拼工作流状态。
|
|
251
|
-
|
|
252
|
-
### 1. 初始化上下文
|
|
253
|
-
|
|
254
|
-
- 读取并校验 `<workspace>/workspace.yml`
|
|
255
|
-
- 读取并校验 `~/.super-engineer/skill-config.yml`
|
|
256
|
-
- 执行 `python3 scripts/run-workflow.py init`
|
|
257
|
-
- 读取 todo 和参考文件
|
|
258
|
-
- 检查代码目录,尽量识别语言、构建方式和可安全执行的验证命令
|
|
259
|
-
|
|
260
|
-
如果 `~/.super-engineer/skill-config.yml` 不存在,初始化阶段必须自动生成该文件,然后立即停止当前工作流,并明确提示用户:
|
|
261
|
-
|
|
262
|
-
- 已生成的配置文件路径
|
|
263
|
-
- 请先完善配置后再重新继续
|
|
264
|
-
|
|
265
|
-
如果 `todo_file` 指向的文件不存在,初始化阶段必须自动创建一个带示例结构的 `todo.md` 模板,方便用户直接填写。
|
|
266
|
-
|
|
267
|
-
创建模板后的行为约束:
|
|
268
|
-
|
|
269
|
-
- 执行 `init` 时:创建模板后停止在初始化阶段,并提示用户先完善 todo
|
|
270
|
-
- 执行 `plan` 时:如果检测到 todo 仍然是模板示例内容,必须停止,不允许继续基于模板内容生成计划
|
|
271
|
-
|
|
272
|
-
参考:
|
|
273
|
-
|
|
274
|
-
- [references/workflow.md](references/workflow.md)
|
|
275
|
-
- [references/project-docs.md](references/project-docs.md)
|
|
276
|
-
- [references/java.md](references/java.md)
|
|
277
|
-
|
|
278
|
-
### 2. 上下文定位
|
|
279
|
-
|
|
280
|
-
使用 `python3 scripts/run-workflow.py discover`。
|
|
281
|
-
|
|
282
|
-
这一步会从 todo 中提取服务名、接口名、字段名、类名、表名等关键词,逐仓执行代码定位,写入:
|
|
283
|
-
|
|
284
|
-
- `discovery.json`
|
|
285
|
-
- `discovery.md`
|
|
286
|
-
|
|
287
|
-
`plan` 命令会自动先执行 discover。通常不需要单独运行,除非 todo 或目标仓库发生变化。
|
|
288
|
-
|
|
289
|
-
### 3. 生成计划
|
|
290
|
-
|
|
291
|
-
使用 `python3 scripts/run-workflow.py plan`。
|
|
292
|
-
|
|
293
|
-
这一步必须:
|
|
294
|
-
|
|
295
|
-
- 创建新的会话目录
|
|
296
|
-
- 更新 `<workspace>/.super-engineer/current-session.json`
|
|
297
|
-
- 写入当前会话的 `plan.json`
|
|
298
|
-
- 写入当前会话对应输出目录的 `plan.md`
|
|
299
|
-
- 初始化当前会话的 `status.json`
|
|
300
|
-
|
|
301
|
-
`auto` 模式下,计划生成后不能因为“影响文件尚未精确到具体代码位置”而停下来要求批准,应直接继续做代码定位。
|
|
302
|
-
|
|
303
|
-
todo 解析规则:
|
|
304
|
-
|
|
305
|
-
- `# 限制条件`:约束信息
|
|
306
|
-
- `# 待办` 或 `# 待办事项`:任务入口
|
|
307
|
-
- `## 模块标题`:大需求模块
|
|
308
|
-
- `- [ ] 任务`:本轮待执行任务
|
|
309
|
-
- `- [x] 任务`:已完成任务,不进入本轮计划
|
|
310
|
-
- `1.` `2.` 或普通说明行:挂到上一个任务下面作为子要求
|
|
311
|
-
|
|
312
|
-
如果 todo 中所有任务都已经标记完成,应停止生成新计划,并明确提示当前没有未完成任务。
|
|
313
|
-
|
|
314
|
-
计划至少要覆盖:
|
|
315
|
-
|
|
316
|
-
- 需求摘要
|
|
317
|
-
- todo 完成进度
|
|
318
|
-
- 任务模块与子任务拆解
|
|
319
|
-
- 上下文定位证据
|
|
320
|
-
- 计划置信度
|
|
321
|
-
- 实际命中的目标代码目录
|
|
322
|
-
- 实际命中的目标仓库列表
|
|
323
|
-
- 关键假设
|
|
324
|
-
- 识别到的项目技术栈
|
|
325
|
-
- 影响模块
|
|
326
|
-
- 影响文件
|
|
327
|
-
- 每个任务的验收标准
|
|
328
|
-
- 可独立推进的实施切片
|
|
329
|
-
- 有序修改步骤
|
|
330
|
-
- 测试计划
|
|
331
|
-
- 风险
|
|
332
|
-
- 未知项
|
|
333
|
-
|
|
334
|
-
`plan.json` 必须兼容 [assets/plan-schema.json](assets/plan-schema.json)。
|
|
335
|
-
|
|
336
|
-
### 4. 按计划实施修改
|
|
337
|
-
|
|
338
|
-
把当前会话的 `plan.json` 作为唯一计划基线,并优先参考 `discovery.json` 中的代码证据。
|
|
339
|
-
|
|
340
|
-
- 严格围绕计划推进实现
|
|
341
|
-
- 如果代码现实与计划冲突,先修正计划再继续
|
|
342
|
-
- 用 [`scripts/update-status.py`](scripts/update-status.py) 更新当前会话的 `status.json`
|
|
343
|
-
- 如果遇到阻塞,把阻塞原因写入状态,不要在聊天里悄悄跳过
|
|
344
|
-
- `auto` 模式下,如果只是需要进一步定位控制器、校验逻辑、调用链或测试入口,这不是阻塞,应直接继续
|
|
345
|
-
|
|
346
|
-
推荐阶段切换:
|
|
347
|
-
|
|
348
|
-
- 开始实现前:`python3 scripts/run-workflow.py start-implement`
|
|
349
|
-
- 实现完成后:`python3 scripts/run-workflow.py finish-implement`
|
|
350
|
-
|
|
351
|
-
`finish-implement` 会自动执行实现自查并生成:
|
|
352
|
-
|
|
353
|
-
- `self-check.json`
|
|
354
|
-
- `self-check.md`
|
|
355
|
-
|
|
356
|
-
自查发现阻塞项时,不进入 review。
|
|
357
|
-
|
|
358
|
-
`manual` 模式下,实现后要停下来等用户确认。
|
|
359
|
-
|
|
360
|
-
`auto` 模式下,不要在实现开始前再发起额外确认。
|
|
361
|
-
|
|
362
|
-
### 5. 审查改动
|
|
363
|
-
|
|
364
|
-
用真实代码差异对照当前会话计划做审查。
|
|
365
|
-
|
|
366
|
-
如果当前会话包含多个独立仓库,必须逐仓读取 Git 差异并汇总审查结论。
|
|
367
|
-
|
|
368
|
-
使用:
|
|
369
|
-
|
|
370
|
-
- `python3 scripts/run-workflow.py review`
|
|
371
|
-
- [references/review-checklist.md](references/review-checklist.md)
|
|
372
|
-
|
|
373
|
-
输出写到当前会话对应的 `review.md`。
|
|
374
|
-
|
|
375
|
-
同时写入结构化门禁结果 `review.json`。如果存在 `blocking=true` 的 finding,工作流进入 `blocked`,不得继续执行 verify。
|
|
376
|
-
|
|
377
|
-
`manual` 模式下,审查后要停下来等用户确认。
|
|
378
|
-
|
|
379
|
-
`auto` 模式下,review 发现计划需要补充时,应先补计划再继续,不要请求批准。
|
|
380
|
-
|
|
381
|
-
### 6. 执行验证
|
|
27
|
+
For command details, read only the relevant section in [references/se-commands.md](references/se-commands.md).
|
|
28
|
+
For mode and artifact details, use [references/workflow.md](references/workflow.md) and [references/execution-modes.md](references/execution-modes.md) only when needed.
|
|
382
29
|
|
|
383
|
-
|
|
30
|
+
## Minimal Required Steps
|
|
384
31
|
|
|
385
|
-
|
|
386
|
-
|
|
32
|
+
1. Read `<workspace>/workspace.yml`.
|
|
33
|
+
2. Identify `workflow_source`, `mode`, `todo_file`, `demand_file`, `reference_files`, `code_path`, `output_dir`, and optional `openspec` fields.
|
|
34
|
+
3. Route the current command through `python3 scripts/run-workflow.py route-se --command-text "<command>"` when possible.
|
|
35
|
+
4. Obey script state validation and script reply constraints.
|
|
36
|
+
5. Report compactly: result, blockers, allowed next command, and key artifact paths.
|
|
387
37
|
|
|
388
|
-
|
|
38
|
+
## Hard Constraints
|
|
389
39
|
|
|
390
|
-
|
|
40
|
+
- Never edit `<workspace>/workspace.yml`; it is a user-maintained contract.
|
|
41
|
+
- Never manually create, edit, or fake `.super-engineer/current-session.json`, `.super-engineer/se-state.json`, `.super-engineer/sessions/**/status.json`, `plan.json`, `review.json`, `verify.json`, `notification.json`, or standard output reports.
|
|
42
|
+
- Standard JSON and Markdown artifacts must be generated by workflow scripts.
|
|
43
|
+
- Execute only the `/se:*` command explicitly requested by the current user message. Suggested next commands are text only.
|
|
44
|
+
- If a script prints `final_reply_must` or `se_reply_constraint_begin` / `se_reply_constraint_end`, the final reply must obey it.
|
|
45
|
+
- `auto` only applies inside `/se:apply`; `/se:init`, `/se:propose`, `/se:bridge`, and `/se:plan` stop after their own stage.
|
|
46
|
+
- In `openspec` mode, `/se:propose <change-name>` requires an explicit change name. Do not infer it from demand title or `demand_name`.
|
|
47
|
+
- In `openspec` mode, only explicit `/se:bridge` may generate or overwrite `todo_file`.
|
|
48
|
+
- `/se:bridge` output must be reviewed by the user before `/se:apply`.
|
|
49
|
+
- `/se:propose` must not call bridge or modify `todo_file`; its next allowed command is `/se:bridge`.
|
|
50
|
+
- `/se:bridge` must not call plan/apply/review/verify or modify code.
|
|
51
|
+
- `/se:verify` must pass before suggesting `/se:archive-check`.
|
|
52
|
+
- `/se:archive-check` must report `safe_merge` before suggesting `/se:archive`.
|
|
53
|
+
- Notifications must be sent only by `python3 scripts/run-workflow.py verify`; AI must not call Feishu/PushPlus webhooks directly.
|
|
54
|
+
- `notification.json` is the only notification success evidence.
|
|
391
55
|
|
|
392
|
-
|
|
56
|
+
## Context Discipline
|
|
393
57
|
|
|
394
|
-
|
|
58
|
+
- Prefer compact summaries and artifact paths over pasting full files into replies.
|
|
59
|
+
- `reference_files` are strong context, but scripts summarize large files by default; read full files only when necessary for the current command.
|
|
60
|
+
- In OpenSpec mode, avoid rereading `proposal.md`, `design.md`, and `specs/**/*.md` unless the current stage needs them.
|
|
61
|
+
- Prefer `plan-summary.json` for downstream context; use `plan.json` only when detailed planning data is required.
|
|
62
|
+
- Final replies should be compact. Detailed reports belong in `output_dir/<session_id>/`.
|
|
395
63
|
|
|
396
|
-
|
|
64
|
+
## State Summary
|
|
397
65
|
|
|
398
|
-
|
|
66
|
+
OpenSpec mode state is script-managed in `<workspace>/.super-engineer/se-state.json`:
|
|
399
67
|
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
- 飞书消息走飞书原生自定义机器人 webhook
|
|
406
|
-
- 不允许 AI 直接调用 webhook 或手工构造飞书卡片;所有飞书通知必须通过 `python3 scripts/run-workflow.py verify` 调用脚本统一模板发送
|
|
407
|
-
- 飞书通知合规性只以当前 session 的 `notification.json` 为准;启用飞书时必须存在 `route=feishu`、`template=interactive`、`status=sent` 的标准结果
|
|
68
|
+
- after `/se:propose`: only `/se:bridge`
|
|
69
|
+
- after `/se:bridge`: `/se:apply` or `/se:plan`
|
|
70
|
+
- after `/se:plan`: only `/se:apply`
|
|
71
|
+
- after successful `/se:verify`: only `/se:archive-check`
|
|
72
|
+
- after safe `/se:archive-check`: only `/se:archive`
|
|
408
73
|
|
|
409
|
-
|
|
74
|
+
Todo mode uses current session `status.json` and `todo-state.json`; do not write OpenSpec `se-state.json` for todo mode.
|
|
410
75
|
|
|
411
|
-
|
|
412
|
-
- [references/workflow.md](references/workflow.md):工作空间契约与产物目录规则
|
|
413
|
-
- [references/contracts.md](references/contracts.md):输入输出契约与归档顺序
|
|
414
|
-
- [references/execution-modes.md](references/execution-modes.md):`manual` 与 `auto` 行为
|
|
415
|
-
- [references/planning.md](references/planning.md):上下文定位与计划质量规则
|
|
416
|
-
- [references/project-docs.md](references/project-docs.md):参考文件的使用方式
|
|
417
|
-
- [references/java.md](references/java.md):Java 项目识别与计划提示
|
|
418
|
-
- [references/review-checklist.md](references/review-checklist.md):代码审查核对项
|
|
419
|
-
- [references/verify-checklist.md](references/verify-checklist.md):验证核对项
|
|
420
|
-
- [references/platform-openclaw.md](references/platform-openclaw.md):面向 OpenClaw 的后续接入约束
|
|
421
|
-
- [scripts/init-workspace.py](scripts/init-workspace.py):初始化工作空间基础目录
|
|
422
|
-
- [scripts/run-workflow.py](scripts/run-workflow.py):统一入口
|
|
423
|
-
- `python3 scripts/run-workflow.py validate-state <command>`:校验当前 OpenSpec 状态是否允许执行指定内部命令
|
|
424
|
-
- [scripts/bootstrap-openspec.py](scripts/bootstrap-openspec.py):OpenSpec `tasks.md` 到桥接 `todo` 的输入适配
|
|
425
|
-
- [scripts/writeback-openspec.py](scripts/writeback-openspec.py):执行摘要回写到 OpenSpec change
|
|
426
|
-
- [scripts/prepare-archive-openspec.py](scripts/prepare-archive-openspec.py):生成归档输入与 merge preview
|
|
427
|
-
- [scripts/archive-openspec.py](scripts/archive-openspec.py):归档 change 并合并 delta specs
|
|
428
|
-
- [scripts/generate-smart-plan.py](scripts/generate-smart-plan.py):生成计划
|
|
429
|
-
- [scripts/update-status.py](scripts/update-status.py):更新状态
|
|
430
|
-
- [scripts/generate-review-report.py](scripts/generate-review-report.py):生成代码审查报告
|
|
431
|
-
- [scripts/run-verify-and-report.py](scripts/run-verify-and-report.py):执行验证并生成报告
|
|
76
|
+
## Script Entry Points
|
|
432
77
|
|
|
433
|
-
|
|
78
|
+
- Main router: `python3 scripts/run-workflow.py route-se --command-text "<command>"`
|
|
79
|
+
- Status: `python3 scripts/run-workflow.py status`
|
|
80
|
+
- Plan: `python3 scripts/run-workflow.py plan`
|
|
81
|
+
- Review: `python3 scripts/run-workflow.py review`
|
|
82
|
+
- Verify: `python3 scripts/run-workflow.py verify`
|
|
434
83
|
|
|
435
|
-
-
|
|
436
|
-
- 属于工作流状态的信息必须写进 `status.json`,不要只留在聊天上下文里
|
|
437
|
-
- 不要覆盖用户写的 todo 内容
|
|
438
|
-
- 优先相信仓库现实,其次才是参考文件
|
|
439
|
-
- 参考文件是强上下文,但不是不可质疑的真理
|
|
84
|
+
OpenSpec-specific internal commands are listed in [references/se-commands.md](references/se-commands.md).
|
|
@@ -1151,6 +1151,54 @@ def file_sha256(path: Path) -> str:
|
|
|
1151
1151
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
1152
1152
|
|
|
1153
1153
|
|
|
1154
|
+
def markdown_headings(text: str, limit: int = 16) -> list[str]:
|
|
1155
|
+
headings: list[str] = []
|
|
1156
|
+
for line in text.splitlines():
|
|
1157
|
+
stripped = line.strip()
|
|
1158
|
+
if stripped.startswith("#"):
|
|
1159
|
+
headings.append(stripped[:160])
|
|
1160
|
+
if len(headings) >= limit:
|
|
1161
|
+
break
|
|
1162
|
+
return headings
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def compact_text_excerpt(text: str, keywords: list[str] | None = None, max_chars: int = 6000) -> str:
|
|
1166
|
+
normalized = text.strip()
|
|
1167
|
+
if len(normalized) <= max_chars:
|
|
1168
|
+
return normalized
|
|
1169
|
+
|
|
1170
|
+
keywords = [item.lower() for item in (keywords or []) if item]
|
|
1171
|
+
lines = normalized.splitlines()
|
|
1172
|
+
selected: list[str] = []
|
|
1173
|
+
selected.extend(lines[:60])
|
|
1174
|
+
for index, line in enumerate(lines):
|
|
1175
|
+
lowered = line.lower()
|
|
1176
|
+
if keywords and not any(keyword in lowered for keyword in keywords):
|
|
1177
|
+
continue
|
|
1178
|
+
start = max(0, index - 2)
|
|
1179
|
+
end = min(len(lines), index + 3)
|
|
1180
|
+
selected.append("")
|
|
1181
|
+
selected.extend(lines[start:end])
|
|
1182
|
+
if len("\n".join(selected)) >= max_chars:
|
|
1183
|
+
break
|
|
1184
|
+
excerpt = "\n".join(selected).strip()
|
|
1185
|
+
if len(excerpt) > max_chars:
|
|
1186
|
+
excerpt = excerpt[:max_chars].rstrip()
|
|
1187
|
+
return excerpt + "\n\n...[已摘要,按需读取原文件全文]..."
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def summarize_markdown_file(path: Path, keywords: list[str] | None = None, max_excerpt_chars: int = 6000) -> dict[str, Any]:
|
|
1191
|
+
text = read_text(path)
|
|
1192
|
+
return {
|
|
1193
|
+
"path": str(path.resolve()),
|
|
1194
|
+
"bytes": path.stat().st_size if path.exists() else 0,
|
|
1195
|
+
"sha256": file_sha256(path),
|
|
1196
|
+
"headings": markdown_headings(text),
|
|
1197
|
+
"excerpt": compact_text_excerpt(text, keywords=keywords, max_chars=max_excerpt_chars),
|
|
1198
|
+
"truncated": len(text.strip()) > max_excerpt_chars,
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
|
|
1154
1202
|
def write_text(path: Path, content: str) -> None:
|
|
1155
1203
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1156
1204
|
path.write_text(content, encoding="utf-8")
|
|
@@ -1627,6 +1675,8 @@ def build_openspec_bridge_context(config: dict[str, Any], tasks_text: str) -> di
|
|
|
1627
1675
|
"spec_reference_files": specs,
|
|
1628
1676
|
"proposal_headings": extract_headings(proposal_text),
|
|
1629
1677
|
"design_headings": extract_headings(design_text),
|
|
1678
|
+
"proposal_excerpt": compact_text_excerpt(proposal_text, max_chars=3000) if proposal_text else "",
|
|
1679
|
+
"design_excerpt": compact_text_excerpt(design_text, max_chars=3000) if design_text else "",
|
|
1630
1680
|
"business_constraints": [
|
|
1631
1681
|
f"需求来源是 OpenSpec change:{openspec.get('change_name', '')}",
|
|
1632
1682
|
"优先以 proposal.md、design.md 和 specs/ 下的 delta specs 作为业务边界",
|
|
@@ -64,6 +64,22 @@ def diff_summary(workspace: Path) -> list[str]:
|
|
|
64
64
|
return lines
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def compact_lines(lines: list[str], limit: int = 80) -> list[str]:
|
|
68
|
+
if len(lines) <= limit:
|
|
69
|
+
return lines
|
|
70
|
+
head = max(1, limit // 2)
|
|
71
|
+
tail = max(1, limit - head)
|
|
72
|
+
return lines[:head] + [f"... 已省略 {len(lines) - limit} 行,详见 git diff。"] + lines[-tail:]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def read_plan_context(config: dict, session_meta: dict) -> dict:
|
|
76
|
+
summary = read_json(data_artifact_path(config, "plan-summary.json", session_meta), {})
|
|
77
|
+
if summary:
|
|
78
|
+
summary["compact"] = True
|
|
79
|
+
return summary
|
|
80
|
+
return read_json(data_artifact_path(config, "plan.json", session_meta), {})
|
|
81
|
+
|
|
82
|
+
|
|
67
83
|
def find_tests(files: list[str]) -> list[str]:
|
|
68
84
|
return [item for item in files if item.endswith("Test.java") or "/test/" in item]
|
|
69
85
|
|
|
@@ -217,7 +233,7 @@ def main() -> None:
|
|
|
217
233
|
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
218
234
|
config = load_workspace_config(workspace)
|
|
219
235
|
session_meta = current_session_meta(config)
|
|
220
|
-
plan =
|
|
236
|
+
plan = read_plan_context(config, session_meta)
|
|
221
237
|
codebases = planned_codebases(config, session_meta)
|
|
222
238
|
target_plans = plan.get("target_codebases", [])
|
|
223
239
|
target_map = {str(item.get("path")): item for item in target_plans}
|
|
@@ -230,7 +246,7 @@ def main() -> None:
|
|
|
230
246
|
plan_files = target_plan.get("impacted_files", [])
|
|
231
247
|
if is_git_repo(codebase):
|
|
232
248
|
changed = changed_files(codebase)
|
|
233
|
-
summary = diff_summary(codebase)
|
|
249
|
+
summary = compact_lines(diff_summary(codebase))
|
|
234
250
|
repo_mode = "git"
|
|
235
251
|
else:
|
|
236
252
|
changed = plan_files
|
|
@@ -139,7 +139,7 @@ def main() -> None:
|
|
|
139
139
|
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
140
140
|
config = load_workspace_config(workspace)
|
|
141
141
|
session_meta = current_session_meta(config)
|
|
142
|
-
plan = read_json(data_artifact_path(config, "plan.json", session_meta), {})
|
|
142
|
+
plan = read_json(data_artifact_path(config, "plan-summary.json", session_meta), {}) or read_json(data_artifact_path(config, "plan.json", session_meta), {})
|
|
143
143
|
|
|
144
144
|
sections: list[dict] = []
|
|
145
145
|
for codebase in planned_codebases(config, session_meta):
|
|
@@ -266,6 +266,29 @@ def build_plan_markdown(plan: dict) -> str:
|
|
|
266
266
|
return "\n".join(lines)
|
|
267
267
|
|
|
268
268
|
|
|
269
|
+
def build_plan_summary(plan: dict) -> dict[str, object]:
|
|
270
|
+
return {
|
|
271
|
+
"session_id": plan.get("session_id", ""),
|
|
272
|
+
"source": "run-workflow.py plan-summary",
|
|
273
|
+
"schema_version": 1,
|
|
274
|
+
"requirement_summary": plan.get("requirement_summary", ""),
|
|
275
|
+
"target_codebases": [
|
|
276
|
+
{
|
|
277
|
+
"name": item.get("name", ""),
|
|
278
|
+
"path": item.get("path", ""),
|
|
279
|
+
"verify_command": (item.get("detected_project") or {}).get("verify_command", ""),
|
|
280
|
+
}
|
|
281
|
+
for item in plan.get("target_codebases", [])
|
|
282
|
+
],
|
|
283
|
+
"impacted_files": plan.get("impacted_files", [])[:80],
|
|
284
|
+
"change_steps": plan.get("change_steps", [])[:20],
|
|
285
|
+
"acceptance_criteria": plan.get("acceptance_criteria", [])[:40],
|
|
286
|
+
"test_plan": plan.get("test_plan", [])[:20],
|
|
287
|
+
"risks": plan.get("risks", [])[:10],
|
|
288
|
+
"unknowns": plan.get("unknowns", [])[:10],
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
269
292
|
def collect_target_plan_data(config: dict, codebases: list[Path]) -> tuple[list[dict], list[str], list[str]]:
|
|
270
293
|
targets: list[dict] = []
|
|
271
294
|
all_impacted_files: list[str] = []
|
|
@@ -404,6 +427,7 @@ def main() -> None:
|
|
|
404
427
|
}
|
|
405
428
|
|
|
406
429
|
write_managed_json(config, data_artifact_path(config, "plan.json", session_meta), plan)
|
|
430
|
+
write_managed_json(config, data_artifact_path(config, "plan-summary.json", session_meta), build_plan_summary(plan))
|
|
407
431
|
write_managed_text(config, report_artifact_path(config, "plan.md", session_meta), build_plan_markdown(plan) + "\n")
|
|
408
432
|
|
|
409
433
|
status = ensure_status(config, session_meta, read_json(data_artifact_path(config, "status.json", session_meta), {}))
|
|
@@ -12,9 +12,9 @@ from common import (
|
|
|
12
12
|
openspec_cli_available,
|
|
13
13
|
openspec_writeback_dir,
|
|
14
14
|
read_demand_source,
|
|
15
|
-
read_text,
|
|
16
15
|
run_openspec_cli,
|
|
17
16
|
select_openspec_change,
|
|
17
|
+
summarize_markdown_file,
|
|
18
18
|
update_se_state,
|
|
19
19
|
validate_openspec_change_name,
|
|
20
20
|
workflow_source,
|
|
@@ -51,11 +51,9 @@ def main() -> None:
|
|
|
51
51
|
raise SystemExit(str(error))
|
|
52
52
|
demand_file = str(demand_source.get("source", "")).strip()
|
|
53
53
|
demand_text = str(demand_source.get("content", "")).strip()
|
|
54
|
+
demand_keywords = [item for item in [change_name, *change_name.replace("-", " ").split()] if item]
|
|
54
55
|
reference_contexts = [
|
|
55
|
-
|
|
56
|
-
"path": item,
|
|
57
|
-
"content": read_text(Path(item)),
|
|
58
|
-
}
|
|
56
|
+
summarize_markdown_file(Path(item), keywords=demand_keywords)
|
|
59
57
|
for item in existing_reference_files(config)
|
|
60
58
|
]
|
|
61
59
|
|
|
@@ -92,15 +90,16 @@ def main() -> None:
|
|
|
92
90
|
"demand_file": demand_file,
|
|
93
91
|
"demand_source_type": demand_source.get("source_type", ""),
|
|
94
92
|
"demand_fetch_command": demand_source.get("command", []),
|
|
95
|
-
"
|
|
93
|
+
"demand_text_available_at": demand_file,
|
|
94
|
+
"demand_excerpt": demand_text[:12000] + ("\n\n...[已摘要,按需读取 demand_file 全文]..." if len(demand_text) > 12000 else ""),
|
|
96
95
|
"reference_files": reference_contexts,
|
|
97
96
|
"openspec_cli_available": openspec_cli_available(),
|
|
98
97
|
"commands": commands,
|
|
99
|
-
"next_action": "Use
|
|
98
|
+
"next_action": "Use demand_excerpt, reference file summaries, and OpenSpec instructions to create or update proposal.md, design.md, tasks.md, and specs/. Read full source files only when necessary.",
|
|
100
99
|
"workflow_phase_after_completion": "proposed",
|
|
101
100
|
"allowed_next_after_completion": ["/se:bridge"],
|
|
102
101
|
"forbidden_next_after_completion": ["/se:plan", "/se:apply"],
|
|
103
|
-
"final_reply_constraint": "
|
|
102
|
+
"final_reply_constraint": "代码未修改。下一步只能执行 /se:bridge。",
|
|
104
103
|
}
|
|
105
104
|
write_managed_json(config, writeback_dir / "propose-input.json", payload)
|
|
106
105
|
write_managed_text(
|
|
@@ -119,7 +118,7 @@ def main() -> None:
|
|
|
119
118
|
"",
|
|
120
119
|
"## Demand",
|
|
121
120
|
"",
|
|
122
|
-
|
|
121
|
+
payload["demand_excerpt"] or "未配置或未找到 demand_file。",
|
|
123
122
|
"",
|
|
124
123
|
"## Reference Files",
|
|
125
124
|
"",
|
|
@@ -129,7 +128,17 @@ def main() -> None:
|
|
|
129
128
|
[
|
|
130
129
|
f"### {item['path']}",
|
|
131
130
|
"",
|
|
132
|
-
|
|
131
|
+
f"- bytes: {item.get('bytes', 0)}",
|
|
132
|
+
f"- sha256: {item.get('sha256', '')}",
|
|
133
|
+
f"- truncated: {item.get('truncated', False)}",
|
|
134
|
+
"",
|
|
135
|
+
"#### Headings",
|
|
136
|
+
"",
|
|
137
|
+
"\n".join(f"- {heading}" for heading in item.get("headings", [])) or "暂无标题",
|
|
138
|
+
"",
|
|
139
|
+
"#### Excerpt",
|
|
140
|
+
"",
|
|
141
|
+
item.get("excerpt", "") or "文件为空或无法读取。",
|
|
133
142
|
]
|
|
134
143
|
)
|
|
135
144
|
for item in reference_contexts
|
|
@@ -163,7 +172,7 @@ def main() -> None:
|
|
|
163
172
|
print("workflow_phase_after_completion=proposed")
|
|
164
173
|
print("allowed_next_after_completion=/se:bridge")
|
|
165
174
|
print("forbidden_next_after_completion=/se:plan,/se:apply")
|
|
166
|
-
print("final_reply_must
|
|
175
|
+
print("final_reply_must=代码未修改。下一步只能执行 /se:bridge。")
|
|
167
176
|
|
|
168
177
|
|
|
169
178
|
if __name__ == "__main__":
|
|
@@ -28,6 +28,29 @@ from common import (
|
|
|
28
28
|
workspace_root,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
MAX_VERIFY_LOG_CHARS = 12000
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def compact_command_output(text: str, max_chars: int = MAX_VERIFY_LOG_CHARS) -> str:
|
|
36
|
+
if len(text) <= max_chars:
|
|
37
|
+
return text
|
|
38
|
+
head = max_chars // 2
|
|
39
|
+
tail = max_chars - head
|
|
40
|
+
omitted = len(text) - max_chars
|
|
41
|
+
return text[:head].rstrip() + f"\n\n...[已省略 {omitted} 字符]...\n\n" + text[-tail:].lstrip()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def compact_command_records(value):
|
|
45
|
+
if isinstance(value, dict):
|
|
46
|
+
return {
|
|
47
|
+
key: compact_command_output(item) if key in ("stdout", "stderr", "output", "logs") and isinstance(item, str) else compact_command_records(item)
|
|
48
|
+
for key, item in value.items()
|
|
49
|
+
}
|
|
50
|
+
if isinstance(value, list):
|
|
51
|
+
return [compact_command_records(item) for item in value]
|
|
52
|
+
return value
|
|
53
|
+
|
|
31
54
|
VERIFY_BLOCKERS = {"未识别到验证命令", "验证失败", "验证执行超时"}
|
|
32
55
|
|
|
33
56
|
|
|
@@ -248,7 +271,7 @@ def main() -> None:
|
|
|
248
271
|
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
249
272
|
config = load_workspace_config(workspace)
|
|
250
273
|
session_meta = current_session_meta(config)
|
|
251
|
-
plan = read_json(data_artifact_path(config, "plan.json", session_meta), {})
|
|
274
|
+
plan = read_json(data_artifact_path(config, "plan-summary.json", session_meta), {}) or read_json(data_artifact_path(config, "plan.json", session_meta), {})
|
|
252
275
|
status_path = data_artifact_path(config, "status.json", session_meta)
|
|
253
276
|
existing_status = ensure_status(config, session_meta, read_json(status_path, {}))
|
|
254
277
|
if (
|
|
@@ -319,8 +342,8 @@ def main() -> None:
|
|
|
319
342
|
"result": repo_result,
|
|
320
343
|
"exit_code": str(result.returncode),
|
|
321
344
|
"duration": repo_duration,
|
|
322
|
-
"stdout": result.stdout,
|
|
323
|
-
"stderr": result.stderr,
|
|
345
|
+
"stdout": compact_command_output(result.stdout),
|
|
346
|
+
"stderr": compact_command_output(result.stderr),
|
|
324
347
|
}
|
|
325
348
|
)
|
|
326
349
|
if repo_result != "通过":
|
|
@@ -363,8 +386,8 @@ def main() -> None:
|
|
|
363
386
|
"result": "超时",
|
|
364
387
|
"exit_code": "timeout",
|
|
365
388
|
"duration": duration,
|
|
366
|
-
"stdout": error.stdout or "",
|
|
367
|
-
"stderr": error.stderr or "",
|
|
389
|
+
"stdout": compact_command_output(error.stdout or ""),
|
|
390
|
+
"stderr": compact_command_output(error.stderr or ""),
|
|
368
391
|
}
|
|
369
392
|
)
|
|
370
393
|
status_for_duration = ensure_status(config, session_meta, read_json(status_path, {}))
|
|
@@ -40,19 +40,19 @@ SE_ROUTE_REPLY_CONSTRAINTS: dict[str, dict[str, str]] = {
|
|
|
40
40
|
"phase": "proposed",
|
|
41
41
|
"allowed_next": "/se:bridge",
|
|
42
42
|
"forbidden_next": "/se:plan,/se:apply",
|
|
43
|
-
"final_reply_must": "
|
|
43
|
+
"final_reply_must": "代码未修改。下一步只能执行 /se:bridge。",
|
|
44
44
|
},
|
|
45
45
|
"/se:bridge": {
|
|
46
46
|
"phase": "bridged",
|
|
47
47
|
"allowed_next": "人工审核 todo.md 后 /se:apply",
|
|
48
48
|
"forbidden_next": "自动执行 /se:plan,自动执行 /se:apply,代码实现",
|
|
49
|
-
"final_reply_must": "桥接 todo
|
|
49
|
+
"final_reply_must": "桥接 todo 已生成。请审核 todo.md,审核通过后发送 /se:apply。",
|
|
50
50
|
},
|
|
51
51
|
"/se:plan": {
|
|
52
52
|
"phase": "planned",
|
|
53
53
|
"allowed_next": "/se:apply",
|
|
54
54
|
"forbidden_next": "代码实现,review,verify",
|
|
55
|
-
"final_reply_must": "
|
|
55
|
+
"final_reply_must": "计划已生成。下一步执行 /se:apply。",
|
|
56
56
|
},
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -223,6 +223,14 @@ def build_markdown(payload: dict) -> str:
|
|
|
223
223
|
return "\n".join(lines)
|
|
224
224
|
|
|
225
225
|
|
|
226
|
+
def read_plan_context(config: dict, session_meta: dict) -> dict:
|
|
227
|
+
summary = read_json(data_artifact_path(config, "plan-summary.json", session_meta), {})
|
|
228
|
+
if summary:
|
|
229
|
+
summary["compact"] = True
|
|
230
|
+
return summary
|
|
231
|
+
return read_json(data_artifact_path(config, "plan.json", session_meta), {})
|
|
232
|
+
|
|
233
|
+
|
|
226
234
|
def main() -> None:
|
|
227
235
|
parser = argparse.ArgumentParser(description="把当前会话执行结果回写到 OpenSpec change 目录。")
|
|
228
236
|
parser.add_argument("--workspace", help="工作空间路径,默认读取当前目录")
|
|
@@ -234,7 +242,7 @@ def main() -> None:
|
|
|
234
242
|
raise SystemExit("当前 workspace.yml 未启用 OpenSpec 模式,无需执行 writeback-openspec。")
|
|
235
243
|
|
|
236
244
|
session_meta = current_session_meta(config)
|
|
237
|
-
plan =
|
|
245
|
+
plan = read_plan_context(config, session_meta)
|
|
238
246
|
review = read_json(data_artifact_path(config, "review.json", session_meta), {})
|
|
239
247
|
verify = read_json(data_artifact_path(config, "verify.json", session_meta), {})
|
|
240
248
|
status = read_json(data_artifact_path(config, "status.json", session_meta), {})
|