@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,3419 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from build_codecgc_task import classify_path
|
|
11
|
+
from build_codecgc_task import load_checklist_yaml
|
|
12
|
+
from build_codecgc_task import load_simple_routing_config
|
|
13
|
+
from codecgc_artifact_roots import discover_flow_directory
|
|
14
|
+
from codecgc_artifact_roots import flow_root
|
|
15
|
+
from codecgc_command_surface import matches_command
|
|
16
|
+
from codecgc_command_surface import to_internal_command
|
|
17
|
+
from codecgc_command_surface import to_public_command
|
|
18
|
+
from codecgc_console_io import configure_utf8_stdio
|
|
19
|
+
from codecgc_console_io import print_json
|
|
20
|
+
from codecgc_routing_paths import resolve_active_routing_file
|
|
21
|
+
from codecgc_runtime_paths import PACKAGE_ROOT
|
|
22
|
+
from codecgc_session_recovery import resolve_session_id_from_task
|
|
23
|
+
from codecgc_workflow_runtime import run_json_script
|
|
24
|
+
|
|
25
|
+
WORKSPACE = PACKAGE_ROOT
|
|
26
|
+
ROUTING_FILE = resolve_active_routing_file()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
description="Single-entry orchestration layer for CodeCGC."
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument("--payload-json", default="")
|
|
34
|
+
parser.add_argument("--payload-base64", default="")
|
|
35
|
+
parser.add_argument("--payload-file", default="")
|
|
36
|
+
parser.add_argument("--mode", choices=["auto", "new", "continue", "explain"], default="auto")
|
|
37
|
+
parser.add_argument("--flow", choices=["feature", "issue"], default="")
|
|
38
|
+
parser.add_argument("--slug", default="")
|
|
39
|
+
parser.add_argument("--summary", default="")
|
|
40
|
+
parser.add_argument("--request", default="")
|
|
41
|
+
parser.add_argument("--date", default="")
|
|
42
|
+
parser.add_argument("--target-path", action="append", default=[])
|
|
43
|
+
parser.add_argument("--kind", choices=["auto", "frontend", "backend"], default="auto")
|
|
44
|
+
parser.add_argument("--goal", default="")
|
|
45
|
+
parser.add_argument("--context", action="append", default=[])
|
|
46
|
+
parser.add_argument("--user-story", default="")
|
|
47
|
+
parser.add_argument("--in-scope", action="append", default=[])
|
|
48
|
+
parser.add_argument("--out-of-scope", action="append", default=[])
|
|
49
|
+
parser.add_argument("--acceptance", action="append", default=[])
|
|
50
|
+
parser.add_argument("--risk", action="append", default=[])
|
|
51
|
+
parser.add_argument("--dependency", action="append", default=[])
|
|
52
|
+
parser.add_argument("--assumption", action="append", default=[])
|
|
53
|
+
parser.add_argument("--open-question", action="append", default=[])
|
|
54
|
+
parser.add_argument("--validation", action="append", default=[])
|
|
55
|
+
parser.add_argument("--rollback", action="append", default=[])
|
|
56
|
+
parser.add_argument("--symptom", default="")
|
|
57
|
+
parser.add_argument("--reproduction", default="")
|
|
58
|
+
parser.add_argument("--expected", default="")
|
|
59
|
+
parser.add_argument("--actual", default="")
|
|
60
|
+
parser.add_argument("--root-cause", default="")
|
|
61
|
+
parser.add_argument("--preferred-fix", default="")
|
|
62
|
+
parser.add_argument("--rejected-fix", default="")
|
|
63
|
+
parser.add_argument("--artifact-class", choices=["product", "fixture"], default="product")
|
|
64
|
+
parser.add_argument("--latest", action="store_true")
|
|
65
|
+
parser.add_argument("--include-fixtures", action="store_true")
|
|
66
|
+
parser.add_argument("--step-number", type=int)
|
|
67
|
+
parser.add_argument("--checklist-file", default="")
|
|
68
|
+
parser.add_argument("--audit-root", default="")
|
|
69
|
+
parser.add_argument("--timeout-seconds", type=int, default=120)
|
|
70
|
+
parser.add_argument("--dry-run", action="store_true")
|
|
71
|
+
parser.add_argument("--return-all-messages", action="store_true")
|
|
72
|
+
parser.add_argument("--auto-dispatch", action="store_true")
|
|
73
|
+
parser.add_argument("--decision", choices=["accepted", "changes-requested"], default="")
|
|
74
|
+
parser.add_argument("--audit-file", default="")
|
|
75
|
+
parser.add_argument("--next-step", default="")
|
|
76
|
+
parser.add_argument("--force", action="store_true")
|
|
77
|
+
return parser
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_payload_input(args: argparse.Namespace) -> dict[str, Any]:
|
|
81
|
+
if args.payload_json.strip():
|
|
82
|
+
payload = json.loads(args.payload_json)
|
|
83
|
+
elif args.payload_base64.strip():
|
|
84
|
+
decoded = base64.b64decode(args.payload_base64).decode("utf-8")
|
|
85
|
+
payload = json.loads(decoded)
|
|
86
|
+
elif args.payload_file.strip():
|
|
87
|
+
payload = json.loads(Path(args.payload_file).read_text(encoding="utf-8-sig"))
|
|
88
|
+
else:
|
|
89
|
+
return {}
|
|
90
|
+
|
|
91
|
+
if not isinstance(payload, dict):
|
|
92
|
+
raise ValueError("Entry payload must be a JSON object.")
|
|
93
|
+
return payload
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def merge_scalar_arg(args: argparse.Namespace, payload: dict[str, Any], field: str, default_value: str = "") -> None:
|
|
97
|
+
current = getattr(args, field)
|
|
98
|
+
if current != default_value:
|
|
99
|
+
return
|
|
100
|
+
value = payload.get(field)
|
|
101
|
+
if isinstance(value, str) and value.strip():
|
|
102
|
+
setattr(args, field, value.strip())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def merge_list_arg(args: argparse.Namespace, payload: dict[str, Any], field: str) -> None:
|
|
106
|
+
current = getattr(args, field)
|
|
107
|
+
if current:
|
|
108
|
+
return
|
|
109
|
+
value = payload.get(field)
|
|
110
|
+
if isinstance(value, list):
|
|
111
|
+
merged = [str(item).strip() for item in value if str(item).strip()]
|
|
112
|
+
if merged:
|
|
113
|
+
setattr(args, field, merged)
|
|
114
|
+
elif isinstance(value, str) and value.strip():
|
|
115
|
+
setattr(args, field, [value.strip()])
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def merge_list_arg_alias(args: argparse.Namespace, payload: dict[str, Any], field: str, alias: str) -> None:
|
|
119
|
+
current = getattr(args, field)
|
|
120
|
+
if current:
|
|
121
|
+
return
|
|
122
|
+
value = payload.get(alias)
|
|
123
|
+
if isinstance(value, list):
|
|
124
|
+
merged = [str(item).strip() for item in value if str(item).strip()]
|
|
125
|
+
if merged:
|
|
126
|
+
setattr(args, field, merged)
|
|
127
|
+
elif isinstance(value, str) and value.strip():
|
|
128
|
+
setattr(args, field, [value.strip()])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def apply_entry_payload(args: argparse.Namespace) -> argparse.Namespace:
|
|
132
|
+
payload = load_payload_input(args)
|
|
133
|
+
if not payload:
|
|
134
|
+
return args
|
|
135
|
+
|
|
136
|
+
merge_scalar_arg(args, payload, "mode", "auto")
|
|
137
|
+
merge_scalar_arg(args, payload, "flow", "")
|
|
138
|
+
merge_scalar_arg(args, payload, "slug", "")
|
|
139
|
+
merge_scalar_arg(args, payload, "summary", "")
|
|
140
|
+
merge_scalar_arg(args, payload, "request", "")
|
|
141
|
+
merge_scalar_arg(args, payload, "date", "")
|
|
142
|
+
merge_scalar_arg(args, payload, "kind", "auto")
|
|
143
|
+
merge_scalar_arg(args, payload, "goal", "")
|
|
144
|
+
merge_scalar_arg(args, payload, "user_story", "")
|
|
145
|
+
merge_scalar_arg(args, payload, "symptom", "")
|
|
146
|
+
merge_scalar_arg(args, payload, "reproduction", "")
|
|
147
|
+
merge_scalar_arg(args, payload, "expected", "")
|
|
148
|
+
merge_scalar_arg(args, payload, "actual", "")
|
|
149
|
+
merge_scalar_arg(args, payload, "root_cause", "")
|
|
150
|
+
merge_scalar_arg(args, payload, "preferred_fix", "")
|
|
151
|
+
merge_scalar_arg(args, payload, "rejected_fix", "")
|
|
152
|
+
merge_scalar_arg(args, payload, "artifact_class", "product")
|
|
153
|
+
merge_scalar_arg(args, payload, "audit_root", "")
|
|
154
|
+
merge_scalar_arg(args, payload, "audit_file", "")
|
|
155
|
+
merge_scalar_arg(args, payload, "next_step", "")
|
|
156
|
+
merge_scalar_arg(args, payload, "decision", "")
|
|
157
|
+
|
|
158
|
+
merge_list_arg(args, payload, "target_path")
|
|
159
|
+
merge_list_arg_alias(args, payload, "target_path", "target_paths")
|
|
160
|
+
merge_list_arg(args, payload, "context")
|
|
161
|
+
merge_list_arg(args, payload, "in_scope")
|
|
162
|
+
merge_list_arg(args, payload, "out_of_scope")
|
|
163
|
+
merge_list_arg(args, payload, "acceptance")
|
|
164
|
+
merge_list_arg(args, payload, "risk")
|
|
165
|
+
merge_list_arg(args, payload, "dependency")
|
|
166
|
+
merge_list_arg(args, payload, "assumption")
|
|
167
|
+
merge_list_arg(args, payload, "open_question")
|
|
168
|
+
merge_list_arg(args, payload, "validation")
|
|
169
|
+
merge_list_arg(args, payload, "rollback")
|
|
170
|
+
|
|
171
|
+
if not args.latest and bool(payload.get("latest")):
|
|
172
|
+
args.latest = True
|
|
173
|
+
if not args.include_fixtures and bool(payload.get("include_fixtures")):
|
|
174
|
+
args.include_fixtures = True
|
|
175
|
+
if args.step_number is None and payload.get("step_number") is not None:
|
|
176
|
+
args.step_number = int(payload["step_number"])
|
|
177
|
+
if args.timeout_seconds == 120 and payload.get("timeout_seconds") is not None:
|
|
178
|
+
args.timeout_seconds = int(payload["timeout_seconds"])
|
|
179
|
+
if not args.auto_dispatch and bool(payload.get("auto_dispatch")):
|
|
180
|
+
args.auto_dispatch = True
|
|
181
|
+
if not args.dry_run and bool(payload.get("dry_run")):
|
|
182
|
+
args.dry_run = True
|
|
183
|
+
if not args.return_all_messages and bool(payload.get("return_all_messages")):
|
|
184
|
+
args.return_all_messages = True
|
|
185
|
+
if not args.force and bool(payload.get("force")):
|
|
186
|
+
args.force = True
|
|
187
|
+
|
|
188
|
+
return args
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def resolve_governance_followup(payload: dict[str, Any], request: str) -> dict[str, Any]:
|
|
192
|
+
governance_type = str(payload.get("governance_type", "")).strip()
|
|
193
|
+
artifact_path = str(payload.get("artifact_path", "")).strip()
|
|
194
|
+
if not governance_type or not artifact_path:
|
|
195
|
+
return {}
|
|
196
|
+
|
|
197
|
+
request_text = request.strip()
|
|
198
|
+
if not request_text:
|
|
199
|
+
return {}
|
|
200
|
+
|
|
201
|
+
lowered = request_text.lower()
|
|
202
|
+
original_summary = str(payload.get("artifact_summary", "")).strip()
|
|
203
|
+
|
|
204
|
+
def pick_clause(*patterns: str) -> str:
|
|
205
|
+
return extract_clause(request_text, list(patterns))
|
|
206
|
+
|
|
207
|
+
def split_labeled_fields(mapping: dict[str, list[str]]) -> dict[str, str]:
|
|
208
|
+
labels: list[tuple[int, int, str]] = []
|
|
209
|
+
for key, aliases in mapping.items():
|
|
210
|
+
for alias in aliases:
|
|
211
|
+
for match in re.finditer(rf"{re.escape(alias)}[::]\s*", request_text, flags=re.IGNORECASE):
|
|
212
|
+
labels.append((match.start(), match.end(), key))
|
|
213
|
+
labels.sort(key=lambda item: item[0])
|
|
214
|
+
values: dict[str, str] = {key: "" for key in mapping}
|
|
215
|
+
for index, (_, content_start, key) in enumerate(labels):
|
|
216
|
+
next_start = labels[index + 1][0] if index + 1 < len(labels) else len(request_text)
|
|
217
|
+
chunk = request_text[content_start:next_start].strip(" 。;;,,\n")
|
|
218
|
+
if chunk and not values.get(key):
|
|
219
|
+
values[key] = chunk
|
|
220
|
+
return values
|
|
221
|
+
|
|
222
|
+
def build_flag_args(pairs: list[tuple[str, str]], *, required_pairs: list[tuple[str, str]] | None = None) -> list[str]:
|
|
223
|
+
args: list[str] = []
|
|
224
|
+
for flag, value in required_pairs or []:
|
|
225
|
+
args.extend([flag, value])
|
|
226
|
+
for flag, value in pairs:
|
|
227
|
+
if value.strip():
|
|
228
|
+
args.extend([flag, value.strip()])
|
|
229
|
+
return args
|
|
230
|
+
|
|
231
|
+
if governance_type == "guide":
|
|
232
|
+
details = split_labeled_fields(
|
|
233
|
+
{
|
|
234
|
+
"purpose": ["目的", "purpose"],
|
|
235
|
+
"steps": ["步骤", "steps", "step"],
|
|
236
|
+
"boundary": ["边界", "boundary"],
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
details["append_note"] = request_text if not any(details.values()) else ""
|
|
240
|
+
return {
|
|
241
|
+
"governance_type": governance_type,
|
|
242
|
+
"script": "write_codecgc_guide.py",
|
|
243
|
+
"args": build_flag_args(
|
|
244
|
+
[
|
|
245
|
+
("--purpose", details["purpose"]),
|
|
246
|
+
("--steps", details["steps"]),
|
|
247
|
+
("--boundary", details["boundary"]),
|
|
248
|
+
("--append-note", details["append_note"]),
|
|
249
|
+
],
|
|
250
|
+
required_pairs=[
|
|
251
|
+
("--summary", original_summary or request_text),
|
|
252
|
+
("--artifact-path", artifact_path),
|
|
253
|
+
],
|
|
254
|
+
),
|
|
255
|
+
"artifact_summary": original_summary or request_text,
|
|
256
|
+
"artifact_path": artifact_path,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if governance_type == "libdoc":
|
|
260
|
+
details = split_labeled_fields(
|
|
261
|
+
{
|
|
262
|
+
"entry": ["入口", "entry"],
|
|
263
|
+
"contract": ["契约", "contract"],
|
|
264
|
+
"example": ["示例", "example"],
|
|
265
|
+
"boundary": ["边界", "boundary"],
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
details["append_note"] = request_text if not any(details.values()) else ""
|
|
269
|
+
return {
|
|
270
|
+
"governance_type": governance_type,
|
|
271
|
+
"script": "write_codecgc_libdoc.py",
|
|
272
|
+
"args": build_flag_args(
|
|
273
|
+
[
|
|
274
|
+
("--entry", details["entry"]),
|
|
275
|
+
("--contract", details["contract"]),
|
|
276
|
+
("--example", details["example"]),
|
|
277
|
+
("--boundary", details["boundary"]),
|
|
278
|
+
("--append-note", details["append_note"]),
|
|
279
|
+
],
|
|
280
|
+
required_pairs=[
|
|
281
|
+
("--summary", original_summary or request_text),
|
|
282
|
+
("--artifact-path", artifact_path),
|
|
283
|
+
],
|
|
284
|
+
),
|
|
285
|
+
"artifact_summary": original_summary or request_text,
|
|
286
|
+
"artifact_path": artifact_path,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if governance_type == "trick":
|
|
290
|
+
details = split_labeled_fields(
|
|
291
|
+
{
|
|
292
|
+
"practice": ["做法", "practice"],
|
|
293
|
+
"scope": ["范围", "scope"],
|
|
294
|
+
"counterexample": ["反例", "误用", "counterexample"],
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
details["append_note"] = request_text if not any(details.values()) else ""
|
|
298
|
+
return {
|
|
299
|
+
"governance_type": governance_type,
|
|
300
|
+
"script": "write_codecgc_trick.py",
|
|
301
|
+
"args": build_flag_args(
|
|
302
|
+
[
|
|
303
|
+
("--practice", details["practice"]),
|
|
304
|
+
("--scope", details["scope"]),
|
|
305
|
+
("--counterexample", details["counterexample"]),
|
|
306
|
+
("--append-note", details["append_note"]),
|
|
307
|
+
],
|
|
308
|
+
required_pairs=[
|
|
309
|
+
("--summary", original_summary or request_text),
|
|
310
|
+
("--artifact-path", artifact_path),
|
|
311
|
+
],
|
|
312
|
+
),
|
|
313
|
+
"artifact_summary": original_summary or request_text,
|
|
314
|
+
"artifact_path": artifact_path,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if governance_type == "explore":
|
|
318
|
+
details = split_labeled_fields(
|
|
319
|
+
{
|
|
320
|
+
"question_detail": ["问题", "question"],
|
|
321
|
+
"target_modules": ["模块", "目录", "文件", "module"],
|
|
322
|
+
"expected_output": ["输出", "结论", "output"],
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
details["append_note"] = request_text if not any(details.values()) else ""
|
|
326
|
+
return {
|
|
327
|
+
"governance_type": governance_type,
|
|
328
|
+
"script": "write_codecgc_explore.py",
|
|
329
|
+
"args": build_flag_args(
|
|
330
|
+
[
|
|
331
|
+
("--question-detail", details["question_detail"]),
|
|
332
|
+
("--target-modules", details["target_modules"]),
|
|
333
|
+
("--expected-output", details["expected_output"]),
|
|
334
|
+
("--append-note", details["append_note"]),
|
|
335
|
+
],
|
|
336
|
+
required_pairs=[
|
|
337
|
+
("--summary", original_summary or request_text),
|
|
338
|
+
("--artifact-path", artifact_path),
|
|
339
|
+
],
|
|
340
|
+
),
|
|
341
|
+
"artifact_summary": original_summary or request_text,
|
|
342
|
+
"artifact_path": artifact_path,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def summarize_new_work_seed(args: argparse.Namespace, flow: str) -> str:
|
|
349
|
+
if args.summary.strip():
|
|
350
|
+
return args.summary.strip()
|
|
351
|
+
if args.request.strip():
|
|
352
|
+
return args.request.strip()
|
|
353
|
+
if flow == "feature":
|
|
354
|
+
for value in (args.goal, args.user_story):
|
|
355
|
+
if value.strip():
|
|
356
|
+
return value.strip()
|
|
357
|
+
else:
|
|
358
|
+
for value in (args.symptom, args.expected, args.actual):
|
|
359
|
+
if value.strip():
|
|
360
|
+
return value.strip()
|
|
361
|
+
target_paths = [item.strip() for item in args.target_path if item.strip()]
|
|
362
|
+
if target_paths:
|
|
363
|
+
return f"{flow} for {target_paths[0]}"
|
|
364
|
+
return ""
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def infer_flow_for_new_work(args: argparse.Namespace) -> str:
|
|
368
|
+
if args.flow:
|
|
369
|
+
return args.flow
|
|
370
|
+
request = args.request.strip().lower()
|
|
371
|
+
issue_signals = [
|
|
372
|
+
args.symptom,
|
|
373
|
+
args.reproduction,
|
|
374
|
+
args.expected,
|
|
375
|
+
args.actual,
|
|
376
|
+
args.root_cause,
|
|
377
|
+
args.preferred_fix,
|
|
378
|
+
args.rejected_fix,
|
|
379
|
+
]
|
|
380
|
+
issue_keywords = [
|
|
381
|
+
"bug",
|
|
382
|
+
"fix",
|
|
383
|
+
"issue",
|
|
384
|
+
"error",
|
|
385
|
+
"failure",
|
|
386
|
+
"broken",
|
|
387
|
+
"problem",
|
|
388
|
+
"修复",
|
|
389
|
+
"问题",
|
|
390
|
+
"报错",
|
|
391
|
+
"异常",
|
|
392
|
+
"故障",
|
|
393
|
+
]
|
|
394
|
+
if any(keyword in request for keyword in issue_keywords):
|
|
395
|
+
return "issue"
|
|
396
|
+
return "issue" if any(value.strip() for value in issue_signals) else "feature"
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def request_implies_continue(request: str) -> bool:
|
|
400
|
+
lowered = request.lower()
|
|
401
|
+
keywords = [
|
|
402
|
+
"continue",
|
|
403
|
+
"resume",
|
|
404
|
+
"pick up",
|
|
405
|
+
"go on",
|
|
406
|
+
"继续",
|
|
407
|
+
"接着",
|
|
408
|
+
"继续做",
|
|
409
|
+
"继续推进",
|
|
410
|
+
]
|
|
411
|
+
return any(keyword in lowered for keyword in keywords)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def request_implies_explain(request: str) -> bool:
|
|
415
|
+
lowered = request.lower()
|
|
416
|
+
keywords = [
|
|
417
|
+
"what next",
|
|
418
|
+
"next step",
|
|
419
|
+
"status",
|
|
420
|
+
"explain",
|
|
421
|
+
"why",
|
|
422
|
+
"state",
|
|
423
|
+
"下一步",
|
|
424
|
+
"现在该做什么",
|
|
425
|
+
"状态",
|
|
426
|
+
"解释",
|
|
427
|
+
"看看进度",
|
|
428
|
+
]
|
|
429
|
+
return any(keyword in lowered for keyword in keywords)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def request_implies_execute(request: str) -> bool:
|
|
433
|
+
lowered = request.lower()
|
|
434
|
+
keywords = [
|
|
435
|
+
"implement",
|
|
436
|
+
"build it",
|
|
437
|
+
"fix it",
|
|
438
|
+
"execute",
|
|
439
|
+
"run it",
|
|
440
|
+
"go ahead",
|
|
441
|
+
"start now",
|
|
442
|
+
"处理掉",
|
|
443
|
+
"解决掉",
|
|
444
|
+
"直接做",
|
|
445
|
+
"开始做",
|
|
446
|
+
"开工",
|
|
447
|
+
"执行",
|
|
448
|
+
"继续处理",
|
|
449
|
+
"继续执行",
|
|
450
|
+
"直接修复",
|
|
451
|
+
"马上修复",
|
|
452
|
+
"立即修复",
|
|
453
|
+
"直接实现",
|
|
454
|
+
"马上实现",
|
|
455
|
+
"立即实现",
|
|
456
|
+
"直接开始",
|
|
457
|
+
"马上开始",
|
|
458
|
+
"立即开始",
|
|
459
|
+
"测试",
|
|
460
|
+
"补测试",
|
|
461
|
+
"写测试",
|
|
462
|
+
"加测试",
|
|
463
|
+
"生成测试",
|
|
464
|
+
]
|
|
465
|
+
return any(keyword in lowered for keyword in keywords)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def request_implies_review(request: str) -> bool:
|
|
469
|
+
lowered = request.lower()
|
|
470
|
+
keywords = [
|
|
471
|
+
"review",
|
|
472
|
+
"acceptance",
|
|
473
|
+
"approve",
|
|
474
|
+
"验收",
|
|
475
|
+
"审核",
|
|
476
|
+
"审查",
|
|
477
|
+
"检查一下",
|
|
478
|
+
"看下结果",
|
|
479
|
+
"过一下",
|
|
480
|
+
"通过",
|
|
481
|
+
"不通过",
|
|
482
|
+
]
|
|
483
|
+
return any(keyword in lowered for keyword in keywords)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def normalize_request_shortcut(request: str) -> str:
|
|
487
|
+
normalized = request.strip().lower()
|
|
488
|
+
normalized = re.sub(r"\s+", "", normalized)
|
|
489
|
+
normalized = normalized.strip("`\"',,。;;::!?!?()()[]{}")
|
|
490
|
+
return normalized
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def classify_existing_workflow_shortcut(request: str) -> str:
|
|
494
|
+
normalized = normalize_request_shortcut(request)
|
|
495
|
+
if not normalized:
|
|
496
|
+
return ""
|
|
497
|
+
|
|
498
|
+
explain_shortcuts = {
|
|
499
|
+
"下一步",
|
|
500
|
+
"现在下一步该做什么",
|
|
501
|
+
"当前下一步",
|
|
502
|
+
"看看进度",
|
|
503
|
+
"当前状态",
|
|
504
|
+
"解释一下当前状态",
|
|
505
|
+
"解释当前状态",
|
|
506
|
+
"看看状态",
|
|
507
|
+
}
|
|
508
|
+
continue_shortcuts = {
|
|
509
|
+
"继续",
|
|
510
|
+
"继续做",
|
|
511
|
+
"继续处理",
|
|
512
|
+
"继续推进",
|
|
513
|
+
"继续执行",
|
|
514
|
+
"继续刚刚的工作",
|
|
515
|
+
"接着做",
|
|
516
|
+
"接着处理",
|
|
517
|
+
"直接做",
|
|
518
|
+
"直接开始",
|
|
519
|
+
"开始做",
|
|
520
|
+
"开始吧",
|
|
521
|
+
"开工",
|
|
522
|
+
"马上开始",
|
|
523
|
+
"立即开始",
|
|
524
|
+
"处理掉",
|
|
525
|
+
"解决掉",
|
|
526
|
+
"审核",
|
|
527
|
+
"审核一下",
|
|
528
|
+
"审查",
|
|
529
|
+
"审查一下",
|
|
530
|
+
"验收",
|
|
531
|
+
"验收一下",
|
|
532
|
+
"看下结果",
|
|
533
|
+
"看结果",
|
|
534
|
+
"过一下",
|
|
535
|
+
"通过",
|
|
536
|
+
"不通过",
|
|
537
|
+
"需修改",
|
|
538
|
+
"需要修改",
|
|
539
|
+
"测试",
|
|
540
|
+
"补测试",
|
|
541
|
+
"写测试",
|
|
542
|
+
"加测试",
|
|
543
|
+
"生成测试",
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if normalized in explain_shortcuts:
|
|
547
|
+
return "explain"
|
|
548
|
+
if normalized in continue_shortcuts:
|
|
549
|
+
return "continue"
|
|
550
|
+
return ""
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def classify_governance_request(request: str) -> dict[str, str]:
|
|
554
|
+
lowered = request.lower().strip()
|
|
555
|
+
if not lowered:
|
|
556
|
+
return {}
|
|
557
|
+
|
|
558
|
+
governance_patterns = [
|
|
559
|
+
(
|
|
560
|
+
"cgc-arch",
|
|
561
|
+
"arch",
|
|
562
|
+
[
|
|
563
|
+
"架构文档",
|
|
564
|
+
"更新架构",
|
|
565
|
+
"架构图",
|
|
566
|
+
"系统图",
|
|
567
|
+
"architecture",
|
|
568
|
+
"system map",
|
|
569
|
+
"integration map",
|
|
570
|
+
],
|
|
571
|
+
"当前请求更适合回写长期架构现状,而不是进入 feature/issue 执行流。",
|
|
572
|
+
"下一步进入 cgc-arch,更新 codecgc/architecture/ 下的当前态文档。",
|
|
573
|
+
),
|
|
574
|
+
(
|
|
575
|
+
"cgc-req",
|
|
576
|
+
"req",
|
|
577
|
+
[
|
|
578
|
+
"需求文档",
|
|
579
|
+
"requirements",
|
|
580
|
+
"回写需求",
|
|
581
|
+
"补需求",
|
|
582
|
+
"当前能力说明",
|
|
583
|
+
"稳定需求",
|
|
584
|
+
"需求边界",
|
|
585
|
+
"稳定需求边界",
|
|
586
|
+
"能力边界",
|
|
587
|
+
"产品边界",
|
|
588
|
+
],
|
|
589
|
+
"当前请求更适合回写稳定需求边界,而不是进入一次性执行流。",
|
|
590
|
+
"下一步进入 cgc-req,更新 codecgc/requirements/ 下的稳定能力说明。",
|
|
591
|
+
),
|
|
592
|
+
(
|
|
593
|
+
"cgc-explore",
|
|
594
|
+
"explore",
|
|
595
|
+
[
|
|
596
|
+
"explore",
|
|
597
|
+
"探索一下",
|
|
598
|
+
"快速熟悉",
|
|
599
|
+
"这个仓库里",
|
|
600
|
+
"怎么实现",
|
|
601
|
+
"module overview",
|
|
602
|
+
"spike",
|
|
603
|
+
],
|
|
604
|
+
"当前请求更适合登记为定向代码探索,而不是直接进入执行流。",
|
|
605
|
+
"下一步进入 cgc-explore,沉淀这条探索问题或模块概览请求。",
|
|
606
|
+
),
|
|
607
|
+
(
|
|
608
|
+
"cgc-roadmap",
|
|
609
|
+
"roadmap",
|
|
610
|
+
[
|
|
611
|
+
"roadmap",
|
|
612
|
+
"路线图",
|
|
613
|
+
"分阶段",
|
|
614
|
+
"阶段拆解",
|
|
615
|
+
"大需求拆解",
|
|
616
|
+
"长期规划",
|
|
617
|
+
"生命周期蓝图",
|
|
618
|
+
"发布规划",
|
|
619
|
+
"运维规划",
|
|
620
|
+
"maintenance",
|
|
621
|
+
"release plan",
|
|
622
|
+
"release roadmap",
|
|
623
|
+
"maintenance roadmap",
|
|
624
|
+
"ops roadmap",
|
|
625
|
+
"lifecycle",
|
|
626
|
+
],
|
|
627
|
+
"当前请求更像 roadmap 级分解,不适合直接落成单个 feature/issue step。",
|
|
628
|
+
"下一步进入 cgc-roadmap,把需求拆成 phases、tracks 或 child workflows。",
|
|
629
|
+
),
|
|
630
|
+
(
|
|
631
|
+
"cgc-decide",
|
|
632
|
+
"decide",
|
|
633
|
+
[
|
|
634
|
+
"记录决定",
|
|
635
|
+
"记录这个决定",
|
|
636
|
+
"决定记录下来",
|
|
637
|
+
"把这条决定记录下来",
|
|
638
|
+
"记成决定",
|
|
639
|
+
"长期决定",
|
|
640
|
+
"记录为约束",
|
|
641
|
+
"作为长期约束",
|
|
642
|
+
"技术选型",
|
|
643
|
+
"架构决策",
|
|
644
|
+
"adr",
|
|
645
|
+
"decision",
|
|
646
|
+
"约束",
|
|
647
|
+
"编码规约",
|
|
648
|
+
],
|
|
649
|
+
"当前请求是长期决策归档动作,不应误走 feature/issue 执行流。",
|
|
650
|
+
"下一步进入 cgc-decide,记录已确认的约束、选型或长期规则。",
|
|
651
|
+
),
|
|
652
|
+
(
|
|
653
|
+
"cgc-learn",
|
|
654
|
+
"learn",
|
|
655
|
+
[
|
|
656
|
+
"沉淀经验",
|
|
657
|
+
"记录经验",
|
|
658
|
+
"沉淀成经验",
|
|
659
|
+
"值得沉淀",
|
|
660
|
+
"值得记录",
|
|
661
|
+
"踩坑",
|
|
662
|
+
"教训",
|
|
663
|
+
"pitfall",
|
|
664
|
+
"learning",
|
|
665
|
+
"最佳实践",
|
|
666
|
+
"经验",
|
|
667
|
+
],
|
|
668
|
+
"当前请求是经验沉淀动作,不应误走一次性执行流。",
|
|
669
|
+
"下一步进入 cgc-learn,记录可复用的经验、坑点或默认做法。",
|
|
670
|
+
),
|
|
671
|
+
(
|
|
672
|
+
"cgc-refactor",
|
|
673
|
+
"refactor",
|
|
674
|
+
[
|
|
675
|
+
"重构",
|
|
676
|
+
"refactor",
|
|
677
|
+
"代码优化",
|
|
678
|
+
"结构优化",
|
|
679
|
+
"可读性优化",
|
|
680
|
+
"性能优化",
|
|
681
|
+
],
|
|
682
|
+
"当前请求更像受控重构,而不是新 feature 或 issue 修复。",
|
|
683
|
+
"下一步进入 cgc-refactor,先确认行为不变边界,再进入受控执行。",
|
|
684
|
+
),
|
|
685
|
+
(
|
|
686
|
+
"cgc-guide",
|
|
687
|
+
"guide",
|
|
688
|
+
[
|
|
689
|
+
"用户指南",
|
|
690
|
+
"开发者指南",
|
|
691
|
+
"操作指南",
|
|
692
|
+
"使用指南",
|
|
693
|
+
"guide",
|
|
694
|
+
"how to",
|
|
695
|
+
"使用文档",
|
|
696
|
+
],
|
|
697
|
+
"当前请求更适合沉淀为面向用户或开发者的任务导向指南。",
|
|
698
|
+
"下一步进入 cgc-guide,把这条使用说明沉淀为长期 guide 资产。",
|
|
699
|
+
),
|
|
700
|
+
(
|
|
701
|
+
"cgc-libdoc",
|
|
702
|
+
"libdoc",
|
|
703
|
+
[
|
|
704
|
+
"api 文档",
|
|
705
|
+
"组件文档",
|
|
706
|
+
"命令文档",
|
|
707
|
+
"参考文档",
|
|
708
|
+
"libdoc",
|
|
709
|
+
"reference doc",
|
|
710
|
+
],
|
|
711
|
+
"当前请求更适合沉淀为公开表面的参考文档,而不是执行流。",
|
|
712
|
+
"下一步进入 cgc-libdoc,生成对应 API、组件或命令参考资产。",
|
|
713
|
+
),
|
|
714
|
+
(
|
|
715
|
+
"cgc-trick",
|
|
716
|
+
"trick",
|
|
717
|
+
[
|
|
718
|
+
"技巧",
|
|
719
|
+
"trick",
|
|
720
|
+
"pattern",
|
|
721
|
+
"技术技巧",
|
|
722
|
+
"记录这个用法",
|
|
723
|
+
"记录库用法",
|
|
724
|
+
"最佳用法",
|
|
725
|
+
],
|
|
726
|
+
"当前请求更适合沉淀为可复用的模式、库用法或技巧处方。",
|
|
727
|
+
"下一步进入 cgc-trick,记录这条可复用技巧资产。",
|
|
728
|
+
),
|
|
729
|
+
]
|
|
730
|
+
|
|
731
|
+
for skill, governance_type, keywords, summary, next_step in governance_patterns:
|
|
732
|
+
if any(keyword in lowered for keyword in keywords):
|
|
733
|
+
return {
|
|
734
|
+
"skill": skill,
|
|
735
|
+
"governance_type": governance_type,
|
|
736
|
+
"human_summary": summary,
|
|
737
|
+
"next": next_step,
|
|
738
|
+
}
|
|
739
|
+
return {}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def infer_review_decision_from_request(request: str) -> str:
|
|
743
|
+
lowered = request.lower()
|
|
744
|
+
accepted_keywords = [
|
|
745
|
+
"accept",
|
|
746
|
+
"approve",
|
|
747
|
+
"passed",
|
|
748
|
+
"通过",
|
|
749
|
+
"通过验收",
|
|
750
|
+
"没问题就通过",
|
|
751
|
+
"可以通过",
|
|
752
|
+
]
|
|
753
|
+
rejected_keywords = [
|
|
754
|
+
"changes requested",
|
|
755
|
+
"request changes",
|
|
756
|
+
"reject",
|
|
757
|
+
"不通过",
|
|
758
|
+
"驳回",
|
|
759
|
+
"需要修改",
|
|
760
|
+
"有问题就退回",
|
|
761
|
+
]
|
|
762
|
+
if any(keyword in lowered for keyword in rejected_keywords):
|
|
763
|
+
return "changes-requested"
|
|
764
|
+
if any(keyword in lowered for keyword in accepted_keywords):
|
|
765
|
+
return "accepted"
|
|
766
|
+
return ""
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def build_request_slug(flow: str, request: str) -> str:
|
|
770
|
+
normalized = re.sub(r"[^a-z0-9]+", "-", request.lower()).strip("-")
|
|
771
|
+
if not normalized:
|
|
772
|
+
normalized = "request"
|
|
773
|
+
normalized = re.sub(r"-{2,}", "-", normalized)
|
|
774
|
+
normalized = "-".join(normalized.split("-")[:6]).strip("-") or "request"
|
|
775
|
+
digest = hashlib.md5(request.encode("utf-8")).hexdigest()[:8]
|
|
776
|
+
return f"{flow}-{normalized}-{digest}"
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def resolve_new_slug(args: argparse.Namespace, flow: str) -> str:
|
|
780
|
+
if args.slug.strip():
|
|
781
|
+
return args.slug
|
|
782
|
+
seed = summarize_new_work_seed(args, flow)
|
|
783
|
+
if seed:
|
|
784
|
+
return build_request_slug(flow, seed)
|
|
785
|
+
raise ValueError("New work requires --slug, --request, or enough payload fields to infer a summary.")
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def resolve_new_summary(args: argparse.Namespace) -> str:
|
|
789
|
+
if args.summary.strip():
|
|
790
|
+
return args.summary
|
|
791
|
+
if args.request.strip():
|
|
792
|
+
return args.request.strip()
|
|
793
|
+
flow = infer_flow_for_new_work(args)
|
|
794
|
+
seed = summarize_new_work_seed(args, flow)
|
|
795
|
+
if seed:
|
|
796
|
+
return seed
|
|
797
|
+
raise ValueError("New work requires --summary, --request, or enough payload fields to infer a summary.")
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def normalize_request_path(path_text: str) -> str:
|
|
801
|
+
normalized = path_text.strip().strip("\"'`")
|
|
802
|
+
normalized = normalized.replace("\\", "/")
|
|
803
|
+
normalized = re.sub(r"/{2,}", "/", normalized)
|
|
804
|
+
return normalized.rstrip(".,;:!?)]}")
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _is_likely_false_positive_path(normalized: str) -> bool:
|
|
808
|
+
segments = normalized.split("/")
|
|
809
|
+
if all(seg.isdigit() for seg in segments if seg):
|
|
810
|
+
return True
|
|
811
|
+
if re.match(r"^\d{4}/\d{2}(/\d{2})?$", normalized):
|
|
812
|
+
return True
|
|
813
|
+
if re.match(r"^v?\d+\.\d+", segments[0]):
|
|
814
|
+
return True
|
|
815
|
+
if normalized.count("/") == 1 and "." not in normalized:
|
|
816
|
+
first, second = segments[0], segments[1] if len(segments) > 1 else ""
|
|
817
|
+
if first.isdigit() or second.isdigit():
|
|
818
|
+
return True
|
|
819
|
+
return False
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def extract_target_paths_from_request(request: str) -> list[str]:
|
|
823
|
+
if not request.strip():
|
|
824
|
+
return []
|
|
825
|
+
|
|
826
|
+
matches = re.findall(r"(?:(?:[A-Za-z]:)?[\\/])?(?:[\w.\-]+[\\/])+[\w.\-]+", request)
|
|
827
|
+
extracted: list[str] = []
|
|
828
|
+
seen: set[str] = set()
|
|
829
|
+
|
|
830
|
+
for raw in matches:
|
|
831
|
+
normalized = normalize_request_path(raw)
|
|
832
|
+
if "/" not in normalized:
|
|
833
|
+
continue
|
|
834
|
+
lowered = normalized.lower()
|
|
835
|
+
if lowered.startswith("http/") or lowered.startswith("https/"):
|
|
836
|
+
continue
|
|
837
|
+
if _is_likely_false_positive_path(lowered):
|
|
838
|
+
continue
|
|
839
|
+
if lowered in seen:
|
|
840
|
+
continue
|
|
841
|
+
seen.add(lowered)
|
|
842
|
+
extracted.append(normalized)
|
|
843
|
+
|
|
844
|
+
return extracted
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def classify_request_paths(paths: list[str]) -> str:
|
|
848
|
+
if not paths:
|
|
849
|
+
return "auto"
|
|
850
|
+
|
|
851
|
+
routing = load_simple_routing_config(ROUTING_FILE)
|
|
852
|
+
categories = {classify_path(path, routing) for path in paths}
|
|
853
|
+
|
|
854
|
+
if categories == {"frontend"}:
|
|
855
|
+
return "frontend"
|
|
856
|
+
if categories == {"backend"}:
|
|
857
|
+
return "backend"
|
|
858
|
+
return "auto"
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def resolve_target_paths(args: argparse.Namespace) -> list[str]:
|
|
862
|
+
explicit = [item.strip() for item in args.target_path if item.strip()]
|
|
863
|
+
if explicit:
|
|
864
|
+
return explicit
|
|
865
|
+
return extract_target_paths_from_request(args.request)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def resolve_kind(args: argparse.Namespace, target_paths: list[str]) -> str:
|
|
869
|
+
if args.kind != "auto":
|
|
870
|
+
return args.kind
|
|
871
|
+
return classify_request_paths(target_paths)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def cleaned_items(items: list[str]) -> list[str]:
|
|
875
|
+
return [item.strip() for item in items if item.strip()]
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def extract_clause(request: str, patterns: list[str]) -> str:
|
|
879
|
+
if not request.strip():
|
|
880
|
+
return ""
|
|
881
|
+
for pattern in patterns:
|
|
882
|
+
match = re.search(pattern, request, flags=re.IGNORECASE)
|
|
883
|
+
if not match:
|
|
884
|
+
continue
|
|
885
|
+
value = str(match.group(1)).strip(" ,,;;。\"'")
|
|
886
|
+
if value:
|
|
887
|
+
return value
|
|
888
|
+
return ""
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def infer_feature_goal(args: argparse.Namespace) -> str:
|
|
892
|
+
if args.goal.strip():
|
|
893
|
+
return args.goal
|
|
894
|
+
request = args.request.strip()
|
|
895
|
+
if not request:
|
|
896
|
+
return ""
|
|
897
|
+
trimmed = re.split(r"[,,。;;]", request, maxsplit=1)[0].strip()
|
|
898
|
+
return trimmed or request
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def infer_feature_user_story(args: argparse.Namespace) -> str:
|
|
902
|
+
if args.user_story.strip():
|
|
903
|
+
return args.user_story
|
|
904
|
+
return extract_clause(
|
|
905
|
+
args.request,
|
|
906
|
+
[
|
|
907
|
+
r"(作为[^,,。;;]*?[我你他她它]?(?:想|希望|需要)[^,,。;;]*)",
|
|
908
|
+
r"(As\s+an?[^,.;\n]*?(?:I want|I need)[^,.;\n]*)",
|
|
909
|
+
],
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def infer_in_scope(args: argparse.Namespace, flow: str, target_paths: list[str]) -> list[str]:
|
|
914
|
+
explicit = [item.strip() for item in args.in_scope if item.strip()]
|
|
915
|
+
if explicit:
|
|
916
|
+
return explicit
|
|
917
|
+
inferred = extract_clause(
|
|
918
|
+
args.request,
|
|
919
|
+
[
|
|
920
|
+
r"范围(?:只)?包括(.+?)(?:[,,;;。]|$)",
|
|
921
|
+
r"只包括(.+?)(?:[,,;;。]|$)",
|
|
922
|
+
r"in scope(?: is|:)?\s*(.+?)(?:[,.;\n]|$)",
|
|
923
|
+
],
|
|
924
|
+
)
|
|
925
|
+
if inferred:
|
|
926
|
+
return [inferred]
|
|
927
|
+
|
|
928
|
+
if target_paths:
|
|
929
|
+
if flow == "feature":
|
|
930
|
+
return [f"Implement the requested feature only in {', '.join(target_paths)}."]
|
|
931
|
+
if flow == "issue":
|
|
932
|
+
return [f"Fix the reported issue only in {', '.join(target_paths)}."]
|
|
933
|
+
return []
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def infer_acceptance(args: argparse.Namespace, flow: str) -> list[str]:
|
|
937
|
+
explicit = [item.strip() for item in args.acceptance if item.strip()]
|
|
938
|
+
if explicit:
|
|
939
|
+
return explicit
|
|
940
|
+
|
|
941
|
+
inferred = extract_clause(
|
|
942
|
+
args.request,
|
|
943
|
+
[
|
|
944
|
+
r"验收(?:标准)?(?:是|为|包括)?(.+?)(?:[,,;;。]|$)",
|
|
945
|
+
r"acceptance(?: criteria)?(?: is| are|:)?\s*(.+?)(?:[,.;\n]|$)",
|
|
946
|
+
],
|
|
947
|
+
)
|
|
948
|
+
if inferred:
|
|
949
|
+
return [inferred]
|
|
950
|
+
|
|
951
|
+
if flow == "issue":
|
|
952
|
+
expected = infer_issue_expected(args)
|
|
953
|
+
if expected:
|
|
954
|
+
return [expected]
|
|
955
|
+
return []
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
def infer_issue_symptom(args: argparse.Namespace) -> str:
|
|
959
|
+
if args.symptom.strip():
|
|
960
|
+
return args.symptom
|
|
961
|
+
request = args.request.strip()
|
|
962
|
+
if not request:
|
|
963
|
+
return ""
|
|
964
|
+
before_expected = re.split(r"预期|expected", request, maxsplit=1, flags=re.IGNORECASE)[0].strip(" ,,;;。")
|
|
965
|
+
return before_expected
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
def infer_issue_expected(args: argparse.Namespace) -> str:
|
|
969
|
+
if args.expected.strip():
|
|
970
|
+
return args.expected
|
|
971
|
+
return extract_clause(
|
|
972
|
+
args.request,
|
|
973
|
+
[
|
|
974
|
+
r"预期(?:是|为)?(.+?)(?:[,,;;。]|$)",
|
|
975
|
+
r"expected(?: is|:)?\s*(.+?)(?:[,.;\n]|$)",
|
|
976
|
+
],
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def infer_issue_actual(args: argparse.Namespace) -> str:
|
|
981
|
+
if args.actual.strip():
|
|
982
|
+
return args.actual
|
|
983
|
+
return extract_clause(
|
|
984
|
+
args.request,
|
|
985
|
+
[
|
|
986
|
+
r"实际(?:是|为)?(.+?)(?:[,,;;。]|$)",
|
|
987
|
+
r"actual(?: is|:)?\s*(.+?)(?:[,.;\n]|$)",
|
|
988
|
+
],
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def clean_placeholder_text(value: str) -> str:
|
|
993
|
+
cleaned = value.strip()
|
|
994
|
+
placeholders = {
|
|
995
|
+
"",
|
|
996
|
+
"TODO",
|
|
997
|
+
"todo",
|
|
998
|
+
"待补充",
|
|
999
|
+
"当前无。",
|
|
1000
|
+
"当前无",
|
|
1001
|
+
"无",
|
|
1002
|
+
}
|
|
1003
|
+
return "" if cleaned in placeholders else cleaned
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def read_text_if_exists(path: Path | None) -> str:
|
|
1007
|
+
if not path or not path.exists():
|
|
1008
|
+
return ""
|
|
1009
|
+
return path.read_text(encoding="utf-8-sig")
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
def find_first_artifact_file(directory: Path, pattern: str) -> Path | None:
|
|
1013
|
+
matches = sorted(directory.glob(pattern))
|
|
1014
|
+
return matches[0] if matches else None
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def extract_frontmatter_value(text: str, key: str) -> str:
|
|
1018
|
+
if not text.strip():
|
|
1019
|
+
return ""
|
|
1020
|
+
match = re.search(rf"(?m)^{re.escape(key)}:\s*(.+?)\s*$", text)
|
|
1021
|
+
if not match:
|
|
1022
|
+
return ""
|
|
1023
|
+
return clean_placeholder_text(str(match.group(1)).strip().strip("\"'"))
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def extract_markdown_bullet_value(text: str, label: str) -> str:
|
|
1027
|
+
if not text.strip():
|
|
1028
|
+
return ""
|
|
1029
|
+
match = re.search(rf"(?m)^\s*-\s*{re.escape(label)}:\s*(.+?)\s*$", text)
|
|
1030
|
+
if not match:
|
|
1031
|
+
return ""
|
|
1032
|
+
return clean_placeholder_text(str(match.group(1)).strip())
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def extract_markdown_section_bullets(text: str, heading: str) -> list[str]:
|
|
1036
|
+
if not text.strip():
|
|
1037
|
+
return []
|
|
1038
|
+
pattern = rf"{re.escape(heading)}\s*\n(?P<body>.*?)(?=\n##\s|\Z)"
|
|
1039
|
+
match = re.search(pattern, text, flags=re.DOTALL)
|
|
1040
|
+
if not match:
|
|
1041
|
+
return []
|
|
1042
|
+
|
|
1043
|
+
bullets: list[str] = []
|
|
1044
|
+
for raw_line in str(match.group("body")).splitlines():
|
|
1045
|
+
stripped = raw_line.strip()
|
|
1046
|
+
if not stripped.startswith("- "):
|
|
1047
|
+
continue
|
|
1048
|
+
value = clean_placeholder_text(stripped[2:].strip())
|
|
1049
|
+
if value:
|
|
1050
|
+
bullets.append(value)
|
|
1051
|
+
return bullets
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
def unique_nonempty(items: list[str]) -> list[str]:
|
|
1055
|
+
result: list[str] = []
|
|
1056
|
+
seen: set[str] = set()
|
|
1057
|
+
for item in items:
|
|
1058
|
+
cleaned = clean_placeholder_text(str(item))
|
|
1059
|
+
if not cleaned:
|
|
1060
|
+
continue
|
|
1061
|
+
key = cleaned.lower()
|
|
1062
|
+
if key in seen:
|
|
1063
|
+
continue
|
|
1064
|
+
seen.add(key)
|
|
1065
|
+
result.append(cleaned)
|
|
1066
|
+
return result
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
def load_step_contract_context(checklist_path: Path | None) -> dict[str, Any]:
|
|
1070
|
+
if not checklist_path or not checklist_path.exists():
|
|
1071
|
+
return {}
|
|
1072
|
+
|
|
1073
|
+
data = load_checklist_yaml(checklist_path)
|
|
1074
|
+
steps = data.get("steps", [])
|
|
1075
|
+
if not isinstance(steps, list):
|
|
1076
|
+
return {}
|
|
1077
|
+
|
|
1078
|
+
target_paths: list[str] = []
|
|
1079
|
+
acceptance: list[str] = []
|
|
1080
|
+
kinds: list[str] = []
|
|
1081
|
+
|
|
1082
|
+
for step in steps:
|
|
1083
|
+
if not isinstance(step, dict):
|
|
1084
|
+
continue
|
|
1085
|
+
codecgc = step.get("codecgc")
|
|
1086
|
+
if not isinstance(codecgc, dict):
|
|
1087
|
+
continue
|
|
1088
|
+
kinds.append(str(codecgc.get("kind", "")).strip())
|
|
1089
|
+
target_paths.extend(str(item).strip() for item in codecgc.get("target_paths", []) if str(item).strip())
|
|
1090
|
+
acceptance.extend(str(item).strip() for item in codecgc.get("acceptance", []) if str(item).strip())
|
|
1091
|
+
|
|
1092
|
+
distinct_kinds = {kind for kind in kinds if kind}
|
|
1093
|
+
resolved_kind = ""
|
|
1094
|
+
if len(distinct_kinds) == 1:
|
|
1095
|
+
resolved_kind = next(iter(distinct_kinds))
|
|
1096
|
+
elif len(distinct_kinds) > 1:
|
|
1097
|
+
resolved_kind = "auto"
|
|
1098
|
+
|
|
1099
|
+
return {
|
|
1100
|
+
"kind": resolved_kind,
|
|
1101
|
+
"target_paths": unique_nonempty(target_paths),
|
|
1102
|
+
"acceptance": unique_nonempty(acceptance),
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
def build_existing_workflow_replan_payload(flow: str, slug: str) -> dict[str, Any]:
|
|
1107
|
+
discovered = discover_flow_directory(flow, slug, "auto")
|
|
1108
|
+
if not discovered:
|
|
1109
|
+
return {}
|
|
1110
|
+
|
|
1111
|
+
_, directory = discovered
|
|
1112
|
+
payload: dict[str, Any] = {
|
|
1113
|
+
"flow": flow,
|
|
1114
|
+
"slug": slug,
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if flow == "issue":
|
|
1118
|
+
report_text = read_text_if_exists(find_first_artifact_file(directory, "*-report.md"))
|
|
1119
|
+
analysis_text = read_text_if_exists(find_first_artifact_file(directory, "*-analysis.md"))
|
|
1120
|
+
step_context = load_step_contract_context(find_first_artifact_file(directory, "*-fix.yaml"))
|
|
1121
|
+
payload["summary"] = extract_frontmatter_value(report_text, "summary")
|
|
1122
|
+
payload["user_story"] = extract_markdown_bullet_value(report_text, "用户影响")
|
|
1123
|
+
payload["symptom"] = extract_markdown_bullet_value(report_text, "现象")
|
|
1124
|
+
payload["expected"] = extract_markdown_bullet_value(report_text, "预期")
|
|
1125
|
+
payload["actual"] = extract_markdown_bullet_value(report_text, "实际")
|
|
1126
|
+
payload["in_scope"] = extract_markdown_section_bullets(analysis_text, "## 2. 范围")
|
|
1127
|
+
else:
|
|
1128
|
+
design_text = read_text_if_exists(find_first_artifact_file(directory, "*-design.md"))
|
|
1129
|
+
step_context = load_step_contract_context(find_first_artifact_file(directory, "*-checklist.yaml"))
|
|
1130
|
+
payload["summary"] = extract_frontmatter_value(design_text, "summary")
|
|
1131
|
+
payload["goal"] = extract_markdown_bullet_value(design_text, "用户目标")
|
|
1132
|
+
payload["user_story"] = extract_markdown_bullet_value(design_text, "用户故事")
|
|
1133
|
+
payload["in_scope"] = extract_markdown_section_bullets(design_text, "## 3. 范围内")
|
|
1134
|
+
|
|
1135
|
+
payload.update(step_context)
|
|
1136
|
+
task_id = str(step_context.get("task_id", "")).strip()
|
|
1137
|
+
reusable_session_id = resolve_session_id_from_task(task_id, discovered[0]) if task_id else ""
|
|
1138
|
+
if reusable_session_id:
|
|
1139
|
+
payload["session_id"] = reusable_session_id
|
|
1140
|
+
|
|
1141
|
+
if not payload.get("summary"):
|
|
1142
|
+
payload["summary"] = slug
|
|
1143
|
+
|
|
1144
|
+
return {
|
|
1145
|
+
key: value
|
|
1146
|
+
for key, value in payload.items()
|
|
1147
|
+
if has_planning_value(value) and not (key == "kind" and value == "auto")
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
def has_planning_value(value: Any) -> bool:
|
|
1152
|
+
if isinstance(value, list):
|
|
1153
|
+
return any(str(item).strip() for item in value)
|
|
1154
|
+
return bool(str(value).strip()) if isinstance(value, str) else bool(value)
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def build_planning_snapshot(flow: str, args: argparse.Namespace, target_paths: list[str], resolved_kind: str) -> dict[str, Any]:
|
|
1158
|
+
snapshot: dict[str, Any] = {
|
|
1159
|
+
"kind": resolved_kind,
|
|
1160
|
+
"target_paths": target_paths,
|
|
1161
|
+
"context": cleaned_items(args.context),
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
if flow == "feature":
|
|
1165
|
+
snapshot.update(
|
|
1166
|
+
{
|
|
1167
|
+
"goal": infer_feature_goal(args),
|
|
1168
|
+
"user_story": infer_feature_user_story(args),
|
|
1169
|
+
"in_scope": infer_in_scope(args, flow, target_paths),
|
|
1170
|
+
"acceptance": infer_acceptance(args, flow),
|
|
1171
|
+
}
|
|
1172
|
+
)
|
|
1173
|
+
else:
|
|
1174
|
+
snapshot.update(
|
|
1175
|
+
{
|
|
1176
|
+
"symptom": infer_issue_symptom(args),
|
|
1177
|
+
"expected": infer_issue_expected(args),
|
|
1178
|
+
"actual": infer_issue_actual(args),
|
|
1179
|
+
"in_scope": infer_in_scope(args, flow, target_paths),
|
|
1180
|
+
"acceptance": infer_acceptance(args, flow),
|
|
1181
|
+
}
|
|
1182
|
+
)
|
|
1183
|
+
return snapshot
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def build_captured_fields(snapshot: dict[str, Any]) -> dict[str, Any]:
|
|
1187
|
+
return {
|
|
1188
|
+
key: value
|
|
1189
|
+
for key, value in snapshot.items()
|
|
1190
|
+
if has_planning_value(value) and not (key == "kind" and value == "auto")
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def build_clarification_mode(flow: str, missing_fields: list[str], resolved_kind: str) -> str:
|
|
1195
|
+
missing = set(missing_fields)
|
|
1196
|
+
if not missing:
|
|
1197
|
+
return ""
|
|
1198
|
+
if missing == {"target_paths"}:
|
|
1199
|
+
return "path-discovery"
|
|
1200
|
+
if "routing_coverage" in missing or resolved_kind == "auto":
|
|
1201
|
+
return f"{flow}-routing-clarification"
|
|
1202
|
+
return f"{flow}-beginner"
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
def build_clarification_prompts(flow: str, missing_fields: list[str]) -> list[dict[str, str]]:
|
|
1206
|
+
prompts: list[dict[str, str]] = []
|
|
1207
|
+
mapping = {
|
|
1208
|
+
"target_paths": {
|
|
1209
|
+
"field": "target_paths",
|
|
1210
|
+
"question": "这次改动具体落在哪些文件或目录?",
|
|
1211
|
+
"example": "例如:src/components/LoginForm.tsx 或 backend/src/sync.py",
|
|
1212
|
+
},
|
|
1213
|
+
"user_story": {
|
|
1214
|
+
"field": "user_story",
|
|
1215
|
+
"question": "这个需求从用户视角想解决什么问题?",
|
|
1216
|
+
"example": "例如:作为用户,我想快速登录,以便进入系统。",
|
|
1217
|
+
},
|
|
1218
|
+
"goal": {
|
|
1219
|
+
"field": "goal",
|
|
1220
|
+
"question": "这次需求的目标是什么?",
|
|
1221
|
+
"example": "例如:提供一个独立登录入口页面。",
|
|
1222
|
+
},
|
|
1223
|
+
"in_scope": {
|
|
1224
|
+
"field": "in_scope",
|
|
1225
|
+
"question": "这次明确要做的范围是什么?",
|
|
1226
|
+
"example": "例如:只新增登录表单,不改后端接口。",
|
|
1227
|
+
},
|
|
1228
|
+
"acceptance": {
|
|
1229
|
+
"field": "acceptance",
|
|
1230
|
+
"question": "怎么才算完成?",
|
|
1231
|
+
"example": "例如:页面展示邮箱和密码输入框,并可提交。",
|
|
1232
|
+
},
|
|
1233
|
+
"symptom": {
|
|
1234
|
+
"field": "symptom",
|
|
1235
|
+
"question": "这个问题的现象是什么?",
|
|
1236
|
+
"example": "例如:批量同步时报错,中途停止。",
|
|
1237
|
+
},
|
|
1238
|
+
"expected": {
|
|
1239
|
+
"field": "expected",
|
|
1240
|
+
"question": "你预期正确行为应该是什么?",
|
|
1241
|
+
"example": "例如:坏记录被跳过,其余记录继续处理。",
|
|
1242
|
+
},
|
|
1243
|
+
"actual": {
|
|
1244
|
+
"field": "actual",
|
|
1245
|
+
"question": "现在实际发生了什么?",
|
|
1246
|
+
"example": "例如:遇到一条坏记录后整批停止。",
|
|
1247
|
+
},
|
|
1248
|
+
"routing_coverage": {
|
|
1249
|
+
"field": "routing_coverage",
|
|
1250
|
+
"question": "这些路径应该归前端、后端,还是需要先拆分?",
|
|
1251
|
+
"example": "例如:src/components/** 归前端,backend/** 归后端。",
|
|
1252
|
+
},
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
preferred_order = (
|
|
1256
|
+
["target_paths", "goal", "user_story", "in_scope", "acceptance"]
|
|
1257
|
+
if flow == "feature"
|
|
1258
|
+
else ["target_paths", "symptom", "expected", "actual", "in_scope", "acceptance"]
|
|
1259
|
+
)
|
|
1260
|
+
ordered_fields = preferred_order + [field for field in missing_fields if field not in preferred_order]
|
|
1261
|
+
|
|
1262
|
+
seen: set[str] = set()
|
|
1263
|
+
for field in ordered_fields:
|
|
1264
|
+
if field not in missing_fields or field in seen:
|
|
1265
|
+
continue
|
|
1266
|
+
seen.add(field)
|
|
1267
|
+
prompt = mapping.get(field)
|
|
1268
|
+
if prompt:
|
|
1269
|
+
prompts.append(prompt)
|
|
1270
|
+
return prompts
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
def build_suggested_reply_payload(flow: str, snapshot: dict[str, Any], missing_fields: list[str]) -> dict[str, Any]:
|
|
1274
|
+
field_order = (
|
|
1275
|
+
["target_paths", "goal", "user_story", "in_scope", "acceptance"]
|
|
1276
|
+
if flow == "feature"
|
|
1277
|
+
else ["target_paths", "symptom", "expected", "actual", "in_scope", "acceptance"]
|
|
1278
|
+
)
|
|
1279
|
+
list_fields = {"target_paths", "in_scope", "acceptance"}
|
|
1280
|
+
payload: dict[str, Any] = {"flow": flow}
|
|
1281
|
+
|
|
1282
|
+
kind = str(snapshot.get("kind", "")).strip()
|
|
1283
|
+
if kind and kind != "auto":
|
|
1284
|
+
payload["kind"] = kind
|
|
1285
|
+
|
|
1286
|
+
missing = set(missing_fields)
|
|
1287
|
+
for field in field_order:
|
|
1288
|
+
value = snapshot.get(field)
|
|
1289
|
+
if field in missing:
|
|
1290
|
+
payload[field] = [] if field in list_fields else ""
|
|
1291
|
+
elif has_planning_value(value):
|
|
1292
|
+
payload[field] = value
|
|
1293
|
+
return payload
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
def build_suggested_reply_template(flow: str, prompts: list[dict[str, str]]) -> str:
|
|
1297
|
+
if not prompts:
|
|
1298
|
+
return ""
|
|
1299
|
+
|
|
1300
|
+
lines: list[str] = []
|
|
1301
|
+
if flow == "feature":
|
|
1302
|
+
lines.append("请补充下面这些信息后我再继续推进:")
|
|
1303
|
+
else:
|
|
1304
|
+
lines.append("请补充下面这些问题信息后我再继续修复:")
|
|
1305
|
+
|
|
1306
|
+
for prompt in prompts:
|
|
1307
|
+
question = str(prompt.get("question", "")).strip()
|
|
1308
|
+
example = str(prompt.get("example", "")).strip()
|
|
1309
|
+
if not question:
|
|
1310
|
+
continue
|
|
1311
|
+
lines.append(f"- {question}")
|
|
1312
|
+
if example:
|
|
1313
|
+
lines.append(f" 参考:{example}")
|
|
1314
|
+
return "\n".join(lines)
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
def resolve_effective_auto_dispatch(
|
|
1318
|
+
args: argparse.Namespace,
|
|
1319
|
+
recommended_command: str,
|
|
1320
|
+
explain_only: bool = False,
|
|
1321
|
+
) -> tuple[bool, str]:
|
|
1322
|
+
if explain_only:
|
|
1323
|
+
return False, "explain-mode"
|
|
1324
|
+
if args.auto_dispatch:
|
|
1325
|
+
return True, "explicit-flag"
|
|
1326
|
+
|
|
1327
|
+
request = args.request.strip()
|
|
1328
|
+
if not request:
|
|
1329
|
+
return False, ""
|
|
1330
|
+
|
|
1331
|
+
if matches_command(recommended_command, "cgc-build", "cgc-fix") and (
|
|
1332
|
+
request_implies_execute(request) or request_implies_continue(request)
|
|
1333
|
+
):
|
|
1334
|
+
return True, "request-implies-execution"
|
|
1335
|
+
|
|
1336
|
+
if matches_command(recommended_command, "cgc-review") and request_implies_review(request):
|
|
1337
|
+
inferred_decision = infer_review_decision_from_request(request)
|
|
1338
|
+
if inferred_decision and not args.decision:
|
|
1339
|
+
args.decision = inferred_decision
|
|
1340
|
+
if args.decision:
|
|
1341
|
+
return True, "request-implies-review"
|
|
1342
|
+
return False, "review-decision-missing"
|
|
1343
|
+
|
|
1344
|
+
return False, ""
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
def build_route_status_summary(route: dict[str, Any]) -> dict[str, Any]:
|
|
1348
|
+
review = route.get("review", {}) if isinstance(route.get("review"), dict) else {}
|
|
1349
|
+
current_step = route.get("current_step", {}) if isinstance(route.get("current_step"), dict) else {}
|
|
1350
|
+
recommended_command = str(route.get("recommended_command", "")).strip()
|
|
1351
|
+
audit_path = str(route.get("audit_path", "")).strip()
|
|
1352
|
+
task_id = str(current_step.get("task_id", "")).strip()
|
|
1353
|
+
artifact_class = str(route.get("artifact_class", "")).strip() or "product"
|
|
1354
|
+
reusable_session_id = resolve_session_id_from_task(task_id, artifact_class) if task_id else ""
|
|
1355
|
+
|
|
1356
|
+
workflow_state = "closed"
|
|
1357
|
+
if matches_command(recommended_command, "cgc-plan"):
|
|
1358
|
+
workflow_state = "needs-planning"
|
|
1359
|
+
elif matches_command(recommended_command, "cgc-build"):
|
|
1360
|
+
workflow_state = "awaiting-build"
|
|
1361
|
+
elif matches_command(recommended_command, "cgc-fix"):
|
|
1362
|
+
workflow_state = "awaiting-fix"
|
|
1363
|
+
elif matches_command(recommended_command, "cgc-review"):
|
|
1364
|
+
workflow_state = "awaiting-review"
|
|
1365
|
+
elif current_step:
|
|
1366
|
+
workflow_state = "step-selected"
|
|
1367
|
+
|
|
1368
|
+
flow_step_labels = {
|
|
1369
|
+
"cgc-build": "功能开发步骤",
|
|
1370
|
+
"cgc-fix": "问题修复步骤",
|
|
1371
|
+
"cgc-test": "测试步骤",
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
human_status_summary = "当前工作流已关闭。"
|
|
1375
|
+
operator_action_summary = "如需继续,先新增后续步骤,或回到规划阶段。"
|
|
1376
|
+
if matches_command(recommended_command, "cgc-plan"):
|
|
1377
|
+
human_status_summary = "当前工作流还需要回到规划阶段补齐可执行信息。"
|
|
1378
|
+
operator_action_summary = "进入规划阶段,补齐范围、归属或可执行步骤。"
|
|
1379
|
+
elif matches_command(recommended_command, "cgc-build"):
|
|
1380
|
+
human_status_summary = "当前工作流正在等待前端或功能实现执行。"
|
|
1381
|
+
operator_action_summary = f"执行当前{flow_step_labels['cgc-build']}。"
|
|
1382
|
+
elif matches_command(recommended_command, "cgc-fix"):
|
|
1383
|
+
human_status_summary = "当前工作流正在等待后端或问题修复执行。"
|
|
1384
|
+
operator_action_summary = f"执行当前{flow_step_labels['cgc-fix']}。"
|
|
1385
|
+
elif matches_command(recommended_command, "cgc-test"):
|
|
1386
|
+
human_status_summary = "当前工作流正在等待测试步骤执行。"
|
|
1387
|
+
operator_action_summary = f"执行当前{flow_step_labels['cgc-test']}。"
|
|
1388
|
+
elif matches_command(recommended_command, "cgc-review"):
|
|
1389
|
+
human_status_summary = "当前工作流已有执行证据,正在等待审核决策。"
|
|
1390
|
+
operator_action_summary = "基于最新审计产物执行审核,并写回“通过”或“需修改”。"
|
|
1391
|
+
elif current_step:
|
|
1392
|
+
human_status_summary = "当前工作流已选中步骤,但还没有明确的后续命令。"
|
|
1393
|
+
operator_action_summary = "检查 route 原因,并确认是否需要回到规划阶段。"
|
|
1394
|
+
|
|
1395
|
+
review_fallback_stage = str(review.get("fallback_stage", "")).strip()
|
|
1396
|
+
review_action_kind = str(review.get("action_kind", "")).strip()
|
|
1397
|
+
review_policy_reason = str(review.get("policy_reason", "")).strip()
|
|
1398
|
+
if recommended_command == "" and review_fallback_stage == "closed":
|
|
1399
|
+
human_status_summary = "当前工作流已关闭,最近一次审核已确认该步骤可以结束。"
|
|
1400
|
+
operator_action_summary = "如需继续,新增后续步骤;否则当前结果已可视为通过。"
|
|
1401
|
+
|
|
1402
|
+
return {
|
|
1403
|
+
"workflow_state": workflow_state,
|
|
1404
|
+
"recommended_command": recommended_command,
|
|
1405
|
+
"current_step_number": int(current_step.get("step_number", 0) or 0),
|
|
1406
|
+
"current_task_id": str(current_step.get("task_id", "")),
|
|
1407
|
+
"current_kind": str(current_step.get("kind", "")),
|
|
1408
|
+
"current_target_paths": current_step.get("target_paths", []),
|
|
1409
|
+
"review_decision": str(review.get("decision", "")),
|
|
1410
|
+
"review_step_number": int(review.get("step_number", 0) or 0),
|
|
1411
|
+
"review_fallback_stage": review_fallback_stage,
|
|
1412
|
+
"review_policy_reason": review_policy_reason,
|
|
1413
|
+
"review_action_kind": review_action_kind,
|
|
1414
|
+
"audit_path": audit_path,
|
|
1415
|
+
"reusable_session_id": reusable_session_id,
|
|
1416
|
+
"human_status_summary": human_status_summary,
|
|
1417
|
+
"operator_action_summary": operator_action_summary,
|
|
1418
|
+
"is_closed": recommended_command == "",
|
|
1419
|
+
"needs_review_decision": matches_command(recommended_command, "cgc-review"),
|
|
1420
|
+
"needs_execution": matches_command(recommended_command, "cgc-build", "cgc-fix", "cgc-test"),
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
def build_review_policy_status(dispatch_result: dict[str, Any]) -> dict[str, str]:
|
|
1425
|
+
fallback_stage = str(dispatch_result.get("fallback_stage", "")).strip()
|
|
1426
|
+
action_kind = str(dispatch_result.get("recommended_action_kind", "")).strip()
|
|
1427
|
+
policy_reason = str(dispatch_result.get("policy_reason", "")).strip()
|
|
1428
|
+
action_kind_labels = {
|
|
1429
|
+
"execute-for-real": "执行一次真实运行",
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
human_status_summary = ""
|
|
1433
|
+
operator_action_summary = ""
|
|
1434
|
+
|
|
1435
|
+
if fallback_stage == "closed":
|
|
1436
|
+
human_status_summary = "当前审核已完成,并确认该步骤可以关闭。"
|
|
1437
|
+
operator_action_summary = "结束当前步骤,或继续处理新的后续工作。"
|
|
1438
|
+
elif fallback_stage == "planning":
|
|
1439
|
+
human_status_summary = "当前审核已把工作退回规划阶段。"
|
|
1440
|
+
operator_action_summary = "回到 cgc-plan,修正范围、归属或拆分策略。"
|
|
1441
|
+
elif fallback_stage == "execution":
|
|
1442
|
+
human_status_summary = "当前审核认为还需要重新执行或补做实现。"
|
|
1443
|
+
if action_kind == "execute-for-real":
|
|
1444
|
+
operator_action_summary = "执行一次非 dry-run 的真实执行。"
|
|
1445
|
+
else:
|
|
1446
|
+
operator_action_summary = "修正实现后重新执行当前步骤。"
|
|
1447
|
+
elif fallback_stage == "review":
|
|
1448
|
+
human_status_summary = "当前审核结果仍需进一步确认。"
|
|
1449
|
+
operator_action_summary = "重新检查审核输入与决策依据。"
|
|
1450
|
+
|
|
1451
|
+
return {
|
|
1452
|
+
"fallback_stage": fallback_stage,
|
|
1453
|
+
"recommended_action_kind": action_kind,
|
|
1454
|
+
"policy_reason": policy_reason,
|
|
1455
|
+
"human_status_summary": human_status_summary,
|
|
1456
|
+
"operator_action_summary": operator_action_summary,
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
|
|
1460
|
+
def infer_dispatch_recovery(command: str, failure_type: str, state: str, next_step: str) -> dict[str, str]:
|
|
1461
|
+
normalized_failure = failure_type.strip()
|
|
1462
|
+
normalized_state = state.strip()
|
|
1463
|
+
normalized_next = next_step.strip()
|
|
1464
|
+
public_command = to_public_command(command)
|
|
1465
|
+
|
|
1466
|
+
if normalized_failure == "workflow-state":
|
|
1467
|
+
recovery_command = public_command or to_public_command("cgc-build")
|
|
1468
|
+
retry_kind = "rerun-without-dry-run" if normalized_state == "executed-dry-run" else "workflow-repair"
|
|
1469
|
+
retry_hint = normalized_next or "先修复当前工作流就绪状态,再重新执行同一个 step。"
|
|
1470
|
+
return {
|
|
1471
|
+
"recovery_command": recovery_command,
|
|
1472
|
+
"recovery_kind": retry_kind,
|
|
1473
|
+
"retry_hint": retry_hint,
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if normalized_failure in {"scope-error", "design-gap"}:
|
|
1477
|
+
retry_hint = normalized_next or "先回到规划阶段,收窄范围,并重新生成可执行 step。"
|
|
1478
|
+
return {
|
|
1479
|
+
"recovery_command": to_public_command("cgc-plan"),
|
|
1480
|
+
"recovery_kind": "returned-to-planning",
|
|
1481
|
+
"retry_hint": retry_hint,
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if normalized_failure == "environment-or-tooling":
|
|
1485
|
+
retry_hint = normalized_next or "先修复本地环境、缺失文件或超时条件,再重试当前 step。"
|
|
1486
|
+
return {
|
|
1487
|
+
"recovery_command": "",
|
|
1488
|
+
"recovery_kind": "repair-environment",
|
|
1489
|
+
"retry_hint": retry_hint,
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if normalized_failure == "executor-failure":
|
|
1493
|
+
retry_hint = normalized_next or "先检查执行器输出和审计产物,修复执行器侧失败后再重试。"
|
|
1494
|
+
return {
|
|
1495
|
+
"recovery_command": public_command,
|
|
1496
|
+
"recovery_kind": "inspect-executor",
|
|
1497
|
+
"retry_hint": retry_hint,
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return {
|
|
1501
|
+
"recovery_command": public_command,
|
|
1502
|
+
"recovery_kind": "",
|
|
1503
|
+
"retry_hint": normalized_next,
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
def build_dispatch_failure_user_summary(dispatch_result: dict[str, Any], command: str) -> tuple[str, str]:
|
|
1508
|
+
failure_type = str(dispatch_result.get("failure_type", "")).strip()
|
|
1509
|
+
dispatch_state = str(dispatch_result.get("state", "")).strip()
|
|
1510
|
+
recovery = infer_dispatch_recovery(command, failure_type, dispatch_state, str(dispatch_result.get("next", "")).strip())
|
|
1511
|
+
recovery_command = str(recovery.get("recovery_command", "")).strip()
|
|
1512
|
+
split_suggestion = dispatch_result.get("split_suggestion", {}) if isinstance(dispatch_result.get("split_suggestion"), dict) else {}
|
|
1513
|
+
|
|
1514
|
+
if failure_type == "workflow-state":
|
|
1515
|
+
if dispatch_state == "executed-dry-run":
|
|
1516
|
+
return "本次只完成了预演执行,尚未真正完成代码改动。", "去掉 dry-run 后重新执行当前步骤。"
|
|
1517
|
+
return "当前步骤还不满足执行条件,执行尚未完成。", f"先修复工作流状态,再通过 {recovery_command or '当前执行命令'} 重试。"
|
|
1518
|
+
|
|
1519
|
+
if failure_type == "environment-or-tooling":
|
|
1520
|
+
return "当前卡在本地环境或工具前置条件,执行尚未完成。", "先修复缺失目录、依赖、权限或超时条件,再重试当前步骤。"
|
|
1521
|
+
|
|
1522
|
+
if failure_type == "executor-failure":
|
|
1523
|
+
return "当前卡在执行器返回异常,需要检查执行日志与审计产物。", f"先检查执行器输出,修复后再通过 {recovery_command or '当前执行命令'} 重试。"
|
|
1524
|
+
|
|
1525
|
+
if failure_type in {"scope-error", "design-gap"}:
|
|
1526
|
+
if failure_type == "scope-error" and split_suggestion:
|
|
1527
|
+
suggested_steps = split_suggestion.get("suggested_split_steps", [])
|
|
1528
|
+
if isinstance(suggested_steps, list):
|
|
1529
|
+
parts: list[str] = []
|
|
1530
|
+
for item in suggested_steps:
|
|
1531
|
+
if not isinstance(item, dict):
|
|
1532
|
+
continue
|
|
1533
|
+
kind = str(item.get("kind", "")).strip()
|
|
1534
|
+
executor = str(item.get("executor", "")).strip()
|
|
1535
|
+
paths = [str(path).strip() for path in item.get("target_paths", []) if str(path).strip()]
|
|
1536
|
+
if not paths:
|
|
1537
|
+
continue
|
|
1538
|
+
parts.append(f"{kind}->{executor}: {', '.join(paths)}")
|
|
1539
|
+
if parts:
|
|
1540
|
+
return (
|
|
1541
|
+
"当前步骤混合了前后端或 shared 范围,执行已返回规划阶段。",
|
|
1542
|
+
"建议按以下方式拆分后回到 cgc-plan:" + ";".join(parts),
|
|
1543
|
+
)
|
|
1544
|
+
return "当前步骤的规划、范围或目标路径还不够清晰,执行已返回规划阶段。", "先回到 cgc-plan 补齐规划、修正目标路径或重新拆分步骤,再继续执行。"
|
|
1545
|
+
|
|
1546
|
+
fallback_summary = str(dispatch_result.get("summary", "")).strip()
|
|
1547
|
+
if fallback_summary:
|
|
1548
|
+
return "自动调度未完成,需要先处理当前阻塞。", fallback_summary
|
|
1549
|
+
return "自动调度未完成,需要先处理当前阻塞。", "先检查当前步骤状态和执行日志,再决定是否重试。"
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
def build_dispatch_recovery_user_hint(dispatch_result: dict[str, Any], command: str) -> str:
|
|
1553
|
+
_, next_step = build_dispatch_failure_user_summary(dispatch_result, command)
|
|
1554
|
+
return next_step
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
def build_command_user_next(command: str) -> str:
|
|
1558
|
+
if matches_command(command, "cgc-build"):
|
|
1559
|
+
return "执行当前功能开发步骤。"
|
|
1560
|
+
if matches_command(command, "cgc-fix"):
|
|
1561
|
+
return "执行当前问题修复步骤。"
|
|
1562
|
+
if matches_command(command, "cgc-plan"):
|
|
1563
|
+
return "回到规划阶段,补齐可执行信息。"
|
|
1564
|
+
if matches_command(command, "cgc-review"):
|
|
1565
|
+
return "基于最新审计产物完成审核,并写回“通过”或“需修改”。"
|
|
1566
|
+
return ""
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
def compose_user_reply(summary: str, next_text: str, prefix: str = "下一步:") -> str:
|
|
1570
|
+
normalized_summary = summary.strip()
|
|
1571
|
+
normalized_next = next_text.strip()
|
|
1572
|
+
if normalized_summary and normalized_next:
|
|
1573
|
+
if normalized_next in normalized_summary:
|
|
1574
|
+
return normalized_summary
|
|
1575
|
+
return f"{normalized_summary}\n\n{prefix}{normalized_next}"
|
|
1576
|
+
return normalized_summary or normalized_next
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
def apply_dispatch_failure_context(result: dict[str, Any], fallback_command: str) -> None:
|
|
1580
|
+
dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
1581
|
+
if not dispatch_result or dispatch_result.get("success"):
|
|
1582
|
+
return
|
|
1583
|
+
|
|
1584
|
+
failure_command = str(dispatch_result.get("recommended_command", "")).strip() or fallback_command
|
|
1585
|
+
recovery = infer_dispatch_recovery(
|
|
1586
|
+
failure_command,
|
|
1587
|
+
str(dispatch_result.get("failure_type", "")).strip(),
|
|
1588
|
+
str(dispatch_result.get("state", "")).strip(),
|
|
1589
|
+
str(dispatch_result.get("next", "")).strip(),
|
|
1590
|
+
)
|
|
1591
|
+
retry_hint = str(recovery.get("retry_hint", "")).strip()
|
|
1592
|
+
user_summary, user_next = build_dispatch_failure_user_summary(dispatch_result, failure_command)
|
|
1593
|
+
|
|
1594
|
+
result["recommended_command"] = failure_command
|
|
1595
|
+
result["next"] = user_next or retry_hint or user_summary
|
|
1596
|
+
|
|
1597
|
+
|
|
1598
|
+
def describe_missing_fields(missing_fields: list[str]) -> str:
|
|
1599
|
+
labels = {
|
|
1600
|
+
"target_paths": "目标文件或目录",
|
|
1601
|
+
"goal": "需求目标",
|
|
1602
|
+
"user_story": "用户故事",
|
|
1603
|
+
"in_scope": "本次范围",
|
|
1604
|
+
"acceptance": "验收标准",
|
|
1605
|
+
"symptom": "问题现象",
|
|
1606
|
+
"expected": "预期行为",
|
|
1607
|
+
"actual": "实际行为",
|
|
1608
|
+
"routing_coverage": "前后端归属",
|
|
1609
|
+
"planning_blockers": "规划阻塞项",
|
|
1610
|
+
"executable_step": "可执行步骤定义",
|
|
1611
|
+
}
|
|
1612
|
+
described = [labels.get(str(item), str(item)) for item in missing_fields]
|
|
1613
|
+
return "、".join(item for item in described if item)
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
def build_user_facing_next(result: dict[str, Any]) -> str:
|
|
1617
|
+
if str(result.get("entry_mode", "")).strip() == "governance":
|
|
1618
|
+
return str(result.get("next", "")).strip()
|
|
1619
|
+
|
|
1620
|
+
entry_mode = str(result.get("entry_mode", "")).strip()
|
|
1621
|
+
if entry_mode == "new":
|
|
1622
|
+
planning_status = str(result.get("planning_status", "")).strip()
|
|
1623
|
+
recommended_command = str(result.get("recommended_command", "")).strip()
|
|
1624
|
+
if planning_status == "needs-clarification":
|
|
1625
|
+
return "请先补充缺失信息,我再继续完成规划和路由。"
|
|
1626
|
+
command_next = build_command_user_next(recommended_command)
|
|
1627
|
+
if command_next:
|
|
1628
|
+
return command_next
|
|
1629
|
+
return str(result.get("next", "")).strip()
|
|
1630
|
+
|
|
1631
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
1632
|
+
recommended_command = str(result.get("recommended_command", "")).strip()
|
|
1633
|
+
workflow_state = str(route_status.get("workflow_state", "")).strip()
|
|
1634
|
+
if workflow_state == "needs-new-workflow":
|
|
1635
|
+
return "先直接描述一个新需求,或显式提供已有工作流的 slug。"
|
|
1636
|
+
if workflow_state == "needs-review-target":
|
|
1637
|
+
return "先定位一个待审核工作流,或先完成执行步骤后再回来审核。"
|
|
1638
|
+
if bool(route_status.get("is_closed")):
|
|
1639
|
+
return str(route_status.get("operator_action_summary", "")).strip() or "如需继续,先新增后续步骤,或回到规划阶段。"
|
|
1640
|
+
if bool(route_status.get("needs_review_decision")):
|
|
1641
|
+
return "基于最新审计产物执行审核,并写回“通过”或“需修改”。"
|
|
1642
|
+
command_next = build_command_user_next(recommended_command)
|
|
1643
|
+
if command_next:
|
|
1644
|
+
return command_next
|
|
1645
|
+
return str(result.get("next", "")).strip()
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
def build_new_mode_human_summary(result: dict[str, Any]) -> str:
|
|
1649
|
+
flow = str(result.get("flow", "")).strip() or "workflow"
|
|
1650
|
+
status = str(result.get("planning_status", "")).strip()
|
|
1651
|
+
recommended_command = str(result.get("recommended_command", "")).strip()
|
|
1652
|
+
dispatched = bool(result.get("dispatched"))
|
|
1653
|
+
dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
1654
|
+
missing_fields = result.get("planning_missing_fields", [])
|
|
1655
|
+
resolved_kind = str(result.get("resolved_kind", "")).strip() or "auto"
|
|
1656
|
+
flow_labels = {
|
|
1657
|
+
"feature": "功能开发",
|
|
1658
|
+
"issue": "问题修复",
|
|
1659
|
+
"workflow": "工作流",
|
|
1660
|
+
}
|
|
1661
|
+
kind_labels = {
|
|
1662
|
+
"frontend": "前端",
|
|
1663
|
+
"backend": "后端",
|
|
1664
|
+
"shared": "共享范围(需先拆分)",
|
|
1665
|
+
"auto": "待路由",
|
|
1666
|
+
}
|
|
1667
|
+
flow_label = flow_labels.get(flow, flow)
|
|
1668
|
+
kind_label = kind_labels.get(resolved_kind, resolved_kind)
|
|
1669
|
+
|
|
1670
|
+
if status == "needs-clarification":
|
|
1671
|
+
if missing_fields:
|
|
1672
|
+
return f"已识别为{flow_label}请求,但信息还不完整,当前缺少:{describe_missing_fields(missing_fields)}。"
|
|
1673
|
+
return f"已识别为{flow_label}请求,但还需要进一步澄清。"
|
|
1674
|
+
if dispatched:
|
|
1675
|
+
if not dispatch_result.get("success"):
|
|
1676
|
+
failure_summary, next_step = build_dispatch_failure_user_summary(dispatch_result, recommended_command)
|
|
1677
|
+
if failure_summary and next_step:
|
|
1678
|
+
return f"已完成{flow_label}规划,并尝试自动调度,但当前未完成:{failure_summary} 下一步建议:{next_step}"
|
|
1679
|
+
if next_step:
|
|
1680
|
+
return f"已完成{flow_label}规划,并尝试自动调度,但当前未完成。下一步建议:{next_step}"
|
|
1681
|
+
return f"已完成{flow_label}规划,但自动调度尚未完成:{failure_summary}"
|
|
1682
|
+
command_next = build_command_user_next(recommended_command)
|
|
1683
|
+
if command_next and recommended_command:
|
|
1684
|
+
return f"已完成{flow_label}规划,并已自动调度到 {recommended_command},当前应继续{command_next}"
|
|
1685
|
+
return f"已完成{flow_label}规划,并已自动调度到 {recommended_command or '后续执行阶段'}。"
|
|
1686
|
+
if recommended_command:
|
|
1687
|
+
command_next = build_command_user_next(recommended_command)
|
|
1688
|
+
if command_next:
|
|
1689
|
+
return f"已完成{flow_label}规划,当前归属为{kind_label},下一步建议进入 {recommended_command},继续{command_next}"
|
|
1690
|
+
return f"已完成{flow_label}规划,当前归属为{kind_label},下一步建议进入 {recommended_command}。"
|
|
1691
|
+
return f"已完成{flow_label}规划,当前没有进一步命令需要执行。"
|
|
1692
|
+
|
|
1693
|
+
|
|
1694
|
+
def build_existing_mode_human_summary(result: dict[str, Any]) -> str:
|
|
1695
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
1696
|
+
summary = str(route_status.get("human_status_summary", "")).strip()
|
|
1697
|
+
action = str(route_status.get("operator_action_summary", "")).strip()
|
|
1698
|
+
recommended_command = str(result.get("recommended_command", "")).strip()
|
|
1699
|
+
dispatched = bool(result.get("dispatched"))
|
|
1700
|
+
dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
1701
|
+
decision_labels = {
|
|
1702
|
+
"accepted": "通过",
|
|
1703
|
+
"changes-requested": "需修改",
|
|
1704
|
+
}
|
|
1705
|
+
fallback_stage_labels = {
|
|
1706
|
+
"closed": "关闭",
|
|
1707
|
+
"planning": "规划阶段",
|
|
1708
|
+
"execution": "执行阶段",
|
|
1709
|
+
"review": "审核阶段",
|
|
1710
|
+
}
|
|
1711
|
+
action_kind_labels = {
|
|
1712
|
+
"close-step": "关闭当前步骤",
|
|
1713
|
+
"repair-plan": "回到规划修正",
|
|
1714
|
+
"execute-for-real": "执行一次真实运行",
|
|
1715
|
+
"refine-and-rerun": "修正实现后重新执行",
|
|
1716
|
+
"re-evaluate": "重新评估当前步骤",
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if dispatched and dispatch_result.get("success"):
|
|
1720
|
+
final_decision = str(dispatch_result.get("final_decision", "")).strip()
|
|
1721
|
+
fallback_stage = str(dispatch_result.get("fallback_stage", "")).strip()
|
|
1722
|
+
action_kind = str(dispatch_result.get("recommended_action_kind", "")).strip()
|
|
1723
|
+
if final_decision:
|
|
1724
|
+
policy_suffix = ""
|
|
1725
|
+
if fallback_stage:
|
|
1726
|
+
policy_suffix = f" 当前回退阶段为{fallback_stage_labels.get(fallback_stage, fallback_stage)}。"
|
|
1727
|
+
if action_kind:
|
|
1728
|
+
policy_suffix += f" 建议动作类型为{action_kind_labels.get(action_kind, action_kind)}。"
|
|
1729
|
+
return f"{summary} 已自动完成审核,最终决策为{decision_labels.get(final_decision, final_decision)}。{policy_suffix}".strip()
|
|
1730
|
+
if recommended_command:
|
|
1731
|
+
command_next = build_command_user_next(recommended_command)
|
|
1732
|
+
if command_next:
|
|
1733
|
+
return f"{summary} 已自动调度到 {recommended_command},当前应继续{command_next}"
|
|
1734
|
+
return f"{summary} 已自动调度到 {recommended_command}。"
|
|
1735
|
+
return f"{summary} 已自动完成当前建议动作。"
|
|
1736
|
+
if dispatched and not dispatch_result.get("success"):
|
|
1737
|
+
failure_summary, next_step = build_dispatch_failure_user_summary(dispatch_result, recommended_command)
|
|
1738
|
+
dispatch_state = str(dispatch_result.get("state", "")).strip()
|
|
1739
|
+
dispatch_failure_type = str(dispatch_result.get("failure_type", "")).strip()
|
|
1740
|
+
if dispatch_failure_type in {"scope-error", "design-gap"} or dispatch_state == "returned-to-planning":
|
|
1741
|
+
if failure_summary and next_step:
|
|
1742
|
+
return f"{failure_summary} 下一步建议:{next_step}"
|
|
1743
|
+
return failure_summary or next_step or "当前步骤已返回规划阶段。"
|
|
1744
|
+
if dispatch_failure_type == "environment-or-tooling":
|
|
1745
|
+
if failure_summary and next_step:
|
|
1746
|
+
return f"{failure_summary} 下一步建议:{next_step}"
|
|
1747
|
+
return failure_summary or next_step or "当前执行被环境或工具前置条件阻塞。"
|
|
1748
|
+
if dispatch_failure_type == "executor-failure":
|
|
1749
|
+
if failure_summary and next_step:
|
|
1750
|
+
return f"{failure_summary} 下一步建议:{next_step}"
|
|
1751
|
+
return failure_summary or next_step or "当前执行器返回异常,需要先处理阻塞。"
|
|
1752
|
+
if summary and failure_summary and next_step:
|
|
1753
|
+
return f"{summary} 自动调度未完成:{failure_summary} 下一步建议:{next_step}"
|
|
1754
|
+
if summary and next_step:
|
|
1755
|
+
return f"{summary} 自动调度未完成。下一步建议:{next_step}"
|
|
1756
|
+
if summary and failure_summary:
|
|
1757
|
+
return f"{summary} 自动调度未完成:{failure_summary}"
|
|
1758
|
+
if failure_summary and next_step:
|
|
1759
|
+
return f"自动调度未完成:{failure_summary} 下一步建议:{next_step}"
|
|
1760
|
+
if next_step:
|
|
1761
|
+
return f"自动调度未完成。下一步建议:{next_step}"
|
|
1762
|
+
if failure_summary:
|
|
1763
|
+
return f"自动调度未完成:{failure_summary}"
|
|
1764
|
+
if summary and action:
|
|
1765
|
+
if action in summary:
|
|
1766
|
+
return summary
|
|
1767
|
+
return f"{summary} 下一步建议:{action}"
|
|
1768
|
+
if summary:
|
|
1769
|
+
return summary
|
|
1770
|
+
if action:
|
|
1771
|
+
return action
|
|
1772
|
+
return "当前已有工作流状态暂时无法判断,请检查输入的 slug 或最近工作流记录。"
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
def build_missing_existing_workflow_result(
|
|
1776
|
+
*,
|
|
1777
|
+
request: str,
|
|
1778
|
+
explain_only: bool,
|
|
1779
|
+
include_fixtures: bool,
|
|
1780
|
+
human_summary: str = "当前没有可继续的 CodeCGC 工作流。",
|
|
1781
|
+
next_text: str = "先直接描述一个新需求,或显式提供已有 workflow 的 slug。",
|
|
1782
|
+
action_reason: str = "当前仓库里还没有可用于 continue / explain 的最近工作流。",
|
|
1783
|
+
workflow_state: str = "needs-new-workflow",
|
|
1784
|
+
reply_kind: str = "start-new-workflow",
|
|
1785
|
+
action_type: str = "wait-new-request",
|
|
1786
|
+
dispatch_blocker: str = "missing-existing-workflow",
|
|
1787
|
+
recovery_hint: str = "直接描述一个新需求,或显式提供已有 workflow slug。",
|
|
1788
|
+
recommended_command: str = "",
|
|
1789
|
+
command_args: list[str] | None = None,
|
|
1790
|
+
) -> dict[str, Any]:
|
|
1791
|
+
command_args = [str(item).strip() for item in (command_args or []) if str(item).strip()]
|
|
1792
|
+
result = {
|
|
1793
|
+
"success": True,
|
|
1794
|
+
"entry_mode": "explain" if explain_only else "continue",
|
|
1795
|
+
"flow": "",
|
|
1796
|
+
"slug": "",
|
|
1797
|
+
"latest": True,
|
|
1798
|
+
"request": request,
|
|
1799
|
+
"auto_dispatch": False,
|
|
1800
|
+
"auto_dispatch_reason": "missing-existing-workflow",
|
|
1801
|
+
"recommended_command": recommended_command,
|
|
1802
|
+
"next": next_text,
|
|
1803
|
+
"dispatched": False,
|
|
1804
|
+
"dispatch_result": None,
|
|
1805
|
+
"route": {},
|
|
1806
|
+
"route_status": {
|
|
1807
|
+
"workflow_state": workflow_state,
|
|
1808
|
+
"recommended_command": recommended_command,
|
|
1809
|
+
"current_step_number": 0,
|
|
1810
|
+
"current_task_id": "",
|
|
1811
|
+
"current_kind": "",
|
|
1812
|
+
"current_target_paths": [],
|
|
1813
|
+
"review_decision": "",
|
|
1814
|
+
"review_step_number": 0,
|
|
1815
|
+
"review_fallback_stage": "",
|
|
1816
|
+
"review_policy_reason": "",
|
|
1817
|
+
"review_action_kind": "",
|
|
1818
|
+
"audit_path": "",
|
|
1819
|
+
"human_status_summary": human_summary,
|
|
1820
|
+
"operator_action_summary": next_text,
|
|
1821
|
+
"is_closed": False,
|
|
1822
|
+
"needs_review_decision": False,
|
|
1823
|
+
"needs_execution": False,
|
|
1824
|
+
"include_fixtures": include_fixtures,
|
|
1825
|
+
},
|
|
1826
|
+
"human_summary": human_summary,
|
|
1827
|
+
"assistant_reply": compose_user_reply(human_summary, next_text, prefix=""),
|
|
1828
|
+
}
|
|
1829
|
+
result["operator_brief"] = {
|
|
1830
|
+
"entry_mode": result["entry_mode"],
|
|
1831
|
+
"flow": "",
|
|
1832
|
+
"slug": "",
|
|
1833
|
+
"recommended_command": recommended_command,
|
|
1834
|
+
"next": next_text,
|
|
1835
|
+
"human_summary": human_summary,
|
|
1836
|
+
"assistant_reply": result["assistant_reply"],
|
|
1837
|
+
"user_message": result["assistant_reply"],
|
|
1838
|
+
"dispatched": False,
|
|
1839
|
+
"needs_user_reply": True,
|
|
1840
|
+
"needs_execution": False,
|
|
1841
|
+
"needs_review": False,
|
|
1842
|
+
"is_closed": False,
|
|
1843
|
+
"reply_kind": reply_kind,
|
|
1844
|
+
"workflow_state": workflow_state,
|
|
1845
|
+
"current_step_number": 0,
|
|
1846
|
+
"current_task_id": "",
|
|
1847
|
+
"current_kind": "",
|
|
1848
|
+
"current_target_paths": [],
|
|
1849
|
+
"review_decision": "",
|
|
1850
|
+
"audit_path": "",
|
|
1851
|
+
"machine_next_action": {
|
|
1852
|
+
"type": action_type,
|
|
1853
|
+
"command": recommended_command,
|
|
1854
|
+
"reason": action_reason,
|
|
1855
|
+
"can_auto_dispatch": False,
|
|
1856
|
+
"auto_dispatch_reason": dispatch_blocker,
|
|
1857
|
+
"command_args": command_args,
|
|
1858
|
+
"requires_user_reply": True,
|
|
1859
|
+
"requires_decision": False,
|
|
1860
|
+
"requires_audit_file": False,
|
|
1861
|
+
"requires_slug": False,
|
|
1862
|
+
"requires_step_number": False,
|
|
1863
|
+
"missing_fields": [],
|
|
1864
|
+
"workflow_state": workflow_state,
|
|
1865
|
+
"current_step_number": 0,
|
|
1866
|
+
"current_task_id": "",
|
|
1867
|
+
"current_kind": "",
|
|
1868
|
+
"audit_path": "",
|
|
1869
|
+
"review_decision": "",
|
|
1870
|
+
"review_fallback_stage": "",
|
|
1871
|
+
"review_policy_reason": "",
|
|
1872
|
+
"review_action_kind": "",
|
|
1873
|
+
"dispatch_attempted": False,
|
|
1874
|
+
"dispatch_success": None,
|
|
1875
|
+
"dispatch_state": "",
|
|
1876
|
+
"dispatch_failure_type": "",
|
|
1877
|
+
"dispatch_error": "",
|
|
1878
|
+
"dispatch_next": "",
|
|
1879
|
+
"dispatch_summary": "",
|
|
1880
|
+
"recovery_command": "",
|
|
1881
|
+
"recovery_kind": "",
|
|
1882
|
+
"retry_hint": "",
|
|
1883
|
+
"followup_payload": {},
|
|
1884
|
+
"dispatch_blocker": dispatch_blocker,
|
|
1885
|
+
"user_action": {
|
|
1886
|
+
"summary": human_summary,
|
|
1887
|
+
"next_step": next_text,
|
|
1888
|
+
"recovery_hint": recovery_hint,
|
|
1889
|
+
"followup_payload": {},
|
|
1890
|
+
},
|
|
1891
|
+
"diagnostics": {
|
|
1892
|
+
"dispatch_attempted": False,
|
|
1893
|
+
"dispatch_success": None,
|
|
1894
|
+
"dispatch_state": "",
|
|
1895
|
+
"dispatch_failure_type": "",
|
|
1896
|
+
"dispatch_blocker": dispatch_blocker,
|
|
1897
|
+
"dispatch_error": "",
|
|
1898
|
+
"dispatch_next": "",
|
|
1899
|
+
"dispatch_summary": "",
|
|
1900
|
+
"recovery_command": "",
|
|
1901
|
+
"recovery_kind": "",
|
|
1902
|
+
"retry_hint": recovery_hint,
|
|
1903
|
+
"audit_path": "",
|
|
1904
|
+
"review_fallback_stage": "",
|
|
1905
|
+
"review_policy_reason": "",
|
|
1906
|
+
"review_action_kind": "",
|
|
1907
|
+
},
|
|
1908
|
+
"execution": {
|
|
1909
|
+
"command": recommended_command,
|
|
1910
|
+
"command_args": command_args,
|
|
1911
|
+
"can_auto_dispatch": False,
|
|
1912
|
+
"auto_dispatch_reason": dispatch_blocker,
|
|
1913
|
+
"requires_user_reply": True,
|
|
1914
|
+
"requires_decision": False,
|
|
1915
|
+
"requires_audit_file": False,
|
|
1916
|
+
"requires_slug": False,
|
|
1917
|
+
"requires_step_number": False,
|
|
1918
|
+
"missing_fields": [],
|
|
1919
|
+
"workflow_state": workflow_state,
|
|
1920
|
+
"current_step_number": 0,
|
|
1921
|
+
"current_task_id": "",
|
|
1922
|
+
"current_kind": "",
|
|
1923
|
+
"review_decision": "",
|
|
1924
|
+
"review_fallback_stage": "",
|
|
1925
|
+
"review_policy_reason": "",
|
|
1926
|
+
"review_action_kind": "",
|
|
1927
|
+
},
|
|
1928
|
+
"preferred_fields": {
|
|
1929
|
+
"user_message": "user_action.summary",
|
|
1930
|
+
"next_step": "user_action.next_step",
|
|
1931
|
+
"recovery_hint": "user_action.recovery_hint",
|
|
1932
|
+
},
|
|
1933
|
+
"compatibility_fields": {
|
|
1934
|
+
"reason": "请优先改用 user_action.summary。",
|
|
1935
|
+
"dispatch_blocker": "请优先改用 diagnostics.dispatch_blocker。",
|
|
1936
|
+
},
|
|
1937
|
+
},
|
|
1938
|
+
"reply_payload": {},
|
|
1939
|
+
"reply_prompts": [],
|
|
1940
|
+
}
|
|
1941
|
+
return attach_entry_summary(result)
|
|
1942
|
+
return "已读取当前工作流状态。"
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
def build_assistant_reply(result: dict[str, Any]) -> str:
|
|
1946
|
+
entry_mode = str(result.get("entry_mode", "")).strip()
|
|
1947
|
+
if entry_mode == "governance":
|
|
1948
|
+
summary = str(result.get("human_summary", "")).strip()
|
|
1949
|
+
next_text = str(result.get("next", "")).strip()
|
|
1950
|
+
return compose_user_reply(summary, next_text, prefix="")
|
|
1951
|
+
|
|
1952
|
+
dispatched = bool(result.get("dispatched"))
|
|
1953
|
+
dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
1954
|
+
user_next = build_user_facing_next(result)
|
|
1955
|
+
if entry_mode == "new":
|
|
1956
|
+
summary = build_new_mode_human_summary(result)
|
|
1957
|
+
suggested = str(result.get("suggested_reply_template", "")).strip()
|
|
1958
|
+
if dispatched and not dispatch_result.get("success"):
|
|
1959
|
+
return summary
|
|
1960
|
+
if suggested:
|
|
1961
|
+
return f"{summary}\n\n{suggested}"
|
|
1962
|
+
return compose_user_reply(summary, user_next)
|
|
1963
|
+
|
|
1964
|
+
summary = build_existing_mode_human_summary(result)
|
|
1965
|
+
if dispatched and not dispatch_result.get("success"):
|
|
1966
|
+
return summary
|
|
1967
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
1968
|
+
if str(route_status.get("operator_action_summary", "")).strip():
|
|
1969
|
+
return compose_user_reply(summary, user_next)
|
|
1970
|
+
return compose_user_reply(summary, user_next)
|
|
1971
|
+
|
|
1972
|
+
|
|
1973
|
+
def build_beginner_action_pack(result: dict[str, Any]) -> list[str]:
|
|
1974
|
+
operator_brief = result.get("operator_brief", {}) if isinstance(result.get("operator_brief"), dict) else {}
|
|
1975
|
+
machine_next_action = operator_brief.get("machine_next_action", {}) if isinstance(operator_brief.get("machine_next_action"), dict) else {}
|
|
1976
|
+
execution = machine_next_action.get("execution", {}) if isinstance(machine_next_action.get("execution"), dict) else {}
|
|
1977
|
+
user_action = machine_next_action.get("user_action", {}) if isinstance(machine_next_action.get("user_action"), dict) else {}
|
|
1978
|
+
command = str(execution.get("command", "") or machine_next_action.get("command", "")).strip()
|
|
1979
|
+
command_args = execution.get("command_args", []) if isinstance(execution.get("command_args"), list) else []
|
|
1980
|
+
prompts = operator_brief.get("reply_prompts", []) if isinstance(operator_brief.get("reply_prompts"), list) else []
|
|
1981
|
+
next_step = str(user_action.get("next_step", "")).strip()
|
|
1982
|
+
|
|
1983
|
+
actions: list[str] = []
|
|
1984
|
+
if command:
|
|
1985
|
+
rendered = " ".join([command, *[str(item).strip() for item in command_args if str(item).strip()]]).strip()
|
|
1986
|
+
if rendered:
|
|
1987
|
+
actions.append(f"直接执行:{rendered}")
|
|
1988
|
+
if next_step:
|
|
1989
|
+
actions.append(f"你现在要做:{next_step}")
|
|
1990
|
+
for item in prompts[:3]:
|
|
1991
|
+
if not isinstance(item, dict):
|
|
1992
|
+
continue
|
|
1993
|
+
label = str(item.get("label", "")).strip()
|
|
1994
|
+
question = str(item.get("question", "")).strip()
|
|
1995
|
+
if label and question:
|
|
1996
|
+
actions.append(f"先补充 {label}:{question}")
|
|
1997
|
+
return actions[:3]
|
|
1998
|
+
|
|
1999
|
+
|
|
2000
|
+
def attach_entry_summary(result: dict[str, Any]) -> dict[str, Any]:
|
|
2001
|
+
operator_brief = result.get("operator_brief", {}) if isinstance(result.get("operator_brief"), dict) else {}
|
|
2002
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
2003
|
+
machine_next_action = operator_brief.get("machine_next_action", {}) if isinstance(operator_brief.get("machine_next_action"), dict) else {}
|
|
2004
|
+
execution = machine_next_action.get("execution", {}) if isinstance(machine_next_action.get("execution"), dict) else {}
|
|
2005
|
+
summary = {
|
|
2006
|
+
"human_summary": str(result.get("human_summary", "")).strip(),
|
|
2007
|
+
"assistant_reply": str(result.get("assistant_reply", "")).strip(),
|
|
2008
|
+
"recommended_command": str(result.get("recommended_command", "")).strip(),
|
|
2009
|
+
"next": str(result.get("next", "")).strip(),
|
|
2010
|
+
"beginner_actions": build_beginner_action_pack(result),
|
|
2011
|
+
"workflow_state": str(
|
|
2012
|
+
execution.get("workflow_state", "")
|
|
2013
|
+
or machine_next_action.get("workflow_state", "")
|
|
2014
|
+
or route_status.get("workflow_state", "")
|
|
2015
|
+
).strip(),
|
|
2016
|
+
"reply_kind": str(operator_brief.get("reply_kind", "")).strip(),
|
|
2017
|
+
"action_type": str(machine_next_action.get("type", "")).strip(),
|
|
2018
|
+
"governance_type": str(operator_brief.get("governance_type", "")).strip(),
|
|
2019
|
+
"is_closed": bool(operator_brief.get("is_closed")),
|
|
2020
|
+
}
|
|
2021
|
+
result["summary"] = summary
|
|
2022
|
+
return result
|
|
2023
|
+
|
|
2024
|
+
|
|
2025
|
+
def extract_governance_artifact_info(governance_type: str, dispatch_result: dict[str, Any]) -> dict[str, str]:
|
|
2026
|
+
key_map = {
|
|
2027
|
+
"decide": ("decision", "path"),
|
|
2028
|
+
"learn": ("learning", "path"),
|
|
2029
|
+
"arch": ("architecture", "path"),
|
|
2030
|
+
"req": ("requirement", "path"),
|
|
2031
|
+
"roadmap": ("roadmap", "directory"),
|
|
2032
|
+
"refactor": ("refactor", "path"),
|
|
2033
|
+
"guide": ("guide", "path"),
|
|
2034
|
+
"libdoc": ("libdoc", "path"),
|
|
2035
|
+
"trick": ("trick", "path"),
|
|
2036
|
+
"explore": ("explore", "path"),
|
|
2037
|
+
}
|
|
2038
|
+
result_key, path_key = key_map.get(governance_type, ("", "path"))
|
|
2039
|
+
artifact = dispatch_result.get(result_key, {}) if result_key and isinstance(dispatch_result.get(result_key), dict) else {}
|
|
2040
|
+
return {
|
|
2041
|
+
"result_key": result_key,
|
|
2042
|
+
"artifact_path": str(artifact.get(path_key, "")).strip(),
|
|
2043
|
+
"summary": str(artifact.get("summary", "")).strip(),
|
|
2044
|
+
"created": str(artifact.get("created", "")).strip(),
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
def build_governance_followup_bundle(
|
|
2049
|
+
governance_type: str,
|
|
2050
|
+
request: str,
|
|
2051
|
+
dispatch_result: dict[str, Any],
|
|
2052
|
+
) -> tuple[dict[str, Any], list[dict[str, str]], bool]:
|
|
2053
|
+
artifact = extract_governance_artifact_info(governance_type, dispatch_result)
|
|
2054
|
+
payload = {
|
|
2055
|
+
"governance_type": governance_type,
|
|
2056
|
+
"source_request": request.strip(),
|
|
2057
|
+
"artifact_path": artifact["artifact_path"],
|
|
2058
|
+
"artifact_summary": artifact["summary"] or request.strip(),
|
|
2059
|
+
"created": artifact["created"] or "unknown",
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
prompts: list[dict[str, str]] = []
|
|
2063
|
+
needs_user_reply = False
|
|
2064
|
+
|
|
2065
|
+
if governance_type == "guide":
|
|
2066
|
+
needs_user_reply = True
|
|
2067
|
+
payload["suggested_request"] = "继续完善刚刚生成的 guide,补全目的、步骤和使用边界。"
|
|
2068
|
+
prompts = [
|
|
2069
|
+
{"label": "目的", "question": "这份 guide 最终要帮谁完成什么任务?"},
|
|
2070
|
+
{"label": "步骤", "question": "最少需要哪些可执行步骤,用户才能照着完成?"},
|
|
2071
|
+
{"label": "边界", "question": "有哪些前置条件、非目标或常见误区需要提前说明?"},
|
|
2072
|
+
]
|
|
2073
|
+
elif governance_type == "libdoc":
|
|
2074
|
+
needs_user_reply = True
|
|
2075
|
+
payload["suggested_request"] = "继续完善刚刚生成的 libdoc,补全公开契约、示例和边界说明。"
|
|
2076
|
+
prompts = [
|
|
2077
|
+
{"label": "契约", "question": "这个公开表面有哪些稳定输入、输出或字段约束?"},
|
|
2078
|
+
{"label": "示例", "question": "最小示例应该如何调用或使用它?"},
|
|
2079
|
+
{"label": "边界", "question": "有哪些限制、异常场景或版本边界需要写清楚?"},
|
|
2080
|
+
]
|
|
2081
|
+
elif governance_type == "trick":
|
|
2082
|
+
needs_user_reply = True
|
|
2083
|
+
payload["suggested_request"] = "继续完善刚刚记录的 trick,补全默认做法、适用范围和反例。"
|
|
2084
|
+
prompts = [
|
|
2085
|
+
{"label": "做法", "question": "这条技巧最推荐的默认做法是什么?"},
|
|
2086
|
+
{"label": "范围", "question": "它适用于哪些场景,不适用于哪些场景?"},
|
|
2087
|
+
{"label": "反例", "question": "有没有常见误用或应该避免的写法?"},
|
|
2088
|
+
]
|
|
2089
|
+
elif governance_type == "explore":
|
|
2090
|
+
needs_user_reply = True
|
|
2091
|
+
payload["suggested_request"] = "继续细化刚刚登记的 explore 请求,明确问题、目标模块和预期输出。"
|
|
2092
|
+
prompts = [
|
|
2093
|
+
{"label": "问题", "question": "这次 explore 最终想回答的核心问题是什么?"},
|
|
2094
|
+
{"label": "模块", "question": "优先应该读哪些目录、模块或文件?"},
|
|
2095
|
+
{"label": "输出", "question": "希望最后产出结论、模块总览,还是 spike 结论?"},
|
|
2096
|
+
]
|
|
2097
|
+
|
|
2098
|
+
return payload, prompts, needs_user_reply
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
def build_operator_brief(result: dict[str, Any]) -> dict[str, Any]:
|
|
2102
|
+
entry_mode = str(result.get("entry_mode", "")).strip()
|
|
2103
|
+
flow = str(result.get("flow", "")).strip()
|
|
2104
|
+
slug = str(result.get("slug", "")).strip()
|
|
2105
|
+
recommended_command = str(result.get("recommended_command", "")).strip()
|
|
2106
|
+
next_text = str(result.get("next", "")).strip()
|
|
2107
|
+
human_summary = str(result.get("human_summary", "")).strip()
|
|
2108
|
+
assistant_reply = str(result.get("assistant_reply", "")).strip()
|
|
2109
|
+
dispatched = bool(result.get("dispatched"))
|
|
2110
|
+
auto_dispatch = bool(result.get("auto_dispatch"))
|
|
2111
|
+
auto_dispatch_reason = str(result.get("auto_dispatch_reason", "")).strip()
|
|
2112
|
+
dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
2113
|
+
replan_payload = (
|
|
2114
|
+
result.get("replan_payload", {})
|
|
2115
|
+
if isinstance(result.get("replan_payload"), dict)
|
|
2116
|
+
else {}
|
|
2117
|
+
)
|
|
2118
|
+
if dispatched and isinstance(dispatch_result.get("replan_payload"), dict) and dispatch_result.get("replan_payload"):
|
|
2119
|
+
merged_replan_payload = dict(replan_payload)
|
|
2120
|
+
merged_replan_payload.update(dispatch_result.get("replan_payload", {}))
|
|
2121
|
+
replan_payload = merged_replan_payload
|
|
2122
|
+
|
|
2123
|
+
def build_command_args(command: str, route_status: dict[str, Any] | None = None) -> list[str]:
|
|
2124
|
+
if not command:
|
|
2125
|
+
return []
|
|
2126
|
+
args: list[str] = []
|
|
2127
|
+
if matches_command(command, "cgc-plan"):
|
|
2128
|
+
if flow:
|
|
2129
|
+
args.extend(["--flow", flow])
|
|
2130
|
+
if slug:
|
|
2131
|
+
args.extend(["--slug", slug])
|
|
2132
|
+
summary = str(replan_payload.get("summary", "")).strip()
|
|
2133
|
+
if summary:
|
|
2134
|
+
args.extend(["--summary", summary])
|
|
2135
|
+
kind = str(replan_payload.get("kind", "")).strip()
|
|
2136
|
+
if kind:
|
|
2137
|
+
args.extend(["--kind", kind])
|
|
2138
|
+
for item in replan_payload.get("target_paths", []):
|
|
2139
|
+
if str(item).strip():
|
|
2140
|
+
args.extend(["--target-path", str(item).strip()])
|
|
2141
|
+
if flow == "feature":
|
|
2142
|
+
for key, flag in (
|
|
2143
|
+
("goal", "--goal"),
|
|
2144
|
+
("user_story", "--user-story"),
|
|
2145
|
+
):
|
|
2146
|
+
value = str(replan_payload.get(key, "")).strip()
|
|
2147
|
+
if value:
|
|
2148
|
+
args.extend([flag, value])
|
|
2149
|
+
else:
|
|
2150
|
+
for key, flag in (
|
|
2151
|
+
("symptom", "--symptom"),
|
|
2152
|
+
("expected", "--expected"),
|
|
2153
|
+
("actual", "--actual"),
|
|
2154
|
+
("user_story", "--user-story"),
|
|
2155
|
+
):
|
|
2156
|
+
value = str(replan_payload.get(key, "")).strip()
|
|
2157
|
+
if value:
|
|
2158
|
+
args.extend([flag, value])
|
|
2159
|
+
for item in replan_payload.get("in_scope", []):
|
|
2160
|
+
if str(item).strip():
|
|
2161
|
+
args.extend(["--in-scope", str(item).strip()])
|
|
2162
|
+
for item in replan_payload.get("acceptance", []):
|
|
2163
|
+
if str(item).strip():
|
|
2164
|
+
args.extend(["--acceptance", str(item).strip()])
|
|
2165
|
+
return args
|
|
2166
|
+
if slug and matches_command(command, "cgc-build", "cgc-fix", "cgc-test"):
|
|
2167
|
+
args.extend(["--slug", slug])
|
|
2168
|
+
if matches_command(command, "cgc-build", "cgc-fix", "cgc-test"):
|
|
2169
|
+
current_step_number = int((route_status or {}).get("current_step_number", 0) or 0)
|
|
2170
|
+
if current_step_number:
|
|
2171
|
+
args.extend(["--step-number", str(current_step_number)])
|
|
2172
|
+
reusable_session_id = str((route_status or {}).get("reusable_session_id", "")).strip()
|
|
2173
|
+
if reusable_session_id:
|
|
2174
|
+
args.extend(["--session-id", reusable_session_id])
|
|
2175
|
+
if matches_command(command, "cgc-review"):
|
|
2176
|
+
audit_path = str((route_status or {}).get("audit_path", "")).strip()
|
|
2177
|
+
review_decision = str((route_status or {}).get("review_decision", "")).strip()
|
|
2178
|
+
if audit_path:
|
|
2179
|
+
args.extend(["--audit-file", audit_path])
|
|
2180
|
+
if review_decision in {"accepted", "changes-requested"}:
|
|
2181
|
+
args.extend(["--decision", review_decision])
|
|
2182
|
+
return args
|
|
2183
|
+
|
|
2184
|
+
def build_machine_next_action(
|
|
2185
|
+
*,
|
|
2186
|
+
action_type: str,
|
|
2187
|
+
command: str,
|
|
2188
|
+
reason: str,
|
|
2189
|
+
route_status: dict[str, Any] | None = None,
|
|
2190
|
+
missing_fields: list[str] | None = None,
|
|
2191
|
+
reply_payload: dict[str, Any] | None = None,
|
|
2192
|
+
reply_prompts: list[dict[str, str]] | None = None,
|
|
2193
|
+
force_can_auto_dispatch: bool | None = None,
|
|
2194
|
+
) -> dict[str, Any]:
|
|
2195
|
+
route_status = route_status or {}
|
|
2196
|
+
missing_fields = missing_fields or []
|
|
2197
|
+
reply_payload = reply_payload or {}
|
|
2198
|
+
reply_prompts = reply_prompts or []
|
|
2199
|
+
current_step_number = int(route_status.get("current_step_number", 0) or 0)
|
|
2200
|
+
audit_path = str(route_status.get("audit_path", "")).strip()
|
|
2201
|
+
review_decision = str(route_status.get("review_decision", "")).strip()
|
|
2202
|
+
review_fallback_stage = str(route_status.get("review_fallback_stage", "")).strip()
|
|
2203
|
+
review_policy_reason = str(route_status.get("review_policy_reason", "")).strip()
|
|
2204
|
+
review_action_kind = str(route_status.get("review_action_kind", "")).strip()
|
|
2205
|
+
dispatch_state = str(dispatch_result.get("state", "")).strip() if dispatched else ""
|
|
2206
|
+
dispatch_failure_type = str(dispatch_result.get("failure_type", "")).strip() if dispatched else ""
|
|
2207
|
+
dispatch_error = str(dispatch_result.get("error", "")).strip() if dispatched else ""
|
|
2208
|
+
dispatch_next = str(dispatch_result.get("next", "")).strip() if dispatched else ""
|
|
2209
|
+
dispatch_summary = str(dispatch_result.get("summary", "")).strip() if dispatched else ""
|
|
2210
|
+
dispatch_audit_path = str(dispatch_result.get("audit_path", "")).strip() if dispatched else ""
|
|
2211
|
+
dispatch_review_fallback_stage = str(dispatch_result.get("fallback_stage", "")).strip() if dispatched else ""
|
|
2212
|
+
dispatch_review_policy_reason = str(dispatch_result.get("policy_reason", "")).strip() if dispatched else ""
|
|
2213
|
+
dispatch_review_action_kind = str(dispatch_result.get("recommended_action_kind", "")).strip() if dispatched else ""
|
|
2214
|
+
dispatch_user_hint = build_dispatch_recovery_user_hint(dispatch_result, command) if dispatched else ""
|
|
2215
|
+
dispatch_recovery = infer_dispatch_recovery(command, dispatch_failure_type, dispatch_state, dispatch_next) if dispatched else {
|
|
2216
|
+
"recovery_command": "",
|
|
2217
|
+
"recovery_kind": "",
|
|
2218
|
+
"retry_hint": "",
|
|
2219
|
+
}
|
|
2220
|
+
followup_payload: dict[str, Any] = {}
|
|
2221
|
+
if reply_payload:
|
|
2222
|
+
followup_payload = {
|
|
2223
|
+
"payload": reply_payload,
|
|
2224
|
+
"prompts": reply_prompts,
|
|
2225
|
+
}
|
|
2226
|
+
if matches_command(command, "cgc-plan") and replan_payload:
|
|
2227
|
+
existing_payload = (
|
|
2228
|
+
followup_payload.get("payload", {})
|
|
2229
|
+
if isinstance(followup_payload.get("payload"), dict)
|
|
2230
|
+
else {}
|
|
2231
|
+
)
|
|
2232
|
+
merged_payload = dict(replan_payload)
|
|
2233
|
+
merged_payload.update(existing_payload)
|
|
2234
|
+
followup_payload = {
|
|
2235
|
+
"payload": merged_payload,
|
|
2236
|
+
"prompts": reply_prompts,
|
|
2237
|
+
}
|
|
2238
|
+
can_auto_dispatch = bool(auto_dispatch and command)
|
|
2239
|
+
if force_can_auto_dispatch is not None:
|
|
2240
|
+
can_auto_dispatch = force_can_auto_dispatch
|
|
2241
|
+
dispatch_blocker = ""
|
|
2242
|
+
if action_type == "wait-review-decision" and review_decision not in {"accepted", "changes-requested"}:
|
|
2243
|
+
dispatch_blocker = "review-decision-missing"
|
|
2244
|
+
elif dispatched and not dispatch_result.get("success"):
|
|
2245
|
+
dispatch_blocker = dispatch_failure_type or dispatch_error or dispatch_state or auto_dispatch_reason
|
|
2246
|
+
elif not can_auto_dispatch and auto_dispatch_reason:
|
|
2247
|
+
dispatch_blocker = auto_dispatch_reason
|
|
2248
|
+
command_args = build_command_args(command, route_status)
|
|
2249
|
+
user_action = {
|
|
2250
|
+
"summary": reason,
|
|
2251
|
+
"next_step": reason,
|
|
2252
|
+
"recovery_hint": dispatch_user_hint or str(dispatch_recovery.get("retry_hint", "")).strip(),
|
|
2253
|
+
"followup_payload": followup_payload,
|
|
2254
|
+
}
|
|
2255
|
+
diagnostics = {
|
|
2256
|
+
"dispatch_attempted": dispatched,
|
|
2257
|
+
"dispatch_success": bool(dispatch_result.get("success")) if dispatched else None,
|
|
2258
|
+
"dispatch_state": dispatch_state,
|
|
2259
|
+
"dispatch_failure_type": dispatch_failure_type,
|
|
2260
|
+
"dispatch_blocker": dispatch_blocker,
|
|
2261
|
+
"dispatch_error": dispatch_error,
|
|
2262
|
+
"dispatch_next": dispatch_next,
|
|
2263
|
+
"dispatch_summary": dispatch_summary,
|
|
2264
|
+
"recovery_command": str(dispatch_recovery.get("recovery_command", "")).strip(),
|
|
2265
|
+
"recovery_kind": str(dispatch_recovery.get("recovery_kind", "")).strip(),
|
|
2266
|
+
"retry_hint": dispatch_user_hint or str(dispatch_recovery.get("retry_hint", "")).strip(),
|
|
2267
|
+
"audit_path": dispatch_audit_path or audit_path,
|
|
2268
|
+
"review_fallback_stage": dispatch_review_fallback_stage or review_fallback_stage,
|
|
2269
|
+
"review_policy_reason": dispatch_review_policy_reason or review_policy_reason,
|
|
2270
|
+
"review_action_kind": dispatch_review_action_kind or review_action_kind,
|
|
2271
|
+
}
|
|
2272
|
+
execution = {
|
|
2273
|
+
"command": command,
|
|
2274
|
+
"command_args": command_args,
|
|
2275
|
+
"can_auto_dispatch": can_auto_dispatch,
|
|
2276
|
+
"auto_dispatch_reason": auto_dispatch_reason,
|
|
2277
|
+
"requires_user_reply": action_type == "wait-user-reply",
|
|
2278
|
+
"requires_decision": action_type in {"review", "wait-review-decision"} and review_decision not in {"accepted", "changes-requested"},
|
|
2279
|
+
"requires_audit_file": action_type == "review" and not audit_path,
|
|
2280
|
+
"requires_slug": not bool(slug) and action_type in {"dispatch", "review"},
|
|
2281
|
+
"requires_step_number": action_type == "dispatch" and matches_command(command, "cgc-build", "cgc-fix") and current_step_number <= 0,
|
|
2282
|
+
"missing_fields": missing_fields,
|
|
2283
|
+
"workflow_state": str(route_status.get("workflow_state", "")).strip(),
|
|
2284
|
+
"current_step_number": current_step_number,
|
|
2285
|
+
"current_task_id": str(route_status.get("current_task_id", "")).strip(),
|
|
2286
|
+
"current_kind": str(route_status.get("current_kind", "")).strip(),
|
|
2287
|
+
"review_decision": review_decision,
|
|
2288
|
+
"review_fallback_stage": dispatch_review_fallback_stage or review_fallback_stage,
|
|
2289
|
+
"review_policy_reason": dispatch_review_policy_reason or review_policy_reason,
|
|
2290
|
+
"review_action_kind": dispatch_review_action_kind or review_action_kind,
|
|
2291
|
+
}
|
|
2292
|
+
preferred_fields = {
|
|
2293
|
+
"user_message": "user_action.summary",
|
|
2294
|
+
"next_step": "user_action.next_step",
|
|
2295
|
+
"recovery_hint": "user_action.recovery_hint",
|
|
2296
|
+
"execution_command": "execution.command",
|
|
2297
|
+
"execution_args": "execution.command_args",
|
|
2298
|
+
"diagnostic_blocker": "diagnostics.dispatch_blocker",
|
|
2299
|
+
"diagnostic_error": "diagnostics.dispatch_error",
|
|
2300
|
+
"diagnostic_next": "diagnostics.dispatch_next",
|
|
2301
|
+
}
|
|
2302
|
+
compatibility_fields = {
|
|
2303
|
+
"reason": "请优先改用 user_action.summary。",
|
|
2304
|
+
"command": "请优先改用 execution.command。",
|
|
2305
|
+
"command_args": "请优先改用 execution.command_args。",
|
|
2306
|
+
"retry_hint": "请优先改用 user_action.recovery_hint 或 diagnostics.retry_hint。",
|
|
2307
|
+
"dispatch_blocker": "请优先改用 diagnostics.dispatch_blocker。",
|
|
2308
|
+
"dispatch_error": "请优先改用 diagnostics.dispatch_error。",
|
|
2309
|
+
"dispatch_next": "请优先改用 diagnostics.dispatch_next。",
|
|
2310
|
+
}
|
|
2311
|
+
return {
|
|
2312
|
+
"type": action_type,
|
|
2313
|
+
"command": command,
|
|
2314
|
+
"reason": reason,
|
|
2315
|
+
"can_auto_dispatch": can_auto_dispatch,
|
|
2316
|
+
"auto_dispatch_reason": auto_dispatch_reason,
|
|
2317
|
+
"command_args": command_args,
|
|
2318
|
+
"requires_user_reply": action_type == "wait-user-reply",
|
|
2319
|
+
"requires_decision": action_type in {"review", "wait-review-decision"} and review_decision not in {"accepted", "changes-requested"},
|
|
2320
|
+
"requires_audit_file": action_type == "review" and not audit_path,
|
|
2321
|
+
"requires_slug": not bool(slug) and action_type in {"dispatch", "review"},
|
|
2322
|
+
"requires_step_number": action_type == "dispatch" and matches_command(command, "cgc-build", "cgc-fix") and current_step_number <= 0,
|
|
2323
|
+
"missing_fields": missing_fields,
|
|
2324
|
+
"workflow_state": str(route_status.get("workflow_state", "")).strip(),
|
|
2325
|
+
"current_step_number": current_step_number,
|
|
2326
|
+
"current_task_id": str(route_status.get("current_task_id", "")).strip(),
|
|
2327
|
+
"current_kind": str(route_status.get("current_kind", "")).strip(),
|
|
2328
|
+
"audit_path": dispatch_audit_path or audit_path,
|
|
2329
|
+
"review_decision": review_decision,
|
|
2330
|
+
"review_fallback_stage": dispatch_review_fallback_stage or review_fallback_stage,
|
|
2331
|
+
"review_policy_reason": dispatch_review_policy_reason or review_policy_reason,
|
|
2332
|
+
"review_action_kind": dispatch_review_action_kind or review_action_kind,
|
|
2333
|
+
"dispatch_attempted": dispatched,
|
|
2334
|
+
"dispatch_success": bool(dispatch_result.get("success")) if dispatched else None,
|
|
2335
|
+
"dispatch_state": dispatch_state,
|
|
2336
|
+
"dispatch_failure_type": dispatch_failure_type,
|
|
2337
|
+
"dispatch_error": dispatch_error,
|
|
2338
|
+
"dispatch_next": dispatch_next,
|
|
2339
|
+
"dispatch_summary": dispatch_summary,
|
|
2340
|
+
"recovery_command": str(dispatch_recovery.get("recovery_command", "")).strip(),
|
|
2341
|
+
"recovery_kind": str(dispatch_recovery.get("recovery_kind", "")).strip(),
|
|
2342
|
+
"retry_hint": dispatch_user_hint or str(dispatch_recovery.get("retry_hint", "")).strip(),
|
|
2343
|
+
"followup_payload": followup_payload,
|
|
2344
|
+
"dispatch_blocker": dispatch_blocker,
|
|
2345
|
+
"user_action": user_action,
|
|
2346
|
+
"diagnostics": diagnostics,
|
|
2347
|
+
"execution": execution,
|
|
2348
|
+
"preferred_fields": preferred_fields,
|
|
2349
|
+
"compatibility_fields": compatibility_fields,
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
brief: dict[str, Any] = {
|
|
2353
|
+
"entry_mode": entry_mode,
|
|
2354
|
+
"flow": flow,
|
|
2355
|
+
"slug": slug,
|
|
2356
|
+
"recommended_command": recommended_command,
|
|
2357
|
+
"next": next_text,
|
|
2358
|
+
"human_summary": human_summary,
|
|
2359
|
+
"assistant_reply": assistant_reply,
|
|
2360
|
+
"user_message": assistant_reply,
|
|
2361
|
+
"dispatched": dispatched,
|
|
2362
|
+
"needs_user_reply": False,
|
|
2363
|
+
"needs_execution": False,
|
|
2364
|
+
"needs_review": False,
|
|
2365
|
+
"is_closed": False,
|
|
2366
|
+
"reply_kind": "status",
|
|
2367
|
+
"machine_next_action": build_machine_next_action(
|
|
2368
|
+
action_type="idle",
|
|
2369
|
+
command=recommended_command,
|
|
2370
|
+
reason=next_text,
|
|
2371
|
+
),
|
|
2372
|
+
"reply_payload": {},
|
|
2373
|
+
"reply_prompts": [],
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
if entry_mode == "governance":
|
|
2377
|
+
governance_skill = str(result.get("recommended_skill", "")).strip()
|
|
2378
|
+
governance_type = str(result.get("governance_type", "")).strip()
|
|
2379
|
+
governance_dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
2380
|
+
governance_reply_payload, governance_reply_prompts, governance_needs_user_reply = build_governance_followup_bundle(
|
|
2381
|
+
governance_type,
|
|
2382
|
+
str(result.get("request", "")),
|
|
2383
|
+
governance_dispatch_result,
|
|
2384
|
+
)
|
|
2385
|
+
brief["reply_kind"] = "governance"
|
|
2386
|
+
brief["governance_type"] = governance_type
|
|
2387
|
+
brief["recommended_skill"] = governance_skill
|
|
2388
|
+
brief["needs_user_reply"] = governance_needs_user_reply
|
|
2389
|
+
brief["reply_payload"] = governance_reply_payload
|
|
2390
|
+
brief["reply_prompts"] = governance_reply_prompts
|
|
2391
|
+
brief["machine_next_action"] = {
|
|
2392
|
+
"type": "wait-governance-followup" if governance_needs_user_reply else "governance",
|
|
2393
|
+
"command": "",
|
|
2394
|
+
"reason": next_text,
|
|
2395
|
+
"recommended_skill": governance_skill,
|
|
2396
|
+
"governance_type": governance_type,
|
|
2397
|
+
"can_auto_dispatch": False,
|
|
2398
|
+
"requires_user_reply": governance_needs_user_reply,
|
|
2399
|
+
"workflow_state": "governance-routing",
|
|
2400
|
+
"user_action": {
|
|
2401
|
+
"summary": human_summary,
|
|
2402
|
+
"next_step": next_text,
|
|
2403
|
+
"recovery_hint": "",
|
|
2404
|
+
"followup_payload": {
|
|
2405
|
+
"payload": governance_reply_payload,
|
|
2406
|
+
"prompts": governance_reply_prompts,
|
|
2407
|
+
},
|
|
2408
|
+
},
|
|
2409
|
+
"diagnostics": {
|
|
2410
|
+
"dispatch_attempted": False,
|
|
2411
|
+
"dispatch_success": None,
|
|
2412
|
+
"dispatch_state": "",
|
|
2413
|
+
"dispatch_failure_type": "",
|
|
2414
|
+
"dispatch_blocker": "",
|
|
2415
|
+
"dispatch_error": "",
|
|
2416
|
+
"dispatch_next": "",
|
|
2417
|
+
"dispatch_summary": "",
|
|
2418
|
+
"recovery_command": "",
|
|
2419
|
+
"recovery_kind": "",
|
|
2420
|
+
"retry_hint": "",
|
|
2421
|
+
"audit_path": "",
|
|
2422
|
+
},
|
|
2423
|
+
"execution": {
|
|
2424
|
+
"command": "",
|
|
2425
|
+
"command_args": [],
|
|
2426
|
+
"can_auto_dispatch": False,
|
|
2427
|
+
"auto_dispatch_reason": "governance-skill-routing",
|
|
2428
|
+
"requires_user_reply": governance_needs_user_reply,
|
|
2429
|
+
"requires_decision": False,
|
|
2430
|
+
"requires_audit_file": False,
|
|
2431
|
+
"requires_slug": False,
|
|
2432
|
+
"requires_step_number": False,
|
|
2433
|
+
"missing_fields": [],
|
|
2434
|
+
"workflow_state": "governance-routing",
|
|
2435
|
+
"current_step_number": 0,
|
|
2436
|
+
"current_task_id": "",
|
|
2437
|
+
"current_kind": "",
|
|
2438
|
+
"review_decision": "",
|
|
2439
|
+
},
|
|
2440
|
+
"preferred_fields": {
|
|
2441
|
+
"user_message": "user_action.summary",
|
|
2442
|
+
"next_step": "user_action.next_step",
|
|
2443
|
+
"governance_skill": "recommended_skill",
|
|
2444
|
+
"followup_payload": "user_action.followup_payload.payload",
|
|
2445
|
+
},
|
|
2446
|
+
"compatibility_fields": {
|
|
2447
|
+
"reason": "请优先改用 user_action.summary。",
|
|
2448
|
+
},
|
|
2449
|
+
}
|
|
2450
|
+
return brief
|
|
2451
|
+
|
|
2452
|
+
if entry_mode == "new":
|
|
2453
|
+
planning_missing_fields = result.get("planning_missing_fields", [])
|
|
2454
|
+
clarification_prompts = result.get("clarification_prompts", [])
|
|
2455
|
+
brief["planning_status"] = str(result.get("planning_status", "")).strip()
|
|
2456
|
+
brief["planning_missing_fields"] = planning_missing_fields
|
|
2457
|
+
brief["resolved_kind"] = str(result.get("resolved_kind", "")).strip()
|
|
2458
|
+
brief["resolved_target_paths"] = result.get("resolved_target_paths", [])
|
|
2459
|
+
brief["needs_user_reply"] = bool(planning_missing_fields)
|
|
2460
|
+
brief["needs_execution"] = matches_command(recommended_command, "cgc-build", "cgc-fix")
|
|
2461
|
+
brief["reply_kind"] = "clarification" if planning_missing_fields else ("execution" if brief["needs_execution"] else "status")
|
|
2462
|
+
brief["reply_payload"] = result.get("suggested_reply_payload", {})
|
|
2463
|
+
brief["reply_prompts"] = clarification_prompts
|
|
2464
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
2465
|
+
brief["workflow_state"] = str(route_status.get("workflow_state", "")).strip()
|
|
2466
|
+
brief["current_step_number"] = int(route_status.get("current_step_number", 0) or 0)
|
|
2467
|
+
brief["current_task_id"] = str(route_status.get("current_task_id", "")).strip()
|
|
2468
|
+
brief["current_kind"] = str(route_status.get("current_kind", "")).strip()
|
|
2469
|
+
brief["current_target_paths"] = route_status.get("current_target_paths", [])
|
|
2470
|
+
brief["review_decision"] = str(route_status.get("review_decision", "")).strip()
|
|
2471
|
+
brief["audit_path"] = str(route_status.get("audit_path", "")).strip()
|
|
2472
|
+
brief["machine_next_action"] = build_machine_next_action(
|
|
2473
|
+
action_type="wait-user-reply" if planning_missing_fields else ("dispatch" if brief["needs_execution"] else "idle"),
|
|
2474
|
+
command=recommended_command,
|
|
2475
|
+
reason=next_text,
|
|
2476
|
+
route_status=route_status,
|
|
2477
|
+
missing_fields=[str(item) for item in planning_missing_fields],
|
|
2478
|
+
reply_payload=brief["reply_payload"] if isinstance(brief["reply_payload"], dict) else {},
|
|
2479
|
+
reply_prompts=clarification_prompts if isinstance(clarification_prompts, list) else [],
|
|
2480
|
+
)
|
|
2481
|
+
return brief
|
|
2482
|
+
|
|
2483
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
2484
|
+
brief["workflow_state"] = str(route_status.get("workflow_state", "")).strip()
|
|
2485
|
+
brief["needs_execution"] = bool(route_status.get("needs_execution"))
|
|
2486
|
+
brief["needs_review"] = bool(route_status.get("needs_review_decision"))
|
|
2487
|
+
brief["is_closed"] = bool(route_status.get("is_closed"))
|
|
2488
|
+
brief["current_step_number"] = int(route_status.get("current_step_number", 0) or 0)
|
|
2489
|
+
brief["current_task_id"] = str(route_status.get("current_task_id", "")).strip()
|
|
2490
|
+
brief["current_kind"] = str(route_status.get("current_kind", "")).strip()
|
|
2491
|
+
brief["current_target_paths"] = route_status.get("current_target_paths", [])
|
|
2492
|
+
brief["review_decision"] = str(route_status.get("review_decision", "")).strip()
|
|
2493
|
+
brief["audit_path"] = str(route_status.get("audit_path", "")).strip()
|
|
2494
|
+
brief["review_fallback_stage"] = str(route_status.get("review_fallback_stage", "")).strip()
|
|
2495
|
+
brief["review_policy_reason"] = str(route_status.get("review_policy_reason", "")).strip()
|
|
2496
|
+
brief["review_action_kind"] = str(route_status.get("review_action_kind", "")).strip()
|
|
2497
|
+
if dispatched:
|
|
2498
|
+
brief["dispatch_success"] = bool(dispatch_result.get("success"))
|
|
2499
|
+
brief["dispatch_error"] = str(dispatch_result.get("error", "")).strip()
|
|
2500
|
+
if dispatched and not dispatch_result.get("success"):
|
|
2501
|
+
failed_command = str(dispatch_result.get("recommended_command", "")).strip() or recommended_command
|
|
2502
|
+
failed_reason = build_dispatch_recovery_user_hint(dispatch_result, failed_command) or next_text
|
|
2503
|
+
brief["reply_kind"] = "blocked"
|
|
2504
|
+
brief["machine_next_action"] = build_machine_next_action(
|
|
2505
|
+
action_type="dispatch-failed",
|
|
2506
|
+
command=failed_command,
|
|
2507
|
+
reason=failed_reason,
|
|
2508
|
+
route_status=route_status,
|
|
2509
|
+
force_can_auto_dispatch=False,
|
|
2510
|
+
)
|
|
2511
|
+
elif brief["needs_review"]:
|
|
2512
|
+
brief["reply_kind"] = "review"
|
|
2513
|
+
brief["machine_next_action"] = build_machine_next_action(
|
|
2514
|
+
action_type="wait-review-decision" if not brief["review_decision"] else "review",
|
|
2515
|
+
command=recommended_command,
|
|
2516
|
+
reason=next_text,
|
|
2517
|
+
route_status=route_status,
|
|
2518
|
+
)
|
|
2519
|
+
elif brief["needs_execution"]:
|
|
2520
|
+
brief["reply_kind"] = "execution"
|
|
2521
|
+
brief["machine_next_action"] = build_machine_next_action(
|
|
2522
|
+
action_type="dispatch",
|
|
2523
|
+
command=recommended_command,
|
|
2524
|
+
reason=next_text,
|
|
2525
|
+
route_status=route_status,
|
|
2526
|
+
)
|
|
2527
|
+
elif brief["is_closed"]:
|
|
2528
|
+
brief["reply_kind"] = "closed"
|
|
2529
|
+
brief["machine_next_action"] = build_machine_next_action(
|
|
2530
|
+
action_type="closed",
|
|
2531
|
+
command="",
|
|
2532
|
+
reason=next_text,
|
|
2533
|
+
route_status=route_status,
|
|
2534
|
+
)
|
|
2535
|
+
else:
|
|
2536
|
+
brief["machine_next_action"] = build_machine_next_action(
|
|
2537
|
+
action_type="idle",
|
|
2538
|
+
command=recommended_command,
|
|
2539
|
+
reason=next_text,
|
|
2540
|
+
route_status=route_status,
|
|
2541
|
+
)
|
|
2542
|
+
return brief
|
|
2543
|
+
|
|
2544
|
+
|
|
2545
|
+
def discover_existing_flow(slug: str) -> tuple[str, str] | None:
|
|
2546
|
+
feature = discover_flow_directory("feature", slug, "auto")
|
|
2547
|
+
issue = discover_flow_directory("issue", slug, "auto")
|
|
2548
|
+
if feature and issue:
|
|
2549
|
+
raise ValueError(f"Slug '{slug}' exists as both feature and issue. Specify --flow explicitly.")
|
|
2550
|
+
if feature:
|
|
2551
|
+
return "feature", feature[0]
|
|
2552
|
+
if issue:
|
|
2553
|
+
return "issue", issue[0]
|
|
2554
|
+
return None
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
def workflow_activity_timestamp(directory: Path) -> float:
|
|
2558
|
+
timestamps = [directory.stat().st_mtime]
|
|
2559
|
+
for path in directory.rglob("*"):
|
|
2560
|
+
try:
|
|
2561
|
+
timestamps.append(path.stat().st_mtime)
|
|
2562
|
+
except OSError:
|
|
2563
|
+
continue
|
|
2564
|
+
return max(timestamps) if timestamps else 0.0
|
|
2565
|
+
|
|
2566
|
+
|
|
2567
|
+
def build_latest_workflow_state_priority(request: str, explain_only: bool = False) -> dict[str, int]:
|
|
2568
|
+
if explain_only or request_implies_explain(request):
|
|
2569
|
+
return {
|
|
2570
|
+
"awaiting-review": 4,
|
|
2571
|
+
"awaiting-build": 3,
|
|
2572
|
+
"awaiting-fix": 3,
|
|
2573
|
+
"needs-planning": 2,
|
|
2574
|
+
"step-selected": 1,
|
|
2575
|
+
"closed": 0,
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
if request_implies_review(request):
|
|
2579
|
+
return {
|
|
2580
|
+
"awaiting-review": 5,
|
|
2581
|
+
"awaiting-build": 3,
|
|
2582
|
+
"awaiting-fix": 3,
|
|
2583
|
+
"needs-planning": 2,
|
|
2584
|
+
"step-selected": 1,
|
|
2585
|
+
"closed": 0,
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
if request_implies_execute(request) or request_implies_continue(request):
|
|
2589
|
+
return {
|
|
2590
|
+
"awaiting-build": 5,
|
|
2591
|
+
"awaiting-fix": 5,
|
|
2592
|
+
"awaiting-review": 4,
|
|
2593
|
+
"needs-planning": 3,
|
|
2594
|
+
"step-selected": 2,
|
|
2595
|
+
"closed": 0,
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
return {
|
|
2599
|
+
"awaiting-review": 4,
|
|
2600
|
+
"awaiting-build": 3,
|
|
2601
|
+
"awaiting-fix": 3,
|
|
2602
|
+
"needs-planning": 2,
|
|
2603
|
+
"step-selected": 1,
|
|
2604
|
+
"closed": 0,
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
|
|
2608
|
+
def build_latest_workflow_required_states(request: str, explain_only: bool = False) -> set[str]:
|
|
2609
|
+
if explain_only or request_implies_explain(request):
|
|
2610
|
+
return set()
|
|
2611
|
+
if request_implies_review(request):
|
|
2612
|
+
return {"awaiting-review"}
|
|
2613
|
+
return set()
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
def score_latest_workflow_candidate(
|
|
2617
|
+
flow: str,
|
|
2618
|
+
slug: str,
|
|
2619
|
+
state_priority: dict[str, int] | None = None,
|
|
2620
|
+
) -> tuple[int, int, str]:
|
|
2621
|
+
try:
|
|
2622
|
+
route = run_json_script("route_codecgc_workflow.py", "--flow", flow, "--slug", slug)
|
|
2623
|
+
except Exception:
|
|
2624
|
+
return (0, 0, "")
|
|
2625
|
+
|
|
2626
|
+
summary = route.get("summary", {}) if isinstance(route.get("summary"), dict) else {}
|
|
2627
|
+
workflow_state = str(summary.get("workflow_state", "")).strip()
|
|
2628
|
+
is_closed = bool(summary.get("is_closed"))
|
|
2629
|
+
recommended_command = str(route.get("recommended_command", "")).strip()
|
|
2630
|
+
priorities = state_priority or build_latest_workflow_state_priority("")
|
|
2631
|
+
|
|
2632
|
+
if not is_closed and workflow_state:
|
|
2633
|
+
return (int(priorities.get(workflow_state, 1)), 1, workflow_state)
|
|
2634
|
+
if not is_closed and recommended_command:
|
|
2635
|
+
return (1, 1, workflow_state)
|
|
2636
|
+
return (0, 0, workflow_state)
|
|
2637
|
+
|
|
2638
|
+
|
|
2639
|
+
def discover_latest_flow(
|
|
2640
|
+
include_fixtures: bool,
|
|
2641
|
+
*,
|
|
2642
|
+
request: str = "",
|
|
2643
|
+
explain_only: bool = False,
|
|
2644
|
+
) -> tuple[str, str] | None:
|
|
2645
|
+
candidates: list[tuple[int, int, float, str, str]] = []
|
|
2646
|
+
state_priority = build_latest_workflow_state_priority(request, explain_only=explain_only)
|
|
2647
|
+
required_states = build_latest_workflow_required_states(request, explain_only=explain_only)
|
|
2648
|
+
artifact_classes = ["product", "fixture"] if include_fixtures else ["product"]
|
|
2649
|
+
for artifact_class in artifact_classes:
|
|
2650
|
+
for flow in ("feature", "issue"):
|
|
2651
|
+
root = flow_root(flow, artifact_class)
|
|
2652
|
+
if not root.exists():
|
|
2653
|
+
continue
|
|
2654
|
+
for child in root.iterdir():
|
|
2655
|
+
if not child.is_dir():
|
|
2656
|
+
continue
|
|
2657
|
+
lifecycle_priority, active_priority, workflow_state = score_latest_workflow_candidate(
|
|
2658
|
+
flow,
|
|
2659
|
+
child.name,
|
|
2660
|
+
state_priority=state_priority,
|
|
2661
|
+
)
|
|
2662
|
+
if required_states and workflow_state not in required_states:
|
|
2663
|
+
continue
|
|
2664
|
+
candidates.append(
|
|
2665
|
+
(
|
|
2666
|
+
active_priority,
|
|
2667
|
+
lifecycle_priority,
|
|
2668
|
+
workflow_activity_timestamp(child),
|
|
2669
|
+
flow,
|
|
2670
|
+
child.name,
|
|
2671
|
+
)
|
|
2672
|
+
)
|
|
2673
|
+
if not candidates:
|
|
2674
|
+
return None
|
|
2675
|
+
candidates.sort(key=lambda item: (item[0], item[1], item[2]), reverse=True)
|
|
2676
|
+
_, _, _, flow, slug = candidates[0]
|
|
2677
|
+
return flow, slug
|
|
2678
|
+
|
|
2679
|
+
|
|
2680
|
+
def resolve_existing_flow(args: argparse.Namespace) -> str:
|
|
2681
|
+
if args.flow:
|
|
2682
|
+
return args.flow
|
|
2683
|
+
use_latest = args.latest or (args.request.strip() and not args.slug.strip())
|
|
2684
|
+
if use_latest and not args.slug:
|
|
2685
|
+
discovered = discover_latest_flow(args.include_fixtures, request=args.request)
|
|
2686
|
+
if not discovered:
|
|
2687
|
+
raise ValueError("No existing workflow was found for the requested continue/explain operation.")
|
|
2688
|
+
return discovered[0]
|
|
2689
|
+
if not args.slug:
|
|
2690
|
+
raise ValueError("Existing workflow operations require --slug.")
|
|
2691
|
+
discovered = discover_existing_flow(args.slug)
|
|
2692
|
+
if not discovered:
|
|
2693
|
+
raise ValueError(f"No existing feature or issue workflow was found for slug '{args.slug}'.")
|
|
2694
|
+
return discovered[0]
|
|
2695
|
+
|
|
2696
|
+
|
|
2697
|
+
def infer_mode(args: argparse.Namespace) -> str:
|
|
2698
|
+
if args.mode != "auto":
|
|
2699
|
+
return args.mode
|
|
2700
|
+
request = args.request.strip()
|
|
2701
|
+
shortcut_mode = classify_existing_workflow_shortcut(request)
|
|
2702
|
+
if shortcut_mode:
|
|
2703
|
+
return shortcut_mode
|
|
2704
|
+
if request and request_implies_explain(request):
|
|
2705
|
+
return "explain" if not args.summary.strip() else "new"
|
|
2706
|
+
if request and request_implies_continue(request):
|
|
2707
|
+
return "continue"
|
|
2708
|
+
if args.latest and not args.slug:
|
|
2709
|
+
return "continue"
|
|
2710
|
+
if args.slug:
|
|
2711
|
+
discovered = discover_existing_flow(args.slug)
|
|
2712
|
+
if discovered:
|
|
2713
|
+
return "continue"
|
|
2714
|
+
if args.summary.strip():
|
|
2715
|
+
return "new"
|
|
2716
|
+
if args.summary.strip() or request or any(item.strip() for item in args.target_path):
|
|
2717
|
+
return "new"
|
|
2718
|
+
if args.slug:
|
|
2719
|
+
return "explain"
|
|
2720
|
+
raise ValueError("Auto mode could not infer intent. Provide --request, --summary, --slug, or --latest.")
|
|
2721
|
+
|
|
2722
|
+
|
|
2723
|
+
def build_plan_args(args: argparse.Namespace, flow: str) -> list[str]:
|
|
2724
|
+
slug = resolve_new_slug(args, flow)
|
|
2725
|
+
summary = resolve_new_summary(args)
|
|
2726
|
+
target_paths = resolve_target_paths(args)
|
|
2727
|
+
kind = resolve_kind(args, target_paths)
|
|
2728
|
+
goal = infer_feature_goal(args) if flow == "feature" else args.goal
|
|
2729
|
+
user_story = infer_feature_user_story(args) if flow == "feature" else args.user_story
|
|
2730
|
+
in_scope = infer_in_scope(args, flow, target_paths)
|
|
2731
|
+
acceptance = infer_acceptance(args, flow)
|
|
2732
|
+
symptom = infer_issue_symptom(args) if flow == "issue" else args.symptom
|
|
2733
|
+
expected = infer_issue_expected(args) if flow == "issue" else args.expected
|
|
2734
|
+
actual = infer_issue_actual(args) if flow == "issue" else args.actual
|
|
2735
|
+
|
|
2736
|
+
command = [
|
|
2737
|
+
"--flow",
|
|
2738
|
+
flow,
|
|
2739
|
+
"--slug",
|
|
2740
|
+
slug,
|
|
2741
|
+
"--summary",
|
|
2742
|
+
summary,
|
|
2743
|
+
"--kind",
|
|
2744
|
+
kind,
|
|
2745
|
+
"--artifact-class",
|
|
2746
|
+
args.artifact_class,
|
|
2747
|
+
]
|
|
2748
|
+
if args.date:
|
|
2749
|
+
command.extend(["--date", args.date])
|
|
2750
|
+
for item in target_paths:
|
|
2751
|
+
command.extend(["--target-path", item])
|
|
2752
|
+
if goal:
|
|
2753
|
+
command.extend(["--goal", goal])
|
|
2754
|
+
if user_story:
|
|
2755
|
+
command.extend(["--user-story", user_story])
|
|
2756
|
+
for item in args.context:
|
|
2757
|
+
command.extend(["--context", item])
|
|
2758
|
+
for item in in_scope:
|
|
2759
|
+
command.extend(["--in-scope", item])
|
|
2760
|
+
for item in args.out_of_scope:
|
|
2761
|
+
command.extend(["--out-of-scope", item])
|
|
2762
|
+
for item in acceptance:
|
|
2763
|
+
command.extend(["--acceptance", item])
|
|
2764
|
+
for item in args.risk:
|
|
2765
|
+
command.extend(["--risk", item])
|
|
2766
|
+
for item in args.dependency:
|
|
2767
|
+
command.extend(["--dependency", item])
|
|
2768
|
+
for item in args.assumption:
|
|
2769
|
+
command.extend(["--assumption", item])
|
|
2770
|
+
for item in args.open_question:
|
|
2771
|
+
command.extend(["--open-question", item])
|
|
2772
|
+
for item in args.validation:
|
|
2773
|
+
command.extend(["--validation", item])
|
|
2774
|
+
for item in args.rollback:
|
|
2775
|
+
command.extend(["--rollback", item])
|
|
2776
|
+
if symptom:
|
|
2777
|
+
command.extend(["--symptom", symptom])
|
|
2778
|
+
if args.reproduction:
|
|
2779
|
+
command.extend(["--reproduction", args.reproduction])
|
|
2780
|
+
if expected:
|
|
2781
|
+
command.extend(["--expected", expected])
|
|
2782
|
+
if actual:
|
|
2783
|
+
command.extend(["--actual", actual])
|
|
2784
|
+
if args.root_cause:
|
|
2785
|
+
command.extend(["--root-cause", args.root_cause])
|
|
2786
|
+
if args.preferred_fix:
|
|
2787
|
+
command.extend(["--preferred-fix", args.preferred_fix])
|
|
2788
|
+
if args.rejected_fix:
|
|
2789
|
+
command.extend(["--rejected-fix", args.rejected_fix])
|
|
2790
|
+
if args.force:
|
|
2791
|
+
command.append("--force")
|
|
2792
|
+
return command
|
|
2793
|
+
|
|
2794
|
+
|
|
2795
|
+
def dispatch_from_route(flow: str, slug: str, route: dict[str, Any], args: argparse.Namespace) -> dict[str, Any]:
|
|
2796
|
+
command = to_internal_command(str(route.get("recommended_command", "")).strip())
|
|
2797
|
+
route_status = build_route_status_summary(route) if isinstance(route, dict) else {}
|
|
2798
|
+
reusable_session_id = str(route_status.get("reusable_session_id", "")).strip()
|
|
2799
|
+
|
|
2800
|
+
if command == "cgc-build":
|
|
2801
|
+
command_args = ["--slug", slug, "--timeout-seconds", str(args.timeout_seconds)]
|
|
2802
|
+
if args.step_number is not None:
|
|
2803
|
+
command_args.extend(["--step-number", str(args.step_number)])
|
|
2804
|
+
if args.checklist_file:
|
|
2805
|
+
command_args.extend(["--checklist-file", args.checklist_file])
|
|
2806
|
+
if args.audit_root:
|
|
2807
|
+
command_args.extend(["--audit-root", args.audit_root])
|
|
2808
|
+
if args.dry_run:
|
|
2809
|
+
command_args.append("--dry-run")
|
|
2810
|
+
if args.return_all_messages:
|
|
2811
|
+
command_args.append("--return-all-messages")
|
|
2812
|
+
if reusable_session_id:
|
|
2813
|
+
command_args.extend(["--session-id", reusable_session_id])
|
|
2814
|
+
return run_json_script("run_codecgc_build.py", *command_args)
|
|
2815
|
+
|
|
2816
|
+
if command == "cgc-fix":
|
|
2817
|
+
command_args = ["--slug", slug, "--timeout-seconds", str(args.timeout_seconds)]
|
|
2818
|
+
if args.step_number is not None:
|
|
2819
|
+
command_args.extend(["--step-number", str(args.step_number)])
|
|
2820
|
+
if args.checklist_file:
|
|
2821
|
+
command_args.extend(["--checklist-file", args.checklist_file])
|
|
2822
|
+
if args.audit_root:
|
|
2823
|
+
command_args.extend(["--audit-root", args.audit_root])
|
|
2824
|
+
if args.dry_run:
|
|
2825
|
+
command_args.append("--dry-run")
|
|
2826
|
+
if args.return_all_messages:
|
|
2827
|
+
command_args.append("--return-all-messages")
|
|
2828
|
+
if reusable_session_id:
|
|
2829
|
+
command_args.extend(["--session-id", reusable_session_id])
|
|
2830
|
+
return run_json_script("run_codecgc_fix.py", *command_args)
|
|
2831
|
+
|
|
2832
|
+
if command == "cgc-test":
|
|
2833
|
+
command_args = ["--flow", flow, "--slug", slug, "--timeout-seconds", str(args.timeout_seconds)]
|
|
2834
|
+
if args.step_number is not None:
|
|
2835
|
+
command_args.extend(["--step-number", str(args.step_number)])
|
|
2836
|
+
if args.checklist_file:
|
|
2837
|
+
command_args.extend(["--checklist-file", args.checklist_file])
|
|
2838
|
+
if args.audit_root:
|
|
2839
|
+
command_args.extend(["--audit-root", args.audit_root])
|
|
2840
|
+
if args.dry_run:
|
|
2841
|
+
command_args.append("--dry-run")
|
|
2842
|
+
if args.return_all_messages:
|
|
2843
|
+
command_args.append("--return-all-messages")
|
|
2844
|
+
if reusable_session_id:
|
|
2845
|
+
command_args.extend(["--session-id", reusable_session_id])
|
|
2846
|
+
return run_json_script("run_codecgc_test.py", *command_args)
|
|
2847
|
+
|
|
2848
|
+
if command == "cgc-review":
|
|
2849
|
+
audit_file = args.audit_file or str(route.get("audit_path", "")).strip()
|
|
2850
|
+
if not audit_file:
|
|
2851
|
+
return {
|
|
2852
|
+
"success": False,
|
|
2853
|
+
"error": "Review 调度需要先提供 audit 文件。",
|
|
2854
|
+
"recommended_command": to_public_command("cgc-review"),
|
|
2855
|
+
"next": route.get("next", "先提供 audit 文件,再执行 review 调度。"),
|
|
2856
|
+
}
|
|
2857
|
+
if not args.decision:
|
|
2858
|
+
return {
|
|
2859
|
+
"success": False,
|
|
2860
|
+
"error": "Review 调度需要先提供 --decision accepted|changes-requested。",
|
|
2861
|
+
"recommended_command": to_public_command("cgc-review"),
|
|
2862
|
+
"next": route.get("next", "先提供 review 决策,再执行 review 调度。"),
|
|
2863
|
+
}
|
|
2864
|
+
command_args = ["--audit-file", audit_file, "--decision", args.decision]
|
|
2865
|
+
for item in args.risk:
|
|
2866
|
+
command_args.extend(["--risk", item])
|
|
2867
|
+
if args.next_step:
|
|
2868
|
+
command_args.extend(["--next-step", args.next_step])
|
|
2869
|
+
if args.force:
|
|
2870
|
+
command_args.append("--force")
|
|
2871
|
+
return run_json_script("review_codecgc_workflow.py", *command_args)
|
|
2872
|
+
|
|
2873
|
+
return {
|
|
2874
|
+
"success": False,
|
|
2875
|
+
"error": f"当前状态 '{command or 'closed'}' 不支持自动调度。",
|
|
2876
|
+
"recommended_command": to_public_command(command),
|
|
2877
|
+
"next": route.get("next", "请手动执行推荐命令。"),
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
|
|
2881
|
+
def refresh_route_after_dispatch(flow: str, slug: str, result: dict[str, Any]) -> None:
|
|
2882
|
+
dispatch_result = result.get("dispatch_result", {}) if isinstance(result.get("dispatch_result"), dict) else {}
|
|
2883
|
+
if not dispatch_result.get("success"):
|
|
2884
|
+
return
|
|
2885
|
+
if result.get("entry_mode") == "new":
|
|
2886
|
+
return
|
|
2887
|
+
|
|
2888
|
+
refreshed_route = run_json_script("route_codecgc_workflow.py", "--flow", flow, "--slug", slug)
|
|
2889
|
+
result["route"] = refreshed_route
|
|
2890
|
+
result["route_status"] = build_route_status_summary(refreshed_route)
|
|
2891
|
+
result["recommended_command"] = to_public_command(str(refreshed_route.get("recommended_command", "")).strip())
|
|
2892
|
+
result["next"] = str(refreshed_route.get("next", "")).strip()
|
|
2893
|
+
|
|
2894
|
+
if str(dispatch_result.get("review_state", "")).strip():
|
|
2895
|
+
policy_status = build_review_policy_status(dispatch_result)
|
|
2896
|
+
route_status = result.get("route_status", {}) if isinstance(result.get("route_status"), dict) else {}
|
|
2897
|
+
route_status["review_fallback_stage"] = policy_status["fallback_stage"]
|
|
2898
|
+
route_status["review_policy_reason"] = policy_status["policy_reason"]
|
|
2899
|
+
route_status["review_action_kind"] = policy_status["recommended_action_kind"]
|
|
2900
|
+
if policy_status["human_status_summary"]:
|
|
2901
|
+
route_status["human_status_summary"] = policy_status["human_status_summary"]
|
|
2902
|
+
if policy_status["operator_action_summary"]:
|
|
2903
|
+
route_status["operator_action_summary"] = policy_status["operator_action_summary"]
|
|
2904
|
+
result["route_status"] = route_status
|
|
2905
|
+
|
|
2906
|
+
|
|
2907
|
+
def run_new_mode(args: argparse.Namespace) -> dict[str, Any]:
|
|
2908
|
+
flow = infer_flow_for_new_work(args)
|
|
2909
|
+
resolved_target_paths = resolve_target_paths(args)
|
|
2910
|
+
resolved_kind = resolve_kind(args, resolved_target_paths)
|
|
2911
|
+
planning_snapshot = build_planning_snapshot(flow, args, resolved_target_paths, resolved_kind)
|
|
2912
|
+
plan = run_json_script("plan_codecgc_workflow.py", *build_plan_args(args, flow))
|
|
2913
|
+
planning_missing_fields = list(plan.get("planning_missing_fields", []))
|
|
2914
|
+
clarification_prompts = build_clarification_prompts(flow, planning_missing_fields)
|
|
2915
|
+
recommended_command = to_public_command(str(plan.get("recommended_command", "")).strip())
|
|
2916
|
+
effective_auto_dispatch, auto_dispatch_reason = resolve_effective_auto_dispatch(args, recommended_command)
|
|
2917
|
+
result = {
|
|
2918
|
+
"success": bool(plan.get("success")),
|
|
2919
|
+
"entry_mode": "new",
|
|
2920
|
+
"flow": flow,
|
|
2921
|
+
"slug": plan.get("slug", args.slug),
|
|
2922
|
+
"request": args.request,
|
|
2923
|
+
"resolved_kind": resolved_kind,
|
|
2924
|
+
"resolved_target_paths": resolved_target_paths,
|
|
2925
|
+
"planning_snapshot": planning_snapshot,
|
|
2926
|
+
"captured_fields": build_captured_fields(planning_snapshot),
|
|
2927
|
+
"auto_dispatch": effective_auto_dispatch,
|
|
2928
|
+
"auto_dispatch_reason": auto_dispatch_reason,
|
|
2929
|
+
"plan": plan,
|
|
2930
|
+
"planning_status": plan.get("planning_status", ""),
|
|
2931
|
+
"planning_reasons": plan.get("planning_reasons", []),
|
|
2932
|
+
"planning_missing_fields": planning_missing_fields,
|
|
2933
|
+
"clarification_mode": build_clarification_mode(flow, planning_missing_fields, resolved_kind),
|
|
2934
|
+
"clarification_prompts": clarification_prompts,
|
|
2935
|
+
"route": plan.get("route", {}),
|
|
2936
|
+
"recommended_command": recommended_command,
|
|
2937
|
+
"next": plan.get("next", ""),
|
|
2938
|
+
"dispatched": False,
|
|
2939
|
+
"dispatch_result": None,
|
|
2940
|
+
}
|
|
2941
|
+
result["suggested_reply_template"] = build_suggested_reply_template(flow, result["clarification_prompts"])
|
|
2942
|
+
result["suggested_reply_payload"] = build_suggested_reply_payload(flow, planning_snapshot, planning_missing_fields)
|
|
2943
|
+
if isinstance(plan.get("route"), dict):
|
|
2944
|
+
result["route_status"] = build_route_status_summary(plan.get("route", {}))
|
|
2945
|
+
if not effective_auto_dispatch or not plan.get("success"):
|
|
2946
|
+
result["next"] = build_user_facing_next(result)
|
|
2947
|
+
result["human_summary"] = build_new_mode_human_summary(result)
|
|
2948
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
2949
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
2950
|
+
return attach_entry_summary(result)
|
|
2951
|
+
|
|
2952
|
+
if recommended_command not in {"cgc-build", "cgc-fix"}:
|
|
2953
|
+
result["next"] = build_user_facing_next(result)
|
|
2954
|
+
result["human_summary"] = build_new_mode_human_summary(result)
|
|
2955
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
2956
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
2957
|
+
return attach_entry_summary(result)
|
|
2958
|
+
|
|
2959
|
+
dispatch_result = dispatch_from_route(flow, str(plan.get("slug", args.slug)), plan.get("route", {}), args)
|
|
2960
|
+
result["dispatched"] = True
|
|
2961
|
+
result["dispatch_result"] = dispatch_result
|
|
2962
|
+
if dispatch_result.get("success"):
|
|
2963
|
+
result["recommended_command"] = to_public_command(dispatch_result.get("recommended_command", ""))
|
|
2964
|
+
result["next"] = dispatch_result.get("next", "")
|
|
2965
|
+
else:
|
|
2966
|
+
apply_dispatch_failure_context(result, recommended_command)
|
|
2967
|
+
result["human_summary"] = build_new_mode_human_summary(result)
|
|
2968
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
2969
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
2970
|
+
return attach_entry_summary(result)
|
|
2971
|
+
|
|
2972
|
+
|
|
2973
|
+
def run_existing_mode(args: argparse.Namespace, explain_only: bool) -> dict[str, Any]:
|
|
2974
|
+
if not args.slug.strip() and not args.latest and not args.request.strip():
|
|
2975
|
+
raise ValueError("Continue or explain mode requires --slug, --latest, or a continue/explain request.")
|
|
2976
|
+
slug = args.slug
|
|
2977
|
+
use_latest = args.latest or (args.request.strip() and not args.slug.strip())
|
|
2978
|
+
latest_discovered: tuple[str, str] | None = None
|
|
2979
|
+
if not slug and use_latest:
|
|
2980
|
+
latest_discovered = discover_latest_flow(
|
|
2981
|
+
args.include_fixtures,
|
|
2982
|
+
request=args.request,
|
|
2983
|
+
explain_only=explain_only,
|
|
2984
|
+
)
|
|
2985
|
+
if not latest_discovered:
|
|
2986
|
+
if request_implies_review(args.request):
|
|
2987
|
+
return build_missing_existing_workflow_result(
|
|
2988
|
+
request=args.request,
|
|
2989
|
+
explain_only=explain_only,
|
|
2990
|
+
include_fixtures=args.include_fixtures,
|
|
2991
|
+
human_summary="当前没有待审核的 CodeCGC 工作流。",
|
|
2992
|
+
next_text="先用“现在下一步该做什么”查看最近工作流,或先完成执行步骤后再回来审核。",
|
|
2993
|
+
action_reason="当前仓库里没有处于 awaiting-review 的最近工作流,无法直接处理“通过/不通过/审核”这类请求。",
|
|
2994
|
+
workflow_state="needs-review-target",
|
|
2995
|
+
reply_kind="review-target-missing",
|
|
2996
|
+
action_type="wait-review-target",
|
|
2997
|
+
dispatch_blocker="missing-review-workflow",
|
|
2998
|
+
recovery_hint="先定位一个待审核 workflow,或先完成 build/fix 再回来审核。",
|
|
2999
|
+
recommended_command="cgc",
|
|
3000
|
+
command_args=["--request", "现在下一步该做什么"],
|
|
3001
|
+
)
|
|
3002
|
+
return build_missing_existing_workflow_result(
|
|
3003
|
+
request=args.request,
|
|
3004
|
+
explain_only=explain_only,
|
|
3005
|
+
include_fixtures=args.include_fixtures,
|
|
3006
|
+
recommended_command="cgc",
|
|
3007
|
+
command_args=["--request", "<描述你的新需求>"],
|
|
3008
|
+
)
|
|
3009
|
+
slug = latest_discovered[1]
|
|
3010
|
+
flow = args.flow or (latest_discovered[0] if latest_discovered else resolve_existing_flow(args))
|
|
3011
|
+
route = run_json_script("route_codecgc_workflow.py", "--flow", flow, "--slug", slug)
|
|
3012
|
+
recommended_command = to_public_command(str(route.get("recommended_command", "")).strip())
|
|
3013
|
+
replan_payload = build_existing_workflow_replan_payload(flow, slug)
|
|
3014
|
+
effective_auto_dispatch, auto_dispatch_reason = resolve_effective_auto_dispatch(
|
|
3015
|
+
args,
|
|
3016
|
+
recommended_command,
|
|
3017
|
+
explain_only=explain_only,
|
|
3018
|
+
)
|
|
3019
|
+
result = {
|
|
3020
|
+
"success": bool(route.get("success", True)),
|
|
3021
|
+
"entry_mode": "explain" if explain_only else "continue",
|
|
3022
|
+
"flow": flow,
|
|
3023
|
+
"slug": slug,
|
|
3024
|
+
"auto_dispatch": effective_auto_dispatch,
|
|
3025
|
+
"auto_dispatch_reason": auto_dispatch_reason,
|
|
3026
|
+
"latest": use_latest,
|
|
3027
|
+
"request": args.request,
|
|
3028
|
+
"route": route,
|
|
3029
|
+
"route_status": build_route_status_summary(route),
|
|
3030
|
+
"replan_payload": replan_payload,
|
|
3031
|
+
"recommended_command": recommended_command,
|
|
3032
|
+
"next": route.get("next", ""),
|
|
3033
|
+
"dispatched": False,
|
|
3034
|
+
"dispatch_result": None,
|
|
3035
|
+
}
|
|
3036
|
+
if explain_only or not effective_auto_dispatch:
|
|
3037
|
+
result["next"] = build_user_facing_next(result)
|
|
3038
|
+
result["human_summary"] = build_existing_mode_human_summary(result)
|
|
3039
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
3040
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
3041
|
+
return attach_entry_summary(result)
|
|
3042
|
+
|
|
3043
|
+
if recommended_command not in {"cgc-build", "cgc-fix", "cgc-review"}:
|
|
3044
|
+
result["next"] = build_user_facing_next(result)
|
|
3045
|
+
result["human_summary"] = build_existing_mode_human_summary(result)
|
|
3046
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
3047
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
3048
|
+
return attach_entry_summary(result)
|
|
3049
|
+
|
|
3050
|
+
dispatch_result = dispatch_from_route(flow, slug, route, args)
|
|
3051
|
+
result["dispatched"] = True
|
|
3052
|
+
result["dispatch_result"] = dispatch_result
|
|
3053
|
+
if dispatch_result.get("success"):
|
|
3054
|
+
result["recommended_command"] = to_public_command(dispatch_result.get("recommended_command", ""))
|
|
3055
|
+
result["next"] = dispatch_result.get("next", "")
|
|
3056
|
+
refresh_route_after_dispatch(flow, slug, result)
|
|
3057
|
+
result["next"] = build_user_facing_next(result)
|
|
3058
|
+
else:
|
|
3059
|
+
apply_dispatch_failure_context(result, recommended_command)
|
|
3060
|
+
result["human_summary"] = build_existing_mode_human_summary(result)
|
|
3061
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
3062
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
3063
|
+
return attach_entry_summary(result)
|
|
3064
|
+
|
|
3065
|
+
|
|
3066
|
+
def run_governance_mode(args: argparse.Namespace, governance: dict[str, str]) -> dict[str, Any]:
|
|
3067
|
+
request = args.request.strip()
|
|
3068
|
+
slug = args.slug.strip()
|
|
3069
|
+
flow = args.flow.strip()
|
|
3070
|
+
human_summary = str(governance.get("human_summary", "")).strip()
|
|
3071
|
+
next_text = str(governance.get("next", "")).strip()
|
|
3072
|
+
skill = str(governance.get("skill", "")).strip()
|
|
3073
|
+
governance_type = str(governance.get("governance_type", "")).strip()
|
|
3074
|
+
dispatch_result: dict[str, Any] | None = None
|
|
3075
|
+
auto_dispatch = False
|
|
3076
|
+
auto_dispatch_reason = "governance-skill-routing"
|
|
3077
|
+
|
|
3078
|
+
if governance_type == "decide":
|
|
3079
|
+
auto_dispatch = True
|
|
3080
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3081
|
+
dispatch_result = run_json_script(
|
|
3082
|
+
"write_codecgc_decision.py",
|
|
3083
|
+
"--summary",
|
|
3084
|
+
request,
|
|
3085
|
+
"--constraint",
|
|
3086
|
+
"将其视为约束未来 CodeCGC 行为的长期有效规则。",
|
|
3087
|
+
"--source",
|
|
3088
|
+
"cgc-entry 治理分诊",
|
|
3089
|
+
)
|
|
3090
|
+
if dispatch_result.get("success"):
|
|
3091
|
+
decision = dispatch_result.get("decision", {}) if isinstance(dispatch_result.get("decision"), dict) else {}
|
|
3092
|
+
decision_path = str(decision.get("path", "")).strip()
|
|
3093
|
+
human_summary = "已将这条长期决定写入 CodeCGC 的长期决策资产。"
|
|
3094
|
+
next_text = f"下一步检查 {decision_path or 'codecgc/compound/codecgc-decisions.md'},确认表述是否还需要补充。"
|
|
3095
|
+
|
|
3096
|
+
elif governance_type == "learn":
|
|
3097
|
+
auto_dispatch = True
|
|
3098
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3099
|
+
dispatch_result = run_json_script(
|
|
3100
|
+
"write_codecgc_learning.py",
|
|
3101
|
+
"--summary",
|
|
3102
|
+
request,
|
|
3103
|
+
"--kind",
|
|
3104
|
+
"practice",
|
|
3105
|
+
"--instruction",
|
|
3106
|
+
"遇到同类问题时,优先把可复用经验沉淀到长期资产,而不是只停留在会话里。",
|
|
3107
|
+
"--source",
|
|
3108
|
+
"cgc-entry 治理分诊",
|
|
3109
|
+
)
|
|
3110
|
+
if dispatch_result.get("success"):
|
|
3111
|
+
learning = dispatch_result.get("learning", {}) if isinstance(dispatch_result.get("learning"), dict) else {}
|
|
3112
|
+
learning_path = str(learning.get("path", "")).strip()
|
|
3113
|
+
human_summary = "已将这条可复用经验写入 CodeCGC 的经验资产。"
|
|
3114
|
+
next_text = f"下一步检查 {learning_path or 'codecgc/compound/codecgc-learning-log.md'},确认是否需要补充更具体的后续指引。"
|
|
3115
|
+
|
|
3116
|
+
elif governance_type == "arch":
|
|
3117
|
+
auto_dispatch = True
|
|
3118
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3119
|
+
dispatch_result = run_json_script(
|
|
3120
|
+
"write_codecgc_architecture.py",
|
|
3121
|
+
"--summary",
|
|
3122
|
+
request,
|
|
3123
|
+
"--note",
|
|
3124
|
+
"将其视为当前仓库架构现状的长期更新记录。",
|
|
3125
|
+
"--source",
|
|
3126
|
+
"cgc-entry 治理分诊",
|
|
3127
|
+
)
|
|
3128
|
+
if dispatch_result.get("success"):
|
|
3129
|
+
architecture = (
|
|
3130
|
+
dispatch_result.get("architecture", {})
|
|
3131
|
+
if isinstance(dispatch_result.get("architecture"), dict)
|
|
3132
|
+
else {}
|
|
3133
|
+
)
|
|
3134
|
+
architecture_path = str(architecture.get("path", "")).strip()
|
|
3135
|
+
human_summary = "已将这条当前态架构更新写入 CodeCGC 的架构资产。"
|
|
3136
|
+
next_text = (
|
|
3137
|
+
f"下一步检查 {architecture_path or 'codecgc/architecture/codecgc-system-map.md'},"
|
|
3138
|
+
"确认是否需要补充更细的模块边界或集成说明。"
|
|
3139
|
+
)
|
|
3140
|
+
|
|
3141
|
+
elif governance_type == "req":
|
|
3142
|
+
auto_dispatch = True
|
|
3143
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3144
|
+
dispatch_result = run_json_script(
|
|
3145
|
+
"write_codecgc_requirement.py",
|
|
3146
|
+
"--summary",
|
|
3147
|
+
request,
|
|
3148
|
+
"--note",
|
|
3149
|
+
"将其视为产品面的长期稳定需求更新。",
|
|
3150
|
+
"--source",
|
|
3151
|
+
"cgc-entry 治理分诊",
|
|
3152
|
+
)
|
|
3153
|
+
if dispatch_result.get("success"):
|
|
3154
|
+
requirement = (
|
|
3155
|
+
dispatch_result.get("requirement", {})
|
|
3156
|
+
if isinstance(dispatch_result.get("requirement"), dict)
|
|
3157
|
+
else {}
|
|
3158
|
+
)
|
|
3159
|
+
requirement_path = str(requirement.get("path", "")).strip()
|
|
3160
|
+
human_summary = "已将这条稳定需求更新写入 CodeCGC 的需求资产。"
|
|
3161
|
+
next_text = (
|
|
3162
|
+
f"下一步检查 {requirement_path or 'codecgc/requirements/codecgc-core-requirements.md'},"
|
|
3163
|
+
"确认是否需要补充更明确的能力边界或非目标说明。"
|
|
3164
|
+
)
|
|
3165
|
+
|
|
3166
|
+
elif governance_type == "roadmap":
|
|
3167
|
+
auto_dispatch = True
|
|
3168
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3169
|
+
dispatch_result = run_json_script(
|
|
3170
|
+
"write_codecgc_roadmap.py",
|
|
3171
|
+
"--summary",
|
|
3172
|
+
request,
|
|
3173
|
+
"--goal",
|
|
3174
|
+
request,
|
|
3175
|
+
"--source",
|
|
3176
|
+
"cgc-entry 治理分诊",
|
|
3177
|
+
)
|
|
3178
|
+
if dispatch_result.get("success"):
|
|
3179
|
+
roadmap = dispatch_result.get("roadmap", {}) if isinstance(dispatch_result.get("roadmap"), dict) else {}
|
|
3180
|
+
roadmap_directory = str(roadmap.get("directory", "")).strip()
|
|
3181
|
+
human_summary = "已将这条大规模规划请求初始化为 CodeCGC 的 roadmap 资产。"
|
|
3182
|
+
next_text = (
|
|
3183
|
+
f"下一步检查 {roadmap_directory or 'codecgc/roadmap/'},"
|
|
3184
|
+
"确认 phases、delivery plan 和后续 child workflow 拆分是否足够清晰。"
|
|
3185
|
+
)
|
|
3186
|
+
|
|
3187
|
+
elif governance_type == "refactor":
|
|
3188
|
+
auto_dispatch = True
|
|
3189
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3190
|
+
dispatch_result = run_json_script(
|
|
3191
|
+
"write_codecgc_refactor.py",
|
|
3192
|
+
"--summary",
|
|
3193
|
+
request,
|
|
3194
|
+
"--note",
|
|
3195
|
+
"将其视为保持行为不变、但仍需走受控执行流程的结构优化候选项。",
|
|
3196
|
+
"--source",
|
|
3197
|
+
"cgc-entry 治理分诊",
|
|
3198
|
+
)
|
|
3199
|
+
if dispatch_result.get("success"):
|
|
3200
|
+
refactor = dispatch_result.get("refactor", {}) if isinstance(dispatch_result.get("refactor"), dict) else {}
|
|
3201
|
+
refactor_path = str(refactor.get("path", "")).strip()
|
|
3202
|
+
human_summary = "已将这条受控重构请求写入 CodeCGC 的重构队列资产。"
|
|
3203
|
+
next_text = (
|
|
3204
|
+
f"下一步检查 {refactor_path or 'codecgc/compound/codecgc-productization-gap.md'},"
|
|
3205
|
+
"确认这条重构是否需要转成更具体的可执行 workflow。"
|
|
3206
|
+
)
|
|
3207
|
+
|
|
3208
|
+
elif governance_type == "guide":
|
|
3209
|
+
auto_dispatch = True
|
|
3210
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3211
|
+
dispatch_result = run_json_script(
|
|
3212
|
+
"write_codecgc_guide.py",
|
|
3213
|
+
"--summary",
|
|
3214
|
+
request,
|
|
3215
|
+
"--audience",
|
|
3216
|
+
"developer",
|
|
3217
|
+
"--note",
|
|
3218
|
+
"将其视为需要长期维护的任务导向指南入口。",
|
|
3219
|
+
"--source",
|
|
3220
|
+
"cgc-entry 治理分诊",
|
|
3221
|
+
)
|
|
3222
|
+
if dispatch_result.get("success"):
|
|
3223
|
+
guide = dispatch_result.get("guide", {}) if isinstance(dispatch_result.get("guide"), dict) else {}
|
|
3224
|
+
guide_path = str(guide.get("path", "")).strip()
|
|
3225
|
+
human_summary = "已将这条指南请求写入 CodeCGC 的 docs guide 资产。"
|
|
3226
|
+
next_text = (
|
|
3227
|
+
f"下一步检查 {guide_path or 'codecgc/docs/'},"
|
|
3228
|
+
"补全目的、步骤和使用边界。"
|
|
3229
|
+
)
|
|
3230
|
+
|
|
3231
|
+
elif governance_type == "libdoc":
|
|
3232
|
+
auto_dispatch = True
|
|
3233
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3234
|
+
dispatch_result = run_json_script(
|
|
3235
|
+
"write_codecgc_libdoc.py",
|
|
3236
|
+
"--summary",
|
|
3237
|
+
request,
|
|
3238
|
+
"--surface",
|
|
3239
|
+
"public-api",
|
|
3240
|
+
"--source",
|
|
3241
|
+
"cgc-entry 治理分诊",
|
|
3242
|
+
)
|
|
3243
|
+
if dispatch_result.get("success"):
|
|
3244
|
+
libdoc = dispatch_result.get("libdoc", {}) if isinstance(dispatch_result.get("libdoc"), dict) else {}
|
|
3245
|
+
libdoc_path = str(libdoc.get("path", "")).strip()
|
|
3246
|
+
human_summary = "已将这条参考文档请求写入 CodeCGC 的 libdoc 资产。"
|
|
3247
|
+
next_text = (
|
|
3248
|
+
f"下一步检查 {libdoc_path or 'codecgc/reference/'},"
|
|
3249
|
+
"补全公开契约、示例与边界说明。"
|
|
3250
|
+
)
|
|
3251
|
+
|
|
3252
|
+
elif governance_type == "trick":
|
|
3253
|
+
auto_dispatch = True
|
|
3254
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3255
|
+
dispatch_result = run_json_script(
|
|
3256
|
+
"write_codecgc_trick.py",
|
|
3257
|
+
"--summary",
|
|
3258
|
+
request,
|
|
3259
|
+
"--kind",
|
|
3260
|
+
"technique",
|
|
3261
|
+
"--instruction",
|
|
3262
|
+
"将其视为可复用默认做法,后续可继续补充示例与适用边界。",
|
|
3263
|
+
"--source",
|
|
3264
|
+
"cgc-entry 治理分诊",
|
|
3265
|
+
)
|
|
3266
|
+
if dispatch_result.get("success"):
|
|
3267
|
+
trick = dispatch_result.get("trick", {}) if isinstance(dispatch_result.get("trick"), dict) else {}
|
|
3268
|
+
trick_path = str(trick.get("path", "")).strip()
|
|
3269
|
+
human_summary = "已将这条技巧请求写入 CodeCGC 的 trick 资产。"
|
|
3270
|
+
next_text = (
|
|
3271
|
+
f"下一步检查 {trick_path or 'codecgc/compound/codecgc-tricks.md'},"
|
|
3272
|
+
"确认是否需要补更多默认做法与适用范围。"
|
|
3273
|
+
)
|
|
3274
|
+
|
|
3275
|
+
elif governance_type == "explore":
|
|
3276
|
+
auto_dispatch = True
|
|
3277
|
+
auto_dispatch_reason = "governance-auto-write"
|
|
3278
|
+
dispatch_result = run_json_script(
|
|
3279
|
+
"write_codecgc_explore.py",
|
|
3280
|
+
"--summary",
|
|
3281
|
+
request,
|
|
3282
|
+
"--kind",
|
|
3283
|
+
"question",
|
|
3284
|
+
"--source",
|
|
3285
|
+
"cgc-entry 治理分诊",
|
|
3286
|
+
)
|
|
3287
|
+
if dispatch_result.get("success"):
|
|
3288
|
+
explore = dispatch_result.get("explore", {}) if isinstance(dispatch_result.get("explore"), dict) else {}
|
|
3289
|
+
explore_path = str(explore.get("path", "")).strip()
|
|
3290
|
+
human_summary = "已将这条探索请求写入 CodeCGC 的 exploration 资产。"
|
|
3291
|
+
next_text = (
|
|
3292
|
+
f"下一步检查 {explore_path or 'codecgc/compound/codecgc-explorations.md'},"
|
|
3293
|
+
"再决定是否要继续做真实代码探索与证据回填。"
|
|
3294
|
+
)
|
|
3295
|
+
|
|
3296
|
+
result = {
|
|
3297
|
+
"success": True,
|
|
3298
|
+
"entry_mode": "governance",
|
|
3299
|
+
"flow": flow,
|
|
3300
|
+
"slug": slug,
|
|
3301
|
+
"request": request,
|
|
3302
|
+
"governance_type": governance_type,
|
|
3303
|
+
"recommended_skill": skill,
|
|
3304
|
+
"recommended_command": "",
|
|
3305
|
+
"next": next_text,
|
|
3306
|
+
"human_summary": human_summary,
|
|
3307
|
+
"assistant_reply": "",
|
|
3308
|
+
"dispatched": bool(dispatch_result),
|
|
3309
|
+
"dispatch_result": dispatch_result,
|
|
3310
|
+
"auto_dispatch": auto_dispatch,
|
|
3311
|
+
"auto_dispatch_reason": auto_dispatch_reason,
|
|
3312
|
+
"route_status": {
|
|
3313
|
+
"workflow_state": "governance-routing",
|
|
3314
|
+
"recommended_command": "",
|
|
3315
|
+
"current_step_number": 0,
|
|
3316
|
+
"current_task_id": "",
|
|
3317
|
+
"current_kind": "",
|
|
3318
|
+
"current_target_paths": [],
|
|
3319
|
+
"review_decision": "",
|
|
3320
|
+
"review_step_number": 0,
|
|
3321
|
+
"audit_path": "",
|
|
3322
|
+
"human_status_summary": human_summary,
|
|
3323
|
+
"operator_action_summary": next_text,
|
|
3324
|
+
"is_closed": False,
|
|
3325
|
+
"needs_review_decision": False,
|
|
3326
|
+
"needs_execution": False,
|
|
3327
|
+
},
|
|
3328
|
+
}
|
|
3329
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
3330
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
3331
|
+
return attach_entry_summary(result)
|
|
3332
|
+
|
|
3333
|
+
|
|
3334
|
+
def main() -> int:
|
|
3335
|
+
configure_utf8_stdio()
|
|
3336
|
+
parser = build_parser()
|
|
3337
|
+
args = parser.parse_args()
|
|
3338
|
+
args = apply_entry_payload(args)
|
|
3339
|
+
|
|
3340
|
+
try:
|
|
3341
|
+
followup_payload = load_payload_input(args)
|
|
3342
|
+
nested_payload = followup_payload.get("payload", {}) if isinstance(followup_payload.get("payload"), dict) else {}
|
|
3343
|
+
governance_followup = resolve_governance_followup(nested_payload, args.request)
|
|
3344
|
+
if governance_followup:
|
|
3345
|
+
dispatch_result = run_json_script(
|
|
3346
|
+
str(governance_followup.get("script", "")).strip(),
|
|
3347
|
+
*[str(item) for item in governance_followup.get("args", []) if str(item).strip()],
|
|
3348
|
+
)
|
|
3349
|
+
artifact_path = str(governance_followup.get("artifact_path", "")).strip() or str(nested_payload.get("artifact_path", "")).strip()
|
|
3350
|
+
governance_type = str(governance_followup.get("governance_type", "")).strip()
|
|
3351
|
+
result = {
|
|
3352
|
+
"success": bool(dispatch_result.get("success")),
|
|
3353
|
+
"entry_mode": "governance",
|
|
3354
|
+
"flow": "",
|
|
3355
|
+
"slug": "",
|
|
3356
|
+
"request": args.request.strip(),
|
|
3357
|
+
"governance_type": governance_type,
|
|
3358
|
+
"recommended_skill": f"cgc-{governance_type}",
|
|
3359
|
+
"recommended_command": "",
|
|
3360
|
+
"next": f"已把补充内容回写到 {artifact_path or '对应长期资产'},如还有缺口可继续补充。",
|
|
3361
|
+
"human_summary": "已将这轮补充内容回写到同一个治理资产中。",
|
|
3362
|
+
"assistant_reply": "",
|
|
3363
|
+
"dispatched": True,
|
|
3364
|
+
"dispatch_result": dispatch_result,
|
|
3365
|
+
"auto_dispatch": True,
|
|
3366
|
+
"auto_dispatch_reason": "governance-followup-write",
|
|
3367
|
+
"route_status": {
|
|
3368
|
+
"workflow_state": "governance-routing",
|
|
3369
|
+
"recommended_command": "",
|
|
3370
|
+
"current_step_number": 0,
|
|
3371
|
+
"current_task_id": "",
|
|
3372
|
+
"current_kind": "",
|
|
3373
|
+
"current_target_paths": [],
|
|
3374
|
+
"review_decision": "",
|
|
3375
|
+
"review_step_number": 0,
|
|
3376
|
+
"audit_path": "",
|
|
3377
|
+
"human_status_summary": "已将这轮补充内容回写到同一个治理资产中。",
|
|
3378
|
+
"operator_action_summary": f"继续检查 {artifact_path or '对应长期资产'},必要时补充下一轮内容。",
|
|
3379
|
+
"is_closed": False,
|
|
3380
|
+
"needs_review_decision": False,
|
|
3381
|
+
"needs_execution": False,
|
|
3382
|
+
},
|
|
3383
|
+
}
|
|
3384
|
+
result["assistant_reply"] = build_assistant_reply(result)
|
|
3385
|
+
result["operator_brief"] = build_operator_brief(result)
|
|
3386
|
+
result = attach_entry_summary(result)
|
|
3387
|
+
result["inferred_mode"] = "governance-followup"
|
|
3388
|
+
print_json(result)
|
|
3389
|
+
return 0 if result.get("success") else 1
|
|
3390
|
+
|
|
3391
|
+
governance = classify_governance_request(args.request)
|
|
3392
|
+
if governance:
|
|
3393
|
+
result = run_governance_mode(args, governance)
|
|
3394
|
+
result["inferred_mode"] = "governance"
|
|
3395
|
+
print_json(result)
|
|
3396
|
+
return 0
|
|
3397
|
+
inferred_mode = infer_mode(args)
|
|
3398
|
+
if inferred_mode == "new":
|
|
3399
|
+
result = run_new_mode(args)
|
|
3400
|
+
elif inferred_mode == "continue":
|
|
3401
|
+
result = run_existing_mode(args, explain_only=False)
|
|
3402
|
+
else:
|
|
3403
|
+
result = run_existing_mode(args, explain_only=True)
|
|
3404
|
+
result["inferred_mode"] = inferred_mode
|
|
3405
|
+
except Exception as error:
|
|
3406
|
+
print_json({"success": False, "error": str(error)}, file=sys.stderr)
|
|
3407
|
+
return 1
|
|
3408
|
+
|
|
3409
|
+
print_json(result)
|
|
3410
|
+
operator_brief = result.get("operator_brief", {}) if isinstance(result.get("operator_brief"), dict) else {}
|
|
3411
|
+
is_closed = bool(operator_brief.get("is_closed"))
|
|
3412
|
+
workflow_state = str(operator_brief.get("workflow_state", "")).strip()
|
|
3413
|
+
if result.get("success") or workflow_state in {"needs-planning", "awaiting-build", "awaiting-fix", "awaiting-review"} or not is_closed:
|
|
3414
|
+
return 0
|
|
3415
|
+
return 1
|
|
3416
|
+
|
|
3417
|
+
|
|
3418
|
+
if __name__ == "__main__":
|
|
3419
|
+
raise SystemExit(main())
|