@hunyed15/codecgc 0.1.6 → 0.1.8
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 +58 -57
- package/INSTALLATION.md +117 -484
- package/README.md +118 -150
- package/bin/cgc-external-status.js +4 -0
- package/bin/cgc-start.js +4 -0
- package/bin/codecgc.js +141 -20
- package/codecgc/compound/codecgc-capability-matrix.md +37 -0
- package/codecgc/reference/README.md +69 -0
- package/codecgc/reference/maintainer-guide.md +93 -0
- package/codecgc/reference/mcp-tool-surface.md +112 -0
- package/codecgc/reference/onboarding.md +69 -0
- package/codecgc/reference/operation-guide.md +29 -23
- package/codecgc/reference/path-contract.md +53 -0
- package/codecgc/reference/policy-routing.md +57 -0
- package/codecgc/reference/project-structure.md +80 -0
- package/codecgc/reference/quickstart.md +108 -0
- package/codecgc/reference/real-workflow-loop.md +123 -0
- package/codecgc/reference/recovery-loop.md +109 -0
- package/codecgc/reference/release-maintenance-playbook.md +4 -1
- package/codecgc/reference/troubleshooting.md +112 -0
- package/codecgc/roadmap/codecgc-release-maintenance/delivery-plan.md +49 -0
- package/codecgc/roadmap/codecgc-release-maintenance/overview.md +41 -0
- package/codecgc/roadmap/codecgc-release-maintenance/phases.md +84 -0
- package/codecgcmcp/README.md +57 -11
- package/codecgcmcp/src/codecgcmcp/server.py +164 -26
- package/codexmcp/src/codexmcp/server.py +32 -26
- package/geminimcp/src/geminimcp/server.py +22 -14
- package/model-routing.yaml +31 -6
- package/package.json +11 -4
- package/scripts/audit_codecgc_external_capabilities.py +83 -4
- package/scripts/audit_codecgc_historical_audits.py +42 -2
- package/scripts/audit_codecgc_package_runtime.py +73 -4
- package/scripts/audit_codecgc_release_readiness.py +55 -3
- package/scripts/audit_codecgc_workflow_history.py +8 -5
- package/scripts/build_codecgc_task.py +62 -45
- package/scripts/codecgc_artifact_roots.py +8 -40
- package/scripts/codecgc_console_io.py +3 -45
- package/scripts/codecgc_executor_registry.py +4 -54
- package/scripts/codecgc_path_contract.py +7 -0
- package/scripts/codecgc_policy.py +275 -0
- package/scripts/codecgc_routing_paths.py +3 -16
- package/scripts/codecgc_routing_template.py +11 -135
- package/scripts/codecgc_runtime/__init__.py +1 -0
- package/scripts/codecgc_runtime/artifacts.py +42 -0
- package/scripts/codecgc_runtime/console.py +45 -0
- package/scripts/codecgc_runtime/executor_registry.py +55 -0
- package/scripts/codecgc_runtime/mcp_config.py +72 -0
- package/scripts/codecgc_runtime/path_contract.py +123 -0
- package/scripts/codecgc_runtime/paths.py +22 -0
- package/scripts/codecgc_runtime/routing_paths.py +16 -0
- package/scripts/codecgc_runtime/routing_template.py +169 -0
- package/scripts/codecgc_runtime/workflow_runtime.py +72 -0
- package/scripts/codecgc_runtime_paths.py +3 -22
- package/scripts/codecgc_step_control.py +3 -2
- package/scripts/codecgc_workflow_runtime.py +3 -71
- package/scripts/entry_codecgc_workflow.py +4 -0
- package/scripts/install_codecgc.py +490 -21
- package/scripts/normalize_codecgc_audits.py +5 -3
- package/scripts/postinstall_codecgc.js +6 -56
- package/scripts/review_codecgc_workflow.py +6 -3
- package/scripts/route_codecgc_workflow.py +67 -36
- package/scripts/run_codecgc_build.py +28 -0
- package/scripts/run_codecgc_fix.py +28 -0
- package/scripts/run_codecgc_task.py +5 -2
- package/scripts/run_codecgc_test.py +28 -0
- package/scripts/sync_codecgc_mcp_config.py +4 -54
- package/scripts/write_codecgc_review.py +7 -3
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunyed15/codecgc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Claude-hosted multi-model workflow product shell for CodeCGC.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|
|
8
8
|
"cgc": "bin/cgc.js",
|
|
9
|
+
"cgc-start": "bin/cgc-start.js",
|
|
9
10
|
"cgc-install": "bin/cgc-install.js",
|
|
10
11
|
"cgc-status": "bin/cgc-status.js",
|
|
11
12
|
"cgc-doctor": "bin/cgc-doctor.js",
|
|
12
13
|
"cgc-package-audit": "bin/cgc-package-audit.js",
|
|
13
14
|
"cgc-external-audit": "bin/cgc-external-audit.js",
|
|
15
|
+
"cgc-external-status": "bin/cgc-external-status.js",
|
|
14
16
|
"cgc-release-readiness": "bin/cgc-release-readiness.js",
|
|
15
17
|
"cgc-lifecycle": "bin/cgc-lifecycle.js",
|
|
16
18
|
"cgc-history": "bin/cgc-history.js",
|
|
@@ -25,10 +27,12 @@
|
|
|
25
27
|
"scripts": {
|
|
26
28
|
"postinstall": "node scripts/postinstall_codecgc.js",
|
|
27
29
|
"cgc:help": "node bin/cgc.js --help",
|
|
30
|
+
"cgc:start": "node bin/cgc-start.js --format summary",
|
|
28
31
|
"cgc:status": "node bin/cgc-status.js --format summary",
|
|
29
32
|
"cgc:doctor": "node bin/cgc-doctor.js --format summary",
|
|
30
33
|
"cgc:package-audit": "node bin/cgc-package-audit.js --format summary",
|
|
31
34
|
"cgc:external-audit": "node bin/cgc-external-audit.js --format summary",
|
|
35
|
+
"cgc:external-status": "node bin/cgc-external-status.js --format summary",
|
|
32
36
|
"cgc:release-readiness": "node bin/cgc-release-readiness.js --format summary",
|
|
33
37
|
"cgc:lifecycle": "node bin/cgc-lifecycle.js --format summary",
|
|
34
38
|
"cgc:history": "node bin/cgc-history.js --status open --last 10 --format summary",
|
|
@@ -40,6 +44,7 @@
|
|
|
40
44
|
"files": [
|
|
41
45
|
"bin/",
|
|
42
46
|
"scripts/*.py",
|
|
47
|
+
"scripts/codecgc_runtime/*.py",
|
|
43
48
|
"scripts/postinstall_codecgc.js",
|
|
44
49
|
"scripts/README-codecgc-cli.md",
|
|
45
50
|
".claude/hooks/route-edit.ps1",
|
|
@@ -56,17 +61,19 @@
|
|
|
56
61
|
"codecgc/cgc-review/",
|
|
57
62
|
"codecgc/cgc-roadmap/",
|
|
58
63
|
"codecgc/cgc-test/",
|
|
64
|
+
"codecgc/compound/",
|
|
59
65
|
"codecgc/reference/",
|
|
66
|
+
"codecgc/roadmap/",
|
|
60
67
|
"codecgcmcp/pyproject.toml",
|
|
61
68
|
"codecgcmcp/README.md",
|
|
62
|
-
"codecgcmcp/src/codecgcmcp
|
|
69
|
+
"codecgcmcp/src/codecgcmcp/*.py",
|
|
63
70
|
"codexmcp/pyproject.toml",
|
|
64
71
|
"codexmcp/README.md",
|
|
65
72
|
"codexmcp/LICENSE",
|
|
66
|
-
"codexmcp/src/codexmcp
|
|
73
|
+
"codexmcp/src/codexmcp/*.py",
|
|
67
74
|
"geminimcp/pyproject.toml",
|
|
68
75
|
"geminimcp/README.md",
|
|
69
|
-
"geminimcp/src/geminimcp
|
|
76
|
+
"geminimcp/src/geminimcp/*.py",
|
|
70
77
|
"requirements.txt",
|
|
71
78
|
"INSTALLATION.md",
|
|
72
79
|
"model-routing.yaml",
|
|
@@ -9,6 +9,16 @@ from codecgc_runtime_paths import resolve_workspace_root
|
|
|
9
9
|
|
|
10
10
|
WORKSPACE = Path(__file__).resolve().parents[1]
|
|
11
11
|
REGISTRY_PATH = WORKSPACE / "codecgc" / "reference" / "external-capability-registry.json"
|
|
12
|
+
STATUS_PANEL_CAPABILITY_ORDER = [
|
|
13
|
+
"memos",
|
|
14
|
+
"github-mcp",
|
|
15
|
+
"linear-mcp",
|
|
16
|
+
"sentry-mcp",
|
|
17
|
+
]
|
|
18
|
+
STATUS_PANEL_SUPPORT_CAPABILITY_ORDER = [
|
|
19
|
+
"augment-search",
|
|
20
|
+
"jira-mcp",
|
|
21
|
+
]
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
def load_registry() -> dict[str, Any]:
|
|
@@ -107,7 +117,7 @@ def normalize_capability_entry(entry: dict[str, Any], workspace_servers: dict[st
|
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
|
|
110
|
-
def audit_external_capabilities(workspace_override: str = "") -> dict[str, Any]:
|
|
120
|
+
def audit_external_capabilities(workspace_override: str = "", view: str = "audit") -> dict[str, Any]:
|
|
111
121
|
registry = load_registry()
|
|
112
122
|
workspace_servers, workspace_root = load_workspace_mcp_servers(workspace_override)
|
|
113
123
|
entries = registry.get("capabilities", [])
|
|
@@ -142,6 +152,8 @@ def audit_external_capabilities(workspace_override: str = "") -> dict[str, Any]:
|
|
|
142
152
|
human_summary = "外部能力登记表存在缺失字段或非法项。"
|
|
143
153
|
elif drift_items:
|
|
144
154
|
human_summary = "外部能力登记表已就绪,但发现本地额外接入漂移。"
|
|
155
|
+
elif view == "status":
|
|
156
|
+
human_summary = "外部能力状态面板已就绪。"
|
|
145
157
|
|
|
146
158
|
recommended_next_action = ""
|
|
147
159
|
if blocking_items:
|
|
@@ -158,12 +170,14 @@ def audit_external_capabilities(workspace_override: str = "") -> dict[str, Any]:
|
|
|
158
170
|
return {
|
|
159
171
|
"success": ready,
|
|
160
172
|
"mode": "external-capability-audit",
|
|
173
|
+
"presentation_view": view,
|
|
161
174
|
"workspace": str(workspace_root),
|
|
162
175
|
"registry_path": str(REGISTRY_PATH),
|
|
163
176
|
"summary": {
|
|
164
177
|
"ready": ready,
|
|
165
178
|
"scope": "外部能力白名单、接入状态声明与本地 MCP 观测状态",
|
|
166
179
|
"human_summary": human_summary,
|
|
180
|
+
"view": view,
|
|
167
181
|
"capability_count": len(normalized),
|
|
168
182
|
"integrated_count": counts["integrated"],
|
|
169
183
|
"planned_count": counts["planned"],
|
|
@@ -178,7 +192,31 @@ def audit_external_capabilities(workspace_override: str = "") -> dict[str, Any]:
|
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
|
|
181
|
-
def
|
|
195
|
+
def _capability_by_id(result: dict[str, Any], capability_id: str) -> dict[str, Any] | None:
|
|
196
|
+
for item in result.get("capabilities", []):
|
|
197
|
+
if not isinstance(item, dict):
|
|
198
|
+
continue
|
|
199
|
+
if str(item.get("id", "")).strip() == capability_id:
|
|
200
|
+
return item
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _format_capability_panel_line(item: dict[str, Any]) -> str:
|
|
205
|
+
status = str(item.get("status", "")).strip()
|
|
206
|
+
status_label = {
|
|
207
|
+
"integrated": "已纳管",
|
|
208
|
+
"planned": "规划中",
|
|
209
|
+
"optional": "可选",
|
|
210
|
+
}.get(status, status or "未知")
|
|
211
|
+
local_label = "已观测" if item.get("local_ready") else "未观测"
|
|
212
|
+
observed = ", ".join(str(value).strip() for value in item.get("observed_servers", []) if str(value).strip()) or "无"
|
|
213
|
+
return (
|
|
214
|
+
f"- {item.get('name', '')} [{item.get('id', '')}]: "
|
|
215
|
+
f"{status_label} | 本地={local_label} | 服务器={observed}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def build_audit_summary(result: dict[str, Any]) -> str:
|
|
182
220
|
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
183
221
|
lines = [
|
|
184
222
|
f"- 工作区: {result.get('workspace', '')}",
|
|
@@ -249,8 +287,46 @@ def build_summary(result: dict[str, Any]) -> str:
|
|
|
249
287
|
return render_summary_block("CodeCGC 外部能力审计", lines, next_actions)
|
|
250
288
|
|
|
251
289
|
|
|
290
|
+
def build_status_panel(result: dict[str, Any]) -> str:
|
|
291
|
+
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
292
|
+
lines = [
|
|
293
|
+
f"- 工作区: {result.get('workspace', '')}",
|
|
294
|
+
f"- 登记表: {result.get('registry_path', '')}",
|
|
295
|
+
f"- 范围: {summary.get('scope', '')}",
|
|
296
|
+
f"- 摘要: {summary.get('human_summary', '')}",
|
|
297
|
+
f"- 就绪: {'是' if summary.get('ready') else '否'}",
|
|
298
|
+
f"- 正式接入: {summary.get('integrated_count', 0)}",
|
|
299
|
+
f"- 规划中: {summary.get('planned_count', 0)}",
|
|
300
|
+
f"- 可选项: {summary.get('optional_count', 0)}",
|
|
301
|
+
f"- 阻塞项: {summary.get('blocking_count', 0)}",
|
|
302
|
+
f"- 漂移项: {summary.get('drift_count', 0)}",
|
|
303
|
+
"- 正式能力面板:",
|
|
304
|
+
]
|
|
305
|
+
for capability_id in STATUS_PANEL_CAPABILITY_ORDER:
|
|
306
|
+
item = _capability_by_id(result, capability_id)
|
|
307
|
+
if isinstance(item, dict):
|
|
308
|
+
lines.append(_format_capability_panel_line(item))
|
|
309
|
+
lines.append("- 其他受管能力:")
|
|
310
|
+
for capability_id in STATUS_PANEL_SUPPORT_CAPABILITY_ORDER:
|
|
311
|
+
item = _capability_by_id(result, capability_id)
|
|
312
|
+
if isinstance(item, dict):
|
|
313
|
+
lines.append(_format_capability_panel_line(item))
|
|
314
|
+
next_actions = []
|
|
315
|
+
next_action = str(summary.get("recommended_next_action", "")).strip()
|
|
316
|
+
if next_action:
|
|
317
|
+
next_actions.append(next_action)
|
|
318
|
+
next_actions.append("需要更细的登记一致性检查时,再跑 cgc-external-audit")
|
|
319
|
+
return render_summary_block("CodeCGC 外部能力状态面板", lines, next_actions)
|
|
320
|
+
|
|
321
|
+
|
|
252
322
|
def main() -> int:
|
|
253
323
|
parser = argparse.ArgumentParser(description="Audit CodeCGC external capability registry and local MCP observations.")
|
|
324
|
+
parser.add_argument(
|
|
325
|
+
"--view",
|
|
326
|
+
choices=["audit", "status"],
|
|
327
|
+
default="audit",
|
|
328
|
+
help="Rendered summary view. Audit is detailed; status is the concise panel view.",
|
|
329
|
+
)
|
|
254
330
|
parser.add_argument(
|
|
255
331
|
"--format",
|
|
256
332
|
choices=["json", "summary"],
|
|
@@ -264,9 +340,12 @@ def main() -> int:
|
|
|
264
340
|
)
|
|
265
341
|
args = parser.parse_args()
|
|
266
342
|
|
|
267
|
-
result = audit_external_capabilities(args.workspace)
|
|
343
|
+
result = audit_external_capabilities(args.workspace, view=args.view)
|
|
268
344
|
if args.format == "summary":
|
|
269
|
-
|
|
345
|
+
if args.view == "status":
|
|
346
|
+
print(build_status_panel(result))
|
|
347
|
+
else:
|
|
348
|
+
print(build_audit_summary(result))
|
|
270
349
|
else:
|
|
271
350
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
272
351
|
return 0 if result.get("success") else 1
|
|
@@ -6,6 +6,8 @@ from typing import Any
|
|
|
6
6
|
from codecgc_artifact_roots import discover_flow_directory
|
|
7
7
|
from codecgc_artifact_roots import execution_root
|
|
8
8
|
from codecgc_artifact_roots import normalize_artifact_class
|
|
9
|
+
from codecgc_path_contract import is_project_relative_path
|
|
10
|
+
from codecgc_path_contract import resolve_project_path
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
WORKSPACE = Path(__file__).resolve().parents[1]
|
|
@@ -33,6 +35,19 @@ def contains_old_repo_name(value: Any) -> bool:
|
|
|
33
35
|
return isinstance(value, str) and OLD_REPO_NAME in value
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def contains_persisted_absolute_project_path(value: Any) -> bool:
|
|
39
|
+
if not isinstance(value, str) or not value.strip():
|
|
40
|
+
return False
|
|
41
|
+
if is_project_relative_path(value):
|
|
42
|
+
return False
|
|
43
|
+
resolved = resolve_project_path(value)
|
|
44
|
+
try:
|
|
45
|
+
resolved.relative_to(WORKSPACE.resolve())
|
|
46
|
+
return True
|
|
47
|
+
except ValueError:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
36
51
|
def expected_artifact_filename(artifact_type: str, artifact_slug: str) -> str:
|
|
37
52
|
base_slug = artifact_slug[11:] if len(artifact_slug) > 11 and artifact_slug[4] == "-" else artifact_slug
|
|
38
53
|
if artifact_type == "feature":
|
|
@@ -97,7 +112,7 @@ def validate_source_contract(source: dict[str, Any]) -> list[dict[str, str]]:
|
|
|
97
112
|
)
|
|
98
113
|
|
|
99
114
|
if artifact_file:
|
|
100
|
-
artifact_file_path =
|
|
115
|
+
artifact_file_path = resolve_project_path(artifact_file)
|
|
101
116
|
if not artifact_file_path.exists():
|
|
102
117
|
issues.append(
|
|
103
118
|
{
|
|
@@ -105,7 +120,7 @@ def validate_source_contract(source: dict[str, Any]) -> list[dict[str, str]]:
|
|
|
105
120
|
"detail": artifact_file,
|
|
106
121
|
}
|
|
107
122
|
)
|
|
108
|
-
elif directory not in artifact_file_path.parents:
|
|
123
|
+
elif directory.resolve() not in artifact_file_path.resolve().parents:
|
|
109
124
|
issues.append(
|
|
110
125
|
{
|
|
111
126
|
"problem": "source-artifact-file-directory-mismatch",
|
|
@@ -174,6 +189,31 @@ def inspect_audit(path: Path) -> list[dict[str, str]]:
|
|
|
174
189
|
}
|
|
175
190
|
)
|
|
176
191
|
|
|
192
|
+
if contains_persisted_absolute_project_path(data.get("routing_file")):
|
|
193
|
+
issues.append(
|
|
194
|
+
{
|
|
195
|
+
"path": str(path),
|
|
196
|
+
"problem": "absolute-project-routing-file",
|
|
197
|
+
"detail": str(data.get("routing_file", "")),
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
if contains_persisted_absolute_project_path(data.get("cd")):
|
|
201
|
+
issues.append(
|
|
202
|
+
{
|
|
203
|
+
"path": str(path),
|
|
204
|
+
"problem": "absolute-project-cd",
|
|
205
|
+
"detail": str(data.get("cd", "")),
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
if contains_persisted_absolute_project_path(source.get("artifact_file")):
|
|
209
|
+
issues.append(
|
|
210
|
+
{
|
|
211
|
+
"path": str(path),
|
|
212
|
+
"problem": "absolute-project-artifact-file",
|
|
213
|
+
"detail": str(source.get("artifact_file", "")),
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
|
|
177
217
|
for item in validate_source_contract(source):
|
|
178
218
|
issues.append(
|
|
179
219
|
{
|
|
@@ -13,14 +13,18 @@ PACKAGE_JSON_PATH = WORKSPACE / "package.json"
|
|
|
13
13
|
|
|
14
14
|
RUNTIME_ENTRYPOINTS = [
|
|
15
15
|
"bin/codecgc.js",
|
|
16
|
+
"bin/cgc-start.js",
|
|
17
|
+
"codecgcmcp/src/codecgcmcp/cli.py",
|
|
16
18
|
"scripts/install_codecgc.py",
|
|
17
19
|
"scripts/codecgc_cli.py",
|
|
20
|
+
"scripts/codecgc_policy.py",
|
|
18
21
|
]
|
|
19
22
|
|
|
20
23
|
RUNTIME_STATIC_REQUIREMENTS = [
|
|
21
24
|
".claude/hooks/route-edit.ps1",
|
|
22
25
|
"model-routing.yaml",
|
|
23
26
|
"requirements.txt",
|
|
27
|
+
"scripts/codecgc_runtime/__init__.py",
|
|
24
28
|
"scripts/audit_codecgc_external_capabilities.py",
|
|
25
29
|
"scripts/audit_codecgc_lifecycle.py",
|
|
26
30
|
"codexmcp/pyproject.toml",
|
|
@@ -45,10 +49,25 @@ DOC_RUNTIME_PATHS = [
|
|
|
45
49
|
"codecgc/cgc-onboard/SKILL.md",
|
|
46
50
|
"codecgc/cgc-plan/SKILL.md",
|
|
47
51
|
"codecgc/cgc-review/SKILL.md",
|
|
52
|
+
"codecgc/compound/codecgc-capability-matrix.md",
|
|
53
|
+
"codecgc/reference/README.md",
|
|
48
54
|
"codecgc/reference/external-capability-registry.json",
|
|
49
55
|
"codecgc/reference/lifecycle-playbook.md",
|
|
56
|
+
"codecgc/reference/maintainer-guide.md",
|
|
57
|
+
"codecgc/reference/mcp-tool-surface.md",
|
|
50
58
|
"codecgc/reference/operation-guide.md",
|
|
59
|
+
"codecgc/reference/path-contract.md",
|
|
60
|
+
"codecgc/reference/policy-routing.md",
|
|
61
|
+
"codecgc/reference/project-structure.md",
|
|
62
|
+
"codecgc/reference/quickstart.md",
|
|
63
|
+
"codecgc/reference/onboarding.md",
|
|
64
|
+
"codecgc/reference/recovery-loop.md",
|
|
65
|
+
"codecgc/reference/real-workflow-loop.md",
|
|
51
66
|
"codecgc/reference/release-maintenance-playbook.md",
|
|
67
|
+
"codecgc/reference/troubleshooting.md",
|
|
68
|
+
"codecgc/roadmap/codecgc-release-maintenance/delivery-plan.md",
|
|
69
|
+
"codecgc/roadmap/codecgc-release-maintenance/overview.md",
|
|
70
|
+
"codecgc/roadmap/codecgc-release-maintenance/phases.md",
|
|
52
71
|
]
|
|
53
72
|
|
|
54
73
|
PLACEHOLDER_METADATA_MARKERS = (
|
|
@@ -148,19 +167,64 @@ def path_matches_package_files(path_text: str, file_rules: list[str]) -> bool:
|
|
|
148
167
|
continue
|
|
149
168
|
if normalized == normalized_rule:
|
|
150
169
|
return True
|
|
170
|
+
if (WORKSPACE / normalized_rule).is_dir() and normalized.startswith(f"{normalized_rule}/"):
|
|
171
|
+
return True
|
|
151
172
|
return False
|
|
152
173
|
|
|
153
174
|
|
|
154
175
|
def resolve_local_python_module(module_name: str) -> str:
|
|
155
176
|
relative = normalize_path_text(module_name.replace(".", "/") + ".py")
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
package_candidates = [
|
|
178
|
+
f"{package}/src/{relative}"
|
|
179
|
+
for package in ("codecgcmcp", "codexmcp", "geminimcp")
|
|
180
|
+
if module_name == package or module_name.startswith(f"{package}.")
|
|
181
|
+
]
|
|
182
|
+
candidates = [
|
|
183
|
+
*package_candidates,
|
|
184
|
+
f"scripts/{relative}",
|
|
185
|
+
f"scripts/{relative.split('/')[-1]}",
|
|
186
|
+
relative,
|
|
187
|
+
]
|
|
188
|
+
for candidate in candidates:
|
|
189
|
+
if (WORKSPACE / candidate).exists():
|
|
190
|
+
return candidate
|
|
191
|
+
package_init = f"scripts/{normalize_path_text(module_name.replace('.', '/'))}/__init__.py"
|
|
192
|
+
if (WORKSPACE / package_init).exists():
|
|
193
|
+
return package_init
|
|
194
|
+
for package in ("codecgcmcp", "codexmcp", "geminimcp"):
|
|
195
|
+
if module_name == package or module_name.startswith(f"{package}."):
|
|
196
|
+
package_init = f"{package}/src/{normalize_path_text(module_name.replace('.', '/'))}/__init__.py"
|
|
197
|
+
if (WORKSPACE / package_init).exists():
|
|
198
|
+
return package_init
|
|
199
|
+
root_package_init = f"{normalize_path_text(module_name.replace('.', '/'))}/__init__.py"
|
|
200
|
+
if (WORKSPACE / root_package_init).exists():
|
|
201
|
+
return root_package_init
|
|
159
202
|
if (WORKSPACE / relative).exists():
|
|
160
203
|
return relative
|
|
161
204
|
return ""
|
|
162
205
|
|
|
163
206
|
|
|
207
|
+
def resolve_relative_python_module(current_path: str, level: int, module_name: str | None) -> str:
|
|
208
|
+
current = Path(normalize_path_text(current_path))
|
|
209
|
+
package_dir = current.parent
|
|
210
|
+
for _ in range(max(level - 1, 0)):
|
|
211
|
+
package_dir = package_dir.parent
|
|
212
|
+
|
|
213
|
+
if module_name:
|
|
214
|
+
candidate = package_dir / normalize_path_text(module_name.replace(".", "/") + ".py")
|
|
215
|
+
if (WORKSPACE / candidate).exists():
|
|
216
|
+
return normalize_path_text(str(candidate))
|
|
217
|
+
|
|
218
|
+
package_init = package_dir / normalize_path_text(module_name.replace(".", "/")) / "__init__.py"
|
|
219
|
+
if (WORKSPACE / package_init).exists():
|
|
220
|
+
return normalize_path_text(str(package_init))
|
|
221
|
+
|
|
222
|
+
init_candidate = package_dir / "__init__.py"
|
|
223
|
+
if (WORKSPACE / init_candidate).exists():
|
|
224
|
+
return normalize_path_text(str(init_candidate))
|
|
225
|
+
return ""
|
|
226
|
+
|
|
227
|
+
|
|
164
228
|
def parse_local_python_dependencies(relative_path: str) -> tuple[list[str], list[str]]:
|
|
165
229
|
path = WORKSPACE / relative_path
|
|
166
230
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
@@ -174,7 +238,12 @@ def parse_local_python_dependencies(relative_path: str) -> tuple[list[str], list
|
|
|
174
238
|
if resolved:
|
|
175
239
|
imports.append(resolved)
|
|
176
240
|
elif isinstance(node, ast.ImportFrom):
|
|
177
|
-
if node.level != 0
|
|
241
|
+
if node.level != 0:
|
|
242
|
+
resolved_relative = resolve_relative_python_module(relative_path, node.level, node.module)
|
|
243
|
+
if resolved_relative:
|
|
244
|
+
imports.append(resolved_relative)
|
|
245
|
+
continue
|
|
246
|
+
if not node.module:
|
|
178
247
|
continue
|
|
179
248
|
resolved = resolve_local_python_module(node.module)
|
|
180
249
|
if resolved:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import json
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
@@ -8,14 +10,25 @@ from audit_codecgc_package_runtime import audit_package_runtime
|
|
|
8
10
|
from codecgc_console_io import render_summary_block
|
|
9
11
|
from install_codecgc import collect_doctor_status
|
|
10
12
|
from install_codecgc import collect_install_status
|
|
13
|
+
from install_codecgc import install_local_runtime
|
|
11
14
|
|
|
12
15
|
WORKSPACE = Path(__file__).resolve().parents[1]
|
|
16
|
+
RELEASE_PROBE_ROOT_ENV = "CODECGC_RELEASE_PROBE_ROOT"
|
|
13
17
|
|
|
14
18
|
LIFECYCLE_REQUIRED_PATHS = [
|
|
19
|
+
"codecgc/reference/README.md",
|
|
15
20
|
"codecgc/reference/lifecycle-map.md",
|
|
16
21
|
"codecgc/reference/lifecycle-playbook.md",
|
|
22
|
+
"codecgc/reference/maintainer-guide.md",
|
|
23
|
+
"codecgc/reference/mcp-tool-surface.md",
|
|
17
24
|
"codecgc/reference/operation-guide.md",
|
|
25
|
+
"codecgc/reference/onboarding.md",
|
|
26
|
+
"codecgc/reference/path-contract.md",
|
|
27
|
+
"codecgc/reference/quickstart.md",
|
|
28
|
+
"codecgc/reference/recovery-loop.md",
|
|
29
|
+
"codecgc/reference/real-workflow-loop.md",
|
|
18
30
|
"codecgc/reference/release-maintenance-playbook.md",
|
|
31
|
+
"codecgc/reference/troubleshooting.md",
|
|
19
32
|
"codecgc/reference/external-capability-registry.json",
|
|
20
33
|
"codecgc/compound/codecgc-capability-matrix.md",
|
|
21
34
|
]
|
|
@@ -83,9 +96,44 @@ def collect_deploy_signals() -> dict[str, Any]:
|
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
|
|
99
|
+
def collect_install_probe(workspace_override: str = "") -> dict[str, Any]:
|
|
100
|
+
if str(workspace_override or "").strip():
|
|
101
|
+
install_status = collect_install_status(workspace_override)
|
|
102
|
+
doctor_status = collect_doctor_status(workspace_override)
|
|
103
|
+
return {
|
|
104
|
+
"mode": "target-workspace",
|
|
105
|
+
"workspace": str(install_status.get("workspace", "")),
|
|
106
|
+
"install_result": {},
|
|
107
|
+
"install_status": install_status,
|
|
108
|
+
"doctor_status": doctor_status,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
probe_root_value = os.environ.get(RELEASE_PROBE_ROOT_ENV, "").strip()
|
|
112
|
+
probe_root = Path(probe_root_value).expanduser().resolve() if probe_root_value else None
|
|
113
|
+
if probe_root is not None:
|
|
114
|
+
probe_root.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
|
|
116
|
+
with tempfile.TemporaryDirectory(
|
|
117
|
+
prefix="codecgc-release-check-",
|
|
118
|
+
dir=str(probe_root) if probe_root is not None else None,
|
|
119
|
+
ignore_cleanup_errors=True,
|
|
120
|
+
) as temp_dir:
|
|
121
|
+
install_result = install_local_runtime(temp_dir)
|
|
122
|
+
install_status = collect_install_status(temp_dir)
|
|
123
|
+
doctor_status = collect_doctor_status(temp_dir)
|
|
124
|
+
return {
|
|
125
|
+
"mode": "temporary-project-install",
|
|
126
|
+
"workspace": temp_dir,
|
|
127
|
+
"install_result": install_result,
|
|
128
|
+
"install_status": install_status,
|
|
129
|
+
"doctor_status": doctor_status,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
86
133
|
def audit_release_readiness(workspace_override: str = "") -> dict[str, Any]:
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
install_probe = collect_install_probe(workspace_override)
|
|
135
|
+
install_status = install_probe["install_status"]
|
|
136
|
+
doctor_status = install_probe["doctor_status"]
|
|
89
137
|
package_audit = audit_package_runtime()
|
|
90
138
|
external_audit = audit_external_capabilities(workspace_override)
|
|
91
139
|
lifecycle_docs = audit_document_set(LIFECYCLE_REQUIRED_PATHS)
|
|
@@ -119,12 +167,14 @@ def audit_release_readiness(workspace_override: str = "") -> dict[str, Any]:
|
|
|
119
167
|
return {
|
|
120
168
|
"success": ready,
|
|
121
169
|
"mode": "release-readiness-audit",
|
|
122
|
-
"workspace": str(
|
|
170
|
+
"workspace": str(WORKSPACE),
|
|
123
171
|
"summary": {
|
|
124
172
|
"ready": ready,
|
|
125
173
|
"scope": "release / maintenance / ops 就绪状态",
|
|
126
174
|
"human_summary": human_summary,
|
|
127
175
|
"recommended_next_action": recommended_next_action,
|
|
176
|
+
"install_probe_mode": install_probe["mode"],
|
|
177
|
+
"install_probe_workspace": install_probe["workspace"],
|
|
128
178
|
"install_ready": install_ready,
|
|
129
179
|
"doctor_ready": doctor_ready,
|
|
130
180
|
"package_ready": package_ready,
|
|
@@ -134,6 +184,7 @@ def audit_release_readiness(workspace_override: str = "") -> dict[str, Any]:
|
|
|
134
184
|
"deploy_signals_detected": deploy_signals["deploy_signals_detected"],
|
|
135
185
|
"deploy_readiness_stage": deploy_signals["deploy_readiness_stage"],
|
|
136
186
|
},
|
|
187
|
+
"install_probe": install_probe,
|
|
137
188
|
"install_status": install_status,
|
|
138
189
|
"doctor_status": doctor_status,
|
|
139
190
|
"package_audit": package_audit,
|
|
@@ -152,6 +203,7 @@ def build_summary(result: dict[str, Any]) -> str:
|
|
|
152
203
|
lines = [
|
|
153
204
|
f"- 工作区: {result.get('workspace', '')}",
|
|
154
205
|
f"- 范围: {summary.get('scope', '')}",
|
|
206
|
+
f"- 安装探针: {summary.get('install_probe_mode', '')} ({summary.get('install_probe_workspace', '')})",
|
|
155
207
|
f"- 就绪: {'是' if summary.get('ready') else '否'}",
|
|
156
208
|
f"- 摘要: {summary.get('human_summary', '')}",
|
|
157
209
|
f"- 项目级集成: {'就绪' if summary.get('install_ready') else '未就绪'}",
|
|
@@ -9,6 +9,8 @@ from typing import Any
|
|
|
9
9
|
from codecgc_artifact_roots import FIXTURE_ROOT
|
|
10
10
|
from codecgc_artifact_roots import PRODUCT_ROOT
|
|
11
11
|
from codecgc_console_io import render_summary_block
|
|
12
|
+
from codecgc_path_contract import normalize_persisted_project_path
|
|
13
|
+
from codecgc_path_contract import resolve_project_path
|
|
12
14
|
from codecgc_runtime_paths import PROJECT_ROOT
|
|
13
15
|
from route_codecgc_workflow import attach_route_summary
|
|
14
16
|
from route_codecgc_workflow import route_feature
|
|
@@ -84,7 +86,7 @@ def locate_summary_file(flow: str, directory: Path) -> str:
|
|
|
84
86
|
matches = sorted(directory.glob(f"*{suffix}"))
|
|
85
87
|
if not matches:
|
|
86
88
|
return ""
|
|
87
|
-
return
|
|
89
|
+
return normalize_persisted_project_path(matches[0])
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
def collect_history_record(flow: str, artifact_class: str, directory: Path) -> dict[str, Any]:
|
|
@@ -97,7 +99,8 @@ def collect_history_record(flow: str, artifact_class: str, directory: Path) -> d
|
|
|
97
99
|
audit_path = str(route.get("audit_path", "")).strip()
|
|
98
100
|
audit_timestamp = ""
|
|
99
101
|
if audit_path:
|
|
100
|
-
|
|
102
|
+
resolved_audit_path = resolve_project_path(audit_path)
|
|
103
|
+
audit_time = parse_iso_like_timestamp(resolved_audit_path.stat().st_mtime_ns and datetime.fromtimestamp(resolved_audit_path.stat().st_mtime).isoformat())
|
|
101
104
|
if audit_time:
|
|
102
105
|
audit_timestamp = audit_time.isoformat()
|
|
103
106
|
|
|
@@ -106,7 +109,7 @@ def collect_history_record(flow: str, artifact_class: str, directory: Path) -> d
|
|
|
106
109
|
"artifact_class": artifact_class,
|
|
107
110
|
"slug": slug,
|
|
108
111
|
"created": extract_created_from_slug(slug),
|
|
109
|
-
"directory":
|
|
112
|
+
"directory": normalize_persisted_project_path(directory),
|
|
110
113
|
"workflow_state": workflow_state,
|
|
111
114
|
"state_label": STATE_LABELS.get(workflow_state, workflow_state or "未知"),
|
|
112
115
|
"recommended_command": str(route.get("recommended_command", "")).strip(),
|
|
@@ -116,10 +119,10 @@ def collect_history_record(flow: str, artifact_class: str, directory: Path) -> d
|
|
|
116
119
|
"current_task_id": str(summary.get("current_task_id", "")).strip(),
|
|
117
120
|
"review_decision": str(summary.get("review_decision", "")).strip(),
|
|
118
121
|
"is_closed": bool(summary.get("is_closed")),
|
|
119
|
-
"audit_path": audit_path,
|
|
122
|
+
"audit_path": normalize_persisted_project_path(audit_path) if audit_path else "",
|
|
120
123
|
"audit_timestamp": audit_timestamp,
|
|
121
124
|
"summary_file": locate_summary_file(flow, directory),
|
|
122
|
-
"root":
|
|
125
|
+
"root": normalize_persisted_project_path(artifact_root(artifact_class)),
|
|
123
126
|
}
|
|
124
127
|
if current_step:
|
|
125
128
|
record["current_step"] = {
|