@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,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def render_bullet_list(items: list[str], indent: str, fallback: str) -> str:
|
|
5
|
+
values = [item.strip() for item in items if item.strip()]
|
|
6
|
+
if not values:
|
|
7
|
+
return f"{indent}- {fallback}"
|
|
8
|
+
return "\n".join(f"{indent}- {item}" for item in values)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_overview(
|
|
12
|
+
*,
|
|
13
|
+
initiative: str,
|
|
14
|
+
summary: str,
|
|
15
|
+
user_story: str,
|
|
16
|
+
goal: str,
|
|
17
|
+
context: list[str],
|
|
18
|
+
scope: list[str],
|
|
19
|
+
risks: list[str],
|
|
20
|
+
reasons: list[str],
|
|
21
|
+
artifact_class: str,
|
|
22
|
+
) -> str:
|
|
23
|
+
return f"""---
|
|
24
|
+
doc_type: roadmap-overview
|
|
25
|
+
artifact_class: {artifact_class}
|
|
26
|
+
initiative: {initiative}
|
|
27
|
+
status: draft
|
|
28
|
+
summary: {summary}
|
|
29
|
+
tags: []
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# {summary}
|
|
33
|
+
|
|
34
|
+
## 1. 目标
|
|
35
|
+
|
|
36
|
+
- 摘要: {summary}
|
|
37
|
+
- 目标: {goal or '待补充'}
|
|
38
|
+
- 用户故事或操作者诉求: {user_story or '待补充'}
|
|
39
|
+
|
|
40
|
+
## 2. 背景
|
|
41
|
+
|
|
42
|
+
{render_bullet_list(context, '', '待补充')}
|
|
43
|
+
|
|
44
|
+
## 3. 为什么需要走 Roadmap
|
|
45
|
+
|
|
46
|
+
{render_bullet_list(reasons, '', '待补充')}
|
|
47
|
+
|
|
48
|
+
## 4. 范围
|
|
49
|
+
|
|
50
|
+
{render_bullet_list(scope, '', '待补充')}
|
|
51
|
+
|
|
52
|
+
## 5. 风险
|
|
53
|
+
|
|
54
|
+
{render_bullet_list(risks, '', '待补充')}
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def render_phases(
|
|
59
|
+
*,
|
|
60
|
+
initiative: str,
|
|
61
|
+
grouped_paths: dict[str, list[str]],
|
|
62
|
+
artifact_class: str,
|
|
63
|
+
) -> str:
|
|
64
|
+
frontend_paths = grouped_paths.get("frontend", [])
|
|
65
|
+
backend_paths = grouped_paths.get("backend", [])
|
|
66
|
+
shared_paths = grouped_paths.get("shared", [])
|
|
67
|
+
unknown_paths = grouped_paths.get("unknown", [])
|
|
68
|
+
return f"""---
|
|
69
|
+
doc_type: roadmap-phases
|
|
70
|
+
artifact_class: {artifact_class}
|
|
71
|
+
initiative: {initiative}
|
|
72
|
+
status: draft
|
|
73
|
+
tags: []
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
# {initiative} 阶段拆分
|
|
77
|
+
|
|
78
|
+
## 1. 阶段说明
|
|
79
|
+
|
|
80
|
+
- 第 1 阶段:澄清范围、依赖和成功标准
|
|
81
|
+
- 第 2 阶段:把 initiative 拆成可执行的 feature 或 issue track
|
|
82
|
+
- 第 3 阶段:按正常 CodeCGC 流程交付前端和后端 track
|
|
83
|
+
|
|
84
|
+
## 2. 候选前端 Track
|
|
85
|
+
|
|
86
|
+
{render_bullet_list(frontend_paths, '', '暂未识别。')}
|
|
87
|
+
|
|
88
|
+
## 3. 候选后端 Track
|
|
89
|
+
|
|
90
|
+
{render_bullet_list(backend_paths, '', '暂未识别。')}
|
|
91
|
+
|
|
92
|
+
## 4. 共享或未知范围
|
|
93
|
+
|
|
94
|
+
共享范围:
|
|
95
|
+
{render_bullet_list(shared_paths, ' ', '无。')}
|
|
96
|
+
|
|
97
|
+
未知范围:
|
|
98
|
+
{render_bullet_list(unknown_paths, ' ', '无。')}
|
|
99
|
+
|
|
100
|
+
## 5. 已初始化的子工作流
|
|
101
|
+
|
|
102
|
+
- 暂无。
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render_delivery_plan(
|
|
107
|
+
*,
|
|
108
|
+
initiative: str,
|
|
109
|
+
dependencies: list[str],
|
|
110
|
+
assumptions: list[str],
|
|
111
|
+
validation: list[str],
|
|
112
|
+
rollback: list[str],
|
|
113
|
+
open_questions: list[str],
|
|
114
|
+
artifact_class: str,
|
|
115
|
+
) -> str:
|
|
116
|
+
return f"""---
|
|
117
|
+
doc_type: roadmap-delivery-plan
|
|
118
|
+
artifact_class: {artifact_class}
|
|
119
|
+
initiative: {initiative}
|
|
120
|
+
status: draft
|
|
121
|
+
tags: []
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
# {initiative} 交付计划
|
|
125
|
+
|
|
126
|
+
## 1. 依赖
|
|
127
|
+
|
|
128
|
+
{render_bullet_list(dependencies, '', '待补充')}
|
|
129
|
+
|
|
130
|
+
## 2. 前提假设
|
|
131
|
+
|
|
132
|
+
{render_bullet_list(assumptions, '', '待补充')}
|
|
133
|
+
|
|
134
|
+
## 3. 验证策略
|
|
135
|
+
|
|
136
|
+
{render_bullet_list(validation, '', '待补充')}
|
|
137
|
+
|
|
138
|
+
## 4. 回退策略
|
|
139
|
+
|
|
140
|
+
{render_bullet_list(rollback, '', '待补充')}
|
|
141
|
+
|
|
142
|
+
## 5. 开放问题
|
|
143
|
+
|
|
144
|
+
{render_bullet_list(open_questions, '', '当前无。')}
|
|
145
|
+
|
|
146
|
+
## 6. 工作流跟踪
|
|
147
|
+
|
|
148
|
+
- 目前还没有登记子工作流。
|
|
149
|
+
"""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from codecgc_runtime_paths import PACKAGE_ROOT
|
|
6
|
+
from codecgc_runtime_paths import PROJECT_ROOT
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
PACKAGE_ROUTING_FILE = PACKAGE_ROOT / "model-routing.yaml"
|
|
10
|
+
PROJECT_ROUTING_FILE = PROJECT_ROOT / "model-routing.yaml"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def resolve_active_routing_file() -> Path:
|
|
14
|
+
if PROJECT_ROUTING_FILE.exists():
|
|
15
|
+
return PROJECT_ROUTING_FILE
|
|
16
|
+
return PACKAGE_ROUTING_FILE
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DEFAULT_FRONTEND_PATHS = [
|
|
7
|
+
"apps/web/**",
|
|
8
|
+
"src/components/**",
|
|
9
|
+
"src/pages/**",
|
|
10
|
+
"src/app/**",
|
|
11
|
+
"src/styles/**",
|
|
12
|
+
"web/**",
|
|
13
|
+
"frontend/**",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
DEFAULT_BACKEND_PATHS = [
|
|
17
|
+
"apps/api/**",
|
|
18
|
+
"server/**",
|
|
19
|
+
"src/server/**",
|
|
20
|
+
"src/services/**",
|
|
21
|
+
"src/repositories/**",
|
|
22
|
+
"backend/**",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
DEFAULT_SHARED_PATHS = [
|
|
26
|
+
"packages/shared/**",
|
|
27
|
+
"src/shared/**",
|
|
28
|
+
"src/lib/**",
|
|
29
|
+
"src/types/**",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
DEFAULT_RULES = {
|
|
33
|
+
"frontend_executor": "geminimcp",
|
|
34
|
+
"backend_executor": "codexmcp",
|
|
35
|
+
"shared_policy": "split-first",
|
|
36
|
+
"claude_role": "plan-review-accept-only",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _render_list_block(name: str, items: list[str]) -> list[str]:
|
|
41
|
+
lines = [f"{name}:"]
|
|
42
|
+
for item in items:
|
|
43
|
+
lines.append(f' - "{item}"')
|
|
44
|
+
return lines
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _render_rules_block() -> list[str]:
|
|
48
|
+
lines = ["rules:"]
|
|
49
|
+
for key, value in DEFAULT_RULES.items():
|
|
50
|
+
lines.append(f' {key}: "{value}"')
|
|
51
|
+
return lines
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def render_default_routing_yaml() -> str:
|
|
55
|
+
lines: list[str] = [
|
|
56
|
+
"version: 1",
|
|
57
|
+
"",
|
|
58
|
+
*_render_list_block("frontend_paths", DEFAULT_FRONTEND_PATHS),
|
|
59
|
+
"",
|
|
60
|
+
*_render_list_block("custom_frontend_paths", []),
|
|
61
|
+
"",
|
|
62
|
+
*_render_list_block("backend_paths", DEFAULT_BACKEND_PATHS),
|
|
63
|
+
"",
|
|
64
|
+
*_render_list_block("custom_backend_paths", []),
|
|
65
|
+
"",
|
|
66
|
+
*_render_list_block("shared_paths", DEFAULT_SHARED_PATHS),
|
|
67
|
+
"",
|
|
68
|
+
*_render_rules_block(),
|
|
69
|
+
"",
|
|
70
|
+
]
|
|
71
|
+
return "\n".join(lines)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _normalize_line_endings(text: str) -> str:
|
|
75
|
+
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _extract_list_block(lines: list[str], block_name: str) -> list[str]:
|
|
79
|
+
items: list[str] = []
|
|
80
|
+
inside = False
|
|
81
|
+
for line in lines:
|
|
82
|
+
stripped = line.strip()
|
|
83
|
+
if not inside:
|
|
84
|
+
if stripped == f"{block_name}:":
|
|
85
|
+
inside = True
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if line and not line.startswith(" "):
|
|
89
|
+
break
|
|
90
|
+
if stripped.startswith("- "):
|
|
91
|
+
value = stripped[2:].strip()
|
|
92
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
|
|
93
|
+
value = value[1:-1]
|
|
94
|
+
if value:
|
|
95
|
+
items.append(value)
|
|
96
|
+
return items
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def merge_routing_template(existing_text: str) -> str:
|
|
100
|
+
if not existing_text.strip():
|
|
101
|
+
return render_default_routing_yaml()
|
|
102
|
+
|
|
103
|
+
lines = _normalize_line_endings(existing_text).split("\n")
|
|
104
|
+
custom_frontend = _extract_list_block(lines, "custom_frontend_paths")
|
|
105
|
+
custom_backend = _extract_list_block(lines, "custom_backend_paths")
|
|
106
|
+
|
|
107
|
+
merged = render_default_routing_yaml().split("\n")
|
|
108
|
+
output: list[str] = []
|
|
109
|
+
current_block = ""
|
|
110
|
+
|
|
111
|
+
for line in merged:
|
|
112
|
+
stripped = line.strip()
|
|
113
|
+
output.append(line)
|
|
114
|
+
if stripped == "custom_frontend_paths:":
|
|
115
|
+
current_block = "custom_frontend_paths"
|
|
116
|
+
for item in custom_frontend:
|
|
117
|
+
output.append(f' - "{item}"')
|
|
118
|
+
continue
|
|
119
|
+
if stripped == "custom_backend_paths:":
|
|
120
|
+
current_block = "custom_backend_paths"
|
|
121
|
+
for item in custom_backend:
|
|
122
|
+
output.append(f' - "{item}"')
|
|
123
|
+
continue
|
|
124
|
+
if stripped.endswith(":") and stripped not in {"custom_frontend_paths:", "custom_backend_paths:"}:
|
|
125
|
+
current_block = stripped[:-1]
|
|
126
|
+
|
|
127
|
+
return "\n".join(output).rstrip() + "\n"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def sync_workspace_routing_file(target_path: Path) -> Path:
|
|
131
|
+
existing_text = target_path.read_text(encoding="utf-8") if target_path.exists() else ""
|
|
132
|
+
merged_text = merge_routing_template(existing_text)
|
|
133
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
134
|
+
target_path.write_text(merged_text, encoding="utf-8")
|
|
135
|
+
return target_path
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resolve_workspace_root(override_workspace: str = "") -> Path:
|
|
11
|
+
explicit = str(override_workspace or "").strip()
|
|
12
|
+
if explicit:
|
|
13
|
+
return Path(explicit).expanduser().resolve()
|
|
14
|
+
|
|
15
|
+
configured = os.environ.get("CODECGC_WORKSPACE_ROOT", "").strip()
|
|
16
|
+
if configured:
|
|
17
|
+
return Path(configured).expanduser().resolve()
|
|
18
|
+
|
|
19
|
+
return Path.cwd().resolve()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
PROJECT_ROOT = resolve_workspace_root()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from codecgc_artifact_roots import execution_root
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_audit_json(path: Path) -> dict[str, Any] | None:
|
|
11
|
+
if not path.exists():
|
|
12
|
+
return None
|
|
13
|
+
try:
|
|
14
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
15
|
+
except Exception:
|
|
16
|
+
return None
|
|
17
|
+
return data if isinstance(data, dict) else None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def extract_reusable_session_id(audit: dict[str, Any] | None) -> str:
|
|
21
|
+
if not isinstance(audit, dict):
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
result = audit.get("result", {})
|
|
25
|
+
if isinstance(result, dict):
|
|
26
|
+
session_id = str(result.get("session_id", "")).strip()
|
|
27
|
+
if session_id:
|
|
28
|
+
return session_id
|
|
29
|
+
|
|
30
|
+
return str(audit.get("requested_session_id", "")).strip()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def resolve_task_audit_path(task_id: str, artifact_class: str) -> Path | None:
|
|
34
|
+
cleaned_task_id = str(task_id).strip()
|
|
35
|
+
if not cleaned_task_id:
|
|
36
|
+
return None
|
|
37
|
+
return execution_root(artifact_class) / f"{cleaned_task_id}.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_session_id_from_task(task_id: str, artifact_class: str) -> str:
|
|
41
|
+
audit_path = resolve_task_audit_path(task_id, artifact_class)
|
|
42
|
+
if audit_path is None:
|
|
43
|
+
return ""
|
|
44
|
+
return extract_reusable_session_id(load_audit_json(audit_path))
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from build_codecgc_task import load_checklist_yaml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_artifact_type(data: dict[str, Any]) -> str:
|
|
10
|
+
if data.get("feature"):
|
|
11
|
+
return "feature"
|
|
12
|
+
if data.get("issue"):
|
|
13
|
+
return "issue"
|
|
14
|
+
return "checklist"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_placeholder_path(path_text: str) -> bool:
|
|
18
|
+
normalized = str(path_text or "").strip().replace("\\", "/").lower()
|
|
19
|
+
return not normalized or normalized == "todo/path" or normalized.startswith("todo/")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_executable_codecgc_block(codecgc: Any) -> bool:
|
|
23
|
+
if not isinstance(codecgc, dict):
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
kind = str(codecgc.get("kind", "")).strip().lower()
|
|
27
|
+
if kind not in {"frontend", "backend"}:
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
target_paths = codecgc.get("target_paths", [])
|
|
31
|
+
if not isinstance(target_paths, list) or not target_paths:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
if any(is_placeholder_path(path) for path in target_paths):
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
task_id = str(codecgc.get("task_id", "")).strip()
|
|
38
|
+
task_summary = str(codecgc.get("task_summary", "")).strip()
|
|
39
|
+
return bool(task_id and task_summary)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_test_codecgc_block(codecgc: Any) -> bool:
|
|
43
|
+
if not is_executable_codecgc_block(codecgc):
|
|
44
|
+
return False
|
|
45
|
+
return str(codecgc.get("step_type", "")).strip().lower() == "test"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def select_next_executable_step(checklist_path: Path) -> dict[str, Any]:
|
|
49
|
+
data = load_checklist_yaml(checklist_path)
|
|
50
|
+
steps = data.get("steps", [])
|
|
51
|
+
if not isinstance(steps, list) or not steps:
|
|
52
|
+
raise ValueError("Checklist does not contain any steps.")
|
|
53
|
+
|
|
54
|
+
first_planning_index: int | None = None
|
|
55
|
+
first_executable_index: int | None = None
|
|
56
|
+
|
|
57
|
+
for index, step in enumerate(steps, start=1):
|
|
58
|
+
if not isinstance(step, dict):
|
|
59
|
+
continue
|
|
60
|
+
status = str(step.get("status", "pending")).strip().lower()
|
|
61
|
+
if status not in {"pending", ""}:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
codecgc = step.get("codecgc")
|
|
65
|
+
if is_executable_codecgc_block(codecgc):
|
|
66
|
+
if first_executable_index is None:
|
|
67
|
+
first_executable_index = index
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if first_planning_index is None:
|
|
71
|
+
first_planning_index = index
|
|
72
|
+
|
|
73
|
+
if first_planning_index is not None:
|
|
74
|
+
step = steps[first_planning_index - 1]
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Planning-only step {first_planning_index} must be resolved before execution: "
|
|
77
|
+
f"{step.get('action', 'unknown action')}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if first_executable_index is None:
|
|
81
|
+
raise ValueError("No pending executable step remains in this checklist.")
|
|
82
|
+
|
|
83
|
+
step = steps[first_executable_index - 1]
|
|
84
|
+
codecgc = step.get("codecgc", {})
|
|
85
|
+
return {
|
|
86
|
+
"step_number": first_executable_index,
|
|
87
|
+
"task_id": str(codecgc.get("task_id", "")),
|
|
88
|
+
"action": str(step.get("action", "")),
|
|
89
|
+
"kind": str(codecgc.get("kind", "")),
|
|
90
|
+
"target_paths": codecgc.get("target_paths", []),
|
|
91
|
+
"task_summary": str(codecgc.get("task_summary", "")),
|
|
92
|
+
"artifact_type": resolve_artifact_type(data),
|
|
93
|
+
"timeout_seconds": int(codecgc.get("timeout_seconds", 0)) or 0,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_step_metadata(checklist_path: Path, step_number: int) -> dict[str, Any]:
|
|
98
|
+
data = load_checklist_yaml(checklist_path)
|
|
99
|
+
steps = data.get("steps", [])
|
|
100
|
+
if not isinstance(steps, list) or step_number < 1 or step_number > len(steps):
|
|
101
|
+
raise ValueError(f"Step number must be between 1 and {len(steps)}.")
|
|
102
|
+
step = steps[step_number - 1]
|
|
103
|
+
codecgc = step.get("codecgc", {}) if isinstance(step, dict) else {}
|
|
104
|
+
executable = is_executable_codecgc_block(codecgc)
|
|
105
|
+
return {
|
|
106
|
+
"step_number": step_number,
|
|
107
|
+
"task_id": str(codecgc.get("task_id", "")),
|
|
108
|
+
"action": str(step.get("action", "")) if isinstance(step, dict) else "",
|
|
109
|
+
"kind": str(codecgc.get("kind", "")),
|
|
110
|
+
"target_paths": codecgc.get("target_paths", []),
|
|
111
|
+
"task_summary": str(codecgc.get("task_summary", "")),
|
|
112
|
+
"artifact_type": resolve_artifact_type(data),
|
|
113
|
+
"executable": executable,
|
|
114
|
+
"timeout_seconds": int(codecgc.get("timeout_seconds", 0)) or 0,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def replace_step_status(text: str, step_number: int, new_status: str) -> str:
|
|
119
|
+
lines = text.splitlines()
|
|
120
|
+
current_step = 0
|
|
121
|
+
inside_steps = False
|
|
122
|
+
current_step_indent = ""
|
|
123
|
+
|
|
124
|
+
for index, line in enumerate(lines):
|
|
125
|
+
stripped = line.strip()
|
|
126
|
+
if stripped == "steps:":
|
|
127
|
+
inside_steps = True
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
if inside_steps and not line.startswith(" "):
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
if inside_steps and line.startswith(" - "):
|
|
134
|
+
current_step += 1
|
|
135
|
+
current_step_indent = line[: len(line) - len(line.lstrip(" "))]
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
inside_steps
|
|
140
|
+
and current_step == step_number
|
|
141
|
+
and stripped.startswith("status:")
|
|
142
|
+
and line.startswith(f"{current_step_indent} ")
|
|
143
|
+
):
|
|
144
|
+
indent = line[: len(line) - len(line.lstrip(" "))]
|
|
145
|
+
lines[index] = f"{indent}status: {new_status}"
|
|
146
|
+
return "\n".join(lines) + ("\n" if text.endswith("\n") else "")
|
|
147
|
+
|
|
148
|
+
raise ValueError(f"Could not find status field for step {step_number}.")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def update_step_status(checklist_path: Path, step_number: int, new_status: str) -> None:
|
|
152
|
+
original = checklist_path.read_text(encoding="utf-8")
|
|
153
|
+
updated = replace_step_status(original, step_number, new_status)
|
|
154
|
+
checklist_path.write_text(updated, encoding="utf-8")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from codecgc_runtime_paths import PACKAGE_ROOT
|
|
8
|
+
from codecgc_runtime_paths import PROJECT_ROOT
|
|
9
|
+
|
|
10
|
+
WORKSPACE = PACKAGE_ROOT
|
|
11
|
+
PROJECT_WORKSPACE = PROJECT_ROOT
|
|
12
|
+
SCRIPTS_DIR = WORKSPACE / "scripts"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_script_command(script_name: str, *args: str) -> list[str]:
|
|
16
|
+
return [sys.executable, str(SCRIPTS_DIR / script_name), *args]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_json_text(text: str) -> dict[str, Any]:
|
|
20
|
+
stripped = text.strip()
|
|
21
|
+
if not stripped:
|
|
22
|
+
raise ValueError("Command produced no JSON output.")
|
|
23
|
+
return json.loads(stripped)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_json_script(script_name: str, *args: str) -> dict[str, Any]:
|
|
27
|
+
command = build_script_command(script_name, *args)
|
|
28
|
+
completed = subprocess.run(
|
|
29
|
+
command,
|
|
30
|
+
cwd=PROJECT_WORKSPACE,
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
encoding="utf-8",
|
|
34
|
+
errors="replace",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
stdout = completed.stdout.strip()
|
|
38
|
+
stderr = completed.stderr.strip()
|
|
39
|
+
|
|
40
|
+
if completed.returncode == 0:
|
|
41
|
+
if not stdout:
|
|
42
|
+
return {"success": True}
|
|
43
|
+
return parse_json_text(stdout)
|
|
44
|
+
|
|
45
|
+
for candidate in (stdout, stderr):
|
|
46
|
+
if not candidate:
|
|
47
|
+
continue
|
|
48
|
+
try:
|
|
49
|
+
parsed = parse_json_text(candidate)
|
|
50
|
+
except Exception:
|
|
51
|
+
continue
|
|
52
|
+
if isinstance(parsed, dict):
|
|
53
|
+
parsed.setdefault("success", False)
|
|
54
|
+
parsed.setdefault("returncode", completed.returncode)
|
|
55
|
+
return parsed
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"success": False,
|
|
59
|
+
"returncode": completed.returncode,
|
|
60
|
+
"stdout": stdout,
|
|
61
|
+
"stderr": stderr,
|
|
62
|
+
"error": f"{script_name} failed with exit code {completed.returncode}.",
|
|
63
|
+
}
|