super-engineer-workflow 0.1.4 → 0.1.5
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 +8 -0
- package/README.md +11 -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 +13 -0
- package/docs//346/250/241/346/235/277/344/275/277/347/224/250/346/214/207/345/215/227.md +55 -0
- package/package.json +4 -2
- package/scripts/se-cli.py +77 -0
- package/scripts/se-e2e-test.py +369 -0
- package/super-engineer-workflow/references/se-commands.md +5 -1
- package/super-engineer-workflow/references/workflow.md +3 -1
- package/super-engineer-workflow/scripts/common.py +53 -1
- package/super-engineer-workflow/scripts/run-workflow.py +15 -1
- package/templates/workspaces/frontend.yml +18 -0
- package/templates/workspaces/java-microservice.yml +19 -0
- package/templates/workspaces/multi-repo.yml +21 -0
- package/templates/workspaces/openspec-auto.yml +18 -0
- package/templates/workspaces/openspec-manual.yml +16 -0
- package/templates/workspaces/todo-auto.yml +13 -0
- package/templates/workspaces/todo-manual.yml +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
- 修复重复执行 `/se:plan` 会创建多个 session 的问题:计划阶段复用当前 session,交付中重复 plan 会被拒绝。
|
|
6
|
+
- 增加 E2E 测试,覆盖模板 CLI、OpenSpec 状态机与桥接、todo auto 会话、verify 长日志截断。
|
|
7
|
+
- 增加 `se templates`、`se template show`、`se template copy`,支持复制内置 `workspace.yml` 模板。
|
|
8
|
+
- 增加 OpenSpec、todo、Java 微服务、前端、多仓库等工作区模板。
|
|
9
|
+
- 增加模板使用指南,并在 README 和快速初始化文档中补充模板入口。
|
|
10
|
+
|
|
3
11
|
## 0.1.4
|
|
4
12
|
|
|
5
13
|
- Review 阶段优先读取 `plan-summary.json`,仅在缺失时回退到 `plan.json`。
|
package/README.md
CHANGED
|
@@ -111,12 +111,23 @@ se init
|
|
|
111
111
|
```bash
|
|
112
112
|
se init # 交互式安装 skill 并初始化工作区
|
|
113
113
|
se doctor # 检查本机环境和 workspace.yml
|
|
114
|
+
se templates # 查看内置 workspace.yml 模板
|
|
114
115
|
se install # 安装 skill 到 Codex / Claude
|
|
115
116
|
se sync # 强制同步最新 skill 到 Codex / Claude
|
|
116
117
|
se migrate # 补齐旧工作区缺失配置
|
|
117
118
|
se version # 查看版本
|
|
118
119
|
```
|
|
119
120
|
|
|
121
|
+
模板入口:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
se templates
|
|
125
|
+
se template show openspec-auto
|
|
126
|
+
se template copy openspec-auto --workspace /path/to/ai-workspace --demand-name 13-your-demand --code-path ../code
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
模板说明见 [docs/模板使用指南.md](/Users/muke/Documents/personal/codex/super-engineer/docs/模板使用指南.md)。
|
|
130
|
+
|
|
120
131
|
本地源码开发时,也可以直接使用引导脚本。默认是一步一步的交互式向导:
|
|
121
132
|
|
|
122
133
|
```bash
|
|
@@ -176,12 +176,25 @@ se init
|
|
|
176
176
|
```bash
|
|
177
177
|
se init # 交互式安装 skill 并初始化工作区
|
|
178
178
|
se doctor # 检查本机环境和 workspace.yml
|
|
179
|
+
se templates # 查看内置 workspace.yml 模板
|
|
179
180
|
se install # 安装 skill 到 Codex / Claude
|
|
180
181
|
se sync # 强制同步最新 skill 到 Codex / Claude
|
|
181
182
|
se migrate # 补齐旧工作区缺失配置
|
|
182
183
|
se version # 查看版本
|
|
183
184
|
```
|
|
184
185
|
|
|
186
|
+
如果你不想从零写 `workspace.yml`,可以先复制内置模板:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
se templates
|
|
190
|
+
se template copy openspec-auto \
|
|
191
|
+
--workspace /path/to/ai-workspace \
|
|
192
|
+
--demand-name 10-your-demand \
|
|
193
|
+
--code-path ../code
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
模板不会覆盖已有 `workspace.yml`,除非显式增加 `--force`。模板说明见 `docs/模板使用指南.md`。
|
|
197
|
+
|
|
185
198
|
npm 安装的核心要求:
|
|
186
199
|
|
|
187
200
|
- `package.json` 声明 `bin.super-engineer` 和 `bin.se`。
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# 模板使用指南
|
|
2
|
+
|
|
3
|
+
`super-engineer` 内置了一组 `workspace.yml` 模板,用于减少团队初始化成本,并让不同项目按统一结构接入工作流。
|
|
4
|
+
|
|
5
|
+
## 查看模板
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
se templates
|
|
9
|
+
se template show openspec-auto
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 复制模板到工作区
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
se template copy openspec-auto \
|
|
16
|
+
--workspace /path/to/ai-workspace \
|
|
17
|
+
--demand-name 13-your-demand \
|
|
18
|
+
--code-path ../code
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
默认不会覆盖已有 `workspace.yml`。确实需要覆盖时加:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
se template copy openspec-auto --workspace /path/to/ai-workspace --force
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 模板选择
|
|
28
|
+
|
|
29
|
+
| 模板 | 适用场景 |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `openspec-auto` | 推荐默认模板。先生成 OpenSpec change,再桥接 todo,审核后自动交付。 |
|
|
32
|
+
| `openspec-manual` | 高风险需求。每个关键阶段都需要人工确认。 |
|
|
33
|
+
| `todo-auto` | 小需求、任务已经明确、不需要规格沉淀。 |
|
|
34
|
+
| `todo-manual` | 首次接入、培训、新手熟悉工作流。 |
|
|
35
|
+
| `java-microservice` | Java / Spring 微服务,默认使用 Maven 验证。 |
|
|
36
|
+
| `frontend` | Vue / React 前端项目,默认使用 pnpm 验证。 |
|
|
37
|
+
| `multi-repo` | 多仓库聚合目录,适合中台系统跨服务需求。 |
|
|
38
|
+
|
|
39
|
+
## 初始化后需要补齐什么
|
|
40
|
+
|
|
41
|
+
模板只负责生成配置骨架。使用前还需要确认:
|
|
42
|
+
|
|
43
|
+
- `code_path` 指向真实代码目录。
|
|
44
|
+
- `vars.demand_name` 是当前需求目录名。
|
|
45
|
+
- `demand_file` 或 `todo_file` 对应文件已经存在。
|
|
46
|
+
- `reference_files` 中的文档存在且与当前项目相关。
|
|
47
|
+
- `verify_commands` 与团队实际构建命令一致。
|
|
48
|
+
|
|
49
|
+
## 推荐实践
|
|
50
|
+
|
|
51
|
+
存量中台系统优先使用 `openspec-auto` 或 `multi-repo`。
|
|
52
|
+
|
|
53
|
+
小范围试点时使用 `openspec-manual`,确认流程稳定后再切换到 `openspec-auto`。
|
|
54
|
+
|
|
55
|
+
如果需求本身已经拆成明确任务,不需要长期规格沉淀,可以使用 `todo-auto`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-engineer-workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Super Engineer workflow skill and CLI for demand-driven AI delivery with OpenSpec bridge support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Gary-Coding",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"super-engineer-workflow/",
|
|
32
32
|
"!super-engineer-workflow/scripts/__pycache__/",
|
|
33
33
|
"!super-engineer-workflow/scripts/**/*.pyc",
|
|
34
|
+
"templates/",
|
|
34
35
|
"docs/",
|
|
35
36
|
"README.md",
|
|
36
37
|
"LICENSE",
|
|
@@ -40,8 +41,9 @@
|
|
|
40
41
|
],
|
|
41
42
|
"scripts": {
|
|
42
43
|
"setup": "python3 scripts/se-setup.py",
|
|
43
|
-
"check": "python3 -m py_compile scripts/se-cli.py scripts/se-setup.py scripts/se-smoke-test.py super-engineer-workflow/scripts/*.py",
|
|
44
|
+
"check": "python3 -m py_compile scripts/se-cli.py scripts/se-setup.py scripts/se-smoke-test.py scripts/se-e2e-test.py super-engineer-workflow/scripts/*.py",
|
|
44
45
|
"smoke": "python3 scripts/se-smoke-test.py",
|
|
46
|
+
"e2e": "python3 scripts/se-e2e-test.py",
|
|
45
47
|
"pack:check": "npm pack --dry-run"
|
|
46
48
|
},
|
|
47
49
|
"keywords": [
|
package/scripts/se-cli.py
CHANGED
|
@@ -14,6 +14,18 @@ from pathlib import Path
|
|
|
14
14
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
15
15
|
SKILL_DIR = REPO_ROOT / "super-engineer-workflow"
|
|
16
16
|
PACKAGE_JSON = REPO_ROOT / "package.json"
|
|
17
|
+
TEMPLATE_DIR = REPO_ROOT / "templates" / "workspaces"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
WORKSPACE_TEMPLATES: dict[str, str] = {
|
|
21
|
+
"openspec-auto": "OpenSpec 规格先行 + 自动交付,推荐用于需求迭代主流程。",
|
|
22
|
+
"openspec-manual": "OpenSpec 规格先行 + 分阶段人工确认,推荐用于高风险变更。",
|
|
23
|
+
"todo-auto": "todo.md 直接驱动 + 自动交付,推荐用于小需求或已有明确任务清单。",
|
|
24
|
+
"todo-manual": "todo.md 直接驱动 + 分阶段人工确认,推荐用于首次接入或新手练习。",
|
|
25
|
+
"java-microservice": "Java / Spring 微服务模板,内置 Maven 验证命令示例。",
|
|
26
|
+
"frontend": "Vue / React 前端模板,内置 pnpm/npm 验证命令示例。",
|
|
27
|
+
"multi-repo": "多仓库聚合目录模板,适用于中台或跨服务需求。",
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
|
|
19
31
|
def main() -> None:
|
|
@@ -49,6 +61,19 @@ def main() -> None:
|
|
|
49
61
|
migrate_parser.add_argument("--workspace", default=".", help="工作区目录,默认当前目录。")
|
|
50
62
|
migrate_parser.add_argument("--dry-run", action="store_true", help="只展示计划,不写入文件。")
|
|
51
63
|
|
|
64
|
+
subparsers.add_parser("templates", help="列出内置 workspace.yml 模板。")
|
|
65
|
+
|
|
66
|
+
template_parser = subparsers.add_parser("template", help="查看或复制内置 workspace.yml 模板。")
|
|
67
|
+
template_subparsers = template_parser.add_subparsers(dest="template_action")
|
|
68
|
+
template_show = template_subparsers.add_parser("show", help="打印指定模板内容。")
|
|
69
|
+
template_show.add_argument("name", choices=sorted(WORKSPACE_TEMPLATES))
|
|
70
|
+
template_copy = template_subparsers.add_parser("copy", help="复制指定模板到工作区 workspace.yml。")
|
|
71
|
+
template_copy.add_argument("name", choices=sorted(WORKSPACE_TEMPLATES))
|
|
72
|
+
template_copy.add_argument("--workspace", default=".", help="工作区目录,默认当前目录。")
|
|
73
|
+
template_copy.add_argument("--demand-name", default="1-your-demand", help="写入 vars.demand_name 的需求目录名。")
|
|
74
|
+
template_copy.add_argument("--code-path", default="../code", help="写入 code_path 的代码目录。")
|
|
75
|
+
template_copy.add_argument("--force", action="store_true", help="允许覆盖已有 workspace.yml。")
|
|
76
|
+
|
|
52
77
|
subparsers.add_parser("version", help="显示版本号。")
|
|
53
78
|
|
|
54
79
|
args = parser.parse_args()
|
|
@@ -67,6 +92,24 @@ def main() -> None:
|
|
|
67
92
|
if args.command == "migrate":
|
|
68
93
|
exit_code = migrate(Path(args.workspace).expanduser().resolve(), dry_run=args.dry_run)
|
|
69
94
|
raise SystemExit(exit_code)
|
|
95
|
+
if args.command == "templates":
|
|
96
|
+
list_templates()
|
|
97
|
+
return
|
|
98
|
+
if args.command == "template":
|
|
99
|
+
if args.template_action == "show":
|
|
100
|
+
show_template(args.name)
|
|
101
|
+
return
|
|
102
|
+
if args.template_action == "copy":
|
|
103
|
+
copy_template(
|
|
104
|
+
args.name,
|
|
105
|
+
Path(args.workspace).expanduser().resolve(),
|
|
106
|
+
demand_name=args.demand_name,
|
|
107
|
+
code_path=args.code_path,
|
|
108
|
+
force=args.force,
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
template_parser.print_help()
|
|
112
|
+
return
|
|
70
113
|
|
|
71
114
|
parser.print_help()
|
|
72
115
|
|
|
@@ -119,6 +162,40 @@ def install_skill(target: Path, force: bool) -> None:
|
|
|
119
162
|
print(f"✓ 已同步 skill: {target}")
|
|
120
163
|
|
|
121
164
|
|
|
165
|
+
def template_path(name: str) -> Path:
|
|
166
|
+
path = TEMPLATE_DIR / f"{name}.yml"
|
|
167
|
+
if not path.exists():
|
|
168
|
+
raise SystemExit(f"模板不存在:{name}")
|
|
169
|
+
return path
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def list_templates() -> None:
|
|
173
|
+
print("内置 workspace.yml 模板:")
|
|
174
|
+
for name in sorted(WORKSPACE_TEMPLATES):
|
|
175
|
+
marker = "✓" if template_path(name).exists() else "!"
|
|
176
|
+
print(f"{marker} {name}: {WORKSPACE_TEMPLATES[name]}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def render_template(name: str, demand_name: str, code_path: str) -> str:
|
|
180
|
+
text = template_path(name).read_text(encoding="utf-8")
|
|
181
|
+
return text.replace("__DEMAND_NAME__", demand_name).replace("__CODE_PATH__", code_path)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def show_template(name: str) -> None:
|
|
185
|
+
print(render_template(name, demand_name="1-your-demand", code_path="../code"))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def copy_template(name: str, workspace: Path, demand_name: str, code_path: str, force: bool) -> None:
|
|
189
|
+
workspace.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
target = workspace / "workspace.yml"
|
|
191
|
+
if target.exists() and not force:
|
|
192
|
+
raise SystemExit(f"workspace.yml 已存在:{target}。如需覆盖请加 --force。")
|
|
193
|
+
target.write_text(render_template(name, demand_name=demand_name, code_path=code_path), encoding="utf-8")
|
|
194
|
+
(workspace / "docs").mkdir(parents=True, exist_ok=True)
|
|
195
|
+
(workspace / "superengineer" / demand_name).mkdir(parents=True, exist_ok=True)
|
|
196
|
+
print(f"✓ 已写入模板:{target}")
|
|
197
|
+
|
|
198
|
+
|
|
122
199
|
def doctor(workspace: Path, output_json: bool) -> int:
|
|
123
200
|
checks: list[dict[str, str]] = []
|
|
124
201
|
add_check(checks, "python", "ok", sys.version.split()[0])
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
14
|
+
RUN_WORKFLOW = REPO_ROOT / "super-engineer-workflow" / "scripts" / "run-workflow.py"
|
|
15
|
+
CLI = REPO_ROOT / "bin" / "super-engineer.js"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
with tempfile.TemporaryDirectory(prefix="se-e2e-") as tmp:
|
|
20
|
+
root = Path(tmp)
|
|
21
|
+
home = root / "home"
|
|
22
|
+
home.mkdir()
|
|
23
|
+
os.environ["HOME"] = str(home)
|
|
24
|
+
os.environ["USERPROFILE"] = str(home)
|
|
25
|
+
test_templates_cli(root)
|
|
26
|
+
test_openspec_state_and_bridge(root)
|
|
27
|
+
test_todo_auto_session_and_verify_compaction(root)
|
|
28
|
+
print("e2e_test=ok")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_templates_cli(root: Path) -> None:
|
|
32
|
+
workspace = root / "template-workspace"
|
|
33
|
+
output = run(["node", str(CLI), "templates"])
|
|
34
|
+
if "openspec-auto" not in output or "todo-auto" not in output:
|
|
35
|
+
raise AssertionError("templates list did not include expected templates")
|
|
36
|
+
|
|
37
|
+
show_output = run(["node", str(CLI), "template", "show", "openspec-auto"])
|
|
38
|
+
if "workflow_source: openspec" not in show_output or "mode: auto" not in show_output:
|
|
39
|
+
raise AssertionError("template show returned unexpected content")
|
|
40
|
+
|
|
41
|
+
run(
|
|
42
|
+
[
|
|
43
|
+
"node",
|
|
44
|
+
str(CLI),
|
|
45
|
+
"template",
|
|
46
|
+
"copy",
|
|
47
|
+
"todo-auto",
|
|
48
|
+
"--workspace",
|
|
49
|
+
str(workspace),
|
|
50
|
+
"--demand-name",
|
|
51
|
+
"9-e2e-template",
|
|
52
|
+
"--code-path",
|
|
53
|
+
"../code",
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
workspace_yml = read_text(workspace / "workspace.yml")
|
|
57
|
+
if "workflow_source: todo" not in workspace_yml or "9-e2e-template" not in workspace_yml:
|
|
58
|
+
raise AssertionError("template copy did not render workspace.yml")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_openspec_state_and_bridge(root: Path) -> None:
|
|
62
|
+
workspace = root / "openspec-workspace"
|
|
63
|
+
code = root / "code" / "demo-service"
|
|
64
|
+
demand_dir = workspace / "superengineer" / "8-demo"
|
|
65
|
+
demand_dir.mkdir(parents=True)
|
|
66
|
+
code.mkdir(parents=True)
|
|
67
|
+
(code / "package.json").write_text(
|
|
68
|
+
json.dumps(
|
|
69
|
+
{
|
|
70
|
+
"name": "demo-service",
|
|
71
|
+
"version": "1.0.0",
|
|
72
|
+
"scripts": {"test": "node -e \"process.exit(0)\""},
|
|
73
|
+
},
|
|
74
|
+
ensure_ascii=False,
|
|
75
|
+
indent=2,
|
|
76
|
+
),
|
|
77
|
+
encoding="utf-8",
|
|
78
|
+
)
|
|
79
|
+
(workspace / "docs").mkdir(parents=True)
|
|
80
|
+
(workspace / "openspec" / "changes").mkdir(parents=True)
|
|
81
|
+
(workspace / "openspec" / "specs").mkdir(parents=True)
|
|
82
|
+
(demand_dir / "需求.md").write_text(
|
|
83
|
+
"# 需求\n\n为 demo-service 增加状态查询接口。\n\n## 验收\n\n- 查询接口返回 ok。\n",
|
|
84
|
+
encoding="utf-8",
|
|
85
|
+
)
|
|
86
|
+
(workspace / "workspace.yml").write_text(
|
|
87
|
+
"\n".join(
|
|
88
|
+
[
|
|
89
|
+
"version: 1",
|
|
90
|
+
"mode: auto",
|
|
91
|
+
"workflow_source: openspec",
|
|
92
|
+
"vars:",
|
|
93
|
+
" demand_name: 8-demo",
|
|
94
|
+
"demand_file: superengineer/${demand_name}/需求.md",
|
|
95
|
+
"todo_file: superengineer/${demand_name}/todo.md",
|
|
96
|
+
"reference_files: []",
|
|
97
|
+
"code_path: ../code/demo-service",
|
|
98
|
+
"output_dir: superengineer/${demand_name}/output",
|
|
99
|
+
"openspec:",
|
|
100
|
+
" changes_dir: openspec/changes",
|
|
101
|
+
"",
|
|
102
|
+
]
|
|
103
|
+
),
|
|
104
|
+
encoding="utf-8",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
invalid = run(
|
|
108
|
+
[
|
|
109
|
+
sys.executable,
|
|
110
|
+
str(RUN_WORKFLOW),
|
|
111
|
+
"route-se",
|
|
112
|
+
"--workspace",
|
|
113
|
+
str(workspace),
|
|
114
|
+
"--command-text",
|
|
115
|
+
"/se:bridge",
|
|
116
|
+
],
|
|
117
|
+
check=False,
|
|
118
|
+
)
|
|
119
|
+
if invalid.returncode == 0 or "请先执行 /se:propose" not in invalid.output:
|
|
120
|
+
raise AssertionError("/se:bridge before /se:propose should be rejected")
|
|
121
|
+
|
|
122
|
+
env_without_openspec = os.environ.copy()
|
|
123
|
+
env_without_openspec["PATH"] = ""
|
|
124
|
+
propose = run(
|
|
125
|
+
[
|
|
126
|
+
sys.executable,
|
|
127
|
+
str(RUN_WORKFLOW),
|
|
128
|
+
"route-se",
|
|
129
|
+
"--workspace",
|
|
130
|
+
str(workspace),
|
|
131
|
+
"--command-text",
|
|
132
|
+
"/se:propose demo-change",
|
|
133
|
+
],
|
|
134
|
+
env=env_without_openspec,
|
|
135
|
+
)
|
|
136
|
+
if "final_reply_must=代码未修改。下一步只能执行 /se:bridge。" not in propose:
|
|
137
|
+
raise AssertionError("/se:propose did not print strict next-step constraint")
|
|
138
|
+
|
|
139
|
+
change_dir = workspace / "openspec" / "changes" / "demo-change"
|
|
140
|
+
(change_dir / "specs").mkdir(parents=True, exist_ok=True)
|
|
141
|
+
(change_dir / "proposal.md").write_text("# Proposal\n\n增加状态查询接口。\n", encoding="utf-8")
|
|
142
|
+
(change_dir / "design.md").write_text("# Design\n\n目标服务 demo-service。\n", encoding="utf-8")
|
|
143
|
+
(change_dir / "tasks.md").write_text(
|
|
144
|
+
"# Tasks\n\n"
|
|
145
|
+
"- [ ] 修改 demo-service controller 增加状态查询接口\n"
|
|
146
|
+
"- [ ] 补充验证,确认接口返回 ok\n",
|
|
147
|
+
encoding="utf-8",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
bridge = run(
|
|
151
|
+
[
|
|
152
|
+
sys.executable,
|
|
153
|
+
str(RUN_WORKFLOW),
|
|
154
|
+
"route-se",
|
|
155
|
+
"--workspace",
|
|
156
|
+
str(workspace),
|
|
157
|
+
"--command-text",
|
|
158
|
+
"/se:bridge",
|
|
159
|
+
]
|
|
160
|
+
)
|
|
161
|
+
if "bridge_generated=true" not in bridge:
|
|
162
|
+
raise AssertionError("/se:bridge did not generate todo.md")
|
|
163
|
+
todo = read_text(workspace / "superengineer" / "8-demo" / "todo.md")
|
|
164
|
+
if "demo-change" not in todo or "demo-service" not in todo:
|
|
165
|
+
raise AssertionError("bridged todo.md missing expected OpenSpec context")
|
|
166
|
+
|
|
167
|
+
first_plan = run(
|
|
168
|
+
[
|
|
169
|
+
sys.executable,
|
|
170
|
+
str(RUN_WORKFLOW),
|
|
171
|
+
"route-se",
|
|
172
|
+
"--workspace",
|
|
173
|
+
str(workspace),
|
|
174
|
+
"--command-text",
|
|
175
|
+
"/se:plan",
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
if "session_action=created" not in first_plan:
|
|
179
|
+
raise AssertionError("first /se:plan should create a session")
|
|
180
|
+
first_session = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
181
|
+
first_session_id = first_session["session_id"]
|
|
182
|
+
|
|
183
|
+
second_plan = run(
|
|
184
|
+
[
|
|
185
|
+
sys.executable,
|
|
186
|
+
str(RUN_WORKFLOW),
|
|
187
|
+
"route-se",
|
|
188
|
+
"--workspace",
|
|
189
|
+
str(workspace),
|
|
190
|
+
"--command-text",
|
|
191
|
+
"/se:plan",
|
|
192
|
+
]
|
|
193
|
+
)
|
|
194
|
+
second_session = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
195
|
+
if "session_action=reused" not in second_plan or second_session["session_id"] != first_session_id:
|
|
196
|
+
raise AssertionError("second /se:plan should reuse the existing planning session")
|
|
197
|
+
if len(list((workspace / ".super-engineer" / "sessions").iterdir())) != 1:
|
|
198
|
+
raise AssertionError("repeated /se:plan created an extra session")
|
|
199
|
+
|
|
200
|
+
apply_output = run(
|
|
201
|
+
[
|
|
202
|
+
sys.executable,
|
|
203
|
+
str(RUN_WORKFLOW),
|
|
204
|
+
"route-se",
|
|
205
|
+
"--workspace",
|
|
206
|
+
str(workspace),
|
|
207
|
+
"--command-text",
|
|
208
|
+
"/se:apply",
|
|
209
|
+
]
|
|
210
|
+
)
|
|
211
|
+
if "apply_phase=implementing" not in apply_output:
|
|
212
|
+
raise AssertionError("/se:apply should enter implementing after planned session")
|
|
213
|
+
blocked_plan = run(
|
|
214
|
+
[
|
|
215
|
+
sys.executable,
|
|
216
|
+
str(RUN_WORKFLOW),
|
|
217
|
+
"route-se",
|
|
218
|
+
"--workspace",
|
|
219
|
+
str(workspace),
|
|
220
|
+
"--command-text",
|
|
221
|
+
"/se:plan",
|
|
222
|
+
],
|
|
223
|
+
check=False,
|
|
224
|
+
)
|
|
225
|
+
final_session = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
226
|
+
if blocked_plan.returncode == 0 or "不能重新执行 /se:plan" not in blocked_plan.output:
|
|
227
|
+
raise AssertionError("/se:plan during active delivery should be rejected")
|
|
228
|
+
if final_session["session_id"] != first_session_id or len(list((workspace / ".super-engineer" / "sessions").iterdir())) != 1:
|
|
229
|
+
raise AssertionError("rejected /se:plan should not create or switch sessions")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_todo_auto_session_and_verify_compaction(root: Path) -> None:
|
|
233
|
+
workspace = root / "todo-workspace"
|
|
234
|
+
code = root / "todo-code" / "demo-service"
|
|
235
|
+
demand_dir = workspace / "superengineer" / "9-demo"
|
|
236
|
+
demand_dir.mkdir(parents=True)
|
|
237
|
+
code.mkdir(parents=True)
|
|
238
|
+
(workspace / "docs").mkdir(parents=True)
|
|
239
|
+
(code / "verify.py").write_text(
|
|
240
|
+
"print('A' * 13000)\n"
|
|
241
|
+
"import sys\n"
|
|
242
|
+
"print('B' * 13000, file=sys.stderr)\n",
|
|
243
|
+
encoding="utf-8",
|
|
244
|
+
)
|
|
245
|
+
(code / "package.json").write_text(
|
|
246
|
+
json.dumps(
|
|
247
|
+
{
|
|
248
|
+
"name": "demo-service",
|
|
249
|
+
"version": "1.0.0",
|
|
250
|
+
"scripts": {"test": "node -e \"process.exit(0)\""},
|
|
251
|
+
},
|
|
252
|
+
ensure_ascii=False,
|
|
253
|
+
indent=2,
|
|
254
|
+
),
|
|
255
|
+
encoding="utf-8",
|
|
256
|
+
)
|
|
257
|
+
verify_command = f'"{sys.executable}" verify.py'
|
|
258
|
+
(workspace / "workspace.yml").write_text(
|
|
259
|
+
"\n".join(
|
|
260
|
+
[
|
|
261
|
+
"version: 1",
|
|
262
|
+
"mode: auto",
|
|
263
|
+
"workflow_source: todo",
|
|
264
|
+
"vars:",
|
|
265
|
+
" demand_name: 9-demo",
|
|
266
|
+
"todo_file: superengineer/${demand_name}/todo.md",
|
|
267
|
+
"reference_files: []",
|
|
268
|
+
"code_path: ../todo-code/demo-service",
|
|
269
|
+
"output_dir: superengineer/${demand_name}/output",
|
|
270
|
+
"verify_commands:",
|
|
271
|
+
f" default: {verify_command}",
|
|
272
|
+
"",
|
|
273
|
+
]
|
|
274
|
+
),
|
|
275
|
+
encoding="utf-8",
|
|
276
|
+
)
|
|
277
|
+
(demand_dir / "todo.md").write_text(
|
|
278
|
+
"# 限制条件\n"
|
|
279
|
+
"- 修改的服务是 demo-service\n\n"
|
|
280
|
+
"# 待办事项\n\n"
|
|
281
|
+
"- [ ] 增加状态查询接口\n"
|
|
282
|
+
"1. 返回 ok\n\n"
|
|
283
|
+
"## 验收补充\n"
|
|
284
|
+
"- [ ] 执行验证命令\n",
|
|
285
|
+
encoding="utf-8",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
apply_output = run(
|
|
289
|
+
[
|
|
290
|
+
sys.executable,
|
|
291
|
+
str(RUN_WORKFLOW),
|
|
292
|
+
"route-se",
|
|
293
|
+
"--workspace",
|
|
294
|
+
str(workspace),
|
|
295
|
+
"--command-text",
|
|
296
|
+
"/se:apply",
|
|
297
|
+
]
|
|
298
|
+
)
|
|
299
|
+
if "apply_phase=implementing" not in apply_output:
|
|
300
|
+
raise AssertionError("/se:apply did not enter implementing phase")
|
|
301
|
+
|
|
302
|
+
run([sys.executable, str(RUN_WORKFLOW), "finish-implement", "--workspace", str(workspace)])
|
|
303
|
+
session = read_json(workspace / ".super-engineer" / "current-session.json")
|
|
304
|
+
data_dir = Path(session["data_dir"])
|
|
305
|
+
plan_summary = read_json(data_dir / "plan-summary.json")
|
|
306
|
+
verify = read_json(data_dir / "verify.json")
|
|
307
|
+
status = read_json(data_dir / "status.json")
|
|
308
|
+
notification = read_json(data_dir / "notification.json")
|
|
309
|
+
|
|
310
|
+
if not plan_summary.get("target_codebases"):
|
|
311
|
+
raise AssertionError("plan-summary.json missing target_codebases")
|
|
312
|
+
if verify.get("result") != "通过":
|
|
313
|
+
raise AssertionError("verify did not pass")
|
|
314
|
+
stdout = verify["sections"][0]["stdout"]
|
|
315
|
+
stderr = verify["sections"][0]["stderr"]
|
|
316
|
+
if "已省略" not in stdout or "已省略" not in stderr:
|
|
317
|
+
raise AssertionError("verify stdout/stderr were not compacted")
|
|
318
|
+
if len(stdout) > 12200 or len(stderr) > 12200:
|
|
319
|
+
raise AssertionError("verify stdout/stderr compaction exceeded expected size")
|
|
320
|
+
if status.get("phase") != "done":
|
|
321
|
+
raise AssertionError("todo auto session did not finish with done status")
|
|
322
|
+
if notification.get("status") != "skipped":
|
|
323
|
+
raise AssertionError("notification should be marked skipped when no provider is configured")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def run(command: list[str], check: bool = True, env: dict[str, str] | None = None):
|
|
327
|
+
result = subprocess.run(
|
|
328
|
+
command,
|
|
329
|
+
cwd=REPO_ROOT,
|
|
330
|
+
env=env,
|
|
331
|
+
text=True,
|
|
332
|
+
stdout=subprocess.PIPE,
|
|
333
|
+
stderr=subprocess.STDOUT,
|
|
334
|
+
check=False,
|
|
335
|
+
)
|
|
336
|
+
if check:
|
|
337
|
+
if result.returncode != 0:
|
|
338
|
+
print(result.stdout)
|
|
339
|
+
raise SystemExit(result.returncode)
|
|
340
|
+
return result.stdout
|
|
341
|
+
return CommandResult(result.returncode, result.stdout)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class CommandResult:
|
|
345
|
+
def __init__(self, returncode: int, output: str) -> None:
|
|
346
|
+
self.returncode = returncode
|
|
347
|
+
self.output = output
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def read_text(path: Path) -> str:
|
|
351
|
+
if not path.is_file():
|
|
352
|
+
raise AssertionError(f"missing file: {path}")
|
|
353
|
+
return path.read_text(encoding="utf-8")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def read_json(path: Path) -> dict:
|
|
357
|
+
if not path.is_file():
|
|
358
|
+
raise AssertionError(f"missing file: {path}")
|
|
359
|
+
with path.open(encoding="utf-8") as handle:
|
|
360
|
+
data = json.load(handle)
|
|
361
|
+
if not isinstance(data, dict):
|
|
362
|
+
raise AssertionError(f"json root is not object: {path}")
|
|
363
|
+
return data
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
if __name__ == "__main__":
|
|
367
|
+
if shutil.which("node") is None:
|
|
368
|
+
raise SystemExit("node is required")
|
|
369
|
+
main()
|
|
@@ -319,6 +319,8 @@ AI 在 `/se:bridge` 完成后必须停止,只能提示用户审核 `todo.md`
|
|
|
319
319
|
内部动作:
|
|
320
320
|
|
|
321
321
|
- 执行 `python3 scripts/run-workflow.py plan`
|
|
322
|
+
- 如果当前 session 已有标准 `plan.json` 且仍停留在计划确认阶段,脚本必须复用当前 session
|
|
323
|
+
- 如果当前 session 已进入实现、自查、审查或验证阶段,脚本必须拒绝重复 `/se:plan`,不能创建第二个 session
|
|
322
324
|
|
|
323
325
|
完成后汇报:
|
|
324
326
|
|
|
@@ -350,7 +352,8 @@ AI 在 `/se:bridge` 完成后必须停止,只能提示用户审核 `todo.md`
|
|
|
350
352
|
|
|
351
353
|
内部动作:
|
|
352
354
|
|
|
353
|
-
- 如果没有当前 session
|
|
355
|
+
- 如果没有当前 session、当前 session 失效,或当前 session 缺少标准 `plan.json`,先执行 `python3 scripts/run-workflow.py plan`
|
|
356
|
+
- 如果当前 session 已有标准 `plan.json` 且未完成,必须复用当前 session
|
|
354
357
|
- 执行 `python3 scripts/run-workflow.py start-implement`
|
|
355
358
|
- 按当前 `plan.json` 实现代码
|
|
356
359
|
- 执行 `python3 scripts/run-workflow.py finish-implement`
|
|
@@ -363,6 +366,7 @@ AI 在 `/se:bridge` 完成后必须停止,只能提示用户审核 `todo.md`
|
|
|
363
366
|
- `todo` 模式下也必须通过当前 session 的 `status.json`、`todo-state.json` 和标准产物来源校验,不能复用旧需求 session
|
|
364
367
|
- `todo` 模式的状态不能写入 OpenSpec 专用 `se-state.json`
|
|
365
368
|
- `todo` 模式下如果 `current-session.json` 指向旧 `output_dir`,`/se:apply` 必须重新创建标准 session
|
|
369
|
+
- AI 不得在 `/se:apply` 链路中额外手工执行第二次 `/se:plan`;脚本检测到活跃交付 session 时必须拒绝重复计划
|
|
366
370
|
- AI 只能在 `start-implement` 和 `finish-implement` 之间修改业务代码
|
|
367
371
|
- AI 不得直接写 `.super-engineer` 下的状态 JSON
|
|
368
372
|
- AI 不得直接写 output 下的标准 Markdown 报告
|
|
@@ -161,7 +161,9 @@ OpenSpec change 名称必须通过 `/se:propose <change-name>` 显式指定。
|
|
|
161
161
|
- `/se:verify` 通过后状态为 `verified`,只允许 `/se:archive-check`
|
|
162
162
|
- `/se:archive-check` 通过后状态为 `archive_ready`,只允许 `/se:archive`
|
|
163
163
|
- 所有阶段推进必须先通过 `run-workflow.py validate-state <command>` 等价校验,不能只依赖 AI 回复
|
|
164
|
-
-
|
|
164
|
+
- `plan` 只有在没有有效计划会话、当前会话已完成/归档/阻塞,或当前会话失效时才创建新的 `session_id`
|
|
165
|
+
- 如果当前 session 已有 `plan.json` 且仍停留在计划确认阶段,重复执行 `plan` 必须复用当前 session,不能创建新 session
|
|
166
|
+
- 如果当前 session 已进入 `implement`、`self_check`、`review`、`verify` 等交付阶段,重复执行 `plan` 必须被脚本拒绝
|
|
165
167
|
- 新会话不能覆盖历史会话目录
|
|
166
168
|
- `current-session.json` 只指向当前正在推进的会话
|
|
167
169
|
- 后续 `start-implement`、`finish-implement`、`review`、`verify`、`status` 都基于当前会话执行
|
|
@@ -665,6 +665,47 @@ def current_session_meta(config: dict[str, Any]) -> dict[str, Any]:
|
|
|
665
665
|
return normalized
|
|
666
666
|
|
|
667
667
|
|
|
668
|
+
def current_session_status(config: dict[str, Any], session_meta: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
669
|
+
meta = session_meta or current_session_meta(config)
|
|
670
|
+
status = read_json(data_artifact_path(config, "status.json", meta), {})
|
|
671
|
+
return status if isinstance(status, dict) else {}
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def active_session_for_plan(config: dict[str, Any]) -> dict[str, Any] | None:
|
|
675
|
+
if current_session_is_stale(config):
|
|
676
|
+
return None
|
|
677
|
+
try:
|
|
678
|
+
session_meta = current_session_meta(config)
|
|
679
|
+
except FileNotFoundError:
|
|
680
|
+
return None
|
|
681
|
+
if not data_artifact_path(config, "plan.json", session_meta).exists():
|
|
682
|
+
return None
|
|
683
|
+
status = current_session_status(config, session_meta)
|
|
684
|
+
status_phase = str(status.get("phase", "") or "").strip()
|
|
685
|
+
if status_phase in ("done", "archived", "blocked"):
|
|
686
|
+
return None
|
|
687
|
+
return {
|
|
688
|
+
"session": session_meta,
|
|
689
|
+
"status": status,
|
|
690
|
+
"phase": status_phase or "plan",
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def ensure_plan_can_run(config: dict[str, Any]) -> dict[str, Any] | None:
|
|
695
|
+
active = active_session_for_plan(config)
|
|
696
|
+
if not active:
|
|
697
|
+
return None
|
|
698
|
+
phase = str(active.get("phase", "")).strip()
|
|
699
|
+
session = active.get("session", {})
|
|
700
|
+
if phase in ("plan", "wait_confirm_plan"):
|
|
701
|
+
return active
|
|
702
|
+
raise RuntimeError(
|
|
703
|
+
f"当前已有活跃 session 正在交付中,禁止重新执行 /se:plan:"
|
|
704
|
+
f"session_id={session.get('session_id', '')}, phase={phase}。"
|
|
705
|
+
"请继续当前 /se:apply 链路,不要创建新的 plan/session。"
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
668
709
|
def data_artifact_path(config: dict[str, Any], name: str, session_meta: dict[str, Any] | None = None) -> Path:
|
|
669
710
|
meta = _normalize_session_meta(config, session_meta or current_session_meta(config))
|
|
670
711
|
return Path(meta["data_dir"]) / name
|
|
@@ -1016,6 +1057,8 @@ def validate_se_state(config: dict[str, Any], run_command: str) -> dict[str, Any
|
|
|
1016
1057
|
elif run_command in ("plan", "apply"):
|
|
1017
1058
|
if not todo_path(config).exists():
|
|
1018
1059
|
errors.append("缺少 todo_file,请先执行 /se:init 或补充 todo.md。")
|
|
1060
|
+
if run_command == "plan" and phase in ("implementing", "self_checked", "reviewed"):
|
|
1061
|
+
errors.append("当前已有活跃 session 正在交付中,不能重新执行 /se:plan。请继续当前 /se:apply、/se:review 或 /se:verify。")
|
|
1019
1062
|
elif run_command == "start-implement":
|
|
1020
1063
|
if phase not in ("planned", "implementing", "blocked"):
|
|
1021
1064
|
errors.append("当前状态不允许进入实现,请先执行 /se:plan。")
|
|
@@ -1065,7 +1108,16 @@ def validate_se_state(config: dict[str, Any], run_command: str) -> dict[str, Any
|
|
|
1065
1108
|
for key in ("proposal", "design", "tasks"):
|
|
1066
1109
|
if not _se_state_artifact_exists(str(artifacts.get(key, ""))):
|
|
1067
1110
|
errors.append(f"缺少 OpenSpec 产物:{key}")
|
|
1068
|
-
elif se_command
|
|
1111
|
+
elif se_command == "/se:plan":
|
|
1112
|
+
if phase not in ("bridged", "planned", "blocked") and se_command not in allowed_next:
|
|
1113
|
+
errors.append("当前状态不允许重新计划,请先完成 /se:bridge,或继续当前活跃交付会话。")
|
|
1114
|
+
if phase in ("implementing", "self_checked", "reviewed", "verified"):
|
|
1115
|
+
errors.append("当前已有活跃 session 正在交付中,不能重新执行 /se:plan。请继续当前 /se:apply、/se:review 或 /se:verify。")
|
|
1116
|
+
if phase == "proposed":
|
|
1117
|
+
errors.append("/se:propose 后不能直接进入交付,必须先执行 /se:bridge。")
|
|
1118
|
+
if not _se_state_artifact_exists(str(artifacts.get("todo", ""))):
|
|
1119
|
+
errors.append("缺少桥接 todo.md,请先执行 /se:bridge。")
|
|
1120
|
+
elif se_command == "/se:apply":
|
|
1069
1121
|
if phase not in ("bridged", "planned", "implementing", "reviewed", "blocked") and se_command not in allowed_next:
|
|
1070
1122
|
errors.append("当前状态不允许进入交付,请先完成 /se:bridge 并人工审核 todo.md。")
|
|
1071
1123
|
if phase == "proposed":
|
|
@@ -11,6 +11,7 @@ from common import (
|
|
|
11
11
|
current_session_is_stale,
|
|
12
12
|
current_session_meta,
|
|
13
13
|
data_artifact_path,
|
|
14
|
+
ensure_plan_can_run,
|
|
14
15
|
ensure_status,
|
|
15
16
|
load_workspace_config,
|
|
16
17
|
now_iso,
|
|
@@ -287,9 +288,22 @@ def command_archive_openspec(workspace: Path | None) -> None:
|
|
|
287
288
|
def command_plan(workspace: Path | None) -> None:
|
|
288
289
|
config = load_workspace_config(workspace)
|
|
289
290
|
require_se_state(config, "plan")
|
|
291
|
+
try:
|
|
292
|
+
active = ensure_plan_can_run(config)
|
|
293
|
+
except RuntimeError as error:
|
|
294
|
+
raise SystemExit(str(error))
|
|
295
|
+
if active:
|
|
296
|
+
session = active.get("session", {})
|
|
297
|
+
print("session_action=reused")
|
|
298
|
+
print(f"session_id={session.get('session_id', '')}")
|
|
299
|
+
print(f"phase={active.get('phase', '')}")
|
|
300
|
+
print("next_action=当前计划已存在,继续执行 /se:apply。")
|
|
301
|
+
return
|
|
290
302
|
command_init(workspace)
|
|
291
303
|
config = load_workspace_config(workspace)
|
|
292
|
-
create_session(config)
|
|
304
|
+
session_meta = create_session(config)
|
|
305
|
+
print("session_action=created")
|
|
306
|
+
print(f"session_id={session_meta.get('session_id', '')}")
|
|
293
307
|
command_discover(workspace)
|
|
294
308
|
args = ["--workspace", str(workspace)] if workspace else []
|
|
295
309
|
run_python("generate-smart-plan.py", args)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: auto
|
|
3
|
+
workflow_source: openspec
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
demand_file: superengineer/${demand_name}/需求.md
|
|
7
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
8
|
+
reference_files:
|
|
9
|
+
- docs/需求分析与实现指南.md
|
|
10
|
+
- docs/前端规范.md
|
|
11
|
+
code_path: __CODE_PATH__
|
|
12
|
+
output_dir: superengineer/${demand_name}/output
|
|
13
|
+
openspec:
|
|
14
|
+
changes_dir: openspec/changes
|
|
15
|
+
verify_commands:
|
|
16
|
+
default: pnpm test && pnpm build
|
|
17
|
+
|
|
18
|
+
# 如果团队使用 npm,可把 verify_commands.default 改成 npm test && npm run build。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: auto
|
|
3
|
+
workflow_source: openspec
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
demand_file: superengineer/${demand_name}/需求.md
|
|
7
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
8
|
+
reference_files:
|
|
9
|
+
- docs/需求分析与实现指南.md
|
|
10
|
+
- docs/开发规范.md
|
|
11
|
+
- docs/接口规范.md
|
|
12
|
+
code_path: __CODE_PATH__
|
|
13
|
+
output_dir: superengineer/${demand_name}/output
|
|
14
|
+
openspec:
|
|
15
|
+
changes_dir: openspec/changes
|
|
16
|
+
verify_commands:
|
|
17
|
+
default: mvn -q test
|
|
18
|
+
|
|
19
|
+
# Spring Boot 多模块项目可把 code_path 指向具体服务目录,或使用 multi-repo 模板。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: auto
|
|
3
|
+
workflow_source: openspec
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
demand_file: superengineer/${demand_name}/需求.md
|
|
7
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
8
|
+
reference_files:
|
|
9
|
+
- docs/需求分析与实现指南.md
|
|
10
|
+
- docs/开发规范.md
|
|
11
|
+
- docs/系统架构.md
|
|
12
|
+
code_path: __CODE_PATH__
|
|
13
|
+
output_dir: superengineer/${demand_name}/output
|
|
14
|
+
openspec:
|
|
15
|
+
changes_dir: openspec/changes
|
|
16
|
+
verify_commands:
|
|
17
|
+
frontend-app: pnpm test && pnpm build
|
|
18
|
+
user-service: mvn -q test
|
|
19
|
+
pricing-service: go test ./...
|
|
20
|
+
|
|
21
|
+
# code_path 指向多个仓库的上层目录时,工作流会结合 todo 与代码结构推断目标仓库。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: auto
|
|
3
|
+
workflow_source: openspec
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
demand_file: superengineer/${demand_name}/需求.md
|
|
7
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
8
|
+
reference_files:
|
|
9
|
+
- docs/需求分析与实现指南.md
|
|
10
|
+
- docs/开发规范.md
|
|
11
|
+
code_path: __CODE_PATH__
|
|
12
|
+
output_dir: superengineer/${demand_name}/output
|
|
13
|
+
openspec:
|
|
14
|
+
changes_dir: openspec/changes
|
|
15
|
+
|
|
16
|
+
# 可选:自动识别不准确时覆盖验证命令
|
|
17
|
+
# verify_commands:
|
|
18
|
+
# default: mvn -q test
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: manual
|
|
3
|
+
workflow_source: openspec
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
demand_file: superengineer/${demand_name}/需求.md
|
|
7
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
8
|
+
reference_files:
|
|
9
|
+
- docs/需求分析与实现指南.md
|
|
10
|
+
- docs/开发规范.md
|
|
11
|
+
code_path: __CODE_PATH__
|
|
12
|
+
output_dir: superengineer/${demand_name}/output
|
|
13
|
+
openspec:
|
|
14
|
+
changes_dir: openspec/changes
|
|
15
|
+
|
|
16
|
+
# manual 模式会在 plan、implement、review 等关键阶段停下来等待确认。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: auto
|
|
3
|
+
workflow_source: todo
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
7
|
+
reference_files:
|
|
8
|
+
- docs/需求分析与实现指南.md
|
|
9
|
+
- docs/开发规范.md
|
|
10
|
+
code_path: __CODE_PATH__
|
|
11
|
+
output_dir: superengineer/${demand_name}/output
|
|
12
|
+
|
|
13
|
+
# todo 模式不使用 OpenSpec change,适合任务已经明确的小需求。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
mode: manual
|
|
3
|
+
workflow_source: todo
|
|
4
|
+
vars:
|
|
5
|
+
demand_name: __DEMAND_NAME__
|
|
6
|
+
todo_file: superengineer/${demand_name}/todo.md
|
|
7
|
+
reference_files:
|
|
8
|
+
- docs/需求分析与实现指南.md
|
|
9
|
+
- docs/开发规范.md
|
|
10
|
+
code_path: __CODE_PATH__
|
|
11
|
+
output_dir: superengineer/${demand_name}/output
|
|
12
|
+
|
|
13
|
+
# manual 模式适合首次接入、复杂需求或需要逐步确认的团队。
|