@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.
Files changed (128) hide show
  1. package/.claude/hooks/route-edit.ps1 +86 -0
  2. package/INSTALLATION.md +550 -0
  3. package/LICENSE +21 -0
  4. package/README.md +171 -0
  5. package/bin/cgc-build.js +4 -0
  6. package/bin/cgc-doctor.js +4 -0
  7. package/bin/cgc-entry.js +4 -0
  8. package/bin/cgc-external-audit.js +4 -0
  9. package/bin/cgc-fix.js +4 -0
  10. package/bin/cgc-history.js +4 -0
  11. package/bin/cgc-install.js +4 -0
  12. package/bin/cgc-lifecycle.js +4 -0
  13. package/bin/cgc-package-audit.js +4 -0
  14. package/bin/cgc-plan.js +4 -0
  15. package/bin/cgc-release-readiness.js +4 -0
  16. package/bin/cgc-review.js +4 -0
  17. package/bin/cgc-route.js +4 -0
  18. package/bin/cgc-status.js +4 -0
  19. package/bin/cgc-test.js +4 -0
  20. package/bin/cgc.js +4 -0
  21. package/bin/codecgc.js +1284 -0
  22. package/codecgc/cgc/SKILL.md +46 -0
  23. package/codecgc/cgc-arch/SKILL.md +61 -0
  24. package/codecgc/cgc-build/SKILL.md +53 -0
  25. package/codecgc/cgc-decide/SKILL.md +55 -0
  26. package/codecgc/cgc-fix/SKILL.md +47 -0
  27. package/codecgc/cgc-learn/SKILL.md +46 -0
  28. package/codecgc/cgc-onboard/SKILL.md +52 -0
  29. package/codecgc/cgc-plan/SKILL.md +48 -0
  30. package/codecgc/cgc-refactor/SKILL.md +46 -0
  31. package/codecgc/cgc-req/SKILL.md +61 -0
  32. package/codecgc/cgc-review/SKILL.md +57 -0
  33. package/codecgc/cgc-roadmap/SKILL.md +55 -0
  34. package/codecgc/cgc-test/SKILL.md +21 -0
  35. package/codecgc/reference/api-cgc-review-libdoc.md +13 -0
  36. package/codecgc/reference/artifact-class-policy.md +81 -0
  37. package/codecgc/reference/build-flow.md +95 -0
  38. package/codecgc/reference/checklist-contract.md +103 -0
  39. package/codecgc/reference/execution-audit.md +121 -0
  40. package/codecgc/reference/execution-model.md +118 -0
  41. package/codecgc/reference/execution-routing.md +130 -0
  42. package/codecgc/reference/executor-contract.md +87 -0
  43. package/codecgc/reference/external-capability-registry.json +104 -0
  44. package/codecgc/reference/fix-flow.md +94 -0
  45. package/codecgc/reference/fixture-governance.md +60 -0
  46. package/codecgc/reference/flow-execution.md +65 -0
  47. package/codecgc/reference/lifecycle-map.md +172 -0
  48. package/codecgc/reference/lifecycle-playbook.md +104 -0
  49. package/codecgc/reference/long-lived-artifacts.md +98 -0
  50. package/codecgc/reference/operation-guide.md +242 -0
  51. package/codecgc/reference/release-maintenance-playbook.md +150 -0
  52. package/codecgc/reference/review-writeback.md +141 -0
  53. package/codecgc/reference/role-model.md +128 -0
  54. package/codecgc/reference/runtime-boundary.md +72 -0
  55. package/codecgc/reference/shared-conventions.md +93 -0
  56. package/codecgc/reference/workflow-scaffold.md +57 -0
  57. package/codexmcp/LICENSE +21 -0
  58. package/codexmcp/README.md +294 -0
  59. package/codexmcp/pyproject.toml +37 -0
  60. package/codexmcp/src/codexmcp/__init__.py +4 -0
  61. package/codexmcp/src/codexmcp/cli.py +12 -0
  62. package/codexmcp/src/codexmcp/server.py +529 -0
  63. package/geminimcp/README.md +258 -0
  64. package/geminimcp/pyproject.toml +15 -0
  65. package/geminimcp/src/geminimcp/__init__.py +4 -0
  66. package/geminimcp/src/geminimcp/cli.py +12 -0
  67. package/geminimcp/src/geminimcp/server.py +465 -0
  68. package/model-routing.yaml +30 -0
  69. package/package.json +90 -0
  70. package/requirements.txt +1 -0
  71. package/scripts/README-codecgc-cli.md +89 -0
  72. package/scripts/audit_codecgc_external_capabilities.py +276 -0
  73. package/scripts/audit_codecgc_historical_audits.py +242 -0
  74. package/scripts/audit_codecgc_lifecycle.py +241 -0
  75. package/scripts/audit_codecgc_package_runtime.py +445 -0
  76. package/scripts/audit_codecgc_release_readiness.py +202 -0
  77. package/scripts/audit_codecgc_review_policy.py +82 -0
  78. package/scripts/audit_codecgc_workflow_history.py +317 -0
  79. package/scripts/build_codecgc_task.py +487 -0
  80. package/scripts/codecgc_artifact_roots.py +40 -0
  81. package/scripts/codecgc_cli.py +843 -0
  82. package/scripts/codecgc_command_surface.py +28 -0
  83. package/scripts/codecgc_console_io.py +45 -0
  84. package/scripts/codecgc_executor_registry.py +54 -0
  85. package/scripts/codecgc_file_evidence.py +349 -0
  86. package/scripts/codecgc_flow_control.py +233 -0
  87. package/scripts/codecgc_governance_dedupe.py +161 -0
  88. package/scripts/codecgc_plan_decision.py +103 -0
  89. package/scripts/codecgc_review_control.py +588 -0
  90. package/scripts/codecgc_roadmap_templates.py +149 -0
  91. package/scripts/codecgc_routing_paths.py +16 -0
  92. package/scripts/codecgc_routing_template.py +135 -0
  93. package/scripts/codecgc_runtime_paths.py +22 -0
  94. package/scripts/codecgc_session_recovery.py +44 -0
  95. package/scripts/codecgc_step_control.py +154 -0
  96. package/scripts/codecgc_workflow_runtime.py +63 -0
  97. package/scripts/codecgc_workflow_templates.py +437 -0
  98. package/scripts/entry_codecgc_workflow.py +3419 -0
  99. package/scripts/exercise_mcp_tools.py +109 -0
  100. package/scripts/expand_codecgc_roadmap.py +664 -0
  101. package/scripts/init_codecgc_roadmap.py +134 -0
  102. package/scripts/init_codecgc_workflow.py +207 -0
  103. package/scripts/install_codecgc.py +938 -0
  104. package/scripts/migrate_demo_workflows_to_fixtures.py +128 -0
  105. package/scripts/normalize_codecgc_audits.py +114 -0
  106. package/scripts/normalize_codecgc_governance_docs.py +79 -0
  107. package/scripts/normalize_codecgc_workflow_docs.py +269 -0
  108. package/scripts/plan_codecgc_workflow.py +970 -0
  109. package/scripts/refresh_codecgc_review_policy.py +223 -0
  110. package/scripts/review_codecgc_workflow.py +88 -0
  111. package/scripts/route_codecgc_workflow.py +671 -0
  112. package/scripts/run_codecgc_build.py +104 -0
  113. package/scripts/run_codecgc_fix.py +104 -0
  114. package/scripts/run_codecgc_flow_step.py +165 -0
  115. package/scripts/run_codecgc_task.py +410 -0
  116. package/scripts/run_codecgc_test.py +105 -0
  117. package/scripts/sync_codecgc_mcp_config.py +41 -0
  118. package/scripts/write_codecgc_architecture.py +78 -0
  119. package/scripts/write_codecgc_decision.py +83 -0
  120. package/scripts/write_codecgc_explore.py +118 -0
  121. package/scripts/write_codecgc_guide.py +141 -0
  122. package/scripts/write_codecgc_learning.py +87 -0
  123. package/scripts/write_codecgc_libdoc.py +140 -0
  124. package/scripts/write_codecgc_refactor.py +78 -0
  125. package/scripts/write_codecgc_requirement.py +78 -0
  126. package/scripts/write_codecgc_review.py +291 -0
  127. package/scripts/write_codecgc_roadmap.py +122 -0
  128. package/scripts/write_codecgc_trick.py +123 -0
@@ -0,0 +1,664 @@
1
+ import argparse
2
+ import json
3
+ import re
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from codecgc_artifact_roots import flow_root
8
+ from codecgc_console_io import configure_utf8_stdio
9
+ from codecgc_console_io import print_json
10
+ from codecgc_workflow_runtime import run_json_script
11
+
12
+ TRACK_HINTS = {
13
+ "frontend": [
14
+ "frontend",
15
+ "ui",
16
+ "browser",
17
+ "client",
18
+ "component",
19
+ "page",
20
+ "render",
21
+ "interaction",
22
+ "state",
23
+ "layout",
24
+ ],
25
+ "backend": [
26
+ "backend",
27
+ "api",
28
+ "server",
29
+ "service",
30
+ "endpoint",
31
+ "database",
32
+ "persistence",
33
+ "job",
34
+ "queue",
35
+ "worker",
36
+ ],
37
+ }
38
+
39
+ TRACK_PROJECTIONS = {
40
+ "frontend": [
41
+ (r"\bfrontend and backend\b", "frontend"),
42
+ (r"\bbackend and frontend\b", "frontend"),
43
+ (r"\bui and api\b", "UI"),
44
+ (r"\bapi and ui\b", "UI"),
45
+ (r"\bbrowser and api\b", "browser"),
46
+ (r"\bapi and browser\b", "browser"),
47
+ ],
48
+ "backend": [
49
+ (r"\bfrontend and backend\b", "backend"),
50
+ (r"\bbackend and frontend\b", "backend"),
51
+ (r"\bui and api\b", "API"),
52
+ (r"\bapi and ui\b", "API"),
53
+ (r"\bbrowser and api\b", "API"),
54
+ (r"\bapi and browser\b", "API"),
55
+ ],
56
+ }
57
+
58
+ WORKSPACE = Path(__file__).resolve().parents[1]
59
+ ROADMAP_ROOT = WORKSPACE / "codecgc" / "roadmap"
60
+
61
+
62
+ def slugify(value: str) -> str:
63
+ normalized = value.strip().lower()
64
+ normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
65
+ normalized = re.sub(r"-{2,}", "-", normalized).strip("-")
66
+ if not normalized:
67
+ raise ValueError("Child workflow slug cannot be empty after normalization.")
68
+ return normalized
69
+
70
+
71
+ def strip_date_prefix(slug: str) -> str:
72
+ if re.match(r"^\d{4}-\d{2}-\d{2}-", slug):
73
+ return slug[11:]
74
+ return slug
75
+
76
+
77
+ def build_parser() -> argparse.ArgumentParser:
78
+ parser = argparse.ArgumentParser(description="Expand a roadmap initiative into child CodeCGC workflows.")
79
+ parser.add_argument("--initiative", required=True)
80
+ parser.add_argument("--summary", required=True)
81
+ parser.add_argument("--artifact-class", choices=["product", "fixture"], default="product")
82
+ parser.add_argument("--source-flow", choices=["feature", "issue"], default="feature")
83
+ parser.add_argument("--frontend-flow", choices=["auto", "feature", "issue"], default="auto")
84
+ parser.add_argument("--backend-flow", choices=["auto", "feature", "issue"], default="auto")
85
+ parser.add_argument("--frontend-path", action="append", default=[])
86
+ parser.add_argument("--backend-path", action="append", default=[])
87
+ parser.add_argument("--goal", default="")
88
+ parser.add_argument("--user-story", default="")
89
+ parser.add_argument("--symptom", default="")
90
+ parser.add_argument("--expected", default="")
91
+ parser.add_argument("--actual", default="")
92
+ parser.add_argument("--root-cause", default="")
93
+ parser.add_argument("--preferred-fix", default="")
94
+ parser.add_argument("--rejected-fix", default="")
95
+ parser.add_argument("--context", action="append", default=[])
96
+ parser.add_argument("--in-scope", action="append", default=[])
97
+ parser.add_argument("--risk", action="append", default=[])
98
+ parser.add_argument("--dependency", action="append", default=[])
99
+ parser.add_argument("--assumption", action="append", default=[])
100
+ parser.add_argument("--validation", action="append", default=[])
101
+ parser.add_argument("--rollback", action="append", default=[])
102
+ parser.add_argument("--open-question", action="append", default=[])
103
+ parser.add_argument("--acceptance", action="append", default=[])
104
+ parser.add_argument("--force", action="store_true")
105
+ return parser
106
+
107
+
108
+ def create_child_workflow(
109
+ *,
110
+ flow: str,
111
+ slug: str,
112
+ summary: str,
113
+ kind: str,
114
+ artifact_class: str,
115
+ paths: list[str],
116
+ force: bool,
117
+ ) -> dict:
118
+ command_args = [
119
+ "--flow",
120
+ flow,
121
+ "--slug",
122
+ slug,
123
+ "--summary",
124
+ summary,
125
+ "--kind",
126
+ kind,
127
+ "--artifact-class",
128
+ artifact_class,
129
+ ]
130
+ for path in paths:
131
+ command_args.extend(["--target-path", path])
132
+ if force:
133
+ command_args.append("--force")
134
+ return run_json_script("init_codecgc_workflow.py", *command_args)
135
+
136
+
137
+ def looks_like_issue_text(value: str) -> bool:
138
+ lowered = value.lower()
139
+ keywords = ["bug", "fix", "issue", "regression", "error", "broken", "failure", "hotfix"]
140
+ return any(keyword in lowered for keyword in keywords)
141
+
142
+
143
+ def infer_track_flow(
144
+ *,
145
+ requested_flow: str,
146
+ source_flow: str,
147
+ summary: str,
148
+ symptom: str,
149
+ expected: str,
150
+ actual: str,
151
+ root_cause: str,
152
+ preferred_fix: str,
153
+ rejected_fix: str,
154
+ ) -> str:
155
+ if requested_flow in {"feature", "issue"}:
156
+ return requested_flow
157
+ if source_flow == "issue":
158
+ return "issue"
159
+ issue_signals = [
160
+ symptom,
161
+ expected,
162
+ actual,
163
+ root_cause,
164
+ preferred_fix,
165
+ rejected_fix,
166
+ summary,
167
+ ]
168
+ if any(text.strip() and looks_like_issue_text(text) for text in issue_signals):
169
+ return "issue"
170
+ return "feature"
171
+
172
+
173
+ def narrow_items_for_track(kind: str, items: list[str], *, fallback: list[str]) -> list[str]:
174
+ values = [item.strip() for item in items if item.strip()]
175
+ if not values:
176
+ return fallback
177
+
178
+ matched: list[str] = []
179
+ for item in values:
180
+ lowered = item.lower()
181
+ if kind == "frontend":
182
+ if "backend" in lowered and "frontend" not in lowered and "ui" not in lowered:
183
+ continue
184
+ if kind == "backend":
185
+ if "frontend" in lowered and "backend" not in lowered and "api" not in lowered:
186
+ continue
187
+ matched.append(item)
188
+ return matched or fallback
189
+
190
+
191
+ def build_track_context(kind: str, context: list[str]) -> list[str]:
192
+ base = [f"这个子工作流是从 roadmap initiative 中拆出的{'前端' if kind == 'frontend' else '后端'} track。"]
193
+ return narrow_items_for_track(kind, context, fallback=base)
194
+
195
+
196
+ def build_track_scope(kind: str, items: list[str], paths: list[str]) -> list[str]:
197
+ if kind == "frontend":
198
+ fallback = [
199
+ "只交付该 roadmap initiative 中前端可见的部分。",
200
+ f"范围限制在已批准的前端 track 路径内:{', '.join(paths)}。",
201
+ ]
202
+ else:
203
+ fallback = [
204
+ "只交付该 roadmap initiative 中后端或 API 的部分。",
205
+ f"范围限制在已批准的后端 track 路径内:{', '.join(paths)}。",
206
+ ]
207
+ return narrow_items_for_track(kind, items, fallback=fallback)
208
+
209
+
210
+ def build_track_validation(kind: str, items: list[str]) -> list[str]:
211
+ if kind == "frontend":
212
+ fallback = [
213
+ "只验证前端 track 的浏览器可见行为。",
214
+ "确认审核证据保持在前端 track 范围内。",
215
+ ]
216
+ else:
217
+ fallback = [
218
+ "只验证后端 track 的 API 或服务行为。",
219
+ "确认审核证据保持在后端 track 范围内。",
220
+ ]
221
+ return narrow_items_for_track(kind, items, fallback=fallback)
222
+
223
+
224
+ def build_track_acceptance(kind: str, items: list[str]) -> list[str]:
225
+ if kind == "frontend":
226
+ fallback = ["前端 track 输出完整、可审核、且范围明确。"]
227
+ else:
228
+ fallback = ["后端 track 输出完整、可审核、且范围明确。"]
229
+ return narrow_items_for_track(kind, items, fallback=fallback)
230
+
231
+
232
+ def build_track_questions(kind: str, items: list[str]) -> list[str]:
233
+ if kind == "frontend":
234
+ fallback = ["前端侧开放问题是否还需要继续拆分?"]
235
+ else:
236
+ fallback = ["后端侧开放问题是否还需要继续拆分?"]
237
+ return narrow_items_for_track(kind, items, fallback=fallback)
238
+
239
+
240
+ def narrow_text_for_track(kind: str, value: str, fallback: str) -> str:
241
+ text = value.strip()
242
+ if not text:
243
+ return fallback
244
+ lowered = text.lower()
245
+ if kind == "frontend" and "backend" in lowered and "frontend" not in lowered and "ui" not in lowered:
246
+ return fallback
247
+ if kind == "backend" and "frontend" in lowered and "backend" not in lowered and "api" not in lowered:
248
+ return fallback
249
+ return text
250
+
251
+
252
+ def track_score(kind: str, value: str) -> int:
253
+ lowered = value.lower()
254
+ return sum(1 for keyword in TRACK_HINTS[kind] if keyword in lowered)
255
+
256
+
257
+ def classify_track_text(value: str) -> str:
258
+ frontend_score = track_score("frontend", value)
259
+ backend_score = track_score("backend", value)
260
+ if frontend_score and not backend_score:
261
+ return "frontend"
262
+ if backend_score and not frontend_score:
263
+ return "backend"
264
+ if frontend_score and backend_score:
265
+ return "shared"
266
+ return "neutral"
267
+
268
+
269
+ def split_sentences(value: str) -> list[str]:
270
+ return [fragment.strip() for fragment in re.split(r"(?<=[.!?])\s+|\s*;\s*", value) if fragment.strip()]
271
+
272
+
273
+ def split_clauses(value: str) -> list[str]:
274
+ return [fragment.strip() for fragment in re.split(r"(?i)\s+(?:and|but|while)\s+|,\s*", value) if fragment.strip()]
275
+
276
+
277
+ def normalize_fragment(value: str) -> str:
278
+ return re.sub(r"\s+", " ", value).strip(" ,;")
279
+
280
+
281
+ def format_fragments(fragments: list[str]) -> str:
282
+ seen: set[str] = set()
283
+ ordered: list[str] = []
284
+ for fragment in fragments:
285
+ normalized = normalize_fragment(fragment)
286
+ if not normalized:
287
+ continue
288
+ key = normalized.lower()
289
+ if key in seen:
290
+ continue
291
+ seen.add(key)
292
+ ordered.append(normalized)
293
+ if not ordered:
294
+ return ""
295
+ joined = "; ".join(ordered)
296
+ if joined[-1] not in ".!?":
297
+ joined += "."
298
+ return joined
299
+
300
+
301
+ def project_shared_issue_text(kind: str, value: str) -> str:
302
+ projected = value.strip()
303
+ for pattern, replacement in TRACK_PROJECTIONS[kind]:
304
+ projected = re.sub(pattern, replacement, projected, flags=re.IGNORECASE)
305
+
306
+ selected: list[str] = []
307
+ for clause in split_clauses(projected):
308
+ classification = classify_track_text(clause)
309
+ if classification == kind:
310
+ selected.append(clause)
311
+ elif classification == "neutral":
312
+ opposite = "backend" if kind == "frontend" else "frontend"
313
+ if track_score(opposite, clause) == 0:
314
+ selected.append(clause)
315
+
316
+ return format_fragments(selected) or format_fragments([projected])
317
+
318
+
319
+ def narrow_issue_text_for_track(kind: str, value: str, fallback: str) -> str:
320
+ text = value.strip()
321
+ if not text:
322
+ return fallback
323
+
324
+ classification = classify_track_text(text)
325
+ if classification in {kind, "neutral"}:
326
+ return text
327
+ if classification == ("backend" if kind == "frontend" else "frontend"):
328
+ return fallback
329
+
330
+ selected: list[str] = []
331
+ for sentence in split_sentences(text):
332
+ sentence_class = classify_track_text(sentence)
333
+ if sentence_class == kind:
334
+ selected.append(sentence)
335
+ continue
336
+ if sentence_class != "shared":
337
+ continue
338
+ for clause in split_clauses(sentence):
339
+ if classify_track_text(clause) == kind:
340
+ selected.append(clause)
341
+
342
+ narrowed = format_fragments(selected)
343
+ if narrowed:
344
+ return narrowed
345
+
346
+ projected = project_shared_issue_text(kind, text)
347
+ projected_class = classify_track_text(projected)
348
+ if projected and projected_class != ("backend" if kind == "frontend" else "frontend"):
349
+ return projected
350
+
351
+ return fallback
352
+
353
+
354
+ def bullet_lines(items: list[str], *, fallback: str) -> list[str]:
355
+ values = [item.strip() for item in items if item.strip()]
356
+ if not values:
357
+ return [f"- {fallback}"]
358
+ return [f"- {item}" for item in values]
359
+
360
+
361
+ def replace_section(content: str, heading: str, lines: list[str]) -> str:
362
+ pattern = re.compile(
363
+ rf"(^## {re.escape(heading)}\n\n)(.*?)(?=^## |\Z)",
364
+ flags=re.MULTILINE | re.DOTALL,
365
+ )
366
+ replacement = "\\1" + "\n".join(lines).rstrip() + "\n\n"
367
+ if pattern.search(content):
368
+ return pattern.sub(replacement, content, count=1)
369
+ trimmed = content.rstrip() + "\n\n"
370
+ return trimmed + f"## {heading}\n\n" + "\n".join(lines).rstrip() + "\n"
371
+
372
+
373
+ def summarize_child_paths(paths: list[str]) -> str:
374
+ if not paths:
375
+ return "无"
376
+ if len(paths) == 1:
377
+ return paths[0]
378
+ return f"{paths[0]} 等 {len(paths) - 1} 个路径"
379
+
380
+
381
+ def resolve_child_directory(flow: str, slug: str, artifact_class: str) -> Path:
382
+ return flow_root(flow, artifact_class) / slug
383
+
384
+
385
+ def roadmap_tracking_lines(children: dict[str, dict]) -> tuple[list[str], list[str]]:
386
+ phase_lines: list[str] = []
387
+ delivery_lines: list[str] = []
388
+
389
+ for track in ("frontend", "backend"):
390
+ child = children.get(track)
391
+ if not isinstance(child, dict) or not child.get("success"):
392
+ continue
393
+ flow = str(child.get("flow", "feature"))
394
+ slug = str(child.get("slug", "")).strip()
395
+ artifact_class = str(child.get("artifact_class", "product")).strip() or "product"
396
+ directory = resolve_child_directory(flow, slug, artifact_class)
397
+ recommended_command = str(child.get("recommended_command", "")).strip() or "cgc-plan"
398
+ next_step = str(child.get("next", "")).strip() or "继续推进该子工作流。"
399
+ notes = [str(item).strip() for item in child.get("notes", []) if str(item).strip()]
400
+ target_paths = child.get("target_paths")
401
+ path_summary = summarize_child_paths(target_paths) if isinstance(target_paths, list) else "见子工作流产物"
402
+
403
+ phase_lines.extend(
404
+ [
405
+ f"- {track.title()} 子工作流: `{slug}`",
406
+ f" 流程: `{flow}`",
407
+ f" 目录: `{directory.relative_to(WORKSPACE).as_posix()}`",
408
+ f" 范围提示: {path_summary}",
409
+ ]
410
+ )
411
+ delivery_lines.extend(
412
+ [
413
+ f"- {track.title()} 子工作流: `{slug}`",
414
+ f" 下一条命令: `{recommended_command}`",
415
+ f" 下一步动作: {next_step}",
416
+ f" 目录: `{directory.relative_to(WORKSPACE).as_posix()}`",
417
+ ]
418
+ )
419
+ for note in notes[:2]:
420
+ delivery_lines.append(f" 备注: {note}")
421
+
422
+ return (
423
+ phase_lines or ["- 暂无。"],
424
+ delivery_lines or ["- 目前还没有登记子工作流。"],
425
+ )
426
+
427
+
428
+ def write_roadmap_tracking(*, initiative: str, children: dict[str, dict]) -> None:
429
+ roadmap_dir = ROADMAP_ROOT / initiative
430
+ phases_path = roadmap_dir / "phases.md"
431
+ delivery_plan_path = roadmap_dir / "delivery-plan.md"
432
+
433
+ if not phases_path.exists() or not delivery_plan_path.exists():
434
+ return
435
+
436
+ phase_lines, delivery_lines = roadmap_tracking_lines(children)
437
+ phases_content = phases_path.read_text(encoding="utf-8")
438
+ delivery_content = delivery_plan_path.read_text(encoding="utf-8")
439
+
440
+ phases_path.write_text(
441
+ replace_section(phases_content, "5. 已初始化的子工作流", phase_lines),
442
+ encoding="utf-8",
443
+ )
444
+ delivery_plan_path.write_text(
445
+ replace_section(delivery_content, "6. 工作流跟踪", delivery_lines),
446
+ encoding="utf-8",
447
+ )
448
+
449
+
450
+ def build_track_dependencies(kind: str, items: list[str]) -> list[str]:
451
+ if kind == "frontend":
452
+ fallback = ["前端 track 依赖已具备,或必须在执行前明确确认。"]
453
+ else:
454
+ fallback = ["后端 track 依赖已具备,或必须在执行前明确确认。"]
455
+ return narrow_items_for_track(kind, items, fallback=fallback)
456
+
457
+
458
+ def build_track_risks(kind: str, items: list[str]) -> list[str]:
459
+ if kind == "frontend":
460
+ fallback = ["在扩大 UI 范围前,仍需先审查前端 track 风险。"]
461
+ else:
462
+ fallback = ["在扩大 API 或服务范围前,仍需先审查后端 track 风险。"]
463
+ return narrow_items_for_track(kind, items, fallback=fallback)
464
+
465
+
466
+ def enrich_child_workflow(
467
+ *,
468
+ flow: str,
469
+ slug: str,
470
+ summary: str,
471
+ kind: str,
472
+ artifact_class: str,
473
+ paths: list[str],
474
+ goal: str,
475
+ user_story: str,
476
+ symptom: str,
477
+ expected: str,
478
+ actual: str,
479
+ root_cause: str,
480
+ preferred_fix: str,
481
+ rejected_fix: str,
482
+ context: list[str],
483
+ in_scope: list[str],
484
+ risk: list[str],
485
+ dependency: list[str],
486
+ assumption: list[str],
487
+ validation: list[str],
488
+ rollback: list[str],
489
+ open_question: list[str],
490
+ acceptance: list[str],
491
+ force: bool,
492
+ ) -> dict:
493
+ command_args = [
494
+ "--flow",
495
+ flow,
496
+ "--slug",
497
+ slug,
498
+ "--summary",
499
+ summary,
500
+ "--kind",
501
+ kind,
502
+ "--artifact-class",
503
+ artifact_class,
504
+ "--goal",
505
+ goal or "TODO",
506
+ "--user-story",
507
+ user_story or "TODO",
508
+ ]
509
+ if symptom:
510
+ command_args.extend(["--symptom", symptom])
511
+ if expected:
512
+ command_args.extend(["--expected", expected])
513
+ if actual:
514
+ command_args.extend(["--actual", actual])
515
+ if root_cause:
516
+ command_args.extend(["--root-cause", root_cause])
517
+ if preferred_fix:
518
+ command_args.extend(["--preferred-fix", preferred_fix])
519
+ if rejected_fix:
520
+ command_args.extend(["--rejected-fix", rejected_fix])
521
+ for path in paths:
522
+ command_args.extend(["--target-path", path])
523
+ for item in context:
524
+ command_args.extend(["--context", item])
525
+ for item in in_scope:
526
+ command_args.extend(["--in-scope", item])
527
+ for item in risk:
528
+ command_args.extend(["--risk", item])
529
+ for item in dependency:
530
+ command_args.extend(["--dependency", item])
531
+ for item in assumption:
532
+ command_args.extend(["--assumption", item])
533
+ for item in validation:
534
+ command_args.extend(["--validation", item])
535
+ for item in rollback:
536
+ command_args.extend(["--rollback", item])
537
+ for item in open_question:
538
+ command_args.extend(["--open-question", item])
539
+ for item in acceptance:
540
+ command_args.extend(["--acceptance", item])
541
+ if force:
542
+ command_args.append("--force")
543
+ return run_json_script("plan_codecgc_workflow.py", *command_args)
544
+
545
+
546
+ def main() -> int:
547
+ configure_utf8_stdio()
548
+ parser = build_parser()
549
+ args = parser.parse_args()
550
+
551
+ try:
552
+ initiative_slug = slugify(strip_date_prefix(args.initiative))
553
+ children: dict[str, dict] = {}
554
+ frontend_flow = infer_track_flow(
555
+ requested_flow=args.frontend_flow,
556
+ source_flow=args.source_flow,
557
+ summary=args.summary,
558
+ symptom=args.symptom,
559
+ expected=args.expected,
560
+ actual=args.actual,
561
+ root_cause=args.root_cause,
562
+ preferred_fix=args.preferred_fix,
563
+ rejected_fix=args.rejected_fix,
564
+ )
565
+ backend_flow = infer_track_flow(
566
+ requested_flow=args.backend_flow,
567
+ source_flow=args.source_flow,
568
+ summary=args.summary,
569
+ symptom=args.symptom,
570
+ expected=args.expected,
571
+ actual=args.actual,
572
+ root_cause=args.root_cause,
573
+ preferred_fix=args.preferred_fix,
574
+ rejected_fix=args.rejected_fix,
575
+ )
576
+
577
+ if args.frontend_path:
578
+ create_child_workflow(
579
+ flow=frontend_flow,
580
+ slug=f"{initiative_slug}-frontend-track",
581
+ summary=f"{args.summary} Frontend Track",
582
+ kind="frontend",
583
+ artifact_class=args.artifact_class,
584
+ paths=args.frontend_path,
585
+ force=args.force,
586
+ )
587
+ children["frontend"] = enrich_child_workflow(
588
+ flow=frontend_flow,
589
+ slug=f"{initiative_slug}-frontend-track",
590
+ summary=f"{args.summary} Frontend Track",
591
+ kind="frontend",
592
+ artifact_class=args.artifact_class,
593
+ paths=args.frontend_path,
594
+ goal=args.goal,
595
+ user_story=args.user_story,
596
+ symptom=narrow_issue_text_for_track("frontend", args.symptom, "Frontend track symptom still needs clarification."),
597
+ expected=narrow_issue_text_for_track("frontend", args.expected, "Frontend expected behavior still needs clarification."),
598
+ actual=narrow_issue_text_for_track("frontend", args.actual, "Frontend actual behavior still needs clarification."),
599
+ root_cause=narrow_issue_text_for_track("frontend", args.root_cause, "Frontend root cause still needs clarification."),
600
+ preferred_fix=narrow_issue_text_for_track("frontend", args.preferred_fix, "Frontend preferred fix still needs clarification."),
601
+ rejected_fix=narrow_issue_text_for_track("frontend", args.rejected_fix, "Frontend rejected fix still needs clarification."),
602
+ context=build_track_context("frontend", args.context),
603
+ in_scope=build_track_scope("frontend", args.in_scope, args.frontend_path),
604
+ risk=build_track_risks("frontend", args.risk),
605
+ dependency=build_track_dependencies("frontend", args.dependency),
606
+ assumption=args.assumption,
607
+ validation=build_track_validation("frontend", args.validation),
608
+ rollback=args.rollback,
609
+ open_question=build_track_questions("frontend", args.open_question),
610
+ acceptance=build_track_acceptance("frontend", args.acceptance),
611
+ force=True,
612
+ )
613
+ children["frontend"]["artifact_class"] = args.artifact_class
614
+ children["frontend"]["target_paths"] = list(args.frontend_path)
615
+
616
+ if args.backend_path:
617
+ create_child_workflow(
618
+ flow=backend_flow,
619
+ slug=f"{initiative_slug}-backend-track",
620
+ summary=f"{args.summary} Backend Track",
621
+ kind="backend",
622
+ artifact_class=args.artifact_class,
623
+ paths=args.backend_path,
624
+ force=args.force,
625
+ )
626
+ children["backend"] = enrich_child_workflow(
627
+ flow=backend_flow,
628
+ slug=f"{initiative_slug}-backend-track",
629
+ summary=f"{args.summary} Backend Track",
630
+ kind="backend",
631
+ artifact_class=args.artifact_class,
632
+ paths=args.backend_path,
633
+ goal=args.goal,
634
+ user_story=args.user_story,
635
+ symptom=narrow_issue_text_for_track("backend", args.symptom, "Backend track symptom still needs clarification."),
636
+ expected=narrow_issue_text_for_track("backend", args.expected, "Backend expected behavior still needs clarification."),
637
+ actual=narrow_issue_text_for_track("backend", args.actual, "Backend actual behavior still needs clarification."),
638
+ root_cause=narrow_issue_text_for_track("backend", args.root_cause, "Backend root cause still needs clarification."),
639
+ preferred_fix=narrow_issue_text_for_track("backend", args.preferred_fix, "Backend preferred fix still needs clarification."),
640
+ rejected_fix=narrow_issue_text_for_track("backend", args.rejected_fix, "Backend rejected fix still needs clarification."),
641
+ context=build_track_context("backend", args.context),
642
+ in_scope=build_track_scope("backend", args.in_scope, args.backend_path),
643
+ risk=build_track_risks("backend", args.risk),
644
+ dependency=build_track_dependencies("backend", args.dependency),
645
+ assumption=args.assumption,
646
+ validation=build_track_validation("backend", args.validation),
647
+ rollback=args.rollback,
648
+ open_question=build_track_questions("backend", args.open_question),
649
+ acceptance=build_track_acceptance("backend", args.acceptance),
650
+ force=True,
651
+ )
652
+ children["backend"]["artifact_class"] = args.artifact_class
653
+ children["backend"]["target_paths"] = list(args.backend_path)
654
+ write_roadmap_tracking(initiative=args.initiative, children=children)
655
+ except Exception as error:
656
+ print_json({"success": False, "error": str(error)}, file=sys.stderr)
657
+ return 1
658
+
659
+ print_json({"success": True, "children": children})
660
+ return 0
661
+
662
+
663
+ if __name__ == "__main__":
664
+ raise SystemExit(main())