super-engineer-workflow 0.1.5 → 0.1.7
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 +17 -0
- package/README.md +6 -10
- package/docs/se/345/221/275/344/273/244/345/215/217/350/256/256.md +42 -297
- package/docs//350/267/250/345/271/263/345/217/260/346/224/257/346/214/201/347/237/251/351/230/265.md +34 -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 +3 -2
- package/package.json +1 -1
- package/scripts/se-cli.py +146 -2
- package/scripts/se-e2e-test.py +168 -0
- package/scripts/se-setup.py +13 -2
- package/super-engineer-workflow/SKILL.md +20 -5
- package/super-engineer-workflow/references/commands/apply.md +28 -0
- package/super-engineer-workflow/references/commands/archive.md +23 -0
- package/super-engineer-workflow/references/commands/bridge.md +25 -0
- package/super-engineer-workflow/references/commands/common.md +32 -0
- package/super-engineer-workflow/references/commands/plan.md +25 -0
- package/super-engineer-workflow/references/commands/propose.md +25 -0
- package/super-engineer-workflow/references/commands/review.md +22 -0
- package/super-engineer-workflow/references/commands/status.md +22 -0
- package/super-engineer-workflow/references/commands/verify.md +23 -0
- package/super-engineer-workflow/scripts/bootstrap-openspec.py +3 -1
- package/super-engineer-workflow/scripts/common.py +143 -7
- package/super-engineer-workflow/scripts/generate-discovery.py +44 -0
- package/super-engineer-workflow/scripts/generate-smart-plan.py +12 -2
- package/super-engineer-workflow/scripts/prepare-archive-openspec.py +4 -0
- package/super-engineer-workflow/scripts/run-workflow.py +108 -33
- package/super-engineer-workflow/scripts/writeback-openspec.py +5 -1
- package/super-engineer-workflow/references/se-commands.md +0 -586
package/scripts/se-cli.py
CHANGED
|
@@ -5,6 +5,7 @@ import argparse
|
|
|
5
5
|
import importlib.util
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import platform
|
|
8
9
|
import shutil
|
|
9
10
|
import subprocess
|
|
10
11
|
import sys
|
|
@@ -28,6 +29,65 @@ WORKSPACE_TEMPLATES: dict[str, str] = {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
SE_COMMANDS: dict[str, str] = {
|
|
33
|
+
"propose.md": """---
|
|
34
|
+
description: Super Engineer:生成或完善 OpenSpec change
|
|
35
|
+
argument-hint: <change-name>
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
请使用 super-engineer-workflow skill 执行:`/se:propose $ARGUMENTS`。
|
|
39
|
+
如果 `$ARGUMENTS` 为空,请先询问用户提供 OpenSpec change 名称。
|
|
40
|
+
""",
|
|
41
|
+
"propose-fix.md": """---
|
|
42
|
+
description: Super Engineer:需求补充后修正当前 OpenSpec change
|
|
43
|
+
argument-hint: <change-name>
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
请使用 super-engineer-workflow skill 执行:`/se:propose $ARGUMENTS`。
|
|
47
|
+
当前需求有补充,请修正当前 OpenSpec change;不要创建新的 change,不要改代码。
|
|
48
|
+
""",
|
|
49
|
+
"bridge.md": """---
|
|
50
|
+
description: Super Engineer:生成桥接 todo
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
请使用 super-engineer-workflow skill 执行:`/se:bridge`。
|
|
54
|
+
生成桥接 todo 并总结待审核项,不要改代码,不要进入实现。
|
|
55
|
+
""",
|
|
56
|
+
"plan.md": """---
|
|
57
|
+
description: Super Engineer:只生成实施计划
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
请使用 super-engineer-workflow skill 执行:`/se:plan`。
|
|
61
|
+
只生成计划,不要改代码。
|
|
62
|
+
""",
|
|
63
|
+
"apply.md": """---
|
|
64
|
+
description: Super Engineer:审核 todo 后进入交付阶段
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
请使用 super-engineer-workflow skill 执行:`/se:apply`。
|
|
68
|
+
我已审核当前桥接 todo,可以进入交付阶段。
|
|
69
|
+
""",
|
|
70
|
+
"archive-check.md": """---
|
|
71
|
+
description: Super Engineer:检查 OpenSpec 归档条件
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
请使用 super-engineer-workflow skill 执行:`/se:archive-check`。
|
|
75
|
+
""",
|
|
76
|
+
"archive.md": """---
|
|
77
|
+
description: Super Engineer:归档 OpenSpec change
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
请使用 super-engineer-workflow skill 执行:`/se:archive`。
|
|
81
|
+
""",
|
|
82
|
+
"status.md": """---
|
|
83
|
+
description: Super Engineer:查看工作流状态
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
请使用 super-engineer-workflow skill 执行:`/se:status`。
|
|
87
|
+
""",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
31
91
|
def main() -> None:
|
|
32
92
|
if len(sys.argv) == 1:
|
|
33
93
|
run_setup([])
|
|
@@ -56,6 +116,7 @@ def main() -> None:
|
|
|
56
116
|
doctor_parser = subparsers.add_parser("doctor", help="检查本机环境和工作区配置。")
|
|
57
117
|
doctor_parser.add_argument("--workspace", default=".", help="工作区目录,默认当前目录。")
|
|
58
118
|
doctor_parser.add_argument("--json", action="store_true", help="输出 JSON。")
|
|
119
|
+
doctor_parser.add_argument("--fix", action="store_true", help="尽量自动补齐 skill 和快捷命令。")
|
|
59
120
|
|
|
60
121
|
migrate_parser = subparsers.add_parser("migrate", help="补齐旧工作区缺失的 workspace.yml 配置项。")
|
|
61
122
|
migrate_parser.add_argument("--workspace", default=".", help="工作区目录,默认当前目录。")
|
|
@@ -63,6 +124,17 @@ def main() -> None:
|
|
|
63
124
|
|
|
64
125
|
subparsers.add_parser("templates", help="列出内置 workspace.yml 模板。")
|
|
65
126
|
|
|
127
|
+
commands_parser = subparsers.add_parser("commands", help="安装 AI 编码工具快捷命令模板。")
|
|
128
|
+
commands_subparsers = commands_parser.add_subparsers(dest="commands_action")
|
|
129
|
+
commands_install = commands_subparsers.add_parser("install", help="安装 /se:* 快捷命令模板。")
|
|
130
|
+
commands_install.add_argument("--workspace", default=".", help="工作区目录,默认当前目录。")
|
|
131
|
+
commands_install.add_argument(
|
|
132
|
+
"--target",
|
|
133
|
+
choices=["claude", "codex", "cursor", "trae", "kimi", "all"],
|
|
134
|
+
default="claude",
|
|
135
|
+
help="目标 AI 编码工具。",
|
|
136
|
+
)
|
|
137
|
+
|
|
66
138
|
template_parser = subparsers.add_parser("template", help="查看或复制内置 workspace.yml 模板。")
|
|
67
139
|
template_subparsers = template_parser.add_subparsers(dest="template_action")
|
|
68
140
|
template_show = template_subparsers.add_parser("show", help="打印指定模板内容。")
|
|
@@ -87,7 +159,7 @@ def main() -> None:
|
|
|
87
159
|
install_targets(args.target, force=True)
|
|
88
160
|
return
|
|
89
161
|
if args.command == "doctor":
|
|
90
|
-
exit_code = doctor(Path(args.workspace).expanduser().resolve(), output_json=args.json)
|
|
162
|
+
exit_code = doctor(Path(args.workspace).expanduser().resolve(), output_json=args.json, fix=args.fix)
|
|
91
163
|
raise SystemExit(exit_code)
|
|
92
164
|
if args.command == "migrate":
|
|
93
165
|
exit_code = migrate(Path(args.workspace).expanduser().resolve(), dry_run=args.dry_run)
|
|
@@ -95,6 +167,12 @@ def main() -> None:
|
|
|
95
167
|
if args.command == "templates":
|
|
96
168
|
list_templates()
|
|
97
169
|
return
|
|
170
|
+
if args.command == "commands":
|
|
171
|
+
if args.commands_action == "install":
|
|
172
|
+
install_commands(Path(args.workspace).expanduser().resolve(), args.target)
|
|
173
|
+
return
|
|
174
|
+
commands_parser.print_help()
|
|
175
|
+
return
|
|
98
176
|
if args.command == "template":
|
|
99
177
|
if args.template_action == "show":
|
|
100
178
|
show_template(args.name)
|
|
@@ -196,14 +274,23 @@ def copy_template(name: str, workspace: Path, demand_name: str, code_path: str,
|
|
|
196
274
|
print(f"✓ 已写入模板:{target}")
|
|
197
275
|
|
|
198
276
|
|
|
199
|
-
def doctor(workspace: Path, output_json: bool) -> int:
|
|
277
|
+
def doctor(workspace: Path, output_json: bool, fix: bool = False) -> int:
|
|
278
|
+
if fix:
|
|
279
|
+
install_targets("both", force=True)
|
|
280
|
+
install_commands(workspace, "all")
|
|
281
|
+
|
|
200
282
|
checks: list[dict[str, str]] = []
|
|
283
|
+
add_check(checks, "platform", "ok", f"{platform.system()} {platform.release()}")
|
|
201
284
|
add_check(checks, "python", "ok", sys.version.split()[0])
|
|
285
|
+
add_check(checks, "node", "ok" if shutil.which("node") else "fail", shutil.which("node") or "未安装")
|
|
286
|
+
add_check(checks, "npm", "ok" if shutil.which("npm") else "fail", shutil.which("npm") or "未安装")
|
|
202
287
|
add_check(checks, "skill_source", "ok" if SKILL_DIR.exists() else "fail", str(SKILL_DIR))
|
|
203
288
|
add_check(checks, "codex_skill", "ok" if skill_target("codex").exists() else "warn", str(skill_target("codex")))
|
|
204
289
|
add_check(checks, "claude_skill", "ok" if skill_target("claude").exists() else "warn", str(skill_target("claude")))
|
|
205
290
|
add_check(checks, "openspec_cli", "ok" if shutil.which("openspec") else "warn", shutil.which("openspec") or "未安装")
|
|
206
291
|
add_check(checks, "workspace", "ok" if workspace.exists() else "fail", str(workspace))
|
|
292
|
+
add_check(checks, "workspace.commands.se", "ok" if workspace_commands_ready(workspace) else "warn", str(workspace / ".claude" / "commands" / "se"))
|
|
293
|
+
add_check(checks, "workspace.openspec.root", "ok" if (workspace / "openspec").exists() else "warn", str(workspace / "openspec"))
|
|
207
294
|
|
|
208
295
|
workspace_yml = workspace / "workspace.yml"
|
|
209
296
|
add_check(checks, "workspace_yml", "ok" if workspace_yml.exists() else "fail", str(workspace_yml))
|
|
@@ -223,10 +310,67 @@ def doctor(workspace: Path, output_json: bool) -> int:
|
|
|
223
310
|
for check in checks:
|
|
224
311
|
mark = {"ok": "✓", "warn": "!", "fail": "✗"}[check["status"]]
|
|
225
312
|
print(f"{mark} {check['name']}: {check['message']}")
|
|
313
|
+
suggestions = doctor_suggestions(checks)
|
|
314
|
+
if suggestions:
|
|
315
|
+
print("\n建议:")
|
|
316
|
+
for item in suggestions:
|
|
317
|
+
print(f"- {item}")
|
|
226
318
|
|
|
227
319
|
return 1 if any(item["status"] == "fail" for item in checks) else 0
|
|
228
320
|
|
|
229
321
|
|
|
322
|
+
def workspace_commands_ready(workspace: Path) -> bool:
|
|
323
|
+
commands_dir = workspace / ".claude" / "commands" / "se"
|
|
324
|
+
required = ["propose.md", "bridge.md", "plan.md", "apply.md", "status.md"]
|
|
325
|
+
return all((commands_dir / name).exists() for name in required)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def ensure_workspace_commands(workspace: Path) -> None:
|
|
329
|
+
install_commands(workspace, "claude")
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def command_target_dirs(workspace: Path, target: str) -> list[tuple[str, Path]]:
|
|
333
|
+
home = Path.home()
|
|
334
|
+
mapping: dict[str, Path] = {
|
|
335
|
+
"claude": workspace / ".claude" / "commands" / "se",
|
|
336
|
+
"codex": Path(os.environ.get("CODEX_HOME", str(home / ".codex"))).expanduser() / "prompts",
|
|
337
|
+
"cursor": workspace / ".cursor" / "commands" / "se",
|
|
338
|
+
"trae": workspace / ".trae" / "commands" / "se",
|
|
339
|
+
"kimi": workspace / ".kimi" / "commands" / "se",
|
|
340
|
+
}
|
|
341
|
+
if target == "all":
|
|
342
|
+
return list(mapping.items())
|
|
343
|
+
return [(target, mapping[target])]
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def install_commands(workspace: Path, target: str) -> None:
|
|
347
|
+
workspace.mkdir(parents=True, exist_ok=True)
|
|
348
|
+
for name, directory in command_target_dirs(workspace, target):
|
|
349
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
350
|
+
for filename, content in SE_COMMANDS.items():
|
|
351
|
+
output_name = f"se-{filename}" if name == "codex" else filename
|
|
352
|
+
(directory / output_name).write_text(content, encoding="utf-8")
|
|
353
|
+
print(f"✓ 已补齐 {name} 快捷命令: {directory}")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def doctor_suggestions(checks: list[dict[str, str]]) -> list[str]:
|
|
357
|
+
by_name = {item["name"]: item for item in checks}
|
|
358
|
+
suggestions: list[str] = []
|
|
359
|
+
if by_name.get("codex_skill", {}).get("status") != "ok" or by_name.get("claude_skill", {}).get("status") != "ok":
|
|
360
|
+
suggestions.append("执行 `se sync --target both` 同步最新 skill。")
|
|
361
|
+
if by_name.get("workspace.commands.se", {}).get("status") != "ok":
|
|
362
|
+
suggestions.append("执行 `se doctor --fix` 补齐工作区 `.claude/commands/se/*` 快捷命令。")
|
|
363
|
+
if by_name.get("workspace_yml", {}).get("status") != "ok":
|
|
364
|
+
suggestions.append("执行 `se init` 初始化工作区,或用 `se template copy <模板名>` 生成 workspace.yml。")
|
|
365
|
+
if by_name.get("openspec_cli", {}).get("status") != "ok":
|
|
366
|
+
suggestions.append("OpenSpec 模式建议先安装并初始化 OpenSpec;todo 模式可忽略。")
|
|
367
|
+
if by_name.get("workspace.openspec.root", {}).get("status") != "ok":
|
|
368
|
+
suggestions.append("OpenSpec 模式请在工作区执行 OpenSpec 初始化;todo 模式可忽略。")
|
|
369
|
+
if by_name.get("node", {}).get("status") != "ok" or by_name.get("npm", {}).get("status") != "ok":
|
|
370
|
+
suggestions.append("请先安装 Node.js/npm。")
|
|
371
|
+
return suggestions
|
|
372
|
+
|
|
373
|
+
|
|
230
374
|
def add_check(checks: list[dict[str, str]], name: str, status: str, message: str) -> None:
|
|
231
375
|
checks.append({"name": name, "status": status, "message": message})
|
|
232
376
|
|
package/scripts/se-e2e-test.py
CHANGED
|
@@ -24,6 +24,7 @@ def main() -> None:
|
|
|
24
24
|
os.environ["USERPROFILE"] = str(home)
|
|
25
25
|
test_templates_cli(root)
|
|
26
26
|
test_openspec_state_and_bridge(root)
|
|
27
|
+
test_incomplete_plan_session_is_reused(root)
|
|
27
28
|
test_todo_auto_session_and_verify_compaction(root)
|
|
28
29
|
print("e2e_test=ok")
|
|
29
30
|
|
|
@@ -57,6 +58,22 @@ def test_templates_cli(root: Path) -> None:
|
|
|
57
58
|
if "workflow_source: todo" not in workspace_yml or "9-e2e-template" not in workspace_yml:
|
|
58
59
|
raise AssertionError("template copy did not render workspace.yml")
|
|
59
60
|
|
|
61
|
+
doctor_before = run(["node", str(CLI), "doctor", "--workspace", str(workspace)], check=False)
|
|
62
|
+
if "workspace.commands.se" not in doctor_before.output:
|
|
63
|
+
raise AssertionError("doctor should report workspace command status")
|
|
64
|
+
run(["node", str(CLI), "doctor", "--workspace", str(workspace), "--fix"], check=False)
|
|
65
|
+
if not (workspace / ".claude" / "commands" / "se" / "apply.md").exists():
|
|
66
|
+
raise AssertionError("doctor --fix should create se command templates")
|
|
67
|
+
run(["node", str(CLI), "commands", "install", "--workspace", str(workspace), "--target", "all"])
|
|
68
|
+
for command_path in [
|
|
69
|
+
workspace / ".cursor" / "commands" / "se" / "apply.md",
|
|
70
|
+
workspace / ".trae" / "commands" / "se" / "apply.md",
|
|
71
|
+
workspace / ".kimi" / "commands" / "se" / "apply.md",
|
|
72
|
+
home_prompt(root) / "se-apply.md",
|
|
73
|
+
]:
|
|
74
|
+
if not command_path.exists():
|
|
75
|
+
raise AssertionError(f"commands install all missing {command_path}")
|
|
76
|
+
|
|
60
77
|
|
|
61
78
|
def test_openspec_state_and_bridge(root: Path) -> None:
|
|
62
79
|
workspace = root / "openspec-workspace"
|
|
@@ -164,6 +181,57 @@ def test_openspec_state_and_bridge(root: Path) -> None:
|
|
|
164
181
|
if "demo-change" not in todo or "demo-service" not in todo:
|
|
165
182
|
raise AssertionError("bridged todo.md missing expected OpenSpec context")
|
|
166
183
|
|
|
184
|
+
route_check = run(
|
|
185
|
+
[
|
|
186
|
+
sys.executable,
|
|
187
|
+
str(RUN_WORKFLOW),
|
|
188
|
+
"route-check",
|
|
189
|
+
"--workspace",
|
|
190
|
+
str(workspace),
|
|
191
|
+
"--command-text",
|
|
192
|
+
"/se:apply",
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
route_payload = json.loads(route_check)
|
|
196
|
+
if not route_payload.get("allowed") or route_payload.get("run_command") != "apply":
|
|
197
|
+
raise AssertionError("route-check should return allowed JSON for bridged /se:apply")
|
|
198
|
+
|
|
199
|
+
(change_dir / "tasks.md").write_text(
|
|
200
|
+
"# Tasks\n\n"
|
|
201
|
+
"- [ ] 修改 demo-service controller 增加状态查询接口\n"
|
|
202
|
+
"- [ ] 补充验证,确认接口返回 ok\n"
|
|
203
|
+
"- [ ] 追加变更后必须重新桥接\n",
|
|
204
|
+
encoding="utf-8",
|
|
205
|
+
)
|
|
206
|
+
stale_apply = run(
|
|
207
|
+
[
|
|
208
|
+
sys.executable,
|
|
209
|
+
str(RUN_WORKFLOW),
|
|
210
|
+
"route-se",
|
|
211
|
+
"--workspace",
|
|
212
|
+
str(workspace),
|
|
213
|
+
"--command-text",
|
|
214
|
+
"/se:apply",
|
|
215
|
+
],
|
|
216
|
+
check=False,
|
|
217
|
+
)
|
|
218
|
+
if stale_apply.returncode == 0 or "tasks.md 已变化" not in stale_apply.output:
|
|
219
|
+
raise AssertionError("changed tasks.md should require /se:bridge before /se:apply")
|
|
220
|
+
|
|
221
|
+
bridge_again = run(
|
|
222
|
+
[
|
|
223
|
+
sys.executable,
|
|
224
|
+
str(RUN_WORKFLOW),
|
|
225
|
+
"route-se",
|
|
226
|
+
"--workspace",
|
|
227
|
+
str(workspace),
|
|
228
|
+
"--command-text",
|
|
229
|
+
"/se:bridge",
|
|
230
|
+
]
|
|
231
|
+
)
|
|
232
|
+
if "tasks_sha256=" not in bridge_again:
|
|
233
|
+
raise AssertionError("second /se:bridge should record tasks hash")
|
|
234
|
+
|
|
167
235
|
first_plan = run(
|
|
168
236
|
[
|
|
169
237
|
sys.executable,
|
|
@@ -179,6 +247,9 @@ def test_openspec_state_and_bridge(root: Path) -> None:
|
|
|
179
247
|
raise AssertionError("first /se:plan should create a session")
|
|
180
248
|
first_session = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
181
249
|
first_session_id = first_session["session_id"]
|
|
250
|
+
data_dir = Path(first_session["data_dir"])
|
|
251
|
+
if not (data_dir / "discovery-summary.json").exists():
|
|
252
|
+
raise AssertionError("plan should create discovery-summary.json")
|
|
182
253
|
|
|
183
254
|
second_plan = run(
|
|
184
255
|
[
|
|
@@ -210,6 +281,21 @@ def test_openspec_state_and_bridge(root: Path) -> None:
|
|
|
210
281
|
)
|
|
211
282
|
if "apply_phase=implementing" not in apply_output:
|
|
212
283
|
raise AssertionError("/se:apply should enter implementing after planned session")
|
|
284
|
+
apply_json_output = run(
|
|
285
|
+
[
|
|
286
|
+
sys.executable,
|
|
287
|
+
str(RUN_WORKFLOW),
|
|
288
|
+
"route-se",
|
|
289
|
+
"--workspace",
|
|
290
|
+
str(workspace),
|
|
291
|
+
"--command-text",
|
|
292
|
+
"/se:apply",
|
|
293
|
+
"--json",
|
|
294
|
+
],
|
|
295
|
+
check=False,
|
|
296
|
+
)
|
|
297
|
+
if "route_result_json_begin" not in apply_json_output.output and "当前状态不允许进入实现" not in apply_json_output.output:
|
|
298
|
+
raise AssertionError("route-se --json should emit JSON summary or a state guard")
|
|
213
299
|
blocked_plan = run(
|
|
214
300
|
[
|
|
215
301
|
sys.executable,
|
|
@@ -323,6 +409,84 @@ def test_todo_auto_session_and_verify_compaction(root: Path) -> None:
|
|
|
323
409
|
raise AssertionError("notification should be marked skipped when no provider is configured")
|
|
324
410
|
|
|
325
411
|
|
|
412
|
+
def test_incomplete_plan_session_is_reused(root: Path) -> None:
|
|
413
|
+
workspace = root / "incomplete-plan-workspace"
|
|
414
|
+
code = root / "broken-code" / "not-a-project"
|
|
415
|
+
demand_dir = workspace / "superengineer" / "10-broken"
|
|
416
|
+
demand_dir.mkdir(parents=True)
|
|
417
|
+
code.mkdir(parents=True)
|
|
418
|
+
(workspace / "docs").mkdir(parents=True)
|
|
419
|
+
(workspace / "workspace.yml").write_text(
|
|
420
|
+
"\n".join(
|
|
421
|
+
[
|
|
422
|
+
"version: 1",
|
|
423
|
+
"mode: auto",
|
|
424
|
+
"workflow_source: todo",
|
|
425
|
+
"vars:",
|
|
426
|
+
" demand_name: 10-broken",
|
|
427
|
+
"todo_file: superengineer/${demand_name}/todo.md",
|
|
428
|
+
"reference_files: []",
|
|
429
|
+
"code_path: ../broken-code/not-a-project",
|
|
430
|
+
"output_dir: superengineer/${demand_name}/output",
|
|
431
|
+
"",
|
|
432
|
+
]
|
|
433
|
+
),
|
|
434
|
+
encoding="utf-8",
|
|
435
|
+
)
|
|
436
|
+
(demand_dir / "todo.md").write_text(
|
|
437
|
+
"# 限制条件\n"
|
|
438
|
+
"- 修改的服务是 broken-service\n\n"
|
|
439
|
+
"# 待办事项\n\n"
|
|
440
|
+
"- [ ] 增加一个测试接口\n",
|
|
441
|
+
encoding="utf-8",
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
first_apply = run(
|
|
445
|
+
[
|
|
446
|
+
sys.executable,
|
|
447
|
+
str(RUN_WORKFLOW),
|
|
448
|
+
"route-se",
|
|
449
|
+
"--workspace",
|
|
450
|
+
str(workspace),
|
|
451
|
+
"--command-text",
|
|
452
|
+
"/se:apply",
|
|
453
|
+
],
|
|
454
|
+
check=False,
|
|
455
|
+
)
|
|
456
|
+
if first_apply.returncode == 0 or "未找到可识别的项目目录" not in first_apply.output:
|
|
457
|
+
raise AssertionError("first /se:apply should fail before plan is generated")
|
|
458
|
+
current = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
459
|
+
first_session_id = current["session_id"]
|
|
460
|
+
sessions_dir = workspace / ".super-engineer" / "sessions"
|
|
461
|
+
output_dir = workspace / "superengineer" / "10-broken" / "output"
|
|
462
|
+
if len(list(sessions_dir.iterdir())) != 1:
|
|
463
|
+
raise AssertionError("first failed /se:apply should create exactly one reusable data session")
|
|
464
|
+
if output_dir.exists() and list(output_dir.iterdir()):
|
|
465
|
+
raise AssertionError("failed plan should not create an empty output report session")
|
|
466
|
+
|
|
467
|
+
second_apply = run(
|
|
468
|
+
[
|
|
469
|
+
sys.executable,
|
|
470
|
+
str(RUN_WORKFLOW),
|
|
471
|
+
"route-se",
|
|
472
|
+
"--workspace",
|
|
473
|
+
str(workspace),
|
|
474
|
+
"--command-text",
|
|
475
|
+
"/se:apply",
|
|
476
|
+
],
|
|
477
|
+
check=False,
|
|
478
|
+
)
|
|
479
|
+
if second_apply.returncode == 0 or "session_action=reused_incomplete" not in second_apply.output:
|
|
480
|
+
raise AssertionError("second /se:apply should reuse the incomplete planning session")
|
|
481
|
+
current = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
482
|
+
if current["session_id"] != first_session_id:
|
|
483
|
+
raise AssertionError("incomplete planning session should remain current")
|
|
484
|
+
if len(list(sessions_dir.iterdir())) != 1:
|
|
485
|
+
raise AssertionError("repeated failed /se:apply should not create another data session")
|
|
486
|
+
if output_dir.exists() and list(output_dir.iterdir()):
|
|
487
|
+
raise AssertionError("repeated failed /se:apply should not create output report sessions")
|
|
488
|
+
|
|
489
|
+
|
|
326
490
|
def run(command: list[str], check: bool = True, env: dict[str, str] | None = None):
|
|
327
491
|
result = subprocess.run(
|
|
328
492
|
command,
|
|
@@ -341,6 +505,10 @@ def run(command: list[str], check: bool = True, env: dict[str, str] | None = Non
|
|
|
341
505
|
return CommandResult(result.returncode, result.stdout)
|
|
342
506
|
|
|
343
507
|
|
|
508
|
+
def home_prompt(root: Path) -> Path:
|
|
509
|
+
return root / "home" / ".codex" / "prompts"
|
|
510
|
+
|
|
511
|
+
|
|
344
512
|
class CommandResult:
|
|
345
513
|
def __init__(self, returncode: int, output: str) -> None:
|
|
346
514
|
self.returncode = returncode
|
package/scripts/se-setup.py
CHANGED
|
@@ -423,14 +423,25 @@ def print_summary(workspace: Path, demand_name: str, source: str, mode: str) ->
|
|
|
423
423
|
print(f"mode={mode}")
|
|
424
424
|
print("")
|
|
425
425
|
if source == "openspec":
|
|
426
|
-
print("
|
|
426
|
+
print("推荐使用流程:")
|
|
427
427
|
print(f"/se:propose <change-name>")
|
|
428
428
|
print("请根据当前 workspace 的 demand_file 生成或完善 OpenSpec change。")
|
|
429
429
|
print("不要改代码。过程中请使用中文。")
|
|
430
|
+
print("")
|
|
431
|
+
print("/se:bridge")
|
|
432
|
+
print("把 OpenSpec tasks.md 桥接为待审核 todo.md,不要改代码。")
|
|
433
|
+
print("")
|
|
434
|
+
print("人工审核 todo.md 后:")
|
|
435
|
+
print("/se:apply")
|
|
436
|
+
print("我已审核当前桥接 todo,可以进入交付阶段。")
|
|
430
437
|
else:
|
|
431
|
-
print("
|
|
438
|
+
print("推荐使用流程:")
|
|
439
|
+
print("先编辑 todo_file,确认任务和验收标准。")
|
|
440
|
+
print("")
|
|
432
441
|
print("/se:apply")
|
|
433
442
|
print("请根据当前 workspace 的 todo_file 推进交付工作流。过程中请使用中文。")
|
|
443
|
+
print("")
|
|
444
|
+
print("诊断命令:se doctor --workspace <workspace>")
|
|
434
445
|
|
|
435
446
|
|
|
436
447
|
if __name__ == "__main__":
|
|
@@ -24,16 +24,27 @@ Supported commands:
|
|
|
24
24
|
- `/se:archive`
|
|
25
25
|
- `/se:status`
|
|
26
26
|
|
|
27
|
-
For command details, read
|
|
27
|
+
For command details, read [references/commands/common.md](references/commands/common.md) plus only the matching command file:
|
|
28
|
+
|
|
29
|
+
- `/se:propose`: [references/commands/propose.md](references/commands/propose.md)
|
|
30
|
+
- `/se:bridge`: [references/commands/bridge.md](references/commands/bridge.md)
|
|
31
|
+
- `/se:plan`: [references/commands/plan.md](references/commands/plan.md)
|
|
32
|
+
- `/se:apply`: [references/commands/apply.md](references/commands/apply.md)
|
|
33
|
+
- `/se:review`: [references/commands/review.md](references/commands/review.md)
|
|
34
|
+
- `/se:verify`: [references/commands/verify.md](references/commands/verify.md)
|
|
35
|
+
- `/se:archive-check` and `/se:archive`: [references/commands/archive.md](references/commands/archive.md)
|
|
36
|
+
- `/se:status`: [references/commands/status.md](references/commands/status.md)
|
|
37
|
+
|
|
28
38
|
For mode and artifact details, use [references/workflow.md](references/workflow.md) and [references/execution-modes.md](references/execution-modes.md) only when needed.
|
|
29
39
|
|
|
30
40
|
## Minimal Required Steps
|
|
31
41
|
|
|
32
42
|
1. Read `<workspace>/workspace.yml`.
|
|
33
43
|
2. Identify `workflow_source`, `mode`, `todo_file`, `demand_file`, `reference_files`, `code_path`, `output_dir`, and optional `openspec` fields.
|
|
34
|
-
3.
|
|
35
|
-
4.
|
|
36
|
-
5.
|
|
44
|
+
3. Preflight with `python3 scripts/run-workflow.py route-check --command-text "<command>"` when possible.
|
|
45
|
+
4. Route the current command through `python3 scripts/run-workflow.py route-se --command-text "<command>"` when preflight passes.
|
|
46
|
+
5. Obey script state validation and script reply constraints.
|
|
47
|
+
6. Report compactly: result, blockers, allowed next command, and key artifact paths.
|
|
37
48
|
|
|
38
49
|
## Hard Constraints
|
|
39
50
|
|
|
@@ -59,6 +70,8 @@ For mode and artifact details, use [references/workflow.md](references/workflow.
|
|
|
59
70
|
- `reference_files` are strong context, but scripts summarize large files by default; read full files only when necessary for the current command.
|
|
60
71
|
- In OpenSpec mode, avoid rereading `proposal.md`, `design.md`, and `specs/**/*.md` unless the current stage needs them.
|
|
61
72
|
- Prefer `plan-summary.json` for downstream context; use `plan.json` only when detailed planning data is required.
|
|
73
|
+
- Prefer `discovery-summary.json` over `discovery.json`; read full discovery only for detailed code evidence.
|
|
74
|
+
- Prefer `route-se --json` when a machine-readable execution summary is useful.
|
|
62
75
|
- Final replies should be compact. Detailed reports belong in `output_dir/<session_id>/`.
|
|
63
76
|
|
|
64
77
|
## State Summary
|
|
@@ -75,10 +88,12 @@ Todo mode uses current session `status.json` and `todo-state.json`; do not write
|
|
|
75
88
|
|
|
76
89
|
## Script Entry Points
|
|
77
90
|
|
|
91
|
+
- Preflight router: `python3 scripts/run-workflow.py route-check --command-text "<command>"`
|
|
78
92
|
- Main router: `python3 scripts/run-workflow.py route-se --command-text "<command>"`
|
|
93
|
+
- JSON router summary: `python3 scripts/run-workflow.py route-se --command-text "<command>" --json`
|
|
79
94
|
- Status: `python3 scripts/run-workflow.py status`
|
|
80
95
|
- Plan: `python3 scripts/run-workflow.py plan`
|
|
81
96
|
- Review: `python3 scripts/run-workflow.py review`
|
|
82
97
|
- Verify: `python3 scripts/run-workflow.py verify`
|
|
83
98
|
|
|
84
|
-
OpenSpec-specific
|
|
99
|
+
OpenSpec-specific execution details are enforced by the command files under [references/commands/](references/commands/).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# `/se:apply`
|
|
2
|
+
|
|
3
|
+
用途:进入交付阶段,按计划实现、生成自查,并在 auto 模式继续 review/verify。
|
|
4
|
+
|
|
5
|
+
## 前置
|
|
6
|
+
|
|
7
|
+
- todo 模式:存在有效 `todo.md`。
|
|
8
|
+
- openspec 模式:已经 `/se:bridge`,且用户已审核 `todo.md`。
|
|
9
|
+
- 如无计划,脚本可创建或复用标准 session 并生成计划。
|
|
10
|
+
- OpenSpec `tasks.md` hash 必须与最近 bridge 记录一致。
|
|
11
|
+
|
|
12
|
+
## 执行
|
|
13
|
+
|
|
14
|
+
1. 先执行公共 `route-check`。
|
|
15
|
+
2. 再执行:
|
|
16
|
+
`python3 scripts/run-workflow.py route-se --command-text "/se:apply"`
|
|
17
|
+
3. AI 按当前 `plan-summary.json` / `plan.json` 修改目标代码。
|
|
18
|
+
4. 修改完成后调用 `finish-implement`,由脚本生成 self-check。
|
|
19
|
+
5. auto 模式继续由标准脚本推进 review、verify 和通知。
|
|
20
|
+
|
|
21
|
+
## 禁止
|
|
22
|
+
|
|
23
|
+
- 禁止第二次手工执行 `/se:plan` 创建新 session。
|
|
24
|
+
- 禁止手写状态文件或 output 报告。
|
|
25
|
+
- 禁止直接发飞书/PushPlus 通知。
|
|
26
|
+
- verify 通过前禁止提示 archive。
|
|
27
|
+
|
|
28
|
+
最终回复汇报改动、review、verify、通知和允许的下一步。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# `/se:archive-check` 与 `/se:archive`
|
|
2
|
+
|
|
3
|
+
## `/se:archive-check`
|
|
4
|
+
|
|
5
|
+
用途:检查 OpenSpec change 是否可以安全归档。
|
|
6
|
+
|
|
7
|
+
前置:`/se:verify` 已通过。
|
|
8
|
+
|
|
9
|
+
执行:
|
|
10
|
+
`python3 scripts/run-workflow.py route-se --command-text "/se:archive-check"`
|
|
11
|
+
|
|
12
|
+
只有 `archive_ready=true`、`merge_mode=safe_merge` 且无 spec 冲突时,才允许提示 `/se:archive`。
|
|
13
|
+
|
|
14
|
+
## `/se:archive`
|
|
15
|
+
|
|
16
|
+
用途:归档当前 OpenSpec change,沉淀长期 specs。
|
|
17
|
+
|
|
18
|
+
前置:已完成 `/se:archive-check` 且结果为 safe merge。
|
|
19
|
+
|
|
20
|
+
执行:
|
|
21
|
+
`python3 scripts/run-workflow.py route-se --command-text "/se:archive"`
|
|
22
|
+
|
|
23
|
+
禁止跳过 archive-check 直接归档。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# `/se:bridge`
|
|
2
|
+
|
|
3
|
+
用途:把当前 OpenSpec change 的 `tasks.md` 转成待审核 `todo.md`。
|
|
4
|
+
|
|
5
|
+
## 前置
|
|
6
|
+
|
|
7
|
+
- 已有 active OpenSpec change。
|
|
8
|
+
- 已存在 `proposal.md`、`design.md`、`tasks.md`。
|
|
9
|
+
- 当前阶段为 `proposed` 或允许重新 bridge 的 `bridged`。
|
|
10
|
+
|
|
11
|
+
## 执行
|
|
12
|
+
|
|
13
|
+
1. 先执行公共 `route-check`。
|
|
14
|
+
2. 再执行:
|
|
15
|
+
`python3 scripts/run-workflow.py route-se --command-text "/se:bridge"`
|
|
16
|
+
3. 脚本生成 `todo.md`,并记录 `tasks.md` hash。
|
|
17
|
+
4. 停在 `bridged` 阶段。
|
|
18
|
+
|
|
19
|
+
## 禁止
|
|
20
|
+
|
|
21
|
+
- 禁止手工同步 `tasks.md` 到 `todo.md`。
|
|
22
|
+
- 禁止自动进入 `/se:plan` 或 `/se:apply`。
|
|
23
|
+
- 如果审核后发现需求或 todo 有偏差,先修正需求或重新 `/se:propose <change-name>`,再重新 `/se:bridge`。
|
|
24
|
+
|
|
25
|
+
最终回复只能提示:请人工审核 `todo.md`,审核通过后发送 `/se:apply`。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `/se:*` 公共协议
|
|
2
|
+
|
|
3
|
+
`/se:*` 是 Super Engineer Workflow 的 AI 阶段命令,不是 shell 命令,也不是 OpenSpec `/opsx:*`。
|
|
4
|
+
|
|
5
|
+
## 必做
|
|
6
|
+
|
|
7
|
+
1. 读取当前工作区 `workspace.yml`。
|
|
8
|
+
2. 通过 `python3 scripts/run-workflow.py route-check --command-text "<用户命令>"` 做状态预检。
|
|
9
|
+
3. 预检通过后,再执行 `python3 scripts/run-workflow.py route-se --command-text "<用户命令>"`。
|
|
10
|
+
4. 遵守脚本输出的 `final_reply_must` 或 `se_reply_constraint_begin/end`。
|
|
11
|
+
5. 最终回复只汇报结果、阻塞点、允许的下一步和关键相对路径。
|
|
12
|
+
|
|
13
|
+
## 硬约束
|
|
14
|
+
|
|
15
|
+
- 禁止编辑 `workspace.yml`。
|
|
16
|
+
- 禁止手写 `.super-engineer/**/status.json`、`se-state.json`、`current-session.json`、`plan.json`、`review.json`、`verify.json`、`notification.json`。
|
|
17
|
+
- 标准状态、报告、通知只能由脚本生成。
|
|
18
|
+
- 一次只执行用户当前明确请求的一个 `/se:*` 命令。
|
|
19
|
+
- `/se:propose` 之后只能提示 `/se:bridge`。
|
|
20
|
+
- `/se:bridge` 之后只能提示人工审核 `todo.md`,审核后 `/se:apply`。
|
|
21
|
+
- `/se:apply` 之前必须已经完成 bridge 且用户已审核 todo。
|
|
22
|
+
- 通知只能由 `run-workflow.py verify` 发送,AI 禁止直接调用飞书或 PushPlus webhook。
|
|
23
|
+
|
|
24
|
+
## 最小上下文
|
|
25
|
+
|
|
26
|
+
- `/se:propose`:`workspace.yml`、`demand_file`、必要 `reference_files` 摘要。
|
|
27
|
+
- `/se:bridge`:`tasks.md`、bridge context、`todo_file`。
|
|
28
|
+
- `/se:plan`:`todo.md`、`discovery-summary` 或脚本生成的 plan 产物。
|
|
29
|
+
- `/se:apply`:优先 `todo.md`、`plan-summary.json`、目标代码文件。
|
|
30
|
+
- `/se:review` / `/se:verify`:优先读取 summary 和脚本报告,不展开长 diff 或长日志。
|
|
31
|
+
|
|
32
|
+
命令细节只允许读取 `references/commands/` 下的对应命令文件。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# `/se:plan`
|
|
2
|
+
|
|
3
|
+
用途:只生成实施计划,不改代码。
|
|
4
|
+
|
|
5
|
+
## 前置
|
|
6
|
+
|
|
7
|
+
- todo 模式:存在有效 `todo.md`。
|
|
8
|
+
- openspec 模式:已经 `/se:bridge`,且用户已审核 `todo.md`。
|
|
9
|
+
- 当前不存在已进入实现、review、verify 的活跃 session。
|
|
10
|
+
|
|
11
|
+
## 执行
|
|
12
|
+
|
|
13
|
+
1. 先执行公共 `route-check`。
|
|
14
|
+
2. 再执行:
|
|
15
|
+
`python3 scripts/run-workflow.py route-se --command-text "/se:plan"`
|
|
16
|
+
3. 脚本生成 `discovery.json`、`plan.json`、`plan-summary.json`、`plan.md`。
|
|
17
|
+
4. 如果已有可复用计划 session,必须复用,不能新建空 session。
|
|
18
|
+
|
|
19
|
+
## 禁止
|
|
20
|
+
|
|
21
|
+
- 禁止改代码。
|
|
22
|
+
- 禁止继续 review/verify。
|
|
23
|
+
- OpenSpec `tasks.md` hash 与 bridge 记录不一致时,必须停止并要求重新 `/se:bridge`。
|
|
24
|
+
|
|
25
|
+
最终回复提示下一步 `/se:apply`。
|