@hunyed15/codecgc 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/.claude/hooks/route-edit.ps1 +86 -0
- package/INSTALLATION.md +550 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/bin/cgc-build.js +4 -0
- package/bin/cgc-doctor.js +4 -0
- package/bin/cgc-entry.js +4 -0
- package/bin/cgc-external-audit.js +4 -0
- package/bin/cgc-fix.js +4 -0
- package/bin/cgc-history.js +4 -0
- package/bin/cgc-install.js +4 -0
- package/bin/cgc-lifecycle.js +4 -0
- package/bin/cgc-package-audit.js +4 -0
- package/bin/cgc-plan.js +4 -0
- package/bin/cgc-release-readiness.js +4 -0
- package/bin/cgc-review.js +4 -0
- package/bin/cgc-route.js +4 -0
- package/bin/cgc-status.js +4 -0
- package/bin/cgc-test.js +4 -0
- package/bin/cgc.js +4 -0
- package/bin/codecgc.js +1284 -0
- package/codecgc/cgc/SKILL.md +46 -0
- package/codecgc/cgc-arch/SKILL.md +61 -0
- package/codecgc/cgc-build/SKILL.md +53 -0
- package/codecgc/cgc-decide/SKILL.md +55 -0
- package/codecgc/cgc-fix/SKILL.md +47 -0
- package/codecgc/cgc-learn/SKILL.md +46 -0
- package/codecgc/cgc-onboard/SKILL.md +52 -0
- package/codecgc/cgc-plan/SKILL.md +48 -0
- package/codecgc/cgc-refactor/SKILL.md +46 -0
- package/codecgc/cgc-req/SKILL.md +61 -0
- package/codecgc/cgc-review/SKILL.md +57 -0
- package/codecgc/cgc-roadmap/SKILL.md +55 -0
- package/codecgc/cgc-test/SKILL.md +21 -0
- package/codecgc/reference/api-cgc-review-libdoc.md +13 -0
- package/codecgc/reference/artifact-class-policy.md +81 -0
- package/codecgc/reference/build-flow.md +95 -0
- package/codecgc/reference/checklist-contract.md +103 -0
- package/codecgc/reference/execution-audit.md +121 -0
- package/codecgc/reference/execution-model.md +118 -0
- package/codecgc/reference/execution-routing.md +130 -0
- package/codecgc/reference/executor-contract.md +87 -0
- package/codecgc/reference/external-capability-registry.json +104 -0
- package/codecgc/reference/fix-flow.md +94 -0
- package/codecgc/reference/fixture-governance.md +60 -0
- package/codecgc/reference/flow-execution.md +65 -0
- package/codecgc/reference/lifecycle-map.md +172 -0
- package/codecgc/reference/lifecycle-playbook.md +104 -0
- package/codecgc/reference/long-lived-artifacts.md +98 -0
- package/codecgc/reference/operation-guide.md +242 -0
- package/codecgc/reference/release-maintenance-playbook.md +150 -0
- package/codecgc/reference/review-writeback.md +141 -0
- package/codecgc/reference/role-model.md +128 -0
- package/codecgc/reference/runtime-boundary.md +72 -0
- package/codecgc/reference/shared-conventions.md +93 -0
- package/codecgc/reference/workflow-scaffold.md +57 -0
- package/codexmcp/LICENSE +21 -0
- package/codexmcp/README.md +294 -0
- package/codexmcp/pyproject.toml +37 -0
- package/codexmcp/src/codexmcp/__init__.py +4 -0
- package/codexmcp/src/codexmcp/cli.py +12 -0
- package/codexmcp/src/codexmcp/server.py +529 -0
- package/geminimcp/README.md +258 -0
- package/geminimcp/pyproject.toml +15 -0
- package/geminimcp/src/geminimcp/__init__.py +4 -0
- package/geminimcp/src/geminimcp/cli.py +12 -0
- package/geminimcp/src/geminimcp/server.py +465 -0
- package/model-routing.yaml +30 -0
- package/package.json +90 -0
- package/requirements.txt +1 -0
- package/scripts/README-codecgc-cli.md +89 -0
- package/scripts/audit_codecgc_external_capabilities.py +276 -0
- package/scripts/audit_codecgc_historical_audits.py +242 -0
- package/scripts/audit_codecgc_lifecycle.py +241 -0
- package/scripts/audit_codecgc_package_runtime.py +445 -0
- package/scripts/audit_codecgc_release_readiness.py +202 -0
- package/scripts/audit_codecgc_review_policy.py +82 -0
- package/scripts/audit_codecgc_workflow_history.py +317 -0
- package/scripts/build_codecgc_task.py +487 -0
- package/scripts/codecgc_artifact_roots.py +40 -0
- package/scripts/codecgc_cli.py +843 -0
- package/scripts/codecgc_command_surface.py +28 -0
- package/scripts/codecgc_console_io.py +45 -0
- package/scripts/codecgc_executor_registry.py +54 -0
- package/scripts/codecgc_file_evidence.py +349 -0
- package/scripts/codecgc_flow_control.py +233 -0
- package/scripts/codecgc_governance_dedupe.py +161 -0
- package/scripts/codecgc_plan_decision.py +103 -0
- package/scripts/codecgc_review_control.py +588 -0
- package/scripts/codecgc_roadmap_templates.py +149 -0
- package/scripts/codecgc_routing_paths.py +16 -0
- package/scripts/codecgc_routing_template.py +135 -0
- package/scripts/codecgc_runtime_paths.py +22 -0
- package/scripts/codecgc_session_recovery.py +44 -0
- package/scripts/codecgc_step_control.py +154 -0
- package/scripts/codecgc_workflow_runtime.py +63 -0
- package/scripts/codecgc_workflow_templates.py +437 -0
- package/scripts/entry_codecgc_workflow.py +3419 -0
- package/scripts/exercise_mcp_tools.py +109 -0
- package/scripts/expand_codecgc_roadmap.py +664 -0
- package/scripts/init_codecgc_roadmap.py +134 -0
- package/scripts/init_codecgc_workflow.py +207 -0
- package/scripts/install_codecgc.py +938 -0
- package/scripts/migrate_demo_workflows_to_fixtures.py +128 -0
- package/scripts/normalize_codecgc_audits.py +114 -0
- package/scripts/normalize_codecgc_governance_docs.py +79 -0
- package/scripts/normalize_codecgc_workflow_docs.py +269 -0
- package/scripts/plan_codecgc_workflow.py +970 -0
- package/scripts/refresh_codecgc_review_policy.py +223 -0
- package/scripts/review_codecgc_workflow.py +88 -0
- package/scripts/route_codecgc_workflow.py +671 -0
- package/scripts/run_codecgc_build.py +104 -0
- package/scripts/run_codecgc_fix.py +104 -0
- package/scripts/run_codecgc_flow_step.py +165 -0
- package/scripts/run_codecgc_task.py +410 -0
- package/scripts/run_codecgc_test.py +105 -0
- package/scripts/sync_codecgc_mcp_config.py +41 -0
- package/scripts/write_codecgc_architecture.py +78 -0
- package/scripts/write_codecgc_decision.py +83 -0
- package/scripts/write_codecgc_explore.py +118 -0
- package/scripts/write_codecgc_guide.py +141 -0
- package/scripts/write_codecgc_learning.py +87 -0
- package/scripts/write_codecgc_libdoc.py +140 -0
- package/scripts/write_codecgc_refactor.py +78 -0
- package/scripts/write_codecgc_requirement.py +78 -0
- package/scripts/write_codecgc_review.py +291 -0
- package/scripts/write_codecgc_roadmap.py +122 -0
- package/scripts/write_codecgc_trick.py +123 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import datetime as dt
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from build_codecgc_task import classify_path
|
|
11
|
+
from codecgc_artifact_roots import flow_root
|
|
12
|
+
from codecgc_plan_decision import evaluate_plan_decision
|
|
13
|
+
from build_codecgc_task import load_simple_routing_config
|
|
14
|
+
from codecgc_console_io import configure_utf8_stdio
|
|
15
|
+
from codecgc_console_io import print_json
|
|
16
|
+
from codecgc_routing_paths import resolve_active_routing_file
|
|
17
|
+
from codecgc_workflow_templates import build_acceptance_lines
|
|
18
|
+
from codecgc_workflow_templates import build_constraint_lines
|
|
19
|
+
from codecgc_workflow_templates import build_test_step
|
|
20
|
+
from codecgc_workflow_templates import render_feature_checklist_steps
|
|
21
|
+
from codecgc_workflow_templates import render_issue_fix_steps
|
|
22
|
+
from codecgc_workflow_runtime import run_json_script
|
|
23
|
+
from codecgc_runtime_paths import PACKAGE_ROOT
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
WORKSPACE = PACKAGE_ROOT
|
|
27
|
+
ROUTING_FILE = resolve_active_routing_file()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def slugify(value: str) -> str:
|
|
31
|
+
normalized = value.strip().lower()
|
|
32
|
+
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
|
|
33
|
+
normalized = re.sub(r"-{2,}", "-", normalized).strip("-")
|
|
34
|
+
if not normalized:
|
|
35
|
+
raise ValueError("Slug cannot be empty after normalization.")
|
|
36
|
+
return normalized
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def resolve_slug_pair(raw_slug: str, default_date: str) -> tuple[str, str]:
|
|
40
|
+
candidate = raw_slug.strip()
|
|
41
|
+
dated_match = re.match(r"^(?P<date>\d{4}-\d{2}-\d{2})-(?P<rest>.+)$", candidate)
|
|
42
|
+
if dated_match:
|
|
43
|
+
base_slug = slugify(dated_match.group("rest"))
|
|
44
|
+
artifact_date = dated_match.group("date")
|
|
45
|
+
return base_slug, f"{artifact_date}-{base_slug}"
|
|
46
|
+
|
|
47
|
+
base_slug = slugify(candidate)
|
|
48
|
+
return base_slug, f"{default_date}-{base_slug}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
description="Plan a CodeCGC feature or issue workflow and create the scaffold when needed."
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument("--flow", required=True, choices=["feature", "issue"])
|
|
56
|
+
parser.add_argument("--slug", required=True)
|
|
57
|
+
parser.add_argument("--summary", required=True)
|
|
58
|
+
parser.add_argument("--date", default=dt.date.today().isoformat())
|
|
59
|
+
parser.add_argument("--target-path", action="append", default=[])
|
|
60
|
+
parser.add_argument("--kind", choices=["auto", "frontend", "backend"], default="auto")
|
|
61
|
+
parser.add_argument("--goal", default="")
|
|
62
|
+
parser.add_argument("--context", action="append", default=[])
|
|
63
|
+
parser.add_argument("--user-story", default="")
|
|
64
|
+
parser.add_argument("--in-scope", action="append", default=[])
|
|
65
|
+
parser.add_argument("--out-of-scope", action="append", default=[])
|
|
66
|
+
parser.add_argument("--acceptance", action="append", default=[])
|
|
67
|
+
parser.add_argument("--risk", action="append", default=[])
|
|
68
|
+
parser.add_argument("--dependency", action="append", default=[])
|
|
69
|
+
parser.add_argument("--assumption", action="append", default=[])
|
|
70
|
+
parser.add_argument("--open-question", action="append", default=[])
|
|
71
|
+
parser.add_argument("--validation", action="append", default=[])
|
|
72
|
+
parser.add_argument("--rollback", action="append", default=[])
|
|
73
|
+
parser.add_argument("--symptom", default="")
|
|
74
|
+
parser.add_argument("--reproduction", default="")
|
|
75
|
+
parser.add_argument("--expected", default="")
|
|
76
|
+
parser.add_argument("--actual", default="")
|
|
77
|
+
parser.add_argument("--root-cause", default="")
|
|
78
|
+
parser.add_argument("--preferred-fix", default="")
|
|
79
|
+
parser.add_argument("--rejected-fix", default="")
|
|
80
|
+
parser.add_argument("--artifact-class", choices=["product", "fixture"], default="product")
|
|
81
|
+
parser.add_argument("--force", action="store_true")
|
|
82
|
+
return parser
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def should_create_scaffold(route_result: dict[str, object]) -> bool:
|
|
86
|
+
reason = str(route_result.get("reason", "")).lower()
|
|
87
|
+
return "does not exist yet" in reason or "目录尚不存在" in str(route_result.get("reason", ""))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def scaffold_incomplete(route_result: dict[str, object]) -> bool:
|
|
91
|
+
reason = str(route_result.get("reason", "")).lower()
|
|
92
|
+
return "scaffold is incomplete" in reason or "骨架不完整" in str(route_result.get("reason", ""))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def owner_label(kind: str) -> str:
|
|
96
|
+
if kind == "frontend":
|
|
97
|
+
return "前端 / Gemini"
|
|
98
|
+
if kind == "backend":
|
|
99
|
+
return "后端 / Codex"
|
|
100
|
+
return "待拆分"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def yaml_list(items: list[str], indent: int, fallback: str) -> str:
|
|
104
|
+
values = [item.strip() for item in items if item.strip()]
|
|
105
|
+
prefix = " " * indent
|
|
106
|
+
if not values:
|
|
107
|
+
return f"{prefix}- {fallback}"
|
|
108
|
+
return "\n".join(f"{prefix}- {item}" for item in values)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cleaned_items(items: list[str]) -> list[str]:
|
|
112
|
+
return [item.strip() for item in items if item.strip()]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_path_groups(args: argparse.Namespace) -> tuple[dict[str, list[str]], list[str]]:
|
|
116
|
+
if args.kind != "auto":
|
|
117
|
+
return {args.kind: [path for path in args.target_path if path.strip()]}, []
|
|
118
|
+
|
|
119
|
+
routing = load_simple_routing_config(ROUTING_FILE)
|
|
120
|
+
grouped: dict[str, list[str]] = defaultdict(list)
|
|
121
|
+
route_notes: list[str] = []
|
|
122
|
+
for raw_path in args.target_path:
|
|
123
|
+
path = raw_path.strip()
|
|
124
|
+
if not path:
|
|
125
|
+
continue
|
|
126
|
+
category = classify_path(path, routing)
|
|
127
|
+
grouped[category].append(path)
|
|
128
|
+
route_notes.append(f"{path} -> {category}")
|
|
129
|
+
return dict(grouped), route_notes
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def summarize_paths(paths: list[str]) -> str:
|
|
133
|
+
if not paths:
|
|
134
|
+
return "当前批准范围"
|
|
135
|
+
if len(paths) == 1:
|
|
136
|
+
return paths[0]
|
|
137
|
+
return f"{paths[0]} 等 {len(paths) - 1} 个路径"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def infer_step_acceptance(kind: str, flow: str, paths: list[str], acceptance: list[str]) -> list[str]:
|
|
141
|
+
scoped: list[str] = []
|
|
142
|
+
lower_kind = kind.lower()
|
|
143
|
+
for item in acceptance:
|
|
144
|
+
lowered = item.lower()
|
|
145
|
+
if lower_kind in lowered:
|
|
146
|
+
scoped.append(item)
|
|
147
|
+
continue
|
|
148
|
+
if kind == "frontend" and "backend" in lowered:
|
|
149
|
+
continue
|
|
150
|
+
if kind == "backend" and "frontend" in lowered:
|
|
151
|
+
continue
|
|
152
|
+
scoped.append(item)
|
|
153
|
+
|
|
154
|
+
if scoped:
|
|
155
|
+
return scoped
|
|
156
|
+
|
|
157
|
+
if flow == "issue":
|
|
158
|
+
if kind == "frontend":
|
|
159
|
+
return [
|
|
160
|
+
f"前端修复必须限定在 {summarize_paths(paths)} 内。",
|
|
161
|
+
"浏览器侧修复行为只覆盖当前限定的缺陷范围。",
|
|
162
|
+
]
|
|
163
|
+
if kind == "backend":
|
|
164
|
+
return [
|
|
165
|
+
f"后端修复必须限定在 {summarize_paths(paths)} 内。",
|
|
166
|
+
"后端修复行为只覆盖当前限定的缺陷范围。",
|
|
167
|
+
]
|
|
168
|
+
else:
|
|
169
|
+
if kind == "frontend":
|
|
170
|
+
return [
|
|
171
|
+
f"前端实现必须限定在 {summarize_paths(paths)} 内。",
|
|
172
|
+
"浏览器侧行为只覆盖当前限定的功能步骤。",
|
|
173
|
+
]
|
|
174
|
+
if kind == "backend":
|
|
175
|
+
return [
|
|
176
|
+
f"后端实现必须限定在 {summarize_paths(paths)} 内。",
|
|
177
|
+
"后端行为只覆盖当前限定的功能步骤。",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
return build_acceptance_lines(kind, paths)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def infer_task_summary(kind: str, flow: str, paths: list[str]) -> str:
|
|
184
|
+
scope_text = summarize_paths(paths)
|
|
185
|
+
if flow == "issue":
|
|
186
|
+
if kind == "frontend":
|
|
187
|
+
return f"只在 {scope_text} 内应用已批准的前端修复。"
|
|
188
|
+
if kind == "backend":
|
|
189
|
+
return f"只在 {scope_text} 内应用已批准的后端修复。"
|
|
190
|
+
return f"只在 {scope_text} 内应用已批准的修复。"
|
|
191
|
+
|
|
192
|
+
if kind == "frontend":
|
|
193
|
+
return f"只在 {scope_text} 内实现已批准的前端步骤。"
|
|
194
|
+
if kind == "backend":
|
|
195
|
+
return f"只在 {scope_text} 内实现已批准的后端步骤。"
|
|
196
|
+
return f"只在 {scope_text} 内实现已批准的步骤。"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def infer_action(kind: str, flow: str) -> str:
|
|
200
|
+
if flow == "issue":
|
|
201
|
+
return "执行一个前端修复步骤" if kind == "frontend" else "执行一个后端修复步骤" if kind == "backend" else "执行一个限定范围的修复步骤"
|
|
202
|
+
return "执行一个前端功能步骤" if kind == "frontend" else "执行一个后端功能步骤" if kind == "backend" else "执行一个限定范围的功能步骤"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def build_execution_steps(slug: str, flow: str, grouped_paths: dict[str, list[str]], acceptance: list[str]) -> list[dict[str, object]]:
|
|
206
|
+
steps: list[dict[str, object]] = []
|
|
207
|
+
executable_index = 1
|
|
208
|
+
|
|
209
|
+
for kind in ("frontend", "backend"):
|
|
210
|
+
paths = grouped_paths.get(kind, [])
|
|
211
|
+
if not paths:
|
|
212
|
+
continue
|
|
213
|
+
task_suffix = "fix-step" if flow == "issue" else "step"
|
|
214
|
+
summary = infer_task_summary(kind, flow, paths)
|
|
215
|
+
action = infer_action(kind, flow)
|
|
216
|
+
exit_signal = "修复步骤已可进入委派执行" if flow == "issue" else "步骤契约已可进入委派执行"
|
|
217
|
+
step_acceptance = infer_step_acceptance(kind, flow, paths, acceptance) or build_acceptance_lines(kind, paths)
|
|
218
|
+
steps.append(
|
|
219
|
+
{
|
|
220
|
+
"action": action,
|
|
221
|
+
"exit_signal": exit_signal,
|
|
222
|
+
"status": "pending",
|
|
223
|
+
"codecgc": {
|
|
224
|
+
"kind": kind,
|
|
225
|
+
"task_id": f"{slug}-{task_suffix}-{executable_index}",
|
|
226
|
+
"task_summary": summary,
|
|
227
|
+
"target_paths": paths,
|
|
228
|
+
"constraints": build_constraint_lines(kind),
|
|
229
|
+
"acceptance": step_acceptance,
|
|
230
|
+
"cd": ".",
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
executable_index += 1
|
|
235
|
+
steps.append(
|
|
236
|
+
build_test_step(
|
|
237
|
+
slug=slug,
|
|
238
|
+
flow=flow,
|
|
239
|
+
kind=kind,
|
|
240
|
+
target_paths=infer_test_target_paths(kind, paths),
|
|
241
|
+
step_index=executable_index,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
executable_index += 1
|
|
245
|
+
|
|
246
|
+
shared_paths = grouped_paths.get("shared", [])
|
|
247
|
+
unknown_paths = grouped_paths.get("unknown", [])
|
|
248
|
+
if shared_paths:
|
|
249
|
+
steps.append(
|
|
250
|
+
{
|
|
251
|
+
"action": "在执行前先拆分共享范围工作",
|
|
252
|
+
"exit_signal": "共享路径已经重新分配到纯前端或纯后端 steps",
|
|
253
|
+
"status": "pending",
|
|
254
|
+
"owner_hint": "Claude 规划",
|
|
255
|
+
"planning_note": "共享路径不能在一个 CodeCGC 步骤中直接执行。",
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
if unknown_paths:
|
|
259
|
+
steps.append(
|
|
260
|
+
{
|
|
261
|
+
"action": "在执行前先确认未知路由路径",
|
|
262
|
+
"exit_signal": "每个目标路径都已经被 model-routing.yaml 覆盖",
|
|
263
|
+
"status": "pending",
|
|
264
|
+
"owner_hint": "Claude 规划",
|
|
265
|
+
"planning_note": "未知路径必须先分类或移除,才能进入执行。",
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if not steps:
|
|
270
|
+
task_suffix = "fix-step" if flow == "issue" else "step"
|
|
271
|
+
summary = "只执行当前已批准的修复步骤。" if flow == "issue" else "只执行当前已批准的步骤。"
|
|
272
|
+
action = "执行一个限定范围的修复步骤" if flow == "issue" else "定义一个可执行步骤"
|
|
273
|
+
exit_signal = "修复步骤已可进入委派执行" if flow == "issue" else "步骤契约已可进入委派执行"
|
|
274
|
+
step_acceptance = acceptance or build_acceptance_lines("auto", [])
|
|
275
|
+
steps.append(
|
|
276
|
+
{
|
|
277
|
+
"action": action,
|
|
278
|
+
"exit_signal": exit_signal,
|
|
279
|
+
"status": "pending",
|
|
280
|
+
"codecgc": {
|
|
281
|
+
"kind": "auto",
|
|
282
|
+
"task_id": f"{slug}-{task_suffix}-1",
|
|
283
|
+
"task_summary": summary,
|
|
284
|
+
"target_paths": ["待补路径"],
|
|
285
|
+
"constraints": build_constraint_lines("auto"),
|
|
286
|
+
"acceptance": step_acceptance,
|
|
287
|
+
"cd": ".",
|
|
288
|
+
},
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return steps
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def infer_test_target_paths(kind: str, paths: list[str]) -> list[str]:
|
|
296
|
+
inferred: list[str] = []
|
|
297
|
+
for path in paths:
|
|
298
|
+
normalized = str(path).replace("\\", "/").strip()
|
|
299
|
+
if kind == "frontend":
|
|
300
|
+
if normalized.endswith(".tsx"):
|
|
301
|
+
inferred.append(normalized.replace(".tsx", ".test.tsx"))
|
|
302
|
+
elif normalized.endswith(".ts"):
|
|
303
|
+
inferred.append(normalized.replace(".ts", ".test.ts"))
|
|
304
|
+
elif normalized.endswith(".jsx"):
|
|
305
|
+
inferred.append(normalized.replace(".jsx", ".test.jsx"))
|
|
306
|
+
elif normalized.endswith(".js"):
|
|
307
|
+
inferred.append(normalized.replace(".js", ".test.js"))
|
|
308
|
+
else:
|
|
309
|
+
inferred.append(normalized)
|
|
310
|
+
else:
|
|
311
|
+
if normalized.endswith(".py"):
|
|
312
|
+
inferred.append(normalized.replace(".py", "_test.py"))
|
|
313
|
+
elif normalized.endswith(".go"):
|
|
314
|
+
inferred.append(normalized.replace(".go", "_test.go"))
|
|
315
|
+
elif normalized.endswith(".ts"):
|
|
316
|
+
inferred.append(normalized.replace(".ts", ".spec.ts"))
|
|
317
|
+
elif normalized.endswith(".js"):
|
|
318
|
+
inferred.append(normalized.replace(".js", ".spec.js"))
|
|
319
|
+
else:
|
|
320
|
+
inferred.append(normalized)
|
|
321
|
+
return inferred or paths
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def downgrade_steps_to_planning_only(steps: list[dict[str, object]], reason: str) -> list[dict[str, object]]:
|
|
325
|
+
downgraded: list[dict[str, object]] = []
|
|
326
|
+
for step in steps:
|
|
327
|
+
codecgc = step.get("codecgc")
|
|
328
|
+
if not isinstance(codecgc, dict):
|
|
329
|
+
downgraded.append(step)
|
|
330
|
+
continue
|
|
331
|
+
downgraded.append(
|
|
332
|
+
{
|
|
333
|
+
"action": str(step.get("action", "执行前先补齐规划信息")),
|
|
334
|
+
"exit_signal": str(step.get("exit_signal", "执行前必须先完成规划")),
|
|
335
|
+
"status": str(step.get("status", "pending")),
|
|
336
|
+
"owner_hint": "Claude 规划",
|
|
337
|
+
"planning_note": reason,
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
return downgraded
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def render_step_outline(steps: list[dict[str, object]]) -> str:
|
|
344
|
+
lines: list[str] = []
|
|
345
|
+
for index, step in enumerate(steps, start=1):
|
|
346
|
+
codecgc = step.get("codecgc")
|
|
347
|
+
if not isinstance(codecgc, dict):
|
|
348
|
+
owner_hint = str(step.get("owner_hint", "Claude 规划")).strip()
|
|
349
|
+
planning_note = str(step.get("planning_note", "")).strip() or "执行前需要先完成规划处理。"
|
|
350
|
+
lines.extend(
|
|
351
|
+
[
|
|
352
|
+
f"- 步骤 {index}: {step['action']}",
|
|
353
|
+
f" 归属: {owner_hint}",
|
|
354
|
+
f" 状态: 仅规划",
|
|
355
|
+
f" 说明: {planning_note}",
|
|
356
|
+
]
|
|
357
|
+
)
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
paths = codecgc.get("target_paths", [])
|
|
361
|
+
acceptance = codecgc.get("acceptance", [])
|
|
362
|
+
lines.extend(
|
|
363
|
+
[
|
|
364
|
+
f"- 步骤 {index}: {step['action']}",
|
|
365
|
+
f" 归属: {owner_label(str(codecgc.get('kind', 'auto')))}",
|
|
366
|
+
f" 路径: {', '.join(str(path) for path in paths) or '待补路径'}",
|
|
367
|
+
f" 摘要: {codecgc.get('task_summary', '')}",
|
|
368
|
+
f" 验收: {' | '.join(str(item) for item in acceptance) or '待补充'}",
|
|
369
|
+
]
|
|
370
|
+
)
|
|
371
|
+
return "\n".join(lines) if lines else "- 待补充"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def render_feature_design_document(directory_name: str, summary: str, args: argparse.Namespace, grouped_paths: dict[str, list[str]], route_notes: list[str], steps: list[dict[str, object]]) -> str:
|
|
375
|
+
mixed_scope = len([kind for kind in ("frontend", "backend") if grouped_paths.get(kind)]) > 1
|
|
376
|
+
planning_split_required = mixed_scope or bool(grouped_paths.get("shared")) or bool(grouped_paths.get("unknown"))
|
|
377
|
+
executable_steps, planning_only_steps = summarize_step_counts(steps)
|
|
378
|
+
plan_decision = evaluate_plan_decision(
|
|
379
|
+
flow="feature",
|
|
380
|
+
grouped_paths=grouped_paths,
|
|
381
|
+
target_paths=cleaned_items(args.target_path),
|
|
382
|
+
executable_steps=executable_steps,
|
|
383
|
+
planning_only_steps=planning_only_steps,
|
|
384
|
+
goal=args.goal,
|
|
385
|
+
user_story=args.user_story,
|
|
386
|
+
symptom="",
|
|
387
|
+
expected="",
|
|
388
|
+
actual="",
|
|
389
|
+
in_scope=cleaned_items(args.in_scope),
|
|
390
|
+
acceptance=cleaned_items(args.acceptance),
|
|
391
|
+
)
|
|
392
|
+
validation_lines = cleaned_items(args.validation) or [
|
|
393
|
+
"对当前待执行步骤运行委派执行。",
|
|
394
|
+
"在验收前先审核审计证据。",
|
|
395
|
+
]
|
|
396
|
+
rollback_lines = cleaned_items(args.rollback) or [
|
|
397
|
+
"如果审核或验证失败,只回退当前限定范围的步骤。",
|
|
398
|
+
"如果归属或范围变化,回到规划阶段。",
|
|
399
|
+
]
|
|
400
|
+
return f"""---
|
|
401
|
+
doc_type: feature-design
|
|
402
|
+
artifact_class: {args.artifact_class}
|
|
403
|
+
feature: {directory_name}
|
|
404
|
+
status: draft
|
|
405
|
+
summary: {summary}
|
|
406
|
+
tags: []
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
# {summary}
|
|
410
|
+
|
|
411
|
+
## 1. 目标
|
|
412
|
+
|
|
413
|
+
- 摘要: {summary}
|
|
414
|
+
- 用户目标: {args.goal.strip() if args.goal else '待补充'}
|
|
415
|
+
- 用户故事: {args.user_story.strip() if args.user_story else '待补充'}
|
|
416
|
+
- 计划执行归属: {"执行前由 Claude 先拆分" if mixed_scope or grouped_paths.get('shared') or grouped_paths.get('unknown') else owner_label(args.kind if args.kind != 'auto' else next(iter([k for k in ('frontend', 'backend') if grouped_paths.get(k)]), 'auto'))}
|
|
417
|
+
- 候选目标路径:
|
|
418
|
+
{yaml_list(args.target_path, 2, '待补路径')}
|
|
419
|
+
|
|
420
|
+
## 2. 背景
|
|
421
|
+
|
|
422
|
+
{yaml_list(cleaned_items(args.context), 0, '待补充')}
|
|
423
|
+
|
|
424
|
+
## 3. 范围内
|
|
425
|
+
|
|
426
|
+
{yaml_list(args.in_scope or [f"仅修改 `{path}`。" for path in args.target_path], 0, '待补充')}
|
|
427
|
+
|
|
428
|
+
## 4. 范围外
|
|
429
|
+
|
|
430
|
+
{yaml_list(args.out_of_scope or [item for item in build_constraint_lines(args.kind) if item != '不要修改 target_paths 之外的文件。'], 0, '待补充')}
|
|
431
|
+
|
|
432
|
+
## 5. 依赖与假设
|
|
433
|
+
|
|
434
|
+
依赖:
|
|
435
|
+
{yaml_list(cleaned_items(args.dependency), 2, '待补充')}
|
|
436
|
+
|
|
437
|
+
假设:
|
|
438
|
+
{yaml_list(cleaned_items(args.assumption), 2, '待补充')}
|
|
439
|
+
|
|
440
|
+
## 6. 执行说明
|
|
441
|
+
|
|
442
|
+
{yaml_list([
|
|
443
|
+
"该产物在真正可执行前,还需要先通过多个受规划控制的 steps 完成拆分或补齐。" if planning_split_required else "该产物当前已经限定在单执行器可接管的 step 内。",
|
|
444
|
+
f"规划状态: {plan_decision['planning_status']}",
|
|
445
|
+
*([f"路由说明: {item}" for item in route_notes] or []),
|
|
446
|
+
*([f"验收提示: {item}" for item in args.acceptance if item.strip()] or []),
|
|
447
|
+
*([f"规划风险: {item}" for item in args.risk if item.strip()] or []),
|
|
448
|
+
*([f"决策说明: {item}" for item in plan_decision.get("reasons", [])] or []),
|
|
449
|
+
], 0, '待补充')}
|
|
450
|
+
|
|
451
|
+
## 7. 验证计划
|
|
452
|
+
|
|
453
|
+
{yaml_list(validation_lines, 0, '待补充')}
|
|
454
|
+
|
|
455
|
+
## 8. 回退计划
|
|
456
|
+
|
|
457
|
+
{yaml_list(rollback_lines, 0, '待补充')}
|
|
458
|
+
|
|
459
|
+
## 9. 开放问题
|
|
460
|
+
|
|
461
|
+
{yaml_list(cleaned_items(args.open_question), 0, '当前无。')}
|
|
462
|
+
|
|
463
|
+
## 10. 计划步骤
|
|
464
|
+
|
|
465
|
+
{render_step_outline(steps)}
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def render_issue_report_document(directory_name: str, summary: str, args: argparse.Namespace, grouped_paths: dict[str, list[str]], route_notes: list[str], steps: list[dict[str, object]]) -> str:
|
|
470
|
+
mixed_scope = len([kind for kind in ("frontend", "backend") if grouped_paths.get(kind)]) > 1
|
|
471
|
+
return f"""---
|
|
472
|
+
doc_type: issue-report
|
|
473
|
+
artifact_class: {args.artifact_class}
|
|
474
|
+
issue: {directory_name}
|
|
475
|
+
status: draft
|
|
476
|
+
severity: P2
|
|
477
|
+
summary: {summary}
|
|
478
|
+
tags: []
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
# {summary} 问题报告
|
|
482
|
+
|
|
483
|
+
## 1. 现象
|
|
484
|
+
|
|
485
|
+
- 摘要: {summary}
|
|
486
|
+
- 现象: {args.symptom.strip() if args.symptom else '待补充'}
|
|
487
|
+
- 用户影响: {args.user_story.strip() if args.user_story else '待补充'}
|
|
488
|
+
- 预估执行归属: {"执行前由 Claude 先拆分" if mixed_scope or grouped_paths.get('shared') or grouped_paths.get('unknown') else owner_label(args.kind if args.kind != 'auto' else next(iter([k for k in ('frontend', 'backend') if grouped_paths.get(k)]), 'auto'))}
|
|
489
|
+
- 候选影响路径:
|
|
490
|
+
{yaml_list(args.target_path, 2, '待补路径')}
|
|
491
|
+
|
|
492
|
+
## 2. 复现方式
|
|
493
|
+
|
|
494
|
+
{yaml_list([args.reproduction.strip()] if args.reproduction else [], 0, '待补充')}
|
|
495
|
+
|
|
496
|
+
## 3. 预期与实际
|
|
497
|
+
|
|
498
|
+
{yaml_list([
|
|
499
|
+
f'预期: {args.expected.strip()}' if args.expected else '预期: 待补充',
|
|
500
|
+
f'实际: {args.actual.strip()}' if args.actual else '实际: 待补充',
|
|
501
|
+
*([f'路由说明: {item}' for item in route_notes] or []),
|
|
502
|
+
], 0, '待补充')}
|
|
503
|
+
|
|
504
|
+
## 4. 背景
|
|
505
|
+
|
|
506
|
+
{yaml_list(cleaned_items(args.context), 0, '待补充')}
|
|
507
|
+
|
|
508
|
+
## 5. 计划步骤
|
|
509
|
+
|
|
510
|
+
{render_step_outline(steps)}
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def render_issue_analysis_document(directory_name: str, summary: str, args: argparse.Namespace, grouped_paths: dict[str, list[str]], steps: list[dict[str, object]]) -> str:
|
|
515
|
+
mixed_scope = len([kind for kind in ("frontend", "backend") if grouped_paths.get(kind)]) > 1
|
|
516
|
+
executable_steps, planning_only_steps = summarize_step_counts(steps)
|
|
517
|
+
plan_decision = evaluate_plan_decision(
|
|
518
|
+
flow="issue",
|
|
519
|
+
grouped_paths=grouped_paths,
|
|
520
|
+
target_paths=cleaned_items(args.target_path),
|
|
521
|
+
executable_steps=executable_steps,
|
|
522
|
+
planning_only_steps=planning_only_steps,
|
|
523
|
+
goal="",
|
|
524
|
+
user_story=args.user_story,
|
|
525
|
+
symptom=args.symptom,
|
|
526
|
+
expected=args.expected,
|
|
527
|
+
actual=args.actual,
|
|
528
|
+
in_scope=cleaned_items(args.in_scope),
|
|
529
|
+
acceptance=cleaned_items(args.acceptance),
|
|
530
|
+
)
|
|
531
|
+
fix_lines = [
|
|
532
|
+
f"首选定点修复: {args.preferred_fix.strip()}" if args.preferred_fix else "首选定点修复: 待补充",
|
|
533
|
+
f"明确不采用的更大范围修复: {args.rejected_fix.strip()}" if args.rejected_fix else "明确不采用的更大范围修复: 待补充",
|
|
534
|
+
]
|
|
535
|
+
fix_lines.extend(f"风险: {item}" for item in args.risk if item.strip())
|
|
536
|
+
fix_lines.extend(f"决策说明: {item}" for item in plan_decision.get("reasons", []))
|
|
537
|
+
return f"""---
|
|
538
|
+
doc_type: issue-analysis
|
|
539
|
+
artifact_class: {args.artifact_class}
|
|
540
|
+
issue: {directory_name}
|
|
541
|
+
status: draft
|
|
542
|
+
summary: {summary}
|
|
543
|
+
tags: []
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
# {summary} 分析
|
|
547
|
+
|
|
548
|
+
## 1. 根因
|
|
549
|
+
|
|
550
|
+
{yaml_list([
|
|
551
|
+
f"当前假设归属: {'执行前由 Claude 先拆分' if mixed_scope or grouped_paths.get('shared') or grouped_paths.get('unknown') else owner_label(args.kind if args.kind != 'auto' else next(iter([k for k in ('frontend', 'backend') if grouped_paths.get(k)]), 'auto'))}",
|
|
552
|
+
f'根因说明: {args.root_cause.strip()}' if args.root_cause else '根因说明: 待补充',
|
|
553
|
+
], 0, '待补充')}
|
|
554
|
+
|
|
555
|
+
## 2. 范围
|
|
556
|
+
|
|
557
|
+
{yaml_list(args.in_scope or [f"仅修改 `{path}`。" for path in args.target_path], 0, '待补充')}
|
|
558
|
+
|
|
559
|
+
## 3. 修复方案
|
|
560
|
+
|
|
561
|
+
{yaml_list(fix_lines, 0, '待补充')}
|
|
562
|
+
|
|
563
|
+
## 4. 依赖与假设
|
|
564
|
+
|
|
565
|
+
依赖:
|
|
566
|
+
{yaml_list(cleaned_items(args.dependency), 2, '待补充')}
|
|
567
|
+
|
|
568
|
+
假设:
|
|
569
|
+
{yaml_list(cleaned_items(args.assumption), 2, '待补充')}
|
|
570
|
+
|
|
571
|
+
## 5. 验证计划
|
|
572
|
+
|
|
573
|
+
{yaml_list(cleaned_items(args.validation), 0, '通过委派执行和 review 验证当前定点修复。')}
|
|
574
|
+
|
|
575
|
+
## 6. 回退计划
|
|
576
|
+
|
|
577
|
+
{yaml_list(cleaned_items(args.rollback), 0, '如果验证失败,只回退当前定点修复步骤。')}
|
|
578
|
+
|
|
579
|
+
## 7. 开放问题
|
|
580
|
+
|
|
581
|
+
{yaml_list(cleaned_items(args.open_question), 0, '当前无。')}
|
|
582
|
+
|
|
583
|
+
## 8. 计划步骤
|
|
584
|
+
|
|
585
|
+
{render_step_outline(steps)}
|
|
586
|
+
"""
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def enrich_feature_artifacts(flow_dir: Path, slug: str, args: argparse.Namespace) -> list[str]:
|
|
590
|
+
design_path = flow_dir / f"{slug}-design.md"
|
|
591
|
+
checklist_path = flow_dir / f"{slug}-checklist.yaml"
|
|
592
|
+
grouped_paths, route_notes = build_path_groups(args)
|
|
593
|
+
acceptance = [item.strip() for item in args.acceptance if item.strip()]
|
|
594
|
+
notes: list[str] = []
|
|
595
|
+
steps = build_execution_steps(slug, "feature", grouped_paths, acceptance)
|
|
596
|
+
executable_steps, planning_only_steps = summarize_step_counts(steps)
|
|
597
|
+
plan_decision = evaluate_plan_decision(
|
|
598
|
+
flow="feature",
|
|
599
|
+
grouped_paths=grouped_paths,
|
|
600
|
+
target_paths=cleaned_items(args.target_path),
|
|
601
|
+
executable_steps=executable_steps,
|
|
602
|
+
planning_only_steps=planning_only_steps,
|
|
603
|
+
goal=args.goal,
|
|
604
|
+
user_story=args.user_story,
|
|
605
|
+
symptom="",
|
|
606
|
+
expected="",
|
|
607
|
+
actual="",
|
|
608
|
+
in_scope=cleaned_items(args.in_scope),
|
|
609
|
+
acceptance=cleaned_items(args.acceptance),
|
|
610
|
+
)
|
|
611
|
+
if plan_decision.get("planning_status") != "ready-for-build":
|
|
612
|
+
steps = downgrade_steps_to_planning_only(
|
|
613
|
+
steps,
|
|
614
|
+
"当前规划输入仍不完整,在 CodeCGC 完成澄清前不要执行。",
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
if design_path.exists():
|
|
618
|
+
design_path.write_text(
|
|
619
|
+
render_feature_design_document(flow_dir.name, args.summary, args, grouped_paths, route_notes, steps),
|
|
620
|
+
encoding="utf-8",
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
if checklist_path.exists():
|
|
624
|
+
checklist_path.write_text(
|
|
625
|
+
render_feature_checklist_steps(flow_dir.name, args.date, args.artifact_class, steps),
|
|
626
|
+
encoding="utf-8",
|
|
627
|
+
)
|
|
628
|
+
executable_steps = sum(1 for step in steps if isinstance(step.get("codecgc"), dict))
|
|
629
|
+
notes.append(f"已准备 {len(steps)} 个计划步骤,其中 {executable_steps} 个为可执行功能开发步骤。")
|
|
630
|
+
if grouped_paths.get("shared"):
|
|
631
|
+
notes.append("共享路径已保留为仅规划拆分步骤。")
|
|
632
|
+
if grouped_paths.get("unknown"):
|
|
633
|
+
notes.append("未知路由路径已保留为仅规划的路由确认步骤。")
|
|
634
|
+
return notes
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def summarize_step_counts(steps: list[dict[str, object]]) -> tuple[int, int]:
|
|
638
|
+
executable_steps = sum(1 for step in steps if isinstance(step.get("codecgc"), dict))
|
|
639
|
+
planning_only_steps = sum(1 for step in steps if not isinstance(step.get("codecgc"), dict))
|
|
640
|
+
return executable_steps, planning_only_steps
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def init_roadmap_from_plan(
|
|
644
|
+
*,
|
|
645
|
+
slug: str,
|
|
646
|
+
summary: str,
|
|
647
|
+
args: argparse.Namespace,
|
|
648
|
+
grouped_paths: dict[str, list[str]],
|
|
649
|
+
reasons: list[str],
|
|
650
|
+
) -> dict[str, Any]:
|
|
651
|
+
command_args = [
|
|
652
|
+
"--slug",
|
|
653
|
+
slug,
|
|
654
|
+
"--summary",
|
|
655
|
+
summary,
|
|
656
|
+
"--artifact-class",
|
|
657
|
+
args.artifact_class,
|
|
658
|
+
"--goal",
|
|
659
|
+
args.goal or "TODO",
|
|
660
|
+
"--user-story",
|
|
661
|
+
args.user_story or "TODO",
|
|
662
|
+
]
|
|
663
|
+
for item in cleaned_items(args.context):
|
|
664
|
+
command_args.extend(["--context", item])
|
|
665
|
+
for item in cleaned_items(args.in_scope):
|
|
666
|
+
command_args.extend(["--in-scope", item])
|
|
667
|
+
for item in cleaned_items(args.risk):
|
|
668
|
+
command_args.extend(["--risk", item])
|
|
669
|
+
for item in cleaned_items(args.dependency):
|
|
670
|
+
command_args.extend(["--dependency", item])
|
|
671
|
+
for item in cleaned_items(args.assumption):
|
|
672
|
+
command_args.extend(["--assumption", item])
|
|
673
|
+
for item in cleaned_items(args.validation):
|
|
674
|
+
command_args.extend(["--validation", item])
|
|
675
|
+
for item in cleaned_items(args.rollback):
|
|
676
|
+
command_args.extend(["--rollback", item])
|
|
677
|
+
for item in cleaned_items(args.open_question):
|
|
678
|
+
command_args.extend(["--open-question", item])
|
|
679
|
+
for item in reasons:
|
|
680
|
+
command_args.extend(["--reason", item])
|
|
681
|
+
for item in grouped_paths.get("frontend", []):
|
|
682
|
+
command_args.extend(["--frontend-path", item])
|
|
683
|
+
for item in grouped_paths.get("backend", []):
|
|
684
|
+
command_args.extend(["--backend-path", item])
|
|
685
|
+
for item in grouped_paths.get("shared", []):
|
|
686
|
+
command_args.extend(["--shared-path", item])
|
|
687
|
+
for item in grouped_paths.get("unknown", []):
|
|
688
|
+
command_args.extend(["--unknown-path", item])
|
|
689
|
+
if args.force:
|
|
690
|
+
command_args.append("--force")
|
|
691
|
+
return run_json_script("init_codecgc_roadmap.py", *command_args)
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def expand_roadmap_children_from_plan(
|
|
695
|
+
*,
|
|
696
|
+
initiative: str,
|
|
697
|
+
summary: str,
|
|
698
|
+
args: argparse.Namespace,
|
|
699
|
+
grouped_paths: dict[str, list[str]],
|
|
700
|
+
) -> dict[str, Any]:
|
|
701
|
+
command_args = [
|
|
702
|
+
"--initiative",
|
|
703
|
+
initiative,
|
|
704
|
+
"--summary",
|
|
705
|
+
summary,
|
|
706
|
+
"--artifact-class",
|
|
707
|
+
args.artifact_class,
|
|
708
|
+
"--source-flow",
|
|
709
|
+
args.flow,
|
|
710
|
+
"--goal",
|
|
711
|
+
args.goal or "TODO",
|
|
712
|
+
"--user-story",
|
|
713
|
+
args.user_story or "TODO",
|
|
714
|
+
]
|
|
715
|
+
if args.symptom:
|
|
716
|
+
command_args.extend(["--symptom", args.symptom])
|
|
717
|
+
if args.expected:
|
|
718
|
+
command_args.extend(["--expected", args.expected])
|
|
719
|
+
if args.actual:
|
|
720
|
+
command_args.extend(["--actual", args.actual])
|
|
721
|
+
if args.root_cause:
|
|
722
|
+
command_args.extend(["--root-cause", args.root_cause])
|
|
723
|
+
if args.preferred_fix:
|
|
724
|
+
command_args.extend(["--preferred-fix", args.preferred_fix])
|
|
725
|
+
if args.rejected_fix:
|
|
726
|
+
command_args.extend(["--rejected-fix", args.rejected_fix])
|
|
727
|
+
for item in cleaned_items(args.context):
|
|
728
|
+
command_args.extend(["--context", item])
|
|
729
|
+
for item in cleaned_items(args.in_scope):
|
|
730
|
+
command_args.extend(["--in-scope", item])
|
|
731
|
+
for item in cleaned_items(args.risk):
|
|
732
|
+
command_args.extend(["--risk", item])
|
|
733
|
+
for item in cleaned_items(args.dependency):
|
|
734
|
+
command_args.extend(["--dependency", item])
|
|
735
|
+
for item in cleaned_items(args.assumption):
|
|
736
|
+
command_args.extend(["--assumption", item])
|
|
737
|
+
for item in cleaned_items(args.validation):
|
|
738
|
+
command_args.extend(["--validation", item])
|
|
739
|
+
for item in cleaned_items(args.rollback):
|
|
740
|
+
command_args.extend(["--rollback", item])
|
|
741
|
+
for item in cleaned_items(args.open_question):
|
|
742
|
+
command_args.extend(["--open-question", item])
|
|
743
|
+
for item in cleaned_items(args.acceptance):
|
|
744
|
+
command_args.extend(["--acceptance", item])
|
|
745
|
+
for item in grouped_paths.get("frontend", []):
|
|
746
|
+
command_args.extend(["--frontend-path", item])
|
|
747
|
+
for item in grouped_paths.get("backend", []):
|
|
748
|
+
command_args.extend(["--backend-path", item])
|
|
749
|
+
if args.force:
|
|
750
|
+
command_args.append("--force")
|
|
751
|
+
return run_json_script("expand_codecgc_roadmap.py", *command_args)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def enrich_issue_artifacts(flow_dir: Path, slug: str, args: argparse.Namespace) -> list[str]:
|
|
755
|
+
report_path = flow_dir / f"{slug}-report.md"
|
|
756
|
+
analysis_path = flow_dir / f"{slug}-analysis.md"
|
|
757
|
+
fix_path = flow_dir / f"{slug}-fix.yaml"
|
|
758
|
+
grouped_paths, route_notes = build_path_groups(args)
|
|
759
|
+
acceptance = [item.strip() for item in args.acceptance if item.strip()]
|
|
760
|
+
notes: list[str] = []
|
|
761
|
+
steps = build_execution_steps(slug, "issue", grouped_paths, acceptance)
|
|
762
|
+
executable_steps, planning_only_steps = summarize_step_counts(steps)
|
|
763
|
+
plan_decision = evaluate_plan_decision(
|
|
764
|
+
flow="issue",
|
|
765
|
+
grouped_paths=grouped_paths,
|
|
766
|
+
target_paths=cleaned_items(args.target_path),
|
|
767
|
+
executable_steps=executable_steps,
|
|
768
|
+
planning_only_steps=planning_only_steps,
|
|
769
|
+
goal="",
|
|
770
|
+
user_story=args.user_story,
|
|
771
|
+
symptom=args.symptom,
|
|
772
|
+
expected=args.expected,
|
|
773
|
+
actual=args.actual,
|
|
774
|
+
in_scope=cleaned_items(args.in_scope),
|
|
775
|
+
acceptance=cleaned_items(args.acceptance),
|
|
776
|
+
)
|
|
777
|
+
if plan_decision.get("planning_status") != "ready-for-fix":
|
|
778
|
+
steps = downgrade_steps_to_planning_only(
|
|
779
|
+
steps,
|
|
780
|
+
"当前规划输入仍不完整,在 CodeCGC 完成澄清前不要执行。",
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
if report_path.exists():
|
|
784
|
+
report_path.write_text(
|
|
785
|
+
render_issue_report_document(flow_dir.name, args.summary, args, grouped_paths, route_notes, steps),
|
|
786
|
+
encoding="utf-8",
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
if analysis_path.exists():
|
|
790
|
+
analysis_path.write_text(
|
|
791
|
+
render_issue_analysis_document(flow_dir.name, args.summary, args, grouped_paths, steps),
|
|
792
|
+
encoding="utf-8",
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
if fix_path.exists():
|
|
796
|
+
fix_path.write_text(
|
|
797
|
+
render_issue_fix_steps(flow_dir.name, args.date, args.artifact_class, steps),
|
|
798
|
+
encoding="utf-8",
|
|
799
|
+
)
|
|
800
|
+
executable_steps = sum(1 for step in steps if isinstance(step.get("codecgc"), dict))
|
|
801
|
+
notes.append(f"已准备 {len(steps)} 个计划步骤,其中 {executable_steps} 个为可执行问题修复步骤。")
|
|
802
|
+
if grouped_paths.get("shared"):
|
|
803
|
+
notes.append("共享路径已保留为仅规划拆分步骤。")
|
|
804
|
+
if grouped_paths.get("unknown"):
|
|
805
|
+
notes.append("未知路由路径已保留为仅规划的路由确认步骤。")
|
|
806
|
+
return notes
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def main() -> int:
|
|
810
|
+
configure_utf8_stdio()
|
|
811
|
+
parser = build_parser()
|
|
812
|
+
args = parser.parse_args()
|
|
813
|
+
|
|
814
|
+
try:
|
|
815
|
+
base_slug, artifact_slug = resolve_slug_pair(args.slug, args.date)
|
|
816
|
+
|
|
817
|
+
route_before = run_json_script(
|
|
818
|
+
"route_codecgc_workflow.py",
|
|
819
|
+
"--flow",
|
|
820
|
+
args.flow,
|
|
821
|
+
"--slug",
|
|
822
|
+
artifact_slug,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
created = None
|
|
826
|
+
notes: list[str] = []
|
|
827
|
+
|
|
828
|
+
if should_create_scaffold(route_before):
|
|
829
|
+
command_args = [
|
|
830
|
+
"--flow",
|
|
831
|
+
args.flow,
|
|
832
|
+
"--slug",
|
|
833
|
+
base_slug,
|
|
834
|
+
"--summary",
|
|
835
|
+
args.summary,
|
|
836
|
+
"--date",
|
|
837
|
+
args.date,
|
|
838
|
+
"--kind",
|
|
839
|
+
args.kind,
|
|
840
|
+
"--artifact-class",
|
|
841
|
+
args.artifact_class,
|
|
842
|
+
]
|
|
843
|
+
for item in args.target_path:
|
|
844
|
+
command_args.extend(["--target-path", item])
|
|
845
|
+
if args.force:
|
|
846
|
+
command_args.append("--force")
|
|
847
|
+
|
|
848
|
+
created = run_json_script("init_codecgc_workflow.py", *command_args)
|
|
849
|
+
if not created.get("success"):
|
|
850
|
+
raise RuntimeError(str(created.get("error", "初始化工作流骨架失败。")))
|
|
851
|
+
notes.append("已初始化最小工作流骨架。")
|
|
852
|
+
elif scaffold_incomplete(route_before):
|
|
853
|
+
if not args.force:
|
|
854
|
+
result = {
|
|
855
|
+
"success": False,
|
|
856
|
+
"flow": args.flow,
|
|
857
|
+
"slug": artifact_slug,
|
|
858
|
+
"route": route_before,
|
|
859
|
+
"recommended_command": "cgc-plan",
|
|
860
|
+
"next": "请先修复现有工作流骨架,或使用 --force 重新覆盖缺失内容。",
|
|
861
|
+
}
|
|
862
|
+
print_json(result)
|
|
863
|
+
return 1
|
|
864
|
+
command_args = [
|
|
865
|
+
"--flow",
|
|
866
|
+
args.flow,
|
|
867
|
+
"--slug",
|
|
868
|
+
base_slug,
|
|
869
|
+
"--summary",
|
|
870
|
+
args.summary,
|
|
871
|
+
"--date",
|
|
872
|
+
args.date,
|
|
873
|
+
"--kind",
|
|
874
|
+
args.kind,
|
|
875
|
+
"--artifact-class",
|
|
876
|
+
args.artifact_class,
|
|
877
|
+
"--force",
|
|
878
|
+
]
|
|
879
|
+
for item in args.target_path:
|
|
880
|
+
command_args.extend(["--target-path", item])
|
|
881
|
+
created = run_json_script("init_codecgc_workflow.py", *command_args)
|
|
882
|
+
if not created.get("success"):
|
|
883
|
+
raise RuntimeError(str(created.get("error", "修复工作流骨架失败。")))
|
|
884
|
+
notes.append("已通过强制覆盖修复工作流骨架。")
|
|
885
|
+
|
|
886
|
+
flow_dir = (
|
|
887
|
+
Path(created["directory"])
|
|
888
|
+
if created and created.get("directory")
|
|
889
|
+
else flow_root(args.flow, args.artifact_class) / artifact_slug
|
|
890
|
+
)
|
|
891
|
+
if args.flow == "feature":
|
|
892
|
+
notes.extend(enrich_feature_artifacts(flow_dir, base_slug, args))
|
|
893
|
+
else:
|
|
894
|
+
notes.extend(enrich_issue_artifacts(flow_dir, base_slug, args))
|
|
895
|
+
notes.append("已将结构化规划提示写入工作流产物。")
|
|
896
|
+
|
|
897
|
+
grouped_paths, _ = build_path_groups(args)
|
|
898
|
+
acceptance = cleaned_items(args.acceptance)
|
|
899
|
+
in_scope = cleaned_items(args.in_scope)
|
|
900
|
+
steps = build_execution_steps(base_slug, args.flow, grouped_paths, acceptance)
|
|
901
|
+
executable_steps, planning_only_steps = summarize_step_counts(steps)
|
|
902
|
+
plan_decision = evaluate_plan_decision(
|
|
903
|
+
flow=args.flow,
|
|
904
|
+
grouped_paths=grouped_paths,
|
|
905
|
+
target_paths=cleaned_items(args.target_path),
|
|
906
|
+
executable_steps=executable_steps,
|
|
907
|
+
planning_only_steps=planning_only_steps,
|
|
908
|
+
goal=args.goal,
|
|
909
|
+
user_story=args.user_story,
|
|
910
|
+
symptom=args.symptom,
|
|
911
|
+
expected=args.expected,
|
|
912
|
+
actual=args.actual,
|
|
913
|
+
in_scope=in_scope,
|
|
914
|
+
acceptance=acceptance,
|
|
915
|
+
)
|
|
916
|
+
roadmap_result = None
|
|
917
|
+
roadmap_children = None
|
|
918
|
+
if plan_decision.get("planning_status") == "needs-roadmap":
|
|
919
|
+
roadmap_result = init_roadmap_from_plan(
|
|
920
|
+
slug=artifact_slug,
|
|
921
|
+
summary=args.summary,
|
|
922
|
+
args=args,
|
|
923
|
+
grouped_paths=grouped_paths,
|
|
924
|
+
reasons=[str(item) for item in plan_decision.get("reasons", [])],
|
|
925
|
+
)
|
|
926
|
+
if roadmap_result.get("success"):
|
|
927
|
+
notes.append("已为这条更大范围的请求初始化 roadmap 骨架。")
|
|
928
|
+
roadmap_children = expand_roadmap_children_from_plan(
|
|
929
|
+
initiative=artifact_slug,
|
|
930
|
+
summary=args.summary,
|
|
931
|
+
args=args,
|
|
932
|
+
grouped_paths=grouped_paths,
|
|
933
|
+
)
|
|
934
|
+
if roadmap_children.get("success"):
|
|
935
|
+
notes.append("已根据 roadmap 分轨初始化子工作流骨架。")
|
|
936
|
+
|
|
937
|
+
route_after = run_json_script(
|
|
938
|
+
"route_codecgc_workflow.py",
|
|
939
|
+
"--flow",
|
|
940
|
+
args.flow,
|
|
941
|
+
"--slug",
|
|
942
|
+
artifact_slug,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
result = {
|
|
946
|
+
"success": True,
|
|
947
|
+
"flow": args.flow,
|
|
948
|
+
"slug": artifact_slug,
|
|
949
|
+
"created": created is not None,
|
|
950
|
+
"init": created,
|
|
951
|
+
"planning_status": plan_decision.get("planning_status", ""),
|
|
952
|
+
"planning_reasons": plan_decision.get("reasons", []),
|
|
953
|
+
"planning_missing_fields": plan_decision.get("missing_fields", []),
|
|
954
|
+
"roadmap": roadmap_result,
|
|
955
|
+
"roadmap_children": roadmap_children,
|
|
956
|
+
"route": route_after,
|
|
957
|
+
"recommended_command": plan_decision.get("recommended_command") or route_after.get("recommended_command", ""),
|
|
958
|
+
"next": plan_decision.get("next") or route_after.get("next", ""),
|
|
959
|
+
"notes": notes,
|
|
960
|
+
}
|
|
961
|
+
except Exception as error:
|
|
962
|
+
print_json({"success": False, "error": str(error)}, file=sys.stderr)
|
|
963
|
+
return 1
|
|
964
|
+
|
|
965
|
+
print_json(result)
|
|
966
|
+
return 0
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
if __name__ == "__main__":
|
|
970
|
+
raise SystemExit(main())
|