super-engineer-workflow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/CONTRIBUTING.md +34 -0
- package/LICENSE +21 -0
- package/README.md +300 -0
- package/SECURITY.md +21 -0
- package/bin/super-engineer.js +19 -0
- package/docs/se/345/221/275/344/273/244/345/215/217/350/256/256.md +335 -0
- package/docs//344/270/255/346/226/207/344/275/277/347/224/250/346/211/213/345/206/214.md +707 -0
- package/docs//345/205/254/345/274/200/345/217/221/345/270/203/346/243/200/346/237/245/346/270/205/345/215/225.md +43 -0
- package/docs//345/277/253/351/200/237/345/210/235/345/247/213/345/214/226/345/267/245/344/275/234/345/214/272.md +419 -0
- package/docs//351/241/271/347/233/256/346/236/266/346/236/204/344/270/216/350/256/276/350/256/241/350/257/264/346/230/216.md +657 -0
- package/package.json +55 -0
- package/scripts/se-cli.py +301 -0
- package/scripts/se-setup.py +331 -0
- package/scripts/se-smoke-test.py +86 -0
- package/super-engineer-workflow/SKILL.md +439 -0
- package/super-engineer-workflow/adapters/go.yml +8 -0
- package/super-engineer-workflow/adapters/java-gradle.yml +8 -0
- package/super-engineer-workflow/adapters/java-maven.yml +8 -0
- package/super-engineer-workflow/adapters/node-react.yml +8 -0
- package/super-engineer-workflow/adapters/node-vue.yml +8 -0
- package/super-engineer-workflow/adapters/python.yml +8 -0
- package/super-engineer-workflow/agents/openai.yaml +4 -0
- package/super-engineer-workflow/assets/config-schema.json +100 -0
- package/super-engineer-workflow/assets/config.example.yml +12 -0
- package/super-engineer-workflow/assets/plan-schema.json +362 -0
- package/super-engineer-workflow/assets/status-schema.json +83 -0
- package/super-engineer-workflow/assets/workspace.example.yml +25 -0
- package/super-engineer-workflow/config.example.yml +12 -0
- package/super-engineer-workflow/references/contracts.md +39 -0
- package/super-engineer-workflow/references/execution-modes.md +38 -0
- package/super-engineer-workflow/references/java.md +21 -0
- package/super-engineer-workflow/references/planning.md +45 -0
- package/super-engineer-workflow/references/platform-openclaw.md +10 -0
- package/super-engineer-workflow/references/project-docs.md +7 -0
- package/super-engineer-workflow/references/review-checklist.md +26 -0
- package/super-engineer-workflow/references/se-commands.md +582 -0
- package/super-engineer-workflow/references/verify-checklist.md +45 -0
- package/super-engineer-workflow/references/workflow.md +208 -0
- package/super-engineer-workflow/scripts/archive-openspec.py +110 -0
- package/super-engineer-workflow/scripts/bootstrap-openspec.py +42 -0
- package/super-engineer-workflow/scripts/common.py +3285 -0
- package/super-engineer-workflow/scripts/generate-discovery.py +185 -0
- package/super-engineer-workflow/scripts/generate-review-report.py +296 -0
- package/super-engineer-workflow/scripts/generate-self-check.py +185 -0
- package/super-engineer-workflow/scripts/generate-smart-plan.py +429 -0
- package/super-engineer-workflow/scripts/init-workspace.py +68 -0
- package/super-engineer-workflow/scripts/prepare-archive-openspec.py +186 -0
- package/super-engineer-workflow/scripts/propose-openspec.py +170 -0
- package/super-engineer-workflow/scripts/run-verify-and-report.py +399 -0
- package/super-engineer-workflow/scripts/run-workflow.py +506 -0
- package/super-engineer-workflow/scripts/update-status.py +43 -0
- package/super-engineer-workflow/scripts/writeback-openspec.py +311 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from common import (
|
|
8
|
+
constraint_items,
|
|
9
|
+
current_session_meta,
|
|
10
|
+
data_artifact_path,
|
|
11
|
+
detect_project,
|
|
12
|
+
ensure_status,
|
|
13
|
+
existing_reference_files,
|
|
14
|
+
infer_java_modules,
|
|
15
|
+
is_todo_template_placeholder,
|
|
16
|
+
load_workspace_config,
|
|
17
|
+
now_iso,
|
|
18
|
+
parse_task_blocks,
|
|
19
|
+
parse_task_modules,
|
|
20
|
+
phase_after,
|
|
21
|
+
openspec_bridge_context_path,
|
|
22
|
+
report_artifact_path,
|
|
23
|
+
read_json,
|
|
24
|
+
read_text,
|
|
25
|
+
resolve_target_codebases,
|
|
26
|
+
scan_java_files,
|
|
27
|
+
service_hints,
|
|
28
|
+
summarize_detected_projects,
|
|
29
|
+
summarize_todo,
|
|
30
|
+
todo_path,
|
|
31
|
+
todo_progress,
|
|
32
|
+
unique,
|
|
33
|
+
workspace_root,
|
|
34
|
+
write_managed_json,
|
|
35
|
+
write_managed_text,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_change_steps(summary: str, detected: dict[str, str], impacted_files: list[str], task_breakdown: list[dict[str, object]]) -> list[dict[str, str]]:
|
|
40
|
+
steps: list[dict[str, str]] = [
|
|
41
|
+
{
|
|
42
|
+
"id": "step-1",
|
|
43
|
+
"title": "分析当前实现",
|
|
44
|
+
"details": "结合 todo、参考文件和代码结构确认真实修改范围。",
|
|
45
|
+
"status": "pending",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "step-2",
|
|
49
|
+
"title": "按计划实施修改",
|
|
50
|
+
"details": "严格围绕计划影响文件和业务规则推进实现,必要时先修订计划。",
|
|
51
|
+
"status": "pending",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "step-3",
|
|
55
|
+
"title": "自查并整理差异",
|
|
56
|
+
"details": "在进入正式审查前,先确认改动与需求、风险和测试计划一致。",
|
|
57
|
+
"status": "pending",
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
task_text = " ".join(
|
|
62
|
+
[summary]
|
|
63
|
+
+ [str(task.get("title", "")) for task in task_breakdown]
|
|
64
|
+
+ [str(detail) for task in task_breakdown for detail in task.get("details", [])]
|
|
65
|
+
)
|
|
66
|
+
if any(path.endswith("Test.java") for path in impacted_files) or "测试" in task_text or "校验" in task_text:
|
|
67
|
+
steps.append(
|
|
68
|
+
{
|
|
69
|
+
"id": "step-4",
|
|
70
|
+
"title": "补齐自动化测试",
|
|
71
|
+
"details": "覆盖正常路径、非法输入和回归风险场景。",
|
|
72
|
+
"status": "pending",
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
verify_command = detected.get("verify_command") or "请根据项目实际情况补充验证命令。"
|
|
77
|
+
if verify_command == "multiple":
|
|
78
|
+
verify_command = "按各目标仓库分别执行验证命令。"
|
|
79
|
+
steps.append(
|
|
80
|
+
{
|
|
81
|
+
"id": f"step-{len(steps) + 1}",
|
|
82
|
+
"title": "执行验证",
|
|
83
|
+
"details": f"执行验证命令并记录结论:{verify_command}",
|
|
84
|
+
"status": "pending",
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
return steps
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def build_acceptance_criteria(task_breakdown: list[dict[str, object]], detected: dict[str, str]) -> list[dict[str, object]]:
|
|
91
|
+
criteria: list[dict[str, object]] = []
|
|
92
|
+
for task in task_breakdown:
|
|
93
|
+
title = str(task.get("title", "")).strip()
|
|
94
|
+
details = [str(item) for item in task.get("details", []) if str(item).strip()]
|
|
95
|
+
checks = [f"需求项已实现:{title}"]
|
|
96
|
+
checks.extend(f"子要求已覆盖:{item}" for item in details[:5])
|
|
97
|
+
checks.append("相关边界场景已通过自查或测试覆盖。")
|
|
98
|
+
criteria.append(
|
|
99
|
+
{
|
|
100
|
+
"task_id": str(task.get("id", "")),
|
|
101
|
+
"task_title": title,
|
|
102
|
+
"checks": checks,
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
if not criteria:
|
|
106
|
+
criteria.append(
|
|
107
|
+
{
|
|
108
|
+
"task_id": "workflow",
|
|
109
|
+
"task_title": "本轮工作流",
|
|
110
|
+
"checks": ["todo 中的未完成任务已逐项核对。"],
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
if detected.get("verify_command"):
|
|
114
|
+
criteria.append(
|
|
115
|
+
{
|
|
116
|
+
"task_id": "verify",
|
|
117
|
+
"task_title": "自动化验证",
|
|
118
|
+
"checks": [f"验证命令可执行并通过:{detected['verify_command']}"],
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
return criteria
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def discovery_evidence(discovery: dict, limit: int = 16) -> list[dict[str, object]]:
|
|
125
|
+
evidence: list[dict[str, object]] = []
|
|
126
|
+
for codebase in discovery.get("codebases", []):
|
|
127
|
+
for match in codebase.get("matches", [])[:limit]:
|
|
128
|
+
evidence.append(
|
|
129
|
+
{
|
|
130
|
+
"codebase": codebase.get("name", ""),
|
|
131
|
+
"keyword": match.get("keyword", ""),
|
|
132
|
+
"file": match.get("file", ""),
|
|
133
|
+
"line": match.get("line", 0),
|
|
134
|
+
"snippet": match.get("snippet", ""),
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
if len(evidence) >= limit:
|
|
138
|
+
return evidence
|
|
139
|
+
return evidence
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def confidence_from_discovery(discovery: dict, impacted_files: list[str]) -> str:
|
|
143
|
+
match_count = sum(len(codebase.get("matches", [])) for codebase in discovery.get("codebases", []))
|
|
144
|
+
if match_count >= 5 and impacted_files:
|
|
145
|
+
return "high"
|
|
146
|
+
if match_count > 0 or impacted_files:
|
|
147
|
+
return "medium"
|
|
148
|
+
return "low"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_plan_markdown(plan: dict) -> str:
|
|
152
|
+
detected_project = plan["detected_project"]
|
|
153
|
+
test_command = detected_project["test_command"] if detected_project["test_command"] != "multiple" else "按各仓库分别执行"
|
|
154
|
+
start_command = detected_project["start_command"] if detected_project["start_command"] != "multiple" else "按各仓库实际需要执行"
|
|
155
|
+
verify_command = detected_project["verify_command"] if detected_project["verify_command"] != "multiple" else "按各仓库分别执行"
|
|
156
|
+
build_tool = detected_project["build_tool"] if detected_project["build_tool"] != "multiple" else "多种构建方式"
|
|
157
|
+
lines = [
|
|
158
|
+
"# 变更计划",
|
|
159
|
+
"",
|
|
160
|
+
"## 限制条件",
|
|
161
|
+
]
|
|
162
|
+
lines.extend(f"- {item}" for item in plan.get("constraints", [])) if plan.get("constraints") else lines.append("- 暂无")
|
|
163
|
+
lines.extend([
|
|
164
|
+
"",
|
|
165
|
+
"## Todo 进度",
|
|
166
|
+
f"- 未完成任务:{plan.get('todo_progress', {}).get('pending_task_count', 0)}",
|
|
167
|
+
f"- 已完成任务:{plan.get('todo_progress', {}).get('completed_task_count', 0)}",
|
|
168
|
+
f"- 总任务数:{plan.get('todo_progress', {}).get('total_task_count', 0)}",
|
|
169
|
+
"",
|
|
170
|
+
"## 需求摘要",
|
|
171
|
+
plan["requirement_summary"],
|
|
172
|
+
"",
|
|
173
|
+
"## 任务拆解",
|
|
174
|
+
])
|
|
175
|
+
task_modules = plan.get("task_modules", [])
|
|
176
|
+
if task_modules:
|
|
177
|
+
for module in task_modules:
|
|
178
|
+
lines.append(f"- 模块:{module.get('title') or '未命名模块'}")
|
|
179
|
+
for task in module.get("tasks", []):
|
|
180
|
+
lines.append(f" - 任务:{task.get('title') or '未命名任务'}")
|
|
181
|
+
details = task.get("details", [])
|
|
182
|
+
if details:
|
|
183
|
+
lines.extend(f" - {item}" for item in details)
|
|
184
|
+
else:
|
|
185
|
+
lines.append("- 暂无")
|
|
186
|
+
lines.extend([
|
|
187
|
+
"",
|
|
188
|
+
"## 工作区",
|
|
189
|
+
plan["workspace"],
|
|
190
|
+
"",
|
|
191
|
+
"## 目标代码目录",
|
|
192
|
+
f"- 配置目录:{plan.get('configured_code_path') or '未知'}",
|
|
193
|
+
f"- 实际目录:{plan.get('resolved_code_path') or '未知'}",
|
|
194
|
+
f"- 解析说明:{plan.get('service_resolution', {}).get('selection_reason') or '未提供'}",
|
|
195
|
+
"",
|
|
196
|
+
"## 目标仓库",
|
|
197
|
+
])
|
|
198
|
+
targets = plan.get("target_codebases", [])
|
|
199
|
+
if targets:
|
|
200
|
+
for target in targets:
|
|
201
|
+
lines.extend(
|
|
202
|
+
[
|
|
203
|
+
f"- {target.get('name') or '未知仓库'}",
|
|
204
|
+
f" - 路径:{target.get('path') or '未知'}",
|
|
205
|
+
f" - 语言:{target.get('detected_project', {}).get('language') or '未知'}",
|
|
206
|
+
f" - 构建工具:{target.get('detected_project', {}).get('build_tool') or '未知'}",
|
|
207
|
+
f" - 验证命令:{target.get('detected_project', {}).get('verify_command') or '未识别'}",
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
lines.append("- 暂无")
|
|
212
|
+
lines.extend([
|
|
213
|
+
"",
|
|
214
|
+
"## 使用的参考文件",
|
|
215
|
+
])
|
|
216
|
+
lines.extend(f"- {item}" for item in plan["reference_files_used"]) if plan["reference_files_used"] else lines.append("- 暂无")
|
|
217
|
+
lines.extend(["", "## 检测到的项目信息"])
|
|
218
|
+
lines.extend(
|
|
219
|
+
[
|
|
220
|
+
f"- 语言:{detected_project['language'] or '未知'}",
|
|
221
|
+
f"- 构建工具:{build_tool or '未知'}",
|
|
222
|
+
f"- 测试命令:{test_command or '未识别'}",
|
|
223
|
+
f"- 启动命令:{start_command or '未识别'}",
|
|
224
|
+
f"- 验证命令:{verify_command or '未识别'}",
|
|
225
|
+
"",
|
|
226
|
+
"## 影响模块",
|
|
227
|
+
]
|
|
228
|
+
)
|
|
229
|
+
lines.extend(f"- {item}" for item in plan["impacted_modules"]) if plan["impacted_modules"] else lines.append("- 暂无识别结果")
|
|
230
|
+
lines.extend(["", "## 影响文件"])
|
|
231
|
+
lines.extend(f"- `{item}`" for item in plan["impacted_files"]) if plan["impacted_files"] else lines.append("- 暂无识别结果")
|
|
232
|
+
lines.extend(["", "## 计划置信度", f"- {plan.get('confidence', 'unknown')}"])
|
|
233
|
+
if plan.get("bridge_context"):
|
|
234
|
+
lines.extend(["", "## Bridge Context"])
|
|
235
|
+
bridge_context = plan.get("bridge_context", {})
|
|
236
|
+
lines.extend(f"- {item}" for item in bridge_context.get("business_constraints", [])) if bridge_context.get("business_constraints") else lines.append("- 暂无")
|
|
237
|
+
if bridge_context.get("compatibility_notes"):
|
|
238
|
+
lines.append("- 兼容/发布提示:")
|
|
239
|
+
lines.extend(f" - {item}" for item in bridge_context.get("compatibility_notes", []))
|
|
240
|
+
lines.extend(["", "## 定位证据"])
|
|
241
|
+
evidence = plan.get("evidence", [])
|
|
242
|
+
if evidence:
|
|
243
|
+
for item in evidence:
|
|
244
|
+
lines.append(
|
|
245
|
+
f"- `{item.get('file')}`:{item.get('line')} 命中 `{item.get('keyword')}`:{item.get('snippet')}"
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
lines.append("- 暂无,实施前需要继续定位代码入口")
|
|
249
|
+
lines.extend(["", "## 验收标准"])
|
|
250
|
+
for criterion in plan.get("acceptance_criteria", []):
|
|
251
|
+
lines.append(f"- {criterion.get('task_title')}")
|
|
252
|
+
for check in criterion.get("checks", []):
|
|
253
|
+
lines.append(f" - {check}")
|
|
254
|
+
lines.extend(["", "## 实施切片"])
|
|
255
|
+
for item in plan.get("implementation_slices", []):
|
|
256
|
+
lines.append(f"- {item.get('title')}:{item.get('goal')}")
|
|
257
|
+
lines.extend(["", "## 修改步骤"])
|
|
258
|
+
lines.extend(f"- {item['title']}:{item['details']}" for item in plan["change_steps"])
|
|
259
|
+
lines.extend(["", "## 测试计划"])
|
|
260
|
+
lines.extend(f"- {item}" for item in plan["test_plan"]) if plan["test_plan"] else lines.append("- 暂无")
|
|
261
|
+
lines.extend(["", "## 风险"])
|
|
262
|
+
lines.extend(f"- {item}" for item in plan["risks"]) if plan["risks"] else lines.append("- 暂无")
|
|
263
|
+
lines.extend(["", "## 未知项"])
|
|
264
|
+
lines.extend(f"- {item}" for item in plan["unknowns"]) if plan["unknowns"] else lines.append("- 暂无")
|
|
265
|
+
lines.append("")
|
|
266
|
+
return "\n".join(lines)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def collect_target_plan_data(config: dict, codebases: list[Path]) -> tuple[list[dict], list[str], list[str]]:
|
|
270
|
+
targets: list[dict] = []
|
|
271
|
+
all_impacted_files: list[str] = []
|
|
272
|
+
all_impacted_modules: list[str] = []
|
|
273
|
+
for codebase in codebases:
|
|
274
|
+
detected = detect_project(codebase, config)
|
|
275
|
+
impacted_files: list[str] = []
|
|
276
|
+
impacted_modules: list[str] = []
|
|
277
|
+
if detected["language"] == "java":
|
|
278
|
+
java_files = scan_java_files(codebase)
|
|
279
|
+
impacted_files = java_files[:8]
|
|
280
|
+
impacted_modules = infer_java_modules(impacted_files)
|
|
281
|
+
targets.append(
|
|
282
|
+
{
|
|
283
|
+
"name": codebase.name,
|
|
284
|
+
"path": str(codebase),
|
|
285
|
+
"detected_project": detected,
|
|
286
|
+
"impacted_modules": impacted_modules,
|
|
287
|
+
"impacted_files": impacted_files,
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
all_impacted_files.extend(impacted_files)
|
|
291
|
+
all_impacted_modules.extend(impacted_modules)
|
|
292
|
+
return targets, unique(all_impacted_files), unique(all_impacted_modules)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def merge_discovery_files(discovery: dict, fallback_files: list[str]) -> list[str]:
|
|
296
|
+
files: list[str] = []
|
|
297
|
+
for codebase in discovery.get("codebases", []):
|
|
298
|
+
for match in codebase.get("matches", []):
|
|
299
|
+
file_path = str(match.get("file", "")).strip()
|
|
300
|
+
if file_path:
|
|
301
|
+
files.append(file_path)
|
|
302
|
+
files.extend(fallback_files)
|
|
303
|
+
return unique(files)[:24]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def build_implementation_slices(task_breakdown: list[dict[str, object]], impacted_files: list[str]) -> list[dict[str, object]]:
|
|
307
|
+
slices: list[dict[str, object]] = []
|
|
308
|
+
for index, task in enumerate(task_breakdown, start=1):
|
|
309
|
+
slices.append(
|
|
310
|
+
{
|
|
311
|
+
"id": f"slice-{index}",
|
|
312
|
+
"title": str(task.get("title", "")).strip() or f"任务 {index}",
|
|
313
|
+
"goal": "完成该任务的最小可验证实现,并同步更新相关测试或人工验证项。",
|
|
314
|
+
"candidate_files": impacted_files[:8],
|
|
315
|
+
"status": "pending",
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
return slices
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def main() -> None:
|
|
322
|
+
parser = argparse.ArgumentParser(description="根据工作区 todo 和仓库上下文生成智能计划。")
|
|
323
|
+
parser.add_argument("--workspace", help="工作空间路径,默认读取当前目录")
|
|
324
|
+
args = parser.parse_args()
|
|
325
|
+
|
|
326
|
+
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
327
|
+
config = load_workspace_config(workspace)
|
|
328
|
+
session_meta = current_session_meta(config)
|
|
329
|
+
todo_file = todo_path(config)
|
|
330
|
+
if not todo_file.exists():
|
|
331
|
+
raise SystemExit(f"未找到 todo 文件:{todo_file}")
|
|
332
|
+
|
|
333
|
+
todo_text = read_text(todo_file)
|
|
334
|
+
if is_todo_template_placeholder(todo_text):
|
|
335
|
+
raise SystemExit(
|
|
336
|
+
f"已自动创建或检测到示例 todo 模板:{todo_file}。"
|
|
337
|
+
"请先完善真实需求内容后,再重新执行 plan。"
|
|
338
|
+
)
|
|
339
|
+
codebases, resolution = resolve_target_codebases(config, todo_text)
|
|
340
|
+
constraints = constraint_items(todo_text)
|
|
341
|
+
task_breakdown = parse_task_blocks(todo_text)
|
|
342
|
+
task_modules = parse_task_modules(todo_text)
|
|
343
|
+
progress = todo_progress(todo_text)
|
|
344
|
+
if not task_breakdown:
|
|
345
|
+
raise SystemExit("todo.md 中没有未完成任务,请检查是否已经全部标记完成。")
|
|
346
|
+
summary = summarize_todo(todo_text)
|
|
347
|
+
docs = existing_reference_files(config)
|
|
348
|
+
target_codebases, impacted_files, impacted_modules = collect_target_plan_data(config, codebases)
|
|
349
|
+
discovery = read_json(data_artifact_path(config, "discovery.json", session_meta), {})
|
|
350
|
+
bridge_context = read_json(openspec_bridge_context_path(config), {})
|
|
351
|
+
impacted_files = merge_discovery_files(discovery, impacted_files)
|
|
352
|
+
impacted_modules = infer_java_modules(impacted_files) or impacted_modules
|
|
353
|
+
detected = summarize_detected_projects([item["detected_project"] for item in target_codebases])
|
|
354
|
+
primary_codebase = codebases[0]
|
|
355
|
+
test_plan = []
|
|
356
|
+
for target in target_codebases:
|
|
357
|
+
verify_command = target["detected_project"].get("verify_command", "")
|
|
358
|
+
if verify_command:
|
|
359
|
+
test_plan.append(f"{target['name']}:{verify_command}")
|
|
360
|
+
if not test_plan:
|
|
361
|
+
test_plan.append("请根据项目实际情况补充测试命令。")
|
|
362
|
+
test_plan.append("覆盖本轮需求涉及的正常路径、边界场景和回归风险。")
|
|
363
|
+
|
|
364
|
+
plan = {
|
|
365
|
+
"session_id": session_meta["session_id"],
|
|
366
|
+
"source": "run-workflow.py plan",
|
|
367
|
+
"schema_version": 1,
|
|
368
|
+
"constraints": constraints,
|
|
369
|
+
"todo_progress": progress,
|
|
370
|
+
"task_modules": task_modules,
|
|
371
|
+
"task_breakdown": task_breakdown,
|
|
372
|
+
"requirement_summary": summary,
|
|
373
|
+
"assumptions": [
|
|
374
|
+
"优先以 todo 和已配置参考文件作为需求边界判断依据。",
|
|
375
|
+
"如果实现过程中发现计划范围不准确,应先更新计划再继续修改代码。",
|
|
376
|
+
],
|
|
377
|
+
"workspace": str(workspace),
|
|
378
|
+
"configured_code_path": str(config["code_path"]),
|
|
379
|
+
"resolved_code_path": str(primary_codebase),
|
|
380
|
+
"resolved_code_paths": [str(item) for item in codebases],
|
|
381
|
+
"service_hints": resolution.get("service_hints") or service_hints(todo_text),
|
|
382
|
+
"service_resolution": resolution,
|
|
383
|
+
"target_codebases": target_codebases,
|
|
384
|
+
"reference_files_used": docs,
|
|
385
|
+
"detected_project": detected,
|
|
386
|
+
"impacted_modules": impacted_modules,
|
|
387
|
+
"impacted_files": impacted_files,
|
|
388
|
+
"change_steps": build_change_steps(summary, detected, impacted_files, task_breakdown),
|
|
389
|
+
"confidence": confidence_from_discovery(discovery, impacted_files),
|
|
390
|
+
"evidence": discovery_evidence(discovery),
|
|
391
|
+
"bridge_context": bridge_context,
|
|
392
|
+
"acceptance_criteria": build_acceptance_criteria(task_breakdown, detected),
|
|
393
|
+
"implementation_slices": build_implementation_slices(task_breakdown, impacted_files),
|
|
394
|
+
"test_plan": test_plan,
|
|
395
|
+
"risks": [
|
|
396
|
+
"如果参考文件过时,计划可能低估真实影响范围。",
|
|
397
|
+
"如果当前仓库缺少测试覆盖,回归风险需要靠人工复核兜底。",
|
|
398
|
+
"如果 todo 中服务名描述不准确,可能会选错目标仓库,需要在计划阶段先确认。",
|
|
399
|
+
"如果本轮需求涉及多个独立仓库,需要逐仓校验计划、改动和验证结果是否一致。",
|
|
400
|
+
],
|
|
401
|
+
"unknowns": [
|
|
402
|
+
"需要确认 todo 中未写明但代码行为依赖的隐含业务规则。",
|
|
403
|
+
],
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
write_managed_json(config, data_artifact_path(config, "plan.json", session_meta), plan)
|
|
407
|
+
write_managed_text(config, report_artifact_path(config, "plan.md", session_meta), build_plan_markdown(plan) + "\n")
|
|
408
|
+
|
|
409
|
+
status = ensure_status(config, session_meta, read_json(data_artifact_path(config, "status.json", session_meta), {}))
|
|
410
|
+
phase, awaiting_confirmation, pending_for, next_action = phase_after("plan", config["mode"])
|
|
411
|
+
status.update(
|
|
412
|
+
{
|
|
413
|
+
"phase": phase,
|
|
414
|
+
"current_task": "已生成变更计划。",
|
|
415
|
+
"progress": 25,
|
|
416
|
+
"awaiting_confirmation": awaiting_confirmation,
|
|
417
|
+
"pending_confirmation_for": pending_for,
|
|
418
|
+
"next_action": next_action,
|
|
419
|
+
"completed_tasks": list(dict.fromkeys(status.get("completed_tasks", []) + ["已生成变更计划"])),
|
|
420
|
+
"blocked_tasks": status.get("blocked_tasks", []),
|
|
421
|
+
"started_at": status.get("started_at") or session_meta.get("started_at", ""),
|
|
422
|
+
"updated_at": now_iso(),
|
|
423
|
+
}
|
|
424
|
+
)
|
|
425
|
+
write_managed_json(config, data_artifact_path(config, "status.json", session_meta), status)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
if __name__ == "__main__":
|
|
429
|
+
main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from common import (
|
|
8
|
+
ensure_runtime_dirs,
|
|
9
|
+
ensure_workflow_inputs,
|
|
10
|
+
load_workspace_config,
|
|
11
|
+
read_text,
|
|
12
|
+
todo_path,
|
|
13
|
+
code_root,
|
|
14
|
+
output_dir,
|
|
15
|
+
workflow_source,
|
|
16
|
+
workspace_root,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> None:
|
|
21
|
+
parser = argparse.ArgumentParser(description="初始化 super-engineer 工作区基础目录。")
|
|
22
|
+
parser.add_argument("--workspace", help="工作空间路径,默认读取当前目录")
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
26
|
+
config = load_workspace_config(workspace)
|
|
27
|
+
if not workspace.exists():
|
|
28
|
+
raise SystemExit(f"工作区不存在:{workspace}")
|
|
29
|
+
if not code_root(config).exists():
|
|
30
|
+
raise SystemExit(f"代码目录不存在:{code_root(config)}")
|
|
31
|
+
|
|
32
|
+
ensure_runtime_dirs(config)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
input_result = ensure_workflow_inputs(config)
|
|
36
|
+
except (FileNotFoundError, ValueError) as error:
|
|
37
|
+
raise SystemExit(str(error))
|
|
38
|
+
todo_file = todo_path(config)
|
|
39
|
+
print(f"workflow_source={workflow_source(config)}")
|
|
40
|
+
print(f"todo_created={'true' if input_result.get('todo_created') else 'false'}")
|
|
41
|
+
print(f"bridge_generated={'true' if input_result.get('bridge_generated') else 'false'}")
|
|
42
|
+
if input_result.get("bridge_source"):
|
|
43
|
+
print(f"bridge_source={input_result.get('bridge_source')}")
|
|
44
|
+
todo_text = read_text(todo_file)
|
|
45
|
+
print(f"todo_needs_edit={'true' if input_result.get('todo_needs_edit') else 'false'}")
|
|
46
|
+
|
|
47
|
+
print(f"workspace={workspace}")
|
|
48
|
+
print(f"todo={todo_file}")
|
|
49
|
+
print(f"code_path={code_root(config)}")
|
|
50
|
+
print(f"data_root={workspace / '.super-engineer'}")
|
|
51
|
+
print(f"output_dir={output_dir(config)}")
|
|
52
|
+
print(f"skill_config={config.get('__skill_config_path', '')}")
|
|
53
|
+
if todo_text.strip():
|
|
54
|
+
print("todo_status=ready")
|
|
55
|
+
|
|
56
|
+
if bool(config.get("__skill_config_created", False)):
|
|
57
|
+
print("skill_config_created=true")
|
|
58
|
+
raise SystemExit(
|
|
59
|
+
"已自动生成 Skill 配置文件:"
|
|
60
|
+
f"{config.get('__skill_config_path', '')}。"
|
|
61
|
+
"当前工作流已暂停。"
|
|
62
|
+
"请先完善配置后再继续。"
|
|
63
|
+
)
|
|
64
|
+
print("skill_config_created=false")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from common import (
|
|
8
|
+
current_session_meta,
|
|
9
|
+
data_artifact_path,
|
|
10
|
+
ensure_status,
|
|
11
|
+
file_sha256,
|
|
12
|
+
is_standard_workflow_notification,
|
|
13
|
+
load_workspace_config,
|
|
14
|
+
openspec_change_dir,
|
|
15
|
+
openspec_writeback_dir,
|
|
16
|
+
collect_openspec_cli_context,
|
|
17
|
+
read_json,
|
|
18
|
+
update_se_state,
|
|
19
|
+
workflow_source,
|
|
20
|
+
workspace_root,
|
|
21
|
+
write_managed_json,
|
|
22
|
+
write_managed_text,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def detect_spec_conflicts(summary: dict) -> list[dict[str, object]]:
|
|
27
|
+
bridge_context = summary.get("bridge_context", {})
|
|
28
|
+
conflicts: list[dict[str, object]] = []
|
|
29
|
+
for item in bridge_context.get("spec_merge_targets", []):
|
|
30
|
+
target = Path(str(item.get("target", "")))
|
|
31
|
+
baseline = str(item.get("target_sha256", ""))
|
|
32
|
+
current = file_sha256(target)
|
|
33
|
+
target_exists = bool(item.get("target_exists", False))
|
|
34
|
+
if not target_exists and target.exists():
|
|
35
|
+
conflicts.append(
|
|
36
|
+
{
|
|
37
|
+
"relative_path": str(item.get("relative_path", "")),
|
|
38
|
+
"reason": "计划阶段目标 spec 不存在,但归档前已出现同路径文件",
|
|
39
|
+
"target": str(target),
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
elif target_exists and baseline and current and baseline != current:
|
|
43
|
+
conflicts.append(
|
|
44
|
+
{
|
|
45
|
+
"relative_path": str(item.get("relative_path", "")),
|
|
46
|
+
"reason": "目标 spec 自计划阶段以来已发生变化",
|
|
47
|
+
"target": str(target),
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
return conflicts
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def notification_blockers(config: dict, summary: dict) -> list[str]:
|
|
54
|
+
session_meta = current_session_meta(config)
|
|
55
|
+
status = ensure_status(config, session_meta, read_json(data_artifact_path(config, "status.json", session_meta), {}))
|
|
56
|
+
verify = read_json(data_artifact_path(config, "verify.json", session_meta), {})
|
|
57
|
+
notification = read_json(data_artifact_path(config, "notification.json", session_meta), {})
|
|
58
|
+
overall_result = str(verify.get("result") or summary.get("verify", {}).get("result", "")).strip()
|
|
59
|
+
if overall_result != "通过":
|
|
60
|
+
return ["verify 未通过,不能归档"]
|
|
61
|
+
if not is_standard_workflow_notification(config, session_meta, status, overall_result, notification):
|
|
62
|
+
return ["缺少标准 notification.json,或通知不是由 run-workflow.py verify 发送"]
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def build_markdown(payload: dict) -> str:
|
|
67
|
+
blockers = payload.get("blockers", [])
|
|
68
|
+
lines = [
|
|
69
|
+
"# Archive Input",
|
|
70
|
+
"",
|
|
71
|
+
"## Summary",
|
|
72
|
+
f"- change: {payload.get('change_name', '')}",
|
|
73
|
+
f"- archive_ready: {payload.get('archive_ready', False)}",
|
|
74
|
+
"",
|
|
75
|
+
"## Blockers",
|
|
76
|
+
]
|
|
77
|
+
if blockers:
|
|
78
|
+
lines.extend(f"- {item}" for item in blockers)
|
|
79
|
+
else:
|
|
80
|
+
lines.append("- 暂无")
|
|
81
|
+
lines.extend([
|
|
82
|
+
"",
|
|
83
|
+
"## Spec Impacts",
|
|
84
|
+
])
|
|
85
|
+
impacts = payload.get("spec_impacts", [])
|
|
86
|
+
if impacts:
|
|
87
|
+
lines.extend(f"- {item}" for item in impacts)
|
|
88
|
+
else:
|
|
89
|
+
lines.append("- 暂无")
|
|
90
|
+
lines.extend([
|
|
91
|
+
"",
|
|
92
|
+
"## Acceptance",
|
|
93
|
+
])
|
|
94
|
+
acceptance = payload.get("acceptance_result", [])
|
|
95
|
+
if acceptance:
|
|
96
|
+
lines.extend(f"- {item.get('task_title', '')}: {item.get('status', '')}" for item in acceptance)
|
|
97
|
+
else:
|
|
98
|
+
lines.append("- 暂无")
|
|
99
|
+
lines.extend([
|
|
100
|
+
"",
|
|
101
|
+
"## Merge Mode",
|
|
102
|
+
f"- {payload.get('merge_mode', '')}",
|
|
103
|
+
"",
|
|
104
|
+
"## Conflicts",
|
|
105
|
+
])
|
|
106
|
+
conflicts = payload.get("spec_conflicts", [])
|
|
107
|
+
if conflicts:
|
|
108
|
+
lines.extend(f"- {item.get('relative_path', '')}: {item.get('reason', '')}" for item in conflicts)
|
|
109
|
+
else:
|
|
110
|
+
lines.append("- 暂无")
|
|
111
|
+
lines.append("")
|
|
112
|
+
return "\n".join(lines)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main() -> None:
|
|
116
|
+
parser = argparse.ArgumentParser(description="根据 OpenSpec 回写结果生成 archive 输入。")
|
|
117
|
+
parser.add_argument("--workspace", help="工作空间路径,默认读取当前目录")
|
|
118
|
+
args = parser.parse_args()
|
|
119
|
+
|
|
120
|
+
workspace = workspace_root(Path(args.workspace).expanduser() if args.workspace else None)
|
|
121
|
+
config = load_workspace_config(workspace)
|
|
122
|
+
if workflow_source(config) != "openspec":
|
|
123
|
+
raise SystemExit("当前 workspace.yml 未启用 OpenSpec 模式,无需执行 prepare-archive-openspec。")
|
|
124
|
+
|
|
125
|
+
writeback_dir = openspec_writeback_dir(config)
|
|
126
|
+
summary = read_json(writeback_dir / "execution-summary.json", {})
|
|
127
|
+
if not summary:
|
|
128
|
+
raise SystemExit(f"未找到 execution-summary.json:{writeback_dir / 'execution-summary.json'}")
|
|
129
|
+
|
|
130
|
+
blockers = list(summary.get("archive_blockers", []))
|
|
131
|
+
if not summary.get("task_mapping"):
|
|
132
|
+
blockers.append("缺少 OpenSpec task -> todo -> evidence 映射")
|
|
133
|
+
acceptance_result = summary.get("acceptance_result", [])
|
|
134
|
+
if any(item.get("status") != "passed" for item in acceptance_result):
|
|
135
|
+
blockers.append("存在未通过的验收项")
|
|
136
|
+
blockers.extend(notification_blockers(config, summary))
|
|
137
|
+
spec_conflicts = detect_spec_conflicts(summary)
|
|
138
|
+
if spec_conflicts:
|
|
139
|
+
blockers.append("存在 spec merge 冲突,请先人工处理")
|
|
140
|
+
merge_mode = "safe_merge" if not spec_conflicts else "manual_merge_required"
|
|
141
|
+
openspec_cli = collect_openspec_cli_context(config, include_archive=True)
|
|
142
|
+
status_json = ((openspec_cli.get("status") or {}).get("json") or {})
|
|
143
|
+
artifacts = status_json.get("artifacts", []) if isinstance(status_json, dict) else []
|
|
144
|
+
incomplete_artifacts = []
|
|
145
|
+
for item in artifacts:
|
|
146
|
+
status = str(item.get("status", "")).strip()
|
|
147
|
+
artifact_id = str(item.get("id") or item.get("artifact") or item.get("name") or "").strip()
|
|
148
|
+
if status and status != "done":
|
|
149
|
+
incomplete_artifacts.append({"id": artifact_id, "status": status})
|
|
150
|
+
if incomplete_artifacts:
|
|
151
|
+
blockers.append("OpenSpec status 存在未完成 artifact")
|
|
152
|
+
|
|
153
|
+
payload = {
|
|
154
|
+
"change_name": summary.get("change_name", ""),
|
|
155
|
+
"change_dir": str(openspec_change_dir(config)),
|
|
156
|
+
"archive_ready": not blockers and bool(summary.get("archive_ready")),
|
|
157
|
+
"blockers": blockers,
|
|
158
|
+
"review_result": summary.get("review", {}).get("result", ""),
|
|
159
|
+
"verify_result": summary.get("verify", {}).get("result", ""),
|
|
160
|
+
"spec_impacts": summary.get("spec_impacts", []),
|
|
161
|
+
"spec_conflicts": spec_conflicts,
|
|
162
|
+
"merge_mode": merge_mode,
|
|
163
|
+
"acceptance_result": acceptance_result,
|
|
164
|
+
"residual_risks": summary.get("residual_risks", []),
|
|
165
|
+
"reports": summary.get("reports", {}),
|
|
166
|
+
"openspec_cli": openspec_cli,
|
|
167
|
+
"openspec_incomplete_artifacts": incomplete_artifacts,
|
|
168
|
+
}
|
|
169
|
+
write_managed_json(config, writeback_dir / "archive-input.json", payload)
|
|
170
|
+
write_managed_text(config, writeback_dir / "merge-preview.md", build_markdown(payload))
|
|
171
|
+
update_se_state(
|
|
172
|
+
config,
|
|
173
|
+
phase="archive_ready" if payload["archive_ready"] and merge_mode == "safe_merge" else "blocked",
|
|
174
|
+
last_command="/se:archive-check",
|
|
175
|
+
artifacts={
|
|
176
|
+
"archive_input": str(writeback_dir / "archive-input.json"),
|
|
177
|
+
"merge_preview": str(writeback_dir / "merge-preview.md"),
|
|
178
|
+
},
|
|
179
|
+
blocked_reason="; ".join(blockers),
|
|
180
|
+
)
|
|
181
|
+
print(f"archive_ready={str(payload['archive_ready']).lower()}")
|
|
182
|
+
print(f"writeback_dir={writeback_dir}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|