GFramework/scripts/generate-ai-environment.py
GeWuYou 2001eddbff refactor(scripts): 重构AI环境生成脚本以优化工具选择逻辑
- 移除shell脚本中的冗余命令执行逻辑
- 添加select_tool函数统一处理工具偏好和回退逻辑
- 使用新函数重构搜索、JSON处理、脚本编写等工具选择
- 更新时间戳以反映代码变更后的重新生成
- 简化构建工具配置的回退策略
2026-03-21 12:49:51 +08:00

237 lines
7.6 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT_DIR = Path(__file__).resolve().parent.parent
RAW_PATH = ROOT_DIR / ".ai" / "environment" / "tools.raw.yaml"
AI_PATH = ROOT_DIR / ".ai" / "environment" / "tools.ai.yaml"
def parse_scalar(value: str) -> Any:
if value == "true":
return True
if value == "false":
return False
if value.startswith('"') and value.endswith('"'):
return value[1:-1]
return value
def parse_simple_yaml(path: Path) -> dict[str, Any]:
root: dict[str, Any] = {}
stack: list[tuple[int, dict[str, Any]]] = [(-1, root)]
for raw_line in path.read_text(encoding="utf-8").splitlines():
if not raw_line.strip():
continue
if raw_line.lstrip().startswith("#"):
continue
indent = len(raw_line) - len(raw_line.lstrip(" "))
key, _, tail = raw_line.strip().partition(":")
while len(stack) > 1 and indent <= stack[-1][0]:
stack.pop()
current = stack[-1][1]
value = tail.strip()
if value == "":
child: dict[str, Any] = {}
current[key] = child
stack.append((indent, child))
continue
current[key] = parse_scalar(value)
return root
def bool_value(data: dict[str, Any], *keys: str) -> bool:
current: Any = data
for key in keys:
current = current[key]
return bool(current)
def string_value(data: dict[str, Any], *keys: str) -> str:
current: Any = data
for key in keys:
current = current[key]
return str(current)
def choose(preferred: str | None, fallback: str | None) -> str:
if preferred:
return preferred
return fallback or "unavailable"
def available_tool(raw: dict[str, Any], section: str, name: str) -> bool:
return bool_value(raw, section, name, "installed")
def select_tool(
use_for: str,
preferred: str | None,
fallback: str | None,
) -> dict[str, str]:
return {
"preferred": choose(preferred, fallback),
"fallback": fallback or "unavailable",
"use_for": use_for,
}
def build_ai_inventory(raw: dict[str, Any]) -> dict[str, Any]:
has_python = available_tool(raw, "required_runtimes", "python3")
has_node = available_tool(raw, "required_runtimes", "node")
has_bun = available_tool(raw, "required_runtimes", "bun")
has_dotnet = available_tool(raw, "required_runtimes", "dotnet")
has_rg = available_tool(raw, "required_tools", "rg")
has_jq = available_tool(raw, "required_tools", "jq")
has_bash = available_tool(raw, "required_tools", "bash")
has_docker = available_tool(raw, "project_tools", "docker")
search = select_tool(
use_for="Repository text search.",
preferred="rg" if has_rg else None,
fallback="grep",
)
json = select_tool(
use_for="Inspecting or transforming JSON command output.",
preferred="jq" if has_jq else None,
fallback="python3" if has_python else None,
)
scripting = select_tool(
use_for="Non-trivial local automation and helper scripts.",
preferred="python3" if has_python else None,
fallback="bash" if has_bash else None,
)
shell = select_tool(
use_for="Repository shell scripts and command execution.",
preferred="bash" if has_bash else None,
fallback="sh",
)
docs = select_tool(
use_for="Installing and previewing the docs site.",
preferred="bun" if has_bun else None,
fallback="npm" if has_node else None,
)
build = select_tool(
use_for="Build, test, restore, and solution validation.",
preferred="dotnet" if has_dotnet else None,
fallback=None,
)
if bool_value(raw, "platform", "wsl"):
platform_family = "wsl-linux"
else:
platform_family = string_value(raw, "platform", "os").lower()
return {
"schema_version": 1,
"generated_at_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"generated_from": ".ai/environment/tools.raw.yaml",
"generator": "scripts/generate-ai-environment.py",
"platform": {
"family": platform_family,
"os": string_value(raw, "platform", "os"),
"distro": string_value(raw, "platform", "distro"),
"shell": string_value(raw, "platform", "shell"),
},
"capabilities": {
"dotnet": has_dotnet,
"python": has_python,
"node": has_node,
"bun": has_bun,
"docker": has_docker,
"fast_search": has_rg,
"json_cli": has_jq,
},
"tool_selection": {
"search": search,
"json": json,
"shell": shell,
"scripting": scripting,
"docs_package_manager": docs,
"build_and_test": build,
},
"python": {
"available": has_python,
"helper_packages": {
"requests": bool_value(raw, "python_packages", "requests", "installed"),
"rich": bool_value(raw, "python_packages", "rich", "installed"),
"openai": bool_value(raw, "python_packages", "openai", "installed"),
"tiktoken": bool_value(raw, "python_packages", "tiktoken", "installed"),
"pydantic": bool_value(raw, "python_packages", "pydantic", "installed"),
"pytest": bool_value(raw, "python_packages", "pytest", "installed"),
},
},
"preferences": {
"prefer_project_listed_tools": True,
"prefer_python_for_non_trivial_automation": has_python,
"avoid_unlisted_system_tools": True,
},
"rules": [
"Use rg instead of grep for repository search when rg is available.",
"Use jq for JSON inspection; fall back to python3 if jq is unavailable.",
"Prefer python3 over complex bash for non-trivial scripting when python3 is available.",
"Use bun for docs preview workflows when bun is available; otherwise fall back to npm.",
"Use dotnet for repository build and test workflows.",
"Do not assume unrelated system tools are part of the supported project environment.",
],
}
def emit_yaml(value: Any, indent: int = 0) -> list[str]:
prefix = " " * indent
if isinstance(value, dict):
lines: list[str] = []
for key, nested in value.items():
if isinstance(nested, (dict, list)):
lines.append(f"{prefix}{key}:")
lines.extend(emit_yaml(nested, indent + 2))
else:
lines.append(f"{prefix}{key}: {format_scalar(nested)}")
return lines
if isinstance(value, list):
lines = []
for item in value:
if isinstance(item, (dict, list)):
lines.append(f"{prefix}-")
lines.extend(emit_yaml(item, indent + 2))
else:
lines.append(f"{prefix}- {format_scalar(item)}")
return lines
return [f"{prefix}{format_scalar(value)}"]
def format_scalar(value: Any) -> str:
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, int):
return str(value)
text = str(value).replace('"', '\\"')
return f'"{text}"'
def main() -> None:
raw = parse_simple_yaml(RAW_PATH)
ai_inventory = build_ai_inventory(raw)
AI_PATH.parent.mkdir(parents=True, exist_ok=True)
AI_PATH.write_text("\n".join(emit_yaml(ai_inventory)) + "\n", encoding="utf-8")
print(f"Wrote {AI_PATH}")
if __name__ == "__main__":
main()